Files
tolaria/apps/mobile/src/mobileAppStateStorage.ts

127 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-05-05 00:24:47 +02:00
export type MobileAppState = {
activeVaultId: string
selectedNoteId: string | null
}
export type MobileAppStateFileInfo = {
exists: boolean
isDirectory?: boolean
}
export type MobileAppStateFileSystem = {
documentDirectory: string | null
getInfoAsync: (uri: string) => Promise<MobileAppStateFileInfo>
makeDirectoryAsync: (uri: string, options: { intermediates: true }) => Promise<void>
readAsStringAsync: (uri: string) => Promise<string>
writeAsStringAsync: (uri: string, content: string) => Promise<void>
}
export type MobileAppStateStorage = {
load: (activeVaultId: string) => Promise<MobileAppState>
save: (state: MobileAppState) => Promise<void>
}
export function createMobileAppStateStorage(
fileSystem: MobileAppStateFileSystem,
): MobileAppStateStorage {
return {
load: async (activeVaultId) => loadMobileAppState({ activeVaultId, fileSystem }),
save: async (state) => saveMobileAppState({ fileSystem, state }),
}
}
async function loadMobileAppState({
activeVaultId,
fileSystem,
}: {
activeVaultId: string
fileSystem: MobileAppStateFileSystem
}) {
const fileUri = appStateFileUri(fileSystem)
const info = await fileSystem.getInfoAsync(fileUri)
if (!info.exists || info.isDirectory) {
return defaultMobileAppState(activeVaultId)
}
return parseMobileAppState({
activeVaultId,
content: await fileSystem.readAsStringAsync(fileUri),
})
}
async function saveMobileAppState({
fileSystem,
state,
}: {
fileSystem: MobileAppStateFileSystem
state: MobileAppState
}) {
const rootUri = appStateRootUri(fileSystem)
await ensureDirectory({ fileSystem, uri: rootUri })
await fileSystem.writeAsStringAsync(appStateFileUri(fileSystem), JSON.stringify(state))
}
function parseMobileAppState({
activeVaultId,
content,
}: {
activeVaultId: string
content: string
}) {
try {
return coerceMobileAppState({ activeVaultId, value: JSON.parse(content) })
} catch {
return defaultMobileAppState(activeVaultId)
}
}
function coerceMobileAppState({
activeVaultId,
value,
}: {
activeVaultId: string
value: unknown
}): MobileAppState {
if (!isStateRecord(value) || value.activeVaultId !== activeVaultId) {
return defaultMobileAppState(activeVaultId)
}
return {
activeVaultId,
selectedNoteId: typeof value.selectedNoteId === 'string' ? value.selectedNoteId : null,
}
}
function defaultMobileAppState(activeVaultId: string): MobileAppState {
return { activeVaultId, selectedNoteId: null }
}
async function ensureDirectory({
fileSystem,
uri,
}: {
fileSystem: MobileAppStateFileSystem
uri: string
}) {
const info = await fileSystem.getInfoAsync(uri)
if (!info.exists) {
await fileSystem.makeDirectoryAsync(uri, { intermediates: true })
}
}
function appStateFileUri(fileSystem: MobileAppStateFileSystem) {
return `${appStateRootUri(fileSystem)}/app-state.json`
}
function appStateRootUri(fileSystem: MobileAppStateFileSystem) {
if (!fileSystem.documentDirectory) {
throw new Error('Expo FileSystem documentDirectory is unavailable')
}
return `${fileSystem.documentDirectory.replace(/\/+$/, '')}/state`
}
function isStateRecord(value: unknown): value is { activeVaultId?: unknown; selectedNoteId?: unknown } {
return typeof value === 'object' && value !== null
}