@ovineko/spa-guard 0.0.1-alpha-4 → 0.0.1-alpha-5

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/README.md CHANGED
@@ -37,6 +37,10 @@ Peer dependencies vary by integration - see sections below for specific requirem
37
37
  - ✅ **Event hooks** - React hooks for subscribing to spa-guard events (`useSPAGuardEvents`, `useSPAGuardChunkError`)
38
38
  - ✅ **Retry control** - Programmatic control over default retry behavior (`disableDefaultRetry`, `enableDefaultRetry`)
39
39
  - ✅ **ESLint plugin** - Enforces usage of spa-guard wrappers instead of direct React imports
40
+ - ✅ **ForceRetryError** - Typed error class that triggers automatic retry without `forceRetry` config
41
+ - ✅ **appName option** - Beacon source identification for monorepo setups
42
+ - ✅ **BeaconError** - Utility class for error tracking service integration (Sentry, Datadog, etc.)
43
+ - ✅ **Configurable unhandled rejection handling** - Control retry and beacon behavior for non-chunk unhandled promise rejections
40
44
 
41
45
  ## Quick Start
42
46
 
@@ -52,6 +56,7 @@ export default defineConfig({
52
56
  plugins: [
53
57
  spaGuardVitePlugin({
54
58
  // Production configuration
59
+ appName: "my-app", // Identifies this app in beacon reports (useful for monorepos)
55
60
  reloadDelays: [1000, 2000, 5000], // 3 attempts with increasing delays
56
61
  html: {
57
62
  fallback: {
@@ -245,6 +250,9 @@ The Vite plugin injects an inline script into your HTML `<head>` that runs **bef
245
250
  import { spaGuardVitePlugin } from "@ovineko/spa-guard/vite-plugin";
246
251
 
247
252
  spaGuardVitePlugin({
253
+ // App identification (useful for monorepos)
254
+ appName: "my-app", // Included in beacon payloads for source identification
255
+
248
256
  // Retry configuration
249
257
  reloadDelays: [1000, 2000, 5000], // Array of delays in milliseconds
250
258
  useRetryId: true, // Use query parameters for cache busting (default: true)
@@ -270,6 +278,12 @@ spaGuardVitePlugin({
270
278
  forceRetry: [], // Array of error message substrings that trigger retry/reload
271
279
  },
272
280
 
281
+ // Unhandled rejection behavior
282
+ handleUnhandledRejections: {
283
+ retry: true, // Attempt page reload on unhandled rejections (default: true)
284
+ sendBeacon: true, // Send beacon report on unhandled rejections (default: true)
285
+ },
286
+
273
287
  // Beacon reporting
274
288
  reportBeacon: {
275
289
  endpoint: "/api/beacon", // Server endpoint for error reports
@@ -292,6 +306,7 @@ spaGuardVitePlugin({
292
306
 
293
307
  ```typescript
294
308
  interface Options {
309
+ appName?: string; // App name for beacon source identification (monorepo setups)
295
310
  reloadDelays?: number[]; // Array of retry delays in ms (default: [1000, 2000, 5000])
296
311
  useRetryId?: boolean; // Use query params for cache busting (default: true)
297
312
  enableRetryReset?: boolean; // Auto-reset retry cycle when enough time passes (default: true)
@@ -311,6 +326,11 @@ interface Options {
311
326
  forceRetry?: string[]; // Error message substrings that trigger retry/reload (default: [])
312
327
  };
313
328
 
329
+ handleUnhandledRejections?: {
330
+ retry?: boolean; // Attempt page reload on unhandled rejections (default: true)
331
+ sendBeacon?: boolean; // Send beacon report on unhandled rejections (default: true)
332
+ };
333
+
314
334
  html?: {
315
335
  fallback?: {
316
336
  content?: string; // Custom error UI HTML (default: minimal error screen)
@@ -353,6 +373,10 @@ interface VitePluginOptions extends Options {
353
373
  ignore: [],
354
374
  forceRetry: [],
355
375
  },
376
+ handleUnhandledRejections: {
377
+ retry: true,
378
+ sendBeacon: true,
379
+ },
356
380
  html: {
357
381
  fallback: {
358
382
  content: defaultErrorFallbackHtml, // minimal error screen (auto-generated)
@@ -379,9 +403,10 @@ app.register(fastifySPAGuard, {
379
403
  onBeacon: async (beacon, request, reply) => {
380
404
  const error = new Error(beacon.errorMessage || "Unknown client error");
381
405
 
382
- // Log structured data
406
+ // Log structured data (appName identifies the source app in monorepos)
383
407
  request.log.error(
384
408
  {
409
+ appName: beacon.appName,
385
410
  errorMessage: beacon.errorMessage,
386
411
  eventName: beacon.eventName,
387
412
  eventMessage: beacon.eventMessage,
@@ -405,6 +430,7 @@ app.register(fastifySPAGuard, {
405
430
 
406
431
  ```typescript
407
432
  interface BeaconSchema {
433
+ appName?: string; // Source app name (from appName option, useful for monorepos)
408
434
  errorMessage?: string; // Error message
409
435
  eventMessage?: string; // Event-specific message
410
436
  eventName?: string; // Event type (e.g., 'chunk_error_max_reloads', 'error', 'unhandledrejection')
@@ -421,6 +447,83 @@ interface BeaconSchema {
421
447
  - `unhandledrejection` - Non-chunk promise rejection
422
448
  - `securitypolicyviolation` - CSP violation
423
449
 
450
+ ### BeaconError
451
+
452
+ `BeaconError` is a utility class that wraps beacon data into a structured `Error` object with typed properties. This makes it easy to integrate with error tracking services like Sentry, Datadog, and others that expect `Error` instances.
453
+
454
+ ```typescript
455
+ import { BeaconError, fastifySPAGuard } from "@ovineko/spa-guard/fastify";
456
+ // BeaconError is also available from the root entry point:
457
+ // import { BeaconError } from "@ovineko/spa-guard";
458
+
459
+ app.register(fastifySPAGuard, {
460
+ path: "/api/beacon",
461
+ onBeacon: async (beacon, request) => {
462
+ const error = new BeaconError(beacon);
463
+
464
+ // Typed properties from the beacon
465
+ error.appName; // string | undefined
466
+ error.errorMessage; // string | undefined
467
+ error.eventName; // string | undefined
468
+ error.retryAttempt; // number | undefined
469
+ error.retryId; // string | undefined
470
+ error.serialized; // string | undefined
471
+ error.eventMessage; // string | undefined
472
+
473
+ // Standard Error properties
474
+ error.name; // "BeaconError"
475
+ error.message; // errorMessage ?? eventMessage ?? "Unknown beacon error"
476
+ error instanceof Error; // true
477
+ error instanceof BeaconError; // true
478
+
479
+ // JSON serialization
480
+ error.toJSON(); // { appName, errorMessage, eventMessage, ... }
481
+ },
482
+ });
483
+ ```
484
+
485
+ **Sentry integration:**
486
+
487
+ ```typescript
488
+ import * as Sentry from "@sentry/node";
489
+ import { BeaconError, fastifySPAGuard } from "@ovineko/spa-guard/fastify";
490
+
491
+ app.register(fastifySPAGuard, {
492
+ path: "/api/beacon",
493
+ onBeacon: async (beacon) => {
494
+ const error = new BeaconError(beacon);
495
+ Sentry.captureException(error, {
496
+ tags: {
497
+ appName: error.appName,
498
+ eventName: error.eventName,
499
+ },
500
+ extra: error.toJSON(),
501
+ });
502
+ return { skipDefaultLog: true };
503
+ },
504
+ });
505
+ ```
506
+
507
+ **Datadog integration:**
508
+
509
+ ```typescript
510
+ import tracer from "dd-trace";
511
+ import { BeaconError, fastifySPAGuard } from "@ovineko/spa-guard/fastify";
512
+
513
+ app.register(fastifySPAGuard, {
514
+ path: "/api/beacon",
515
+ onBeacon: async (beacon) => {
516
+ const error = new BeaconError(beacon);
517
+ const span = tracer.scope().active();
518
+ span?.setTag("error", true);
519
+ span?.setTag("error.message", error.message);
520
+ span?.setTag("error.type", error.eventName);
521
+ if (error.appName) span?.setTag("app.name", error.appName);
522
+ return { skipDefaultLog: true };
523
+ },
524
+ });
525
+ ```
526
+
424
527
  ### React Integration
425
528
 
426
529
  spa-guard provides two React error boundary components with automatic chunk error recovery.
@@ -1019,6 +1122,7 @@ Return `{ skipDefaultLog: true }` from `onBeacon` or `onUnknownBeacon` to suppre
1019
1122
 
1020
1123
  ```typescript
1021
1124
  interface Options {
1125
+ appName?: string; // App name for beacon source identification (monorepo setups)
1022
1126
  reloadDelays?: number[]; // Retry delays in ms (default: [1000, 2000, 5000])
1023
1127
  useRetryId?: boolean; // Use query params for cache busting (default: true)
1024
1128
  enableRetryReset?: boolean; // Auto-reset retry cycle when enough time passes (default: true)
@@ -1038,6 +1142,11 @@ interface Options {
1038
1142
  forceRetry?: string[]; // Error message substrings that trigger retry/reload (default: [])
1039
1143
  };
1040
1144
 
1145
+ handleUnhandledRejections?: {
1146
+ retry?: boolean; // Attempt page reload on unhandled rejections (default: true)
1147
+ sendBeacon?: boolean; // Send beacon report on unhandled rejections (default: true)
1148
+ };
1149
+
1041
1150
  html?: {
1042
1151
  fallback?: {
1043
1152
  content?: string; // Custom error UI HTML
@@ -1071,6 +1180,7 @@ interface VitePluginOptions extends Options {
1071
1180
 
1072
1181
  ```typescript
1073
1182
  interface BeaconSchema {
1183
+ appName?: string; // Source app name (from appName option)
1074
1184
  errorMessage?: string;
1075
1185
  eventMessage?: string;
1076
1186
  eventName?: string;
@@ -1092,6 +1202,8 @@ From `@ovineko/spa-guard`:
1092
1202
  - `disableDefaultRetry()` - Disable inline script's automatic retry
1093
1203
  - `enableDefaultRetry()` - Re-enable automatic retry
1094
1204
  - `isDefaultRetryEnabled()` - Check if default retry is enabled
1205
+ - `ForceRetryError` - Error class that triggers automatic retry when thrown
1206
+ - `BeaconError` - Utility class that wraps beacon data into a structured Error
1095
1207
 
1096
1208
  ### Schema Exports
1097
1209
 
@@ -1113,6 +1225,7 @@ From `@ovineko/spa-guard/runtime`:
1113
1225
  - `subscribeToState(callback)` - Subscribe to state changes, returns unsubscribe function
1114
1226
  - `startVersionCheck()` - Start periodic version polling
1115
1227
  - `stopVersionCheck()` - Stop version polling
1228
+ - `ForceRetryError` - Error class that triggers automatic retry when thrown
1116
1229
  - `SpaGuardState` - TypeScript type for state object
1117
1230
  - `RecommendedSetupOptions` - TypeScript type for recommendedSetup overrides
1118
1231
 
@@ -1343,14 +1456,14 @@ spa-guard provides 11 export entry points:
1343
1456
 
1344
1457
  | Export | Description | Peer Dependencies |
1345
1458
  | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
1346
- | `.` | Core functionality (events, listen, options, retry control) | None |
1459
+ | `.` | Core functionality (events, listen, options, retry control, ForceRetryError, BeaconError) | None |
1347
1460
  | `./schema` | BeaconSchema type definitions | `typebox@^1` |
1348
1461
  | `./schema/parse` | Beacon parsing utilities | `typebox@^1` |
1349
- | `./runtime` | Runtime state management and subscriptions | None |
1462
+ | `./runtime` | Runtime state management, subscriptions, and ForceRetryError | None |
1350
1463
  | `./react` | React hooks and components (useSpaGuardState, useSPAGuardEvents, useSPAGuardChunkError, lazyWithRetry, DebugSyncErrorTrigger) | `react@^19` |
1351
1464
  | `./runtime/debug` | Debug panel factory (`createDebugger`) - framework-agnostic vanilla JS | None |
1352
1465
  | `./react-router` | React Router error boundary (ErrorBoundaryReactRouter) | `react@^19`, `react-router@^7` |
1353
- | `./fastify` | Fastify server plugin | `fastify@^5 \|\| ^4`, `fastify-plugin@^5 \|\| ^4` |
1466
+ | `./fastify` | Fastify server plugin and BeaconError | `fastify@^5 \|\| ^4`, `fastify-plugin@^5 \|\| ^4` |
1354
1467
  | `./vite-plugin` | Vite build plugin | `vite@^8 \|\| ^7` |
1355
1468
  | `./react-error-boundary` | React error boundary component (ErrorBoundary) | `react@^19` |
1356
1469
  | `./eslint` | ESLint plugin with `configs.recommended` preset (`no-direct-error-boundary`, `no-direct-lazy`) | `eslint@^9 \|\| ^10` (optional) |
@@ -1358,22 +1471,32 @@ spa-guard provides 11 export entry points:
1358
1471
  **Import examples:**
1359
1472
 
1360
1473
  ```typescript
1361
- // Core
1362
- import { events, listen, disableDefaultRetry } from "@ovineko/spa-guard";
1363
-
1364
- // Runtime state + version check
1365
- import { getState, subscribeToState, startVersionCheck } from "@ovineko/spa-guard/runtime";
1474
+ // Core (ForceRetryError and BeaconError also available here)
1475
+ import {
1476
+ events,
1477
+ listen,
1478
+ disableDefaultRetry,
1479
+ ForceRetryError,
1480
+ BeaconError,
1481
+ } from "@ovineko/spa-guard";
1482
+
1483
+ // Runtime state + version check + ForceRetryError
1484
+ import {
1485
+ getState,
1486
+ subscribeToState,
1487
+ startVersionCheck,
1488
+ ForceRetryError,
1489
+ } from "@ovineko/spa-guard/runtime";
1366
1490
 
1367
- // React hooks and components
1491
+ // React hooks, components, and ForceRetryError
1368
1492
  import {
1369
1493
  useSpaGuardState,
1370
1494
  useSPAGuardEvents,
1371
1495
  useSPAGuardChunkError,
1372
1496
  DebugSyncErrorTrigger,
1497
+ ForceRetryError,
1498
+ lazyWithRetry,
1373
1499
  } from "@ovineko/spa-guard/react";
1374
-
1375
- // Lazy imports with retry
1376
- import { lazyWithRetry } from "@ovineko/spa-guard/react";
1377
1500
  import type { LazyRetryOptions } from "@ovineko/spa-guard/react";
1378
1501
 
1379
1502
  // Debug panel (framework-agnostic)
@@ -1389,8 +1512,8 @@ import type { BeaconSchema } from "@ovineko/spa-guard/schema";
1389
1512
  // Vite plugin
1390
1513
  import { spaGuardVitePlugin } from "@ovineko/spa-guard/vite-plugin";
1391
1514
 
1392
- // Fastify
1393
- import { fastifySPAGuard } from "@ovineko/spa-guard/fastify";
1515
+ // Fastify (BeaconError also available here)
1516
+ import { fastifySPAGuard, BeaconError } from "@ovineko/spa-guard/fastify";
1394
1517
 
1395
1518
  // ESLint plugin
1396
1519
  import spaGuardEslint from "@ovineko/spa-guard/eslint";
@@ -1527,6 +1650,87 @@ spaGuardVitePlugin({
1527
1650
 
1528
1651
  **`errors.forceRetry`** - Errors containing any of these substrings will trigger the same retry/reload process as chunk load errors (calls `attemptReload()`). Useful for custom error patterns that indicate a stale deployment. Case-sensitive substring matching.
1529
1652
 
1653
+ ### Configuring Unhandled Rejection Handling
1654
+
1655
+ By default, spa-guard retries **and** sends a beacon for regular unhandled promise rejections (those that are not chunk errors or ForceRetry errors). You can control this behavior with the `handleUnhandledRejections` option:
1656
+
1657
+ ```typescript
1658
+ spaGuardVitePlugin({
1659
+ handleUnhandledRejections: {
1660
+ retry: true, // Attempt page reload (default: true)
1661
+ sendBeacon: true, // Send error report to server (default: true)
1662
+ },
1663
+ });
1664
+ ```
1665
+
1666
+ **Behavior matrix:**
1667
+
1668
+ | `retry` | `sendBeacon` | Behavior |
1669
+ | ------- | ------------ | ------------------------------------------------ |
1670
+ | `true` | `true` | Send beacon first, then attempt reload (default) |
1671
+ | `true` | `false` | Attempt reload only |
1672
+ | `false` | `true` | Send beacon only (pre-v0.0.1-alpha behavior) |
1673
+ | `false` | `false` | Do nothing (only the captured error log remains) |
1674
+
1675
+ **Examples:**
1676
+
1677
+ ```typescript
1678
+ // Restore pre-default behavior: beacon only, no retry
1679
+ spaGuardVitePlugin({
1680
+ handleUnhandledRejections: {
1681
+ retry: false,
1682
+ },
1683
+ });
1684
+
1685
+ // Silent mode: only log, no retry or beacon
1686
+ spaGuardVitePlugin({
1687
+ handleUnhandledRejections: {
1688
+ retry: false,
1689
+ sendBeacon: false,
1690
+ },
1691
+ });
1692
+ ```
1693
+
1694
+ **Note:** Chunk load errors and `ForceRetryError` always bypass this configuration and use their own dedicated handling paths regardless of these settings.
1695
+
1696
+ ### ForceRetryError
1697
+
1698
+ `ForceRetryError` is a typed error class that automatically triggers spa-guard's retry mechanism when thrown, without requiring any `errors.forceRetry` configuration. It works by prepending a magic substring to the error message that spa-guard always recognizes.
1699
+
1700
+ ```typescript
1701
+ import { ForceRetryError } from "@ovineko/spa-guard";
1702
+ // or
1703
+ import { ForceRetryError } from "@ovineko/spa-guard/react";
1704
+ // or
1705
+ import { ForceRetryError } from "@ovineko/spa-guard/runtime";
1706
+
1707
+ // Throw in any error handler to trigger automatic retry
1708
+ throw new ForceRetryError("stale module detected");
1709
+
1710
+ // Works with no message too
1711
+ throw new ForceRetryError();
1712
+ ```
1713
+
1714
+ **Use cases:**
1715
+
1716
+ - Custom module loaders that detect stale deployments
1717
+ - Service workers that need to force a refresh
1718
+ - API responses indicating version mismatch
1719
+
1720
+ **How it works:**
1721
+
1722
+ 1. `ForceRetryError` prepends a magic substring (`__SPA_GUARD_FORCE_RETRY__`) to the error message
1723
+ 2. `shouldForceRetry()` always checks for this substring, regardless of `errors.forceRetry` config
1724
+ 3. When matched, spa-guard triggers the same retry/reload cycle as chunk load errors
1725
+
1726
+ ```typescript
1727
+ const err = new ForceRetryError("version mismatch");
1728
+ err.name; // "ForceRetryError"
1729
+ err.message; // "__SPA_GUARD_FORCE_RETRY__version mismatch"
1730
+ err instanceof ForceRetryError; // true
1731
+ err instanceof Error; // true
1732
+ ```
1733
+
1530
1734
  ### Custom Retry Strategy
1531
1735
 
1532
1736
  Adjust retry delays for different environments:
@@ -0,0 +1,38 @@
1
+ // src/common/errors/BeaconError.ts
2
+ var BeaconError = class extends Error {
3
+ appName;
4
+ errorMessage;
5
+ eventMessage;
6
+ eventName;
7
+ retryAttempt;
8
+ retryId;
9
+ serialized;
10
+ constructor(beacon) {
11
+ super(beacon.errorMessage ?? beacon.eventMessage ?? "Unknown beacon error");
12
+ this.name = "BeaconError";
13
+ this.appName = beacon.appName;
14
+ this.errorMessage = beacon.errorMessage;
15
+ this.eventMessage = beacon.eventMessage;
16
+ this.eventName = beacon.eventName;
17
+ this.retryAttempt = beacon.retryAttempt;
18
+ this.retryId = beacon.retryId;
19
+ this.serialized = beacon.serialized;
20
+ }
21
+ toJSON() {
22
+ return {
23
+ appName: this.appName,
24
+ errorMessage: this.errorMessage,
25
+ eventMessage: this.eventMessage,
26
+ eventName: this.eventName,
27
+ message: this.message,
28
+ name: this.name,
29
+ retryAttempt: this.retryAttempt,
30
+ retryId: this.retryId,
31
+ serialized: this.serialized
32
+ };
33
+ }
34
+ };
35
+
36
+ export {
37
+ BeaconError
38
+ };
@@ -28,6 +28,10 @@ var defaultOptions = {
28
28
  forceRetry: [],
29
29
  ignore: []
30
30
  },
31
+ handleUnhandledRejections: {
32
+ retry: true,
33
+ sendBeacon: true
34
+ },
31
35
  html: {
32
36
  fallback: {
33
37
  content: defaultErrorFallbackHtml,
@@ -58,6 +62,10 @@ var getOptions = () => {
58
62
  ...defaultOptions.errors,
59
63
  ...windowOptions?.errors
60
64
  },
65
+ handleUnhandledRejections: {
66
+ ...defaultOptions.handleUnhandledRejections,
67
+ ...windowOptions?.handleUnhandledRejections
68
+ },
61
69
  html: {
62
70
  fallback: {
63
71
  ...defaultOptions.html?.fallback,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  attemptReload,
3
3
  isChunkError
4
- } from "./chunk-6XKIZF5S.js";
4
+ } from "./chunk-RFGT43OM.js";
5
5
  import {
6
6
  getState,
7
7
  subscribeToState
@@ -13,7 +13,7 @@ import {
13
13
  } from "./chunk-T5RWH2HR.js";
14
14
  import {
15
15
  getOptions
16
- } from "./chunk-DPOQIK4J.js";
16
+ } from "./chunk-G6QM353W.js";
17
17
  import {
18
18
  debugSyncErrorEventType
19
19
  } from "./chunk-EDRTFPCN.js";
@@ -2,6 +2,7 @@
2
2
  import { Type } from "typebox";
3
3
  var beaconSchema = Type.Object(
4
4
  {
5
+ appName: Type.Optional(Type.String()),
5
6
  errorMessage: Type.Optional(Type.String()),
6
7
  eventMessage: Type.Optional(Type.String()),
7
8
  eventName: Type.Optional(Type.String()),
@@ -0,0 +1,13 @@
1
+ // src/common/errors/ForceRetryError.ts
2
+ var FORCE_RETRY_MAGIC = "__SPA_GUARD_FORCE_RETRY__";
3
+ var ForceRetryError = class extends Error {
4
+ constructor(message) {
5
+ super(`${FORCE_RETRY_MAGIC}${message ?? ""}`);
6
+ this.name = "ForceRetryError";
7
+ }
8
+ };
9
+
10
+ export {
11
+ FORCE_RETRY_MAGIC,
12
+ ForceRetryError
13
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-T5RWH2HR.js";
4
4
  import {
5
5
  getOptions
6
- } from "./chunk-DPOQIK4J.js";
6
+ } from "./chunk-G6QM353W.js";
7
7
 
8
8
  // src/common/checkVersion.ts
9
9
  var versionCheckInterval = null;
@@ -14,7 +14,7 @@ var visibilityHandler = null;
14
14
  var focusHandler = null;
15
15
  var blurHandler = null;
16
16
  var checkInProgress = false;
17
- var stopped = false;
17
+ var runEpoch = 0;
18
18
  var fetchJsonVersion = async () => {
19
19
  const endpoint = getOptions().checkVersion?.endpoint;
20
20
  if (!endpoint) {
@@ -30,7 +30,10 @@ var fetchJsonVersion = async () => {
30
30
  return null;
31
31
  }
32
32
  const data = await response.json();
33
- return data.version ?? null;
33
+ if (typeof data !== "object" || data === null) {
34
+ return null;
35
+ }
36
+ return "version" in data && typeof data.version === "string" ? data.version : null;
34
37
  };
35
38
  var fetchHtmlVersion = async () => {
36
39
  const url = new URL(globalThis.location.href);
@@ -73,9 +76,10 @@ var checkVersionOnce = async (mode) => {
73
76
  return;
74
77
  }
75
78
  checkInProgress = true;
79
+ const epochAtStart = runEpoch;
76
80
  try {
77
81
  const remoteVersion = await fetchRemoteVersion(mode);
78
- if (stopped) {
82
+ if (epochAtStart !== runEpoch) {
79
83
  return;
80
84
  }
81
85
  if (remoteVersion && remoteVersion !== lastKnownVersion) {
@@ -86,7 +90,9 @@ var checkVersionOnce = async (mode) => {
86
90
  } catch (error) {
87
91
  getLogger()?.versionCheckFailed(error);
88
92
  } finally {
89
- checkInProgress = false;
93
+ if (epochAtStart === runEpoch) {
94
+ checkInProgress = false;
95
+ }
90
96
  }
91
97
  };
92
98
  var startPolling = (mode, interval) => {
@@ -144,7 +150,7 @@ var startVersionCheck = () => {
144
150
  getLogger()?.versionCheckAlreadyRunning();
145
151
  return;
146
152
  }
147
- stopped = false;
153
+ runEpoch++;
148
154
  lastKnownVersion = options.version;
149
155
  const interval = options.checkVersion?.interval ?? 3e5;
150
156
  const mode = options.checkVersion?.mode ?? "html";
@@ -176,7 +182,8 @@ var startVersionCheck = () => {
176
182
  globalThis.addEventListener("blur", blurHandler);
177
183
  };
178
184
  var stopVersionCheck = () => {
179
- stopped = true;
185
+ runEpoch++;
186
+ checkInProgress = false;
180
187
  const wasRunning = versionCheckInterval !== null || versionCheckTimeout !== null || visibilityHandler !== null;
181
188
  clearTimers();
182
189
  if (visibilityHandler !== null) {
@@ -1,3 +1,6 @@
1
+ import {
2
+ FORCE_RETRY_MAGIC
3
+ } from "./chunk-L5UEVGWR.js";
1
4
  import {
2
5
  clearLastReloadTime,
3
6
  clearRetryAttemptFromUrl,
@@ -15,7 +18,7 @@ import {
15
18
  } from "./chunk-T5RWH2HR.js";
16
19
  import {
17
20
  getOptions
18
- } from "./chunk-DPOQIK4J.js";
21
+ } from "./chunk-G6QM353W.js";
19
22
  import {
20
23
  RETRY_ATTEMPT_PARAM,
21
24
  RETRY_ID_PARAM
@@ -68,10 +71,7 @@ var shouldIgnoreMessages = (messages) => {
68
71
  };
69
72
  var shouldForceRetry = (messages) => {
70
73
  const options = getOptions();
71
- const forceRetryPatterns = options.errors?.forceRetry ?? [];
72
- if (forceRetryPatterns.length === 0) {
73
- return false;
74
- }
74
+ const forceRetryPatterns = [...options.errors?.forceRetry ?? [], FORCE_RETRY_MAGIC];
75
75
  const validMessages = messages.filter((msg) => typeof msg === "string");
76
76
  return validMessages.some(
77
77
  (message) => forceRetryPatterns.some((pattern) => message.includes(pattern))
@@ -91,8 +91,9 @@ var sendBeacon = (beacon) => {
91
91
  getLogger()?.noBeaconEndpoint();
92
92
  return;
93
93
  }
94
- const body = JSON.stringify(beacon);
95
- const isSendBeaconAvailable = typeof globalThis.window?.navigator.sendBeacon === "function";
94
+ const enrichedBeacon = options.appName ? { ...beacon, appName: options.appName } : beacon;
95
+ const body = JSON.stringify(enrichedBeacon);
96
+ const isSendBeaconAvailable = typeof globalThis.window?.navigator?.sendBeacon === "function";
96
97
  const isSentBeacon = isSendBeaconAvailable && globalThis.window.navigator.sendBeacon(options.reportBeacon.endpoint, body);
97
98
  if (!isSentBeacon) {
98
99
  fetch(options.reportBeacon.endpoint, { body, keepalive: true, method: "POST" }).catch(
@@ -6,14 +6,14 @@ import {
6
6
  isChunkError,
7
7
  sendBeacon,
8
8
  shouldForceRetry
9
- } from "./chunk-6XKIZF5S.js";
9
+ } from "./chunk-RFGT43OM.js";
10
10
  import {
11
11
  getRetryInfoForBeacon
12
12
  } from "./chunk-T5RWH2HR.js";
13
13
  import {
14
14
  defaultErrorFallbackHtml,
15
15
  defaultLoadingFallbackHtml
16
- } from "./chunk-DPOQIK4J.js";
16
+ } from "./chunk-G6QM353W.js";
17
17
 
18
18
  // src/common/DefaultErrorFallback.tsx
19
19
  import { useLayoutEffect, useMemo, useRef } from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  beaconSchema
3
- } from "./chunk-7HWVSDJZ.js";
3
+ } from "./chunk-KZEBQNOZ.js";
4
4
 
5
5
  // src/schema/parse.ts
6
6
  import { Value } from "typebox/value";
@@ -0,0 +1,12 @@
1
+ import type { BeaconSchema } from "../../schema";
2
+ export declare class BeaconError extends Error {
3
+ readonly appName: string | undefined;
4
+ readonly errorMessage: string | undefined;
5
+ readonly eventMessage: string | undefined;
6
+ readonly eventName: string | undefined;
7
+ readonly retryAttempt: number | undefined;
8
+ readonly retryId: string | undefined;
9
+ readonly serialized: string | undefined;
10
+ constructor(beacon: BeaconSchema);
11
+ toJSON(): Record<string, unknown>;
12
+ }
@@ -0,0 +1,5 @@
1
+ declare const FORCE_RETRY_MAGIC = "__SPA_GUARD_FORCE_RETRY__";
2
+ export { FORCE_RETRY_MAGIC };
3
+ export declare class ForceRetryError extends Error {
4
+ constructor(message?: string);
5
+ }
@@ -1,3 +1,5 @@
1
+ export { BeaconError } from "./errors/BeaconError";
2
+ export { ForceRetryError } from "./errors/ForceRetryError";
1
3
  export * as events from "./events";
2
4
  export { disableDefaultRetry, enableDefaultRetry, isDefaultRetryEnabled } from "./events/internal";
3
5
  export { listen } from "./listen";
@@ -1,3 +1,6 @@
1
+ import {
2
+ BeaconError
3
+ } from "../chunk-3SCN2UE4.js";
1
4
  import {
2
5
  serializeError
3
6
  } from "../chunk-HUAI4DRW.js";
@@ -7,7 +10,10 @@ import {
7
10
  sendBeacon,
8
11
  shouldForceRetry,
9
12
  shouldIgnoreMessages
10
- } from "../chunk-6XKIZF5S.js";
13
+ } from "../chunk-RFGT43OM.js";
14
+ import {
15
+ ForceRetryError
16
+ } from "../chunk-L5UEVGWR.js";
11
17
  import {
12
18
  disableDefaultRetry,
13
19
  emitEvent,
@@ -25,7 +31,7 @@ import {
25
31
  import {
26
32
  getOptions,
27
33
  options_exports
28
- } from "../chunk-DPOQIK4J.js";
34
+ } from "../chunk-G6QM353W.js";
29
35
  import "../chunk-EDRTFPCN.js";
30
36
  import "../chunk-RP52SPBK.js";
31
37
  import {
@@ -246,13 +252,20 @@ var listenInternal = (serializeError2, logger) => {
246
252
  attemptReload(event.reason);
247
253
  return;
248
254
  }
249
- const serialized = serializeError2(event);
250
- sendBeacon({
251
- errorMessage,
252
- eventName: "unhandledrejection",
253
- serialized,
254
- ...getRetryInfoForBeacon()
255
- });
255
+ const rejectionConfig = options.handleUnhandledRejections;
256
+ if (rejectionConfig?.sendBeacon !== false) {
257
+ const serialized = serializeError2(event);
258
+ sendBeacon({
259
+ errorMessage,
260
+ eventName: "unhandledrejection",
261
+ serialized,
262
+ ...getRetryInfoForBeacon()
263
+ });
264
+ }
265
+ if (rejectionConfig?.retry !== false) {
266
+ event.preventDefault();
267
+ attemptReload(event.reason);
268
+ }
256
269
  });
257
270
  wa("securitypolicyviolation", (event) => {
258
271
  const eventMessage = `${event.violatedDirective}: ${event.blockedURI}`;
@@ -287,6 +300,8 @@ var listen = () => {
287
300
  listenInternal(serializeError, createLogger());
288
301
  };
289
302
  export {
303
+ BeaconError,
304
+ ForceRetryError,
290
305
  disableDefaultRetry,
291
306
  enableDefaultRetry,
292
307
  events_exports as events,
@@ -1,5 +1,10 @@
1
1
  export { optionsWindowKey } from "./constants";
2
2
  export interface Options {
3
+ /**
4
+ * Application name for beacon source identification.
5
+ * Useful in monorepo setups to identify which app sent the beacon.
6
+ */
7
+ appName?: string;
3
8
  /**
4
9
  * Configuration for proactive version checking to detect new deployments.
5
10
  * When configured with a `version`, periodically polls to detect version changes
@@ -55,6 +60,23 @@ export interface Options {
55
60
  */
56
61
  ignore?: string[];
57
62
  };
63
+ /**
64
+ * Controls behavior for regular unhandled promise rejections
65
+ * (those that are not chunk errors or ForceRetry errors).
66
+ * @default { retry: true, sendBeacon: true }
67
+ */
68
+ handleUnhandledRejections?: {
69
+ /**
70
+ * Whether to attempt a page reload on unhandled rejections.
71
+ * @default true
72
+ */
73
+ retry?: boolean;
74
+ /**
75
+ * Whether to send a beacon report on unhandled rejections.
76
+ * @default true
77
+ */
78
+ sendBeacon?: boolean;
79
+ };
58
80
  html?: {
59
81
  fallback?: {
60
82
  /** Custom HTML to display when all reload attempts are exhausted */
@@ -1,5 +1,6 @@
1
1
  import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
2
2
  import type { BeaconSchema } from "../schema";
3
+ export { BeaconError } from "../common/errors/BeaconError";
3
4
  export interface BeaconHandlerResult {
4
5
  /**
5
6
  * If true, skips default logging behavior
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  parseBeacon
3
- } from "../chunk-AB733L4R.js";
4
- import "../chunk-7HWVSDJZ.js";
3
+ } from "../chunk-WE7SWL5H.js";
4
+ import "../chunk-KZEBQNOZ.js";
5
+ import {
6
+ BeaconError
7
+ } from "../chunk-3SCN2UE4.js";
5
8
  import {
6
9
  name
7
10
  } from "../chunk-RP52SPBK.js";
@@ -37,29 +40,20 @@ var handleBeaconRequest = async (params) => {
37
40
  }
38
41
  return;
39
42
  }
43
+ const logPayload = {
44
+ ...beacon.appName && { appName: beacon.appName },
45
+ errorMessage: beacon.errorMessage,
46
+ eventMessage: beacon.eventMessage,
47
+ eventName: beacon.eventName,
48
+ serialized: beacon.serialized
49
+ };
40
50
  if (options.onBeacon) {
41
51
  const result = await options.onBeacon(beacon, request, reply);
42
52
  if (!result?.skipDefaultLog) {
43
- request.log.info(
44
- {
45
- errorMessage: beacon.errorMessage,
46
- eventMessage: beacon.eventMessage,
47
- eventName: beacon.eventName,
48
- serialized: beacon.serialized
49
- },
50
- logMessage("Beacon received")
51
- );
53
+ request.log.info(logPayload, logMessage("Beacon received"));
52
54
  }
53
55
  } else {
54
- request.log.info(
55
- {
56
- errorMessage: beacon.errorMessage,
57
- eventMessage: beacon.eventMessage,
58
- eventName: beacon.eventName,
59
- serialized: beacon.serialized
60
- },
61
- logMessage("Beacon received")
62
- );
56
+ request.log.info(logPayload, logMessage("Beacon received"));
63
57
  }
64
58
  };
65
59
  var fastifySPAGuardPlugin = async (fastify, options) => {
@@ -85,5 +79,6 @@ var fastifySPAGuard = fp(fastifySPAGuardPlugin, {
85
79
  name: `${name}/fastify`
86
80
  });
87
81
  export {
82
+ BeaconError,
88
83
  fastifySPAGuard
89
84
  };
@@ -1,3 +1,4 @@
1
+ export { ForceRetryError } from "../common/errors/ForceRetryError";
1
2
  export type { SpaGuardState } from "../runtime";
2
3
  export { DebugSyncErrorTrigger } from "./DebugSyncErrorTrigger";
3
4
  export { lazyWithRetry } from "./lazyWithRetry";
@@ -4,17 +4,21 @@ import {
4
4
  useSPAGuardChunkError,
5
5
  useSPAGuardEvents,
6
6
  useSpaGuardState
7
- } from "../chunk-UMNFZ7IW.js";
8
- import "../chunk-6XKIZF5S.js";
9
- import "../chunk-FAFKLSME.js";
7
+ } from "../chunk-IC7SCT5F.js";
8
+ import "../chunk-RFGT43OM.js";
9
+ import "../chunk-LGPIRNKE.js";
10
10
  import "../chunk-2DFYUVHH.js";
11
+ import {
12
+ ForceRetryError
13
+ } from "../chunk-L5UEVGWR.js";
11
14
  import "../chunk-T5RWH2HR.js";
12
- import "../chunk-DPOQIK4J.js";
15
+ import "../chunk-G6QM353W.js";
13
16
  import "../chunk-EDRTFPCN.js";
14
17
  import "../chunk-RP52SPBK.js";
15
18
  import "../chunk-MLKGABMK.js";
16
19
  export {
17
20
  DebugSyncErrorTrigger,
21
+ ForceRetryError,
18
22
  lazyWithRetry,
19
23
  useSPAGuardChunkError,
20
24
  useSPAGuardEvents,
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  DefaultErrorFallback,
3
3
  handleErrorWithSpaGuard
4
- } from "../chunk-HVBPX75C.js";
4
+ } from "../chunk-RXW44IWI.js";
5
5
  import "../chunk-HUAI4DRW.js";
6
6
  import {
7
7
  useSpaGuardState
8
- } from "../chunk-UMNFZ7IW.js";
8
+ } from "../chunk-IC7SCT5F.js";
9
9
  import {
10
10
  isChunkError
11
- } from "../chunk-6XKIZF5S.js";
12
- import "../chunk-FAFKLSME.js";
11
+ } from "../chunk-RFGT43OM.js";
12
+ import "../chunk-LGPIRNKE.js";
13
13
  import "../chunk-2DFYUVHH.js";
14
+ import "../chunk-L5UEVGWR.js";
14
15
  import "../chunk-T5RWH2HR.js";
15
- import "../chunk-DPOQIK4J.js";
16
+ import "../chunk-G6QM353W.js";
16
17
  import "../chunk-EDRTFPCN.js";
17
18
  import "../chunk-RP52SPBK.js";
18
19
  import "../chunk-MLKGABMK.js";
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  DefaultErrorFallback,
3
3
  handleErrorWithSpaGuard
4
- } from "../chunk-HVBPX75C.js";
4
+ } from "../chunk-RXW44IWI.js";
5
5
  import "../chunk-HUAI4DRW.js";
6
6
  import {
7
7
  useSpaGuardState
8
- } from "../chunk-UMNFZ7IW.js";
8
+ } from "../chunk-IC7SCT5F.js";
9
9
  import {
10
10
  isChunkError
11
- } from "../chunk-6XKIZF5S.js";
12
- import "../chunk-FAFKLSME.js";
11
+ } from "../chunk-RFGT43OM.js";
12
+ import "../chunk-LGPIRNKE.js";
13
13
  import "../chunk-2DFYUVHH.js";
14
+ import "../chunk-L5UEVGWR.js";
14
15
  import "../chunk-T5RWH2HR.js";
15
- import "../chunk-DPOQIK4J.js";
16
+ import "../chunk-G6QM353W.js";
16
17
  import "../chunk-EDRTFPCN.js";
17
18
  import "../chunk-RP52SPBK.js";
18
19
  import "../chunk-MLKGABMK.js";
@@ -59,6 +59,11 @@ function createDebugger(options) {
59
59
  console.warn("[spa-guard] Debug panel already exists. Returning existing cleanup function.");
60
60
  return activeInstance;
61
61
  }
62
+ if (!document.body) {
63
+ console.warn("[spa-guard] document.body is not available. Debug panel cannot be mounted.");
64
+ return () => {
65
+ };
66
+ }
62
67
  let isOpen = options?.defaultOpen ?? true;
63
68
  const unsubscribers = [];
64
69
  const panel = el(
@@ -151,7 +156,7 @@ function createEventsSection(unsubscribers) {
151
156
  "debug-event-list"
152
157
  );
153
158
  section.append(eventList);
154
- wireEventSubscription(countSpan, clearBtn, eventList, unsubscribers);
159
+ wireEventSubscription({ clearBtn, countSpan, eventList, unsubscribers });
155
160
  return section;
156
161
  }
157
162
  function createHeader(isOpen) {
@@ -239,7 +244,8 @@ function getButtonLabel(label, status) {
239
244
  }
240
245
  }
241
246
  }
242
- function wireEventSubscription(countSpan, clearBtn, eventList, unsubscribers) {
247
+ function wireEventSubscription(params) {
248
+ const { clearBtn, countSpan, eventList, unsubscribers } = params;
243
249
  let history = [];
244
250
  function render() {
245
251
  countSpan.textContent = `Event History (${String(history.length)})`;
@@ -1,4 +1,5 @@
1
1
  export { startVersionCheck, stopVersionCheck } from "../common/checkVersion";
2
+ export { ForceRetryError } from "../common/errors/ForceRetryError";
2
3
  export { recommendedSetup } from "./recommendedSetup";
3
4
  export type { RecommendedSetupOptions } from "./recommendedSetup";
4
5
  export { getState, subscribeToState } from "./state";
@@ -2,17 +2,21 @@ import {
2
2
  recommendedSetup,
3
3
  startVersionCheck,
4
4
  stopVersionCheck
5
- } from "../chunk-FAFKLSME.js";
5
+ } from "../chunk-LGPIRNKE.js";
6
6
  import {
7
7
  getState,
8
8
  subscribeToState
9
9
  } from "../chunk-2DFYUVHH.js";
10
+ import {
11
+ ForceRetryError
12
+ } from "../chunk-L5UEVGWR.js";
10
13
  import "../chunk-T5RWH2HR.js";
11
- import "../chunk-DPOQIK4J.js";
14
+ import "../chunk-G6QM353W.js";
12
15
  import "../chunk-EDRTFPCN.js";
13
16
  import "../chunk-RP52SPBK.js";
14
17
  import "../chunk-MLKGABMK.js";
15
18
  export {
19
+ ForceRetryError,
16
20
  getState,
17
21
  recommendedSetup,
18
22
  startVersionCheck,
@@ -1,5 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  export declare const beaconSchema: Type.TObject<{
3
+ appName: Type.TOptional<Type.TString>;
3
4
  errorMessage: Type.TOptional<Type.TString>;
4
5
  eventMessage: Type.TOptional<Type.TString>;
5
6
  eventName: Type.TOptional<Type.TString>;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  beaconSchema
3
- } from "../chunk-7HWVSDJZ.js";
3
+ } from "../chunk-KZEBQNOZ.js";
4
4
  import "../chunk-MLKGABMK.js";
5
5
  export {
6
6
  beaconSchema
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseBeacon
3
- } from "../chunk-AB733L4R.js";
4
- import "../chunk-7HWVSDJZ.js";
3
+ } from "../chunk-WE7SWL5H.js";
4
+ import "../chunk-KZEBQNOZ.js";
5
5
  import "../chunk-MLKGABMK.js";
6
6
  export {
7
7
  parseBeacon
@@ -1,4 +1,4 @@
1
- import "../chunk-DPOQIK4J.js";
1
+ import "../chunk-G6QM353W.js";
2
2
  import {
3
3
  optionsWindowKey
4
4
  } from "../chunk-EDRTFPCN.js";
@@ -1 +1 @@
1
- var e="@ovineko/spa-guard",t=Symbol.for(e+":event-subscribers"),r=Symbol.for(e+":internal-config"),a=Symbol.for(e+":initialized"),n=Symbol.for(e+":logger"),o="spaGuardRetryId",l="spaGuardRetryAttempt";globalThis.window&&!globalThis.window[t]&&(globalThis.window[t]=new Set),globalThis.window&&!globalThis.window[r]&&(globalThis.window[r]={defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1});var i=globalThis.window?.[t]??new Set,s=globalThis.window?.[r]??{defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1},d=()=>globalThis.window?.[n],c=(e,t)=>{t?.silent||d()?.logEvent(e),i.forEach(t=>{try{t(e)}catch{}})},y=e=>{let t=g(e);return!!t&&[/Failed to fetch dynamically imported module/i,/Importing a module script failed/i,/error loading dynamically imported module/i,/Unable to preload CSS/i,/Loading chunk \d+ failed/i,/Loading CSS chunk \d+ failed/i,/ChunkLoadError/i].some(e=>e.test(t))},g=e=>e instanceof Error?e.message:"string"==typeof e?e:e&&"object"==typeof e&&"message"in e?e.message+"":e&&"object"==typeof e&&"reason"in e?g(e.reason):null,m={checkVersion:{interval:3e5,mode:"html",onUpdate:"reload"},enableRetryReset:!0,errors:{forceRetry:[],ignore:[]},html:{fallback:{content:'<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h1 data-spa-guard-content="heading">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:1rem auto">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center"><button data-spa-guard-action="try-again" type="button" style="display:none">Try again</button> <button data-spa-guard-action="reload" type="button" onclick="location.reload()">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:20px;font-size:12px">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>',selector:"body"},loading:{content:'<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h2>Loading...</h2><p data-spa-guard-section="retrying" style="display:none">Retry attempt <span data-spa-guard-content="attempt"></span></p></div></div>'}},lazyRetry:{callReloadOnFailure:!0,retryDelays:[1e3,2e3]},minTimeBetweenResets:5e3,reloadDelays:[1e3,2e3,5e3],useRetryId:!0},u=()=>{let e=globalThis.window?.__SPA_GUARD_OPTIONS__;return{...m,...e,checkVersion:{...m.checkVersion,...e?.checkVersion},errors:{...m.errors,...e?.errors},html:{fallback:{...m.html?.fallback,...e?.html?.fallback},loading:{...m.html?.loading,...e?.html?.loading}},lazyRetry:{...m.lazyRetry,...e?.lazyRetry},reportBeacon:{...m.reportBeacon,...e?.reportBeacon}}},p="__spa_guard_last_reload_timestamp__",h="__spa_guard_last_retry_reset__",f=null,w=null,b=()=>{try{return void 0!==globalThis.window&&typeof sessionStorage<"u"}catch{return!1}},v=()=>{if(b())try{let e=sessionStorage.getItem(p);if(e)return JSON.parse(e)}catch{return f}return f},S=()=>{try{let e=new URLSearchParams(globalThis.window.location.search),t=e.get(o),r=e.get(l);if(t&&r){let e=parseInt(r,10);return Number.isNaN(e)?null:{retryAttempt:e,retryId:t}}return null}catch{return null}},R=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(o),e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},I=()=>{if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint32Array(2);return crypto.getRandomValues(e),`${Date.now()}-${e[0].toString(36)}${e[1].toString(36)}`}return`${Date.now()}-${Math.random().toString(36).slice(2,15)}`},T=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},k=()=>{let e=S();return e?{retryAttempt:e.retryAttempt,retryId:e.retryId}:{}},_=e=>{let t=u().errors?.ignore??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},A=e=>{let t=u().errors?.forceRetry??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},N=e=>{if((e=>_([e.errorMessage,e.eventMessage]))(e))return;let t=u();if(!t.reportBeacon?.endpoint)return void d()?.noBeaconEndpoint();let r=JSON.stringify(e);"function"==typeof globalThis.window?.navigator.sendBeacon&&globalThis.window.navigator.sendBeacon(t.reportBeacon.endpoint,r)||fetch(t.reportBeacon.endpoint,{body:r,keepalive:!0,method:"POST"}).catch(e=>{d()?.beaconSendFailed(e)})},D=!1,E=e=>{if(D)return void d()?.reloadAlreadyScheduled(e);let t,r=u(),a=r.reloadDelays??[1e3,2e3,5e3],n=r.useRetryId??!0,i=r.enableRetryReset??!0,y=r.minTimeBetweenResets??5e3;if(n)t=S();else{let e=(()=>{try{let e=new URLSearchParams(globalThis.window.location.search).get(l);if(e){let t=parseInt(e,10);return Number.isNaN(t)?null:t}return null}catch{return null}})();t=null===e?null:{retryAttempt:e,retryId:I()}}let g=t?t.retryAttempt:0,m=t?.retryId??I();d()?.retryCycleStarting(m,g);let k=s.defaultRetryEnabled;if(c({error:e,isRetrying:k&&g>=0&&g<a.length,name:"chunk-error"}),!k)return;if(i&&t&&t.retryAttempt>0&&((e,t,r=5e3)=>{if(0===e.retryAttempt)return!1;let a=v();if(!a||a.retryId!==e.retryId)return!1;let n=(()=>{if(b())try{let e=sessionStorage.getItem(h);if(e)return JSON.parse(e)}catch{return w}return w})();return!(n&&Date.now()-n.timestamp<r)&&Date.now()-a.timestamp>(t[a.attemptNumber-1]??1e3)+3e4})(t,a,y)){let r=v(),a=r?Date.now()-r.timestamp:0;R(),(()=>{if(b())try{sessionStorage.removeItem(p)}catch{}f=null})(),(e=>{let t={previousRetryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(h,JSON.stringify(t))}catch{w=t}else w=t})(t.retryId);let n=e+"";c({name:"retry-reset",previousAttempt:t.retryAttempt,previousRetryId:t.retryId,timeSinceReload:a},{silent:_([n])}),g=0,m=I()}if(-1===g)return _([e+""])||d()?.fallbackAlreadyShown(e),void U();if(g>=a.length){let r=e+"";return c({finalAttempt:g,name:"retry-exhausted",retryId:t?.retryId??""},{silent:_([r])}),N({errorMessage:"Exceeded maximum reload attempts",eventName:"chunk_error_max_reloads",retryAttempt:g,retryId:t?.retryId,serialized:JSON.stringify({error:e+"",retryAttempt:g,retryId:t?.retryId})}),n||T(),void U()}let A=g+1,E=a[g]??1e3;c({attempt:A,delay:E,name:"retry-attempt",retryId:m},{silent:_([e+""])}),D=!0,d()?.retrySchedulingReload(m,A,E),setTimeout(()=>{if(n&&i&&((e,t)=>{let r={attemptNumber:t,retryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(p,JSON.stringify(r))}catch{f=r}else f=r})(m,A),n){let e=((e,t)=>{let r=new URL(globalThis.window.location.href);return r.searchParams.set(o,e),r.searchParams.set(l,t+""),r.toString()})(m,A);globalThis.window.location.href=e}else globalThis.window.location.href=(e=>{let t=new URL(globalThis.window.location.href);return t.searchParams.set(l,e+""),t.toString()})(A)},E)},U=()=>{let e=u(),t=e.html?.fallback?.content,r=e.html?.fallback?.selector??"body";if(t)try{let a=document.querySelector(r);if(!a)return void d()?.fallbackTargetNotFound(r);a.innerHTML=t;let n=e.useRetryId??!0,o=S();if(o&&-1===o.retryAttempt?(d()?.clearingRetryState(),R()):!n&&!o&&T(),o){let e=document.getElementsByClassName("spa-guard-retry-id");for(let t of e)t.textContent=o.retryId}c({name:"fallback-ui-shown"})}catch(e){d()?.fallbackInjectFailed(e)}else d()?.noFallbackConfigured()};(()=>{if(s.initialized)return;s.initialized=!0,void 0!==globalThis.window&&(globalThis.window[a]=!0);let e=u().reloadDelays??[],t=S();t&&t.retryAttempt>=e.length&&(d()?.retryLimitExceeded(t.retryAttempt,e.length),(e=>{try{let t=new URL(globalThis.window.location.href);t.searchParams.set(o,e),t.searchParams.set(l,"-1"),globalThis.window.history.replaceState(null,"",t.toString())}catch{}})(t.retryId));let r=globalThis.window.addEventListener.bind(globalThis.window);r("error",e=>(_([e.message])||d()?.capturedError("error",e),y(e)||A([e.message])?(e.preventDefault(),void E(e.error??e)):void N({errorMessage:e.message,eventName:"error",serialized:"",...k()})),!0),r("unhandledrejection",e=>{let t=e.reason+"";return _([t])||d()?.capturedError("unhandledrejection",e),y(e.reason)||A([t])?(e.preventDefault(),void E(e.reason)):void N({errorMessage:t,eventName:"unhandledrejection",serialized:"",...k()})}),r("securitypolicyviolation",e=>{let t=`${e.violatedDirective}: ${e.blockedURI}`;_([t])||d()?.capturedError("csp",e.blockedURI,e.violatedDirective),N({eventMessage:t,eventName:"securitypolicyviolation",serialized:"",...k()})}),r("vite:preloadError",e=>{_([e?.payload?.message||e?.message])||d()?.capturedError("vite:preloadError",e),e.preventDefault(),E(e?.payload??e)})})();
1
+ var e="@ovineko/spa-guard",t=Symbol.for(e+":event-subscribers"),r=Symbol.for(e+":internal-config"),a=Symbol.for(e+":initialized"),n=Symbol.for(e+":logger"),l="spaGuardRetryId",o="spaGuardRetryAttempt";globalThis.window&&!globalThis.window[t]&&(globalThis.window[t]=new Set),globalThis.window&&!globalThis.window[r]&&(globalThis.window[r]={defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1});var i=globalThis.window?.[t]??new Set,s=globalThis.window?.[r]??{defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1},d=()=>globalThis.window?.[n],c=(e,t)=>{t?.silent||d()?.logEvent(e),i.forEach(t=>{try{t(e)}catch{}})},y=e=>{let t=p(e);return!!t&&[/Failed to fetch dynamically imported module/i,/Importing a module script failed/i,/error loading dynamically imported module/i,/Unable to preload CSS/i,/Loading chunk \d+ failed/i,/Loading CSS chunk \d+ failed/i,/ChunkLoadError/i].some(e=>e.test(t))},p=e=>e instanceof Error?e.message:"string"==typeof e?e:e&&"object"==typeof e&&"message"in e?e.message+"":e&&"object"==typeof e&&"reason"in e?p(e.reason):null,m={checkVersion:{interval:3e5,mode:"html",onUpdate:"reload"},enableRetryReset:!0,errors:{forceRetry:[],ignore:[]},handleUnhandledRejections:{retry:!0,sendBeacon:!0},html:{fallback:{content:'<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h1 data-spa-guard-content="heading">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:1rem auto">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center"><button data-spa-guard-action="try-again" type="button" style="display:none">Try again</button> <button data-spa-guard-action="reload" type="button" onclick="location.reload()">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:20px;font-size:12px">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>',selector:"body"},loading:{content:'<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h2>Loading...</h2><p data-spa-guard-section="retrying" style="display:none">Retry attempt <span data-spa-guard-content="attempt"></span></p></div></div>'}},lazyRetry:{callReloadOnFailure:!0,retryDelays:[1e3,2e3]},minTimeBetweenResets:5e3,reloadDelays:[1e3,2e3,5e3],useRetryId:!0},u=()=>{let e=globalThis.window?.__SPA_GUARD_OPTIONS__;return{...m,...e,checkVersion:{...m.checkVersion,...e?.checkVersion},errors:{...m.errors,...e?.errors},handleUnhandledRejections:{...m.handleUnhandledRejections,...e?.handleUnhandledRejections},html:{fallback:{...m.html?.fallback,...e?.html?.fallback},loading:{...m.html?.loading,...e?.html?.loading}},lazyRetry:{...m.lazyRetry,...e?.lazyRetry},reportBeacon:{...m.reportBeacon,...e?.reportBeacon}}},g="__spa_guard_last_reload_timestamp__",h="__spa_guard_last_retry_reset__",f=null,w=null,b=()=>{try{return void 0!==globalThis.window&&typeof sessionStorage<"u"}catch{return!1}},v=()=>{if(b())try{let e=sessionStorage.getItem(g);if(e)return JSON.parse(e)}catch{return f}return f},R=()=>{try{let e=new URLSearchParams(globalThis.window.location.search),t=e.get(l),r=e.get(o);if(t&&r){let e=parseInt(r,10);return Number.isNaN(e)?null:{retryAttempt:e,retryId:t}}return null}catch{return null}},S=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(l),e.searchParams.delete(o),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},I=()=>{if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint32Array(2);return crypto.getRandomValues(e),`${Date.now()}-${e[0].toString(36)}${e[1].toString(36)}`}return`${Date.now()}-${Math.random().toString(36).slice(2,15)}`},T=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(o),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},_=()=>{let e=R();return e?{retryAttempt:e.retryAttempt,retryId:e.retryId}:{}},k=e=>{let t=u().errors?.ignore??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},N=e=>{let t=[...u().errors?.forceRetry??[],"__SPA_GUARD_FORCE_RETRY__"];return e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},A=e=>{if((e=>k([e.errorMessage,e.eventMessage]))(e))return;let t=u();if(!t.reportBeacon?.endpoint)return void d()?.noBeaconEndpoint();let r=t.appName?{...e,appName:t.appName}:e,a=JSON.stringify(r);"function"==typeof globalThis.window?.navigator?.sendBeacon&&globalThis.window.navigator.sendBeacon(t.reportBeacon.endpoint,a)||fetch(t.reportBeacon.endpoint,{body:a,keepalive:!0,method:"POST"}).catch(e=>{d()?.beaconSendFailed(e)})},D=!1,U=e=>{if(D)return void d()?.reloadAlreadyScheduled(e);let t,r=u(),a=r.reloadDelays??[1e3,2e3,5e3],n=r.useRetryId??!0,i=r.enableRetryReset??!0,y=r.minTimeBetweenResets??5e3;if(n)t=R();else{let e=(()=>{try{let e=new URLSearchParams(globalThis.window.location.search).get(o);if(e){let t=parseInt(e,10);return Number.isNaN(t)?null:t}return null}catch{return null}})();t=null===e?null:{retryAttempt:e,retryId:I()}}let p=t?t.retryAttempt:0,m=t?.retryId??I();d()?.retryCycleStarting(m,p);let _=s.defaultRetryEnabled;if(c({error:e,isRetrying:_&&p>=0&&p<a.length,name:"chunk-error"}),!_)return;if(i&&t&&t.retryAttempt>0&&((e,t,r=5e3)=>{if(0===e.retryAttempt)return!1;let a=v();if(!a||a.retryId!==e.retryId)return!1;let n=(()=>{if(b())try{let e=sessionStorage.getItem(h);if(e)return JSON.parse(e)}catch{return w}return w})();return!(n&&Date.now()-n.timestamp<r)&&Date.now()-a.timestamp>(t[a.attemptNumber-1]??1e3)+3e4})(t,a,y)){let r=v(),a=r?Date.now()-r.timestamp:0;S(),(()=>{if(b())try{sessionStorage.removeItem(g)}catch{}f=null})(),(e=>{let t={previousRetryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(h,JSON.stringify(t))}catch{w=t}else w=t})(t.retryId);let n=e+"";c({name:"retry-reset",previousAttempt:t.retryAttempt,previousRetryId:t.retryId,timeSinceReload:a},{silent:k([n])}),p=0,m=I()}if(-1===p)return k([e+""])||d()?.fallbackAlreadyShown(e),void E();if(p>=a.length){let r=e+"";return c({finalAttempt:p,name:"retry-exhausted",retryId:t?.retryId??""},{silent:k([r])}),A({errorMessage:"Exceeded maximum reload attempts",eventName:"chunk_error_max_reloads",retryAttempt:p,retryId:t?.retryId,serialized:JSON.stringify({error:e+"",retryAttempt:p,retryId:t?.retryId})}),n||T(),void E()}let N=p+1,U=a[p]??1e3;c({attempt:N,delay:U,name:"retry-attempt",retryId:m},{silent:k([e+""])}),D=!0,d()?.retrySchedulingReload(m,N,U),setTimeout(()=>{if(n&&i&&((e,t)=>{let r={attemptNumber:t,retryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(g,JSON.stringify(r))}catch{f=r}else f=r})(m,N),n){let e=((e,t)=>{let r=new URL(globalThis.window.location.href);return r.searchParams.set(l,e),r.searchParams.set(o,t+""),r.toString()})(m,N);globalThis.window.location.href=e}else globalThis.window.location.href=(e=>{let t=new URL(globalThis.window.location.href);return t.searchParams.set(o,e+""),t.toString()})(N)},U)},E=()=>{let e=u(),t=e.html?.fallback?.content,r=e.html?.fallback?.selector??"body";if(t)try{let a=document.querySelector(r);if(!a)return void d()?.fallbackTargetNotFound(r);a.innerHTML=t;let n=e.useRetryId??!0,l=R();if(l&&-1===l.retryAttempt?(d()?.clearingRetryState(),S()):!n&&!l&&T(),l){let e=document.getElementsByClassName("spa-guard-retry-id");for(let t of e)t.textContent=l.retryId}c({name:"fallback-ui-shown"})}catch(e){d()?.fallbackInjectFailed(e)}else d()?.noFallbackConfigured()};(()=>{if(s.initialized)return;s.initialized=!0,void 0!==globalThis.window&&(globalThis.window[a]=!0);let e=u(),t=e.reloadDelays??[],r=R();r&&r.retryAttempt>=t.length&&(d()?.retryLimitExceeded(r.retryAttempt,t.length),(e=>{try{let t=new URL(globalThis.window.location.href);t.searchParams.set(l,e),t.searchParams.set(o,"-1"),globalThis.window.history.replaceState(null,"",t.toString())}catch{}})(r.retryId));let n=globalThis.window.addEventListener.bind(globalThis.window);n("error",e=>(k([e.message])||d()?.capturedError("error",e),y(e)||N([e.message])?(e.preventDefault(),void U(e.error??e)):void A({errorMessage:e.message,eventName:"error",serialized:"",..._()})),!0),n("unhandledrejection",t=>{let r=t.reason+"";if(k([r])||d()?.capturedError("unhandledrejection",t),y(t.reason))return t.preventDefault(),void U(t.reason);if(N([r]))return t.preventDefault(),void U(t.reason);let a=e.handleUnhandledRejections;!1!==a?.sendBeacon&&A({errorMessage:r,eventName:"unhandledrejection",serialized:"",..._()}),!1!==a?.retry&&(t.preventDefault(),U(t.reason))}),n("securitypolicyviolation",e=>{let t=`${e.violatedDirective}: ${e.blockedURI}`;k([t])||d()?.capturedError("csp",e.blockedURI,e.violatedDirective),A({eventMessage:t,eventName:"securitypolicyviolation",serialized:"",..._()})}),n("vite:preloadError",e=>{k([e?.payload?.message||e?.message])||d()?.capturedError("vite:preloadError",e),e.preventDefault(),U(e?.payload??e)})})();
@@ -1 +1 @@
1
- var e="@ovineko/spa-guard",t=Symbol.for(e+":event-subscribers"),r=Symbol.for(e+":internal-config"),a=Symbol.for(e+":initialized"),o=Symbol.for(e+":logger"),n="spaGuardRetryId",l="spaGuardRetryAttempt";globalThis.window&&!globalThis.window[t]&&(globalThis.window[t]=new Set),globalThis.window&&!globalThis.window[r]&&(globalThis.window[r]={defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1});var i=globalThis.window?.[t]??new Set,s=globalThis.window?.[r]??{defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1},d=()=>globalThis.window?.[o],c=(e,t)=>{t?.silent||d()?.logEvent(e),i.forEach(t=>{try{t(e)}catch{}})},y=e=>{let t=u(e);return!!t&&[/Failed to fetch dynamically imported module/i,/Importing a module script failed/i,/error loading dynamically imported module/i,/Unable to preload CSS/i,/Loading chunk \d+ failed/i,/Loading CSS chunk \d+ failed/i,/ChunkLoadError/i].some(e=>e.test(t))},u=e=>e instanceof Error?e.message:"string"==typeof e?e:e&&"object"==typeof e&&"message"in e?e.message+"":e&&"object"==typeof e&&"reason"in e?u(e.reason):null,m={checkVersion:{interval:3e5,mode:"html",onUpdate:"reload"},enableRetryReset:!0,errors:{forceRetry:[],ignore:[]},html:{fallback:{content:'<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h1 data-spa-guard-content="heading">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:1rem auto">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center"><button data-spa-guard-action="try-again" type="button" style="display:none">Try again</button> <button data-spa-guard-action="reload" type="button" onclick="location.reload()">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:20px;font-size:12px">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>',selector:"body"},loading:{content:'<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h2>Loading...</h2><p data-spa-guard-section="retrying" style="display:none">Retry attempt <span data-spa-guard-content="attempt"></span></p></div></div>'}},lazyRetry:{callReloadOnFailure:!0,retryDelays:[1e3,2e3]},minTimeBetweenResets:5e3,reloadDelays:[1e3,2e3,5e3],useRetryId:!0},p=()=>{let e=globalThis.window?.__SPA_GUARD_OPTIONS__;return{...m,...e,checkVersion:{...m.checkVersion,...e?.checkVersion},errors:{...m.errors,...e?.errors},html:{fallback:{...m.html?.fallback,...e?.html?.fallback},loading:{...m.html?.loading,...e?.html?.loading}},lazyRetry:{...m.lazyRetry,...e?.lazyRetry},reportBeacon:{...m.reportBeacon,...e?.reportBeacon}}},g="__spa_guard_last_reload_timestamp__",h="__spa_guard_last_retry_reset__",f=null,w=null,b=()=>{try{return void 0!==globalThis.window&&typeof sessionStorage<"u"}catch{return!1}},v=()=>{if(b())try{let e=sessionStorage.getItem(g);if(e)return JSON.parse(e)}catch{return f}return f},k=()=>{try{let e=new URLSearchParams(globalThis.window.location.search),t=e.get(n),r=e.get(l);if(t&&r){let e=parseInt(r,10);return Number.isNaN(e)?null:{retryAttempt:e,retryId:t}}return null}catch{return null}},S=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(n),e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},R=()=>{if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint32Array(2);return crypto.getRandomValues(e),`${Date.now()}-${e[0].toString(36)}${e[1].toString(36)}`}return`${Date.now()}-${Math.random().toString(36).slice(2,15)}`},$=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},I=()=>{let e=k();return e?{retryAttempt:e.retryAttempt,retryId:e.retryId}:{}},T=e=>{let t=p().errors?.ignore??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},N=e=>{let t=p().errors?.forceRetry??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},E=e=>{if((e=>T([e.errorMessage,e.eventMessage]))(e))return;let t=p();if(!t.reportBeacon?.endpoint)return void d()?.noBeaconEndpoint();let r=JSON.stringify(e);"function"==typeof globalThis.window?.navigator.sendBeacon&&globalThis.window.navigator.sendBeacon(t.reportBeacon.endpoint,r)||fetch(t.reportBeacon.endpoint,{body:r,keepalive:!0,method:"POST"}).catch(e=>{d()?.beaconSendFailed(e)})},A=!1,D=e=>{if(A)return void d()?.reloadAlreadyScheduled(e);let t,r=p(),a=r.reloadDelays??[1e3,2e3,5e3],o=r.useRetryId??!0,i=r.enableRetryReset??!0,y=r.minTimeBetweenResets??5e3;if(o)t=k();else{let e=(()=>{try{let e=new URLSearchParams(globalThis.window.location.search).get(l);if(e){let t=parseInt(e,10);return Number.isNaN(t)?null:t}return null}catch{return null}})();t=null===e?null:{retryAttempt:e,retryId:R()}}let u=t?t.retryAttempt:0,m=t?.retryId??R();d()?.retryCycleStarting(m,u);let I=s.defaultRetryEnabled;if(c({error:e,isRetrying:I&&u>=0&&u<a.length,name:"chunk-error"}),!I)return;if(i&&t&&t.retryAttempt>0&&((e,t,r=5e3)=>{if(0===e.retryAttempt)return!1;let a=v();if(!a||a.retryId!==e.retryId)return!1;let o=(()=>{if(b())try{let e=sessionStorage.getItem(h);if(e)return JSON.parse(e)}catch{return w}return w})();return!(o&&Date.now()-o.timestamp<r)&&Date.now()-a.timestamp>(t[a.attemptNumber-1]??1e3)+3e4})(t,a,y)){let r=v(),a=r?Date.now()-r.timestamp:0;S(),(()=>{if(b())try{sessionStorage.removeItem(g)}catch{}f=null})(),(e=>{let t={previousRetryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(h,JSON.stringify(t))}catch{w=t}else w=t})(t.retryId);let o=e+"";c({name:"retry-reset",previousAttempt:t.retryAttempt,previousRetryId:t.retryId,timeSinceReload:a},{silent:T([o])}),u=0,m=R()}if(-1===u)return T([e+""])||d()?.fallbackAlreadyShown(e),void U();if(u>=a.length){let r=e+"";return c({finalAttempt:u,name:"retry-exhausted",retryId:t?.retryId??""},{silent:T([r])}),E({errorMessage:"Exceeded maximum reload attempts",eventName:"chunk_error_max_reloads",retryAttempt:u,retryId:t?.retryId,serialized:JSON.stringify({error:e+"",retryAttempt:u,retryId:t?.retryId})}),o||$(),void U()}let N=u+1,D=a[u]??1e3;c({attempt:N,delay:D,name:"retry-attempt",retryId:m},{silent:T([e+""])}),A=!0,d()?.retrySchedulingReload(m,N,D),setTimeout(()=>{if(o&&i&&((e,t)=>{let r={attemptNumber:t,retryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(g,JSON.stringify(r))}catch{f=r}else f=r})(m,N),o){let e=((e,t)=>{let r=new URL(globalThis.window.location.href);return r.searchParams.set(n,e),r.searchParams.set(l,t+""),r.toString()})(m,N);globalThis.window.location.href=e}else globalThis.window.location.href=(e=>{let t=new URL(globalThis.window.location.href);return t.searchParams.set(l,e+""),t.toString()})(N)},D)},U=()=>{let e=p(),t=e.html?.fallback?.content,r=e.html?.fallback?.selector??"body";if(t)try{let a=document.querySelector(r);if(!a)return void d()?.fallbackTargetNotFound(r);a.innerHTML=t;let o=e.useRetryId??!0,n=k();if(n&&-1===n.retryAttempt?(d()?.clearingRetryState(),S()):!o&&!n&&$(),n){let e=document.getElementsByClassName("spa-guard-retry-id");for(let t of e)t.textContent=n.retryId}c({name:"fallback-ui-shown"})}catch(e){d()?.fallbackInjectFailed(e)}else d()?.noFallbackConfigured()},_="[spa-guard]",z={"chunk-error":"error","fallback-ui-shown":"warn","lazy-retry-attempt":"warn","lazy-retry-exhausted":"error","lazy-retry-success":"log","retry-attempt":"warn","retry-exhausted":"error","retry-reset":"log"},x=e=>{if(null==e)return{type:"null",value:e};if("object"!=typeof e)return{type:typeof e,value:e};if(e instanceof Error)return{message:e.message,name:e.name,stack:e.stack,type:"Error",...P(e)};if("reason"in e&&"promise"in e)return{reason:x(e.reason),type:"PromiseRejectionEvent"};if("error"in e&&"message"in e&&"filename"in e)return{colno:e.colno,error:x(e.error),filename:e.filename,lineno:e.lineno,message:e.message,type:"ErrorEvent"};if("violatedDirective"in e&&"blockedURI"in e){let t=e;return{blockedURI:t.blockedURI,columnNumber:t.columnNumber,effectiveDirective:t.effectiveDirective,lineNumber:t.lineNumber,originalPolicy:t.originalPolicy,sourceFile:t.sourceFile,type:"SecurityPolicyViolationEvent",violatedDirective:t.violatedDirective}}if("type"in e&&"target"in e){let t=e;return{eventType:t.type,target:C(t.target),timeStamp:t.timeStamp,type:"Event"}}return{type:"object",value:L(e)}},P=e=>{let t={};for(let r of Object.getOwnPropertyNames(e))if(!["message","name","stack"].includes(r))try{t[r]=e[r]}catch{}return t},C=e=>e?e instanceof HTMLElement?{className:e.className,href:e.href,id:e.id,src:e.src,tagName:e.tagName}:{type:e+""}:null,L=e=>{let t={};for(let r of Object.keys(e))try{let a=e[r];t[r]="object"==typeof a?a+"":a}catch{}return t};((e,t)=>{if(t&&(e=>{void 0!==globalThis.window&&(globalThis.window[o]=e)})(t),s.initialized)return;s.initialized=!0,void 0!==globalThis.window&&(globalThis.window[a]=!0);let r=p().reloadDelays??[],i=k();i&&i.retryAttempt>=r.length&&(d()?.retryLimitExceeded(i.retryAttempt,r.length),(e=>{try{let t=new URL(globalThis.window.location.href);t.searchParams.set(n,e),t.searchParams.set(l,"-1"),globalThis.window.history.replaceState(null,"",t.toString())}catch{}})(i.retryId));let c=globalThis.window.addEventListener.bind(globalThis.window);c("error",t=>{if(T([t.message])||d()?.capturedError("error",t),y(t))return t.preventDefault(),void D(t.error??t);if(N([t.message]))return t.preventDefault(),void D(t.error??t);let r=e(t);E({errorMessage:t.message,eventName:"error",serialized:r,...I()})},!0),c("unhandledrejection",t=>{let r=t.reason+"";if(T([r])||d()?.capturedError("unhandledrejection",t),y(t.reason))return t.preventDefault(),void D(t.reason);if(N([r]))return t.preventDefault(),void D(t.reason);let a=e(t);E({errorMessage:r,eventName:"unhandledrejection",serialized:a,...I()})}),c("securitypolicyviolation",t=>{let r=`${t.violatedDirective}: ${t.blockedURI}`;T([r])||d()?.capturedError("csp",t.blockedURI,t.violatedDirective);let a=e(t);E({eventMessage:r,eventName:"securitypolicyviolation",serialized:a,...I()})}),c("vite:preloadError",e=>{T([e?.payload?.message||e?.message])||d()?.capturedError("vite:preloadError",e),e.preventDefault(),D(e?.payload??e)})})(e=>{try{let t=x(e);return JSON.stringify(t,null,2)}catch{return JSON.stringify({error:"Failed to serialize error",fallback:e+""})}},{beaconSendFailed(e){console.error(_+" Failed to send beacon:",e)},capturedError(e,...t){console.error(`${_} ${e}:capture:`,...t)},clearingRetryState(){console.log(_+" Clearing retry state from URL to allow clean reload attempt")},error(e,...t){console.error(`${_} ${e}`,...t)},fallbackAlreadyShown(e){console.error(_+" Fallback UI was already shown. Not retrying to prevent infinite loop.",e)},fallbackInjectFailed(e){console.error(_+" Failed to inject fallback UI",e)},fallbackTargetNotFound(e){console.error(`${_} Target element not found for selector: ${e}`)},log(e,...t){console.log(`${_} ${e}`,...t)},logEvent(e){let t=z[e.name],r=(e=>{switch(e.name){case"chunk-error":return`${_} chunk-error: isRetrying=${e.isRetrying}`;case"fallback-ui-shown":return _+" fallback-ui-shown";case"lazy-retry-attempt":return`${_} lazy-retry-attempt: attempt ${e.attempt}/${e.totalAttempts}, delay ${e.delay}ms`;case"lazy-retry-exhausted":return`${_} lazy-retry-exhausted: ${e.totalAttempts} attempts, willReload=${e.willReload}`;case"lazy-retry-success":return`${_} lazy-retry-success: succeeded on attempt ${e.attempt}`;case"retry-attempt":return`${_} retry-attempt: attempt ${e.attempt} in ${e.delay}ms (retryId: ${e.retryId})`;case"retry-exhausted":return`${_} retry-exhausted: finalAttempt=${e.finalAttempt} (retryId: ${e.retryId})`;case"retry-reset":return`${_} retry-reset: ${e.timeSinceReload}ms since last reload (retryId: ${e.previousRetryId})`}})(e);"chunk-error"===e.name?console[t](r,e.error):console[t](r)},noBeaconEndpoint(){console.warn(_+" Report endpoint is not configured")},noFallbackConfigured(){console.error(_+" No fallback UI configured")},reloadAlreadyScheduled(e){console.log(_+" Reload already scheduled, ignoring duplicate chunk error:",e)},retryCycleStarting(e,t){console.log(`${_} Retry cycle starting: retryId=${e}, fromAttempt=${t}`)},retryLimitExceeded(e,t){console.log(`${_} Retry limit exceeded (${e}/${t}), marking as fallback shown`)},retrySchedulingReload(e,t,r){console.log(`${_} Scheduling reload: retryId=${e}, attempt=${t}, delay=${r}ms`)},updatedRetryAttempt(e){console.log(`${_} Updated retry attempt to ${e} in URL for fallback UI`)},versionChangeDetected(e,t){console.warn(`${_} New version available (${e??"unknown"} → ${t}). Please refresh to get the latest version.`)},versionCheckAlreadyRunning(){console.warn(_+" Version check already running")},versionCheckDisabled(){console.warn(_+" Version checking disabled: no version configured")},versionCheckFailed(e){console.error(_+" Version check failed",e)},versionCheckHttpError(e){console.warn(`${_} Version check HTTP error: ${e}`)},versionCheckParseError(){console.warn(_+" Failed to parse version from HTML")},versionCheckPaused(){console.log(_+" Version check paused (tab hidden)")},versionCheckRequiresEndpoint(){console.warn(_+" JSON version check mode requires endpoint")},versionCheckResumed(){console.log(_+" Version check resumed (tab visible)")},versionCheckResumedImmediate(){console.log(_+" Version check resumed with immediate check (tab visible, interval elapsed)")},versionCheckStarted(e,t,r){console.log(`${_} Starting version check (mode: ${e}, interval: ${t}ms, current: ${r})`)},versionCheckStopped(){console.log(_+" Version check stopped")},warn(e,...t){console.warn(`${_} ${e}`,...t)}});
1
+ var e="@ovineko/spa-guard",t=Symbol.for(e+":event-subscribers"),r=Symbol.for(e+":internal-config"),a=Symbol.for(e+":initialized"),n=Symbol.for(e+":logger"),o="spaGuardRetryId",l="spaGuardRetryAttempt";globalThis.window&&!globalThis.window[t]&&(globalThis.window[t]=new Set),globalThis.window&&!globalThis.window[r]&&(globalThis.window[r]={defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1});var i=globalThis.window?.[t]??new Set,s=globalThis.window?.[r]??{defaultRetryEnabled:!0,initialized:!1,inlineScriptLoaded:!1},d=()=>globalThis.window?.[n],c=(e,t)=>{t?.silent||d()?.logEvent(e),i.forEach(t=>{try{t(e)}catch{}})},y=e=>{let t=u(e);return!!t&&[/Failed to fetch dynamically imported module/i,/Importing a module script failed/i,/error loading dynamically imported module/i,/Unable to preload CSS/i,/Loading chunk \d+ failed/i,/Loading CSS chunk \d+ failed/i,/ChunkLoadError/i].some(e=>e.test(t))},u=e=>e instanceof Error?e.message:"string"==typeof e?e:e&&"object"==typeof e&&"message"in e?e.message+"":e&&"object"==typeof e&&"reason"in e?u(e.reason):null,m={checkVersion:{interval:3e5,mode:"html",onUpdate:"reload"},enableRetryReset:!0,errors:{forceRetry:[],ignore:[]},handleUnhandledRejections:{retry:!0,sendBeacon:!0},html:{fallback:{content:'<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h1 data-spa-guard-content="heading">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:1rem auto">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center"><button data-spa-guard-action="try-again" type="button" style="display:none">Try again</button> <button data-spa-guard-action="reload" type="button" onclick="location.reload()">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:20px;font-size:12px">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>',selector:"body"},loading:{content:'<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem"><div style="text-align:center"><h2>Loading...</h2><p data-spa-guard-section="retrying" style="display:none">Retry attempt <span data-spa-guard-content="attempt"></span></p></div></div>'}},lazyRetry:{callReloadOnFailure:!0,retryDelays:[1e3,2e3]},minTimeBetweenResets:5e3,reloadDelays:[1e3,2e3,5e3],useRetryId:!0},p=()=>{let e=globalThis.window?.__SPA_GUARD_OPTIONS__;return{...m,...e,checkVersion:{...m.checkVersion,...e?.checkVersion},errors:{...m.errors,...e?.errors},handleUnhandledRejections:{...m.handleUnhandledRejections,...e?.handleUnhandledRejections},html:{fallback:{...m.html?.fallback,...e?.html?.fallback},loading:{...m.html?.loading,...e?.html?.loading}},lazyRetry:{...m.lazyRetry,...e?.lazyRetry},reportBeacon:{...m.reportBeacon,...e?.reportBeacon}}},g="__spa_guard_last_reload_timestamp__",h="__spa_guard_last_retry_reset__",f=null,w=null,b=()=>{try{return void 0!==globalThis.window&&typeof sessionStorage<"u"}catch{return!1}},v=()=>{if(b())try{let e=sessionStorage.getItem(g);if(e)return JSON.parse(e)}catch{return f}return f},k=()=>{try{let e=new URLSearchParams(globalThis.window.location.search),t=e.get(o),r=e.get(l);if(t&&r){let e=parseInt(r,10);return Number.isNaN(e)?null:{retryAttempt:e,retryId:t}}return null}catch{return null}},R=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(o),e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},S=()=>{if(typeof crypto<"u"&&crypto.randomUUID)return crypto.randomUUID();if(typeof crypto<"u"&&crypto.getRandomValues){let e=new Uint32Array(2);return crypto.getRandomValues(e),`${Date.now()}-${e[0].toString(36)}${e[1].toString(36)}`}return`${Date.now()}-${Math.random().toString(36).slice(2,15)}`},$=()=>{try{let e=new URL(globalThis.window.location.href);e.searchParams.delete(l),globalThis.window.history.replaceState(null,"",e.toString())}catch{}},I=()=>{let e=k();return e?{retryAttempt:e.retryAttempt,retryId:e.retryId}:{}},T=e=>{let t=p().errors?.ignore??[];return 0!==t.length&&e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},N=e=>{let t=[...p().errors?.forceRetry??[],"__SPA_GUARD_FORCE_RETRY__"];return e.filter(e=>"string"==typeof e).some(e=>t.some(t=>e.includes(t)))},E=e=>{if((e=>T([e.errorMessage,e.eventMessage]))(e))return;let t=p();if(!t.reportBeacon?.endpoint)return void d()?.noBeaconEndpoint();let r=t.appName?{...e,appName:t.appName}:e,a=JSON.stringify(r);"function"==typeof globalThis.window?.navigator?.sendBeacon&&globalThis.window.navigator.sendBeacon(t.reportBeacon.endpoint,a)||fetch(t.reportBeacon.endpoint,{body:a,keepalive:!0,method:"POST"}).catch(e=>{d()?.beaconSendFailed(e)})},U=!1,A=e=>{if(U)return void d()?.reloadAlreadyScheduled(e);let t,r=p(),a=r.reloadDelays??[1e3,2e3,5e3],n=r.useRetryId??!0,i=r.enableRetryReset??!0,y=r.minTimeBetweenResets??5e3;if(n)t=k();else{let e=(()=>{try{let e=new URLSearchParams(globalThis.window.location.search).get(l);if(e){let t=parseInt(e,10);return Number.isNaN(t)?null:t}return null}catch{return null}})();t=null===e?null:{retryAttempt:e,retryId:S()}}let u=t?t.retryAttempt:0,m=t?.retryId??S();d()?.retryCycleStarting(m,u);let I=s.defaultRetryEnabled;if(c({error:e,isRetrying:I&&u>=0&&u<a.length,name:"chunk-error"}),!I)return;if(i&&t&&t.retryAttempt>0&&((e,t,r=5e3)=>{if(0===e.retryAttempt)return!1;let a=v();if(!a||a.retryId!==e.retryId)return!1;let n=(()=>{if(b())try{let e=sessionStorage.getItem(h);if(e)return JSON.parse(e)}catch{return w}return w})();return!(n&&Date.now()-n.timestamp<r)&&Date.now()-a.timestamp>(t[a.attemptNumber-1]??1e3)+3e4})(t,a,y)){let r=v(),a=r?Date.now()-r.timestamp:0;R(),(()=>{if(b())try{sessionStorage.removeItem(g)}catch{}f=null})(),(e=>{let t={previousRetryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(h,JSON.stringify(t))}catch{w=t}else w=t})(t.retryId);let n=e+"";c({name:"retry-reset",previousAttempt:t.retryAttempt,previousRetryId:t.retryId,timeSinceReload:a},{silent:T([n])}),u=0,m=S()}if(-1===u)return T([e+""])||d()?.fallbackAlreadyShown(e),void _();if(u>=a.length){let r=e+"";return c({finalAttempt:u,name:"retry-exhausted",retryId:t?.retryId??""},{silent:T([r])}),E({errorMessage:"Exceeded maximum reload attempts",eventName:"chunk_error_max_reloads",retryAttempt:u,retryId:t?.retryId,serialized:JSON.stringify({error:e+"",retryAttempt:u,retryId:t?.retryId})}),n||$(),void _()}let N=u+1,A=a[u]??1e3;c({attempt:N,delay:A,name:"retry-attempt",retryId:m},{silent:T([e+""])}),U=!0,d()?.retrySchedulingReload(m,N,A),setTimeout(()=>{if(n&&i&&((e,t)=>{let r={attemptNumber:t,retryId:e,timestamp:Date.now()};if(b())try{sessionStorage.setItem(g,JSON.stringify(r))}catch{f=r}else f=r})(m,N),n){let e=((e,t)=>{let r=new URL(globalThis.window.location.href);return r.searchParams.set(o,e),r.searchParams.set(l,t+""),r.toString()})(m,N);globalThis.window.location.href=e}else globalThis.window.location.href=(e=>{let t=new URL(globalThis.window.location.href);return t.searchParams.set(l,e+""),t.toString()})(N)},A)},_=()=>{let e=p(),t=e.html?.fallback?.content,r=e.html?.fallback?.selector??"body";if(t)try{let a=document.querySelector(r);if(!a)return void d()?.fallbackTargetNotFound(r);a.innerHTML=t;let n=e.useRetryId??!0,o=k();if(o&&-1===o.retryAttempt?(d()?.clearingRetryState(),R()):!n&&!o&&$(),o){let e=document.getElementsByClassName("spa-guard-retry-id");for(let t of e)t.textContent=o.retryId}c({name:"fallback-ui-shown"})}catch(e){d()?.fallbackInjectFailed(e)}else d()?.noFallbackConfigured()},D="[spa-guard]",z={"chunk-error":"error","fallback-ui-shown":"warn","lazy-retry-attempt":"warn","lazy-retry-exhausted":"error","lazy-retry-success":"log","retry-attempt":"warn","retry-exhausted":"error","retry-reset":"log"},P=e=>{if(null==e)return{type:"null",value:e};if("object"!=typeof e)return{type:typeof e,value:e};if(e instanceof Error)return{message:e.message,name:e.name,stack:e.stack,type:"Error",...x(e)};if("reason"in e&&"promise"in e)return{reason:P(e.reason),type:"PromiseRejectionEvent"};if("error"in e&&"message"in e&&"filename"in e)return{colno:e.colno,error:P(e.error),filename:e.filename,lineno:e.lineno,message:e.message,type:"ErrorEvent"};if("violatedDirective"in e&&"blockedURI"in e){let t=e;return{blockedURI:t.blockedURI,columnNumber:t.columnNumber,effectiveDirective:t.effectiveDirective,lineNumber:t.lineNumber,originalPolicy:t.originalPolicy,sourceFile:t.sourceFile,type:"SecurityPolicyViolationEvent",violatedDirective:t.violatedDirective}}if("type"in e&&"target"in e){let t=e;return{eventType:t.type,target:C(t.target),timeStamp:t.timeStamp,type:"Event"}}return{type:"object",value:j(e)}},x=e=>{let t={};for(let r of Object.getOwnPropertyNames(e))if(!["message","name","stack"].includes(r))try{t[r]=e[r]}catch{}return t},C=e=>e?e instanceof HTMLElement?{className:e.className,href:e.href,id:e.id,src:e.src,tagName:e.tagName}:{type:e+""}:null,j=e=>{let t={};for(let r of Object.keys(e))try{let a=e[r];t[r]="object"==typeof a?a+"":a}catch{}return t};((e,t)=>{if(t&&(e=>{void 0!==globalThis.window&&(globalThis.window[n]=e)})(t),s.initialized)return;s.initialized=!0,void 0!==globalThis.window&&(globalThis.window[a]=!0);let r=p(),i=r.reloadDelays??[],c=k();c&&c.retryAttempt>=i.length&&(d()?.retryLimitExceeded(c.retryAttempt,i.length),(e=>{try{let t=new URL(globalThis.window.location.href);t.searchParams.set(o,e),t.searchParams.set(l,"-1"),globalThis.window.history.replaceState(null,"",t.toString())}catch{}})(c.retryId));let u=globalThis.window.addEventListener.bind(globalThis.window);u("error",t=>{if(T([t.message])||d()?.capturedError("error",t),y(t))return t.preventDefault(),void A(t.error??t);if(N([t.message]))return t.preventDefault(),void A(t.error??t);let r=e(t);E({errorMessage:t.message,eventName:"error",serialized:r,...I()})},!0),u("unhandledrejection",t=>{let a=t.reason+"";if(T([a])||d()?.capturedError("unhandledrejection",t),y(t.reason))return t.preventDefault(),void A(t.reason);if(N([a]))return t.preventDefault(),void A(t.reason);let n=r.handleUnhandledRejections;if(!1!==n?.sendBeacon){let r=e(t);E({errorMessage:a,eventName:"unhandledrejection",serialized:r,...I()})}!1!==n?.retry&&(t.preventDefault(),A(t.reason))}),u("securitypolicyviolation",t=>{let r=`${t.violatedDirective}: ${t.blockedURI}`;T([r])||d()?.capturedError("csp",t.blockedURI,t.violatedDirective);let a=e(t);E({eventMessage:r,eventName:"securitypolicyviolation",serialized:a,...I()})}),u("vite:preloadError",e=>{T([e?.payload?.message||e?.message])||d()?.capturedError("vite:preloadError",e),e.preventDefault(),A(e?.payload??e)})})(e=>{try{let t=P(e);return JSON.stringify(t,null,2)}catch{return JSON.stringify({error:"Failed to serialize error",fallback:e+""})}},{beaconSendFailed(e){console.error(D+" Failed to send beacon:",e)},capturedError(e,...t){console.error(`${D} ${e}:capture:`,...t)},clearingRetryState(){console.log(D+" Clearing retry state from URL to allow clean reload attempt")},error(e,...t){console.error(`${D} ${e}`,...t)},fallbackAlreadyShown(e){console.error(D+" Fallback UI was already shown. Not retrying to prevent infinite loop.",e)},fallbackInjectFailed(e){console.error(D+" Failed to inject fallback UI",e)},fallbackTargetNotFound(e){console.error(`${D} Target element not found for selector: ${e}`)},log(e,...t){console.log(`${D} ${e}`,...t)},logEvent(e){let t=z[e.name],r=(e=>{switch(e.name){case"chunk-error":return`${D} chunk-error: isRetrying=${e.isRetrying}`;case"fallback-ui-shown":return D+" fallback-ui-shown";case"lazy-retry-attempt":return`${D} lazy-retry-attempt: attempt ${e.attempt}/${e.totalAttempts}, delay ${e.delay}ms`;case"lazy-retry-exhausted":return`${D} lazy-retry-exhausted: ${e.totalAttempts} attempts, willReload=${e.willReload}`;case"lazy-retry-success":return`${D} lazy-retry-success: succeeded on attempt ${e.attempt}`;case"retry-attempt":return`${D} retry-attempt: attempt ${e.attempt} in ${e.delay}ms (retryId: ${e.retryId})`;case"retry-exhausted":return`${D} retry-exhausted: finalAttempt=${e.finalAttempt} (retryId: ${e.retryId})`;case"retry-reset":return`${D} retry-reset: ${e.timeSinceReload}ms since last reload (retryId: ${e.previousRetryId})`}})(e);"chunk-error"===e.name?console[t](r,e.error):console[t](r)},noBeaconEndpoint(){console.warn(D+" Report endpoint is not configured")},noFallbackConfigured(){console.error(D+" No fallback UI configured")},reloadAlreadyScheduled(e){console.log(D+" Reload already scheduled, ignoring duplicate chunk error:",e)},retryCycleStarting(e,t){console.log(`${D} Retry cycle starting: retryId=${e}, fromAttempt=${t}`)},retryLimitExceeded(e,t){console.log(`${D} Retry limit exceeded (${e}/${t}), marking as fallback shown`)},retrySchedulingReload(e,t,r){console.log(`${D} Scheduling reload: retryId=${e}, attempt=${t}, delay=${r}ms`)},updatedRetryAttempt(e){console.log(`${D} Updated retry attempt to ${e} in URL for fallback UI`)},versionChangeDetected(e,t){console.warn(`${D} New version available (${e??"unknown"} → ${t}). Please refresh to get the latest version.`)},versionCheckAlreadyRunning(){console.warn(D+" Version check already running")},versionCheckDisabled(){console.warn(D+" Version checking disabled: no version configured")},versionCheckFailed(e){console.error(D+" Version check failed",e)},versionCheckHttpError(e){console.warn(`${D} Version check HTTP error: ${e}`)},versionCheckParseError(){console.warn(D+" Failed to parse version from HTML")},versionCheckPaused(){console.log(D+" Version check paused (tab hidden)")},versionCheckRequiresEndpoint(){console.warn(D+" JSON version check mode requires endpoint")},versionCheckResumed(){console.log(D+" Version check resumed (tab visible)")},versionCheckResumedImmediate(){console.log(D+" Version check resumed with immediate check (tab visible, interval elapsed)")},versionCheckStarted(e,t,r){console.log(`${D} Starting version check (mode: ${e}, interval: ${t}ms, current: ${r})`)},versionCheckStopped(){console.log(D+" Version check stopped")},warn(e,...t){console.warn(`${D} ${e}`,...t)}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ovineko/spa-guard",
3
- "version": "0.0.1-alpha-4",
3
+ "version": "0.0.1-alpha-5",
4
4
  "description": "Chunk load error handling for SPAs with automatic recovery, beacon reporting, and deployment monitoring",
5
5
  "keywords": [
6
6
  "spa",