@ovineko/spa-guard 0.0.2-alpha-1 → 0.0.4
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 +22 -173
- package/dist/ForceRetryError-BWLv3UVK.d.mts +6 -0
- package/dist/_internal.d.ts +149 -20
- package/dist/_internal.js +113 -170
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/common/index.d.ts +29 -9
- package/dist/common/index.js +47 -83
- package/dist/errorDispatchers-Cl_pa0DT.mjs +105 -0
- package/dist/i18n/index.d.ts +2 -21
- package/dist/i18n/index.js +344 -341
- package/dist/index-DL8CfPXg.d.mts +26 -0
- package/dist/index-rPxPv6iu.d.mts +16 -0
- package/dist/logger-Cp1Eyk6S.mjs +534 -0
- package/dist/retryOrchestrator-DNGIHV2M.mjs +758 -0
- package/dist/retryOrchestrator-mn9XcIVu.d.mts +257 -0
- package/dist/runtime/debug/index.d.ts +6 -4
- package/dist/runtime/debug/index.js +218 -238
- package/dist/runtime/index.d.ts +66 -8
- package/dist/runtime/index.js +279 -367
- package/dist/schema/index.d.ts +2 -13
- package/dist/schema/index.js +1 -0
- package/dist/schema/parse.d.ts +6 -2
- package/dist/schema/parse.js +29 -44
- package/dist/spinner-BbZVKZ-6.mjs +60 -0
- package/dist/spinner-X23gI09z.d.mts +37 -0
- package/dist/state-I20jENMD.mjs +93 -0
- package/dist/types-DrN8pgyc.d.mts +107 -0
- package/package.json +1 -4
- package/dist/chunk-3UJ67DPX.js +0 -98
- package/dist/chunk-GE63YJOT.js +0 -865
- package/dist/chunk-MLKGABMK.js +0 -9
- package/dist/chunk-PERG4557.js +0 -74
- package/dist/chunk-VZ2DLGXX.js +0 -111
- package/dist/chunk-XIFXSNSD.js +0 -678
- package/dist/common/checkVersion.d.ts +0 -5
- package/dist/common/constants.d.ts +0 -14
- package/dist/common/errors/BeaconError.d.ts +0 -12
- package/dist/common/errors/ForceRetryError.d.ts +0 -5
- package/dist/common/events/index.d.ts +0 -2
- package/dist/common/events/internal.d.ts +0 -13
- package/dist/common/events/types.d.ts +0 -104
- package/dist/common/fallbackRendering.d.ts +0 -23
- package/dist/common/fallbackState.d.ts +0 -3
- package/dist/common/handleErrorWithSpaGuard.d.ts +0 -18
- package/dist/common/html.generated.d.ts +0 -3
- package/dist/common/i18n.d.ts +0 -23
- package/dist/common/isChunkError.d.ts +0 -1
- package/dist/common/isStaticAssetError.d.ts +0 -3
- package/dist/common/lastReloadTime.d.ts +0 -17
- package/dist/common/listen/index.d.ts +0 -1
- package/dist/common/listen/internal.d.ts +0 -2
- package/dist/common/log.d.ts +0 -1
- package/dist/common/logger.d.ts +0 -30
- package/dist/common/options.d.ts +0 -182
- package/dist/common/parseVersion.d.ts +0 -9
- package/dist/common/retryImport.d.ts +0 -43
- package/dist/common/retryOrchestrator.d.ts +0 -35
- package/dist/common/retryState.d.ts +0 -11
- package/dist/common/sendBeacon.d.ts +0 -2
- package/dist/common/serializeError.d.ts +0 -1
- package/dist/common/shouldIgnore.d.ts +0 -13
- package/dist/common/spinner.d.ts +0 -8
- package/dist/common/staticAssetRecovery.d.ts +0 -2
- package/dist/i18n/translations.d.ts +0 -2
- package/dist/runtime/debug/errorDispatchers.d.ts +0 -63
- package/dist/runtime/recommendedSetup.d.ts +0 -36
- package/dist/runtime/state.d.ts +0 -20
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/i18n/translations.d.ts
|
|
2
|
+
declare const translations: Record<string, SpaGuardTranslations>;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/i18n/index.d.ts
|
|
5
|
+
interface SpaGuardTranslations {
|
|
6
|
+
heading: string;
|
|
7
|
+
loading: string;
|
|
8
|
+
message: string;
|
|
9
|
+
reload: string;
|
|
10
|
+
retrying: string;
|
|
11
|
+
rtl?: boolean;
|
|
12
|
+
tryAgain: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Match a language code or Accept-Language header against available translations.
|
|
16
|
+
*
|
|
17
|
+
* - If `input` is undefined, returns `"en"`.
|
|
18
|
+
* - If `input` contains `,` or `;q=`, it's parsed as an Accept-Language header
|
|
19
|
+
* with quality values sorted descending.
|
|
20
|
+
* - Otherwise it's treated as a direct language code.
|
|
21
|
+
*
|
|
22
|
+
* Resolution: exact match → prefix match → `"en"` (or first available if `"en"` not in list).
|
|
23
|
+
*/
|
|
24
|
+
declare function matchLang(input: string | undefined, available?: string[]): string;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { matchLang as n, translations as r, SpaGuardTranslations as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/schema/index.d.ts
|
|
2
|
+
interface BeaconSchema {
|
|
3
|
+
appName?: string;
|
|
4
|
+
errorContext?: string;
|
|
5
|
+
errorMessage?: string;
|
|
6
|
+
errorType?: string;
|
|
7
|
+
eventMessage?: string;
|
|
8
|
+
eventName?: string;
|
|
9
|
+
httpStatus?: number;
|
|
10
|
+
retryAttempt?: number;
|
|
11
|
+
retryId?: string;
|
|
12
|
+
serialized?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BeaconSchema as t };
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { A as markInitialized, D as getLogger, I as staticAssetRecoveryKey, T as emitEvent, a as triggerRetry, c as shouldIgnoreMessages, g as getOptions, j as setLogger, k as isInitialized, m as getRetryInfoForBeacon, o as sendBeacon, s as shouldForceRetry } from "./retryOrchestrator-DNGIHV2M.mjs";
|
|
2
|
+
//#region src/common/isChunkError.ts
|
|
3
|
+
const isChunkError = (error) => {
|
|
4
|
+
const message = getErrorMessage(error);
|
|
5
|
+
if (!message) return false;
|
|
6
|
+
return [
|
|
7
|
+
/Failed to fetch dynamically imported module/i,
|
|
8
|
+
/Importing a module script failed/i,
|
|
9
|
+
/error loading dynamically imported module/i,
|
|
10
|
+
/Unable to preload CSS/i,
|
|
11
|
+
/Loading chunk \d+ failed/i,
|
|
12
|
+
/Loading CSS chunk \d+ failed/i,
|
|
13
|
+
/ChunkLoadError/i
|
|
14
|
+
].some((pattern) => pattern.test(message));
|
|
15
|
+
};
|
|
16
|
+
const getErrorMessage = (error) => {
|
|
17
|
+
if (error instanceof Error) return error.message;
|
|
18
|
+
if (typeof error === "string") return error;
|
|
19
|
+
if (error && typeof error === "object" && "message" in error) return String(error.message);
|
|
20
|
+
if (error && typeof error === "object" && "reason" in error) return getErrorMessage(error.reason);
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/common/serializeError.ts
|
|
25
|
+
const serializeError = (error) => {
|
|
26
|
+
try {
|
|
27
|
+
const serialized = serializeErrorInternal(error);
|
|
28
|
+
return JSON.stringify(serialized, null, 2);
|
|
29
|
+
} catch {
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
error: "Failed to serialize error",
|
|
32
|
+
fallback: String(error)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const MAX_DEPTH = 4;
|
|
37
|
+
const MAX_KEYS = 20;
|
|
38
|
+
const MAX_STRING_LEN = 500;
|
|
39
|
+
const truncate = (str) => str.length > MAX_STRING_LEN ? str.slice(0, MAX_STRING_LEN) + "…" : str;
|
|
40
|
+
const serializeErrorInternal = (error) => {
|
|
41
|
+
if (error === null || error === void 0) return {
|
|
42
|
+
type: "null",
|
|
43
|
+
value: error
|
|
44
|
+
};
|
|
45
|
+
if (typeof error !== "object") return {
|
|
46
|
+
type: typeof error,
|
|
47
|
+
value: error
|
|
48
|
+
};
|
|
49
|
+
if (error instanceof Error) return {
|
|
50
|
+
message: error.message,
|
|
51
|
+
name: error.name,
|
|
52
|
+
stack: error.stack ? truncate(error.stack) : void 0,
|
|
53
|
+
type: "Error",
|
|
54
|
+
...extractErrorProperties(error)
|
|
55
|
+
};
|
|
56
|
+
if ("reason" in error && "promise" in error) {
|
|
57
|
+
const evt = error;
|
|
58
|
+
const reason = evt.reason;
|
|
59
|
+
const pageUrl = typeof window !== "undefined" && typeof window.location?.href === "string" ? window.location.href : void 0;
|
|
60
|
+
return {
|
|
61
|
+
constructorName: reason?.constructor?.name,
|
|
62
|
+
isTrusted: evt.isTrusted,
|
|
63
|
+
pageUrl,
|
|
64
|
+
reason: serializeRejectionReason(reason, /* @__PURE__ */ new WeakSet(), 0),
|
|
65
|
+
timeStamp: evt.timeStamp,
|
|
66
|
+
type: "PromiseRejectionEvent"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if ("error" in error && "message" in error && "filename" in error) return {
|
|
70
|
+
colno: error.colno,
|
|
71
|
+
error: serializeErrorInternal(error.error),
|
|
72
|
+
filename: error.filename,
|
|
73
|
+
lineno: error.lineno,
|
|
74
|
+
message: error.message,
|
|
75
|
+
type: "ErrorEvent"
|
|
76
|
+
};
|
|
77
|
+
if ("violatedDirective" in error && "blockedURI" in error) {
|
|
78
|
+
const evt = error;
|
|
79
|
+
return {
|
|
80
|
+
blockedURI: evt.blockedURI,
|
|
81
|
+
columnNumber: evt.columnNumber,
|
|
82
|
+
effectiveDirective: evt.effectiveDirective,
|
|
83
|
+
lineNumber: evt.lineNumber,
|
|
84
|
+
originalPolicy: evt.originalPolicy,
|
|
85
|
+
sourceFile: evt.sourceFile,
|
|
86
|
+
type: "SecurityPolicyViolationEvent",
|
|
87
|
+
violatedDirective: evt.violatedDirective
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if ("type" in error && "target" in error) {
|
|
91
|
+
const evt = error;
|
|
92
|
+
return {
|
|
93
|
+
eventType: evt.type,
|
|
94
|
+
target: extractEventTarget(evt.target),
|
|
95
|
+
timeStamp: evt.timeStamp,
|
|
96
|
+
type: "Event"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
type: "object",
|
|
101
|
+
value: extractOwnProperties(error)
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const serializeRejectionReason = (reason, visited, depth) => {
|
|
105
|
+
if (reason === null || reason === void 0) return {
|
|
106
|
+
type: "null",
|
|
107
|
+
value: reason
|
|
108
|
+
};
|
|
109
|
+
if (typeof reason !== "object") {
|
|
110
|
+
const val = typeof reason === "string" ? truncate(reason) : reason;
|
|
111
|
+
return {
|
|
112
|
+
type: typeof reason,
|
|
113
|
+
value: val
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (visited.has(reason)) return { type: "circular" };
|
|
117
|
+
visited.add(reason);
|
|
118
|
+
if (typeof AggregateError !== "undefined" && reason instanceof AggregateError) return {
|
|
119
|
+
errors: reason.errors.slice(0, 3).map((e) => serializeSafeError(e, visited, depth + 1)),
|
|
120
|
+
message: truncate(reason.message),
|
|
121
|
+
name: reason.name,
|
|
122
|
+
stack: reason.stack ? truncate(reason.stack) : void 0,
|
|
123
|
+
type: "Error"
|
|
124
|
+
};
|
|
125
|
+
if (typeof DOMException !== "undefined" && reason instanceof DOMException) return {
|
|
126
|
+
code: reason.code,
|
|
127
|
+
message: truncate(reason.message),
|
|
128
|
+
name: reason.name,
|
|
129
|
+
type: "Error"
|
|
130
|
+
};
|
|
131
|
+
const reasonObj = reason;
|
|
132
|
+
if (reasonObj.response != null) {
|
|
133
|
+
const response = reasonObj.response;
|
|
134
|
+
const result = { type: "HttpError" };
|
|
135
|
+
if (response.status !== void 0) result.status = response.status;
|
|
136
|
+
if (response.statusText !== void 0) result.statusText = typeof response.statusText === "string" ? truncate(response.statusText) : response.statusText;
|
|
137
|
+
if (response.url !== void 0) result.url = typeof response.url === "string" ? truncate(response.url) : response.url;
|
|
138
|
+
if (response.method !== void 0) result.method = response.method;
|
|
139
|
+
if (response.type !== void 0) result.responseType = response.type;
|
|
140
|
+
try {
|
|
141
|
+
const headers = response.headers;
|
|
142
|
+
if (headers) {
|
|
143
|
+
let xRequestId;
|
|
144
|
+
if (typeof headers.get === "function") xRequestId = headers.get("X-Request-ID") ?? headers.get("x-request-id");
|
|
145
|
+
else if (typeof headers === "object") xRequestId = headers["X-Request-ID"] ?? headers["x-request-id"];
|
|
146
|
+
if (xRequestId) result.xRequestId = truncate(String(xRequestId));
|
|
147
|
+
}
|
|
148
|
+
} catch {}
|
|
149
|
+
const reqSource = reasonObj.config ?? reasonObj.request;
|
|
150
|
+
if (reqSource != null) {
|
|
151
|
+
const req = {};
|
|
152
|
+
if (reqSource.method !== void 0) req.method = reqSource.method;
|
|
153
|
+
if (reqSource.url !== void 0) req.url = typeof reqSource.url === "string" ? truncate(reqSource.url) : reqSource.url;
|
|
154
|
+
if (reqSource.baseURL !== void 0) req.baseURL = typeof reqSource.baseURL === "string" ? truncate(reqSource.baseURL) : reqSource.baseURL;
|
|
155
|
+
result.request = req;
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
if (reason instanceof Error) return serializeSafeError(reason, visited, depth);
|
|
160
|
+
return {
|
|
161
|
+
type: "object",
|
|
162
|
+
value: extractBoundedObject(reasonObj, visited, depth)
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
const serializeSafeError = (error, visited, depth) => {
|
|
166
|
+
if (!(error instanceof Error)) return serializeRejectionReason(error, visited, depth + 1);
|
|
167
|
+
const result = {
|
|
168
|
+
message: truncate(error.message),
|
|
169
|
+
name: error.name,
|
|
170
|
+
stack: error.stack ? truncate(error.stack) : void 0,
|
|
171
|
+
type: "Error"
|
|
172
|
+
};
|
|
173
|
+
if (depth < MAX_DEPTH && error.cause !== void 0) {
|
|
174
|
+
const cause = error.cause;
|
|
175
|
+
if (typeof cause === "object" && cause !== null) {
|
|
176
|
+
if (!visited.has(cause)) result.cause = serializeRejectionReason(cause, visited, depth + 1);
|
|
177
|
+
} else result.cause = cause;
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
};
|
|
181
|
+
const extractBoundedObject = (obj, visited, depth) => {
|
|
182
|
+
const result = {};
|
|
183
|
+
let keyCount = 0;
|
|
184
|
+
for (const key of Object.keys(obj)) {
|
|
185
|
+
if (keyCount >= MAX_KEYS) break;
|
|
186
|
+
try {
|
|
187
|
+
const value = obj[key];
|
|
188
|
+
if (value === null || value === void 0 || typeof value !== "object") result[key] = typeof value === "string" ? truncate(value) : value;
|
|
189
|
+
else if (depth < MAX_DEPTH && !visited.has(value)) {
|
|
190
|
+
visited.add(value);
|
|
191
|
+
result[key] = extractBoundedObject(value, visited, depth + 1);
|
|
192
|
+
} else result[key] = "[object]";
|
|
193
|
+
} catch {}
|
|
194
|
+
keyCount++;
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
198
|
+
const extractErrorProperties = (error) => {
|
|
199
|
+
const props = {};
|
|
200
|
+
let keyCount = 0;
|
|
201
|
+
for (const key of Object.getOwnPropertyNames(error)) {
|
|
202
|
+
if (keyCount >= MAX_KEYS) break;
|
|
203
|
+
if (![
|
|
204
|
+
"message",
|
|
205
|
+
"name",
|
|
206
|
+
"stack"
|
|
207
|
+
].includes(key)) {
|
|
208
|
+
try {
|
|
209
|
+
const value = error[key];
|
|
210
|
+
if (value === null || value === void 0 || typeof value !== "object") props[key] = typeof value === "string" ? truncate(value) : value;
|
|
211
|
+
else props[key] = "[object]";
|
|
212
|
+
} catch {}
|
|
213
|
+
keyCount++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return props;
|
|
217
|
+
};
|
|
218
|
+
const extractEventTarget = (target) => {
|
|
219
|
+
if (!target) return null;
|
|
220
|
+
if (target instanceof HTMLElement) return {
|
|
221
|
+
className: target.className,
|
|
222
|
+
href: target.href,
|
|
223
|
+
id: target.id,
|
|
224
|
+
src: target.src,
|
|
225
|
+
tagName: target.tagName
|
|
226
|
+
};
|
|
227
|
+
return { type: String(target) };
|
|
228
|
+
};
|
|
229
|
+
const extractOwnProperties = (obj) => {
|
|
230
|
+
const props = {};
|
|
231
|
+
let keyCount = 0;
|
|
232
|
+
for (const key of Object.keys(obj)) {
|
|
233
|
+
if (keyCount >= MAX_KEYS) break;
|
|
234
|
+
try {
|
|
235
|
+
const value = obj[key];
|
|
236
|
+
if (value === null || value === void 0 || typeof value !== "object") props[key] = typeof value === "string" ? truncate(value) : value;
|
|
237
|
+
else props[key] = "[object]";
|
|
238
|
+
} catch {}
|
|
239
|
+
keyCount++;
|
|
240
|
+
}
|
|
241
|
+
return props;
|
|
242
|
+
};
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/common/isStaticAssetError.ts
|
|
245
|
+
const HASHED_ASSET_RE = /[-._][a-zA-Z0-9]{6,}\.(js|mjs|css)$/i;
|
|
246
|
+
const isHashedAssetUrl = (url) => {
|
|
247
|
+
try {
|
|
248
|
+
const pathname = new URL(url).pathname;
|
|
249
|
+
return HASHED_ASSET_RE.test(pathname);
|
|
250
|
+
} catch {
|
|
251
|
+
return HASHED_ASSET_RE.test(url);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const isStaticAssetError = (event) => {
|
|
255
|
+
const target = event.target;
|
|
256
|
+
if (target instanceof HTMLScriptElement) return isHashedAssetUrl(target.src);
|
|
257
|
+
if (target instanceof HTMLLinkElement) return isHashedAssetUrl(target.href);
|
|
258
|
+
return false;
|
|
259
|
+
};
|
|
260
|
+
const checkResourceStatus = (url) => {
|
|
261
|
+
if (typeof performance === "undefined" || typeof performance.getEntriesByName !== "function") return true;
|
|
262
|
+
const entries = performance.getEntriesByName(url, "resource");
|
|
263
|
+
if (entries.length === 0) return true;
|
|
264
|
+
const entry = entries.at(-1);
|
|
265
|
+
if (entry.responseStatus >= 400) return true;
|
|
266
|
+
if (entry.transferSize === 0 && entry.decodedBodySize === 0) return true;
|
|
267
|
+
return false;
|
|
268
|
+
};
|
|
269
|
+
const isLikely404 = (url, timeSinceNavMs) => {
|
|
270
|
+
if (url !== void 0) return checkResourceStatus(url);
|
|
271
|
+
return (timeSinceNavMs ?? (typeof performance === "undefined" ? 0 : performance.now())) > 3e4;
|
|
272
|
+
};
|
|
273
|
+
const getAssetUrl = (event) => {
|
|
274
|
+
const target = event.target;
|
|
275
|
+
if (target instanceof HTMLScriptElement) return target.src;
|
|
276
|
+
if (target instanceof HTMLLinkElement) return target.href;
|
|
277
|
+
return "";
|
|
278
|
+
};
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/common/staticAssetRecovery.ts
|
|
281
|
+
const getState = () => {
|
|
282
|
+
if (globalThis.window === void 0) return {
|
|
283
|
+
failedAssets: /* @__PURE__ */ new Set(),
|
|
284
|
+
recoveryTimer: null
|
|
285
|
+
};
|
|
286
|
+
if (!globalThis.window[staticAssetRecoveryKey]) globalThis.window[staticAssetRecoveryKey] = {
|
|
287
|
+
failedAssets: /* @__PURE__ */ new Set(),
|
|
288
|
+
recoveryTimer: null
|
|
289
|
+
};
|
|
290
|
+
return globalThis.window[staticAssetRecoveryKey];
|
|
291
|
+
};
|
|
292
|
+
const handleStaticAssetFailure = (url) => {
|
|
293
|
+
if (globalThis.window === void 0) return;
|
|
294
|
+
const state = getState();
|
|
295
|
+
state.failedAssets.add(url);
|
|
296
|
+
if (state.recoveryTimer !== null) return;
|
|
297
|
+
const delay = getOptions().staticAssets?.recoveryDelay ?? 500;
|
|
298
|
+
state.recoveryTimer = setTimeout(() => {
|
|
299
|
+
const s = getState();
|
|
300
|
+
s.recoveryTimer = null;
|
|
301
|
+
const assets = [...s.failedAssets];
|
|
302
|
+
s.failedAssets = /* @__PURE__ */ new Set();
|
|
303
|
+
triggerRetry({
|
|
304
|
+
cacheBust: true,
|
|
305
|
+
error: /* @__PURE__ */ new Error(`Static asset load failed: ${assets.join(", ")}`),
|
|
306
|
+
source: "static-asset-error"
|
|
307
|
+
});
|
|
308
|
+
}, delay);
|
|
309
|
+
};
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/common/listen/internal.ts
|
|
312
|
+
const listenInternal = (serializeError, logger) => {
|
|
313
|
+
if (globalThis.window === void 0) return;
|
|
314
|
+
if (isInitialized()) return;
|
|
315
|
+
if (logger) setLogger(logger);
|
|
316
|
+
markInitialized();
|
|
317
|
+
const wa = globalThis.window.addEventListener.bind(globalThis.window);
|
|
318
|
+
wa("error", (event) => {
|
|
319
|
+
const assetUrl = getAssetUrl(event);
|
|
320
|
+
if (isStaticAssetError(event) && isLikely404(assetUrl)) {
|
|
321
|
+
if (shouldIgnoreMessages([assetUrl, event.message])) return;
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
emitEvent({
|
|
324
|
+
name: "static-asset-load-failed",
|
|
325
|
+
url: assetUrl
|
|
326
|
+
});
|
|
327
|
+
if (getOptions().staticAssets?.autoRecover !== false) handleStaticAssetFailure(assetUrl);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (shouldIgnoreMessages([event.message])) return;
|
|
331
|
+
getLogger()?.capturedError("error", event);
|
|
332
|
+
if (isChunkError(event)) {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
triggerRetry({
|
|
335
|
+
error: event.error ?? event,
|
|
336
|
+
source: "chunk-error"
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (shouldForceRetry([event.message])) {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
triggerRetry({
|
|
343
|
+
error: event.error ?? event,
|
|
344
|
+
source: "force-retry"
|
|
345
|
+
});
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const serialized = serializeError(event);
|
|
349
|
+
sendBeacon({
|
|
350
|
+
errorMessage: event.message,
|
|
351
|
+
eventName: "error",
|
|
352
|
+
serialized,
|
|
353
|
+
...getRetryInfoForBeacon()
|
|
354
|
+
});
|
|
355
|
+
}, true);
|
|
356
|
+
wa("unhandledrejection", (event) => {
|
|
357
|
+
const errorMessage = String(event.reason);
|
|
358
|
+
if (shouldIgnoreMessages([errorMessage])) return;
|
|
359
|
+
getLogger()?.capturedError("unhandledrejection", event);
|
|
360
|
+
if (isChunkError(event.reason)) {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
triggerRetry({
|
|
363
|
+
error: event.reason,
|
|
364
|
+
source: "chunk-error"
|
|
365
|
+
});
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (shouldForceRetry([errorMessage])) {
|
|
369
|
+
event.preventDefault();
|
|
370
|
+
triggerRetry({
|
|
371
|
+
error: event.reason,
|
|
372
|
+
source: "force-retry"
|
|
373
|
+
});
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const rejectionConfig = getOptions().handleUnhandledRejections;
|
|
377
|
+
if (rejectionConfig?.sendBeacon !== false) sendBeacon({
|
|
378
|
+
errorMessage,
|
|
379
|
+
eventName: "unhandledrejection",
|
|
380
|
+
serialized: serializeError(event),
|
|
381
|
+
...getRetryInfoForBeacon()
|
|
382
|
+
});
|
|
383
|
+
if (rejectionConfig?.retry !== false) {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
triggerRetry({
|
|
386
|
+
error: event.reason,
|
|
387
|
+
source: "unhandled-rejection"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
wa("securitypolicyviolation", (event) => {
|
|
392
|
+
const eventMessage = `${event.violatedDirective}: ${event.blockedURI}`;
|
|
393
|
+
if (shouldIgnoreMessages([eventMessage])) return;
|
|
394
|
+
getLogger()?.capturedError("csp", event.blockedURI, event.violatedDirective);
|
|
395
|
+
sendBeacon({
|
|
396
|
+
eventMessage,
|
|
397
|
+
eventName: "securitypolicyviolation",
|
|
398
|
+
serialized: serializeError(event),
|
|
399
|
+
...getRetryInfoForBeacon()
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
wa("vite:preloadError", (event) => {
|
|
403
|
+
const payload = event?.payload;
|
|
404
|
+
if (shouldIgnoreMessages([payload?.message || event?.message])) return;
|
|
405
|
+
getLogger()?.capturedError("vite:preloadError", event);
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
triggerRetry({
|
|
408
|
+
error: payload ?? event,
|
|
409
|
+
source: "vite:preloadError"
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
};
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/common/logger.ts
|
|
415
|
+
const PREFIX = "[spa-guard]";
|
|
416
|
+
const eventLogConfig = {
|
|
417
|
+
"chunk-error": "error",
|
|
418
|
+
"fallback-ui-not-rendered": "error",
|
|
419
|
+
"fallback-ui-shown": "warn",
|
|
420
|
+
"lazy-retry-attempt": "warn",
|
|
421
|
+
"lazy-retry-exhausted": "error",
|
|
422
|
+
"lazy-retry-start": "log",
|
|
423
|
+
"lazy-retry-success": "log",
|
|
424
|
+
"retry-attempt": "warn",
|
|
425
|
+
"retry-exhausted": "error",
|
|
426
|
+
"retry-reset": "log",
|
|
427
|
+
"static-asset-load-failed": "error"
|
|
428
|
+
};
|
|
429
|
+
const formatEvent = (event) => {
|
|
430
|
+
switch (event.name) {
|
|
431
|
+
case "chunk-error": return `${PREFIX} chunk-error: isRetrying=${event.isRetrying}`;
|
|
432
|
+
case "fallback-ui-not-rendered": {
|
|
433
|
+
const selectorPart = event.selector ? ` selector=${event.selector}` : "";
|
|
434
|
+
return `${PREFIX} fallback-ui-not-rendered: reason=${event.reason}${selectorPart}`;
|
|
435
|
+
}
|
|
436
|
+
case "fallback-ui-shown": return `${PREFIX} fallback-ui-shown`;
|
|
437
|
+
case "lazy-retry-attempt": return `${PREFIX} lazy-retry-attempt: attempt ${event.attempt}/${event.totalAttempts}, delay ${event.delay}ms`;
|
|
438
|
+
case "lazy-retry-exhausted": return `${PREFIX} lazy-retry-exhausted: ${event.totalAttempts} attempts, willReload=${event.willReload}`;
|
|
439
|
+
case "lazy-retry-start": return `${PREFIX} lazy-retry-start: totalAttempts=${event.totalAttempts}`;
|
|
440
|
+
case "lazy-retry-success": {
|
|
441
|
+
const timePart = event.totalTime === void 0 ? "" : `, totalTime=${event.totalTime}ms`;
|
|
442
|
+
return `${PREFIX} lazy-retry-success: succeeded on attempt ${event.attempt}${timePart}`;
|
|
443
|
+
}
|
|
444
|
+
case "retry-attempt": return `${PREFIX} retry-attempt: attempt ${event.attempt} in ${event.delay}ms (retryId: ${event.retryId})`;
|
|
445
|
+
case "retry-exhausted": return `${PREFIX} retry-exhausted: finalAttempt=${event.finalAttempt} (retryId: ${event.retryId})`;
|
|
446
|
+
case "retry-reset": return `${PREFIX} retry-reset: ${event.timeSinceReload}ms since last reload (retryId: ${event.previousRetryId})`;
|
|
447
|
+
case "static-asset-load-failed": return `${PREFIX} static-asset-load-failed: ${event.url}`;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const createLogger = () => ({
|
|
451
|
+
beaconSendFailed(error) {
|
|
452
|
+
console.error(`${PREFIX} Failed to send beacon:`, error);
|
|
453
|
+
},
|
|
454
|
+
capturedError(type, ...args) {
|
|
455
|
+
console.error(`${PREFIX} ${type}:capture:`, ...args);
|
|
456
|
+
},
|
|
457
|
+
error(msg, ...args) {
|
|
458
|
+
console.error(`${PREFIX} ${msg}`, ...args);
|
|
459
|
+
},
|
|
460
|
+
fallbackAlreadyShown(error) {
|
|
461
|
+
console.error(`${PREFIX} Fallback UI was already shown. Not retrying to prevent infinite loop.`, error);
|
|
462
|
+
},
|
|
463
|
+
fallbackInjectFailed(error) {
|
|
464
|
+
console.error(`${PREFIX} Failed to inject fallback UI`, error);
|
|
465
|
+
},
|
|
466
|
+
fallbackTargetNotFound(selector) {
|
|
467
|
+
console.error(`${PREFIX} Target element not found for selector: ${selector}`);
|
|
468
|
+
},
|
|
469
|
+
log(msg, ...args) {
|
|
470
|
+
console.log(`${PREFIX} ${msg}`, ...args);
|
|
471
|
+
},
|
|
472
|
+
logEvent(event) {
|
|
473
|
+
const level = eventLogConfig[event.name];
|
|
474
|
+
const message = formatEvent(event);
|
|
475
|
+
if (event.name === "chunk-error" || event.name === "lazy-retry-attempt" || event.name === "lazy-retry-exhausted") console[level](message, event.error);
|
|
476
|
+
else console[level](message);
|
|
477
|
+
},
|
|
478
|
+
noBeaconEndpoint() {
|
|
479
|
+
console.warn(`${PREFIX} Report endpoint is not configured`);
|
|
480
|
+
},
|
|
481
|
+
noFallbackConfigured() {
|
|
482
|
+
console.error(`${PREFIX} No fallback UI configured`);
|
|
483
|
+
},
|
|
484
|
+
reloadAlreadyScheduled(error) {
|
|
485
|
+
console.log(`${PREFIX} Reload already scheduled, ignoring duplicate chunk error:`, error);
|
|
486
|
+
},
|
|
487
|
+
retryCycleStarting(retryId, fromAttempt) {
|
|
488
|
+
console.log(`${PREFIX} Retry cycle starting: retryId=${retryId}, fromAttempt=${fromAttempt}`);
|
|
489
|
+
},
|
|
490
|
+
retrySchedulingReload(retryId, attempt, delay) {
|
|
491
|
+
console.log(`${PREFIX} Scheduling reload: retryId=${retryId}, attempt=${attempt}, delay=${delay}ms`);
|
|
492
|
+
},
|
|
493
|
+
versionChangeDetected(oldVersion, latestVersion) {
|
|
494
|
+
console.warn(`${PREFIX} New version available (${oldVersion ?? "unknown"} → ${latestVersion}). Please refresh to get the latest version.`);
|
|
495
|
+
},
|
|
496
|
+
versionCheckAlreadyRunning() {
|
|
497
|
+
console.warn(`${PREFIX} Version check already running`);
|
|
498
|
+
},
|
|
499
|
+
versionCheckDisabled() {
|
|
500
|
+
console.warn(`${PREFIX} Version checking disabled: no version configured`);
|
|
501
|
+
},
|
|
502
|
+
versionCheckFailed(error) {
|
|
503
|
+
console.error(`${PREFIX} Version check failed`, error);
|
|
504
|
+
},
|
|
505
|
+
versionCheckHttpError(status) {
|
|
506
|
+
console.warn(`${PREFIX} Version check HTTP error: ${status}`);
|
|
507
|
+
},
|
|
508
|
+
versionCheckParseError() {
|
|
509
|
+
console.warn(`${PREFIX} Failed to parse version from HTML`);
|
|
510
|
+
},
|
|
511
|
+
versionCheckPaused() {
|
|
512
|
+
console.log(`${PREFIX} Version check paused (tab hidden)`);
|
|
513
|
+
},
|
|
514
|
+
versionCheckRequiresEndpoint() {
|
|
515
|
+
console.warn(`${PREFIX} JSON version check mode requires endpoint`);
|
|
516
|
+
},
|
|
517
|
+
versionCheckResumed() {
|
|
518
|
+
console.log(`${PREFIX} Version check resumed (tab visible)`);
|
|
519
|
+
},
|
|
520
|
+
versionCheckResumedImmediate() {
|
|
521
|
+
console.log(`${PREFIX} Version check resumed with immediate check (tab visible, interval elapsed)`);
|
|
522
|
+
},
|
|
523
|
+
versionCheckStarted(mode, interval, version) {
|
|
524
|
+
console.log(`${PREFIX} Starting version check (mode: ${mode}, interval: ${interval}ms, current: ${version})`);
|
|
525
|
+
},
|
|
526
|
+
versionCheckStopped() {
|
|
527
|
+
console.log(`${PREFIX} Version check stopped`);
|
|
528
|
+
},
|
|
529
|
+
warn(msg, ...args) {
|
|
530
|
+
console.warn(`${PREFIX} ${msg}`, ...args);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
//#endregion
|
|
534
|
+
export { isChunkError as i, listenInternal as n, serializeError as r, createLogger as t };
|