this repo has no description
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 progress: {
26 repoExported: state.progress.repoExported,
27 repoImported: state.progress.repoImported,
28 blobsTotal: state.progress.blobsTotal,
29 blobsMigrated: state.progress.blobsMigrated,
30 prefsMigrated: state.progress.prefsMigrated,
31 plcSigned: state.progress.plcSigned,
32 },
33 lastError: state.error ?? undefined,
34 lastErrorStep: state.error ? state.step : undefined,
35 };
36
37 try {
38 localStorage.setItem(STORAGE_KEY, JSON.stringify(storedState));
39 } catch { /* localStorage unavailable */ }
40}
41
42export function loadMigrationState(): StoredMigrationState | null {
43 try {
44 const stored = localStorage.getItem(STORAGE_KEY);
45 if (!stored) return null;
46
47 const state = JSON.parse(stored) as StoredMigrationState;
48
49 if (state.version !== 1) {
50 clearMigrationState();
51 return null;
52 }
53
54 const startedAt = new Date(state.startedAt).getTime();
55 if (Date.now() - startedAt > MAX_AGE_MS) {
56 clearMigrationState();
57 return null;
58 }
59
60 return state;
61 } catch {
62 clearMigrationState();
63 return null;
64 }
65}
66
67export function clearMigrationState(): void {
68 try {
69 localStorage.removeItem(STORAGE_KEY);
70 clearDPoPKey();
71 } catch { /* localStorage unavailable */ }
72}
73
74export function hasPendingMigration(): boolean {
75 return loadMigrationState() !== null;
76}
77
78export function getResumeInfo(): {
79 direction: MigrationDirection;
80 sourceHandle: string;
81 targetHandle: string;
82 sourcePdsUrl: string;
83 targetPdsUrl: string;
84 targetEmail: string;
85 authMethod?: "password" | "passkey";
86 progressSummary: string;
87 step: string;
88} | null {
89 const state = loadMigrationState();
90 if (!state) return null;
91
92 const progressParts: string[] = [];
93 if (state.progress.repoExported) progressParts.push("repo exported");
94 if (state.progress.repoImported) progressParts.push("repo imported");
95 if (state.progress.blobsMigrated > 0) {
96 progressParts.push(
97 `${state.progress.blobsMigrated}/${state.progress.blobsTotal} blobs`,
98 );
99 }
100 if (state.progress.prefsMigrated) progressParts.push("preferences migrated");
101 if (state.progress.plcSigned) progressParts.push("PLC signed");
102
103 return {
104 direction: state.direction,
105 sourceHandle: state.sourceHandle,
106 targetHandle: state.targetHandle,
107 sourcePdsUrl: state.sourcePdsUrl,
108 targetPdsUrl: state.targetPdsUrl,
109 targetEmail: state.targetEmail,
110 authMethod: state.authMethod,
111 progressSummary: progressParts.length > 0
112 ? progressParts.join(", ")
113 : "just started",
114 step: state.step,
115 };
116}
117
118export function updateProgress(
119 updates: Partial<StoredMigrationState["progress"]>,
120): void {
121 const state = loadMigrationState();
122 if (!state) return;
123
124 state.progress = { ...state.progress, ...updates };
125 try {
126 localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
127 } catch { /* localStorage unavailable */ }
128}
129
130export function updateStep(step: string): void {
131 const state = loadMigrationState();
132 if (!state) return;
133
134 state.step = step;
135 try {
136 localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
137 } catch { /* localStorage unavailable */ }
138}
139
140export function setError(error: string, step: string): void {
141 const state = loadMigrationState();
142 if (!state) return;
143
144 state.lastError = error;
145 state.lastErrorStep = step;
146 try {
147 localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
148 } catch { /* localStorage unavailable */ }
149}