heliumts 0.5.14 → 0.6.0
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/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/staleRecovery.d.ts +23 -0
- package/dist/client/staleRecovery.d.ts.map +1 -0
- package/dist/client/staleRecovery.js +159 -0
- package/dist/client/staleRecovery.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAClF,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAG9B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGxF,cAAc,YAAY,CAAC"}
|
package/dist/client/index.js
CHANGED
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAEhE,0BAA0B,EAAE,CAAC;AAI7B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAOlF,4CAA4C;AAC5C,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAE9B,iBAAiB;AACjB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAExF,mBAAmB;AACnB,cAAc,YAAY,CAAC;AAE3B,yEAAyE","sourcesContent":["import { installStaleClientRecovery } from \"./staleRecovery.js\";\n\ninstallStaleClientRecovery();\n\n// Router components and hooks\nexport type { AppShellProps, LinkProps, RouterNavigationOptions } from \"./Router.js\";\nexport { AppRouter, Link, Redirect, RouterContext, useRouter } from \"./Router.js\";\nexport type { LayoutProps } from \"./routerManifest.js\";\n\n// React 18+ page transitions (separate module for better tree-shaking)\n// Import from \"heliumts/client/transitions\" to ensure they're only bundled when used\nexport type { PageTransitionProps } from \"./transitions.js\";\n\n// RPC hooks for data fetching and mutations\nexport * from \"./useCall.js\";\nexport * from \"./useFetch.js\";\n\n// RPC error type\nexport { RpcError } from \"./RpcError.js\";\n\n// RPC transport info (configured via helium.config.js)\nexport type { RpcTransport } from \"./rpcClient.js\";\nexport { getRpcTransport, isAutoHttpOnMobileEnabled, preconnect } from \"./rpcClient.js\";\n\n// Type definitions\nexport * from \"./types.js\";\n\n// Internal - rpcClient and cache are used by hooks, not exposed to users\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type StorageLike = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
2
|
+
type InstallStaleClientRecoveryOptions = {
|
|
3
|
+
windowObject?: Window;
|
|
4
|
+
documentObject?: Document;
|
|
5
|
+
storage?: StorageLike | null;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
reload?: () => void;
|
|
8
|
+
staleThresholdMs?: number;
|
|
9
|
+
reloadCooldownMs?: number;
|
|
10
|
+
disableDedupe?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @internal Exported for testing
|
|
14
|
+
*/
|
|
15
|
+
export declare function isChunkLoadError(reason: unknown): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Installs default stale-client recovery for mobile suspend/resume and stale chunks.
|
|
18
|
+
*
|
|
19
|
+
* @internal Exported for testing
|
|
20
|
+
*/
|
|
21
|
+
export declare function installStaleClientRecovery(options?: InstallStaleClientRecoveryOptions): void;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=staleRecovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleRecovery.d.ts","sourceRoot":"","sources":["../../src/client/staleRecovery.ts"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC;AAEvE,KAAK,iCAAiC,GAAG;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AA+DF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAWzD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,iCAAsC,GAAG,IAAI,CAyGhG"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const STALE_RESUME_THRESHOLD_MS = 30 * 60 * 1000;
|
|
2
|
+
const RELOAD_COOLDOWN_MS = 30 * 1000;
|
|
3
|
+
const INSTALL_FLAG = "__helium_stale_client_recovery_installed__";
|
|
4
|
+
const HIDDEN_AT_STORAGE_KEY = "__helium_stale_client_hidden_at__";
|
|
5
|
+
const LAST_RELOAD_STORAGE_KEY = "__helium_stale_client_last_reload_at__";
|
|
6
|
+
const CHUNK_ERROR_PATTERNS = [
|
|
7
|
+
/ChunkLoadError/i,
|
|
8
|
+
/Loading chunk\s+\d+\s+failed/i,
|
|
9
|
+
/Failed to fetch dynamically imported module/i,
|
|
10
|
+
/Importing a module script failed/i,
|
|
11
|
+
/dynamically imported module/i,
|
|
12
|
+
];
|
|
13
|
+
function readStoredNumber(storage, key) {
|
|
14
|
+
if (!storage) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = storage.getItem(key);
|
|
19
|
+
if (!raw) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const parsed = Number(raw);
|
|
23
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeStoredNumber(storage, key, value) {
|
|
30
|
+
if (!storage) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
if (value === null) {
|
|
35
|
+
storage.removeItem(key);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
storage.setItem(key, String(value));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Ignore storage write failures (private mode, quota exceeded, etc.)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function extractErrorText(reason) {
|
|
45
|
+
if (typeof reason === "string") {
|
|
46
|
+
return reason;
|
|
47
|
+
}
|
|
48
|
+
if (reason instanceof Error) {
|
|
49
|
+
return `${reason.name}: ${reason.message}`;
|
|
50
|
+
}
|
|
51
|
+
if (typeof reason === "object" && reason !== null) {
|
|
52
|
+
const maybeMessage = Reflect.get(reason, "message");
|
|
53
|
+
if (typeof maybeMessage === "string") {
|
|
54
|
+
return maybeMessage;
|
|
55
|
+
}
|
|
56
|
+
const maybeName = Reflect.get(reason, "name");
|
|
57
|
+
if (typeof maybeName === "string") {
|
|
58
|
+
return maybeName;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @internal Exported for testing
|
|
65
|
+
*/
|
|
66
|
+
export function isChunkLoadError(reason) {
|
|
67
|
+
if (reason instanceof Error && reason.name === "ChunkLoadError") {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const text = extractErrorText(reason);
|
|
71
|
+
if (!text) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Installs default stale-client recovery for mobile suspend/resume and stale chunks.
|
|
78
|
+
*
|
|
79
|
+
* @internal Exported for testing
|
|
80
|
+
*/
|
|
81
|
+
export function installStaleClientRecovery(options = {}) {
|
|
82
|
+
const windowObject = options.windowObject ?? (typeof window !== "undefined" ? window : undefined);
|
|
83
|
+
const documentObject = options.documentObject ?? (typeof document !== "undefined" ? document : undefined);
|
|
84
|
+
if (!windowObject || !documentObject) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const trackedWindow = windowObject;
|
|
88
|
+
if (!options.disableDedupe && trackedWindow[INSTALL_FLAG]) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
trackedWindow[INSTALL_FLAG] = true;
|
|
92
|
+
const now = options.now ?? (() => Date.now());
|
|
93
|
+
const storage = options.storage ?? windowObject.sessionStorage;
|
|
94
|
+
const staleThresholdMs = options.staleThresholdMs ?? STALE_RESUME_THRESHOLD_MS;
|
|
95
|
+
const reloadCooldownMs = options.reloadCooldownMs ?? RELOAD_COOLDOWN_MS;
|
|
96
|
+
const reload = options.reload ?? (() => windowObject.location.reload());
|
|
97
|
+
let isReloading = false;
|
|
98
|
+
let hiddenAt = readStoredNumber(storage, HIDDEN_AT_STORAGE_KEY);
|
|
99
|
+
const markHidden = () => {
|
|
100
|
+
hiddenAt = now();
|
|
101
|
+
writeStoredNumber(storage, HIDDEN_AT_STORAGE_KEY, hiddenAt);
|
|
102
|
+
};
|
|
103
|
+
const clearHidden = () => {
|
|
104
|
+
hiddenAt = null;
|
|
105
|
+
writeStoredNumber(storage, HIDDEN_AT_STORAGE_KEY, null);
|
|
106
|
+
};
|
|
107
|
+
const attemptReload = () => {
|
|
108
|
+
if (isReloading) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const current = now();
|
|
112
|
+
const lastReloadAt = readStoredNumber(storage, LAST_RELOAD_STORAGE_KEY) ?? 0;
|
|
113
|
+
if (current - lastReloadAt < reloadCooldownMs) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
isReloading = true;
|
|
117
|
+
writeStoredNumber(storage, LAST_RELOAD_STORAGE_KEY, current);
|
|
118
|
+
reload();
|
|
119
|
+
};
|
|
120
|
+
const maybeRecoverFromStaleResume = () => {
|
|
121
|
+
const hiddenTimestamp = hiddenAt ?? readStoredNumber(storage, HIDDEN_AT_STORAGE_KEY);
|
|
122
|
+
if (hiddenTimestamp === null) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const hiddenDuration = now() - hiddenTimestamp;
|
|
126
|
+
clearHidden();
|
|
127
|
+
if (hiddenDuration >= staleThresholdMs) {
|
|
128
|
+
attemptReload();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
documentObject.addEventListener("visibilitychange", () => {
|
|
132
|
+
if (documentObject.hidden) {
|
|
133
|
+
markHidden();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
maybeRecoverFromStaleResume();
|
|
137
|
+
}, { passive: true });
|
|
138
|
+
windowObject.addEventListener("pagehide", () => {
|
|
139
|
+
markHidden();
|
|
140
|
+
}, { passive: true });
|
|
141
|
+
windowObject.addEventListener("pageshow", (event) => {
|
|
142
|
+
if (event.persisted) {
|
|
143
|
+
maybeRecoverFromStaleResume();
|
|
144
|
+
}
|
|
145
|
+
}, { passive: true });
|
|
146
|
+
windowObject.addEventListener("error", (event) => {
|
|
147
|
+
const errorEvent = event;
|
|
148
|
+
if (isChunkLoadError(errorEvent.error ?? errorEvent.message)) {
|
|
149
|
+
attemptReload();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
windowObject.addEventListener("unhandledrejection", (event) => {
|
|
153
|
+
const rejectionEvent = event;
|
|
154
|
+
if (isChunkLoadError(rejectionEvent.reason)) {
|
|
155
|
+
attemptReload();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=staleRecovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleRecovery.js","sourceRoot":"","sources":["../../src/client/staleRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["const STALE_RESUME_THRESHOLD_MS = 30 * 60 * 1000;\nconst RELOAD_COOLDOWN_MS = 30 * 1000;\n\nconst INSTALL_FLAG = \"__helium_stale_client_recovery_installed__\";\nconst HIDDEN_AT_STORAGE_KEY = \"__helium_stale_client_hidden_at__\";\nconst LAST_RELOAD_STORAGE_KEY = \"__helium_stale_client_last_reload_at__\";\n\nconst CHUNK_ERROR_PATTERNS: RegExp[] = [\n /ChunkLoadError/i,\n /Loading chunk\\s+\\d+\\s+failed/i,\n /Failed to fetch dynamically imported module/i,\n /Importing a module script failed/i,\n /dynamically imported module/i,\n];\n\ntype StorageLike = Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\">;\n\ntype InstallStaleClientRecoveryOptions = {\n windowObject?: Window;\n documentObject?: Document;\n storage?: StorageLike | null;\n now?: () => number;\n reload?: () => void;\n staleThresholdMs?: number;\n reloadCooldownMs?: number;\n disableDedupe?: boolean;\n};\n\ntype WindowWithInstallFlag = Window & {\n [INSTALL_FLAG]?: boolean;\n};\n\nfunction readStoredNumber(storage: StorageLike | null, key: string): number | null {\n if (!storage) {\n return null;\n }\n\n try {\n const raw = storage.getItem(key);\n if (!raw) {\n return null;\n }\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction writeStoredNumber(storage: StorageLike | null, key: string, value: number | null): void {\n if (!storage) {\n return;\n }\n\n try {\n if (value === null) {\n storage.removeItem(key);\n return;\n }\n storage.setItem(key, String(value));\n } catch {\n // Ignore storage write failures (private mode, quota exceeded, etc.)\n }\n}\n\nfunction extractErrorText(reason: unknown): string {\n if (typeof reason === \"string\") {\n return reason;\n }\n\n if (reason instanceof Error) {\n return `${reason.name}: ${reason.message}`;\n }\n\n if (typeof reason === \"object\" && reason !== null) {\n const maybeMessage = Reflect.get(reason, \"message\");\n if (typeof maybeMessage === \"string\") {\n return maybeMessage;\n }\n\n const maybeName = Reflect.get(reason, \"name\");\n if (typeof maybeName === \"string\") {\n return maybeName;\n }\n }\n\n return \"\";\n}\n\n/**\n * @internal Exported for testing\n */\nexport function isChunkLoadError(reason: unknown): boolean {\n if (reason instanceof Error && reason.name === \"ChunkLoadError\") {\n return true;\n }\n\n const text = extractErrorText(reason);\n if (!text) {\n return false;\n }\n\n return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(text));\n}\n\n/**\n * Installs default stale-client recovery for mobile suspend/resume and stale chunks.\n *\n * @internal Exported for testing\n */\nexport function installStaleClientRecovery(options: InstallStaleClientRecoveryOptions = {}): void {\n const windowObject = options.windowObject ?? (typeof window !== \"undefined\" ? window : undefined);\n const documentObject = options.documentObject ?? (typeof document !== \"undefined\" ? document : undefined);\n\n if (!windowObject || !documentObject) {\n return;\n }\n\n const trackedWindow = windowObject as WindowWithInstallFlag;\n if (!options.disableDedupe && trackedWindow[INSTALL_FLAG]) {\n return;\n }\n trackedWindow[INSTALL_FLAG] = true;\n\n const now = options.now ?? (() => Date.now());\n const storage = options.storage ?? windowObject.sessionStorage;\n const staleThresholdMs = options.staleThresholdMs ?? STALE_RESUME_THRESHOLD_MS;\n const reloadCooldownMs = options.reloadCooldownMs ?? RELOAD_COOLDOWN_MS;\n const reload = options.reload ?? (() => windowObject.location.reload());\n\n let isReloading = false;\n let hiddenAt = readStoredNumber(storage, HIDDEN_AT_STORAGE_KEY);\n\n const markHidden = () => {\n hiddenAt = now();\n writeStoredNumber(storage, HIDDEN_AT_STORAGE_KEY, hiddenAt);\n };\n\n const clearHidden = () => {\n hiddenAt = null;\n writeStoredNumber(storage, HIDDEN_AT_STORAGE_KEY, null);\n };\n\n const attemptReload = () => {\n if (isReloading) {\n return;\n }\n\n const current = now();\n const lastReloadAt = readStoredNumber(storage, LAST_RELOAD_STORAGE_KEY) ?? 0;\n if (current - lastReloadAt < reloadCooldownMs) {\n return;\n }\n\n isReloading = true;\n writeStoredNumber(storage, LAST_RELOAD_STORAGE_KEY, current);\n reload();\n };\n\n const maybeRecoverFromStaleResume = () => {\n const hiddenTimestamp = hiddenAt ?? readStoredNumber(storage, HIDDEN_AT_STORAGE_KEY);\n if (hiddenTimestamp === null) {\n return;\n }\n\n const hiddenDuration = now() - hiddenTimestamp;\n clearHidden();\n if (hiddenDuration >= staleThresholdMs) {\n attemptReload();\n }\n };\n\n documentObject.addEventListener(\n \"visibilitychange\",\n () => {\n if (documentObject.hidden) {\n markHidden();\n return;\n }\n maybeRecoverFromStaleResume();\n },\n { passive: true }\n );\n\n windowObject.addEventListener(\n \"pagehide\",\n () => {\n markHidden();\n },\n { passive: true }\n );\n\n windowObject.addEventListener(\n \"pageshow\",\n (event) => {\n if ((event as PageTransitionEvent).persisted) {\n maybeRecoverFromStaleResume();\n }\n },\n { passive: true }\n );\n\n windowObject.addEventListener(\"error\", (event) => {\n const errorEvent = event as ErrorEvent;\n if (isChunkLoadError(errorEvent.error ?? errorEvent.message)) {\n attemptReload();\n }\n });\n\n windowObject.addEventListener(\"unhandledrejection\", (event) => {\n const rejectionEvent = event as PromiseRejectionEvent;\n if (isChunkLoadError(rejectionEvent.reason)) {\n attemptReload();\n }\n });\n}\n"]}
|