+38
.github/workflows/sync-tangled.yaml
+38
.github/workflows/sync-tangled.yaml
···
···
1
+
name: sync-tangled
2
+
3
+
on:
4
+
push:
5
+
6
+
jobs:
7
+
sync:
8
+
name: ${{ matrix.name }}
9
+
runs-on: ubuntu-latest
10
+
steps:
11
+
- uses: actions/checkout@v4.1.7
12
+
with:
13
+
fetch-depth: 0
14
+
ref: ${{ github.event.pull_request.head.sha }}
15
+
- name: sync tangled
16
+
env:
17
+
TANGLED_SSH_KEY: ${{ secrets.TANGLED_SSH_KEY }}
18
+
run: |
19
+
set -euo pipefail
20
+
# Turn off strict SSH key checking
21
+
mkdir -p ~/.ssh
22
+
echo "Host *
23
+
StrictHostKeyChecking no
24
+
UserKnownHostsFile=/dev/null" > ~/.ssh/config
25
+
26
+
# Write SSH key to disk
27
+
echo "$TANGLED_SSH_KEY" > ~/.ssh/tangled_key
28
+
chmod 600 ~/.ssh/tangled_key
29
+
30
+
# Configure SSH to use the key for tangled.sh
31
+
echo "Host tangled.sh
32
+
IdentityFile ~/.ssh/tangled_key" >> ~/.ssh/config
33
+
34
+
chmod 600 ~/.ssh/config
35
+
36
+
git remote add tangled git@tangled.sh:noob.quest/atproto-migrator
37
+
git push -f --all tangled
38
+
git push -f --tags tangled
+1
.nvmrc
+1
.nvmrc
···
···
1
+
v24.1.0
+4
-2
README.md
+4
-2
README.md
···
1
# atproto-migrator
2
-
basic (NON-FUNCTIONAL) web application that allows for an user to migrate their bluesky/at protocol account to another PDS without using command line tools.
3
4
[live version available here](https://atproto-migrator.pages.dev)
5
6
-
## install
7
8
```bash
9
npm install
···
15
```bash
16
npm run build
17
```
18
19
open the `dist` directory and you're set
···
1
# atproto-migrator
2
+
basic (NON-FUNCTIONAL) web application that allows for an user to migrate their bluesky/at protocol account to another PDS without using command line tools, as well as some other repo stuff
3
4
[live version available here](https://atproto-migrator.pages.dev)
5
6
+
## run for developing
7
8
```bash
9
npm install
···
15
```bash
16
npm run build
17
```
18
+
19
+
if you type ```ANALYZE=1``` you will receive a bundle analysis, useful for optimizing
20
21
open the `dist` directory and you're set
-28
eslint.config.js
-28
eslint.config.js
···
1
-
import js from '@eslint/js'
2
-
import globals from 'globals'
3
-
import reactHooks from 'eslint-plugin-react-hooks'
4
-
import reactRefresh from 'eslint-plugin-react-refresh'
5
-
import tseslint from 'typescript-eslint'
6
-
7
-
export default tseslint.config(
8
-
{ ignores: ['dist'] },
9
-
{
10
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
-
files: ['**/*.{ts,tsx}'],
12
-
languageOptions: {
13
-
ecmaVersion: 2020,
14
-
globals: globals.browser,
15
-
},
16
-
plugins: {
17
-
'react-hooks': reactHooks,
18
-
'react-refresh': reactRefresh,
19
-
},
20
-
rules: {
21
-
...reactHooks.configs.recommended.rules,
22
-
'react-refresh/only-export-components': [
23
-
'warn',
24
-
{ allowConstantExport: true },
25
-
],
26
-
},
27
-
},
28
-
)
···
+40
eslint.config.ts
+40
eslint.config.ts
···
···
1
+
import { FlatCompat } from '@eslint/eslintrc'
2
+
import js from '@eslint/js'
3
+
import * as path from 'node:path'
4
+
import { fileURLToPath } from 'node:url'
5
+
import tseslint from 'typescript-eslint'
6
+
7
+
const __filename = fileURLToPath(import.meta.url)
8
+
const __dirname = path.dirname(__filename)
9
+
10
+
const compat = new FlatCompat({
11
+
baseDirectory: __dirname,
12
+
recommendedConfig: js.configs.recommended
13
+
})
14
+
15
+
export default tseslint.config(
16
+
...compat.config({
17
+
root: true,
18
+
extends: [
19
+
'eslint:recommended',
20
+
'plugin:@typescript-eslint/recommended',
21
+
'plugin:react-hooks/recommended'
22
+
],
23
+
parser: '@typescript-eslint/parser',
24
+
parserOptions: {
25
+
project: ['./tsconfig.app.json'],
26
+
tsconfigRootDir: __dirname,
27
+
},
28
+
plugins: ['@typescript-eslint', 'react-refresh'],
29
+
rules: {
30
+
'react-refresh/only-export-components': [
31
+
'warn',
32
+
{ allowConstantExport: true }
33
+
],
34
+
'@typescript-eslint/no-deprecated': 'error'
35
+
}
36
+
}),
37
+
{
38
+
ignores: ['dist/**/*']
39
+
}
40
+
)
+2
-2
index.html
+2
-2
index.html
···
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8" />
5
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
<title>ATproto Migrator</title>
8
</head>
···
41
<div id="root"></div>
42
<script type="module" src="/src/main.tsx"></script>
43
</body>
44
-
</html>
···
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8" />
5
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
<title>ATproto Migrator</title>
8
</head>
···
41
<div id="root"></div>
42
<script type="module" src="/src/main.tsx"></script>
43
</body>
44
+
</html>
+893
-1309
package-lock.json
+893
-1309
package-lock.json
···
1
{
2
"name": "atproto-migration",
3
-
"version": "0.0.0",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "atproto-migration",
9
-
"version": "0.0.0",
10
"dependencies": {
11
"@atproto/api": "^0.15.5",
12
"react": "^19.0.0",
13
"react-dom": "^19.0.0",
14
-
"react-router-dom": "^7.5.3"
15
},
16
"devDependencies": {
17
"@eslint/js": "^9.22.0",
18
-
"@types/react": "^19.0.10",
19
-
"@types/react-dom": "^19.0.4",
20
"@vitejs/plugin-react": "^4.3.4",
21
"eslint": "^9.22.0",
22
"eslint-plugin-react-hooks": "^5.2.0",
23
"eslint-plugin-react-refresh": "^0.4.19",
24
"globals": "^16.0.0",
25
"typescript": "~5.7.2",
26
"typescript-eslint": "^8.26.1",
27
"vite": "^6.3.1"
···
42
}
43
},
44
"node_modules/@atproto/api": {
45
-
"version": "0.15.5",
46
-
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.5.tgz",
47
-
"integrity": "sha512-GiKOrjSXMm8OSpc+pfjFTBYQGX62jmorECkTx2VZbS6KtFKFY0cRQAI+JnQoOLF/8TvzpaAZB7+it73uIqDM7A==",
48
"license": "MIT",
49
"dependencies": {
50
-
"@atproto/common-web": "^0.4.1",
51
-
"@atproto/lexicon": "^0.4.10",
52
"@atproto/syntax": "^0.4.0",
53
-
"@atproto/xrpc": "^0.6.12",
54
"await-lock": "^2.2.2",
55
"multiformats": "^9.9.0",
56
"tlds": "^1.234.0",
57
"zod": "^3.23.8"
58
}
59
},
60
"node_modules/@atproto/common-web": {
61
-
"version": "0.4.1",
62
-
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.1.tgz",
63
-
"integrity": "sha512-Ghh+djHYMAUCktLKwr2IuGgtjcwSWGudp+K7+N7KBA9pDDloOXUEY8Agjc5SHSo9B1QIEFkegClU5n+apn2e0w==",
64
"license": "MIT",
65
"dependencies": {
66
"graphemer": "^1.4.0",
···
69
"zod": "^3.23.8"
70
}
71
},
72
"node_modules/@atproto/lexicon": {
73
-
"version": "0.4.10",
74
-
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.10.tgz",
75
-
"integrity": "sha512-uDbP20vetBgtXPuxoyRcvOGBt2gNe1dFc9yYKcb6jWmXfseHiGTnIlORJOLBXIT2Pz15Eap4fLxAu6zFAykD5A==",
76
"license": "MIT",
77
"dependencies": {
78
-
"@atproto/common-web": "^0.4.1",
79
"@atproto/syntax": "^0.4.0",
80
"iso-datestring-validator": "^2.2.2",
81
"multiformats": "^9.9.0",
82
"zod": "^3.23.8"
83
}
84
},
85
"node_modules/@atproto/syntax": {
86
"version": "0.4.0",
87
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz",
···
89
"license": "MIT"
90
},
91
"node_modules/@atproto/xrpc": {
92
-
"version": "0.6.12",
93
-
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.12.tgz",
94
-
"integrity": "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w==",
95
"license": "MIT",
96
"dependencies": {
97
-
"@atproto/lexicon": "^0.4.10",
98
"zod": "^3.23.8"
99
}
100
},
···
114
}
115
},
116
"node_modules/@babel/compat-data": {
117
-
"version": "7.27.1",
118
-
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz",
119
-
"integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==",
120
"dev": true,
121
"license": "MIT",
122
"engines": {
···
124
}
125
},
126
"node_modules/@babel/core": {
127
-
"version": "7.27.1",
128
-
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
129
-
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
130
"dev": true,
131
"license": "MIT",
132
"dependencies": {
133
"@ampproject/remapping": "^2.2.0",
134
"@babel/code-frame": "^7.27.1",
135
-
"@babel/generator": "^7.27.1",
136
-
"@babel/helper-compilation-targets": "^7.27.1",
137
-
"@babel/helper-module-transforms": "^7.27.1",
138
-
"@babel/helpers": "^7.27.1",
139
-
"@babel/parser": "^7.27.1",
140
-
"@babel/template": "^7.27.1",
141
-
"@babel/traverse": "^7.27.1",
142
-
"@babel/types": "^7.27.1",
143
"convert-source-map": "^2.0.0",
144
"debug": "^4.1.0",
145
"gensync": "^1.0.0-beta.2",
···
154
"url": "https://opencollective.com/babel"
155
}
156
},
157
"node_modules/@babel/generator": {
158
-
"version": "7.27.1",
159
-
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
160
-
"integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
161
"dev": true,
162
"license": "MIT",
163
"dependencies": {
164
-
"@babel/parser": "^7.27.1",
165
-
"@babel/types": "^7.27.1",
166
"@jridgewell/gen-mapping": "^0.3.5",
167
"@jridgewell/trace-mapping": "^0.3.25",
168
"jsesc": "^3.0.2"
···
172
}
173
},
174
"node_modules/@babel/helper-compilation-targets": {
175
-
"version": "7.27.1",
176
-
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz",
177
-
"integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==",
178
"dev": true,
179
"license": "MIT",
180
"dependencies": {
181
-
"@babel/compat-data": "^7.27.1",
182
"@babel/helper-validator-option": "^7.27.1",
183
"browserslist": "^4.24.0",
184
"lru-cache": "^5.1.1",
···
188
"node": ">=6.9.0"
189
}
190
},
191
"node_modules/@babel/helper-module-imports": {
192
"version": "7.27.1",
193
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
···
203
}
204
},
205
"node_modules/@babel/helper-module-transforms": {
206
-
"version": "7.27.1",
207
-
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
208
-
"integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
209
"dev": true,
210
"license": "MIT",
211
"dependencies": {
212
"@babel/helper-module-imports": "^7.27.1",
213
"@babel/helper-validator-identifier": "^7.27.1",
214
-
"@babel/traverse": "^7.27.1"
215
},
216
"engines": {
217
"node": ">=6.9.0"
···
261
}
262
},
263
"node_modules/@babel/helpers": {
264
-
"version": "7.27.1",
265
-
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
266
-
"integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
267
"dev": true,
268
"license": "MIT",
269
"dependencies": {
270
-
"@babel/template": "^7.27.1",
271
-
"@babel/types": "^7.27.1"
272
},
273
"engines": {
274
"node": ">=6.9.0"
275
}
276
},
277
"node_modules/@babel/parser": {
278
-
"version": "7.27.1",
279
-
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz",
280
-
"integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==",
281
"dev": true,
282
"license": "MIT",
283
"dependencies": {
284
-
"@babel/types": "^7.27.1"
285
},
286
"bin": {
287
"parser": "bin/babel-parser.js"
···
323
}
324
},
325
"node_modules/@babel/template": {
326
-
"version": "7.27.1",
327
-
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz",
328
-
"integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==",
329
"dev": true,
330
"license": "MIT",
331
"dependencies": {
332
"@babel/code-frame": "^7.27.1",
333
-
"@babel/parser": "^7.27.1",
334
"@babel/types": "^7.27.1"
335
},
336
"engines": {
···
338
}
339
},
340
"node_modules/@babel/traverse": {
341
-
"version": "7.27.1",
342
-
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz",
343
-
"integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
344
"dev": true,
345
"license": "MIT",
346
"dependencies": {
347
"@babel/code-frame": "^7.27.1",
348
-
"@babel/generator": "^7.27.1",
349
-
"@babel/parser": "^7.27.1",
350
-
"@babel/template": "^7.27.1",
351
-
"@babel/types": "^7.27.1",
352
"debug": "^4.3.1",
353
"globals": "^11.1.0"
354
},
···
367
}
368
},
369
"node_modules/@babel/types": {
370
-
"version": "7.27.1",
371
-
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
372
-
"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
373
"dev": true,
374
"license": "MIT",
375
"dependencies": {
···
381
}
382
},
383
"node_modules/@esbuild/aix-ppc64": {
384
-
"version": "0.25.3",
385
-
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
386
-
"integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==",
387
"cpu": [
388
"ppc64"
389
],
···
398
}
399
},
400
"node_modules/@esbuild/android-arm": {
401
-
"version": "0.25.3",
402
-
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz",
403
-
"integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==",
404
"cpu": [
405
"arm"
406
],
···
415
}
416
},
417
"node_modules/@esbuild/android-arm64": {
418
-
"version": "0.25.3",
419
-
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz",
420
-
"integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==",
421
"cpu": [
422
"arm64"
423
],
···
432
}
433
},
434
"node_modules/@esbuild/android-x64": {
435
-
"version": "0.25.3",
436
-
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz",
437
-
"integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==",
438
"cpu": [
439
"x64"
440
],
···
449
}
450
},
451
"node_modules/@esbuild/darwin-arm64": {
452
-
"version": "0.25.3",
453
-
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz",
454
-
"integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==",
455
"cpu": [
456
"arm64"
457
],
···
466
}
467
},
468
"node_modules/@esbuild/darwin-x64": {
469
-
"version": "0.25.3",
470
-
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz",
471
-
"integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==",
472
"cpu": [
473
"x64"
474
],
···
483
}
484
},
485
"node_modules/@esbuild/freebsd-arm64": {
486
-
"version": "0.25.3",
487
-
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz",
488
-
"integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==",
489
"cpu": [
490
"arm64"
491
],
···
500
}
501
},
502
"node_modules/@esbuild/freebsd-x64": {
503
-
"version": "0.25.3",
504
-
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz",
505
-
"integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==",
506
"cpu": [
507
"x64"
508
],
···
517
}
518
},
519
"node_modules/@esbuild/linux-arm": {
520
-
"version": "0.25.3",
521
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz",
522
-
"integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==",
523
"cpu": [
524
"arm"
525
],
···
534
}
535
},
536
"node_modules/@esbuild/linux-arm64": {
537
-
"version": "0.25.3",
538
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz",
539
-
"integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==",
540
"cpu": [
541
"arm64"
542
],
···
551
}
552
},
553
"node_modules/@esbuild/linux-ia32": {
554
-
"version": "0.25.3",
555
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz",
556
-
"integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==",
557
"cpu": [
558
"ia32"
559
],
···
568
}
569
},
570
"node_modules/@esbuild/linux-loong64": {
571
-
"version": "0.25.3",
572
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz",
573
-
"integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==",
574
"cpu": [
575
"loong64"
576
],
···
585
}
586
},
587
"node_modules/@esbuild/linux-mips64el": {
588
-
"version": "0.25.3",
589
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz",
590
-
"integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==",
591
"cpu": [
592
"mips64el"
593
],
···
602
}
603
},
604
"node_modules/@esbuild/linux-ppc64": {
605
-
"version": "0.25.3",
606
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz",
607
-
"integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==",
608
"cpu": [
609
"ppc64"
610
],
···
619
}
620
},
621
"node_modules/@esbuild/linux-riscv64": {
622
-
"version": "0.25.3",
623
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz",
624
-
"integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==",
625
"cpu": [
626
"riscv64"
627
],
···
636
}
637
},
638
"node_modules/@esbuild/linux-s390x": {
639
-
"version": "0.25.3",
640
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz",
641
-
"integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==",
642
"cpu": [
643
"s390x"
644
],
···
653
}
654
},
655
"node_modules/@esbuild/linux-x64": {
656
-
"version": "0.25.3",
657
-
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz",
658
-
"integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==",
659
"cpu": [
660
"x64"
661
],
···
670
}
671
},
672
"node_modules/@esbuild/netbsd-arm64": {
673
-
"version": "0.25.3",
674
-
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz",
675
-
"integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==",
676
"cpu": [
677
"arm64"
678
],
···
687
}
688
},
689
"node_modules/@esbuild/netbsd-x64": {
690
-
"version": "0.25.3",
691
-
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz",
692
-
"integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==",
693
"cpu": [
694
"x64"
695
],
···
704
}
705
},
706
"node_modules/@esbuild/openbsd-arm64": {
707
-
"version": "0.25.3",
708
-
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz",
709
-
"integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==",
710
"cpu": [
711
"arm64"
712
],
···
721
}
722
},
723
"node_modules/@esbuild/openbsd-x64": {
724
-
"version": "0.25.3",
725
-
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz",
726
-
"integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==",
727
"cpu": [
728
"x64"
729
],
···
738
}
739
},
740
"node_modules/@esbuild/sunos-x64": {
741
-
"version": "0.25.3",
742
-
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz",
743
-
"integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==",
744
"cpu": [
745
"x64"
746
],
···
755
}
756
},
757
"node_modules/@esbuild/win32-arm64": {
758
-
"version": "0.25.3",
759
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz",
760
-
"integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==",
761
"cpu": [
762
"arm64"
763
],
···
772
}
773
},
774
"node_modules/@esbuild/win32-ia32": {
775
-
"version": "0.25.3",
776
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz",
777
-
"integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==",
778
"cpu": [
779
"ia32"
780
],
···
789
}
790
},
791
"node_modules/@esbuild/win32-x64": {
792
-
"version": "0.25.3",
793
-
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz",
794
-
"integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==",
795
"cpu": [
796
"x64"
797
],
···
824
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
825
}
826
},
827
-
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
828
-
"version": "3.4.3",
829
-
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
830
-
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
831
-
"dev": true,
832
-
"license": "Apache-2.0",
833
-
"engines": {
834
-
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
835
-
},
836
-
"funding": {
837
-
"url": "https://opencollective.com/eslint"
838
-
}
839
-
},
840
"node_modules/@eslint-community/regexpp": {
841
"version": "4.12.1",
842
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
···
873
}
874
},
875
"node_modules/@eslint/core": {
876
-
"version": "0.13.0",
877
-
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
878
-
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
879
"dev": true,
880
"license": "Apache-2.0",
881
"dependencies": {
···
923
}
924
},
925
"node_modules/@eslint/js": {
926
-
"version": "9.26.0",
927
-
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
928
-
"integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==",
929
"dev": true,
930
"license": "MIT",
931
"engines": {
932
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
933
}
934
},
935
"node_modules/@eslint/object-schema": {
···
943
}
944
},
945
"node_modules/@eslint/plugin-kit": {
946
-
"version": "0.2.8",
947
-
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
948
-
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
949
"dev": true,
950
"license": "Apache-2.0",
951
"dependencies": {
952
-
"@eslint/core": "^0.13.0",
953
"levn": "^0.4.1"
954
},
955
"engines": {
···
1009
}
1010
},
1011
"node_modules/@humanwhocodes/retry": {
1012
-
"version": "0.4.2",
1013
-
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
1014
-
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
1015
"dev": true,
1016
"license": "Apache-2.0",
1017
"engines": {
···
1057
"node": ">=6.0.0"
1058
}
1059
},
1060
"node_modules/@jridgewell/sourcemap-codec": {
1061
"version": "1.5.0",
1062
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
···
1075
"@jridgewell/sourcemap-codec": "^1.4.14"
1076
}
1077
},
1078
-
"node_modules/@modelcontextprotocol/sdk": {
1079
-
"version": "1.11.0",
1080
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz",
1081
-
"integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==",
1082
-
"dev": true,
1083
"license": "MIT",
1084
"dependencies": {
1085
-
"content-type": "^1.0.5",
1086
-
"cors": "^2.8.5",
1087
-
"cross-spawn": "^7.0.3",
1088
-
"eventsource": "^3.0.2",
1089
-
"express": "^5.0.1",
1090
-
"express-rate-limit": "^7.5.0",
1091
-
"pkce-challenge": "^5.0.0",
1092
-
"raw-body": "^3.0.0",
1093
-
"zod": "^3.23.8",
1094
-
"zod-to-json-schema": "^3.24.1"
1095
},
1096
"engines": {
1097
-
"node": ">=18"
1098
}
1099
},
1100
"node_modules/@nodelib/fs.scandir": {
···
1135
"node": ">= 8"
1136
}
1137
},
1138
"node_modules/@rollup/rollup-android-arm-eabi": {
1139
-
"version": "4.40.1",
1140
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
1141
-
"integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
1142
"cpu": [
1143
"arm"
1144
],
···
1150
]
1151
},
1152
"node_modules/@rollup/rollup-android-arm64": {
1153
-
"version": "4.40.1",
1154
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
1155
-
"integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
1156
"cpu": [
1157
"arm64"
1158
],
···
1164
]
1165
},
1166
"node_modules/@rollup/rollup-darwin-arm64": {
1167
-
"version": "4.40.1",
1168
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
1169
-
"integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
1170
"cpu": [
1171
"arm64"
1172
],
···
1178
]
1179
},
1180
"node_modules/@rollup/rollup-darwin-x64": {
1181
-
"version": "4.40.1",
1182
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
1183
-
"integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
1184
"cpu": [
1185
"x64"
1186
],
···
1192
]
1193
},
1194
"node_modules/@rollup/rollup-freebsd-arm64": {
1195
-
"version": "4.40.1",
1196
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
1197
-
"integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
1198
"cpu": [
1199
"arm64"
1200
],
···
1206
]
1207
},
1208
"node_modules/@rollup/rollup-freebsd-x64": {
1209
-
"version": "4.40.1",
1210
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
1211
-
"integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
1212
"cpu": [
1213
"x64"
1214
],
···
1220
]
1221
},
1222
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1223
-
"version": "4.40.1",
1224
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
1225
-
"integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
1226
"cpu": [
1227
"arm"
1228
],
···
1234
]
1235
},
1236
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
1237
-
"version": "4.40.1",
1238
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
1239
-
"integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
1240
"cpu": [
1241
"arm"
1242
],
···
1248
]
1249
},
1250
"node_modules/@rollup/rollup-linux-arm64-gnu": {
1251
-
"version": "4.40.1",
1252
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
1253
-
"integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
1254
"cpu": [
1255
"arm64"
1256
],
···
1262
]
1263
},
1264
"node_modules/@rollup/rollup-linux-arm64-musl": {
1265
-
"version": "4.40.1",
1266
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
1267
-
"integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
1268
"cpu": [
1269
"arm64"
1270
],
···
1276
]
1277
},
1278
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
1279
-
"version": "4.40.1",
1280
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
1281
-
"integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
1282
"cpu": [
1283
"loong64"
1284
],
···
1290
]
1291
},
1292
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
1293
-
"version": "4.40.1",
1294
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
1295
-
"integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
1296
"cpu": [
1297
"ppc64"
1298
],
···
1304
]
1305
},
1306
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
1307
-
"version": "4.40.1",
1308
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
1309
-
"integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
1310
"cpu": [
1311
"riscv64"
1312
],
···
1318
]
1319
},
1320
"node_modules/@rollup/rollup-linux-riscv64-musl": {
1321
-
"version": "4.40.1",
1322
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
1323
-
"integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
1324
"cpu": [
1325
"riscv64"
1326
],
···
1332
]
1333
},
1334
"node_modules/@rollup/rollup-linux-s390x-gnu": {
1335
-
"version": "4.40.1",
1336
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
1337
-
"integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
1338
"cpu": [
1339
"s390x"
1340
],
···
1346
]
1347
},
1348
"node_modules/@rollup/rollup-linux-x64-gnu": {
1349
-
"version": "4.40.1",
1350
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
1351
-
"integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
1352
"cpu": [
1353
"x64"
1354
],
···
1360
]
1361
},
1362
"node_modules/@rollup/rollup-linux-x64-musl": {
1363
-
"version": "4.40.1",
1364
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
1365
-
"integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
1366
"cpu": [
1367
"x64"
1368
],
···
1374
]
1375
},
1376
"node_modules/@rollup/rollup-win32-arm64-msvc": {
1377
-
"version": "4.40.1",
1378
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
1379
-
"integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
1380
"cpu": [
1381
"arm64"
1382
],
···
1388
]
1389
},
1390
"node_modules/@rollup/rollup-win32-ia32-msvc": {
1391
-
"version": "4.40.1",
1392
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
1393
-
"integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
1394
"cpu": [
1395
"ia32"
1396
],
···
1402
]
1403
},
1404
"node_modules/@rollup/rollup-win32-x64-msvc": {
1405
-
"version": "4.40.1",
1406
-
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
1407
-
"integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
1408
"cpu": [
1409
"x64"
1410
],
···
1474
"dev": true,
1475
"license": "MIT"
1476
},
1477
"node_modules/@types/react": {
1478
-
"version": "19.1.2",
1479
-
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
1480
-
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
1481
"dev": true,
1482
"license": "MIT",
1483
"dependencies": {
···
1485
}
1486
},
1487
"node_modules/@types/react-dom": {
1488
-
"version": "19.1.3",
1489
-
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz",
1490
-
"integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==",
1491
"dev": true,
1492
"license": "MIT",
1493
"peerDependencies": {
···
1495
}
1496
},
1497
"node_modules/@typescript-eslint/eslint-plugin": {
1498
-
"version": "8.31.1",
1499
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz",
1500
-
"integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==",
1501
"dev": true,
1502
"license": "MIT",
1503
"dependencies": {
1504
"@eslint-community/regexpp": "^4.10.0",
1505
-
"@typescript-eslint/scope-manager": "8.31.1",
1506
-
"@typescript-eslint/type-utils": "8.31.1",
1507
-
"@typescript-eslint/utils": "8.31.1",
1508
-
"@typescript-eslint/visitor-keys": "8.31.1",
1509
"graphemer": "^1.4.0",
1510
-
"ignore": "^5.3.1",
1511
"natural-compare": "^1.4.0",
1512
-
"ts-api-utils": "^2.0.1"
1513
},
1514
"engines": {
1515
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1519
"url": "https://opencollective.com/typescript-eslint"
1520
},
1521
"peerDependencies": {
1522
-
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
1523
"eslint": "^8.57.0 || ^9.0.0",
1524
"typescript": ">=4.8.4 <5.9.0"
1525
}
1526
},
1527
"node_modules/@typescript-eslint/parser": {
1528
-
"version": "8.31.1",
1529
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz",
1530
-
"integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==",
1531
"dev": true,
1532
"license": "MIT",
1533
"dependencies": {
1534
-
"@typescript-eslint/scope-manager": "8.31.1",
1535
-
"@typescript-eslint/types": "8.31.1",
1536
-
"@typescript-eslint/typescript-estree": "8.31.1",
1537
-
"@typescript-eslint/visitor-keys": "8.31.1",
1538
"debug": "^4.3.4"
1539
},
1540
"engines": {
···
1549
"typescript": ">=4.8.4 <5.9.0"
1550
}
1551
},
1552
"node_modules/@typescript-eslint/scope-manager": {
1553
-
"version": "8.31.1",
1554
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz",
1555
-
"integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==",
1556
"dev": true,
1557
"license": "MIT",
1558
"dependencies": {
1559
-
"@typescript-eslint/types": "8.31.1",
1560
-
"@typescript-eslint/visitor-keys": "8.31.1"
1561
},
1562
"engines": {
1563
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1567
"url": "https://opencollective.com/typescript-eslint"
1568
}
1569
},
1570
"node_modules/@typescript-eslint/type-utils": {
1571
-
"version": "8.31.1",
1572
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz",
1573
-
"integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==",
1574
"dev": true,
1575
"license": "MIT",
1576
"dependencies": {
1577
-
"@typescript-eslint/typescript-estree": "8.31.1",
1578
-
"@typescript-eslint/utils": "8.31.1",
1579
"debug": "^4.3.4",
1580
-
"ts-api-utils": "^2.0.1"
1581
},
1582
"engines": {
1583
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1592
}
1593
},
1594
"node_modules/@typescript-eslint/types": {
1595
-
"version": "8.31.1",
1596
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz",
1597
-
"integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==",
1598
"dev": true,
1599
"license": "MIT",
1600
"engines": {
···
1606
}
1607
},
1608
"node_modules/@typescript-eslint/typescript-estree": {
1609
-
"version": "8.31.1",
1610
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz",
1611
-
"integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==",
1612
"dev": true,
1613
"license": "MIT",
1614
"dependencies": {
1615
-
"@typescript-eslint/types": "8.31.1",
1616
-
"@typescript-eslint/visitor-keys": "8.31.1",
1617
"debug": "^4.3.4",
1618
"fast-glob": "^3.3.2",
1619
"is-glob": "^4.0.3",
1620
"minimatch": "^9.0.4",
1621
"semver": "^7.6.0",
1622
-
"ts-api-utils": "^2.0.1"
1623
},
1624
"engines": {
1625
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1658
"url": "https://github.com/sponsors/isaacs"
1659
}
1660
},
1661
-
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
1662
-
"version": "7.7.1",
1663
-
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
1664
-
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
1665
-
"dev": true,
1666
-
"license": "ISC",
1667
-
"bin": {
1668
-
"semver": "bin/semver.js"
1669
-
},
1670
-
"engines": {
1671
-
"node": ">=10"
1672
-
}
1673
-
},
1674
"node_modules/@typescript-eslint/utils": {
1675
-
"version": "8.31.1",
1676
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz",
1677
-
"integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==",
1678
"dev": true,
1679
"license": "MIT",
1680
"dependencies": {
1681
-
"@eslint-community/eslint-utils": "^4.4.0",
1682
-
"@typescript-eslint/scope-manager": "8.31.1",
1683
-
"@typescript-eslint/types": "8.31.1",
1684
-
"@typescript-eslint/typescript-estree": "8.31.1"
1685
},
1686
"engines": {
1687
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1696
}
1697
},
1698
"node_modules/@typescript-eslint/visitor-keys": {
1699
-
"version": "8.31.1",
1700
-
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz",
1701
-
"integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==",
1702
"dev": true,
1703
"license": "MIT",
1704
"dependencies": {
1705
-
"@typescript-eslint/types": "8.31.1",
1706
"eslint-visitor-keys": "^4.2.0"
1707
},
1708
"engines": {
···
1713
"url": "https://opencollective.com/typescript-eslint"
1714
}
1715
},
1716
"node_modules/@vitejs/plugin-react": {
1717
-
"version": "4.4.1",
1718
-
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
1719
-
"integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
1720
"dev": true,
1721
"license": "MIT",
1722
"dependencies": {
1723
"@babel/core": "^7.26.10",
1724
"@babel/plugin-transform-react-jsx-self": "^7.25.9",
1725
"@babel/plugin-transform-react-jsx-source": "^7.25.9",
1726
"@types/babel__core": "^7.20.5",
1727
"react-refresh": "^0.17.0"
1728
},
···
1733
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
1734
}
1735
},
1736
-
"node_modules/accepts": {
1737
-
"version": "2.0.0",
1738
-
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
1739
-
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
1740
-
"dev": true,
1741
-
"license": "MIT",
1742
-
"dependencies": {
1743
-
"mime-types": "^3.0.0",
1744
-
"negotiator": "^1.0.0"
1745
-
},
1746
-
"engines": {
1747
-
"node": ">= 0.6"
1748
-
}
1749
-
},
1750
"node_modules/acorn": {
1751
"version": "8.14.1",
1752
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
···
1787
"url": "https://github.com/sponsors/epoberezkin"
1788
}
1789
},
1790
"node_modules/ansi-styles": {
1791
"version": "4.3.0",
1792
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
···
1823
"dev": true,
1824
"license": "MIT"
1825
},
1826
-
"node_modules/body-parser": {
1827
-
"version": "2.2.0",
1828
-
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
1829
-
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
1830
-
"dev": true,
1831
-
"license": "MIT",
1832
-
"dependencies": {
1833
-
"bytes": "^3.1.2",
1834
-
"content-type": "^1.0.5",
1835
-
"debug": "^4.4.0",
1836
-
"http-errors": "^2.0.0",
1837
-
"iconv-lite": "^0.6.3",
1838
-
"on-finished": "^2.4.1",
1839
-
"qs": "^6.14.0",
1840
-
"raw-body": "^3.0.0",
1841
-
"type-is": "^2.0.0"
1842
-
},
1843
-
"engines": {
1844
-
"node": ">=18"
1845
-
}
1846
-
},
1847
"node_modules/brace-expansion": {
1848
"version": "1.1.11",
1849
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
···
1901
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1902
}
1903
},
1904
-
"node_modules/bytes": {
1905
-
"version": "3.1.2",
1906
-
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
1907
-
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
1908
"dev": true,
1909
-
"license": "MIT",
1910
-
"engines": {
1911
-
"node": ">= 0.8"
1912
-
}
1913
-
},
1914
-
"node_modules/call-bind-apply-helpers": {
1915
-
"version": "1.0.2",
1916
-
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
1917
-
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
1918
-
"dev": true,
1919
-
"license": "MIT",
1920
-
"dependencies": {
1921
-
"es-errors": "^1.3.0",
1922
-
"function-bind": "^1.1.2"
1923
-
},
1924
-
"engines": {
1925
-
"node": ">= 0.4"
1926
-
}
1927
-
},
1928
-
"node_modules/call-bound": {
1929
-
"version": "1.0.4",
1930
-
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
1931
-
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
1932
-
"dev": true,
1933
-
"license": "MIT",
1934
-
"dependencies": {
1935
-
"call-bind-apply-helpers": "^1.0.2",
1936
-
"get-intrinsic": "^1.3.0"
1937
-
},
1938
-
"engines": {
1939
-
"node": ">= 0.4"
1940
-
},
1941
-
"funding": {
1942
-
"url": "https://github.com/sponsors/ljharb"
1943
-
}
1944
},
1945
"node_modules/callsites": {
1946
"version": "3.1.0",
···
1953
}
1954
},
1955
"node_modules/caniuse-lite": {
1956
-
"version": "1.0.30001716",
1957
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001716.tgz",
1958
-
"integrity": "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw==",
1959
"dev": true,
1960
"funding": [
1961
{
···
1990
"url": "https://github.com/chalk/chalk?sponsor=1"
1991
}
1992
},
1993
"node_modules/color-convert": {
1994
"version": "2.0.1",
1995
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
···
2010
"dev": true,
2011
"license": "MIT"
2012
},
2013
"node_modules/concat-map": {
2014
"version": "0.0.1",
2015
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
···
2017
"dev": true,
2018
"license": "MIT"
2019
},
2020
-
"node_modules/content-disposition": {
2021
-
"version": "1.0.0",
2022
-
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
2023
-
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
2024
-
"dev": true,
2025
-
"license": "MIT",
2026
-
"dependencies": {
2027
-
"safe-buffer": "5.2.1"
2028
-
},
2029
-
"engines": {
2030
-
"node": ">= 0.6"
2031
-
}
2032
-
},
2033
-
"node_modules/content-type": {
2034
-
"version": "1.0.5",
2035
-
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
2036
-
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
2037
-
"dev": true,
2038
-
"license": "MIT",
2039
-
"engines": {
2040
-
"node": ">= 0.6"
2041
-
}
2042
-
},
2043
"node_modules/convert-source-map": {
2044
"version": "2.0.0",
2045
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
···
2048
"license": "MIT"
2049
},
2050
"node_modules/cookie": {
2051
-
"version": "0.7.2",
2052
-
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
2053
-
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
2054
-
"dev": true,
2055
-
"license": "MIT",
2056
-
"engines": {
2057
-
"node": ">= 0.6"
2058
-
}
2059
-
},
2060
-
"node_modules/cookie-signature": {
2061
-
"version": "1.2.2",
2062
-
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
2063
-
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
2064
-
"dev": true,
2065
"license": "MIT",
2066
"engines": {
2067
-
"node": ">=6.6.0"
2068
-
}
2069
-
},
2070
-
"node_modules/cors": {
2071
-
"version": "2.8.5",
2072
-
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
2073
-
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
2074
-
"dev": true,
2075
-
"license": "MIT",
2076
-
"dependencies": {
2077
-
"object-assign": "^4",
2078
-
"vary": "^1"
2079
-
},
2080
-
"engines": {
2081
-
"node": ">= 0.10"
2082
}
2083
},
2084
"node_modules/cross-spawn": {
···
2104
"license": "MIT"
2105
},
2106
"node_modules/debug": {
2107
-
"version": "4.4.0",
2108
-
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
2109
-
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
2110
"dev": true,
2111
"license": "MIT",
2112
"dependencies": {
···
2128
"dev": true,
2129
"license": "MIT"
2130
},
2131
-
"node_modules/depd": {
2132
"version": "2.0.0",
2133
-
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
2134
-
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
2135
"dev": true,
2136
"license": "MIT",
2137
"engines": {
2138
-
"node": ">= 0.8"
2139
-
}
2140
-
},
2141
-
"node_modules/dunder-proto": {
2142
-
"version": "1.0.1",
2143
-
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
2144
-
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
2145
-
"dev": true,
2146
-
"license": "MIT",
2147
-
"dependencies": {
2148
-
"call-bind-apply-helpers": "^1.0.1",
2149
-
"es-errors": "^1.3.0",
2150
-
"gopd": "^1.2.0"
2151
-
},
2152
-
"engines": {
2153
-
"node": ">= 0.4"
2154
}
2155
},
2156
-
"node_modules/ee-first": {
2157
-
"version": "1.1.1",
2158
-
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
2159
-
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
2160
-
"dev": true,
2161
-
"license": "MIT"
2162
-
},
2163
"node_modules/electron-to-chromium": {
2164
-
"version": "1.5.149",
2165
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.149.tgz",
2166
-
"integrity": "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==",
2167
"dev": true,
2168
"license": "ISC"
2169
},
2170
-
"node_modules/encodeurl": {
2171
-
"version": "2.0.0",
2172
-
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
2173
-
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
2174
"dev": true,
2175
-
"license": "MIT",
2176
-
"engines": {
2177
-
"node": ">= 0.8"
2178
-
}
2179
-
},
2180
-
"node_modules/es-define-property": {
2181
-
"version": "1.0.1",
2182
-
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
2183
-
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
2184
-
"dev": true,
2185
-
"license": "MIT",
2186
-
"engines": {
2187
-
"node": ">= 0.4"
2188
-
}
2189
-
},
2190
-
"node_modules/es-errors": {
2191
-
"version": "1.3.0",
2192
-
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
2193
-
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
2194
-
"dev": true,
2195
-
"license": "MIT",
2196
-
"engines": {
2197
-
"node": ">= 0.4"
2198
-
}
2199
-
},
2200
-
"node_modules/es-object-atoms": {
2201
-
"version": "1.1.1",
2202
-
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
2203
-
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
2204
-
"dev": true,
2205
-
"license": "MIT",
2206
-
"dependencies": {
2207
-
"es-errors": "^1.3.0"
2208
-
},
2209
-
"engines": {
2210
-
"node": ">= 0.4"
2211
-
}
2212
},
2213
"node_modules/esbuild": {
2214
-
"version": "0.25.3",
2215
-
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
2216
-
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
2217
"dev": true,
2218
"hasInstallScript": true,
2219
"license": "MIT",
···
2224
"node": ">=18"
2225
},
2226
"optionalDependencies": {
2227
-
"@esbuild/aix-ppc64": "0.25.3",
2228
-
"@esbuild/android-arm": "0.25.3",
2229
-
"@esbuild/android-arm64": "0.25.3",
2230
-
"@esbuild/android-x64": "0.25.3",
2231
-
"@esbuild/darwin-arm64": "0.25.3",
2232
-
"@esbuild/darwin-x64": "0.25.3",
2233
-
"@esbuild/freebsd-arm64": "0.25.3",
2234
-
"@esbuild/freebsd-x64": "0.25.3",
2235
-
"@esbuild/linux-arm": "0.25.3",
2236
-
"@esbuild/linux-arm64": "0.25.3",
2237
-
"@esbuild/linux-ia32": "0.25.3",
2238
-
"@esbuild/linux-loong64": "0.25.3",
2239
-
"@esbuild/linux-mips64el": "0.25.3",
2240
-
"@esbuild/linux-ppc64": "0.25.3",
2241
-
"@esbuild/linux-riscv64": "0.25.3",
2242
-
"@esbuild/linux-s390x": "0.25.3",
2243
-
"@esbuild/linux-x64": "0.25.3",
2244
-
"@esbuild/netbsd-arm64": "0.25.3",
2245
-
"@esbuild/netbsd-x64": "0.25.3",
2246
-
"@esbuild/openbsd-arm64": "0.25.3",
2247
-
"@esbuild/openbsd-x64": "0.25.3",
2248
-
"@esbuild/sunos-x64": "0.25.3",
2249
-
"@esbuild/win32-arm64": "0.25.3",
2250
-
"@esbuild/win32-ia32": "0.25.3",
2251
-
"@esbuild/win32-x64": "0.25.3"
2252
}
2253
},
2254
"node_modules/escalade": {
···
2261
"node": ">=6"
2262
}
2263
},
2264
-
"node_modules/escape-html": {
2265
-
"version": "1.0.3",
2266
-
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
2267
-
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
2268
-
"dev": true,
2269
-
"license": "MIT"
2270
-
},
2271
"node_modules/escape-string-regexp": {
2272
"version": "4.0.0",
2273
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
···
2282
}
2283
},
2284
"node_modules/eslint": {
2285
-
"version": "9.26.0",
2286
-
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz",
2287
-
"integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==",
2288
"dev": true,
2289
"license": "MIT",
2290
"dependencies": {
···
2292
"@eslint-community/regexpp": "^4.12.1",
2293
"@eslint/config-array": "^0.20.0",
2294
"@eslint/config-helpers": "^0.2.1",
2295
-
"@eslint/core": "^0.13.0",
2296
"@eslint/eslintrc": "^3.3.1",
2297
-
"@eslint/js": "9.26.0",
2298
-
"@eslint/plugin-kit": "^0.2.8",
2299
"@humanfs/node": "^0.16.6",
2300
"@humanwhocodes/module-importer": "^1.0.1",
2301
"@humanwhocodes/retry": "^0.4.2",
2302
-
"@modelcontextprotocol/sdk": "^1.8.0",
2303
"@types/estree": "^1.0.6",
2304
"@types/json-schema": "^7.0.15",
2305
"ajv": "^6.12.4",
···
2323
"lodash.merge": "^4.6.2",
2324
"minimatch": "^3.1.2",
2325
"natural-compare": "^1.4.0",
2326
-
"optionator": "^0.9.3",
2327
-
"zod": "^3.24.2"
2328
},
2329
"bin": {
2330
"eslint": "bin/eslint.js"
···
2385
}
2386
},
2387
"node_modules/eslint-visitor-keys": {
2388
"version": "4.2.0",
2389
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
2390
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
···
2415
"url": "https://opencollective.com/eslint"
2416
}
2417
},
2418
"node_modules/esquery": {
2419
"version": "1.6.0",
2420
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
···
2461
"node": ">=0.10.0"
2462
}
2463
},
2464
-
"node_modules/etag": {
2465
-
"version": "1.8.1",
2466
-
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
2467
-
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
2468
-
"dev": true,
2469
-
"license": "MIT",
2470
-
"engines": {
2471
-
"node": ">= 0.6"
2472
-
}
2473
-
},
2474
-
"node_modules/eventsource": {
2475
-
"version": "3.0.6",
2476
-
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
2477
-
"integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
2478
-
"dev": true,
2479
-
"license": "MIT",
2480
-
"dependencies": {
2481
-
"eventsource-parser": "^3.0.1"
2482
-
},
2483
-
"engines": {
2484
-
"node": ">=18.0.0"
2485
-
}
2486
-
},
2487
-
"node_modules/eventsource-parser": {
2488
-
"version": "3.0.1",
2489
-
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
2490
-
"integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
2491
-
"dev": true,
2492
-
"license": "MIT",
2493
-
"engines": {
2494
-
"node": ">=18.0.0"
2495
-
}
2496
-
},
2497
-
"node_modules/express": {
2498
-
"version": "5.1.0",
2499
-
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
2500
-
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
2501
-
"dev": true,
2502
-
"license": "MIT",
2503
-
"dependencies": {
2504
-
"accepts": "^2.0.0",
2505
-
"body-parser": "^2.2.0",
2506
-
"content-disposition": "^1.0.0",
2507
-
"content-type": "^1.0.5",
2508
-
"cookie": "^0.7.1",
2509
-
"cookie-signature": "^1.2.1",
2510
-
"debug": "^4.4.0",
2511
-
"encodeurl": "^2.0.0",
2512
-
"escape-html": "^1.0.3",
2513
-
"etag": "^1.8.1",
2514
-
"finalhandler": "^2.1.0",
2515
-
"fresh": "^2.0.0",
2516
-
"http-errors": "^2.0.0",
2517
-
"merge-descriptors": "^2.0.0",
2518
-
"mime-types": "^3.0.0",
2519
-
"on-finished": "^2.4.1",
2520
-
"once": "^1.4.0",
2521
-
"parseurl": "^1.3.3",
2522
-
"proxy-addr": "^2.0.7",
2523
-
"qs": "^6.14.0",
2524
-
"range-parser": "^1.2.1",
2525
-
"router": "^2.2.0",
2526
-
"send": "^1.1.0",
2527
-
"serve-static": "^2.2.0",
2528
-
"statuses": "^2.0.1",
2529
-
"type-is": "^2.0.1",
2530
-
"vary": "^1.1.2"
2531
-
},
2532
-
"engines": {
2533
-
"node": ">= 18"
2534
-
},
2535
-
"funding": {
2536
-
"type": "opencollective",
2537
-
"url": "https://opencollective.com/express"
2538
-
}
2539
-
},
2540
-
"node_modules/express-rate-limit": {
2541
-
"version": "7.5.0",
2542
-
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
2543
-
"integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
2544
-
"dev": true,
2545
-
"license": "MIT",
2546
-
"engines": {
2547
-
"node": ">= 16"
2548
-
},
2549
-
"funding": {
2550
-
"url": "https://github.com/sponsors/express-rate-limit"
2551
-
},
2552
-
"peerDependencies": {
2553
-
"express": "^4.11 || 5 || ^5.0.0-beta.1"
2554
-
}
2555
-
},
2556
"node_modules/fast-deep-equal": {
2557
"version": "3.1.3",
2558
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
···
2640
"node": ">=8"
2641
}
2642
},
2643
-
"node_modules/finalhandler": {
2644
-
"version": "2.1.0",
2645
-
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
2646
-
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
2647
-
"dev": true,
2648
-
"license": "MIT",
2649
-
"dependencies": {
2650
-
"debug": "^4.4.0",
2651
-
"encodeurl": "^2.0.0",
2652
-
"escape-html": "^1.0.3",
2653
-
"on-finished": "^2.4.1",
2654
-
"parseurl": "^1.3.3",
2655
-
"statuses": "^2.0.1"
2656
-
},
2657
-
"engines": {
2658
-
"node": ">= 0.8"
2659
-
}
2660
-
},
2661
"node_modules/find-up": {
2662
"version": "5.0.0",
2663
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
···
2696
"dev": true,
2697
"license": "ISC"
2698
},
2699
-
"node_modules/forwarded": {
2700
-
"version": "0.2.0",
2701
-
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
2702
-
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
2703
-
"dev": true,
2704
-
"license": "MIT",
2705
-
"engines": {
2706
-
"node": ">= 0.6"
2707
-
}
2708
-
},
2709
-
"node_modules/fresh": {
2710
-
"version": "2.0.0",
2711
-
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
2712
-
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
2713
-
"dev": true,
2714
-
"license": "MIT",
2715
-
"engines": {
2716
-
"node": ">= 0.8"
2717
-
}
2718
-
},
2719
"node_modules/fsevents": {
2720
"version": "2.3.3",
2721
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
···
2731
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
2732
}
2733
},
2734
-
"node_modules/function-bind": {
2735
-
"version": "1.1.2",
2736
-
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
2737
-
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
2738
-
"dev": true,
2739
-
"license": "MIT",
2740
-
"funding": {
2741
-
"url": "https://github.com/sponsors/ljharb"
2742
-
}
2743
-
},
2744
"node_modules/gensync": {
2745
"version": "1.0.0-beta.2",
2746
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
···
2751
"node": ">=6.9.0"
2752
}
2753
},
2754
-
"node_modules/get-intrinsic": {
2755
-
"version": "1.3.0",
2756
-
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
2757
-
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
2758
-
"dev": true,
2759
-
"license": "MIT",
2760
-
"dependencies": {
2761
-
"call-bind-apply-helpers": "^1.0.2",
2762
-
"es-define-property": "^1.0.1",
2763
-
"es-errors": "^1.3.0",
2764
-
"es-object-atoms": "^1.1.1",
2765
-
"function-bind": "^1.1.2",
2766
-
"get-proto": "^1.0.1",
2767
-
"gopd": "^1.2.0",
2768
-
"has-symbols": "^1.1.0",
2769
-
"hasown": "^2.0.2",
2770
-
"math-intrinsics": "^1.1.0"
2771
-
},
2772
-
"engines": {
2773
-
"node": ">= 0.4"
2774
-
},
2775
-
"funding": {
2776
-
"url": "https://github.com/sponsors/ljharb"
2777
-
}
2778
-
},
2779
-
"node_modules/get-proto": {
2780
-
"version": "1.0.1",
2781
-
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
2782
-
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
2783
"dev": true,
2784
-
"license": "MIT",
2785
-
"dependencies": {
2786
-
"dunder-proto": "^1.0.1",
2787
-
"es-object-atoms": "^1.0.0"
2788
-
},
2789
"engines": {
2790
-
"node": ">= 0.4"
2791
}
2792
},
2793
"node_modules/glob-parent": {
···
2804
}
2805
},
2806
"node_modules/globals": {
2807
-
"version": "16.0.0",
2808
-
"resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz",
2809
-
"integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==",
2810
"dev": true,
2811
"license": "MIT",
2812
"engines": {
···
2816
"url": "https://github.com/sponsors/sindresorhus"
2817
}
2818
},
2819
-
"node_modules/gopd": {
2820
-
"version": "1.2.0",
2821
-
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
2822
-
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
2823
-
"dev": true,
2824
-
"license": "MIT",
2825
-
"engines": {
2826
-
"node": ">= 0.4"
2827
-
},
2828
-
"funding": {
2829
-
"url": "https://github.com/sponsors/ljharb"
2830
-
}
2831
-
},
2832
"node_modules/graphemer": {
2833
"version": "1.4.0",
2834
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
···
2845
"node": ">=8"
2846
}
2847
},
2848
-
"node_modules/has-symbols": {
2849
-
"version": "1.1.0",
2850
-
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
2851
-
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
2852
-
"dev": true,
2853
-
"license": "MIT",
2854
-
"engines": {
2855
-
"node": ">= 0.4"
2856
-
},
2857
-
"funding": {
2858
-
"url": "https://github.com/sponsors/ljharb"
2859
-
}
2860
-
},
2861
-
"node_modules/hasown": {
2862
-
"version": "2.0.2",
2863
-
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
2864
-
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
2865
-
"dev": true,
2866
-
"license": "MIT",
2867
-
"dependencies": {
2868
-
"function-bind": "^1.1.2"
2869
-
},
2870
-
"engines": {
2871
-
"node": ">= 0.4"
2872
-
}
2873
-
},
2874
-
"node_modules/http-errors": {
2875
-
"version": "2.0.0",
2876
-
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
2877
-
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
2878
-
"dev": true,
2879
-
"license": "MIT",
2880
-
"dependencies": {
2881
-
"depd": "2.0.0",
2882
-
"inherits": "2.0.4",
2883
-
"setprototypeof": "1.2.0",
2884
-
"statuses": "2.0.1",
2885
-
"toidentifier": "1.0.1"
2886
-
},
2887
-
"engines": {
2888
-
"node": ">= 0.8"
2889
-
}
2890
-
},
2891
-
"node_modules/iconv-lite": {
2892
-
"version": "0.6.3",
2893
-
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
2894
-
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
2895
-
"dev": true,
2896
-
"license": "MIT",
2897
-
"dependencies": {
2898
-
"safer-buffer": ">= 2.1.2 < 3.0.0"
2899
-
},
2900
-
"engines": {
2901
-
"node": ">=0.10.0"
2902
-
}
2903
-
},
2904
"node_modules/ignore": {
2905
"version": "5.3.2",
2906
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
···
2938
"node": ">=0.8.19"
2939
}
2940
},
2941
-
"node_modules/inherits": {
2942
-
"version": "2.0.4",
2943
-
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
2944
-
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
2945
-
"dev": true,
2946
-
"license": "ISC"
2947
-
},
2948
-
"node_modules/ipaddr.js": {
2949
-
"version": "1.9.1",
2950
-
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
2951
-
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
2952
"dev": true,
2953
"license": "MIT",
2954
"engines": {
2955
-
"node": ">= 0.10"
2956
}
2957
},
2958
"node_modules/is-extglob": {
···
2965
"node": ">=0.10.0"
2966
}
2967
},
2968
"node_modules/is-glob": {
2969
"version": "4.0.3",
2970
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
···
2988
"node": ">=0.12.0"
2989
}
2990
},
2991
-
"node_modules/is-promise": {
2992
-
"version": "4.0.0",
2993
-
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
2994
-
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
2995
"dev": true,
2996
-
"license": "MIT"
2997
},
2998
"node_modules/isexe": {
2999
"version": "2.0.0",
···
3007
"resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
3008
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
3009
"license": "MIT"
3010
},
3011
"node_modules/js-tokens": {
3012
"version": "4.0.0",
···
3132
"yallist": "^3.0.2"
3133
}
3134
},
3135
-
"node_modules/math-intrinsics": {
3136
-
"version": "1.1.0",
3137
-
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
3138
-
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
3139
-
"dev": true,
3140
-
"license": "MIT",
3141
-
"engines": {
3142
-
"node": ">= 0.4"
3143
-
}
3144
-
},
3145
-
"node_modules/media-typer": {
3146
-
"version": "1.1.0",
3147
-
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
3148
-
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
3149
-
"dev": true,
3150
-
"license": "MIT",
3151
-
"engines": {
3152
-
"node": ">= 0.8"
3153
-
}
3154
-
},
3155
-
"node_modules/merge-descriptors": {
3156
-
"version": "2.0.0",
3157
-
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
3158
-
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
3159
-
"dev": true,
3160
-
"license": "MIT",
3161
-
"engines": {
3162
-
"node": ">=18"
3163
-
},
3164
-
"funding": {
3165
-
"url": "https://github.com/sponsors/sindresorhus"
3166
-
}
3167
-
},
3168
"node_modules/merge2": {
3169
"version": "1.4.1",
3170
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
···
3189
"node": ">=8.6"
3190
}
3191
},
3192
-
"node_modules/mime-db": {
3193
-
"version": "1.54.0",
3194
-
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
3195
-
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
3196
-
"dev": true,
3197
-
"license": "MIT",
3198
-
"engines": {
3199
-
"node": ">= 0.6"
3200
-
}
3201
-
},
3202
-
"node_modules/mime-types": {
3203
-
"version": "3.0.1",
3204
-
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
3205
-
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
3206
-
"dev": true,
3207
-
"license": "MIT",
3208
-
"dependencies": {
3209
-
"mime-db": "^1.54.0"
3210
-
},
3211
-
"engines": {
3212
-
"node": ">= 0.6"
3213
-
}
3214
-
},
3215
"node_modules/minimatch": {
3216
"version": "3.1.2",
3217
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
···
3233
"license": "MIT"
3234
},
3235
"node_modules/multiformats": {
3236
-
"version": "9.9.0",
3237
-
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
3238
-
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
3239
-
"license": "(Apache-2.0 AND MIT)"
3240
},
3241
"node_modules/nanoid": {
3242
"version": "3.3.11",
···
3264
"dev": true,
3265
"license": "MIT"
3266
},
3267
-
"node_modules/negotiator": {
3268
-
"version": "1.0.0",
3269
-
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
3270
-
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
3271
-
"dev": true,
3272
-
"license": "MIT",
3273
-
"engines": {
3274
-
"node": ">= 0.6"
3275
-
}
3276
-
},
3277
"node_modules/node-releases": {
3278
"version": "2.0.19",
3279
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
···
3281
"dev": true,
3282
"license": "MIT"
3283
},
3284
-
"node_modules/object-assign": {
3285
-
"version": "4.1.1",
3286
-
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
3287
-
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
3288
-
"dev": true,
3289
-
"license": "MIT",
3290
-
"engines": {
3291
-
"node": ">=0.10.0"
3292
-
}
3293
-
},
3294
-
"node_modules/object-inspect": {
3295
-
"version": "1.13.4",
3296
-
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
3297
-
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
3298
"dev": true,
3299
"license": "MIT",
3300
"engines": {
3301
-
"node": ">= 0.4"
3302
},
3303
"funding": {
3304
-
"url": "https://github.com/sponsors/ljharb"
3305
-
}
3306
-
},
3307
-
"node_modules/on-finished": {
3308
-
"version": "2.4.1",
3309
-
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
3310
-
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
3311
-
"dev": true,
3312
-
"license": "MIT",
3313
-
"dependencies": {
3314
-
"ee-first": "1.1.1"
3315
-
},
3316
-
"engines": {
3317
-
"node": ">= 0.8"
3318
-
}
3319
-
},
3320
-
"node_modules/once": {
3321
-
"version": "1.4.0",
3322
-
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
3323
-
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
3324
-
"dev": true,
3325
-
"license": "ISC",
3326
-
"dependencies": {
3327
-
"wrappy": "1"
3328
}
3329
},
3330
"node_modules/optionator": {
···
3390
"node": ">=6"
3391
}
3392
},
3393
-
"node_modules/parseurl": {
3394
-
"version": "1.3.3",
3395
-
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
3396
-
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
3397
-
"dev": true,
3398
-
"license": "MIT",
3399
-
"engines": {
3400
-
"node": ">= 0.8"
3401
-
}
3402
-
},
3403
"node_modules/path-exists": {
3404
"version": "4.0.0",
3405
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
···
3420
"node": ">=8"
3421
}
3422
},
3423
-
"node_modules/path-to-regexp": {
3424
-
"version": "8.2.0",
3425
-
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
3426
-
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
3427
-
"dev": true,
3428
-
"license": "MIT",
3429
-
"engines": {
3430
-
"node": ">=16"
3431
-
}
3432
-
},
3433
"node_modules/picocolors": {
3434
"version": "1.1.1",
3435
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
···
3448
},
3449
"funding": {
3450
"url": "https://github.com/sponsors/jonschlinkert"
3451
-
}
3452
-
},
3453
-
"node_modules/pkce-challenge": {
3454
-
"version": "5.0.0",
3455
-
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
3456
-
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
3457
-
"dev": true,
3458
-
"license": "MIT",
3459
-
"engines": {
3460
-
"node": ">=16.20.0"
3461
}
3462
},
3463
"node_modules/postcss": {
···
3499
"node": ">= 0.8.0"
3500
}
3501
},
3502
-
"node_modules/proxy-addr": {
3503
-
"version": "2.0.7",
3504
-
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
3505
-
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
3506
-
"dev": true,
3507
-
"license": "MIT",
3508
-
"dependencies": {
3509
-
"forwarded": "0.2.0",
3510
-
"ipaddr.js": "1.9.1"
3511
-
},
3512
-
"engines": {
3513
-
"node": ">= 0.10"
3514
-
}
3515
-
},
3516
"node_modules/punycode": {
3517
"version": "2.3.1",
3518
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
···
3523
"node": ">=6"
3524
}
3525
},
3526
-
"node_modules/qs": {
3527
-
"version": "6.14.0",
3528
-
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
3529
-
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
3530
-
"dev": true,
3531
-
"license": "BSD-3-Clause",
3532
-
"dependencies": {
3533
-
"side-channel": "^1.1.0"
3534
-
},
3535
-
"engines": {
3536
-
"node": ">=0.6"
3537
-
},
3538
-
"funding": {
3539
-
"url": "https://github.com/sponsors/ljharb"
3540
-
}
3541
-
},
3542
"node_modules/queue-microtask": {
3543
"version": "1.2.3",
3544
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
···
3560
],
3561
"license": "MIT"
3562
},
3563
-
"node_modules/range-parser": {
3564
-
"version": "1.2.1",
3565
-
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
3566
-
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
3567
-
"dev": true,
3568
-
"license": "MIT",
3569
-
"engines": {
3570
-
"node": ">= 0.6"
3571
-
}
3572
-
},
3573
-
"node_modules/raw-body": {
3574
-
"version": "3.0.0",
3575
-
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
3576
-
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
3577
-
"dev": true,
3578
-
"license": "MIT",
3579
-
"dependencies": {
3580
-
"bytes": "3.1.2",
3581
-
"http-errors": "2.0.0",
3582
-
"iconv-lite": "0.6.3",
3583
-
"unpipe": "1.0.0"
3584
-
},
3585
-
"engines": {
3586
-
"node": ">= 0.8"
3587
-
}
3588
-
},
3589
"node_modules/react": {
3590
"version": "19.1.0",
3591
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
···
3618
}
3619
},
3620
"node_modules/react-router": {
3621
-
"version": "7.5.3",
3622
-
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
3623
-
"integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
3624
"license": "MIT",
3625
"dependencies": {
3626
"cookie": "^1.0.1",
3627
-
"set-cookie-parser": "^2.6.0",
3628
-
"turbo-stream": "2.4.0"
3629
},
3630
"engines": {
3631
"node": ">=20.0.0"
···
3641
}
3642
},
3643
"node_modules/react-router-dom": {
3644
-
"version": "7.5.3",
3645
-
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.3.tgz",
3646
-
"integrity": "sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==",
3647
"license": "MIT",
3648
"dependencies": {
3649
-
"react-router": "7.5.3"
3650
},
3651
"engines": {
3652
"node": ">=20.0.0"
···
3656
"react-dom": ">=18"
3657
}
3658
},
3659
-
"node_modules/react-router/node_modules/cookie": {
3660
-
"version": "1.0.2",
3661
-
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
3662
-
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
3663
"license": "MIT",
3664
"engines": {
3665
-
"node": ">=18"
3666
}
3667
},
3668
"node_modules/resolve-from": {
···
3687
}
3688
},
3689
"node_modules/rollup": {
3690
-
"version": "4.40.1",
3691
-
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
3692
-
"integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
3693
"dev": true,
3694
"license": "MIT",
3695
"dependencies": {
···
3703
"npm": ">=8.0.0"
3704
},
3705
"optionalDependencies": {
3706
-
"@rollup/rollup-android-arm-eabi": "4.40.1",
3707
-
"@rollup/rollup-android-arm64": "4.40.1",
3708
-
"@rollup/rollup-darwin-arm64": "4.40.1",
3709
-
"@rollup/rollup-darwin-x64": "4.40.1",
3710
-
"@rollup/rollup-freebsd-arm64": "4.40.1",
3711
-
"@rollup/rollup-freebsd-x64": "4.40.1",
3712
-
"@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
3713
-
"@rollup/rollup-linux-arm-musleabihf": "4.40.1",
3714
-
"@rollup/rollup-linux-arm64-gnu": "4.40.1",
3715
-
"@rollup/rollup-linux-arm64-musl": "4.40.1",
3716
-
"@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
3717
-
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
3718
-
"@rollup/rollup-linux-riscv64-gnu": "4.40.1",
3719
-
"@rollup/rollup-linux-riscv64-musl": "4.40.1",
3720
-
"@rollup/rollup-linux-s390x-gnu": "4.40.1",
3721
-
"@rollup/rollup-linux-x64-gnu": "4.40.1",
3722
-
"@rollup/rollup-linux-x64-musl": "4.40.1",
3723
-
"@rollup/rollup-win32-arm64-msvc": "4.40.1",
3724
-
"@rollup/rollup-win32-ia32-msvc": "4.40.1",
3725
-
"@rollup/rollup-win32-x64-msvc": "4.40.1",
3726
"fsevents": "~2.3.2"
3727
}
3728
},
3729
-
"node_modules/router": {
3730
-
"version": "2.2.0",
3731
-
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
3732
-
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
3733
"dev": true,
3734
"license": "MIT",
3735
"dependencies": {
3736
-
"debug": "^4.4.0",
3737
-
"depd": "^2.0.0",
3738
-
"is-promise": "^4.0.0",
3739
-
"parseurl": "^1.3.3",
3740
-
"path-to-regexp": "^8.0.0"
3741
},
3742
"engines": {
3743
-
"node": ">= 18"
3744
}
3745
},
3746
"node_modules/run-parallel": {
···
3767
"queue-microtask": "^1.2.2"
3768
}
3769
},
3770
-
"node_modules/safe-buffer": {
3771
-
"version": "5.2.1",
3772
-
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
3773
-
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
3774
-
"dev": true,
3775
-
"funding": [
3776
-
{
3777
-
"type": "github",
3778
-
"url": "https://github.com/sponsors/feross"
3779
-
},
3780
-
{
3781
-
"type": "patreon",
3782
-
"url": "https://www.patreon.com/feross"
3783
-
},
3784
-
{
3785
-
"type": "consulting",
3786
-
"url": "https://feross.org/support"
3787
-
}
3788
-
],
3789
-
"license": "MIT"
3790
-
},
3791
-
"node_modules/safer-buffer": {
3792
-
"version": "2.1.2",
3793
-
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
3794
-
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
3795
-
"dev": true,
3796
-
"license": "MIT"
3797
-
},
3798
"node_modules/scheduler": {
3799
"version": "0.26.0",
3800
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
···
3802
"license": "MIT"
3803
},
3804
"node_modules/semver": {
3805
-
"version": "6.3.1",
3806
-
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
3807
-
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
3808
"dev": true,
3809
"license": "ISC",
3810
"bin": {
3811
"semver": "bin/semver.js"
3812
-
}
3813
-
},
3814
-
"node_modules/send": {
3815
-
"version": "1.2.0",
3816
-
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
3817
-
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
3818
-
"dev": true,
3819
-
"license": "MIT",
3820
-
"dependencies": {
3821
-
"debug": "^4.3.5",
3822
-
"encodeurl": "^2.0.0",
3823
-
"escape-html": "^1.0.3",
3824
-
"etag": "^1.8.1",
3825
-
"fresh": "^2.0.0",
3826
-
"http-errors": "^2.0.0",
3827
-
"mime-types": "^3.0.1",
3828
-
"ms": "^2.1.3",
3829
-
"on-finished": "^2.4.1",
3830
-
"range-parser": "^1.2.1",
3831
-
"statuses": "^2.0.1"
3832
},
3833
"engines": {
3834
-
"node": ">= 18"
3835
-
}
3836
-
},
3837
-
"node_modules/serve-static": {
3838
-
"version": "2.2.0",
3839
-
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
3840
-
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
3841
-
"dev": true,
3842
-
"license": "MIT",
3843
-
"dependencies": {
3844
-
"encodeurl": "^2.0.0",
3845
-
"escape-html": "^1.0.3",
3846
-
"parseurl": "^1.3.3",
3847
-
"send": "^1.2.0"
3848
-
},
3849
-
"engines": {
3850
-
"node": ">= 18"
3851
}
3852
},
3853
"node_modules/set-cookie-parser": {
···
3855
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
3856
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
3857
"license": "MIT"
3858
-
},
3859
-
"node_modules/setprototypeof": {
3860
-
"version": "1.2.0",
3861
-
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
3862
-
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
3863
-
"dev": true,
3864
-
"license": "ISC"
3865
},
3866
"node_modules/shebang-command": {
3867
"version": "2.0.0",
···
3886
"node": ">=8"
3887
}
3888
},
3889
-
"node_modules/side-channel": {
3890
-
"version": "1.1.0",
3891
-
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
3892
-
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
3893
"dev": true,
3894
-
"license": "MIT",
3895
-
"dependencies": {
3896
-
"es-errors": "^1.3.0",
3897
-
"object-inspect": "^1.13.3",
3898
-
"side-channel-list": "^1.0.0",
3899
-
"side-channel-map": "^1.0.1",
3900
-
"side-channel-weakmap": "^1.0.2"
3901
-
},
3902
"engines": {
3903
-
"node": ">= 0.4"
3904
-
},
3905
-
"funding": {
3906
-
"url": "https://github.com/sponsors/ljharb"
3907
}
3908
},
3909
-
"node_modules/side-channel-list": {
3910
-
"version": "1.0.0",
3911
-
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
3912
-
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
3913
"dev": true,
3914
-
"license": "MIT",
3915
-
"dependencies": {
3916
-
"es-errors": "^1.3.0",
3917
-
"object-inspect": "^1.13.3"
3918
-
},
3919
"engines": {
3920
-
"node": ">= 0.4"
3921
-
},
3922
-
"funding": {
3923
-
"url": "https://github.com/sponsors/ljharb"
3924
}
3925
},
3926
-
"node_modules/side-channel-map": {
3927
-
"version": "1.0.1",
3928
-
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
3929
-
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
3930
"dev": true,
3931
"license": "MIT",
3932
"dependencies": {
3933
-
"call-bound": "^1.0.2",
3934
-
"es-errors": "^1.3.0",
3935
-
"get-intrinsic": "^1.2.5",
3936
-
"object-inspect": "^1.13.3"
3937
-
},
3938
"engines": {
3939
-
"node": ">= 0.4"
3940
-
},
3941
-
"funding": {
3942
-
"url": "https://github.com/sponsors/ljharb"
3943
}
3944
},
3945
-
"node_modules/side-channel-weakmap": {
3946
-
"version": "1.0.2",
3947
-
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
3948
-
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
3949
"dev": true,
3950
"license": "MIT",
3951
"dependencies": {
3952
-
"call-bound": "^1.0.2",
3953
-
"es-errors": "^1.3.0",
3954
-
"get-intrinsic": "^1.2.5",
3955
-
"object-inspect": "^1.13.3",
3956
-
"side-channel-map": "^1.0.1"
3957
},
3958
"engines": {
3959
-
"node": ">= 0.4"
3960
-
},
3961
-
"funding": {
3962
-
"url": "https://github.com/sponsors/ljharb"
3963
-
}
3964
-
},
3965
-
"node_modules/source-map-js": {
3966
-
"version": "1.2.1",
3967
-
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
3968
-
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
3969
-
"dev": true,
3970
-
"license": "BSD-3-Clause",
3971
-
"engines": {
3972
-
"node": ">=0.10.0"
3973
}
3974
},
3975
-
"node_modules/statuses": {
3976
-
"version": "2.0.1",
3977
-
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
3978
-
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
3979
"dev": true,
3980
"license": "MIT",
3981
"engines": {
3982
-
"node": ">= 0.8"
3983
}
3984
},
3985
"node_modules/strip-json-comments": {
···
4008
"node": ">=8"
4009
}
4010
},
4011
"node_modules/tinyglobby": {
4012
-
"version": "0.2.13",
4013
-
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
4014
-
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
4015
"dev": true,
4016
"license": "MIT",
4017
"dependencies": {
···
4026
}
4027
},
4028
"node_modules/tinyglobby/node_modules/fdir": {
4029
-
"version": "6.4.4",
4030
-
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
4031
-
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
4032
"dev": true,
4033
"license": "MIT",
4034
"peerDependencies": {
···
4054
}
4055
},
4056
"node_modules/tlds": {
4057
-
"version": "1.258.0",
4058
-
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.258.0.tgz",
4059
-
"integrity": "sha512-XGhStWuOlBA5D8QnyN2xtgB2cUOdJ3ztisne1DYVWMcVH29qh8eQIpRmP3HnuJLdgyzG0HpdGzRMu1lm/Oictw==",
4060
"license": "MIT",
4061
"bin": {
4062
"tlds": "bin.js"
···
4073
},
4074
"engines": {
4075
"node": ">=8.0"
4076
-
}
4077
-
},
4078
-
"node_modules/toidentifier": {
4079
-
"version": "1.0.1",
4080
-
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
4081
-
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
4082
-
"dev": true,
4083
-
"license": "MIT",
4084
-
"engines": {
4085
-
"node": ">=0.6"
4086
}
4087
},
4088
"node_modules/ts-api-utils": {
···
4098
"typescript": ">=4.8.4"
4099
}
4100
},
4101
-
"node_modules/turbo-stream": {
4102
-
"version": "2.4.0",
4103
-
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
4104
-
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
4105
-
"license": "ISC"
4106
-
},
4107
"node_modules/type-check": {
4108
"version": "0.4.0",
4109
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
···
4117
"node": ">= 0.8.0"
4118
}
4119
},
4120
-
"node_modules/type-is": {
4121
-
"version": "2.0.1",
4122
-
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
4123
-
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
4124
-
"dev": true,
4125
-
"license": "MIT",
4126
-
"dependencies": {
4127
-
"content-type": "^1.0.5",
4128
-
"media-typer": "^1.1.0",
4129
-
"mime-types": "^3.0.0"
4130
-
},
4131
-
"engines": {
4132
-
"node": ">= 0.6"
4133
-
}
4134
-
},
4135
"node_modules/typescript": {
4136
"version": "5.7.3",
4137
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
···
4147
}
4148
},
4149
"node_modules/typescript-eslint": {
4150
-
"version": "8.31.1",
4151
-
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz",
4152
-
"integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==",
4153
"dev": true,
4154
"license": "MIT",
4155
"dependencies": {
4156
-
"@typescript-eslint/eslint-plugin": "8.31.1",
4157
-
"@typescript-eslint/parser": "8.31.1",
4158
-
"@typescript-eslint/utils": "8.31.1"
4159
},
4160
"engines": {
4161
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
4170
}
4171
},
4172
"node_modules/uint8arrays": {
4173
-
"version": "3.0.0",
4174
-
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
4175
-
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
4176
-
"license": "MIT",
4177
"dependencies": {
4178
-
"multiformats": "^9.4.2"
4179
}
4180
},
4181
-
"node_modules/unpipe": {
4182
-
"version": "1.0.0",
4183
-
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
4184
-
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
4185
"dev": true,
4186
-
"license": "MIT",
4187
-
"engines": {
4188
-
"node": ">= 0.8"
4189
-
}
4190
},
4191
"node_modules/update-browserslist-db": {
4192
"version": "1.1.3",
···
4229
"punycode": "^2.1.0"
4230
}
4231
},
4232
-
"node_modules/vary": {
4233
-
"version": "1.1.2",
4234
-
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
4235
-
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
4236
-
"dev": true,
4237
-
"license": "MIT",
4238
-
"engines": {
4239
-
"node": ">= 0.8"
4240
-
}
4241
-
},
4242
"node_modules/vite": {
4243
-
"version": "6.3.4",
4244
-
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
4245
-
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
4246
"dev": true,
4247
"license": "MIT",
4248
"dependencies": {
···
4315
}
4316
},
4317
"node_modules/vite/node_modules/fdir": {
4318
-
"version": "6.4.4",
4319
-
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
4320
-
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
4321
"dev": true,
4322
"license": "MIT",
4323
"peerDependencies": {
···
4368
"node": ">=0.10.0"
4369
}
4370
},
4371
-
"node_modules/wrappy": {
4372
-
"version": "1.0.2",
4373
-
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
4374
-
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
4375
"dev": true,
4376
-
"license": "ISC"
4377
},
4378
"node_modules/yallist": {
4379
"version": "3.1.1",
···
4382
"dev": true,
4383
"license": "ISC"
4384
},
4385
"node_modules/yocto-queue": {
4386
"version": "0.1.0",
4387
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
···
4396
}
4397
},
4398
"node_modules/zod": {
4399
-
"version": "3.24.3",
4400
-
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz",
4401
-
"integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==",
4402
"license": "MIT",
4403
"funding": {
4404
"url": "https://github.com/sponsors/colinhacks"
4405
-
}
4406
-
},
4407
-
"node_modules/zod-to-json-schema": {
4408
-
"version": "3.24.5",
4409
-
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
4410
-
"integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
4411
-
"dev": true,
4412
-
"license": "ISC",
4413
-
"peerDependencies": {
4414
-
"zod": "^3.24.1"
4415
}
4416
}
4417
}
···
1
{
2
"name": "atproto-migration",
3
+
"version": "0.1.0",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "atproto-migration",
9
+
"version": "0.1.0",
10
"dependencies": {
11
"@atproto/api": "^0.15.5",
12
+
"@atproto/crypto": "^0.4.4",
13
+
"multiformats": "^13.3.6",
14
"react": "^19.0.0",
15
"react-dom": "^19.0.0",
16
+
"react-router-dom": "^7.5.3",
17
+
"uint8arrays": "^5.1.0"
18
},
19
"devDependencies": {
20
+
"@eslint/eslintrc": "^3.3.1",
21
"@eslint/js": "^9.22.0",
22
+
"@humanwhocodes/module-importer": "^1.0.1",
23
+
"@types/node": "^22.15.14",
24
+
"@types/react": "^19.1.6",
25
+
"@types/react-dom": "^19.1.5",
26
+
"@typescript-eslint/eslint-plugin": "^8.33.0",
27
+
"@typescript-eslint/parser": "^8.32.0",
28
"@vitejs/plugin-react": "^4.3.4",
29
"eslint": "^9.22.0",
30
"eslint-plugin-react-hooks": "^5.2.0",
31
"eslint-plugin-react-refresh": "^0.4.19",
32
"globals": "^16.0.0",
33
+
"jiti": "^2.4.2",
34
+
"rollup-plugin-visualizer": "^6.0.1",
35
+
"terser": "^5.40.0",
36
"typescript": "~5.7.2",
37
"typescript-eslint": "^8.26.1",
38
"vite": "^6.3.1"
···
53
}
54
},
55
"node_modules/@atproto/api": {
56
+
"version": "0.15.10",
57
+
"resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.15.10.tgz",
58
+
"integrity": "sha512-/PsvYoYMA6VGAbMEOU2rOuaNQHkWPU6CVQAUDK2XRlIgFO2d21KEjZsZ4Z3lELvZlcw25fuMp7gLgFRijpk78w==",
59
"license": "MIT",
60
"dependencies": {
61
+
"@atproto/common-web": "^0.4.2",
62
+
"@atproto/lexicon": "^0.4.11",
63
"@atproto/syntax": "^0.4.0",
64
+
"@atproto/xrpc": "^0.7.0",
65
"await-lock": "^2.2.2",
66
"multiformats": "^9.9.0",
67
"tlds": "^1.234.0",
68
"zod": "^3.23.8"
69
}
70
},
71
+
"node_modules/@atproto/api/node_modules/multiformats": {
72
+
"version": "9.9.0",
73
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
74
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
75
+
"license": "(Apache-2.0 AND MIT)"
76
+
},
77
"node_modules/@atproto/common-web": {
78
+
"version": "0.4.2",
79
+
"resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.2.tgz",
80
+
"integrity": "sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw==",
81
"license": "MIT",
82
"dependencies": {
83
"graphemer": "^1.4.0",
···
86
"zod": "^3.23.8"
87
}
88
},
89
+
"node_modules/@atproto/common-web/node_modules/multiformats": {
90
+
"version": "9.9.0",
91
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
92
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
93
+
"license": "(Apache-2.0 AND MIT)"
94
+
},
95
+
"node_modules/@atproto/common-web/node_modules/uint8arrays": {
96
+
"version": "3.0.0",
97
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
98
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
99
+
"license": "MIT",
100
+
"dependencies": {
101
+
"multiformats": "^9.4.2"
102
+
}
103
+
},
104
+
"node_modules/@atproto/crypto": {
105
+
"version": "0.4.4",
106
+
"resolved": "https://registry.npmjs.org/@atproto/crypto/-/crypto-0.4.4.tgz",
107
+
"integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==",
108
+
"license": "MIT",
109
+
"dependencies": {
110
+
"@noble/curves": "^1.7.0",
111
+
"@noble/hashes": "^1.6.1",
112
+
"uint8arrays": "3.0.0"
113
+
},
114
+
"engines": {
115
+
"node": ">=18.7.0"
116
+
}
117
+
},
118
+
"node_modules/@atproto/crypto/node_modules/multiformats": {
119
+
"version": "9.9.0",
120
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
121
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
122
+
"license": "(Apache-2.0 AND MIT)"
123
+
},
124
+
"node_modules/@atproto/crypto/node_modules/uint8arrays": {
125
+
"version": "3.0.0",
126
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz",
127
+
"integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==",
128
+
"license": "MIT",
129
+
"dependencies": {
130
+
"multiformats": "^9.4.2"
131
+
}
132
+
},
133
"node_modules/@atproto/lexicon": {
134
+
"version": "0.4.11",
135
+
"resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.11.tgz",
136
+
"integrity": "sha512-btefdnvNz2Ao2I+qbmj0F06HC8IlrM/IBz6qOBS50r0S6uDf5tOO+Mv2tSVdimFkdzyDdLtBI1sV36ONxz2cOw==",
137
"license": "MIT",
138
"dependencies": {
139
+
"@atproto/common-web": "^0.4.2",
140
"@atproto/syntax": "^0.4.0",
141
"iso-datestring-validator": "^2.2.2",
142
"multiformats": "^9.9.0",
143
"zod": "^3.23.8"
144
}
145
},
146
+
"node_modules/@atproto/lexicon/node_modules/multiformats": {
147
+
"version": "9.9.0",
148
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
149
+
"integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==",
150
+
"license": "(Apache-2.0 AND MIT)"
151
+
},
152
"node_modules/@atproto/syntax": {
153
"version": "0.4.0",
154
"resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.0.tgz",
···
156
"license": "MIT"
157
},
158
"node_modules/@atproto/xrpc": {
159
+
"version": "0.7.0",
160
+
"resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.0.tgz",
161
+
"integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==",
162
"license": "MIT",
163
"dependencies": {
164
+
"@atproto/lexicon": "^0.4.11",
165
"zod": "^3.23.8"
166
}
167
},
···
181
}
182
},
183
"node_modules/@babel/compat-data": {
184
+
"version": "7.27.3",
185
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz",
186
+
"integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==",
187
"dev": true,
188
"license": "MIT",
189
"engines": {
···
191
}
192
},
193
"node_modules/@babel/core": {
194
+
"version": "7.27.3",
195
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz",
196
+
"integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==",
197
"dev": true,
198
"license": "MIT",
199
"dependencies": {
200
"@ampproject/remapping": "^2.2.0",
201
"@babel/code-frame": "^7.27.1",
202
+
"@babel/generator": "^7.27.3",
203
+
"@babel/helper-compilation-targets": "^7.27.2",
204
+
"@babel/helper-module-transforms": "^7.27.3",
205
+
"@babel/helpers": "^7.27.3",
206
+
"@babel/parser": "^7.27.3",
207
+
"@babel/template": "^7.27.2",
208
+
"@babel/traverse": "^7.27.3",
209
+
"@babel/types": "^7.27.3",
210
"convert-source-map": "^2.0.0",
211
"debug": "^4.1.0",
212
"gensync": "^1.0.0-beta.2",
···
221
"url": "https://opencollective.com/babel"
222
}
223
},
224
+
"node_modules/@babel/core/node_modules/semver": {
225
+
"version": "6.3.1",
226
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
227
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
228
+
"dev": true,
229
+
"license": "ISC",
230
+
"bin": {
231
+
"semver": "bin/semver.js"
232
+
}
233
+
},
234
"node_modules/@babel/generator": {
235
+
"version": "7.27.3",
236
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
237
+
"integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
238
"dev": true,
239
"license": "MIT",
240
"dependencies": {
241
+
"@babel/parser": "^7.27.3",
242
+
"@babel/types": "^7.27.3",
243
"@jridgewell/gen-mapping": "^0.3.5",
244
"@jridgewell/trace-mapping": "^0.3.25",
245
"jsesc": "^3.0.2"
···
249
}
250
},
251
"node_modules/@babel/helper-compilation-targets": {
252
+
"version": "7.27.2",
253
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
254
+
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
255
"dev": true,
256
"license": "MIT",
257
"dependencies": {
258
+
"@babel/compat-data": "^7.27.2",
259
"@babel/helper-validator-option": "^7.27.1",
260
"browserslist": "^4.24.0",
261
"lru-cache": "^5.1.1",
···
265
"node": ">=6.9.0"
266
}
267
},
268
+
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
269
+
"version": "6.3.1",
270
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
271
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
272
+
"dev": true,
273
+
"license": "ISC",
274
+
"bin": {
275
+
"semver": "bin/semver.js"
276
+
}
277
+
},
278
"node_modules/@babel/helper-module-imports": {
279
"version": "7.27.1",
280
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
···
290
}
291
},
292
"node_modules/@babel/helper-module-transforms": {
293
+
"version": "7.27.3",
294
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
295
+
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
296
"dev": true,
297
"license": "MIT",
298
"dependencies": {
299
"@babel/helper-module-imports": "^7.27.1",
300
"@babel/helper-validator-identifier": "^7.27.1",
301
+
"@babel/traverse": "^7.27.3"
302
},
303
"engines": {
304
"node": ">=6.9.0"
···
348
}
349
},
350
"node_modules/@babel/helpers": {
351
+
"version": "7.27.3",
352
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz",
353
+
"integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==",
354
"dev": true,
355
"license": "MIT",
356
"dependencies": {
357
+
"@babel/template": "^7.27.2",
358
+
"@babel/types": "^7.27.3"
359
},
360
"engines": {
361
"node": ">=6.9.0"
362
}
363
},
364
"node_modules/@babel/parser": {
365
+
"version": "7.27.3",
366
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
367
+
"integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
368
"dev": true,
369
"license": "MIT",
370
"dependencies": {
371
+
"@babel/types": "^7.27.3"
372
},
373
"bin": {
374
"parser": "bin/babel-parser.js"
···
410
}
411
},
412
"node_modules/@babel/template": {
413
+
"version": "7.27.2",
414
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
415
+
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
416
"dev": true,
417
"license": "MIT",
418
"dependencies": {
419
"@babel/code-frame": "^7.27.1",
420
+
"@babel/parser": "^7.27.2",
421
"@babel/types": "^7.27.1"
422
},
423
"engines": {
···
425
}
426
},
427
"node_modules/@babel/traverse": {
428
+
"version": "7.27.3",
429
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz",
430
+
"integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==",
431
"dev": true,
432
"license": "MIT",
433
"dependencies": {
434
"@babel/code-frame": "^7.27.1",
435
+
"@babel/generator": "^7.27.3",
436
+
"@babel/parser": "^7.27.3",
437
+
"@babel/template": "^7.27.2",
438
+
"@babel/types": "^7.27.3",
439
"debug": "^4.3.1",
440
"globals": "^11.1.0"
441
},
···
454
}
455
},
456
"node_modules/@babel/types": {
457
+
"version": "7.27.3",
458
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
459
+
"integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
460
"dev": true,
461
"license": "MIT",
462
"dependencies": {
···
468
}
469
},
470
"node_modules/@esbuild/aix-ppc64": {
471
+
"version": "0.25.5",
472
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
473
+
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
474
"cpu": [
475
"ppc64"
476
],
···
485
}
486
},
487
"node_modules/@esbuild/android-arm": {
488
+
"version": "0.25.5",
489
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
490
+
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
491
"cpu": [
492
"arm"
493
],
···
502
}
503
},
504
"node_modules/@esbuild/android-arm64": {
505
+
"version": "0.25.5",
506
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
507
+
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
508
"cpu": [
509
"arm64"
510
],
···
519
}
520
},
521
"node_modules/@esbuild/android-x64": {
522
+
"version": "0.25.5",
523
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
524
+
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
525
"cpu": [
526
"x64"
527
],
···
536
}
537
},
538
"node_modules/@esbuild/darwin-arm64": {
539
+
"version": "0.25.5",
540
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
541
+
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
542
"cpu": [
543
"arm64"
544
],
···
553
}
554
},
555
"node_modules/@esbuild/darwin-x64": {
556
+
"version": "0.25.5",
557
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
558
+
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
559
"cpu": [
560
"x64"
561
],
···
570
}
571
},
572
"node_modules/@esbuild/freebsd-arm64": {
573
+
"version": "0.25.5",
574
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
575
+
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
576
"cpu": [
577
"arm64"
578
],
···
587
}
588
},
589
"node_modules/@esbuild/freebsd-x64": {
590
+
"version": "0.25.5",
591
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
592
+
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
593
"cpu": [
594
"x64"
595
],
···
604
}
605
},
606
"node_modules/@esbuild/linux-arm": {
607
+
"version": "0.25.5",
608
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
609
+
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
610
"cpu": [
611
"arm"
612
],
···
621
}
622
},
623
"node_modules/@esbuild/linux-arm64": {
624
+
"version": "0.25.5",
625
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
626
+
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
627
"cpu": [
628
"arm64"
629
],
···
638
}
639
},
640
"node_modules/@esbuild/linux-ia32": {
641
+
"version": "0.25.5",
642
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
643
+
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
644
"cpu": [
645
"ia32"
646
],
···
655
}
656
},
657
"node_modules/@esbuild/linux-loong64": {
658
+
"version": "0.25.5",
659
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
660
+
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
661
"cpu": [
662
"loong64"
663
],
···
672
}
673
},
674
"node_modules/@esbuild/linux-mips64el": {
675
+
"version": "0.25.5",
676
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
677
+
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
678
"cpu": [
679
"mips64el"
680
],
···
689
}
690
},
691
"node_modules/@esbuild/linux-ppc64": {
692
+
"version": "0.25.5",
693
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
694
+
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
695
"cpu": [
696
"ppc64"
697
],
···
706
}
707
},
708
"node_modules/@esbuild/linux-riscv64": {
709
+
"version": "0.25.5",
710
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
711
+
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
712
"cpu": [
713
"riscv64"
714
],
···
723
}
724
},
725
"node_modules/@esbuild/linux-s390x": {
726
+
"version": "0.25.5",
727
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
728
+
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
729
"cpu": [
730
"s390x"
731
],
···
740
}
741
},
742
"node_modules/@esbuild/linux-x64": {
743
+
"version": "0.25.5",
744
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
745
+
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
746
"cpu": [
747
"x64"
748
],
···
757
}
758
},
759
"node_modules/@esbuild/netbsd-arm64": {
760
+
"version": "0.25.5",
761
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
762
+
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
763
"cpu": [
764
"arm64"
765
],
···
774
}
775
},
776
"node_modules/@esbuild/netbsd-x64": {
777
+
"version": "0.25.5",
778
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
779
+
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
780
"cpu": [
781
"x64"
782
],
···
791
}
792
},
793
"node_modules/@esbuild/openbsd-arm64": {
794
+
"version": "0.25.5",
795
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
796
+
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
797
"cpu": [
798
"arm64"
799
],
···
808
}
809
},
810
"node_modules/@esbuild/openbsd-x64": {
811
+
"version": "0.25.5",
812
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
813
+
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
814
"cpu": [
815
"x64"
816
],
···
825
}
826
},
827
"node_modules/@esbuild/sunos-x64": {
828
+
"version": "0.25.5",
829
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
830
+
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
831
"cpu": [
832
"x64"
833
],
···
842
}
843
},
844
"node_modules/@esbuild/win32-arm64": {
845
+
"version": "0.25.5",
846
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
847
+
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
848
"cpu": [
849
"arm64"
850
],
···
859
}
860
},
861
"node_modules/@esbuild/win32-ia32": {
862
+
"version": "0.25.5",
863
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
864
+
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
865
"cpu": [
866
"ia32"
867
],
···
876
}
877
},
878
"node_modules/@esbuild/win32-x64": {
879
+
"version": "0.25.5",
880
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
881
+
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
882
"cpu": [
883
"x64"
884
],
···
911
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
912
}
913
},
914
"node_modules/@eslint-community/regexpp": {
915
"version": "4.12.1",
916
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
···
947
}
948
},
949
"node_modules/@eslint/core": {
950
+
"version": "0.14.0",
951
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz",
952
+
"integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==",
953
"dev": true,
954
"license": "Apache-2.0",
955
"dependencies": {
···
997
}
998
},
999
"node_modules/@eslint/js": {
1000
+
"version": "9.27.0",
1001
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
1002
+
"integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
1003
"dev": true,
1004
"license": "MIT",
1005
"engines": {
1006
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1007
+
},
1008
+
"funding": {
1009
+
"url": "https://eslint.org/donate"
1010
}
1011
},
1012
"node_modules/@eslint/object-schema": {
···
1020
}
1021
},
1022
"node_modules/@eslint/plugin-kit": {
1023
+
"version": "0.3.1",
1024
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz",
1025
+
"integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==",
1026
"dev": true,
1027
"license": "Apache-2.0",
1028
"dependencies": {
1029
+
"@eslint/core": "^0.14.0",
1030
"levn": "^0.4.1"
1031
},
1032
"engines": {
···
1086
}
1087
},
1088
"node_modules/@humanwhocodes/retry": {
1089
+
"version": "0.4.3",
1090
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
1091
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
1092
"dev": true,
1093
"license": "Apache-2.0",
1094
"engines": {
···
1134
"node": ">=6.0.0"
1135
}
1136
},
1137
+
"node_modules/@jridgewell/source-map": {
1138
+
"version": "0.3.6",
1139
+
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
1140
+
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
1141
+
"dev": true,
1142
+
"license": "MIT",
1143
+
"dependencies": {
1144
+
"@jridgewell/gen-mapping": "^0.3.5",
1145
+
"@jridgewell/trace-mapping": "^0.3.25"
1146
+
}
1147
+
},
1148
"node_modules/@jridgewell/sourcemap-codec": {
1149
"version": "1.5.0",
1150
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
···
1163
"@jridgewell/sourcemap-codec": "^1.4.14"
1164
}
1165
},
1166
+
"node_modules/@noble/curves": {
1167
+
"version": "1.9.1",
1168
+
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
1169
+
"integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==",
1170
"license": "MIT",
1171
"dependencies": {
1172
+
"@noble/hashes": "1.8.0"
1173
+
},
1174
+
"engines": {
1175
+
"node": "^14.21.3 || >=16"
1176
},
1177
+
"funding": {
1178
+
"url": "https://paulmillr.com/funding/"
1179
+
}
1180
+
},
1181
+
"node_modules/@noble/hashes": {
1182
+
"version": "1.8.0",
1183
+
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
1184
+
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
1185
+
"license": "MIT",
1186
"engines": {
1187
+
"node": "^14.21.3 || >=16"
1188
+
},
1189
+
"funding": {
1190
+
"url": "https://paulmillr.com/funding/"
1191
}
1192
},
1193
"node_modules/@nodelib/fs.scandir": {
···
1228
"node": ">= 8"
1229
}
1230
},
1231
+
"node_modules/@rolldown/pluginutils": {
1232
+
"version": "1.0.0-beta.9",
1233
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
1234
+
"integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
1235
+
"dev": true,
1236
+
"license": "MIT"
1237
+
},
1238
"node_modules/@rollup/rollup-android-arm-eabi": {
1239
+
"version": "4.41.1",
1240
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
1241
+
"integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
1242
"cpu": [
1243
"arm"
1244
],
···
1250
]
1251
},
1252
"node_modules/@rollup/rollup-android-arm64": {
1253
+
"version": "4.41.1",
1254
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
1255
+
"integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
1256
"cpu": [
1257
"arm64"
1258
],
···
1264
]
1265
},
1266
"node_modules/@rollup/rollup-darwin-arm64": {
1267
+
"version": "4.41.1",
1268
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
1269
+
"integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
1270
"cpu": [
1271
"arm64"
1272
],
···
1278
]
1279
},
1280
"node_modules/@rollup/rollup-darwin-x64": {
1281
+
"version": "4.41.1",
1282
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
1283
+
"integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
1284
"cpu": [
1285
"x64"
1286
],
···
1292
]
1293
},
1294
"node_modules/@rollup/rollup-freebsd-arm64": {
1295
+
"version": "4.41.1",
1296
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
1297
+
"integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
1298
"cpu": [
1299
"arm64"
1300
],
···
1306
]
1307
},
1308
"node_modules/@rollup/rollup-freebsd-x64": {
1309
+
"version": "4.41.1",
1310
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
1311
+
"integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
1312
"cpu": [
1313
"x64"
1314
],
···
1320
]
1321
},
1322
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
1323
+
"version": "4.41.1",
1324
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
1325
+
"integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
1326
"cpu": [
1327
"arm"
1328
],
···
1334
]
1335
},
1336
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
1337
+
"version": "4.41.1",
1338
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
1339
+
"integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
1340
"cpu": [
1341
"arm"
1342
],
···
1348
]
1349
},
1350
"node_modules/@rollup/rollup-linux-arm64-gnu": {
1351
+
"version": "4.41.1",
1352
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
1353
+
"integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
1354
"cpu": [
1355
"arm64"
1356
],
···
1362
]
1363
},
1364
"node_modules/@rollup/rollup-linux-arm64-musl": {
1365
+
"version": "4.41.1",
1366
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
1367
+
"integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
1368
"cpu": [
1369
"arm64"
1370
],
···
1376
]
1377
},
1378
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
1379
+
"version": "4.41.1",
1380
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
1381
+
"integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
1382
"cpu": [
1383
"loong64"
1384
],
···
1390
]
1391
},
1392
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
1393
+
"version": "4.41.1",
1394
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
1395
+
"integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
1396
"cpu": [
1397
"ppc64"
1398
],
···
1404
]
1405
},
1406
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
1407
+
"version": "4.41.1",
1408
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
1409
+
"integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
1410
"cpu": [
1411
"riscv64"
1412
],
···
1418
]
1419
},
1420
"node_modules/@rollup/rollup-linux-riscv64-musl": {
1421
+
"version": "4.41.1",
1422
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
1423
+
"integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
1424
"cpu": [
1425
"riscv64"
1426
],
···
1432
]
1433
},
1434
"node_modules/@rollup/rollup-linux-s390x-gnu": {
1435
+
"version": "4.41.1",
1436
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
1437
+
"integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
1438
"cpu": [
1439
"s390x"
1440
],
···
1446
]
1447
},
1448
"node_modules/@rollup/rollup-linux-x64-gnu": {
1449
+
"version": "4.41.1",
1450
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
1451
+
"integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
1452
"cpu": [
1453
"x64"
1454
],
···
1460
]
1461
},
1462
"node_modules/@rollup/rollup-linux-x64-musl": {
1463
+
"version": "4.41.1",
1464
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
1465
+
"integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
1466
"cpu": [
1467
"x64"
1468
],
···
1474
]
1475
},
1476
"node_modules/@rollup/rollup-win32-arm64-msvc": {
1477
+
"version": "4.41.1",
1478
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
1479
+
"integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
1480
"cpu": [
1481
"arm64"
1482
],
···
1488
]
1489
},
1490
"node_modules/@rollup/rollup-win32-ia32-msvc": {
1491
+
"version": "4.41.1",
1492
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
1493
+
"integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
1494
"cpu": [
1495
"ia32"
1496
],
···
1502
]
1503
},
1504
"node_modules/@rollup/rollup-win32-x64-msvc": {
1505
+
"version": "4.41.1",
1506
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
1507
+
"integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
1508
"cpu": [
1509
"x64"
1510
],
···
1574
"dev": true,
1575
"license": "MIT"
1576
},
1577
+
"node_modules/@types/node": {
1578
+
"version": "22.15.23",
1579
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.23.tgz",
1580
+
"integrity": "sha512-7Ec1zaFPF4RJ0eXu1YT/xgiebqwqoJz8rYPDi/O2BcZ++Wpt0Kq9cl0eg6NN6bYbPnR67ZLo7St5Q3UK0SnARw==",
1581
+
"dev": true,
1582
+
"license": "MIT",
1583
+
"dependencies": {
1584
+
"undici-types": "~6.21.0"
1585
+
}
1586
+
},
1587
"node_modules/@types/react": {
1588
+
"version": "19.1.6",
1589
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
1590
+
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
1591
"dev": true,
1592
"license": "MIT",
1593
"dependencies": {
···
1595
}
1596
},
1597
"node_modules/@types/react-dom": {
1598
+
"version": "19.1.5",
1599
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
1600
+
"integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
1601
"dev": true,
1602
"license": "MIT",
1603
"peerDependencies": {
···
1605
}
1606
},
1607
"node_modules/@typescript-eslint/eslint-plugin": {
1608
+
"version": "8.33.0",
1609
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz",
1610
+
"integrity": "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==",
1611
"dev": true,
1612
"license": "MIT",
1613
"dependencies": {
1614
"@eslint-community/regexpp": "^4.10.0",
1615
+
"@typescript-eslint/scope-manager": "8.33.0",
1616
+
"@typescript-eslint/type-utils": "8.33.0",
1617
+
"@typescript-eslint/utils": "8.33.0",
1618
+
"@typescript-eslint/visitor-keys": "8.33.0",
1619
"graphemer": "^1.4.0",
1620
+
"ignore": "^7.0.0",
1621
"natural-compare": "^1.4.0",
1622
+
"ts-api-utils": "^2.1.0"
1623
},
1624
"engines": {
1625
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1629
"url": "https://opencollective.com/typescript-eslint"
1630
},
1631
"peerDependencies": {
1632
+
"@typescript-eslint/parser": "^8.33.0",
1633
"eslint": "^8.57.0 || ^9.0.0",
1634
"typescript": ">=4.8.4 <5.9.0"
1635
}
1636
},
1637
+
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
1638
+
"version": "7.0.4",
1639
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz",
1640
+
"integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==",
1641
+
"dev": true,
1642
+
"license": "MIT",
1643
+
"engines": {
1644
+
"node": ">= 4"
1645
+
}
1646
+
},
1647
"node_modules/@typescript-eslint/parser": {
1648
+
"version": "8.33.0",
1649
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.0.tgz",
1650
+
"integrity": "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==",
1651
"dev": true,
1652
"license": "MIT",
1653
"dependencies": {
1654
+
"@typescript-eslint/scope-manager": "8.33.0",
1655
+
"@typescript-eslint/types": "8.33.0",
1656
+
"@typescript-eslint/typescript-estree": "8.33.0",
1657
+
"@typescript-eslint/visitor-keys": "8.33.0",
1658
"debug": "^4.3.4"
1659
},
1660
"engines": {
···
1669
"typescript": ">=4.8.4 <5.9.0"
1670
}
1671
},
1672
+
"node_modules/@typescript-eslint/project-service": {
1673
+
"version": "8.33.0",
1674
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz",
1675
+
"integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==",
1676
+
"dev": true,
1677
+
"license": "MIT",
1678
+
"dependencies": {
1679
+
"@typescript-eslint/tsconfig-utils": "^8.33.0",
1680
+
"@typescript-eslint/types": "^8.33.0",
1681
+
"debug": "^4.3.4"
1682
+
},
1683
+
"engines": {
1684
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1685
+
},
1686
+
"funding": {
1687
+
"type": "opencollective",
1688
+
"url": "https://opencollective.com/typescript-eslint"
1689
+
}
1690
+
},
1691
"node_modules/@typescript-eslint/scope-manager": {
1692
+
"version": "8.33.0",
1693
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz",
1694
+
"integrity": "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==",
1695
"dev": true,
1696
"license": "MIT",
1697
"dependencies": {
1698
+
"@typescript-eslint/types": "8.33.0",
1699
+
"@typescript-eslint/visitor-keys": "8.33.0"
1700
},
1701
"engines": {
1702
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1706
"url": "https://opencollective.com/typescript-eslint"
1707
}
1708
},
1709
+
"node_modules/@typescript-eslint/tsconfig-utils": {
1710
+
"version": "8.33.0",
1711
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz",
1712
+
"integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==",
1713
+
"dev": true,
1714
+
"license": "MIT",
1715
+
"engines": {
1716
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1717
+
},
1718
+
"funding": {
1719
+
"type": "opencollective",
1720
+
"url": "https://opencollective.com/typescript-eslint"
1721
+
},
1722
+
"peerDependencies": {
1723
+
"typescript": ">=4.8.4 <5.9.0"
1724
+
}
1725
+
},
1726
"node_modules/@typescript-eslint/type-utils": {
1727
+
"version": "8.33.0",
1728
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz",
1729
+
"integrity": "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==",
1730
"dev": true,
1731
"license": "MIT",
1732
"dependencies": {
1733
+
"@typescript-eslint/typescript-estree": "8.33.0",
1734
+
"@typescript-eslint/utils": "8.33.0",
1735
"debug": "^4.3.4",
1736
+
"ts-api-utils": "^2.1.0"
1737
},
1738
"engines": {
1739
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1748
}
1749
},
1750
"node_modules/@typescript-eslint/types": {
1751
+
"version": "8.33.0",
1752
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz",
1753
+
"integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==",
1754
"dev": true,
1755
"license": "MIT",
1756
"engines": {
···
1762
}
1763
},
1764
"node_modules/@typescript-eslint/typescript-estree": {
1765
+
"version": "8.33.0",
1766
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz",
1767
+
"integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==",
1768
"dev": true,
1769
"license": "MIT",
1770
"dependencies": {
1771
+
"@typescript-eslint/project-service": "8.33.0",
1772
+
"@typescript-eslint/tsconfig-utils": "8.33.0",
1773
+
"@typescript-eslint/types": "8.33.0",
1774
+
"@typescript-eslint/visitor-keys": "8.33.0",
1775
"debug": "^4.3.4",
1776
"fast-glob": "^3.3.2",
1777
"is-glob": "^4.0.3",
1778
"minimatch": "^9.0.4",
1779
"semver": "^7.6.0",
1780
+
"ts-api-utils": "^2.1.0"
1781
},
1782
"engines": {
1783
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1816
"url": "https://github.com/sponsors/isaacs"
1817
}
1818
},
1819
"node_modules/@typescript-eslint/utils": {
1820
+
"version": "8.33.0",
1821
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.0.tgz",
1822
+
"integrity": "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==",
1823
"dev": true,
1824
"license": "MIT",
1825
"dependencies": {
1826
+
"@eslint-community/eslint-utils": "^4.7.0",
1827
+
"@typescript-eslint/scope-manager": "8.33.0",
1828
+
"@typescript-eslint/types": "8.33.0",
1829
+
"@typescript-eslint/typescript-estree": "8.33.0"
1830
},
1831
"engines": {
1832
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
1841
}
1842
},
1843
"node_modules/@typescript-eslint/visitor-keys": {
1844
+
"version": "8.33.0",
1845
+
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz",
1846
+
"integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==",
1847
"dev": true,
1848
"license": "MIT",
1849
"dependencies": {
1850
+
"@typescript-eslint/types": "8.33.0",
1851
"eslint-visitor-keys": "^4.2.0"
1852
},
1853
"engines": {
···
1858
"url": "https://opencollective.com/typescript-eslint"
1859
}
1860
},
1861
+
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
1862
+
"version": "4.2.0",
1863
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
1864
+
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
1865
+
"dev": true,
1866
+
"license": "Apache-2.0",
1867
+
"engines": {
1868
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1869
+
},
1870
+
"funding": {
1871
+
"url": "https://opencollective.com/eslint"
1872
+
}
1873
+
},
1874
"node_modules/@vitejs/plugin-react": {
1875
+
"version": "4.5.0",
1876
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
1877
+
"integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==",
1878
"dev": true,
1879
"license": "MIT",
1880
"dependencies": {
1881
"@babel/core": "^7.26.10",
1882
"@babel/plugin-transform-react-jsx-self": "^7.25.9",
1883
"@babel/plugin-transform-react-jsx-source": "^7.25.9",
1884
+
"@rolldown/pluginutils": "1.0.0-beta.9",
1885
"@types/babel__core": "^7.20.5",
1886
"react-refresh": "^0.17.0"
1887
},
···
1892
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
1893
}
1894
},
1895
"node_modules/acorn": {
1896
"version": "8.14.1",
1897
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
···
1932
"url": "https://github.com/sponsors/epoberezkin"
1933
}
1934
},
1935
+
"node_modules/ansi-regex": {
1936
+
"version": "5.0.1",
1937
+
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1938
+
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1939
+
"dev": true,
1940
+
"license": "MIT",
1941
+
"engines": {
1942
+
"node": ">=8"
1943
+
}
1944
+
},
1945
"node_modules/ansi-styles": {
1946
"version": "4.3.0",
1947
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
···
1978
"dev": true,
1979
"license": "MIT"
1980
},
1981
"node_modules/brace-expansion": {
1982
"version": "1.1.11",
1983
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
···
2035
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
2036
}
2037
},
2038
+
"node_modules/buffer-from": {
2039
+
"version": "1.1.2",
2040
+
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
2041
+
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
2042
"dev": true,
2043
+
"license": "MIT"
2044
},
2045
"node_modules/callsites": {
2046
"version": "3.1.0",
···
2053
}
2054
},
2055
"node_modules/caniuse-lite": {
2056
+
"version": "1.0.30001718",
2057
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
2058
+
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
2059
"dev": true,
2060
"funding": [
2061
{
···
2090
"url": "https://github.com/chalk/chalk?sponsor=1"
2091
}
2092
},
2093
+
"node_modules/cliui": {
2094
+
"version": "8.0.1",
2095
+
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
2096
+
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
2097
+
"dev": true,
2098
+
"license": "ISC",
2099
+
"dependencies": {
2100
+
"string-width": "^4.2.0",
2101
+
"strip-ansi": "^6.0.1",
2102
+
"wrap-ansi": "^7.0.0"
2103
+
},
2104
+
"engines": {
2105
+
"node": ">=12"
2106
+
}
2107
+
},
2108
"node_modules/color-convert": {
2109
"version": "2.0.1",
2110
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
···
2125
"dev": true,
2126
"license": "MIT"
2127
},
2128
+
"node_modules/commander": {
2129
+
"version": "2.20.3",
2130
+
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
2131
+
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
2132
+
"dev": true,
2133
+
"license": "MIT"
2134
+
},
2135
"node_modules/concat-map": {
2136
"version": "0.0.1",
2137
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
···
2139
"dev": true,
2140
"license": "MIT"
2141
},
2142
"node_modules/convert-source-map": {
2143
"version": "2.0.0",
2144
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
···
2147
"license": "MIT"
2148
},
2149
"node_modules/cookie": {
2150
+
"version": "1.0.2",
2151
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
2152
+
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
2153
"license": "MIT",
2154
"engines": {
2155
+
"node": ">=18"
2156
}
2157
},
2158
"node_modules/cross-spawn": {
···
2178
"license": "MIT"
2179
},
2180
"node_modules/debug": {
2181
+
"version": "4.4.1",
2182
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
2183
+
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
2184
"dev": true,
2185
"license": "MIT",
2186
"dependencies": {
···
2202
"dev": true,
2203
"license": "MIT"
2204
},
2205
+
"node_modules/define-lazy-prop": {
2206
"version": "2.0.0",
2207
+
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
2208
+
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
2209
"dev": true,
2210
"license": "MIT",
2211
"engines": {
2212
+
"node": ">=8"
2213
}
2214
},
2215
"node_modules/electron-to-chromium": {
2216
+
"version": "1.5.159",
2217
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.159.tgz",
2218
+
"integrity": "sha512-CEvHptWAMV5p6GJ0Lq8aheyvVbfzVrv5mmidu1D3pidoVNkB3tTBsTMVtPJ+rzRK5oV229mCLz9Zj/hNvU8GBA==",
2219
"dev": true,
2220
"license": "ISC"
2221
},
2222
+
"node_modules/emoji-regex": {
2223
+
"version": "8.0.0",
2224
+
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
2225
+
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
2226
"dev": true,
2227
+
"license": "MIT"
2228
},
2229
"node_modules/esbuild": {
2230
+
"version": "0.25.5",
2231
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
2232
+
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
2233
"dev": true,
2234
"hasInstallScript": true,
2235
"license": "MIT",
···
2240
"node": ">=18"
2241
},
2242
"optionalDependencies": {
2243
+
"@esbuild/aix-ppc64": "0.25.5",
2244
+
"@esbuild/android-arm": "0.25.5",
2245
+
"@esbuild/android-arm64": "0.25.5",
2246
+
"@esbuild/android-x64": "0.25.5",
2247
+
"@esbuild/darwin-arm64": "0.25.5",
2248
+
"@esbuild/darwin-x64": "0.25.5",
2249
+
"@esbuild/freebsd-arm64": "0.25.5",
2250
+
"@esbuild/freebsd-x64": "0.25.5",
2251
+
"@esbuild/linux-arm": "0.25.5",
2252
+
"@esbuild/linux-arm64": "0.25.5",
2253
+
"@esbuild/linux-ia32": "0.25.5",
2254
+
"@esbuild/linux-loong64": "0.25.5",
2255
+
"@esbuild/linux-mips64el": "0.25.5",
2256
+
"@esbuild/linux-ppc64": "0.25.5",
2257
+
"@esbuild/linux-riscv64": "0.25.5",
2258
+
"@esbuild/linux-s390x": "0.25.5",
2259
+
"@esbuild/linux-x64": "0.25.5",
2260
+
"@esbuild/netbsd-arm64": "0.25.5",
2261
+
"@esbuild/netbsd-x64": "0.25.5",
2262
+
"@esbuild/openbsd-arm64": "0.25.5",
2263
+
"@esbuild/openbsd-x64": "0.25.5",
2264
+
"@esbuild/sunos-x64": "0.25.5",
2265
+
"@esbuild/win32-arm64": "0.25.5",
2266
+
"@esbuild/win32-ia32": "0.25.5",
2267
+
"@esbuild/win32-x64": "0.25.5"
2268
}
2269
},
2270
"node_modules/escalade": {
···
2277
"node": ">=6"
2278
}
2279
},
2280
"node_modules/escape-string-regexp": {
2281
"version": "4.0.0",
2282
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
···
2291
}
2292
},
2293
"node_modules/eslint": {
2294
+
"version": "9.27.0",
2295
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
2296
+
"integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
2297
"dev": true,
2298
"license": "MIT",
2299
"dependencies": {
···
2301
"@eslint-community/regexpp": "^4.12.1",
2302
"@eslint/config-array": "^0.20.0",
2303
"@eslint/config-helpers": "^0.2.1",
2304
+
"@eslint/core": "^0.14.0",
2305
"@eslint/eslintrc": "^3.3.1",
2306
+
"@eslint/js": "9.27.0",
2307
+
"@eslint/plugin-kit": "^0.3.1",
2308
"@humanfs/node": "^0.16.6",
2309
"@humanwhocodes/module-importer": "^1.0.1",
2310
"@humanwhocodes/retry": "^0.4.2",
2311
"@types/estree": "^1.0.6",
2312
"@types/json-schema": "^7.0.15",
2313
"ajv": "^6.12.4",
···
2331
"lodash.merge": "^4.6.2",
2332
"minimatch": "^3.1.2",
2333
"natural-compare": "^1.4.0",
2334
+
"optionator": "^0.9.3"
2335
},
2336
"bin": {
2337
"eslint": "bin/eslint.js"
···
2392
}
2393
},
2394
"node_modules/eslint-visitor-keys": {
2395
+
"version": "3.4.3",
2396
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
2397
+
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
2398
+
"dev": true,
2399
+
"license": "Apache-2.0",
2400
+
"engines": {
2401
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2402
+
},
2403
+
"funding": {
2404
+
"url": "https://opencollective.com/eslint"
2405
+
}
2406
+
},
2407
+
"node_modules/eslint/node_modules/eslint-visitor-keys": {
2408
"version": "4.2.0",
2409
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
2410
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
···
2435
"url": "https://opencollective.com/eslint"
2436
}
2437
},
2438
+
"node_modules/espree/node_modules/eslint-visitor-keys": {
2439
+
"version": "4.2.0",
2440
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
2441
+
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
2442
+
"dev": true,
2443
+
"license": "Apache-2.0",
2444
+
"engines": {
2445
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2446
+
},
2447
+
"funding": {
2448
+
"url": "https://opencollective.com/eslint"
2449
+
}
2450
+
},
2451
"node_modules/esquery": {
2452
"version": "1.6.0",
2453
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
···
2494
"node": ">=0.10.0"
2495
}
2496
},
2497
"node_modules/fast-deep-equal": {
2498
"version": "3.1.3",
2499
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
···
2581
"node": ">=8"
2582
}
2583
},
2584
"node_modules/find-up": {
2585
"version": "5.0.0",
2586
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
···
2619
"dev": true,
2620
"license": "ISC"
2621
},
2622
"node_modules/fsevents": {
2623
"version": "2.3.3",
2624
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
···
2634
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
2635
}
2636
},
2637
"node_modules/gensync": {
2638
"version": "1.0.0-beta.2",
2639
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
···
2644
"node": ">=6.9.0"
2645
}
2646
},
2647
+
"node_modules/get-caller-file": {
2648
+
"version": "2.0.5",
2649
+
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
2650
+
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
2651
"dev": true,
2652
+
"license": "ISC",
2653
"engines": {
2654
+
"node": "6.* || 8.* || >= 10.*"
2655
}
2656
},
2657
"node_modules/glob-parent": {
···
2668
}
2669
},
2670
"node_modules/globals": {
2671
+
"version": "16.2.0",
2672
+
"resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
2673
+
"integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
2674
"dev": true,
2675
"license": "MIT",
2676
"engines": {
···
2680
"url": "https://github.com/sponsors/sindresorhus"
2681
}
2682
},
2683
"node_modules/graphemer": {
2684
"version": "1.4.0",
2685
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
···
2696
"node": ">=8"
2697
}
2698
},
2699
"node_modules/ignore": {
2700
"version": "5.3.2",
2701
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
···
2733
"node": ">=0.8.19"
2734
}
2735
},
2736
+
"node_modules/is-docker": {
2737
+
"version": "2.2.1",
2738
+
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
2739
+
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
2740
"dev": true,
2741
"license": "MIT",
2742
+
"bin": {
2743
+
"is-docker": "cli.js"
2744
+
},
2745
"engines": {
2746
+
"node": ">=8"
2747
+
},
2748
+
"funding": {
2749
+
"url": "https://github.com/sponsors/sindresorhus"
2750
}
2751
},
2752
"node_modules/is-extglob": {
···
2759
"node": ">=0.10.0"
2760
}
2761
},
2762
+
"node_modules/is-fullwidth-code-point": {
2763
+
"version": "3.0.0",
2764
+
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
2765
+
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
2766
+
"dev": true,
2767
+
"license": "MIT",
2768
+
"engines": {
2769
+
"node": ">=8"
2770
+
}
2771
+
},
2772
"node_modules/is-glob": {
2773
"version": "4.0.3",
2774
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
···
2792
"node": ">=0.12.0"
2793
}
2794
},
2795
+
"node_modules/is-wsl": {
2796
+
"version": "2.2.0",
2797
+
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
2798
+
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
2799
"dev": true,
2800
+
"license": "MIT",
2801
+
"dependencies": {
2802
+
"is-docker": "^2.0.0"
2803
+
},
2804
+
"engines": {
2805
+
"node": ">=8"
2806
+
}
2807
},
2808
"node_modules/isexe": {
2809
"version": "2.0.0",
···
2817
"resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz",
2818
"integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==",
2819
"license": "MIT"
2820
+
},
2821
+
"node_modules/jiti": {
2822
+
"version": "2.4.2",
2823
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
2824
+
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
2825
+
"dev": true,
2826
+
"license": "MIT",
2827
+
"bin": {
2828
+
"jiti": "lib/jiti-cli.mjs"
2829
+
}
2830
},
2831
"node_modules/js-tokens": {
2832
"version": "4.0.0",
···
2952
"yallist": "^3.0.2"
2953
}
2954
},
2955
"node_modules/merge2": {
2956
"version": "1.4.1",
2957
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
···
2976
"node": ">=8.6"
2977
}
2978
},
2979
"node_modules/minimatch": {
2980
"version": "3.1.2",
2981
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
···
2997
"license": "MIT"
2998
},
2999
"node_modules/multiformats": {
3000
+
"version": "13.3.6",
3001
+
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.6.tgz",
3002
+
"integrity": "sha512-yakbt9cPYj8d3vi/8o/XWm61MrOILo7fsTL0qxNx6zS0Nso6K5JqqS2WV7vK/KSuDBvrW3KfCwAdAgarAgOmww==",
3003
+
"license": "Apache-2.0 OR MIT"
3004
},
3005
"node_modules/nanoid": {
3006
"version": "3.3.11",
···
3028
"dev": true,
3029
"license": "MIT"
3030
},
3031
"node_modules/node-releases": {
3032
"version": "2.0.19",
3033
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
···
3035
"dev": true,
3036
"license": "MIT"
3037
},
3038
+
"node_modules/open": {
3039
+
"version": "8.4.2",
3040
+
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
3041
+
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
3042
"dev": true,
3043
"license": "MIT",
3044
+
"dependencies": {
3045
+
"define-lazy-prop": "^2.0.0",
3046
+
"is-docker": "^2.1.1",
3047
+
"is-wsl": "^2.2.0"
3048
+
},
3049
"engines": {
3050
+
"node": ">=12"
3051
},
3052
"funding": {
3053
+
"url": "https://github.com/sponsors/sindresorhus"
3054
}
3055
},
3056
"node_modules/optionator": {
···
3116
"node": ">=6"
3117
}
3118
},
3119
"node_modules/path-exists": {
3120
"version": "4.0.0",
3121
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
···
3136
"node": ">=8"
3137
}
3138
},
3139
"node_modules/picocolors": {
3140
"version": "1.1.1",
3141
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
···
3154
},
3155
"funding": {
3156
"url": "https://github.com/sponsors/jonschlinkert"
3157
}
3158
},
3159
"node_modules/postcss": {
···
3195
"node": ">= 0.8.0"
3196
}
3197
},
3198
"node_modules/punycode": {
3199
"version": "2.3.1",
3200
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
···
3205
"node": ">=6"
3206
}
3207
},
3208
"node_modules/queue-microtask": {
3209
"version": "1.2.3",
3210
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
···
3226
],
3227
"license": "MIT"
3228
},
3229
"node_modules/react": {
3230
"version": "19.1.0",
3231
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
···
3258
}
3259
},
3260
"node_modules/react-router": {
3261
+
"version": "7.6.1",
3262
+
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz",
3263
+
"integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==",
3264
"license": "MIT",
3265
"dependencies": {
3266
"cookie": "^1.0.1",
3267
+
"set-cookie-parser": "^2.6.0"
3268
},
3269
"engines": {
3270
"node": ">=20.0.0"
···
3280
}
3281
},
3282
"node_modules/react-router-dom": {
3283
+
"version": "7.6.1",
3284
+
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.1.tgz",
3285
+
"integrity": "sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==",
3286
"license": "MIT",
3287
"dependencies": {
3288
+
"react-router": "7.6.1"
3289
},
3290
"engines": {
3291
"node": ">=20.0.0"
···
3295
"react-dom": ">=18"
3296
}
3297
},
3298
+
"node_modules/require-directory": {
3299
+
"version": "2.1.1",
3300
+
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
3301
+
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
3302
+
"dev": true,
3303
"license": "MIT",
3304
"engines": {
3305
+
"node": ">=0.10.0"
3306
}
3307
},
3308
"node_modules/resolve-from": {
···
3327
}
3328
},
3329
"node_modules/rollup": {
3330
+
"version": "4.41.1",
3331
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
3332
+
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
3333
"dev": true,
3334
"license": "MIT",
3335
"dependencies": {
···
3343
"npm": ">=8.0.0"
3344
},
3345
"optionalDependencies": {
3346
+
"@rollup/rollup-android-arm-eabi": "4.41.1",
3347
+
"@rollup/rollup-android-arm64": "4.41.1",
3348
+
"@rollup/rollup-darwin-arm64": "4.41.1",
3349
+
"@rollup/rollup-darwin-x64": "4.41.1",
3350
+
"@rollup/rollup-freebsd-arm64": "4.41.1",
3351
+
"@rollup/rollup-freebsd-x64": "4.41.1",
3352
+
"@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
3353
+
"@rollup/rollup-linux-arm-musleabihf": "4.41.1",
3354
+
"@rollup/rollup-linux-arm64-gnu": "4.41.1",
3355
+
"@rollup/rollup-linux-arm64-musl": "4.41.1",
3356
+
"@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
3357
+
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
3358
+
"@rollup/rollup-linux-riscv64-gnu": "4.41.1",
3359
+
"@rollup/rollup-linux-riscv64-musl": "4.41.1",
3360
+
"@rollup/rollup-linux-s390x-gnu": "4.41.1",
3361
+
"@rollup/rollup-linux-x64-gnu": "4.41.1",
3362
+
"@rollup/rollup-linux-x64-musl": "4.41.1",
3363
+
"@rollup/rollup-win32-arm64-msvc": "4.41.1",
3364
+
"@rollup/rollup-win32-ia32-msvc": "4.41.1",
3365
+
"@rollup/rollup-win32-x64-msvc": "4.41.1",
3366
"fsevents": "~2.3.2"
3367
}
3368
},
3369
+
"node_modules/rollup-plugin-visualizer": {
3370
+
"version": "6.0.1",
3371
+
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.1.tgz",
3372
+
"integrity": "sha512-NjlGElvLXCSZSAi3gNRZbfX3qlQbQcJ9TW97c5JpqfVwMhttj9YwEdPwcvbKj91RnMX2PWAjonvSEv6UEYtnRQ==",
3373
"dev": true,
3374
"license": "MIT",
3375
"dependencies": {
3376
+
"open": "^8.0.0",
3377
+
"picomatch": "^4.0.2",
3378
+
"source-map": "^0.7.4",
3379
+
"yargs": "^17.5.1"
3380
+
},
3381
+
"bin": {
3382
+
"rollup-plugin-visualizer": "dist/bin/cli.js"
3383
},
3384
"engines": {
3385
+
"node": ">=18"
3386
+
},
3387
+
"peerDependencies": {
3388
+
"rolldown": "1.x",
3389
+
"rollup": "2.x || 3.x || 4.x"
3390
+
},
3391
+
"peerDependenciesMeta": {
3392
+
"rolldown": {
3393
+
"optional": true
3394
+
},
3395
+
"rollup": {
3396
+
"optional": true
3397
+
}
3398
+
}
3399
+
},
3400
+
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
3401
+
"version": "4.0.2",
3402
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
3403
+
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
3404
+
"dev": true,
3405
+
"license": "MIT",
3406
+
"engines": {
3407
+
"node": ">=12"
3408
+
},
3409
+
"funding": {
3410
+
"url": "https://github.com/sponsors/jonschlinkert"
3411
}
3412
},
3413
"node_modules/run-parallel": {
···
3434
"queue-microtask": "^1.2.2"
3435
}
3436
},
3437
"node_modules/scheduler": {
3438
"version": "0.26.0",
3439
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
···
3441
"license": "MIT"
3442
},
3443
"node_modules/semver": {
3444
+
"version": "7.7.2",
3445
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
3446
+
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
3447
"dev": true,
3448
"license": "ISC",
3449
"bin": {
3450
"semver": "bin/semver.js"
3451
},
3452
"engines": {
3453
+
"node": ">=10"
3454
}
3455
},
3456
"node_modules/set-cookie-parser": {
···
3458
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
3459
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
3460
"license": "MIT"
3461
},
3462
"node_modules/shebang-command": {
3463
"version": "2.0.0",
···
3482
"node": ">=8"
3483
}
3484
},
3485
+
"node_modules/source-map": {
3486
+
"version": "0.7.4",
3487
+
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
3488
+
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
3489
"dev": true,
3490
+
"license": "BSD-3-Clause",
3491
"engines": {
3492
+
"node": ">= 8"
3493
}
3494
},
3495
+
"node_modules/source-map-js": {
3496
+
"version": "1.2.1",
3497
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
3498
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
3499
"dev": true,
3500
+
"license": "BSD-3-Clause",
3501
"engines": {
3502
+
"node": ">=0.10.0"
3503
}
3504
},
3505
+
"node_modules/source-map-support": {
3506
+
"version": "0.5.21",
3507
+
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
3508
+
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
3509
"dev": true,
3510
"license": "MIT",
3511
"dependencies": {
3512
+
"buffer-from": "^1.0.0",
3513
+
"source-map": "^0.6.0"
3514
+
}
3515
+
},
3516
+
"node_modules/source-map-support/node_modules/source-map": {
3517
+
"version": "0.6.1",
3518
+
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
3519
+
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
3520
+
"dev": true,
3521
+
"license": "BSD-3-Clause",
3522
"engines": {
3523
+
"node": ">=0.10.0"
3524
}
3525
},
3526
+
"node_modules/string-width": {
3527
+
"version": "4.2.3",
3528
+
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
3529
+
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
3530
"dev": true,
3531
"license": "MIT",
3532
"dependencies": {
3533
+
"emoji-regex": "^8.0.0",
3534
+
"is-fullwidth-code-point": "^3.0.0",
3535
+
"strip-ansi": "^6.0.1"
3536
},
3537
"engines": {
3538
+
"node": ">=8"
3539
}
3540
},
3541
+
"node_modules/strip-ansi": {
3542
+
"version": "6.0.1",
3543
+
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
3544
+
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
3545
"dev": true,
3546
"license": "MIT",
3547
+
"dependencies": {
3548
+
"ansi-regex": "^5.0.1"
3549
+
},
3550
"engines": {
3551
+
"node": ">=8"
3552
}
3553
},
3554
"node_modules/strip-json-comments": {
···
3577
"node": ">=8"
3578
}
3579
},
3580
+
"node_modules/terser": {
3581
+
"version": "5.40.0",
3582
+
"resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz",
3583
+
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
3584
+
"dev": true,
3585
+
"license": "BSD-2-Clause",
3586
+
"dependencies": {
3587
+
"@jridgewell/source-map": "^0.3.3",
3588
+
"acorn": "^8.14.0",
3589
+
"commander": "^2.20.0",
3590
+
"source-map-support": "~0.5.20"
3591
+
},
3592
+
"bin": {
3593
+
"terser": "bin/terser"
3594
+
},
3595
+
"engines": {
3596
+
"node": ">=10"
3597
+
}
3598
+
},
3599
"node_modules/tinyglobby": {
3600
+
"version": "0.2.14",
3601
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
3602
+
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
3603
"dev": true,
3604
"license": "MIT",
3605
"dependencies": {
···
3614
}
3615
},
3616
"node_modules/tinyglobby/node_modules/fdir": {
3617
+
"version": "6.4.5",
3618
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
3619
+
"integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
3620
"dev": true,
3621
"license": "MIT",
3622
"peerDependencies": {
···
3642
}
3643
},
3644
"node_modules/tlds": {
3645
+
"version": "1.259.0",
3646
+
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz",
3647
+
"integrity": "sha512-AldGGlDP0PNgwppe2quAvuBl18UcjuNtOnDuUkqhd6ipPqrYYBt3aTxK1QTsBVknk97lS2JcafWMghjGWFtunw==",
3648
"license": "MIT",
3649
"bin": {
3650
"tlds": "bin.js"
···
3661
},
3662
"engines": {
3663
"node": ">=8.0"
3664
}
3665
},
3666
"node_modules/ts-api-utils": {
···
3676
"typescript": ">=4.8.4"
3677
}
3678
},
3679
"node_modules/type-check": {
3680
"version": "0.4.0",
3681
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
···
3689
"node": ">= 0.8.0"
3690
}
3691
},
3692
"node_modules/typescript": {
3693
"version": "5.7.3",
3694
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
···
3704
}
3705
},
3706
"node_modules/typescript-eslint": {
3707
+
"version": "8.33.0",
3708
+
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.0.tgz",
3709
+
"integrity": "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==",
3710
"dev": true,
3711
"license": "MIT",
3712
"dependencies": {
3713
+
"@typescript-eslint/eslint-plugin": "8.33.0",
3714
+
"@typescript-eslint/parser": "8.33.0",
3715
+
"@typescript-eslint/utils": "8.33.0"
3716
},
3717
"engines": {
3718
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
···
3727
}
3728
},
3729
"node_modules/uint8arrays": {
3730
+
"version": "5.1.0",
3731
+
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz",
3732
+
"integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==",
3733
+
"license": "Apache-2.0 OR MIT",
3734
"dependencies": {
3735
+
"multiformats": "^13.0.0"
3736
}
3737
},
3738
+
"node_modules/undici-types": {
3739
+
"version": "6.21.0",
3740
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
3741
+
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
3742
"dev": true,
3743
+
"license": "MIT"
3744
},
3745
"node_modules/update-browserslist-db": {
3746
"version": "1.1.3",
···
3783
"punycode": "^2.1.0"
3784
}
3785
},
3786
"node_modules/vite": {
3787
+
"version": "6.3.5",
3788
+
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
3789
+
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
3790
"dev": true,
3791
"license": "MIT",
3792
"dependencies": {
···
3859
}
3860
},
3861
"node_modules/vite/node_modules/fdir": {
3862
+
"version": "6.4.5",
3863
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
3864
+
"integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
3865
"dev": true,
3866
"license": "MIT",
3867
"peerDependencies": {
···
3912
"node": ">=0.10.0"
3913
}
3914
},
3915
+
"node_modules/wrap-ansi": {
3916
+
"version": "7.0.0",
3917
+
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
3918
+
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
3919
+
"dev": true,
3920
+
"license": "MIT",
3921
+
"dependencies": {
3922
+
"ansi-styles": "^4.0.0",
3923
+
"string-width": "^4.1.0",
3924
+
"strip-ansi": "^6.0.0"
3925
+
},
3926
+
"engines": {
3927
+
"node": ">=10"
3928
+
},
3929
+
"funding": {
3930
+
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
3931
+
}
3932
+
},
3933
+
"node_modules/y18n": {
3934
+
"version": "5.0.8",
3935
+
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
3936
+
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
3937
"dev": true,
3938
+
"license": "ISC",
3939
+
"engines": {
3940
+
"node": ">=10"
3941
+
}
3942
},
3943
"node_modules/yallist": {
3944
"version": "3.1.1",
···
3947
"dev": true,
3948
"license": "ISC"
3949
},
3950
+
"node_modules/yargs": {
3951
+
"version": "17.7.2",
3952
+
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
3953
+
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
3954
+
"dev": true,
3955
+
"license": "MIT",
3956
+
"dependencies": {
3957
+
"cliui": "^8.0.1",
3958
+
"escalade": "^3.1.1",
3959
+
"get-caller-file": "^2.0.5",
3960
+
"require-directory": "^2.1.1",
3961
+
"string-width": "^4.2.3",
3962
+
"y18n": "^5.0.5",
3963
+
"yargs-parser": "^21.1.1"
3964
+
},
3965
+
"engines": {
3966
+
"node": ">=12"
3967
+
}
3968
+
},
3969
+
"node_modules/yargs-parser": {
3970
+
"version": "21.1.1",
3971
+
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
3972
+
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
3973
+
"dev": true,
3974
+
"license": "ISC",
3975
+
"engines": {
3976
+
"node": ">=12"
3977
+
}
3978
+
},
3979
"node_modules/yocto-queue": {
3980
"version": "0.1.0",
3981
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
···
3990
}
3991
},
3992
"node_modules/zod": {
3993
+
"version": "3.25.32",
3994
+
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.32.tgz",
3995
+
"integrity": "sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g==",
3996
"license": "MIT",
3997
"funding": {
3998
"url": "https://github.com/sponsors/colinhacks"
3999
}
4000
}
4001
}
+16
-5
package.json
+16
-5
package.json
···
1
{
2
"name": "atproto-migration",
3
"private": true,
4
-
"version": "0.0.0",
5
"type": "module",
6
"scripts": {
7
"dev": "vite",
8
"build": "tsc -b && vite build",
9
-
"lint": "eslint .",
10
"preview": "vite preview"
11
},
12
"dependencies": {
13
"@atproto/api": "^0.15.5",
14
"react": "^19.0.0",
15
"react-dom": "^19.0.0",
16
-
"react-router-dom": "^7.5.3"
17
},
18
"devDependencies": {
19
"@eslint/js": "^9.22.0",
20
-
"@types/react": "^19.0.10",
21
-
"@types/react-dom": "^19.0.4",
22
"@vitejs/plugin-react": "^4.3.4",
23
"eslint": "^9.22.0",
24
"eslint-plugin-react-hooks": "^5.2.0",
25
"eslint-plugin-react-refresh": "^0.4.19",
26
"globals": "^16.0.0",
27
"typescript": "~5.7.2",
28
"typescript-eslint": "^8.26.1",
29
"vite": "^6.3.1"
···
1
{
2
"name": "atproto-migration",
3
"private": true,
4
+
"version": "0.1.0",
5
"type": "module",
6
"scripts": {
7
"dev": "vite",
8
"build": "tsc -b && vite build",
9
+
"lint": "eslint src/",
10
"preview": "vite preview"
11
},
12
"dependencies": {
13
"@atproto/api": "^0.15.5",
14
+
"@atproto/crypto": "^0.4.4",
15
+
"multiformats": "^13.3.6",
16
"react": "^19.0.0",
17
"react-dom": "^19.0.0",
18
+
"react-router-dom": "^7.5.3",
19
+
"uint8arrays": "^5.1.0"
20
},
21
"devDependencies": {
22
+
"@eslint/eslintrc": "^3.3.1",
23
"@eslint/js": "^9.22.0",
24
+
"@humanwhocodes/module-importer": "^1.0.1",
25
+
"@types/node": "^22.15.14",
26
+
"@types/react": "^19.1.6",
27
+
"@types/react-dom": "^19.1.5",
28
+
"@typescript-eslint/eslint-plugin": "^8.33.0",
29
+
"@typescript-eslint/parser": "^8.32.0",
30
"@vitejs/plugin-react": "^4.3.4",
31
"eslint": "^9.22.0",
32
"eslint-plugin-react-hooks": "^5.2.0",
33
"eslint-plugin-react-refresh": "^0.4.19",
34
"globals": "^16.0.0",
35
+
"jiti": "^2.4.2",
36
+
"rollup-plugin-visualizer": "^6.0.1",
37
+
"terser": "^5.40.0",
38
"typescript": "~5.7.2",
39
"typescript-eslint": "^8.26.1",
40
"vite": "^6.3.1"
+37
public/favicon.svg
+37
public/favicon.svg
···
···
1
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+
<svg
3
+
width="256"
4
+
height="256"
5
+
viewBox="0 0 32 32"
6
+
fill="none"
7
+
version="1.1"
8
+
id="svg5"
9
+
xmlns="http://www.w3.org/2000/svg"
10
+
xmlns:svg="http://www.w3.org/2000/svg">
11
+
<path
12
+
d="M24 8H8C6.89543 8 6 8.89543 6 10V24C6 25.1046 6.89543 26 8 26H24C25.1046 26 26 25.1046 26 24V10C26 8.89543 25.1046 8 24 8Z"
13
+
stroke="#3b82f6"
14
+
stroke-width="2"
15
+
stroke-linecap="round"
16
+
stroke-linejoin="round"
17
+
id="path1" />
18
+
<path
19
+
d="M20 8V6C20 4.89543 19.1046 4 18 4H14C12.8954 4 12 4.89543 12 6V8"
20
+
stroke="#3b82f6"
21
+
stroke-width="2"
22
+
stroke-linecap="round"
23
+
stroke-linejoin="round"
24
+
id="path2" />
25
+
<path
26
+
d="M6 12H26"
27
+
stroke="#3b82f6"
28
+
stroke-width="2"
29
+
stroke-linecap="round"
30
+
stroke-linejoin="round"
31
+
id="path3" />
32
+
<path
33
+
style="font-weight:bold;font-size:12px;font-family:Galvji;-inkscape-font-specification:'Galvji, Bold';fill:#3b82f6;stroke-width:0.250394"
34
+
d="m 15.861771,17.466183 q -0.46875,0 -0.732422,0.351562 -0.257812,0.351563 -0.257812,0.984375 0,0.626953 0.257812,0.984375 0.263672,0.351563 0.726563,0.351563 0.474609,0 0.74414,-0.357422 0.275391,-0.357422 0.275391,-0.978516 0,-0.621094 -0.275391,-0.978515 -0.269531,-0.357422 -0.738281,-0.357422 z m 0.1875,-3.591797 q 1.054688,0 1.921875,0.316406 0.873047,0.316406 1.494141,0.890625 0.626953,0.574219 0.966797,1.376953 0.345703,0.802735 0.345703,1.769531 0,0.697266 -0.164063,1.259766 -0.158203,0.5625 -0.457031,0.955078 -0.298828,0.392578 -0.726562,0.603516 -0.421875,0.210937 -0.955079,0.210937 -0.550781,0 -0.925781,-0.246093 -0.36914,-0.246094 -0.46875,-0.667969 h -0.105469 q -0.375,0.896484 -1.40625,0.896484 -0.457031,0 -0.832031,-0.175781 -0.375,-0.175781 -0.638672,-0.498047 -0.263672,-0.328125 -0.410156,-0.785156 -0.146484,-0.457031 -0.146484,-1.013672 0,-0.533203 0.140625,-0.972656 0.146484,-0.439453 0.404297,-0.75586 0.257812,-0.316406 0.621093,-0.486328 0.363282,-0.175781 0.802735,-0.175781 0.474609,0 0.832031,0.216797 0.363281,0.216797 0.539062,0.609375 h 0.105469 v -0.697266 h 1.183594 v 3.105469 q 0,0.316406 0.146484,0.486328 0.146485,0.169922 0.416016,0.169922 0.222656,0 0.398437,-0.134766 0.181641,-0.134765 0.310547,-0.386718 0.128907,-0.251954 0.19336,-0.609375 0.07031,-0.363282 0.07031,-0.808594 0,-0.796875 -0.263672,-1.441406 -0.263672,-0.650391 -0.75,-1.107422 -0.480468,-0.457032 -1.166015,-0.703125 -0.679688,-0.251953 -1.511719,-0.251953 -0.849609,0 -1.558594,0.292968 -0.703125,0.28711 -1.21289,0.814453 -0.509766,0.521485 -0.791016,1.253907 -0.28125,0.726562 -0.28125,1.605468 0,0.890625 0.28125,1.611329 0.287109,0.714843 0.814453,1.21875 0.533203,0.503906 1.283203,0.773437 0.75586,0.275391 1.6875,0.275391 0.234375,0 0.462891,-0.01758 0.234375,-0.01172 0.445312,-0.03516 0.210938,-0.02344 0.386719,-0.05859 0.175781,-0.03516 0.298828,-0.08203 v 0.955078 q -0.345703,0.09961 -0.779297,0.152344 -0.433593,0.05859 -0.902343,0.05859 -1.142579,0 -2.080079,-0.351563 -0.93164,-0.345703 -1.59375,-0.984375 -0.65625,-0.638671 -1.019531,-1.541015 -0.357422,-0.902344 -0.357422,-2.003906 0,-1.083985 0.357422,-1.980469 0.363281,-0.902344 1.019531,-1.546875 0.65625,-0.644531 1.564454,-1.001953 0.908203,-0.357422 2.009765,-0.357422 z"
35
+
id="text5"
36
+
aria-label="@" />
37
+
</svg>
-1
public/vite.svg
-1
public/vite.svg
···
1
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
···
-172
src/App.tsx
-172
src/App.tsx
···
1
-
import { useState, useEffect } from 'react'
2
-
import { BrowserRouter as Router, Routes, Route, Navigate, useLocation } from 'react-router-dom'
3
-
import { AtpAgent } from '@atproto/api'
4
-
import { AvatarProvider } from './contexts/AvatarContext'
5
-
import { NetworkProvider } from './contexts/NetworkContext'
6
-
import NetworkWarning from './components/common/NetworkWarning'
7
-
import Login from './components/auth/Login'
8
-
import Actions from './components/common/Actions'
9
-
import Migration from './components/common/Migration'
10
-
import MigrationProcess from './components/common/MigrationProcess'
11
-
import RecoveryKey from './components/common/RecoveryKey'
12
-
import RecoveryKeyProcess from './components/common/RecoveryKeyProcess'
13
-
import './styles/App.css'
14
-
15
-
const SESSION_KEY = 'atproto_session';
16
-
const SESSION_EXPIRY = 60 * 60 * 1000; // 1 hour in milliseconds
17
-
18
-
function AppRoutes({ agent, onLogout, handleLogin }: {
19
-
agent: AtpAgent | null;
20
-
onLogout: () => void;
21
-
handleLogin: (agent: AtpAgent) => void;
22
-
}) {
23
-
const location = useLocation();
24
-
25
-
useEffect(() => {
26
-
const checkSession = async () => {
27
-
if (agent) {
28
-
try {
29
-
// Try to make a simple API call to verify the session
30
-
await agent.getProfile({ actor: agent.session?.handle || '' });
31
-
} catch (err) {
32
-
// If the API call fails, the session is likely invalid
33
-
onLogout();
34
-
alert('Your session has expired. Please log in again.');
35
-
}
36
-
}
37
-
};
38
-
39
-
checkSession();
40
-
}, [location.pathname, agent, onLogout]);
41
-
42
-
return (
43
-
<>
44
-
<NetworkWarning />
45
-
<Routes>
46
-
<Route
47
-
path="/"
48
-
element={
49
-
agent ? (
50
-
<Navigate to="/actions" replace />
51
-
) : (
52
-
<Login onLogin={handleLogin} />
53
-
)
54
-
}
55
-
/>
56
-
<Route
57
-
path="/actions"
58
-
element={
59
-
agent ? (
60
-
<Actions agent={agent} onLogout={onLogout} />
61
-
) : (
62
-
<Navigate to="/" replace />
63
-
)
64
-
}
65
-
/>
66
-
<Route
67
-
path="/migration"
68
-
element={
69
-
agent ? (
70
-
<Migration agent={agent} onLogout={onLogout} />
71
-
) : (
72
-
<Navigate to="/" replace />
73
-
)
74
-
}
75
-
/>
76
-
<Route
77
-
path="/migration/process"
78
-
element={
79
-
agent ? (
80
-
<MigrationProcess agent={agent} onLogout={onLogout} />
81
-
) : (
82
-
<Navigate to="/" replace />
83
-
)
84
-
}
85
-
/>
86
-
<Route
87
-
path="/recovery-key"
88
-
element={
89
-
agent ? (
90
-
<RecoveryKey agent={agent} onLogout={onLogout} />
91
-
) : (
92
-
<Navigate to="/" replace />
93
-
)
94
-
}
95
-
/>
96
-
<Route
97
-
path="/recovery-key/process"
98
-
element={
99
-
agent ? (
100
-
<RecoveryKeyProcess agent={agent} onLogout={onLogout} />
101
-
) : (
102
-
<Navigate to="/" replace />
103
-
)
104
-
}
105
-
/>
106
-
</Routes>
107
-
</>
108
-
);
109
-
}
110
-
111
-
function App() {
112
-
const [agent, setAgent] = useState<AtpAgent | null>(null)
113
-
114
-
useEffect(() => {
115
-
// Load session from localStorage on initial load
116
-
const loadSession = async () => {
117
-
const savedSession = localStorage.getItem(SESSION_KEY);
118
-
if (savedSession) {
119
-
const { session, service, timestamp } = JSON.parse(savedSession);
120
-
121
-
// Check if session is expired
122
-
if (Date.now() - timestamp > SESSION_EXPIRY) {
123
-
localStorage.removeItem(SESSION_KEY);
124
-
return;
125
-
}
126
-
127
-
const newAgent = new AtpAgent({ service });
128
-
await newAgent.resumeSession(session);
129
-
setAgent(newAgent);
130
-
}
131
-
};
132
-
133
-
loadSession();
134
-
}, []);
135
-
136
-
const handleLogin = (newAgent: AtpAgent) => {
137
-
setAgent(newAgent);
138
-
// Save session to localStorage
139
-
localStorage.setItem(SESSION_KEY, JSON.stringify({
140
-
session: newAgent.session,
141
-
service: newAgent.service.toString(),
142
-
timestamp: Date.now()
143
-
}));
144
-
};
145
-
146
-
const handleLogout = () => {
147
-
setAgent(null);
148
-
localStorage.removeItem(SESSION_KEY);
149
-
// Clear avatar URL from context
150
-
const avatarContext = document.querySelector('[data-avatar-context]');
151
-
if (avatarContext) {
152
-
const event = new CustomEvent('clearAvatar');
153
-
avatarContext.dispatchEvent(event);
154
-
}
155
-
};
156
-
157
-
return (
158
-
<NetworkProvider>
159
-
<AvatarProvider>
160
-
<Router>
161
-
<AppRoutes
162
-
agent={agent}
163
-
onLogout={handleLogout}
164
-
handleLogin={handleLogin}
165
-
/>
166
-
</Router>
167
-
</AvatarProvider>
168
-
</NetworkProvider>
169
-
)
170
-
}
171
-
172
-
export default App
···
-189
src/components/auth/Login.tsx
-189
src/components/auth/Login.tsx
···
1
-
import { useState } from 'react';
2
-
import { useNavigate } from 'react-router-dom';
3
-
import { AtpAgent } from '@atproto/api';
4
-
import Footer from '../layout/Footer';
5
-
import '../../styles/App.css';
6
-
7
-
interface LoginProps {
8
-
onLogin: (agent: AtpAgent) => void;
9
-
}
10
-
11
-
interface DidDocument {
12
-
service: Array<{
13
-
id: string;
14
-
type: string;
15
-
serviceEndpoint: string;
16
-
}>;
17
-
}
18
-
19
-
type LoginStep = 'idle' | 'resolving-handle' | 'resolving-did' | 'connecting-pds' | 'authenticating' | 'success';
20
-
21
-
export default function Login({ onLogin }: LoginProps) {
22
-
const [handle, setHandle] = useState('');
23
-
const [password, setPassword] = useState('');
24
-
const [error, setError] = useState('');
25
-
const [appPasswordAttempts, setAppPasswordAttempts] = useState(0);
26
-
const [loginStep, setLoginStep] = useState<LoginStep>('idle');
27
-
const navigate = useNavigate();
28
-
29
-
const isAppPassword = (password: string) => {
30
-
// App passwords are typically in the format xxxx-xxxx-xxxx-xxxx
31
-
return /^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/.test(password);
32
-
};
33
-
34
-
const getStepMessage = (step: LoginStep) => {
35
-
switch (step) {
36
-
case 'resolving-handle':
37
-
return 'Resolving your handle...';
38
-
case 'resolving-did':
39
-
return 'Resolving your DID...';
40
-
case 'connecting-pds':
41
-
return 'Connecting to your Personal Data Server...';
42
-
case 'authenticating':
43
-
return 'Authenticating your credentials...';
44
-
case 'success':
45
-
return 'Login successful! Redirecting...';
46
-
default:
47
-
return '';
48
-
}
49
-
};
50
-
51
-
const handleSubmit = async (e: React.FormEvent) => {
52
-
e.preventDefault();
53
-
setError('');
54
-
setLoginStep('resolving-handle');
55
-
56
-
// app password check and debug method
57
-
if (isAppPassword(password)) {
58
-
if (appPasswordAttempts < 3) {
59
-
setAppPasswordAttempts(appPasswordAttempts + 1);
60
-
setError(`You have entered an app password, which does not allow for you to migrate your account. Please enter your main account password instead.`);
61
-
setLoginStep('idle');
62
-
return;
63
-
}
64
-
}
65
-
66
-
setHandle(handle.trim());
67
-
68
-
try {
69
-
// Create temporary agent to resolve DID
70
-
const tempAgent = new AtpAgent({ service: 'https://public.api.bsky.app' });
71
-
72
-
// Get DID document from handle
73
-
setLoginStep('resolving-handle');
74
-
const didResponse = await tempAgent.com.atproto.identity.resolveHandle({
75
-
handle: handle
76
-
});
77
-
78
-
if (!didResponse.success) {
79
-
// Try did:web resolution first
80
-
const domain = handle.split('.').join(':');
81
-
const webDid = `did:web:${domain}`;
82
-
try {
83
-
const webResponse = await fetch(`https://${handle}/.well-known/did.json`);
84
-
if (webResponse.ok) {
85
-
// If successful, continue with the did:web
86
-
didResponse.data.did = webDid;
87
-
} else {
88
-
throw new Error('Invalid handle');
89
-
}
90
-
} catch {
91
-
throw new Error('Invalid handle');
92
-
}
93
-
}
94
-
95
-
// Get PDS endpoint from DID document
96
-
setLoginStep('resolving-did');
97
-
let didDocResponse;
98
-
const did = didResponse.data.did;
99
-
100
-
if (did.startsWith('did:plc:')) {
101
-
// For PLC DIDs, resolve from plc.directory
102
-
const plcResponse = await fetch(`https://plc.directory/${did}`);
103
-
didDocResponse = { data: await plcResponse.json() };
104
-
} else if (did.startsWith('did:web:')) {
105
-
// For Web DIDs, get from .well-known/did.json
106
-
const domain = did.split(':')[2];
107
-
const webResponse = await fetch(`https://${domain}/.well-known/did.json`);
108
-
didDocResponse = { data: await webResponse.json() };
109
-
} else {
110
-
// Fallback to ATP resolver for other DID types
111
-
didDocResponse = await tempAgent.com.atproto.identity.resolveDid({
112
-
did: did
113
-
});
114
-
}
115
-
116
-
setLoginStep('connecting-pds');
117
-
const pds = ((didDocResponse.data as unknown) as DidDocument).service.find((s) => s.id === '#atproto_pds')?.serviceEndpoint || 'https://bsky.social';
118
-
119
-
const agent = new AtpAgent({ service: pds });
120
-
121
-
setLoginStep('authenticating');
122
-
await agent.login({ identifier: handle, password });
123
-
124
-
setLoginStep('success');
125
-
onLogin(agent);
126
-
navigate('/actions');
127
-
} catch (err) {
128
-
setError(err instanceof Error ? err.message : 'Login failed');
129
-
setLoginStep('idle');
130
-
}
131
-
};
132
-
133
-
return (
134
-
<div>
135
-
<h1 className="login-title">ATproto Migrator</h1>
136
-
<div className="login-container">
137
-
<div className="login-card">
138
-
<h2 className="login-title">Sign in to your account</h2>
139
-
<div className="warning-message">
140
-
โ ๏ธ Please use your main account password, not an app password. All operations are performed locally in your browser.
141
-
</div>
142
-
<div className="warning-message">
143
-
ALSO This tool currently does not do anything, it should be done SOONโข๏ธ
144
-
</div>
145
-
<form className="login-form" onSubmit={handleSubmit}>
146
-
<div className="form-group">
147
-
<input
148
-
type="text"
149
-
required
150
-
className="form-input"
151
-
placeholder="Handle (e.g., example.bsky.social)"
152
-
value={handle}
153
-
onChange={(e) => setHandle(e.target.value)}
154
-
disabled={loginStep !== 'idle'}
155
-
/>
156
-
</div>
157
-
<div className="form-group">
158
-
<input
159
-
type="password"
160
-
required
161
-
className="form-input"
162
-
placeholder="Password"
163
-
value={password}
164
-
onChange={(e) => setPassword(e.target.value)}
165
-
disabled={loginStep !== 'idle'}
166
-
/>
167
-
</div>
168
-
169
-
{error && <div className="error-message">{error}</div>}
170
-
{loginStep !== 'idle' && (
171
-
<div className="loading-message">
172
-
{getStepMessage(loginStep)}
173
-
</div>
174
-
)}
175
-
176
-
<button
177
-
type="submit"
178
-
className="submit-button"
179
-
disabled={loginStep !== 'idle'}
180
-
>
181
-
{loginStep === 'idle' ? 'Sign in' : 'Signing in...'}
182
-
</button>
183
-
</form>
184
-
</div>
185
-
</div>
186
-
<Footer />
187
-
</div>
188
-
);
189
-
}
···
-131
src/components/common/Actions.tsx
-131
src/components/common/Actions.tsx
···
1
-
import { useEffect, useState } from 'react';
2
-
import { AtpAgent } from '@atproto/api';
3
-
import { useNavigate } from 'react-router-dom';
4
-
import Footer from '../layout/Footer';
5
-
import Header from '../layout/Header';
6
-
import '../../styles/App.css';
7
-
8
-
interface ActionsProps {
9
-
agent: AtpAgent;
10
-
onLogout: () => void;
11
-
}
12
-
13
-
export default function Actions({ agent, onLogout }: ActionsProps) {
14
-
const [didDoc, setDidDoc] = useState<string>('');
15
-
const [loading, setLoading] = useState(true);
16
-
const navigate = useNavigate();
17
-
18
-
const handleLogout = () => {
19
-
onLogout();
20
-
navigate('/');
21
-
};
22
-
23
-
useEffect(() => {
24
-
const fetchProfile = async () => {
25
-
try {
26
-
const did = agent.session?.did;
27
-
if (!did) {
28
-
throw new Error('No DID found in session');
29
-
}
30
-
31
-
let didDocResponse;
32
-
33
-
if (did.startsWith('did:plc:')) {
34
-
// For PLC DIDs, resolve from plc.directory
35
-
const response = await fetch(`https://plc.directory/${did}`);
36
-
didDocResponse = await response.json();
37
-
} else if (did.startsWith('did:web:')) {
38
-
// For Web DIDs, get from .well-known/did.json
39
-
const domain = did.split(':')[2];
40
-
const response = await fetch(`https://${domain}/.well-known/did.json`);
41
-
didDocResponse = await response.json();
42
-
} else {
43
-
throw new Error(`Unsupported DID type: ${did}`);
44
-
}
45
-
46
-
setDidDoc(JSON.stringify(didDocResponse, null, 2));
47
-
} catch (err) {
48
-
console.error('Error fetching DID document:', err);
49
-
setDidDoc(`Error fetching DID document: ${err instanceof Error ? err.message : 'Unknown error'}`);
50
-
} finally {
51
-
setLoading(false);
52
-
}
53
-
};
54
-
55
-
fetchProfile();
56
-
}, [agent]);
57
-
58
-
if (loading) {
59
-
return (
60
-
<div className="loading-container">
61
-
<div className="loading-text">Loading...</div>
62
-
</div>
63
-
);
64
-
}
65
-
66
-
return (
67
-
<div className="actions-page">
68
-
<Header agent={agent} onLogout={handleLogout} />
69
-
70
-
<div className="actions-container">
71
-
<div className="actions-list">
72
-
<button
73
-
className="action-item"
74
-
onClick={() => navigate('/migration')}
75
-
>
76
-
<div className="action-icon">
77
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
78
-
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
79
-
</svg>
80
-
</div>
81
-
<div className="action-content">
82
-
<div className="action-title">Migrate account</div>
83
-
<div className="action-subtitle">Move your account to a new data server</div>
84
-
</div>
85
-
</button>
86
-
87
-
<button
88
-
className="action-item"
89
-
onClick={() => navigate('/recovery-key')}
90
-
>
91
-
<div className="action-icon">
92
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
93
-
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
94
-
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
95
-
</svg>
96
-
</div>
97
-
<div className="action-content">
98
-
<div className="action-title">Add recovery key</div>
99
-
<div className="action-subtitle">Create a new recovery key for your account</div>
100
-
</div>
101
-
</button>
102
-
</div>
103
-
104
-
<details className="user-info-section">
105
-
<summary className="user-info-summary">User Information</summary>
106
-
<div className="user-info-content">
107
-
<section>
108
-
<h2>Account Details</h2>
109
-
<dl>
110
-
<dt>DID</dt>
111
-
<dd>{agent.session?.did || 'N/A'}</dd>
112
-
<dt>Handle</dt>
113
-
<dd>@{agent.session?.handle || 'N/A'}</dd>
114
-
<dt>PDS</dt>
115
-
<dd>{agent.serviceUrl.toString() || 'N/A'}</dd>
116
-
</dl>
117
-
</section>
118
-
119
-
<section>
120
-
<h2>DID Document</h2>
121
-
<pre className="did-document">
122
-
<code>{didDoc}</code>
123
-
</pre>
124
-
</section>
125
-
</div>
126
-
</details>
127
-
</div>
128
-
<Footer />
129
-
</div>
130
-
);
131
-
}
···
+50
src/components/common/Header.tsx
+50
src/components/common/Header.tsx
···
···
1
+
import { useEffect } from 'react';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import { useAvatar } from '../../contexts/AvatarContext';
4
+
5
+
interface HeaderProps {
6
+
agent: AtpAgent;
7
+
onLogout: () => void;
8
+
}
9
+
10
+
export default function Header({ agent, onLogout }: HeaderProps) {
11
+
const { avatarUrl, setAvatarUrl } = useAvatar();
12
+
13
+
useEffect(() => {
14
+
const fetchProfile = async () => {
15
+
// Only fetch if we don't already have an avatar
16
+
if (!avatarUrl) {
17
+
try {
18
+
const profile = await agent.getProfile({ actor: agent.session?.handle || '' });
19
+
if (profile.data.avatar) {
20
+
setAvatarUrl(profile.data.avatar);
21
+
}
22
+
} catch (err) {
23
+
console.error('Error fetching profile avatar:', err);
24
+
setAvatarUrl('https://placehold.co/400x400');
25
+
}
26
+
}
27
+
};
28
+
29
+
fetchProfile();
30
+
}, [agent, avatarUrl, setAvatarUrl]);
31
+
32
+
return (
33
+
<header className="app-header">
34
+
<h1 className="app-title">ATproto Migrator</h1>
35
+
<div className="user-info">
36
+
{avatarUrl && (
37
+
<img
38
+
src={avatarUrl}
39
+
alt="Profile"
40
+
className="user-avatar"
41
+
/>
42
+
)}
43
+
<span className="user-handle" title={agent.session?.handle}>@{agent.session?.handle}</span>
44
+
<button className="logout-button" onClick={onLogout}>
45
+
Logout
46
+
</button>
47
+
</div>
48
+
</header>
49
+
);
50
+
}
-72
src/components/common/Migration.tsx
-72
src/components/common/Migration.tsx
···
1
-
import { useNavigate } from 'react-router-dom';
2
-
import { AtpAgent } from '@atproto/api';
3
-
import Footer from '../layout/Footer';
4
-
import Header from '../layout/Header';
5
-
import '../../styles/App.css';
6
-
7
-
interface MigrationProps {
8
-
agent: AtpAgent;
9
-
onLogout: () => void;
10
-
}
11
-
12
-
export default function Migration({ agent, onLogout }: MigrationProps) {
13
-
const navigate = useNavigate();
14
-
15
-
return (
16
-
<div className="actions-page">
17
-
<Header agent={agent} onLogout={onLogout} />
18
-
19
-
<div className="actions-container">
20
-
<div className="page-content">
21
-
<h2>Migrate your account</h2>
22
-
<p>This tool allows you to migrate your account to a new Personal Data Server, a data storage service that hosts your account and all of its data.</p>
23
-
<h3>What to expect</h3>
24
-
<p>The migration process is <i>possible</i>, however it is not recommended if you are unsure about what you are doing, especially if you are migrating your primary account.</p>
25
-
<p>You will need the following items to begin the migration process:</p>
26
-
<ul>
27
-
<li>A new PDS to migrate to</li>
28
-
<li>An invite code from the new PDS (if required)</li>
29
-
<li>A PLC operation token to confirm the migration</li>
30
-
<li>A new password for your account <b>(Your password will not be stored by this tool.)</b></li>
31
-
<li>If you are not using a custom domain, you will need a new handle as the default domain (such as alice.bsky.social or bob.example-pds.com) is non-transferable.</li>
32
-
</ul>
33
-
34
-
<div className="warning-section">
35
-
<h3>โ ๏ธ Read Before Continuing โ ๏ธ</h3>
36
-
<ul>
37
-
<li>If you are already on a third-party PDS, it must be able to send emails or you will not be able to get a PLC operation token without direct access to the server.</li>
38
-
<li>Due to performance issues, the main Bluesky data servers do not allow for account data to be imported at this time. <b>You will not be able to migrate back to Bluesky servers.</b></li>
39
-
<li>If your PDS goes down and you do not have access to a recovery key, you will be locked out of your account. <b>Bluesky developers will not be able to help you.</b></li>
40
-
</ul>
41
-
</div>
42
-
43
-
<div className="docs-section">
44
-
<h3>Additional Resources</h3>
45
-
<p>For the technically inclined, here are some additional resources for how the migration process works:</p>
46
-
<ul>
47
-
<li><a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md" target="_blank" rel="noopener noreferrer">Detailed document on migration for PDS hosters</a></li>
48
-
<li><a href="https://atproto.com/guides/account-migration" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on account migration</a></li>
49
-
<li><a href="https://whtwnd.com/bnewbold.net/3l5ii332pf32u">Guide to migrating an account using the command line</a></li>
50
-
</ul>
51
-
</div>
52
-
53
-
<div className="button-container">
54
-
<button
55
-
className="back-button"
56
-
onClick={() => navigate('/actions')}
57
-
>
58
-
โ Go back
59
-
</button>
60
-
<button
61
-
className="continue-button"
62
-
onClick={() => navigate('/migration/process')}
63
-
>
64
-
Continue โ
65
-
</button>
66
-
</div>
67
-
</div>
68
-
</div>
69
-
<Footer />
70
-
</div>
71
-
);
72
-
}
···
-377
src/components/common/MigrationProcess.tsx
-377
src/components/common/MigrationProcess.tsx
···
1
-
import { useNavigate } from 'react-router-dom';
2
-
import { useState, useEffect, useCallback } from 'react';
3
-
import { AtpAgent } from '@atproto/api';
4
-
import Footer from '../layout/Footer';
5
-
import Header from '../layout/Header';
6
-
import '../../styles/App.css';
7
-
8
-
interface MigrationProcessProps {
9
-
agent: AtpAgent;
10
-
onLogout: () => void;
11
-
}
12
-
13
-
interface PDSInfo {
14
-
exists: boolean;
15
-
requiresInvite: boolean;
16
-
domain: string;
17
-
availableUserDomains: string[];
18
-
}
19
-
20
-
interface AccountDetails {
21
-
handle: string;
22
-
email: string;
23
-
password: string;
24
-
}
25
-
26
-
export default function MigrationProcess({ agent, onLogout }: MigrationProcessProps) {
27
-
const navigate = useNavigate();
28
-
const [pds, setPds] = useState('');
29
-
const [pdsInfo, setPdsInfo] = useState<PDSInfo | null>(null);
30
-
const [isValidating, setIsValidating] = useState(false);
31
-
const [error, setError] = useState('');
32
-
const [inviteCode, setInviteCode] = useState('');
33
-
const [isInviteValid, setIsInviteValid] = useState(false);
34
-
const [showAccountForm, setShowAccountForm] = useState(false);
35
-
const [accountDetails, setAccountDetails] = useState<AccountDetails>({
36
-
handle: '',
37
-
email: '',
38
-
password: ''
39
-
});
40
-
const [isCustomHandle, setIsCustomHandle] = useState(false);
41
-
const [currentHandle, setCurrentHandle] = useState('');
42
-
43
-
// Add warning when trying to close or navigate away and clean up expired data
44
-
useEffect(() => {
45
-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
46
-
// Clean up expired data
47
-
const savedDetails = localStorage.getItem('migration_details');
48
-
if (savedDetails) {
49
-
const { expiryTime } = JSON.parse(savedDetails);
50
-
if (Date.now() >= expiryTime) {
51
-
localStorage.removeItem('migration_details');
52
-
}
53
-
}
54
-
55
-
e.preventDefault();
56
-
e.returnValue = '';
57
-
return '';
58
-
};
59
-
60
-
window.addEventListener('beforeunload', handleBeforeUnload);
61
-
62
-
return () => {
63
-
window.removeEventListener('beforeunload', handleBeforeUnload);
64
-
// Clean up expired data when component unmounts
65
-
const savedDetails = localStorage.getItem('migration_details');
66
-
if (savedDetails) {
67
-
const { expiryTime } = JSON.parse(savedDetails);
68
-
if (Date.now() >= expiryTime) {
69
-
localStorage.removeItem('migration_details');
70
-
}
71
-
}
72
-
};
73
-
}, []);
74
-
75
-
// Get current user's handle and check if it's a default handle
76
-
useEffect(() => {
77
-
const checkCurrentHandle = async () => {
78
-
try {
79
-
const session = agent.session;
80
-
if (session?.handle) {
81
-
setCurrentHandle(session.handle);
82
-
}
83
-
} catch (err) {
84
-
console.error('Failed to get current handle:', err);
85
-
}
86
-
};
87
-
checkCurrentHandle();
88
-
}, [agent]);
89
-
90
-
// Debounced PDS validation
91
-
const validatePDS = useCallback(async (pdsUrl: string) => {
92
-
if (!pdsUrl) {
93
-
setPdsInfo(null);
94
-
setError('');
95
-
return;
96
-
}
97
-
98
-
setIsValidating(true);
99
-
setError('');
100
-
101
-
try {
102
-
// Ensure the URL has the correct format
103
-
if (!pdsUrl.startsWith('http://') && !pdsUrl.startsWith('https://')) {
104
-
pdsUrl = 'https://' + pdsUrl;
105
-
}
106
-
107
-
// Check if the PDS is a Bluesky PDS
108
-
const hostname = new URL(pdsUrl).hostname;
109
-
if (hostname === 'bsky.social' || hostname === 'bsky.app' || hostname.endsWith('bsky.network')) {
110
-
setPdsInfo({
111
-
exists: false,
112
-
requiresInvite: false,
113
-
domain: hostname,
114
-
availableUserDomains: []
115
-
});
116
-
setError('Bluesky currently does not support migrating accounts to their data servers.');
117
-
return;
118
-
}
119
-
120
-
// Create a temporary agent to check the PDS
121
-
const tempAgent = new AtpAgent({ service: pdsUrl });
122
-
123
-
try {
124
-
// Try to get the server info
125
-
const info = await tempAgent.api.com.atproto.server.describeServer();
126
-
const domain = new URL(pdsUrl).hostname;
127
-
128
-
setPdsInfo({
129
-
exists: true,
130
-
requiresInvite: info.data.inviteCodeRequired || false,
131
-
domain,
132
-
availableUserDomains: info.data.availableUserDomains || []
133
-
});
134
-
} catch (err) {
135
-
setPdsInfo({
136
-
exists: false,
137
-
requiresInvite: false,
138
-
domain: '',
139
-
availableUserDomains: []
140
-
});
141
-
setError('Could not connect to the specified PDS. Please check the URL and try again.');
142
-
}
143
-
} catch (err) {
144
-
setError('Invalid PDS URL format. Please enter a valid URL.');
145
-
} finally {
146
-
setIsValidating(false);
147
-
}
148
-
}, []);
149
-
150
-
// Debounce the validation
151
-
useEffect(() => {
152
-
const timeoutId = setTimeout(() => {
153
-
if (pds) {
154
-
validatePDS(pds);
155
-
}
156
-
}, 500);
157
-
158
-
return () => clearTimeout(timeoutId);
159
-
}, [pds, validatePDS]);
160
-
161
-
// Validate invite code when it changes
162
-
useEffect(() => {
163
-
if (pdsInfo?.requiresInvite && inviteCode) {
164
-
const inviteRegex = /^bsky-noob-quest-[a-zA-Z0-9]{5}-[a-zA-Z0-9]{5}$/;
165
-
setIsInviteValid(inviteRegex.test(inviteCode));
166
-
}
167
-
}, [inviteCode, pdsInfo]);
168
-
169
-
// Check if handle is custom
170
-
useEffect(() => {
171
-
if (accountDetails.handle && pdsInfo?.availableUserDomains?.length) {
172
-
const defaultDomain = pdsInfo.availableUserDomains[0];
173
-
const handleRegex = new RegExp(`^[a-zA-Z0-9._-]+@${defaultDomain}$`);
174
-
const isDefaultHandle = handleRegex.test(accountDetails.handle);
175
-
setIsCustomHandle(!isDefaultHandle);
176
-
}
177
-
}, [accountDetails.handle, pdsInfo]);
178
-
179
-
// Auto-scroll to latest step
180
-
useEffect(() => {
181
-
const formSection = document.querySelector('.form-section:not(.completed)');
182
-
if (formSection) {
183
-
formSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
184
-
}
185
-
}, [showAccountForm]);
186
-
187
-
// Check if current handle is default
188
-
const isCurrentHandleDefault = useCallback(() => {
189
-
if (!currentHandle || !pdsInfo?.availableUserDomains?.length) return false;
190
-
191
-
// If migrating from Bluesky PDS (bsky.network), check if handle is from bsky.social
192
-
if (agent.serviceUrl.host.endsWith('.bsky.network')) {
193
-
return currentHandle.endsWith('.bsky.social');
194
-
}
195
-
196
-
// For third-party PDS, check if handle ends with any of the available user domains
197
-
return pdsInfo.availableUserDomains.some(domain =>
198
-
currentHandle.endsWith(`${domain}`)
199
-
);
200
-
}, [currentHandle, pdsInfo]);
201
-
202
-
const handlePdsBlur = () => {
203
-
if (pds) {
204
-
validatePDS(pds);
205
-
}
206
-
};
207
-
208
-
const handleContinue = () => {
209
-
if (pdsInfo?.exists && (!pdsInfo.requiresInvite || isInviteValid)) {
210
-
setShowAccountForm(true);
211
-
}
212
-
};
213
-
214
-
const handleStartMigration = () => {
215
-
// Clean up any existing expired data first
216
-
const savedDetails = localStorage.getItem('migration_details');
217
-
if (savedDetails) {
218
-
const { expiryTime } = JSON.parse(savedDetails);
219
-
if (Date.now() >= expiryTime) {
220
-
localStorage.removeItem('migration_details');
221
-
}
222
-
}
223
-
224
-
// Save account details to localStorage with 30-minute expiry
225
-
const expiryTime = Date.now() + (30 * 60 * 1000); // 30 minutes in milliseconds
226
-
const migrationDetails = {
227
-
pds: pds,
228
-
inviteCode: inviteCode || null,
229
-
handle: accountDetails.handle + (pdsInfo?.availableUserDomains?.[0] ? `${pdsInfo.availableUserDomains[0]}` : ''),
230
-
email: accountDetails.email,
231
-
password: accountDetails.password,
232
-
expiryTime: expiryTime
233
-
};
234
-
localStorage.setItem('migration_details', JSON.stringify(migrationDetails));
235
-
236
-
// TODO: Implement migration
237
-
};
238
-
239
-
return (
240
-
<div className="actions-page">
241
-
<Header agent={agent} onLogout={onLogout} />
242
-
243
-
<div className="actions-container">
244
-
<div className="page-content">
245
-
<h2>Migrate your account</h2>
246
-
247
-
<div className={`form-section ${showAccountForm ? 'completed' : ''}`}>
248
-
<h3>Select your new PDS</h3>
249
-
<div className="form-group">
250
-
<label htmlFor="pds-input">Personal Data Server (PDS)</label>
251
-
<input
252
-
id="pds-input"
253
-
type="text"
254
-
className="form-input"
255
-
placeholder="Example: example-pds.com"
256
-
value={pds}
257
-
onChange={(e) => setPds(e.target.value)}
258
-
onBlur={handlePdsBlur}
259
-
disabled={isValidating || showAccountForm}
260
-
/>
261
-
{isValidating && (
262
-
<div className="loading-message">Checking PDS availability...</div>
263
-
)}
264
-
{error && (
265
-
<div className="error-message">{error}</div>
266
-
)}
267
-
{pdsInfo?.exists && !pdsInfo.requiresInvite && (
268
-
<div className="success-message">โ This PDS does not require an invite code</div>
269
-
)}
270
-
</div>
271
-
272
-
{pdsInfo?.exists && pdsInfo.requiresInvite && (
273
-
<div className="form-group">
274
-
<label htmlFor="invite-code">Invite Code</label>
275
-
<input
276
-
id="invite-code"
277
-
type="text"
278
-
className="form-input"
279
-
placeholder="Example: bsky-noob-quest-abcde-12345"
280
-
value={inviteCode}
281
-
onChange={(e) => setInviteCode(e.target.value)}
282
-
disabled={showAccountForm}
283
-
/>
284
-
</div>
285
-
)}
286
-
287
-
{!showAccountForm && (
288
-
<div className="button-container">
289
-
<button
290
-
className="back-button"
291
-
onClick={() => navigate('/migration')}
292
-
>
293
-
โ Go back
294
-
</button>
295
-
{pdsInfo?.exists && (!pdsInfo.requiresInvite || isInviteValid) && (
296
-
<button
297
-
className="continue-button"
298
-
onClick={handleContinue}
299
-
>
300
-
Continue โ
301
-
</button>
302
-
)}
303
-
</div>
304
-
)}
305
-
</div>
306
-
307
-
{showAccountForm && (
308
-
<div className="form-section">
309
-
<h3>New account details</h3>
310
-
<div className="form-group">
311
-
<label htmlFor="handle-input">Handle</label>
312
-
<div className="handle-input-container">
313
-
<input
314
-
id="handle-input"
315
-
type="text"
316
-
className="form-input"
317
-
placeholder="username"
318
-
value={accountDetails.handle}
319
-
onChange={(e) => setAccountDetails(prev => ({ ...prev, handle: e.target.value }))}
320
-
/>
321
-
{pdsInfo?.availableUserDomains?.[0] && (
322
-
<span className="handle-domain">{pdsInfo.availableUserDomains[0]}</span>
323
-
)}
324
-
</div>
325
-
{isCustomHandle && !isCurrentHandleDefault() && (
326
-
<div className="info-message">
327
-
During the migration, you'll be assigned a temporary handle. After the migration is completed, we will assign your custom handle automatically.
328
-
</div>
329
-
)}
330
-
</div>
331
-
332
-
<div className="form-group">
333
-
<label htmlFor="email-input">Email</label>
334
-
<input
335
-
id="email-input"
336
-
type="email"
337
-
className="form-input"
338
-
placeholder="Your email address"
339
-
value={accountDetails.email}
340
-
onChange={(e) => setAccountDetails(prev => ({ ...prev, email: e.target.value }))}
341
-
/>
342
-
</div>
343
-
344
-
<div className="form-group">
345
-
<label htmlFor="password-input">Password</label>
346
-
<input
347
-
id="password-input"
348
-
type="password"
349
-
className="form-input"
350
-
placeholder="Your new password"
351
-
value={accountDetails.password}
352
-
onChange={(e) => setAccountDetails(prev => ({ ...prev, password: e.target.value }))}
353
-
/>
354
-
</div>
355
-
<small>We recommend using a different password for your new account. Save all of the above details somewhere before continuing.</small>
356
-
<div className="button-container">
357
-
<button
358
-
className="back-button"
359
-
onClick={() => setShowAccountForm(false)}
360
-
>
361
-
โ Go back
362
-
</button>
363
-
<button
364
-
className="continue-button"
365
-
onClick={handleStartMigration}
366
-
>
367
-
Continue โ
368
-
</button>
369
-
</div>
370
-
</div>
371
-
)}
372
-
</div>
373
-
</div>
374
-
<Footer />
375
-
</div>
376
-
);
377
-
}
···
-1
src/components/common/NetworkWarning.tsx
-1
src/components/common/NetworkWarning.tsx
-67
src/components/common/RecoveryKey.tsx
-67
src/components/common/RecoveryKey.tsx
···
1
-
import { useNavigate } from 'react-router-dom';
2
-
import { AtpAgent } from '@atproto/api';
3
-
import Footer from '../layout/Footer';
4
-
import Header from '../layout/Header';
5
-
import '../../styles/App.css';
6
-
7
-
interface RecoveryKeyProps {
8
-
agent: AtpAgent;
9
-
onLogout: () => void;
10
-
}
11
-
12
-
export default function RecoveryKey({ agent, onLogout }: RecoveryKeyProps) {
13
-
const navigate = useNavigate();
14
-
15
-
return (
16
-
<div className="actions-page">
17
-
<Header agent={agent} onLogout={onLogout} />
18
-
19
-
<div className="actions-container">
20
-
<div className="page-content">
21
-
<h2>Add a recovery key</h2>
22
-
<p>A recovery key (known as a <b>rotation key</b> in the AT Protocol) is a cryptographic key associated with your account that allows you to modify your account's core identity.</p>
23
-
24
-
<h3>How rotation keys work</h3>
25
-
<p>In the AT Protocol, your account is identified using a DID (<b>Decentralized Identifier</b>), with most accounts on the protocol using a variant of it developed specifically for the protocol. The account's core information (such as your handle and data server on the network) is stored in the account's DID document.</p>
26
-
<p>To change this document, you use a rotation key to confirm that you are the owner of the account and that you are authorized to make the changes. For example, when changing your handle, your data server (also known as a PDS) will use its own rotation key to change it without asking you to manually sign the operation.</p>
27
-
<h3>Why should I add another key?</h3>
28
-
<p>Adding a rotation key allows you to regain control of your account if it is compromised. It also allows you to move your account to a new data server, even if the current server is down.</p>
29
-
<div className="warning-section">
30
-
<h3>โ ๏ธ Read Before Continuing โ ๏ธ</h3>
31
-
<ul>
32
-
<li>You will need a PLC operation token to add a recovery key. Tokens are sent to the email address associated with your account.</li>
33
-
<li>While we do generate a key for you, we will not store it. Please save it in a secure location.</li>
34
-
<li>Keep your recovery key private. Anyone with access to it could potentially take control of your account.</li>
35
-
<li>If you're using a third-party PDS, it must be able to send emails or you will not be able to use this tool to add a recovery key.</li>
36
-
</ul>
37
-
</div>
38
-
<div className="docs-section">
39
-
<h3>Additional Resources</h3>
40
-
<p>For the technically inclined, here are some additional resources for how rotation keys work:</p>
41
-
<ul>
42
-
<li><a href="https://atproto.com/guides/identity" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on identity</a></li>
43
-
<li><a href="https://whtwnd.com/did:plc:xz3euvkhf44iadavovbsmqoo/3laimapx6ks2b" target="_blank" rel="noopener noreferrer">Guide to adding a recovery key using the command line</a></li>
44
-
<li><a href="https://whtwnd.com/did:plc:44ybard66vv44zksje25o7dz/3lj7jmt2ct72r" target="_blank" rel="noopener noreferrer">More in-depth guide to adding a recovery key</a></li>
45
-
</ul>
46
-
</div>
47
-
48
-
<div className="button-container">
49
-
<button
50
-
className="back-button"
51
-
onClick={() => navigate('/actions')}
52
-
>
53
-
โ Go back
54
-
</button>
55
-
<button
56
-
className="continue-button"
57
-
onClick={() => navigate('/recovery-key/process')}
58
-
>
59
-
Continue โ
60
-
</button>
61
-
</div>
62
-
</div>
63
-
</div>
64
-
<Footer />
65
-
</div>
66
-
);
67
-
}
···
-53
src/components/common/RecoveryKeyProcess.tsx
-53
src/components/common/RecoveryKeyProcess.tsx
···
1
-
import { useNavigate } from 'react-router-dom';
2
-
import { useEffect } from 'react';
3
-
import { AtpAgent } from '@atproto/api';
4
-
import Footer from '../layout/Footer';
5
-
import Header from '../layout/Header';
6
-
import '../../styles/App.css';
7
-
8
-
interface RecoveryKeyProcessProps {
9
-
agent: AtpAgent;
10
-
onLogout: () => void;
11
-
}
12
-
13
-
export default function RecoveryKeyProcess({ agent, onLogout }: RecoveryKeyProcessProps) {
14
-
const navigate = useNavigate();
15
-
16
-
// Add warning when trying to close or navigate away
17
-
useEffect(() => {
18
-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
19
-
e.preventDefault();
20
-
e.returnValue = '';
21
-
return '';
22
-
};
23
-
24
-
window.addEventListener('beforeunload', handleBeforeUnload);
25
-
26
-
return () => {
27
-
window.removeEventListener('beforeunload', handleBeforeUnload);
28
-
};
29
-
}, []);
30
-
31
-
return (
32
-
<div className="actions-page">
33
-
<Header agent={agent} onLogout={onLogout} />
34
-
35
-
<div className="actions-container">
36
-
<div className="page-content">
37
-
<h2>Add Recovery Key</h2>
38
-
<p>This page will guide you through the process of adding a recovery key to your account.</p>
39
-
40
-
<div className="button-container">
41
-
<button
42
-
className="back-button"
43
-
onClick={() => navigate('/recovery-key')}
44
-
>
45
-
โ Go back
46
-
</button>
47
-
</div>
48
-
</div>
49
-
</div>
50
-
<Footer />
51
-
</div>
52
-
);
53
-
}
···
-50
src/components/layout/Header.tsx
-50
src/components/layout/Header.tsx
···
1
-
import { useEffect } from 'react';
2
-
import { AtpAgent } from '@atproto/api';
3
-
import { useAvatar } from '../../contexts/AvatarContext';
4
-
import '../../styles/App.css';
5
-
6
-
interface HeaderProps {
7
-
agent: AtpAgent;
8
-
onLogout: () => void;
9
-
}
10
-
11
-
export default function Header({ agent, onLogout }: HeaderProps) {
12
-
const { avatarUrl, setAvatarUrl } = useAvatar();
13
-
14
-
useEffect(() => {
15
-
const fetchProfile = async () => {
16
-
// Only fetch if we don't already have an avatar
17
-
if (!avatarUrl) {
18
-
try {
19
-
const profile = await agent.getProfile({ actor: agent.session?.handle || '' });
20
-
if (profile.data.avatar) {
21
-
setAvatarUrl(profile.data.avatar);
22
-
}
23
-
} catch (err) {
24
-
console.error('Error fetching profile:', err);
25
-
}
26
-
}
27
-
};
28
-
29
-
fetchProfile();
30
-
}, [agent, avatarUrl, setAvatarUrl]);
31
-
32
-
return (
33
-
<header className="app-header">
34
-
<h1 className="app-title">ATproto Migrator</h1>
35
-
<div className="user-info">
36
-
{avatarUrl && (
37
-
<img
38
-
src={avatarUrl}
39
-
alt="Profile"
40
-
className="user-avatar"
41
-
/>
42
-
)}
43
-
<span className="user-handle" title={agent.session?.handle}>{agent.session?.handle}</span>
44
-
<button className="logout-button" onClick={onLogout}>
45
-
Logout
46
-
</button>
47
-
</div>
48
-
</header>
49
-
);
50
-
}
···
+206
src/components/migration/accountDetailsForm.tsx
+206
src/components/migration/accountDetailsForm.tsx
···
···
1
+
import { useState, useEffect, useCallback } from 'react';
2
+
import { ServerDescription } from '../../lib/migration/serverDescription';
3
+
import { validateHandle } from '../../lib/migration/accountDetailsValidation';
4
+
5
+
interface AccountDetailsFormProps {
6
+
currentHandle: string;
7
+
pds: string;
8
+
inviteCode: string;
9
+
serverDescription: ServerDescription;
10
+
newServerDescription: ServerDescription;
11
+
onBack: () => void;
12
+
onSubmit: (handle: string, email: string, password: string) => void;
13
+
}
14
+
15
+
export default function AccountDetailsForm({
16
+
currentHandle,
17
+
serverDescription,
18
+
newServerDescription,
19
+
onBack,
20
+
onSubmit
21
+
}: AccountDetailsFormProps) {
22
+
const [handle, setHandle] = useState('');
23
+
const [email, setEmail] = useState('');
24
+
const [password, setPassword] = useState('');
25
+
const [isUsingDefaultDomain, setIsUsingDefaultDomain] = useState(false);
26
+
const [emailError, setEmailError] = useState('');
27
+
const [passwordError, setPasswordError] = useState('');
28
+
const [handleError, setHandleError] = useState('');
29
+
30
+
useEffect(() => {
31
+
// Check if current handle is using a default domain
32
+
const availableDomains = serverDescription.getAvailableUserDomains();
33
+
const domainNames = Object.values(availableDomains);
34
+
const { isUsingDefaultDomain, customHandle } = validateHandle(currentHandle, domainNames);
35
+
36
+
37
+
setIsUsingDefaultDomain(isUsingDefaultDomain);
38
+
if (isUsingDefaultDomain) {
39
+
// Set initial handle value from current handle
40
+
setHandle(customHandle);
41
+
} else {
42
+
setHandle(customHandle);
43
+
}
44
+
}, [currentHandle, serverDescription]);
45
+
46
+
const validateEmail = (email: string) => {
47
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
48
+
return emailRegex.test(email);
49
+
};
50
+
51
+
const validatePassword = (password: string) => {
52
+
return password.length >= 8;
53
+
};
54
+
55
+
const validateHandleInput = (handle: string) => {
56
+
if (!isUsingDefaultDomain) {
57
+
return '';
58
+
}
59
+
60
+
if (handle.length < 3) {
61
+
return 'Handle must be at least 3 characters long';
62
+
}
63
+
if (!/^[a-zA-Z0-9-]+$/.test(handle)) {
64
+
return 'Handle can only contain letters, numbers, and hyphens';
65
+
}
66
+
return '';
67
+
};
68
+
69
+
// Debounced validation functions
70
+
const debouncedValidateHandle = useCallback((value: string) => {
71
+
const timeoutId = setTimeout(() => {
72
+
setHandleError(validateHandleInput(value));
73
+
}, 500);
74
+
return () => clearTimeout(timeoutId);
75
+
}, []);
76
+
77
+
const debouncedValidateEmail = useCallback((value: string) => {
78
+
const timeoutId = setTimeout(() => {
79
+
setEmailError(validateEmail(value) ? '' : 'Please enter a valid email address');
80
+
}, 500);
81
+
return () => clearTimeout(timeoutId);
82
+
}, []);
83
+
84
+
const debouncedValidatePassword = useCallback((value: string) => {
85
+
const timeoutId = setTimeout(() => {
86
+
setPasswordError(validatePassword(value) ? '' : 'Password must be at least 8 characters long');
87
+
}, 500);
88
+
return () => clearTimeout(timeoutId);
89
+
}, []);
90
+
91
+
const validateAllFields = () => {
92
+
// Run all validations immediately
93
+
const handleValidationError = validateHandleInput(handle);
94
+
const emailValidationError = !validateEmail(email) ? 'Please enter a valid email address' : '';
95
+
const passwordValidationError = !validatePassword(password) ? 'Password must be at least 8 characters long' : '';
96
+
97
+
// Update all error states
98
+
setHandleError(handleValidationError);
99
+
setEmailError(emailValidationError);
100
+
setPasswordError(passwordValidationError);
101
+
102
+
// Return true if all validations pass
103
+
return !handleValidationError && !emailValidationError && !passwordValidationError;
104
+
};
105
+
106
+
const handleSubmit = (e: React.FormEvent) => {
107
+
e.preventDefault();
108
+
109
+
// Validate all fields and only proceed if all validations pass
110
+
if (validateAllFields()) {
111
+
onSubmit(handle, email, password);
112
+
}
113
+
};
114
+
115
+
// Get the first available domain from the new server description
116
+
const newAvailableDomains = newServerDescription.getAvailableUserDomains();
117
+
const newFirstAvailableDomain = Object.values(newAvailableDomains)[0];
118
+
119
+
return (
120
+
<form onSubmit={handleSubmit}>
121
+
<h3>Create your new account's credentials</h3>
122
+
123
+
{isUsingDefaultDomain && (
124
+
<div>
125
+
<div className="info-message">
126
+
<h3>Default handle detected!</h3>
127
+
We have detected that your current handle is <strong>@{currentHandle}</strong>, which is a default handle provided by your data server. Because of this, we are unable to migrate your current handle to your new data server. We have automatically filled in your previous handle for you, however you can change it to something different if you wish.
128
+
</div>
129
+
130
+
<div className="form-group">
131
+
<label htmlFor="handle">Handle</label>
132
+
<div className="handle-input-container">
133
+
<input
134
+
type="text"
135
+
id="handle"
136
+
value={handle}
137
+
onChange={(e) => {
138
+
const newValue = e.target.value;
139
+
setHandle(newValue);
140
+
debouncedValidateHandle(newValue);
141
+
}}
142
+
placeholder="alice"
143
+
className={`form-input ${handleError ? 'error' : ''}`}
144
+
/>
145
+
<span className="handle-domain">{newFirstAvailableDomain}</span>
146
+
</div>
147
+
{handleError && <div className="error-message">{handleError}</div>}
148
+
</div>
149
+
</div>
150
+
)}
151
+
152
+
<div className="form-group">
153
+
<label htmlFor="email">Email</label>
154
+
<input
155
+
type="text"
156
+
id="email"
157
+
value={email}
158
+
onChange={(e) => {
159
+
const newValue = e.target.value;
160
+
setEmail(newValue);
161
+
debouncedValidateEmail(newValue);
162
+
}}
163
+
placeholder="popbob@example.com"
164
+
required
165
+
className={`form-input ${emailError ? 'error' : ''}`}
166
+
/>
167
+
{emailError && <div className="error-message">{emailError}</div>}
168
+
</div>
169
+
170
+
<div className="form-group">
171
+
<label htmlFor="password">Password</label>
172
+
<input
173
+
type="password"
174
+
id="password"
175
+
value={password}
176
+
onChange={(e) => {
177
+
const newValue = e.target.value;
178
+
setPassword(newValue);
179
+
debouncedValidatePassword(newValue);
180
+
}}
181
+
placeholder="hunter2"
182
+
required
183
+
className={`form-input ${passwordError ? 'error' : ''}`}
184
+
/>
185
+
{passwordError && <div className="error-message">{passwordError}</div>}
186
+
</div>
187
+
<small>We recommend using a new, unique password for your new account.</small>
188
+
189
+
<div className="button-container">
190
+
<button
191
+
type="button"
192
+
className="back-button"
193
+
onClick={onBack}
194
+
>
195
+
โ Go back
196
+
</button>
197
+
<button
198
+
type="submit"
199
+
className="continue-button"
200
+
>
201
+
Continue โ
202
+
</button>
203
+
</div>
204
+
</form>
205
+
);
206
+
}
+165
src/components/migration/confirmationStep.tsx
+165
src/components/migration/confirmationStep.tsx
···
···
1
+
import { useState } from 'react';
2
+
import '../../css/confirmation.css';
3
+
4
+
interface ConfirmationStepProps {
5
+
handle: string;
6
+
email: string;
7
+
password: string;
8
+
pds: string;
9
+
onBack: () => void;
10
+
onConfirm: () => void;
11
+
currentHandle?: string;
12
+
currentPds?: string;
13
+
}
14
+
15
+
export default function ConfirmationStep({
16
+
handle,
17
+
email,
18
+
password,
19
+
pds,
20
+
onBack,
21
+
onConfirm,
22
+
currentHandle = '',
23
+
currentPds = ''
24
+
}: ConfirmationStepProps) {
25
+
const [showPassword, setShowPassword] = useState(false);
26
+
const [showWarning, setShowWarning] = useState(false);
27
+
const [pdsVerification, setPdsVerification] = useState('');
28
+
29
+
const handleConfirmClick = () => {
30
+
setShowWarning(true);
31
+
};
32
+
33
+
const handleWarningConfirm = () => {
34
+
setShowWarning(false);
35
+
setPdsVerification('');
36
+
onConfirm();
37
+
};
38
+
39
+
const isPdsVerified = pdsVerification.toLowerCase() === pds.toLowerCase();
40
+
41
+
return (
42
+
<div>
43
+
<h3>Migration summary</h3>
44
+
45
+
<div className="comparison-container">
46
+
<div className="comparison-side">
47
+
<h3>Before</h3>
48
+
<div className="detail-group">
49
+
<label>Handle</label>
50
+
<div>@{currentHandle}</div>
51
+
</div>
52
+
<div className="detail-group">
53
+
<label>PDS</label>
54
+
<div>{currentPds}</div>
55
+
</div>
56
+
</div>
57
+
58
+
<div className="comparison-arrow">โ</div>
59
+
60
+
<div className="comparison-side">
61
+
<h3>After</h3>
62
+
<div className="detail-group">
63
+
<label>Handle</label>
64
+
<div>@{handle}</div>
65
+
</div>
66
+
<div className="detail-group">
67
+
<label>PDS</label>
68
+
<div>{pds}</div>
69
+
</div>
70
+
</div>
71
+
</div>
72
+
73
+
<div className="detail-group">
74
+
<label>New Email</label>
75
+
<div className="detail-value">{email}</div>
76
+
</div>
77
+
78
+
<div className="detail-group">
79
+
<label>New Password</label>
80
+
<div className="password-container">
81
+
<div className="detail-value">
82
+
{showPassword ? (
83
+
password
84
+
) : (
85
+
<span className="hidden-password">
86
+
{'โข'.repeat(password.length)}
87
+
</span>
88
+
)}
89
+
</div>
90
+
<button
91
+
type="button"
92
+
className="password-toggle"
93
+
onClick={() => setShowPassword(!showPassword)}
94
+
>
95
+
{showPassword ? 'Hide' : 'Show'}
96
+
</button>
97
+
</div>
98
+
</div>
99
+
100
+
<div className="button-container">
101
+
<button
102
+
type="button"
103
+
className="back-button"
104
+
onClick={onBack}
105
+
>
106
+
โ Go back
107
+
</button>
108
+
<button
109
+
type="button"
110
+
className="confirm-button"
111
+
onClick={handleConfirmClick}
112
+
>
113
+
Confirm Migration โ
114
+
</button>
115
+
</div>
116
+
117
+
{showWarning && (
118
+
<div className="warning-overlay">
119
+
<div className="warning-dialog">
120
+
<h3>Last chance to turn back!</h3>
121
+
<div className="warning-content">
122
+
<p>This tool is currently in beta, as such:</p>
123
+
<ul>
124
+
<li>We do not guarantee that the migration will succeed</li>
125
+
<li>While most errors are recoverable, for the moment we cannot help you recover from them and it will need to be continued manually using command line tools</li>
126
+
<li>We claim no responsibility for any problems that may occur during the process</li>
127
+
</ul>
128
+
<p>If you understand the risks and want to proceed, please type your new PDS <b>({pds})</b> below:</p>
129
+
<div className="pds-verification">
130
+
<input
131
+
type="text"
132
+
value={pdsVerification}
133
+
onChange={(e) => setPdsVerification(e.target.value)}
134
+
placeholder="Type your new PDS here"
135
+
className={pdsVerification && !isPdsVerified ? 'error' : ''}
136
+
/>
137
+
</div>
138
+
</div>
139
+
<div className="warning-buttons">
140
+
<button
141
+
type="button"
142
+
className="back-button"
143
+
onClick={handleWarningConfirm}
144
+
disabled={!isPdsVerified}
145
+
>
146
+
Migrate!
147
+
</button>
148
+
<button
149
+
type="button"
150
+
className="confirm-button"
151
+
onClick={() => {
152
+
setShowWarning(false);
153
+
setPdsVerification('');
154
+
}}
155
+
>
156
+
Cancel
157
+
</button>
158
+
159
+
</div>
160
+
</div>
161
+
</div>
162
+
)}
163
+
</div>
164
+
);
165
+
}
+258
src/components/migration/pdsForm.tsx
+258
src/components/migration/pdsForm.tsx
···
···
1
+
import { useState, useCallback, useEffect } from 'react';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import { validatePDS, getServerDescription, validateInviteCode } from '../../lib/migration/pdsValidation';
4
+
import { ServerDescription } from '../../lib/migration/serverDescription';
5
+
import { useDebounce } from '../../hooks/useDebounce';
6
+
7
+
interface PdsFormProps {
8
+
agent: AtpAgent;
9
+
onSubmit: (pds: string, inviteCode: string, serverDescription: ServerDescription) => void;
10
+
onBack: () => void;
11
+
}
12
+
13
+
export default function PdsForm({ agent, onSubmit, onBack }: PdsFormProps) {
14
+
const [pds, setPds] = useState('');
15
+
const [inviteCode, setInviteCode] = useState('');
16
+
const [pdsError, setPdsError] = useState<string | null>(null);
17
+
const [inviteCodeError, setInviteCodeError] = useState<string | null>(null);
18
+
const [isValidatingPds, setIsValidatingPds] = useState(false);
19
+
const [isValidatingInviteCode, setIsValidatingInviteCode] = useState(false);
20
+
const [isPdsValid, setIsPdsValid] = useState(false);
21
+
const [isInviteCodeValid, setIsInviteCodeValid] = useState(false);
22
+
const [serverDescription, setServerDescription] = useState<ServerDescription | null>(null);
23
+
const [lastValidatedPds, setLastValidatedPds] = useState('');
24
+
const [isFormReady, setIsFormReady] = useState(false);
25
+
26
+
useEffect(() => {
27
+
const isInviteCodeSectionValid = !serverDescription?.isInviteCodeRequired() ||
28
+
Boolean(inviteCode && !inviteCodeError && !isValidatingInviteCode && isInviteCodeValid);
29
+
30
+
setIsFormReady(
31
+
Boolean(isPdsValid &&
32
+
!pdsError &&
33
+
!isValidatingPds &&
34
+
isInviteCodeSectionValid)
35
+
);
36
+
}, [isPdsValid, pdsError, isValidatingPds, serverDescription, inviteCode, inviteCodeError, isValidatingInviteCode, isInviteCodeValid]);
37
+
38
+
const validatePdsInput = useCallback(async (value: string) => {
39
+
if (value === lastValidatedPds && isPdsValid && !pdsError) {
40
+
return;
41
+
}
42
+
43
+
setIsValidatingPds(true);
44
+
setIsPdsValid(false);
45
+
setServerDescription(null);
46
+
setPdsError(null);
47
+
48
+
try {
49
+
const validationResult = await validatePDS(value, agent);
50
+
if (!validationResult.isValid) {
51
+
console.error('PDS validation failed:', {
52
+
pds: value,
53
+
error: validationResult.error
54
+
});
55
+
setPdsError(validationResult.error);
56
+
setIsValidatingPds(false);
57
+
return;
58
+
}
59
+
60
+
try {
61
+
const description = await getServerDescription(value);
62
+
setServerDescription(description);
63
+
setPdsError(null);
64
+
setIsPdsValid(true);
65
+
setLastValidatedPds(value);
66
+
} catch (e) {
67
+
console.error('Error getting server description:', {
68
+
pds: value,
69
+
error: e instanceof Error ? {
70
+
name: e.name,
71
+
message: e.message,
72
+
stack: e.stack
73
+
} : e
74
+
});
75
+
setPdsError('Failed to get server information');
76
+
}
77
+
} catch (e) {
78
+
console.error('Error validating PDS:', {
79
+
pds: value,
80
+
error: e instanceof Error ? {
81
+
name: e.name,
82
+
message: e.message,
83
+
stack: e.stack
84
+
} : e
85
+
});
86
+
setPdsError('An unexpected error occurred');
87
+
}
88
+
89
+
setIsValidatingPds(false);
90
+
}, [agent, lastValidatedPds, isPdsValid, pdsError]);
91
+
92
+
const validateInviteCodeInput = useCallback(async (pdsValue: string, inviteCodeValue: string) => {
93
+
if (!pdsValue || !inviteCodeValue) {
94
+
setIsInviteCodeValid(false);
95
+
return;
96
+
}
97
+
98
+
setIsValidatingInviteCode(true);
99
+
setInviteCodeError(null);
100
+
setIsInviteCodeValid(false);
101
+
102
+
try {
103
+
const inviteCodeValidationResult = await validateInviteCode(pdsValue, inviteCodeValue);
104
+
if (!inviteCodeValidationResult.isValid) {
105
+
setInviteCodeError(inviteCodeValidationResult.error);
106
+
setIsInviteCodeValid(false);
107
+
} else {
108
+
setIsInviteCodeValid(true);
109
+
}
110
+
} catch (e) {
111
+
console.error('Error validating invite code:', {
112
+
pds: pdsValue,
113
+
inviteCode: inviteCodeValue,
114
+
error: e instanceof Error ? {
115
+
name: e.name,
116
+
message: e.message,
117
+
stack: e.stack
118
+
} : e
119
+
});
120
+
setInviteCodeError('Failed to validate invite code');
121
+
setIsInviteCodeValid(false);
122
+
}
123
+
124
+
setIsValidatingInviteCode(false);
125
+
}, []);
126
+
127
+
const debouncedValidatePds = useDebounce(validatePdsInput, 500);
128
+
const debouncedValidateInviteCode = useDebounce(validateInviteCodeInput, 500);
129
+
130
+
const handleSubmit = async (e: React.FormEvent) => {
131
+
e.preventDefault();
132
+
if (!isFormReady) return;
133
+
134
+
setIsValidatingPds(true);
135
+
setIsValidatingInviteCode(true);
136
+
137
+
try {
138
+
await validatePdsInput(pds);
139
+
140
+
if (serverDescription?.isInviteCodeRequired()) {
141
+
await validateInviteCodeInput(pds, inviteCode);
142
+
}
143
+
144
+
if (!pdsError && !inviteCodeError) {
145
+
onSubmit(pds, inviteCode, serverDescription!);
146
+
}
147
+
} catch (e) {
148
+
console.error('Error during form submission:', {
149
+
pds,
150
+
inviteCode,
151
+
error: e instanceof Error ? {
152
+
name: e.name,
153
+
message: e.message,
154
+
stack: e.stack
155
+
} : e
156
+
});
157
+
setPdsError('An unexpected error occurred');
158
+
}
159
+
160
+
setIsValidatingPds(false);
161
+
setIsValidatingInviteCode(false);
162
+
};
163
+
164
+
const handlePdsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
165
+
const value = e.target.value;
166
+
setPds(value);
167
+
setIsPdsValid(false);
168
+
setServerDescription(null);
169
+
setLastValidatedPds('');
170
+
setPdsError(null);
171
+
debouncedValidatePds(value);
172
+
};
173
+
174
+
const handleInviteCodeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
175
+
const value = e.target.value;
176
+
setInviteCode(value);
177
+
setInviteCodeError(null);
178
+
setIsInviteCodeValid(false);
179
+
if (pds) {
180
+
debouncedValidateInviteCode(pds, value);
181
+
}
182
+
};
183
+
184
+
const handlePdsBlur = () => {
185
+
validatePdsInput(pds);
186
+
};
187
+
188
+
const handleInviteCodeBlur = () => {
189
+
if (pds && inviteCode) {
190
+
validateInviteCodeInput(pds, inviteCode);
191
+
}
192
+
};
193
+
194
+
return (
195
+
<form onSubmit={handleSubmit}>
196
+
<h3>Find your new host</h3>
197
+
<div className="form-group">
198
+
<label htmlFor="pds">Personal Data Server (PDS)</label>
199
+
<input
200
+
type="text"
201
+
id="pds"
202
+
value={pds}
203
+
onChange={handlePdsChange}
204
+
onBlur={handlePdsBlur}
205
+
placeholder="Example: pds.example.com"
206
+
required
207
+
className={`form-input ${pdsError ? 'error' : ''} ${isPdsValid ? 'success' : ''}`}
208
+
disabled={isValidatingPds}
209
+
/>
210
+
{(!isValidatingPds && pdsError && <div className="error-message">{pdsError}</div>) ||
211
+
(isValidatingPds && <div className="info-message" style={{ marginTop: '0.5rem' }}>Checking if the data server is valid...</div>) ||
212
+
(isPdsValid && <div className="success-message" style={{ marginTop: '0.5rem' }}>โ This data server is alive!</div>)}
213
+
214
+
{serverDescription?.isInviteCodeRequired() && (
215
+
<div className="form-group" style={{ marginTop: '1rem' }}>
216
+
<label htmlFor="inviteCode">Invite Code</label>
217
+
<input
218
+
type="text"
219
+
id="inviteCode"
220
+
value={inviteCode}
221
+
onChange={handleInviteCodeChange}
222
+
onBlur={handleInviteCodeBlur}
223
+
placeholder={`Example: ${pds.replace(/\./g, '-')}-XXXXX-XXXXX`}
224
+
required
225
+
className={`form-input ${inviteCodeError ? 'error' : ''} ${isInviteCodeValid ? 'success' : ''}`}
226
+
disabled={isValidatingInviteCode}
227
+
/>
228
+
{(!isValidatingInviteCode && inviteCodeError && <div className="error-message">{inviteCodeError}</div>) ||
229
+
(isValidatingInviteCode && <div className="info-message" style={{ marginTop: '0.5rem' }}>Validating invite code...</div>) ||
230
+
(isInviteCodeValid && <div className="success-message" style={{ marginTop: '0.5rem' }}>โ Invite code is valid</div>)}
231
+
<div className="info-message" style={{ marginTop: '0.5rem' }}>
232
+
This server requires an invite code to register.
233
+
</div>
234
+
</div>
235
+
)}
236
+
237
+
<div className="button-container">
238
+
<button
239
+
type="button"
240
+
className="back-button"
241
+
onClick={onBack}
242
+
disabled={isValidatingPds || isValidatingInviteCode}
243
+
>
244
+
โ Go back
245
+
</button>
246
+
{isFormReady && (
247
+
<button
248
+
type="submit"
249
+
className="continue-button"
250
+
>
251
+
Continue โ
252
+
</button>
253
+
)}
254
+
</div>
255
+
</div>
256
+
</form>
257
+
);
258
+
}
+158
src/css/actions.css
+158
src/css/actions.css
···
···
1
+
/* Actions page styles */
2
+
.actions-page {
3
+
display: flex;
4
+
flex-direction: column;
5
+
min-height: 100vh;
6
+
max-width: 800px;
7
+
margin: 0 auto;
8
+
padding: 0 20px;
9
+
gap: 1rem;
10
+
}
11
+
12
+
.actions-container {
13
+
padding: 0;
14
+
max-width: 800px;
15
+
width: 100%;
16
+
margin: 0 auto;
17
+
}
18
+
19
+
.actions-list {
20
+
display: flex;
21
+
flex-direction: column;
22
+
gap: 1rem;
23
+
margin-top: 1rem;
24
+
}
25
+
26
+
.action-item {
27
+
display: flex;
28
+
align-items: center;
29
+
gap: 1rem;
30
+
padding: 1rem;
31
+
background-color: var(--white);
32
+
border-radius: 0.5rem;
33
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
34
+
cursor: pointer;
35
+
transition: transform 0.2s, box-shadow 0.2s;
36
+
border: none;
37
+
width: 100%;
38
+
text-align: left;
39
+
}
40
+
41
+
.action-item:hover {
42
+
transform: translateY(-2px);
43
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
44
+
}
45
+
46
+
.action-icon {
47
+
width: 2.5rem;
48
+
height: 2.5rem;
49
+
display: flex;
50
+
align-items: center;
51
+
justify-content: center;
52
+
color: var(--primary-color);
53
+
background-color: var(--bg-color);
54
+
border-radius: 0.5rem;
55
+
flex-shrink: 0;
56
+
}
57
+
58
+
.action-content {
59
+
flex: 1;
60
+
text-align: left;
61
+
}
62
+
63
+
.action-title {
64
+
font-size: 1rem;
65
+
font-weight: 600;
66
+
color: var(--text-color);
67
+
margin-bottom: 0.25rem;
68
+
text-align: left;
69
+
}
70
+
71
+
.action-subtitle {
72
+
font-size: 0.875rem;
73
+
color: var(--text-light);
74
+
text-align: left;
75
+
}
76
+
77
+
/* User info section */
78
+
.user-info-section {
79
+
background-color: var(--white);
80
+
border-radius: 0.5rem;
81
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
82
+
overflow: hidden;
83
+
margin-top: 1rem;
84
+
}
85
+
86
+
.user-info-summary {
87
+
padding: 1rem;
88
+
cursor: pointer;
89
+
display: flex;
90
+
align-items: center;
91
+
justify-content: space-between;
92
+
list-style: none;
93
+
font-weight: 600;
94
+
color: var(--text-color);
95
+
}
96
+
97
+
.user-info-summary::after {
98
+
content: "โผ";
99
+
color: var(--primary-color);
100
+
transition: transform 0.2s;
101
+
}
102
+
103
+
.user-info-section[open] .user-info-summary::after {
104
+
transform: rotate(180deg);
105
+
}
106
+
107
+
.user-info-content {
108
+
padding: 1.5rem;
109
+
border-top: 1px solid var(--border-color);
110
+
}
111
+
112
+
.user-info-content section {
113
+
margin-bottom: 2rem;
114
+
}
115
+
116
+
.user-info-content section:last-child {
117
+
margin-bottom: 0;
118
+
}
119
+
120
+
.user-info-content h2 {
121
+
font-size: 1.125rem;
122
+
font-weight: 600;
123
+
color: var(--text-color);
124
+
margin-bottom: 1rem;
125
+
}
126
+
127
+
.user-info-content dl {
128
+
display: grid;
129
+
grid-template-columns: max-content 1fr;
130
+
gap: 0.75rem 1rem;
131
+
margin: 0;
132
+
}
133
+
134
+
.user-info-content dt {
135
+
font-weight: 500;
136
+
color: var(--text-light);
137
+
}
138
+
139
+
.user-info-content dd {
140
+
margin: 0;
141
+
color: var(--text-color);
142
+
word-break: break-all;
143
+
}
144
+
145
+
.did-document {
146
+
background-color: var(--bg-color);
147
+
padding: 1rem;
148
+
border-radius: 0.375rem;
149
+
overflow-x: auto;
150
+
margin: 0;
151
+
}
152
+
153
+
.did-document code {
154
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
155
+
font-size: 0.875rem;
156
+
color: var(--text-color);
157
+
white-space: pre;
158
+
}
+175
src/css/base.css
+175
src/css/base.css
···
···
1
+
/* Base styles */
2
+
:root {
3
+
--primary-color: #4338ca;
4
+
--primary-hover: #4f46e5;
5
+
--text-color: #1f2937;
6
+
--text-light: #6b7280;
7
+
--bg-color: #f3f4f6;
8
+
--white: #ffffff;
9
+
--error-color: #ef4444;
10
+
--border-color: #e5e7eb;
11
+
--input-bg: #ffffff;
12
+
}
13
+
14
+
@media (prefers-color-scheme: dark) {
15
+
:root {
16
+
--primary-color: #4f46e5;
17
+
--primary-hover: #6366f1;
18
+
--text-color: #f3f4f6;
19
+
--text-light: #9ca3af;
20
+
--bg-color: #111827;
21
+
--white: #1f2937;
22
+
--error-color: #f87171;
23
+
--border-color: #374151;
24
+
--input-bg: #1f2937;
25
+
}
26
+
}
27
+
28
+
body {
29
+
margin: 0;
30
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
31
+
background-color: var(--bg-color);
32
+
color: var(--text-color);
33
+
min-height: 100vh;
34
+
display: flex;
35
+
flex-direction: column;
36
+
}
37
+
38
+
/* Common utility classes */
39
+
.page-content {
40
+
background-color: var(--white);
41
+
border-radius: 0.5rem;
42
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
43
+
padding: 2rem;
44
+
padding-top: 1rem;
45
+
margin-top: 1rem;
46
+
}
47
+
48
+
.page-content h2 {
49
+
font-size: 1.5rem;
50
+
font-weight: 600;
51
+
color: var(--text-color);
52
+
margin-bottom: 1rem;
53
+
}
54
+
55
+
.page-content p {
56
+
color: var(--text-color);
57
+
margin-bottom: 1rem;
58
+
}
59
+
60
+
.page-content ul {
61
+
list-style-type: disc;
62
+
margin-left: 1.5rem;
63
+
margin-bottom: 1.5rem;
64
+
color: var(--text-color);
65
+
}
66
+
67
+
.page-content li {
68
+
margin-bottom: 0.5rem;
69
+
}
70
+
71
+
.page-content h3 {
72
+
font-size: 1.25rem;
73
+
font-weight: 600;
74
+
color: var(--text-color);
75
+
margin: 1.5rem 0 1rem 0;
76
+
}
77
+
78
+
/* Warning section */
79
+
.warning-section {
80
+
background-color: #fef3c7;
81
+
border: 1px solid #fbbf24;
82
+
border-radius: 0.5rem;
83
+
padding: 1.5rem;
84
+
margin: 1.5rem 0;
85
+
}
86
+
87
+
.warning-section h3 {
88
+
color: #92400e;
89
+
margin-top: 0;
90
+
}
91
+
92
+
.warning-section ul {
93
+
margin-bottom: 0;
94
+
padding-left: 0;
95
+
}
96
+
97
+
.warning-section li {
98
+
color: #92400e;
99
+
}
100
+
101
+
.warning-section b {
102
+
color: #78350f;
103
+
}
104
+
105
+
/* Docs section */
106
+
.docs-section {
107
+
background-color: var(--bg-color);
108
+
border-radius: 0.5rem;
109
+
padding: 1.5rem;
110
+
margin: 1.5rem 0;
111
+
}
112
+
113
+
.docs-section h3 {
114
+
margin-top: 0;
115
+
}
116
+
117
+
.docs-section ul {
118
+
margin-bottom: 0;
119
+
padding-left: 0;
120
+
}
121
+
122
+
.docs-section a {
123
+
color: var(--primary-color);
124
+
text-decoration: none;
125
+
transition: color 0.2s;
126
+
display: inline-flex;
127
+
align-items: center;
128
+
gap: 0.5rem;
129
+
}
130
+
131
+
.docs-section a:hover {
132
+
color: var(--primary-hover);
133
+
}
134
+
135
+
.docs-section a::after {
136
+
content: "โ";
137
+
transition: transform 0.2s;
138
+
}
139
+
140
+
.docs-section a:hover::after {
141
+
transform: translateX(4px);
142
+
}
143
+
144
+
/* Network warning */
145
+
.network-warning {
146
+
position: fixed;
147
+
top: 0;
148
+
left: 0;
149
+
right: 0;
150
+
bottom: 0;
151
+
background-color: rgba(0, 0, 0, 0.8);
152
+
z-index: 1000;
153
+
display: flex;
154
+
align-items: center;
155
+
justify-content: center;
156
+
}
157
+
158
+
.network-warning-content {
159
+
max-width: 800px;
160
+
padding: 2rem;
161
+
display: flex;
162
+
align-items: center;
163
+
gap: 0.75rem;
164
+
color: #92400e;
165
+
font-size: 1.125rem;
166
+
text-align: center;
167
+
background-color: rgba(255, 255, 255, 0.9);
168
+
border-radius: 0.5rem;
169
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
170
+
}
171
+
172
+
.network-warning-icon {
173
+
font-size: 1.5rem;
174
+
flex-shrink: 0;
175
+
}
+216
src/css/confirmation.css
+216
src/css/confirmation.css
···
···
1
+
.comparison-container {
2
+
display: flex;
3
+
align-items: center;
4
+
gap: 1rem;
5
+
padding: 0 1.5rem 1.5rem 0;
6
+
background-color: var(--white);
7
+
border-radius: 0.5rem;
8
+
flex-wrap: wrap;
9
+
width: 100%;
10
+
}
11
+
12
+
.comparison-side {
13
+
flex: 1;
14
+
min-width: 200px;
15
+
padding: 1rem;
16
+
background-color: var(--bg-color);
17
+
border-radius: 0.375rem;
18
+
}
19
+
20
+
.comparison-side h4 {
21
+
margin: 0 0 0.75rem 0;
22
+
color: var(--text-light);
23
+
font-size: 0.875rem;
24
+
font-weight: 500;
25
+
}
26
+
27
+
.comparison-arrow {
28
+
display: flex;
29
+
align-items: center;
30
+
justify-content: center;
31
+
width: 2.5rem;
32
+
height: 2.5rem;
33
+
background-color: var(--primary-color);
34
+
border-radius: 50%;
35
+
color: white;
36
+
font-size: 1.25rem;
37
+
}
38
+
39
+
.detail-group {
40
+
margin-bottom: 1rem;
41
+
}
42
+
43
+
.detail-group label {
44
+
display: block;
45
+
margin-bottom: 0.5rem;
46
+
color: var(--text-light);
47
+
font-size: 0.875rem;
48
+
}
49
+
50
+
.detail-value {
51
+
padding: 0.75rem;
52
+
background-color: var(--bg-color);
53
+
border-radius: 0.375rem;
54
+
font-size: 0.875rem;
55
+
color: var(--text-color);
56
+
}
57
+
58
+
.password-container {
59
+
position: relative;
60
+
}
61
+
62
+
.password-toggle {
63
+
position: absolute;
64
+
right: 0.75rem;
65
+
top: 50%;
66
+
transform: translateY(-50%);
67
+
background: none;
68
+
border: none;
69
+
color: var(--text-light);
70
+
cursor: pointer;
71
+
padding: 0.25rem;
72
+
font-size: 0.875rem;
73
+
z-index: 1;
74
+
}
75
+
76
+
.password-toggle:hover {
77
+
color: var(--text-color);
78
+
}
79
+
80
+
.hidden-password {
81
+
letter-spacing: 0.25em;
82
+
}
83
+
84
+
.form-section.completed .button-container {
85
+
display: none;
86
+
}
87
+
88
+
.confirm-button {
89
+
background-color: #c72424;
90
+
color: var(--text-color);
91
+
border: none;
92
+
padding: 0.75rem 1.5rem;
93
+
border-radius: 0.375rem;
94
+
font-size: 0.875rem;
95
+
font-weight: 500;
96
+
cursor: pointer;
97
+
transition: background-color 0.2s;
98
+
display: inline-flex;
99
+
align-items: center;
100
+
gap: 0.5rem;
101
+
margin-left: auto;
102
+
}
103
+
104
+
.confirm-button:hover {
105
+
background-color: #f32e2e;
106
+
}
107
+
108
+
@media (max-width: 707px) {
109
+
.comparison-container {
110
+
flex-direction: column;
111
+
align-items: center;
112
+
}
113
+
114
+
.comparison-side {
115
+
margin: 0 0 0 0;
116
+
padding: 1rem 0 0 0;
117
+
}
118
+
119
+
/* UTTER WOKE NONSENSE */
120
+
.comparison-side h3, .comparison-side label, .comparison-side .detail-group div {
121
+
padding: 0 1rem 0 1rem;
122
+
}
123
+
124
+
.comparison-arrow {
125
+
transform: rotate(90deg);
126
+
margin: 0.5rem 0;
127
+
}
128
+
}
129
+
130
+
.warning-overlay {
131
+
position: fixed;
132
+
top: 0;
133
+
left: 0;
134
+
right: 0;
135
+
bottom: 0;
136
+
background-color: rgba(0, 0, 0, 0.5);
137
+
display: flex;
138
+
align-items: center;
139
+
justify-content: center;
140
+
z-index: 1000;
141
+
}
142
+
143
+
.warning-dialog {
144
+
background-color: var(--white);
145
+
border-radius: 0.5rem;
146
+
padding: 1.5rem;
147
+
max-width: 500px;
148
+
width: 90%;
149
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
150
+
}
151
+
152
+
.warning-dialog h3 {
153
+
color: var(--error-color);
154
+
margin: 0 0 1rem 0;
155
+
font-size: 1.25rem;
156
+
}
157
+
158
+
.warning-content {
159
+
margin-bottom: 1.5rem;
160
+
}
161
+
162
+
.warning-content p {
163
+
margin: 0 0 1rem 0;
164
+
color: var(--text-color);
165
+
}
166
+
167
+
.warning-content ul {
168
+
margin: 0 0 1rem 0;
169
+
padding-left: 1.5rem;
170
+
}
171
+
172
+
.warning-content li {
173
+
margin-bottom: 0.5rem;
174
+
color: var(--text-color);
175
+
}
176
+
177
+
.warning-buttons {
178
+
display: flex;
179
+
justify-content: flex-end;
180
+
gap: 1rem;
181
+
}
182
+
183
+
.pds-verification {
184
+
margin: 1rem 0;
185
+
}
186
+
187
+
.pds-verification input {
188
+
width: 98%;
189
+
padding: 0.75rem 0 0.75rem 0.5rem;
190
+
border: 1px solid var(--border-color);
191
+
border-radius: 0.375rem;
192
+
font-size: 0.875rem;
193
+
color: var(--text-color);
194
+
background-color: var(--white);
195
+
transition: border-color 0.2s;
196
+
}
197
+
198
+
.pds-verification input:focus {
199
+
outline: none;
200
+
border-color: var(--primary-color);
201
+
}
202
+
203
+
.pds-verification input.error {
204
+
border-color: var(--error-color);
205
+
}
206
+
207
+
.error-message {
208
+
color: var(--error-color);
209
+
font-size: 0.875rem;
210
+
margin: 0.5rem 0 0 0;
211
+
}
212
+
213
+
.warning-buttons button:disabled {
214
+
opacity: 0.5;
215
+
cursor: not-allowed;
216
+
}
+94
src/css/forms.css
+94
src/css/forms.css
···
···
1
+
/* Form styles */
2
+
.form-input {
3
+
width: 100%;
4
+
padding: 0.75rem 1rem;
5
+
border: 1px solid var(--border-color);
6
+
border-radius: 0.375rem;
7
+
font-size: 0.875rem;
8
+
color: var(--text-color);
9
+
background-color: var(--input-bg);
10
+
box-sizing: border-box;
11
+
}
12
+
13
+
.form-input:focus {
14
+
outline: none;
15
+
border-color: var(--primary-color);
16
+
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
17
+
}
18
+
19
+
.form-input::placeholder {
20
+
color: var(--text-light);
21
+
}
22
+
23
+
.form-input.error {
24
+
border-color: var(--error-color);
25
+
}
26
+
27
+
.form-input.error:focus {
28
+
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.1);
29
+
}
30
+
31
+
.form-input:disabled {
32
+
background-color: var(--bg-color);
33
+
border-color: var(--border-color);
34
+
color: var(--text-light);
35
+
cursor: not-allowed;
36
+
opacity: 0.8;
37
+
}
38
+
39
+
.error-message {
40
+
color: var(--error-color);
41
+
font-size: 0.875rem;
42
+
margin-top: 0.25rem;
43
+
}
44
+
45
+
.success-message {
46
+
color: #059669;
47
+
font-size: 0.875rem;
48
+
margin: 0.5rem 0;
49
+
display: flex;
50
+
align-items: center;
51
+
gap: 0.5rem;
52
+
}
53
+
54
+
.form-section {
55
+
background: var(--white);
56
+
border-radius: 8px;
57
+
padding: 2rem;
58
+
margin-bottom: 2rem;
59
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
60
+
transition: all 0.3s ease;
61
+
border: 1px solid var(--border-color);
62
+
}
63
+
64
+
.form-section.completed {
65
+
opacity: 0.6;
66
+
pointer-events: none;
67
+
background: var(--bg-color);
68
+
}
69
+
70
+
.form-section h3 {
71
+
margin-top: 0;
72
+
margin-bottom: 1.5rem;
73
+
color: var(--text-color);
74
+
font-size: 1.25rem;
75
+
}
76
+
77
+
.info-message {
78
+
color: var(--text-light);
79
+
font-size: 0.875rem;
80
+
margin-top: 0;
81
+
margin-bottom: 1rem;
82
+
padding: 1rem;
83
+
background-color: rgba(76, 67, 249, 0.25);
84
+
border-radius: 0.375rem;
85
+
}
86
+
87
+
.info-message h3 {
88
+
margin-top: 0;
89
+
margin-bottom: 0.5rem;
90
+
}
91
+
92
+
.info-message strong {
93
+
word-break: break-all;
94
+
}
+84
src/css/header.css
+84
src/css/header.css
···
···
1
+
/* Header styles */
2
+
.app-header {
3
+
display: flex;
4
+
flex-wrap: wrap;
5
+
align-items: center;
6
+
justify-content: space-between;
7
+
gap: 1rem;
8
+
padding: 1rem;
9
+
background-color: var(--white);
10
+
border-radius: 0.5rem;
11
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
12
+
margin: 1rem 0 0 0;
13
+
position: relative;
14
+
z-index: 10;
15
+
}
16
+
17
+
.app-title {
18
+
font-size: 1.25rem;
19
+
font-weight: 700;
20
+
color: var(--text-color);
21
+
margin: 0;
22
+
white-space: nowrap;
23
+
overflow: hidden;
24
+
text-overflow: ellipsis;
25
+
max-width: 200px;
26
+
}
27
+
28
+
.user-info {
29
+
display: flex;
30
+
align-items: center;
31
+
gap: 0.75rem;
32
+
flex-wrap: wrap;
33
+
}
34
+
35
+
.user-avatar {
36
+
width: 2rem;
37
+
height: 2rem;
38
+
border-radius: 50%;
39
+
object-fit: cover;
40
+
flex-shrink: 0;
41
+
}
42
+
43
+
.user-handle {
44
+
font-size: 0.875rem;
45
+
color: var(--text-color);
46
+
white-space: nowrap;
47
+
overflow: hidden;
48
+
text-overflow: ellipsis;
49
+
max-width: 150px;
50
+
}
51
+
52
+
.logout-button {
53
+
background-color: #f44336;
54
+
color: white;
55
+
border: none;
56
+
padding: 8px 16px;
57
+
border-radius: 4px;
58
+
cursor: pointer;
59
+
font-size: 14px;
60
+
transition: background-color 0.2s;
61
+
white-space: nowrap;
62
+
flex-shrink: 0;
63
+
}
64
+
65
+
@media (max-width: 480px) {
66
+
.app-header {
67
+
padding: 0.75rem;
68
+
gap: 0.5rem;
69
+
}
70
+
71
+
.app-title {
72
+
font-size: 1.125rem;
73
+
max-width: 150px;
74
+
}
75
+
76
+
.user-handle {
77
+
max-width: 100px;
78
+
}
79
+
80
+
.logout-button {
81
+
padding: 6px 12px;
82
+
font-size: 13px;
83
+
}
84
+
}
+47
src/css/loading.css
+47
src/css/loading.css
···
···
1
+
/* Loading state styles */
2
+
.loading-container {
3
+
flex: 1;
4
+
display: flex;
5
+
align-items: center;
6
+
justify-content: center;
7
+
min-height: 100vh;
8
+
padding: 1rem;
9
+
}
10
+
11
+
.loading-text {
12
+
font-size: 1.25rem;
13
+
color: var(--text-color);
14
+
background-color: var(--white);
15
+
padding: 1.5rem 2rem;
16
+
border-radius: 0.5rem;
17
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
18
+
display: flex;
19
+
align-items: center;
20
+
gap: 0.75rem;
21
+
}
22
+
23
+
.loading-text::before {
24
+
content: "";
25
+
width: 1.5rem;
26
+
height: 1.5rem;
27
+
border: 2px solid var(--primary-color);
28
+
border-right-color: transparent;
29
+
border-radius: 50%;
30
+
animation: spin 1s linear infinite;
31
+
}
32
+
33
+
@keyframes spin {
34
+
to {
35
+
transform: rotate(360deg);
36
+
}
37
+
}
38
+
39
+
.loading-message {
40
+
margin: 10px 0;
41
+
padding: 10px;
42
+
background-color: var(--bg-color);
43
+
border-radius: 4px;
44
+
color: var(--text-color);
45
+
font-size: 0.9em;
46
+
text-align: center;
47
+
}
+140
src/css/login.css
+140
src/css/login.css
···
···
1
+
/* Login page styles */
2
+
.login-container {
3
+
flex: 1;
4
+
display: flex;
5
+
align-items: center;
6
+
justify-content: center;
7
+
padding: 1rem;
8
+
}
9
+
10
+
.login-card {
11
+
max-width: 28rem;
12
+
width: 100%;
13
+
padding: 2rem;
14
+
padding-top: 0;
15
+
background-color: var(--white);
16
+
border-radius: 0.5rem;
17
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
18
+
}
19
+
20
+
.login-title {
21
+
padding-top: 0;
22
+
text-align: center;
23
+
font-size: 1.875rem;
24
+
font-weight: 800;
25
+
color: var(--text-color);
26
+
}
27
+
28
+
.warning-message {
29
+
margin: 1rem 0;
30
+
padding: 0.75rem;
31
+
background-color: #fef3c7;
32
+
border: 1px solid #fbbf24;
33
+
border-radius: 0.375rem;
34
+
color: #92400e;
35
+
font-size: 0.875rem;
36
+
text-align: center;
37
+
}
38
+
39
+
.login-form {
40
+
margin-top: 2rem;
41
+
padding: 0 1rem;
42
+
}
43
+
44
+
.form-group {
45
+
margin-bottom: 1rem;
46
+
}
47
+
48
+
.form-group label {
49
+
display: block;
50
+
margin-bottom: 0.5rem;
51
+
color: var(--text-color);
52
+
font-weight: 500;
53
+
}
54
+
55
+
.handle-input-container {
56
+
display: flex;
57
+
align-items: stretch;
58
+
gap: 0;
59
+
background-color: var(--input-bg);
60
+
border: 1px solid var(--border-color);
61
+
border-radius: 0.375rem;
62
+
}
63
+
64
+
.handle-input-container .form-input {
65
+
border: none;
66
+
padding-right: 0;
67
+
flex: 1;
68
+
border-radius: 0.375rem 0 0 0.375rem;
69
+
}
70
+
71
+
.handle-input-container .form-input:focus {
72
+
box-shadow: none;
73
+
}
74
+
75
+
.handle-domain {
76
+
color: var(--text-light);
77
+
font-size: 0.875rem;
78
+
white-space: nowrap;
79
+
background-color: var(--bg-color);
80
+
padding: 0 0.75rem;
81
+
border-radius: 0 0.375rem 0.375rem 0;
82
+
display: flex;
83
+
align-items: center;
84
+
}
85
+
86
+
/* 2FA Modal styles */
87
+
.modal-overlay {
88
+
position: fixed;
89
+
top: 0;
90
+
left: 0;
91
+
right: 0;
92
+
bottom: 0;
93
+
background-color: rgba(0, 0, 0, 0.75);
94
+
display: flex;
95
+
justify-content: center;
96
+
align-items: center;
97
+
z-index: 1000;
98
+
}
99
+
100
+
.modal-content {
101
+
background-color: var(--white);
102
+
padding: 2rem;
103
+
border-radius: 0.5rem;
104
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
105
+
width: 90%;
106
+
max-width: 28rem;
107
+
}
108
+
109
+
.modal-content h2 {
110
+
margin-top: 0;
111
+
margin-bottom: 1rem;
112
+
color: var(--text-color);
113
+
text-align: center;
114
+
}
115
+
116
+
.modal-content p {
117
+
margin-bottom: 1.5rem;
118
+
color: var(--text-light);
119
+
text-align: center;
120
+
}
121
+
122
+
.two-factor-form {
123
+
margin-top: 2rem;
124
+
}
125
+
126
+
.button-group {
127
+
display: flex;
128
+
gap: 1rem;
129
+
margin-top: 1rem;
130
+
}
131
+
132
+
.button-group .submit-button {
133
+
margin-top: 0;
134
+
flex: 1;
135
+
width: fit-content;
136
+
}
137
+
138
+
.button-group .back-button {
139
+
flex: 1;
140
+
}
+71
src/css/migration.css
+71
src/css/migration.css
···
···
1
+
/* Migration progress styles */
2
+
.migration-progress {
3
+
margin-top: 2rem;
4
+
padding: 1.5rem;
5
+
background-color: var(--white);
6
+
border-radius: 0.5rem;
7
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
8
+
}
9
+
10
+
.migration-progress h3 {
11
+
margin: 0 0 1rem 0;
12
+
color: var(--text-color);
13
+
}
14
+
15
+
.progress-steps {
16
+
display: flex;
17
+
flex-direction: column;
18
+
gap: 1rem;
19
+
}
20
+
21
+
.progress-step {
22
+
display: flex;
23
+
align-items: center;
24
+
gap: 1rem;
25
+
padding: 0.75rem;
26
+
border-radius: 0.5rem;
27
+
background-color: var(--bg-color);
28
+
transition: background-color 0.2s;
29
+
}
30
+
31
+
.progress-step.active {
32
+
background-color: var(--primary-color);
33
+
color: white;
34
+
}
35
+
36
+
.step-number {
37
+
width: 2rem;
38
+
height: 2rem;
39
+
display: flex;
40
+
align-items: center;
41
+
justify-content: center;
42
+
background-color: var(--white);
43
+
border-radius: 50%;
44
+
font-weight: bold;
45
+
color: var(--text-color);
46
+
}
47
+
48
+
.progress-step.active .step-number {
49
+
background-color: var(--white);
50
+
color: var(--primary-color);
51
+
}
52
+
53
+
.step-text {
54
+
font-size: 0.875rem;
55
+
}
56
+
57
+
@media (max-width: 480px) {
58
+
.migration-progress {
59
+
padding: 1rem;
60
+
}
61
+
62
+
.progress-step {
63
+
padding: 0.5rem;
64
+
}
65
+
66
+
.step-number {
67
+
width: 1.75rem;
68
+
height: 1.75rem;
69
+
font-size: 0.875rem;
70
+
}
71
+
}
+91
src/css/plcToken.css
+91
src/css/plcToken.css
···
···
1
+
/* PLC token section styles */
2
+
.plc-token-section {
3
+
background: #f8f9fa;
4
+
border-radius: 8px;
5
+
padding: 20px;
6
+
margin: 15px 0;
7
+
}
8
+
9
+
.token-request-container {
10
+
text-align: center;
11
+
}
12
+
13
+
.token-request-container p {
14
+
margin-bottom: 15px;
15
+
color: #666;
16
+
}
17
+
18
+
.token-input-container {
19
+
max-width: 500px;
20
+
margin: 0 auto;
21
+
}
22
+
23
+
.token-input-container p {
24
+
margin-bottom: 10px;
25
+
color: #666;
26
+
}
27
+
28
+
.token-input-group {
29
+
display: flex;
30
+
gap: 10px;
31
+
margin-bottom: 15px;
32
+
}
33
+
34
+
.token-input {
35
+
flex: 1;
36
+
padding: 10px;
37
+
border: 1px solid #ddd;
38
+
border-radius: 4px;
39
+
font-size: 14px;
40
+
}
41
+
42
+
.request-token-button,
43
+
.submit-token-button,
44
+
.request-new-token-button {
45
+
padding: 10px 20px;
46
+
border: none;
47
+
border-radius: 4px;
48
+
font-size: 14px;
49
+
cursor: pointer;
50
+
transition: background-color 0.2s;
51
+
}
52
+
53
+
.request-token-button {
54
+
background-color: #007bff;
55
+
color: white;
56
+
}
57
+
58
+
.request-token-button:hover {
59
+
background-color: #0056b3;
60
+
}
61
+
62
+
.submit-token-button {
63
+
background-color: #28a745;
64
+
color: white;
65
+
}
66
+
67
+
.submit-token-button:hover {
68
+
background-color: #218838;
69
+
}
70
+
71
+
.submit-token-button:disabled {
72
+
background-color: #6c757d;
73
+
cursor: not-allowed;
74
+
}
75
+
76
+
.request-new-token-button {
77
+
background-color: transparent;
78
+
color: #007bff;
79
+
border: 1px solid #007bff;
80
+
width: 100%;
81
+
}
82
+
83
+
.request-new-token-button:hover:not(:disabled) {
84
+
background-color: #f0f7ff;
85
+
}
86
+
87
+
.request-new-token-button:disabled {
88
+
color: #6c757d;
89
+
border-color: #6c757d;
90
+
cursor: not-allowed;
91
+
}
+21
src/hooks/useDebounce.ts
+21
src/hooks/useDebounce.ts
···
···
1
+
import { useCallback, useRef } from 'react';
2
+
3
+
export function useDebounce<T extends (...args: any[]) => void>(
4
+
callback: T,
5
+
delay: number
6
+
): T {
7
+
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
8
+
9
+
return useCallback(
10
+
(...args: Parameters<T>) => {
11
+
if (timeoutRef.current) {
12
+
clearTimeout(timeoutRef.current);
13
+
}
14
+
15
+
timeoutRef.current = setTimeout(() => {
16
+
callback(...args);
17
+
}, delay);
18
+
},
19
+
[callback, delay]
20
+
) as T;
21
+
}
+24
src/lib/migration/accountDetailsValidation.ts
+24
src/lib/migration/accountDetailsValidation.ts
···
···
1
+
export const validateHandle = (handle: string, availableUserDomains: string[]): {
2
+
isUsingDefaultDomain: boolean;
3
+
customHandle: string;
4
+
} => {
5
+
// Check if handle ends with any of the available user domains
6
+
const isUsingDefaultDomain = availableUserDomains.some(domain =>
7
+
handle.endsWith(domain)
8
+
);
9
+
10
+
if (!isUsingDefaultDomain) {
11
+
return {
12
+
isUsingDefaultDomain: false,
13
+
customHandle: handle
14
+
};
15
+
}
16
+
17
+
// Extract the custom part of the handle (everything before the domain)
18
+
const customHandle = handle.split('.')[0];
19
+
20
+
return {
21
+
isUsingDefaultDomain: true,
22
+
customHandle: customHandle
23
+
};
24
+
};
+43
src/lib/migration/migrationData.ts
+43
src/lib/migration/migrationData.ts
···
···
1
+
import { AtpAgent } from "@atproto/api";
2
+
3
+
export class MigrationData {
4
+
private readonly oldPds: String;
5
+
private readonly newPds: String;
6
+
private readonly inviteCode: String | null;
7
+
private readonly handle: String;
8
+
private readonly email: String;
9
+
private readonly password: String;
10
+
11
+
constructor(oldPds: String, newPds: String, inviteCode: String | null, handle: String, email: String, password: String) {
12
+
this.oldPds = oldPds;
13
+
this.newPds = newPds;
14
+
this.inviteCode = inviteCode;
15
+
this.handle = handle;
16
+
this.email = email;
17
+
this.password = password;
18
+
}
19
+
20
+
public getOldPds(): String {
21
+
return this.oldPds;
22
+
}
23
+
24
+
public getNewPds(): String {
25
+
return this.newPds;
26
+
}
27
+
28
+
public getInviteCode(): String | null {
29
+
return this.inviteCode;
30
+
}
31
+
32
+
public getHandle(): String {
33
+
return this.handle;
34
+
}
35
+
36
+
public getEmail(): String {
37
+
return this.email;
38
+
}
39
+
40
+
public getPassword(): String {
41
+
return this.password;
42
+
}
43
+
}
+236
src/lib/migration/pdsValidation.ts
+236
src/lib/migration/pdsValidation.ts
···
···
1
+
import { AtpAgent } from '@atproto/api';
2
+
import { ServerDescription } from './serverDescription';
3
+
4
+
export interface ValidationResult {
5
+
isValid: boolean;
6
+
error: string | null;
7
+
}
8
+
9
+
const ensureProtocol = (url: string): string => {
10
+
if (url.startsWith('http://') || url.startsWith('https://')) {
11
+
return url;
12
+
}
13
+
return `https://${url}`;
14
+
};
15
+
16
+
const isValidUrl = (url: string): boolean => {
17
+
try {
18
+
new URL(url);
19
+
return true;
20
+
} catch {
21
+
return false;
22
+
}
23
+
};
24
+
25
+
const checkPDSHealth = async (pdsUrl: string): Promise<boolean> => {
26
+
const controller = new AbortController();
27
+
const timeoutId = setTimeout(() => controller.abort(), 15000);
28
+
29
+
try {
30
+
const response = await fetch(`${pdsUrl}/xrpc/_health`, {
31
+
signal: controller.signal
32
+
});
33
+
if (!response.ok) return false;
34
+
35
+
const data = await response.json();
36
+
// Check if response is just a version number
37
+
return typeof data === 'string' || (typeof data === 'object' && Object.keys(data).length === 1 && typeof data.version === 'string');
38
+
} catch (e) {
39
+
if (e instanceof Error && e.name === 'AbortError') {
40
+
throw new Error('Request timed out');
41
+
}
42
+
return false;
43
+
} finally {
44
+
clearTimeout(timeoutId);
45
+
}
46
+
};
47
+
48
+
const getServerDescription = async (pdsUrl: string): Promise<ServerDescription> => {
49
+
const controller = new AbortController();
50
+
const timeoutId = setTimeout(() => controller.abort(), 15000);
51
+
52
+
try {
53
+
const response = await fetch(`https://${pdsUrl}/xrpc/com.atproto.server.describeServer`, {
54
+
signal: controller.signal
55
+
});
56
+
if (!response.ok) {
57
+
const errorData = await response.text();
58
+
console.error('Server description request failed:', {
59
+
status: response.status,
60
+
statusText: response.statusText,
61
+
url: pdsUrl,
62
+
response: errorData
63
+
});
64
+
throw new Error('Failed to get server description');
65
+
}
66
+
67
+
const data = await response.json();
68
+
console.log('Server description response:', {
69
+
url: pdsUrl,
70
+
data
71
+
});
72
+
73
+
return new ServerDescription({
74
+
did: data.did,
75
+
availableUserDomains: data.availableUserDomains ?? {},
76
+
inviteCodeRequired: data.inviteCodeRequired ?? false,
77
+
links: data.links ?? {},
78
+
contact: data.contact ?? {}
79
+
});
80
+
} catch (e) {
81
+
console.error('Error getting server description:', {
82
+
url: pdsUrl,
83
+
error: e instanceof Error ? {
84
+
name: e.name,
85
+
message: e.message,
86
+
stack: e.stack
87
+
} : e
88
+
});
89
+
if (e instanceof Error && e.name === 'AbortError') {
90
+
throw new Error('Request timed out');
91
+
}
92
+
throw e;
93
+
} finally {
94
+
clearTimeout(timeoutId);
95
+
}
96
+
};
97
+
98
+
export const validateInviteCode = async (pdsUrl: string, inviteCode: string): Promise<ValidationResult> => {
99
+
inviteCode = inviteCode.trim();
100
+
101
+
try {
102
+
if (!inviteCode) {
103
+
return {
104
+
isValid: false,
105
+
error: 'This server requires an invite code'
106
+
};
107
+
}
108
+
109
+
const pdsPart = pdsUrl.replace(/\./g, '-');
110
+
const inviteCodeRegex = new RegExp(`^${pdsPart}-[a-zA-Z0-9]{5}-[a-zA-Z0-9]{5}$`);
111
+
const isValid = inviteCodeRegex.test(inviteCode);
112
+
113
+
if (!isValid) {
114
+
console.error('Invalid invite code format:', {
115
+
pds: pdsUrl,
116
+
inviteCode,
117
+
expectedFormat: `${pdsUrl.replace(/\./g, '-')}-XXXXX-XXXXX`
118
+
});
119
+
return {
120
+
isValid: false,
121
+
error: 'Incorrect invite code format'
122
+
};
123
+
}
124
+
125
+
return {
126
+
isValid: true,
127
+
error: null
128
+
};
129
+
} catch (e) {
130
+
console.error('Error validating invite code:', {
131
+
pds: pdsUrl,
132
+
inviteCode,
133
+
error: e instanceof Error ? {
134
+
name: e.name,
135
+
message: e.message,
136
+
stack: e.stack
137
+
} : e
138
+
});
139
+
return {
140
+
isValid: false,
141
+
error: 'Failed to validate invite code'
142
+
};
143
+
}
144
+
};
145
+
146
+
export const validatePDS = async (pdsUrl: string, agent: AtpAgent): Promise<ValidationResult> => {
147
+
if (!pdsUrl.trim()) {
148
+
return {
149
+
isValid: false,
150
+
error: null
151
+
};
152
+
}
153
+
154
+
const urlWithProtocol = ensureProtocol(pdsUrl);
155
+
156
+
if (!isValidUrl(urlWithProtocol)) {
157
+
return {
158
+
isValid: false,
159
+
error: 'Please enter a valid URL'
160
+
};
161
+
}
162
+
163
+
try {
164
+
const url = new URL(urlWithProtocol);
165
+
const hostname = url.hostname;
166
+
167
+
// Check for Bluesky domains
168
+
if (hostname.endsWith('bsky.network') ||
169
+
hostname.endsWith('bsky.social') ||
170
+
hostname.endsWith('bsky.app')) {
171
+
return {
172
+
isValid: false,
173
+
error: 'You cannot migrate to Bluesky data servers at this time.'
174
+
};
175
+
}
176
+
177
+
// Check if trying to migrate to current PDS
178
+
const currentPDS = agent.serviceUrl;
179
+
if (hostname === new URL(currentPDS).hostname) {
180
+
return {
181
+
isValid: false,
182
+
error: 'You cannot migrate to the same PDS that you are currently using.'
183
+
};
184
+
}
185
+
186
+
// Check if PDS is alive and valid
187
+
try {
188
+
const isHealthy = await checkPDSHealth(urlWithProtocol);
189
+
if (!isHealthy) {
190
+
return {
191
+
isValid: false,
192
+
error: 'This PDS is either offline or not a valid server.'
193
+
};
194
+
}
195
+
} catch (e) {
196
+
console.error('Error checking PDS health:', {
197
+
url: urlWithProtocol,
198
+
error: e instanceof Error ? {
199
+
name: e.name,
200
+
message: e.message,
201
+
stack: e.stack
202
+
} : e
203
+
});
204
+
if (e instanceof Error && e.message === 'Request timed out') {
205
+
return {
206
+
isValid: false,
207
+
error: 'The server took too long to respond. Please try again later.'
208
+
};
209
+
}
210
+
return {
211
+
isValid: false,
212
+
error: 'This PDS is either offline or not a valid server.'
213
+
};
214
+
}
215
+
216
+
return {
217
+
isValid: true,
218
+
error: null
219
+
};
220
+
} catch (e) {
221
+
console.error('Error validating PDS:', {
222
+
url: pdsUrl,
223
+
error: e instanceof Error ? {
224
+
name: e.name,
225
+
message: e.message,
226
+
stack: e.stack
227
+
} : e
228
+
});
229
+
return {
230
+
isValid: false,
231
+
error: 'Please enter a valid URL'
232
+
};
233
+
}
234
+
};
235
+
236
+
export { getServerDescription };
+48
src/lib/migration/serverDescription.ts
+48
src/lib/migration/serverDescription.ts
···
···
1
+
export class ServerDescription {
2
+
private readonly did: string;
3
+
private readonly availableUserDomains: Record<number, string>;
4
+
private readonly inviteCodeRequired: boolean;
5
+
private readonly phoneVerificationRequired: boolean;
6
+
private readonly links: Record<string, string>;
7
+
private readonly contact: Record<string, string>;
8
+
9
+
constructor(data: {
10
+
did: string;
11
+
availableUserDomains: Record<number, string>;
12
+
inviteCodeRequired: boolean;
13
+
phoneVerificationRequired?: boolean;
14
+
links: Record<string, string>;
15
+
contact: Record<string, string>;
16
+
}) {
17
+
this.did = data.did;
18
+
this.availableUserDomains = data.availableUserDomains;
19
+
this.inviteCodeRequired = data.inviteCodeRequired;
20
+
this.phoneVerificationRequired = data.phoneVerificationRequired ?? false;
21
+
this.links = data.links;
22
+
this.contact = data.contact;
23
+
}
24
+
25
+
getDid(): string {
26
+
return this.did;
27
+
}
28
+
29
+
getAvailableUserDomains(): Record<number, string> {
30
+
return this.availableUserDomains;
31
+
}
32
+
33
+
isInviteCodeRequired(): boolean {
34
+
return this.inviteCodeRequired;
35
+
}
36
+
37
+
isPhoneVerificationRequired(): boolean {
38
+
return this.phoneVerificationRequired;
39
+
}
40
+
41
+
getLinks(): Record<string, string> {
42
+
return this.links;
43
+
}
44
+
45
+
getContact(): Record<string, string> {
46
+
return this.contact;
47
+
}
48
+
}
+11
src/main.css
+11
src/main.css
···
···
1
+
@import 'css/base.css';
2
+
@import 'css/header.css';
3
+
@import 'css/footer.css';
4
+
@import 'css/login.css';
5
+
@import 'css/forms.css';
6
+
@import 'css/buttons.css';
7
+
@import 'css/loading.css';
8
+
@import 'css/migration.css';
9
+
@import 'css/plcToken.css';
10
+
@import 'css/actions.css';
11
+
@import 'css/confirmation.css';
+72
-3
src/main.tsx
+72
-3
src/main.tsx
···
1
+
import { StrictMode, useState, useEffect } from 'react'
2
import { createRoot } from 'react-dom/client'
3
+
import { BrowserRouter as Router } from 'react-router-dom'
4
+
import { AtpAgent } from '@atproto/api'
5
+
import { AvatarProvider } from './contexts/AvatarContext'
6
+
import { NetworkProvider } from './contexts/NetworkContext'
7
+
import { AppRoutes } from './Routes'
8
+
import './main.css'
9
+
10
+
const SESSION_KEY = 'atproto_session';
11
+
const SESSION_EXPIRY = 60 * 60 * 1000; // 1 hour in milliseconds
12
+
13
+
function App() {
14
+
const [agent, setAgent] = useState<AtpAgent | null>(null)
15
+
16
+
useEffect(() => {
17
+
// Load session from localStorage on initial load
18
+
const loadSession = async () => {
19
+
const savedSession = localStorage.getItem(SESSION_KEY);
20
+
if (savedSession) {
21
+
const { session, service, timestamp } = JSON.parse(savedSession);
22
+
23
+
// Check if session is expired
24
+
if (Date.now() - timestamp > SESSION_EXPIRY) {
25
+
localStorage.removeItem(SESSION_KEY);
26
+
return;
27
+
}
28
+
29
+
const newAgent = new AtpAgent({ service });
30
+
await newAgent.resumeSession(session);
31
+
setAgent(newAgent);
32
+
}
33
+
};
34
+
35
+
loadSession();
36
+
}, []);
37
+
38
+
const handleLogin = (newAgent: AtpAgent) => {
39
+
setAgent(newAgent);
40
+
// Save session to localStorage
41
+
localStorage.setItem(SESSION_KEY, JSON.stringify({
42
+
session: newAgent.session,
43
+
service: newAgent.serviceUrl,
44
+
timestamp: Date.now()
45
+
}));
46
+
};
47
+
48
+
const handleLogout = () => {
49
+
setAgent(null);
50
+
localStorage.removeItem(SESSION_KEY);
51
+
// Clear avatar URL from context
52
+
const avatarContext = document.querySelector('[data-avatar-context]');
53
+
if (avatarContext) {
54
+
const event = new CustomEvent('clearAvatar');
55
+
avatarContext.dispatchEvent(event);
56
+
}
57
+
};
58
59
+
return (
60
+
<NetworkProvider>
61
+
<AvatarProvider>
62
+
<Router>
63
+
<AppRoutes
64
+
agent={agent}
65
+
onLogout={handleLogout}
66
+
handleLogin={handleLogin}
67
+
/>
68
+
</Router>
69
+
</AvatarProvider>
70
+
</NetworkProvider>
71
+
)
72
+
}
73
+
74
+
// Initialize the app
75
createRoot(document.getElementById('root')!).render(
76
<StrictMode>
77
<App />
+130
src/pages/Actions.tsx
+130
src/pages/Actions.tsx
···
···
1
+
import { useEffect, useState } from 'react';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import { useNavigate } from 'react-router-dom';
4
+
import Footer from '../components/common/Footer';
5
+
import Header from '../components/common/Header';
6
+
7
+
interface ActionsProps {
8
+
agent: AtpAgent;
9
+
onLogout: () => void;
10
+
}
11
+
12
+
export default function Actions({ agent, onLogout }: ActionsProps) {
13
+
const [didDoc, setDidDoc] = useState<string>('');
14
+
const [loading, setLoading] = useState(true);
15
+
const navigate = useNavigate();
16
+
17
+
const handleLogout = () => {
18
+
onLogout();
19
+
navigate('/');
20
+
};
21
+
22
+
useEffect(() => {
23
+
const fetchProfile = async () => {
24
+
try {
25
+
const did = agent.session?.did;
26
+
if (!did) {
27
+
throw new Error('No DID found in session');
28
+
}
29
+
30
+
let didDocResponse;
31
+
32
+
if (did.startsWith('did:plc:')) {
33
+
// For PLC DIDs, resolve from plc.directory
34
+
const response = await fetch(`https://plc.directory/${did}`);
35
+
didDocResponse = await response.json();
36
+
} else if (did.startsWith('did:web:')) {
37
+
// For Web DIDs, get from .well-known/did.json
38
+
const domain = did.split(':')[2];
39
+
const response = await fetch(`https://${domain}/.well-known/did.json`);
40
+
didDocResponse = await response.json();
41
+
} else {
42
+
throw new Error(`Unsupported DID type: ${did}`);
43
+
}
44
+
45
+
setDidDoc(JSON.stringify(didDocResponse, null, 2));
46
+
} catch (err) {
47
+
console.error('Error fetching DID document:', err);
48
+
setDidDoc(`Error fetching DID document: ${err instanceof Error ? err.message : 'Unknown error'}`);
49
+
} finally {
50
+
setLoading(false);
51
+
}
52
+
};
53
+
54
+
fetchProfile();
55
+
}, [agent]);
56
+
57
+
if (loading) {
58
+
return (
59
+
<div className="loading-container">
60
+
<div className="loading-text">Loading...</div>
61
+
</div>
62
+
);
63
+
}
64
+
65
+
return (
66
+
<div className="actions-page">
67
+
<Header agent={agent} onLogout={handleLogout} />
68
+
69
+
<div className="actions-container">
70
+
<div className="actions-list">
71
+
<button
72
+
className="action-item"
73
+
onClick={() => navigate('/migration')}
74
+
>
75
+
<div className="action-icon">
76
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
77
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
78
+
</svg>
79
+
</div>
80
+
<div className="action-content">
81
+
<div className="action-title">Migrate account</div>
82
+
<div className="action-subtitle">Move your account to a new data server</div>
83
+
</div>
84
+
</button>
85
+
86
+
<button
87
+
className="action-item"
88
+
onClick={() => navigate('/recovery-key')}
89
+
>
90
+
<div className="action-icon">
91
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
92
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
93
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
94
+
</svg>
95
+
</div>
96
+
<div className="action-content">
97
+
<div className="action-title">Add recovery key</div>
98
+
<div className="action-subtitle">Create a new recovery key for your account</div>
99
+
</div>
100
+
</button>
101
+
</div>
102
+
103
+
<details className="user-info-section">
104
+
<summary className="user-info-summary">User Information</summary>
105
+
<div className="user-info-content">
106
+
<section>
107
+
<h2>Account Details</h2>
108
+
<dl>
109
+
<dt>DID</dt>
110
+
<dd>{agent.session?.did || 'N/A'}</dd>
111
+
<dt>Handle</dt>
112
+
<dd>{agent.session?.handle || 'N/A'}</dd>
113
+
<dt>PDS</dt>
114
+
<dd>{agent.serviceUrl.toString() || 'N/A'}</dd>
115
+
</dl>
116
+
</section>
117
+
118
+
<section>
119
+
<h2>DID Document</h2>
120
+
<pre className="did-document">
121
+
<code>{didDoc}</code>
122
+
</pre>
123
+
</section>
124
+
</div>
125
+
</details>
126
+
</div>
127
+
<Footer />
128
+
</div>
129
+
);
130
+
}
+266
src/pages/Login.tsx
+266
src/pages/Login.tsx
···
···
1
+
import { useState } from 'react';
2
+
import { useNavigate } from 'react-router-dom';
3
+
import { AtpAgent } from '@atproto/api';
4
+
import Footer from '../components/common/Footer';
5
+
6
+
interface LoginProps {
7
+
onLogin: (agent: AtpAgent) => void;
8
+
}
9
+
10
+
interface DidDocument {
11
+
service: Array<{
12
+
id: string;
13
+
type: string;
14
+
serviceEndpoint: string;
15
+
}>;
16
+
}
17
+
18
+
type LoginStep = 'idle' | 'resolving-handle' | 'resolving-did' | 'connecting-pds' | 'authenticating' | '2fa-required' | 'success';
19
+
20
+
export default function Login({ onLogin }: LoginProps) {
21
+
const [handle, setHandle] = useState('');
22
+
const [password, setPassword] = useState('');
23
+
const [twoFactorCode, setTwoFactorCode] = useState('');
24
+
const [error, setError] = useState('');
25
+
const [appPasswordAttempts, setAppPasswordAttempts] = useState(0);
26
+
const [loginStep, setLoginStep] = useState<LoginStep>('idle');
27
+
const [agent, setAgent] = useState<AtpAgent | null>(null);
28
+
const navigate = useNavigate();
29
+
30
+
const isAppPassword = (password: string) => {
31
+
// App passwords are typically in the format xxxx-xxxx-xxxx-xxxx
32
+
return /^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$/.test(password);
33
+
};
34
+
35
+
const getStepMessage = (step: LoginStep) => {
36
+
switch (step) {
37
+
case 'resolving-handle':
38
+
return 'Resolving your handle...';
39
+
case 'resolving-did':
40
+
return 'Resolving your DID...';
41
+
case 'connecting-pds':
42
+
return 'Connecting to your Personal Data Server...';
43
+
case 'authenticating':
44
+
return 'Authenticating your credentials...';
45
+
case '2fa-required':
46
+
return 'Please enter your 2FA code';
47
+
case 'success':
48
+
return 'Login successful! Redirecting...';
49
+
default:
50
+
return '';
51
+
}
52
+
};
53
+
54
+
const handleSubmit = async (e: React.FormEvent) => {
55
+
e.preventDefault();
56
+
setError('');
57
+
58
+
// If we're in 2FA step, handle that separately
59
+
if (loginStep === '2fa-required') {
60
+
if (!agent) {
61
+
setError('Session expired. Please try logging in again.');
62
+
setLoginStep('idle');
63
+
return;
64
+
}
65
+
66
+
try {
67
+
setLoginStep('authenticating');
68
+
await agent.login({ identifier: handle, password, authFactorToken: twoFactorCode });
69
+
setLoginStep('success');
70
+
onLogin(agent);
71
+
navigate('/actions');
72
+
} catch (err) {
73
+
setError(err instanceof Error ? err.message : '2FA verification failed');
74
+
setLoginStep('2fa-required');
75
+
}
76
+
return;
77
+
}
78
+
79
+
setLoginStep('resolving-handle');
80
+
81
+
// app password check and debug method
82
+
if (isAppPassword(password)) {
83
+
if (appPasswordAttempts < 3) {
84
+
setAppPasswordAttempts(appPasswordAttempts + 1);
85
+
setError(`You have entered an app password, which does not allow for you to migrate your account. Please enter your main account password instead.`);
86
+
setLoginStep('idle');
87
+
return;
88
+
}
89
+
}
90
+
91
+
setHandle(handle.trim());
92
+
93
+
try {
94
+
// Create temporary agent to resolve DID
95
+
const tempAgent = new AtpAgent({ service: 'https://public.api.bsky.app' });
96
+
97
+
// Get DID document from handle
98
+
setLoginStep('resolving-handle');
99
+
const didResponse = await tempAgent.com.atproto.identity.resolveHandle({
100
+
handle: handle
101
+
});
102
+
103
+
if (!didResponse.success) {
104
+
// Try did:web resolution first
105
+
const domain = handle.split('.').join(':');
106
+
const webDid = `did:web:${domain}`;
107
+
try {
108
+
const webResponse = await fetch(`https://${handle}/.well-known/did.json`);
109
+
if (webResponse.ok) {
110
+
// If successful, continue with the did:web
111
+
didResponse.data.did = webDid;
112
+
} else {
113
+
throw new Error('Invalid handle');
114
+
}
115
+
} catch {
116
+
throw new Error('Invalid handle');
117
+
}
118
+
}
119
+
120
+
// Get PDS endpoint from DID document
121
+
setLoginStep('resolving-did');
122
+
let didDocResponse;
123
+
const did = didResponse.data.did;
124
+
125
+
if (did.startsWith('did:plc:')) {
126
+
// For PLC DIDs, resolve from plc.directory
127
+
const plcResponse = await fetch(`https://plc.directory/${did}`);
128
+
didDocResponse = { data: await plcResponse.json() };
129
+
} else if (did.startsWith('did:web:')) {
130
+
// For Web DIDs, get from .well-known/did.json
131
+
const domain = did.split(':')[2];
132
+
const webResponse = await fetch(`https://${domain}/.well-known/did.json`);
133
+
didDocResponse = { data: await webResponse.json() };
134
+
} else {
135
+
// Fallback to ATP resolver for other DID types
136
+
didDocResponse = await tempAgent.com.atproto.identity.resolveDid({
137
+
did: did
138
+
});
139
+
}
140
+
141
+
setLoginStep('connecting-pds');
142
+
const pds = ((didDocResponse.data as unknown) as DidDocument).service.find((s) => s.id === '#atproto_pds')?.serviceEndpoint || 'https://bsky.social';
143
+
144
+
const newAgent = new AtpAgent({ service: pds });
145
+
setAgent(newAgent);
146
+
147
+
setLoginStep('authenticating');
148
+
try {
149
+
await newAgent.login({ identifier: handle, password });
150
+
setLoginStep('success');
151
+
onLogin(newAgent);
152
+
navigate('/actions');
153
+
} catch (err) {
154
+
if (err instanceof Error && (
155
+
err.message.includes('AuthFactorTokenRequired') ||
156
+
err.message.includes('A sign in code has been sent to your email address')
157
+
)) {
158
+
setLoginStep('2fa-required');
159
+
return;
160
+
}
161
+
throw err;
162
+
}
163
+
} catch (err) {
164
+
setError(err instanceof Error ? err.message : 'Login failed');
165
+
setLoginStep('idle');
166
+
}
167
+
};
168
+
169
+
return (
170
+
<div>
171
+
<h1 className="login-title">ATproto Migrator</h1>
172
+
<div className="login-container">
173
+
<div className="login-card">
174
+
<h2 className="login-title">Sign in to your account</h2>
175
+
<div className="warning-message">
176
+
โ ๏ธ Please use your main account password, not an app password. All operations are performed locally in your browser.
177
+
</div>
178
+
<form className="login-form" onSubmit={handleSubmit}>
179
+
<div className="form-group">
180
+
<input
181
+
type="text"
182
+
required
183
+
className="form-input"
184
+
placeholder="Handle (e.g., example.bsky.social)"
185
+
value={handle}
186
+
onChange={(e) => setHandle(e.target.value)}
187
+
disabled={loginStep !== 'idle'}
188
+
/>
189
+
</div>
190
+
<div className="form-group">
191
+
<input
192
+
type="password"
193
+
required
194
+
className="form-input"
195
+
placeholder="Password"
196
+
value={password}
197
+
onChange={(e) => setPassword(e.target.value)}
198
+
disabled={loginStep !== 'idle'}
199
+
/>
200
+
</div>
201
+
202
+
{error && <div className="error-message">{error}</div>}
203
+
{loginStep !== 'idle' && loginStep !== '2fa-required' && (
204
+
<div className="loading-message">
205
+
{getStepMessage(loginStep)}
206
+
</div>
207
+
)}
208
+
209
+
<button
210
+
type="submit"
211
+
className="submit-button"
212
+
disabled={loginStep !== 'idle'}
213
+
>
214
+
{loginStep === 'idle' ? 'Sign in' : 'Signing in...'}
215
+
</button>
216
+
</form>
217
+
</div>
218
+
</div>
219
+
220
+
{/* 2FA Modal */}
221
+
{loginStep === '2fa-required' && (
222
+
<div className="modal-overlay">
223
+
<div className="modal-content">
224
+
<h2>Two-Factor Authentication Required</h2>
225
+
<p>A sign in code has been sent to your email address.</p>
226
+
<form onSubmit={handleSubmit} className="two-factor-form">
227
+
<div className="form-group">
228
+
<input
229
+
type="text"
230
+
required
231
+
className="form-input"
232
+
placeholder="Enter 2FA code"
233
+
value={twoFactorCode}
234
+
onChange={(e) => setTwoFactorCode(e.target.value)}
235
+
autoFocus
236
+
/>
237
+
</div>
238
+
{error && <div className="error-message">{error}</div>}
239
+
<div className="button-group">
240
+
<button
241
+
type="button"
242
+
className="back-button"
243
+
onClick={() => {
244
+
setLoginStep('idle');
245
+
setError('');
246
+
setTwoFactorCode('');
247
+
}}
248
+
>
249
+
Cancel
250
+
</button>
251
+
<button
252
+
type="submit"
253
+
className="submit-button"
254
+
>
255
+
Verify
256
+
</button>
257
+
</div>
258
+
</form>
259
+
</div>
260
+
</div>
261
+
)}
262
+
263
+
<Footer />
264
+
</div>
265
+
);
266
+
}
+35
src/pages/error.tsx
+35
src/pages/error.tsx
···
···
1
+
import React from 'react';
2
+
import { useNavigate } from 'react-router-dom';
3
+
4
+
interface ErrorPageProps {
5
+
statusCode?: number;
6
+
message?: string;
7
+
}
8
+
9
+
const ErrorPage: React.FC<ErrorPageProps> = ({
10
+
statusCode = 404,
11
+
message = "The page you're looking for doesn't exist."
12
+
}) => {
13
+
const navigate = useNavigate();
14
+
15
+
return (
16
+
<div className="page-content" style={{ textAlign: 'center', maxWidth: '600px', margin: '4rem auto' }}>
17
+
<h2 style={{ fontSize: '3rem', marginBottom: '1rem', color: 'var(--error-color)' }}>
18
+
{statusCode}
19
+
</h2>
20
+
<p style={{ fontSize: '1.25rem', marginBottom: '2rem' }}>
21
+
{message}
22
+
</p>
23
+
<div className="button-container" style={{ display: 'inline' }}>
24
+
<button
25
+
className="continue-button"
26
+
onClick={() => navigate('/')}
27
+
>
28
+
Come back home
29
+
</button>
30
+
</div>
31
+
</div>
32
+
);
33
+
};
34
+
35
+
export default ErrorPage;
+72
src/pages/migration/Migration.tsx
+72
src/pages/migration/Migration.tsx
···
···
1
+
import { useNavigate } from 'react-router-dom';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import Header from '../../components/common/Header';
4
+
import Footer from '../../components/common/Footer';
5
+
6
+
interface MigrationProps {
7
+
agent: AtpAgent;
8
+
onLogout: () => void;
9
+
}
10
+
11
+
export default function Migration({ agent, onLogout }: MigrationProps) {
12
+
const navigate = useNavigate();
13
+
14
+
return (
15
+
<div className="actions-page">
16
+
<Header agent={agent} onLogout={onLogout} />
17
+
18
+
<div className="actions-container">
19
+
<div className="page-content">
20
+
<h2>Migrate your account</h2>
21
+
<p>This tool allows you to migrate your account to a new Personal Data Server, a data server that hosts your account and all of its data.</p>
22
+
<h3>What to expect</h3>
23
+
<p>The migration process is <i>possible</i>, however it is not recommended if you are unsure about what you are doing. We recommend that you migrate a secondary account to your new PDS to make sure that it is able to migrate successfully <i>before</i> migrating your primary account.</p>
24
+
<p>You will need the following items to begin the migration process:</p>
25
+
<ul>
26
+
<li>A new PDS to migrate to.</li>
27
+
<li>An invite code from the new PDS (if required).</li>
28
+
<li>A way to confirm the migration, which is either a code sent to your email or your private rotation key.</li>
29
+
<li>A new password for your account <b>(which will not be stored by this tool)</b>.</li>
30
+
</ul>
31
+
32
+
<div className="warning-section">
33
+
<h3>โ ๏ธ Read Before You Continue โ ๏ธ</h3>
34
+
<ul>
35
+
<li>If you are already on a third-party PDS, it must be able to send emails or you will be unable to get a confirmation code without direct server access. We are currently unable to check this for you, however if you can verify your email address the server supports it.</li>
36
+
<li>If you are not using a custom domain, you will need a new handle as the default domain (such as alice.bsky.social or bob.pds.example.com) is non-transferable.</li>
37
+
<li>If your account is using the did:web method, you will need to modify your DID document manually. If you don't know what this means, you don't need to worry about it.</li>
38
+
<li>Due to performance issues, the main Bluesky data servers have temporarily disabled the ability to import account data. <b>As a result, you cannot migrate back to Bluesky servers for the foreseeable future.</b></li>
39
+
<li>If your PDS goes down and you do not have access to a recovery key, you will be locked out of your account. <b>Bluesky developers will not be able to help you.</b></li>
40
+
</ul>
41
+
</div>
42
+
43
+
<div className="docs-section">
44
+
<h3>Additional Resources</h3>
45
+
<p>For the technically inclined, here are some additional resources for how the migration process works:</p>
46
+
<ul>
47
+
<li><a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md" target="_blank" rel="noopener noreferrer">Detailed document on migration for PDS hosters</a></li>
48
+
<li><a href="https://atproto.com/guides/account-migration" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on account migration</a></li>
49
+
<li><a href="https://whtwnd.com/bnewbold.net/3l5ii332pf32u">Guide to migrating an account using the command line</a></li>
50
+
</ul>
51
+
</div>
52
+
53
+
<div className="button-container">
54
+
<button
55
+
className="back-button"
56
+
onClick={() => navigate('/actions')}
57
+
>
58
+
โ Go back
59
+
</button>
60
+
<button
61
+
className="continue-button"
62
+
onClick={() => navigate('/migration/registration')}
63
+
>
64
+
Continue โ
65
+
</button>
66
+
</div>
67
+
</div>
68
+
</div>
69
+
<Footer />
70
+
</div>
71
+
);
72
+
}
+82
src/pages/migration/MigrationProcess.tsx
+82
src/pages/migration/MigrationProcess.tsx
···
···
1
+
import { useEffect, useState } from 'react';
2
+
import { useNavigate } from 'react-router-dom';
3
+
import { AtpAgent } from '@atproto/api';
4
+
import Header from '../../components/common/Header';
5
+
import Footer from '../../components/common/Footer';
6
+
import { MigrationData } from '../../lib/migration/migrationData';
7
+
import '../../css/migration.css';
8
+
9
+
interface MigrationProcessProps {
10
+
agent: AtpAgent;
11
+
onLogout: () => void;
12
+
}
13
+
14
+
type MigrationStep = 'account-creation' | 'data-transfer' | 'identity-update' | 'finalization';
15
+
16
+
export default function MigrationProcess({ agent, onLogout }: MigrationProcessProps) {
17
+
let navigate = useNavigate();
18
+
const [migrationData, setMigrationData] = useState<MigrationData | null>(null);
19
+
const [currentStep, setCurrentStep] = useState<MigrationStep>('account-creation');
20
+
21
+
useEffect(() => {
22
+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
23
+
e.preventDefault();
24
+
return '';
25
+
};
26
+
27
+
window.addEventListener('beforeunload', handleBeforeUnload);
28
+
29
+
return () => {
30
+
window.removeEventListener('beforeunload', handleBeforeUnload);
31
+
};
32
+
}, []);
33
+
34
+
useEffect(() => {
35
+
if (localStorage.getItem('migrationData') == null) {
36
+
navigate('/actions');
37
+
}
38
+
39
+
const migrationData = JSON.parse(localStorage.getItem('migrationData') || '{}');
40
+
setMigrationData(new MigrationData(migrationData.oldPds, migrationData.newPds, migrationData.inviteCode, migrationData.handle, migrationData.email, migrationData.password));
41
+
}, []);
42
+
43
+
return (
44
+
<div className="actions-page">
45
+
<Header agent={agent} onLogout={onLogout} />
46
+
<div className="page-content">
47
+
<h2>Account migration in progress...</h2>
48
+
<div className="progress-steps">
49
+
<div className={`progress-step ${currentStep === 'account-creation' ? 'active' : ''}`}>
50
+
<div className="step-number">1</div>
51
+
<div className="step-text">Creating your new account</div>
52
+
</div>
53
+
<div className={`progress-step ${currentStep === 'data-transfer' ? 'active' : ''}`}>
54
+
<div className="step-number">2</div>
55
+
<div className="step-text">Moving your data</div>
56
+
</div>
57
+
<div className={`progress-step ${currentStep === 'identity-update' ? 'active' : ''}`}>
58
+
<div className="step-number">3</div>
59
+
<div className="step-text">Declaring your new host</div>
60
+
</div>
61
+
<div className={`progress-step ${currentStep === 'finalization' ? 'active' : ''}`}>
62
+
<div className="step-number">4</div>
63
+
<div className="step-text">Finalizing migration</div>
64
+
</div>
65
+
</div><br />
66
+
<div className="info-message">
67
+
<h3>Details</h3>
68
+
<strong>example</strong>
69
+
</div>
70
+
<div className="warning-section">
71
+
<h3>Important!</h3>
72
+
<ul>
73
+
<li>Do not close this window or navigate away</li>
74
+
<li>Keep your browser open until the process is complete</li>
75
+
<li>You will be notified when the migration is finished</li>
76
+
</ul>
77
+
</div>
78
+
</div>
79
+
<Footer />
80
+
</div>
81
+
);
82
+
}
+138
src/pages/migration/migrationForms.tsx
+138
src/pages/migration/migrationForms.tsx
···
···
1
+
import { useNavigate } from 'react-router-dom';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import { useState, useRef, useEffect } from 'react';
4
+
import Header from '../../components/common/Header';
5
+
import Footer from '../../components/common/Footer';
6
+
import PdsForm from '../../components/migration/PdsForm';
7
+
import AccountDetailsForm from '../../components/migration/AccountDetailsForm';
8
+
import ConfirmationStep from '../../components/migration/ConfirmationStep';
9
+
import { ServerDescription } from '../../lib/migration/serverDescription';
10
+
import { getServerDescription } from '../../lib/migration/pdsValidation';
11
+
import { MigrationData } from '../../lib/migration/migrationData';
12
+
13
+
interface MigrationFormsProps {
14
+
agent: AtpAgent;
15
+
onLogout: () => void;
16
+
}
17
+
18
+
export default function MigrationForms({ agent, onLogout }: MigrationFormsProps) {
19
+
const navigate = useNavigate();
20
+
const [currentStep, setCurrentStep] = useState<'pds' | 'account' | 'confirmation'>('pds');
21
+
const [pdsDetails, setPdsDetails] = useState<{ pds: string; inviteCode: string; serverDescription: ServerDescription } | null>(null);
22
+
const [accountDetails, setAccountDetails] = useState<{ handle: string; email: string; password: string } | null>(null);
23
+
const [currentServerDescription, setCurrentServerDescription] = useState<ServerDescription | null>(null);
24
+
const accountSectionRef = useRef<HTMLDivElement>(null);
25
+
const confirmationSectionRef = useRef<HTMLDivElement>(null);
26
+
27
+
useEffect(() => {
28
+
if (currentStep === 'account' && accountSectionRef.current) {
29
+
accountSectionRef.current.scrollIntoView({ behavior: 'smooth' });
30
+
} else if (currentStep === 'confirmation' && confirmationSectionRef.current) {
31
+
confirmationSectionRef.current.scrollIntoView({ behavior: 'smooth' });
32
+
}
33
+
}, [currentStep]);
34
+
35
+
// Fetch current PDS server description when component mounts
36
+
useEffect(() => {
37
+
const fetchCurrentServerDescription = async () => {
38
+
try {
39
+
const currentPds = new URL(agent.serviceUrl).hostname;
40
+
41
+
// If the current PDS is a Bluesky network server, use bsky.social's description
42
+
if (currentPds.endsWith('.bsky.network')) {
43
+
const description = await getServerDescription('bsky.social');
44
+
setCurrentServerDescription(description);
45
+
} else {
46
+
const description = await getServerDescription(currentPds);
47
+
setCurrentServerDescription(description);
48
+
}
49
+
} catch (e) {
50
+
console.error('Failed to fetch current server description:', e);
51
+
}
52
+
};
53
+
fetchCurrentServerDescription();
54
+
}, [agent.serviceUrl]);
55
+
56
+
const handlePdsSubmit = (pds: string, inviteCode: string, serverDescription: ServerDescription) => {
57
+
setPdsDetails({ pds, inviteCode, serverDescription });
58
+
setCurrentStep('account');
59
+
};
60
+
61
+
const handleAccountSubmit = (handle: string, email: string, password: string) => {
62
+
setAccountDetails({ handle, email, password });
63
+
setCurrentStep('confirmation');
64
+
};
65
+
66
+
const handleBack = () => {
67
+
if (currentStep === 'confirmation') {
68
+
setCurrentStep('account');
69
+
} else if (currentStep === 'account') {
70
+
setCurrentStep('pds');
71
+
} else {
72
+
navigate('/migration');
73
+
}
74
+
};
75
+
76
+
const handleConfirm = () => {
77
+
if (pdsDetails && accountDetails) {
78
+
const migrationData = new MigrationData(agent.serviceUrl.hostname, pdsDetails.pds, pdsDetails.inviteCode, accountDetails.handle, accountDetails.email, accountDetails.password);
79
+
console.log('Migration data:', migrationData);
80
+
localStorage.setItem('migrationData', JSON.stringify(migrationData));
81
+
} else {
82
+
console.error('Migration data is not complete');
83
+
return;
84
+
}
85
+
navigate('/migration/process');
86
+
};
87
+
88
+
return (
89
+
<div className="actions-page">
90
+
<Header agent={agent} onLogout={onLogout} />
91
+
92
+
<div className="actions-container">
93
+
<div className="page-content">
94
+
<h2>Migrate your account</h2>
95
+
96
+
<div className={currentStep !== 'pds' ? 'form-section completed' : 'form-section'}>
97
+
<PdsForm
98
+
agent={agent}
99
+
onSubmit={handlePdsSubmit}
100
+
onBack={handleBack}
101
+
/>
102
+
</div>
103
+
104
+
{currentStep !== 'pds' && pdsDetails && currentServerDescription && (
105
+
<div className={currentStep === 'account' ? 'form-section' : 'form-section completed'} ref={accountSectionRef}>
106
+
<AccountDetailsForm
107
+
currentHandle={agent.session?.handle || ''}
108
+
pds={pdsDetails.pds}
109
+
inviteCode={pdsDetails.inviteCode}
110
+
serverDescription={currentServerDescription}
111
+
newServerDescription={pdsDetails.serverDescription}
112
+
onSubmit={handleAccountSubmit}
113
+
onBack={handleBack}
114
+
/>
115
+
</div>
116
+
)}
117
+
118
+
{currentStep === 'confirmation' && pdsDetails && accountDetails && (
119
+
<div className="form-section" ref={confirmationSectionRef}>
120
+
<ConfirmationStep
121
+
handle={accountDetails.handle}
122
+
email={accountDetails.email}
123
+
password={accountDetails.password}
124
+
pds={pdsDetails.pds}
125
+
currentHandle={agent.session?.handle || ''}
126
+
currentPds={new URL(agent.serviceUrl).hostname}
127
+
onBack={handleBack}
128
+
onConfirm={handleConfirm}
129
+
/>
130
+
</div>
131
+
)}
132
+
</div>
133
+
</div>
134
+
135
+
<Footer />
136
+
</div>
137
+
);
138
+
}
+53
src/pages/recoveryKey/RecoveryKeyProcess.tsx
+53
src/pages/recoveryKey/RecoveryKeyProcess.tsx
···
···
1
+
import { useNavigate } from 'react-router-dom';
2
+
import { useEffect } from 'react';
3
+
import { AtpAgent } from '@atproto/api';
4
+
import Footer from '../../components/common/Footer';
5
+
import Header from '../../components/common/Header';
6
+
7
+
interface RecoveryKeyProcessProps {
8
+
agent: AtpAgent;
9
+
onLogout: () => void;
10
+
}
11
+
12
+
export default function RecoveryKeyProcess({ agent, onLogout }: RecoveryKeyProcessProps) {
13
+
const navigate = useNavigate();
14
+
15
+
// Add warning when trying to close or navigate away
16
+
useEffect(() => {
17
+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
18
+
e.preventDefault();
19
+
return '';
20
+
};
21
+
22
+
window.addEventListener('beforeunload', handleBeforeUnload);
23
+
24
+
return () => {
25
+
window.removeEventListener('beforeunload', handleBeforeUnload);
26
+
};
27
+
}, []);
28
+
29
+
return (
30
+
<div className="actions-page">
31
+
<Header agent={agent} onLogout={onLogout} />
32
+
33
+
<div className="actions-container">
34
+
<div className="page-content">
35
+
<h2>Add Recovery Key</h2>
36
+
<p>This page will guide you through the process of adding a recovery key to your account.</p>
37
+
<div className="warning-section">
38
+
<h3>This is not implemented yet!</h3>
39
+
</div>
40
+
<div className="button-container">
41
+
<button
42
+
className="back-button"
43
+
onClick={() => navigate('/recovery-key')}
44
+
>
45
+
โ Go back
46
+
</button>
47
+
</div>
48
+
</div>
49
+
</div>
50
+
<Footer />
51
+
</div>
52
+
);
53
+
}
+66
src/pages/recoveryKey/recovery.tsx
+66
src/pages/recoveryKey/recovery.tsx
···
···
1
+
import { useNavigate } from 'react-router-dom';
2
+
import { AtpAgent } from '@atproto/api';
3
+
import Footer from '../../components/common/Footer';
4
+
import Header from '../../components/common/Header';
5
+
6
+
interface RecoveryKeyProps {
7
+
agent: AtpAgent;
8
+
onLogout: () => void;
9
+
}
10
+
11
+
export default function RecoveryKey({ agent, onLogout }: RecoveryKeyProps) {
12
+
const navigate = useNavigate();
13
+
14
+
return (
15
+
<div className="actions-page">
16
+
<Header agent={agent} onLogout={onLogout} />
17
+
18
+
<div className="actions-container">
19
+
<div className="page-content">
20
+
<h2>Add a recovery key</h2>
21
+
<p>A recovery key (known as a <b>rotation key</b> in the AT Protocol) is a cryptographic key associated with your account that allows you to modify your account's core identity.</p>
22
+
23
+
<h3>How rotation keys work</h3>
24
+
<p>In the AT Protocol, your account is identified using a DID (<b>Decentralized Identifier</b>), with most accounts on the protocol using a variant of it developed specifically for the protocol named PLC. The account's core information (such as your handle and data server on the network) is stored in the account's DID document.</p>
25
+
<p>To change this document (an event known as a <b>PLC operation</b>), you use a rotation key to confirm that you are the owner of the account and that you are authorized to make the changes. For example, when changing your handle, your data server (also known as a PDS) will use its own rotation key to change the document, allowing the user to change their handle without needing to provide their own key.</p>
26
+
<h3>Why should I add another key?</h3>
27
+
<p>Adding a rotation key allows you to regain control of your account if it is compromised. It also allows you to move your account to a new data server, even if the current server is down.</p>
28
+
<div className="warning-section">
29
+
<h3>โ ๏ธ Read Before You Continue โ ๏ธ</h3>
30
+
<ul>
31
+
<li>You will need a to add a recovery key. Tokens are sent to the email address associated with your account.</li>
32
+
<li>While we do generate a key for you, we will not store it. Please save it in a secure location.</li>
33
+
<li>Keep your recovery key private. Anyone with access to it could potentially take control of your account.</li>
34
+
<li>If you're using a third-party PDS, it must be able to send emails or you will not be able to use this tool to add a recovery key.</li>
35
+
</ul>
36
+
</div>
37
+
<div className="docs-section">
38
+
<h3>Additional Resources</h3>
39
+
<p>For the technically inclined, here are some additional resources for how rotation keys work:</p>
40
+
<ul>
41
+
<li><a href="https://atproto.com/guides/identity" target="_blank" rel="noopener noreferrer">AT Protocol's developer documentation on identity</a></li>
42
+
<li><a href="https://whtwnd.com/did:plc:xz3euvkhf44iadavovbsmqoo/3laimapx6ks2b" target="_blank" rel="noopener noreferrer">Guide to adding a recovery key using the command line</a></li>
43
+
<li><a href="https://whtwnd.com/did:plc:44ybard66vv44zksje25o7dz/3lj7jmt2ct72r" target="_blank" rel="noopener noreferrer">More in-depth guide to adding a recovery key</a></li>
44
+
</ul>
45
+
</div>
46
+
47
+
<div className="button-container">
48
+
<button
49
+
className="back-button"
50
+
onClick={() => navigate('/actions')}
51
+
>
52
+
โ Go back
53
+
</button>
54
+
<button
55
+
className="continue-button"
56
+
onClick={() => navigate('/recovery-key/process')}
57
+
>
58
+
Continue โ
59
+
</button>
60
+
</div>
61
+
</div>
62
+
</div>
63
+
<Footer />
64
+
</div>
65
+
);
66
+
}
+119
src/routes.tsx
+119
src/routes.tsx
···
···
1
+
import { useEffect } from 'react'
2
+
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
3
+
import { AtpAgent } from '@atproto/api'
4
+
5
+
import NetworkWarning from './components/common/NetworkWarning'
6
+
import Login from './pages/Login'
7
+
import Actions from './pages/Actions'
8
+
import Migration from './pages/migration/Migration'
9
+
import MigrationForms from './pages/migration/MigrationForms'
10
+
import MigrationProcess from './pages/migration/MigrationProcess'
11
+
import RecoveryKey from './pages/recoveryKey/Recovery'
12
+
import RecoveryKeyProcess from './pages/recoveryKey/RecoveryKeyProcess'
13
+
import ErrorPage from './pages/Error'
14
+
15
+
export function AppRoutes({ agent, onLogout, handleLogin }: {
16
+
agent: AtpAgent | null;
17
+
onLogout: () => void;
18
+
handleLogin: (agent: AtpAgent) => void;
19
+
}) {
20
+
const location = useLocation();
21
+
22
+
useEffect(() => {
23
+
const checkSession = async () => {
24
+
if (agent) {
25
+
try {
26
+
// Try to make a simple API call to verify the session
27
+
await agent.getProfile({ actor: agent.session?.handle || '' });
28
+
} catch (err) {
29
+
// If the API call fails, the session is likely invalid
30
+
console.error('Session check failed: ', err);
31
+
onLogout();
32
+
alert('Your session has expired. Please log in again.');
33
+
}
34
+
}
35
+
};
36
+
37
+
checkSession();
38
+
}, [location.pathname, agent, onLogout]);
39
+
40
+
return (
41
+
<>
42
+
<NetworkWarning />
43
+
<Routes>
44
+
<Route
45
+
path="/"
46
+
element={
47
+
agent ? (
48
+
<Navigate to="/actions" replace />
49
+
) : (
50
+
<Login onLogin={handleLogin} />
51
+
)
52
+
}
53
+
/>
54
+
<Route
55
+
path="/actions"
56
+
element={
57
+
agent ? (
58
+
<Actions agent={agent} onLogout={onLogout} />
59
+
) : (
60
+
<Navigate to="/" replace />
61
+
)
62
+
}
63
+
/>
64
+
<Route
65
+
path="/migration"
66
+
element={
67
+
agent ? (
68
+
<Migration agent={agent} onLogout={onLogout} />
69
+
) : (
70
+
<Navigate to="/" replace />
71
+
)
72
+
}
73
+
/>
74
+
<Route
75
+
path="/migration/registration"
76
+
element={
77
+
agent ? (
78
+
<MigrationForms agent={agent} onLogout={onLogout} />
79
+
) : (
80
+
<Navigate to="/" replace />
81
+
)
82
+
}
83
+
/>
84
+
<Route
85
+
path="/recovery-key"
86
+
element={
87
+
agent ? (
88
+
<RecoveryKey agent={agent} onLogout={onLogout} />
89
+
) : (
90
+
<Navigate to="/" replace />
91
+
)
92
+
}
93
+
/>
94
+
<Route
95
+
path="/recovery-key/process"
96
+
element={
97
+
agent ? (
98
+
<RecoveryKeyProcess agent={agent} onLogout={onLogout} />
99
+
) : (
100
+
<Navigate to="/" replace />
101
+
)
102
+
}
103
+
/>
104
+
<Route
105
+
path="/migration/process"
106
+
element={
107
+
agent ? (
108
+
<MigrationProcess agent={agent} onLogout={onLogout} />
109
+
) : (
110
+
<Navigate to="/" replace />
111
+
)
112
+
}
113
+
/>
114
+
{/* Catch-all route for 404s */}
115
+
<Route path="*" element={<ErrorPage />} />
116
+
</Routes>
117
+
</>
118
+
);
119
+
}
-738
src/styles/App.css
-738
src/styles/App.css
···
1
-
/* Base styles */
2
-
:root {
3
-
--primary-color: #4f46e5;
4
-
--primary-hover: #4338ca;
5
-
--text-color: #1f2937;
6
-
--text-light: #6b7280;
7
-
--bg-color: #f3f4f6;
8
-
--white: #ffffff;
9
-
--error-color: #ef4444;
10
-
--border-color: #e5e7eb;
11
-
--input-bg: #ffffff;
12
-
}
13
-
14
-
@media (prefers-color-scheme: dark) {
15
-
:root {
16
-
--primary-color: #6366f1;
17
-
--primary-hover: #4f46e5;
18
-
--text-color: #f3f4f6;
19
-
--text-light: #9ca3af;
20
-
--bg-color: #111827;
21
-
--white: #1f2937;
22
-
--error-color: #f87171;
23
-
--border-color: #374151;
24
-
--input-bg: #1f2937;
25
-
}
26
-
}
27
-
28
-
body {
29
-
margin: 0;
30
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
31
-
background-color: var(--bg-color);
32
-
color: var(--text-color);
33
-
min-height: 100vh;
34
-
display: flex;
35
-
flex-direction: column;
36
-
}
37
-
38
-
/* Login page styles */
39
-
.login-container {
40
-
flex: 1;
41
-
display: flex;
42
-
align-items: center;
43
-
justify-content: center;
44
-
padding: 1rem;
45
-
}
46
-
47
-
.login-card {
48
-
max-width: 28rem;
49
-
width: 100%;
50
-
padding: 2rem;
51
-
padding-top: 0;
52
-
background-color: var(--white);
53
-
border-radius: 0.5rem;
54
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
55
-
}
56
-
57
-
.login-title {
58
-
padding-top: 0;
59
-
text-align: center;
60
-
font-size: 1.875rem;
61
-
font-weight: 800;
62
-
color: var(--text-color);
63
-
}
64
-
65
-
.warning-message {
66
-
margin: 1rem 0;
67
-
padding: 0.75rem;
68
-
background-color: #fef3c7;
69
-
border: 1px solid #fbbf24;
70
-
border-radius: 0.375rem;
71
-
color: #92400e;
72
-
font-size: 0.875rem;
73
-
text-align: center;
74
-
}
75
-
76
-
.login-form {
77
-
margin-top: 2rem;
78
-
padding: 0 1rem;
79
-
}
80
-
81
-
.form-group {
82
-
margin-bottom: 1rem;
83
-
}
84
-
85
-
.form-group label {
86
-
display: block;
87
-
margin-bottom: 0.5rem;
88
-
color: var(--text-color);
89
-
font-weight: 500;
90
-
}
91
-
92
-
.handle-input-container {
93
-
display: flex;
94
-
align-items: stretch;
95
-
gap: 0;
96
-
background-color: var(--input-bg);
97
-
border: 1px solid var(--border-color);
98
-
border-radius: 0.375rem;
99
-
}
100
-
101
-
.handle-input-container .form-input {
102
-
border: none;
103
-
padding-right: 0;
104
-
flex: 1;
105
-
border-radius: 0.375rem 0 0 0.375rem;
106
-
}
107
-
108
-
.handle-input-container .form-input:focus {
109
-
box-shadow: none;
110
-
}
111
-
112
-
.handle-domain {
113
-
color: var(--text-light);
114
-
font-size: 0.875rem;
115
-
white-space: nowrap;
116
-
background-color: var(--bg-color);
117
-
padding: 0 0.75rem;
118
-
border-radius: 0 0.375rem 0.375rem 0;
119
-
display: flex;
120
-
align-items: center;
121
-
}
122
-
123
-
.form-input {
124
-
width: 100%;
125
-
padding: 0.75rem 1rem;
126
-
border: 1px solid var(--border-color);
127
-
border-radius: 0.375rem;
128
-
font-size: 0.875rem;
129
-
color: var(--text-color);
130
-
background-color: var(--input-bg);
131
-
box-sizing: border-box;
132
-
}
133
-
134
-
.form-input:focus {
135
-
outline: none;
136
-
border-color: var(--primary-color);
137
-
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
138
-
}
139
-
140
-
.form-input::placeholder {
141
-
color: var(--text-light);
142
-
}
143
-
144
-
.error-message {
145
-
color: var(--error-color);
146
-
font-size: 0.875rem;
147
-
text-align: center;
148
-
margin: 0.5rem 0;
149
-
}
150
-
151
-
.success-message {
152
-
color: #059669;
153
-
font-size: 0.875rem;
154
-
margin: 0.5rem 0;
155
-
display: flex;
156
-
align-items: center;
157
-
gap: 0.5rem;
158
-
}
159
-
160
-
.submit-button {
161
-
width: 100%;
162
-
padding: 0.75rem;
163
-
background-color: var(--primary-color);
164
-
color: var(--white);
165
-
border: none;
166
-
border-radius: 0.375rem;
167
-
font-size: 0.875rem;
168
-
font-weight: 500;
169
-
cursor: pointer;
170
-
transition: background-color 0.2s;
171
-
margin-top: 1rem;
172
-
}
173
-
174
-
.submit-button:hover {
175
-
background-color: var(--primary-hover);
176
-
}
177
-
178
-
/* Header styles */
179
-
.app-header {
180
-
display: flex;
181
-
flex-wrap: wrap;
182
-
align-items: center;
183
-
justify-content: space-between;
184
-
gap: 1rem;
185
-
padding: 1rem;
186
-
background-color: var(--white);
187
-
border-radius: 0.5rem;
188
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
189
-
margin: 1rem 0 0 0;
190
-
position: relative;
191
-
z-index: 10;
192
-
}
193
-
194
-
.app-title {
195
-
font-size: 1.25rem;
196
-
font-weight: 700;
197
-
color: var(--text-color);
198
-
margin: 0;
199
-
white-space: nowrap;
200
-
overflow: hidden;
201
-
text-overflow: ellipsis;
202
-
max-width: 200px;
203
-
}
204
-
205
-
.user-info {
206
-
display: flex;
207
-
align-items: center;
208
-
gap: 0.75rem;
209
-
flex-wrap: wrap;
210
-
}
211
-
212
-
.user-avatar {
213
-
width: 2rem;
214
-
height: 2rem;
215
-
border-radius: 50%;
216
-
object-fit: cover;
217
-
flex-shrink: 0;
218
-
}
219
-
220
-
.user-handle {
221
-
font-size: 0.875rem;
222
-
color: var(--text-color);
223
-
white-space: nowrap;
224
-
overflow: hidden;
225
-
text-overflow: ellipsis;
226
-
max-width: 150px;
227
-
}
228
-
229
-
.logout-button {
230
-
background-color: #f44336;
231
-
color: white;
232
-
border: none;
233
-
padding: 8px 16px;
234
-
border-radius: 4px;
235
-
cursor: pointer;
236
-
font-size: 14px;
237
-
transition: background-color 0.2s;
238
-
white-space: nowrap;
239
-
flex-shrink: 0;
240
-
}
241
-
242
-
@media (max-width: 480px) {
243
-
.app-header {
244
-
padding: 0.75rem;
245
-
gap: 0.5rem;
246
-
}
247
-
248
-
.app-title {
249
-
font-size: 1.125rem;
250
-
max-width: 150px;
251
-
}
252
-
253
-
.user-handle {
254
-
max-width: 100px;
255
-
}
256
-
257
-
.logout-button {
258
-
padding: 6px 12px;
259
-
font-size: 13px;
260
-
}
261
-
}
262
-
263
-
/* Actions page styles */
264
-
.actions-page {
265
-
display: flex;
266
-
flex-direction: column;
267
-
min-height: 100vh;
268
-
max-width: 800px;
269
-
margin: 0 auto;
270
-
padding: 0 20px;
271
-
gap: 1rem;
272
-
}
273
-
274
-
.actions-container {
275
-
padding: 0;
276
-
max-width: 800px;
277
-
width: 100%;
278
-
margin: 0 auto;
279
-
}
280
-
281
-
.actions-list {
282
-
display: flex;
283
-
flex-direction: column;
284
-
gap: 1rem;
285
-
margin-top: 1rem;
286
-
}
287
-
288
-
.action-item {
289
-
display: flex;
290
-
align-items: center;
291
-
gap: 1rem;
292
-
padding: 1rem;
293
-
background-color: var(--white);
294
-
border-radius: 0.5rem;
295
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
296
-
cursor: pointer;
297
-
transition: transform 0.2s, box-shadow 0.2s;
298
-
border: none;
299
-
width: 100%;
300
-
text-align: left;
301
-
}
302
-
303
-
.action-item:hover {
304
-
transform: translateY(-2px);
305
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
306
-
}
307
-
308
-
.action-icon {
309
-
width: 2.5rem;
310
-
height: 2.5rem;
311
-
display: flex;
312
-
align-items: center;
313
-
justify-content: center;
314
-
color: var(--primary-color);
315
-
background-color: var(--bg-color);
316
-
border-radius: 0.5rem;
317
-
flex-shrink: 0;
318
-
}
319
-
320
-
.action-content {
321
-
flex: 1;
322
-
text-align: left;
323
-
}
324
-
325
-
.action-title {
326
-
font-size: 1rem;
327
-
font-weight: 600;
328
-
color: var(--text-color);
329
-
margin-bottom: 0.25rem;
330
-
text-align: left;
331
-
}
332
-
333
-
.action-subtitle {
334
-
font-size: 0.875rem;
335
-
color: var(--text-light);
336
-
text-align: left;
337
-
}
338
-
339
-
/* User info section */
340
-
.user-info-section {
341
-
background-color: var(--white);
342
-
border-radius: 0.5rem;
343
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
344
-
overflow: hidden;
345
-
margin-top: 1rem;
346
-
}
347
-
348
-
.user-info-summary {
349
-
padding: 1rem;
350
-
cursor: pointer;
351
-
display: flex;
352
-
align-items: center;
353
-
justify-content: space-between;
354
-
list-style: none;
355
-
font-weight: 600;
356
-
color: var(--text-color);
357
-
}
358
-
359
-
.user-info-summary::after {
360
-
content: "โผ";
361
-
color: var(--primary-color);
362
-
transition: transform 0.2s;
363
-
}
364
-
365
-
.user-info-section[open] .user-info-summary::after {
366
-
transform: rotate(180deg);
367
-
}
368
-
369
-
.user-info-content {
370
-
padding: 1.5rem;
371
-
border-top: 1px solid var(--border-color);
372
-
}
373
-
374
-
.user-info-content section {
375
-
margin-bottom: 2rem;
376
-
}
377
-
378
-
.user-info-content section:last-child {
379
-
margin-bottom: 0;
380
-
}
381
-
382
-
.user-info-content h2 {
383
-
font-size: 1.125rem;
384
-
font-weight: 600;
385
-
color: var(--text-color);
386
-
margin-bottom: 1rem;
387
-
}
388
-
389
-
.user-info-content dl {
390
-
display: grid;
391
-
grid-template-columns: max-content 1fr;
392
-
gap: 0.75rem 1rem;
393
-
margin: 0;
394
-
}
395
-
396
-
.user-info-content dt {
397
-
font-weight: 500;
398
-
color: var(--text-light);
399
-
}
400
-
401
-
.user-info-content dd {
402
-
margin: 0;
403
-
color: var(--text-color);
404
-
word-break: break-all;
405
-
}
406
-
407
-
.did-document {
408
-
background-color: var(--bg-color);
409
-
padding: 1rem;
410
-
border-radius: 0.375rem;
411
-
overflow-x: auto;
412
-
margin: 0;
413
-
}
414
-
415
-
.did-document code {
416
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
417
-
font-size: 0.875rem;
418
-
color: var(--text-color);
419
-
white-space: pre;
420
-
}
421
-
422
-
/* Loading state */
423
-
.loading-container {
424
-
flex: 1;
425
-
display: flex;
426
-
align-items: center;
427
-
justify-content: center;
428
-
min-height: 100vh;
429
-
padding: 1rem;
430
-
}
431
-
432
-
.loading-text {
433
-
font-size: 1.25rem;
434
-
color: var(--text-color);
435
-
background-color: var(--white);
436
-
padding: 1.5rem 2rem;
437
-
border-radius: 0.5rem;
438
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
439
-
display: flex;
440
-
align-items: center;
441
-
gap: 0.75rem;
442
-
}
443
-
444
-
.loading-text::before {
445
-
content: "";
446
-
width: 1.5rem;
447
-
height: 1.5rem;
448
-
border: 2px solid var(--primary-color);
449
-
border-right-color: transparent;
450
-
border-radius: 50%;
451
-
animation: spin 1s linear infinite;
452
-
}
453
-
454
-
@keyframes spin {
455
-
to {
456
-
transform: rotate(360deg);
457
-
}
458
-
}
459
-
460
-
/* Footer styles */
461
-
.footer {
462
-
text-align: center;
463
-
padding: 1rem;
464
-
color: var(--text-light);
465
-
font-size: 0.875rem;
466
-
margin: 1rem 0;
467
-
}
468
-
469
-
.footer-link {
470
-
color: var(--primary-color);
471
-
text-decoration: none;
472
-
transition: color 0.2s;
473
-
}
474
-
475
-
.footer-link:hover {
476
-
color: var(--primary-hover);
477
-
}
478
-
479
-
.footer-link strong {
480
-
font-weight: 600;
481
-
}
482
-
483
-
.loading-message {
484
-
margin: 10px 0;
485
-
padding: 10px;
486
-
background-color: var(--bg-color);
487
-
border-radius: 4px;
488
-
color: var(--text-color);
489
-
font-size: 0.9em;
490
-
text-align: center;
491
-
}
492
-
493
-
.form-input:disabled {
494
-
background-color: var(--bg-color);
495
-
border-color: var(--border-color);
496
-
color: var(--text-light);
497
-
cursor: not-allowed;
498
-
opacity: 0.8;
499
-
}
500
-
501
-
.submit-button:disabled {
502
-
background-color: var(--text-light);
503
-
cursor: not-allowed;
504
-
opacity: 0.8;
505
-
}
506
-
507
-
.page-content {
508
-
background-color: var(--white);
509
-
border-radius: 0.5rem;
510
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
511
-
padding: 2rem;
512
-
padding-top: 1rem;
513
-
margin-top: 1rem;
514
-
}
515
-
516
-
.page-content h2 {
517
-
font-size: 1.5rem;
518
-
font-weight: 600;
519
-
color: var(--text-color);
520
-
margin-bottom: 1rem;
521
-
}
522
-
523
-
.page-content p {
524
-
color: var(--text-color);
525
-
margin-bottom: 1rem;
526
-
}
527
-
528
-
.page-content ul {
529
-
list-style-type: disc;
530
-
margin-left: 1.5rem;
531
-
margin-bottom: 1.5rem;
532
-
color: var(--text-color);
533
-
}
534
-
535
-
.page-content li {
536
-
margin-bottom: 0.5rem;
537
-
}
538
-
539
-
.back-button {
540
-
background-color: var(--primary-color);
541
-
color: var(--white);
542
-
border: none;
543
-
padding: 0.75rem 1.5rem;
544
-
border-radius: 0.375rem;
545
-
font-size: 0.875rem;
546
-
font-weight: 500;
547
-
cursor: pointer;
548
-
transition: background-color 0.2s;
549
-
display: inline-flex;
550
-
align-items: center;
551
-
gap: 0.5rem;
552
-
}
553
-
554
-
.button-container {
555
-
display: flex;
556
-
justify-content: space-between;
557
-
gap: 1rem;
558
-
margin-top: 2rem;
559
-
}
560
-
561
-
.back-button {
562
-
background-color: var(--text-light);
563
-
color: var(--white);
564
-
border: none;
565
-
padding: 0.75rem 1.5rem;
566
-
border-radius: 0.375rem;
567
-
font-size: 0.875rem;
568
-
font-weight: 500;
569
-
cursor: pointer;
570
-
transition: background-color 0.2s;
571
-
display: inline-flex;
572
-
align-items: center;
573
-
gap: 0.5rem;
574
-
}
575
-
576
-
.back-button:hover {
577
-
background-color: #c7c7c7;
578
-
}
579
-
580
-
.continue-button {
581
-
background-color: var(--primary-color);
582
-
color: var(--white);
583
-
border: none;
584
-
padding: 0.75rem 1.5rem;
585
-
border-radius: 0.375rem;
586
-
font-size: 0.875rem;
587
-
font-weight: 500;
588
-
cursor: pointer;
589
-
transition: background-color 0.2s;
590
-
display: inline-flex;
591
-
align-items: center;
592
-
gap: 0.5rem;
593
-
margin-left: auto;
594
-
}
595
-
596
-
.continue-button:hover {
597
-
background-color: var(--primary-hover);
598
-
}
599
-
600
-
.page-content h3 {
601
-
font-size: 1.25rem;
602
-
font-weight: 600;
603
-
color: var(--text-color);
604
-
margin: 1.5rem 0 1rem 0;
605
-
}
606
-
607
-
.warning-section {
608
-
background-color: #fef3c7;
609
-
border: 1px solid #fbbf24;
610
-
border-radius: 0.5rem;
611
-
padding: 1.5rem;
612
-
margin: 1.5rem 0;
613
-
}
614
-
615
-
.warning-section h3 {
616
-
color: #92400e;
617
-
margin-top: 0;
618
-
}
619
-
620
-
.warning-section ul {
621
-
margin-bottom: 0;
622
-
padding-left: 0;
623
-
}
624
-
625
-
.warning-section li {
626
-
color: #92400e;
627
-
}
628
-
629
-
.warning-section b {
630
-
color: #78350f;
631
-
}
632
-
633
-
.docs-section {
634
-
background-color: var(--bg-color);
635
-
border-radius: 0.5rem;
636
-
padding: 1.5rem;
637
-
margin: 1.5rem 0;
638
-
}
639
-
640
-
.docs-section h3 {
641
-
margin-top: 0;
642
-
}
643
-
644
-
.docs-section ul {
645
-
margin-bottom: 0;
646
-
padding-left: 0;
647
-
}
648
-
649
-
.docs-section a {
650
-
color: var(--primary-color);
651
-
text-decoration: none;
652
-
transition: color 0.2s;
653
-
display: inline-flex;
654
-
align-items: center;
655
-
gap: 0.5rem;
656
-
}
657
-
658
-
.docs-section a:hover {
659
-
color: var(--primary-hover);
660
-
}
661
-
662
-
.docs-section a::after {
663
-
content: "โ";
664
-
transition: transform 0.2s;
665
-
}
666
-
667
-
.docs-section a:hover::after {
668
-
transform: translateX(4px);
669
-
}
670
-
671
-
.network-warning {
672
-
position: fixed;
673
-
top: 0;
674
-
left: 0;
675
-
right: 0;
676
-
bottom: 0;
677
-
background-color: rgba(0, 0, 0, 0.8);
678
-
z-index: 1000;
679
-
display: flex;
680
-
align-items: center;
681
-
justify-content: center;
682
-
}
683
-
684
-
.network-warning-content {
685
-
max-width: 800px;
686
-
padding: 2rem;
687
-
display: flex;
688
-
align-items: center;
689
-
gap: 0.75rem;
690
-
color: #92400e;
691
-
font-size: 1.125rem;
692
-
text-align: center;
693
-
background-color: rgba(255, 255, 255, 0.9);
694
-
border-radius: 0.5rem;
695
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
696
-
}
697
-
698
-
.network-warning-icon {
699
-
font-size: 1.5rem;
700
-
flex-shrink: 0;
701
-
}
702
-
703
-
.form-section {
704
-
background: var(--white);
705
-
border-radius: 8px;
706
-
padding: 2rem;
707
-
margin-bottom: 2rem;
708
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
709
-
transition: all 0.3s ease;
710
-
border: 1px solid var(--border-color);
711
-
}
712
-
713
-
.form-section.completed {
714
-
opacity: 0.6;
715
-
pointer-events: none;
716
-
background: var(--bg-color);
717
-
}
718
-
719
-
.form-section h3 {
720
-
margin-top: 0;
721
-
margin-bottom: 1.5rem;
722
-
color: var(--text-color);
723
-
font-size: 1.25rem;
724
-
}
725
-
726
-
.info-message {
727
-
color: var(--primary-color);
728
-
font-size: 0.875rem;
729
-
margin-top: 0.5rem;
730
-
display: flex;
731
-
align-items: center;
732
-
gap: 0.5rem;
733
-
}
734
-
735
-
.info-message::before {
736
-
content: "โน๏ธ";
737
-
font-size: 1rem;
738
-
}
···
+10
-4
tsconfig.app.json
+10
-4
tsconfig.app.json
···
1
{
2
"compilerOptions": {
3
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
-
"target": "ES2020",
5
"useDefineForClassFields": true,
6
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7
"module": "ESNext",
8
"skipLibCheck": true,
9
10
/* Bundler mode */
11
"moduleResolution": "bundler",
12
"allowImportingTsExtensions": true,
13
"isolatedModules": true,
14
"moduleDetection": "force",
15
"noEmit": true,
···
20
"noUnusedLocals": true,
21
"noUnusedParameters": true,
22
"noFallthroughCasesInSwitch": true,
23
-
"noUncheckedSideEffectImports": true
24
},
25
-
"include": ["src"]
26
}
···
1
{
2
"compilerOptions": {
3
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+
"target": "ES2023",
5
"useDefineForClassFields": true,
6
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
7
"module": "ESNext",
8
"skipLibCheck": true,
9
10
/* Bundler mode */
11
"moduleResolution": "bundler",
12
"allowImportingTsExtensions": true,
13
+
"resolveJsonModule": true,
14
"isolatedModules": true,
15
"moduleDetection": "force",
16
"noEmit": true,
···
21
"noUnusedLocals": true,
22
"noUnusedParameters": true,
23
"noFallthroughCasesInSwitch": true,
24
+
"noUncheckedSideEffectImports": true,
25
+
"forceConsistentCasingInFileNames": true,
26
+
27
+
/* Types */
28
+
"types": ["react", "react-dom", "node"]
29
},
30
+
"include": ["src/**/*"],
31
+
"references": [{ "path": "./tsconfig.node.json" }]
32
}
+5
-5
tsconfig.node.json
+5
-5
tsconfig.node.json
···
1
{
2
"compilerOptions": {
3
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
-
"target": "ES2022",
5
"lib": ["ES2023"],
6
"module": "ESNext",
7
"skipLibCheck": true,
8
9
/* Bundler mode */
10
-
"moduleResolution": "bundler",
11
-
"allowImportingTsExtensions": true,
12
"isolatedModules": true,
13
"moduleDetection": "force",
14
-
"noEmit": true,
15
16
/* Linting */
17
"strict": true,
18
"noUnusedLocals": true,
19
"noUnusedParameters": true,
20
"noFallthroughCasesInSwitch": true,
21
-
"noUncheckedSideEffectImports": true
22
},
23
"include": ["vite.config.ts"]
24
}
···
1
{
2
"compilerOptions": {
3
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+
"target": "ES2023",
5
"lib": ["ES2023"],
6
"module": "ESNext",
7
"skipLibCheck": true,
8
9
/* Bundler mode */
10
+
"moduleResolution": "node",
11
"isolatedModules": true,
12
"moduleDetection": "force",
13
+
"composite": true,
14
15
/* Linting */
16
"strict": true,
17
"noUnusedLocals": true,
18
"noUnusedParameters": true,
19
"noFallthroughCasesInSwitch": true,
20
+
"noUncheckedSideEffectImports": true,
21
+
"forceConsistentCasingInFileNames": true
22
},
23
"include": ["vite.config.ts"]
24
}
+37
-3
vite.config.ts
+37
-3
vite.config.ts
···
1
+
import { defineConfig } from 'vite';
2
+
import react from '@vitejs/plugin-react';
3
+
import { visualizer } from 'rollup-plugin-visualizer';
4
5
// https://vitejs.dev/config/
6
export default defineConfig({
7
+
plugins: [
8
+
react(),
9
+
...(process.env.ANALYZE ? [visualizer({
10
+
open: true,
11
+
filename: 'dist/bundle-analysis.html',
12
+
gzipSize: true
13
+
})] : [])
14
+
],
15
build: {
16
outDir: 'dist',
17
+
emptyOutDir: true,
18
sourcemap: true,
19
+
minify: 'terser',
20
+
terserOptions: {
21
+
compress: {
22
+
drop_console: true,
23
+
drop_debugger: true
24
+
}
25
+
},
26
+
rollupOptions: {
27
+
external: [
28
+
'multiformats',
29
+
'multiformats/basics',
30
+
'multiformats/cid',
31
+
'multiformats/hashes/sha2',
32
+
'multiformats/hashes/sha3',
33
+
'iso-datestring-validator',
34
+
'uint8arrays'
35
+
],
36
+
output: {
37
+
manualChunks: {
38
+
'vendor-react': ['react', 'react-dom', 'react-router-dom', 'scheduler', 'react-dom/client'],
39
+
'vendor-atproto': ['@atproto/api', '@atproto/crypto']
40
+
}
41
+
}
42
+
}
43
},
44
server: {
45
port: 3000,
46
},
47
+
publicDir: 'public'
48
})