From 810a243cb70f34ba0ca882e69103e1a7c67962f1 Mon Sep 17 00:00:00 2001 From: karthi murugathas Date: Sun, 6 Jul 2025 22:49:04 +0100 Subject: [PATCH 1/2] add expo-secure-store.ts --- package.json | 2 + src/persist-plugins/expo-secure-store.ts | 93 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/persist-plugins/expo-secure-store.ts diff --git a/package.json b/package.json index fb82f3cf..883d9a4b 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,8 @@ "typescript": "^5.7.2" }, "dependencies": { + "expo": "^53.0.17", + "expo-secure-store": "~14.2.3", "use-sync-external-store": "^1.2.2" }, "peerDependencies": { diff --git a/src/persist-plugins/expo-secure-store.ts b/src/persist-plugins/expo-secure-store.ts new file mode 100644 index 00000000..acc8124a --- /dev/null +++ b/src/persist-plugins/expo-secure-store.ts @@ -0,0 +1,93 @@ +import type { Change } from '@legendapp/state'; +import { applyChanges, internal, isArray } from '@legendapp/state'; +import type { ObservablePersistPlugin, ObservablePersistPluginOptions, PersistMetadata } from '@legendapp/state/sync'; +import * as SecureStore from 'expo-secure-store'; + +const MetadataSuffix = '__m'; +const { safeParse, safeStringify } = internal; + +export interface ObservablePersistExpoSecureStoreOptions { + preload?: string[] | boolean; +} + +export class ObservablePersistExpoSecureStore implements ObservablePersistPlugin { + private data: Record = {}; + private config: ObservablePersistExpoSecureStoreOptions; + + constructor(configuration: ObservablePersistExpoSecureStoreOptions) { + this.config = configuration; + } + + public async initialize(_: ObservablePersistPluginOptions) { + const { preload } = this.config; + + if (isArray(preload) && preload.length) { + const keys = preload.flatMap((key) => (key.endsWith(MetadataSuffix) ? [key] : [key, key + MetadataSuffix])); + const pairs = await Promise.all( + keys.map(async (key) => [key, await SecureStore.getItemAsync(key)] as const), + ); + pairs.forEach(([key, val]) => { + this.data[key] = val ? safeParse(val) : undefined; + }); + } else if (preload === true) { + console.warn('[legend-state] Expo SecureStore cannot preload all keys; please supply a string[]'); + } + } + + public loadTable(table: string): void | Promise { + if (this.data[table] === undefined) { + return Promise.all([SecureStore.getItemAsync(table), SecureStore.getItemAsync(table + MetadataSuffix)]) + .then(([raw, meta]) => { + try { + this.data[table] = raw ? safeParse(raw) : undefined; + this.data[table + MetadataSuffix] = meta ? safeParse(meta) : undefined; + } catch (err) { + console.error('[legend-state] SecureStore parse failed for', table, err); + } + }) + .catch((err) => { + console.error('[legend-state] SecureStore.getItemAsync failed', table, err); + }); + } + } + + public getTable(table: string, init: object) { + return this.data[table] ?? init ?? {}; + } + public getMetadata(table: string): PersistMetadata { + return this.getTable(table + MetadataSuffix, {}); + } + + public set(table: string, changes: Change[]): Promise { + this.data[table] = applyChanges(this.data[table] ?? {}, changes); + return this.save(table); + } + public setMetadata(table: string, metadata: PersistMetadata) { + return this.setValue(table + MetadataSuffix, metadata); + } + + public async deleteTable(table: string) { + this.data[table] = undefined; + return SecureStore.deleteItemAsync(table); + } + public deleteMetadata(table: string) { + return this.deleteTable(table + MetadataSuffix); + } + + private async setValue(key: string, value: any) { + this.data[key] = value; + await this.save(key); + } + private async save(key: string) { + const v = this.data[key]; + if (v !== undefined && v !== null) { + return SecureStore.setItemAsync(key, safeStringify(v)); + } else { + return SecureStore.deleteItemAsync(key); + } + } +} + +export function observablePersistExpoSecureStore(config: ObservablePersistExpoSecureStoreOptions) { + return new ObservablePersistExpoSecureStore(config); +} From 94d50f1b1ed27c8f0d8da562b97f48f957f4a186 Mon Sep 17 00:00:00 2001 From: karthi murugathas Date: Sun, 6 Jul 2025 22:55:24 +0100 Subject: [PATCH 2/2] remove dependencies --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 883d9a4b..fb82f3cf 100644 --- a/package.json +++ b/package.json @@ -110,8 +110,6 @@ "typescript": "^5.7.2" }, "dependencies": { - "expo": "^53.0.17", - "expo-secure-store": "~14.2.3", "use-sync-external-store": "^1.2.2" }, "peerDependencies": {