@ovineko/spa-guard 0.0.1-alpha-23 → 0.0.1-alpha-24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal.js +19 -19
- package/dist/{chunk-YSKH5K6P.js → chunk-5DE7AKYB.js} +157 -0
- package/dist/{chunk-SZS6GOPK.js → chunk-CQ5IVYGC.js} +1 -1
- package/dist/{chunk-4FKSMB5F.js → chunk-DUR4QNQE.js} +7 -289
- package/dist/{chunk-74K44EMP.js → chunk-H4DWKO5I.js} +18 -2
- package/dist/{chunk-3ISQYZVD.js → chunk-UF7QAEI7.js} +1 -1
- package/dist/chunk-VZCUDNEG.js +295 -0
- package/dist/common/index.js +4 -5
- package/dist/common/reload.d.ts +1 -0
- package/dist/runtime/debug/errorDispatchers.d.ts +6 -0
- package/dist/runtime/debug/index.js +7 -4
- package/dist/runtime/index.js +6 -8
- package/package.json +1 -1
- package/dist/chunk-DOTM7FSY.js +0 -162
package/dist/_internal.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
attemptReload,
|
|
3
|
-
createLogger,
|
|
4
|
-
isChunkError,
|
|
5
|
-
listenInternal,
|
|
6
|
-
sendBeacon,
|
|
7
|
-
serializeError,
|
|
8
|
-
shouldForceRetry,
|
|
9
|
-
shouldIgnoreMessages
|
|
10
|
-
} from "./chunk-4FKSMB5F.js";
|
|
11
1
|
import {
|
|
12
2
|
SPINNER_ID,
|
|
13
3
|
defaultSpinnerSvg,
|
|
14
4
|
extractVersionFromHtml
|
|
15
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UF7QAEI7.js";
|
|
16
6
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "./chunk-DOTM7FSY.js";
|
|
7
|
+
createLogger,
|
|
8
|
+
isChunkError,
|
|
9
|
+
listenInternal,
|
|
10
|
+
serializeError
|
|
11
|
+
} from "./chunk-DUR4QNQE.js";
|
|
23
12
|
import {
|
|
24
13
|
dispatchAsyncRuntimeError,
|
|
25
14
|
dispatchChunkLoadError,
|
|
@@ -28,17 +17,28 @@ import {
|
|
|
28
17
|
dispatchNetworkTimeout,
|
|
29
18
|
dispatchSyncRuntimeError,
|
|
30
19
|
dispatchUnhandledRejection
|
|
31
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-H4DWKO5I.js";
|
|
32
21
|
import {
|
|
22
|
+
attemptReload,
|
|
23
|
+
sendBeacon,
|
|
24
|
+
shouldForceRetry,
|
|
25
|
+
shouldIgnoreMessages
|
|
26
|
+
} from "./chunk-VZCUDNEG.js";
|
|
27
|
+
import {
|
|
28
|
+
applyI18n,
|
|
33
29
|
debugSyncErrorEventType,
|
|
30
|
+
defaultErrorFallbackHtml,
|
|
31
|
+
defaultLoadingFallbackHtml,
|
|
34
32
|
disableDefaultRetry,
|
|
35
33
|
emitEvent,
|
|
36
34
|
enableDefaultRetry,
|
|
35
|
+
getI18n,
|
|
36
|
+
getOptions,
|
|
37
37
|
getRetryInfoForBeacon,
|
|
38
38
|
isDefaultRetryEnabled,
|
|
39
39
|
optionsWindowKey,
|
|
40
40
|
subscribe
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-5DE7AKYB.js";
|
|
42
42
|
import "./chunk-MLKGABMK.js";
|
|
43
43
|
|
|
44
44
|
// src/common/handleErrorWithSpaGuard.ts
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export
|
|
3
|
+
} from "./chunk-MLKGABMK.js";
|
|
4
|
+
|
|
1
5
|
// package.json
|
|
2
6
|
var name = "@ovineko/spa-guard";
|
|
3
7
|
|
|
@@ -68,6 +72,151 @@ var isDefaultRetryEnabled = () => {
|
|
|
68
72
|
return internalConfig.defaultRetryEnabled;
|
|
69
73
|
};
|
|
70
74
|
|
|
75
|
+
// src/common/i18n.ts
|
|
76
|
+
function applyI18n(container, t) {
|
|
77
|
+
const contentEls = container.querySelectorAll("[data-spa-guard-content]");
|
|
78
|
+
for (const el of contentEls) {
|
|
79
|
+
const key = el.dataset.spaGuardContent;
|
|
80
|
+
if (key && key in t) {
|
|
81
|
+
const value = t[key];
|
|
82
|
+
if (typeof value === "string") {
|
|
83
|
+
el.textContent = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const actionEls = container.querySelectorAll("[data-spa-guard-action]");
|
|
88
|
+
for (const el of actionEls) {
|
|
89
|
+
const action = el.dataset.spaGuardAction;
|
|
90
|
+
const tKey = action === "try-again" ? "tryAgain" : action;
|
|
91
|
+
if (tKey && tKey in t) {
|
|
92
|
+
const value = t[tKey];
|
|
93
|
+
if (typeof value === "string") {
|
|
94
|
+
el.textContent = value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (t.rtl) {
|
|
99
|
+
for (const child of container.children) {
|
|
100
|
+
if (child instanceof HTMLElement && child.tagName !== "STYLE") {
|
|
101
|
+
child.style.direction = "rtl";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function getI18n() {
|
|
107
|
+
try {
|
|
108
|
+
const el = document.querySelector('meta[name="spa-guard-i18n"]');
|
|
109
|
+
if (!el) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const content = el.getAttribute("content");
|
|
113
|
+
if (!content) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return JSON.parse(content);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function setTranslations(translations) {
|
|
122
|
+
let el = document.querySelector('meta[name="spa-guard-i18n"]');
|
|
123
|
+
if (!el) {
|
|
124
|
+
el = document.createElement("meta");
|
|
125
|
+
el.setAttribute("name", "spa-guard-i18n");
|
|
126
|
+
document.head.append(el);
|
|
127
|
+
}
|
|
128
|
+
el.setAttribute("content", JSON.stringify(translations));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/common/html.generated.ts
|
|
132
|
+
var defaultErrorFallbackHtml = `<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}.spa-guard-error-id{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",Menlo,monospace}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem;font-family:system-ui,sans-serif"><div style="text-align:center;max-width:480px"><div style="margin-bottom:1.5rem"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none" viewBox="0 0 24 24" stroke="#b0b0b0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div><h1 data-spa-guard-content="heading" style="font-size:1.375rem;font-weight:600;margin:0 0 .5rem;color:#1a1a1a;line-height:1.3">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:0 auto 1.5rem;color:#666;font-size:.9375rem;line-height:1.5">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap"><button data-spa-guard-action="try-again" type="button" style="display:none;padding:.5rem 1.25rem;font-size:.875rem;font-family:inherit;border-radius:6px;border:1px solid #d0d0d0;background:#fff;color:#333;cursor:pointer;line-height:1.5">Try again</button> <button data-spa-guard-action="reload" type="button" style="padding:.5rem 1.25rem;font-size:.875rem;font-family:inherit;border-radius:6px;border:1px solid transparent;background:#111;color:#fff;cursor:pointer;line-height:1.5">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:1.5rem;font-size:.6875rem;color:#999">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>`;
|
|
133
|
+
var defaultLoadingFallbackHtml = `<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem;font-family:system-ui,sans-serif"><div style="text-align:center"><div data-spa-guard-spinner style="margin-bottom:1.25rem"></div><h2 data-spa-guard-content="loading" style="font-size:1.125rem;font-weight:600;margin:0 0 .25rem;color:#1a1a1a">Loading...</h2><p data-spa-guard-section="retrying" style="display:none;font-size:.8125rem;color:#999;margin:.5rem 0 0"><span data-spa-guard-content="retrying">Retry attempt</span> <span data-spa-guard-content="attempt"></span></p></div></div>`;
|
|
134
|
+
var defaultSpinnerHtml = `<svg width="40" height="40" viewBox="0 0 40 40" style="animation:spa-guard-spin .8s linear infinite"><circle cx="20" cy="20" r="16" fill="none" stroke="#e8e8e8" stroke-width="3"/><circle cx="20" cy="20" r="16" fill="none" stroke="#666" stroke-width="3" stroke-dasharray="80" stroke-dashoffset="60" stroke-linecap="round"/></svg><style>@keyframes spa-guard-spin{to{transform:rotate(360deg)}}</style>`;
|
|
135
|
+
|
|
136
|
+
// src/common/options.ts
|
|
137
|
+
var options_exports = {};
|
|
138
|
+
__export(options_exports, {
|
|
139
|
+
getOptions: () => getOptions,
|
|
140
|
+
optionsWindowKey: () => optionsWindowKey
|
|
141
|
+
});
|
|
142
|
+
var defaultOptions = {
|
|
143
|
+
checkVersion: {
|
|
144
|
+
interval: 3e5,
|
|
145
|
+
mode: "html",
|
|
146
|
+
onUpdate: "reload"
|
|
147
|
+
},
|
|
148
|
+
enableRetryReset: true,
|
|
149
|
+
errors: {
|
|
150
|
+
forceRetry: [],
|
|
151
|
+
ignore: []
|
|
152
|
+
},
|
|
153
|
+
handleUnhandledRejections: {
|
|
154
|
+
retry: true,
|
|
155
|
+
sendBeacon: true
|
|
156
|
+
},
|
|
157
|
+
html: {
|
|
158
|
+
fallback: {
|
|
159
|
+
content: defaultErrorFallbackHtml,
|
|
160
|
+
selector: "body"
|
|
161
|
+
},
|
|
162
|
+
loading: {
|
|
163
|
+
content: defaultLoadingFallbackHtml
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
lazyRetry: {
|
|
167
|
+
callReloadOnFailure: true,
|
|
168
|
+
retryDelays: [1e3, 2e3]
|
|
169
|
+
},
|
|
170
|
+
minTimeBetweenResets: 5e3,
|
|
171
|
+
reloadDelays: [1e3, 2e3, 5e3],
|
|
172
|
+
spinner: {
|
|
173
|
+
background: "#fff",
|
|
174
|
+
disabled: false
|
|
175
|
+
},
|
|
176
|
+
useRetryId: true
|
|
177
|
+
};
|
|
178
|
+
var getOptions = () => {
|
|
179
|
+
const windowOptions = globalThis.window?.[optionsWindowKey];
|
|
180
|
+
return {
|
|
181
|
+
...defaultOptions,
|
|
182
|
+
...windowOptions,
|
|
183
|
+
checkVersion: {
|
|
184
|
+
...defaultOptions.checkVersion,
|
|
185
|
+
...windowOptions?.checkVersion
|
|
186
|
+
},
|
|
187
|
+
errors: {
|
|
188
|
+
...defaultOptions.errors,
|
|
189
|
+
...windowOptions?.errors
|
|
190
|
+
},
|
|
191
|
+
handleUnhandledRejections: {
|
|
192
|
+
...defaultOptions.handleUnhandledRejections,
|
|
193
|
+
...windowOptions?.handleUnhandledRejections
|
|
194
|
+
},
|
|
195
|
+
html: {
|
|
196
|
+
fallback: {
|
|
197
|
+
...defaultOptions.html?.fallback,
|
|
198
|
+
...windowOptions?.html?.fallback
|
|
199
|
+
},
|
|
200
|
+
loading: {
|
|
201
|
+
...defaultOptions.html?.loading,
|
|
202
|
+
...windowOptions?.html?.loading
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
lazyRetry: {
|
|
206
|
+
...defaultOptions.lazyRetry,
|
|
207
|
+
...windowOptions?.lazyRetry
|
|
208
|
+
},
|
|
209
|
+
reportBeacon: {
|
|
210
|
+
...defaultOptions.reportBeacon,
|
|
211
|
+
...windowOptions?.reportBeacon
|
|
212
|
+
},
|
|
213
|
+
spinner: {
|
|
214
|
+
...defaultOptions.spinner,
|
|
215
|
+
...windowOptions?.spinner
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
71
220
|
// src/common/errors/ForceRetryError.ts
|
|
72
221
|
var FORCE_RETRY_MAGIC = "__SPA_GUARD_FORCE_RETRY__";
|
|
73
222
|
var ForceRetryError = class extends Error {
|
|
@@ -279,12 +428,20 @@ export {
|
|
|
279
428
|
disableDefaultRetry,
|
|
280
429
|
enableDefaultRetry,
|
|
281
430
|
isDefaultRetryEnabled,
|
|
431
|
+
applyI18n,
|
|
432
|
+
getI18n,
|
|
433
|
+
setTranslations,
|
|
282
434
|
setLastReloadTime,
|
|
283
435
|
getLastReloadTime,
|
|
284
436
|
clearLastReloadTime,
|
|
285
437
|
shouldResetRetryCycle,
|
|
286
438
|
setLastRetryResetInfo,
|
|
287
439
|
getLastRetryResetInfo,
|
|
440
|
+
defaultErrorFallbackHtml,
|
|
441
|
+
defaultLoadingFallbackHtml,
|
|
442
|
+
defaultSpinnerHtml,
|
|
443
|
+
getOptions,
|
|
444
|
+
options_exports,
|
|
288
445
|
getRetryStateFromUrl,
|
|
289
446
|
clearRetryStateFromUrl,
|
|
290
447
|
updateRetryStateInUrl,
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
attemptReload,
|
|
3
|
+
sendBeacon,
|
|
4
|
+
shouldForceRetry,
|
|
5
|
+
shouldIgnoreMessages
|
|
6
|
+
} from "./chunk-VZCUDNEG.js";
|
|
6
7
|
import {
|
|
7
|
-
FORCE_RETRY_MAGIC,
|
|
8
|
-
RETRY_ATTEMPT_PARAM,
|
|
9
|
-
RETRY_ID_PARAM,
|
|
10
|
-
clearLastReloadTime,
|
|
11
|
-
clearRetryAttemptFromUrl,
|
|
12
|
-
clearRetryStateFromUrl,
|
|
13
|
-
emitEvent,
|
|
14
|
-
generateRetryId,
|
|
15
|
-
getLastReloadTime,
|
|
16
8
|
getLogger,
|
|
17
|
-
|
|
9
|
+
getOptions,
|
|
18
10
|
getRetryInfoForBeacon,
|
|
19
11
|
getRetryStateFromUrl,
|
|
20
|
-
isDefaultRetryEnabled,
|
|
21
12
|
isInitialized,
|
|
22
13
|
markInitialized,
|
|
23
|
-
setLastReloadTime,
|
|
24
|
-
setLastRetryResetInfo,
|
|
25
14
|
setLogger,
|
|
26
|
-
shouldResetRetryCycle,
|
|
27
15
|
updateRetryStateInUrl
|
|
28
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-5DE7AKYB.js";
|
|
29
17
|
|
|
30
18
|
// src/common/isChunkError.ts
|
|
31
19
|
var isChunkError = (error) => {
|
|
@@ -60,272 +48,6 @@ var getErrorMessage = (error) => {
|
|
|
60
48
|
return null;
|
|
61
49
|
};
|
|
62
50
|
|
|
63
|
-
// src/common/shouldIgnore.ts
|
|
64
|
-
var shouldIgnoreMessages = (messages) => {
|
|
65
|
-
const options = getOptions();
|
|
66
|
-
const ignorePatterns = (options.errors?.ignore ?? []).filter((p) => p !== "");
|
|
67
|
-
if (ignorePatterns.length === 0) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
const validMessages = messages.filter((msg) => typeof msg === "string");
|
|
71
|
-
return validMessages.some(
|
|
72
|
-
(message) => ignorePatterns.some((pattern) => message.includes(pattern))
|
|
73
|
-
);
|
|
74
|
-
};
|
|
75
|
-
var shouldForceRetry = (messages) => {
|
|
76
|
-
const options = getOptions();
|
|
77
|
-
const forceRetryPatterns = [...options.errors?.forceRetry ?? [], FORCE_RETRY_MAGIC].filter(
|
|
78
|
-
(p) => p !== ""
|
|
79
|
-
);
|
|
80
|
-
const validMessages = messages.filter((msg) => typeof msg === "string");
|
|
81
|
-
return validMessages.some(
|
|
82
|
-
(message) => forceRetryPatterns.some((pattern) => message.includes(pattern))
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
var shouldIgnoreBeacon = (beacon) => {
|
|
86
|
-
return shouldIgnoreMessages([beacon.errorMessage, beacon.eventMessage]);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// src/common/sendBeacon.ts
|
|
90
|
-
var sendBeacon = (beacon) => {
|
|
91
|
-
if (shouldIgnoreBeacon(beacon)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const options = getOptions();
|
|
95
|
-
if (!options.reportBeacon?.endpoint) {
|
|
96
|
-
getLogger()?.noBeaconEndpoint();
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const enrichedBeacon = options.appName ? { ...beacon, appName: options.appName } : beacon;
|
|
100
|
-
const body = JSON.stringify(enrichedBeacon);
|
|
101
|
-
const isSendBeaconAvailable = typeof globalThis.window?.navigator?.sendBeacon === "function";
|
|
102
|
-
const isSentBeacon = isSendBeaconAvailable && globalThis.window.navigator.sendBeacon(options.reportBeacon.endpoint, body);
|
|
103
|
-
if (!isSentBeacon && typeof fetch === "function") {
|
|
104
|
-
fetch(options.reportBeacon.endpoint, { body, keepalive: true, method: "POST" }).catch(
|
|
105
|
-
(error) => {
|
|
106
|
-
getLogger()?.beaconSendFailed(error);
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// src/common/reload.ts
|
|
113
|
-
var buildReloadUrl = (retryId, retryAttempt) => {
|
|
114
|
-
const url = new URL(globalThis.window.location.href);
|
|
115
|
-
url.searchParams.set(RETRY_ID_PARAM, retryId);
|
|
116
|
-
url.searchParams.set(RETRY_ATTEMPT_PARAM, String(retryAttempt));
|
|
117
|
-
return url.toString();
|
|
118
|
-
};
|
|
119
|
-
var buildReloadUrlAttemptOnly = (retryAttempt) => {
|
|
120
|
-
const url = new URL(globalThis.window.location.href);
|
|
121
|
-
url.searchParams.set(RETRY_ATTEMPT_PARAM, String(retryAttempt));
|
|
122
|
-
return url.toString();
|
|
123
|
-
};
|
|
124
|
-
var reloadScheduled = false;
|
|
125
|
-
var attemptReload = (error) => {
|
|
126
|
-
if (reloadScheduled) {
|
|
127
|
-
getLogger()?.reloadAlreadyScheduled(error);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
reloadScheduled = true;
|
|
131
|
-
try {
|
|
132
|
-
const options = getOptions();
|
|
133
|
-
const reloadDelays = options.reloadDelays ?? [1e3, 2e3, 5e3];
|
|
134
|
-
const useRetryId = options.useRetryId ?? true;
|
|
135
|
-
const enableRetryReset = options.enableRetryReset ?? true;
|
|
136
|
-
const minTimeBetweenResets = options.minTimeBetweenResets ?? 5e3;
|
|
137
|
-
let retryState;
|
|
138
|
-
if (useRetryId) {
|
|
139
|
-
retryState = getRetryStateFromUrl();
|
|
140
|
-
} else {
|
|
141
|
-
const attempt = getRetryAttemptFromUrl();
|
|
142
|
-
retryState = attempt === null ? null : { retryAttempt: attempt, retryId: generateRetryId() };
|
|
143
|
-
}
|
|
144
|
-
let currentAttempt = retryState ? retryState.retryAttempt : 0;
|
|
145
|
-
let retryId = retryState?.retryId ?? generateRetryId();
|
|
146
|
-
getLogger()?.retryCycleStarting(retryId, currentAttempt);
|
|
147
|
-
const retryEnabled = isDefaultRetryEnabled();
|
|
148
|
-
emitEvent({
|
|
149
|
-
error,
|
|
150
|
-
isRetrying: retryEnabled && currentAttempt >= 0 && currentAttempt < reloadDelays.length,
|
|
151
|
-
name: "chunk-error"
|
|
152
|
-
});
|
|
153
|
-
if (!retryEnabled) {
|
|
154
|
-
reloadScheduled = false;
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (enableRetryReset && retryState && retryState.retryAttempt > 0 && shouldResetRetryCycle(retryState, reloadDelays, minTimeBetweenResets)) {
|
|
158
|
-
const lastReload = getLastReloadTime();
|
|
159
|
-
const timeSinceReload = lastReload ? Date.now() - lastReload.timestamp : 0;
|
|
160
|
-
clearRetryStateFromUrl();
|
|
161
|
-
clearLastReloadTime();
|
|
162
|
-
setLastRetryResetInfo(retryState.retryId);
|
|
163
|
-
const errorMsg2 = String(error);
|
|
164
|
-
emitEvent(
|
|
165
|
-
{
|
|
166
|
-
name: "retry-reset",
|
|
167
|
-
previousAttempt: retryState.retryAttempt,
|
|
168
|
-
previousRetryId: retryState.retryId,
|
|
169
|
-
timeSinceReload
|
|
170
|
-
},
|
|
171
|
-
{ silent: shouldIgnoreMessages([errorMsg2]) }
|
|
172
|
-
);
|
|
173
|
-
currentAttempt = 0;
|
|
174
|
-
retryId = generateRetryId();
|
|
175
|
-
}
|
|
176
|
-
if (currentAttempt === -1) {
|
|
177
|
-
const errorMsg2 = String(error);
|
|
178
|
-
if (!shouldIgnoreMessages([errorMsg2])) {
|
|
179
|
-
getLogger()?.fallbackAlreadyShown(error);
|
|
180
|
-
}
|
|
181
|
-
reloadScheduled = false;
|
|
182
|
-
showFallbackUI();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (currentAttempt >= reloadDelays.length) {
|
|
186
|
-
const errorMsg2 = String(error);
|
|
187
|
-
emitEvent(
|
|
188
|
-
{
|
|
189
|
-
finalAttempt: currentAttempt,
|
|
190
|
-
name: "retry-exhausted",
|
|
191
|
-
retryId: retryState?.retryId ?? ""
|
|
192
|
-
},
|
|
193
|
-
{ silent: shouldIgnoreMessages([errorMsg2]) }
|
|
194
|
-
);
|
|
195
|
-
sendBeacon({
|
|
196
|
-
errorMessage: "Exceeded maximum reload attempts",
|
|
197
|
-
eventName: "chunk_error_max_reloads",
|
|
198
|
-
retryAttempt: currentAttempt,
|
|
199
|
-
retryId: retryState?.retryId,
|
|
200
|
-
serialized: JSON.stringify({
|
|
201
|
-
error: String(error),
|
|
202
|
-
retryAttempt: currentAttempt,
|
|
203
|
-
retryId: retryState?.retryId
|
|
204
|
-
})
|
|
205
|
-
});
|
|
206
|
-
if (!useRetryId) {
|
|
207
|
-
clearRetryAttemptFromUrl();
|
|
208
|
-
}
|
|
209
|
-
reloadScheduled = false;
|
|
210
|
-
showFallbackUI();
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const nextAttempt = currentAttempt + 1;
|
|
214
|
-
const delay = reloadDelays[currentAttempt] ?? 1e3;
|
|
215
|
-
const errorMsg = String(error);
|
|
216
|
-
emitEvent(
|
|
217
|
-
{
|
|
218
|
-
attempt: nextAttempt,
|
|
219
|
-
delay,
|
|
220
|
-
name: "retry-attempt",
|
|
221
|
-
retryId
|
|
222
|
-
},
|
|
223
|
-
{ silent: shouldIgnoreMessages([errorMsg]) }
|
|
224
|
-
);
|
|
225
|
-
getLogger()?.retrySchedulingReload(retryId, nextAttempt, delay);
|
|
226
|
-
showLoadingUI(nextAttempt);
|
|
227
|
-
setTimeout(() => {
|
|
228
|
-
if (useRetryId && enableRetryReset) {
|
|
229
|
-
setLastReloadTime(retryId, nextAttempt);
|
|
230
|
-
}
|
|
231
|
-
if (useRetryId) {
|
|
232
|
-
const reloadUrl = buildReloadUrl(retryId, nextAttempt);
|
|
233
|
-
globalThis.window.location.href = reloadUrl;
|
|
234
|
-
} else {
|
|
235
|
-
globalThis.window.location.href = buildReloadUrlAttemptOnly(nextAttempt);
|
|
236
|
-
}
|
|
237
|
-
}, delay);
|
|
238
|
-
} catch {
|
|
239
|
-
reloadScheduled = false;
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
var showLoadingUI = (attempt) => {
|
|
243
|
-
const options = getOptions();
|
|
244
|
-
const loadingHtml = options.html?.loading?.content;
|
|
245
|
-
const selector = options.html?.fallback?.selector ?? "body";
|
|
246
|
-
if (!loadingHtml) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
try {
|
|
250
|
-
const targetElement = document.querySelector(selector);
|
|
251
|
-
if (!targetElement) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const container = document.createElement("div");
|
|
255
|
-
container.innerHTML = loadingHtml;
|
|
256
|
-
const spinnerEl = container.querySelector("[data-spa-guard-spinner]");
|
|
257
|
-
if (spinnerEl) {
|
|
258
|
-
if (options.spinner?.disabled) {
|
|
259
|
-
spinnerEl.remove();
|
|
260
|
-
} else if (options.spinner?.content) {
|
|
261
|
-
spinnerEl.innerHTML = options.spinner.content;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const retryingSection = container.querySelector(
|
|
265
|
-
'[data-spa-guard-section="retrying"]'
|
|
266
|
-
);
|
|
267
|
-
if (retryingSection) {
|
|
268
|
-
retryingSection.style.display = "block";
|
|
269
|
-
}
|
|
270
|
-
const attemptEl = container.querySelector('[data-spa-guard-content="attempt"]');
|
|
271
|
-
if (attemptEl) {
|
|
272
|
-
attemptEl.textContent = String(attempt);
|
|
273
|
-
}
|
|
274
|
-
const t = getI18n();
|
|
275
|
-
if (t) {
|
|
276
|
-
applyI18n(container, t);
|
|
277
|
-
}
|
|
278
|
-
targetElement.innerHTML = container.innerHTML;
|
|
279
|
-
} catch {
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
var showFallbackUI = () => {
|
|
283
|
-
const options = getOptions();
|
|
284
|
-
const fallbackHtml = options.html?.fallback?.content;
|
|
285
|
-
const selector = options.html?.fallback?.selector ?? "body";
|
|
286
|
-
if (!fallbackHtml) {
|
|
287
|
-
getLogger()?.noFallbackConfigured();
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const targetElement = document.querySelector(selector);
|
|
292
|
-
if (!targetElement) {
|
|
293
|
-
getLogger()?.fallbackTargetNotFound(selector);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const container = document.createElement("div");
|
|
297
|
-
container.innerHTML = fallbackHtml;
|
|
298
|
-
const t = getI18n();
|
|
299
|
-
if (t) {
|
|
300
|
-
applyI18n(container, t);
|
|
301
|
-
}
|
|
302
|
-
targetElement.innerHTML = container.innerHTML;
|
|
303
|
-
const useRetryId = options.useRetryId ?? true;
|
|
304
|
-
const retryState = getRetryStateFromUrl();
|
|
305
|
-
if (retryState && retryState.retryAttempt === -1) {
|
|
306
|
-
getLogger()?.clearingRetryState();
|
|
307
|
-
clearRetryStateFromUrl();
|
|
308
|
-
} else if (!useRetryId && !retryState) {
|
|
309
|
-
clearRetryAttemptFromUrl();
|
|
310
|
-
}
|
|
311
|
-
const reloadBtn = targetElement.querySelector('[data-spa-guard-action="reload"]');
|
|
312
|
-
if (reloadBtn) {
|
|
313
|
-
reloadBtn.addEventListener("click", () => location.reload());
|
|
314
|
-
}
|
|
315
|
-
if (retryState) {
|
|
316
|
-
const retryIdElements = document.getElementsByClassName("spa-guard-retry-id");
|
|
317
|
-
for (const element of retryIdElements) {
|
|
318
|
-
element.textContent = retryState.retryId;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
emitEvent({
|
|
322
|
-
name: "fallback-ui-shown"
|
|
323
|
-
});
|
|
324
|
-
} catch (error) {
|
|
325
|
-
getLogger()?.fallbackInjectFailed(error);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
|
|
329
51
|
// src/common/serializeError.ts
|
|
330
52
|
var serializeError = (error) => {
|
|
331
53
|
try {
|
|
@@ -685,10 +407,6 @@ var createLogger = () => ({
|
|
|
685
407
|
|
|
686
408
|
export {
|
|
687
409
|
isChunkError,
|
|
688
|
-
shouldIgnoreMessages,
|
|
689
|
-
shouldForceRetry,
|
|
690
|
-
sendBeacon,
|
|
691
|
-
attemptReload,
|
|
692
410
|
serializeError,
|
|
693
411
|
listenInternal,
|
|
694
412
|
createLogger
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
showFallbackUI
|
|
3
|
+
} from "./chunk-VZCUDNEG.js";
|
|
1
4
|
import {
|
|
2
5
|
ForceRetryError,
|
|
3
|
-
debugSyncErrorEventType
|
|
4
|
-
|
|
6
|
+
debugSyncErrorEventType,
|
|
7
|
+
emitEvent,
|
|
8
|
+
getOptions
|
|
9
|
+
} from "./chunk-5DE7AKYB.js";
|
|
5
10
|
|
|
6
11
|
// src/runtime/debug/errorDispatchers.ts
|
|
7
12
|
function dispatchAsyncRuntimeError() {
|
|
@@ -29,6 +34,16 @@ function dispatchNetworkTimeout(delayMs = 3e3) {
|
|
|
29
34
|
void Promise.reject(new TypeError("NetworkError: request timed out"));
|
|
30
35
|
}, delayMs);
|
|
31
36
|
}
|
|
37
|
+
function dispatchRetryExhausted() {
|
|
38
|
+
const options = getOptions();
|
|
39
|
+
const reloadDelays = options.reloadDelays ?? [1e3, 2e3, 5e3];
|
|
40
|
+
emitEvent({
|
|
41
|
+
finalAttempt: reloadDelays.length,
|
|
42
|
+
name: "retry-exhausted",
|
|
43
|
+
retryId: ""
|
|
44
|
+
});
|
|
45
|
+
showFallbackUI();
|
|
46
|
+
}
|
|
32
47
|
function dispatchSyncRuntimeError() {
|
|
33
48
|
const error = new Error("Simulated sync runtime error from spa-guard debug panel");
|
|
34
49
|
globalThis.dispatchEvent(new CustomEvent(debugSyncErrorEventType, { detail: { error } }));
|
|
@@ -45,6 +60,7 @@ export {
|
|
|
45
60
|
dispatchFinallyError,
|
|
46
61
|
dispatchForceRetryError,
|
|
47
62
|
dispatchNetworkTimeout,
|
|
63
|
+
dispatchRetryExhausted,
|
|
48
64
|
dispatchSyncRuntimeError,
|
|
49
65
|
dispatchUnhandledRejection
|
|
50
66
|
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FORCE_RETRY_MAGIC,
|
|
3
|
+
RETRY_ATTEMPT_PARAM,
|
|
4
|
+
RETRY_ID_PARAM,
|
|
5
|
+
applyI18n,
|
|
6
|
+
clearLastReloadTime,
|
|
7
|
+
clearRetryAttemptFromUrl,
|
|
8
|
+
clearRetryStateFromUrl,
|
|
9
|
+
emitEvent,
|
|
10
|
+
generateRetryId,
|
|
11
|
+
getI18n,
|
|
12
|
+
getLastReloadTime,
|
|
13
|
+
getLogger,
|
|
14
|
+
getOptions,
|
|
15
|
+
getRetryAttemptFromUrl,
|
|
16
|
+
getRetryStateFromUrl,
|
|
17
|
+
isDefaultRetryEnabled,
|
|
18
|
+
setLastReloadTime,
|
|
19
|
+
setLastRetryResetInfo,
|
|
20
|
+
shouldResetRetryCycle
|
|
21
|
+
} from "./chunk-5DE7AKYB.js";
|
|
22
|
+
|
|
23
|
+
// src/common/shouldIgnore.ts
|
|
24
|
+
var shouldIgnoreMessages = (messages) => {
|
|
25
|
+
const options = getOptions();
|
|
26
|
+
const ignorePatterns = (options.errors?.ignore ?? []).filter((p) => p !== "");
|
|
27
|
+
if (ignorePatterns.length === 0) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const validMessages = messages.filter((msg) => typeof msg === "string");
|
|
31
|
+
return validMessages.some(
|
|
32
|
+
(message) => ignorePatterns.some((pattern) => message.includes(pattern))
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
var shouldForceRetry = (messages) => {
|
|
36
|
+
const options = getOptions();
|
|
37
|
+
const forceRetryPatterns = [...options.errors?.forceRetry ?? [], FORCE_RETRY_MAGIC].filter(
|
|
38
|
+
(p) => p !== ""
|
|
39
|
+
);
|
|
40
|
+
const validMessages = messages.filter((msg) => typeof msg === "string");
|
|
41
|
+
return validMessages.some(
|
|
42
|
+
(message) => forceRetryPatterns.some((pattern) => message.includes(pattern))
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
var shouldIgnoreBeacon = (beacon) => {
|
|
46
|
+
return shouldIgnoreMessages([beacon.errorMessage, beacon.eventMessage]);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/common/sendBeacon.ts
|
|
50
|
+
var sendBeacon = (beacon) => {
|
|
51
|
+
if (shouldIgnoreBeacon(beacon)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const options = getOptions();
|
|
55
|
+
if (!options.reportBeacon?.endpoint) {
|
|
56
|
+
getLogger()?.noBeaconEndpoint();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const enrichedBeacon = options.appName ? { ...beacon, appName: options.appName } : beacon;
|
|
60
|
+
const body = JSON.stringify(enrichedBeacon);
|
|
61
|
+
const isSendBeaconAvailable = typeof globalThis.window?.navigator?.sendBeacon === "function";
|
|
62
|
+
const isSentBeacon = isSendBeaconAvailable && globalThis.window.navigator.sendBeacon(options.reportBeacon.endpoint, body);
|
|
63
|
+
if (!isSentBeacon && typeof fetch === "function") {
|
|
64
|
+
fetch(options.reportBeacon.endpoint, { body, keepalive: true, method: "POST" }).catch(
|
|
65
|
+
(error) => {
|
|
66
|
+
getLogger()?.beaconSendFailed(error);
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/common/reload.ts
|
|
73
|
+
var buildReloadUrl = (retryId, retryAttempt) => {
|
|
74
|
+
const url = new URL(globalThis.window.location.href);
|
|
75
|
+
url.searchParams.set(RETRY_ID_PARAM, retryId);
|
|
76
|
+
url.searchParams.set(RETRY_ATTEMPT_PARAM, String(retryAttempt));
|
|
77
|
+
return url.toString();
|
|
78
|
+
};
|
|
79
|
+
var buildReloadUrlAttemptOnly = (retryAttempt) => {
|
|
80
|
+
const url = new URL(globalThis.window.location.href);
|
|
81
|
+
url.searchParams.set(RETRY_ATTEMPT_PARAM, String(retryAttempt));
|
|
82
|
+
return url.toString();
|
|
83
|
+
};
|
|
84
|
+
var reloadScheduled = false;
|
|
85
|
+
var attemptReload = (error) => {
|
|
86
|
+
if (reloadScheduled) {
|
|
87
|
+
getLogger()?.reloadAlreadyScheduled(error);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
reloadScheduled = true;
|
|
91
|
+
try {
|
|
92
|
+
const options = getOptions();
|
|
93
|
+
const reloadDelays = options.reloadDelays ?? [1e3, 2e3, 5e3];
|
|
94
|
+
const useRetryId = options.useRetryId ?? true;
|
|
95
|
+
const enableRetryReset = options.enableRetryReset ?? true;
|
|
96
|
+
const minTimeBetweenResets = options.minTimeBetweenResets ?? 5e3;
|
|
97
|
+
let retryState;
|
|
98
|
+
if (useRetryId) {
|
|
99
|
+
retryState = getRetryStateFromUrl();
|
|
100
|
+
} else {
|
|
101
|
+
const attempt = getRetryAttemptFromUrl();
|
|
102
|
+
retryState = attempt === null ? null : { retryAttempt: attempt, retryId: generateRetryId() };
|
|
103
|
+
}
|
|
104
|
+
let currentAttempt = retryState ? retryState.retryAttempt : 0;
|
|
105
|
+
let retryId = retryState?.retryId ?? generateRetryId();
|
|
106
|
+
getLogger()?.retryCycleStarting(retryId, currentAttempt);
|
|
107
|
+
const retryEnabled = isDefaultRetryEnabled();
|
|
108
|
+
emitEvent({
|
|
109
|
+
error,
|
|
110
|
+
isRetrying: retryEnabled && currentAttempt >= 0 && currentAttempt < reloadDelays.length,
|
|
111
|
+
name: "chunk-error"
|
|
112
|
+
});
|
|
113
|
+
if (!retryEnabled) {
|
|
114
|
+
reloadScheduled = false;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (enableRetryReset && retryState && retryState.retryAttempt > 0 && shouldResetRetryCycle(retryState, reloadDelays, minTimeBetweenResets)) {
|
|
118
|
+
const lastReload = getLastReloadTime();
|
|
119
|
+
const timeSinceReload = lastReload ? Date.now() - lastReload.timestamp : 0;
|
|
120
|
+
clearRetryStateFromUrl();
|
|
121
|
+
clearLastReloadTime();
|
|
122
|
+
setLastRetryResetInfo(retryState.retryId);
|
|
123
|
+
const errorMsg2 = String(error);
|
|
124
|
+
emitEvent(
|
|
125
|
+
{
|
|
126
|
+
name: "retry-reset",
|
|
127
|
+
previousAttempt: retryState.retryAttempt,
|
|
128
|
+
previousRetryId: retryState.retryId,
|
|
129
|
+
timeSinceReload
|
|
130
|
+
},
|
|
131
|
+
{ silent: shouldIgnoreMessages([errorMsg2]) }
|
|
132
|
+
);
|
|
133
|
+
currentAttempt = 0;
|
|
134
|
+
retryId = generateRetryId();
|
|
135
|
+
}
|
|
136
|
+
if (currentAttempt === -1) {
|
|
137
|
+
const errorMsg2 = String(error);
|
|
138
|
+
if (!shouldIgnoreMessages([errorMsg2])) {
|
|
139
|
+
getLogger()?.fallbackAlreadyShown(error);
|
|
140
|
+
}
|
|
141
|
+
reloadScheduled = false;
|
|
142
|
+
showFallbackUI();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (currentAttempt >= reloadDelays.length) {
|
|
146
|
+
const errorMsg2 = String(error);
|
|
147
|
+
emitEvent(
|
|
148
|
+
{
|
|
149
|
+
finalAttempt: currentAttempt,
|
|
150
|
+
name: "retry-exhausted",
|
|
151
|
+
retryId: retryState?.retryId ?? ""
|
|
152
|
+
},
|
|
153
|
+
{ silent: shouldIgnoreMessages([errorMsg2]) }
|
|
154
|
+
);
|
|
155
|
+
sendBeacon({
|
|
156
|
+
errorMessage: "Exceeded maximum reload attempts",
|
|
157
|
+
eventName: "chunk_error_max_reloads",
|
|
158
|
+
retryAttempt: currentAttempt,
|
|
159
|
+
retryId: retryState?.retryId,
|
|
160
|
+
serialized: JSON.stringify({
|
|
161
|
+
error: String(error),
|
|
162
|
+
retryAttempt: currentAttempt,
|
|
163
|
+
retryId: retryState?.retryId
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
if (!useRetryId) {
|
|
167
|
+
clearRetryAttemptFromUrl();
|
|
168
|
+
}
|
|
169
|
+
reloadScheduled = false;
|
|
170
|
+
showFallbackUI();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const nextAttempt = currentAttempt + 1;
|
|
174
|
+
const delay = reloadDelays[currentAttempt] ?? 1e3;
|
|
175
|
+
const errorMsg = String(error);
|
|
176
|
+
emitEvent(
|
|
177
|
+
{
|
|
178
|
+
attempt: nextAttempt,
|
|
179
|
+
delay,
|
|
180
|
+
name: "retry-attempt",
|
|
181
|
+
retryId
|
|
182
|
+
},
|
|
183
|
+
{ silent: shouldIgnoreMessages([errorMsg]) }
|
|
184
|
+
);
|
|
185
|
+
getLogger()?.retrySchedulingReload(retryId, nextAttempt, delay);
|
|
186
|
+
showLoadingUI(nextAttempt);
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
if (useRetryId && enableRetryReset) {
|
|
189
|
+
setLastReloadTime(retryId, nextAttempt);
|
|
190
|
+
}
|
|
191
|
+
if (useRetryId) {
|
|
192
|
+
const reloadUrl = buildReloadUrl(retryId, nextAttempt);
|
|
193
|
+
globalThis.window.location.href = reloadUrl;
|
|
194
|
+
} else {
|
|
195
|
+
globalThis.window.location.href = buildReloadUrlAttemptOnly(nextAttempt);
|
|
196
|
+
}
|
|
197
|
+
}, delay);
|
|
198
|
+
} catch {
|
|
199
|
+
reloadScheduled = false;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var showLoadingUI = (attempt) => {
|
|
203
|
+
const options = getOptions();
|
|
204
|
+
const loadingHtml = options.html?.loading?.content;
|
|
205
|
+
const selector = options.html?.fallback?.selector ?? "body";
|
|
206
|
+
if (!loadingHtml) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const targetElement = document.querySelector(selector);
|
|
211
|
+
if (!targetElement) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const container = document.createElement("div");
|
|
215
|
+
container.innerHTML = loadingHtml;
|
|
216
|
+
const spinnerEl = container.querySelector("[data-spa-guard-spinner]");
|
|
217
|
+
if (spinnerEl) {
|
|
218
|
+
if (options.spinner?.disabled) {
|
|
219
|
+
spinnerEl.remove();
|
|
220
|
+
} else if (options.spinner?.content) {
|
|
221
|
+
spinnerEl.innerHTML = options.spinner.content;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const retryingSection = container.querySelector(
|
|
225
|
+
'[data-spa-guard-section="retrying"]'
|
|
226
|
+
);
|
|
227
|
+
if (retryingSection) {
|
|
228
|
+
retryingSection.style.display = "block";
|
|
229
|
+
}
|
|
230
|
+
const attemptEl = container.querySelector('[data-spa-guard-content="attempt"]');
|
|
231
|
+
if (attemptEl) {
|
|
232
|
+
attemptEl.textContent = String(attempt);
|
|
233
|
+
}
|
|
234
|
+
const t = getI18n();
|
|
235
|
+
if (t) {
|
|
236
|
+
applyI18n(container, t);
|
|
237
|
+
}
|
|
238
|
+
targetElement.innerHTML = container.innerHTML;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var showFallbackUI = () => {
|
|
243
|
+
const options = getOptions();
|
|
244
|
+
const fallbackHtml = options.html?.fallback?.content;
|
|
245
|
+
const selector = options.html?.fallback?.selector ?? "body";
|
|
246
|
+
if (!fallbackHtml) {
|
|
247
|
+
getLogger()?.noFallbackConfigured();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const targetElement = document.querySelector(selector);
|
|
252
|
+
if (!targetElement) {
|
|
253
|
+
getLogger()?.fallbackTargetNotFound(selector);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const container = document.createElement("div");
|
|
257
|
+
container.innerHTML = fallbackHtml;
|
|
258
|
+
const t = getI18n();
|
|
259
|
+
if (t) {
|
|
260
|
+
applyI18n(container, t);
|
|
261
|
+
}
|
|
262
|
+
targetElement.innerHTML = container.innerHTML;
|
|
263
|
+
const useRetryId = options.useRetryId ?? true;
|
|
264
|
+
const retryState = getRetryStateFromUrl();
|
|
265
|
+
if (retryState && retryState.retryAttempt === -1) {
|
|
266
|
+
getLogger()?.clearingRetryState();
|
|
267
|
+
clearRetryStateFromUrl();
|
|
268
|
+
} else if (!useRetryId && !retryState) {
|
|
269
|
+
clearRetryAttemptFromUrl();
|
|
270
|
+
}
|
|
271
|
+
const reloadBtn = targetElement.querySelector('[data-spa-guard-action="reload"]');
|
|
272
|
+
if (reloadBtn) {
|
|
273
|
+
reloadBtn.addEventListener("click", () => location.reload());
|
|
274
|
+
}
|
|
275
|
+
if (retryState) {
|
|
276
|
+
const retryIdElements = document.getElementsByClassName("spa-guard-retry-id");
|
|
277
|
+
for (const element of retryIdElements) {
|
|
278
|
+
element.textContent = retryState.retryId;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
emitEvent({
|
|
282
|
+
name: "fallback-ui-shown"
|
|
283
|
+
});
|
|
284
|
+
} catch (error) {
|
|
285
|
+
getLogger()?.fallbackInjectFailed(error);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export {
|
|
290
|
+
shouldIgnoreMessages,
|
|
291
|
+
shouldForceRetry,
|
|
292
|
+
sendBeacon,
|
|
293
|
+
attemptReload,
|
|
294
|
+
showFallbackUI
|
|
295
|
+
};
|
package/dist/common/index.js
CHANGED
|
@@ -2,18 +2,17 @@ import {
|
|
|
2
2
|
createLogger,
|
|
3
3
|
listenInternal,
|
|
4
4
|
serializeError
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import
|
|
7
|
-
options_exports
|
|
8
|
-
} from "../chunk-DOTM7FSY.js";
|
|
5
|
+
} from "../chunk-DUR4QNQE.js";
|
|
6
|
+
import "../chunk-VZCUDNEG.js";
|
|
9
7
|
import {
|
|
10
8
|
ForceRetryError,
|
|
11
9
|
disableDefaultRetry,
|
|
12
10
|
emitEvent,
|
|
13
11
|
enableDefaultRetry,
|
|
14
12
|
isDefaultRetryEnabled,
|
|
13
|
+
options_exports,
|
|
15
14
|
subscribe
|
|
16
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-5DE7AKYB.js";
|
|
17
16
|
import {
|
|
18
17
|
__export
|
|
19
18
|
} from "../chunk-MLKGABMK.js";
|
package/dist/common/reload.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare function dispatchForceRetryError(): void;
|
|
|
35
35
|
* Uses setTimeout + void Promise.reject() to trigger window "unhandledrejection".
|
|
36
36
|
*/
|
|
37
37
|
export declare function dispatchNetworkTimeout(delayMs?: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Simulates the retry-exhausted state by emitting the "retry-exhausted" event
|
|
40
|
+
* with finalAttempt equal to the configured reloadDelays length, then renders
|
|
41
|
+
* the fallback UI into the DOM.
|
|
42
|
+
*/
|
|
43
|
+
export declare function dispatchRetryExhausted(): void;
|
|
38
44
|
/**
|
|
39
45
|
* Dispatches a sync runtime error via CustomEvent.
|
|
40
46
|
* DebugSyncErrorTrigger (a React component) listens for this event,
|
|
@@ -4,15 +4,17 @@ import {
|
|
|
4
4
|
dispatchFinallyError,
|
|
5
5
|
dispatchForceRetryError,
|
|
6
6
|
dispatchNetworkTimeout,
|
|
7
|
+
dispatchRetryExhausted,
|
|
7
8
|
dispatchSyncRuntimeError,
|
|
8
9
|
dispatchUnhandledRejection
|
|
9
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-H4DWKO5I.js";
|
|
10
11
|
import {
|
|
11
12
|
subscribeToState
|
|
12
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-CQ5IVYGC.js";
|
|
14
|
+
import "../../chunk-VZCUDNEG.js";
|
|
13
15
|
import {
|
|
14
16
|
subscribe
|
|
15
|
-
} from "../../chunk-
|
|
17
|
+
} from "../../chunk-5DE7AKYB.js";
|
|
16
18
|
import "../../chunk-MLKGABMK.js";
|
|
17
19
|
|
|
18
20
|
// src/runtime/debug/index.ts
|
|
@@ -27,7 +29,8 @@ var SCENARIOS = [
|
|
|
27
29
|
dispatch: dispatchUnhandledRejection,
|
|
28
30
|
key: "unhandled-rejection",
|
|
29
31
|
label: "Unhandled Rejection"
|
|
30
|
-
}
|
|
32
|
+
},
|
|
33
|
+
{ dispatch: dispatchRetryExhausted, key: "exhaust-retries", label: "Exhaust Retries" }
|
|
31
34
|
];
|
|
32
35
|
var POSITION_MAP = {
|
|
33
36
|
"bottom-left": "bottom:16px;left:16px;",
|
package/dist/runtime/index.js
CHANGED
|
@@ -3,19 +3,17 @@ import {
|
|
|
3
3
|
extractVersionFromHtml,
|
|
4
4
|
getSpinnerHtml,
|
|
5
5
|
showSpinner
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import {
|
|
8
|
-
getOptions,
|
|
9
|
-
setTranslations
|
|
10
|
-
} from "../chunk-DOTM7FSY.js";
|
|
6
|
+
} from "../chunk-UF7QAEI7.js";
|
|
11
7
|
import {
|
|
12
8
|
getState,
|
|
13
9
|
subscribeToState
|
|
14
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-CQ5IVYGC.js";
|
|
15
11
|
import {
|
|
16
12
|
ForceRetryError,
|
|
17
|
-
getLogger
|
|
18
|
-
|
|
13
|
+
getLogger,
|
|
14
|
+
getOptions,
|
|
15
|
+
setTranslations
|
|
16
|
+
} from "../chunk-5DE7AKYB.js";
|
|
19
17
|
import "../chunk-MLKGABMK.js";
|
|
20
18
|
|
|
21
19
|
// src/common/checkVersion.ts
|
package/package.json
CHANGED
package/dist/chunk-DOTM7FSY.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
optionsWindowKey
|
|
3
|
-
} from "./chunk-YSKH5K6P.js";
|
|
4
|
-
import {
|
|
5
|
-
__export
|
|
6
|
-
} from "./chunk-MLKGABMK.js";
|
|
7
|
-
|
|
8
|
-
// src/common/i18n.ts
|
|
9
|
-
function applyI18n(container, t) {
|
|
10
|
-
const contentEls = container.querySelectorAll("[data-spa-guard-content]");
|
|
11
|
-
for (const el of contentEls) {
|
|
12
|
-
const key = el.dataset.spaGuardContent;
|
|
13
|
-
if (key && key in t) {
|
|
14
|
-
const value = t[key];
|
|
15
|
-
if (typeof value === "string") {
|
|
16
|
-
el.textContent = value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const actionEls = container.querySelectorAll("[data-spa-guard-action]");
|
|
21
|
-
for (const el of actionEls) {
|
|
22
|
-
const action = el.dataset.spaGuardAction;
|
|
23
|
-
const tKey = action === "try-again" ? "tryAgain" : action;
|
|
24
|
-
if (tKey && tKey in t) {
|
|
25
|
-
const value = t[tKey];
|
|
26
|
-
if (typeof value === "string") {
|
|
27
|
-
el.textContent = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (t.rtl) {
|
|
32
|
-
for (const child of container.children) {
|
|
33
|
-
if (child instanceof HTMLElement && child.tagName !== "STYLE") {
|
|
34
|
-
child.style.direction = "rtl";
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function getI18n() {
|
|
40
|
-
try {
|
|
41
|
-
const el = document.querySelector('meta[name="spa-guard-i18n"]');
|
|
42
|
-
if (!el) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
const content = el.getAttribute("content");
|
|
46
|
-
if (!content) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return JSON.parse(content);
|
|
50
|
-
} catch {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function setTranslations(translations) {
|
|
55
|
-
let el = document.querySelector('meta[name="spa-guard-i18n"]');
|
|
56
|
-
if (!el) {
|
|
57
|
-
el = document.createElement("meta");
|
|
58
|
-
el.setAttribute("name", "spa-guard-i18n");
|
|
59
|
-
document.head.append(el);
|
|
60
|
-
}
|
|
61
|
-
el.setAttribute("content", JSON.stringify(translations));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/common/html.generated.ts
|
|
65
|
-
var defaultErrorFallbackHtml = `<style>.spa-guard-error-id:has(.spa-guard-retry-id:empty){display:none}.spa-guard-error-id{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",Menlo,monospace}</style><div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem;font-family:system-ui,sans-serif"><div style="text-align:center;max-width:480px"><div style="margin-bottom:1.5rem"><svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none" viewBox="0 0 24 24" stroke="#b0b0b0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div><h1 data-spa-guard-content="heading" style="font-size:1.375rem;font-weight:600;margin:0 0 .5rem;color:#1a1a1a;line-height:1.3">Something went wrong</h1><p data-spa-guard-content="message" style="max-width:600px;margin:0 auto 1.5rem;color:#666;font-size:.9375rem;line-height:1.5">Please refresh the page to continue.</p><div style="display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap"><button data-spa-guard-action="try-again" type="button" style="display:none;padding:.5rem 1.25rem;font-size:.875rem;font-family:inherit;border-radius:6px;border:1px solid #d0d0d0;background:#fff;color:#333;cursor:pointer;line-height:1.5">Try again</button> <button data-spa-guard-action="reload" type="button" style="padding:.5rem 1.25rem;font-size:.875rem;font-family:inherit;border-radius:6px;border:1px solid transparent;background:#111;color:#fff;cursor:pointer;line-height:1.5">Reload page</button></div><p class="spa-guard-error-id" style="margin-top:1.5rem;font-size:.6875rem;color:#999">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>`;
|
|
66
|
-
var defaultLoadingFallbackHtml = `<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem;font-family:system-ui,sans-serif"><div style="text-align:center"><div data-spa-guard-spinner style="margin-bottom:1.25rem"></div><h2 data-spa-guard-content="loading" style="font-size:1.125rem;font-weight:600;margin:0 0 .25rem;color:#1a1a1a">Loading...</h2><p data-spa-guard-section="retrying" style="display:none;font-size:.8125rem;color:#999;margin:.5rem 0 0"><span data-spa-guard-content="retrying">Retry attempt</span> <span data-spa-guard-content="attempt"></span></p></div></div>`;
|
|
67
|
-
var defaultSpinnerHtml = `<svg width="40" height="40" viewBox="0 0 40 40" style="animation:spa-guard-spin .8s linear infinite"><circle cx="20" cy="20" r="16" fill="none" stroke="#e8e8e8" stroke-width="3"/><circle cx="20" cy="20" r="16" fill="none" stroke="#666" stroke-width="3" stroke-dasharray="80" stroke-dashoffset="60" stroke-linecap="round"/></svg><style>@keyframes spa-guard-spin{to{transform:rotate(360deg)}}</style>`;
|
|
68
|
-
|
|
69
|
-
// src/common/options.ts
|
|
70
|
-
var options_exports = {};
|
|
71
|
-
__export(options_exports, {
|
|
72
|
-
getOptions: () => getOptions,
|
|
73
|
-
optionsWindowKey: () => optionsWindowKey
|
|
74
|
-
});
|
|
75
|
-
var defaultOptions = {
|
|
76
|
-
checkVersion: {
|
|
77
|
-
interval: 3e5,
|
|
78
|
-
mode: "html",
|
|
79
|
-
onUpdate: "reload"
|
|
80
|
-
},
|
|
81
|
-
enableRetryReset: true,
|
|
82
|
-
errors: {
|
|
83
|
-
forceRetry: [],
|
|
84
|
-
ignore: []
|
|
85
|
-
},
|
|
86
|
-
handleUnhandledRejections: {
|
|
87
|
-
retry: true,
|
|
88
|
-
sendBeacon: true
|
|
89
|
-
},
|
|
90
|
-
html: {
|
|
91
|
-
fallback: {
|
|
92
|
-
content: defaultErrorFallbackHtml,
|
|
93
|
-
selector: "body"
|
|
94
|
-
},
|
|
95
|
-
loading: {
|
|
96
|
-
content: defaultLoadingFallbackHtml
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
lazyRetry: {
|
|
100
|
-
callReloadOnFailure: true,
|
|
101
|
-
retryDelays: [1e3, 2e3]
|
|
102
|
-
},
|
|
103
|
-
minTimeBetweenResets: 5e3,
|
|
104
|
-
reloadDelays: [1e3, 2e3, 5e3],
|
|
105
|
-
spinner: {
|
|
106
|
-
background: "#fff",
|
|
107
|
-
disabled: false
|
|
108
|
-
},
|
|
109
|
-
useRetryId: true
|
|
110
|
-
};
|
|
111
|
-
var getOptions = () => {
|
|
112
|
-
const windowOptions = globalThis.window?.[optionsWindowKey];
|
|
113
|
-
return {
|
|
114
|
-
...defaultOptions,
|
|
115
|
-
...windowOptions,
|
|
116
|
-
checkVersion: {
|
|
117
|
-
...defaultOptions.checkVersion,
|
|
118
|
-
...windowOptions?.checkVersion
|
|
119
|
-
},
|
|
120
|
-
errors: {
|
|
121
|
-
...defaultOptions.errors,
|
|
122
|
-
...windowOptions?.errors
|
|
123
|
-
},
|
|
124
|
-
handleUnhandledRejections: {
|
|
125
|
-
...defaultOptions.handleUnhandledRejections,
|
|
126
|
-
...windowOptions?.handleUnhandledRejections
|
|
127
|
-
},
|
|
128
|
-
html: {
|
|
129
|
-
fallback: {
|
|
130
|
-
...defaultOptions.html?.fallback,
|
|
131
|
-
...windowOptions?.html?.fallback
|
|
132
|
-
},
|
|
133
|
-
loading: {
|
|
134
|
-
...defaultOptions.html?.loading,
|
|
135
|
-
...windowOptions?.html?.loading
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
lazyRetry: {
|
|
139
|
-
...defaultOptions.lazyRetry,
|
|
140
|
-
...windowOptions?.lazyRetry
|
|
141
|
-
},
|
|
142
|
-
reportBeacon: {
|
|
143
|
-
...defaultOptions.reportBeacon,
|
|
144
|
-
...windowOptions?.reportBeacon
|
|
145
|
-
},
|
|
146
|
-
spinner: {
|
|
147
|
-
...defaultOptions.spinner,
|
|
148
|
-
...windowOptions?.spinner
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
export {
|
|
154
|
-
applyI18n,
|
|
155
|
-
getI18n,
|
|
156
|
-
setTranslations,
|
|
157
|
-
defaultErrorFallbackHtml,
|
|
158
|
-
defaultLoadingFallbackHtml,
|
|
159
|
-
defaultSpinnerHtml,
|
|
160
|
-
getOptions,
|
|
161
|
-
options_exports
|
|
162
|
-
};
|