import type { TokenStorage } from '@zyphr-dev/auth-core'; /** * The minimal async key/value shape common to React Native secure stores — * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes * whichever it uses, so we don't force a native module on every consumer. */ /** Adapt `expo-secure-store` (the recommended secure default). */ export function secureStoreAdapter(secureStore: { getItemAsync(key: string): Promise; setItemAsync(key: string, value: string): Promise; deleteItemAsync(key: string): Promise; }): TokenStorage { return { getItem: (key) => secureStore.getItemAsync(key), setItem: (key, value) => secureStore.setItemAsync(key, value), removeItem: (key) => secureStore.deleteItemAsync(key), }; } /** * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps * that already standardize on AsyncStorage and accept the tradeoff. */ export function asyncStorageAdapter(asyncStorage: { getItem(key: string): Promise; setItem(key: string, value: string): Promise; removeItem(key: string): Promise; }): TokenStorage { return { getItem: (key) => asyncStorage.getItem(key), setItem: (key, value) => asyncStorage.setItem(key, value), removeItem: (key) => asyncStorage.removeItem(key), }; } /** The subset of `react-native-keychain` this adapter uses. */ export interface KeychainModule { setGenericPassword( username: string, password: string, options?: { service?: string }, ): Promise; getGenericPassword( options?: { service?: string }, ): Promise; resetGenericPassword(options?: { service?: string }): Promise; } /** Fixed username — the session lives in the `password` field; only one entry per service. */ const KEYCHAIN_USER = 'zyphr'; /** * Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the * recommended secure store for **bare** React Native (no Expo). Encrypted at the * OS level, appropriate for long-lived refresh tokens. * * Keychain's API is service-based rather than key/value, so each storage `key` * maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`). * The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session` * for per-account isolation), so each session is one service entry. * * ```ts * import * as Keychain from 'react-native-keychain'; * * ``` * * `react-native-keychain` is an OPTIONAL peer dependency — install it only if * you use this adapter. */ export function keychainAdapter(keychain: KeychainModule): TokenStorage { return { getItem: async (key) => { const result = await keychain.getGenericPassword({ service: key }); return result ? result.password : null; }, setItem: (key, value) => keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => undefined), removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => undefined), }; }