@imtbl/auth-next-client 2.12.5-alpha.13

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.
@@ -0,0 +1,501 @@
1
+ 'use client';
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var src_exports = {};
23
+ __export(src_exports, {
24
+ CallbackPage: () => CallbackPage,
25
+ ImmutableAuthProvider: () => ImmutableAuthProvider,
26
+ MarketingConsentStatus: () => import_auth4.MarketingConsentStatus,
27
+ useAccessToken: () => useAccessToken,
28
+ useHydratedData: () => useHydratedData,
29
+ useImmutableAuth: () => useImmutableAuth
30
+ });
31
+ module.exports = __toCommonJS(src_exports);
32
+
33
+ // src/provider.tsx
34
+ var import_react = require("react");
35
+ var import_react2 = require("next-auth/react");
36
+ var import_auth2 = require("@imtbl/auth");
37
+
38
+ // src/utils/token.ts
39
+ var import_auth = require("@imtbl/auth");
40
+
41
+ // src/constants.ts
42
+ var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
43
+ var DEFAULT_AUDIENCE = "platform_api";
44
+ var DEFAULT_SCOPE = "openid profile email offline_access transact";
45
+ var IMMUTABLE_PROVIDER_ID = "immutable";
46
+ var DEFAULT_NEXTAUTH_BASE_PATH = "/api/auth";
47
+ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
48
+ var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
49
+
50
+ // src/utils/token.ts
51
+ function getTokenExpiry(accessToken) {
52
+ if (!accessToken) {
53
+ return Date.now() + DEFAULT_TOKEN_EXPIRY_MS;
54
+ }
55
+ try {
56
+ const payload = (0, import_auth.decodeJwtPayload)(accessToken);
57
+ if (payload.exp && typeof payload.exp === "number") {
58
+ return payload.exp * 1e3;
59
+ }
60
+ return Date.now() + DEFAULT_TOKEN_EXPIRY_MS;
61
+ } catch {
62
+ return Date.now() + DEFAULT_TOKEN_EXPIRY_MS;
63
+ }
64
+ }
65
+
66
+ // src/provider.tsx
67
+ var import_jsx_runtime = require("react/jsx-runtime");
68
+ var ImmutableAuthContext = (0, import_react.createContext)(null);
69
+ function ImmutableAuthInner({
70
+ children,
71
+ config,
72
+ basePath
73
+ }) {
74
+ const [auth, setAuth] = (0, import_react.useState)(null);
75
+ const prevConfigRef = (0, import_react.useRef)(null);
76
+ const authInstanceRef = (0, import_react.useRef)(null);
77
+ const [isAuthReady, setIsAuthReady] = (0, import_react.useState)(false);
78
+ const { data: session, update: updateSession } = (0, import_react2.useSession)();
79
+ (0, import_react.useEffect)(() => {
80
+ if (typeof window === "undefined") return void 0;
81
+ const configKey = [
82
+ config.clientId,
83
+ config.redirectUri,
84
+ config.popupRedirectUri || "",
85
+ config.logoutRedirectUri || "",
86
+ config.audience || DEFAULT_AUDIENCE,
87
+ config.scope || DEFAULT_SCOPE,
88
+ config.authenticationDomain || DEFAULT_AUTH_DOMAIN,
89
+ config.passportDomain || ""
90
+ ].join(":");
91
+ if (prevConfigRef.current === configKey && authInstanceRef.current !== null) {
92
+ return void 0;
93
+ }
94
+ prevConfigRef.current = configKey;
95
+ const newAuth = new import_auth2.Auth({
96
+ clientId: config.clientId,
97
+ redirectUri: config.redirectUri,
98
+ popupRedirectUri: config.popupRedirectUri,
99
+ logoutRedirectUri: config.logoutRedirectUri,
100
+ audience: config.audience || DEFAULT_AUDIENCE,
101
+ scope: config.scope || DEFAULT_SCOPE,
102
+ authenticationDomain: config.authenticationDomain || DEFAULT_AUTH_DOMAIN,
103
+ passportDomain: config.passportDomain
104
+ });
105
+ authInstanceRef.current = newAuth;
106
+ setAuth(newAuth);
107
+ setIsAuthReady(true);
108
+ return () => {
109
+ authInstanceRef.current = null;
110
+ setAuth(null);
111
+ setIsAuthReady(false);
112
+ };
113
+ }, [config]);
114
+ (0, import_react.useEffect)(() => {
115
+ if (!auth || !isAuthReady) return void 0;
116
+ const handleLoggedIn = async (authUser) => {
117
+ if (session?.accessToken && authUser.accessToken !== session.accessToken) {
118
+ await updateSession({
119
+ accessToken: authUser.accessToken,
120
+ refreshToken: authUser.refreshToken,
121
+ idToken: authUser.idToken,
122
+ accessTokenExpires: getTokenExpiry(authUser.accessToken),
123
+ zkEvm: authUser.zkEvm
124
+ });
125
+ }
126
+ };
127
+ const handleTokenRefreshed = async (authUser) => {
128
+ await updateSession({
129
+ accessToken: authUser.accessToken,
130
+ refreshToken: authUser.refreshToken,
131
+ idToken: authUser.idToken,
132
+ accessTokenExpires: getTokenExpiry(authUser.accessToken),
133
+ zkEvm: authUser.zkEvm
134
+ });
135
+ };
136
+ const handleUserRemoved = async (payload) => {
137
+ console.warn("[auth-next-client] User removed from Auth SDK:", payload.reason, payload.error);
138
+ await (0, import_react2.signOut)({ redirect: false });
139
+ };
140
+ const handleLoggedOut = async () => {
141
+ await (0, import_react2.signOut)({ redirect: false });
142
+ };
143
+ auth.eventEmitter.on(import_auth2.AuthEvents.LOGGED_IN, handleLoggedIn);
144
+ auth.eventEmitter.on(import_auth2.AuthEvents.TOKEN_REFRESHED, handleTokenRefreshed);
145
+ auth.eventEmitter.on(import_auth2.AuthEvents.USER_REMOVED, handleUserRemoved);
146
+ auth.eventEmitter.on(import_auth2.AuthEvents.LOGGED_OUT, handleLoggedOut);
147
+ return () => {
148
+ auth.eventEmitter.removeListener(import_auth2.AuthEvents.LOGGED_IN, handleLoggedIn);
149
+ auth.eventEmitter.removeListener(import_auth2.AuthEvents.TOKEN_REFRESHED, handleTokenRefreshed);
150
+ auth.eventEmitter.removeListener(import_auth2.AuthEvents.USER_REMOVED, handleUserRemoved);
151
+ auth.eventEmitter.removeListener(import_auth2.AuthEvents.LOGGED_OUT, handleLoggedOut);
152
+ };
153
+ }, [auth, isAuthReady, session, updateSession]);
154
+ const contextValue = (0, import_react.useMemo)(
155
+ () => ({ auth, config, basePath }),
156
+ [auth, config, basePath]
157
+ );
158
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImmutableAuthContext.Provider, { value: contextValue, children });
159
+ }
160
+ function ImmutableAuthProvider({
161
+ children,
162
+ config,
163
+ session,
164
+ basePath = DEFAULT_NEXTAUTH_BASE_PATH
165
+ }) {
166
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SessionProvider, { session, basePath, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImmutableAuthInner, { config, basePath, children }) });
167
+ }
168
+ function useImmutableAuth() {
169
+ const context = (0, import_react.useContext)(ImmutableAuthContext);
170
+ const { data: sessionData, status } = (0, import_react2.useSession)();
171
+ const [isLoggingIn, setIsLoggingIn] = (0, import_react.useState)(false);
172
+ if (!context) {
173
+ throw new Error("useImmutableAuth must be used within ImmutableAuthProvider");
174
+ }
175
+ const session = sessionData;
176
+ const { auth } = context;
177
+ const isLoading = status === "loading";
178
+ const isAuthenticated = status === "authenticated" && !!session;
179
+ const user = session?.user ? {
180
+ sub: session.user.sub,
181
+ email: session.user.email,
182
+ nickname: session.user.nickname
183
+ } : null;
184
+ const handleSignIn = (0, import_react.useCallback)(async (options) => {
185
+ if (!auth) {
186
+ throw new Error("Auth not initialized");
187
+ }
188
+ setIsLoggingIn(true);
189
+ try {
190
+ const authUser = await auth.login(options);
191
+ if (!authUser) {
192
+ throw new Error("Login failed");
193
+ }
194
+ const tokenData = {
195
+ accessToken: authUser.accessToken,
196
+ refreshToken: authUser.refreshToken,
197
+ idToken: authUser.idToken,
198
+ accessTokenExpires: getTokenExpiry(authUser.accessToken),
199
+ profile: {
200
+ sub: authUser.profile.sub,
201
+ email: authUser.profile.email,
202
+ nickname: authUser.profile.nickname
203
+ },
204
+ zkEvm: authUser.zkEvm
205
+ };
206
+ const result = await (0, import_react2.signIn)(IMMUTABLE_PROVIDER_ID, {
207
+ tokens: JSON.stringify(tokenData),
208
+ redirect: false
209
+ });
210
+ if (result?.error) {
211
+ throw new Error(`NextAuth sign-in failed: ${result.error}`);
212
+ }
213
+ if (!result?.ok) {
214
+ throw new Error("NextAuth sign-in failed: unknown error");
215
+ }
216
+ } finally {
217
+ setIsLoggingIn(false);
218
+ }
219
+ }, [auth]);
220
+ const handleSignOut = (0, import_react.useCallback)(async () => {
221
+ if (auth) {
222
+ try {
223
+ await auth.getLogoutUrl();
224
+ } catch (error) {
225
+ console.warn("[auth-next-client] Logout cleanup error:", error);
226
+ await (0, import_react2.signOut)({ redirect: false });
227
+ }
228
+ } else {
229
+ await (0, import_react2.signOut)({ redirect: false });
230
+ }
231
+ }, [auth]);
232
+ const getAccessToken = (0, import_react.useCallback)(async () => {
233
+ if (auth) {
234
+ try {
235
+ const token = await auth.getAccessToken();
236
+ if (token) {
237
+ return token;
238
+ }
239
+ } catch {
240
+ }
241
+ }
242
+ if (session?.error) {
243
+ throw new Error(
244
+ session.error === "TokenExpired" ? "Session expired. Please log in again." : `Authentication error: ${session.error}`
245
+ );
246
+ }
247
+ if (session?.accessToken) {
248
+ return session.accessToken;
249
+ }
250
+ throw new Error("No access token available");
251
+ }, [auth, session]);
252
+ return {
253
+ user,
254
+ session,
255
+ isLoading,
256
+ isLoggingIn,
257
+ isAuthenticated,
258
+ signIn: handleSignIn,
259
+ signOut: handleSignOut,
260
+ getAccessToken,
261
+ auth
262
+ };
263
+ }
264
+ function useAccessToken() {
265
+ const { getAccessToken } = useImmutableAuth();
266
+ return getAccessToken;
267
+ }
268
+ function useHydratedData(props, fetcher) {
269
+ const { getAccessToken, auth } = useImmutableAuth();
270
+ const {
271
+ ssr,
272
+ data: serverData,
273
+ fetchError
274
+ } = props;
275
+ const needsClientFetch = !ssr || Boolean(fetchError);
276
+ const [data, setData] = (0, import_react.useState)(serverData);
277
+ const [isLoading, setIsLoading] = (0, import_react.useState)(needsClientFetch);
278
+ const [error, setError] = (0, import_react.useState)(
279
+ fetchError ? new Error(fetchError) : null
280
+ );
281
+ const hasFetchedRef = (0, import_react.useRef)(false);
282
+ const fetchIdRef = (0, import_react.useRef)(0);
283
+ const prevPropsRef = (0, import_react.useRef)({ serverData, ssr, fetchError });
284
+ (0, import_react.useEffect)(() => {
285
+ const prevProps = prevPropsRef.current;
286
+ const propsChanged = prevProps.serverData !== serverData || prevProps.ssr !== ssr || prevProps.fetchError !== fetchError;
287
+ if (propsChanged) {
288
+ prevPropsRef.current = { serverData, ssr, fetchError };
289
+ hasFetchedRef.current = false;
290
+ fetchIdRef.current += 1;
291
+ if (ssr && !fetchError) {
292
+ setData(serverData);
293
+ setIsLoading(false);
294
+ setError(null);
295
+ } else {
296
+ setData(null);
297
+ setIsLoading(true);
298
+ setError(fetchError ? new Error(fetchError) : null);
299
+ }
300
+ }
301
+ }, [serverData, ssr, fetchError]);
302
+ const fetchData = (0, import_react.useCallback)(async () => {
303
+ const currentFetchId = fetchIdRef.current;
304
+ setIsLoading(true);
305
+ setError(null);
306
+ try {
307
+ const token = await getAccessToken();
308
+ const result = await fetcher(token);
309
+ if (fetchIdRef.current === currentFetchId) {
310
+ setData(result);
311
+ }
312
+ } catch (err) {
313
+ if (fetchIdRef.current === currentFetchId) {
314
+ setError(err instanceof Error ? err : new Error(String(err)));
315
+ }
316
+ } finally {
317
+ if (fetchIdRef.current === currentFetchId) {
318
+ setIsLoading(false);
319
+ }
320
+ }
321
+ }, [fetcher, getAccessToken]);
322
+ (0, import_react.useEffect)(() => {
323
+ if (hasFetchedRef.current) return;
324
+ if (!needsClientFetch) return;
325
+ if (!ssr && !auth) return;
326
+ hasFetchedRef.current = true;
327
+ fetchData();
328
+ }, [needsClientFetch, ssr, auth, fetchData]);
329
+ return {
330
+ data,
331
+ isLoading,
332
+ error,
333
+ refetch: fetchData
334
+ };
335
+ }
336
+
337
+ // src/callback.tsx
338
+ var import_react3 = require("react");
339
+ var import_navigation = require("next/navigation");
340
+ var import_react4 = require("next-auth/react");
341
+ var import_auth3 = require("@imtbl/auth");
342
+ var import_jsx_runtime2 = require("react/jsx-runtime");
343
+ function getSearchParams() {
344
+ if (typeof window === "undefined") {
345
+ return new URLSearchParams();
346
+ }
347
+ return new URLSearchParams(window.location.search);
348
+ }
349
+ function CallbackPage({
350
+ config,
351
+ redirectTo = "/",
352
+ loadingComponent = null,
353
+ errorComponent,
354
+ onSuccess,
355
+ onError
356
+ }) {
357
+ const router = (0, import_navigation.useRouter)();
358
+ const [error, setError] = (0, import_react3.useState)(null);
359
+ const callbackProcessedRef = (0, import_react3.useRef)(false);
360
+ (0, import_react3.useEffect)(() => {
361
+ const searchParams = getSearchParams();
362
+ const handleCallback = async () => {
363
+ try {
364
+ const auth = new import_auth3.Auth({
365
+ clientId: config.clientId,
366
+ redirectUri: config.redirectUri,
367
+ popupRedirectUri: config.popupRedirectUri,
368
+ logoutRedirectUri: config.logoutRedirectUri,
369
+ audience: config.audience || DEFAULT_AUDIENCE,
370
+ scope: config.scope || DEFAULT_SCOPE,
371
+ authenticationDomain: config.authenticationDomain || DEFAULT_AUTH_DOMAIN,
372
+ passportDomain: config.passportDomain
373
+ });
374
+ const authUser = await auth.loginCallback();
375
+ if (window.opener) {
376
+ if (!authUser) {
377
+ throw new Error("Authentication failed: no user data received from login callback");
378
+ }
379
+ const user = {
380
+ sub: authUser.profile.sub,
381
+ email: authUser.profile.email,
382
+ nickname: authUser.profile.nickname
383
+ };
384
+ if (onSuccess) {
385
+ await onSuccess(user);
386
+ }
387
+ window.close();
388
+ } else if (authUser) {
389
+ const tokenData = {
390
+ accessToken: authUser.accessToken,
391
+ refreshToken: authUser.refreshToken,
392
+ idToken: authUser.idToken,
393
+ accessTokenExpires: getTokenExpiry(authUser.accessToken),
394
+ profile: {
395
+ sub: authUser.profile.sub,
396
+ email: authUser.profile.email,
397
+ nickname: authUser.profile.nickname
398
+ },
399
+ zkEvm: authUser.zkEvm
400
+ };
401
+ const result = await (0, import_react4.signIn)(IMMUTABLE_PROVIDER_ID, {
402
+ tokens: JSON.stringify(tokenData),
403
+ redirect: false
404
+ });
405
+ if (result?.error) {
406
+ throw new Error(`NextAuth sign-in failed: ${result.error}`);
407
+ }
408
+ if (!result?.ok) {
409
+ throw new Error("NextAuth sign-in failed: unknown error");
410
+ }
411
+ const user = {
412
+ sub: authUser.profile.sub,
413
+ email: authUser.profile.email,
414
+ nickname: authUser.profile.nickname
415
+ };
416
+ if (onSuccess) {
417
+ await onSuccess(user);
418
+ }
419
+ const resolvedRedirectTo = typeof redirectTo === "function" ? redirectTo(user) || "/" : redirectTo;
420
+ router.replace(resolvedRedirectTo);
421
+ } else {
422
+ throw new Error("Authentication failed: no user data received from login callback");
423
+ }
424
+ } catch (err) {
425
+ const errorMessage2 = err instanceof Error ? err.message : "Authentication failed";
426
+ if (onError) {
427
+ onError(errorMessage2);
428
+ }
429
+ setError(errorMessage2);
430
+ }
431
+ };
432
+ const handleOAuthError = () => {
433
+ const errorCode = searchParams.get("error");
434
+ const errorDescription = searchParams.get("error_description");
435
+ const errorMessage2 = errorDescription || errorCode || "Authentication failed";
436
+ if (onError) {
437
+ onError(errorMessage2);
438
+ }
439
+ setError(errorMessage2);
440
+ };
441
+ if (callbackProcessedRef.current) {
442
+ return;
443
+ }
444
+ const hasError = searchParams.get("error");
445
+ const hasCode = searchParams.get("code");
446
+ if (hasError) {
447
+ callbackProcessedRef.current = true;
448
+ handleOAuthError();
449
+ return;
450
+ }
451
+ if (hasCode) {
452
+ callbackProcessedRef.current = true;
453
+ handleCallback();
454
+ return;
455
+ }
456
+ callbackProcessedRef.current = true;
457
+ const errorMessage = "Invalid callback: missing OAuth parameters. Please try logging in again.";
458
+ if (onError) {
459
+ onError(errorMessage);
460
+ }
461
+ setError(errorMessage);
462
+ }, [router, config, redirectTo, onSuccess, onError]);
463
+ if (error) {
464
+ if (errorComponent) {
465
+ return errorComponent(error);
466
+ }
467
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "2rem", textAlign: "center" }, children: [
468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: { color: "#dc3545" }, children: "Authentication Error" }),
469
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: error }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
471
+ "button",
472
+ {
473
+ onClick: () => router.push("/"),
474
+ type: "button",
475
+ style: {
476
+ padding: "0.5rem 1rem",
477
+ marginTop: "1rem",
478
+ cursor: "pointer"
479
+ },
480
+ children: "Return to Home"
481
+ }
482
+ )
483
+ ] });
484
+ }
485
+ if (loadingComponent) {
486
+ return loadingComponent;
487
+ }
488
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "2rem", textAlign: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "Completing authentication..." }) });
489
+ }
490
+
491
+ // src/index.ts
492
+ var import_auth4 = require("@imtbl/auth");
493
+ // Annotate the CommonJS export names for ESM import in node:
494
+ 0 && (module.exports = {
495
+ CallbackPage,
496
+ ImmutableAuthProvider,
497
+ MarketingConsentStatus,
498
+ useAccessToken,
499
+ useHydratedData,
500
+ useImmutableAuth
501
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @imtbl/auth-next-client
3
+ *
4
+ * Client-side components for Immutable Auth.js v5 integration with Next.js.
5
+ * This package provides React components and hooks for authentication.
6
+ *
7
+ * Note: This package depends on @imtbl/auth and should only be used in
8
+ * browser/client environments. For server-side utilities, use @imtbl/auth-next-server.
9
+ */
10
+ export { ImmutableAuthProvider, useImmutableAuth, useAccessToken, useHydratedData, type UseHydratedDataResult, type HydratedDataProps, } from './provider';
11
+ export { CallbackPage, type CallbackPageProps } from './callback';
12
+ export type { ImmutableAuthProviderProps, UseImmutableAuthReturn, ImmutableUserClient, ImmutableTokenDataClient, ZkEvmInfo, } from './types';
13
+ export type { ImmutableAuthConfig, ImmutableTokenData, ImmutableUser, AuthProps, AuthPropsWithData, ProtectedAuthProps, ProtectedAuthPropsWithData, } from '@imtbl/auth-next-server';
14
+ export type { LoginOptions, DirectLoginOptions } from '@imtbl/auth';
15
+ export { MarketingConsentStatus } from '@imtbl/auth';