@ovineko/spa-guard 0.0.1-alpha-28 → 0.0.1-alpha-30
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 +72 -1
- package/dist/_internal.d.ts +2 -1
- package/dist/_internal.js +24 -20
- package/dist/{chunk-BA5VUNSU.js → chunk-6TP2S7L6.js} +1 -1
- package/dist/{chunk-62AC5565.js → chunk-A5HF6LY6.js} +9 -8
- package/dist/{chunk-5ANASVKX.js → chunk-DINLBER6.js} +29 -39
- package/dist/{chunk-B7C3LV2W.js → chunk-EFWD2I2Y.js} +7 -11
- package/dist/{chunk-PE5QPP5Y.js → chunk-XRBUBTPZ.js} +401 -73
- package/dist/common/constants.d.ts +1 -1
- package/dist/common/events/types.d.ts +8 -1
- package/dist/common/fallbackRendering.d.ts +13 -0
- package/dist/common/fallbackState.d.ts +3 -0
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +7 -4
- package/dist/common/options.d.ts +1 -1
- package/dist/common/retryImport.d.ts +1 -1
- package/dist/common/retryOrchestrator.d.ts +33 -0
- package/dist/common/retryState.d.ts +0 -3
- package/dist/runtime/debug/index.js +3 -4
- package/dist/runtime/index.js +5 -3
- package/dist/schema/parse.js +8 -2
- package/package.json +1 -1
- package/dist/chunk-4EXCHJBE.js +0 -308
- package/dist/common/reload.d.ts +0 -6
package/README.md
CHANGED
|
@@ -28,6 +28,52 @@ Disable version checking:
|
|
|
28
28
|
const cleanup = recommendedSetup({ versionCheck: false });
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Retry behavior and event flow
|
|
32
|
+
|
|
33
|
+
spa-guard uses a single retry orchestrator (`retryOrchestrator.ts`) as the sole owner of retry lifecycle. All reload scheduling, deduplication, and fallback transitions run through `triggerRetry()`.
|
|
34
|
+
|
|
35
|
+
### Retry state machine
|
|
36
|
+
|
|
37
|
+
The orchestrator maintains an explicit phase:
|
|
38
|
+
|
|
39
|
+
- `idle` — no retry in progress
|
|
40
|
+
- `scheduled` — a reload has been scheduled (timer running); concurrent triggers are deduplicated
|
|
41
|
+
- `fallback` — retries exhausted, fallback UI is shown; further triggers are ignored
|
|
42
|
+
|
|
43
|
+
### Retry progression
|
|
44
|
+
|
|
45
|
+
When a recoverable error occurs (chunk load error, unhandled rejection, `vite:preloadError`, or static asset 404):
|
|
46
|
+
|
|
47
|
+
1. Event listener classifies the event and calls `triggerRetry({ source, error })`.
|
|
48
|
+
2. Orchestrator checks phase — if already `scheduled` or `fallback`, returns immediately without scheduling another reload.
|
|
49
|
+
3. On first trigger: reads `RETRY_ATTEMPT_PARAM` from URL to restore attempt count after a reload.
|
|
50
|
+
4. If attempts remain: increments attempt, sets a timer for `reloadDelays[currentAttempt]`, encodes `retryId` and attempt count into the reload URL, then navigates.
|
|
51
|
+
5. If attempts are exhausted: transitions to `fallback`, calls `setFallbackMode()`, sends a beacon, and renders fallback UI.
|
|
52
|
+
|
|
53
|
+
### URL params
|
|
54
|
+
|
|
55
|
+
The orchestrator serializes state into URL params for cross-reload continuity:
|
|
56
|
+
|
|
57
|
+
- `RETRY_ATTEMPT_PARAM` — current attempt count (strict non-negative integer; malformed values are ignored)
|
|
58
|
+
- `RETRY_ID_PARAM` — unique ID for this retry session
|
|
59
|
+
- `CACHE_BUST_PARAM` — timestamp for cache busting (set when `cacheBust: true` is passed, e.g. for static asset errors)
|
|
60
|
+
|
|
61
|
+
### Retry reset
|
|
62
|
+
|
|
63
|
+
If enough time has passed since the last reload (configurable via `minTimeBetweenResets`, default 5000 ms), the orchestrator resets the attempt counter and starts a fresh retry cycle instead of continuing to fallback. This prevents stale URL params from triggering fallback on a clean page load.
|
|
64
|
+
|
|
65
|
+
### Healthy boot
|
|
66
|
+
|
|
67
|
+
After a successful app boot following a retry reload, call `markRetryHealthyBoot()`. This clears retry URL params, cancels any pending timer, and resets orchestrator state.
|
|
68
|
+
|
|
69
|
+
### Static asset burst coalescing
|
|
70
|
+
|
|
71
|
+
Multiple 404'd asset errors within a short window (default 500 ms) are coalesced into a single `triggerRetry({ cacheBust: true, source: "static-asset-error" })`. The orchestrator's deduplication ensures only one reload is scheduled.
|
|
72
|
+
|
|
73
|
+
### Retry ownership rule
|
|
74
|
+
|
|
75
|
+
Only `retryOrchestrator.ts` may schedule reloads, advance retry state, or transition to fallback. Listeners, renderers, and other modules must not schedule their own retry timers or set fallback state directly.
|
|
76
|
+
|
|
31
77
|
## API
|
|
32
78
|
|
|
33
79
|
### `@ovineko/spa-guard` (common)
|
|
@@ -36,9 +82,22 @@ const cleanup = recommendedSetup({ versionCheck: false });
|
|
|
36
82
|
- `events` — event type constants
|
|
37
83
|
- `options` — runtime options helpers
|
|
38
84
|
- `disableDefaultRetry` / `enableDefaultRetry` / `isDefaultRetryEnabled` — control default retry behaviour
|
|
85
|
+
- `isInFallbackMode` — returns `true` when fallback UI is active (retries exhausted)
|
|
86
|
+
- `resetFallbackMode` — clears the fallback flag; use in tests or programmatic recovery flows
|
|
39
87
|
- `BeaconError` — error class for beacon failures
|
|
40
88
|
- `ForceRetryError` — error class to force a retry
|
|
41
89
|
|
|
90
|
+
**Retry orchestrator (single owner of retry lifecycle):**
|
|
91
|
+
|
|
92
|
+
- `triggerRetry(input?)` — trigger a retry from any source; returns `TriggerResult`:
|
|
93
|
+
- `{ status: "accepted" }` — reload scheduled
|
|
94
|
+
- `{ status: "deduped", reason: string }` — ignored because another retry is already scheduled or an internal error occurred
|
|
95
|
+
- `{ status: "fallback" }` — already in fallback mode; no retry scheduled
|
|
96
|
+
- `{ status: "retry-disabled" }` — retry is disabled via `disableDefaultRetry()`
|
|
97
|
+
- `markRetryHealthyBoot()` — call after a successful boot following a retry reload; clears URL params, cancels timers, resets orchestrator state and fallback flag
|
|
98
|
+
- `getRetrySnapshot()` — returns current orchestrator state: `{ phase, attempt, retryId, lastSource, lastTriggerTime }`
|
|
99
|
+
- `resetRetryOrchestratorForTests()` — resets all orchestrator state including fallback flag; use in test teardown
|
|
100
|
+
|
|
42
101
|
### `@ovineko/spa-guard/runtime`
|
|
43
102
|
|
|
44
103
|
- `recommendedSetup(options?)` — enable recommended runtime features; returns cleanup function
|
|
@@ -60,7 +119,19 @@ Built-in translation strings.
|
|
|
60
119
|
|
|
61
120
|
### `@ovineko/spa-guard/runtime/debug`
|
|
62
121
|
|
|
63
|
-
Debug helpers for testing error scenarios.
|
|
122
|
+
Debug helpers for testing error scenarios. Use `createDebugger()` to mount an in-page panel with buttons for each scenario.
|
|
123
|
+
|
|
124
|
+
Available error scenarios:
|
|
125
|
+
|
|
126
|
+
- `dispatchChunkLoadError` — unhandled ChunkLoadError rejection
|
|
127
|
+
- `dispatchNetworkTimeout` — unhandled network timeout after a delay
|
|
128
|
+
- `dispatchSyncRuntimeError` — sync error thrown during React render
|
|
129
|
+
- `dispatchAsyncRuntimeError` — uncaught error via setTimeout
|
|
130
|
+
- `dispatchFinallyError` — unhandled rejection from Promise.finally
|
|
131
|
+
- `dispatchForceRetryError` — ForceRetryError to exercise the force-retry path
|
|
132
|
+
- `dispatchUnhandledRejection` — generic unhandled promise rejection
|
|
133
|
+
- `dispatchRetryExhausted` — renders fallback UI directly (bypasses orchestrator; orchestrator phase remains `idle`)
|
|
134
|
+
- `dispatchStaticAsset404` — appends a hashed `<script>` that 404s, exercising the Resource Timing API-based static asset detection path
|
|
64
135
|
|
|
65
136
|
## Related packages
|
|
66
137
|
|
package/dist/_internal.d.ts
CHANGED
|
@@ -12,8 +12,9 @@ export { createLogger } from "./common/logger";
|
|
|
12
12
|
export { getOptions, optionsWindowKey } from "./common/options";
|
|
13
13
|
export type { Options } from "./common/options";
|
|
14
14
|
export { extractVersionFromHtml } from "./common/parseVersion";
|
|
15
|
-
export { attemptReload } from "./common/reload";
|
|
16
15
|
export { retryImport } from "./common/retryImport";
|
|
16
|
+
export { getRetrySnapshot, markRetryHealthyBoot, resetRetryOrchestratorForTests, triggerRetry, } from "./common/retryOrchestrator";
|
|
17
|
+
export type { RetryPhase, RetrySnapshot, TriggerInput, TriggerResult, } from "./common/retryOrchestrator";
|
|
17
18
|
export { serializeError } from "./common/serializeError";
|
|
18
19
|
export { defaultSpinnerSvg, sanitizeCssValue, SPINNER_ID } from "./common/spinner";
|
|
19
20
|
export { dispatchAsyncRuntimeError, dispatchChunkLoadError, dispatchFinallyError, dispatchForceRetryError, dispatchNetworkTimeout, dispatchSyncRuntimeError, dispatchUnhandledRejection, } from "./runtime/debug/errorDispatchers";
|
package/dist/_internal.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createLogger,
|
|
3
|
-
isChunkError,
|
|
4
|
-
listenInternal,
|
|
5
|
-
serializeError
|
|
6
|
-
} from "./chunk-5ANASVKX.js";
|
|
7
1
|
import {
|
|
8
2
|
SPINNER_ID,
|
|
9
3
|
defaultSpinnerSvg,
|
|
10
4
|
extractVersionFromHtml,
|
|
11
5
|
sanitizeCssValue
|
|
12
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-6TP2S7L6.js";
|
|
7
|
+
import {
|
|
8
|
+
createLogger,
|
|
9
|
+
isChunkError,
|
|
10
|
+
listenInternal,
|
|
11
|
+
serializeError
|
|
12
|
+
} from "./chunk-DINLBER6.js";
|
|
13
13
|
import {
|
|
14
14
|
dispatchAsyncRuntimeError,
|
|
15
15
|
dispatchChunkLoadError,
|
|
@@ -18,13 +18,7 @@ import {
|
|
|
18
18
|
dispatchNetworkTimeout,
|
|
19
19
|
dispatchSyncRuntimeError,
|
|
20
20
|
dispatchUnhandledRejection
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import {
|
|
23
|
-
attemptReload,
|
|
24
|
-
sendBeacon,
|
|
25
|
-
shouldForceRetry,
|
|
26
|
-
shouldIgnoreMessages
|
|
27
|
-
} from "./chunk-4EXCHJBE.js";
|
|
21
|
+
} from "./chunk-A5HF6LY6.js";
|
|
28
22
|
import {
|
|
29
23
|
applyI18n,
|
|
30
24
|
debugSyncErrorEventType,
|
|
@@ -36,10 +30,17 @@ import {
|
|
|
36
30
|
getI18n,
|
|
37
31
|
getOptions,
|
|
38
32
|
getRetryInfoForBeacon,
|
|
33
|
+
getRetrySnapshot,
|
|
39
34
|
isDefaultRetryEnabled,
|
|
35
|
+
markRetryHealthyBoot,
|
|
40
36
|
optionsWindowKey,
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
resetRetryOrchestratorForTests,
|
|
38
|
+
sendBeacon,
|
|
39
|
+
shouldForceRetry,
|
|
40
|
+
shouldIgnoreMessages,
|
|
41
|
+
subscribe,
|
|
42
|
+
triggerRetry
|
|
43
|
+
} from "./chunk-XRBUBTPZ.js";
|
|
43
44
|
import "./chunk-MLKGABMK.js";
|
|
44
45
|
|
|
45
46
|
// src/common/handleErrorWithSpaGuard.ts
|
|
@@ -62,7 +63,7 @@ var handleErrorWithSpaGuard = (error, options) => {
|
|
|
62
63
|
const isChunk = isChunkError(error);
|
|
63
64
|
const isForceRetry = shouldForceRetry([errorMessage]);
|
|
64
65
|
if ((isChunk || isForceRetry) && autoRetryChunkErrors) {
|
|
65
|
-
|
|
66
|
+
triggerRetry({ error, source: "error-boundary" });
|
|
66
67
|
} else if (sendBeaconOnError) {
|
|
67
68
|
sendBeacon({
|
|
68
69
|
errorMessage: error instanceof Error ? error.message : String(error),
|
|
@@ -130,14 +131,13 @@ var retryImport = async (importFn, delays, options) => {
|
|
|
130
131
|
const willReload = callReloadOnFailure === true && isChunkError(lastError) && isDefaultRetryEnabled();
|
|
131
132
|
emitEvent({ error: lastError, name: "lazy-retry-exhausted", totalAttempts, willReload });
|
|
132
133
|
if (willReload) {
|
|
133
|
-
|
|
134
|
+
triggerRetry({ error: lastError, source: "lazy-import-failure" });
|
|
134
135
|
}
|
|
135
136
|
throw lastError;
|
|
136
137
|
};
|
|
137
138
|
export {
|
|
138
139
|
SPINNER_ID,
|
|
139
140
|
applyI18n,
|
|
140
|
-
attemptReload,
|
|
141
141
|
createLogger,
|
|
142
142
|
debugSyncErrorEventType,
|
|
143
143
|
defaultErrorFallbackHtml,
|
|
@@ -156,14 +156,18 @@ export {
|
|
|
156
156
|
extractVersionFromHtml,
|
|
157
157
|
getI18n,
|
|
158
158
|
getOptions,
|
|
159
|
+
getRetrySnapshot,
|
|
159
160
|
handleErrorWithSpaGuard,
|
|
160
161
|
isChunkError,
|
|
161
162
|
isDefaultRetryEnabled,
|
|
162
163
|
listenInternal,
|
|
163
164
|
logMessage,
|
|
165
|
+
markRetryHealthyBoot,
|
|
164
166
|
optionsWindowKey,
|
|
167
|
+
resetRetryOrchestratorForTests,
|
|
165
168
|
retryImport,
|
|
166
169
|
sanitizeCssValue,
|
|
167
170
|
serializeError,
|
|
168
|
-
subscribe
|
|
171
|
+
subscribe,
|
|
172
|
+
triggerRetry
|
|
169
173
|
};
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
showFallbackUI
|
|
3
|
-
} from "./chunk-4EXCHJBE.js";
|
|
4
1
|
import {
|
|
5
2
|
ForceRetryError,
|
|
6
3
|
debugSyncErrorEventType,
|
|
7
4
|
emitEvent,
|
|
8
|
-
getOptions
|
|
9
|
-
|
|
5
|
+
getOptions,
|
|
6
|
+
setFallbackStateForDebug
|
|
7
|
+
} from "./chunk-XRBUBTPZ.js";
|
|
10
8
|
|
|
11
9
|
// src/runtime/debug/errorDispatchers.ts
|
|
12
10
|
function dispatchAsyncRuntimeError() {
|
|
@@ -36,18 +34,21 @@ function dispatchNetworkTimeout(delayMs = 3e3) {
|
|
|
36
34
|
}
|
|
37
35
|
function dispatchRetryExhausted() {
|
|
38
36
|
const options = getOptions();
|
|
39
|
-
const reloadDelays = options.reloadDelays ?? [
|
|
37
|
+
const reloadDelays = options.reloadDelays ?? [];
|
|
40
38
|
emitEvent({
|
|
41
39
|
finalAttempt: reloadDelays.length,
|
|
42
40
|
name: "retry-exhausted",
|
|
43
41
|
retryId: ""
|
|
44
42
|
});
|
|
45
|
-
|
|
43
|
+
setFallbackStateForDebug();
|
|
46
44
|
}
|
|
47
45
|
function dispatchStaticAsset404() {
|
|
48
|
-
const hash = crypto.randomUUID().
|
|
46
|
+
const hash = crypto.randomUUID().slice(0, 8);
|
|
49
47
|
const script = document.createElement("script");
|
|
50
48
|
script.src = `/assets/index-${hash}.js`;
|
|
49
|
+
script.addEventListener("error", () => {
|
|
50
|
+
script.remove();
|
|
51
|
+
});
|
|
51
52
|
document.head.append(script);
|
|
52
53
|
}
|
|
53
54
|
function dispatchSyncRuntimeError() {
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
attemptReload,
|
|
3
|
-
sendBeacon,
|
|
4
|
-
shouldForceRetry,
|
|
5
|
-
shouldIgnoreMessages
|
|
6
|
-
} from "./chunk-4EXCHJBE.js";
|
|
7
|
-
import {
|
|
8
|
-
clearRetryStateFromUrl,
|
|
9
2
|
emitEvent,
|
|
10
3
|
getLogger,
|
|
11
4
|
getOptions,
|
|
12
5
|
getRetryInfoForBeacon,
|
|
13
|
-
getRetryStateFromUrl,
|
|
14
6
|
isInitialized,
|
|
15
7
|
markInitialized,
|
|
8
|
+
sendBeacon,
|
|
16
9
|
setLogger,
|
|
10
|
+
shouldForceRetry,
|
|
11
|
+
shouldIgnoreMessages,
|
|
17
12
|
staticAssetRecoveryKey,
|
|
18
|
-
|
|
19
|
-
} from "./chunk-
|
|
13
|
+
triggerRetry
|
|
14
|
+
} from "./chunk-XRBUBTPZ.js";
|
|
20
15
|
|
|
21
16
|
// src/common/isChunkError.ts
|
|
22
17
|
var isChunkError = (error) => {
|
|
@@ -182,6 +177,9 @@ var isStaticAssetError = (event) => {
|
|
|
182
177
|
return false;
|
|
183
178
|
};
|
|
184
179
|
var checkResourceStatus = (url) => {
|
|
180
|
+
if (typeof performance === "undefined" || typeof performance.getEntriesByName !== "function") {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
185
183
|
const entries = performance.getEntriesByName(url, "resource");
|
|
186
184
|
if (entries.length === 0) {
|
|
187
185
|
return true;
|
|
@@ -195,11 +193,12 @@ var checkResourceStatus = (url) => {
|
|
|
195
193
|
}
|
|
196
194
|
return false;
|
|
197
195
|
};
|
|
198
|
-
var isLikely404 = (url, timeSinceNavMs
|
|
196
|
+
var isLikely404 = (url, timeSinceNavMs) => {
|
|
199
197
|
if (url !== void 0) {
|
|
200
198
|
return checkResourceStatus(url);
|
|
201
199
|
}
|
|
202
|
-
|
|
200
|
+
const elapsed = timeSinceNavMs ?? (typeof performance === "undefined" ? 0 : performance.now());
|
|
201
|
+
return elapsed > 3e4;
|
|
203
202
|
};
|
|
204
203
|
var getAssetUrl = (event) => {
|
|
205
204
|
const target = event.target;
|
|
@@ -226,6 +225,9 @@ var getState = () => {
|
|
|
226
225
|
return globalThis.window[staticAssetRecoveryKey];
|
|
227
226
|
};
|
|
228
227
|
var handleStaticAssetFailure = (url) => {
|
|
228
|
+
if (globalThis.window === void 0) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
229
231
|
const state = getState();
|
|
230
232
|
state.failedAssets.add(url);
|
|
231
233
|
if (state.recoveryTimer !== null) {
|
|
@@ -235,11 +237,11 @@ var handleStaticAssetFailure = (url) => {
|
|
|
235
237
|
const delay = options.staticAssets?.recoveryDelay ?? 500;
|
|
236
238
|
state.recoveryTimer = setTimeout(() => {
|
|
237
239
|
const s = getState();
|
|
240
|
+
s.recoveryTimer = null;
|
|
238
241
|
const assets = [...s.failedAssets];
|
|
239
242
|
s.failedAssets = /* @__PURE__ */ new Set();
|
|
240
|
-
s.recoveryTimer = null;
|
|
241
243
|
const error = new Error(`Static asset load failed: ${assets.join(", ")}`);
|
|
242
|
-
|
|
244
|
+
triggerRetry({ cacheBust: true, error, source: "static-asset-error" });
|
|
243
245
|
}, delay);
|
|
244
246
|
};
|
|
245
247
|
|
|
@@ -253,24 +255,6 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
253
255
|
}
|
|
254
256
|
markInitialized();
|
|
255
257
|
const options = getOptions();
|
|
256
|
-
const reloadDelays = options.reloadDelays ?? [];
|
|
257
|
-
const retryState = getRetryStateFromUrl();
|
|
258
|
-
if (retryState && retryState.retryAttempt >= reloadDelays.length) {
|
|
259
|
-
getLogger()?.retryLimitExceeded(retryState.retryAttempt, reloadDelays.length);
|
|
260
|
-
updateRetryStateInUrl(retryState.retryId, -1);
|
|
261
|
-
}
|
|
262
|
-
if (retryState && (retryState.retryAttempt >= reloadDelays.length || retryState.retryAttempt === -1)) {
|
|
263
|
-
globalThis.window.addEventListener(
|
|
264
|
-
"load",
|
|
265
|
-
() => {
|
|
266
|
-
const current = getRetryStateFromUrl();
|
|
267
|
-
if (current?.retryAttempt === -1) {
|
|
268
|
-
clearRetryStateFromUrl();
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
{ once: true }
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
258
|
const wa = globalThis.window.addEventListener.bind(globalThis.window);
|
|
275
259
|
wa(
|
|
276
260
|
"error",
|
|
@@ -293,12 +277,12 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
293
277
|
getLogger()?.capturedError("error", event);
|
|
294
278
|
if (isChunkError(event)) {
|
|
295
279
|
event.preventDefault();
|
|
296
|
-
|
|
280
|
+
triggerRetry({ error: event.error ?? event, source: "chunk-error" });
|
|
297
281
|
return;
|
|
298
282
|
}
|
|
299
283
|
if (shouldForceRetry([event.message])) {
|
|
300
284
|
event.preventDefault();
|
|
301
|
-
|
|
285
|
+
triggerRetry({ error: event.error ?? event, source: "force-retry" });
|
|
302
286
|
return;
|
|
303
287
|
}
|
|
304
288
|
const serialized = serializeError2(event);
|
|
@@ -319,12 +303,12 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
319
303
|
getLogger()?.capturedError("unhandledrejection", event);
|
|
320
304
|
if (isChunkError(event.reason)) {
|
|
321
305
|
event.preventDefault();
|
|
322
|
-
|
|
306
|
+
triggerRetry({ error: event.reason, source: "chunk-error" });
|
|
323
307
|
return;
|
|
324
308
|
}
|
|
325
309
|
if (shouldForceRetry([errorMessage])) {
|
|
326
310
|
event.preventDefault();
|
|
327
|
-
|
|
311
|
+
triggerRetry({ error: event.reason, source: "force-retry" });
|
|
328
312
|
return;
|
|
329
313
|
}
|
|
330
314
|
const rejectionConfig = options.handleUnhandledRejections;
|
|
@@ -339,7 +323,7 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
339
323
|
}
|
|
340
324
|
if (rejectionConfig?.retry !== false) {
|
|
341
325
|
event.preventDefault();
|
|
342
|
-
|
|
326
|
+
triggerRetry({ error: event.reason, source: "unhandled-rejection" });
|
|
343
327
|
}
|
|
344
328
|
});
|
|
345
329
|
wa("securitypolicyviolation", (event) => {
|
|
@@ -357,13 +341,14 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
357
341
|
});
|
|
358
342
|
});
|
|
359
343
|
wa("vite:preloadError", (event) => {
|
|
360
|
-
const
|
|
344
|
+
const payload = event?.payload;
|
|
345
|
+
const errorMsg = payload?.message || event?.message;
|
|
361
346
|
if (shouldIgnoreMessages([errorMsg])) {
|
|
362
347
|
return;
|
|
363
348
|
}
|
|
364
349
|
getLogger()?.capturedError("vite:preloadError", event);
|
|
365
350
|
event.preventDefault();
|
|
366
|
-
|
|
351
|
+
triggerRetry({ error: payload ?? event, source: "vite:preloadError" });
|
|
367
352
|
});
|
|
368
353
|
};
|
|
369
354
|
|
|
@@ -371,6 +356,7 @@ var listenInternal = (serializeError2, logger) => {
|
|
|
371
356
|
var PREFIX = "[spa-guard]";
|
|
372
357
|
var eventLogConfig = {
|
|
373
358
|
"chunk-error": "error",
|
|
359
|
+
"fallback-ui-not-rendered": "error",
|
|
374
360
|
"fallback-ui-shown": "warn",
|
|
375
361
|
"lazy-retry-attempt": "warn",
|
|
376
362
|
"lazy-retry-exhausted": "error",
|
|
@@ -386,6 +372,10 @@ var formatEvent = (event) => {
|
|
|
386
372
|
case "chunk-error": {
|
|
387
373
|
return `${PREFIX} chunk-error: isRetrying=${event.isRetrying}`;
|
|
388
374
|
}
|
|
375
|
+
case "fallback-ui-not-rendered": {
|
|
376
|
+
const selectorPart = event.selector ? ` selector=${event.selector}` : "";
|
|
377
|
+
return `${PREFIX} fallback-ui-not-rendered: reason=${event.reason}${selectorPart}`;
|
|
378
|
+
}
|
|
389
379
|
case "fallback-ui-shown": {
|
|
390
380
|
return `${PREFIX} fallback-ui-shown`;
|
|
391
381
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getRetryAttemptFromUrl,
|
|
4
4
|
getRetryStateFromUrl,
|
|
5
5
|
subscribe
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XRBUBTPZ.js";
|
|
7
7
|
|
|
8
8
|
// src/runtime/state.ts
|
|
9
9
|
var getInitialStateFromUrl = () => {
|
|
@@ -37,15 +37,6 @@ var getInitialStateFromUrl = () => {
|
|
|
37
37
|
lastRetryResetTime: resetInfo?.timestamp
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
if (retryState.retryAttempt === -1) {
|
|
41
|
-
return {
|
|
42
|
-
currentAttempt: 0,
|
|
43
|
-
isFallbackShown: true,
|
|
44
|
-
isWaiting: false,
|
|
45
|
-
lastResetRetryId: resetInfo?.previousRetryId,
|
|
46
|
-
lastRetryResetTime: resetInfo?.timestamp
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
40
|
return {
|
|
50
41
|
currentAttempt: retryState.retryAttempt,
|
|
51
42
|
isFallbackShown: false,
|
|
@@ -58,7 +49,12 @@ var currentState = getInitialStateFromUrl();
|
|
|
58
49
|
var stateSubscribers = /* @__PURE__ */ new Set();
|
|
59
50
|
var updateState = (nextState) => {
|
|
60
51
|
currentState = nextState;
|
|
61
|
-
stateSubscribers.forEach((cb) =>
|
|
52
|
+
stateSubscribers.forEach((cb) => {
|
|
53
|
+
try {
|
|
54
|
+
cb(currentState);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
});
|
|
62
58
|
};
|
|
63
59
|
subscribe((event) => {
|
|
64
60
|
switch (event.name) {
|