@blimu/react 1.1.4 → 1.2.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/client/auth.service.cjs +28 -28
- package/dist/client/auth.service.cjs.map +1 -1
- package/dist/client/auth.service.d.ts +2 -2
- package/dist/client/auth.service.d.ts.map +1 -1
- package/dist/client/auth.service.js +29 -29
- package/dist/client/auth.service.js.map +1 -1
- package/dist/client/runtime-client.cjs +28 -9
- package/dist/client/runtime-client.cjs.map +1 -1
- package/dist/client/runtime-client.d.ts +3 -1
- package/dist/client/runtime-client.d.ts.map +1 -1
- package/dist/client/runtime-client.js +29 -10
- package/dist/client/runtime-client.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/Icon.cjs +4 -3
- package/dist/node_modules/lucide-react/dist/esm/Icon.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/Icon.js +2 -1
- package/dist/node_modules/lucide-react/dist/esm/Icon.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/createLucideIcon.cjs +6 -4
- package/dist/node_modules/lucide-react/dist/esm/createLucideIcon.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/createLucideIcon.js +3 -1
- package/dist/node_modules/lucide-react/dist/esm/createLucideIcon.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/defaultAttributes.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/defaultAttributes.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/check.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/check.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-right.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/chevron-right.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/circle.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/circle.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/log-out.cjs.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/log-out.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.cjs +12 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.js +12 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.cjs +7 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js +7 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.cjs +8 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.js +8 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.cjs +5 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.js +5 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.cjs +9 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.js +9 -0
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.js.map +1 -0
- package/package.json +17 -17
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils.cjs +0 -27
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils.cjs.map +0 -1
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils.js +0 -27
- package/dist/node_modules/lucide-react/dist/esm/shared/src/utils.js.map +0 -1
|
@@ -31,6 +31,31 @@ class AuthSessionService {
|
|
|
31
31
|
this.refreshPromise = null;
|
|
32
32
|
this.refreshingSignals = /* @__PURE__ */ new Set();
|
|
33
33
|
this.refreshingSignalAbortController = null;
|
|
34
|
+
this.handleRequestError = (error) => {
|
|
35
|
+
if (error instanceof client.BlimuError && [401, 500].includes(error.status)) {
|
|
36
|
+
js_cookie.default.remove(SESSION_COOKIE_NAME);
|
|
37
|
+
js_cookie.default.remove(LOCALHOST_JWT_COOKIE_NAME);
|
|
38
|
+
return {
|
|
39
|
+
error: error.message,
|
|
40
|
+
user: null
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (error instanceof DOMException)
|
|
44
|
+
return {
|
|
45
|
+
error: error.message,
|
|
46
|
+
user: null
|
|
47
|
+
};
|
|
48
|
+
if (error instanceof Error) {
|
|
49
|
+
return {
|
|
50
|
+
error: error.message,
|
|
51
|
+
user: null
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
error: "unknown error",
|
|
56
|
+
user: null
|
|
57
|
+
};
|
|
58
|
+
};
|
|
34
59
|
this.localClient = new client.Blimu({
|
|
35
60
|
baseURL,
|
|
36
61
|
credentials: "include",
|
|
@@ -75,31 +100,6 @@ class AuthSessionService {
|
|
|
75
100
|
}
|
|
76
101
|
return js_cookie.default.get(SESSION_COOKIE_NAME);
|
|
77
102
|
}
|
|
78
|
-
async handleRequestError(error) {
|
|
79
|
-
if (error instanceof client.BlimuError && [401, 500].includes(error.status)) {
|
|
80
|
-
js_cookie.default.remove(SESSION_COOKIE_NAME);
|
|
81
|
-
js_cookie.default.remove(LOCALHOST_JWT_COOKIE_NAME);
|
|
82
|
-
return {
|
|
83
|
-
error: error.message,
|
|
84
|
-
user: null
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (error instanceof DOMException)
|
|
88
|
-
return {
|
|
89
|
-
error: error.message,
|
|
90
|
-
user: null
|
|
91
|
-
};
|
|
92
|
-
if (error instanceof Error) {
|
|
93
|
-
return {
|
|
94
|
-
error: error.message,
|
|
95
|
-
user: null
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
error: "unknown error",
|
|
100
|
-
user: null
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
103
|
async initialize({ signal } = {}) {
|
|
104
104
|
const url = new URL(window.location.href);
|
|
105
105
|
const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? void 0;
|
|
@@ -143,7 +143,7 @@ class AuthSessionService {
|
|
|
143
143
|
const onlineHandler = () => {
|
|
144
144
|
isOnline = true;
|
|
145
145
|
if (this.pendingRefresher) {
|
|
146
|
-
this.pendingRefresher();
|
|
146
|
+
void this.pendingRefresher();
|
|
147
147
|
this.pendingRefresher = null;
|
|
148
148
|
}
|
|
149
149
|
};
|
|
@@ -184,7 +184,7 @@ class AuthSessionService {
|
|
|
184
184
|
refresh();
|
|
185
185
|
}
|
|
186
186
|
};
|
|
187
|
-
timeoutId = window.setTimeout(refresher, timeoutDelayMS);
|
|
187
|
+
timeoutId = window.setTimeout(() => void refresher(), timeoutDelayMS);
|
|
188
188
|
};
|
|
189
189
|
refresh();
|
|
190
190
|
return () => {
|
|
@@ -223,7 +223,7 @@ class AuthSessionService {
|
|
|
223
223
|
this.refreshPromise = this.localClient.auth.refresh(queryParams, {
|
|
224
224
|
signal: this.refreshingSignalAbortController.signal
|
|
225
225
|
}).then((response) => {
|
|
226
|
-
if (!this.isLive) {
|
|
226
|
+
if (!this.isLive && response.sessionToken) {
|
|
227
227
|
this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {
|
|
228
228
|
maxAge: 30 * 24 * 60 * 60
|
|
229
229
|
// 30 days
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.cjs","sources":["../../src/client/auth.service.ts"],"sourcesContent":["import { Blimu, BlimuError } from '@blimu/client';\nimport type { RefreshResponse } from '@blimu/client/schema';\nimport Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\n\nimport type { AuthState, User } from '../types';\nimport { ExternalStore } from './external-store';\n\nexport const SESSION_COOKIE_NAME = '__bli_session';\nexport const LOCALHOST_JWT_URL_PARAM_NAME = '__lh_jwt';\nexport const LOCALHOST_JWT_COOKIE_NAME = '__lh_jwt';\nexport const SESSION_EXPIRATION_BUFFER = 10;\n\n/**\n * Time conversion utilities\n * JWT tokens use seconds (Unix timestamp), JavaScript Date uses milliseconds\n */\nconst SECONDS_TO_MS = 1000;\n\n/**\n * Convert seconds to milliseconds\n */\nconst secondsToMilliseconds = (seconds: number): number => Math.floor(seconds * SECONDS_TO_MS);\n\n/**\n * Convert milliseconds to seconds\n */\nconst millisecondsToSeconds = (ms: number): number => Math.floor(ms / SECONDS_TO_MS);\n\n/**\n * Get current time in seconds (Unix timestamp)\n */\nconst nowInSeconds = (): number => millisecondsToSeconds(Date.now());\n\n/**\n * Convert JWT expiration time (seconds) to milliseconds\n */\nconst jwtExpToMilliseconds = (exp: number): number => secondsToMilliseconds(exp);\n\n/**\n * Calculate timeout delay in milliseconds for refreshing before expiration\n * @param expirationSeconds - JWT exp claim in seconds\n * @param bufferSeconds - Buffer time in seconds before expiration\n * @returns Timeout delay in milliseconds\n */\nconst calculateTimeoutDelay = (expirationSeconds: number, bufferSeconds: number): number => {\n const expirationMS = jwtExpToMilliseconds(expirationSeconds);\n const bufferMS = secondsToMilliseconds(bufferSeconds);\n const nowMS = Date.now();\n return Math.max(expirationMS - bufferMS - nowMS, 0);\n};\n\n/**\n * Check if a JWT expiration time (in seconds) is expired or will expire within the buffer\n * @param expiration - JWT exp claim in seconds (Unix timestamp)\n * @param buffer - Buffer time in seconds before expiration to consider it expired\n * @returns true if expired or will expire within buffer\n */\nexport const isExpiredIn = (expiration: number, buffer: number): boolean => {\n const now = nowInSeconds();\n return expiration - buffer < now;\n};\n\nexport class AuthSessionService {\n private pendingRefresher: (() => void) | null = null;\n private refreshPromise: Promise<RefreshResponse | { error: string; user: null }> | null = null;\n private refreshingSignals: Set<AbortSignal> = new Set();\n private refreshingSignalAbortController: AbortController | null = null;\n // A local client that doesn't call getSessionToken() which calls refreshSession().\n private localClient: Blimu;\n\n constructor(\n private readonly isLive: boolean,\n private readonly client: Blimu,\n private readonly store: ExternalStore<AuthState>,\n baseURL: string,\n publishableKey: string,\n ) {\n this.localClient = new Blimu({\n baseURL,\n credentials: 'include',\n headers: { 'x-blimu-publishable-key': publishableKey },\n });\n this.handleRequestError = this.handleRequestError.bind(this);\n }\n\n /**\n * Set cookie with appropriate security settings based on environment\n */\n private setCookie(name: string, value: string, options: { maxAge?: number } = {}): void {\n const cookieOptions: Cookies.CookieAttributes = {\n secure: this.isLive, // true for live environments, false for localhost\n sameSite: 'lax',\n path: '/',\n };\n\n if (options.maxAge !== undefined) {\n cookieOptions.expires = options.maxAge / (24 * 60 * 60); // Convert seconds to days\n }\n\n Cookies.set(name, value, cookieOptions);\n }\n\n getSessionPayload(): {\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n } | null {\n const token = Cookies.get(SESSION_COOKIE_NAME);\n\n if (!token) {\n return null;\n }\n\n const decoded = jwtDecode<{\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n }>(token);\n\n return decoded;\n }\n\n async getSessionToken(): Promise<string | undefined> {\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return undefined;\n }\n\n // If a refresh is already in progress, wait for it to complete\n // This prevents infinite recursion when refreshSession() calls client.auth.refresh()\n // which triggers accessToken() which calls getSessionToken()\n if (this.refreshPromise) {\n await this.refreshPromise;\n // After refresh completes, return the updated token from cookie\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n if (isExpiredIn(sessionPayload.exp, 0)) {\n await this.refreshSession();\n }\n\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n async handleRequestError(error: unknown): Promise<{ error: string; user: null }> {\n if (error instanceof BlimuError && [401, 500].includes(error.status)) {\n Cookies.remove(SESSION_COOKIE_NAME);\n Cookies.remove(LOCALHOST_JWT_COOKIE_NAME);\n\n return {\n error: error.message,\n user: null,\n };\n }\n\n if (error instanceof DOMException)\n return {\n error: error.message,\n user: null,\n };\n\n if (error instanceof Error) {\n return {\n error: error.message,\n user: null,\n };\n }\n\n return {\n error: 'unknown error',\n user: null,\n };\n }\n\n async initialize({ signal }: { signal?: AbortSignal } = {}): Promise<{\n error: string | null;\n user: User | null;\n }> {\n const url = new URL(window.location.href);\n const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? undefined;\n\n // Clean up URL parameters immediately to prevent re-processing on re-renders\n if (localhostJWT) {\n url.searchParams.delete(LOCALHOST_JWT_URL_PARAM_NAME);\n window.history.replaceState({}, '', url.toString());\n }\n\n // Handle localhost JWT from URL parameter\n if (localhostJWT) {\n // Set localhost JWT cookie on customer app domain\n this.setCookie(LOCALHOST_JWT_COOKIE_NAME, localhostJWT, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n\n const localhostJWTCookie = Cookies.get(LOCALHOST_JWT_COOKIE_NAME);\n\n if (localhostJWTCookie && !Cookies.get(SESSION_COOKIE_NAME)) {\n const result = await this.refreshSession({ signal }).catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return {\n error: null,\n user: null,\n };\n }\n\n if (isExpiredIn(sessionPayload.exp, SESSION_EXPIRATION_BUFFER)) {\n const result = await this.refreshSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const result = await this.client.auth.getSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n\n return {\n error: null,\n user: result.user,\n };\n }\n\n scheduleRefresh() {\n const abortController = new AbortController();\n let isOnline = true;\n let timeoutId: number | null = null;\n\n const onlineHandler = () => {\n isOnline = true;\n if (this.pendingRefresher) {\n this.pendingRefresher();\n this.pendingRefresher = null;\n }\n };\n const offlineHandler = () => {\n isOnline = false;\n };\n\n window.addEventListener('online', onlineHandler);\n window.addEventListener('offline', offlineHandler);\n\n const refresh = () => {\n // Clear any existing timeout before scheduling a new one\n // This prevents infinite loops when refresh() is called recursively\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload || isExpiredIn(sessionPayload.exp, 0)) return;\n\n // Calculate timeout delay: refresh SESSION_EXPIRATION_BUFFER seconds before expiration\n const timeoutDelayMS = calculateTimeoutDelay(sessionPayload.exp, SESSION_EXPIRATION_BUFFER);\n\n // Prevent scheduling if timeout is negative (token already expired)\n // Allow 0ms delays to handle edge cases where we're exactly at the refresh point\n // This prevents immediate re-scheduling that could cause infinite loops\n if (timeoutDelayMS < 0) {\n return;\n }\n\n // Having this function outside timeout, allows us to call refresh immediately when back online\n const refresher = async () => {\n // Clear timeoutId since we're executing now\n timeoutId = null;\n\n if (!isOnline) {\n // Keep closure of refresh function alive\n this.pendingRefresher = refresher;\n return;\n }\n\n const result = await this.refreshSession({ signal: abortController.signal });\n\n if ('error' in result) {\n // If the refresh was aborted (e.g., due to React strict mode unmounting),\n // don't treat it as an error - just return and let the component remount handle it\n if (result.error === 'aborted') {\n return;\n }\n // For real errors, update the store\n this.store.setState({\n status: 'error',\n user: null,\n error: result.error,\n });\n return;\n } else {\n // After successful refresh, schedule the next refresh with the new token\n refresh();\n }\n };\n\n timeoutId = window.setTimeout(refresher, timeoutDelayMS);\n };\n\n refresh();\n\n return () => {\n if (timeoutId) {\n window.clearTimeout(timeoutId);\n }\n abortController.abort();\n window.removeEventListener('online', onlineHandler);\n window.removeEventListener('offline', offlineHandler);\n };\n }\n\n private async refreshSession({ signal }: { signal?: AbortSignal | undefined } = {}) {\n if (signal) {\n // Add signal to tracking set (was using .has() instead of .add() - bug fix)\n this.refreshingSignals.add(signal);\n\n signal.addEventListener('abort', () => {\n this.refreshingSignals.delete(signal);\n\n if (this.refreshingSignals.size === 0) {\n this.refreshingSignalAbortController?.abort();\n }\n });\n }\n\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshingSignalAbortController = new AbortController();\n\n this.refreshingSignalAbortController.signal.addEventListener('abort', () => {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n });\n\n // Get localhost_jwt from cookie to send as query parameter (only for non-live environments)\n const localhostJWT = !this.isLive ? Cookies.get(LOCALHOST_JWT_COOKIE_NAME) : undefined;\n\n // Build query parameters - only include __lh_jwt for non-live environments when cookie exists\n const queryParams: { __lh_jwt?: string } = {};\n if (localhostJWT) {\n queryParams.__lh_jwt = localhostJWT;\n }\n\n // Use local client to avoid infinite recursion. Regular client calls getSessionToken() which calls refreshSession().\n // There are hacks to avoid infinite recursion, but they are not reliable.\n // The localhost_jwt is sent as a query parameter only in non-live environments\n // Note: Type assertion needed because the generated SDK type doesn't include 'query' in RequestInit,\n // but the underlying CoreClient.request() method does support it\n this.refreshPromise = this.localClient.auth\n .refresh(queryParams, {\n signal: this.refreshingSignalAbortController.signal,\n })\n .then((response) => {\n // Update cookie with new session token from response body\n // Server also sets it via Set-Cookie header on auth domain, but we set it manually on customer domain\n if (!this.isLive) {\n this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n return response;\n })\n .catch(this.handleRequestError);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n }\n }\n}\n"],"names":["client","Blimu","Cookies","jwtDecode","BlimuError","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.service.cjs","sources":["../../src/client/auth.service.ts"],"sourcesContent":["import { Blimu, BlimuError } from '@blimu/client';\nimport type { RefreshResponse } from '@blimu/client/schema';\nimport Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\n\nimport type { AuthState, User } from '../types';\nimport { ExternalStore } from './external-store';\n\nexport const SESSION_COOKIE_NAME = '__bli_session';\nexport const LOCALHOST_JWT_URL_PARAM_NAME = '__lh_jwt';\nexport const LOCALHOST_JWT_COOKIE_NAME = '__lh_jwt';\nexport const SESSION_EXPIRATION_BUFFER = 10;\n\n/**\n * Time conversion utilities\n * JWT tokens use seconds (Unix timestamp), JavaScript Date uses milliseconds\n */\nconst SECONDS_TO_MS = 1000;\n\n/**\n * Convert seconds to milliseconds\n */\nconst secondsToMilliseconds = (seconds: number): number => Math.floor(seconds * SECONDS_TO_MS);\n\n/**\n * Convert milliseconds to seconds\n */\nconst millisecondsToSeconds = (ms: number): number => Math.floor(ms / SECONDS_TO_MS);\n\n/**\n * Get current time in seconds (Unix timestamp)\n */\nconst nowInSeconds = (): number => millisecondsToSeconds(Date.now());\n\n/**\n * Convert JWT expiration time (seconds) to milliseconds\n */\nconst jwtExpToMilliseconds = (exp: number): number => secondsToMilliseconds(exp);\n\n/**\n * Calculate timeout delay in milliseconds for refreshing before expiration\n * @param expirationSeconds - JWT exp claim in seconds\n * @param bufferSeconds - Buffer time in seconds before expiration\n * @returns Timeout delay in milliseconds\n */\nconst calculateTimeoutDelay = (expirationSeconds: number, bufferSeconds: number): number => {\n const expirationMS = jwtExpToMilliseconds(expirationSeconds);\n const bufferMS = secondsToMilliseconds(bufferSeconds);\n const nowMS = Date.now();\n return Math.max(expirationMS - bufferMS - nowMS, 0);\n};\n\n/**\n * Check if a JWT expiration time (in seconds) is expired or will expire within the buffer\n * @param expiration - JWT exp claim in seconds (Unix timestamp)\n * @param buffer - Buffer time in seconds before expiration to consider it expired\n * @returns true if expired or will expire within buffer\n */\nexport const isExpiredIn = (expiration: number, buffer: number): boolean => {\n const now = nowInSeconds();\n return expiration - buffer < now;\n};\n\nexport class AuthSessionService {\n private pendingRefresher: (() => void | Promise<void>) | null = null;\n private refreshPromise: Promise<RefreshResponse | { error: string; user: null }> | null = null;\n private refreshingSignals = new Set<AbortSignal>();\n private refreshingSignalAbortController: AbortController | null = null;\n // A local client that doesn't call getSessionToken() which calls refreshSession().\n private localClient: Blimu;\n\n constructor(\n private readonly isLive: boolean,\n private readonly client: Blimu,\n private readonly store: ExternalStore<AuthState>,\n baseURL: string,\n publishableKey: string,\n ) {\n this.localClient = new Blimu({\n baseURL,\n credentials: 'include',\n headers: { 'x-blimu-publishable-key': publishableKey },\n });\n this.handleRequestError = this.handleRequestError.bind(this);\n }\n\n /**\n * Set cookie with appropriate security settings based on environment\n */\n private setCookie(name: string, value: string, options: { maxAge?: number } = {}): void {\n const cookieOptions: Cookies.CookieAttributes = {\n secure: this.isLive, // true for live environments, false for localhost\n sameSite: 'lax',\n path: '/',\n };\n\n if (options.maxAge !== undefined) {\n cookieOptions.expires = options.maxAge / (24 * 60 * 60); // Convert seconds to days\n }\n\n Cookies.set(name, value, cookieOptions);\n }\n\n getSessionPayload(): {\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n } | null {\n const token = Cookies.get(SESSION_COOKIE_NAME);\n\n if (!token) {\n return null;\n }\n\n const decoded = jwtDecode<{\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n }>(token);\n\n return decoded;\n }\n\n async getSessionToken(): Promise<string | undefined> {\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return undefined;\n }\n\n // If a refresh is already in progress, wait for it to complete\n // This prevents infinite recursion when refreshSession() calls client.auth.refresh()\n // which triggers accessToken() which calls getSessionToken()\n if (this.refreshPromise) {\n await this.refreshPromise;\n // After refresh completes, return the updated token from cookie\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n if (isExpiredIn(sessionPayload.exp, 0)) {\n await this.refreshSession();\n }\n\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n handleRequestError = (error: unknown): { error: string; user: null } => {\n if (error instanceof BlimuError && [401, 500].includes(error.status)) {\n Cookies.remove(SESSION_COOKIE_NAME);\n Cookies.remove(LOCALHOST_JWT_COOKIE_NAME);\n\n return {\n error: error.message,\n user: null,\n };\n }\n\n if (error instanceof DOMException)\n return {\n error: error.message,\n user: null,\n };\n\n if (error instanceof Error) {\n return {\n error: error.message,\n user: null,\n };\n }\n\n return {\n error: 'unknown error',\n user: null,\n };\n };\n\n async initialize({ signal }: { signal?: AbortSignal } = {}): Promise<{\n error: string | null;\n user: User | null;\n }> {\n const url = new URL(window.location.href);\n const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? undefined;\n\n // Clean up URL parameters immediately to prevent re-processing on re-renders\n if (localhostJWT) {\n url.searchParams.delete(LOCALHOST_JWT_URL_PARAM_NAME);\n window.history.replaceState({}, '', url.toString());\n }\n\n // Handle localhost JWT from URL parameter\n if (localhostJWT) {\n // Set localhost JWT cookie on customer app domain\n this.setCookie(LOCALHOST_JWT_COOKIE_NAME, localhostJWT, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n\n const localhostJWTCookie = Cookies.get(LOCALHOST_JWT_COOKIE_NAME);\n\n if (localhostJWTCookie && !Cookies.get(SESSION_COOKIE_NAME)) {\n const result = await this.refreshSession({ signal }).catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return {\n error: null,\n user: null,\n };\n }\n\n if (isExpiredIn(sessionPayload.exp, SESSION_EXPIRATION_BUFFER)) {\n const result = await this.refreshSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const result = await this.client.auth.getSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n\n return {\n error: null,\n user: result.user,\n };\n }\n\n scheduleRefresh() {\n const abortController = new AbortController();\n let isOnline = true;\n let timeoutId: number | null = null;\n\n const onlineHandler = () => {\n isOnline = true;\n if (this.pendingRefresher) {\n void this.pendingRefresher();\n this.pendingRefresher = null;\n }\n };\n const offlineHandler = () => {\n isOnline = false;\n };\n\n window.addEventListener('online', onlineHandler);\n window.addEventListener('offline', offlineHandler);\n\n const refresh = () => {\n // Clear any existing timeout before scheduling a new one\n // This prevents infinite loops when refresh() is called recursively\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload || isExpiredIn(sessionPayload.exp, 0)) return;\n\n // Calculate timeout delay: refresh SESSION_EXPIRATION_BUFFER seconds before expiration\n const timeoutDelayMS = calculateTimeoutDelay(sessionPayload.exp, SESSION_EXPIRATION_BUFFER);\n\n // Prevent scheduling if timeout is negative (token already expired)\n // Allow 0ms delays to handle edge cases where we're exactly at the refresh point\n // This prevents immediate re-scheduling that could cause infinite loops\n if (timeoutDelayMS < 0) {\n return;\n }\n\n // Having this function outside timeout, allows us to call refresh immediately when back online\n const refresher = async (): Promise<void> => {\n // Clear timeoutId since we're executing now\n timeoutId = null;\n\n if (!isOnline) {\n // Keep closure of refresh function alive\n this.pendingRefresher = refresher;\n return;\n }\n\n const result = await this.refreshSession({ signal: abortController.signal });\n\n if ('error' in result) {\n // If the refresh was aborted (e.g., due to React strict mode unmounting),\n // don't treat it as an error - just return and let the component remount handle it\n if (result.error === 'aborted') {\n return;\n }\n // For real errors, update the store\n this.store.setState({\n status: 'error',\n user: null,\n error: result.error,\n });\n return;\n } else {\n // After successful refresh, schedule the next refresh with the new token\n refresh();\n }\n };\n\n timeoutId = window.setTimeout(() => void refresher(), timeoutDelayMS);\n };\n\n refresh();\n\n return (): void => {\n if (timeoutId) {\n window.clearTimeout(timeoutId);\n }\n abortController.abort();\n window.removeEventListener('online', onlineHandler);\n window.removeEventListener('offline', offlineHandler);\n };\n }\n\n private async refreshSession({ signal }: { signal?: AbortSignal | undefined } = {}) {\n if (signal) {\n // Add signal to tracking set (was using .has() instead of .add() - bug fix)\n this.refreshingSignals.add(signal);\n\n signal.addEventListener('abort', () => {\n this.refreshingSignals.delete(signal);\n\n if (this.refreshingSignals.size === 0) {\n this.refreshingSignalAbortController?.abort();\n }\n });\n }\n\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshingSignalAbortController = new AbortController();\n\n this.refreshingSignalAbortController.signal.addEventListener('abort', () => {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n });\n\n // Get localhost_jwt from cookie to send as query parameter (only for non-live environments)\n const localhostJWT = !this.isLive ? Cookies.get(LOCALHOST_JWT_COOKIE_NAME) : undefined;\n\n // Build query parameters - only include __lh_jwt for non-live environments when cookie exists\n const queryParams: { __lh_jwt?: string } = {};\n if (localhostJWT) {\n queryParams.__lh_jwt = localhostJWT;\n }\n\n // Use local client to avoid infinite recursion. Regular client calls getSessionToken() which calls refreshSession().\n // There are hacks to avoid infinite recursion, but they are not reliable.\n // The localhost_jwt is sent as a query parameter only in non-live environments\n // Note: Type assertion needed because the generated SDK type doesn't include 'query' in RequestInit,\n // but the underlying CoreClient.request() method does support it\n this.refreshPromise = this.localClient.auth\n .refresh(queryParams, {\n signal: this.refreshingSignalAbortController.signal,\n })\n .then((response) => {\n // Update cookie with new session token from response body\n // Server also sets it via Set-Cookie header on auth domain, but we set it manually on customer domain\n if (!this.isLive && response.sessionToken) {\n this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n return response;\n })\n .catch(this.handleRequestError);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n }\n }\n}\n"],"names":["client","BlimuError","Cookies","Blimu","jwtDecode","result"],"mappings":"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"}
|
|
@@ -34,10 +34,10 @@ export declare class AuthSessionService {
|
|
|
34
34
|
exp: number;
|
|
35
35
|
} | null;
|
|
36
36
|
getSessionToken(): Promise<string | undefined>;
|
|
37
|
-
handleRequestError(error: unknown)
|
|
37
|
+
handleRequestError: (error: unknown) => {
|
|
38
38
|
error: string;
|
|
39
39
|
user: null;
|
|
40
|
-
}
|
|
40
|
+
};
|
|
41
41
|
initialize({ signal }?: {
|
|
42
42
|
signal?: AbortSignal;
|
|
43
43
|
}): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../src/client/auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAc,MAAM,eAAe,CAAC;AAKlD,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,4BAA4B,aAAa,CAAC;AACvD,eAAO,MAAM,yBAAyB,aAAa,CAAC;AACpD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAyC5C;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,MAAM,EAAE,QAAQ,MAAM,KAAG,OAGhE,CAAC;AAEF,qBAAa,kBAAkB;IAS3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAVxB,OAAO,CAAC,gBAAgB,
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../src/client/auth.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAc,MAAM,eAAe,CAAC;AAKlD,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,4BAA4B,aAAa,CAAC;AACvD,eAAO,MAAM,yBAAyB,aAAa,CAAC;AACpD,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAyC5C;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,MAAM,EAAE,QAAQ,MAAM,KAAG,OAGhE,CAAC;AAEF,qBAAa,kBAAkB;IAS3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAVxB,OAAO,CAAC,gBAAgB,CAA6C;IACrE,OAAO,CAAC,cAAc,CAAyE;IAC/F,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,+BAA+B,CAAgC;IAEvE,OAAO,CAAC,WAAW,CAAQ;gBAGR,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,KAAK,EACb,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,EAChD,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM;IAUxB;;OAEG;IACH,OAAO,CAAC,SAAS;IAcjB,iBAAiB,IAAI;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,IAAI;IAkBF,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAuBpD,kBAAkB,GAAI,OAAO,OAAO,KAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CA4BlE;IAEI,UAAU,CAAC,EAAE,MAAM,EAAE,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAAG,OAAO,CAAC;QACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;KACnB,CAAC;IAgDF,eAAe,UA8EF,IAAI;YAUH,cAAc;CAgE7B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlimuError, Blimu } from "@blimu/client";
|
|
2
2
|
import api from "../node_modules/js-cookie/dist/js.cookie.js";
|
|
3
3
|
import { jwtDecode } from "../node_modules/jwt-decode/build/esm/index.js";
|
|
4
4
|
const SESSION_COOKIE_NAME = "__bli_session";
|
|
@@ -29,6 +29,31 @@ class AuthSessionService {
|
|
|
29
29
|
this.refreshPromise = null;
|
|
30
30
|
this.refreshingSignals = /* @__PURE__ */ new Set();
|
|
31
31
|
this.refreshingSignalAbortController = null;
|
|
32
|
+
this.handleRequestError = (error) => {
|
|
33
|
+
if (error instanceof BlimuError && [401, 500].includes(error.status)) {
|
|
34
|
+
api.remove(SESSION_COOKIE_NAME);
|
|
35
|
+
api.remove(LOCALHOST_JWT_COOKIE_NAME);
|
|
36
|
+
return {
|
|
37
|
+
error: error.message,
|
|
38
|
+
user: null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (error instanceof DOMException)
|
|
42
|
+
return {
|
|
43
|
+
error: error.message,
|
|
44
|
+
user: null
|
|
45
|
+
};
|
|
46
|
+
if (error instanceof Error) {
|
|
47
|
+
return {
|
|
48
|
+
error: error.message,
|
|
49
|
+
user: null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
error: "unknown error",
|
|
54
|
+
user: null
|
|
55
|
+
};
|
|
56
|
+
};
|
|
32
57
|
this.localClient = new Blimu({
|
|
33
58
|
baseURL,
|
|
34
59
|
credentials: "include",
|
|
@@ -73,31 +98,6 @@ class AuthSessionService {
|
|
|
73
98
|
}
|
|
74
99
|
return api.get(SESSION_COOKIE_NAME);
|
|
75
100
|
}
|
|
76
|
-
async handleRequestError(error) {
|
|
77
|
-
if (error instanceof BlimuError && [401, 500].includes(error.status)) {
|
|
78
|
-
api.remove(SESSION_COOKIE_NAME);
|
|
79
|
-
api.remove(LOCALHOST_JWT_COOKIE_NAME);
|
|
80
|
-
return {
|
|
81
|
-
error: error.message,
|
|
82
|
-
user: null
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (error instanceof DOMException)
|
|
86
|
-
return {
|
|
87
|
-
error: error.message,
|
|
88
|
-
user: null
|
|
89
|
-
};
|
|
90
|
-
if (error instanceof Error) {
|
|
91
|
-
return {
|
|
92
|
-
error: error.message,
|
|
93
|
-
user: null
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
error: "unknown error",
|
|
98
|
-
user: null
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
101
|
async initialize({ signal } = {}) {
|
|
102
102
|
const url = new URL(window.location.href);
|
|
103
103
|
const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? void 0;
|
|
@@ -141,7 +141,7 @@ class AuthSessionService {
|
|
|
141
141
|
const onlineHandler = () => {
|
|
142
142
|
isOnline = true;
|
|
143
143
|
if (this.pendingRefresher) {
|
|
144
|
-
this.pendingRefresher();
|
|
144
|
+
void this.pendingRefresher();
|
|
145
145
|
this.pendingRefresher = null;
|
|
146
146
|
}
|
|
147
147
|
};
|
|
@@ -182,7 +182,7 @@ class AuthSessionService {
|
|
|
182
182
|
refresh();
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
|
-
timeoutId = window.setTimeout(refresher, timeoutDelayMS);
|
|
185
|
+
timeoutId = window.setTimeout(() => void refresher(), timeoutDelayMS);
|
|
186
186
|
};
|
|
187
187
|
refresh();
|
|
188
188
|
return () => {
|
|
@@ -221,7 +221,7 @@ class AuthSessionService {
|
|
|
221
221
|
this.refreshPromise = this.localClient.auth.refresh(queryParams, {
|
|
222
222
|
signal: this.refreshingSignalAbortController.signal
|
|
223
223
|
}).then((response) => {
|
|
224
|
-
if (!this.isLive) {
|
|
224
|
+
if (!this.isLive && response.sessionToken) {
|
|
225
225
|
this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {
|
|
226
226
|
maxAge: 30 * 24 * 60 * 60
|
|
227
227
|
// 30 days
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.js","sources":["../../src/client/auth.service.ts"],"sourcesContent":["import { Blimu, BlimuError } from '@blimu/client';\nimport type { RefreshResponse } from '@blimu/client/schema';\nimport Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\n\nimport type { AuthState, User } from '../types';\nimport { ExternalStore } from './external-store';\n\nexport const SESSION_COOKIE_NAME = '__bli_session';\nexport const LOCALHOST_JWT_URL_PARAM_NAME = '__lh_jwt';\nexport const LOCALHOST_JWT_COOKIE_NAME = '__lh_jwt';\nexport const SESSION_EXPIRATION_BUFFER = 10;\n\n/**\n * Time conversion utilities\n * JWT tokens use seconds (Unix timestamp), JavaScript Date uses milliseconds\n */\nconst SECONDS_TO_MS = 1000;\n\n/**\n * Convert seconds to milliseconds\n */\nconst secondsToMilliseconds = (seconds: number): number => Math.floor(seconds * SECONDS_TO_MS);\n\n/**\n * Convert milliseconds to seconds\n */\nconst millisecondsToSeconds = (ms: number): number => Math.floor(ms / SECONDS_TO_MS);\n\n/**\n * Get current time in seconds (Unix timestamp)\n */\nconst nowInSeconds = (): number => millisecondsToSeconds(Date.now());\n\n/**\n * Convert JWT expiration time (seconds) to milliseconds\n */\nconst jwtExpToMilliseconds = (exp: number): number => secondsToMilliseconds(exp);\n\n/**\n * Calculate timeout delay in milliseconds for refreshing before expiration\n * @param expirationSeconds - JWT exp claim in seconds\n * @param bufferSeconds - Buffer time in seconds before expiration\n * @returns Timeout delay in milliseconds\n */\nconst calculateTimeoutDelay = (expirationSeconds: number, bufferSeconds: number): number => {\n const expirationMS = jwtExpToMilliseconds(expirationSeconds);\n const bufferMS = secondsToMilliseconds(bufferSeconds);\n const nowMS = Date.now();\n return Math.max(expirationMS - bufferMS - nowMS, 0);\n};\n\n/**\n * Check if a JWT expiration time (in seconds) is expired or will expire within the buffer\n * @param expiration - JWT exp claim in seconds (Unix timestamp)\n * @param buffer - Buffer time in seconds before expiration to consider it expired\n * @returns true if expired or will expire within buffer\n */\nexport const isExpiredIn = (expiration: number, buffer: number): boolean => {\n const now = nowInSeconds();\n return expiration - buffer < now;\n};\n\nexport class AuthSessionService {\n private pendingRefresher: (() => void) | null = null;\n private refreshPromise: Promise<RefreshResponse | { error: string; user: null }> | null = null;\n private refreshingSignals: Set<AbortSignal> = new Set();\n private refreshingSignalAbortController: AbortController | null = null;\n // A local client that doesn't call getSessionToken() which calls refreshSession().\n private localClient: Blimu;\n\n constructor(\n private readonly isLive: boolean,\n private readonly client: Blimu,\n private readonly store: ExternalStore<AuthState>,\n baseURL: string,\n publishableKey: string,\n ) {\n this.localClient = new Blimu({\n baseURL,\n credentials: 'include',\n headers: { 'x-blimu-publishable-key': publishableKey },\n });\n this.handleRequestError = this.handleRequestError.bind(this);\n }\n\n /**\n * Set cookie with appropriate security settings based on environment\n */\n private setCookie(name: string, value: string, options: { maxAge?: number } = {}): void {\n const cookieOptions: Cookies.CookieAttributes = {\n secure: this.isLive, // true for live environments, false for localhost\n sameSite: 'lax',\n path: '/',\n };\n\n if (options.maxAge !== undefined) {\n cookieOptions.expires = options.maxAge / (24 * 60 * 60); // Convert seconds to days\n }\n\n Cookies.set(name, value, cookieOptions);\n }\n\n getSessionPayload(): {\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n } | null {\n const token = Cookies.get(SESSION_COOKIE_NAME);\n\n if (!token) {\n return null;\n }\n\n const decoded = jwtDecode<{\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n }>(token);\n\n return decoded;\n }\n\n async getSessionToken(): Promise<string | undefined> {\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return undefined;\n }\n\n // If a refresh is already in progress, wait for it to complete\n // This prevents infinite recursion when refreshSession() calls client.auth.refresh()\n // which triggers accessToken() which calls getSessionToken()\n if (this.refreshPromise) {\n await this.refreshPromise;\n // After refresh completes, return the updated token from cookie\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n if (isExpiredIn(sessionPayload.exp, 0)) {\n await this.refreshSession();\n }\n\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n async handleRequestError(error: unknown): Promise<{ error: string; user: null }> {\n if (error instanceof BlimuError && [401, 500].includes(error.status)) {\n Cookies.remove(SESSION_COOKIE_NAME);\n Cookies.remove(LOCALHOST_JWT_COOKIE_NAME);\n\n return {\n error: error.message,\n user: null,\n };\n }\n\n if (error instanceof DOMException)\n return {\n error: error.message,\n user: null,\n };\n\n if (error instanceof Error) {\n return {\n error: error.message,\n user: null,\n };\n }\n\n return {\n error: 'unknown error',\n user: null,\n };\n }\n\n async initialize({ signal }: { signal?: AbortSignal } = {}): Promise<{\n error: string | null;\n user: User | null;\n }> {\n const url = new URL(window.location.href);\n const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? undefined;\n\n // Clean up URL parameters immediately to prevent re-processing on re-renders\n if (localhostJWT) {\n url.searchParams.delete(LOCALHOST_JWT_URL_PARAM_NAME);\n window.history.replaceState({}, '', url.toString());\n }\n\n // Handle localhost JWT from URL parameter\n if (localhostJWT) {\n // Set localhost JWT cookie on customer app domain\n this.setCookie(LOCALHOST_JWT_COOKIE_NAME, localhostJWT, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n\n const localhostJWTCookie = Cookies.get(LOCALHOST_JWT_COOKIE_NAME);\n\n if (localhostJWTCookie && !Cookies.get(SESSION_COOKIE_NAME)) {\n const result = await this.refreshSession({ signal }).catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return {\n error: null,\n user: null,\n };\n }\n\n if (isExpiredIn(sessionPayload.exp, SESSION_EXPIRATION_BUFFER)) {\n const result = await this.refreshSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const result = await this.client.auth.getSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n\n return {\n error: null,\n user: result.user,\n };\n }\n\n scheduleRefresh() {\n const abortController = new AbortController();\n let isOnline = true;\n let timeoutId: number | null = null;\n\n const onlineHandler = () => {\n isOnline = true;\n if (this.pendingRefresher) {\n this.pendingRefresher();\n this.pendingRefresher = null;\n }\n };\n const offlineHandler = () => {\n isOnline = false;\n };\n\n window.addEventListener('online', onlineHandler);\n window.addEventListener('offline', offlineHandler);\n\n const refresh = () => {\n // Clear any existing timeout before scheduling a new one\n // This prevents infinite loops when refresh() is called recursively\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload || isExpiredIn(sessionPayload.exp, 0)) return;\n\n // Calculate timeout delay: refresh SESSION_EXPIRATION_BUFFER seconds before expiration\n const timeoutDelayMS = calculateTimeoutDelay(sessionPayload.exp, SESSION_EXPIRATION_BUFFER);\n\n // Prevent scheduling if timeout is negative (token already expired)\n // Allow 0ms delays to handle edge cases where we're exactly at the refresh point\n // This prevents immediate re-scheduling that could cause infinite loops\n if (timeoutDelayMS < 0) {\n return;\n }\n\n // Having this function outside timeout, allows us to call refresh immediately when back online\n const refresher = async () => {\n // Clear timeoutId since we're executing now\n timeoutId = null;\n\n if (!isOnline) {\n // Keep closure of refresh function alive\n this.pendingRefresher = refresher;\n return;\n }\n\n const result = await this.refreshSession({ signal: abortController.signal });\n\n if ('error' in result) {\n // If the refresh was aborted (e.g., due to React strict mode unmounting),\n // don't treat it as an error - just return and let the component remount handle it\n if (result.error === 'aborted') {\n return;\n }\n // For real errors, update the store\n this.store.setState({\n status: 'error',\n user: null,\n error: result.error,\n });\n return;\n } else {\n // After successful refresh, schedule the next refresh with the new token\n refresh();\n }\n };\n\n timeoutId = window.setTimeout(refresher, timeoutDelayMS);\n };\n\n refresh();\n\n return () => {\n if (timeoutId) {\n window.clearTimeout(timeoutId);\n }\n abortController.abort();\n window.removeEventListener('online', onlineHandler);\n window.removeEventListener('offline', offlineHandler);\n };\n }\n\n private async refreshSession({ signal }: { signal?: AbortSignal | undefined } = {}) {\n if (signal) {\n // Add signal to tracking set (was using .has() instead of .add() - bug fix)\n this.refreshingSignals.add(signal);\n\n signal.addEventListener('abort', () => {\n this.refreshingSignals.delete(signal);\n\n if (this.refreshingSignals.size === 0) {\n this.refreshingSignalAbortController?.abort();\n }\n });\n }\n\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshingSignalAbortController = new AbortController();\n\n this.refreshingSignalAbortController.signal.addEventListener('abort', () => {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n });\n\n // Get localhost_jwt from cookie to send as query parameter (only for non-live environments)\n const localhostJWT = !this.isLive ? Cookies.get(LOCALHOST_JWT_COOKIE_NAME) : undefined;\n\n // Build query parameters - only include __lh_jwt for non-live environments when cookie exists\n const queryParams: { __lh_jwt?: string } = {};\n if (localhostJWT) {\n queryParams.__lh_jwt = localhostJWT;\n }\n\n // Use local client to avoid infinite recursion. Regular client calls getSessionToken() which calls refreshSession().\n // There are hacks to avoid infinite recursion, but they are not reliable.\n // The localhost_jwt is sent as a query parameter only in non-live environments\n // Note: Type assertion needed because the generated SDK type doesn't include 'query' in RequestInit,\n // but the underlying CoreClient.request() method does support it\n this.refreshPromise = this.localClient.auth\n .refresh(queryParams, {\n signal: this.refreshingSignalAbortController.signal,\n })\n .then((response) => {\n // Update cookie with new session token from response body\n // Server also sets it via Set-Cookie header on auth domain, but we set it manually on customer domain\n if (!this.isLive) {\n this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n return response;\n })\n .catch(this.handleRequestError);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n }\n }\n}\n"],"names":["Cookies","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.service.js","sources":["../../src/client/auth.service.ts"],"sourcesContent":["import { Blimu, BlimuError } from '@blimu/client';\nimport type { RefreshResponse } from '@blimu/client/schema';\nimport Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\n\nimport type { AuthState, User } from '../types';\nimport { ExternalStore } from './external-store';\n\nexport const SESSION_COOKIE_NAME = '__bli_session';\nexport const LOCALHOST_JWT_URL_PARAM_NAME = '__lh_jwt';\nexport const LOCALHOST_JWT_COOKIE_NAME = '__lh_jwt';\nexport const SESSION_EXPIRATION_BUFFER = 10;\n\n/**\n * Time conversion utilities\n * JWT tokens use seconds (Unix timestamp), JavaScript Date uses milliseconds\n */\nconst SECONDS_TO_MS = 1000;\n\n/**\n * Convert seconds to milliseconds\n */\nconst secondsToMilliseconds = (seconds: number): number => Math.floor(seconds * SECONDS_TO_MS);\n\n/**\n * Convert milliseconds to seconds\n */\nconst millisecondsToSeconds = (ms: number): number => Math.floor(ms / SECONDS_TO_MS);\n\n/**\n * Get current time in seconds (Unix timestamp)\n */\nconst nowInSeconds = (): number => millisecondsToSeconds(Date.now());\n\n/**\n * Convert JWT expiration time (seconds) to milliseconds\n */\nconst jwtExpToMilliseconds = (exp: number): number => secondsToMilliseconds(exp);\n\n/**\n * Calculate timeout delay in milliseconds for refreshing before expiration\n * @param expirationSeconds - JWT exp claim in seconds\n * @param bufferSeconds - Buffer time in seconds before expiration\n * @returns Timeout delay in milliseconds\n */\nconst calculateTimeoutDelay = (expirationSeconds: number, bufferSeconds: number): number => {\n const expirationMS = jwtExpToMilliseconds(expirationSeconds);\n const bufferMS = secondsToMilliseconds(bufferSeconds);\n const nowMS = Date.now();\n return Math.max(expirationMS - bufferMS - nowMS, 0);\n};\n\n/**\n * Check if a JWT expiration time (in seconds) is expired or will expire within the buffer\n * @param expiration - JWT exp claim in seconds (Unix timestamp)\n * @param buffer - Buffer time in seconds before expiration to consider it expired\n * @returns true if expired or will expire within buffer\n */\nexport const isExpiredIn = (expiration: number, buffer: number): boolean => {\n const now = nowInSeconds();\n return expiration - buffer < now;\n};\n\nexport class AuthSessionService {\n private pendingRefresher: (() => void | Promise<void>) | null = null;\n private refreshPromise: Promise<RefreshResponse | { error: string; user: null }> | null = null;\n private refreshingSignals = new Set<AbortSignal>();\n private refreshingSignalAbortController: AbortController | null = null;\n // A local client that doesn't call getSessionToken() which calls refreshSession().\n private localClient: Blimu;\n\n constructor(\n private readonly isLive: boolean,\n private readonly client: Blimu,\n private readonly store: ExternalStore<AuthState>,\n baseURL: string,\n publishableKey: string,\n ) {\n this.localClient = new Blimu({\n baseURL,\n credentials: 'include',\n headers: { 'x-blimu-publishable-key': publishableKey },\n });\n this.handleRequestError = this.handleRequestError.bind(this);\n }\n\n /**\n * Set cookie with appropriate security settings based on environment\n */\n private setCookie(name: string, value: string, options: { maxAge?: number } = {}): void {\n const cookieOptions: Cookies.CookieAttributes = {\n secure: this.isLive, // true for live environments, false for localhost\n sameSite: 'lax',\n path: '/',\n };\n\n if (options.maxAge !== undefined) {\n cookieOptions.expires = options.maxAge / (24 * 60 * 60); // Convert seconds to days\n }\n\n Cookies.set(name, value, cookieOptions);\n }\n\n getSessionPayload(): {\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n } | null {\n const token = Cookies.get(SESSION_COOKIE_NAME);\n\n if (!token) {\n return null;\n }\n\n const decoded = jwtDecode<{\n sub: string;\n environmentId: string;\n type: string;\n iat: number;\n exp: number;\n }>(token);\n\n return decoded;\n }\n\n async getSessionToken(): Promise<string | undefined> {\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return undefined;\n }\n\n // If a refresh is already in progress, wait for it to complete\n // This prevents infinite recursion when refreshSession() calls client.auth.refresh()\n // which triggers accessToken() which calls getSessionToken()\n if (this.refreshPromise) {\n await this.refreshPromise;\n // After refresh completes, return the updated token from cookie\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n if (isExpiredIn(sessionPayload.exp, 0)) {\n await this.refreshSession();\n }\n\n return Cookies.get(SESSION_COOKIE_NAME);\n }\n\n handleRequestError = (error: unknown): { error: string; user: null } => {\n if (error instanceof BlimuError && [401, 500].includes(error.status)) {\n Cookies.remove(SESSION_COOKIE_NAME);\n Cookies.remove(LOCALHOST_JWT_COOKIE_NAME);\n\n return {\n error: error.message,\n user: null,\n };\n }\n\n if (error instanceof DOMException)\n return {\n error: error.message,\n user: null,\n };\n\n if (error instanceof Error) {\n return {\n error: error.message,\n user: null,\n };\n }\n\n return {\n error: 'unknown error',\n user: null,\n };\n };\n\n async initialize({ signal }: { signal?: AbortSignal } = {}): Promise<{\n error: string | null;\n user: User | null;\n }> {\n const url = new URL(window.location.href);\n const localhostJWT = url.searchParams.get(LOCALHOST_JWT_URL_PARAM_NAME) ?? undefined;\n\n // Clean up URL parameters immediately to prevent re-processing on re-renders\n if (localhostJWT) {\n url.searchParams.delete(LOCALHOST_JWT_URL_PARAM_NAME);\n window.history.replaceState({}, '', url.toString());\n }\n\n // Handle localhost JWT from URL parameter\n if (localhostJWT) {\n // Set localhost JWT cookie on customer app domain\n this.setCookie(LOCALHOST_JWT_COOKIE_NAME, localhostJWT, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n\n const localhostJWTCookie = Cookies.get(LOCALHOST_JWT_COOKIE_NAME);\n\n if (localhostJWTCookie && !Cookies.get(SESSION_COOKIE_NAME)) {\n const result = await this.refreshSession({ signal }).catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload) {\n return {\n error: null,\n user: null,\n };\n }\n\n if (isExpiredIn(sessionPayload.exp, SESSION_EXPIRATION_BUFFER)) {\n const result = await this.refreshSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n }\n\n const result = await this.client.auth.getSession().catch(this.handleRequestError);\n if ('error' in result) return result;\n\n return {\n error: null,\n user: result.user,\n };\n }\n\n scheduleRefresh() {\n const abortController = new AbortController();\n let isOnline = true;\n let timeoutId: number | null = null;\n\n const onlineHandler = () => {\n isOnline = true;\n if (this.pendingRefresher) {\n void this.pendingRefresher();\n this.pendingRefresher = null;\n }\n };\n const offlineHandler = () => {\n isOnline = false;\n };\n\n window.addEventListener('online', onlineHandler);\n window.addEventListener('offline', offlineHandler);\n\n const refresh = () => {\n // Clear any existing timeout before scheduling a new one\n // This prevents infinite loops when refresh() is called recursively\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n const sessionPayload = this.getSessionPayload();\n\n if (!sessionPayload || isExpiredIn(sessionPayload.exp, 0)) return;\n\n // Calculate timeout delay: refresh SESSION_EXPIRATION_BUFFER seconds before expiration\n const timeoutDelayMS = calculateTimeoutDelay(sessionPayload.exp, SESSION_EXPIRATION_BUFFER);\n\n // Prevent scheduling if timeout is negative (token already expired)\n // Allow 0ms delays to handle edge cases where we're exactly at the refresh point\n // This prevents immediate re-scheduling that could cause infinite loops\n if (timeoutDelayMS < 0) {\n return;\n }\n\n // Having this function outside timeout, allows us to call refresh immediately when back online\n const refresher = async (): Promise<void> => {\n // Clear timeoutId since we're executing now\n timeoutId = null;\n\n if (!isOnline) {\n // Keep closure of refresh function alive\n this.pendingRefresher = refresher;\n return;\n }\n\n const result = await this.refreshSession({ signal: abortController.signal });\n\n if ('error' in result) {\n // If the refresh was aborted (e.g., due to React strict mode unmounting),\n // don't treat it as an error - just return and let the component remount handle it\n if (result.error === 'aborted') {\n return;\n }\n // For real errors, update the store\n this.store.setState({\n status: 'error',\n user: null,\n error: result.error,\n });\n return;\n } else {\n // After successful refresh, schedule the next refresh with the new token\n refresh();\n }\n };\n\n timeoutId = window.setTimeout(() => void refresher(), timeoutDelayMS);\n };\n\n refresh();\n\n return (): void => {\n if (timeoutId) {\n window.clearTimeout(timeoutId);\n }\n abortController.abort();\n window.removeEventListener('online', onlineHandler);\n window.removeEventListener('offline', offlineHandler);\n };\n }\n\n private async refreshSession({ signal }: { signal?: AbortSignal | undefined } = {}) {\n if (signal) {\n // Add signal to tracking set (was using .has() instead of .add() - bug fix)\n this.refreshingSignals.add(signal);\n\n signal.addEventListener('abort', () => {\n this.refreshingSignals.delete(signal);\n\n if (this.refreshingSignals.size === 0) {\n this.refreshingSignalAbortController?.abort();\n }\n });\n }\n\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshingSignalAbortController = new AbortController();\n\n this.refreshingSignalAbortController.signal.addEventListener('abort', () => {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n });\n\n // Get localhost_jwt from cookie to send as query parameter (only for non-live environments)\n const localhostJWT = !this.isLive ? Cookies.get(LOCALHOST_JWT_COOKIE_NAME) : undefined;\n\n // Build query parameters - only include __lh_jwt for non-live environments when cookie exists\n const queryParams: { __lh_jwt?: string } = {};\n if (localhostJWT) {\n queryParams.__lh_jwt = localhostJWT;\n }\n\n // Use local client to avoid infinite recursion. Regular client calls getSessionToken() which calls refreshSession().\n // There are hacks to avoid infinite recursion, but they are not reliable.\n // The localhost_jwt is sent as a query parameter only in non-live environments\n // Note: Type assertion needed because the generated SDK type doesn't include 'query' in RequestInit,\n // but the underlying CoreClient.request() method does support it\n this.refreshPromise = this.localClient.auth\n .refresh(queryParams, {\n signal: this.refreshingSignalAbortController.signal,\n })\n .then((response) => {\n // Update cookie with new session token from response body\n // Server also sets it via Set-Cookie header on auth domain, but we set it manually on customer domain\n if (!this.isLive && response.sessionToken) {\n this.setCookie(SESSION_COOKIE_NAME, response.sessionToken, {\n maxAge: 30 * 24 * 60 * 60, // 30 days\n });\n }\n return response;\n })\n .catch(this.handleRequestError);\n\n try {\n return await this.refreshPromise;\n } finally {\n this.refreshPromise = null;\n this.refreshingSignalAbortController = null;\n this.refreshingSignals.clear();\n }\n }\n}\n"],"names":["Cookies","result"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const client = require("@blimu/client");
|
|
4
|
+
const js_cookie = require("../node_modules/js-cookie/dist/js.cookie.cjs");
|
|
4
5
|
const publishableKey = require("../utils/publishable-key.cjs");
|
|
5
6
|
const auth_service = require("./auth.service.cjs");
|
|
6
7
|
const externalStore = require("./external-store.cjs");
|
|
@@ -11,30 +12,46 @@ class BlimuRuntimeClientWrapper {
|
|
|
11
12
|
this.initializePromise = null;
|
|
12
13
|
this.initializeAbortController = null;
|
|
13
14
|
this.redirectToAuth = (returnUrl) => {
|
|
14
|
-
const redirectUrl = returnUrl
|
|
15
|
+
const redirectUrl = returnUrl ?? window.location.href;
|
|
15
16
|
const authUrl = new URL(`${this.authDomain}/login`);
|
|
16
17
|
authUrl.searchParams.set("redirect_url", encodeURIComponent(redirectUrl));
|
|
17
18
|
window.location.href = authUrl.toString();
|
|
18
19
|
};
|
|
19
20
|
this.logout = async () => {
|
|
21
|
+
console.log("[logout] Starting logout...");
|
|
22
|
+
console.log("[logout] Cookies before logout:", document.cookie);
|
|
20
23
|
try {
|
|
21
|
-
const sessionToken = this.session.getSessionToken();
|
|
24
|
+
const sessionToken = await this.session.getSessionToken();
|
|
22
25
|
if (sessionToken) {
|
|
26
|
+
const localhostJWT = this.isLive ? void 0 : js_cookie.default.get(auth_service.LOCALHOST_JWT_COOKIE_NAME);
|
|
27
|
+
console.log("[logout] Calling logout API...");
|
|
23
28
|
await this.client.auth.logout({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Authorization: `Bearer ${sessionToken}`
|
|
27
|
-
}
|
|
29
|
+
// Build query parameters - only include __lh_jwt for non-live environments when cookie exists
|
|
30
|
+
...localhostJWT ? { __lh_jwt: localhostJWT } : {}
|
|
28
31
|
});
|
|
32
|
+
console.log("[logout] Logout API call successful");
|
|
33
|
+
if (!this.isLive && localhostJWT) {
|
|
34
|
+
js_cookie.default.remove(auth_service.LOCALHOST_JWT_COOKIE_NAME, { path: "/" });
|
|
35
|
+
}
|
|
29
36
|
}
|
|
30
37
|
} catch (error) {
|
|
31
|
-
console.error("Logout error:", error);
|
|
38
|
+
console.error("[logout] Logout error:", error);
|
|
32
39
|
} finally {
|
|
40
|
+
console.log("[logout] Clearing session cookie...");
|
|
41
|
+
if (this.isLive && this.authDomain) {
|
|
42
|
+
const baseDomain = this.authDomain.replace(/^https?:\/\//, "").replace(/^id\./, "");
|
|
43
|
+
console.log("[logout] Removing cookie with domain:", `.${baseDomain}`);
|
|
44
|
+
js_cookie.default.remove(auth_service.SESSION_COOKIE_NAME, { path: "/", domain: `.${baseDomain}` });
|
|
45
|
+
} else {
|
|
46
|
+
js_cookie.default.remove(auth_service.SESSION_COOKIE_NAME, { path: "/" });
|
|
47
|
+
}
|
|
48
|
+
console.log("[logout] Cookies after clearing:", document.cookie);
|
|
33
49
|
this.store.setState({
|
|
34
50
|
user: null,
|
|
35
51
|
error: null,
|
|
36
52
|
status: "unauthenticated"
|
|
37
53
|
});
|
|
54
|
+
console.log("[logout] Logout complete, state set to unauthenticated");
|
|
38
55
|
}
|
|
39
56
|
};
|
|
40
57
|
this.getAccessToken = async (_options) => {
|
|
@@ -58,7 +75,9 @@ class BlimuRuntimeClientWrapper {
|
|
|
58
75
|
this.client = new client.Blimu({
|
|
59
76
|
baseURL: authApiUrl,
|
|
60
77
|
bearer: () => this.session.getSessionToken(),
|
|
61
|
-
headers: { "x-blimu-publishable-key": config.publishableKey }
|
|
78
|
+
headers: { "x-blimu-publishable-key": config.publishableKey },
|
|
79
|
+
credentials: "include"
|
|
80
|
+
// Required to send httpOnly cookies (like __bli_client) in cross-origin requests
|
|
62
81
|
});
|
|
63
82
|
this.session = new auth_service.AuthSessionService(
|
|
64
83
|
this.isLive,
|
|
@@ -124,7 +143,7 @@ class BlimuRuntimeClientWrapper {
|
|
|
124
143
|
* This method is idempotent - if a refresh is already in progress, it will wait for that refresh to complete
|
|
125
144
|
*/
|
|
126
145
|
scheduleRefresh() {
|
|
127
|
-
|
|
146
|
+
this.session.scheduleRefresh();
|
|
128
147
|
}
|
|
129
148
|
/**
|
|
130
149
|
* Get the runtime client for direct API calls
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-client.cjs","sources":["../../src/client/runtime-client.ts"],"sourcesContent":["import { Blimu } from '@blimu/client';\n\nimport type { AuthState, BlimuConfig } from '../types';\nimport { getAuthApiUrl, getAuthDomainFromPublishableKey } from '../utils/publishable-key';\nimport { AuthSessionService } from './auth.service';\nimport { ExternalStore } from './external-store';\n\nexport class BlimuRuntimeClientWrapper {\n private client: Blimu;\n private authDomain: string | null = null;\n private initialized: boolean = false;\n private readonly session: AuthSessionService;\n\n public config: BlimuConfig;\n public isLive: boolean;\n private initializePromise: Promise<void> | null = null;\n private initializeAbortController: AbortController | null = null;\n\n public readonly store: ExternalStore<AuthState>;\n\n constructor(config: BlimuConfig) {\n this.config = config;\n this.store = new ExternalStore<AuthState>({\n user: null,\n error: null,\n status: 'idle',\n });\n this.isLive = config.publishableKey.includes('live');\n\n // Get auth UI domain from publishable key\n this.authDomain = getAuthDomainFromPublishableKey(config.publishableKey);\n if (!this.authDomain) {\n throw new Error('Failed to determine auth domain from publishable key');\n }\n\n // Get auth API URL from publishable key\n const authApiUrl = getAuthApiUrl(config.publishableKey);\n\n if (!authApiUrl) {\n throw new Error('Failed to determine auth API URL from publishable key');\n }\n\n this.client = new Blimu({\n baseURL: authApiUrl,\n bearer: () => this.session.getSessionToken(),\n headers: { 'x-blimu-publishable-key': config.publishableKey },\n });\n\n this.session = new AuthSessionService(\n this.isLive,\n this.client,\n this.store,\n authApiUrl,\n config.publishableKey,\n );\n\n this.initialize = this.initialize.bind(this);\n }\n\n /**\n * Initialize authentication by checking for existing session\n * Reads session token from cookie and validates it\n * This method is idempotent - if initialization is already in progress, it will wait for that to complete\n */\n initialize(): (() => void) | void {\n // If already initialized, return early\n if (this.initialized) return;\n\n // If initialization is in progress, return early (prevents concurrent calls)\n if (this.initializePromise) return;\n\n this.initializeAbortController = new AbortController();\n\n this.initializePromise = this.session\n .initialize({\n signal: this.initializeAbortController.signal,\n })\n .then((result) => {\n if (result.error) {\n this.store.setState({\n user: null,\n error: result.error,\n status: 'error',\n });\n } else if (result.user) {\n this.store.setState({\n user: result.user,\n error: null,\n status: 'authenticated',\n });\n } else {\n this.store.setState({\n user: null,\n error: null,\n status: 'unauthenticated',\n });\n }\n })\n .catch((error) => {\n console.error('Initialize error:', error);\n this.store.setState({\n user: null,\n error: error instanceof Error ? error.message : 'Initialization failed',\n status: 'error',\n });\n })\n .finally(() => {\n // Mark as initialized after completion (success or failure)\n // This prevents infinite loops from re-renders\n this.initialized = true;\n this.initializePromise = null;\n });\n\n return () => {\n console.log('aborting initialize');\n this.initializeAbortController?.abort('unmounting');\n this.initializePromise = null;\n // Don't reset initialized flag here - let the promise handle it\n };\n }\n\n /**\n * Refresh session token using the client token from httpOnly cookie\n * The client token is stored in an httpOnly cookie and sent automatically\n * Note: The auth worker should extract the __bli_client cookie and include it in the request body\n * This method is idempotent - if a refresh is already in progress, it will wait for that refresh to complete\n */\n public scheduleRefresh() {\n return this.session.scheduleRefresh();\n }\n\n /**\n * Redirect user to auth domain for authentication\n * Uses the auth domain derived from the publishable key\n */\n public redirectToAuth = (returnUrl?: string): void => {\n const redirectUrl = returnUrl || window.location.href;\n\n // Build auth URL on the auth domain\n const authUrl = new URL(`${this.authDomain}/login`);\n authUrl.searchParams.set('redirect_url', encodeURIComponent(redirectUrl));\n\n // Redirect to auth domain\n window.location.href = authUrl.toString();\n };\n\n /**\n * Logout user\n * Calls the logout endpoint on the API domain using this.client\n */\n public logout = async (): Promise<void> => {\n try {\n const sessionToken = this.session.getSessionToken();\n\n if (sessionToken) {\n await this.client.auth.logout({\n headers: {\n 'x-blimu-publishable-key': this.config.publishableKey,\n Authorization: `Bearer ${sessionToken}`,\n },\n });\n }\n } catch (error) {\n console.error('Logout error:', error);\n } finally {\n this.store.setState({\n user: null,\n error: null,\n status: 'unauthenticated',\n });\n }\n };\n\n /**\n * Get the current session token\n */\n\n getAccessToken = async (_options: { template: 'web' }): Promise<string | undefined> => {\n return await this.session.getSessionToken();\n };\n\n /**\n * Get the runtime client for direct API calls\n */\n getClient(): Blimu {\n return this.client;\n }\n}\n"],"names":["ExternalStore","getAuthDomainFromPublishableKey","getAuthApiUrl","Blimu","AuthSessionService"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime-client.cjs","sources":["../../src/client/runtime-client.ts"],"sourcesContent":["import { Blimu } from '@blimu/client';\nimport Cookies from 'js-cookie';\n\nimport type { AuthState, BlimuConfig } from '../types';\nimport { getAuthApiUrl, getAuthDomainFromPublishableKey } from '../utils/publishable-key';\nimport { AuthSessionService, LOCALHOST_JWT_COOKIE_NAME, SESSION_COOKIE_NAME } from './auth.service';\nimport { ExternalStore } from './external-store';\n\nexport class BlimuRuntimeClientWrapper {\n private client: Blimu;\n private authDomain: string | null = null;\n private initialized = false;\n private readonly session: AuthSessionService;\n\n public config: BlimuConfig;\n public isLive: boolean;\n private initializePromise: Promise<void> | null = null;\n private initializeAbortController: AbortController | null = null;\n\n public readonly store: ExternalStore<AuthState>;\n\n constructor(config: BlimuConfig) {\n this.config = config;\n this.store = new ExternalStore<AuthState>({\n user: null,\n error: null,\n status: 'idle',\n });\n this.isLive = config.publishableKey.includes('live');\n\n // Get auth UI domain from publishable key\n this.authDomain = getAuthDomainFromPublishableKey(config.publishableKey);\n if (!this.authDomain) {\n throw new Error('Failed to determine auth domain from publishable key');\n }\n\n // Get auth API URL from publishable key\n const authApiUrl = getAuthApiUrl(config.publishableKey);\n\n if (!authApiUrl) {\n throw new Error('Failed to determine auth API URL from publishable key');\n }\n\n this.client = new Blimu({\n baseURL: authApiUrl,\n bearer: () => this.session.getSessionToken(),\n headers: { 'x-blimu-publishable-key': config.publishableKey },\n credentials: 'include', // Required to send httpOnly cookies (like __bli_client) in cross-origin requests\n });\n\n this.session = new AuthSessionService(\n this.isLive,\n this.client,\n this.store,\n authApiUrl,\n config.publishableKey,\n );\n\n this.initialize = this.initialize.bind(this);\n }\n\n /**\n * Initialize authentication by checking for existing session\n * Reads session token from cookie and validates it\n * This method is idempotent - if initialization is already in progress, it will wait for that to complete\n */\n initialize(): (() => void) | void {\n // If already initialized, return early\n if (this.initialized) return;\n\n // If initialization is in progress, return early (prevents concurrent calls)\n if (this.initializePromise) return;\n\n this.initializeAbortController = new AbortController();\n\n this.initializePromise = this.session\n .initialize({\n signal: this.initializeAbortController.signal,\n })\n .then((result) => {\n if (result.error) {\n this.store.setState({\n user: null,\n error: result.error,\n status: 'error',\n });\n } else if (result.user) {\n this.store.setState({\n user: result.user,\n error: null,\n status: 'authenticated',\n });\n } else {\n this.store.setState({\n user: null,\n error: null,\n status: 'unauthenticated',\n });\n }\n })\n .catch((error) => {\n console.error('Initialize error:', error);\n this.store.setState({\n user: null,\n error: error instanceof Error ? error.message : 'Initialization failed',\n status: 'error',\n });\n })\n .finally(() => {\n // Mark as initialized after completion (success or failure)\n // This prevents infinite loops from re-renders\n this.initialized = true;\n this.initializePromise = null;\n });\n\n return () => {\n console.log('aborting initialize');\n this.initializeAbortController?.abort('unmounting');\n this.initializePromise = null;\n // Don't reset initialized flag here - let the promise handle it\n };\n }\n\n /**\n * Refresh session token using the client token from httpOnly cookie\n * The client token is stored in an httpOnly cookie and sent automatically\n * Note: The auth worker should extract the __bli_client cookie and include it in the request body\n * This method is idempotent - if a refresh is already in progress, it will wait for that refresh to complete\n */\n public scheduleRefresh(): void {\n this.session.scheduleRefresh();\n }\n\n /**\n * Redirect user to auth domain for authentication\n * Uses the auth domain derived from the publishable key\n */\n public redirectToAuth = (returnUrl?: string): void => {\n const redirectUrl = returnUrl ?? window.location.href;\n\n // Build auth URL on the auth domain\n const authUrl = new URL(`${this.authDomain}/login`);\n authUrl.searchParams.set('redirect_url', encodeURIComponent(redirectUrl));\n\n // Redirect to auth domain\n window.location.href = authUrl.toString();\n };\n\n /**\n * Logout user\n * Calls the logout endpoint on the API domain using this.client\n * For TEST environments: sends __lh_jwt query parameter from cookie\n * For LIVE environments: uses __bli_client cookie automatically\n */\n public logout = async (): Promise<void> => {\n console.log('[logout] Starting logout...');\n console.log('[logout] Cookies before logout:', document.cookie);\n try {\n const sessionToken = await this.session.getSessionToken();\n\n if (sessionToken) {\n // For TEST environments: Get localhostJwt from cookie and pass as query parameter\n const localhostJWT = this.isLive ? undefined : Cookies.get(LOCALHOST_JWT_COOKIE_NAME);\n\n console.log('[logout] Calling logout API...');\n await this.client.auth.logout({\n // Build query parameters - only include __lh_jwt for non-live environments when cookie exists\n ...(localhostJWT ? { __lh_jwt: localhostJWT } : {}),\n });\n console.log('[logout] Logout API call successful');\n\n // Clear localhostJwt cookie for TEST environments after logout\n if (!this.isLive && localhostJWT) {\n Cookies.remove(LOCALHOST_JWT_COOKIE_NAME, { path: '/' });\n }\n }\n } catch (error) {\n console.error('[logout] Logout error:', error);\n } finally {\n // Clear session cookie manually (server Set-Cookie may not work cross-origin)\n // For LIVE environments, cookie has domain attribute (e.g., .dev-blimu.dev)\n // Extract base domain from auth domain (e.g., id.dev-blimu.dev -> .dev-blimu.dev)\n console.log('[logout] Clearing session cookie...');\n if (this.isLive && this.authDomain) {\n const baseDomain = this.authDomain.replace(/^https?:\\/\\//, '').replace(/^id\\./, '');\n console.log('[logout] Removing cookie with domain:', `.${baseDomain}`);\n Cookies.remove(SESSION_COOKIE_NAME, { path: '/', domain: `.${baseDomain}` });\n } else {\n Cookies.remove(SESSION_COOKIE_NAME, { path: '/' });\n }\n console.log('[logout] Cookies after clearing:', document.cookie);\n\n this.store.setState({\n user: null,\n error: null,\n status: 'unauthenticated',\n });\n console.log('[logout] Logout complete, state set to unauthenticated');\n }\n };\n\n /**\n * Get the current session token\n */\n\n getAccessToken = async (_options: { template: 'web' }): Promise<string | undefined> => {\n return await this.session.getSessionToken();\n };\n\n /**\n * Get the runtime client for direct API calls\n */\n getClient(): Blimu {\n return this.client;\n }\n}\n"],"names":["Cookies","LOCALHOST_JWT_COOKIE_NAME","SESSION_COOKIE_NAME","ExternalStore","getAuthDomainFromPublishableKey","getAuthApiUrl","Blimu","AuthSessionService"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ export declare class BlimuRuntimeClientWrapper {
|
|
|
24
24
|
* Note: The auth worker should extract the __bli_client cookie and include it in the request body
|
|
25
25
|
* This method is idempotent - if a refresh is already in progress, it will wait for that refresh to complete
|
|
26
26
|
*/
|
|
27
|
-
scheduleRefresh():
|
|
27
|
+
scheduleRefresh(): void;
|
|
28
28
|
/**
|
|
29
29
|
* Redirect user to auth domain for authentication
|
|
30
30
|
* Uses the auth domain derived from the publishable key
|
|
@@ -33,6 +33,8 @@ export declare class BlimuRuntimeClientWrapper {
|
|
|
33
33
|
/**
|
|
34
34
|
* Logout user
|
|
35
35
|
* Calls the logout endpoint on the API domain using this.client
|
|
36
|
+
* For TEST environments: sends __lh_jwt query parameter from cookie
|
|
37
|
+
* For LIVE environments: uses __bli_client cookie automatically
|
|
36
38
|
*/
|
|
37
39
|
logout: () => Promise<void>;
|
|
38
40
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-client.d.ts","sourceRoot":"","sources":["../../src/client/runtime-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-client.d.ts","sourceRoot":"","sources":["../../src/client/runtime-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAEtC,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,yBAAyB,CAAgC;IAEjE,SAAgB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;gBAEpC,MAAM,EAAE,WAAW;IAwC/B;;;;OAIG;IACH,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI;IAyDjC;;;;;OAKG;IACI,eAAe,IAAI,IAAI;IAI9B;;;OAGG;IACI,cAAc,GAAI,YAAY,MAAM,KAAG,IAAI,CAShD;IAEF;;;;;OAKG;IACI,MAAM,QAAa,OAAO,CAAC,IAAI,CAAC,CA6CrC;IAEF;;OAEG;IAEH,cAAc,GAAU,UAAU;QAAE,QAAQ,EAAE,KAAK,CAAA;KAAE,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAEjF;IAEF;;OAEG;IACH,SAAS,IAAI,KAAK;CAGnB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Blimu } from "@blimu/client";
|
|
2
|
+
import api from "../node_modules/js-cookie/dist/js.cookie.js";
|
|
2
3
|
import { getAuthDomainFromPublishableKey, getAuthApiUrl } from "../utils/publishable-key.js";
|
|
3
|
-
import { AuthSessionService } from "./auth.service.js";
|
|
4
|
+
import { LOCALHOST_JWT_COOKIE_NAME, SESSION_COOKIE_NAME, AuthSessionService } from "./auth.service.js";
|
|
4
5
|
import { ExternalStore } from "./external-store.js";
|
|
5
6
|
class BlimuRuntimeClientWrapper {
|
|
6
7
|
constructor(config) {
|
|
@@ -9,30 +10,46 @@ class BlimuRuntimeClientWrapper {
|
|
|
9
10
|
this.initializePromise = null;
|
|
10
11
|
this.initializeAbortController = null;
|
|
11
12
|
this.redirectToAuth = (returnUrl) => {
|
|
12
|
-
const redirectUrl = returnUrl
|
|
13
|
+
const redirectUrl = returnUrl ?? window.location.href;
|
|
13
14
|
const authUrl = new URL(`${this.authDomain}/login`);
|
|
14
15
|
authUrl.searchParams.set("redirect_url", encodeURIComponent(redirectUrl));
|
|
15
16
|
window.location.href = authUrl.toString();
|
|
16
17
|
};
|
|
17
18
|
this.logout = async () => {
|
|
19
|
+
console.log("[logout] Starting logout...");
|
|
20
|
+
console.log("[logout] Cookies before logout:", document.cookie);
|
|
18
21
|
try {
|
|
19
|
-
const sessionToken = this.session.getSessionToken();
|
|
22
|
+
const sessionToken = await this.session.getSessionToken();
|
|
20
23
|
if (sessionToken) {
|
|
24
|
+
const localhostJWT = this.isLive ? void 0 : api.get(LOCALHOST_JWT_COOKIE_NAME);
|
|
25
|
+
console.log("[logout] Calling logout API...");
|
|
21
26
|
await this.client.auth.logout({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Authorization: `Bearer ${sessionToken}`
|
|
25
|
-
}
|
|
27
|
+
// Build query parameters - only include __lh_jwt for non-live environments when cookie exists
|
|
28
|
+
...localhostJWT ? { __lh_jwt: localhostJWT } : {}
|
|
26
29
|
});
|
|
30
|
+
console.log("[logout] Logout API call successful");
|
|
31
|
+
if (!this.isLive && localhostJWT) {
|
|
32
|
+
api.remove(LOCALHOST_JWT_COOKIE_NAME, { path: "/" });
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
35
|
} catch (error) {
|
|
29
|
-
console.error("Logout error:", error);
|
|
36
|
+
console.error("[logout] Logout error:", error);
|
|
30
37
|
} finally {
|
|
38
|
+
console.log("[logout] Clearing session cookie...");
|
|
39
|
+
if (this.isLive && this.authDomain) {
|
|
40
|
+
const baseDomain = this.authDomain.replace(/^https?:\/\//, "").replace(/^id\./, "");
|
|
41
|
+
console.log("[logout] Removing cookie with domain:", `.${baseDomain}`);
|
|
42
|
+
api.remove(SESSION_COOKIE_NAME, { path: "/", domain: `.${baseDomain}` });
|
|
43
|
+
} else {
|
|
44
|
+
api.remove(SESSION_COOKIE_NAME, { path: "/" });
|
|
45
|
+
}
|
|
46
|
+
console.log("[logout] Cookies after clearing:", document.cookie);
|
|
31
47
|
this.store.setState({
|
|
32
48
|
user: null,
|
|
33
49
|
error: null,
|
|
34
50
|
status: "unauthenticated"
|
|
35
51
|
});
|
|
52
|
+
console.log("[logout] Logout complete, state set to unauthenticated");
|
|
36
53
|
}
|
|
37
54
|
};
|
|
38
55
|
this.getAccessToken = async (_options) => {
|
|
@@ -56,7 +73,9 @@ class BlimuRuntimeClientWrapper {
|
|
|
56
73
|
this.client = new Blimu({
|
|
57
74
|
baseURL: authApiUrl,
|
|
58
75
|
bearer: () => this.session.getSessionToken(),
|
|
59
|
-
headers: { "x-blimu-publishable-key": config.publishableKey }
|
|
76
|
+
headers: { "x-blimu-publishable-key": config.publishableKey },
|
|
77
|
+
credentials: "include"
|
|
78
|
+
// Required to send httpOnly cookies (like __bli_client) in cross-origin requests
|
|
60
79
|
});
|
|
61
80
|
this.session = new AuthSessionService(
|
|
62
81
|
this.isLive,
|
|
@@ -122,7 +141,7 @@ class BlimuRuntimeClientWrapper {
|
|
|
122
141
|
* This method is idempotent - if a refresh is already in progress, it will wait for that refresh to complete
|
|
123
142
|
*/
|
|
124
143
|
scheduleRefresh() {
|
|
125
|
-
|
|
144
|
+
this.session.scheduleRefresh();
|
|
126
145
|
}
|
|
127
146
|
/**
|
|
128
147
|
* Get the runtime client for direct API calls
|