@yca-software/yca-react-core 0.0.15 → 0.0.16

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.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-CLRNH2MD.js';
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-EQK7Y66J.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -46,4 +46,26 @@ interface AccessInfo {
46
46
  declare function getAccessInfoFromToken(accessToken: string): AccessInfo | null;
47
47
  declare function getInitials(firstName?: string, lastName?: string): string;
48
48
 
49
- export { type AccessInfo, type CookieConsent, type DecodedAccessToken, type JWTAccessTokenPermissionData, type TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
49
+ /** Placeholder when the refresh token lives in an HttpOnly cookie (not readable by JS). */
50
+ declare const HTTP_ONLY_REFRESH_MARKER = "__http_only_cookie__";
51
+ /**
52
+ * Refresh token the API client should act on.
53
+ *
54
+ * HttpOnly: JS cannot see the cookie. Return a marker so 401 retries can POST
55
+ * `/auth/refresh` with credentials, unless restore already failed or the caller
56
+ * forbids a blind refresh (public sign-in with no access cookie / session).
57
+ */
58
+ declare function resolveApiRefreshToken(input: {
59
+ httpOnly: boolean;
60
+ sessionRestoreFailed: boolean;
61
+ readRefreshCookie: () => string | null;
62
+ /** False on public auth routes when there is no access cookie and no in-memory session. */
63
+ cookieRefreshAllowed: boolean;
64
+ }): string | null;
65
+ /**
66
+ * GET /users/me statuses that mean the account is gone — not a deploy blip.
67
+ * HTTP 401 must not log the SPA out: the API client refreshes the access JWT.
68
+ */
69
+ declare function isInvalidSessionStatus(status: number | undefined): boolean;
70
+
71
+ export { type AccessInfo, type CookieConsent, type DecodedAccessToken, HTTP_ONLY_REFRESH_MARKER, type JWTAccessTokenPermissionData, type TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, isInvalidSessionStatus, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, resolveApiRefreshToken, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
@@ -1,3 +1,3 @@
1
- export { getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from '../chunk-PUG7JXTD.js';
1
+ export { HTTP_ONLY_REFRESH_MARKER, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, isInvalidSessionStatus, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, resolveApiRefreshToken, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from '../chunk-LJXHUEXF.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -60,10 +60,19 @@ 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 isTransientRefreshStatus(status) {
64
+ return status === 0 || status === 429 || status === 502 || status === 503 || status === 504;
65
+ }
66
+ var REFRESH_TRANSIENT_ATTEMPTS = 3;
67
+ var REFRESH_TRANSIENT_DELAY_MS = 50;
68
+ function wait(ms) {
69
+ return new Promise((resolve) => {
70
+ setTimeout(resolve, ms);
71
+ });
72
+ }
63
73
  async function performAccessTokenRefresh(params) {
64
74
  const refreshToken = params.useCookieCredentials ? null : params.getRefreshToken();
65
75
  if (!params.useCookieCredentials && !refreshToken) {
66
- params.onFailure();
67
76
  throw { error: new Error("missing refresh token"), status: 401 };
68
77
  }
69
78
  const bodyContext = {
@@ -76,37 +85,55 @@ async function performAccessTokenRefresh(params) {
76
85
  body: params.request.buildBody(bodyContext),
77
86
  credentials: params.useCookieCredentials ? "include" : params.request.credentials
78
87
  };
79
- let response;
80
- try {
81
- response = await fetch(
82
- buildRequestUrl(params.baseURL, requestConfig.endpoint),
83
- getRequestOptions(requestConfig, null, params.acceptLanguage)
84
- );
85
- } catch {
86
- throw { error: new Error("refresh network error"), status: 0 };
87
- }
88
- const responseText = await response.text();
89
- let data = null;
90
- if (responseText) {
88
+ let lastStatus = 0;
89
+ for (let attempt = 1; attempt <= REFRESH_TRANSIENT_ATTEMPTS; attempt += 1) {
90
+ let response;
91
91
  try {
92
- data = JSON.parse(responseText);
92
+ response = await fetch(
93
+ buildRequestUrl(params.baseURL, requestConfig.endpoint),
94
+ getRequestOptions(requestConfig, null, params.acceptLanguage)
95
+ );
93
96
  } catch {
94
- throw { error: new Error("invalid refresh response"), status: 0 };
97
+ lastStatus = 0;
98
+ if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {
99
+ await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);
100
+ continue;
101
+ }
102
+ throw { error: new Error("refresh network error"), status: 0 };
95
103
  }
96
- }
97
- const accessToken = params.request.parseAccessToken(data);
98
- if (!response.ok || !accessToken) {
99
- const status = response.status || 0;
100
- if (isFatalRefreshFailureStatus(status)) {
104
+ const responseText = await response.text();
105
+ let data = null;
106
+ if (responseText) {
107
+ try {
108
+ data = JSON.parse(responseText);
109
+ } catch {
110
+ lastStatus = 0;
111
+ if (attempt < REFRESH_TRANSIENT_ATTEMPTS) {
112
+ await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);
113
+ continue;
114
+ }
115
+ throw { error: new Error("invalid refresh response"), status: 0 };
116
+ }
117
+ }
118
+ const accessToken = params.request.parseAccessToken(data);
119
+ if (response.ok && accessToken) {
120
+ params.setAccessToken(accessToken);
121
+ return accessToken;
122
+ }
123
+ lastStatus = response.status || 0;
124
+ if (isTransientRefreshStatus(lastStatus) && attempt < REFRESH_TRANSIENT_ATTEMPTS) {
125
+ await wait(REFRESH_TRANSIENT_DELAY_MS * attempt);
126
+ continue;
127
+ }
128
+ if (isFatalRefreshFailureStatus(lastStatus)) {
101
129
  params.onFailure();
102
130
  }
103
131
  throw {
104
132
  error: new Error("failed to refresh access token"),
105
- status
133
+ status: lastStatus
106
134
  };
107
135
  }
108
- params.setAccessToken(accessToken);
109
- return accessToken;
136
+ throw { error: new Error("failed to refresh access token"), status: lastStatus };
110
137
  }
111
138
  var refreshInFlight = null;
112
139
  async function executeConfiguredRefresh(config) {
@@ -135,7 +162,11 @@ async function executeConfiguredRefresh(config) {
135
162
 
136
163
  // src/api/responseHandler.ts
137
164
  function shouldRetryWithRefresh(status, endpoint, isRetry, refreshToken, refresh) {
138
- if (status !== 401 || !refreshToken || !refresh || !endpoint || isRetry) {
165
+ if (status !== 401 || !refresh || !endpoint || isRetry) {
166
+ return false;
167
+ }
168
+ const canRefresh = !!refreshToken || refresh.cookieCredentialsEnabled();
169
+ if (!canRefresh) {
139
170
  return false;
140
171
  }
141
172
  const normalized = endpoint.replace(/^\//, "");
@@ -285,5 +316,5 @@ function useAccessTokenKeepAlive(options) {
285
316
  }
286
317
 
287
318
  export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, isFatalRefreshFailureStatus, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
288
- //# sourceMappingURL=chunk-CLRNH2MD.js.map
289
- //# sourceMappingURL=chunk-CLRNH2MD.js.map
319
+ //# sourceMappingURL=chunk-EQK7Y66J.js.map
320
+ //# sourceMappingURL=chunk-EQK7Y66J.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-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"]}
@@ -83,6 +83,21 @@ function getInitials(firstName, lastName) {
83
83
  return `${firstName?.[0] || ""}${lastName?.[0] || ""}`.toUpperCase();
84
84
  }
85
85
 
86
- export { getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
87
- //# sourceMappingURL=chunk-PUG7JXTD.js.map
88
- //# sourceMappingURL=chunk-PUG7JXTD.js.map
86
+ // src/auth/sessionRestore.ts
87
+ var HTTP_ONLY_REFRESH_MARKER = "__http_only_cookie__";
88
+ function resolveApiRefreshToken(input) {
89
+ if (!input.httpOnly) {
90
+ return input.readRefreshCookie();
91
+ }
92
+ if (input.sessionRestoreFailed || !input.cookieRefreshAllowed) {
93
+ return null;
94
+ }
95
+ return HTTP_ONLY_REFRESH_MARKER;
96
+ }
97
+ function isInvalidSessionStatus(status) {
98
+ return status === 404;
99
+ }
100
+
101
+ export { HTTP_ONLY_REFRESH_MARKER, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, isInvalidSessionStatus, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, resolveApiRefreshToken, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
102
+ //# sourceMappingURL=chunk-LJXHUEXF.js.map
103
+ //# sourceMappingURL=chunk-LJXHUEXF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth/cookie.ts","../src/auth/jwt.ts","../src/auth/sessionRestore.ts"],"names":["jwtDecode"],"mappings":"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","file":"chunk-LJXHUEXF.js","sourcesContent":["import Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\nimport type { CookieNames } from '../constants/cookies';\n\nexport interface CookieConsent {\n analytics: boolean;\n timestamp: string;\n}\n\nexport interface TokenCookieOptions {\n names: CookieNames;\n domain?: string;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\nconst tokenCookieOptions = (options: TokenCookieOptions): Cookies.CookieAttributes => {\n const opts: Cookies.CookieAttributes = { path: '/' };\n if (options.domain) opts.domain = options.domain;\n if (options.secure) opts.secure = true;\n if (options.sameSite) opts.sameSite = options.sameSite;\n return opts;\n};\n\n/** Prefer a dated cookie over a session cookie — iOS drops session cookies when backgrounded. */\nfunction accessTokenCookieExpires(token: string): Date {\n try {\n const { exp } = jwtDecode<{ exp?: number }>(token);\n if (typeof exp === 'number' && exp * 1000 > Date.now()) {\n return new Date(exp * 1000);\n }\n } catch {\n // fall through\n }\n return new Date(Date.now() + 15 * 60 * 1000);\n}\n\nexport function getAccessTokenFromCookies(names: CookieNames): string | null {\n return Cookies.get(names.accessToken) || null;\n}\n\nexport function getRefreshTokenFromCookies(names: CookieNames): string | null {\n return Cookies.get(names.refreshToken) || null;\n}\n\nexport function getCookieConsentFromCookies(names: CookieNames): CookieConsent | null {\n const consentStr = Cookies.get(names.cookieConsent);\n if (!consentStr) return null;\n try {\n return JSON.parse(consentStr) as CookieConsent;\n } catch {\n return null;\n }\n}\n\nexport function removeAccessTokenCookie(options: TokenCookieOptions) {\n Cookies.remove(options.names.accessToken, tokenCookieOptions(options));\n}\n\nexport function removeRefreshTokenCookie(options: TokenCookieOptions) {\n Cookies.remove(options.names.refreshToken, tokenCookieOptions(options));\n}\n\nexport function removeCookieConsentCookie(names: CookieNames) {\n Cookies.remove(names.cookieConsent);\n}\n\nexport function setCookieConsentCookie(\n names: CookieNames,\n timestamp: string,\n analytics: boolean,\n domain?: string,\n) {\n const consent: CookieConsent = { analytics, timestamp };\n const cookieOptions: Cookies.CookieAttributes = {};\n if (domain) cookieOptions.domain = domain;\n Cookies.set(names.cookieConsent, JSON.stringify(consent), cookieOptions);\n}\n\nexport function setAccessTokenCookie(token: string, options: TokenCookieOptions) {\n Cookies.set(options.names.accessToken, token, {\n ...tokenCookieOptions(options),\n expires: accessTokenCookieExpires(token),\n });\n}\n\nexport function setRefreshTokenCookie(token: string, options: TokenCookieOptions) {\n Cookies.set(options.names.refreshToken, token, tokenCookieOptions(options));\n}\n\nexport function setTokens(accessToken: string, refreshToken: string, options: TokenCookieOptions) {\n setAccessTokenCookie(accessToken, options);\n setRefreshTokenCookie(refreshToken, options);\n}\n","import { jwtDecode } from 'jwt-decode';\nimport type { AccessInfo, DecodedAccessToken } from './types';\n\nexport function getAccessInfoFromToken(accessToken: string): AccessInfo | null {\n if (!accessToken?.trim()) return null;\n try {\n const decoded = jwtDecode<DecodedAccessToken>(accessToken);\n return {\n userId: decoded.sub,\n email: decoded.email ?? '',\n permissions: decoded.permissions ?? [],\n isAdmin: decoded.isAdmin ?? false,\n impersonatedBy: decoded.impersonatedBy ?? null,\n impersonatedByEmail: decoded.impersonatedByEmail ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function getInitials(firstName?: string, lastName?: string): string {\n if (!firstName && !lastName) return 'U';\n return `${firstName?.[0] || ''}${lastName?.[0] || ''}`.toUpperCase();\n}\n","/** Placeholder when the refresh token lives in an HttpOnly cookie (not readable by JS). */\nexport const HTTP_ONLY_REFRESH_MARKER = '__http_only_cookie__';\n\n/**\n * Refresh token the API client should act on.\n *\n * HttpOnly: JS cannot see the cookie. Return a marker so 401 retries can POST\n * `/auth/refresh` with credentials, unless restore already failed or the caller\n * forbids a blind refresh (public sign-in with no access cookie / session).\n */\nexport function resolveApiRefreshToken(input: {\n httpOnly: boolean;\n sessionRestoreFailed: boolean;\n readRefreshCookie: () => string | null;\n /** False on public auth routes when there is no access cookie and no in-memory session. */\n cookieRefreshAllowed: boolean;\n}): string | null {\n if (!input.httpOnly) {\n return input.readRefreshCookie();\n }\n if (input.sessionRestoreFailed || !input.cookieRefreshAllowed) {\n return null;\n }\n return HTTP_ONLY_REFRESH_MARKER;\n}\n\n/**\n * GET /users/me statuses that mean the account is gone — not a deploy blip.\n * HTTP 401 must not log the SPA out: the API client refreshes the access JWT.\n */\nexport function isInvalidSessionStatus(status: number | undefined): boolean {\n return status === 404;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { A as AdminDetailErrorKind } from './admin-BVYdWtEy.js';
2
2
  export { getAdminDetailErrorKind } from './admin/index.js';
3
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';
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
- export { AccessInfo, CookieConsent, DecodedAccessToken, JWTAccessTokenPermissionData, TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from './auth/index.js';
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';
7
7
  export { AdminDetailPage, AdminDetailPageProps, AdminListPage, AdminListPageColumn, AdminListPageProps, AuditLogFilterOption, AuditLogListFilters, AuditLogListFiltersProps, BoundQueryStateProps, DateRangeFilter, DateRangeFilterProps, DetailField, DetailFieldList, DetailFieldListProps, DetailFieldVariant, EntityRow, EntityRowAction, EntityRowProps, PageLoader, PageLoaderProps, QueryState, QueryStateBindings, QueryStateProps, RouteSuspense, RouteSuspenseProps, ThemeProvider, ThemeProviderProps, ThemeStore, ThemeToggle, ThemeToggleProps, createBoundQueryState, useDefaultThemeStore, useThemeStoreContext } from './components/spa/index.js';
8
8
  export { Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Avatar, AvatarFallback, AvatarImage, Badge, Button, Calendar, CalendarProps, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, ConfirmDialog, ConfirmDialogProps, DatePicker, DatePickerProps, DateRangePicker, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownScrollableList, DropdownScrollableListProps, DropdownSelectSearchInput, DropdownSelectSearchInputProps, Form, FormControl, FormDescription, FormDrawer, FormDrawerProps, FormField, FormItem, FormLabel, FormMessage, H3, Heading, Input, Label, Link, LinkProps, MultiSelect, MultiSelectOption, MultiSelectTranslations, Paragraph, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, SEARCHABLE_SELECT_MIN_OPTIONS, ScrollArea, ScrollBar, Select, SelectOption, SelectSearchableOption, SelectTranslations, 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, TooltipProps, TooltipProvider, UploadDropzone, UploadDropzoneFileProps, UploadDropzoneImageProps, UploadDropzoneProps, UploadDropzoneTranslations, badgeVariants, buttonVariants, dropdownMenuSelectOptionsMaxHeightClassName, filterSelectOptions, foldSearchText, headingVariants, isSelectSearchable, linkStyles, paragraphVariants, useFormField } from './components/ui/index.js';
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ 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-CLRNH2MD.js';
9
- export { getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from './chunk-PUG7JXTD.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-EQK7Y66J.js';
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';
12
12
  export { useAdminListPage, useTranslationNamespace } from './chunk-LP7Q75C6.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yca-software/yca-react-core",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Shared React components and helpers for YCA apps.",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@11.12.0",
@@ -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-CLRNH2MD.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\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 params.onFailure();\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 response: Response;\n try {\n response = await fetch(\n buildRequestUrl(params.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, null, params.acceptLanguage),\n );\n } catch {\n // Radio sleep / aborted fetch after backgrounding — retry, do not log out.\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 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 const status = response.status || 0;\n // Rate limits / upstream blips / missing body: keep the session so resume can retry.\n if (isFatalRefreshFailureStatus(status)) {\n params.onFailure();\n }\n throw {\n error: new Error('failed to refresh access token'),\n status,\n };\n }\n\n params.setAccessToken(accessToken);\n return accessToken;\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 || !refreshToken || !refresh || !endpoint || isRetry) {\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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/auth/cookie.ts","../src/auth/jwt.ts"],"names":["jwtDecode"],"mappings":"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","file":"chunk-PUG7JXTD.js","sourcesContent":["import Cookies from 'js-cookie';\nimport { jwtDecode } from 'jwt-decode';\nimport type { CookieNames } from '../constants/cookies';\n\nexport interface CookieConsent {\n analytics: boolean;\n timestamp: string;\n}\n\nexport interface TokenCookieOptions {\n names: CookieNames;\n domain?: string;\n secure?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n}\n\nconst tokenCookieOptions = (options: TokenCookieOptions): Cookies.CookieAttributes => {\n const opts: Cookies.CookieAttributes = { path: '/' };\n if (options.domain) opts.domain = options.domain;\n if (options.secure) opts.secure = true;\n if (options.sameSite) opts.sameSite = options.sameSite;\n return opts;\n};\n\n/** Prefer a dated cookie over a session cookie — iOS drops session cookies when backgrounded. */\nfunction accessTokenCookieExpires(token: string): Date {\n try {\n const { exp } = jwtDecode<{ exp?: number }>(token);\n if (typeof exp === 'number' && exp * 1000 > Date.now()) {\n return new Date(exp * 1000);\n }\n } catch {\n // fall through\n }\n return new Date(Date.now() + 15 * 60 * 1000);\n}\n\nexport function getAccessTokenFromCookies(names: CookieNames): string | null {\n return Cookies.get(names.accessToken) || null;\n}\n\nexport function getRefreshTokenFromCookies(names: CookieNames): string | null {\n return Cookies.get(names.refreshToken) || null;\n}\n\nexport function getCookieConsentFromCookies(names: CookieNames): CookieConsent | null {\n const consentStr = Cookies.get(names.cookieConsent);\n if (!consentStr) return null;\n try {\n return JSON.parse(consentStr) as CookieConsent;\n } catch {\n return null;\n }\n}\n\nexport function removeAccessTokenCookie(options: TokenCookieOptions) {\n Cookies.remove(options.names.accessToken, tokenCookieOptions(options));\n}\n\nexport function removeRefreshTokenCookie(options: TokenCookieOptions) {\n Cookies.remove(options.names.refreshToken, tokenCookieOptions(options));\n}\n\nexport function removeCookieConsentCookie(names: CookieNames) {\n Cookies.remove(names.cookieConsent);\n}\n\nexport function setCookieConsentCookie(\n names: CookieNames,\n timestamp: string,\n analytics: boolean,\n domain?: string,\n) {\n const consent: CookieConsent = { analytics, timestamp };\n const cookieOptions: Cookies.CookieAttributes = {};\n if (domain) cookieOptions.domain = domain;\n Cookies.set(names.cookieConsent, JSON.stringify(consent), cookieOptions);\n}\n\nexport function setAccessTokenCookie(token: string, options: TokenCookieOptions) {\n Cookies.set(options.names.accessToken, token, {\n ...tokenCookieOptions(options),\n expires: accessTokenCookieExpires(token),\n });\n}\n\nexport function setRefreshTokenCookie(token: string, options: TokenCookieOptions) {\n Cookies.set(options.names.refreshToken, token, tokenCookieOptions(options));\n}\n\nexport function setTokens(accessToken: string, refreshToken: string, options: TokenCookieOptions) {\n setAccessTokenCookie(accessToken, options);\n setRefreshTokenCookie(refreshToken, options);\n}\n","import { jwtDecode } from 'jwt-decode';\nimport type { AccessInfo, DecodedAccessToken } from './types';\n\nexport function getAccessInfoFromToken(accessToken: string): AccessInfo | null {\n if (!accessToken?.trim()) return null;\n try {\n const decoded = jwtDecode<DecodedAccessToken>(accessToken);\n return {\n userId: decoded.sub,\n email: decoded.email ?? '',\n permissions: decoded.permissions ?? [],\n isAdmin: decoded.isAdmin ?? false,\n impersonatedBy: decoded.impersonatedBy ?? null,\n impersonatedByEmail: decoded.impersonatedByEmail ?? null,\n };\n } catch {\n return null;\n }\n}\n\nexport function getInitials(firstName?: string, lastName?: string): string {\n if (!firstName && !lastName) return 'U';\n return `${firstName?.[0] || ''}${lastName?.[0] || ''}`.toUpperCase();\n}\n"]}