@auth-gate/react-native 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,403 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AuthGateError: () => import_core2.AuthGateError,
34
+ AuthGateNativeClient: () => AuthGateNativeClient,
35
+ AuthGuard: () => AuthGuard,
36
+ AuthProvider: () => AuthProvider,
37
+ ConfigurationError: () => import_core2.ConfigurationError,
38
+ STORAGE_KEYS: () => STORAGE_KEYS,
39
+ SUPPORTED_PROVIDERS: () => import_core2.SUPPORTED_PROVIDERS,
40
+ TokenVerificationError: () => import_core2.TokenVerificationError,
41
+ useAuth: () => useAuth,
42
+ useSession: () => useSession
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/provider.tsx
47
+ var import_react = require("react");
48
+
49
+ // src/client.ts
50
+ var import_core = require("@auth-gate/core");
51
+
52
+ // src/storage.ts
53
+ var SecureStore = __toESM(require("expo-secure-store"), 1);
54
+
55
+ // src/types.ts
56
+ var STORAGE_KEYS = {
57
+ ACCESS_TOKEN: "authgate_access_token",
58
+ REFRESH_TOKEN: "authgate_refresh_token",
59
+ USER: "authgate_user"
60
+ };
61
+
62
+ // src/storage.ts
63
+ async function saveAccessToken(token) {
64
+ await SecureStore.setItemAsync(STORAGE_KEYS.ACCESS_TOKEN, token);
65
+ }
66
+ async function getAccessToken() {
67
+ return SecureStore.getItemAsync(STORAGE_KEYS.ACCESS_TOKEN);
68
+ }
69
+ async function saveRefreshToken(token) {
70
+ await SecureStore.setItemAsync(STORAGE_KEYS.REFRESH_TOKEN, token);
71
+ }
72
+ async function getRefreshToken() {
73
+ return SecureStore.getItemAsync(STORAGE_KEYS.REFRESH_TOKEN);
74
+ }
75
+ async function saveUser(user) {
76
+ await SecureStore.setItemAsync(STORAGE_KEYS.USER, JSON.stringify(user));
77
+ }
78
+ async function clearStorage() {
79
+ await Promise.all([
80
+ SecureStore.deleteItemAsync(STORAGE_KEYS.ACCESS_TOKEN),
81
+ SecureStore.deleteItemAsync(STORAGE_KEYS.REFRESH_TOKEN),
82
+ SecureStore.deleteItemAsync(STORAGE_KEYS.USER)
83
+ ]);
84
+ }
85
+
86
+ // src/oauth.ts
87
+ var WebBrowser = __toESM(require("expo-web-browser"), 1);
88
+ async function openOAuthFlow(client, provider, scheme, callbackPath) {
89
+ const callbackUrl = `${scheme}://${callbackPath}`;
90
+ const authUrl = await client.getOAuthUrl(provider, callbackUrl);
91
+ const result = await WebBrowser.openAuthSessionAsync(authUrl, callbackUrl);
92
+ if (result.type !== "success" || !result.url) {
93
+ throw new Error(`OAuth flow cancelled or failed: ${result.type}`);
94
+ }
95
+ const url = new URL(result.url);
96
+ const token = url.searchParams.get("token");
97
+ const refreshToken = url.searchParams.get("refresh_token");
98
+ if (!token) {
99
+ const error = url.searchParams.get("error");
100
+ throw new Error(error || "No token received from OAuth callback");
101
+ }
102
+ return { token, refreshToken: refreshToken != null ? refreshToken : void 0 };
103
+ }
104
+
105
+ // src/client.ts
106
+ var AuthGateNativeClient = class {
107
+ constructor(config) {
108
+ this.refreshTimer = null;
109
+ this.onSessionChange = null;
110
+ var _a;
111
+ this.core = (0, import_core.createAuthGateClient)({
112
+ apiKey: config.apiKey,
113
+ baseUrl: config.baseUrl,
114
+ sessionMaxAge: config.sessionMaxAge
115
+ });
116
+ this.scheme = config.scheme;
117
+ this.callbackPath = (_a = config.callbackPath) != null ? _a : "auth/callback";
118
+ }
119
+ /** Register a callback to be notified when the session changes. */
120
+ setSessionChangeListener(listener) {
121
+ this.onSessionChange = listener;
122
+ }
123
+ /**
124
+ * Restore session from secure storage on app launch.
125
+ * Verifies the stored token is still valid.
126
+ */
127
+ async restoreSession() {
128
+ const token = await getAccessToken();
129
+ if (!token) return null;
130
+ const result = await this.core.verifyToken(token);
131
+ if (result.valid && result.user) {
132
+ await saveUser(result.user);
133
+ this.scheduleRefresh(result.expiresAt);
134
+ return result.user;
135
+ }
136
+ return this.tryRefresh();
137
+ }
138
+ /** Start an OAuth flow in the system browser. */
139
+ async loginWithOAuth(provider) {
140
+ const result = await openOAuthFlow(
141
+ this.core,
142
+ provider,
143
+ this.scheme,
144
+ this.callbackPath
145
+ );
146
+ await this.handleAuthResult(result);
147
+ }
148
+ /** Sign in with email and password. May return MFA challenge. */
149
+ async loginWithEmail(email, password) {
150
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
151
+ const response = await this.core.emailSignin({
152
+ email,
153
+ password,
154
+ callbackUrl
155
+ });
156
+ const data = await response.json();
157
+ if (data.mfa_required) {
158
+ return {
159
+ mfaRequired: true,
160
+ challenge: data.mfa_challenge,
161
+ methods: data.mfa_methods
162
+ };
163
+ }
164
+ if (data.token) {
165
+ await this.handleAuthResult({
166
+ token: data.token,
167
+ refreshToken: data.refresh_token
168
+ });
169
+ } else if (!response.ok) {
170
+ throw new Error(data.error || `Sign in failed: ${response.status}`);
171
+ }
172
+ }
173
+ /** Sign up with email and password. */
174
+ async signUp(email, password, name) {
175
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
176
+ const response = await this.core.emailSignup({
177
+ email,
178
+ password,
179
+ name,
180
+ callbackUrl
181
+ });
182
+ const data = await response.json();
183
+ if (data.token) {
184
+ await this.handleAuthResult({
185
+ token: data.token,
186
+ refreshToken: data.refresh_token
187
+ });
188
+ } else if (!response.ok) {
189
+ throw new Error(data.error || `Sign up failed: ${response.status}`);
190
+ }
191
+ }
192
+ /** Send a magic link email. */
193
+ async sendMagicLink(email) {
194
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
195
+ const response = await this.core.magicLinkSend({ email, callbackUrl });
196
+ if (!response.ok) {
197
+ const data = await response.json().catch(() => ({}));
198
+ throw new Error(data.error || `Magic link failed: ${response.status}`);
199
+ }
200
+ }
201
+ /** Send an SMS verification code. */
202
+ async sendSmsCode(phone) {
203
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
204
+ const response = await this.core.smsSendCode({ phone, callbackUrl });
205
+ if (!response.ok) {
206
+ const data = await response.json().catch(() => ({}));
207
+ throw new Error(data.error || `SMS send failed: ${response.status}`);
208
+ }
209
+ }
210
+ /** Verify an SMS code and complete authentication. */
211
+ async verifySmsCode(phone, code) {
212
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
213
+ const response = await this.core.smsVerifyCode({
214
+ phone,
215
+ code,
216
+ callbackUrl
217
+ });
218
+ const data = await response.json();
219
+ if (data.token) {
220
+ await this.handleAuthResult({
221
+ token: data.token,
222
+ refreshToken: data.refresh_token
223
+ });
224
+ } else if (!response.ok) {
225
+ throw new Error(data.error || `SMS verify failed: ${response.status}`);
226
+ }
227
+ }
228
+ /** Verify MFA code to complete authentication after email sign-in. */
229
+ async verifyMfa(challenge, code, method) {
230
+ const response = await this.core.mfaVerify({ challenge, code, method });
231
+ const data = await response.json();
232
+ if (data.token) {
233
+ await this.handleAuthResult({
234
+ token: data.token,
235
+ refreshToken: data.refresh_token
236
+ });
237
+ } else if (!response.ok) {
238
+ throw new Error(data.error || `MFA verify failed: ${response.status}`);
239
+ }
240
+ }
241
+ /** Sign out: revoke session and clear storage. */
242
+ async logout() {
243
+ var _a;
244
+ this.cancelRefresh();
245
+ const refreshToken = await getRefreshToken();
246
+ if (refreshToken) {
247
+ await this.core.revokeSession(refreshToken).catch(() => {
248
+ });
249
+ }
250
+ await clearStorage();
251
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
252
+ }
253
+ /** Manually refresh the session using the stored refresh token. */
254
+ async refresh() {
255
+ return this.tryRefresh();
256
+ }
257
+ // ── Internal ─────────────────────────────────────────────────────
258
+ async handleAuthResult(result) {
259
+ var _a;
260
+ const verified = await this.core.verifyToken(result.token);
261
+ if (!verified.valid || !verified.user) {
262
+ throw new Error(verified.error || "Token verification failed");
263
+ }
264
+ await saveAccessToken(result.token);
265
+ if (result.refreshToken) {
266
+ await saveRefreshToken(result.refreshToken);
267
+ }
268
+ await saveUser(verified.user);
269
+ this.scheduleRefresh(verified.expiresAt);
270
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, verified.user);
271
+ }
272
+ async tryRefresh() {
273
+ var _a, _b, _c;
274
+ const refreshToken = await getRefreshToken();
275
+ if (!refreshToken) {
276
+ await clearStorage();
277
+ return null;
278
+ }
279
+ const result = await this.core.refreshToken(refreshToken);
280
+ if (!result) {
281
+ await clearStorage();
282
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
283
+ return null;
284
+ }
285
+ await saveAccessToken(result.token);
286
+ const verified = await this.core.verifyToken(result.token);
287
+ if (verified.valid && verified.user) {
288
+ await saveUser(verified.user);
289
+ this.scheduleRefresh(verified.expiresAt);
290
+ (_b = this.onSessionChange) == null ? void 0 : _b.call(this, verified.user);
291
+ return verified.user;
292
+ }
293
+ await clearStorage();
294
+ (_c = this.onSessionChange) == null ? void 0 : _c.call(this, null);
295
+ return null;
296
+ }
297
+ scheduleRefresh(expiresAt) {
298
+ this.cancelRefresh();
299
+ if (!expiresAt) return;
300
+ const expiresMs = new Date(expiresAt).getTime();
301
+ const refreshIn = Math.max(expiresMs - Date.now() - 6e4, 1e4);
302
+ this.refreshTimer = setTimeout(() => {
303
+ this.tryRefresh();
304
+ }, refreshIn);
305
+ }
306
+ cancelRefresh() {
307
+ if (this.refreshTimer) {
308
+ clearTimeout(this.refreshTimer);
309
+ this.refreshTimer = null;
310
+ }
311
+ }
312
+ };
313
+
314
+ // src/provider.tsx
315
+ var import_jsx_runtime = require("react/jsx-runtime");
316
+ var AuthContext = (0, import_react.createContext)(null);
317
+ function AuthProvider({ children, config }) {
318
+ const [user, setUser] = (0, import_react.useState)(null);
319
+ const [loading, setLoading] = (0, import_react.useState)(true);
320
+ const clientRef = (0, import_react.useRef)(null);
321
+ if (!clientRef.current) {
322
+ clientRef.current = new AuthGateNativeClient(config);
323
+ }
324
+ const client = clientRef.current;
325
+ (0, import_react.useEffect)(() => {
326
+ client.setSessionChangeListener(setUser);
327
+ return () => client.setSessionChangeListener(() => {
328
+ });
329
+ }, [client]);
330
+ (0, import_react.useEffect)(() => {
331
+ client.restoreSession().then((restored) => setUser(restored)).finally(() => setLoading(false));
332
+ }, [client]);
333
+ const refresh = (0, import_react.useCallback)(async () => {
334
+ const refreshed = await client.refresh();
335
+ setUser(refreshed);
336
+ }, [client]);
337
+ const logout = (0, import_react.useCallback)(async () => {
338
+ await client.logout();
339
+ setUser(null);
340
+ }, [client]);
341
+ const login = (0, import_react.useMemo)(
342
+ () => ({
343
+ withOAuth: (provider) => client.loginWithOAuth(provider),
344
+ withEmail: (email, password) => client.loginWithEmail(email, password),
345
+ withMagicLink: (email) => client.sendMagicLink(email),
346
+ signUp: (email, password, name) => client.signUp(email, password, name),
347
+ withSms: (phone) => client.sendSmsCode(phone),
348
+ verifySmsCode: (phone, code) => client.verifySmsCode(phone, code),
349
+ verifyMfa: (challenge, code, method) => client.verifyMfa(challenge, code, method)
350
+ }),
351
+ [client]
352
+ );
353
+ const value = (0, import_react.useMemo)(
354
+ () => ({ user, loading, login, logout, refresh }),
355
+ [user, loading, login, logout, refresh]
356
+ );
357
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext, { value, children });
358
+ }
359
+ function useAuthContext() {
360
+ const context = (0, import_react.useContext)(AuthContext);
361
+ if (!context) {
362
+ throw new Error("useAuth must be used within an AuthProvider");
363
+ }
364
+ return context;
365
+ }
366
+
367
+ // src/hooks.ts
368
+ function useAuth() {
369
+ return useAuthContext();
370
+ }
371
+ function useSession() {
372
+ const { user } = useAuthContext();
373
+ return user;
374
+ }
375
+
376
+ // src/guard.tsx
377
+ var import_jsx_runtime2 = require("react/jsx-runtime");
378
+ function AuthGuard({ children, fallback, loading }) {
379
+ const { user, loading: isLoading } = useAuthContext();
380
+ if (isLoading) {
381
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loading != null ? loading : null });
382
+ }
383
+ if (!user) {
384
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
385
+ }
386
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
387
+ }
388
+
389
+ // src/index.ts
390
+ var import_core2 = require("@auth-gate/core");
391
+ // Annotate the CommonJS export names for ESM import in node:
392
+ 0 && (module.exports = {
393
+ AuthGateError,
394
+ AuthGateNativeClient,
395
+ AuthGuard,
396
+ AuthProvider,
397
+ ConfigurationError,
398
+ STORAGE_KEYS,
399
+ SUPPORTED_PROVIDERS,
400
+ TokenVerificationError,
401
+ useAuth,
402
+ useSession
403
+ });
@@ -0,0 +1,128 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { AuthGateUser, OAuthProvider, AuthGateClient } from '@auth-gate/core';
4
+ export { AuthGateError, AuthGateUser, BackupCodesResponse, ConfigurationError, MfaChallengeResponse, MfaStatus, OAuthProvider, SUPPORTED_PROVIDERS, TokenVerificationError, TotpSetupResponse } from '@auth-gate/core';
5
+
6
+ /** Configuration for the React Native AuthGate SDK. */
7
+ interface AuthGateNativeConfig {
8
+ /** Server-side API key for your AuthGate project. */
9
+ apiKey: string;
10
+ /** Base URL of the AuthGate service. */
11
+ baseUrl: string;
12
+ /** Deep link scheme for OAuth callbacks (e.g. "myapp"). */
13
+ scheme: string;
14
+ /**
15
+ * Session lifetime in seconds.
16
+ * @defaultValue 604800 (7 days)
17
+ */
18
+ sessionMaxAge?: number;
19
+ /** Custom callback path fragment appended to the deep link scheme. @defaultValue "auth/callback" */
20
+ callbackPath?: string;
21
+ }
22
+ /** Login methods exposed via the useAuth hook. */
23
+ interface LoginMethods {
24
+ /** Start an OAuth flow in the system browser. */
25
+ withOAuth: (provider: OAuthProvider) => Promise<void>;
26
+ /** Sign in with email and password. Returns MFA challenge if required. */
27
+ withEmail: (email: string, password: string) => Promise<MfaChallengeResult | void>;
28
+ /** Send a magic link to the given email. */
29
+ withMagicLink: (email: string) => Promise<void>;
30
+ /** Sign up with email, password, and optional name. */
31
+ signUp: (email: string, password: string, name?: string) => Promise<void>;
32
+ /** Send an SMS verification code. */
33
+ withSms: (phone: string) => Promise<void>;
34
+ /** Verify an SMS code. */
35
+ verifySmsCode: (phone: string, code: string) => Promise<void>;
36
+ /** Complete MFA verification. */
37
+ verifyMfa: (challenge: string, code: string, method: "totp" | "sms" | "backup_code") => Promise<void>;
38
+ }
39
+ /** Value returned by useAuth(). */
40
+ interface AuthContextValue {
41
+ user: AuthGateUser | null;
42
+ loading: boolean;
43
+ login: LoginMethods;
44
+ logout: () => Promise<void>;
45
+ refresh: () => Promise<void>;
46
+ }
47
+ /** Returned when MFA is required after email sign-in. */
48
+ interface MfaChallengeResult {
49
+ mfaRequired: true;
50
+ challenge: string;
51
+ methods: string[];
52
+ }
53
+ /** Keys used for secure storage. */
54
+ declare const STORAGE_KEYS: {
55
+ readonly ACCESS_TOKEN: "authgate_access_token";
56
+ readonly REFRESH_TOKEN: "authgate_refresh_token";
57
+ readonly USER: "authgate_user";
58
+ };
59
+
60
+ interface AuthProviderProps {
61
+ children: ReactNode;
62
+ /** AuthGate configuration including API key, base URL, and deep link scheme. */
63
+ config: AuthGateNativeConfig;
64
+ }
65
+ declare function AuthProvider({ children, config }: AuthProviderProps): react_jsx_runtime.JSX.Element;
66
+
67
+ /** Access the full auth context: user, loading, login methods, logout, refresh. */
68
+ declare function useAuth(): AuthContextValue;
69
+ /** Shorthand hook that returns just the current user (or null). */
70
+ declare function useSession(): AuthGateUser | null;
71
+
72
+ interface AuthGuardProps {
73
+ children: ReactNode;
74
+ /** Content to render when the user is not authenticated. */
75
+ fallback: ReactNode;
76
+ /** Content to render while the session is loading. */
77
+ loading?: ReactNode;
78
+ }
79
+ /**
80
+ * Renders children when authenticated, fallback when not.
81
+ * Unlike the web AuthGuard, this doesn't redirect — it renders the fallback component
82
+ * (typically a login screen) since mobile navigation works differently.
83
+ */
84
+ declare function AuthGuard({ children, fallback, loading }: AuthGuardProps): react_jsx_runtime.JSX.Element;
85
+
86
+ /**
87
+ * Native client that wraps core AuthGateClient with mobile-specific
88
+ * storage and OAuth flows. Manages tokens in secure storage instead of cookies.
89
+ */
90
+ declare class AuthGateNativeClient {
91
+ readonly core: AuthGateClient;
92
+ private readonly scheme;
93
+ private readonly callbackPath;
94
+ private refreshTimer;
95
+ private onSessionChange;
96
+ constructor(config: AuthGateNativeConfig);
97
+ /** Register a callback to be notified when the session changes. */
98
+ setSessionChangeListener(listener: (user: AuthGateUser | null) => void): void;
99
+ /**
100
+ * Restore session from secure storage on app launch.
101
+ * Verifies the stored token is still valid.
102
+ */
103
+ restoreSession(): Promise<AuthGateUser | null>;
104
+ /** Start an OAuth flow in the system browser. */
105
+ loginWithOAuth(provider: OAuthProvider): Promise<void>;
106
+ /** Sign in with email and password. May return MFA challenge. */
107
+ loginWithEmail(email: string, password: string): Promise<MfaChallengeResult | void>;
108
+ /** Sign up with email and password. */
109
+ signUp(email: string, password: string, name?: string): Promise<void>;
110
+ /** Send a magic link email. */
111
+ sendMagicLink(email: string): Promise<void>;
112
+ /** Send an SMS verification code. */
113
+ sendSmsCode(phone: string): Promise<void>;
114
+ /** Verify an SMS code and complete authentication. */
115
+ verifySmsCode(phone: string, code: string): Promise<void>;
116
+ /** Verify MFA code to complete authentication after email sign-in. */
117
+ verifyMfa(challenge: string, code: string, method: "totp" | "sms" | "backup_code"): Promise<void>;
118
+ /** Sign out: revoke session and clear storage. */
119
+ logout(): Promise<void>;
120
+ /** Manually refresh the session using the stored refresh token. */
121
+ refresh(): Promise<AuthGateUser | null>;
122
+ private handleAuthResult;
123
+ private tryRefresh;
124
+ private scheduleRefresh;
125
+ private cancelRefresh;
126
+ }
127
+
128
+ export { type AuthContextValue, AuthGateNativeClient, type AuthGateNativeConfig, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type LoginMethods, type MfaChallengeResult, STORAGE_KEYS, useAuth, useSession };
@@ -0,0 +1,128 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { AuthGateUser, OAuthProvider, AuthGateClient } from '@auth-gate/core';
4
+ export { AuthGateError, AuthGateUser, BackupCodesResponse, ConfigurationError, MfaChallengeResponse, MfaStatus, OAuthProvider, SUPPORTED_PROVIDERS, TokenVerificationError, TotpSetupResponse } from '@auth-gate/core';
5
+
6
+ /** Configuration for the React Native AuthGate SDK. */
7
+ interface AuthGateNativeConfig {
8
+ /** Server-side API key for your AuthGate project. */
9
+ apiKey: string;
10
+ /** Base URL of the AuthGate service. */
11
+ baseUrl: string;
12
+ /** Deep link scheme for OAuth callbacks (e.g. "myapp"). */
13
+ scheme: string;
14
+ /**
15
+ * Session lifetime in seconds.
16
+ * @defaultValue 604800 (7 days)
17
+ */
18
+ sessionMaxAge?: number;
19
+ /** Custom callback path fragment appended to the deep link scheme. @defaultValue "auth/callback" */
20
+ callbackPath?: string;
21
+ }
22
+ /** Login methods exposed via the useAuth hook. */
23
+ interface LoginMethods {
24
+ /** Start an OAuth flow in the system browser. */
25
+ withOAuth: (provider: OAuthProvider) => Promise<void>;
26
+ /** Sign in with email and password. Returns MFA challenge if required. */
27
+ withEmail: (email: string, password: string) => Promise<MfaChallengeResult | void>;
28
+ /** Send a magic link to the given email. */
29
+ withMagicLink: (email: string) => Promise<void>;
30
+ /** Sign up with email, password, and optional name. */
31
+ signUp: (email: string, password: string, name?: string) => Promise<void>;
32
+ /** Send an SMS verification code. */
33
+ withSms: (phone: string) => Promise<void>;
34
+ /** Verify an SMS code. */
35
+ verifySmsCode: (phone: string, code: string) => Promise<void>;
36
+ /** Complete MFA verification. */
37
+ verifyMfa: (challenge: string, code: string, method: "totp" | "sms" | "backup_code") => Promise<void>;
38
+ }
39
+ /** Value returned by useAuth(). */
40
+ interface AuthContextValue {
41
+ user: AuthGateUser | null;
42
+ loading: boolean;
43
+ login: LoginMethods;
44
+ logout: () => Promise<void>;
45
+ refresh: () => Promise<void>;
46
+ }
47
+ /** Returned when MFA is required after email sign-in. */
48
+ interface MfaChallengeResult {
49
+ mfaRequired: true;
50
+ challenge: string;
51
+ methods: string[];
52
+ }
53
+ /** Keys used for secure storage. */
54
+ declare const STORAGE_KEYS: {
55
+ readonly ACCESS_TOKEN: "authgate_access_token";
56
+ readonly REFRESH_TOKEN: "authgate_refresh_token";
57
+ readonly USER: "authgate_user";
58
+ };
59
+
60
+ interface AuthProviderProps {
61
+ children: ReactNode;
62
+ /** AuthGate configuration including API key, base URL, and deep link scheme. */
63
+ config: AuthGateNativeConfig;
64
+ }
65
+ declare function AuthProvider({ children, config }: AuthProviderProps): react_jsx_runtime.JSX.Element;
66
+
67
+ /** Access the full auth context: user, loading, login methods, logout, refresh. */
68
+ declare function useAuth(): AuthContextValue;
69
+ /** Shorthand hook that returns just the current user (or null). */
70
+ declare function useSession(): AuthGateUser | null;
71
+
72
+ interface AuthGuardProps {
73
+ children: ReactNode;
74
+ /** Content to render when the user is not authenticated. */
75
+ fallback: ReactNode;
76
+ /** Content to render while the session is loading. */
77
+ loading?: ReactNode;
78
+ }
79
+ /**
80
+ * Renders children when authenticated, fallback when not.
81
+ * Unlike the web AuthGuard, this doesn't redirect — it renders the fallback component
82
+ * (typically a login screen) since mobile navigation works differently.
83
+ */
84
+ declare function AuthGuard({ children, fallback, loading }: AuthGuardProps): react_jsx_runtime.JSX.Element;
85
+
86
+ /**
87
+ * Native client that wraps core AuthGateClient with mobile-specific
88
+ * storage and OAuth flows. Manages tokens in secure storage instead of cookies.
89
+ */
90
+ declare class AuthGateNativeClient {
91
+ readonly core: AuthGateClient;
92
+ private readonly scheme;
93
+ private readonly callbackPath;
94
+ private refreshTimer;
95
+ private onSessionChange;
96
+ constructor(config: AuthGateNativeConfig);
97
+ /** Register a callback to be notified when the session changes. */
98
+ setSessionChangeListener(listener: (user: AuthGateUser | null) => void): void;
99
+ /**
100
+ * Restore session from secure storage on app launch.
101
+ * Verifies the stored token is still valid.
102
+ */
103
+ restoreSession(): Promise<AuthGateUser | null>;
104
+ /** Start an OAuth flow in the system browser. */
105
+ loginWithOAuth(provider: OAuthProvider): Promise<void>;
106
+ /** Sign in with email and password. May return MFA challenge. */
107
+ loginWithEmail(email: string, password: string): Promise<MfaChallengeResult | void>;
108
+ /** Sign up with email and password. */
109
+ signUp(email: string, password: string, name?: string): Promise<void>;
110
+ /** Send a magic link email. */
111
+ sendMagicLink(email: string): Promise<void>;
112
+ /** Send an SMS verification code. */
113
+ sendSmsCode(phone: string): Promise<void>;
114
+ /** Verify an SMS code and complete authentication. */
115
+ verifySmsCode(phone: string, code: string): Promise<void>;
116
+ /** Verify MFA code to complete authentication after email sign-in. */
117
+ verifyMfa(challenge: string, code: string, method: "totp" | "sms" | "backup_code"): Promise<void>;
118
+ /** Sign out: revoke session and clear storage. */
119
+ logout(): Promise<void>;
120
+ /** Manually refresh the session using the stored refresh token. */
121
+ refresh(): Promise<AuthGateUser | null>;
122
+ private handleAuthResult;
123
+ private tryRefresh;
124
+ private scheduleRefresh;
125
+ private cancelRefresh;
126
+ }
127
+
128
+ export { type AuthContextValue, AuthGateNativeClient, type AuthGateNativeConfig, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type LoginMethods, type MfaChallengeResult, STORAGE_KEYS, useAuth, useSession };
package/dist/index.mjs ADDED
@@ -0,0 +1,372 @@
1
+ // src/provider.tsx
2
+ import {
3
+ createContext,
4
+ useContext,
5
+ useState,
6
+ useEffect,
7
+ useCallback,
8
+ useRef,
9
+ useMemo
10
+ } from "react";
11
+
12
+ // src/client.ts
13
+ import {
14
+ createAuthGateClient
15
+ } from "@auth-gate/core";
16
+
17
+ // src/storage.ts
18
+ import * as SecureStore from "expo-secure-store";
19
+
20
+ // src/types.ts
21
+ var STORAGE_KEYS = {
22
+ ACCESS_TOKEN: "authgate_access_token",
23
+ REFRESH_TOKEN: "authgate_refresh_token",
24
+ USER: "authgate_user"
25
+ };
26
+
27
+ // src/storage.ts
28
+ async function saveAccessToken(token) {
29
+ await SecureStore.setItemAsync(STORAGE_KEYS.ACCESS_TOKEN, token);
30
+ }
31
+ async function getAccessToken() {
32
+ return SecureStore.getItemAsync(STORAGE_KEYS.ACCESS_TOKEN);
33
+ }
34
+ async function saveRefreshToken(token) {
35
+ await SecureStore.setItemAsync(STORAGE_KEYS.REFRESH_TOKEN, token);
36
+ }
37
+ async function getRefreshToken() {
38
+ return SecureStore.getItemAsync(STORAGE_KEYS.REFRESH_TOKEN);
39
+ }
40
+ async function saveUser(user) {
41
+ await SecureStore.setItemAsync(STORAGE_KEYS.USER, JSON.stringify(user));
42
+ }
43
+ async function clearStorage() {
44
+ await Promise.all([
45
+ SecureStore.deleteItemAsync(STORAGE_KEYS.ACCESS_TOKEN),
46
+ SecureStore.deleteItemAsync(STORAGE_KEYS.REFRESH_TOKEN),
47
+ SecureStore.deleteItemAsync(STORAGE_KEYS.USER)
48
+ ]);
49
+ }
50
+
51
+ // src/oauth.ts
52
+ import * as WebBrowser from "expo-web-browser";
53
+ async function openOAuthFlow(client, provider, scheme, callbackPath) {
54
+ const callbackUrl = `${scheme}://${callbackPath}`;
55
+ const authUrl = await client.getOAuthUrl(provider, callbackUrl);
56
+ const result = await WebBrowser.openAuthSessionAsync(authUrl, callbackUrl);
57
+ if (result.type !== "success" || !result.url) {
58
+ throw new Error(`OAuth flow cancelled or failed: ${result.type}`);
59
+ }
60
+ const url = new URL(result.url);
61
+ const token = url.searchParams.get("token");
62
+ const refreshToken = url.searchParams.get("refresh_token");
63
+ if (!token) {
64
+ const error = url.searchParams.get("error");
65
+ throw new Error(error || "No token received from OAuth callback");
66
+ }
67
+ return { token, refreshToken: refreshToken != null ? refreshToken : void 0 };
68
+ }
69
+
70
+ // src/client.ts
71
+ var AuthGateNativeClient = class {
72
+ constructor(config) {
73
+ this.refreshTimer = null;
74
+ this.onSessionChange = null;
75
+ var _a;
76
+ this.core = createAuthGateClient({
77
+ apiKey: config.apiKey,
78
+ baseUrl: config.baseUrl,
79
+ sessionMaxAge: config.sessionMaxAge
80
+ });
81
+ this.scheme = config.scheme;
82
+ this.callbackPath = (_a = config.callbackPath) != null ? _a : "auth/callback";
83
+ }
84
+ /** Register a callback to be notified when the session changes. */
85
+ setSessionChangeListener(listener) {
86
+ this.onSessionChange = listener;
87
+ }
88
+ /**
89
+ * Restore session from secure storage on app launch.
90
+ * Verifies the stored token is still valid.
91
+ */
92
+ async restoreSession() {
93
+ const token = await getAccessToken();
94
+ if (!token) return null;
95
+ const result = await this.core.verifyToken(token);
96
+ if (result.valid && result.user) {
97
+ await saveUser(result.user);
98
+ this.scheduleRefresh(result.expiresAt);
99
+ return result.user;
100
+ }
101
+ return this.tryRefresh();
102
+ }
103
+ /** Start an OAuth flow in the system browser. */
104
+ async loginWithOAuth(provider) {
105
+ const result = await openOAuthFlow(
106
+ this.core,
107
+ provider,
108
+ this.scheme,
109
+ this.callbackPath
110
+ );
111
+ await this.handleAuthResult(result);
112
+ }
113
+ /** Sign in with email and password. May return MFA challenge. */
114
+ async loginWithEmail(email, password) {
115
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
116
+ const response = await this.core.emailSignin({
117
+ email,
118
+ password,
119
+ callbackUrl
120
+ });
121
+ const data = await response.json();
122
+ if (data.mfa_required) {
123
+ return {
124
+ mfaRequired: true,
125
+ challenge: data.mfa_challenge,
126
+ methods: data.mfa_methods
127
+ };
128
+ }
129
+ if (data.token) {
130
+ await this.handleAuthResult({
131
+ token: data.token,
132
+ refreshToken: data.refresh_token
133
+ });
134
+ } else if (!response.ok) {
135
+ throw new Error(data.error || `Sign in failed: ${response.status}`);
136
+ }
137
+ }
138
+ /** Sign up with email and password. */
139
+ async signUp(email, password, name) {
140
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
141
+ const response = await this.core.emailSignup({
142
+ email,
143
+ password,
144
+ name,
145
+ callbackUrl
146
+ });
147
+ const data = await response.json();
148
+ if (data.token) {
149
+ await this.handleAuthResult({
150
+ token: data.token,
151
+ refreshToken: data.refresh_token
152
+ });
153
+ } else if (!response.ok) {
154
+ throw new Error(data.error || `Sign up failed: ${response.status}`);
155
+ }
156
+ }
157
+ /** Send a magic link email. */
158
+ async sendMagicLink(email) {
159
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
160
+ const response = await this.core.magicLinkSend({ email, callbackUrl });
161
+ if (!response.ok) {
162
+ const data = await response.json().catch(() => ({}));
163
+ throw new Error(data.error || `Magic link failed: ${response.status}`);
164
+ }
165
+ }
166
+ /** Send an SMS verification code. */
167
+ async sendSmsCode(phone) {
168
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
169
+ const response = await this.core.smsSendCode({ phone, callbackUrl });
170
+ if (!response.ok) {
171
+ const data = await response.json().catch(() => ({}));
172
+ throw new Error(data.error || `SMS send failed: ${response.status}`);
173
+ }
174
+ }
175
+ /** Verify an SMS code and complete authentication. */
176
+ async verifySmsCode(phone, code) {
177
+ const callbackUrl = `${this.scheme}://${this.callbackPath}`;
178
+ const response = await this.core.smsVerifyCode({
179
+ phone,
180
+ code,
181
+ callbackUrl
182
+ });
183
+ const data = await response.json();
184
+ if (data.token) {
185
+ await this.handleAuthResult({
186
+ token: data.token,
187
+ refreshToken: data.refresh_token
188
+ });
189
+ } else if (!response.ok) {
190
+ throw new Error(data.error || `SMS verify failed: ${response.status}`);
191
+ }
192
+ }
193
+ /** Verify MFA code to complete authentication after email sign-in. */
194
+ async verifyMfa(challenge, code, method) {
195
+ const response = await this.core.mfaVerify({ challenge, code, method });
196
+ const data = await response.json();
197
+ if (data.token) {
198
+ await this.handleAuthResult({
199
+ token: data.token,
200
+ refreshToken: data.refresh_token
201
+ });
202
+ } else if (!response.ok) {
203
+ throw new Error(data.error || `MFA verify failed: ${response.status}`);
204
+ }
205
+ }
206
+ /** Sign out: revoke session and clear storage. */
207
+ async logout() {
208
+ var _a;
209
+ this.cancelRefresh();
210
+ const refreshToken = await getRefreshToken();
211
+ if (refreshToken) {
212
+ await this.core.revokeSession(refreshToken).catch(() => {
213
+ });
214
+ }
215
+ await clearStorage();
216
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
217
+ }
218
+ /** Manually refresh the session using the stored refresh token. */
219
+ async refresh() {
220
+ return this.tryRefresh();
221
+ }
222
+ // ── Internal ─────────────────────────────────────────────────────
223
+ async handleAuthResult(result) {
224
+ var _a;
225
+ const verified = await this.core.verifyToken(result.token);
226
+ if (!verified.valid || !verified.user) {
227
+ throw new Error(verified.error || "Token verification failed");
228
+ }
229
+ await saveAccessToken(result.token);
230
+ if (result.refreshToken) {
231
+ await saveRefreshToken(result.refreshToken);
232
+ }
233
+ await saveUser(verified.user);
234
+ this.scheduleRefresh(verified.expiresAt);
235
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, verified.user);
236
+ }
237
+ async tryRefresh() {
238
+ var _a, _b, _c;
239
+ const refreshToken = await getRefreshToken();
240
+ if (!refreshToken) {
241
+ await clearStorage();
242
+ return null;
243
+ }
244
+ const result = await this.core.refreshToken(refreshToken);
245
+ if (!result) {
246
+ await clearStorage();
247
+ (_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
248
+ return null;
249
+ }
250
+ await saveAccessToken(result.token);
251
+ const verified = await this.core.verifyToken(result.token);
252
+ if (verified.valid && verified.user) {
253
+ await saveUser(verified.user);
254
+ this.scheduleRefresh(verified.expiresAt);
255
+ (_b = this.onSessionChange) == null ? void 0 : _b.call(this, verified.user);
256
+ return verified.user;
257
+ }
258
+ await clearStorage();
259
+ (_c = this.onSessionChange) == null ? void 0 : _c.call(this, null);
260
+ return null;
261
+ }
262
+ scheduleRefresh(expiresAt) {
263
+ this.cancelRefresh();
264
+ if (!expiresAt) return;
265
+ const expiresMs = new Date(expiresAt).getTime();
266
+ const refreshIn = Math.max(expiresMs - Date.now() - 6e4, 1e4);
267
+ this.refreshTimer = setTimeout(() => {
268
+ this.tryRefresh();
269
+ }, refreshIn);
270
+ }
271
+ cancelRefresh() {
272
+ if (this.refreshTimer) {
273
+ clearTimeout(this.refreshTimer);
274
+ this.refreshTimer = null;
275
+ }
276
+ }
277
+ };
278
+
279
+ // src/provider.tsx
280
+ import { jsx } from "react/jsx-runtime";
281
+ var AuthContext = createContext(null);
282
+ function AuthProvider({ children, config }) {
283
+ const [user, setUser] = useState(null);
284
+ const [loading, setLoading] = useState(true);
285
+ const clientRef = useRef(null);
286
+ if (!clientRef.current) {
287
+ clientRef.current = new AuthGateNativeClient(config);
288
+ }
289
+ const client = clientRef.current;
290
+ useEffect(() => {
291
+ client.setSessionChangeListener(setUser);
292
+ return () => client.setSessionChangeListener(() => {
293
+ });
294
+ }, [client]);
295
+ useEffect(() => {
296
+ client.restoreSession().then((restored) => setUser(restored)).finally(() => setLoading(false));
297
+ }, [client]);
298
+ const refresh = useCallback(async () => {
299
+ const refreshed = await client.refresh();
300
+ setUser(refreshed);
301
+ }, [client]);
302
+ const logout = useCallback(async () => {
303
+ await client.logout();
304
+ setUser(null);
305
+ }, [client]);
306
+ const login = useMemo(
307
+ () => ({
308
+ withOAuth: (provider) => client.loginWithOAuth(provider),
309
+ withEmail: (email, password) => client.loginWithEmail(email, password),
310
+ withMagicLink: (email) => client.sendMagicLink(email),
311
+ signUp: (email, password, name) => client.signUp(email, password, name),
312
+ withSms: (phone) => client.sendSmsCode(phone),
313
+ verifySmsCode: (phone, code) => client.verifySmsCode(phone, code),
314
+ verifyMfa: (challenge, code, method) => client.verifyMfa(challenge, code, method)
315
+ }),
316
+ [client]
317
+ );
318
+ const value = useMemo(
319
+ () => ({ user, loading, login, logout, refresh }),
320
+ [user, loading, login, logout, refresh]
321
+ );
322
+ return /* @__PURE__ */ jsx(AuthContext, { value, children });
323
+ }
324
+ function useAuthContext() {
325
+ const context = useContext(AuthContext);
326
+ if (!context) {
327
+ throw new Error("useAuth must be used within an AuthProvider");
328
+ }
329
+ return context;
330
+ }
331
+
332
+ // src/hooks.ts
333
+ function useAuth() {
334
+ return useAuthContext();
335
+ }
336
+ function useSession() {
337
+ const { user } = useAuthContext();
338
+ return user;
339
+ }
340
+
341
+ // src/guard.tsx
342
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
343
+ function AuthGuard({ children, fallback, loading }) {
344
+ const { user, loading: isLoading } = useAuthContext();
345
+ if (isLoading) {
346
+ return /* @__PURE__ */ jsx2(Fragment, { children: loading != null ? loading : null });
347
+ }
348
+ if (!user) {
349
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
350
+ }
351
+ return /* @__PURE__ */ jsx2(Fragment, { children });
352
+ }
353
+
354
+ // src/index.ts
355
+ import {
356
+ AuthGateError,
357
+ TokenVerificationError,
358
+ ConfigurationError,
359
+ SUPPORTED_PROVIDERS
360
+ } from "@auth-gate/core";
361
+ export {
362
+ AuthGateError,
363
+ AuthGateNativeClient,
364
+ AuthGuard,
365
+ AuthProvider,
366
+ ConfigurationError,
367
+ STORAGE_KEYS,
368
+ SUPPORTED_PROVIDERS,
369
+ TokenVerificationError,
370
+ useAuth,
371
+ useSession
372
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@auth-gate/react-native",
3
+ "version": "0.7.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "import": "./dist/index.mjs",
9
+ "require": "./dist/index.cjs"
10
+ }
11
+ },
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@auth-gate/core": "0.7.1"
20
+ },
21
+ "peerDependencies": {
22
+ "react": ">=18",
23
+ "react-native": ">=0.72",
24
+ "expo-secure-store": ">=13",
25
+ "expo-web-browser": ">=13",
26
+ "expo-linking": ">=6"
27
+ },
28
+ "devDependencies": {
29
+ "react": "^19.0.0",
30
+ "react-native": "^0.76.0",
31
+ "@types/react": "^19",
32
+ "expo-secure-store": "^14.0.0",
33
+ "expo-web-browser": "^14.0.0",
34
+ "expo-linking": "^7.0.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5"
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "typecheck": "tsc --noEmit"
41
+ }
42
+ }