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