heliumts 0.4.5 → 0.5.1
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/rpcClient.d.ts +4 -0
- package/dist/client/rpcClient.d.ts.map +1 -1
- package/dist/client/rpcClient.js +164 -32
- package/dist/client/rpcClient.js.map +1 -1
- package/dist/client/useFetch.d.ts +3 -0
- package/dist/client/useFetch.d.ts.map +1 -1
- package/dist/client/useFetch.js +4 -2
- package/dist/client/useFetch.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,10 @@ export declare function isAutoHttpOnMobileEnabled(): boolean;
|
|
|
21
21
|
/**
|
|
22
22
|
* Make an RPC call using the appropriate transport.
|
|
23
23
|
* Automatically selects HTTP or WebSocket based on network conditions and configuration.
|
|
24
|
+
*
|
|
25
|
+
* Includes automatic retry logic: if a call fails due to a connection error
|
|
26
|
+
* (e.g. stale WebSocket after mobile browser was backgrounded), the client
|
|
27
|
+
* forces a fresh connection (with a new token) and retries once.
|
|
24
28
|
*/
|
|
25
29
|
export declare function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>>;
|
|
26
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcClient.d.ts","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA2B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGhF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAUzD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;
|
|
1
|
+
{"version":3,"file":"rpcClient.d.ts","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA2B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGhF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAUzD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAieD;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAE7H;AA6BD;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAYjC"}
|
package/dist/client/rpcClient.js
CHANGED
|
@@ -109,12 +109,9 @@ async function sendBatchHttp(batch) {
|
|
|
109
109
|
async function sendBatchWebSocket(batch) {
|
|
110
110
|
const ws = await ensureSocketReady();
|
|
111
111
|
const requests = batch.map((b) => b.req);
|
|
112
|
-
// Register pending promises
|
|
112
|
+
// Register pending promises with timeout safeguards
|
|
113
113
|
batch.forEach((item) => {
|
|
114
|
-
|
|
115
|
-
resolve: (v) => item.resolve(v),
|
|
116
|
-
reject: item.reject,
|
|
117
|
-
});
|
|
114
|
+
trackPending(item.req.id, (v) => item.resolve(v), item.reject);
|
|
118
115
|
});
|
|
119
116
|
try {
|
|
120
117
|
// Always use msgpack encoding
|
|
@@ -123,7 +120,7 @@ async function sendBatchWebSocket(batch) {
|
|
|
123
120
|
}
|
|
124
121
|
catch (err) {
|
|
125
122
|
batch.forEach((item) => {
|
|
126
|
-
|
|
123
|
+
removePending(item.req.id);
|
|
127
124
|
item.reject(err);
|
|
128
125
|
});
|
|
129
126
|
}
|
|
@@ -134,20 +131,108 @@ async function sendBatchWebSocket(batch) {
|
|
|
134
131
|
let socket = null;
|
|
135
132
|
let connectionPromise = null;
|
|
136
133
|
const pending = new Map();
|
|
134
|
+
const pendingTimeouts = new Map();
|
|
135
|
+
// ── Connection resilience constants ──────────────────────────────────────────
|
|
136
|
+
/** How long (ms) the page must be hidden before we consider the WebSocket stale. */
|
|
137
|
+
const STALE_THRESHOLD_MS = 15000;
|
|
138
|
+
/** Max time (ms) to wait for a response before timing out a request. */
|
|
139
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
140
|
+
/** Number of automatic retries on retriable connection errors. */
|
|
141
|
+
const MAX_RETRIES = 3;
|
|
142
|
+
/** Base delay (ms) for exponential backoff between retries (doubles each attempt). */
|
|
143
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
144
|
+
/** Maximum delay (ms) cap for backoff to avoid excessively long waits. */
|
|
145
|
+
const RETRY_MAX_DELAY_MS = 5000;
|
|
146
|
+
/** Timestamp when the page was last hidden (for visibility-change detection). */
|
|
147
|
+
let lastHiddenTimestamp = null;
|
|
148
|
+
// ── Pending-request helpers ──────────────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Register a pending request with an automatic timeout safeguard.
|
|
151
|
+
* If no response arrives within REQUEST_TIMEOUT_MS the promise is rejected
|
|
152
|
+
* so the caller's retry logic can kick in.
|
|
153
|
+
*/
|
|
154
|
+
function trackPending(id, resolve, reject) {
|
|
155
|
+
pending.set(id, { resolve, reject });
|
|
156
|
+
const timer = setTimeout(() => {
|
|
157
|
+
const entry = pending.get(id);
|
|
158
|
+
if (entry) {
|
|
159
|
+
pending.delete(id);
|
|
160
|
+
pendingTimeouts.delete(id);
|
|
161
|
+
entry.reject(new RpcError("Request timed out"));
|
|
162
|
+
}
|
|
163
|
+
}, REQUEST_TIMEOUT_MS);
|
|
164
|
+
pendingTimeouts.set(id, timer);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Remove a pending request and clear its timeout.
|
|
168
|
+
* Returns the entry so the caller can resolve/reject it.
|
|
169
|
+
*/
|
|
170
|
+
function removePending(id) {
|
|
171
|
+
const entry = pending.get(id);
|
|
172
|
+
if (!entry) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
pending.delete(id);
|
|
176
|
+
const timer = pendingTimeouts.get(id);
|
|
177
|
+
if (timer) {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
pendingTimeouts.delete(id);
|
|
180
|
+
}
|
|
181
|
+
return entry;
|
|
182
|
+
}
|
|
183
|
+
/** Reject every in-flight request (e.g. when the socket closes unexpectedly). */
|
|
184
|
+
function rejectAllPending(reason) {
|
|
185
|
+
for (const timer of pendingTimeouts.values()) {
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
}
|
|
188
|
+
pendingTimeouts.clear();
|
|
189
|
+
const entries = [...pending.entries()];
|
|
190
|
+
pending.clear();
|
|
191
|
+
for (const [, entry] of entries) {
|
|
192
|
+
entry.reject(reason);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// ── Reconnection helpers ─────────────────────────────────────────────────────
|
|
196
|
+
/**
|
|
197
|
+
* Force-close the current WebSocket so the next call creates a fresh
|
|
198
|
+
* connection (which fetches a brand-new token).
|
|
199
|
+
*/
|
|
200
|
+
function forceReconnect() {
|
|
201
|
+
const oldSocket = socket;
|
|
202
|
+
socket = null;
|
|
203
|
+
connectionPromise = null;
|
|
204
|
+
if (oldSocket) {
|
|
205
|
+
// Detach handlers to avoid double-rejecting pending from the close event
|
|
206
|
+
oldSocket.onclose = null;
|
|
207
|
+
oldSocket.onerror = null;
|
|
208
|
+
oldSocket.onmessage = null;
|
|
209
|
+
oldSocket.close();
|
|
210
|
+
}
|
|
211
|
+
// Reject all in-flight requests – callers with retry logic will resend
|
|
212
|
+
rejectAllPending(new Error("Connection reset"));
|
|
213
|
+
}
|
|
214
|
+
/** Determine whether an error warrants an automatic retry. */
|
|
215
|
+
function isRetriableError(err) {
|
|
216
|
+
// Network / connection errors are always retriable
|
|
217
|
+
if (err instanceof Error && !(err instanceof RpcError)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
// Timed-out requests are retriable (socket may have died silently)
|
|
221
|
+
if (err instanceof RpcError && err.message === "Request timed out") {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
137
226
|
// Clean up WebSocket connection on HMR (Hot Module Replacement)
|
|
138
227
|
if (import.meta.hot) {
|
|
139
228
|
import.meta.hot.dispose(() => {
|
|
140
229
|
if (socket) {
|
|
141
|
-
|
|
230
|
+
socket.onclose = null;
|
|
142
231
|
socket.close();
|
|
143
232
|
socket = null;
|
|
144
233
|
connectionPromise = null;
|
|
145
234
|
}
|
|
146
|
-
|
|
147
|
-
pending.forEach((entry) => {
|
|
148
|
-
entry.reject(new Error("Module reloaded"));
|
|
149
|
-
});
|
|
150
|
-
pending.clear();
|
|
235
|
+
rejectAllPending(new Error("Module reloaded"));
|
|
151
236
|
});
|
|
152
237
|
}
|
|
153
238
|
let msgId = 0;
|
|
@@ -202,11 +287,10 @@ async function createSocket() {
|
|
|
202
287
|
// Always expect binary MessagePack
|
|
203
288
|
const msg = msgpackDecode(data);
|
|
204
289
|
const handleResponse = (res) => {
|
|
205
|
-
const entry =
|
|
290
|
+
const entry = removePending(res.id);
|
|
206
291
|
if (!entry) {
|
|
207
292
|
return;
|
|
208
293
|
}
|
|
209
|
-
pending.delete(res.id);
|
|
210
294
|
if (res.ok) {
|
|
211
295
|
entry.resolve({ data: res.result, stats: res.stats });
|
|
212
296
|
}
|
|
@@ -221,10 +305,16 @@ async function createSocket() {
|
|
|
221
305
|
handleResponse(msg);
|
|
222
306
|
}
|
|
223
307
|
};
|
|
308
|
+
ws.onerror = () => {
|
|
309
|
+
// WebSocket errors are always followed by a close event.
|
|
310
|
+
// The close handler takes care of rejecting pending promises.
|
|
311
|
+
};
|
|
224
312
|
ws.onclose = () => {
|
|
225
313
|
if (socket === ws) {
|
|
226
314
|
socket = null;
|
|
227
315
|
connectionPromise = null;
|
|
316
|
+
// Reject every in-flight request so callers can retry
|
|
317
|
+
rejectAllPending(new Error("WebSocket connection closed"));
|
|
228
318
|
}
|
|
229
319
|
};
|
|
230
320
|
return ws;
|
|
@@ -308,16 +398,13 @@ async function rpcCallWebSocket(methodId, args) {
|
|
|
308
398
|
const id = nextId();
|
|
309
399
|
const req = { id, method: methodId, args };
|
|
310
400
|
return new Promise((resolve, reject) => {
|
|
311
|
-
|
|
312
|
-
resolve: (v) => resolve(v),
|
|
313
|
-
reject,
|
|
314
|
-
});
|
|
401
|
+
trackPending(id, (v) => resolve(v), reject);
|
|
315
402
|
try {
|
|
316
403
|
const encoded = msgpackEncode(req);
|
|
317
404
|
socket.send(encoded);
|
|
318
405
|
}
|
|
319
406
|
catch (err) {
|
|
320
|
-
|
|
407
|
+
removePending(id);
|
|
321
408
|
reject(err);
|
|
322
409
|
}
|
|
323
410
|
});
|
|
@@ -326,17 +413,14 @@ async function rpcCallWebSocket(methodId, args) {
|
|
|
326
413
|
const id = nextId();
|
|
327
414
|
const req = { id, method: methodId, args };
|
|
328
415
|
return new Promise((resolve, reject) => {
|
|
329
|
-
|
|
330
|
-
resolve: (v) => resolve(v),
|
|
331
|
-
reject,
|
|
332
|
-
});
|
|
416
|
+
trackPending(id, (v) => resolve(v), reject);
|
|
333
417
|
try {
|
|
334
418
|
// Always use msgpack encoding
|
|
335
419
|
const encoded = msgpackEncode(req);
|
|
336
420
|
ws.send(encoded);
|
|
337
421
|
}
|
|
338
422
|
catch (err) {
|
|
339
|
-
|
|
423
|
+
removePending(id);
|
|
340
424
|
reject(err);
|
|
341
425
|
}
|
|
342
426
|
});
|
|
@@ -344,17 +428,38 @@ async function rpcCallWebSocket(methodId, args) {
|
|
|
344
428
|
/**
|
|
345
429
|
* Make an RPC call using the appropriate transport.
|
|
346
430
|
* Automatically selects HTTP or WebSocket based on network conditions and configuration.
|
|
431
|
+
*
|
|
432
|
+
* Includes automatic retry logic: if a call fails due to a connection error
|
|
433
|
+
* (e.g. stale WebSocket after mobile browser was backgrounded), the client
|
|
434
|
+
* forces a fresh connection (with a new token) and retries once.
|
|
347
435
|
*/
|
|
348
436
|
export async function rpcCall(methodId, args) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
437
|
+
return rpcCallWithRetry(methodId, args);
|
|
438
|
+
}
|
|
439
|
+
async function rpcCallWithRetry(methodId, args, attempt = 0) {
|
|
440
|
+
try {
|
|
441
|
+
if (shouldUseHttpTransport()) {
|
|
442
|
+
const id = nextId();
|
|
443
|
+
const req = { id, method: methodId, args };
|
|
444
|
+
return await new Promise((resolve, reject) => {
|
|
445
|
+
pendingBatch.push({ req, resolve: resolve, reject });
|
|
446
|
+
scheduleBatch();
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return await rpcCallWebSocket(methodId, args);
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
if (attempt < MAX_RETRIES && isRetriableError(err)) {
|
|
453
|
+
// Force a fresh connection (fetches a new token)
|
|
454
|
+
forceReconnect();
|
|
455
|
+
// Exponential backoff with jitter: 500ms, 1000ms, 2000ms (capped at 5s)
|
|
456
|
+
const baseDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** attempt, RETRY_MAX_DELAY_MS);
|
|
457
|
+
const jitter = Math.random() * baseDelay * 0.3;
|
|
458
|
+
await new Promise((r) => setTimeout(r, baseDelay + jitter));
|
|
459
|
+
return rpcCallWithRetry(methodId, args, attempt + 1);
|
|
460
|
+
}
|
|
461
|
+
throw err;
|
|
356
462
|
}
|
|
357
|
-
return rpcCallWebSocket(methodId, args);
|
|
358
463
|
}
|
|
359
464
|
/**
|
|
360
465
|
* Pre-establishes the WebSocket connection.
|
|
@@ -381,4 +486,31 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
|
381
486
|
const schedulePreconnect = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
|
|
382
487
|
schedulePreconnect(() => preconnect());
|
|
383
488
|
}
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// Visibility-change reconnection (critical for mobile browsers)
|
|
491
|
+
// ============================================================================
|
|
492
|
+
//
|
|
493
|
+
// Mobile browsers freeze or kill WebSocket connections when the tab is
|
|
494
|
+
// backgrounded. When the user returns the socket may *appear* open but
|
|
495
|
+
// is actually stale. We detect this via the Page Visibility API and
|
|
496
|
+
// proactively tear down the old connection so the next RPC call creates
|
|
497
|
+
// a fresh one (with a brand-new auth token).
|
|
498
|
+
// ============================================================================
|
|
499
|
+
if (typeof document !== "undefined") {
|
|
500
|
+
document.addEventListener("visibilitychange", () => {
|
|
501
|
+
if (document.hidden) {
|
|
502
|
+
lastHiddenTimestamp = Date.now();
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// Page became visible again
|
|
506
|
+
if (lastHiddenTimestamp !== null) {
|
|
507
|
+
const hiddenDuration = Date.now() - lastHiddenTimestamp;
|
|
508
|
+
if (hiddenDuration > STALE_THRESHOLD_MS && socket) {
|
|
509
|
+
forceReconnect();
|
|
510
|
+
}
|
|
511
|
+
lastHiddenTimestamp = null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
384
516
|
//# sourceMappingURL=rpcClient.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcClient.js","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { RpcError } from \"./RpcError.js\";\n\nexport type RpcResult<T> = {\n data: T;\n stats: RpcStats;\n};\n\n/**\n * Transport mode for RPC calls.\n * - \"http\": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)\n * - \"websocket\": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)\n * - \"auto\": Automatically selects based on connection type\n */\nexport type RpcTransport = \"http\" | \"websocket\" | \"auto\";\n\n// Build-time configuration injected by Vite plugin from helium.config.js\ndeclare const __HELIUM_RPC_TRANSPORT__: RpcTransport;\ndeclare const __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__: boolean;\n\n// Read build-time config with fallback defaults\nconst configuredTransport: RpcTransport = typeof __HELIUM_RPC_TRANSPORT__ !== \"undefined\" ? __HELIUM_RPC_TRANSPORT__ : \"websocket\";\nconst configuredAutoHttpOnMobile: boolean = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== \"undefined\" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;\n\n/**\n * Get the configured RPC transport mode (from helium.config.js).\n */\nexport function getRpcTransport(): RpcTransport {\n return configuredTransport;\n}\n\n/**\n * Check if auto HTTP on mobile is enabled (from helium.config.js).\n */\nexport function isAutoHttpOnMobileEnabled(): boolean {\n return configuredAutoHttpOnMobile;\n}\n\n// Detect if we should prefer HTTP transport (mobile/slow networks)\nfunction shouldUseHttpTransport(): boolean {\n if (configuredTransport === \"http\") {\n return true;\n }\n if (configuredTransport === \"websocket\") {\n return false;\n }\n\n // Auto mode: check if mobile HTTP optimization is enabled\n if (!configuredAutoHttpOnMobile) {\n return false;\n }\n\n // Prefer HTTP on mobile/slow connections\n if (typeof navigator !== \"undefined\") {\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n // Use HTTP for cellular connections or slow effective types\n const slowTypes = [\"slow-2g\", \"2g\", \"3g\"];\n if (conn.type === \"cellular\" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {\n return true;\n }\n }\n }\n\n return false;\n}\n\ninterface NetworkInformation {\n type?: string;\n effectiveType?: string;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n connection?: NetworkInformation;\n}\n\n// ============================================================================\n// Batching Logic\n// ============================================================================\n\ntype PendingRequest = {\n req: RpcRequest;\n resolve: (value: RpcResult<any>) => void;\n reject: (reason?: any) => void;\n};\n\nlet pendingBatch: PendingRequest[] = [];\nlet isBatchScheduled = false;\n\nfunction scheduleBatch() {\n if (isBatchScheduled) {\n return;\n }\n isBatchScheduled = true;\n queueMicrotask(() => {\n isBatchScheduled = false;\n flushBatch();\n });\n}\n\nasync function flushBatch() {\n const batch = pendingBatch;\n pendingBatch = [];\n\n if (batch.length === 0) {\n return;\n }\n\n try {\n if (shouldUseHttpTransport()) {\n await sendBatchHttp(batch);\n } else {\n await sendBatchWebSocket(batch);\n }\n } catch (err) {\n // Transport error, fail all\n for (const item of batch) {\n item.reject(err);\n }\n }\n}\n\nasync function sendBatchHttp(batch: PendingRequest[]) {\n const requests = batch.map((b) => b.req);\n const encoded = msgpackEncode(requests);\n\n const response = await fetch(\"/__helium__/rpc\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/msgpack\",\n Accept: \"application/msgpack\",\n },\n body: encoded as unknown as BodyInit,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP RPC failed: ${response.status}`);\n }\n\n const responseBuffer = await response.arrayBuffer();\n const msg = msgpackDecode(new Uint8Array(responseBuffer)) as RpcResponse | RpcResponse[];\n\n const responses = Array.isArray(msg) ? msg : [msg];\n const responseMap = new Map(responses.map((r) => [r.id, r]));\n\n for (const item of batch) {\n const res = responseMap.get(item.req.id);\n if (res) {\n if (res.ok) {\n item.resolve({ data: res.result, stats: res.stats });\n } else {\n item.reject(new RpcError(res.error, res.stats));\n }\n } else {\n item.reject(new RpcError(\"No response for request\"));\n }\n }\n}\n\nasync function sendBatchWebSocket(batch: PendingRequest[]) {\n const ws = await ensureSocketReady();\n const requests = batch.map((b) => b.req);\n\n // Register pending promises\n batch.forEach((item) => {\n pending.set(item.req.id, {\n resolve: (v: unknown) => item.resolve(v as RpcResult<any>),\n reject: item.reject,\n });\n });\n\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(requests);\n ws.send(encoded);\n } catch (err) {\n batch.forEach((item) => {\n pending.delete(item.req.id);\n item.reject(err);\n });\n }\n}\n\n// ============================================================================\n// WebSocket Transport (original implementation)\n// ============================================================================\n\nlet socket: WebSocket | null = null;\nlet connectionPromise: Promise<WebSocket> | null = null;\n\nconst pending = new Map<string | number, { resolve: (v: unknown) => void; reject: (e: unknown) => void }>();\n\n// Clean up WebSocket connection on HMR (Hot Module Replacement)\nif (import.meta.hot) {\n import.meta.hot.dispose(() => {\n if (socket) {\n // Close the socket gracefully\n socket.close();\n socket = null;\n connectionPromise = null;\n }\n // Reject all pending requests\n pending.forEach((entry) => {\n entry.reject(new Error(\"Module reloaded\"));\n });\n pending.clear();\n });\n}\n\nlet msgId = 0;\nfunction nextId() {\n return msgId++;\n}\n\nasync function fetchFreshToken(): Promise<string | undefined> {\n try {\n const response = await fetch(\"/__helium__/refresh-token\");\n if (!response.ok) {\n console.warn(\"Failed to fetch fresh token:\", response.status);\n return undefined;\n }\n const data = await response.json();\n return data.token;\n } catch (error) {\n console.warn(\"Error fetching fresh token:\", error);\n return undefined;\n }\n}\n\nasync function createSocket(): Promise<WebSocket> {\n // Fetch a fresh token before creating the WebSocket connection\n const token = await fetchFreshToken();\n\n // Use the same protocol, hostname and port as the current page\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host; // includes hostname and port\n const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : \"\"}`;\n const ws = new WebSocket(url);\n ws.binaryType = \"arraybuffer\";\n\n ws.onmessage = async (event) => {\n let data = new Uint8Array(event.data as ArrayBuffer);\n\n // Check for Gzip header (0x1f 0x8b) to detect compressed messages\n if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {\n try {\n // Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)\n if (typeof DecompressionStream !== \"undefined\") {\n const ds = new DecompressionStream(\"gzip\");\n const stream = new Response(data).body;\n if (stream) {\n const decompressed = stream.pipeThrough(ds);\n const buffer = await new Response(decompressed).arrayBuffer();\n data = new Uint8Array(buffer);\n }\n }\n } catch (err) {\n console.error(\"Failed to decompress WebSocket message:\", err);\n return;\n }\n }\n\n // Always expect binary MessagePack\n const msg = msgpackDecode(data) as RpcResponse | RpcResponse[];\n\n const handleResponse = (res: RpcResponse) => {\n const entry = pending.get(res.id);\n if (!entry) {\n return;\n }\n pending.delete(res.id);\n if (res.ok) {\n entry.resolve({ data: res.result, stats: res.stats });\n } else {\n entry.reject(new RpcError(res.error, res.stats));\n }\n };\n\n if (Array.isArray(msg)) {\n msg.forEach(handleResponse);\n } else {\n handleResponse(msg);\n }\n };\n\n ws.onclose = () => {\n if (socket === ws) {\n socket = null;\n connectionPromise = null;\n }\n };\n\n return ws;\n}\n\nasync function ensureSocketReady(): Promise<WebSocket> {\n // If we have an open socket, return it immediately\n if (socket && socket.readyState === WebSocket.OPEN) {\n return socket;\n }\n\n // If we have a connection in progress, reuse that promise\n if (connectionPromise) {\n return connectionPromise;\n }\n\n // If we have a socket that's connecting, wait for it\n if (socket && socket.readyState === WebSocket.CONNECTING) {\n connectionPromise = new Promise((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n return connectionPromise;\n }\n\n // Create a new socket and connection promise\n connectionPromise = (async () => {\n socket = await createSocket();\n return new Promise<WebSocket>((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n })();\n\n return connectionPromise;\n}\n\nasync function rpcCallWebSocket<TResult, TArgs>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n // Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)\n if (socket && socket.readyState === WebSocket.OPEN) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n pending.set(id, {\n resolve: (v: unknown) => resolve(v as RpcResult<TResult>),\n reject,\n });\n try {\n const encoded = msgpackEncode(req);\n socket!.send(encoded);\n } catch (err) {\n pending.delete(id);\n reject(err);\n }\n });\n }\n\n const ws = await ensureSocketReady();\n const id = nextId();\n\n const req: RpcRequest = { id, method: methodId, args };\n\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n pending.set(id, {\n resolve: (v: unknown) => resolve(v as RpcResult<TResult>),\n reject,\n });\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(req);\n ws.send(encoded);\n } catch (err) {\n pending.delete(id);\n reject(err);\n }\n });\n}\n\n/**\n * Make an RPC call using the appropriate transport.\n * Automatically selects HTTP or WebSocket based on network conditions and configuration.\n */\nexport async function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n if (shouldUseHttpTransport()) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n pendingBatch.push({ req, resolve: resolve as any, reject });\n scheduleBatch();\n });\n }\n\n return rpcCallWebSocket<TResult, TArgs>(methodId, args);\n}\n\n/**\n * Pre-establishes the WebSocket connection.\n * Call this early (e.g., on page load) to avoid connection latency on first RPC call.\n * This is especially beneficial on high-latency networks like mobile LTE.\n * Note: Only effective when using WebSocket transport (not HTTP transport).\n */\nexport function preconnect(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n // Only preconnect if we're using WebSocket transport\n if (shouldUseHttpTransport()) {\n return;\n }\n // Fire and forget - establishes connection in background\n ensureSocketReady().catch(() => {\n // Silently ignore preconnect failures, will retry on actual call\n });\n}\n\n// Auto-preconnect when the module loads (browser only, WebSocket transport only)\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Use requestIdleCallback if available, otherwise setTimeout\n const schedulePreconnect = window.requestIdleCallback || ((cb: () => void) => setTimeout(cb, 1));\n schedulePreconnect(() => preconnect());\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpcClient.js","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGpF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAmBzC,gDAAgD;AAChD,MAAM,mBAAmB,GAAiB,OAAO,wBAAwB,KAAK,WAAW,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,WAAW,CAAC;AACnI,MAAM,0BAA0B,GAAY,OAAO,kCAAkC,KAAK,WAAW,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,KAAK,CAAC;AAEnJ;;GAEG;AACH,MAAM,UAAU,eAAe;IAC3B,OAAO,mBAAmB,CAAC;AAC/B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB;IACrC,OAAO,0BAA0B,CAAC;AACtC,CAAC;AAED,mEAAmE;AACnE,SAAS,sBAAsB;IAC3B,IAAI,mBAAmB,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,mBAAmB,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,0DAA0D;IAC1D,IAAI,CAAC,0BAA0B,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,yCAAyC;IACzC,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;QACnC,MAAM,IAAI,GAAI,SAAqC,CAAC,UAAU,CAAC;QAC/D,IAAI,IAAI,EAAE,CAAC;YACP,4DAA4D;YAC5D,MAAM,SAAS,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;gBAC7F,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAqBD,IAAI,YAAY,GAAqB,EAAE,CAAC;AACxC,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,aAAa;IAClB,IAAI,gBAAgB,EAAE,CAAC;QACnB,OAAO;IACX,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC;IACxB,cAAc,CAAC,GAAG,EAAE;QAChB,gBAAgB,GAAG,KAAK,CAAC;QACzB,UAAU,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,KAAK,UAAU,UAAU;IACrB,MAAM,KAAK,GAAG,YAAY,CAAC;IAC3B,YAAY,GAAG,EAAE,CAAC;IAElB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;IACX,CAAC;IAED,IAAI,CAAC;QACD,IAAI,sBAAsB,EAAE,EAAE,CAAC;YAC3B,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;aAAM,CAAC;YACJ,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,4BAA4B;QAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;IACL,CAAC;AACL,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAAuB;IAChD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAExC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,iBAAiB,EAAE;QAC5C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACL,cAAc,EAAE,qBAAqB;YACrC,MAAM,EAAE,qBAAqB;SAChC;QACD,IAAI,EAAE,OAA8B;KACvC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IACpD,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,UAAU,CAAC,cAAc,CAAC,CAAgC,CAAC;IAEzF,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,GAAG,EAAE,CAAC;YACN,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACT,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACpD,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACzD,CAAC;IACL,CAAC;AACL,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,KAAuB;IACrD,MAAM,EAAE,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAEzC,oDAAoD;IACpD,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACnB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAU,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAmB,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,8BAA8B;QAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACnB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACP,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,gDAAgD;AAChD,+EAA+E;AAE/E,IAAI,MAAM,GAAqB,IAAI,CAAC;AACpC,IAAI,iBAAiB,GAA8B,IAAI,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoF,CAAC;AAC5G,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkD,CAAC;AAElF,gFAAgF;AAEhF,oFAAoF;AACpF,MAAM,kBAAkB,GAAG,KAAM,CAAC;AAElC,wEAAwE;AACxE,MAAM,kBAAkB,GAAG,KAAM,CAAC;AAElC,kEAAkE;AAClE,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,sFAAsF;AACtF,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,0EAA0E;AAC1E,MAAM,kBAAkB,GAAG,IAAK,CAAC;AAEjC,iFAAiF;AACjF,IAAI,mBAAmB,GAAkB,IAAI,CAAC;AAE9C,gFAAgF;AAEhF;;;;GAIG;AACH,SAAS,YAAY,CAAC,EAAmB,EAAE,OAA6B,EAAE,MAA4B;IAClG,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,KAAK,EAAE,CAAC;YACR,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACnB,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3B,KAAK,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;QACpD,CAAC;IACL,CAAC,EAAE,kBAAkB,CAAC,CAAC;IACvB,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,EAAmB;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnB,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,KAAK,EAAE,CAAC;QACR,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,iFAAiF;AACjF,SAAS,gBAAgB,CAAC,MAAa;IACnC,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3C,YAAY,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IACD,eAAe,CAAC,KAAK,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QAC9B,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;AACL,CAAC;AAED,gFAAgF;AAEhF;;;GAGG;AACH,SAAS,cAAc;IACnB,MAAM,SAAS,GAAG,MAAM,CAAC;IACzB,MAAM,GAAG,IAAI,CAAC;IACd,iBAAiB,GAAG,IAAI,CAAC;IAEzB,IAAI,SAAS,EAAE,CAAC;QACZ,yEAAyE;QACzE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QACzB,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QACzB,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC;QAC3B,SAAS,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,uEAAuE;IACvE,gBAAgB,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,8DAA8D;AAC9D,SAAS,gBAAgB,CAAC,GAAY;IAClC,mDAAmD;IACnD,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,mEAAmE;IACnE,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,OAAO,KAAK,mBAAmB,EAAE,CAAC;QACjE,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,gEAAgE;AAChE,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAClB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE;QACzB,IAAI,MAAM,EAAE,CAAC;YACT,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,IAAI,CAAC;YACd,iBAAiB,GAAG,IAAI,CAAC;QAC7B,CAAC;QACD,gBAAgB,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACP,CAAC;AAED,IAAI,KAAK,GAAG,CAAC,CAAC;AACd,SAAS,MAAM;IACX,OAAO,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,eAAe;IAC1B,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9D,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;QACnD,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC;AAED,KAAK,UAAU,YAAY;IACvB,+DAA+D;IAC/D,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IAEtC,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IACxE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,6BAA6B;IAChE,MAAM,GAAG,GAAG,GAAG,QAAQ,KAAK,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACxE,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,EAAE,CAAC,UAAU,GAAG,aAAa,CAAC;IAE9B,EAAE,CAAC,SAAS,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE;QAC3B,IAAI,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,IAAmB,CAAC,CAAC;QAErD,kEAAkE;QAClE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1D,IAAI,CAAC;gBACD,gFAAgF;gBAChF,IAAI,OAAO,mBAAmB,KAAK,WAAW,EAAE,CAAC;oBAC7C,MAAM,EAAE,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;oBAC3C,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;oBACvC,IAAI,MAAM,EAAE,CAAC;wBACT,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;wBAC5C,MAAM,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;wBAC9D,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;oBAClC,CAAC;gBACL,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,GAAG,CAAC,CAAC;gBAC9D,OAAO;YACX,CAAC;QACL,CAAC;QAED,mCAAmC;QACnC,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAgC,CAAC;QAE/D,MAAM,cAAc,GAAG,CAAC,GAAgB,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,OAAO;YACX,CAAC;YACD,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACT,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACJ,KAAK,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACrD,CAAC;QACL,CAAC,CAAC;QAEF,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACJ,cAAc,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACL,CAAC,CAAC;IAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;QACd,yDAAyD;QACzD,8DAA8D;IAClE,CAAC,CAAC;IAEF,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;QACd,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAChB,MAAM,GAAG,IAAI,CAAC;YACd,iBAAiB,GAAG,IAAI,CAAC;YACzB,sDAAsD;YACtD,gBAAgB,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,EAAE,CAAC;AACd,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC5B,mDAAmD;IACnD,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,0DAA0D;IAC1D,IAAI,iBAAiB,EAAE,CAAC;QACpB,OAAO,iBAAiB,CAAC;IAC7B,CAAC;IAED,qDAAqD;IACrD,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;QACvD,iBAAiB,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAChD,MAAM,OAAO,GAAG,GAAG,EAAE;gBACjB,MAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBAChD,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;gBAClD,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACtD,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,GAAG,EAAE;gBACpB,OAAO,EAAE,CAAC;gBACV,iBAAiB,GAAG,IAAI,CAAC;gBACzB,OAAO,CAAC,MAAO,CAAC,CAAC;YACrB,CAAC,CAAC;YACF,MAAM,WAAW,GAAG,GAAG,EAAE;gBACrB,OAAO,EAAE,CAAC;gBACV,MAAM,GAAG,IAAI,CAAC;gBACd,iBAAiB,GAAG,IAAI,CAAC;gBACzB,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACrD,CAAC,CAAC;YACF,MAAM,WAAW,GAAG,GAAG,EAAE;gBACrB,OAAO,EAAE,CAAC;gBACV,MAAM,GAAG,IAAI,CAAC;gBACd,iBAAiB,GAAG,IAAI,CAAC;gBACzB,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;YACzD,CAAC,CAAC;YAEF,MAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAC7C,MAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC/C,MAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QACH,OAAO,iBAAiB,CAAC;IAC7B,CAAC;IAED,6CAA6C;IAC7C,iBAAiB,GAAG,CAAC,KAAK,IAAI,EAAE;QAC5B,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;QAC9B,OAAO,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC9C,MAAM,OAAO,GAAG,GAAG,EAAE;gBACjB,MAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;gBAChD,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;gBAClD,MAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YACtD,CAAC,CAAC;YACF,MAAM,UAAU,GAAG,GAAG,EAAE;gBACpB,OAAO,EAAE,CAAC;gBACV,iBAAiB,GAAG,IAAI,CAAC;gBACzB,OAAO,CAAC,MAAO,CAAC,CAAC;YACrB,CAAC,CAAC;YACF,MAAM,WAAW,GAAG,GAAG,EAAE;gBACrB,OAAO,EAAE,CAAC;gBACV,MAAM,GAAG,IAAI,CAAC;gBACd,iBAAiB,GAAG,IAAI,CAAC;gBACzB,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACrD,CAAC,CAAC;YACF,MAAM,WAAW,GAAG,GAAG,EAAE;gBACrB,OAAO,EAAE,CAAC;gBACV,MAAM,GAAG,IAAI,CAAC;gBACd,iBAAiB,GAAG,IAAI,CAAC;gBACzB,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;YACzD,CAAC,CAAC;YAEF,MAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAC7C,MAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC/C,MAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,EAAE,CAAC;IAEL,OAAO,iBAAiB,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAiB,QAAgB,EAAE,IAAY;IAC1E,qHAAqH;IACrH,IAAI,MAAM,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,GAAG,GAAe,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACvD,OAAO,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACvD,YAAY,CAAC,EAAE,EAAE,CAAC,CAAU,EAAE,EAAE,CAAC,OAAO,CAAC,CAAuB,CAAC,EAAE,MAAM,CAAC,CAAC;YAC3E,IAAI,CAAC;gBACD,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;gBACnC,MAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,aAAa,CAAC,EAAE,CAAC,CAAC;gBAClB,MAAM,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACrC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IAEpB,MAAM,GAAG,GAAe,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEvD,OAAO,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACvD,YAAY,CAAC,EAAE,EAAE,CAAC,CAAU,EAAE,EAAE,CAAC,OAAO,CAAC,CAAuB,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3E,IAAI,CAAC;YACD,8BAA8B;YAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YACnC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,aAAa,CAAC,EAAE,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAqC,QAAgB,EAAE,IAAY;IAC5F,OAAO,gBAAgB,CAAiB,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAiB,QAAgB,EAAE,IAAuB,EAAE,OAAO,GAAG,CAAC;IAClG,IAAI,CAAC;QACD,IAAI,sBAAsB,EAAE,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;YACpB,MAAM,GAAG,GAAe,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YAEvD,OAAO,MAAM,IAAI,OAAO,CAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC7D,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAA8C,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC5F,aAAa,EAAE,CAAC;YACpB,CAAC,CAAC,CAAC;QACP,CAAC;QAED,OAAO,MAAM,gBAAgB,CAAiB,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,OAAO,GAAG,WAAW,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,iDAAiD;YACjD,cAAc,EAAE,CAAC;YACjB,wEAAwE;YACxE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,IAAI,OAAO,EAAE,kBAAkB,CAAC,CAAC;YACnF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,GAAG,GAAG,CAAC;YAC/C,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC;YAClE,OAAO,gBAAgB,CAAiB,QAAQ,EAAE,IAAI,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,GAAG,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU;IACtB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO;IACX,CAAC;IACD,qDAAqD;IACrD,IAAI,sBAAsB,EAAE,EAAE,CAAC;QAC3B,OAAO;IACX,CAAC;IACD,yDAAyD;IACzD,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QAC3B,iEAAiE;IACrE,CAAC,CAAC,CAAC;AACP,CAAC;AAED,iFAAiF;AACjF,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IACnE,6DAA6D;IAC7D,MAAM,kBAAkB,GAAG,MAAM,CAAC,mBAAmB,IAAI,CAAC,CAAC,EAAc,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACjG,kBAAkB,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,+EAA+E;AAC/E,gEAAgE;AAChE,+EAA+E;AAC/E,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,qEAAqE;AACrE,wEAAwE;AACxE,6CAA6C;AAC7C,+EAA+E;AAE/E,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC/C,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YAClB,mBAAmB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,CAAC;aAAM,CAAC;YACJ,4BAA4B;YAC5B,IAAI,mBAAmB,KAAK,IAAI,EAAE,CAAC;gBAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,mBAAmB,CAAC;gBACxD,IAAI,cAAc,GAAG,kBAAkB,IAAI,MAAM,EAAE,CAAC;oBAChD,cAAc,EAAE,CAAC;gBACrB,CAAC;gBACD,mBAAmB,GAAG,IAAI,CAAC;YAC/B,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { RpcError } from \"./RpcError.js\";\n\nexport type RpcResult<T> = {\n data: T;\n stats: RpcStats;\n};\n\n/**\n * Transport mode for RPC calls.\n * - \"http\": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)\n * - \"websocket\": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)\n * - \"auto\": Automatically selects based on connection type\n */\nexport type RpcTransport = \"http\" | \"websocket\" | \"auto\";\n\n// Build-time configuration injected by Vite plugin from helium.config.js\ndeclare const __HELIUM_RPC_TRANSPORT__: RpcTransport;\ndeclare const __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__: boolean;\n\n// Read build-time config with fallback defaults\nconst configuredTransport: RpcTransport = typeof __HELIUM_RPC_TRANSPORT__ !== \"undefined\" ? __HELIUM_RPC_TRANSPORT__ : \"websocket\";\nconst configuredAutoHttpOnMobile: boolean = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== \"undefined\" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;\n\n/**\n * Get the configured RPC transport mode (from helium.config.js).\n */\nexport function getRpcTransport(): RpcTransport {\n return configuredTransport;\n}\n\n/**\n * Check if auto HTTP on mobile is enabled (from helium.config.js).\n */\nexport function isAutoHttpOnMobileEnabled(): boolean {\n return configuredAutoHttpOnMobile;\n}\n\n// Detect if we should prefer HTTP transport (mobile/slow networks)\nfunction shouldUseHttpTransport(): boolean {\n if (configuredTransport === \"http\") {\n return true;\n }\n if (configuredTransport === \"websocket\") {\n return false;\n }\n\n // Auto mode: check if mobile HTTP optimization is enabled\n if (!configuredAutoHttpOnMobile) {\n return false;\n }\n\n // Prefer HTTP on mobile/slow connections\n if (typeof navigator !== \"undefined\") {\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n // Use HTTP for cellular connections or slow effective types\n const slowTypes = [\"slow-2g\", \"2g\", \"3g\"];\n if (conn.type === \"cellular\" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {\n return true;\n }\n }\n }\n\n return false;\n}\n\ninterface NetworkInformation {\n type?: string;\n effectiveType?: string;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n connection?: NetworkInformation;\n}\n\n// ============================================================================\n// Batching Logic\n// ============================================================================\n\ntype PendingRequest = {\n req: RpcRequest;\n resolve: (value: RpcResult<any>) => void;\n reject: (reason?: any) => void;\n};\n\nlet pendingBatch: PendingRequest[] = [];\nlet isBatchScheduled = false;\n\nfunction scheduleBatch() {\n if (isBatchScheduled) {\n return;\n }\n isBatchScheduled = true;\n queueMicrotask(() => {\n isBatchScheduled = false;\n flushBatch();\n });\n}\n\nasync function flushBatch() {\n const batch = pendingBatch;\n pendingBatch = [];\n\n if (batch.length === 0) {\n return;\n }\n\n try {\n if (shouldUseHttpTransport()) {\n await sendBatchHttp(batch);\n } else {\n await sendBatchWebSocket(batch);\n }\n } catch (err) {\n // Transport error, fail all\n for (const item of batch) {\n item.reject(err);\n }\n }\n}\n\nasync function sendBatchHttp(batch: PendingRequest[]) {\n const requests = batch.map((b) => b.req);\n const encoded = msgpackEncode(requests);\n\n const response = await fetch(\"/__helium__/rpc\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/msgpack\",\n Accept: \"application/msgpack\",\n },\n body: encoded as unknown as BodyInit,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP RPC failed: ${response.status}`);\n }\n\n const responseBuffer = await response.arrayBuffer();\n const msg = msgpackDecode(new Uint8Array(responseBuffer)) as RpcResponse | RpcResponse[];\n\n const responses = Array.isArray(msg) ? msg : [msg];\n const responseMap = new Map(responses.map((r) => [r.id, r]));\n\n for (const item of batch) {\n const res = responseMap.get(item.req.id);\n if (res) {\n if (res.ok) {\n item.resolve({ data: res.result, stats: res.stats });\n } else {\n item.reject(new RpcError(res.error, res.stats));\n }\n } else {\n item.reject(new RpcError(\"No response for request\"));\n }\n }\n}\n\nasync function sendBatchWebSocket(batch: PendingRequest[]) {\n const ws = await ensureSocketReady();\n const requests = batch.map((b) => b.req);\n\n // Register pending promises with timeout safeguards\n batch.forEach((item) => {\n trackPending(item.req.id, (v: unknown) => item.resolve(v as RpcResult<any>), item.reject);\n });\n\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(requests);\n ws.send(encoded);\n } catch (err) {\n batch.forEach((item) => {\n removePending(item.req.id);\n item.reject(err);\n });\n }\n}\n\n// ============================================================================\n// WebSocket Transport (original implementation)\n// ============================================================================\n\nlet socket: WebSocket | null = null;\nlet connectionPromise: Promise<WebSocket> | null = null;\n\nconst pending = new Map<string | number, { resolve: (v: unknown) => void; reject: (e: unknown) => void }>();\nconst pendingTimeouts = new Map<string | number, ReturnType<typeof setTimeout>>();\n\n// ── Connection resilience constants ──────────────────────────────────────────\n\n/** How long (ms) the page must be hidden before we consider the WebSocket stale. */\nconst STALE_THRESHOLD_MS = 15_000;\n\n/** Max time (ms) to wait for a response before timing out a request. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\n/** Number of automatic retries on retriable connection errors. */\nconst MAX_RETRIES = 3;\n\n/** Base delay (ms) for exponential backoff between retries (doubles each attempt). */\nconst RETRY_BASE_DELAY_MS = 500;\n\n/** Maximum delay (ms) cap for backoff to avoid excessively long waits. */\nconst RETRY_MAX_DELAY_MS = 5_000;\n\n/** Timestamp when the page was last hidden (for visibility-change detection). */\nlet lastHiddenTimestamp: number | null = null;\n\n// ── Pending-request helpers ──────────────────────────────────────────────────\n\n/**\n * Register a pending request with an automatic timeout safeguard.\n * If no response arrives within REQUEST_TIMEOUT_MS the promise is rejected\n * so the caller's retry logic can kick in.\n */\nfunction trackPending(id: string | number, resolve: (v: unknown) => void, reject: (e: unknown) => void): void {\n pending.set(id, { resolve, reject });\n const timer = setTimeout(() => {\n const entry = pending.get(id);\n if (entry) {\n pending.delete(id);\n pendingTimeouts.delete(id);\n entry.reject(new RpcError(\"Request timed out\"));\n }\n }, REQUEST_TIMEOUT_MS);\n pendingTimeouts.set(id, timer);\n}\n\n/**\n * Remove a pending request and clear its timeout.\n * Returns the entry so the caller can resolve/reject it.\n */\nfunction removePending(id: string | number): { resolve: (v: unknown) => void; reject: (e: unknown) => void } | undefined {\n const entry = pending.get(id);\n if (!entry) {\n return undefined;\n }\n pending.delete(id);\n const timer = pendingTimeouts.get(id);\n if (timer) {\n clearTimeout(timer);\n pendingTimeouts.delete(id);\n }\n return entry;\n}\n\n/** Reject every in-flight request (e.g. when the socket closes unexpectedly). */\nfunction rejectAllPending(reason: Error): void {\n for (const timer of pendingTimeouts.values()) {\n clearTimeout(timer);\n }\n pendingTimeouts.clear();\n const entries = [...pending.entries()];\n pending.clear();\n for (const [, entry] of entries) {\n entry.reject(reason);\n }\n}\n\n// ── Reconnection helpers ─────────────────────────────────────────────────────\n\n/**\n * Force-close the current WebSocket so the next call creates a fresh\n * connection (which fetches a brand-new token).\n */\nfunction forceReconnect(): void {\n const oldSocket = socket;\n socket = null;\n connectionPromise = null;\n\n if (oldSocket) {\n // Detach handlers to avoid double-rejecting pending from the close event\n oldSocket.onclose = null;\n oldSocket.onerror = null;\n oldSocket.onmessage = null;\n oldSocket.close();\n }\n\n // Reject all in-flight requests – callers with retry logic will resend\n rejectAllPending(new Error(\"Connection reset\"));\n}\n\n/** Determine whether an error warrants an automatic retry. */\nfunction isRetriableError(err: unknown): boolean {\n // Network / connection errors are always retriable\n if (err instanceof Error && !(err instanceof RpcError)) {\n return true;\n }\n // Timed-out requests are retriable (socket may have died silently)\n if (err instanceof RpcError && err.message === \"Request timed out\") {\n return true;\n }\n return false;\n}\n\n// Clean up WebSocket connection on HMR (Hot Module Replacement)\nif (import.meta.hot) {\n import.meta.hot.dispose(() => {\n if (socket) {\n socket.onclose = null;\n socket.close();\n socket = null;\n connectionPromise = null;\n }\n rejectAllPending(new Error(\"Module reloaded\"));\n });\n}\n\nlet msgId = 0;\nfunction nextId() {\n return msgId++;\n}\n\nasync function fetchFreshToken(): Promise<string | undefined> {\n try {\n const response = await fetch(\"/__helium__/refresh-token\");\n if (!response.ok) {\n console.warn(\"Failed to fetch fresh token:\", response.status);\n return undefined;\n }\n const data = await response.json();\n return data.token;\n } catch (error) {\n console.warn(\"Error fetching fresh token:\", error);\n return undefined;\n }\n}\n\nasync function createSocket(): Promise<WebSocket> {\n // Fetch a fresh token before creating the WebSocket connection\n const token = await fetchFreshToken();\n\n // Use the same protocol, hostname and port as the current page\n const protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const host = window.location.host; // includes hostname and port\n const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : \"\"}`;\n const ws = new WebSocket(url);\n ws.binaryType = \"arraybuffer\";\n\n ws.onmessage = async (event) => {\n let data = new Uint8Array(event.data as ArrayBuffer);\n\n // Check for Gzip header (0x1f 0x8b) to detect compressed messages\n if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {\n try {\n // Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)\n if (typeof DecompressionStream !== \"undefined\") {\n const ds = new DecompressionStream(\"gzip\");\n const stream = new Response(data).body;\n if (stream) {\n const decompressed = stream.pipeThrough(ds);\n const buffer = await new Response(decompressed).arrayBuffer();\n data = new Uint8Array(buffer);\n }\n }\n } catch (err) {\n console.error(\"Failed to decompress WebSocket message:\", err);\n return;\n }\n }\n\n // Always expect binary MessagePack\n const msg = msgpackDecode(data) as RpcResponse | RpcResponse[];\n\n const handleResponse = (res: RpcResponse) => {\n const entry = removePending(res.id);\n if (!entry) {\n return;\n }\n if (res.ok) {\n entry.resolve({ data: res.result, stats: res.stats });\n } else {\n entry.reject(new RpcError(res.error, res.stats));\n }\n };\n\n if (Array.isArray(msg)) {\n msg.forEach(handleResponse);\n } else {\n handleResponse(msg);\n }\n };\n\n ws.onerror = () => {\n // WebSocket errors are always followed by a close event.\n // The close handler takes care of rejecting pending promises.\n };\n\n ws.onclose = () => {\n if (socket === ws) {\n socket = null;\n connectionPromise = null;\n // Reject every in-flight request so callers can retry\n rejectAllPending(new Error(\"WebSocket connection closed\"));\n }\n };\n\n return ws;\n}\n\nasync function ensureSocketReady(): Promise<WebSocket> {\n // If we have an open socket, return it immediately\n if (socket && socket.readyState === WebSocket.OPEN) {\n return socket;\n }\n\n // If we have a connection in progress, reuse that promise\n if (connectionPromise) {\n return connectionPromise;\n }\n\n // If we have a socket that's connecting, wait for it\n if (socket && socket.readyState === WebSocket.CONNECTING) {\n connectionPromise = new Promise((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n return connectionPromise;\n }\n\n // Create a new socket and connection promise\n connectionPromise = (async () => {\n socket = await createSocket();\n return new Promise<WebSocket>((resolve, reject) => {\n const cleanup = () => {\n socket!.removeEventListener(\"open\", handleOpen);\n socket!.removeEventListener(\"error\", handleError);\n socket!.removeEventListener(\"close\", handleClose);\n };\n const handleOpen = () => {\n cleanup();\n connectionPromise = null;\n resolve(socket!);\n };\n const handleError = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket connection failed\"));\n };\n const handleClose = () => {\n cleanup();\n socket = null;\n connectionPromise = null;\n reject(new Error(\"WebSocket closed before opening\"));\n };\n\n socket!.addEventListener(\"open\", handleOpen);\n socket!.addEventListener(\"error\", handleError);\n socket!.addEventListener(\"close\", handleClose);\n });\n })();\n\n return connectionPromise;\n}\n\nasync function rpcCallWebSocket<TResult, TArgs>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n // Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)\n if (socket && socket.readyState === WebSocket.OPEN) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n const encoded = msgpackEncode(req);\n socket!.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n }\n\n const ws = await ensureSocketReady();\n const id = nextId();\n\n const req: RpcRequest = { id, method: methodId, args };\n\n return new Promise<RpcResult<TResult>>((resolve, reject) => {\n trackPending(id, (v: unknown) => resolve(v as RpcResult<TResult>), reject);\n try {\n // Always use msgpack encoding\n const encoded = msgpackEncode(req);\n ws.send(encoded);\n } catch (err) {\n removePending(id);\n reject(err);\n }\n });\n}\n\n/**\n * Make an RPC call using the appropriate transport.\n * Automatically selects HTTP or WebSocket based on network conditions and configuration.\n *\n * Includes automatic retry logic: if a call fails due to a connection error\n * (e.g. stale WebSocket after mobile browser was backgrounded), the client\n * forces a fresh connection (with a new token) and retries once.\n */\nexport async function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>> {\n return rpcCallWithRetry<TResult, TArgs>(methodId, args);\n}\n\nasync function rpcCallWithRetry<TResult, TArgs>(methodId: string, args: TArgs | undefined, attempt = 0): Promise<RpcResult<TResult>> {\n try {\n if (shouldUseHttpTransport()) {\n const id = nextId();\n const req: RpcRequest = { id, method: methodId, args };\n\n return await new Promise<RpcResult<TResult>>((resolve, reject) => {\n pendingBatch.push({ req, resolve: resolve as (value: RpcResult<TResult>) => void, reject });\n scheduleBatch();\n });\n }\n\n return await rpcCallWebSocket<TResult, TArgs>(methodId, args);\n } catch (err) {\n if (attempt < MAX_RETRIES && isRetriableError(err)) {\n // Force a fresh connection (fetches a new token)\n forceReconnect();\n // Exponential backoff with jitter: 500ms, 1000ms, 2000ms (capped at 5s)\n const baseDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** attempt, RETRY_MAX_DELAY_MS);\n const jitter = Math.random() * baseDelay * 0.3;\n await new Promise<void>((r) => setTimeout(r, baseDelay + jitter));\n return rpcCallWithRetry<TResult, TArgs>(methodId, args, attempt + 1);\n }\n throw err;\n }\n}\n\n/**\n * Pre-establishes the WebSocket connection.\n * Call this early (e.g., on page load) to avoid connection latency on first RPC call.\n * This is especially beneficial on high-latency networks like mobile LTE.\n * Note: Only effective when using WebSocket transport (not HTTP transport).\n */\nexport function preconnect(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n // Only preconnect if we're using WebSocket transport\n if (shouldUseHttpTransport()) {\n return;\n }\n // Fire and forget - establishes connection in background\n ensureSocketReady().catch(() => {\n // Silently ignore preconnect failures, will retry on actual call\n });\n}\n\n// Auto-preconnect when the module loads (browser only, WebSocket transport only)\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Use requestIdleCallback if available, otherwise setTimeout\n const schedulePreconnect = window.requestIdleCallback || ((cb: () => void) => setTimeout(cb, 1));\n schedulePreconnect(() => preconnect());\n}\n\n// ============================================================================\n// Visibility-change reconnection (critical for mobile browsers)\n// ============================================================================\n//\n// Mobile browsers freeze or kill WebSocket connections when the tab is\n// backgrounded. When the user returns the socket may *appear* open but\n// is actually stale. We detect this via the Page Visibility API and\n// proactively tear down the old connection so the next RPC call creates\n// a fresh one (with a brand-new auth token).\n// ============================================================================\n\nif (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", () => {\n if (document.hidden) {\n lastHiddenTimestamp = Date.now();\n } else {\n // Page became visible again\n if (lastHiddenTimestamp !== null) {\n const hiddenDuration = Date.now() - lastHiddenTimestamp;\n if (hiddenDuration > STALE_THRESHOLD_MS && socket) {\n forceReconnect();\n }\n lastHiddenTimestamp = null;\n }\n }\n });\n}\n"]}
|
|
@@ -8,6 +8,8 @@ import type { MethodStub } from "./types.js";
|
|
|
8
8
|
* window focus/visibility change and re-run the fetch.
|
|
9
9
|
* - showLoaderOnRefocus: when false (default), refetches triggered by window
|
|
10
10
|
* focus/visibility will update data silently without showing the loading state.
|
|
11
|
+
* - showLoaderOnInvalidate: when false (default), refetches triggered by cache
|
|
12
|
+
* invalidation will update data silently without showing the loading state.
|
|
11
13
|
* - enabled: disable automatic fetching (defaults to true) — useful when
|
|
12
14
|
* you only want to fetch when a required value (e.g. id) is present.
|
|
13
15
|
*/
|
|
@@ -15,6 +17,7 @@ export interface UseFetchOptions {
|
|
|
15
17
|
ttl?: number;
|
|
16
18
|
refetchOnWindowFocus?: boolean;
|
|
17
19
|
showLoaderOnRefocus?: boolean;
|
|
20
|
+
showLoaderOnInvalidate?: boolean;
|
|
18
21
|
enabled?: boolean;
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFetch.d.ts","sourceRoot":"","sources":["../../src/client/useFetch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"useFetch.d.ts","sourceRoot":"","sources":["../../src/client/useFetch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAsED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,eAAe;;;;;2BA4OvE,OAAO;EAGnD"}
|
package/dist/client/useFetch.js
CHANGED
|
@@ -69,7 +69,7 @@ function setupFocusListeners() {
|
|
|
69
69
|
export function useFetch(method, args, options) {
|
|
70
70
|
// Compute cache key
|
|
71
71
|
const key = cacheKey(method.__id, args);
|
|
72
|
-
const { ttl, refetchOnWindowFocus = true, showLoaderOnRefocus = false, enabled = true } = options ?? {};
|
|
72
|
+
const { ttl, refetchOnWindowFocus = true, showLoaderOnRefocus = false, showLoaderOnInvalidate = false, enabled = true } = options ?? {};
|
|
73
73
|
// Use refs to store latest values without causing effect re-runs
|
|
74
74
|
const methodIdRef = useRef(method.__id);
|
|
75
75
|
const argsRef = useRef(args);
|
|
@@ -77,6 +77,7 @@ export function useFetch(method, args, options) {
|
|
|
77
77
|
const ttlRef = useRef(ttl);
|
|
78
78
|
const enabledRef = useRef(enabled);
|
|
79
79
|
const showLoaderOnRefocusRef = useRef(showLoaderOnRefocus);
|
|
80
|
+
const showLoaderOnInvalidateRef = useRef(showLoaderOnInvalidate);
|
|
80
81
|
// Update refs on each render
|
|
81
82
|
methodIdRef.current = method.__id;
|
|
82
83
|
argsRef.current = args;
|
|
@@ -84,6 +85,7 @@ export function useFetch(method, args, options) {
|
|
|
84
85
|
ttlRef.current = ttl;
|
|
85
86
|
enabledRef.current = enabled;
|
|
86
87
|
showLoaderOnRefocusRef.current = showLoaderOnRefocus;
|
|
88
|
+
showLoaderOnInvalidateRef.current = showLoaderOnInvalidate;
|
|
87
89
|
// Track if component is mounted
|
|
88
90
|
const isMountedRef = useRef(true);
|
|
89
91
|
const [data, setData] = useState(() => (has(key) ? get(key) : undefined));
|
|
@@ -238,7 +240,7 @@ export function useFetch(method, args, options) {
|
|
|
238
240
|
useEffect(() => {
|
|
239
241
|
const unsubscribe = subscribeInvalidations((methodId) => {
|
|
240
242
|
if (methodId === methodIdRef.current && enabledRef.current && isMountedRef.current && !isPending(keyRef.current)) {
|
|
241
|
-
doFetch(
|
|
243
|
+
doFetch(showLoaderOnInvalidateRef.current);
|
|
242
244
|
}
|
|
243
245
|
});
|
|
244
246
|
return unsubscribe;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFetch.js","sourceRoot":"","sources":["../../src/client/useFetch.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type { RpcStats } from \"../runtime/protocol.js\";\nimport { cacheKey, get, getPendingFetch, has, isPending, set, setPendingFetch, subscribeInvalidations } from \"./cache.js\";\nimport { rpcCall } from \"./rpcClient.js\";\nimport { RpcError } from \"./RpcError.js\";\nimport type { MethodStub } from \"./types.js\";\n\n/**\n * Options controlling `useFetch` behaviour.\n *\n * - ttl: optional time-to-live for the cached response (milliseconds).\n * - refetchOnWindowFocus: when true the hook will invalidate cache on\n * window focus/visibility change and re-run the fetch.\n * - showLoaderOnRefocus: when false (default), refetches triggered by window\n * focus/visibility will update data silently without showing the loading state.\n * - enabled: disable automatic fetching (defaults to true) — useful when\n * you only want to fetch when a required value (e.g. id) is present.\n */\nexport interface UseFetchOptions {\n ttl?: number; // TTL in milliseconds\n refetchOnWindowFocus?: boolean; // Whether to refetch when tab becomes visible\n showLoaderOnRefocus?: boolean; // Whether to show loader when refetching on focus (defaults to false)\n enabled?: boolean; // Whether to fetch data. Defaults to true. Useful for conditional fetching (e.g., only fetch when an ID exists)\n}\n\n// Store focus refetch callbacks globally (survives HMR)\ntype FocusCallback = (showLoader: boolean) => void;\n\nfunction getFocusCallbacksSet(): Set<FocusCallback> {\n if (typeof window === \"undefined\") {\n return new Set();\n }\n const win = window as typeof window & {\n __heliumFocusCallbacks?: Set<FocusCallback>;\n };\n if (!win.__heliumFocusCallbacks) {\n win.__heliumFocusCallbacks = new Set();\n }\n return win.__heliumFocusCallbacks;\n}\n\nfunction getVisibilityState(): { registered: boolean; lastTrigger: number } {\n if (typeof window === \"undefined\") {\n return { registered: false, lastTrigger: 0 };\n }\n const win = window as typeof window & {\n __heliumVisibilityState?: { registered: boolean; lastTrigger: number };\n };\n if (!win.__heliumVisibilityState) {\n win.__heliumVisibilityState = { registered: false, lastTrigger: 0 };\n }\n return win.__heliumVisibilityState;\n}\n\n// Minimum time between focus-triggered refetches (debounce)\nconst FOCUS_DEBOUNCE_MS = 2000;\n\nfunction setupFocusListeners() {\n const state = getVisibilityState();\n if (state.registered || typeof document === \"undefined\") {\n return;\n }\n state.registered = true;\n\n const triggerRefetch = () => {\n const now = Date.now();\n // Debounce to prevent rapid refetches during HMR\n if (now - state.lastTrigger < FOCUS_DEBOUNCE_MS) {\n return;\n }\n state.lastTrigger = now;\n\n // Get all registered callbacks and call them\n const callbacks = getFocusCallbacksSet();\n callbacks.forEach((cb) => {\n try {\n cb(false); // Silent refetch on focus\n } catch {\n // Ignore errors from stale callbacks\n }\n });\n };\n\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n triggerRefetch();\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange, { passive: true });\n window.addEventListener(\"focus\", triggerRefetch, { passive: true });\n}\n\n/**\n * React hook for fetching and caching the result of a server method.\n *\n * @template TArgs - method argument type\n * @template TResult - expected return type\n * @param method - a MethodStub representing the server method to call\n * @param args - optional argument object passed to the server method\n * @param options - controls caching and refetch behavior (see UseFetchOptions)\n * @returns { data, isLoading, error, stats, refetch } — `data` is the cached or latest value; `refetch` triggers an immediate request\n */\nexport function useFetch<TArgs, TResult>(method: MethodStub<TArgs, TResult>, args?: TArgs, options?: UseFetchOptions) {\n // Compute cache key\n const key = cacheKey(method.__id, args);\n\n const { ttl, refetchOnWindowFocus = true, showLoaderOnRefocus = false, enabled = true } = options ?? {};\n\n // Use refs to store latest values without causing effect re-runs\n const methodIdRef = useRef(method.__id);\n const argsRef = useRef(args);\n const keyRef = useRef(key);\n const ttlRef = useRef(ttl);\n const enabledRef = useRef(enabled);\n const showLoaderOnRefocusRef = useRef(showLoaderOnRefocus);\n\n // Update refs on each render\n methodIdRef.current = method.__id;\n argsRef.current = args;\n keyRef.current = key;\n ttlRef.current = ttl;\n enabledRef.current = enabled;\n showLoaderOnRefocusRef.current = showLoaderOnRefocus;\n\n // Track if component is mounted\n const isMountedRef = useRef(true);\n\n const [data, setData] = useState<TResult | undefined>(() => (has(key) ? get<TResult>(key) : undefined));\n const [isLoading, setLoading] = useState(!has(key) && enabled);\n const [error, setError] = useState<string | null>(null);\n const [stats, setStats] = useState<RpcStats | null>(null);\n\n // Core fetch function using refs (stable reference)\n // Uses global deduplication to prevent multiple fetches for the same key\n const doFetch = useCallback(async (showLoader: boolean = true): Promise<TResult | undefined> => {\n if (!isMountedRef.current) {\n return undefined;\n }\n\n const currentKey = keyRef.current;\n\n // Check if there's already a pending fetch for this key (global deduplication)\n const existingFetch = getPendingFetch<{ data: TResult; stats: RpcStats }>(currentKey);\n if (existingFetch) {\n // Wait for the existing fetch and use its result\n if (showLoader) {\n setLoading(true);\n }\n try {\n const result = await existingFetch;\n if (isMountedRef.current) {\n setData(result.data);\n setStats(result.stats);\n setError(null);\n }\n return result.data;\n } catch (err: unknown) {\n if (isMountedRef.current) {\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n }\n return undefined;\n } finally {\n if (isMountedRef.current && showLoader) {\n setLoading(false);\n }\n }\n }\n\n if (showLoader) {\n setLoading(true);\n }\n setError(null);\n\n // Create the fetch promise and register it globally\n const fetchPromise = rpcCall<TResult, TArgs>(methodIdRef.current, argsRef.current as TArgs);\n const dedupedPromise = setPendingFetch(currentKey, fetchPromise);\n\n try {\n const result = await dedupedPromise;\n if (!isMountedRef.current) {\n return undefined;\n }\n set(currentKey, result.data, ttlRef.current);\n setData(result.data);\n setStats(result.stats);\n return result.data;\n } catch (err: unknown) {\n if (!isMountedRef.current) {\n return undefined;\n }\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n return undefined;\n } finally {\n if (isMountedRef.current && showLoader) {\n setLoading(false);\n }\n }\n }, []); // No dependencies - uses refs\n\n // Track mounted state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // Sync data from cache when key changes\n useEffect(() => {\n if (has(key)) {\n const cachedData = get<TResult>(key);\n if (cachedData !== undefined) {\n setData(cachedData);\n setLoading(false);\n }\n }\n }, [key]);\n\n // Initial fetch on mount or when key/enabled changes\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n\n // Only fetch if not in cache and not already pending globally\n if (!has(key) && !isPending(key)) {\n doFetch(true);\n } else if (isPending(key)) {\n // There's a pending fetch - wait for it\n setLoading(true);\n const pendingFetch = getPendingFetch<{ data: TResult; stats: RpcStats }>(key);\n if (pendingFetch) {\n pendingFetch\n .then((result) => {\n if (isMountedRef.current) {\n setData(result.data);\n setStats(result.stats);\n setError(null);\n }\n })\n .catch((err: unknown) => {\n if (isMountedRef.current) {\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n }\n })\n .finally(() => {\n if (isMountedRef.current) {\n setLoading(false);\n }\n });\n }\n }\n }, [key, enabled, doFetch]);\n\n // Register for focus/visibility refetch\n useEffect(() => {\n if (!refetchOnWindowFocus) {\n return;\n }\n\n // Setup global focus listeners once\n setupFocusListeners();\n\n // Create a stable callback for this hook instance\n const focusCallback: FocusCallback = (showLoader: boolean) => {\n if (enabledRef.current && isMountedRef.current && !isPending(keyRef.current)) {\n doFetch(showLoaderOnRefocusRef.current || showLoader);\n }\n };\n\n // Register this callback\n const callbacks = getFocusCallbacksSet();\n callbacks.add(focusCallback);\n\n return () => {\n callbacks.delete(focusCallback);\n };\n }, [refetchOnWindowFocus, doFetch]);\n\n // Subscribe to cache invalidations (from useCall or manual invalidation)\n useEffect(() => {\n const unsubscribe = subscribeInvalidations((methodId) => {\n if (methodId === methodIdRef.current && enabledRef.current && isMountedRef.current && !isPending(keyRef.current)) {\n doFetch(true);\n }\n });\n\n return unsubscribe;\n }, [doFetch]);\n\n // TTL-based auto-refetch\n useEffect(() => {\n if (!enabled || !ttl) {\n return;\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let isActive = true;\n\n const scheduleRefetch = () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(async () => {\n if (!isActive || !enabledRef.current || !isMountedRef.current) {\n return;\n }\n await doFetch(false); // Silent refetch for TTL\n if (isActive) {\n scheduleRefetch();\n }\n }, ttl);\n };\n\n // Only schedule if data is already cached\n if (has(key)) {\n scheduleRefetch();\n }\n\n return () => {\n isActive = false;\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n };\n }, [key, ttl, enabled, doFetch]);\n\n // Public refetch function\n const refetch = useCallback((showLoader: boolean = true) => doFetch(showLoader), [doFetch]);\n\n return { data, isLoading, error, stats, refetch };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useFetch.js","sourceRoot":"","sources":["../../src/client/useFetch.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type { RpcStats } from \"../runtime/protocol.js\";\nimport { cacheKey, get, getPendingFetch, has, isPending, set, setPendingFetch, subscribeInvalidations } from \"./cache.js\";\nimport { rpcCall } from \"./rpcClient.js\";\nimport { RpcError } from \"./RpcError.js\";\nimport type { MethodStub } from \"./types.js\";\n\n/**\n * Options controlling `useFetch` behaviour.\n *\n * - ttl: optional time-to-live for the cached response (milliseconds).\n * - refetchOnWindowFocus: when true the hook will invalidate cache on\n * window focus/visibility change and re-run the fetch.\n * - showLoaderOnRefocus: when false (default), refetches triggered by window\n * focus/visibility will update data silently without showing the loading state.\n * - showLoaderOnInvalidate: when false (default), refetches triggered by cache\n * invalidation will update data silently without showing the loading state.\n * - enabled: disable automatic fetching (defaults to true) — useful when\n * you only want to fetch when a required value (e.g. id) is present.\n */\nexport interface UseFetchOptions {\n ttl?: number; // TTL in milliseconds\n refetchOnWindowFocus?: boolean; // Whether to refetch when tab becomes visible\n showLoaderOnRefocus?: boolean; // Whether to show loader when refetching on focus (defaults to false)\n showLoaderOnInvalidate?: boolean; // Whether to show loader when refetching on cache invalidation (defaults to false)\n enabled?: boolean; // Whether to fetch data. Defaults to true. Useful for conditional fetching (e.g., only fetch when an ID exists)\n}\n\n// Store focus refetch callbacks globally (survives HMR)\ntype FocusCallback = (showLoader: boolean) => void;\n\nfunction getFocusCallbacksSet(): Set<FocusCallback> {\n if (typeof window === \"undefined\") {\n return new Set();\n }\n const win = window as typeof window & {\n __heliumFocusCallbacks?: Set<FocusCallback>;\n };\n if (!win.__heliumFocusCallbacks) {\n win.__heliumFocusCallbacks = new Set();\n }\n return win.__heliumFocusCallbacks;\n}\n\nfunction getVisibilityState(): { registered: boolean; lastTrigger: number } {\n if (typeof window === \"undefined\") {\n return { registered: false, lastTrigger: 0 };\n }\n const win = window as typeof window & {\n __heliumVisibilityState?: { registered: boolean; lastTrigger: number };\n };\n if (!win.__heliumVisibilityState) {\n win.__heliumVisibilityState = { registered: false, lastTrigger: 0 };\n }\n return win.__heliumVisibilityState;\n}\n\n// Minimum time between focus-triggered refetches (debounce)\nconst FOCUS_DEBOUNCE_MS = 2000;\n\nfunction setupFocusListeners() {\n const state = getVisibilityState();\n if (state.registered || typeof document === \"undefined\") {\n return;\n }\n state.registered = true;\n\n const triggerRefetch = () => {\n const now = Date.now();\n // Debounce to prevent rapid refetches during HMR\n if (now - state.lastTrigger < FOCUS_DEBOUNCE_MS) {\n return;\n }\n state.lastTrigger = now;\n\n // Get all registered callbacks and call them\n const callbacks = getFocusCallbacksSet();\n callbacks.forEach((cb) => {\n try {\n cb(false); // Silent refetch on focus\n } catch {\n // Ignore errors from stale callbacks\n }\n });\n };\n\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n triggerRefetch();\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange, { passive: true });\n window.addEventListener(\"focus\", triggerRefetch, { passive: true });\n}\n\n/**\n * React hook for fetching and caching the result of a server method.\n *\n * @template TArgs - method argument type\n * @template TResult - expected return type\n * @param method - a MethodStub representing the server method to call\n * @param args - optional argument object passed to the server method\n * @param options - controls caching and refetch behavior (see UseFetchOptions)\n * @returns { data, isLoading, error, stats, refetch } — `data` is the cached or latest value; `refetch` triggers an immediate request\n */\nexport function useFetch<TArgs, TResult>(method: MethodStub<TArgs, TResult>, args?: TArgs, options?: UseFetchOptions) {\n // Compute cache key\n const key = cacheKey(method.__id, args);\n\n const { ttl, refetchOnWindowFocus = true, showLoaderOnRefocus = false, showLoaderOnInvalidate = false, enabled = true } = options ?? {};\n\n // Use refs to store latest values without causing effect re-runs\n const methodIdRef = useRef(method.__id);\n const argsRef = useRef(args);\n const keyRef = useRef(key);\n const ttlRef = useRef(ttl);\n const enabledRef = useRef(enabled);\n const showLoaderOnRefocusRef = useRef(showLoaderOnRefocus);\n const showLoaderOnInvalidateRef = useRef(showLoaderOnInvalidate);\n\n // Update refs on each render\n methodIdRef.current = method.__id;\n argsRef.current = args;\n keyRef.current = key;\n ttlRef.current = ttl;\n enabledRef.current = enabled;\n showLoaderOnRefocusRef.current = showLoaderOnRefocus;\n showLoaderOnInvalidateRef.current = showLoaderOnInvalidate;\n\n // Track if component is mounted\n const isMountedRef = useRef(true);\n\n const [data, setData] = useState<TResult | undefined>(() => (has(key) ? get<TResult>(key) : undefined));\n const [isLoading, setLoading] = useState(!has(key) && enabled);\n const [error, setError] = useState<string | null>(null);\n const [stats, setStats] = useState<RpcStats | null>(null);\n\n // Core fetch function using refs (stable reference)\n // Uses global deduplication to prevent multiple fetches for the same key\n const doFetch = useCallback(async (showLoader: boolean = true): Promise<TResult | undefined> => {\n if (!isMountedRef.current) {\n return undefined;\n }\n\n const currentKey = keyRef.current;\n\n // Check if there's already a pending fetch for this key (global deduplication)\n const existingFetch = getPendingFetch<{ data: TResult; stats: RpcStats }>(currentKey);\n if (existingFetch) {\n // Wait for the existing fetch and use its result\n if (showLoader) {\n setLoading(true);\n }\n try {\n const result = await existingFetch;\n if (isMountedRef.current) {\n setData(result.data);\n setStats(result.stats);\n setError(null);\n }\n return result.data;\n } catch (err: unknown) {\n if (isMountedRef.current) {\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n }\n return undefined;\n } finally {\n if (isMountedRef.current && showLoader) {\n setLoading(false);\n }\n }\n }\n\n if (showLoader) {\n setLoading(true);\n }\n setError(null);\n\n // Create the fetch promise and register it globally\n const fetchPromise = rpcCall<TResult, TArgs>(methodIdRef.current, argsRef.current as TArgs);\n const dedupedPromise = setPendingFetch(currentKey, fetchPromise);\n\n try {\n const result = await dedupedPromise;\n if (!isMountedRef.current) {\n return undefined;\n }\n set(currentKey, result.data, ttlRef.current);\n setData(result.data);\n setStats(result.stats);\n return result.data;\n } catch (err: unknown) {\n if (!isMountedRef.current) {\n return undefined;\n }\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n return undefined;\n } finally {\n if (isMountedRef.current && showLoader) {\n setLoading(false);\n }\n }\n }, []); // No dependencies - uses refs\n\n // Track mounted state\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n // Sync data from cache when key changes\n useEffect(() => {\n if (has(key)) {\n const cachedData = get<TResult>(key);\n if (cachedData !== undefined) {\n setData(cachedData);\n setLoading(false);\n }\n }\n }, [key]);\n\n // Initial fetch on mount or when key/enabled changes\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n\n // Only fetch if not in cache and not already pending globally\n if (!has(key) && !isPending(key)) {\n doFetch(true);\n } else if (isPending(key)) {\n // There's a pending fetch - wait for it\n setLoading(true);\n const pendingFetch = getPendingFetch<{ data: TResult; stats: RpcStats }>(key);\n if (pendingFetch) {\n pendingFetch\n .then((result) => {\n if (isMountedRef.current) {\n setData(result.data);\n setStats(result.stats);\n setError(null);\n }\n })\n .catch((err: unknown) => {\n if (isMountedRef.current) {\n const rpcError = err instanceof RpcError ? err : new RpcError(err instanceof Error ? err.message : \"Unknown error\");\n setError(rpcError.message);\n setStats(rpcError.stats);\n }\n })\n .finally(() => {\n if (isMountedRef.current) {\n setLoading(false);\n }\n });\n }\n }\n }, [key, enabled, doFetch]);\n\n // Register for focus/visibility refetch\n useEffect(() => {\n if (!refetchOnWindowFocus) {\n return;\n }\n\n // Setup global focus listeners once\n setupFocusListeners();\n\n // Create a stable callback for this hook instance\n const focusCallback: FocusCallback = (showLoader: boolean) => {\n if (enabledRef.current && isMountedRef.current && !isPending(keyRef.current)) {\n doFetch(showLoaderOnRefocusRef.current || showLoader);\n }\n };\n\n // Register this callback\n const callbacks = getFocusCallbacksSet();\n callbacks.add(focusCallback);\n\n return () => {\n callbacks.delete(focusCallback);\n };\n }, [refetchOnWindowFocus, doFetch]);\n\n // Subscribe to cache invalidations (from useCall or manual invalidation)\n useEffect(() => {\n const unsubscribe = subscribeInvalidations((methodId) => {\n if (methodId === methodIdRef.current && enabledRef.current && isMountedRef.current && !isPending(keyRef.current)) {\n doFetch(showLoaderOnInvalidateRef.current);\n }\n });\n\n return unsubscribe;\n }, [doFetch]);\n\n // TTL-based auto-refetch\n useEffect(() => {\n if (!enabled || !ttl) {\n return;\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n let isActive = true;\n\n const scheduleRefetch = () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n timeoutId = setTimeout(async () => {\n if (!isActive || !enabledRef.current || !isMountedRef.current) {\n return;\n }\n await doFetch(false); // Silent refetch for TTL\n if (isActive) {\n scheduleRefetch();\n }\n }, ttl);\n };\n\n // Only schedule if data is already cached\n if (has(key)) {\n scheduleRefetch();\n }\n\n return () => {\n isActive = false;\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n };\n }, [key, ttl, enabled, doFetch]);\n\n // Public refetch function\n const refetch = useCallback((showLoader: boolean = true) => doFetch(showLoader), [doFetch]);\n\n return { data, isLoading, error, stats, refetch };\n}\n"]}
|