@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 +219 -15
- package/dist/chunk-3SCN2UE4.js +38 -0
- package/dist/{chunk-DPOQIK4J.js → chunk-G6QM353W.js} +8 -0
- package/dist/{chunk-UMNFZ7IW.js → chunk-IC7SCT5F.js} +2 -2
- package/dist/{chunk-7HWVSDJZ.js → chunk-KZEBQNOZ.js} +1 -0
- package/dist/chunk-L5UEVGWR.js +13 -0
- package/dist/{chunk-FAFKLSME.js → chunk-LGPIRNKE.js} +14 -7
- package/dist/{chunk-6XKIZF5S.js → chunk-RFGT43OM.js} +8 -7
- package/dist/{chunk-HVBPX75C.js → chunk-RXW44IWI.js} +2 -2
- package/dist/{chunk-AB733L4R.js → chunk-WE7SWL5H.js} +1 -1
- package/dist/common/errors/BeaconError.d.ts +12 -0
- package/dist/common/errors/ForceRetryError.d.ts +5 -0
- package/dist/common/index.d.ts +2 -0
- package/dist/common/index.js +24 -9
- package/dist/common/options.d.ts +22 -0
- package/dist/fastify/index.d.ts +1 -0
- package/dist/fastify/index.js +15 -20
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +8 -4
- package/dist/react-error-boundary/index.js +6 -5
- package/dist/react-router/index.js +6 -5
- package/dist/runtime/debug/index.js +8 -2
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +6 -2
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -1
- package/dist/schema/parse.js +2 -2
- package/dist/vite-plugin/index.js +1 -1
- package/dist-inline/index.js +1 -1
- package/dist-inline-trace/index.js +1 -1
- package/package.json +1 -1
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)
|
|
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
|
|
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
|
|
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 {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
|
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-
|
|
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-
|
|
16
|
+
} from "./chunk-G6QM353W.js";
|
|
17
17
|
import {
|
|
18
18
|
debugSyncErrorEventType
|
|
19
19
|
} from "./chunk-EDRTFPCN.js";
|
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
95
|
-
const
|
|
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-
|
|
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-
|
|
16
|
+
} from "./chunk-G6QM353W.js";
|
|
17
17
|
|
|
18
18
|
// src/common/DefaultErrorFallback.tsx
|
|
19
19
|
import { useLayoutEffect, useMemo, useRef } from "react";
|
|
@@ -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
|
+
}
|
package/dist/common/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/common/index.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
|
250
|
-
sendBeacon
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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,
|
package/dist/common/options.d.ts
CHANGED
|
@@ -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 */
|
package/dist/fastify/index.d.ts
CHANGED
|
@@ -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
|
package/dist/fastify/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseBeacon
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
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
|
};
|
package/dist/react/index.d.ts
CHANGED
package/dist/react/index.js
CHANGED
|
@@ -4,17 +4,21 @@ import {
|
|
|
4
4
|
useSPAGuardChunkError,
|
|
5
5
|
useSPAGuardEvents,
|
|
6
6
|
useSpaGuardState
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
9
|
-
import "../chunk-
|
|
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-
|
|
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-
|
|
4
|
+
} from "../chunk-RXW44IWI.js";
|
|
5
5
|
import "../chunk-HUAI4DRW.js";
|
|
6
6
|
import {
|
|
7
7
|
useSpaGuardState
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-IC7SCT5F.js";
|
|
9
9
|
import {
|
|
10
10
|
isChunkError
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
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-
|
|
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-
|
|
4
|
+
} from "../chunk-RXW44IWI.js";
|
|
5
5
|
import "../chunk-HUAI4DRW.js";
|
|
6
6
|
import {
|
|
7
7
|
useSpaGuardState
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-IC7SCT5F.js";
|
|
9
9
|
import {
|
|
10
10
|
isChunkError
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
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-
|
|
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(
|
|
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(
|
|
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)})`;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/runtime/index.js
CHANGED
|
@@ -2,17 +2,21 @@ import {
|
|
|
2
2
|
recommendedSetup,
|
|
3
3
|
startVersionCheck,
|
|
4
4
|
stopVersionCheck
|
|
5
|
-
} from "../chunk-
|
|
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-
|
|
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,
|
package/dist/schema/index.d.ts
CHANGED
package/dist/schema/index.js
CHANGED
package/dist/schema/parse.js
CHANGED
package/dist-inline/index.js
CHANGED
|
@@ -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"),
|
|
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