@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 +65 -0
- package/dist/index.cjs +152 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
- package/src/index.ts +16 -0
- package/src/provider.tsx +137 -0
- package/src/storage.ts +39 -0
- package/src/transport.ts +30 -0
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAUO;AACP,0BAA8C;AAE9C,IAAAA,oBAMO;;;AClBP,uBAA0F;AAQnF,SAAS,qBAAqB,QAAqB,MAAoB,KAAK,KAAoB;AACrG,QAAM,UAAU,CAAC,eACf;AAAA,IACE,EAAE,aAAa,QAAQ,aAAa,cAAc,QAAQ,cAAc,WAAW,QAAQ,UAAU;AAAA,IACrG,IAAI;AAAA,EACN;AAEF,SAAO;AAAA,IACL,MAAM,MAAM,EAAE,OAAO,SAAS,GAAwC;AACpE,YAAM,MAAM,MAAM,OAAO,KAAK,MAAM,aAAa,EAAE,OAAO,SAAS,CAAC;AACpE,aAAO,QAAQ,IAAI,MAAM,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,QAAQ,cAA8C;AAC1D,YAAM,MAAM,MAAM,OAAO,KAAK,SAAS,oBAAoB,EAAE,aAAa,CAAC;AAC3E,aAAO,QAAQ,IAAI,MAAM,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,OAAO,cAAqC;AAChD,YAAM,OAAO,KAAK,SAAS,qBAAqB,EAAE,aAAa,CAAC;AAAA,IAClE;AAAA,EACF;AACF;;;ADqES;AAnDT,IAAM,uBAAmB,4BAA4C,IAAI;AAQlE,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB;AACF,GAAyC;AACvC,QAAM,CAAC,OAAO,QAAI;AAAA,IAChB,MACE,IAAI,iCAAe;AAAA,MACjB,WAAW,qBAAqB,MAAM;AAAA,MACtC,SAAS,WAAW,IAAI,qCAAmB;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACL;AAEA,QAAM,kBAAc,qBAAO,KAAK;AAChC,8BAAU,MAAM;AACd,QAAI,CAAC,YAAY,SAAS;AACxB,kBAAY,UAAU;AACtB,WAAK,QAAQ,WAAW;AAAA,IAC1B;AACA,WAAO,MAAM;AACX,cAAQ,QAAQ;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAGZ,8BAAU,MAAM;AACd,QAAI,CAAC,uBAAuB,CAAC,8BAAU,iBAAkB;AACzD,UAAM,WAAW,CAAC,SAAyB;AACzC,UAAI,SAAS,YAAY,QAAQ,SAAS,EAAE,iBAAiB;AAC3D,aAAK,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACF;AACA,UAAM,MAAM,6BAAS,iBAAiB,UAAU,QAAQ;AACxD,WAAO,MAAM;AAEX,MAAC,KAA6C,SAAS;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,mBAAmB,CAAC;AAEjC,QAAM,YAAQ,sBAAQ,OAAO,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;AACpD,SAAO,4CAAC,iBAAiB,UAAjB,EAA0B,OAAe,UAAS;AAC5D;AAEA,SAAS,aAA6B;AACpC,QAAM,UAAM,yBAAW,gBAAgB;AACvC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,SAAO,IAAI;AACb;AAGO,SAAS,aAA2B;AACzC,QAAM,UAAU,WAAW;AAC3B,aAAO;AAAA,IACL,CAAC,aAAa,QAAQ,UAAU,MAAM,SAAS,CAAC;AAAA,IAChD,MAAM,QAAQ,SAAS;AAAA,IACvB,MAAM,QAAQ,SAAS;AAAA,EACzB;AACF;AASO,SAAS,UAAyB;AACvC,QAAM,UAAU,WAAW;AAC3B,aAAO;AAAA,IACL,OAAO;AAAA,MACL,OAAO,CAAC,WAAW,QAAQ,MAAM,MAAM;AAAA,MACvC,QAAQ,MAAM,QAAQ,OAAO;AAAA,MAC7B,SAAS,MAAM,QAAQ,QAAQ;AAAA,IACjC;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AACF;;;AE7HO,SAAS,mBAAmB,aAIlB;AACf,SAAO;AAAA,IACL,SAAS,CAAC,QAAQ,YAAY,aAAa,GAAG;AAAA,IAC9C,SAAS,CAAC,KAAK,UAAU,YAAY,aAAa,KAAK,KAAK;AAAA,IAC5D,YAAY,CAAC,QAAQ,YAAY,gBAAgB,GAAG;AAAA,EACtD;AACF;AAOO,SAAS,oBAAoB,cAInB;AACf,SAAO;AAAA,IACL,SAAS,CAAC,QAAQ,aAAa,QAAQ,GAAG;AAAA,IAC1C,SAAS,CAAC,KAAK,UAAU,aAAa,QAAQ,KAAK,KAAK;AAAA,IACxD,YAAY,CAAC,QAAQ,aAAa,WAAW,GAAG;AAAA,EAClD;AACF;","names":["import_auth_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,gBAAqC;AAE9C;AAAA,EACE;AAAA,EACA;AAAA,OAIK;;;AClBP,SAAmE,uBAAuB;AAQnF,SAAS,qBAAqB,QAAqB,MAAoB,KAAK,KAAoB;AACrG,QAAM,UAAU,CAAC,WACf;AAAA,IACE,EAAE,aAAa,QAAQ,aAAa,cAAc,QAAQ,cAAc,WAAW,QAAQ,UAAU;AAAA,IACrG,IAAI;AAAA,EACN;AAEF,SAAO;AAAA,IACL,MAAM,MAAM,EAAE,OAAO,SAAS,GAAwC;AACpE,YAAM,MAAM,MAAM,OAAO,KAAK,MAAM,aAAa,EAAE,OAAO,SAAS,CAAC;AACpE,aAAO,QAAQ,IAAI,MAAM,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,QAAQ,cAA8C;AAC1D,YAAM,MAAM,MAAM,OAAO,KAAK,SAAS,oBAAoB,EAAE,aAAa,CAAC;AAC3E,aAAO,QAAQ,IAAI,MAAM,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,OAAO,cAAqC;AAChD,YAAM,OAAO,KAAK,SAAS,qBAAqB,EAAE,aAAa,CAAC;AAAA,IAClE;AAAA,EACF;AACF;;;ADqES;AAnDT,IAAM,mBAAmB,cAA4C,IAAI;AAQlE,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB;AACF,GAAyC;AACvC,QAAM,CAAC,OAAO,IAAI;AAAA,IAChB,MACE,IAAI,eAAe;AAAA,MACjB,WAAW,qBAAqB,MAAM;AAAA,MACtC,SAAS,WAAW,IAAI,mBAAmB;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACL;AAEA,QAAM,cAAc,OAAO,KAAK;AAChC,YAAU,MAAM;AACd,QAAI,CAAC,YAAY,SAAS;AACxB,kBAAY,UAAU;AACtB,WAAK,QAAQ,WAAW;AAAA,IAC1B;AACA,WAAO,MAAM;AACX,cAAQ,QAAQ;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAGZ,YAAU,MAAM;AACd,QAAI,CAAC,uBAAuB,CAAC,UAAU,iBAAkB;AACzD,UAAM,WAAW,CAAC,SAAyB;AACzC,UAAI,SAAS,YAAY,QAAQ,SAAS,EAAE,iBAAiB;AAC3D,aAAK,QAAQ,QAAQ;AAAA,MACvB;AAAA,IACF;AACA,UAAM,MAAM,SAAS,iBAAiB,UAAU,QAAQ;AACxD,WAAO,MAAM;AAEX,MAAC,KAA6C,SAAS;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,mBAAmB,CAAC;AAEjC,QAAM,QAAQ,QAAQ,OAAO,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;AACpD,SAAO,oBAAC,iBAAiB,UAAjB,EAA0B,OAAe,UAAS;AAC5D;AAEA,SAAS,aAA6B;AACpC,QAAM,MAAM,WAAW,gBAAgB;AACvC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,SAAO,IAAI;AACb;AAGO,SAAS,aAA2B;AACzC,QAAM,UAAU,WAAW;AAC3B,SAAO;AAAA,IACL,CAAC,aAAa,QAAQ,UAAU,MAAM,SAAS,CAAC;AAAA,IAChD,MAAM,QAAQ,SAAS;AAAA,IACvB,MAAM,QAAQ,SAAS;AAAA,EACzB;AACF;AASO,SAAS,UAAyB;AACvC,QAAM,UAAU,WAAW;AAC3B,SAAO;AAAA,IACL,OAAO;AAAA,MACL,OAAO,CAAC,WAAW,QAAQ,MAAM,MAAM;AAAA,MACvC,QAAQ,MAAM,QAAQ,OAAO;AAAA,MAC7B,SAAS,MAAM,QAAQ,QAAQ;AAAA,IACjC;AAAA,IACA,CAAC,OAAO;AAAA,EACV;AACF;;;AE7HO,SAAS,mBAAmB,aAIlB;AACf,SAAO;AAAA,IACL,SAAS,CAAC,QAAQ,YAAY,aAAa,GAAG;AAAA,IAC9C,SAAS,CAAC,KAAK,UAAU,YAAY,aAAa,KAAK,KAAK;AAAA,IAC5D,YAAY,CAAC,QAAQ,YAAY,gBAAgB,GAAG;AAAA,EACtD;AACF;AAOO,SAAS,oBAAoB,cAInB;AACf,SAAO;AAAA,IACL,SAAS,CAAC,QAAQ,aAAa,QAAQ,GAAG;AAAA,IAC1C,SAAS,CAAC,KAAK,UAAU,aAAa,QAAQ,KAAK,KAAK;AAAA,IACxD,YAAY,CAAC,QAAQ,aAAa,WAAW,GAAG;AAAA,EAClD;AACF;","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';
|
package/src/provider.tsx
ADDED
|
@@ -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
|
+
}
|
package/src/transport.ts
ADDED
|
@@ -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
|
+
}
|