@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
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
errorContext?: string;
|
|
4
|
-
errorMessage?: string;
|
|
5
|
-
errorType?: string;
|
|
6
|
-
eventMessage?: string;
|
|
7
|
-
eventName?: string;
|
|
8
|
-
httpStatus?: number;
|
|
9
|
-
retryAttempt?: number;
|
|
10
|
-
retryId?: string;
|
|
11
|
-
serialized?: string;
|
|
12
|
-
url?: string;
|
|
13
|
-
}
|
|
1
|
+
import { t as BeaconSchema } from "../index-rPxPv6iu.mjs";
|
|
2
|
+
export { BeaconSchema };
|
package/dist/schema/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/schema/parse.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { t as BeaconSchema } from "../index-rPxPv6iu.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/schema/parse.d.ts
|
|
4
|
+
declare function parseBeacon(data: unknown): BeaconSchema;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { parseBeacon };
|
package/dist/schema/parse.js
CHANGED
|
@@ -1,47 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"serialized",
|
|
13
|
-
"url"
|
|
1
|
+
//#region src/schema/parse.ts
|
|
2
|
+
const STRING_FIELDS = [
|
|
3
|
+
"appName",
|
|
4
|
+
"errorContext",
|
|
5
|
+
"errorMessage",
|
|
6
|
+
"errorType",
|
|
7
|
+
"eventMessage",
|
|
8
|
+
"eventName",
|
|
9
|
+
"retryId",
|
|
10
|
+
"serialized",
|
|
11
|
+
"url"
|
|
14
12
|
];
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const MAX_STRING_FIELD_LENGTH = 500;
|
|
14
|
+
const MAX_SERIALIZED_LENGTH = 1e4;
|
|
17
15
|
function parseBeacon(data) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
result[field] = d[field];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
for (const field of ["retryAttempt", "httpStatus"]) {
|
|
36
|
-
if (field in d) {
|
|
37
|
-
if (typeof d[field] !== "number" || !Number.isFinite(d[field])) {
|
|
38
|
-
throw new TypeError(`Beacon validation failed: ${field} must be a finite number`);
|
|
39
|
-
}
|
|
40
|
-
result[field] = d[field];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return result;
|
|
16
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("Invalid beacon");
|
|
17
|
+
const d = data;
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const field of STRING_FIELDS) if (field in d) {
|
|
20
|
+
if (typeof d[field] !== "string") throw new TypeError(`Beacon validation failed: ${field} must be a string`);
|
|
21
|
+
const maxLen = field === "serialized" ? MAX_SERIALIZED_LENGTH : MAX_STRING_FIELD_LENGTH;
|
|
22
|
+
if (d[field].length > maxLen) throw new TypeError(`Beacon validation failed: ${field} exceeds maximum length`);
|
|
23
|
+
result[field] = d[field];
|
|
24
|
+
}
|
|
25
|
+
for (const field of ["retryAttempt", "httpStatus"]) if (field in d) {
|
|
26
|
+
if (typeof d[field] !== "number" || !Number.isFinite(d[field])) throw new TypeError(`Beacon validation failed: ${field} must be a finite number`);
|
|
27
|
+
result[field] = d[field];
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
44
30
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { parseBeacon };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { F as spinnerStateWindowKey, b as defaultSpinnerHtml, g as getOptions } from "./retryOrchestrator-DNGIHV2M.mjs";
|
|
2
|
+
//#region src/common/parseVersion.ts
|
|
3
|
+
/**
|
|
4
|
+
* Extract the SPA Guard version string from an HTML document.
|
|
5
|
+
*
|
|
6
|
+
* Tries the new `__SPA_GUARD_VERSION__` format first, then falls back
|
|
7
|
+
* to the legacy `__SPA_GUARD_OPTIONS__` object with a `version` key.
|
|
8
|
+
*
|
|
9
|
+
* Returns `null` when neither marker is found.
|
|
10
|
+
*/
|
|
11
|
+
function extractVersionFromHtml(html) {
|
|
12
|
+
const collapsed = html.replaceAll(/[\r\n]+/g, "");
|
|
13
|
+
const versionMatch = collapsed.match(/__SPA_GUARD_VERSION__\s*=\s*"([^"]+)"/);
|
|
14
|
+
if (versionMatch?.[1]) return versionMatch[1];
|
|
15
|
+
const markerIdx = collapsed.indexOf("__SPA_GUARD_OPTIONS__");
|
|
16
|
+
if (markerIdx === -1) return null;
|
|
17
|
+
return collapsed.slice(markerIdx, markerIdx + 1e3).match(/__SPA_GUARD_OPTIONS__\s*=\s*\{.*?"?version"?\s*:\s*"([^"]+)"/)?.[1] ?? null;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/common/spinner.ts
|
|
21
|
+
const SPINNER_ID = "__spa-guard-spinner";
|
|
22
|
+
const defaultSpinnerSvg = defaultSpinnerHtml;
|
|
23
|
+
const getState = () => {
|
|
24
|
+
if (!globalThis.window[spinnerStateWindowKey]) globalThis.window[spinnerStateWindowKey] = { savedOverflow: null };
|
|
25
|
+
return globalThis.window[spinnerStateWindowKey];
|
|
26
|
+
};
|
|
27
|
+
function dismissSpinner() {
|
|
28
|
+
if (typeof document === "undefined") return;
|
|
29
|
+
const el = document.getElementById(SPINNER_ID);
|
|
30
|
+
if (el) el.remove();
|
|
31
|
+
const state = getState();
|
|
32
|
+
if (state.savedOverflow !== null) {
|
|
33
|
+
document.body.style.overflow = state.savedOverflow;
|
|
34
|
+
state.savedOverflow = null;
|
|
35
|
+
} else if (el) document.body.style.overflow = "";
|
|
36
|
+
}
|
|
37
|
+
const sanitizeCssValue = (value) => value.replaceAll(/["'<>\\{};\n]/g, "");
|
|
38
|
+
function getSpinnerHtml(backgroundOverride) {
|
|
39
|
+
const opts = getOptions();
|
|
40
|
+
if (opts.html?.spinner?.disabled) return "";
|
|
41
|
+
const spinnerContent = opts.html?.spinner?.content ?? defaultSpinnerSvg;
|
|
42
|
+
return `<div id="${SPINNER_ID}" style="position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:var(--spa-guard-spinner-bg,${sanitizeCssValue(backgroundOverride ?? opts.html?.spinner?.background ?? "#fff")})">${spinnerContent}</div>`;
|
|
43
|
+
}
|
|
44
|
+
function showSpinner(options) {
|
|
45
|
+
if (typeof document === "undefined") return () => {};
|
|
46
|
+
if (getOptions().html?.spinner?.disabled) return () => {};
|
|
47
|
+
const existing = document.getElementById(SPINNER_ID);
|
|
48
|
+
if (existing) existing.remove();
|
|
49
|
+
const html = getSpinnerHtml(options?.background);
|
|
50
|
+
if (!html) return () => {};
|
|
51
|
+
const wrapper = document.createElement("div");
|
|
52
|
+
wrapper.innerHTML = html;
|
|
53
|
+
const overlay = wrapper.firstElementChild;
|
|
54
|
+
if (!existing) getState().savedOverflow = document.body.style.overflow;
|
|
55
|
+
document.body.style.overflow = "hidden";
|
|
56
|
+
document.body.append(overlay);
|
|
57
|
+
return () => dismissSpinner();
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { sanitizeCssValue as a, getSpinnerHtml as i, defaultSpinnerSvg as n, showSpinner as o, dismissSpinner as r, extractVersionFromHtml as s, SPINNER_ID as t };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { t as SpaGuardTranslations } from "./index-DL8CfPXg.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/common/i18n.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Apply i18n translations to a virtual container's data-attributed elements.
|
|
6
|
+
* Patches `[data-spa-guard-content]` and `[data-spa-guard-action]` elements,
|
|
7
|
+
* and applies RTL direction if needed.
|
|
8
|
+
*
|
|
9
|
+
* Must be called on a virtual (detached) container BEFORE inserting into DOM
|
|
10
|
+
* to avoid flash of untranslated content.
|
|
11
|
+
*/
|
|
12
|
+
declare function applyI18n(container: HTMLElement, t: SpaGuardTranslations): void;
|
|
13
|
+
/**
|
|
14
|
+
* Read i18n translations from the `<meta name="spa-guard-i18n">` tag.
|
|
15
|
+
* Returns parsed translations or null if the tag is absent or malformed.
|
|
16
|
+
*/
|
|
17
|
+
declare function getI18n(): null | SpaGuardTranslations;
|
|
18
|
+
/**
|
|
19
|
+
* Write i18n translations to the `<meta name="spa-guard-i18n">` tag.
|
|
20
|
+
* Creates the tag if it doesn't exist, or updates it if it does.
|
|
21
|
+
*
|
|
22
|
+
* Use this at runtime to dynamically patch the inline fallback/loading UI
|
|
23
|
+
* translations without server-side rendering.
|
|
24
|
+
*/
|
|
25
|
+
declare function setTranslations(translations: SpaGuardTranslations): void;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/common/spinner.d.ts
|
|
28
|
+
declare const SPINNER_ID = "__spa-guard-spinner";
|
|
29
|
+
declare const defaultSpinnerSvg = "<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>";
|
|
30
|
+
declare function dismissSpinner(): void;
|
|
31
|
+
declare const sanitizeCssValue: (value: string) => string;
|
|
32
|
+
declare function getSpinnerHtml(backgroundOverride?: string): string;
|
|
33
|
+
declare function showSpinner(options?: {
|
|
34
|
+
background?: string;
|
|
35
|
+
}): () => void;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { sanitizeCssValue as a, getI18n as c, getSpinnerHtml as i, setTranslations as l, defaultSpinnerSvg as n, showSpinner as o, dismissSpinner as r, applyI18n as s, SPINNER_ID as t };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { M as subscribe, h as getRetryStateFromUrl, p as getRetryAttemptFromUrl, u as getLastRetryResetInfo } from "./retryOrchestrator-DNGIHV2M.mjs";
|
|
2
|
+
//#region src/runtime/state.ts
|
|
3
|
+
const getInitialStateFromUrl = () => {
|
|
4
|
+
const resetInfo = getLastRetryResetInfo();
|
|
5
|
+
const resetInfoSpread = {
|
|
6
|
+
...resetInfo?.previousRetryId !== void 0 && { lastResetRetryId: resetInfo.previousRetryId },
|
|
7
|
+
...resetInfo?.timestamp !== void 0 && { lastRetryResetTime: resetInfo.timestamp }
|
|
8
|
+
};
|
|
9
|
+
if (globalThis.window === void 0) return {
|
|
10
|
+
currentAttempt: 0,
|
|
11
|
+
isFallbackShown: false,
|
|
12
|
+
isWaiting: false,
|
|
13
|
+
...resetInfoSpread
|
|
14
|
+
};
|
|
15
|
+
const retryState = getRetryStateFromUrl();
|
|
16
|
+
if (!retryState) {
|
|
17
|
+
const attempt = getRetryAttemptFromUrl();
|
|
18
|
+
if (attempt !== null) return {
|
|
19
|
+
currentAttempt: attempt,
|
|
20
|
+
isFallbackShown: false,
|
|
21
|
+
isWaiting: false,
|
|
22
|
+
...resetInfoSpread
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
currentAttempt: 0,
|
|
26
|
+
isFallbackShown: false,
|
|
27
|
+
isWaiting: false,
|
|
28
|
+
...resetInfoSpread
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
currentAttempt: retryState.retryAttempt,
|
|
33
|
+
isFallbackShown: false,
|
|
34
|
+
isWaiting: false,
|
|
35
|
+
...resetInfoSpread
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
let currentState = getInitialStateFromUrl();
|
|
39
|
+
const stateSubscribers = /* @__PURE__ */ new Set();
|
|
40
|
+
const updateState = (nextState) => {
|
|
41
|
+
currentState = nextState;
|
|
42
|
+
stateSubscribers.forEach((cb) => {
|
|
43
|
+
try {
|
|
44
|
+
cb(currentState);
|
|
45
|
+
} catch {}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
subscribe((event) => {
|
|
49
|
+
switch (event.name) {
|
|
50
|
+
case "fallback-ui-shown":
|
|
51
|
+
updateState({
|
|
52
|
+
...currentState,
|
|
53
|
+
isFallbackShown: true
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
case "retry-attempt":
|
|
57
|
+
updateState({
|
|
58
|
+
...currentState,
|
|
59
|
+
currentAttempt: event.attempt,
|
|
60
|
+
isFallbackShown: false,
|
|
61
|
+
isWaiting: true
|
|
62
|
+
});
|
|
63
|
+
break;
|
|
64
|
+
case "retry-exhausted":
|
|
65
|
+
updateState({
|
|
66
|
+
...currentState,
|
|
67
|
+
currentAttempt: event.finalAttempt,
|
|
68
|
+
isFallbackShown: false,
|
|
69
|
+
isWaiting: false
|
|
70
|
+
});
|
|
71
|
+
break;
|
|
72
|
+
case "retry-reset":
|
|
73
|
+
updateState({
|
|
74
|
+
...currentState,
|
|
75
|
+
currentAttempt: 0,
|
|
76
|
+
isFallbackShown: false,
|
|
77
|
+
isWaiting: false,
|
|
78
|
+
lastResetRetryId: event.previousRetryId,
|
|
79
|
+
lastRetryResetTime: Date.now()
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const getState = () => currentState;
|
|
85
|
+
const subscribeToState = (cb) => {
|
|
86
|
+
cb(currentState);
|
|
87
|
+
stateSubscribers.add(cb);
|
|
88
|
+
return () => {
|
|
89
|
+
stateSubscribers.delete(cb);
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
93
|
+
export { subscribeToState as n, getState as t };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//#region src/common/events/types.d.ts
|
|
2
|
+
interface EmitOptions {
|
|
3
|
+
silent?: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface InternalConfig {
|
|
6
|
+
defaultRetryEnabled: boolean;
|
|
7
|
+
initialized: boolean;
|
|
8
|
+
}
|
|
9
|
+
type SPAGuardEvent = (SPAGuardEventChunkError & {
|
|
10
|
+
name: "chunk-error";
|
|
11
|
+
}) | (SPAGuardEventFallbackUINotRendered & {
|
|
12
|
+
name: "fallback-ui-not-rendered";
|
|
13
|
+
}) | (SPAGuardEventFallbackUIShown & {
|
|
14
|
+
name: "fallback-ui-shown";
|
|
15
|
+
}) | (SPAGuardEventLazyRetryAttempt & {
|
|
16
|
+
name: "lazy-retry-attempt";
|
|
17
|
+
}) | (SPAGuardEventLazyRetryExhausted & {
|
|
18
|
+
name: "lazy-retry-exhausted";
|
|
19
|
+
}) | (SPAGuardEventLazyRetryStart & {
|
|
20
|
+
name: "lazy-retry-start";
|
|
21
|
+
}) | (SPAGuardEventLazyRetrySuccess & {
|
|
22
|
+
name: "lazy-retry-success";
|
|
23
|
+
}) | (SPAGuardEventRetryAttempt & {
|
|
24
|
+
name: "retry-attempt";
|
|
25
|
+
}) | (SPAGuardEventRetryExhausted & {
|
|
26
|
+
name: "retry-exhausted";
|
|
27
|
+
}) | (SPAGuardEventRetryReset & {
|
|
28
|
+
name: "retry-reset";
|
|
29
|
+
}) | (SPAGuardEventStaticAssetLoadFailed & {
|
|
30
|
+
name: "static-asset-load-failed";
|
|
31
|
+
});
|
|
32
|
+
interface SPAGuardEventChunkError {
|
|
33
|
+
error: unknown;
|
|
34
|
+
isRetrying: boolean;
|
|
35
|
+
name: "chunk-error";
|
|
36
|
+
}
|
|
37
|
+
interface SPAGuardEventFallbackUINotRendered {
|
|
38
|
+
name: "fallback-ui-not-rendered";
|
|
39
|
+
reason: "no-html-configured" | "target-not-found";
|
|
40
|
+
selector?: string;
|
|
41
|
+
}
|
|
42
|
+
interface SPAGuardEventFallbackUIShown {
|
|
43
|
+
name: "fallback-ui-shown";
|
|
44
|
+
}
|
|
45
|
+
/** Emitted before each module-level retry attempt initiated by retryImport. */
|
|
46
|
+
interface SPAGuardEventLazyRetryAttempt {
|
|
47
|
+
/** 1-based index of the current retry attempt. */
|
|
48
|
+
attempt: number;
|
|
49
|
+
/** Delay in milliseconds before this retry attempt. */
|
|
50
|
+
delay: number;
|
|
51
|
+
/** The error that caused the previous attempt to fail. */
|
|
52
|
+
error?: unknown;
|
|
53
|
+
name: "lazy-retry-attempt";
|
|
54
|
+
/** Total number of attempts including the initial try (delays.length + 1). */
|
|
55
|
+
totalAttempts: number;
|
|
56
|
+
}
|
|
57
|
+
/** Emitted when all module-level retry attempts are exhausted. */
|
|
58
|
+
interface SPAGuardEventLazyRetryExhausted {
|
|
59
|
+
/** The final error after all attempts failed. */
|
|
60
|
+
error: unknown;
|
|
61
|
+
name: "lazy-retry-exhausted";
|
|
62
|
+
/** Total number of attempts that were made (delays.length + 1). */
|
|
63
|
+
totalAttempts: number;
|
|
64
|
+
/** Whether triggerRetry() will be called after this event. */
|
|
65
|
+
willReload: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Emitted once before the first import attempt, when retries are configured. */
|
|
68
|
+
interface SPAGuardEventLazyRetryStart {
|
|
69
|
+
name: "lazy-retry-start";
|
|
70
|
+
/** Total number of attempts that will be made (delays.length + 1). */
|
|
71
|
+
totalAttempts: number;
|
|
72
|
+
}
|
|
73
|
+
/** Emitted when a module loads successfully after one or more retry attempts. */
|
|
74
|
+
interface SPAGuardEventLazyRetrySuccess {
|
|
75
|
+
/** 1-based retry number on which the import succeeded (1 = first retry). */
|
|
76
|
+
attempt: number;
|
|
77
|
+
name: "lazy-retry-success";
|
|
78
|
+
/** Total time in milliseconds from first attempt to success. */
|
|
79
|
+
totalTime?: number;
|
|
80
|
+
}
|
|
81
|
+
interface SPAGuardEventRetryAttempt {
|
|
82
|
+
attempt: number;
|
|
83
|
+
delay: number;
|
|
84
|
+
name: "retry-attempt";
|
|
85
|
+
retryId: string;
|
|
86
|
+
}
|
|
87
|
+
interface SPAGuardEventRetryExhausted {
|
|
88
|
+
finalAttempt: number;
|
|
89
|
+
name: "retry-exhausted";
|
|
90
|
+
retryId: string;
|
|
91
|
+
}
|
|
92
|
+
interface SPAGuardEventRetryReset {
|
|
93
|
+
name: "retry-reset";
|
|
94
|
+
previousAttempt: number;
|
|
95
|
+
previousRetryId: string;
|
|
96
|
+
timeSinceReload: number;
|
|
97
|
+
}
|
|
98
|
+
/** Emitted when a hashed static asset (script/link) fails to load, likely due to a stale deployment. */
|
|
99
|
+
interface SPAGuardEventStaticAssetLoadFailed {
|
|
100
|
+
name: "static-asset-load-failed";
|
|
101
|
+
/** The URL of the asset that failed to load. */
|
|
102
|
+
url: string;
|
|
103
|
+
}
|
|
104
|
+
type SubscribeFn = (event: SPAGuardEvent) => void;
|
|
105
|
+
type UnsubscribeFn = () => void;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { SPAGuardEventFallbackUINotRendered as a, SPAGuardEventLazyRetryExhausted as c, SPAGuardEventRetryAttempt as d, SPAGuardEventRetryExhausted as f, UnsubscribeFn as g, SubscribeFn as h, SPAGuardEventChunkError as i, SPAGuardEventLazyRetryStart as l, SPAGuardEventStaticAssetLoadFailed as m, InternalConfig as n, SPAGuardEventFallbackUIShown as o, SPAGuardEventRetryReset as p, SPAGuardEvent as r, SPAGuardEventLazyRetryAttempt as s, EmitOptions as t, SPAGuardEventLazyRetrySuccess as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ovineko/spa-guard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Chunk load error handling for SPAs — core runtime, error handling, schema, i18n",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spa",
|
|
@@ -61,9 +61,6 @@
|
|
|
61
61
|
"files": [
|
|
62
62
|
"dist"
|
|
63
63
|
],
|
|
64
|
-
"engines": {
|
|
65
|
-
"node": ">=22.15.0"
|
|
66
|
-
},
|
|
67
64
|
"publishConfig": {
|
|
68
65
|
"access": "public"
|
|
69
66
|
}
|
package/dist/chunk-3UJ67DPX.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defaultSpinnerHtml,
|
|
3
|
-
getOptions,
|
|
4
|
-
spinnerStateWindowKey
|
|
5
|
-
} from "./chunk-GE63YJOT.js";
|
|
6
|
-
|
|
7
|
-
// src/common/parseVersion.ts
|
|
8
|
-
function extractVersionFromHtml(html) {
|
|
9
|
-
const collapsed = html.replaceAll(/[\r\n]+/g, "");
|
|
10
|
-
const versionMatch = collapsed.match(/__SPA_GUARD_VERSION__\s*=\s*"([^"]+)"/);
|
|
11
|
-
if (versionMatch?.[1]) {
|
|
12
|
-
return versionMatch[1];
|
|
13
|
-
}
|
|
14
|
-
const markerIdx = collapsed.indexOf("__SPA_GUARD_OPTIONS__");
|
|
15
|
-
if (markerIdx === -1) {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const segment = collapsed.slice(markerIdx, markerIdx + 1e3);
|
|
19
|
-
const optionsMatch = segment.match(
|
|
20
|
-
/__SPA_GUARD_OPTIONS__\s*=\s*\{.*?"?version"?\s*:\s*"([^"]+)"/
|
|
21
|
-
);
|
|
22
|
-
return optionsMatch?.[1] ?? null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// src/common/spinner.ts
|
|
26
|
-
var SPINNER_ID = "__spa-guard-spinner";
|
|
27
|
-
var defaultSpinnerSvg = defaultSpinnerHtml;
|
|
28
|
-
var getState = () => {
|
|
29
|
-
if (!globalThis.window[spinnerStateWindowKey]) {
|
|
30
|
-
globalThis.window[spinnerStateWindowKey] = { savedOverflow: null };
|
|
31
|
-
}
|
|
32
|
-
return globalThis.window[spinnerStateWindowKey];
|
|
33
|
-
};
|
|
34
|
-
function dismissSpinner() {
|
|
35
|
-
if (typeof document === "undefined") {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const el = document.getElementById(SPINNER_ID);
|
|
39
|
-
if (el) {
|
|
40
|
-
el.remove();
|
|
41
|
-
}
|
|
42
|
-
const state = getState();
|
|
43
|
-
if (state.savedOverflow !== null) {
|
|
44
|
-
document.body.style.overflow = state.savedOverflow;
|
|
45
|
-
state.savedOverflow = null;
|
|
46
|
-
} else if (el) {
|
|
47
|
-
document.body.style.overflow = "";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
var sanitizeCssValue = (value) => value.replaceAll(/["'<>\\{};\n]/g, "");
|
|
51
|
-
function getSpinnerHtml(backgroundOverride) {
|
|
52
|
-
const opts = getOptions();
|
|
53
|
-
if (opts.html?.spinner?.disabled) {
|
|
54
|
-
return "";
|
|
55
|
-
}
|
|
56
|
-
const spinnerContent = opts.html?.spinner?.content ?? defaultSpinnerSvg;
|
|
57
|
-
const bg = sanitizeCssValue(backgroundOverride ?? opts.html?.spinner?.background ?? "#fff");
|
|
58
|
-
return `<div id="${SPINNER_ID}" style="position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:var(--spa-guard-spinner-bg,${bg})">${spinnerContent}</div>`;
|
|
59
|
-
}
|
|
60
|
-
function showSpinner(options) {
|
|
61
|
-
if (typeof document === "undefined") {
|
|
62
|
-
return () => {
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const opts = getOptions();
|
|
66
|
-
if (opts.html?.spinner?.disabled) {
|
|
67
|
-
return () => {
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
const existing = document.getElementById(SPINNER_ID);
|
|
71
|
-
if (existing) {
|
|
72
|
-
existing.remove();
|
|
73
|
-
}
|
|
74
|
-
const html = getSpinnerHtml(options?.background);
|
|
75
|
-
if (!html) {
|
|
76
|
-
return () => {
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
const wrapper = document.createElement("div");
|
|
80
|
-
wrapper.innerHTML = html;
|
|
81
|
-
const overlay = wrapper.firstElementChild;
|
|
82
|
-
if (!existing) {
|
|
83
|
-
getState().savedOverflow = document.body.style.overflow;
|
|
84
|
-
}
|
|
85
|
-
document.body.style.overflow = "hidden";
|
|
86
|
-
document.body.append(overlay);
|
|
87
|
-
return () => dismissSpinner();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export {
|
|
91
|
-
extractVersionFromHtml,
|
|
92
|
-
SPINNER_ID,
|
|
93
|
-
defaultSpinnerSvg,
|
|
94
|
-
dismissSpinner,
|
|
95
|
-
sanitizeCssValue,
|
|
96
|
-
getSpinnerHtml,
|
|
97
|
-
showSpinner
|
|
98
|
-
};
|