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

1:1

Changed files
+238 -4
web-ui
src
routes
missing-blobs
+238 -4
web-ui/src/routes/missing-blobs/+page.svelte
··· 3 3 import missingPicture from '$lib/assets/missing.webp' 4 4 import {MissingBlobs} from '@pds-moover/moover'; 5 5 6 - let {data} = $props(); 6 + let missingBlobs = $state(new MissingBlobs()); 7 + // Form state 8 + let showCurrentLogin = $state(true); 9 + let showOldLogin = $state(false); 10 + let showAdvance = $state(false); 11 + let disableLoginButton = $state(false); 12 + let showBlobMoveProgress = $state(false); 13 + let oldPdsUrl = $state<string | null>(null); 14 + let showTryAgain = $state(false); 15 + let errorMessage: string | null = $state(null); 16 + let showStatusMessage = $state(false); 17 + let statusMessage = $state(''); 18 + 19 + let currentLogin = $state({ 20 + handle: '', 21 + password: '', 22 + twoFactorCode: '', 23 + showTwoFactorCodeInput: false, 24 + }); 25 + 26 + let oldLogin = $state({ 27 + password: '', 28 + twoFactorCode: '', 29 + showTwoFactorCodeInput: false, 30 + }); 31 + 32 + function resetStatusAndErrors() { 33 + showStatusMessage = false; 34 + statusMessage = ''; 35 + errorMessage = ''; 36 + disableLoginButton = true; 37 + } 38 + 39 + async function handleCurrentLogin(event: SubmitEvent) { 40 + event.preventDefault(); 41 + resetStatusAndErrors(); 42 + try { 43 + const { 44 + accountStatus, 45 + missingBlobsCount 46 + } = await missingBlobs.currentAgentLogin(currentLogin.handle, currentLogin.password, currentLogin.twoFactorCode); 47 + console.log(missingBlobsCount); 48 + const noMissingBlobs = missingBlobsCount === 0; 49 + if (noMissingBlobs) { 50 + statusMessage = `You are good to go! You are not missing any blobs. Your account has ${accountStatus.importedBlobs} imported blobs and expects to have at least ${accountStatus.expectedBlobs} blobs. No action is required.` 51 + } else { 52 + showCurrentLogin = false; 53 + statusMessage = 'You are currently missing some blobs. Login with your old password to import the missing blobs. We will automatically find your old handle.'; 54 + showOldLogin = true; 55 + } 56 + showStatusMessage = true; 57 + 58 + } catch (err) { 59 + //@ts-expect-error: Should always have an error message 60 + if (err.error === 'AuthFactorTokenRequired') { 61 + currentLogin.showTwoFactorCodeInput = true; 62 + } 63 + //@ts-expect-error: Should always have an error message 64 + error = err.message; 65 + } 66 + disableLoginButton = false; 67 + } 68 + 69 + async function handleOldLogin(event: SubmitEvent) { 70 + event.preventDefault(); 71 + resetStatusAndErrors(); 72 + try { 73 + await missingBlobs.oldAgentLogin(oldLogin.password, oldLogin.twoFactorCode, oldPdsUrl); 74 + showOldLogin = false; 75 + showBlobMoveProgress = true; 76 + showStatusMessage = true; 77 + statusMessage = ''; 78 + await migrateMissingBlobs(); 79 + } catch (err) { 80 + //@ts-expect-error: Should always have an error message 81 + if (err.error === 'AuthFactorTokenRequired') { 82 + oldLogin.showTwoFactorCodeInput = true; 83 + } 84 + //@ts-expect-error: Should always have an error message 85 + error = err.message; 86 + } 87 + disableLoginButton = false; 88 + } 89 + 90 + function updateStatusHandler(status: string) { 91 + console.log('Status update:', status); 92 + const statusElement = document.getElementById('missing-status-message'); 93 + if (statusElement) { 94 + statusElement.innerText = status; 95 + } 96 + } 97 + 98 + async function migrateMissingBlobs() { 99 + try { 100 + resetStatusAndErrors(); 101 + showStatusMessage = true; 102 + showTryAgain = false; 103 + const { 104 + accountStatus, 105 + missingBlobsCount 106 + } = await missingBlobs.migrateMissingBlobs(updateStatusHandler); 107 + const noMissingBlobs = missingBlobsCount === 0; 108 + if (noMissingBlobs) { 109 + statusMessage = `You are good to go! You have all ${accountStatus.importedBlobs} of the expected ${accountStatus.expectedBlobs} blobs. You're done!!` 110 + } else { 111 + statusMessage = `Expected blobs: ${accountStatus.expectedBlobs} Imported blobs: ${accountStatus.importedBlobs}`; 112 + showTryAgain = true; 113 + } 114 + } catch (err) { 115 + //@ts-expect-error: Should always have an error message 116 + error = err.message; 117 + showTryAgain = true; 118 + } 119 + disableLoginButton = false; 120 + } 121 + 122 + function toggleAdvanceMenu() { 123 + showAdvance = !showAdvance; 124 + } 7 125 </script> 8 126 9 127 <svelte:head> 10 - <title>PDS MOOver - Missing</title> 128 + <title>PDS MOOver - Missing Blobs</title> 11 129 <meta property="og:description" content="Import missing blobs from your old PDS to your new PDS"/> 12 130 <meta property="og:image" content="{missingPicture}"> 13 131 </svelte:head> 14 - 15 132 16 133 {#snippet custom_img()} 17 134 <img src='{missingPicture}' alt='Cartoon milk cow on a missing poster' ··· 20 137 21 138 <div class="container"> 22 139 <MooHeader title="Missing Blobs Importer" customImg={custom_img}/> 23 - </div> 140 + 141 + <a href="https://blacksky.community/profile/did:plc:g7j6qok5us4hjqlwjxwrrkjm/post/3lyylumcpok2c">How to video 142 + guide</a> 143 + 144 + <!-- First section: Current Login credentials --> 145 + {#if showCurrentLogin} 146 + <form id="moover-form" onsubmit={handleCurrentLogin}> 147 + <div class="section"> 148 + <h2>Login for your current PDS</h2> 149 + <div class="form-group"> 150 + <label for="current_handle">Current Handle:</label> 151 + <input type="text" id="current_handle" name="handle" placeholder="alice.bsky.social" 152 + bind:value={currentLogin.handle} 153 + required> 154 + </div> 155 + 156 + <div class="form-group"> 157 + <label for="current_password">Current Password:</label> 158 + <input type="password" id="current_password" name="password" bind:value={currentLogin.password} 159 + required> 160 + </div> 161 + 162 + {#if currentLogin.showTwoFactorCodeInput} 163 + <div class="form-group"> 164 + <label for="current_two-factor-code">2FA from the email sent</label> 165 + <input type="text" id="current_two-factor-code" name="two-factor-code" 166 + bind:value={currentLogin.twoFactorCode}> 167 + <div class="error-message">Enter your 2fa code here</div> 168 + </div> 169 + {/if} 170 + {#if errorMessage} 171 + <div class="error-message">{errorMessage}</div> 172 + {/if} 173 + {#if showStatusMessage} 174 + <div class="status-message">{statusMessage}</div> 175 + {/if} 176 + 177 + <div> 178 + <button disabled={disableLoginButton} type="submit">Login</button> 179 + </div> 180 + </div> 181 + </form> 182 + {/if} 183 + 184 + <!-- Second section: Old Login credentials --> 185 + {#if showOldLogin} 186 + <form onsubmit={handleOldLogin}> 187 + <div class="section"> 188 + <h2>Password for your OLD PDS</h2> 189 + <p>We only need your password for your old account. We can find your old handle from your current 190 + login.</p> 191 + <div class="form-group"> 192 + <label for="password">OLD Password:</label> 193 + <input type="password" id="password" name="password" bind:value={oldLogin.password} required> 194 + </div> 195 + 196 + {#if oldLogin.showTwoFactorCodeInput} 197 + <div class="form-group"> 198 + <label for="two-factor-code">2FA from the email sent</label> 199 + <input type="text" id="two-factor-code" name="two-factor-code" 200 + bind:value={oldLogin.twoFactorCode}> 201 + <div class="error-message">Enter your 2fa code here</div> 202 + </div> 203 + {/if} 204 + 205 + {#if showAdvance} 206 + <div class="form-group show-advance"> 207 + <label for="old_pds">This is optional. If you do not know your old PDS url please leave it 208 + blank. We 209 + will find it for you. </label> 210 + <input type="url" id="old_pds" name="two-factor-code" 211 + placeholder="(Optional) Your old PDS URL" bind:value={oldPdsUrl}> 212 + </div> 213 + {/if} 214 + <div class="form-group"> 215 + <button type="button" onclick={toggleAdvanceMenu} id="advance" name="advance">Advance Options 216 + </button> 217 + </div> 218 + 219 + {#if errorMessage} 220 + <div class="error-message">{errorMessage}</div> 221 + {/if} 222 + {#if showStatusMessage} 223 + <div class="status-message">{statusMessage}</div> 224 + {/if} 225 + 226 + <div> 227 + <button disabled={disableLoginButton} type="submit">Login and start the import of missing blobs 228 + </button> 229 + </div> 230 + </div> 231 + </form> 232 + {/if} 233 + 234 + <!-- Third section: Progress while uploading blobs--> 235 + {#if showBlobMoveProgress} 236 + <div> 237 + {#if showStatusMessage} 238 + <div id="warning">*This will take a while. Please do not close this tab. And watch 239 + the status message below for updates 240 + </div> 241 + {/if} 242 + {#if showStatusMessage} 243 + <div id="missing-status-message" class="status-message">{statusMessage}</div> 244 + {/if} 245 + {#if errorMessage} 246 + <div class="error-message">{errorMessage}</div> 247 + {/if} 248 + {#if showTryAgain} 249 + <p style="color: yellow">We were unable to import all of your previous blobs, please try again. If it is 250 + still not completing give it a few hours and come back and try again. It may be rate limited. Re 251 + running this tool does not harm your account.</p> 252 + <br> 253 + <button onclick={migrateMissingBlobs}>Try again</button> 254 + {/if} 255 + </div> 256 + {/if} 257 + </div>