@zyphr-dev/auth-react-native 0.1.0

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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @zyphr-dev/auth-react-native
2
+
3
+ React Native bindings for [Zyphr](https://zyphr.dev) auth. Same reactive session +
4
+ proactive token refresh as the web binding, plus a **secure-storage adapter** and
5
+ **refresh-on-foreground**.
6
+
7
+ - `<ZyphrAuthProvider>` — provides the session; refreshes when the app returns to the foreground.
8
+ - `useSession()` — reactive `{ status, accessToken, isAuthenticated }`.
9
+ - `useAuth()` — `login`, `logout`, `refresh`.
10
+
11
+ Built on [`@zyphr-dev/auth-core`](https://zyphr.dev/docs/sdks/auth) and the
12
+ client-safe `ZyphrClient` (publishable key).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm i @zyphr-dev/auth-react-native @zyphr-dev/node-sdk
18
+ # plus your secure store (recommended):
19
+ npx expo install expo-secure-store
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```tsx
25
+ import * as SecureStore from 'expo-secure-store';
26
+ import { ZyphrClient } from '@zyphr-dev/node-sdk';
27
+ import {
28
+ ZyphrAuthProvider,
29
+ useSession,
30
+ useAuth,
31
+ secureStoreAdapter,
32
+ } from '@zyphr-dev/auth-react-native';
33
+
34
+ const client = new ZyphrClient({ applicationKey: 'za_pub_xxx' });
35
+
36
+ export default function App() {
37
+ return (
38
+ <ZyphrAuthProvider client={client} storage={secureStoreAdapter(SecureStore)}>
39
+ <Screen />
40
+ </ZyphrAuthProvider>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ```tsx
46
+ function Screen() {
47
+ const { status, isAuthenticated } = useSession();
48
+ const { login, logout } = useAuth();
49
+ // render based on status / isAuthenticated
50
+ }
51
+ ```
52
+
53
+ ## Token storage
54
+
55
+ Pass a secure store — `secureStoreAdapter(SecureStore)` (encrypted; recommended)
56
+ or `asyncStorageAdapter(AsyncStorage)` (not encrypted; use only if you already
57
+ standardize on it). Both are optional peer dependencies — install whichever you
58
+ use. The default (no `storage`) is in-memory and does not survive an app restart,
59
+ so it's for prototyping only.
60
+
61
+ ## Refresh on foreground
62
+
63
+ A backgrounded app's scheduled refresh timer may not fire reliably, so the
64
+ provider also refreshes when the app returns to `active` (via React Native's
65
+ `AppState`). Disable with `refreshOnForeground={false}`.
package/dist/index.cjs ADDED
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ZyphrAuthProvider: () => ZyphrAuthProvider,
24
+ asyncStorageAdapter: () => asyncStorageAdapter,
25
+ createZyphrTransport: () => createZyphrTransport,
26
+ secureStoreAdapter: () => secureStoreAdapter,
27
+ useAuth: () => useAuth,
28
+ useSession: () => useSession
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/provider.tsx
33
+ var import_react = require("react");
34
+ var import_react_native = require("react-native");
35
+ var import_auth_core2 = require("@zyphr-dev/auth-core");
36
+
37
+ // src/transport.ts
38
+ var import_auth_core = require("@zyphr-dev/auth-core");
39
+ function createZyphrTransport(client, now = Date.now) {
40
+ const extract = (tokens) => (0, import_auth_core.toSessionTokens)(
41
+ { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },
42
+ now()
43
+ );
44
+ return {
45
+ async login({ email, password }) {
46
+ const res = await client.auth.login.loginEndUser({ email, password });
47
+ return extract(res.data?.tokens);
48
+ },
49
+ async refresh(refreshToken) {
50
+ const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });
51
+ return extract(res.data?.tokens);
52
+ },
53
+ async logout(refreshToken) {
54
+ await client.auth.sessions.revokeEndUserSession({ refreshToken });
55
+ }
56
+ };
57
+ }
58
+
59
+ // src/provider.tsx
60
+ var import_jsx_runtime = require("react/jsx-runtime");
61
+ var ZyphrAuthContext = (0, import_react.createContext)(null);
62
+ function ZyphrAuthProvider({
63
+ client,
64
+ storage,
65
+ refreshSkewMs,
66
+ refreshOnForeground = true,
67
+ children
68
+ }) {
69
+ const [manager] = (0, import_react.useState)(
70
+ () => new import_auth_core2.SessionManager({
71
+ transport: createZyphrTransport(client),
72
+ storage: storage ?? new import_auth_core2.MemoryTokenStorage(),
73
+ refreshSkewMs
74
+ })
75
+ );
76
+ const initialized = (0, import_react.useRef)(false);
77
+ (0, import_react.useEffect)(() => {
78
+ if (!initialized.current) {
79
+ initialized.current = true;
80
+ void manager.initialize();
81
+ }
82
+ return () => {
83
+ manager.destroy();
84
+ };
85
+ }, [manager]);
86
+ (0, import_react.useEffect)(() => {
87
+ if (!refreshOnForeground || !import_react_native.AppState?.addEventListener) return;
88
+ const onChange = (next) => {
89
+ if (next === "active" && manager.getState().isAuthenticated) {
90
+ void manager.refresh();
91
+ }
92
+ };
93
+ const sub = import_react_native.AppState.addEventListener("change", onChange);
94
+ return () => {
95
+ sub?.remove?.();
96
+ };
97
+ }, [manager, refreshOnForeground]);
98
+ const value = (0, import_react.useMemo)(() => ({ manager }), [manager]);
99
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZyphrAuthContext.Provider, { value, children });
100
+ }
101
+ function useManager() {
102
+ const ctx = (0, import_react.useContext)(ZyphrAuthContext);
103
+ if (!ctx) {
104
+ throw new Error("useSession/useAuth must be used within a <ZyphrAuthProvider>.");
105
+ }
106
+ return ctx.manager;
107
+ }
108
+ function useSession() {
109
+ const manager = useManager();
110
+ return (0, import_react.useSyncExternalStore)(
111
+ (onChange) => manager.subscribe(() => onChange()),
112
+ () => manager.getState(),
113
+ () => manager.getState()
114
+ );
115
+ }
116
+ function useAuth() {
117
+ const manager = useManager();
118
+ return (0, import_react.useMemo)(
119
+ () => ({
120
+ login: (params) => manager.login(params),
121
+ logout: () => manager.logout(),
122
+ refresh: () => manager.refresh()
123
+ }),
124
+ [manager]
125
+ );
126
+ }
127
+
128
+ // src/storage.ts
129
+ function secureStoreAdapter(secureStore) {
130
+ return {
131
+ getItem: (key) => secureStore.getItemAsync(key),
132
+ setItem: (key, value) => secureStore.setItemAsync(key, value),
133
+ removeItem: (key) => secureStore.deleteItemAsync(key)
134
+ };
135
+ }
136
+ function asyncStorageAdapter(asyncStorage) {
137
+ return {
138
+ getItem: (key) => asyncStorage.getItem(key),
139
+ setItem: (key, value) => asyncStorage.setItem(key, value),
140
+ removeItem: (key) => asyncStorage.removeItem(key)
141
+ };
142
+ }
143
+ // Annotate the CommonJS export names for ESM import in node:
144
+ 0 && (module.exports = {
145
+ ZyphrAuthProvider,
146
+ asyncStorageAdapter,
147
+ createZyphrTransport,
148
+ secureStoreAdapter,
149
+ useAuth,
150
+ useSession
151
+ });
152
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,75 @@
1
+ import { ReactNode, ReactElement } from 'react';
2
+ import { ZyphrClient } from '@zyphr-dev/node-sdk';
3
+ import { LoginParams, TokenStorage, SessionState, AuthTransport } from '@zyphr-dev/auth-core';
4
+ export { LoginParams, SessionState, SessionStatus, TokenStorage } from '@zyphr-dev/auth-core';
5
+
6
+ interface ZyphrAuthProviderProps {
7
+ /** A client-safe ZyphrClient (constructed with your publishable application key). */
8
+ client: ZyphrClient;
9
+ /**
10
+ * Where tokens persist. Strongly recommended in production — pass a secure
11
+ * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or
12
+ * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost
13
+ * on app restart), which is fine only for prototyping.
14
+ */
15
+ storage?: TokenStorage;
16
+ /** Refresh this many ms before the access token expires. Default 60_000. */
17
+ refreshSkewMs?: number;
18
+ /**
19
+ * Refresh the session when the app returns to the foreground (default true).
20
+ * A backgrounded app's scheduled refresh timer may not fire reliably, so we
21
+ * also refresh on foreground to keep the token fresh.
22
+ */
23
+ refreshOnForeground?: boolean;
24
+ children: ReactNode;
25
+ }
26
+ /**
27
+ * Provides a reactive Zyphr auth session to a React Native app. Same session
28
+ * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground
29
+ * via React Native's AppState. Read state with {@link useSession}, act with
30
+ * {@link useAuth}.
31
+ */
32
+ declare function ZyphrAuthProvider({ client, storage, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
33
+ /** Reactive session state — re-renders on login, logout, refresh. */
34
+ declare function useSession(): SessionState;
35
+ interface UseAuthResult {
36
+ login: (params: LoginParams) => Promise<void>;
37
+ logout: () => Promise<void>;
38
+ refresh: () => Promise<void>;
39
+ }
40
+ /** Auth actions bound to the provider's session manager. */
41
+ declare function useAuth(): UseAuthResult;
42
+
43
+ /**
44
+ * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable
45
+ * application key — no secret in the app bundle). Same transport the web binding
46
+ * uses; re-implemented here so the React Native package doesn't depend on
47
+ * `@zyphr-dev/auth-react` (which pulls web-React types).
48
+ */
49
+ declare function createZyphrTransport(client: ZyphrClient, now?: () => number): AuthTransport;
50
+
51
+ /**
52
+ * The minimal async key/value shape common to React Native secure stores —
53
+ * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and
54
+ * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both
55
+ * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes
56
+ * whichever it uses, so we don't force a native module on every consumer.
57
+ */
58
+ /** Adapt `expo-secure-store` (the recommended secure default). */
59
+ declare function secureStoreAdapter(secureStore: {
60
+ getItemAsync(key: string): Promise<string | null>;
61
+ setItemAsync(key: string, value: string): Promise<void>;
62
+ deleteItemAsync(key: string): Promise<void>;
63
+ }): TokenStorage;
64
+ /**
65
+ * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT
66
+ * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps
67
+ * that already standardize on AsyncStorage and accept the tradeoff.
68
+ */
69
+ declare function asyncStorageAdapter(asyncStorage: {
70
+ getItem(key: string): Promise<string | null>;
71
+ setItem(key: string, value: string): Promise<void>;
72
+ removeItem(key: string): Promise<void>;
73
+ }): TokenStorage;
74
+
75
+ export { type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, secureStoreAdapter, useAuth, useSession };
@@ -0,0 +1,75 @@
1
+ import { ReactNode, ReactElement } from 'react';
2
+ import { ZyphrClient } from '@zyphr-dev/node-sdk';
3
+ import { LoginParams, TokenStorage, SessionState, AuthTransport } from '@zyphr-dev/auth-core';
4
+ export { LoginParams, SessionState, SessionStatus, TokenStorage } from '@zyphr-dev/auth-core';
5
+
6
+ interface ZyphrAuthProviderProps {
7
+ /** A client-safe ZyphrClient (constructed with your publishable application key). */
8
+ client: ZyphrClient;
9
+ /**
10
+ * Where tokens persist. Strongly recommended in production — pass a secure
11
+ * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or
12
+ * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost
13
+ * on app restart), which is fine only for prototyping.
14
+ */
15
+ storage?: TokenStorage;
16
+ /** Refresh this many ms before the access token expires. Default 60_000. */
17
+ refreshSkewMs?: number;
18
+ /**
19
+ * Refresh the session when the app returns to the foreground (default true).
20
+ * A backgrounded app's scheduled refresh timer may not fire reliably, so we
21
+ * also refresh on foreground to keep the token fresh.
22
+ */
23
+ refreshOnForeground?: boolean;
24
+ children: ReactNode;
25
+ }
26
+ /**
27
+ * Provides a reactive Zyphr auth session to a React Native app. Same session
28
+ * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground
29
+ * via React Native's AppState. Read state with {@link useSession}, act with
30
+ * {@link useAuth}.
31
+ */
32
+ declare function ZyphrAuthProvider({ client, storage, refreshSkewMs, refreshOnForeground, children, }: ZyphrAuthProviderProps): ReactElement;
33
+ /** Reactive session state — re-renders on login, logout, refresh. */
34
+ declare function useSession(): SessionState;
35
+ interface UseAuthResult {
36
+ login: (params: LoginParams) => Promise<void>;
37
+ logout: () => Promise<void>;
38
+ refresh: () => Promise<void>;
39
+ }
40
+ /** Auth actions bound to the provider's session manager. */
41
+ declare function useAuth(): UseAuthResult;
42
+
43
+ /**
44
+ * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable
45
+ * application key — no secret in the app bundle). Same transport the web binding
46
+ * uses; re-implemented here so the React Native package doesn't depend on
47
+ * `@zyphr-dev/auth-react` (which pulls web-React types).
48
+ */
49
+ declare function createZyphrTransport(client: ZyphrClient, now?: () => number): AuthTransport;
50
+
51
+ /**
52
+ * The minimal async key/value shape common to React Native secure stores —
53
+ * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and
54
+ * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both
55
+ * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes
56
+ * whichever it uses, so we don't force a native module on every consumer.
57
+ */
58
+ /** Adapt `expo-secure-store` (the recommended secure default). */
59
+ declare function secureStoreAdapter(secureStore: {
60
+ getItemAsync(key: string): Promise<string | null>;
61
+ setItemAsync(key: string, value: string): Promise<void>;
62
+ deleteItemAsync(key: string): Promise<void>;
63
+ }): TokenStorage;
64
+ /**
65
+ * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT
66
+ * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps
67
+ * that already standardize on AsyncStorage and accept the tradeoff.
68
+ */
69
+ declare function asyncStorageAdapter(asyncStorage: {
70
+ getItem(key: string): Promise<string | null>;
71
+ setItem(key: string, value: string): Promise<void>;
72
+ removeItem(key: string): Promise<void>;
73
+ }): TokenStorage;
74
+
75
+ export { type UseAuthResult, ZyphrAuthProvider, type ZyphrAuthProviderProps, asyncStorageAdapter, createZyphrTransport, secureStoreAdapter, useAuth, useSession };
package/dist/index.js ADDED
@@ -0,0 +1,131 @@
1
+ // src/provider.tsx
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ useSyncExternalStore
10
+ } from "react";
11
+ import { AppState } from "react-native";
12
+ import {
13
+ SessionManager,
14
+ MemoryTokenStorage
15
+ } from "@zyphr-dev/auth-core";
16
+
17
+ // src/transport.ts
18
+ import { toSessionTokens } from "@zyphr-dev/auth-core";
19
+ function createZyphrTransport(client, now = Date.now) {
20
+ const extract = (tokens) => toSessionTokens(
21
+ { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },
22
+ now()
23
+ );
24
+ return {
25
+ async login({ email, password }) {
26
+ const res = await client.auth.login.loginEndUser({ email, password });
27
+ return extract(res.data?.tokens);
28
+ },
29
+ async refresh(refreshToken) {
30
+ const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });
31
+ return extract(res.data?.tokens);
32
+ },
33
+ async logout(refreshToken) {
34
+ await client.auth.sessions.revokeEndUserSession({ refreshToken });
35
+ }
36
+ };
37
+ }
38
+
39
+ // src/provider.tsx
40
+ import { jsx } from "react/jsx-runtime";
41
+ var ZyphrAuthContext = createContext(null);
42
+ function ZyphrAuthProvider({
43
+ client,
44
+ storage,
45
+ refreshSkewMs,
46
+ refreshOnForeground = true,
47
+ children
48
+ }) {
49
+ const [manager] = useState(
50
+ () => new SessionManager({
51
+ transport: createZyphrTransport(client),
52
+ storage: storage ?? new MemoryTokenStorage(),
53
+ refreshSkewMs
54
+ })
55
+ );
56
+ const initialized = useRef(false);
57
+ useEffect(() => {
58
+ if (!initialized.current) {
59
+ initialized.current = true;
60
+ void manager.initialize();
61
+ }
62
+ return () => {
63
+ manager.destroy();
64
+ };
65
+ }, [manager]);
66
+ useEffect(() => {
67
+ if (!refreshOnForeground || !AppState?.addEventListener) return;
68
+ const onChange = (next) => {
69
+ if (next === "active" && manager.getState().isAuthenticated) {
70
+ void manager.refresh();
71
+ }
72
+ };
73
+ const sub = AppState.addEventListener("change", onChange);
74
+ return () => {
75
+ sub?.remove?.();
76
+ };
77
+ }, [manager, refreshOnForeground]);
78
+ const value = useMemo(() => ({ manager }), [manager]);
79
+ return /* @__PURE__ */ jsx(ZyphrAuthContext.Provider, { value, children });
80
+ }
81
+ function useManager() {
82
+ const ctx = useContext(ZyphrAuthContext);
83
+ if (!ctx) {
84
+ throw new Error("useSession/useAuth must be used within a <ZyphrAuthProvider>.");
85
+ }
86
+ return ctx.manager;
87
+ }
88
+ function useSession() {
89
+ const manager = useManager();
90
+ return useSyncExternalStore(
91
+ (onChange) => manager.subscribe(() => onChange()),
92
+ () => manager.getState(),
93
+ () => manager.getState()
94
+ );
95
+ }
96
+ function useAuth() {
97
+ const manager = useManager();
98
+ return useMemo(
99
+ () => ({
100
+ login: (params) => manager.login(params),
101
+ logout: () => manager.logout(),
102
+ refresh: () => manager.refresh()
103
+ }),
104
+ [manager]
105
+ );
106
+ }
107
+
108
+ // src/storage.ts
109
+ function secureStoreAdapter(secureStore) {
110
+ return {
111
+ getItem: (key) => secureStore.getItemAsync(key),
112
+ setItem: (key, value) => secureStore.setItemAsync(key, value),
113
+ removeItem: (key) => secureStore.deleteItemAsync(key)
114
+ };
115
+ }
116
+ function asyncStorageAdapter(asyncStorage) {
117
+ return {
118
+ getItem: (key) => asyncStorage.getItem(key),
119
+ setItem: (key, value) => asyncStorage.setItem(key, value),
120
+ removeItem: (key) => asyncStorage.removeItem(key)
121
+ };
122
+ }
123
+ export {
124
+ ZyphrAuthProvider,
125
+ asyncStorageAdapter,
126
+ createZyphrTransport,
127
+ secureStoreAdapter,
128
+ useAuth,
129
+ useSession
130
+ };
131
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@zyphr-dev/auth-react-native",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "sideEffects": false,
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "README.md"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "lint": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "clean": "rm -rf dist"
32
+ },
33
+ "peerDependencies": {
34
+ "react": ">=17.0.0",
35
+ "react-native": ">=0.64.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@react-native-async-storage/async-storage": {
39
+ "optional": true
40
+ },
41
+ "expo-secure-store": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "dependencies": {
46
+ "@zyphr-dev/auth-core": "^0.1.0",
47
+ "@zyphr-dev/node-sdk": "^0.1.42"
48
+ },
49
+ "devDependencies": {
50
+ "@testing-library/react": "^16.0.0",
51
+ "@types/react": "^18.3.0",
52
+ "jsdom": "^25.0.0",
53
+ "react": "^18.3.0",
54
+ "react-dom": "^18.3.0",
55
+ "react-native": "^0.75.0",
56
+ "tsup": "^8.5.1",
57
+ "typescript": "^5.3.0",
58
+ "vitest": "^4.0.18"
59
+ },
60
+ "keywords": [
61
+ "zyphr",
62
+ "auth",
63
+ "react-native",
64
+ "session",
65
+ "useSession",
66
+ "expo",
67
+ "secure-store"
68
+ ],
69
+ "license": "MIT",
70
+ "homepage": "https://zyphr.dev/docs/sdks/auth",
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/ArcRaz/zyphr.git",
74
+ "directory": "packages/auth-react-native"
75
+ },
76
+ "engines": {
77
+ "node": ">=18.0.0"
78
+ }
79
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @zyphr-dev/auth-react-native
3
+ *
4
+ * React Native bindings for Zyphr auth. Wrap your app in <ZyphrAuthProvider> and
5
+ * read the session with useSession() / act with useAuth(). Same session engine as
6
+ * the web binding (@zyphr-dev/auth-core), plus a secure-storage adapter and
7
+ * refresh-on-foreground via React Native's AppState.
8
+ */
9
+
10
+ export { ZyphrAuthProvider, useSession, useAuth } from './provider.js';
11
+ export type { ZyphrAuthProviderProps, UseAuthResult } from './provider.js';
12
+
13
+ export { createZyphrTransport } from './transport.js';
14
+ export { secureStoreAdapter, asyncStorageAdapter } from './storage.js';
15
+
16
+ export type { SessionState, SessionStatus, LoginParams, TokenStorage } from '@zyphr-dev/auth-core';
@@ -0,0 +1,137 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ useSyncExternalStore,
9
+ type ReactElement,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { AppState, type AppStateStatus } from 'react-native';
13
+ import type { ZyphrClient } from '@zyphr-dev/node-sdk';
14
+ import {
15
+ SessionManager,
16
+ MemoryTokenStorage,
17
+ type LoginParams,
18
+ type SessionState,
19
+ type TokenStorage,
20
+ } from '@zyphr-dev/auth-core';
21
+ import { createZyphrTransport } from './transport.js';
22
+
23
+ export interface ZyphrAuthProviderProps {
24
+ /** A client-safe ZyphrClient (constructed with your publishable application key). */
25
+ client: ZyphrClient;
26
+ /**
27
+ * Where tokens persist. Strongly recommended in production — pass a secure
28
+ * store via `secureStoreAdapter(SecureStore)` (expo-secure-store) or
29
+ * `asyncStorageAdapter(AsyncStorage)`. Defaults to in-memory (tokens are lost
30
+ * on app restart), which is fine only for prototyping.
31
+ */
32
+ storage?: TokenStorage;
33
+ /** Refresh this many ms before the access token expires. Default 60_000. */
34
+ refreshSkewMs?: number;
35
+ /**
36
+ * Refresh the session when the app returns to the foreground (default true).
37
+ * A backgrounded app's scheduled refresh timer may not fire reliably, so we
38
+ * also refresh on foreground to keep the token fresh.
39
+ */
40
+ refreshOnForeground?: boolean;
41
+ children: ReactNode;
42
+ }
43
+
44
+ interface ZyphrAuthContextValue {
45
+ manager: SessionManager;
46
+ }
47
+
48
+ const ZyphrAuthContext = createContext<ZyphrAuthContextValue | null>(null);
49
+
50
+ /**
51
+ * Provides a reactive Zyphr auth session to a React Native app. Same session
52
+ * engine as the web binding (@zyphr-dev/auth-core), plus refresh-on-foreground
53
+ * via React Native's AppState. Read state with {@link useSession}, act with
54
+ * {@link useAuth}.
55
+ */
56
+ export function ZyphrAuthProvider({
57
+ client,
58
+ storage,
59
+ refreshSkewMs,
60
+ refreshOnForeground = true,
61
+ children,
62
+ }: ZyphrAuthProviderProps): ReactElement {
63
+ const [manager] = useState(
64
+ () =>
65
+ new SessionManager({
66
+ transport: createZyphrTransport(client),
67
+ storage: storage ?? new MemoryTokenStorage(),
68
+ refreshSkewMs,
69
+ }),
70
+ );
71
+
72
+ const initialized = useRef(false);
73
+ useEffect(() => {
74
+ if (!initialized.current) {
75
+ initialized.current = true;
76
+ void manager.initialize();
77
+ }
78
+ return () => {
79
+ manager.destroy();
80
+ };
81
+ }, [manager]);
82
+
83
+ // Refresh when the app returns to the foreground.
84
+ useEffect(() => {
85
+ if (!refreshOnForeground || !AppState?.addEventListener) return;
86
+ const onChange = (next: AppStateStatus) => {
87
+ if (next === 'active' && manager.getState().isAuthenticated) {
88
+ void manager.refresh();
89
+ }
90
+ };
91
+ const sub = AppState.addEventListener('change', onChange);
92
+ return () => {
93
+ // RN >= 0.65 returns a subscription with .remove(); older returns void.
94
+ (sub as { remove?: () => void } | undefined)?.remove?.();
95
+ };
96
+ }, [manager, refreshOnForeground]);
97
+
98
+ const value = useMemo(() => ({ manager }), [manager]);
99
+ return <ZyphrAuthContext.Provider value={value}>{children}</ZyphrAuthContext.Provider>;
100
+ }
101
+
102
+ function useManager(): SessionManager {
103
+ const ctx = useContext(ZyphrAuthContext);
104
+ if (!ctx) {
105
+ throw new Error('useSession/useAuth must be used within a <ZyphrAuthProvider>.');
106
+ }
107
+ return ctx.manager;
108
+ }
109
+
110
+ /** Reactive session state — re-renders on login, logout, refresh. */
111
+ export function useSession(): SessionState {
112
+ const manager = useManager();
113
+ return useSyncExternalStore(
114
+ (onChange) => manager.subscribe(() => onChange()),
115
+ () => manager.getState(),
116
+ () => manager.getState(),
117
+ );
118
+ }
119
+
120
+ export interface UseAuthResult {
121
+ login: (params: LoginParams) => Promise<void>;
122
+ logout: () => Promise<void>;
123
+ refresh: () => Promise<void>;
124
+ }
125
+
126
+ /** Auth actions bound to the provider's session manager. */
127
+ export function useAuth(): UseAuthResult {
128
+ const manager = useManager();
129
+ return useMemo(
130
+ () => ({
131
+ login: (params) => manager.login(params),
132
+ logout: () => manager.logout(),
133
+ refresh: () => manager.refresh(),
134
+ }),
135
+ [manager],
136
+ );
137
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { TokenStorage } from '@zyphr-dev/auth-core';
2
+
3
+ /**
4
+ * The minimal async key/value shape common to React Native secure stores —
5
+ * `expo-secure-store` (getItemAsync/setItemAsync/deleteItemAsync) and
6
+ * `@react-native-async-storage/async-storage` (getItem/setItem/removeItem) both
7
+ * fit one of these once wrapped. We keep the dependency OPTIONAL: the app passes
8
+ * whichever it uses, so we don't force a native module on every consumer.
9
+ */
10
+
11
+ /** Adapt `expo-secure-store` (the recommended secure default). */
12
+ export function secureStoreAdapter(secureStore: {
13
+ getItemAsync(key: string): Promise<string | null>;
14
+ setItemAsync(key: string, value: string): Promise<void>;
15
+ deleteItemAsync(key: string): Promise<void>;
16
+ }): TokenStorage {
17
+ return {
18
+ getItem: (key) => secureStore.getItemAsync(key),
19
+ setItem: (key, value) => secureStore.setItemAsync(key, value),
20
+ removeItem: (key) => secureStore.deleteItemAsync(key),
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Adapt `@react-native-async-storage/async-storage`. NOTE: AsyncStorage is NOT
26
+ * encrypted — prefer {@link secureStoreAdapter} for tokens. Provided for apps
27
+ * that already standardize on AsyncStorage and accept the tradeoff.
28
+ */
29
+ export function asyncStorageAdapter(asyncStorage: {
30
+ getItem(key: string): Promise<string | null>;
31
+ setItem(key: string, value: string): Promise<void>;
32
+ removeItem(key: string): Promise<void>;
33
+ }): TokenStorage {
34
+ return {
35
+ getItem: (key) => asyncStorage.getItem(key),
36
+ setItem: (key, value) => asyncStorage.setItem(key, value),
37
+ removeItem: (key) => asyncStorage.removeItem(key),
38
+ };
39
+ }
@@ -0,0 +1,30 @@
1
+ import type { ZyphrClient } from '@zyphr-dev/node-sdk';
2
+ import { type AuthTransport, type LoginParams, type SessionTokens, toSessionTokens } from '@zyphr-dev/auth-core';
3
+
4
+ /**
5
+ * An {@link AuthTransport} over the client-safe `ZyphrClient` (publishable
6
+ * application key — no secret in the app bundle). Same transport the web binding
7
+ * uses; re-implemented here so the React Native package doesn't depend on
8
+ * `@zyphr-dev/auth-react` (which pulls web-React types).
9
+ */
10
+ export function createZyphrTransport(client: ZyphrClient, now: () => number = Date.now): AuthTransport {
11
+ const extract = (tokens?: { accessToken?: string; refreshToken?: string; expiresIn?: number }): SessionTokens =>
12
+ toSessionTokens(
13
+ { accessToken: tokens?.accessToken, refreshToken: tokens?.refreshToken, expiresIn: tokens?.expiresIn },
14
+ now(),
15
+ );
16
+
17
+ return {
18
+ async login({ email, password }: LoginParams): Promise<SessionTokens> {
19
+ const res = await client.auth.login.loginEndUser({ email, password });
20
+ return extract(res.data?.tokens);
21
+ },
22
+ async refresh(refreshToken: string): Promise<SessionTokens> {
23
+ const res = await client.auth.sessions.refreshEndUserToken({ refreshToken });
24
+ return extract(res.data?.tokens);
25
+ },
26
+ async logout(refreshToken: string): Promise<void> {
27
+ await client.auth.sessions.revokeEndUserSession({ refreshToken });
28
+ },
29
+ };
30
+ }