Our Personal Data Server from scratch!
at nix-fixes 150 lines 4.3 kB view raw
1import type { 2 MigrationDirection, 3 MigrationState, 4 StoredMigrationState, 5} from "./types.ts"; 6import { clearDPoPKey } from "./atproto-client.ts"; 7 8const STORAGE_KEY = "tranquil_migration_state"; 9const MAX_AGE_MS = 24 * 60 * 60 * 1000; 10 11export function saveMigrationState(state: MigrationState): void { 12 const storedState: StoredMigrationState = { 13 version: 1, 14 direction: state.direction, 15 step: state.step, 16 startedAt: new Date().toISOString(), 17 sourcePdsUrl: state.sourcePdsUrl, 18 targetPdsUrl: globalThis.location.origin, 19 sourceDid: state.sourceDid, 20 sourceHandle: state.sourceHandle, 21 targetHandle: state.targetHandle, 22 targetEmail: state.targetEmail, 23 authMethod: state.authMethod, 24 passkeySetupToken: state.passkeySetupToken ?? undefined, 25 localAccessToken: state.localAccessToken ?? undefined, 26 progress: { 27 repoExported: state.progress.repoExported, 28 repoImported: state.progress.repoImported, 29 blobsTotal: state.progress.blobsTotal, 30 blobsMigrated: state.progress.blobsMigrated, 31 prefsMigrated: state.progress.prefsMigrated, 32 plcSigned: state.progress.plcSigned, 33 }, 34 lastError: state.error ?? undefined, 35 lastErrorStep: state.error ? state.step : undefined, 36 }; 37 38 try { 39 localStorage.setItem(STORAGE_KEY, JSON.stringify(storedState)); 40 } catch { /* localStorage unavailable */ } 41} 42 43export function loadMigrationState(): StoredMigrationState | null { 44 try { 45 const stored = localStorage.getItem(STORAGE_KEY); 46 if (!stored) return null; 47 48 const state = JSON.parse(stored) as StoredMigrationState; 49 50 if (state.version !== 1) { 51 clearMigrationState(); 52 return null; 53 } 54 55 const startedAt = new Date(state.startedAt).getTime(); 56 if (Date.now() - startedAt > MAX_AGE_MS) { 57 clearMigrationState(); 58 return null; 59 } 60 61 return state; 62 } catch { 63 clearMigrationState(); 64 return null; 65 } 66} 67 68export function clearMigrationState(): void { 69 try { 70 localStorage.removeItem(STORAGE_KEY); 71 clearDPoPKey(); 72 } catch { /* localStorage unavailable */ } 73} 74 75export function hasPendingMigration(): boolean { 76 return loadMigrationState() !== null; 77} 78 79export function getResumeInfo(): { 80 direction: MigrationDirection; 81 sourceHandle: string; 82 targetHandle: string; 83 sourcePdsUrl: string; 84 targetPdsUrl: string; 85 targetEmail: string; 86 authMethod?: "password" | "passkey"; 87 progressSummary: string; 88 step: string; 89} | null { 90 const state = loadMigrationState(); 91 if (!state) return null; 92 93 const progressParts: string[] = []; 94 if (state.progress.repoExported) progressParts.push("repo exported"); 95 if (state.progress.repoImported) progressParts.push("repo imported"); 96 if (state.progress.blobsMigrated > 0) { 97 progressParts.push( 98 `${state.progress.blobsMigrated}/${state.progress.blobsTotal} blobs`, 99 ); 100 } 101 if (state.progress.prefsMigrated) progressParts.push("preferences migrated"); 102 if (state.progress.plcSigned) progressParts.push("PLC signed"); 103 104 return { 105 direction: state.direction, 106 sourceHandle: state.sourceHandle, 107 targetHandle: state.targetHandle, 108 sourcePdsUrl: state.sourcePdsUrl, 109 targetPdsUrl: state.targetPdsUrl, 110 targetEmail: state.targetEmail, 111 authMethod: state.authMethod, 112 progressSummary: progressParts.length > 0 113 ? progressParts.join(", ") 114 : "just started", 115 step: state.step, 116 }; 117} 118 119export function updateProgress( 120 updates: Partial<StoredMigrationState["progress"]>, 121): void { 122 const state = loadMigrationState(); 123 if (!state) return; 124 125 state.progress = { ...state.progress, ...updates }; 126 try { 127 localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); 128 } catch { /* localStorage unavailable */ } 129} 130 131export function updateStep(step: string): void { 132 const state = loadMigrationState(); 133 if (!state) return; 134 135 state.step = step; 136 try { 137 localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); 138 } catch { /* localStorage unavailable */ } 139} 140 141export function setError(error: string, step: string): void { 142 const state = loadMigrationState(); 143 if (!state) return; 144 145 state.lastError = error; 146 state.lastErrorStep = step; 147 try { 148 localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); 149 } catch { /* localStorage unavailable */ } 150}