Our Personal Data Server from scratch!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 198 lines 5.4 kB view raw
1import type { 2 AccountResult, 3 ExternalDidWebState, 4 RegistrationInfo, 5 RegistrationMode, 6 RegistrationStep, 7 SessionState, 8} from "./types.ts"; 9 10const STORAGE_KEY = "tranquil_registration_state"; 11const MAX_AGE_MS = 60 * 60 * 1000; 12 13interface StoredRegistrationState { 14 version: 1; 15 startedAt: string; 16 mode: RegistrationMode; 17 step: RegistrationStep; 18 pdsHostname: string; 19 info: RegistrationInfo; 20 externalDidWeb: StoredExternalDidWebState; 21 account: StoredAccountResult | null; 22 session: StoredSessionState | null; 23} 24 25interface StoredExternalDidWebState { 26 keyMode: "reserved" | "byod"; 27 reservedSigningKey?: string; 28 byodPrivateKeyBase64?: string; 29 byodPublicKeyMultibase?: string; 30 initialDidDocument?: string; 31 updatedDidDocument?: string; 32} 33 34interface StoredAccountResult { 35 did: string; 36 handle: string; 37 setupToken?: string; 38 appPassword?: string; 39 appPasswordName?: string; 40} 41 42interface StoredSessionState { 43 accessJwt: string; 44 refreshJwt: string; 45} 46 47function uint8ArrayToBase64(arr: Uint8Array): string { 48 return btoa(Array.from(arr, (byte) => String.fromCharCode(byte)).join("")); 49} 50 51function base64ToUint8Array(base64: string): Uint8Array { 52 const binary = atob(base64); 53 return Uint8Array.from(binary, (char) => char.charCodeAt(0)); 54} 55 56export function saveRegistrationState( 57 mode: RegistrationMode, 58 step: RegistrationStep, 59 pdsHostname: string, 60 info: RegistrationInfo, 61 externalDidWeb: ExternalDidWebState, 62 account: AccountResult | null, 63 session: SessionState | null, 64): void { 65 const stored: StoredRegistrationState = { 66 version: 1, 67 startedAt: new Date().toISOString(), 68 mode, 69 step, 70 pdsHostname, 71 info: { ...info, password: undefined }, 72 externalDidWeb: { 73 keyMode: externalDidWeb.keyMode, 74 reservedSigningKey: externalDidWeb.reservedSigningKey, 75 byodPrivateKeyBase64: externalDidWeb.byodPrivateKey 76 ? uint8ArrayToBase64(externalDidWeb.byodPrivateKey) 77 : undefined, 78 byodPublicKeyMultibase: externalDidWeb.byodPublicKeyMultibase, 79 initialDidDocument: externalDidWeb.initialDidDocument, 80 updatedDidDocument: externalDidWeb.updatedDidDocument, 81 }, 82 account: account 83 ? { 84 did: account.did, 85 handle: account.handle, 86 setupToken: account.setupToken, 87 appPassword: account.appPassword, 88 appPasswordName: account.appPasswordName, 89 } 90 : null, 91 session: session 92 ? { 93 accessJwt: session.accessJwt, 94 refreshJwt: session.refreshJwt, 95 } 96 : null, 97 }; 98 99 try { 100 localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)); 101 } catch { /* localStorage unavailable */ } 102} 103 104export function loadRegistrationState(): { 105 mode: RegistrationMode; 106 step: RegistrationStep; 107 pdsHostname: string; 108 info: RegistrationInfo; 109 externalDidWeb: ExternalDidWebState; 110 account: AccountResult | null; 111 session: SessionState | null; 112} | null { 113 try { 114 const stored = localStorage.getItem(STORAGE_KEY); 115 if (!stored) return null; 116 117 const state = JSON.parse(stored) as StoredRegistrationState; 118 119 if (state.version !== 1) { 120 clearRegistrationState(); 121 return null; 122 } 123 124 const startedAt = new Date(state.startedAt).getTime(); 125 if (Date.now() - startedAt > MAX_AGE_MS) { 126 clearRegistrationState(); 127 return null; 128 } 129 130 return { 131 mode: state.mode, 132 step: state.step, 133 pdsHostname: state.pdsHostname, 134 info: state.info, 135 externalDidWeb: { 136 keyMode: state.externalDidWeb.keyMode, 137 reservedSigningKey: state.externalDidWeb.reservedSigningKey, 138 byodPrivateKey: state.externalDidWeb.byodPrivateKeyBase64 139 ? base64ToUint8Array(state.externalDidWeb.byodPrivateKeyBase64) 140 : undefined, 141 byodPublicKeyMultibase: state.externalDidWeb.byodPublicKeyMultibase, 142 initialDidDocument: state.externalDidWeb.initialDidDocument, 143 updatedDidDocument: state.externalDidWeb.updatedDidDocument, 144 }, 145 account: state.account 146 ? { 147 did: state.account.did as AccountResult["did"], 148 handle: state.account.handle as AccountResult["handle"], 149 setupToken: state.account.setupToken, 150 appPassword: state.account.appPassword, 151 appPasswordName: state.account.appPasswordName, 152 } 153 : null, 154 session: state.session 155 ? { 156 accessJwt: state.session.accessJwt as SessionState["accessJwt"], 157 refreshJwt: state.session.refreshJwt as SessionState["refreshJwt"], 158 } 159 : null, 160 }; 161 } catch { 162 clearRegistrationState(); 163 return null; 164 } 165} 166 167export function clearRegistrationState(): void { 168 try { 169 localStorage.removeItem(STORAGE_KEY); 170 } catch { /* localStorage unavailable */ } 171} 172 173export function hasPendingRegistration(): boolean { 174 const state = loadRegistrationState(); 175 return state !== null && state.step !== "info" && 176 state.step !== "redirect-to-dashboard"; 177} 178 179export function getRegistrationResumeInfo(): { 180 mode: RegistrationMode; 181 handle: string; 182 step: RegistrationStep; 183 did?: string; 184} | null { 185 const state = loadRegistrationState(); 186 if ( 187 !state || state.step === "info" || state.step === "redirect-to-dashboard" 188 ) { 189 return null; 190 } 191 192 return { 193 mode: state.mode, 194 handle: state.info.handle, 195 step: state.step, 196 did: state.account?.did, 197 }; 198}