@lanonasis/oauth-client 1.2.8 → 2.0.3

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,261 @@
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/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ COOKIE_NAMES: () => COOKIE_NAMES,
24
+ DEFAULT_AUTH_GATEWAY: () => DEFAULT_AUTH_GATEWAY,
25
+ DEFAULT_COOKIE_DOMAIN: () => DEFAULT_COOKIE_DOMAIN,
26
+ DEFAULT_POLL_INTERVAL: () => DEFAULT_POLL_INTERVAL,
27
+ DEFAULT_PROJECT_SCOPE: () => DEFAULT_PROJECT_SCOPE,
28
+ clearUserCookie: () => clearUserCookie,
29
+ hasAuthCookies: () => hasAuthCookies,
30
+ hasSessionCookie: () => hasSessionCookie,
31
+ isBrowser: () => isBrowser,
32
+ parseUserCookie: () => parseUserCookie,
33
+ useSSO: () => useSSO,
34
+ useSSOSync: () => useSSOSync
35
+ });
36
+ module.exports = __toCommonJS(react_exports);
37
+
38
+ // src/react/useSSO.ts
39
+ var import_react = require("react");
40
+
41
+ // src/cookies/constants.ts
42
+ var COOKIE_NAMES = {
43
+ /** HttpOnly JWT session token */
44
+ SESSION: "lanonasis_session",
45
+ /** Readable user metadata (JSON) */
46
+ USER: "lanonasis_user"
47
+ };
48
+ var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
49
+ var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
50
+ var DEFAULT_POLL_INTERVAL = 3e4;
51
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
52
+
53
+ // src/react/cookie-utils-browser.ts
54
+ function isBrowser() {
55
+ return typeof window !== "undefined" && typeof document !== "undefined";
56
+ }
57
+ function parseUserCookie() {
58
+ if (!isBrowser()) return null;
59
+ try {
60
+ const cookies = document.cookie.split(";");
61
+ const userCookie = cookies.find(
62
+ (c) => c.trim().startsWith(`${COOKIE_NAMES.USER}=`)
63
+ );
64
+ if (!userCookie) return null;
65
+ const value = userCookie.split("=").slice(1).join("=").trim();
66
+ const decoded = decodeURIComponent(value);
67
+ const parsed = JSON.parse(decoded);
68
+ if (!parsed.id || !parsed.email || !parsed.role) {
69
+ console.warn("[oauth-client] Invalid user cookie: missing required fields");
70
+ return null;
71
+ }
72
+ return parsed;
73
+ } catch (error) {
74
+ console.warn("[oauth-client] Failed to parse user cookie:", error);
75
+ return null;
76
+ }
77
+ }
78
+ function hasSessionCookie() {
79
+ if (!isBrowser()) return false;
80
+ return document.cookie.includes(`${COOKIE_NAMES.SESSION}=`);
81
+ }
82
+ function hasAuthCookies() {
83
+ return hasSessionCookie() && parseUserCookie() !== null;
84
+ }
85
+ function clearUserCookie(domain = ".lanonasis.com") {
86
+ if (!isBrowser()) return;
87
+ document.cookie = `${COOKIE_NAMES.USER}=; domain=${domain}; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
88
+ }
89
+
90
+ // src/react/useSSO.ts
91
+ function useSSO(config = {}) {
92
+ const {
93
+ authGatewayUrl = DEFAULT_AUTH_GATEWAY,
94
+ onAuthChange,
95
+ pollInterval = DEFAULT_POLL_INTERVAL
96
+ } = config;
97
+ const [state, setState] = (0, import_react.useState)({
98
+ isAuthenticated: false,
99
+ isLoading: true,
100
+ user: null,
101
+ error: null
102
+ });
103
+ const prevStateRef = (0, import_react.useRef)(null);
104
+ const checkAuthState = (0, import_react.useCallback)(() => {
105
+ if (!isBrowser()) {
106
+ setState({
107
+ isAuthenticated: false,
108
+ isLoading: false,
109
+ user: null,
110
+ error: null
111
+ });
112
+ return;
113
+ }
114
+ try {
115
+ const hasSession = hasSessionCookie();
116
+ const user = parseUserCookie();
117
+ const newState = {
118
+ isAuthenticated: hasSession && user !== null,
119
+ isLoading: false,
120
+ user: hasSession ? user : null,
121
+ error: null
122
+ };
123
+ setState(newState);
124
+ if (onAuthChange && prevStateRef.current && prevStateRef.current.isAuthenticated !== newState.isAuthenticated) {
125
+ onAuthChange(newState);
126
+ }
127
+ prevStateRef.current = newState;
128
+ } catch (error) {
129
+ const errorState = {
130
+ isAuthenticated: false,
131
+ isLoading: false,
132
+ user: null,
133
+ error: error instanceof Error ? error.message : "Failed to check auth state"
134
+ };
135
+ setState(errorState);
136
+ prevStateRef.current = errorState;
137
+ }
138
+ }, [onAuthChange]);
139
+ const refresh = (0, import_react.useCallback)(() => {
140
+ setState((prev) => ({ ...prev, isLoading: true }));
141
+ checkAuthState();
142
+ }, [checkAuthState]);
143
+ const logout = (0, import_react.useCallback)((returnTo) => {
144
+ if (!isBrowser()) return;
145
+ const logoutUrl = new URL("/web/logout", authGatewayUrl);
146
+ if (returnTo) {
147
+ logoutUrl.searchParams.set("return_to", returnTo);
148
+ }
149
+ window.location.href = logoutUrl.toString();
150
+ }, [authGatewayUrl]);
151
+ const getLoginUrl = (0, import_react.useCallback)((returnTo) => {
152
+ const loginUrl = new URL("/web/login", authGatewayUrl);
153
+ if (returnTo) {
154
+ loginUrl.searchParams.set("return_to", returnTo);
155
+ }
156
+ return loginUrl.toString();
157
+ }, [authGatewayUrl]);
158
+ (0, import_react.useEffect)(() => {
159
+ const timer = setTimeout(checkAuthState, 0);
160
+ return () => clearTimeout(timer);
161
+ }, [checkAuthState]);
162
+ (0, import_react.useEffect)(() => {
163
+ if (!isBrowser() || pollInterval <= 0) return;
164
+ const interval = setInterval(checkAuthState, pollInterval);
165
+ return () => clearInterval(interval);
166
+ }, [checkAuthState, pollInterval]);
167
+ (0, import_react.useEffect)(() => {
168
+ if (!isBrowser()) return;
169
+ const handleStorageChange = (event) => {
170
+ if (event.key === null || event.key?.includes("auth")) {
171
+ checkAuthState();
172
+ }
173
+ };
174
+ window.addEventListener("storage", handleStorageChange);
175
+ return () => window.removeEventListener("storage", handleStorageChange);
176
+ }, [checkAuthState]);
177
+ (0, import_react.useEffect)(() => {
178
+ if (!isBrowser()) return;
179
+ const handleFocus = () => {
180
+ checkAuthState();
181
+ };
182
+ window.addEventListener("focus", handleFocus);
183
+ return () => window.removeEventListener("focus", handleFocus);
184
+ }, [checkAuthState]);
185
+ return {
186
+ ...state,
187
+ refresh,
188
+ logout,
189
+ getLoginUrl
190
+ };
191
+ }
192
+
193
+ // src/react/useSSOSync.ts
194
+ var import_react2 = require("react");
195
+ function useSSOSync(supabaseSession, config = {}) {
196
+ const {
197
+ authGatewayUrl = DEFAULT_AUTH_GATEWAY,
198
+ projectScope = DEFAULT_PROJECT_SCOPE,
199
+ onSyncComplete
200
+ } = config;
201
+ const lastSyncedTokenRef = (0, import_react2.useRef)(null);
202
+ const syncWithGateway = (0, import_react2.useCallback)(
203
+ async (session) => {
204
+ if (!isBrowser()) return false;
205
+ try {
206
+ const response = await fetch(
207
+ `${authGatewayUrl}/v1/auth/token/exchange`,
208
+ {
209
+ method: "POST",
210
+ headers: {
211
+ Authorization: `Bearer ${session.access_token}`,
212
+ "Content-Type": "application/json",
213
+ "X-Project-Scope": projectScope
214
+ },
215
+ credentials: "include",
216
+ // Important: include cookies
217
+ body: JSON.stringify({
218
+ project_scope: projectScope,
219
+ platform: "web"
220
+ })
221
+ }
222
+ );
223
+ if (!response.ok) {
224
+ console.warn("[oauth-client] SSO sync failed:", response.status);
225
+ onSyncComplete?.(false);
226
+ return false;
227
+ }
228
+ const data = await response.json();
229
+ if (data.cookies_set) {
230
+ console.log("[oauth-client] SSO cookies set successfully");
231
+ }
232
+ onSyncComplete?.(true);
233
+ return true;
234
+ } catch (error) {
235
+ console.error("[oauth-client] SSO sync error:", error);
236
+ onSyncComplete?.(false);
237
+ return false;
238
+ }
239
+ },
240
+ [authGatewayUrl, projectScope, onSyncComplete]
241
+ );
242
+ const sync = (0, import_react2.useCallback)(async () => {
243
+ if (!supabaseSession?.access_token) {
244
+ console.warn("[oauth-client] Cannot sync: no Supabase session");
245
+ return false;
246
+ }
247
+ return syncWithGateway(supabaseSession);
248
+ }, [supabaseSession, syncWithGateway]);
249
+ (0, import_react2.useEffect)(() => {
250
+ const token = supabaseSession?.access_token;
251
+ if (!token || token === lastSyncedTokenRef.current) {
252
+ return;
253
+ }
254
+ lastSyncedTokenRef.current = token;
255
+ syncWithGateway(supabaseSession);
256
+ }, [supabaseSession?.access_token, syncWithGateway, supabaseSession]);
257
+ return {
258
+ sync,
259
+ syncWithGateway
260
+ };
261
+ }
@@ -0,0 +1,95 @@
1
+ import { S as SSOConfig, U as UseSSOReturn, a as SupabaseSession, b as SSOSyncConfig, c as UseSSOSyncReturn, d as SSOUser } from '../constants-BZPTHasL.cjs';
2
+ export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, e as DEFAULT_COOKIE_DOMAIN, f as DEFAULT_POLL_INTERVAL, g as DEFAULT_PROJECT_SCOPE, h as SSOState } from '../constants-BZPTHasL.cjs';
3
+
4
+ /**
5
+ * React hook for SSO authentication state
6
+ * @module @lanonasis/oauth-client/react
7
+ */
8
+
9
+ /**
10
+ * React hook for SSO authentication state
11
+ *
12
+ * Reads the lanonasis_user cookie to determine auth state.
13
+ * Works across all *.lanonasis.com subdomains.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function Navbar() {
18
+ * const { isAuthenticated, user, logout } = useSSO();
19
+ *
20
+ * if (isAuthenticated && user) {
21
+ * return <span>Welcome, {user.email}</span>;
22
+ * }
23
+ * return <a href="https://auth.lanonasis.com/web/login">Sign In</a>;
24
+ * }
25
+ * ```
26
+ */
27
+ declare function useSSO(config?: SSOConfig): UseSSOReturn;
28
+
29
+ /**
30
+ * React hook to sync Supabase auth state with lanonasis cookies
31
+ * @module @lanonasis/oauth-client/react
32
+ */
33
+
34
+ /**
35
+ * React hook to sync Supabase auth state with lanonasis cookies
36
+ *
37
+ * Call this hook in components that use Supabase auth to ensure
38
+ * cross-subdomain SSO cookies are set when user logs in.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * function AuthProvider({ children }) {
43
+ * const { data: { session } } = useSupabaseSession();
44
+ *
45
+ * // Sync Supabase session with SSO cookies
46
+ * useSSOSync(session, {
47
+ * projectScope: 'my-app',
48
+ * onSyncComplete: (success) => {
49
+ * console.log('SSO sync:', success ? 'success' : 'failed');
50
+ * }
51
+ * });
52
+ *
53
+ * return <>{children}</>;
54
+ * }
55
+ * ```
56
+ */
57
+ declare function useSSOSync(supabaseSession: SupabaseSession | null, config?: SSOSyncConfig): UseSSOSyncReturn;
58
+
59
+ /**
60
+ * Browser-side cookie utilities for SSO authentication
61
+ * @module @lanonasis/oauth-client/react
62
+ */
63
+
64
+ /**
65
+ * Check if we're in a browser environment
66
+ */
67
+ declare function isBrowser(): boolean;
68
+ /**
69
+ * Parse the lanonasis_user cookie (non-HttpOnly, readable by JS)
70
+ * This cookie contains: { id, email, role, name?, avatar_url? }
71
+ *
72
+ * @returns User data or null if cookie doesn't exist or is invalid
73
+ */
74
+ declare function parseUserCookie(): SSOUser | null;
75
+ /**
76
+ * Check if the session cookie exists (cannot read value due to HttpOnly)
77
+ *
78
+ * @returns true if lanonasis_session cookie exists
79
+ */
80
+ declare function hasSessionCookie(): boolean;
81
+ /**
82
+ * Check if user appears to be authenticated based on cookies
83
+ *
84
+ * @returns true if both session and user cookies exist
85
+ */
86
+ declare function hasAuthCookies(): boolean;
87
+ /**
88
+ * Clear auth cookies (client-side only clears non-HttpOnly cookies)
89
+ * Full logout should redirect to auth gateway
90
+ *
91
+ * @param domain - Cookie domain (default: .lanonasis.com)
92
+ */
93
+ declare function clearUserCookie(domain?: string): void;
94
+
95
+ export { SSOConfig, SSOSyncConfig, SSOUser, SupabaseSession, UseSSOReturn, UseSSOSyncReturn, clearUserCookie, hasAuthCookies, hasSessionCookie, isBrowser, parseUserCookie, useSSO, useSSOSync };
@@ -0,0 +1,95 @@
1
+ import { S as SSOConfig, U as UseSSOReturn, a as SupabaseSession, b as SSOSyncConfig, c as UseSSOSyncReturn, d as SSOUser } from '../constants-BZPTHasL.js';
2
+ export { C as COOKIE_NAMES, D as DEFAULT_AUTH_GATEWAY, e as DEFAULT_COOKIE_DOMAIN, f as DEFAULT_POLL_INTERVAL, g as DEFAULT_PROJECT_SCOPE, h as SSOState } from '../constants-BZPTHasL.js';
3
+
4
+ /**
5
+ * React hook for SSO authentication state
6
+ * @module @lanonasis/oauth-client/react
7
+ */
8
+
9
+ /**
10
+ * React hook for SSO authentication state
11
+ *
12
+ * Reads the lanonasis_user cookie to determine auth state.
13
+ * Works across all *.lanonasis.com subdomains.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function Navbar() {
18
+ * const { isAuthenticated, user, logout } = useSSO();
19
+ *
20
+ * if (isAuthenticated && user) {
21
+ * return <span>Welcome, {user.email}</span>;
22
+ * }
23
+ * return <a href="https://auth.lanonasis.com/web/login">Sign In</a>;
24
+ * }
25
+ * ```
26
+ */
27
+ declare function useSSO(config?: SSOConfig): UseSSOReturn;
28
+
29
+ /**
30
+ * React hook to sync Supabase auth state with lanonasis cookies
31
+ * @module @lanonasis/oauth-client/react
32
+ */
33
+
34
+ /**
35
+ * React hook to sync Supabase auth state with lanonasis cookies
36
+ *
37
+ * Call this hook in components that use Supabase auth to ensure
38
+ * cross-subdomain SSO cookies are set when user logs in.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * function AuthProvider({ children }) {
43
+ * const { data: { session } } = useSupabaseSession();
44
+ *
45
+ * // Sync Supabase session with SSO cookies
46
+ * useSSOSync(session, {
47
+ * projectScope: 'my-app',
48
+ * onSyncComplete: (success) => {
49
+ * console.log('SSO sync:', success ? 'success' : 'failed');
50
+ * }
51
+ * });
52
+ *
53
+ * return <>{children}</>;
54
+ * }
55
+ * ```
56
+ */
57
+ declare function useSSOSync(supabaseSession: SupabaseSession | null, config?: SSOSyncConfig): UseSSOSyncReturn;
58
+
59
+ /**
60
+ * Browser-side cookie utilities for SSO authentication
61
+ * @module @lanonasis/oauth-client/react
62
+ */
63
+
64
+ /**
65
+ * Check if we're in a browser environment
66
+ */
67
+ declare function isBrowser(): boolean;
68
+ /**
69
+ * Parse the lanonasis_user cookie (non-HttpOnly, readable by JS)
70
+ * This cookie contains: { id, email, role, name?, avatar_url? }
71
+ *
72
+ * @returns User data or null if cookie doesn't exist or is invalid
73
+ */
74
+ declare function parseUserCookie(): SSOUser | null;
75
+ /**
76
+ * Check if the session cookie exists (cannot read value due to HttpOnly)
77
+ *
78
+ * @returns true if lanonasis_session cookie exists
79
+ */
80
+ declare function hasSessionCookie(): boolean;
81
+ /**
82
+ * Check if user appears to be authenticated based on cookies
83
+ *
84
+ * @returns true if both session and user cookies exist
85
+ */
86
+ declare function hasAuthCookies(): boolean;
87
+ /**
88
+ * Clear auth cookies (client-side only clears non-HttpOnly cookies)
89
+ * Full logout should redirect to auth gateway
90
+ *
91
+ * @param domain - Cookie domain (default: .lanonasis.com)
92
+ */
93
+ declare function clearUserCookie(domain?: string): void;
94
+
95
+ export { SSOConfig, SSOSyncConfig, SSOUser, SupabaseSession, UseSSOReturn, UseSSOSyncReturn, clearUserCookie, hasAuthCookies, hasSessionCookie, isBrowser, parseUserCookie, useSSO, useSSOSync };
@@ -0,0 +1,238 @@
1
+ // src/react/useSSO.ts
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+
4
+ // src/cookies/constants.ts
5
+ var COOKIE_NAMES = {
6
+ /** HttpOnly JWT session token */
7
+ SESSION: "lanonasis_session",
8
+ /** Readable user metadata (JSON) */
9
+ USER: "lanonasis_user"
10
+ };
11
+ var DEFAULT_COOKIE_DOMAIN = ".lanonasis.com";
12
+ var DEFAULT_AUTH_GATEWAY = "https://auth.lanonasis.com";
13
+ var DEFAULT_POLL_INTERVAL = 3e4;
14
+ var DEFAULT_PROJECT_SCOPE = "lanonasis-maas";
15
+
16
+ // src/react/cookie-utils-browser.ts
17
+ function isBrowser() {
18
+ return typeof window !== "undefined" && typeof document !== "undefined";
19
+ }
20
+ function parseUserCookie() {
21
+ if (!isBrowser()) return null;
22
+ try {
23
+ const cookies = document.cookie.split(";");
24
+ const userCookie = cookies.find(
25
+ (c) => c.trim().startsWith(`${COOKIE_NAMES.USER}=`)
26
+ );
27
+ if (!userCookie) return null;
28
+ const value = userCookie.split("=").slice(1).join("=").trim();
29
+ const decoded = decodeURIComponent(value);
30
+ const parsed = JSON.parse(decoded);
31
+ if (!parsed.id || !parsed.email || !parsed.role) {
32
+ console.warn("[oauth-client] Invalid user cookie: missing required fields");
33
+ return null;
34
+ }
35
+ return parsed;
36
+ } catch (error) {
37
+ console.warn("[oauth-client] Failed to parse user cookie:", error);
38
+ return null;
39
+ }
40
+ }
41
+ function hasSessionCookie() {
42
+ if (!isBrowser()) return false;
43
+ return document.cookie.includes(`${COOKIE_NAMES.SESSION}=`);
44
+ }
45
+ function hasAuthCookies() {
46
+ return hasSessionCookie() && parseUserCookie() !== null;
47
+ }
48
+ function clearUserCookie(domain = ".lanonasis.com") {
49
+ if (!isBrowser()) return;
50
+ document.cookie = `${COOKIE_NAMES.USER}=; domain=${domain}; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
51
+ }
52
+
53
+ // src/react/useSSO.ts
54
+ function useSSO(config = {}) {
55
+ const {
56
+ authGatewayUrl = DEFAULT_AUTH_GATEWAY,
57
+ onAuthChange,
58
+ pollInterval = DEFAULT_POLL_INTERVAL
59
+ } = config;
60
+ const [state, setState] = useState({
61
+ isAuthenticated: false,
62
+ isLoading: true,
63
+ user: null,
64
+ error: null
65
+ });
66
+ const prevStateRef = useRef(null);
67
+ const checkAuthState = useCallback(() => {
68
+ if (!isBrowser()) {
69
+ setState({
70
+ isAuthenticated: false,
71
+ isLoading: false,
72
+ user: null,
73
+ error: null
74
+ });
75
+ return;
76
+ }
77
+ try {
78
+ const hasSession = hasSessionCookie();
79
+ const user = parseUserCookie();
80
+ const newState = {
81
+ isAuthenticated: hasSession && user !== null,
82
+ isLoading: false,
83
+ user: hasSession ? user : null,
84
+ error: null
85
+ };
86
+ setState(newState);
87
+ if (onAuthChange && prevStateRef.current && prevStateRef.current.isAuthenticated !== newState.isAuthenticated) {
88
+ onAuthChange(newState);
89
+ }
90
+ prevStateRef.current = newState;
91
+ } catch (error) {
92
+ const errorState = {
93
+ isAuthenticated: false,
94
+ isLoading: false,
95
+ user: null,
96
+ error: error instanceof Error ? error.message : "Failed to check auth state"
97
+ };
98
+ setState(errorState);
99
+ prevStateRef.current = errorState;
100
+ }
101
+ }, [onAuthChange]);
102
+ const refresh = useCallback(() => {
103
+ setState((prev) => ({ ...prev, isLoading: true }));
104
+ checkAuthState();
105
+ }, [checkAuthState]);
106
+ const logout = useCallback((returnTo) => {
107
+ if (!isBrowser()) return;
108
+ const logoutUrl = new URL("/web/logout", authGatewayUrl);
109
+ if (returnTo) {
110
+ logoutUrl.searchParams.set("return_to", returnTo);
111
+ }
112
+ window.location.href = logoutUrl.toString();
113
+ }, [authGatewayUrl]);
114
+ const getLoginUrl = useCallback((returnTo) => {
115
+ const loginUrl = new URL("/web/login", authGatewayUrl);
116
+ if (returnTo) {
117
+ loginUrl.searchParams.set("return_to", returnTo);
118
+ }
119
+ return loginUrl.toString();
120
+ }, [authGatewayUrl]);
121
+ useEffect(() => {
122
+ const timer = setTimeout(checkAuthState, 0);
123
+ return () => clearTimeout(timer);
124
+ }, [checkAuthState]);
125
+ useEffect(() => {
126
+ if (!isBrowser() || pollInterval <= 0) return;
127
+ const interval = setInterval(checkAuthState, pollInterval);
128
+ return () => clearInterval(interval);
129
+ }, [checkAuthState, pollInterval]);
130
+ useEffect(() => {
131
+ if (!isBrowser()) return;
132
+ const handleStorageChange = (event) => {
133
+ if (event.key === null || event.key?.includes("auth")) {
134
+ checkAuthState();
135
+ }
136
+ };
137
+ window.addEventListener("storage", handleStorageChange);
138
+ return () => window.removeEventListener("storage", handleStorageChange);
139
+ }, [checkAuthState]);
140
+ useEffect(() => {
141
+ if (!isBrowser()) return;
142
+ const handleFocus = () => {
143
+ checkAuthState();
144
+ };
145
+ window.addEventListener("focus", handleFocus);
146
+ return () => window.removeEventListener("focus", handleFocus);
147
+ }, [checkAuthState]);
148
+ return {
149
+ ...state,
150
+ refresh,
151
+ logout,
152
+ getLoginUrl
153
+ };
154
+ }
155
+
156
+ // src/react/useSSOSync.ts
157
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
158
+ function useSSOSync(supabaseSession, config = {}) {
159
+ const {
160
+ authGatewayUrl = DEFAULT_AUTH_GATEWAY,
161
+ projectScope = DEFAULT_PROJECT_SCOPE,
162
+ onSyncComplete
163
+ } = config;
164
+ const lastSyncedTokenRef = useRef2(null);
165
+ const syncWithGateway = useCallback2(
166
+ async (session) => {
167
+ if (!isBrowser()) return false;
168
+ try {
169
+ const response = await fetch(
170
+ `${authGatewayUrl}/v1/auth/token/exchange`,
171
+ {
172
+ method: "POST",
173
+ headers: {
174
+ Authorization: `Bearer ${session.access_token}`,
175
+ "Content-Type": "application/json",
176
+ "X-Project-Scope": projectScope
177
+ },
178
+ credentials: "include",
179
+ // Important: include cookies
180
+ body: JSON.stringify({
181
+ project_scope: projectScope,
182
+ platform: "web"
183
+ })
184
+ }
185
+ );
186
+ if (!response.ok) {
187
+ console.warn("[oauth-client] SSO sync failed:", response.status);
188
+ onSyncComplete?.(false);
189
+ return false;
190
+ }
191
+ const data = await response.json();
192
+ if (data.cookies_set) {
193
+ console.log("[oauth-client] SSO cookies set successfully");
194
+ }
195
+ onSyncComplete?.(true);
196
+ return true;
197
+ } catch (error) {
198
+ console.error("[oauth-client] SSO sync error:", error);
199
+ onSyncComplete?.(false);
200
+ return false;
201
+ }
202
+ },
203
+ [authGatewayUrl, projectScope, onSyncComplete]
204
+ );
205
+ const sync = useCallback2(async () => {
206
+ if (!supabaseSession?.access_token) {
207
+ console.warn("[oauth-client] Cannot sync: no Supabase session");
208
+ return false;
209
+ }
210
+ return syncWithGateway(supabaseSession);
211
+ }, [supabaseSession, syncWithGateway]);
212
+ useEffect2(() => {
213
+ const token = supabaseSession?.access_token;
214
+ if (!token || token === lastSyncedTokenRef.current) {
215
+ return;
216
+ }
217
+ lastSyncedTokenRef.current = token;
218
+ syncWithGateway(supabaseSession);
219
+ }, [supabaseSession?.access_token, syncWithGateway, supabaseSession]);
220
+ return {
221
+ sync,
222
+ syncWithGateway
223
+ };
224
+ }
225
+ export {
226
+ COOKIE_NAMES,
227
+ DEFAULT_AUTH_GATEWAY,
228
+ DEFAULT_COOKIE_DOMAIN,
229
+ DEFAULT_POLL_INTERVAL,
230
+ DEFAULT_PROJECT_SCOPE,
231
+ clearUserCookie,
232
+ hasAuthCookies,
233
+ hasSessionCookie,
234
+ isBrowser,
235
+ parseUserCookie,
236
+ useSSO,
237
+ useSSOSync
238
+ };