@yca-software/yca-react-core 0.0.16 → 0.0.17
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/api/index.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ declare function getRequestHeaders(accessToken: string | null, options?: {
|
|
|
51
51
|
declare function getRequestOptions(config: RequestConfig, token: string | null, acceptLanguage?: string): RequestInit;
|
|
52
52
|
/** Auth errors where the refresh cookie/token is actually unusable. */
|
|
53
53
|
declare function isFatalRefreshFailureStatus(status: number): boolean;
|
|
54
|
+
/** Background/frozen tabs must not clear the session — cookies may be omitted from the fetch. */
|
|
55
|
+
declare function shouldClearSessionOnRefreshFailure(): boolean;
|
|
54
56
|
declare function performAccessTokenRefresh(params: {
|
|
55
57
|
baseURL: string;
|
|
56
58
|
getRefreshToken: () => string | null;
|
|
@@ -68,6 +70,8 @@ declare function createResponseHandler(config: ApiClientConfig): (response: Resp
|
|
|
68
70
|
}) => Promise<unknown>;
|
|
69
71
|
|
|
70
72
|
declare function accessTokenNeedsRefresh(token: string | null, nowMs?: number, skewMs?: number): boolean;
|
|
73
|
+
/** Milliseconds until the access JWT should be refreshed (0 = now). */
|
|
74
|
+
declare function msUntilAccessRefresh(token: string | null, nowMs?: number, skewMs?: number): number;
|
|
71
75
|
type UseAccessTokenKeepAliveOptions = {
|
|
72
76
|
/** When false, no timers or listeners are registered. */
|
|
73
77
|
enabled: boolean;
|
|
@@ -76,9 +80,9 @@ type UseAccessTokenKeepAliveOptions = {
|
|
|
76
80
|
};
|
|
77
81
|
/**
|
|
78
82
|
* Keeps the access token fresh while a session exists.
|
|
79
|
-
* -
|
|
80
|
-
* -
|
|
83
|
+
* - Timeout scheduled from JWT exp (capped at 4 minutes)
|
|
84
|
+
* - Also refreshes on visibility / focus / pageshow after expiry
|
|
81
85
|
*/
|
|
82
86
|
declare function useAccessTokenKeepAlive(options: UseAccessTokenKeepAliveOptions): void;
|
|
83
87
|
|
|
84
|
-
export { ApiClientConfig, ApiClientProvider, ApiClientProviderConfig, type CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, type DefaultRefreshRequestOptions, FetchFn, RefreshRequestConfig, RequestConfig, type UseAccessTokenKeepAliveOptions, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
|
|
88
|
+
export { ApiClientConfig, ApiClientProvider, ApiClientProviderConfig, type CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, type DefaultRefreshRequestOptions, FetchFn, RefreshRequestConfig, RequestConfig, type UseAccessTokenKeepAliveOptions, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, msUntilAccessRefresh, performAccessTokenRefresh, shouldClearSessionOnRefreshFailure, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
|
package/dist/api/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from '../chunk-
|
|
1
|
+
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, msUntilAccessRefresh, performAccessTokenRefresh, shouldClearSessionOnRefreshFailure, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from '../chunk-NL5V3XXV.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -60,6 +60,9 @@ function getRequestOptions(config, token, acceptLanguage) {
|
|
|
60
60
|
function isFatalRefreshFailureStatus(status) {
|
|
61
61
|
return status === 400 || status === 401 || status === 403 || status === 404;
|
|
62
62
|
}
|
|
63
|
+
function shouldClearSessionOnRefreshFailure() {
|
|
64
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
65
|
+
}
|
|
63
66
|
function isTransientRefreshStatus(status) {
|
|
64
67
|
return status === 0 || status === 429 || status === 502 || status === 503 || status === 504;
|
|
65
68
|
}
|
|
@@ -125,7 +128,7 @@ async function performAccessTokenRefresh(params) {
|
|
|
125
128
|
await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);
|
|
126
129
|
continue;
|
|
127
130
|
}
|
|
128
|
-
if (isFatalRefreshFailureStatus(lastStatus)) {
|
|
131
|
+
if (isFatalRefreshFailureStatus(lastStatus) && shouldClearSessionOnRefreshFailure()) {
|
|
129
132
|
params.onFailure();
|
|
130
133
|
}
|
|
131
134
|
throw {
|
|
@@ -267,16 +270,19 @@ function createApiProvider(useOptions) {
|
|
|
267
270
|
return /* @__PURE__ */ jsx(ApiClientProvider, { baseURL: options.baseURL, config: apiConfig, children });
|
|
268
271
|
};
|
|
269
272
|
}
|
|
270
|
-
var KEEP_ALIVE_INTERVAL_MS = 10 * 60 * 1e3;
|
|
271
273
|
var REFRESH_SKEW_MS = 2 * 60 * 1e3;
|
|
274
|
+
var KEEP_ALIVE_MAX_DELAY_MS = 4 * 60 * 1e3;
|
|
272
275
|
function accessTokenNeedsRefresh(token, nowMs = Date.now(), skewMs = REFRESH_SKEW_MS) {
|
|
273
|
-
|
|
276
|
+
return msUntilAccessRefresh(token, nowMs, skewMs) === 0;
|
|
277
|
+
}
|
|
278
|
+
function msUntilAccessRefresh(token, nowMs = Date.now(), skewMs = REFRESH_SKEW_MS) {
|
|
279
|
+
if (!token) return 0;
|
|
274
280
|
try {
|
|
275
281
|
const { exp } = jwtDecode(token);
|
|
276
|
-
if (typeof exp !== "number") return
|
|
277
|
-
return exp * 1e3
|
|
282
|
+
if (typeof exp !== "number") return 0;
|
|
283
|
+
return Math.max(0, exp * 1e3 - skewMs - nowMs);
|
|
278
284
|
} catch {
|
|
279
|
-
return
|
|
285
|
+
return 0;
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
function useAccessTokenKeepAlive(options) {
|
|
@@ -287,34 +293,42 @@ function useAccessTokenKeepAlive(options) {
|
|
|
287
293
|
return;
|
|
288
294
|
}
|
|
289
295
|
const refreshIfNeeded = () => {
|
|
290
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
296
|
if (!accessTokenNeedsRefresh(getAccessToken())) {
|
|
294
297
|
return;
|
|
295
298
|
}
|
|
296
299
|
void executeConfiguredRefresh(apiConfig).catch(() => {
|
|
297
300
|
});
|
|
298
301
|
};
|
|
302
|
+
let timeoutId = 0;
|
|
303
|
+
const schedule = () => {
|
|
304
|
+
window.clearTimeout(timeoutId);
|
|
305
|
+
const until = msUntilAccessRefresh(getAccessToken());
|
|
306
|
+
const delay = until === 0 ? KEEP_ALIVE_MAX_DELAY_MS : Math.min(until, KEEP_ALIVE_MAX_DELAY_MS);
|
|
307
|
+
timeoutId = window.setTimeout(() => {
|
|
308
|
+
refreshIfNeeded();
|
|
309
|
+
schedule();
|
|
310
|
+
}, delay);
|
|
311
|
+
};
|
|
299
312
|
const onVisibility = () => {
|
|
300
313
|
if (document.visibilityState === "visible") {
|
|
301
314
|
refreshIfNeeded();
|
|
315
|
+
schedule();
|
|
302
316
|
}
|
|
303
317
|
};
|
|
304
318
|
refreshIfNeeded();
|
|
319
|
+
schedule();
|
|
305
320
|
document.addEventListener("visibilitychange", onVisibility);
|
|
306
321
|
window.addEventListener("focus", refreshIfNeeded);
|
|
307
322
|
window.addEventListener("pageshow", refreshIfNeeded);
|
|
308
|
-
const id = window.setInterval(refreshIfNeeded, KEEP_ALIVE_INTERVAL_MS);
|
|
309
323
|
return () => {
|
|
310
324
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
311
325
|
window.removeEventListener("focus", refreshIfNeeded);
|
|
312
326
|
window.removeEventListener("pageshow", refreshIfNeeded);
|
|
313
|
-
window.
|
|
327
|
+
window.clearTimeout(timeoutId);
|
|
314
328
|
};
|
|
315
329
|
}, [apiConfig, enabled, getAccessToken]);
|
|
316
330
|
}
|
|
317
331
|
|
|
318
|
-
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
|
|
319
|
-
//# sourceMappingURL=chunk-
|
|
320
|
-
//# sourceMappingURL=chunk-
|
|
332
|
+
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, msUntilAccessRefresh, performAccessTokenRefresh, shouldClearSessionOnRefreshFailure, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
|
|
333
|
+
//# sourceMappingURL=chunk-NL5V3XXV.js.map
|
|
334
|
+
//# sourceMappingURL=chunk-NL5V3XXV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/request.ts","../src/api/responseHandler.ts","../src/api/ApiClientContext.tsx","../src/api/createApiProvider.tsx","../src/api/useAccessTokenKeepAlive.ts"],"names":["useMemo","jsx"],"mappings":"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","file":"chunk-NL5V3XXV.js","sourcesContent":["import type {\n ApiClientConfig,\n RefreshBodyContext,\n RefreshRequestConfig,\n RequestConfig,\n} from './types';\n\nexport const DEFAULT_REFRESH_ENDPOINT = 'auth/refresh';\nexport const DEFAULT_REFRESH_EXCLUDED_PREFIXES = ['auth/'];\n\nexport interface DefaultRefreshRequestOptions {\n endpoint?: string;\n excludedRetryPrefixes?: string[];\n}\n\nexport function createDefaultRefreshRequest({\n endpoint = DEFAULT_REFRESH_ENDPOINT,\n excludedRetryPrefixes = DEFAULT_REFRESH_EXCLUDED_PREFIXES,\n}: DefaultRefreshRequestOptions = {}): RefreshRequestConfig {\n return {\n endpoint,\n method: 'POST',\n buildBody: ({ refreshToken }: RefreshBodyContext) => (refreshToken ? { refreshToken } : {}),\n parseAccessToken: (data: unknown) => {\n if (data && typeof data === 'object' && 'accessToken' in data) {\n const token = (data as { accessToken: unknown }).accessToken;\n return typeof token === 'string' ? token : null;\n }\n return null;\n },\n excludedRetryPrefixes,\n };\n}\n\nexport function buildRequestUrl(baseURL: string, endpoint: string): string {\n const base = baseURL.replace(/\\/$/, '');\n const path = endpoint.replace(/^\\//, '');\n return `${base}/${path}`;\n}\n\nexport function getRequestHeaders(\n accessToken: string | null,\n options?: { multipart?: boolean; acceptLanguage?: string },\n): HeadersInit {\n const headers: HeadersInit = {};\n if (!options?.multipart) {\n headers['Content-Type'] = 'application/json';\n }\n if (accessToken) {\n headers.Authorization = `Bearer ${accessToken}`;\n }\n if (options?.acceptLanguage) {\n headers['Accept-Language'] = options.acceptLanguage;\n }\n return headers;\n}\n\nexport function getRequestOptions(\n config: RequestConfig,\n token: string | null,\n acceptLanguage?: string,\n): RequestInit {\n const init: RequestInit = {\n method: config.method,\n headers: getRequestHeaders(token, {\n multipart: config.multipart,\n acceptLanguage,\n }),\n body: config.multipart\n ? (config.body as BodyInit)\n : config.body != null\n ? JSON.stringify(config.body)\n : undefined,\n };\n if (config.credentials) {\n init.credentials = config.credentials;\n }\n return init;\n}\n\n/** Auth errors where the refresh cookie/token is actually unusable. */\nexport function isFatalRefreshFailureStatus(status: number): boolean {\n return status === 400 || status === 401 || status === 403 || status === 404;\n}\n\n/** Background/frozen tabs must not clear the session — cookies may be omitted from the fetch. */\nexport function shouldClearSessionOnRefreshFailure(): boolean {\n return typeof document === 'undefined' || document.visibilityState !== 'hidden';\n}\n\nfunction isTransientRefreshStatus(status: number): boolean {\n return status === 0 || status === 429 || status === 502 || status === 503 || status === 504;\n}\n\nconst REFRESH_TRANSIENT_ATTEMPTS = 3;\nconst REFRESH_TRANSIENT_DELAY_MS = 50;\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport async function performAccessTokenRefresh(params: {\n baseURL: string;\n getRefreshToken: () => string | null;\n request: RefreshRequestConfig;\n useCookieCredentials: boolean;\n setAccessToken: (token: string) => void;\n onFailure: () => void;\n acceptLanguage?: string;\n}): Promise<string> {\n const refreshToken = params.useCookieCredentials ? null : params.getRefreshToken();\n if (!params.useCookieCredentials && !refreshToken) {\n // Missing JS token is not proof the session is dead (hydration may still\n // install an HttpOnly marker). Do not log the user out.\n throw { error: new Error('missing refresh token'), status: 401 };\n }\n\n const bodyContext: RefreshBodyContext = {\n refreshToken,\n useCookieCredentials: params.useCookieCredentials,\n };\n\n const requestConfig: RequestConfig = {\n endpoint: params.request.endpoint,\n method: params.request.method,\n body: params.request.buildBody(bodyContext),\n credentials: params.useCookieCredentials ? 'include' : params.request.credentials,\n };\n\n let lastStatus = 0;\n for (let attempt = 1; attempt <= REFRESH_TRANSIENT_ATTEMPTS; attempt += 1) {\n let response: Response;\n try {\n response = await fetch(\n buildRequestUrl(params.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, null, params.acceptLanguage),\n );\n } catch {\n lastStatus = 0;\n if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n throw { error: new Error('refresh network error'), status: 0 };\n }\n\n const responseText = await response.text();\n\n let data: unknown = null;\n if (responseText) {\n try {\n data = JSON.parse(responseText);\n } catch {\n lastStatus = 0;\n if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n throw { error: new Error('invalid refresh response'), status: 0 };\n }\n }\n\n const accessToken = params.request.parseAccessToken(data);\n if (response.ok && accessToken) {\n params.setAccessToken(accessToken);\n return accessToken;\n }\n\n lastStatus = response.status || 0;\n if (isTransientRefreshStatus(lastStatus) && attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n\n if (isFatalRefreshFailureStatus(lastStatus) && shouldClearSessionOnRefreshFailure()) {\n params.onFailure();\n }\n throw {\n error: new Error('failed to refresh access token'),\n status: lastStatus,\n };\n }\n\n throw { error: new Error('failed to refresh access token'), status: lastStatus };\n}\n\n/** One in-flight refresh per page — parallel 401 retries share the same promise. */\nlet refreshInFlight: Promise<string> | null = null;\n\nexport async function executeConfiguredRefresh(config: ApiClientConfig): Promise<string> {\n const refresh = config.refresh;\n if (!refresh) {\n throw new Error('refresh is not configured');\n }\n\n if (refreshInFlight) {\n return refreshInFlight;\n }\n\n refresh.onStart?.();\n refreshInFlight = performAccessTokenRefresh({\n baseURL: config.baseURL,\n getRefreshToken: config.getRefreshToken,\n request: refresh.request,\n useCookieCredentials: refresh.cookieCredentialsEnabled(),\n setAccessToken: refresh.setAccessToken,\n onFailure: refresh.onFailure,\n acceptLanguage: config.getAcceptLanguage?.(),\n }).finally(() => {\n refreshInFlight = null;\n refresh.onEnd?.();\n });\n\n return refreshInFlight;\n}\n","import { buildRequestUrl, executeConfiguredRefresh, getRequestOptions } from './request';\nimport type { ApiClientConfig, ApiErrorBody, RequestConfig, RequestError } from './types';\n\nfunction shouldRetryWithRefresh(\n status: number,\n endpoint: string | undefined,\n isRetry: boolean | undefined,\n refreshToken: string | null | undefined,\n refresh: ApiClientConfig['refresh'],\n): boolean {\n if (status !== 401 || !refresh || !endpoint || isRetry) {\n return false;\n }\n const canRefresh = !!refreshToken || refresh.cookieCredentialsEnabled();\n if (!canRefresh) {\n return false;\n }\n const normalized = endpoint.replace(/^\\//, '');\n return !refresh.request.excludedRetryPrefixes.some((prefix) =>\n normalized.startsWith(prefix.replace(/^\\//, '')),\n );\n}\n\nexport function createResponseHandler(config: ApiClientConfig) {\n const { baseURL, getRefreshToken, refresh } = config;\n\n async function handleResponse(\n response: Response,\n options?: { config?: RequestConfig; isRetry?: boolean },\n ): Promise<unknown> {\n const responseText = await response.text();\n let data: unknown;\n if (responseText) {\n try {\n data = JSON.parse(responseText);\n } catch {\n data = responseText;\n }\n }\n\n if (!response.ok) {\n const refreshToken = getRefreshToken();\n if (\n shouldRetryWithRefresh(\n response.status,\n options?.config?.endpoint,\n options?.isRetry,\n refreshToken,\n refresh,\n ) &&\n options?.config\n ) {\n const newToken = await executeConfiguredRefresh(config);\n const retryResponse = await fetch(\n buildRequestUrl(baseURL, options.config.endpoint),\n getRequestOptions(options.config, newToken, config.getAcceptLanguage?.()),\n );\n return handleResponse(retryResponse, { ...options, isRetry: true });\n }\n\n throw {\n error: data as ApiErrorBody,\n status: response.status,\n retry: response.status >= 500,\n } satisfies RequestError;\n }\n\n return data;\n }\n\n return handleResponse;\n}\n","import { createContext, type ReactNode, useCallback, useContext, useMemo } from 'react';\nimport { buildRequestUrl, getRequestOptions } from './request';\nimport { createResponseHandler } from './responseHandler';\nimport type { ApiClientConfig, ApiClientProviderConfig, FetchFn, RequestConfig } from './types';\n\nconst ApiClientContext = createContext<ApiClientConfig | null>(null);\n\nexport function ApiClientProvider({\n baseURL,\n config,\n children,\n}: {\n baseURL: string;\n config: ApiClientProviderConfig;\n children: ReactNode;\n}) {\n const value = useMemo<ApiClientConfig>(() => ({ ...config, baseURL }), [baseURL, config]);\n\n return <ApiClientContext.Provider value={value}>{children}</ApiClientContext.Provider>;\n}\n\nexport function useApiClientConfig(): ApiClientConfig {\n const ctx = useContext(ApiClientContext);\n if (!ctx) {\n throw new Error('useApiClientConfig must be used within ApiClientProvider');\n }\n return ctx;\n}\n\nexport function useAPI(): FetchFn {\n const config = useApiClientConfig();\n const responseHandler = useMemo(() => createResponseHandler(config), [config]);\n\n return useCallback(\n (requestConfig: RequestConfig) => {\n const accessToken = config.getAccessToken();\n return fetch(\n buildRequestUrl(config.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, accessToken, config.getAcceptLanguage?.()),\n ).then((response) => responseHandler(response, { config: requestConfig }));\n },\n [config, responseHandler],\n );\n}\n","import { type ReactNode, useMemo } from 'react';\n\nimport { ApiClientProvider } from './ApiClientContext';\nimport { createDefaultRefreshRequest } from './request';\nimport type { ApiClientProviderConfig } from './types';\n\nexport interface CreateApiProviderOptions {\n baseURL: string;\n getAccessToken: () => string | null;\n getRefreshToken: () => string | null;\n /** When true, refresh uses HttpOnly cookie credentials instead of body token. */\n cookieCredentialsEnabled: () => boolean;\n setAccessToken: (token: string) => void;\n onRefreshFailure: () => void;\n getAcceptLanguage?: () => string;\n onRefreshStart?: () => void;\n onRefreshEnd?: () => void;\n /** Refresh endpoint path relative to baseURL. Defaults to `auth/refresh`. */\n refreshEndpoint?: string;\n /** Paths that must not trigger automatic refresh retry on 401. Defaults to `['auth/']`. */\n refreshExcludedPrefixes?: string[];\n}\n\n/**\n * Factory for app `ApiProvider` components. Pass a hook that returns wiring options\n * (cookies, i18n, zustand). Memoize that return value with `useMemo` so the API\n * client context is not rebuilt on every parent render.\n */\nexport function createApiProvider(useOptions: () => CreateApiProviderOptions) {\n return function ApiProvider({ children }: { children: ReactNode }) {\n const options = useOptions();\n\n const apiConfig = useMemo<ApiClientProviderConfig>(\n () => ({\n getAccessToken: options.getAccessToken,\n getRefreshToken: options.getRefreshToken,\n getAcceptLanguage: options.getAcceptLanguage,\n refresh: {\n request: createDefaultRefreshRequest({\n endpoint: options.refreshEndpoint,\n excludedRetryPrefixes: options.refreshExcludedPrefixes,\n }),\n cookieCredentialsEnabled: options.cookieCredentialsEnabled,\n setAccessToken: options.setAccessToken,\n onFailure: options.onRefreshFailure,\n onStart: options.onRefreshStart,\n onEnd: options.onRefreshEnd,\n },\n }),\n [options],\n );\n\n return (\n <ApiClientProvider baseURL={options.baseURL} config={apiConfig}>\n {children}\n </ApiClientProvider>\n );\n };\n}\n","import { jwtDecode } from 'jwt-decode';\nimport { useEffect } from 'react';\nimport { useApiClientConfig } from './ApiClientContext';\nimport { executeConfiguredRefresh } from './request';\n\n/** Typical access JWT TTL is 15m — refresh ahead of expiry. */\nconst REFRESH_SKEW_MS = 2 * 60 * 1000;\n/** Cap so we re-check often enough if the JWT has no exp. */\nconst KEEP_ALIVE_MAX_DELAY_MS = 4 * 60 * 1000;\n\nexport function accessTokenNeedsRefresh(\n token: string | null,\n nowMs: number = Date.now(),\n skewMs: number = REFRESH_SKEW_MS,\n): boolean {\n return msUntilAccessRefresh(token, nowMs, skewMs) === 0;\n}\n\n/** Milliseconds until the access JWT should be refreshed (0 = now). */\nexport function msUntilAccessRefresh(\n token: string | null,\n nowMs: number = Date.now(),\n skewMs: number = REFRESH_SKEW_MS,\n): number {\n if (!token) return 0;\n try {\n const { exp } = jwtDecode<{ exp?: number }>(token);\n if (typeof exp !== 'number') return 0;\n return Math.max(0, exp * 1000 - skewMs - nowMs);\n } catch {\n return 0;\n }\n}\n\nexport type UseAccessTokenKeepAliveOptions = {\n /** When false, no timers or listeners are registered. */\n enabled: boolean;\n /** Same accessor the API client uses (cookie and/or in-memory). */\n getAccessToken: () => string | null;\n};\n\n/**\n * Keeps the access token fresh while a session exists.\n * - Timeout scheduled from JWT exp (capped at 4 minutes)\n * - Also refreshes on visibility / focus / pageshow after expiry\n */\nexport function useAccessTokenKeepAlive(options: UseAccessTokenKeepAliveOptions): void {\n const apiConfig = useApiClientConfig();\n const { enabled, getAccessToken } = options;\n\n useEffect(() => {\n if (!enabled || !apiConfig.refresh) {\n return;\n }\n\n const refreshIfNeeded = () => {\n if (!accessTokenNeedsRefresh(getAccessToken())) {\n return;\n }\n void executeConfiguredRefresh(apiConfig).catch(() => {\n // Hard auth failures clear via onRefreshFailure only while the tab is visible.\n });\n };\n\n let timeoutId = 0;\n const schedule = () => {\n window.clearTimeout(timeoutId);\n const until = msUntilAccessRefresh(getAccessToken());\n const delay =\n until === 0 ? KEEP_ALIVE_MAX_DELAY_MS : Math.min(until, KEEP_ALIVE_MAX_DELAY_MS);\n timeoutId = window.setTimeout(() => {\n refreshIfNeeded();\n schedule();\n }, delay);\n };\n\n const onVisibility = () => {\n if (document.visibilityState === 'visible') {\n refreshIfNeeded();\n schedule();\n }\n };\n\n refreshIfNeeded();\n schedule();\n document.addEventListener('visibilitychange', onVisibility);\n window.addEventListener('focus', refreshIfNeeded);\n window.addEventListener('pageshow', refreshIfNeeded);\n\n return () => {\n document.removeEventListener('visibilitychange', onVisibility);\n window.removeEventListener('focus', refreshIfNeeded);\n window.removeEventListener('pageshow', refreshIfNeeded);\n window.clearTimeout(timeoutId);\n };\n }, [apiConfig, enabled, getAccessToken]);\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { A as AdminDetailErrorKind } from './admin-BVYdWtEy.js';
|
|
2
2
|
export { getAdminDetailErrorKind } from './admin/index.js';
|
|
3
|
-
export { ApiClientProvider, CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, DefaultRefreshRequestOptions, UseAccessTokenKeepAliveOptions, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from './api/index.js';
|
|
3
|
+
export { ApiClientProvider, CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, DefaultRefreshRequestOptions, UseAccessTokenKeepAliveOptions, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, msUntilAccessRefresh, performAccessTokenRefresh, shouldClearSessionOnRefreshFailure, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from './api/index.js';
|
|
4
4
|
export { A as ApiClientConfig, a as ApiClientProviderConfig, b as ApiErrorBody, F as FetchFn, R as RefreshBodyContext, c as RefreshConfig, d as RefreshRequestConfig, e as RequestConfig, f as RequestError } from './types-CN73TWSY.js';
|
|
5
5
|
export { AccessInfo, CookieConsent, DecodedAccessToken, HTTP_ONLY_REFRESH_MARKER, JWTAccessTokenPermissionData, TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, isInvalidSessionStatus, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, resolveApiRefreshToken, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from './auth/index.js';
|
|
6
6
|
export { BentoCell, BentoFeatures, BentoFeaturesProps, BlogPostTeaser, BlogTeaser, BlogTeaserProps, ContactSection, ContactSectionProps, CtaBanner, CtaBannerProps, ErrorSpotlight, ErrorSpotlightProps, FaqItem, FaqSection, FaqSectionProps, FeatureMedia, FeatureMediaProps, Footer, FooterContact, FooterLink, FooterProps, Hero, HeroAction, HeroProps, HeroSplit, HeroSplitProps, LaunchProject, LogoCloud, LogoCloudItem, LogoCloudProps, Navigation, NavigationCTA, NavigationLanguageOption, NavigationLanguageSwitcher, NavigationLink, NavigationProps, Newsletter, NewsletterProps, PricingComparisonRow, PricingSection, PricingSectionProps, PricingTier, ProjectLaunches, ProjectLaunchesProps, PromoBanner, PromoBannerProps, Section, SectionProps, SectionVariant, ServiceCard, ServiceCardProps, StatItem, StatsStrip, StatsStripProps, TeamGrid, TeamGridProps, TeamMember, Testimonial, TestimonialProps, TrustBadgeItem, TrustBadges, TrustBadgesProps, VideoShowcase, VideoShowcaseProps } from './components/marketing/index.js';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { AdminDetailPage, AdminListPage, AuditLogListFilters, DateRangeFilter, D
|
|
|
5
5
|
export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarFallback, AvatarImage, Badge, Calendar, Checkbox, ConfirmDialog, DatePicker, DateRangePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownScrollableList, DropdownSelectSearchInput, Form, FormControl, FormDescription, FormDrawer, FormField, FormItem, FormLabel, FormMessage, H3, Heading, Label, Link, MultiSelect, Paragraph, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, SEARCHABLE_SELECT_MIN_OPTIONS, ScrollArea, ScrollBar, Select, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Tooltip, TooltipProvider, UploadDropzone, badgeVariants, dropdownMenuSelectOptionsMaxHeightClassName, filterSelectOptions, foldSearchText, headingVariants, isSelectSearchable, linkStyles, paragraphVariants, useFormField } from './chunk-CBJJUQMY.js';
|
|
6
6
|
export { Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Input, buttonVariants } from './chunk-3AYG73DK.js';
|
|
7
7
|
export { getAdminDetailErrorKind } from './chunk-3ZLODBRJ.js';
|
|
8
|
-
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from './chunk-
|
|
8
|
+
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, msUntilAccessRefresh, performAccessTokenRefresh, shouldClearSessionOnRefreshFailure, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from './chunk-NL5V3XXV.js';
|
|
9
9
|
export { HTTP_ONLY_REFRESH_MARKER, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, isInvalidSessionStatus, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, resolveApiRefreshToken, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from './chunk-LJXHUEXF.js';
|
|
10
10
|
export { createCookieNames } from './chunk-KG4JVAKS.js';
|
|
11
11
|
export { constraintNameToErrorKey, errorCodeFromHttpStatus, formatApiError, getApiErrorFromRequestError, getConflictFieldFromError, getConflictFieldFromExtra, getConstraintNameFromError, getConstraintNameFromExtra, handleApiMutationError, isApiErrorBody, resolveApiError, useQueryApiErrorToast } from './chunk-KZH56VPP.js';
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/api/request.ts","../src/api/responseHandler.ts","../src/api/ApiClientContext.tsx","../src/api/createApiProvider.tsx","../src/api/useAccessTokenKeepAlive.ts"],"names":["useMemo","jsx"],"mappings":"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","file":"chunk-EQK7Y66J.js","sourcesContent":["import type {\n ApiClientConfig,\n RefreshBodyContext,\n RefreshRequestConfig,\n RequestConfig,\n} from './types';\n\nexport const DEFAULT_REFRESH_ENDPOINT = 'auth/refresh';\nexport const DEFAULT_REFRESH_EXCLUDED_PREFIXES = ['auth/'];\n\nexport interface DefaultRefreshRequestOptions {\n endpoint?: string;\n excludedRetryPrefixes?: string[];\n}\n\nexport function createDefaultRefreshRequest({\n endpoint = DEFAULT_REFRESH_ENDPOINT,\n excludedRetryPrefixes = DEFAULT_REFRESH_EXCLUDED_PREFIXES,\n}: DefaultRefreshRequestOptions = {}): RefreshRequestConfig {\n return {\n endpoint,\n method: 'POST',\n buildBody: ({ refreshToken }: RefreshBodyContext) => (refreshToken ? { refreshToken } : {}),\n parseAccessToken: (data: unknown) => {\n if (data && typeof data === 'object' && 'accessToken' in data) {\n const token = (data as { accessToken: unknown }).accessToken;\n return typeof token === 'string' ? token : null;\n }\n return null;\n },\n excludedRetryPrefixes,\n };\n}\n\nexport function buildRequestUrl(baseURL: string, endpoint: string): string {\n const base = baseURL.replace(/\\/$/, '');\n const path = endpoint.replace(/^\\//, '');\n return `${base}/${path}`;\n}\n\nexport function getRequestHeaders(\n accessToken: string | null,\n options?: { multipart?: boolean; acceptLanguage?: string },\n): HeadersInit {\n const headers: HeadersInit = {};\n if (!options?.multipart) {\n headers['Content-Type'] = 'application/json';\n }\n if (accessToken) {\n headers.Authorization = `Bearer ${accessToken}`;\n }\n if (options?.acceptLanguage) {\n headers['Accept-Language'] = options.acceptLanguage;\n }\n return headers;\n}\n\nexport function getRequestOptions(\n config: RequestConfig,\n token: string | null,\n acceptLanguage?: string,\n): RequestInit {\n const init: RequestInit = {\n method: config.method,\n headers: getRequestHeaders(token, {\n multipart: config.multipart,\n acceptLanguage,\n }),\n body: config.multipart\n ? (config.body as BodyInit)\n : config.body != null\n ? JSON.stringify(config.body)\n : undefined,\n };\n if (config.credentials) {\n init.credentials = config.credentials;\n }\n return init;\n}\n\n/** Auth errors where the refresh cookie/token is actually unusable. */\nexport function isFatalRefreshFailureStatus(status: number): boolean {\n return status === 400 || status === 401 || status === 403 || status === 404;\n}\n\nfunction isTransientRefreshStatus(status: number): boolean {\n return status === 0 || status === 429 || status === 502 || status === 503 || status === 504;\n}\n\nconst REFRESH_TRANSIENT_ATTEMPTS = 3;\nconst REFRESH_TRANSIENT_DELAY_MS = 50;\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\nexport async function performAccessTokenRefresh(params: {\n baseURL: string;\n getRefreshToken: () => string | null;\n request: RefreshRequestConfig;\n useCookieCredentials: boolean;\n setAccessToken: (token: string) => void;\n onFailure: () => void;\n acceptLanguage?: string;\n}): Promise<string> {\n const refreshToken = params.useCookieCredentials ? null : params.getRefreshToken();\n if (!params.useCookieCredentials && !refreshToken) {\n // Missing JS token is not proof the session is dead (hydration may still\n // install an HttpOnly marker). Do not log the user out.\n throw { error: new Error('missing refresh token'), status: 401 };\n }\n\n const bodyContext: RefreshBodyContext = {\n refreshToken,\n useCookieCredentials: params.useCookieCredentials,\n };\n\n const requestConfig: RequestConfig = {\n endpoint: params.request.endpoint,\n method: params.request.method,\n body: params.request.buildBody(bodyContext),\n credentials: params.useCookieCredentials ? 'include' : params.request.credentials,\n };\n\n let lastStatus = 0;\n for (let attempt = 1; attempt <= REFRESH_TRANSIENT_ATTEMPTS; attempt += 1) {\n let response: Response;\n try {\n response = await fetch(\n buildRequestUrl(params.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, null, params.acceptLanguage),\n );\n } catch {\n lastStatus = 0;\n if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n throw { error: new Error('refresh network error'), status: 0 };\n }\n\n const responseText = await response.text();\n\n let data: unknown = null;\n if (responseText) {\n try {\n data = JSON.parse(responseText);\n } catch {\n lastStatus = 0;\n if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n throw { error: new Error('invalid refresh response'), status: 0 };\n }\n }\n\n const accessToken = params.request.parseAccessToken(data);\n if (response.ok && accessToken) {\n params.setAccessToken(accessToken);\n return accessToken;\n }\n\n lastStatus = response.status || 0;\n if (isTransientRefreshStatus(lastStatus) && attempt < REFRESH_TRANSIENT_ATTEMPTS) {\n await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);\n continue;\n }\n\n if (isFatalRefreshFailureStatus(lastStatus)) {\n params.onFailure();\n }\n throw {\n error: new Error('failed to refresh access token'),\n status: lastStatus,\n };\n }\n\n throw { error: new Error('failed to refresh access token'), status: lastStatus };\n}\n\n/** One in-flight refresh per page — parallel 401 retries share the same promise. */\nlet refreshInFlight: Promise<string> | null = null;\n\nexport async function executeConfiguredRefresh(config: ApiClientConfig): Promise<string> {\n const refresh = config.refresh;\n if (!refresh) {\n throw new Error('refresh is not configured');\n }\n\n if (refreshInFlight) {\n return refreshInFlight;\n }\n\n refresh.onStart?.();\n refreshInFlight = performAccessTokenRefresh({\n baseURL: config.baseURL,\n getRefreshToken: config.getRefreshToken,\n request: refresh.request,\n useCookieCredentials: refresh.cookieCredentialsEnabled(),\n setAccessToken: refresh.setAccessToken,\n onFailure: refresh.onFailure,\n acceptLanguage: config.getAcceptLanguage?.(),\n }).finally(() => {\n refreshInFlight = null;\n refresh.onEnd?.();\n });\n\n return refreshInFlight;\n}\n","import { buildRequestUrl, executeConfiguredRefresh, getRequestOptions } from './request';\nimport type { ApiClientConfig, ApiErrorBody, RequestConfig, RequestError } from './types';\n\nfunction shouldRetryWithRefresh(\n status: number,\n endpoint: string | undefined,\n isRetry: boolean | undefined,\n refreshToken: string | null | undefined,\n refresh: ApiClientConfig['refresh'],\n): boolean {\n if (status !== 401 || !refresh || !endpoint || isRetry) {\n return false;\n }\n const canRefresh = !!refreshToken || refresh.cookieCredentialsEnabled();\n if (!canRefresh) {\n return false;\n }\n const normalized = endpoint.replace(/^\\//, '');\n return !refresh.request.excludedRetryPrefixes.some((prefix) =>\n normalized.startsWith(prefix.replace(/^\\//, '')),\n );\n}\n\nexport function createResponseHandler(config: ApiClientConfig) {\n const { baseURL, getRefreshToken, refresh } = config;\n\n async function handleResponse(\n response: Response,\n options?: { config?: RequestConfig; isRetry?: boolean },\n ): Promise<unknown> {\n const responseText = await response.text();\n let data: unknown;\n if (responseText) {\n try {\n data = JSON.parse(responseText);\n } catch {\n data = responseText;\n }\n }\n\n if (!response.ok) {\n const refreshToken = getRefreshToken();\n if (\n shouldRetryWithRefresh(\n response.status,\n options?.config?.endpoint,\n options?.isRetry,\n refreshToken,\n refresh,\n ) &&\n options?.config\n ) {\n const newToken = await executeConfiguredRefresh(config);\n const retryResponse = await fetch(\n buildRequestUrl(baseURL, options.config.endpoint),\n getRequestOptions(options.config, newToken, config.getAcceptLanguage?.()),\n );\n return handleResponse(retryResponse, { ...options, isRetry: true });\n }\n\n throw {\n error: data as ApiErrorBody,\n status: response.status,\n retry: response.status >= 500,\n } satisfies RequestError;\n }\n\n return data;\n }\n\n return handleResponse;\n}\n","import { createContext, type ReactNode, useCallback, useContext, useMemo } from 'react';\nimport { buildRequestUrl, getRequestOptions } from './request';\nimport { createResponseHandler } from './responseHandler';\nimport type { ApiClientConfig, ApiClientProviderConfig, FetchFn, RequestConfig } from './types';\n\nconst ApiClientContext = createContext<ApiClientConfig | null>(null);\n\nexport function ApiClientProvider({\n baseURL,\n config,\n children,\n}: {\n baseURL: string;\n config: ApiClientProviderConfig;\n children: ReactNode;\n}) {\n const value = useMemo<ApiClientConfig>(() => ({ ...config, baseURL }), [baseURL, config]);\n\n return <ApiClientContext.Provider value={value}>{children}</ApiClientContext.Provider>;\n}\n\nexport function useApiClientConfig(): ApiClientConfig {\n const ctx = useContext(ApiClientContext);\n if (!ctx) {\n throw new Error('useApiClientConfig must be used within ApiClientProvider');\n }\n return ctx;\n}\n\nexport function useAPI(): FetchFn {\n const config = useApiClientConfig();\n const responseHandler = useMemo(() => createResponseHandler(config), [config]);\n\n return useCallback(\n (requestConfig: RequestConfig) => {\n const accessToken = config.getAccessToken();\n return fetch(\n buildRequestUrl(config.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, accessToken, config.getAcceptLanguage?.()),\n ).then((response) => responseHandler(response, { config: requestConfig }));\n },\n [config, responseHandler],\n );\n}\n","import { type ReactNode, useMemo } from 'react';\n\nimport { ApiClientProvider } from './ApiClientContext';\nimport { createDefaultRefreshRequest } from './request';\nimport type { ApiClientProviderConfig } from './types';\n\nexport interface CreateApiProviderOptions {\n baseURL: string;\n getAccessToken: () => string | null;\n getRefreshToken: () => string | null;\n /** When true, refresh uses HttpOnly cookie credentials instead of body token. */\n cookieCredentialsEnabled: () => boolean;\n setAccessToken: (token: string) => void;\n onRefreshFailure: () => void;\n getAcceptLanguage?: () => string;\n onRefreshStart?: () => void;\n onRefreshEnd?: () => void;\n /** Refresh endpoint path relative to baseURL. Defaults to `auth/refresh`. */\n refreshEndpoint?: string;\n /** Paths that must not trigger automatic refresh retry on 401. Defaults to `['auth/']`. */\n refreshExcludedPrefixes?: string[];\n}\n\n/**\n * Factory for app `ApiProvider` components. Pass a hook that returns wiring options\n * (cookies, i18n, zustand). Memoize that return value with `useMemo` so the API\n * client context is not rebuilt on every parent render.\n */\nexport function createApiProvider(useOptions: () => CreateApiProviderOptions) {\n return function ApiProvider({ children }: { children: ReactNode }) {\n const options = useOptions();\n\n const apiConfig = useMemo<ApiClientProviderConfig>(\n () => ({\n getAccessToken: options.getAccessToken,\n getRefreshToken: options.getRefreshToken,\n getAcceptLanguage: options.getAcceptLanguage,\n refresh: {\n request: createDefaultRefreshRequest({\n endpoint: options.refreshEndpoint,\n excludedRetryPrefixes: options.refreshExcludedPrefixes,\n }),\n cookieCredentialsEnabled: options.cookieCredentialsEnabled,\n setAccessToken: options.setAccessToken,\n onFailure: options.onRefreshFailure,\n onStart: options.onRefreshStart,\n onEnd: options.onRefreshEnd,\n },\n }),\n [options],\n );\n\n return (\n <ApiClientProvider baseURL={options.baseURL} config={apiConfig}>\n {children}\n </ApiClientProvider>\n );\n };\n}\n","import { jwtDecode } from 'jwt-decode';\nimport { useEffect } from 'react';\nimport { useApiClientConfig } from './ApiClientContext';\nimport { executeConfiguredRefresh } from './request';\n\n/** Typical access JWT TTL is 15m — refresh ahead of expiry. */\nconst KEEP_ALIVE_INTERVAL_MS = 10 * 60 * 1000;\nconst REFRESH_SKEW_MS = 2 * 60 * 1000;\n\nexport function accessTokenNeedsRefresh(\n token: string | null,\n nowMs: number = Date.now(),\n skewMs: number = REFRESH_SKEW_MS,\n): boolean {\n if (!token) return true;\n try {\n const { exp } = jwtDecode<{ exp?: number }>(token);\n if (typeof exp !== 'number') return true;\n return exp * 1000 <= nowMs + skewMs;\n } catch {\n return true;\n }\n}\n\nexport type UseAccessTokenKeepAliveOptions = {\n /** When false, no timers or listeners are registered. */\n enabled: boolean;\n /** Same accessor the API client uses (cookie and/or in-memory). */\n getAccessToken: () => string | null;\n};\n\n/**\n * Keeps the access token fresh while a session exists.\n * - Interval while the tab is foregrounded (browsers throttle background timers)\n * - Immediate refresh when the tab becomes visible / focused after expiry\n */\nexport function useAccessTokenKeepAlive(options: UseAccessTokenKeepAliveOptions): void {\n const apiConfig = useApiClientConfig();\n const { enabled, getAccessToken } = options;\n\n useEffect(() => {\n if (!enabled || !apiConfig.refresh) {\n return;\n }\n\n const refreshIfNeeded = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n return;\n }\n if (!accessTokenNeedsRefresh(getAccessToken())) {\n return;\n }\n void executeConfiguredRefresh(apiConfig).catch(() => {\n // Hard auth failures clear via onRefreshFailure; 429/5xx stay intact.\n });\n };\n\n const onVisibility = () => {\n if (document.visibilityState === 'visible') {\n refreshIfNeeded();\n }\n };\n\n refreshIfNeeded();\n document.addEventListener('visibilitychange', onVisibility);\n window.addEventListener('focus', refreshIfNeeded);\n window.addEventListener('pageshow', refreshIfNeeded);\n const id = window.setInterval(refreshIfNeeded, KEEP_ALIVE_INTERVAL_MS);\n\n return () => {\n document.removeEventListener('visibilitychange', onVisibility);\n window.removeEventListener('focus', refreshIfNeeded);\n window.removeEventListener('pageshow', refreshIfNeeded);\n window.clearInterval(id);\n };\n }, [apiConfig, enabled, getAccessToken]);\n}\n"]}
|