+1
-1
justfile
+1
-1
justfile
+5
-1
packages/moover/lib/pdsmoover.js
+5
-1
packages/moover/lib/pdsmoover.js
···
49
* @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one
50
* @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status)
51
* @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required
52
*/
53
-
async migrate(oldHandle, password, newPdsUrl, newEmail, newHandle, inviteCode, statusUpdateHandler = null, twoFactorCode = null) {
54
//Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations.
55
// handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best
56
oldHandle = cleanHandle(oldHandle);
···
115
if (inviteCode) {
116
createAccountRequest.inviteCode = inviteCode;
117
}
118
const createNewAccount = await newAgent.com.atproto.server.createAccount(
119
createAccountRequest,
120
{
···
49
* @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one
50
* @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status)
51
* @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required
52
+
* @param verificationCode - Optional verification captcha code for account creation if the PDS requires it
53
*/
54
+
async migrate(oldHandle, password, newPdsUrl, newEmail, newHandle, inviteCode, statusUpdateHandler = null, twoFactorCode = null, verificationCode = null) {
55
//Leaving this logic that either sets the agent to bsky.social, or the PDS since it's what I found worked best for migrations.
56
// handleAndPDSResolver should be able to handle it, but there have been edge cases and this was what worked best
57
oldHandle = cleanHandle(oldHandle);
···
116
if (inviteCode) {
117
createAccountRequest.inviteCode = inviteCode;
118
}
119
+
if (verificationCode) {
120
+
createAccountRequest.verificationCode = verificationCode;
121
+
}
122
const createNewAccount = await newAgent.com.atproto.server.createAccount(
123
createAccountRequest,
124
{
+1
-1
packages/moover/package.json
+1
-1
packages/moover/package.json
+2
-1
packages/moover/types/pdsmoover.d.ts
+2
-1
packages/moover/types/pdsmoover.d.ts
···
35
* @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one
36
* @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status)
37
* @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required
38
*/
39
-
migrate(oldHandle: string, password: string, newPdsUrl: string, newEmail: string, newHandle: string, inviteCode: string | null, statusUpdateHandler?: Function | null, twoFactorCode?: string | null): Promise<void>;
40
/**
41
* Sign and submits the PLC operation to officially migrate the account
42
* @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord
···
35
* @param {string|null} inviteCode - The invite code you got from the PDS you are migrating to. If null does not include one
36
* @param {function|null} statusUpdateHandler - a function that takes a string used to update the UI. Like (status) => console.log(status)
37
* @param {string|null} twoFactorCode - Optional, but needed if it fails with 2fa required
38
+
* @param verificationCode - Optional verification captcha code for account creation if the PDS requires it
39
*/
40
+
migrate(oldHandle: string, password: string, newPdsUrl: string, newEmail: string, newHandle: string, inviteCode: string | null, statusUpdateHandler?: Function | null, twoFactorCode?: string | null, verificationCode?: any): Promise<void>;
41
/**
42
* Sign and submits the PLC operation to officially migrate the account
43
* @param {string} token - the PLC token sent in the email. If you're just wanting to run this rerun migrate with all the flags set as false except for migratePlcRecord
+1
-1
packages/moover/types/pdsmoover.d.ts.map
+1
-1
packages/moover/types/pdsmoover.d.ts.map
···
1
-
{"version":3,"file":"pdsmoover.d.ts","sourceRoot":"","sources":["../lib/pdsmoover.js"],"names":[],"mappings":"AAUA;;;GAGG;AACH;IAEQ,uBAAuB;IACvB,UADW,QAAQ,CACC;IACpB,uBAAuB;IACvB,UADW,QAAQ,CACC;IACpB,uBAAuB;IACvB,cADW,CAAC,MAAM,CAAC,CACG;IAEtB,sBAAsB;IACtB,kBADW,OAAO,CACU;IAC5B,sBAAsB;IACtB,aADW,OAAO,CACK;IACvB,sBAAsB;IACtB,cADW,OAAO,CACM;IACxB,sBAAsB;IACtB,qBADW,OAAO,CACa;IAC/B,sBAAsB;IACtB,cADW,OAAO,CACM;IACxB,sBAAsB;IACtB,kBADW,OAAO,CACU;IAGhC;;;;;;;;;;;;;;OAcG;IACH,mBATW,MAAM,YACN,MAAM,aACN,MAAM,YACN,MAAM,aACN,MAAM,cACN,MAAM,GAAC,IAAI,wBACX,WAAS,IAAI,kBACb,MAAM,GAAC,IAAI,iBAuLrB;IAED;;;;;OAKG;IACH,wBAJW,MAAM,gCACsB,MAAM,EAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CA4BzB;IAED;;;;;;;;;;;OAWG;IACH,gCAPqB,MAAM,eACJ,MAAM,wBAClB,WAAS,IAAI,kBAEb,MAAM,GAAC,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAiEzB;IAED;;;;;OAKG;IACH,uCAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAkCzB;CACJ;yBAhYsB,cAAc"}
···
1
+
{"version":3,"file":"pdsmoover.d.ts","sourceRoot":"","sources":["../lib/pdsmoover.js"],"names":[],"mappings":"AAUA;;;GAGG;AACH;IAEQ,uBAAuB;IACvB,UADW,QAAQ,CACC;IACpB,uBAAuB;IACvB,UADW,QAAQ,CACC;IACpB,uBAAuB;IACvB,cADW,CAAC,MAAM,CAAC,CACG;IAEtB,sBAAsB;IACtB,kBADW,OAAO,CACU;IAC5B,sBAAsB;IACtB,aADW,OAAO,CACK;IACvB,sBAAsB;IACtB,cADW,OAAO,CACM;IACxB,sBAAsB;IACtB,qBADW,OAAO,CACa;IAC/B,sBAAsB;IACtB,cADW,OAAO,CACM;IACxB,sBAAsB;IACtB,kBADW,OAAO,CACU;IAGhC;;;;;;;;;;;;;;;OAeG;IACH,mBAVW,MAAM,YACN,MAAM,aACN,MAAM,YACN,MAAM,aACN,MAAM,cACN,MAAM,GAAC,IAAI,wBACX,WAAS,IAAI,kBACb,MAAM,GAAC,IAAI,yCA2LrB;IAED;;;;;OAKG;IACH,wBAJW,MAAM,gCACsB,MAAM,EAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CA4BzB;IAED;;;;;;;;;;;OAWG;IACH,gCAPqB,MAAM,eACJ,MAAM,wBAClB,WAAS,IAAI,kBAEb,MAAM,GAAC,IAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAiEzB;IAED;;;;;OAKG;IACH,uCAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAkCzB;CACJ;yBApYsB,cAAc"}
+1
-1
web-ui/package.json
+1
-1
web-ui/package.json
+18
-18
web-ui/pnpm-lock.yaml
+18
-18
web-ui/pnpm-lock.yaml
···
21
specifier: ^1.0.1
22
version: 1.0.1
23
'@pds-moover/moover':
24
-
specifier: ^1.0.4
25
-
version: 1.0.4(@atcute/identity@1.1.1)(vite@7.1.12(@types/node@22.19.0))
26
devDependencies:
27
'@eslint/compat':
28
specifier: ^1.4.0
···
79
'@atcute/atproto@3.1.9':
80
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
81
82
-
'@atcute/cbor@2.2.7':
83
-
resolution: {integrity: sha512-/mwAF0gnokOphceZqFq3uzMGdd8sbw5y6bxF8CRutRkCCUcpjjpJc5fkLwhxyGgOveF3mZuHE6p7t/+IAqb7Aw==}
84
85
'@atcute/cid@2.2.6':
86
resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==}
···
111
'@atcute/uint8array@1.0.5':
112
resolution: {integrity: sha512-XLWWxoR2HNl2qU+FCr0rp1APwJXci7HnzbOQLxK55OaMNBXZ19+xNC5ii4QCsThsDxa4JS/JTzuiQLziITWf2Q==}
113
114
-
'@atcute/util-fetch@1.0.3':
115
-
resolution: {integrity: sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==}
116
117
'@atproto/api@0.16.11':
118
resolution: {integrity: sha512-1dhfQNHiclb102RW+Ea8Nft5olfqU0Ev/vlQaSX6mWNo1aP5zT+sPODJ8+BTUOYk3vcuvL7QMkqA/rLYy2PMyw==}
···
386
'@pds-moover/lexicons@1.0.1':
387
resolution: {integrity: sha512-fv5b/DtHM7FEo/JklyF9gdK0ainlb6mWjWrBe6cmSAeg9G/4O2jBlQUOqfOAICY9gOcrCpkOrk9PHgGw//JQ2A==}
388
389
-
'@pds-moover/moover@1.0.4':
390
-
resolution: {integrity: sha512-VR4pMB9fMUEV0QCxLDaxfreaRGyxZR54UfOk5TLRYEcp2zmaLKFTI3NTcpvIFiVREpy6EzK+RNkwhxUD16OaVA==}
391
392
'@polka/url@1.0.0-next.29':
393
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
···
677
ajv@6.12.6:
678
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
679
680
-
alpinejs@3.15.1:
681
-
resolution: {integrity: sha512-HLO1TtiE92VajFHtLLPK8BWaK1YepV/uj31UrfoGnQ00lyFOJZ+oVY3F0DghPAwvg8sLU79pmjGQSytERa2gEg==}
682
683
ansi-styles@4.3.0:
684
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
···
1352
dependencies:
1353
'@atcute/lexicons': 1.2.2
1354
1355
-
'@atcute/cbor@2.2.7':
1356
dependencies:
1357
'@atcute/cid': 2.2.6
1358
'@atcute/multibase': 1.1.6
···
1376
1377
'@atcute/did-plc@0.1.7':
1378
dependencies:
1379
-
'@atcute/cbor': 2.2.7
1380
'@atcute/cid': 2.2.6
1381
'@atcute/crypto': 2.2.6
1382
'@atcute/identity': 1.1.1
···
1389
dependencies:
1390
'@atcute/identity': 1.1.1
1391
'@atcute/lexicons': 1.2.2
1392
-
'@atcute/util-fetch': 1.0.3
1393
'@badrap/valita': 0.4.6
1394
1395
'@atcute/identity@1.1.1':
···
1408
1409
'@atcute/uint8array@1.0.5': {}
1410
1411
-
'@atcute/util-fetch@1.0.3':
1412
dependencies:
1413
'@badrap/valita': 0.4.6
1414
···
1626
'@atproto/lexicon': 0.5.1
1627
'@atproto/xrpc': 0.7.5
1628
1629
-
'@pds-moover/moover@1.0.4(@atcute/identity@1.1.1)(vite@7.1.12(@types/node@22.19.0))':
1630
dependencies:
1631
-
'@atcute/cbor': 2.2.7
1632
'@atcute/client': 4.0.5
1633
'@atcute/crypto': 2.2.6
1634
'@atcute/did-plc': 0.1.7
···
1637
'@atcute/multibase': 1.1.6
1638
'@atproto/api': 0.16.11
1639
'@pds-moover/lexicons': 1.0.1
1640
-
alpinejs: 3.15.1
1641
vite-plugin-full-reload: 1.2.0
1642
vite-rs-plugin: 1.0.1(vite@7.1.12(@types/node@22.19.0))
1643
transitivePeerDependencies:
···
1930
json-schema-traverse: 0.4.1
1931
uri-js: 4.4.1
1932
1933
-
alpinejs@3.15.1:
1934
dependencies:
1935
'@vue/reactivity': 3.1.5
1936
···
21
specifier: ^1.0.1
22
version: 1.0.1
23
'@pds-moover/moover':
24
+
specifier: ^1.0.5
25
+
version: 1.0.5(@atcute/identity@1.1.1)(vite@7.1.12(@types/node@22.19.0))
26
devDependencies:
27
'@eslint/compat':
28
specifier: ^1.4.0
···
79
'@atcute/atproto@3.1.9':
80
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
81
82
+
'@atcute/cbor@2.2.8':
83
+
resolution: {integrity: sha512-UzOAN9BuN6JCXgn0ryV8qZuRJUDrNqrbLd6EFM8jc6RYssjRyGRxNy6RZ1NU/07Hd8Tq/0pz8+nQiMu5Zai5uw==}
84
85
'@atcute/cid@2.2.6':
86
resolution: {integrity: sha512-bTAHHbJ24p+E//V4KCS4xdmd39o211jJswvqQOevj7vk+5IYcgDLx1ryZWZ1sEPOo9x875li/kj5gpKL14RDwQ==}
···
111
'@atcute/uint8array@1.0.5':
112
resolution: {integrity: sha512-XLWWxoR2HNl2qU+FCr0rp1APwJXci7HnzbOQLxK55OaMNBXZ19+xNC5ii4QCsThsDxa4JS/JTzuiQLziITWf2Q==}
113
114
+
'@atcute/util-fetch@1.0.4':
115
+
resolution: {integrity: sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg==}
116
117
'@atproto/api@0.16.11':
118
resolution: {integrity: sha512-1dhfQNHiclb102RW+Ea8Nft5olfqU0Ev/vlQaSX6mWNo1aP5zT+sPODJ8+BTUOYk3vcuvL7QMkqA/rLYy2PMyw==}
···
386
'@pds-moover/lexicons@1.0.1':
387
resolution: {integrity: sha512-fv5b/DtHM7FEo/JklyF9gdK0ainlb6mWjWrBe6cmSAeg9G/4O2jBlQUOqfOAICY9gOcrCpkOrk9PHgGw//JQ2A==}
388
389
+
'@pds-moover/moover@1.0.5':
390
+
resolution: {integrity: sha512-do8Itd1mrH/446KYJf+velZqsA45ldJCPrEV10eD3nhFJyhf4KBEuseSHIhV0+KIZGU06HvmofBO+v8EZx9ToA==}
391
392
'@polka/url@1.0.0-next.29':
393
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
···
677
ajv@6.12.6:
678
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
679
680
+
alpinejs@3.15.2:
681
+
resolution: {integrity: sha512-2kYF2aG+DTFkE6p0rHG5XmN4VEb6sO9b02aOdU4+i8QN6rL0DbRZQiypDE1gBcGO65yDcqMz5KKYUYgMUxgNkw==}
682
683
ansi-styles@4.3.0:
684
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
···
1352
dependencies:
1353
'@atcute/lexicons': 1.2.2
1354
1355
+
'@atcute/cbor@2.2.8':
1356
dependencies:
1357
'@atcute/cid': 2.2.6
1358
'@atcute/multibase': 1.1.6
···
1376
1377
'@atcute/did-plc@0.1.7':
1378
dependencies:
1379
+
'@atcute/cbor': 2.2.8
1380
'@atcute/cid': 2.2.6
1381
'@atcute/crypto': 2.2.6
1382
'@atcute/identity': 1.1.1
···
1389
dependencies:
1390
'@atcute/identity': 1.1.1
1391
'@atcute/lexicons': 1.2.2
1392
+
'@atcute/util-fetch': 1.0.4
1393
'@badrap/valita': 0.4.6
1394
1395
'@atcute/identity@1.1.1':
···
1408
1409
'@atcute/uint8array@1.0.5': {}
1410
1411
+
'@atcute/util-fetch@1.0.4':
1412
dependencies:
1413
'@badrap/valita': 0.4.6
1414
···
1626
'@atproto/lexicon': 0.5.1
1627
'@atproto/xrpc': 0.7.5
1628
1629
+
'@pds-moover/moover@1.0.5(@atcute/identity@1.1.1)(vite@7.1.12(@types/node@22.19.0))':
1630
dependencies:
1631
+
'@atcute/cbor': 2.2.8
1632
'@atcute/client': 4.0.5
1633
'@atcute/crypto': 2.2.6
1634
'@atcute/did-plc': 0.1.7
···
1637
'@atcute/multibase': 1.1.6
1638
'@atproto/api': 0.16.11
1639
'@pds-moover/lexicons': 1.0.1
1640
+
alpinejs: 3.15.2
1641
vite-plugin-full-reload: 1.2.0
1642
vite-rs-plugin: 1.0.1(vite@7.1.12(@types/node@22.19.0))
1643
transitivePeerDependencies:
···
1930
json-schema-traverse: 0.4.1
1931
uri-js: 4.4.1
1932
1933
+
alpinejs@3.15.2:
1934
dependencies:
1935
'@vue/reactivity': 3.1.5
1936
+212
-168
web-ui/src/routes/moover/[[pds]]/+page.svelte
+212
-168
web-ui/src/routes/moover/[[pds]]/+page.svelte
···
4
import {resolve} from '$app/paths';
5
import {Migrator} from '@pds-moover/moover';
6
import SignThePapers from './SignThePapers.svelte';
7
8
let {data} = $props();
9
···
43
newHandle: '',
44
inviteCode: null,
45
twoFactorCode: null,
46
confirmation: false,
47
// Acceptance of provider policies (when required by selected PDS)
48
acceptPolicies: false,
···
63
let showStatusMessage = $state(false);
64
let askForPlcToken = $state(false);
65
let disableSubmit = $state(false);
66
67
let errorMessage: null | string = $state(null);
68
let statusMessage: null | string = $state(null);
···
72
const tosUrl = $derived(selectedPds?.links?.termsOfService);
73
const requiresAccept = $derived(!!(privacyUrl || tosUrl));
74
75
const updateStatusHandler = (status: string) => {
76
statusMessage = status;
77
}
78
79
async function submitMoove(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }) {
80
event.preventDefault();
81
disableSubmit = true;
···
105
}
106
}
107
108
try {
109
110
if (showTwoFactorCodeInput) {
···
127
128
updateStatusHandler('Starting migration...');
129
showStatusMessage = true;
130
await migrator.migrate(
131
formData.handle,
132
formData.password,
···
136
formData.inviteCode,
137
updateStatusHandler,
138
formData.twoFactorCode,
139
);
140
if (migrator.migratePlcRecord) {
141
//I don't think disable submit is needed, but you never know.
···
170
<br/>
171
<a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lw3hcuojck2u">Video guide for
172
joining blacksky.app</a>
173
174
-
<form id="moover-form" onsubmit={submitMoove}>
175
-
<!-- First section: Login credentials -->
176
-
<div class="section">
177
-
<h2>Login for your current PDS</h2>
178
-
<div class="form-group">
179
-
<label for="handle">Old Handle:</label>
180
-
<input type="text" id="handle" name="handle" placeholder="alice.bsky.social" required
181
-
bind:value={formData.handle}>
182
</div>
183
184
-
<div class="form-group">
185
-
<label for="password">Old Password (Will also be your new password):</label>
186
-
<input type="password" id="password" name="password" required bind:value={formData.password}>
187
-
</div>
188
-
{#if showTwoFactorCodeInput}
189
-
<div class="form-group">
190
-
<label for="two-factor-code">2FA from the email sent</label>
191
-
<input type="text" id="two-factor-code" name="twoFactorCode"
192
-
bind:value={formData.twoFactorCode}>
193
-
<div class="error-message">Enter your 2fa code here</div>
194
195
</div>
196
-
{/if}
197
-
</div>
198
199
-
<!-- Second section: New account details -->
200
-
<div class="section">
201
-
<h2>{selectedPds ? `Setup for ${cleanSelectedPds}` : 'Setup for the new PDS'}</h2>
202
-
{#if !selectedPds}
203
<div class="form-group">
204
-
<label for="new-pds">New PDS (URL):</label>
205
-
<input type="url" id="new-pds" name="newPds" placeholder="https://coolnewpds.com"
206
-
required bind:value={formData.newPds}>
207
</div>
208
-
{/if}
209
210
-
<div class="form-group">
211
-
<label for="new-email">New Email:</label>
212
-
<input type="email" id="new-email" name="newEmail"
213
-
placeholder="CanBeSameEmailAsTheOldPds@email.com"
214
-
required bind:value={formData.newEmail}>
215
</div>
216
217
218
<div class="form-group">
219
-
<label for="new-handle">New Handle:</label>
220
-
<div class={selectedPds ? 'input-group' : ''}>
221
-
<input type="text" id="new-handle" name="newHandle"
222
-
placeholder="{handlePlaceHolder}"
223
-
required
224
-
bind:value={formData.newHandle}>
225
-
{#if selectedPds}
226
-
227
-
<select bind:value={selectedDomain} class="domain-select">
228
-
{#each selectedPds?.availableUserDomains as domain (domain)}
229
-
<option value={domain}>{domain}</option>
230
-
{/each}
231
-
<option value="custom">I have my own domain setup</option>
232
-
233
-
</select>
234
-
{/if}
235
-
</div>
236
</div>
237
238
-
{#if !selectedPds || selectedPds.inviteCodeRequired !== false}
239
-
<div class="form-group">
240
-
<label for="invite-code">Invite Code:</label>
241
-
<input type="text" id="invite-code" name="inviteCode"
242
-
placeholder="Invite code from your new PDS (Leave blank if you don't have one)"
243
-
bind:value={formData.inviteCode}>
244
</div>
245
{/if}
246
-
</div>
247
-
248
-
249
-
<div class="form-group">
250
-
<button type="button" onclick={() => showAdvance = !showAdvance} id="advance" name="advance">Advance
251
-
Options
252
-
</button>
253
-
</div>
254
-
{#if showAdvance}
255
-
<div class="section" style="padding-bottom: 10px; text-align: left">
256
-
<h3>Pick and choose which actions to run</h3>
257
-
<p>Useful if a migration failed and you want to have a bit more manual control</p>
258
-
<div class="form-control">
259
-
<label class="moove-checkbox-label">
260
-
<input type="checkbox" id="createNewAccount" name="createNewAccount"
261
-
bind:checked={formData.createNewAccount}>
262
-
Create a New Account on the New PDS
263
-
</label>
264
-
</div>
265
-
<div class="form-control">
266
-
<label class="moove-checkbox-label">
267
-
<input bind:checked={formData.migrateRepo} type="checkbox" id="migrateRepo"
268
-
name="migrateRepo">
269
-
Migrate Repo
270
-
</label>
271
-
</div>
272
-
<div class="form-control">
273
-
<label class="moove-checkbox-label">
274
-
<input bind:checked={formData.migrateBlobs} type="checkbox" id="migrateBlobs"
275
-
name="migrateBlobs">
276
-
Migrate Blobs
277
-
</label>
278
-
</div>
279
-
<div class="form-control">
280
-
<label class="moove-checkbox-label">
281
-
<input bind:checked={formData.migrateMissingBlobs} type="checkbox" id="migrateMissingBlobs"
282
-
name="migrateMissingBlobs">
283
-
Migrate Missing Blobs
284
-
</label>
285
-
</div>
286
-
<div class="form-control">
287
-
<label class="moove-checkbox-label">
288
-
<input bind:checked={formData.migratePrefs} type="checkbox" id="migratePrefs"
289
-
name="migratePrefs">
290
-
Migrate Prefs
291
-
</label>
292
-
</div>
293
-
<div class="form-control">
294
-
<label class="moove-checkbox-label">
295
-
<input bind:checked={formData.migratePlcRecord} type="checkbox" id="migratePlcRecord"
296
-
name="migratePlcRecord">
297
-
Migrate PLC Record
298
-
</label>
299
-
</div>
300
301
-
</div>
302
-
{/if}
303
-
304
-
{#if requiresAccept}
305
-
<div class="section" style="text-align: left">
306
-
<h3>Provider policies</h3>
307
-
<p>
308
-
To migrate to {cleanSelectedPds}, you must review and accept:
309
-
</p>
310
-
<ul>
311
-
{#if privacyUrl}
312
-
<li><a href={privacyUrl} target="_blank" rel="noopener noreferrer">Privacy
313
-
Policy</a></li>
314
-
{/if}
315
-
{#if tosUrl}
316
-
<li><a href={tosUrl} target="_blank" rel="noopener noreferrer">Terms of Service</a></li>
317
-
{/if}
318
-
</ul>
319
-
<div class="form-group">
320
-
<label for="accept-policies" class="moove-checkbox-label">
321
-
<input bind:checked={formData.acceptPolicies} type="checkbox" id="accept-policies"
322
-
name="acceptPolicies" required>
323
-
<span>
324
I have read and accept
325
326
</span>
327
-
</label>
328
</div>
329
</div>
330
-
{/if}
331
-
<p style="text-align: left">There are some risks that come with doing an account migration.
332
-
(Can view them
333
-
<a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md#%EF%B8%8F-warning-%EF%B8%8F-%EF%B8%8F">here</a>)
334
-
and that the creator or host of this migration tool is not liable and will not be able to help you in
335
-
the
336
-
event something goes wrong. I also have read over the <a href={resolve('/info')}>extended information
337
-
from
338
-
PDS MOOver
339
-
about account
340
-
migrations.</a>
341
-
</p>
342
-
<div class="form-group">
343
-
<label for="confirmation" class="moove-checkbox-label">
344
-
<input bind:checked={formData.confirmation} type="checkbox" id="confirmation" name="confirmation"
345
-
required>
346
-
<span>I understand</span>
347
-
</label>
348
-
</div>
349
-
{#if errorMessage !== null}
350
-
<div class="error-message">{errorMessage}</div>
351
-
{/if}
352
-
353
-
{#if showStatusMessage}
354
-
<div id="warning">*Please make sure to stay on this page during the MOOve for the
355
-
best result
356
-
</div>
357
-
<div id="status-message" class="status-message">{statusMessage}</div>
358
-
{/if}
359
360
-
<div>
361
-
<button disabled={disableSubmit}
362
-
type="submit">{selectedPds ? `MOOve to ${cleanSelectedPds}` : 'MOOve'}</button>
363
-
</div>
364
-
</form>
365
366
{:else}
367
<SignThePapers migrator={migrator} newHandle={newHandle}/>
368
{/if}
···
4
import {resolve} from '$app/paths';
5
import {Migrator} from '@pds-moover/moover';
6
import SignThePapers from './SignThePapers.svelte';
7
+
import Captcha from './Captcha.svelte';
8
9
let {data} = $props();
10
···
44
newHandle: '',
45
inviteCode: null,
46
twoFactorCode: null,
47
+
verificationCode: null,
48
confirmation: false,
49
// Acceptance of provider policies (when required by selected PDS)
50
acceptPolicies: false,
···
65
let showStatusMessage = $state(false);
66
let askForPlcToken = $state(false);
67
let disableSubmit = $state(false);
68
+
let showCaptcha = $state(false);
69
70
let errorMessage: null | string = $state(null);
71
let statusMessage: null | string = $state(null);
···
75
const tosUrl = $derived(selectedPds?.links?.termsOfService);
76
const requiresAccept = $derived(!!(privacyUrl || tosUrl));
77
78
+
// Check if phone verification is required
79
+
const captchaVerificationRequired = $derived(selectedPds?.phoneVerificationRequired === true);
80
+
81
const updateStatusHandler = (status: string) => {
82
statusMessage = status;
83
}
84
85
+
function handleCaptchaSuccess(code: string) {
86
+
formData.verificationCode = code;
87
+
showCaptcha = false;
88
+
// Continue with the migration
89
+
performMigration();
90
+
}
91
+
92
+
function handleCaptchaError(error: string) {
93
+
errorMessage = `Verification failed: ${error}`;
94
+
disableSubmit = false;
95
+
}
96
+
97
async function submitMoove(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }) {
98
event.preventDefault();
99
disableSubmit = true;
···
123
}
124
}
125
126
+
if (captchaVerificationRequired && formData.createNewAccount && !formData.verificationCode) {
127
+
showCaptcha = true;
128
+
return;
129
+
}
130
+
131
+
// Continue with migration
132
+
await performMigration();
133
+
}
134
+
135
+
async function performMigration() {
136
try {
137
138
if (showTwoFactorCodeInput) {
···
155
156
updateStatusHandler('Starting migration...');
157
showStatusMessage = true;
158
+
159
await migrator.migrate(
160
formData.handle,
161
formData.password,
···
165
formData.inviteCode,
166
updateStatusHandler,
167
formData.twoFactorCode,
168
+
formData.verificationCode,
169
);
170
if (migrator.migratePlcRecord) {
171
//I don't think disable submit is needed, but you never know.
···
200
<br/>
201
<a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lw3hcuojck2u">Video guide for
202
joining blacksky.app</a>
203
+
{#if showCaptcha}
204
+
<Captcha
205
+
pdsUrl={formData.newPds}
206
+
handle={newHandle}
207
+
onSuccess={handleCaptchaSuccess}
208
+
onError={handleCaptchaError}
209
+
/>
210
+
{:else}
211
+
212
+
<form id="moover-form" onsubmit={submitMoove}>
213
+
<!-- First section: Login credentials -->
214
+
<div class="section">
215
+
<h2>Login for your current PDS</h2>
216
+
<div class="form-group">
217
+
<label for="handle">Old Handle:</label>
218
+
<input type="text" id="handle" name="handle" placeholder="alice.bsky.social" required
219
+
bind:value={formData.handle}>
220
+
</div>
221
222
+
<div class="form-group">
223
+
<label for="password">Old Password (Will also be your new password):</label>
224
+
<input type="password" id="password" name="password" required bind:value={formData.password}>
225
+
</div>
226
+
{#if showTwoFactorCodeInput}
227
+
<div class="form-group">
228
+
<label for="two-factor-code">2FA from the email sent</label>
229
+
<input type="text" id="two-factor-code" name="twoFactorCode"
230
+
bind:value={formData.twoFactorCode}>
231
+
<div class="error-message">Enter your 2fa code here</div>
232
+
233
+
</div>
234
+
{/if}
235
</div>
236
237
+
<!-- Second section: New account details -->
238
+
<div class="section">
239
+
<h2>{selectedPds ? `Setup for ${cleanSelectedPds}` : 'Setup for the new PDS'}</h2>
240
+
{#if !selectedPds}
241
+
<div class="form-group">
242
+
<label for="new-pds">New PDS (URL):</label>
243
+
<input type="url" id="new-pds" name="newPds" placeholder="https://coolnewpds.com"
244
+
required bind:value={formData.newPds}>
245
+
</div>
246
+
{/if}
247
248
+
<div class="form-group">
249
+
<label for="new-email">New Email:</label>
250
+
<input type="email" id="new-email" name="newEmail"
251
+
placeholder="CanBeSameEmailAsTheOldPds@email.com"
252
+
required bind:value={formData.newEmail}>
253
</div>
254
255
+
256
<div class="form-group">
257
+
<label for="new-handle">New Handle:</label>
258
+
<div class={selectedPds ? 'input-group' : ''}>
259
+
<input type="text" id="new-handle" name="newHandle"
260
+
placeholder="{handlePlaceHolder}"
261
+
required
262
+
bind:value={formData.newHandle}>
263
+
{#if selectedPds}
264
+
265
+
<select bind:value={selectedDomain} class="domain-select">
266
+
{#each selectedPds?.availableUserDomains as domain (domain)}
267
+
<option value={domain}>{domain}</option>
268
+
{/each}
269
+
<option value="custom">I have my own domain setup</option>
270
+
271
+
</select>
272
+
{/if}
273
+
</div>
274
</div>
275
276
+
{#if !selectedPds || selectedPds.inviteCodeRequired !== false}
277
+
<div class="form-group">
278
+
<label for="invite-code">Invite Code:</label>
279
+
<input type="text" id="invite-code" name="inviteCode"
280
+
placeholder="Invite code from your new PDS (Leave blank if you don't have one)"
281
+
bind:value={formData.inviteCode}>
282
+
</div>
283
+
{/if}
284
</div>
285
286
287
<div class="form-group">
288
+
<button type="button" onclick={() => showAdvance = !showAdvance} id="advance" name="advance">Advance
289
+
Options
290
+
</button>
291
</div>
292
+
{#if showAdvance}
293
+
<div class="section" style="padding-bottom: 10px; text-align: left">
294
+
<h3>Pick and choose which actions to run</h3>
295
+
<p>Useful if a migration failed and you want to have a bit more manual control</p>
296
+
<div class="form-control">
297
+
<label class="moove-checkbox-label">
298
+
<input type="checkbox" id="createNewAccount" name="createNewAccount"
299
+
bind:checked={formData.createNewAccount}>
300
+
Create a New Account on the New PDS
301
+
</label>
302
+
</div>
303
+
<div class="form-control">
304
+
<label class="moove-checkbox-label">
305
+
<input bind:checked={formData.migrateRepo} type="checkbox" id="migrateRepo"
306
+
name="migrateRepo">
307
+
Migrate Repo
308
+
</label>
309
+
</div>
310
+
<div class="form-control">
311
+
<label class="moove-checkbox-label">
312
+
<input bind:checked={formData.migrateBlobs} type="checkbox" id="migrateBlobs"
313
+
name="migrateBlobs">
314
+
Migrate Blobs
315
+
</label>
316
+
</div>
317
+
<div class="form-control">
318
+
<label class="moove-checkbox-label">
319
+
<input bind:checked={formData.migrateMissingBlobs} type="checkbox"
320
+
id="migrateMissingBlobs"
321
+
name="migrateMissingBlobs">
322
+
Migrate Missing Blobs
323
+
</label>
324
+
</div>
325
+
<div class="form-control">
326
+
<label class="moove-checkbox-label">
327
+
<input bind:checked={formData.migratePrefs} type="checkbox" id="migratePrefs"
328
+
name="migratePrefs">
329
+
Migrate Prefs
330
+
</label>
331
+
</div>
332
+
<div class="form-control">
333
+
<label class="moove-checkbox-label">
334
+
<input bind:checked={formData.migratePlcRecord} type="checkbox" id="migratePlcRecord"
335
+
name="migratePlcRecord">
336
+
Migrate PLC Record
337
+
</label>
338
+
</div>
339
340
</div>
341
{/if}
342
343
+
{#if requiresAccept}
344
+
<div class="section" style="text-align: left">
345
+
<h3>Provider policies</h3>
346
+
<p>
347
+
To migrate to {cleanSelectedPds}, you must review and accept:
348
+
</p>
349
+
<ul>
350
+
{#if privacyUrl}
351
+
<li><a href={privacyUrl} target="_blank" rel="noopener noreferrer">Privacy
352
+
Policy</a></li>
353
+
{/if}
354
+
{#if tosUrl}
355
+
<li><a href={tosUrl} target="_blank" rel="noopener noreferrer">Terms of Service</a></li>
356
+
{/if}
357
+
</ul>
358
+
<div class="form-group">
359
+
<label for="accept-policies" class="moove-checkbox-label">
360
+
<input bind:checked={formData.acceptPolicies} type="checkbox" id="accept-policies"
361
+
name="acceptPolicies" required>
362
+
<span>
363
I have read and accept
364
365
</span>
366
+
</label>
367
+
</div>
368
</div>
369
+
{/if}
370
+
<p style="text-align: left">There are some risks that come with doing an account migration.
371
+
(Can view them
372
+
<a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md#%EF%B8%8F-warning-%EF%B8%8F-%EF%B8%8F">here</a>)
373
+
and that the creator or host of this migration tool is not liable and will not be able to help you
374
+
in
375
+
the
376
+
event something goes wrong. I also have read over the <a href={resolve('/info')}>extended
377
+
information
378
+
from
379
+
PDS MOOver
380
+
about account
381
+
migrations.</a>
382
+
</p>
383
+
<div class="form-group">
384
+
<label for="confirmation" class="moove-checkbox-label">
385
+
<input bind:checked={formData.confirmation} type="checkbox" id="confirmation"
386
+
name="confirmation"
387
+
required>
388
+
<span>I understand</span>
389
+
</label>
390
</div>
391
+
{#if errorMessage !== null}
392
+
<div class="error-message">{errorMessage}</div>
393
+
{/if}
394
395
+
{#if showStatusMessage}
396
+
<div id="warning">*Please make sure to stay on this page during the MOOve for the
397
+
best result
398
+
</div>
399
+
<div id="status-message" class="status-message">{statusMessage}</div>
400
+
{/if}
401
+
402
+
403
+
<div>
404
+
<button disabled={disableSubmit}
405
+
type="submit">{selectedPds ? `MOOve to ${cleanSelectedPds}` : 'MOOve'}</button>
406
+
</div>
407
408
+
</form>
409
+
{/if}
410
{:else}
411
<SignThePapers migrator={migrator} newHandle={newHandle}/>
412
{/if}
+116
web-ui/src/routes/moover/[[pds]]/Captcha.svelte
+116
web-ui/src/routes/moover/[[pds]]/Captcha.svelte
···
···
1
+
<script lang="ts">
2
+
import {onMount} from 'svelte';
3
+
4
+
interface CaptchaProps {
5
+
pdsUrl: string;
6
+
handle: string;
7
+
onSuccess: (code: string) => void;
8
+
onError?: (error: string) => void;
9
+
}
10
+
11
+
let {pdsUrl, handle, onSuccess, onError}: CaptchaProps = $props();
12
+
13
+
function generateState(): string {
14
+
const array = new Uint8Array(32);
15
+
crypto.getRandomValues(array);
16
+
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
17
+
}
18
+
19
+
let captcha_state = $state(generateState());
20
+
let iframeRef: HTMLIFrameElement | null = $state(null);
21
+
let isLoading = $state(true);
22
+
23
+
24
+
const gateUrl = $derived(
25
+
`${pdsUrl}/gate/signup?state=${encodeURIComponent(captcha_state)}&handle=${encodeURIComponent(handle)}&redirect_url=${encodeURIComponent(window.location.origin)}`,
26
+
);
27
+
28
+
// Monitor iframe for URL changes
29
+
function checkIframeUrl() {
30
+
if (!iframeRef) return;
31
+
32
+
try {
33
+
const iframeUrl = new URL(iframeRef.contentWindow?.location.href ?? '');
34
+
35
+
// Check if the iframe has been redirected with code and state parameters
36
+
// This indicates the captcha was completed
37
+
const urlState = iframeUrl.searchParams.get('state');
38
+
const code = iframeUrl.searchParams.get('code');
39
+
40
+
// Only process if we have at least a state parameter (indicates redirect happened)
41
+
if (urlState) {
42
+
43
+
// Verify state matches
44
+
if (urlState !== captcha_state) {
45
+
const stateError = 'State mismatch - possible security issue';
46
+
onError?.(stateError);
47
+
return;
48
+
}
49
+
if (!code) {
50
+
const codeError = 'No code returned from captcha';
51
+
onError?.(codeError);
52
+
return;
53
+
54
+
}
55
+
56
+
onSuccess(code);
57
+
}
58
+
} catch {
59
+
/* empty */
60
+
}
61
+
}
62
+
63
+
onMount(() => {
64
+
// Poll for URL changes
65
+
const interval = setInterval(checkIframeUrl, 100);
66
+
67
+
return () => clearInterval(interval);
68
+
});
69
+
</script>
70
+
71
+
<div class="iframe-wrapper">
72
+
<iframe
73
+
bind:this={iframeRef}
74
+
src={gateUrl}
75
+
title="Captcha Verification"
76
+
onload={() => isLoading = false}
77
+
></iframe>
78
+
{#if isLoading}
79
+
<div class="loading-overlay">
80
+
<p>Loading verification...</p>
81
+
</div>
82
+
{/if}
83
+
</div>
84
+
85
+
<style>
86
+
87
+
.iframe-wrapper {
88
+
position: relative;
89
+
width: 100%;
90
+
height: 500px;
91
+
background: white;
92
+
border: 1px solid #ddd;
93
+
border-radius: 4px;
94
+
overflow: hidden;
95
+
}
96
+
97
+
iframe {
98
+
width: 100%;
99
+
height: 100%;
100
+
border: none;
101
+
}
102
+
103
+
.loading-overlay {
104
+
position: absolute;
105
+
top: 0;
106
+
left: 0;
107
+
right: 0;
108
+
bottom: 0;
109
+
background: rgba(255, 255, 255, 0.9);
110
+
display: flex;
111
+
align-items: center;
112
+
justify-content: center;
113
+
color: #666;
114
+
}
115
+
116
+
</style>