@yca-software/yca-react-core 0.0.11 → 0.0.14

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.
@@ -65,4 +65,18 @@ declare function createResponseHandler(config: ApiClientConfig): (response: Resp
65
65
  isRetry?: boolean;
66
66
  }) => Promise<unknown>;
67
67
 
68
- export { ApiClientConfig, ApiClientProvider, ApiClientProviderConfig, type CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, type DefaultRefreshRequestOptions, FetchFn, RefreshRequestConfig, RequestConfig, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig };
68
+ declare function accessTokenNeedsRefresh(token: string | null, nowMs?: number, skewMs?: number): boolean;
69
+ type UseAccessTokenKeepAliveOptions = {
70
+ /** When false, no timers or listeners are registered. */
71
+ enabled: boolean;
72
+ /** Same accessor the API client uses (cookie and/or in-memory). */
73
+ getAccessToken: () => string | null;
74
+ };
75
+ /**
76
+ * Keeps the access token fresh while a session exists.
77
+ * - Interval while the tab is foregrounded (browsers throttle background timers)
78
+ * - Immediate refresh when the tab becomes visible / focused after expiry
79
+ */
80
+ declare function useAccessTokenKeepAlive(options: UseAccessTokenKeepAliveOptions): void;
81
+
82
+ 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, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
package/dist/api/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig } from '../chunk-OCJCQXEB.js';
1
+ export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig } from '../chunk-BYJAPLGO.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,6 @@
1
- import { createContext, useMemo, useContext, useCallback } from 'react';
1
+ import { createContext, useMemo, useContext, useCallback, useEffect } from 'react';
2
2
  import { jsx } from 'react/jsx-runtime';
3
+ import { jwtDecode } from 'jwt-decode';
3
4
 
4
5
  // src/api/ApiClientContext.tsx
5
6
 
@@ -230,7 +231,52 @@ function createApiProvider(useOptions) {
230
231
  return /* @__PURE__ */ jsx(ApiClientProvider, { baseURL: options.baseURL, config: apiConfig, children });
231
232
  };
232
233
  }
234
+ var KEEP_ALIVE_INTERVAL_MS = 10 * 60 * 1e3;
235
+ var REFRESH_SKEW_MS = 2 * 60 * 1e3;
236
+ function accessTokenNeedsRefresh(token, nowMs = Date.now(), skewMs = REFRESH_SKEW_MS) {
237
+ if (!token) return true;
238
+ try {
239
+ const { exp } = jwtDecode(token);
240
+ if (typeof exp !== "number") return true;
241
+ return exp * 1e3 <= nowMs + skewMs;
242
+ } catch {
243
+ return true;
244
+ }
245
+ }
246
+ function useAccessTokenKeepAlive(options) {
247
+ const apiConfig = useApiClientConfig();
248
+ const { enabled, getAccessToken } = options;
249
+ useEffect(() => {
250
+ if (!enabled || !apiConfig.refresh) {
251
+ return;
252
+ }
253
+ const refreshIfNeeded = () => {
254
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
255
+ return;
256
+ }
257
+ if (!accessTokenNeedsRefresh(getAccessToken())) {
258
+ return;
259
+ }
260
+ void executeConfiguredRefresh(apiConfig).catch(() => {
261
+ });
262
+ };
263
+ const onVisibility = () => {
264
+ if (document.visibilityState === "visible") {
265
+ refreshIfNeeded();
266
+ }
267
+ };
268
+ refreshIfNeeded();
269
+ document.addEventListener("visibilitychange", onVisibility);
270
+ window.addEventListener("focus", refreshIfNeeded);
271
+ const id = window.setInterval(refreshIfNeeded, KEEP_ALIVE_INTERVAL_MS);
272
+ return () => {
273
+ document.removeEventListener("visibilitychange", onVisibility);
274
+ window.removeEventListener("focus", refreshIfNeeded);
275
+ window.clearInterval(id);
276
+ };
277
+ }, [apiConfig, enabled, getAccessToken]);
278
+ }
233
279
 
234
- export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig };
235
- //# sourceMappingURL=chunk-OCJCQXEB.js.map
236
- //# sourceMappingURL=chunk-OCJCQXEB.js.map
280
+ export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, accessTokenNeedsRefresh, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useAccessTokenKeepAlive, useApiClientConfig };
281
+ //# sourceMappingURL=chunk-BYJAPLGO.js.map
282
+ //# sourceMappingURL=chunk-BYJAPLGO.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-BYJAPLGO.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/** Transient refresh failures must not clear the session (SPA onRefreshFailure). */\nfunction isTransientRefreshStatus(status: number): boolean {\n return status === 429 || status >= 500;\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 const response = await fetch(\n buildRequestUrl(params.baseURL, requestConfig.endpoint),\n getRequestOptions(requestConfig, null, params.acceptLanguage),\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 params.onFailure();\n throw { error: new Error('invalid refresh response'), status: 401 };\n }\n }\n\n const accessToken = params.request.parseAccessToken(data);\n if (!response.ok || !accessToken) {\n // Rate limits / upstream blips: keep the session so the next navigation can retry.\n if (!isTransientRefreshStatus(response.status)) {\n params.onFailure();\n }\n throw {\n error: new Error('failed to refresh access token'),\n status: response.status || 401,\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 const id = window.setInterval(refreshIfNeeded, KEEP_ALIVE_INTERVAL_MS);\n\n return () => {\n document.removeEventListener('visibilitychange', onVisibility);\n window.removeEventListener('focus', refreshIfNeeded);\n window.clearInterval(id);\n };\n }, [apiConfig, enabled, getAccessToken]);\n}\n"]}
@@ -919,7 +919,7 @@ var DateRangePickerTrigger = React7.forwardRef(
919
919
  className
920
920
  ),
921
921
  children: [
922
- /* @__PURE__ */ jsx("span", { className: "flex h-10 w-10 shrink-0 items-center justify-center border-r border-border/40 bg-muted/25 text-muted-foreground", children: /* @__PURE__ */ jsx(Calendar$1, { className: "size-4", "aria-hidden": "true" }) }),
922
+ /* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 shrink-0 items-center justify-center border-r border-border/40 bg-muted/25 text-muted-foreground", children: /* @__PURE__ */ jsx(Calendar$1, { className: "size-4", "aria-hidden": "true" }) }),
923
923
  /* @__PURE__ */ jsx(
924
924
  Input,
925
925
  {
@@ -943,7 +943,7 @@ var DateRangePickerTrigger = React7.forwardRef(
943
943
  placeholder: effectivePlaceholder,
944
944
  "data-empty": isEmpty || void 0,
945
945
  className: cn(
946
- "h-10 min-w-0 flex-1 cursor-pointer rounded-none border-0 bg-transparent py-2 pl-3 pr-3 text-sm shadow-none focus-visible:ring-0 whitespace-nowrap",
946
+ "h-9 min-w-0 flex-1 cursor-pointer rounded-none border-0 bg-transparent py-2 pl-3 pr-3 text-sm shadow-none focus-visible:ring-0 whitespace-nowrap",
947
947
  isEmpty && "text-muted-foreground"
948
948
  ),
949
949
  title: !isEmpty ? inputText : void 0,
@@ -2538,5 +2538,5 @@ function UploadDropzone(props) {
2538
2538
  }
2539
2539
 
2540
2540
  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, headingVariants, isSelectSearchable, linkStyles, paragraphVariants, useFormField };
2541
- //# sourceMappingURL=chunk-KRAKQWFP.js.map
2542
- //# sourceMappingURL=chunk-KRAKQWFP.js.map
2541
+ //# sourceMappingURL=chunk-JE5DI4OX.js.map
2542
+ //# sourceMappingURL=chunk-JE5DI4OX.js.map