@zyphr-dev/auth-react-native 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +19 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/src/index.ts +2 -1
- package/src/provider.tsx +12 -2
- package/src/storage.ts +46 -0
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
ZyphrAuthProvider: () => ZyphrAuthProvider,
|
|
24
24
|
asyncStorageAdapter: () => asyncStorageAdapter,
|
|
25
25
|
createZyphrTransport: () => createZyphrTransport,
|
|
26
|
+
keychainAdapter: () => keychainAdapter,
|
|
26
27
|
secureStoreAdapter: () => secureStoreAdapter,
|
|
27
28
|
useAuth: () => useAuth,
|
|
28
29
|
useSession: () => useSession
|
|
@@ -62,6 +63,7 @@ var ZyphrAuthContext = (0, import_react.createContext)(null);
|
|
|
62
63
|
function ZyphrAuthProvider({
|
|
63
64
|
client,
|
|
64
65
|
storage,
|
|
66
|
+
keyPrefix,
|
|
65
67
|
refreshSkewMs,
|
|
66
68
|
refreshOnForeground = true,
|
|
67
69
|
children
|
|
@@ -69,7 +71,11 @@ function ZyphrAuthProvider({
|
|
|
69
71
|
const [manager] = (0, import_react.useState)(
|
|
70
72
|
() => new import_auth_core2.SessionManager({
|
|
71
73
|
transport: createZyphrTransport(client),
|
|
72
|
-
storage:
|
|
74
|
+
// Pass storage through as-is: auth-core applies the in-memory default
|
|
75
|
+
// AND emits the dev-mode "no storage" warning when it's omitted. Don't
|
|
76
|
+
// default it here or that warning would never fire for RN consumers.
|
|
77
|
+
storage,
|
|
78
|
+
keyPrefix,
|
|
73
79
|
refreshSkewMs
|
|
74
80
|
})
|
|
75
81
|
);
|
|
@@ -140,11 +146,23 @@ function asyncStorageAdapter(asyncStorage) {
|
|
|
140
146
|
removeItem: (key) => asyncStorage.removeItem(key)
|
|
141
147
|
};
|
|
142
148
|
}
|
|
149
|
+
var KEYCHAIN_USER = "zyphr";
|
|
150
|
+
function keychainAdapter(keychain) {
|
|
151
|
+
return {
|
|
152
|
+
getItem: async (key) => {
|
|
153
|
+
const result = await keychain.getGenericPassword({ service: key });
|
|
154
|
+
return result ? result.password : null;
|
|
155
|
+
},
|
|
156
|
+
setItem: (key, value) => keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => void 0),
|
|
157
|
+
removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => void 0)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
143
160
|
// Annotate the CommonJS export names for ESM import in node:
|
|
144
161
|
0 && (module.exports = {
|
|
145
162
|
ZyphrAuthProvider,
|
|
146
163
|
asyncStorageAdapter,
|
|
147
164
|
createZyphrTransport,
|
|
165
|
+
keychainAdapter,
|
|
148
166
|
secureStoreAdapter,
|
|
149
167
|
useAuth,
|
|
150
168
|
useSession
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/transport.ts","../src/storage.ts"],"sourcesContent":["/**\n * @zyphr-dev/auth-react-native\n *\n * React Native bindings for Zyphr auth. Wrap your app in <ZyphrAuthProvider> and\n * read the session with useSession() / act with useAuth(). Same session engine as\n * the web binding (@zyphr-dev/auth-core), plus a secure-storage adapter and\n * refresh-on-foreground via React Native's AppState.\n */\n\nexport { ZyphrAuthProvider, useSession, useAuth } from './provider.js';\nexport type { ZyphrAuthProviderProps, UseAuthResult } from './provider.js';\n\nexport { createZyphrTransport } from './transport.js';\nexport { secureStoreAdapter, asyncStorageAdapter } from './storage.js';\n\nexport type { SessionState, SessionStatus, LoginParams, TokenStorage } from '@zyphr-dev/auth-core';\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { AppState, type AppStateStatus } from 'react-native';\nimport type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport {\n SessionManager,\n MemoryTokenStorage,\n type LoginParams,\n type SessionState,\n type TokenStorage,\n} from '@zyphr-dev/auth-core';\nimport { createZyphrTransport } from './transport.js';\n\nexport interface ZyphrAuthProviderProps {\n /** A client-safe ZyphrClient (constructed with your publishable application key). */\n client: ZyphrClient;\n /**\n * Where tokens persist. Strongly recommended in production — pass a secure\n * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or\n * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost\n * on app restart), which is fine only for prototyping.\n */\n storage?: TokenStorage;\n /** Refresh this many ms before the access token expires. Default 60_000. */\n refreshSkewMs?: number;\n /**\n * Refresh the session when the app returns to the foreground (default true).\n * A backgrounded app's scheduled refresh timer may not fire reliably, so we\n * also refresh on foreground to keep the token fresh.\n */\n refreshOnForeground?: boolean;\n children: ReactNode;\n}\n\ninterface ZyphrAuthContextValue {\n manager: SessionManager;\n}\n\nconst ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);\n\n/**\n * Provides a reactive Zyphr auth session to a React Native app. Same session\n * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground\n * via React Native's AppState. Read state with {@link useSession}, act with\n * {@link useAuth}.\n */\nexport function ZyphrAuthProvider({\n client,\n storage,\n refreshSkewMs,\n refreshOnForeground = true,\n children,\n}: ZyphrAuthProviderProps): ReactElement {\n const [manager] = useState(\n () =>\n new SessionManager({\n transport: createZyphrTransport(client),\n storage: storage ?? new MemoryTokenStorage(),\n refreshSkewMs,\n }),\n );\n\n const initialized = useRef(false);\n useEffect(() => {\n if (!initialized.current) {\n initialized.current = true;\n void manager.initialize();\n }\n return () => {\n manager.destroy();\n };\n }, [manager]);\n\n // Refresh when the app returns to the foreground.\n useEffect(() => {\n if (!refreshOnForeground || !AppState?.addEventListener) return;\n const onChange = (next: AppStateStatus) => {\n if (next === 'active' && manager.getState().isAuthenticated) {\n void manager.refresh();\n }\n };\n const sub = AppState.addEventListener('change', onChange);\n return () => {\n // RN >= 0.65 returns a subscription with .remove(); older returns void.\n (sub as { remove?: () => void } | undefined)?.remove?.();\n };\n }, [manager, refreshOnForeground]);\n\n const value = useMemo(() => ({ manager }), [manager]);\n return <ZyphrAuthContext.Provider value={value}>{children}</ZyphrAuthContext.Provider>;\n}\n\nfunction useManager(): SessionManager {\n const ctx = useContext(ZyphrAuthContext);\n if (!ctx) {\n throw new Error('useSession/useAuth must be used within a <ZyphrAuthProvider>.');\n }\n return ctx.manager;\n}\n\n/** Reactive session state — re-renders on login, logout, refresh. */\nexport function useSession(): SessionState {\n const manager = useManager();\n return useSyncExternalStore(\n (onChange) => manager.subscribe(() => onChange()),\n () => manager.getState(),\n () => manager.getState(),\n );\n}\n\nexport interface UseAuthResult {\n login: (params: LoginParams) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n}\n\n/** Auth actions bound to the provider's session manager. */\nexport function useAuth(): UseAuthResult {\n const manager = useManager();\n return useMemo(\n () => ({\n login: (params) => manager.login(params),\n logout: () => manager.logout(),\n refresh: () => manager.refresh(),\n }),\n [manager],\n );\n}\n","import type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport { type AuthTransport, type LoginParams, type SessionTokens, toSessionTokens } from '@zyphr-dev/auth-core';\n\n/**\n * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable\n * application key — no secret in the app bundle). Same transport the web binding\n * uses; re-implemented here so the React Native package doesn't depend on\n * `@zyphr-dev/auth-react` (which pulls web-React types).\n */\nexport function createZyphrTransport(client: ZyphrClient, now: () => number = Date.now): AuthTransport {\n const extract = (tokens?: { accessToken?: string; refreshToken?: string; expiresIn?: number }): SessionTokens =>\n toSessionTokens(\n { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },\n now(),\n );\n\n return {\n async login({ email, password }: LoginParams): Promise<SessionTokens> {\n const res = await client.auth.login.loginEndUser({ email, password });\n return extract(res.data?.tokens);\n },\n async refresh(refreshToken: string): Promise<SessionTokens> {\n const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });\n return extract(res.data?.tokens);\n },\n async logout(refreshToken: string): Promise<void> {\n await client.auth.sessions.revokeEndUserSession({ refreshToken });\n },\n };\n}\n","import type { TokenStorage } from '@zyphr-dev/auth-core';\n\n/**\n * The minimal async key/value shape common to React Native secure stores —\n * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and\n * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both\n * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes\n * whichever it uses, so we don't force a native module on every consumer.\n */\n\n/** Adapt `expo-secure-store` (the recommended secure default). */\nexport function secureStoreAdapter(secureStore: {\n getItemAsync(key: string): Promise<string | null>;\n setItemAsync(key: string, value: string): Promise<void>;\n deleteItemAsync(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n };\n}\n\n/**\n * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT\n * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps\n * that already standardize on AsyncStorage and accept the tradeoff.\n */\nexport function asyncStorageAdapter(asyncStorage: {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => asyncStorage.getItem(key),\n setItem: (key, value) => asyncStorage.setItem(key, value),\n removeItem: (key) => asyncStorage.removeItem(key),\n };\n}\n"],"mappings":"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","names":["import_auth_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/transport.ts","../src/storage.ts"],"sourcesContent":["/**\n * @zyphr-dev/auth-react-native\n *\n * React Native bindings for Zyphr auth. Wrap your app in <ZyphrAuthProvider> and\n * read the session with useSession() / act with useAuth(). Same session engine as\n * the web binding (@zyphr-dev/auth-core), plus a secure-storage adapter and\n * refresh-on-foreground via React Native's AppState.\n */\n\nexport { ZyphrAuthProvider, useSession, useAuth } from './provider.js';\nexport type { ZyphrAuthProviderProps, UseAuthResult } from './provider.js';\n\nexport { createZyphrTransport } from './transport.js';\nexport { secureStoreAdapter, asyncStorageAdapter, keychainAdapter } from './storage.js';\nexport type { KeychainModule } from './storage.js';\n\nexport type { SessionState, SessionStatus, LoginParams, TokenStorage } from '@zyphr-dev/auth-core';\n","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { AppState, type AppStateStatus } from 'react-native';\nimport type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport {\n SessionManager,\n type LoginParams,\n type SessionState,\n type TokenStorage,\n} from '@zyphr-dev/auth-core';\nimport { createZyphrTransport } from './transport.js';\n\nexport interface ZyphrAuthProviderProps {\n /** A client-safe ZyphrClient (constructed with your publishable application key). */\n client: ZyphrClient;\n /**\n * Where tokens persist. Strongly recommended in production — pass a secure\n * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or\n * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost\n * on app restart), which is fine only for prototyping.\n */\n storage?: TokenStorage;\n /**\n * Namespace the storage key so multiple providers can share one storage\n * adapter without colliding (e.g. one session per account for account\n * switching). The effective key becomes `${keyPrefix}zyphr.session`. Default `''`.\n */\n keyPrefix?: string;\n /** Refresh this many ms before the access token expires. Default 60_000. */\n refreshSkewMs?: number;\n /**\n * Refresh the session when the app returns to the foreground (default true).\n * A backgrounded app's scheduled refresh timer may not fire reliably, so we\n * also refresh on foreground to keep the token fresh.\n */\n refreshOnForeground?: boolean;\n children: ReactNode;\n}\n\ninterface ZyphrAuthContextValue {\n manager: SessionManager;\n}\n\nconst ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);\n\n/**\n * Provides a reactive Zyphr auth session to a React Native app. Same session\n * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground\n * via React Native's AppState. Read state with {@link useSession}, act with\n * {@link useAuth}.\n */\nexport function ZyphrAuthProvider({\n client,\n storage,\n keyPrefix,\n refreshSkewMs,\n refreshOnForeground = true,\n children,\n}: ZyphrAuthProviderProps): ReactElement {\n const [manager] = useState(\n () =>\n new SessionManager({\n transport: createZyphrTransport(client),\n // Pass storage through as-is: auth-core applies the in-memory default\n // AND emits the dev-mode \"no storage\" warning when it's omitted. Don't\n // default it here or that warning would never fire for RN consumers.\n storage,\n keyPrefix,\n refreshSkewMs,\n }),\n );\n\n const initialized = useRef(false);\n useEffect(() => {\n if (!initialized.current) {\n initialized.current = true;\n void manager.initialize();\n }\n return () => {\n manager.destroy();\n };\n }, [manager]);\n\n // Refresh when the app returns to the foreground.\n useEffect(() => {\n if (!refreshOnForeground || !AppState?.addEventListener) return;\n const onChange = (next: AppStateStatus) => {\n if (next === 'active' && manager.getState().isAuthenticated) {\n void manager.refresh();\n }\n };\n const sub = AppState.addEventListener('change', onChange);\n return () => {\n // RN >= 0.65 returns a subscription with .remove(); older returns void.\n (sub as { remove?: () => void } | undefined)?.remove?.();\n };\n }, [manager, refreshOnForeground]);\n\n const value = useMemo(() => ({ manager }), [manager]);\n return <ZyphrAuthContext.Provider value={value}>{children}</ZyphrAuthContext.Provider>;\n}\n\nfunction useManager(): SessionManager {\n const ctx = useContext(ZyphrAuthContext);\n if (!ctx) {\n throw new Error('useSession/useAuth must be used within a <ZyphrAuthProvider>.');\n }\n return ctx.manager;\n}\n\n/** Reactive session state — re-renders on login, logout, refresh. */\nexport function useSession(): SessionState {\n const manager = useManager();\n return useSyncExternalStore(\n (onChange) => manager.subscribe(() => onChange()),\n () => manager.getState(),\n () => manager.getState(),\n );\n}\n\nexport interface UseAuthResult {\n login: (params: LoginParams) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n}\n\n/** Auth actions bound to the provider's session manager. */\nexport function useAuth(): UseAuthResult {\n const manager = useManager();\n return useMemo(\n () => ({\n login: (params) => manager.login(params),\n logout: () => manager.logout(),\n refresh: () => manager.refresh(),\n }),\n [manager],\n );\n}\n","import type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport { type AuthTransport, type LoginParams, type SessionTokens, toSessionTokens } from '@zyphr-dev/auth-core';\n\n/**\n * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable\n * application key — no secret in the app bundle). Same transport the web binding\n * uses; re-implemented here so the React Native package doesn't depend on\n * `@zyphr-dev/auth-react` (which pulls web-React types).\n */\nexport function createZyphrTransport(client: ZyphrClient, now: () => number = Date.now): AuthTransport {\n const extract = (tokens?: { accessToken?: string; refreshToken?: string; expiresIn?: number }): SessionTokens =>\n toSessionTokens(\n { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },\n now(),\n );\n\n return {\n async login({ email, password }: LoginParams): Promise<SessionTokens> {\n const res = await client.auth.login.loginEndUser({ email, password });\n return extract(res.data?.tokens);\n },\n async refresh(refreshToken: string): Promise<SessionTokens> {\n const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });\n return extract(res.data?.tokens);\n },\n async logout(refreshToken: string): Promise<void> {\n await client.auth.sessions.revokeEndUserSession({ refreshToken });\n },\n };\n}\n","import type { TokenStorage } from '@zyphr-dev/auth-core';\n\n/**\n * The minimal async key/value shape common to React Native secure stores —\n * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and\n * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both\n * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes\n * whichever it uses, so we don't force a native module on every consumer.\n */\n\n/** Adapt `expo-secure-store` (the recommended secure default). */\nexport function secureStoreAdapter(secureStore: {\n getItemAsync(key: string): Promise<string | null>;\n setItemAsync(key: string, value: string): Promise<void>;\n deleteItemAsync(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n };\n}\n\n/**\n * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT\n * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps\n * that already standardize on AsyncStorage and accept the tradeoff.\n */\nexport function asyncStorageAdapter(asyncStorage: {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => asyncStorage.getItem(key),\n setItem: (key, value) => asyncStorage.setItem(key, value),\n removeItem: (key) => asyncStorage.removeItem(key),\n };\n}\n\n/** The subset of `react-native-keychain` this adapter uses. */\nexport interface KeychainModule {\n setGenericPassword(\n username: string,\n password: string,\n options?: { service?: string },\n ): Promise<unknown>;\n getGenericPassword(\n options?: { service?: string },\n ): Promise<false | { username: string; password: string }>;\n resetGenericPassword(options?: { service?: string }): Promise<boolean>;\n}\n\n/** Fixed username — the session lives in the `password` field; only one entry per service. */\nconst KEYCHAIN_USER = 'zyphr';\n\n/**\n * Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the\n * recommended secure store for **bare** React Native (no Expo). Encrypted at the\n * OS level, appropriate for long-lived refresh tokens.\n *\n * Keychain's API is service-based rather than key/value, so each storage `key`\n * maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`).\n * The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session`\n * for per-account isolation), so each session is one service entry.\n *\n * ```ts\n * import * as Keychain from 'react-native-keychain';\n * <ZyphrAuthProvider client={client} storage={keychainAdapter(Keychain)} />\n * ```\n *\n * `react-native-keychain` is an OPTIONAL peer dependency — install it only if\n * you use this adapter.\n */\nexport function keychainAdapter(keychain: KeychainModule): TokenStorage {\n return {\n getItem: async (key) => {\n const result = await keychain.getGenericPassword({ service: key });\n return result ? result.password : null;\n },\n setItem: (key, value) =>\n keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => undefined),\n removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => undefined),\n };\n}\n"],"mappings":"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","names":["import_auth_core"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,12 @@ interface ZyphrAuthProviderProps {
|
|
|
13
13
|
* on app restart), which is fine only for prototyping.
|
|
14
14
|
*/
|
|
15
15
|
storage?: TokenStorage;
|
|
16
|
+
/**
|
|
17
|
+
* Namespace the storage key so multiple providers can share one storage
|
|
18
|
+
* adapter without colliding (e.g. one session per account for account
|
|
19
|
+
* switching). The effective key becomes `${keyPrefix}zyphr.session`. Default `''`.
|
|
20
|
+
*/
|
|
21
|
+
keyPrefix?: string;
|
|
16
22
|
/** Refresh this many ms before the access token expires. Default 60_000. */
|
|
17
23
|
refreshSkewMs?: number;
|
|
18
24
|
/**
|
|
@@ -29,7 +35,7 @@ interface ZyphrAuthProviderProps {
|
|
|
29
35
|
* via React Native's AppState. Read state with {@link useSession}, act with
|
|
30
36
|
* {@link useAuth}.
|
|
31
37
|
*/
|
|
32
|
-
declare function ZyphrAuthProvider({ client, storage, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
|
|
38
|
+
declare function ZyphrAuthProvider({ client, storage, keyPrefix, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
|
|
33
39
|
/** Reactive session state — re-renders on login, logout, refresh. */
|
|
34
40
|
declare function useSession(): SessionState;
|
|
35
41
|
interface UseAuthResult {
|
|
@@ -71,5 +77,39 @@ declare function asyncStorageAdapter(asyncStorage: {
|
|
|
71
77
|
setItem(key: string, value: string): Promise<void>;
|
|
72
78
|
removeItem(key: string): Promise<void>;
|
|
73
79
|
}): TokenStorage;
|
|
80
|
+
/** The subset of `react-native-keychain` this adapter uses. */
|
|
81
|
+
interface KeychainModule {
|
|
82
|
+
setGenericPassword(username: string, password: string, options?: {
|
|
83
|
+
service?: string;
|
|
84
|
+
}): Promise<unknown>;
|
|
85
|
+
getGenericPassword(options?: {
|
|
86
|
+
service?: string;
|
|
87
|
+
}): Promise<false | {
|
|
88
|
+
username: string;
|
|
89
|
+
password: string;
|
|
90
|
+
}>;
|
|
91
|
+
resetGenericPassword(options?: {
|
|
92
|
+
service?: string;
|
|
93
|
+
}): Promise<boolean>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the
|
|
97
|
+
* recommended secure store for **bare** React Native (no Expo). Encrypted at the
|
|
98
|
+
* OS level, appropriate for long-lived refresh tokens.
|
|
99
|
+
*
|
|
100
|
+
* Keychain's API is service-based rather than key/value, so each storage `key`
|
|
101
|
+
* maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`).
|
|
102
|
+
* The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session`
|
|
103
|
+
* for per-account isolation), so each session is one service entry.
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* import * as Keychain from 'react-native-keychain';
|
|
107
|
+
* <ZyphrAuthProvider client={client} storage={keychainAdapter(Keychain)} />
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* `react-native-keychain` is an OPTIONAL peer dependency — install it only if
|
|
111
|
+
* you use this adapter.
|
|
112
|
+
*/
|
|
113
|
+
declare function keychainAdapter(keychain: KeychainModule): TokenStorage;
|
|
74
114
|
|
|
75
|
-
export { type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, secureStoreAdapter, useAuth, useSession };
|
|
115
|
+
export { type KeychainModule, type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, keychainAdapter, secureStoreAdapter, useAuth, useSession };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ interface ZyphrAuthProviderProps {
|
|
|
13
13
|
* on app restart), which is fine only for prototyping.
|
|
14
14
|
*/
|
|
15
15
|
storage?: TokenStorage;
|
|
16
|
+
/**
|
|
17
|
+
* Namespace the storage key so multiple providers can share one storage
|
|
18
|
+
* adapter without colliding (e.g. one session per account for account
|
|
19
|
+
* switching). The effective key becomes `${keyPrefix}zyphr.session`. Default `''`.
|
|
20
|
+
*/
|
|
21
|
+
keyPrefix?: string;
|
|
16
22
|
/** Refresh this many ms before the access token expires. Default 60_000. */
|
|
17
23
|
refreshSkewMs?: number;
|
|
18
24
|
/**
|
|
@@ -29,7 +35,7 @@ interface ZyphrAuthProviderProps {
|
|
|
29
35
|
* via React Native's AppState. Read state with {@link useSession}, act with
|
|
30
36
|
* {@link useAuth}.
|
|
31
37
|
*/
|
|
32
|
-
declare function ZyphrAuthProvider({ client, storage, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
|
|
38
|
+
declare function ZyphrAuthProvider({ client, storage, keyPrefix, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
|
|
33
39
|
/** Reactive session state — re-renders on login, logout, refresh. */
|
|
34
40
|
declare function useSession(): SessionState;
|
|
35
41
|
interface UseAuthResult {
|
|
@@ -71,5 +77,39 @@ declare function asyncStorageAdapter(asyncStorage: {
|
|
|
71
77
|
setItem(key: string, value: string): Promise<void>;
|
|
72
78
|
removeItem(key: string): Promise<void>;
|
|
73
79
|
}): TokenStorage;
|
|
80
|
+
/** The subset of `react-native-keychain` this adapter uses. */
|
|
81
|
+
interface KeychainModule {
|
|
82
|
+
setGenericPassword(username: string, password: string, options?: {
|
|
83
|
+
service?: string;
|
|
84
|
+
}): Promise<unknown>;
|
|
85
|
+
getGenericPassword(options?: {
|
|
86
|
+
service?: string;
|
|
87
|
+
}): Promise<false | {
|
|
88
|
+
username: string;
|
|
89
|
+
password: string;
|
|
90
|
+
}>;
|
|
91
|
+
resetGenericPassword(options?: {
|
|
92
|
+
service?: string;
|
|
93
|
+
}): Promise<boolean>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the
|
|
97
|
+
* recommended secure store for **bare** React Native (no Expo). Encrypted at the
|
|
98
|
+
* OS level, appropriate for long-lived refresh tokens.
|
|
99
|
+
*
|
|
100
|
+
* Keychain's API is service-based rather than key/value, so each storage `key`
|
|
101
|
+
* maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`).
|
|
102
|
+
* The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session`
|
|
103
|
+
* for per-account isolation), so each session is one service entry.
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* import * as Keychain from 'react-native-keychain';
|
|
107
|
+
* <ZyphrAuthProvider client={client} storage={keychainAdapter(Keychain)} />
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* `react-native-keychain` is an OPTIONAL peer dependency — install it only if
|
|
111
|
+
* you use this adapter.
|
|
112
|
+
*/
|
|
113
|
+
declare function keychainAdapter(keychain: KeychainModule): TokenStorage;
|
|
74
114
|
|
|
75
|
-
export { type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, secureStoreAdapter, useAuth, useSession };
|
|
115
|
+
export { type KeychainModule, type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, keychainAdapter, secureStoreAdapter, useAuth, useSession };
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
import { AppState } from "react-native";
|
|
12
12
|
import {
|
|
13
|
-
SessionManager
|
|
14
|
-
MemoryTokenStorage
|
|
13
|
+
SessionManager
|
|
15
14
|
} from "@zyphr-dev/auth-core";
|
|
16
15
|
|
|
17
16
|
// src/transport.ts
|
|
@@ -42,6 +41,7 @@ var ZyphrAuthContext = createContext(null);
|
|
|
42
41
|
function ZyphrAuthProvider({
|
|
43
42
|
client,
|
|
44
43
|
storage,
|
|
44
|
+
keyPrefix,
|
|
45
45
|
refreshSkewMs,
|
|
46
46
|
refreshOnForeground = true,
|
|
47
47
|
children
|
|
@@ -49,7 +49,11 @@ function ZyphrAuthProvider({
|
|
|
49
49
|
const [manager] = useState(
|
|
50
50
|
() => new SessionManager({
|
|
51
51
|
transport: createZyphrTransport(client),
|
|
52
|
-
storage:
|
|
52
|
+
// Pass storage through as-is: auth-core applies the in-memory default
|
|
53
|
+
// AND emits the dev-mode "no storage" warning when it's omitted. Don't
|
|
54
|
+
// default it here or that warning would never fire for RN consumers.
|
|
55
|
+
storage,
|
|
56
|
+
keyPrefix,
|
|
53
57
|
refreshSkewMs
|
|
54
58
|
})
|
|
55
59
|
);
|
|
@@ -120,10 +124,22 @@ function asyncStorageAdapter(asyncStorage) {
|
|
|
120
124
|
removeItem: (key) => asyncStorage.removeItem(key)
|
|
121
125
|
};
|
|
122
126
|
}
|
|
127
|
+
var KEYCHAIN_USER = "zyphr";
|
|
128
|
+
function keychainAdapter(keychain) {
|
|
129
|
+
return {
|
|
130
|
+
getItem: async (key) => {
|
|
131
|
+
const result = await keychain.getGenericPassword({ service: key });
|
|
132
|
+
return result ? result.password : null;
|
|
133
|
+
},
|
|
134
|
+
setItem: (key, value) => keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => void 0),
|
|
135
|
+
removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => void 0)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
123
138
|
export {
|
|
124
139
|
ZyphrAuthProvider,
|
|
125
140
|
asyncStorageAdapter,
|
|
126
141
|
createZyphrTransport,
|
|
142
|
+
keychainAdapter,
|
|
127
143
|
secureStoreAdapter,
|
|
128
144
|
useAuth,
|
|
129
145
|
useSession
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/transport.ts","../src/storage.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { AppState, type AppStateStatus } from 'react-native';\nimport type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport {\n SessionManager,\n MemoryTokenStorage,\n type LoginParams,\n type SessionState,\n type TokenStorage,\n} from '@zyphr-dev/auth-core';\nimport { createZyphrTransport } from './transport.js';\n\nexport interface ZyphrAuthProviderProps {\n /** A client-safe ZyphrClient (constructed with your publishable application key). */\n client: ZyphrClient;\n /**\n * Where tokens persist. Strongly recommended in production — pass a secure\n * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or\n * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost\n * on app restart), which is fine only for prototyping.\n */\n storage?: TokenStorage;\n /** Refresh this many ms before the access token expires. Default 60_000. */\n refreshSkewMs?: number;\n /**\n * Refresh the session when the app returns to the foreground (default true).\n * A backgrounded app's scheduled refresh timer may not fire reliably, so we\n * also refresh on foreground to keep the token fresh.\n */\n refreshOnForeground?: boolean;\n children: ReactNode;\n}\n\ninterface ZyphrAuthContextValue {\n manager: SessionManager;\n}\n\nconst ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);\n\n/**\n * Provides a reactive Zyphr auth session to a React Native app. Same session\n * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground\n * via React Native's AppState. Read state with {@link useSession}, act with\n * {@link useAuth}.\n */\nexport function ZyphrAuthProvider({\n client,\n storage,\n refreshSkewMs,\n refreshOnForeground = true,\n children,\n}: ZyphrAuthProviderProps): ReactElement {\n const [manager] = useState(\n () =>\n new SessionManager({\n transport: createZyphrTransport(client),\n storage: storage ?? new MemoryTokenStorage(),\n refreshSkewMs,\n }),\n );\n\n const initialized = useRef(false);\n useEffect(() => {\n if (!initialized.current) {\n initialized.current = true;\n void manager.initialize();\n }\n return () => {\n manager.destroy();\n };\n }, [manager]);\n\n // Refresh when the app returns to the foreground.\n useEffect(() => {\n if (!refreshOnForeground || !AppState?.addEventListener) return;\n const onChange = (next: AppStateStatus) => {\n if (next === 'active' && manager.getState().isAuthenticated) {\n void manager.refresh();\n }\n };\n const sub = AppState.addEventListener('change', onChange);\n return () => {\n // RN >= 0.65 returns a subscription with .remove(); older returns void.\n (sub as { remove?: () => void } | undefined)?.remove?.();\n };\n }, [manager, refreshOnForeground]);\n\n const value = useMemo(() => ({ manager }), [manager]);\n return <ZyphrAuthContext.Provider value={value}>{children}</ZyphrAuthContext.Provider>;\n}\n\nfunction useManager(): SessionManager {\n const ctx = useContext(ZyphrAuthContext);\n if (!ctx) {\n throw new Error('useSession/useAuth must be used within a <ZyphrAuthProvider>.');\n }\n return ctx.manager;\n}\n\n/** Reactive session state — re-renders on login, logout, refresh. */\nexport function useSession(): SessionState {\n const manager = useManager();\n return useSyncExternalStore(\n (onChange) => manager.subscribe(() => onChange()),\n () => manager.getState(),\n () => manager.getState(),\n );\n}\n\nexport interface UseAuthResult {\n login: (params: LoginParams) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n}\n\n/** Auth actions bound to the provider's session manager. */\nexport function useAuth(): UseAuthResult {\n const manager = useManager();\n return useMemo(\n () => ({\n login: (params) => manager.login(params),\n logout: () => manager.logout(),\n refresh: () => manager.refresh(),\n }),\n [manager],\n );\n}\n","import type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport { type AuthTransport, type LoginParams, type SessionTokens, toSessionTokens } from '@zyphr-dev/auth-core';\n\n/**\n * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable\n * application key — no secret in the app bundle). Same transport the web binding\n * uses; re-implemented here so the React Native package doesn't depend on\n * `@zyphr-dev/auth-react` (which pulls web-React types).\n */\nexport function createZyphrTransport(client: ZyphrClient, now: () => number = Date.now): AuthTransport {\n const extract = (tokens?: { accessToken?: string; refreshToken?: string; expiresIn?: number }): SessionTokens =>\n toSessionTokens(\n { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },\n now(),\n );\n\n return {\n async login({ email, password }: LoginParams): Promise<SessionTokens> {\n const res = await client.auth.login.loginEndUser({ email, password });\n return extract(res.data?.tokens);\n },\n async refresh(refreshToken: string): Promise<SessionTokens> {\n const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });\n return extract(res.data?.tokens);\n },\n async logout(refreshToken: string): Promise<void> {\n await client.auth.sessions.revokeEndUserSession({ refreshToken });\n },\n };\n}\n","import type { TokenStorage } from '@zyphr-dev/auth-core';\n\n/**\n * The minimal async key/value shape common to React Native secure stores —\n * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and\n * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both\n * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes\n * whichever it uses, so we don't force a native module on every consumer.\n */\n\n/** Adapt `expo-secure-store` (the recommended secure default). */\nexport function secureStoreAdapter(secureStore: {\n getItemAsync(key: string): Promise<string | null>;\n setItemAsync(key: string, value: string): Promise<void>;\n deleteItemAsync(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n };\n}\n\n/**\n * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT\n * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps\n * that already standardize on AsyncStorage and accept the tradeoff.\n */\nexport function asyncStorageAdapter(asyncStorage: {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => asyncStorage.getItem(key),\n setItem: (key, value) => asyncStorage.setItem(key, value),\n removeItem: (key) => asyncStorage.removeItem(key),\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/transport.ts","../src/storage.ts"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { AppState, type AppStateStatus } from 'react-native';\nimport type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport {\n SessionManager,\n type LoginParams,\n type SessionState,\n type TokenStorage,\n} from '@zyphr-dev/auth-core';\nimport { createZyphrTransport } from './transport.js';\n\nexport interface ZyphrAuthProviderProps {\n /** A client-safe ZyphrClient (constructed with your publishable application key). */\n client: ZyphrClient;\n /**\n * Where tokens persist. Strongly recommended in production — pass a secure\n * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or\n * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost\n * on app restart), which is fine only for prototyping.\n */\n storage?: TokenStorage;\n /**\n * Namespace the storage key so multiple providers can share one storage\n * adapter without colliding (e.g. one session per account for account\n * switching). The effective key becomes `${keyPrefix}zyphr.session`. Default `''`.\n */\n keyPrefix?: string;\n /** Refresh this many ms before the access token expires. Default 60_000. */\n refreshSkewMs?: number;\n /**\n * Refresh the session when the app returns to the foreground (default true).\n * A backgrounded app's scheduled refresh timer may not fire reliably, so we\n * also refresh on foreground to keep the token fresh.\n */\n refreshOnForeground?: boolean;\n children: ReactNode;\n}\n\ninterface ZyphrAuthContextValue {\n manager: SessionManager;\n}\n\nconst ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);\n\n/**\n * Provides a reactive Zyphr auth session to a React Native app. Same session\n * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground\n * via React Native's AppState. Read state with {@link useSession}, act with\n * {@link useAuth}.\n */\nexport function ZyphrAuthProvider({\n client,\n storage,\n keyPrefix,\n refreshSkewMs,\n refreshOnForeground = true,\n children,\n}: ZyphrAuthProviderProps): ReactElement {\n const [manager] = useState(\n () =>\n new SessionManager({\n transport: createZyphrTransport(client),\n // Pass storage through as-is: auth-core applies the in-memory default\n // AND emits the dev-mode \"no storage\" warning when it's omitted. Don't\n // default it here or that warning would never fire for RN consumers.\n storage,\n keyPrefix,\n refreshSkewMs,\n }),\n );\n\n const initialized = useRef(false);\n useEffect(() => {\n if (!initialized.current) {\n initialized.current = true;\n void manager.initialize();\n }\n return () => {\n manager.destroy();\n };\n }, [manager]);\n\n // Refresh when the app returns to the foreground.\n useEffect(() => {\n if (!refreshOnForeground || !AppState?.addEventListener) return;\n const onChange = (next: AppStateStatus) => {\n if (next === 'active' && manager.getState().isAuthenticated) {\n void manager.refresh();\n }\n };\n const sub = AppState.addEventListener('change', onChange);\n return () => {\n // RN >= 0.65 returns a subscription with .remove(); older returns void.\n (sub as { remove?: () => void } | undefined)?.remove?.();\n };\n }, [manager, refreshOnForeground]);\n\n const value = useMemo(() => ({ manager }), [manager]);\n return <ZyphrAuthContext.Provider value={value}>{children}</ZyphrAuthContext.Provider>;\n}\n\nfunction useManager(): SessionManager {\n const ctx = useContext(ZyphrAuthContext);\n if (!ctx) {\n throw new Error('useSession/useAuth must be used within a <ZyphrAuthProvider>.');\n }\n return ctx.manager;\n}\n\n/** Reactive session state — re-renders on login, logout, refresh. */\nexport function useSession(): SessionState {\n const manager = useManager();\n return useSyncExternalStore(\n (onChange) => manager.subscribe(() => onChange()),\n () => manager.getState(),\n () => manager.getState(),\n );\n}\n\nexport interface UseAuthResult {\n login: (params: LoginParams) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n}\n\n/** Auth actions bound to the provider's session manager. */\nexport function useAuth(): UseAuthResult {\n const manager = useManager();\n return useMemo(\n () => ({\n login: (params) => manager.login(params),\n logout: () => manager.logout(),\n refresh: () => manager.refresh(),\n }),\n [manager],\n );\n}\n","import type { ZyphrClient } from '@zyphr-dev/node-sdk';\nimport { type AuthTransport, type LoginParams, type SessionTokens, toSessionTokens } from '@zyphr-dev/auth-core';\n\n/**\n * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable\n * application key — no secret in the app bundle). Same transport the web binding\n * uses; re-implemented here so the React Native package doesn't depend on\n * `@zyphr-dev/auth-react` (which pulls web-React types).\n */\nexport function createZyphrTransport(client: ZyphrClient, now: () => number = Date.now): AuthTransport {\n const extract = (tokens?: { accessToken?: string; refreshToken?: string; expiresIn?: number }): SessionTokens =>\n toSessionTokens(\n { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },\n now(),\n );\n\n return {\n async login({ email, password }: LoginParams): Promise<SessionTokens> {\n const res = await client.auth.login.loginEndUser({ email, password });\n return extract(res.data?.tokens);\n },\n async refresh(refreshToken: string): Promise<SessionTokens> {\n const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });\n return extract(res.data?.tokens);\n },\n async logout(refreshToken: string): Promise<void> {\n await client.auth.sessions.revokeEndUserSession({ refreshToken });\n },\n };\n}\n","import type { TokenStorage } from '@zyphr-dev/auth-core';\n\n/**\n * The minimal async key/value shape common to React Native secure stores —\n * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and\n * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both\n * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes\n * whichever it uses, so we don't force a native module on every consumer.\n */\n\n/** Adapt `expo-secure-store` (the recommended secure default). */\nexport function secureStoreAdapter(secureStore: {\n getItemAsync(key: string): Promise<string | null>;\n setItemAsync(key: string, value: string): Promise<void>;\n deleteItemAsync(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n };\n}\n\n/**\n * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT\n * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps\n * that already standardize on AsyncStorage and accept the tradeoff.\n */\nexport function asyncStorageAdapter(asyncStorage: {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}): TokenStorage {\n return {\n getItem: (key) => asyncStorage.getItem(key),\n setItem: (key, value) => asyncStorage.setItem(key, value),\n removeItem: (key) => asyncStorage.removeItem(key),\n };\n}\n\n/** The subset of `react-native-keychain` this adapter uses. */\nexport interface KeychainModule {\n setGenericPassword(\n username: string,\n password: string,\n options?: { service?: string },\n ): Promise<unknown>;\n getGenericPassword(\n options?: { service?: string },\n ): Promise<false | { username: string; password: string }>;\n resetGenericPassword(options?: { service?: string }): Promise<boolean>;\n}\n\n/** Fixed username — the session lives in the `password` field; only one entry per service. */\nconst KEYCHAIN_USER = 'zyphr';\n\n/**\n * Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the\n * recommended secure store for **bare** React Native (no Expo). Encrypted at the\n * OS level, appropriate for long-lived refresh tokens.\n *\n * Keychain's API is service-based rather than key/value, so each storage `key`\n * maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`).\n * The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session`\n * for per-account isolation), so each session is one service entry.\n *\n * ```ts\n * import * as Keychain from 'react-native-keychain';\n * <ZyphrAuthProvider client={client} storage={keychainAdapter(Keychain)} />\n * ```\n *\n * `react-native-keychain` is an OPTIONAL peer dependency — install it only if\n * you use this adapter.\n */\nexport function keychainAdapter(keychain: KeychainModule): TokenStorage {\n return {\n getItem: async (key) => {\n const result = await keychain.getGenericPassword({ service: key });\n return result ? result.password : null;\n },\n setItem: (key, value) =>\n keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => undefined),\n removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => undefined),\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zyphr-dev/auth-react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "React Native bindings for Zyphr auth — <ZyphrAuthProvider>, useSession, useAuth, with a secure-storage adapter and refresh-on-foreground. Built on @zyphr-dev/auth-core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"expo-secure-store": {
|
|
42
42
|
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"react-native-keychain": {
|
|
45
|
+
"optional": true
|
|
43
46
|
}
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { ZyphrAuthProvider, useSession, useAuth } from './provider.js';
|
|
|
11
11
|
export type { ZyphrAuthProviderProps, UseAuthResult } from './provider.js';
|
|
12
12
|
|
|
13
13
|
export { createZyphrTransport } from './transport.js';
|
|
14
|
-
export { secureStoreAdapter, asyncStorageAdapter } from './storage.js';
|
|
14
|
+
export { secureStoreAdapter, asyncStorageAdapter, keychainAdapter } from './storage.js';
|
|
15
|
+
export type { KeychainModule } from './storage.js';
|
|
15
16
|
|
|
16
17
|
export type { SessionState, SessionStatus, LoginParams, TokenStorage } from '@zyphr-dev/auth-core';
|
package/src/provider.tsx
CHANGED
|
@@ -13,7 +13,6 @@ import { AppState, type AppStateStatus } from 'react-native';
|
|
|
13
13
|
import type { ZyphrClient } from '@zyphr-dev/node-sdk';
|
|
14
14
|
import {
|
|
15
15
|
SessionManager,
|
|
16
|
-
MemoryTokenStorage,
|
|
17
16
|
type LoginParams,
|
|
18
17
|
type SessionState,
|
|
19
18
|
type TokenStorage,
|
|
@@ -30,6 +29,12 @@ export interface ZyphrAuthProviderProps {
|
|
|
30
29
|
* on app restart), which is fine only for prototyping.
|
|
31
30
|
*/
|
|
32
31
|
storage?: TokenStorage;
|
|
32
|
+
/**
|
|
33
|
+
* Namespace the storage key so multiple providers can share one storage
|
|
34
|
+
* adapter without colliding (e.g. one session per account for account
|
|
35
|
+
* switching). The effective key becomes `${keyPrefix}zyphr.session`. Default `''`.
|
|
36
|
+
*/
|
|
37
|
+
keyPrefix?: string;
|
|
33
38
|
/** Refresh this many ms before the access token expires. Default 60_000. */
|
|
34
39
|
refreshSkewMs?: number;
|
|
35
40
|
/**
|
|
@@ -56,6 +61,7 @@ const ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);
|
|
|
56
61
|
export function ZyphrAuthProvider({
|
|
57
62
|
client,
|
|
58
63
|
storage,
|
|
64
|
+
keyPrefix,
|
|
59
65
|
refreshSkewMs,
|
|
60
66
|
refreshOnForeground = true,
|
|
61
67
|
children,
|
|
@@ -64,7 +70,11 @@ export function ZyphrAuthProvider({
|
|
|
64
70
|
() =>
|
|
65
71
|
new SessionManager({
|
|
66
72
|
transport: createZyphrTransport(client),
|
|
67
|
-
storage:
|
|
73
|
+
// Pass storage through as-is: auth-core applies the in-memory default
|
|
74
|
+
// AND emits the dev-mode "no storage" warning when it's omitted. Don't
|
|
75
|
+
// default it here or that warning would never fire for RN consumers.
|
|
76
|
+
storage,
|
|
77
|
+
keyPrefix,
|
|
68
78
|
refreshSkewMs,
|
|
69
79
|
}),
|
|
70
80
|
);
|
package/src/storage.ts
CHANGED
|
@@ -37,3 +37,49 @@ export function asyncStorageAdapter(asyncStorage: {
|
|
|
37
37
|
removeItem: (key) => asyncStorage.removeItem(key),
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/** The subset of `react-native-keychain` this adapter uses. */
|
|
42
|
+
export interface KeychainModule {
|
|
43
|
+
setGenericPassword(
|
|
44
|
+
username: string,
|
|
45
|
+
password: string,
|
|
46
|
+
options?: { service?: string },
|
|
47
|
+
): Promise<unknown>;
|
|
48
|
+
getGenericPassword(
|
|
49
|
+
options?: { service?: string },
|
|
50
|
+
): Promise<false | { username: string; password: string }>;
|
|
51
|
+
resetGenericPassword(options?: { service?: string }): Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Fixed username — the session lives in the `password` field; only one entry per service. */
|
|
55
|
+
const KEYCHAIN_USER = 'zyphr';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Adapt `react-native-keychain` (iOS Keychain / Android Keystore) — the
|
|
59
|
+
* recommended secure store for **bare** React Native (no Expo). Encrypted at the
|
|
60
|
+
* OS level, appropriate for long-lived refresh tokens.
|
|
61
|
+
*
|
|
62
|
+
* Keychain's API is service-based rather than key/value, so each storage `key`
|
|
63
|
+
* maps to its own keychain **service** (`setGenericPassword(user, value, { service: key })`).
|
|
64
|
+
* The core only ever uses a single key (`zyphr.session`, or `${keyPrefix}zyphr.session`
|
|
65
|
+
* for per-account isolation), so each session is one service entry.
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* import * as Keychain from 'react-native-keychain';
|
|
69
|
+
* <ZyphrAuthProvider client={client} storage={keychainAdapter(Keychain)} />
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* `react-native-keychain` is an OPTIONAL peer dependency — install it only if
|
|
73
|
+
* you use this adapter.
|
|
74
|
+
*/
|
|
75
|
+
export function keychainAdapter(keychain: KeychainModule): TokenStorage {
|
|
76
|
+
return {
|
|
77
|
+
getItem: async (key) => {
|
|
78
|
+
const result = await keychain.getGenericPassword({ service: key });
|
|
79
|
+
return result ? result.password : null;
|
|
80
|
+
},
|
|
81
|
+
setItem: (key, value) =>
|
|
82
|
+
keychain.setGenericPassword(KEYCHAIN_USER, value, { service: key }).then(() => undefined),
|
|
83
|
+
removeItem: (key) => keychain.resetGenericPassword({ service: key }).then(() => undefined),
|
|
84
|
+
};
|
|
85
|
+
}
|