Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations.

Looking solid. New flags logic

Changed files
+105 -88
src
+12 -2
index.html
··· 71 71 if (!this.confirmation) { 72 72 this.error = 'Please confirm that you understand the risks.' 73 73 } 74 - // Pass the form data to migrate function for future implementation 74 + 75 + window.Migrator.createNewAccount = this.createNewAccount; 76 + window.Migrator.migrateRepo = this.migrateRepo; 77 + window.Migrator.migrateBlobs = this.migrateBlobs; 78 + window.Migrator.migrateMissingBlobs = this.migrateMissingBlobs; 79 + window.Migrator.migratePrefs = this.migratePrefs; 80 + window.Migrator.migratePlcRecord = this.migratePlcRecord; 75 81 this.updateStatusHandler('Starting migration...'); 76 82 this.showStatusMessage = true; 77 83 await window.Migrator.migrate( ··· 84 90 this.updateStatusHandler, 85 91 this.twoFactorCode 86 92 ); 87 - this.askForPlcToken = true; 93 + if (this.migratePlcRecord) { 94 + this.askForPlcToken = true; 95 + } else { 96 + this.updateStatusHandler('Migration of your repo is complete! But the PLC operation was not done so your old account is still the valid one.'); 97 + } 88 98 } catch (error) { 89 99 console.error(error.error, error.message); 90 100 if (error.error === 'AuthFactorTokenRequired') {
+93 -86
src/pdsmoover.js
··· 37 37 this.newAgent = null; 38 38 this.missingBlobs = []; 39 39 //State for reruns 40 - this.oldAccountStatus = null; 41 - this.newAccountStatus = null; 42 40 this.createNewAccount = true; 43 41 this.migrateRepo = true; 44 42 this.migrateBlobs = true; ··· 90 88 safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); 91 89 const newAgent = new AtpAgent({service: newPdsUrl}); 92 90 const newHostDesc = await newAgent.com.atproto.server.describeServer(); 93 - const newHostWebDid = newHostDesc.data.did; 91 + if (this.createNewAccount) { 92 + const newHostWebDid = newHostDesc.data.did; 94 93 95 - safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); 94 + safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); 96 95 97 - const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 98 - aud: newHostWebDid, 99 - lxm: 'com.atproto.server.createAccount', 100 - }); 101 - const serviceJwt = createAuthResp.data.token; 96 + const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 97 + aud: newHostWebDid, 98 + lxm: 'com.atproto.server.createAccount', 99 + }); 100 + const serviceJwt = createAuthResp.data.token; 102 101 103 - const createNewAccount = await newAgent.com.atproto.server.createAccount({ 104 - did: usersDid, 105 - handle: newHandle, 106 - email: newEmail, 107 - password: password, 108 - inviteCode: inviteCode, 109 - }, 110 - { 111 - headers: {authorization: `Bearer ${serviceJwt}`}, 112 - encoding: 'application/json', 113 - }); 102 + const createNewAccount = await newAgent.com.atproto.server.createAccount({ 103 + did: usersDid, 104 + handle: newHandle, 105 + email: newEmail, 106 + password: password, 107 + inviteCode: inviteCode, 108 + }, 109 + { 110 + headers: {authorization: `Bearer ${serviceJwt}`}, 111 + encoding: 'application/json', 112 + }); 114 113 115 - if (createNewAccount.data.did !== usersDid.toString()) { 116 - throw new Error('Did not create the new account with the same did as the old account'); 114 + if (createNewAccount.data.did !== usersDid.toString()) { 115 + throw new Error('Did not create the new account with the same did as the old account'); 116 + } 117 117 } 118 - 119 118 safeStatusUpdate(statusUpdateHandler, 'Logging in with the new account'); 120 119 121 120 await newAgent.login({ ··· 123 122 password: password, 124 123 }); 125 124 126 - safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); 127 - const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); 128 - await newAgent.com.atproto.repo.importRepo(repoRes.data, { 129 - encoding: 'application/vnd.ipld.car', 130 - }); 125 + if (this.migrateRepo) { 126 + safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); 127 + const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); 128 + await newAgent.com.atproto.repo.importRepo(repoRes.data, { 129 + encoding: 'application/vnd.ipld.car', 130 + }); 131 + } 131 132 132 133 let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 133 - safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 134 134 135 - let blobCursor = undefined; 136 - let uploadedBlobs = 0; 137 - do { 138 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 135 + if (this.migrateBlobs) { 136 + safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 139 137 140 - const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 141 - did: usersDid, 142 - cursor: blobCursor, 143 - limit: 100, 144 - }); 145 - 146 - for (const cid of listedBlobs.data.cids) { 147 - try { 148 - //TODO may move the status update here but would have it only update like every 10 149 - const blobRes = await oldAgent.com.atproto.sync.getBlob({ 150 - did: usersDid, 151 - cid, 152 - }); 153 - await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 154 - encoding: blobRes.headers['content-type'], 155 - }); 156 - uploadedBlobs++; 157 - if (uploadedBlobs % 10 === 0) { 158 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 159 - } 160 - } catch (error) { 161 - //TODO silently logging for now will do a missing blobs later 162 - console.error(error); 163 - } 164 - } 165 - blobCursor = listedBlobs.data.cursor; 166 - } while (blobCursor); 167 - 168 - newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 169 - if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { 170 - let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; 171 - safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 172 - //Probably should be shared between main blob uploader, but eh 173 - let missingBlobCursor = undefined; 174 - let missingUploadedBlobs = 0; 138 + let blobCursor = undefined; 139 + let uploadedBlobs = 0; 175 140 do { 176 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 141 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 177 142 178 - const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ 143 + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 179 144 did: usersDid, 180 - cursor: missingBlobCursor, 145 + cursor: blobCursor, 181 146 limit: 100, 182 147 }); 183 148 184 - for (const cid of missingBlobs.data.cids) { 149 + for (const cid of listedBlobs.data.cids) { 185 150 try { 186 - //TODO may move the status update here but would have it only update like every 10 187 151 const blobRes = await oldAgent.com.atproto.sync.getBlob({ 152 + did: usersDid, 188 153 cid, 189 154 }); 190 155 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 191 156 encoding: blobRes.headers['content-type'], 192 157 }); 158 + uploadedBlobs++; 193 159 if (uploadedBlobs % 10 === 0) { 194 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); 160 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 195 161 } 196 - uploadedBlobs++; 197 162 } catch (error) { 198 - //TODO silently logging for now will do a missing blobs later 199 163 console.error(error); 200 - this.missingBlobs.push(cid); 201 164 } 202 165 } 203 - missingBlobCursor = missingBlobs.data.cursor; 204 - } while (missingBlobCursor); 166 + blobCursor = listedBlobs.data.cursor; 167 + } while (blobCursor); 168 + } 169 + 170 + if (this.migrateMissingBlobs) { 171 + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 172 + if (newAccountStatus.data.expectedBlobs !== newAccountStatus.data.importedBlobs) { 173 + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - newAccountStatus.data.importedBlobs; 174 + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 175 + //Probably should be shared between main blob uploader, but eh 176 + let missingBlobCursor = undefined; 177 + let missingUploadedBlobs = 0; 178 + do { 179 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 180 + 181 + const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs({ 182 + cursor: missingBlobCursor, 183 + limit: 100, 184 + }); 205 185 206 - } 186 + for (const recordBlob of missingBlobs.data.blobs) { 187 + try { 188 + //TODO may move the status update here but would have it only update like every 10 189 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 190 + did: usersDid, 191 + cid: recordBlob.cid, 192 + }); 193 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 194 + encoding: blobRes.headers['content-type'], 195 + }); 196 + if (missingUploadedBlobs % 10 === 0) { 197 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 198 + } 199 + missingUploadedBlobs++; 200 + } catch (error) { 201 + //TODO silently logging for now will do a missing blobs later 202 + console.error(error); 203 + this.missingBlobs.push(cid); 204 + } 205 + } 206 + missingBlobCursor = missingBlobs.data.cursor; 207 + } while (missingBlobCursor); 207 208 208 - const prefs = await oldAgent.app.bsky.actor.getPreferences(); 209 - await newAgent.app.bsky.actor.putPreferences(prefs.data); 210 - this.oldAgent = oldAgent; 211 - this.newAgent = newAgent; 212 - await oldAgent.com.atproto.identity.requestPlcOperationSignature(); 213 - safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); 209 + } 210 + } 211 + if (this.migratePrefs) { 212 + const prefs = await oldAgent.app.bsky.actor.getPreferences(); 213 + await newAgent.app.bsky.actor.putPreferences(prefs.data); 214 + this.oldAgent = oldAgent; 215 + this.newAgent = newAgent; 216 + } 214 217 218 + if (this.migratePlcRecord) { 219 + await oldAgent.com.atproto.identity.requestPlcOperationSignature(); 220 + safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); 221 + } 215 222 } 216 223 217 224 async signPlcOperation(token) {