proposal-system/mobile/src/lib/storage.ts
Adam Moussa 47c3b8e5eb Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00

67 lines
1.7 KiB
TypeScript

import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Keychain from 'react-native-keychain';
import { KEYCHAIN_SERVICE, STORAGE_KEY_USER } from '../constants';
export interface StoredTokens {
accessToken: string;
idToken: string;
refreshToken: string;
expiresAt: string;
}
export interface StoredUser {
id: string;
email: string;
displayName: string;
role: 'Dispatcher' | 'Admin' | 'SysAdmin';
}
export const tokenStorage = {
save: async (tokens: StoredTokens): Promise<void> => {
await Keychain.setGenericPassword('tokens', JSON.stringify(tokens), {
service: KEYCHAIN_SERVICE,
});
},
get: async (): Promise<StoredTokens | null> => {
const result = await Keychain.getGenericPassword({
service: KEYCHAIN_SERVICE,
});
if (!result) return null;
try {
return JSON.parse(result.password);
} catch {
return null;
}
},
clear: async (): Promise<void> => {
await Keychain.resetGenericPassword({ service: KEYCHAIN_SERVICE });
},
isExpired: async (): Promise<boolean> => {
const tokens = await tokenStorage.get();
if (!tokens) return true;
return new Date(tokens.expiresAt) <= new Date();
},
};
export const userStorage = {
save: async (user: StoredUser): Promise<void> => {
await AsyncStorage.setItem(STORAGE_KEY_USER, JSON.stringify(user));
},
get: async (): Promise<StoredUser | null> => {
const data = await AsyncStorage.getItem(STORAGE_KEY_USER);
if (!data) return null;
try {
return JSON.parse(data);
} catch {
return null;
}
},
clear: async (): Promise<void> => {
await AsyncStorage.removeItem(STORAGE_KEY_USER);
},
};