heliumts 0.5.7 → 0.5.9
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/bin/helium.js +0 -0
- package/dist/client/useFetch.d.ts.map +1 -1
- package/dist/client/useFetch.js +35 -6
- package/dist/client/useFetch.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/ipExtractor.d.ts +48 -0
- package/dist/server/ipExtractor.d.ts.map +1 -0
- package/dist/server/ipExtractor.js +96 -0
- package/dist/server/ipExtractor.js.map +1 -0
- package/dist/server/rpcRegistry.d.ts +3 -0
- package/dist/server/rpcRegistry.d.ts.map +1 -1
- package/dist/server/rpcRegistry.js +32 -1
- package/dist/server/rpcRegistry.js.map +1 -1
- package/dist/utils/deepEqual.d.ts +1 -0
- package/dist/utils/deepEqual.d.ts.map +1 -0
- package/dist/utils/deepEqual.js +2 -0
- package/dist/utils/deepEqual.js.map +1 -0
- package/dist/utils/formatError.d.ts +2 -0
- package/dist/utils/formatError.d.ts.map +1 -0
- package/dist/utils/formatError.js +18 -0
- package/dist/utils/formatError.js.map +1 -0
- package/package.json +1 -1
package/dist/bin/helium.js
CHANGED
|
File without changes
|
|
@@ -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;;;;;;;;;;;;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;;;;;
|
|
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;;;;;2BA+QvE,OAAO;EAGnD"}
|
package/dist/client/useFetch.js
CHANGED
|
@@ -78,6 +78,8 @@ export function useFetch(method, args, options) {
|
|
|
78
78
|
const enabledRef = useRef(enabled);
|
|
79
79
|
const showLoaderOnRefocusRef = useRef(showLoaderOnRefocus);
|
|
80
80
|
const showLoaderOnInvalidateRef = useRef(showLoaderOnInvalidate);
|
|
81
|
+
const queuedRefetchRef = useRef(false);
|
|
82
|
+
const queuedRefetchShowLoaderRef = useRef(false);
|
|
81
83
|
// Update refs on each render
|
|
82
84
|
methodIdRef.current = method.__id;
|
|
83
85
|
argsRef.current = args;
|
|
@@ -92,6 +94,10 @@ export function useFetch(method, args, options) {
|
|
|
92
94
|
const [isLoading, setLoading] = useState(!has(key) && enabled);
|
|
93
95
|
const [error, setError] = useState(null);
|
|
94
96
|
const [stats, setStats] = useState(null);
|
|
97
|
+
const queueRefetch = useCallback((showLoader) => {
|
|
98
|
+
queuedRefetchRef.current = true;
|
|
99
|
+
queuedRefetchShowLoaderRef.current = queuedRefetchShowLoaderRef.current || showLoader;
|
|
100
|
+
}, []);
|
|
95
101
|
// Core fetch function using refs (stable reference)
|
|
96
102
|
// Uses global deduplication to prevent multiple fetches for the same key
|
|
97
103
|
const doFetch = useCallback(async (showLoader = true) => {
|
|
@@ -127,6 +133,12 @@ export function useFetch(method, args, options) {
|
|
|
127
133
|
if (isMountedRef.current && showLoader) {
|
|
128
134
|
setLoading(false);
|
|
129
135
|
}
|
|
136
|
+
if (queuedRefetchRef.current && isMountedRef.current && enabledRef.current && !isPending(keyRef.current)) {
|
|
137
|
+
const nextShowLoader = queuedRefetchShowLoaderRef.current;
|
|
138
|
+
queuedRefetchRef.current = false;
|
|
139
|
+
queuedRefetchShowLoaderRef.current = false;
|
|
140
|
+
void doFetch(nextShowLoader);
|
|
141
|
+
}
|
|
130
142
|
}
|
|
131
143
|
}
|
|
132
144
|
if (showLoader) {
|
|
@@ -159,6 +171,12 @@ export function useFetch(method, args, options) {
|
|
|
159
171
|
if (isMountedRef.current && showLoader) {
|
|
160
172
|
setLoading(false);
|
|
161
173
|
}
|
|
174
|
+
if (queuedRefetchRef.current && isMountedRef.current && enabledRef.current && !isPending(keyRef.current)) {
|
|
175
|
+
const nextShowLoader = queuedRefetchShowLoaderRef.current;
|
|
176
|
+
queuedRefetchRef.current = false;
|
|
177
|
+
queuedRefetchShowLoaderRef.current = false;
|
|
178
|
+
void doFetch(nextShowLoader);
|
|
179
|
+
}
|
|
162
180
|
}
|
|
163
181
|
}, []); // No dependencies - uses refs
|
|
164
182
|
// Track mounted state
|
|
@@ -225,9 +243,15 @@ export function useFetch(method, args, options) {
|
|
|
225
243
|
setupFocusListeners();
|
|
226
244
|
// Create a stable callback for this hook instance
|
|
227
245
|
const focusCallback = (showLoader) => {
|
|
228
|
-
if (enabledRef.current
|
|
229
|
-
|
|
246
|
+
if (!enabledRef.current || !isMountedRef.current) {
|
|
247
|
+
return;
|
|
230
248
|
}
|
|
249
|
+
const shouldShowLoader = showLoaderOnRefocusRef.current || showLoader;
|
|
250
|
+
if (isPending(keyRef.current)) {
|
|
251
|
+
queueRefetch(shouldShowLoader);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
void doFetch(shouldShowLoader);
|
|
231
255
|
};
|
|
232
256
|
// Register this callback
|
|
233
257
|
const callbacks = getFocusCallbacksSet();
|
|
@@ -235,16 +259,21 @@ export function useFetch(method, args, options) {
|
|
|
235
259
|
return () => {
|
|
236
260
|
callbacks.delete(focusCallback);
|
|
237
261
|
};
|
|
238
|
-
}, [refetchOnWindowFocus, doFetch]);
|
|
262
|
+
}, [refetchOnWindowFocus, doFetch, queueRefetch]);
|
|
239
263
|
// Subscribe to cache invalidations (from useCall or manual invalidation)
|
|
240
264
|
useEffect(() => {
|
|
241
265
|
const unsubscribe = subscribeInvalidations((methodId) => {
|
|
242
|
-
if (methodId
|
|
243
|
-
|
|
266
|
+
if (methodId !== methodIdRef.current || !enabledRef.current || !isMountedRef.current) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (isPending(keyRef.current)) {
|
|
270
|
+
queueRefetch(showLoaderOnInvalidateRef.current);
|
|
271
|
+
return;
|
|
244
272
|
}
|
|
273
|
+
void doFetch(showLoaderOnInvalidateRef.current);
|
|
245
274
|
});
|
|
246
275
|
return unsubscribe;
|
|
247
|
-
}, [doFetch]);
|
|
276
|
+
}, [doFetch, queueRefetch]);
|
|
248
277
|
// TTL-based auto-refetch
|
|
249
278
|
useEffect(() => {
|
|
250
279
|
if (!enabled || !ttl) {
|
|
@@ -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 * - 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"]}
|
|
1
|
+
{"version":3,"file":"useFetch.js","sourceRoot":"","sources":["../../src/client/useFetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAC1H,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA2BzC,SAAS,oBAAoB;IACzB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO,IAAI,GAAG,EAAE,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAG,MAEX,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;QAC9B,GAAG,CAAC,sBAAsB,GAAG,IAAI,GAAG,EAAE,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC,sBAAsB,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB;IACvB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,GAAG,GAAG,MAEX,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC;QAC/B,GAAG,CAAC,uBAAuB,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IACxE,CAAC;IACD,OAAO,GAAG,CAAC,uBAAuB,CAAC;AACvC,CAAC;AAED,4DAA4D;AAC5D,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,SAAS,mBAAmB;IACxB,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,IAAI,KAAK,CAAC,UAAU,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACtD,OAAO;IACX,CAAC;IACD,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;IAExB,MAAM,cAAc,GAAG,GAAG,EAAE;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,iDAAiD;QACjD,IAAI,GAAG,GAAG,KAAK,CAAC,WAAW,GAAG,iBAAiB,EAAE,CAAC;YAC9C,OAAO;QACX,CAAC;QACD,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;QAExB,6CAA6C;QAC7C,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;QACzC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;YACrB,IAAI,CAAC;gBACD,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,0BAA0B;YACzC,CAAC;YAAC,MAAM,CAAC;gBACL,qCAAqC;YACzC,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,MAAM,sBAAsB,GAAG,GAAG,EAAE;QAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACnB,cAAc,EAAE,CAAC;QACrB,CAAC;IACL,CAAC,CAAC;IAEF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,sBAAsB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAiB,MAAkC,EAAE,IAAY,EAAE,OAAyB;IAChH,oBAAoB;IACpB,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAExC,MAAM,EAAE,GAAG,EAAE,oBAAoB,GAAG,IAAI,EAAE,mBAAmB,GAAG,KAAK,EAAE,sBAAsB,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,IAAI,EAAE,CAAC;IAExI,iEAAiE;IACjE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,sBAAsB,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC3D,MAAM,yBAAyB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACjE,MAAM,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,0BAA0B,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjD,6BAA6B;IAC7B,WAAW,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;IAClC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IACrB,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC;IACrB,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,sBAAsB,CAAC,OAAO,GAAG,mBAAmB,CAAC;IACrD,yBAAyB,CAAC,OAAO,GAAG,sBAAsB,CAAC;IAE3D,gCAAgC;IAChC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAElC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAU,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACxG,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,IAAI,CAAC,CAAC;IAE1D,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,UAAmB,EAAE,EAAE;QACrD,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;QAChC,0BAA0B,CAAC,OAAO,GAAG,0BAA0B,CAAC,OAAO,IAAI,UAAU,CAAC;IAC1F,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,oDAAoD;IACpD,yEAAyE;IACzE,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,aAAsB,IAAI,EAAgC,EAAE;QAC3F,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC;QAElC,+EAA+E;QAC/E,MAAM,aAAa,GAAG,eAAe,CAAqC,UAAU,CAAC,CAAC;QACtF,IAAI,aAAa,EAAE,CAAC;YAChB,iDAAiD;YACjD,IAAI,UAAU,EAAE,CAAC;gBACb,UAAU,CAAC,IAAI,CAAC,CAAC;YACrB,CAAC;YACD,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;gBACnC,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;oBACvB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;oBACrB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACvB,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACnB,CAAC;gBACD,OAAO,MAAM,CAAC,IAAI,CAAC;YACvB,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACpB,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;oBACvB,MAAM,QAAQ,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;oBACpH,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBAC3B,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;gBACD,OAAO,SAAS,CAAC;YACrB,CAAC;oBAAS,CAAC;gBACP,IAAI,YAAY,CAAC,OAAO,IAAI,UAAU,EAAE,CAAC;oBACrC,UAAU,CAAC,KAAK,CAAC,CAAC;gBACtB,CAAC;gBACD,IAAI,gBAAgB,CAAC,OAAO,IAAI,YAAY,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;oBACvG,MAAM,cAAc,GAAG,0BAA0B,CAAC,OAAO,CAAC;oBAC1D,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;oBACjC,0BAA0B,CAAC,OAAO,GAAG,KAAK,CAAC;oBAC3C,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;gBACjC,CAAC;YACL,CAAC;QACL,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACb,UAAU,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEf,oDAAoD;QACpD,MAAM,YAAY,GAAG,OAAO,CAAiB,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,OAAgB,CAAC,CAAC;QAC5F,MAAM,cAAc,GAAG,eAAe,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAEjE,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;YACpC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBACxB,OAAO,SAAS,CAAC;YACrB,CAAC;YACD,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC7C,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACrB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC,IAAI,CAAC;QACvB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBACxB,OAAO,SAAS,CAAC;YACrB,CAAC;YACD,MAAM,QAAQ,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;YACpH,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC3B,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACzB,OAAO,SAAS,CAAC;QACrB,CAAC;gBAAS,CAAC;YACP,IAAI,YAAY,CAAC,OAAO,IAAI,UAAU,EAAE,CAAC;gBACrC,UAAU,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;YACD,IAAI,gBAAgB,CAAC,OAAO,IAAI,YAAY,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvG,MAAM,cAAc,GAAG,0BAA0B,CAAC,OAAO,CAAC;gBAC1D,gBAAgB,CAAC,OAAO,GAAG,KAAK,CAAC;gBACjC,0BAA0B,CAAC,OAAO,GAAG,KAAK,CAAC;gBAC3C,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,8BAA8B;IAEtC,sBAAsB;IACtB,SAAS,CAAC,GAAG,EAAE;QACX,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,OAAO,GAAG,EAAE;YACR,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC;QACjC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,wCAAwC;IACxC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,GAAG,CAAU,GAAG,CAAC,CAAC;YACrC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO,CAAC,UAAU,CAAC,CAAC;gBACpB,UAAU,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEV,qDAAqD;IACrD,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,UAAU,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO;QACX,CAAC;QAED,8DAA8D;QAC9D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;aAAM,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,wCAAwC;YACxC,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,YAAY,GAAG,eAAe,CAAqC,GAAG,CAAC,CAAC;YAC9E,IAAI,YAAY,EAAE,CAAC;gBACf,YAAY;qBACP,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;oBACb,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;wBACvB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;wBACrB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC,CAAC;oBACnB,CAAC;gBACL,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;oBACpB,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;wBACvB,MAAM,QAAQ,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;wBACpH,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;wBAC3B,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;oBAC7B,CAAC;gBACL,CAAC,CAAC;qBACD,OAAO,CAAC,GAAG,EAAE;oBACV,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;wBACvB,UAAU,CAAC,KAAK,CAAC,CAAC;oBACtB,CAAC;gBACL,CAAC,CAAC,CAAC;YACX,CAAC;QACL,CAAC;IACL,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAE5B,wCAAwC;IACxC,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACxB,OAAO;QACX,CAAC;QAED,oCAAoC;QACpC,mBAAmB,EAAE,CAAC;QAEtB,kDAAkD;QAClD,MAAM,aAAa,GAAkB,CAAC,UAAmB,EAAE,EAAE;YACzD,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBAC/C,OAAO;YACX,CAAC;YAED,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,OAAO,IAAI,UAAU,CAAC;YAEtE,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,YAAY,CAAC,gBAAgB,CAAC,CAAC;gBAC/B,OAAO;YACX,CAAC;YAED,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACnC,CAAC,CAAC;QAEF,yBAAyB;QACzB,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;QACzC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAE7B,OAAO,GAAG,EAAE;YACR,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QACpC,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,oBAAoB,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAElD,yEAAyE;IACzE,SAAS,CAAC,GAAG,EAAE;QACX,MAAM,WAAW,GAAG,sBAAsB,CAAC,CAAC,QAAQ,EAAE,EAAE;YACpD,IAAI,QAAQ,KAAK,WAAW,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;gBACnF,OAAO;YACX,CAAC;YAED,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC5B,YAAY,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC;gBAChD,OAAO;YACX,CAAC;YAED,KAAK,OAAO,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACvB,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAE5B,yBAAyB;IACzB,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,OAAO;QACX,CAAC;QAED,IAAI,SAAoD,CAAC;QACzD,IAAI,QAAQ,GAAG,IAAI,CAAC;QAEpB,MAAM,eAAe,GAAG,GAAG,EAAE;YACzB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC1B,YAAY,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;YAED,SAAS,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;gBAC9B,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;oBAC5D,OAAO;gBACX,CAAC;gBACD,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,yBAAyB;gBAC/C,IAAI,QAAQ,EAAE,CAAC;oBACX,eAAe,EAAE,CAAC;gBACtB,CAAC;YACL,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,CAAC,CAAC;QAEF,0CAA0C;QAC1C,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACX,eAAe,EAAE,CAAC;QACtB,CAAC;QAED,OAAO,GAAG,EAAE;YACR,QAAQ,GAAG,KAAK,CAAC;YACjB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC1B,YAAY,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;QACL,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAEjC,0BAA0B;IAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,aAAsB,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAE5F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtD,CAAC","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 const queuedRefetchRef = useRef(false);\n const queuedRefetchShowLoaderRef = useRef(false);\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 const queueRefetch = useCallback((showLoader: boolean) => {\n queuedRefetchRef.current = true;\n queuedRefetchShowLoaderRef.current = queuedRefetchShowLoaderRef.current || showLoader;\n }, []);\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 if (queuedRefetchRef.current && isMountedRef.current && enabledRef.current && !isPending(keyRef.current)) {\n const nextShowLoader = queuedRefetchShowLoaderRef.current;\n queuedRefetchRef.current = false;\n queuedRefetchShowLoaderRef.current = false;\n void doFetch(nextShowLoader);\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 if (queuedRefetchRef.current && isMountedRef.current && enabledRef.current && !isPending(keyRef.current)) {\n const nextShowLoader = queuedRefetchShowLoaderRef.current;\n queuedRefetchRef.current = false;\n queuedRefetchShowLoaderRef.current = false;\n void doFetch(nextShowLoader);\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) {\n return;\n }\n\n const shouldShowLoader = showLoaderOnRefocusRef.current || showLoader;\n\n if (isPending(keyRef.current)) {\n queueRefetch(shouldShowLoader);\n return;\n }\n\n void doFetch(shouldShowLoader);\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, queueRefetch]);\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) {\n return;\n }\n\n if (isPending(keyRef.current)) {\n queueRefetch(showLoaderOnInvalidateRef.current);\n return;\n }\n\n void doFetch(showLoaderOnInvalidateRef.current);\n });\n\n return unsubscribe;\n }, [doFetch, queueRefetch]);\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"]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./middleware.js";
|
|
|
7
7
|
export type { HTTPRouter } from "./httpRouter.js";
|
|
8
8
|
export { startProdServer } from "./prodServer.js";
|
|
9
9
|
export type { RpcRegistry } from "./rpcRegistry.js";
|
|
10
|
+
export { PublicError } from "./rpcRegistry.js";
|
|
10
11
|
export { injectEnvToProcess, loadEnvFiles } from "../utils/envLoader.js";
|
|
11
12
|
export { log } from "../utils/logger.js";
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAGhC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAGhC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAI/C,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./defineMethod.js";
|
|
|
6
6
|
export * from "./defineWorker.js";
|
|
7
7
|
export * from "./middleware.js";
|
|
8
8
|
export { startProdServer } from "./prodServer.js";
|
|
9
|
+
export { PublicError } from "./rpcRegistry.js";
|
|
9
10
|
// Internal utilities needed by generated server code (from helium build)
|
|
10
11
|
// Note: These are exported for framework-generated code only, not for direct user consumption
|
|
11
12
|
export { injectEnvToProcess, loadEnvFiles } from "../utils/envLoader.js";
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAIhC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAIhC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,yEAAyE;AACzE,8FAA8F;AAC9F,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC","sourcesContent":["// Public API for users\nexport * from \"./config.js\";\nexport * from \"./context.js\";\nexport * from \"./defineHTTPRequest.js\";\nexport * from \"./defineMethod.js\";\nexport * from \"./defineWorker.js\";\nexport * from \"./middleware.js\";\n\n// Production server API\nexport type { HTTPRouter } from \"./httpRouter.js\";\nexport { startProdServer } from \"./prodServer.js\";\nexport type { RpcRegistry } from \"./rpcRegistry.js\";\nexport { PublicError } from \"./rpcRegistry.js\";\n\n// Internal utilities needed by generated server code (from helium build)\n// Note: These are exported for framework-generated code only, not for direct user consumption\nexport { injectEnvToProcess, loadEnvFiles } from \"../utils/envLoader.js\";\nexport { log } from \"../utils/logger.js\";\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type http from "http";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the client IP address from an HTTP request, taking into account proxy configurations.
|
|
4
|
+
*
|
|
5
|
+
* When behind proxies (like Vercel, Cloudflare, AWS ALB, etc.), the X-Forwarded-For header
|
|
6
|
+
* contains a chain of IP addresses. The format is: "client, proxy1, proxy2, ..."
|
|
7
|
+
*
|
|
8
|
+
* @param req - The HTTP request object
|
|
9
|
+
* @param trustProxyDepth - Number of proxy levels to trust
|
|
10
|
+
* - 0: Only use req.socket.remoteAddress (no proxy trust)
|
|
11
|
+
* - 1: Trust 1 proxy level (get the last IP before your server)
|
|
12
|
+
* - 2+: Trust multiple proxy levels (for complex setups)
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* - trustProxyDepth=0: Direct connection, no proxies
|
|
16
|
+
* X-Forwarded-For: ignored
|
|
17
|
+
* Result: req.socket.remoteAddress
|
|
18
|
+
*
|
|
19
|
+
* - trustProxyDepth=1: Behind one proxy (e.g., Vercel, Netlify)
|
|
20
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1"
|
|
21
|
+
* Result: "203.0.113.1" (client IP)
|
|
22
|
+
*
|
|
23
|
+
* - trustProxyDepth=2: Behind two proxies (e.g., Cloudflare -> Load Balancer)
|
|
24
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1, 192.0.2.1"
|
|
25
|
+
* Result: "203.0.113.1" (client IP)
|
|
26
|
+
*
|
|
27
|
+
* Common configurations:
|
|
28
|
+
* - Vercel/Netlify/Railway: trustProxyDepth=1
|
|
29
|
+
* - Cloudflare -> Origin: trustProxyDepth=1 or 2 (depending on your setup)
|
|
30
|
+
* - AWS ALB -> EC2: trustProxyDepth=1
|
|
31
|
+
* - Nginx -> Node: trustProxyDepth=1
|
|
32
|
+
* - Cloudflare -> Nginx -> Node: trustProxyDepth=2
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractClientIP(req: http.IncomingMessage, trustProxyDepth?: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* Alternative extraction method that works from the right (trusts the rightmost IPs).
|
|
37
|
+
* This is useful when you want to trust the last N proxies in the chain.
|
|
38
|
+
*
|
|
39
|
+
* @param req - The HTTP request object
|
|
40
|
+
* @param trustProxyDepth - Number of proxy levels to trust from the right
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1, 192.0.2.1"
|
|
44
|
+
* trustProxyDepth=1: Result is "198.51.100.1" (skip the last trusted proxy)
|
|
45
|
+
* trustProxyDepth=2: Result is "203.0.113.1" (skip the last 2 trusted proxies)
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractClientIPFromRight(req: http.IncomingMessage, trustProxyDepth?: number): string;
|
|
48
|
+
//# sourceMappingURL=ipExtractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipExtractor.d.ts","sourceRoot":"","sources":["../../src/server/ipExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,eAAe,GAAE,MAAU,GAAG,MAAM,CAqC9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,eAAe,GAAE,MAAU,GAAG,MAAM,CAsBvG"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the client IP address from an HTTP request, taking into account proxy configurations.
|
|
3
|
+
*
|
|
4
|
+
* When behind proxies (like Vercel, Cloudflare, AWS ALB, etc.), the X-Forwarded-For header
|
|
5
|
+
* contains a chain of IP addresses. The format is: "client, proxy1, proxy2, ..."
|
|
6
|
+
*
|
|
7
|
+
* @param req - The HTTP request object
|
|
8
|
+
* @param trustProxyDepth - Number of proxy levels to trust
|
|
9
|
+
* - 0: Only use req.socket.remoteAddress (no proxy trust)
|
|
10
|
+
* - 1: Trust 1 proxy level (get the last IP before your server)
|
|
11
|
+
* - 2+: Trust multiple proxy levels (for complex setups)
|
|
12
|
+
*
|
|
13
|
+
* Examples:
|
|
14
|
+
* - trustProxyDepth=0: Direct connection, no proxies
|
|
15
|
+
* X-Forwarded-For: ignored
|
|
16
|
+
* Result: req.socket.remoteAddress
|
|
17
|
+
*
|
|
18
|
+
* - trustProxyDepth=1: Behind one proxy (e.g., Vercel, Netlify)
|
|
19
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1"
|
|
20
|
+
* Result: "203.0.113.1" (client IP)
|
|
21
|
+
*
|
|
22
|
+
* - trustProxyDepth=2: Behind two proxies (e.g., Cloudflare -> Load Balancer)
|
|
23
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1, 192.0.2.1"
|
|
24
|
+
* Result: "203.0.113.1" (client IP)
|
|
25
|
+
*
|
|
26
|
+
* Common configurations:
|
|
27
|
+
* - Vercel/Netlify/Railway: trustProxyDepth=1
|
|
28
|
+
* - Cloudflare -> Origin: trustProxyDepth=1 or 2 (depending on your setup)
|
|
29
|
+
* - AWS ALB -> EC2: trustProxyDepth=1
|
|
30
|
+
* - Nginx -> Node: trustProxyDepth=1
|
|
31
|
+
* - Cloudflare -> Nginx -> Node: trustProxyDepth=2
|
|
32
|
+
*/
|
|
33
|
+
export function extractClientIP(req, trustProxyDepth = 0) {
|
|
34
|
+
// If not trusting any proxies, return the direct connection IP
|
|
35
|
+
if (trustProxyDepth === 0) {
|
|
36
|
+
return req.socket.remoteAddress || "unknown";
|
|
37
|
+
}
|
|
38
|
+
// Get X-Forwarded-For header
|
|
39
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
40
|
+
if (!forwardedFor) {
|
|
41
|
+
// No X-Forwarded-For header, fall back to direct connection
|
|
42
|
+
return req.socket.remoteAddress || "unknown";
|
|
43
|
+
}
|
|
44
|
+
// Parse X-Forwarded-For header (can be a string or array of strings)
|
|
45
|
+
const forwardedIPs = (Array.isArray(forwardedFor) ? forwardedFor.join(",") : forwardedFor)
|
|
46
|
+
.split(",")
|
|
47
|
+
.map((ip) => ip.trim())
|
|
48
|
+
.filter((ip) => ip.length > 0);
|
|
49
|
+
if (forwardedIPs.length === 0) {
|
|
50
|
+
// Empty X-Forwarded-For, fall back to direct connection
|
|
51
|
+
return req.socket.remoteAddress || "unknown";
|
|
52
|
+
}
|
|
53
|
+
// The client IP is at the beginning of the chain
|
|
54
|
+
// We trust the chain up to trustProxyDepth levels
|
|
55
|
+
// Format: [clientIP, proxy1, proxy2, ..., lastProxy]
|
|
56
|
+
// We want the clientIP, but we need to verify we have enough trusted proxies
|
|
57
|
+
if (forwardedIPs.length < trustProxyDepth) {
|
|
58
|
+
// Not enough IPs in the chain, the chain might be incomplete or spoofed
|
|
59
|
+
// Fall back to direct connection for safety
|
|
60
|
+
return req.socket.remoteAddress || "unknown";
|
|
61
|
+
}
|
|
62
|
+
// Return the client IP (first in the chain)
|
|
63
|
+
return forwardedIPs[0];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Alternative extraction method that works from the right (trusts the rightmost IPs).
|
|
67
|
+
* This is useful when you want to trust the last N proxies in the chain.
|
|
68
|
+
*
|
|
69
|
+
* @param req - The HTTP request object
|
|
70
|
+
* @param trustProxyDepth - Number of proxy levels to trust from the right
|
|
71
|
+
*
|
|
72
|
+
* Example:
|
|
73
|
+
* X-Forwarded-For: "203.0.113.1, 198.51.100.1, 192.0.2.1"
|
|
74
|
+
* trustProxyDepth=1: Result is "198.51.100.1" (skip the last trusted proxy)
|
|
75
|
+
* trustProxyDepth=2: Result is "203.0.113.1" (skip the last 2 trusted proxies)
|
|
76
|
+
*/
|
|
77
|
+
export function extractClientIPFromRight(req, trustProxyDepth = 0) {
|
|
78
|
+
if (trustProxyDepth === 0) {
|
|
79
|
+
return req.socket.remoteAddress || "unknown";
|
|
80
|
+
}
|
|
81
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
82
|
+
if (!forwardedFor) {
|
|
83
|
+
return req.socket.remoteAddress || "unknown";
|
|
84
|
+
}
|
|
85
|
+
const forwardedIPs = (Array.isArray(forwardedFor) ? forwardedFor.join(",") : forwardedFor)
|
|
86
|
+
.split(",")
|
|
87
|
+
.map((ip) => ip.trim())
|
|
88
|
+
.filter((ip) => ip.length > 0);
|
|
89
|
+
if (forwardedIPs.length === 0) {
|
|
90
|
+
return req.socket.remoteAddress || "unknown";
|
|
91
|
+
}
|
|
92
|
+
// Calculate which IP to trust by skipping the rightmost N trusted proxies
|
|
93
|
+
const clientIPIndex = Math.max(0, forwardedIPs.length - trustProxyDepth - 1);
|
|
94
|
+
return forwardedIPs[clientIPIndex];
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=ipExtractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipExtractor.js","sourceRoot":"","sources":["../../src/server/ipExtractor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,eAAe,CAAC,GAAyB,EAAE,kBAA0B,CAAC;IAClF,+DAA+D;IAC/D,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,6BAA6B;IAC7B,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,CAAC,YAAY,EAAE,CAAC;QAChB,4DAA4D;QAC5D,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,qEAAqE;IACrE,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;SACrF,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;SACtB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEnC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,wDAAwD;QACxD,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,iDAAiD;IACjD,kDAAkD;IAClD,qDAAqD;IACrD,6EAA6E;IAE7E,IAAI,YAAY,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACxC,wEAAwE;QACxE,4CAA4C;QAC5C,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,4CAA4C;IAC5C,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAyB,EAAE,kBAA0B,CAAC;IAC3F,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpD,IAAI,CAAC,YAAY,EAAE,CAAC;QAChB,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;SACrF,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;SACtB,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEnC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IACjD,CAAC;IAED,0EAA0E;IAC1E,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,MAAM,GAAG,eAAe,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,YAAY,CAAC,aAAa,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -7,6 +7,9 @@ import type { RateLimiter } from "./rateLimiter.js";
|
|
|
7
7
|
export interface HttpRpcResult {
|
|
8
8
|
response: RpcResponse | RpcResponse[];
|
|
9
9
|
}
|
|
10
|
+
export declare class PublicError extends Error {
|
|
11
|
+
readonly public = true;
|
|
12
|
+
}
|
|
10
13
|
export declare class RpcRegistry {
|
|
11
14
|
private methods;
|
|
12
15
|
private middleware;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcRegistry.d.ts","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,SAAS,MAAM,IAAI,CAAC;AAG3B,OAAO,KAAK,EAAc,WAAW,EAAY,MAAM,wBAAwB,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAUpD,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACzC;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,YAAY,CAAc;IAElC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;IAKnD,aAAa,CAAC,UAAU,EAAE,gBAAgB;IAI1C,cAAc,CAAC,WAAW,EAAE,WAAW;IAIvC,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe;IAI1E,OAAO,CAAC,QAAQ;YAmBF,cAAc;IAuEtB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;YAuC7C,kBAAkB;IAoEhC;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CAsCvH"}
|
|
1
|
+
{"version":3,"file":"rpcRegistry.d.ts","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,SAAS,MAAM,IAAI,CAAC;AAG3B,OAAO,KAAK,EAAc,WAAW,EAAY,MAAM,wBAAwB,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAUpD,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACzC;AAED,qBAAa,WAAY,SAAQ,KAAK;IAClC,SAAgB,MAAM,QAAQ;CACjC;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,YAAY,CAAc;IAElC,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;IAKnD,aAAa,CAAC,UAAU,EAAE,gBAAgB;IAI1C,cAAc,CAAC,WAAW,EAAE,WAAW;IAIvC,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe;IAI1E,OAAO,CAAC,QAAQ;YAmBF,cAAc;IAuEtB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;YAuC7C,kBAAkB;IAoEhC;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;CAsCvH"}
|
|
@@ -4,6 +4,12 @@ import { gzip } from "zlib";
|
|
|
4
4
|
import { log } from "../utils/logger.js";
|
|
5
5
|
import { prepareForMsgpack } from "./serializer.js";
|
|
6
6
|
const gzipAsync = promisify(gzip);
|
|
7
|
+
export class PublicError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.public = true;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
7
13
|
export class RpcRegistry {
|
|
8
14
|
constructor() {
|
|
9
15
|
this.methods = new Map();
|
|
@@ -261,12 +267,37 @@ export class RpcRegistry {
|
|
|
261
267
|
* In development, returns the actual error message for debugging.
|
|
262
268
|
*/
|
|
263
269
|
function sanitizeErrorMessage(err) {
|
|
270
|
+
const message = getErrorMessage(err);
|
|
271
|
+
const isPublic = isPublicError(err);
|
|
264
272
|
if (process.env.NODE_ENV === "production") {
|
|
273
|
+
if (isPublic && message) {
|
|
274
|
+
return message;
|
|
275
|
+
}
|
|
265
276
|
return "Server error";
|
|
266
277
|
}
|
|
278
|
+
if (message) {
|
|
279
|
+
return message;
|
|
280
|
+
}
|
|
281
|
+
return "Server error";
|
|
282
|
+
}
|
|
283
|
+
function isPublicError(err) {
|
|
284
|
+
if (err instanceof PublicError) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (!err || typeof err !== "object") {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const publicFlag = err.public;
|
|
291
|
+
return publicFlag === true;
|
|
292
|
+
}
|
|
293
|
+
function getErrorMessage(err) {
|
|
267
294
|
if (err instanceof Error) {
|
|
268
295
|
return err.message;
|
|
269
296
|
}
|
|
270
|
-
|
|
297
|
+
if (err && typeof err === "object") {
|
|
298
|
+
const message = err.message;
|
|
299
|
+
return typeof message === "string" ? message : null;
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
271
302
|
}
|
|
272
303
|
//# sourceMappingURL=rpcRegistry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcRegistry.js","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport type http from \"http\";\nimport { promisify } from \"util\";\nimport WebSocket from \"ws\";\nimport { gzip } from \"zlib\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { log } from \"../utils/logger.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumMethodDef } from \"./defineMethod.js\";\nimport type { HeliumMiddleware } from \"./middleware.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\n\ninterface SocketMetadata {\n ip: string;\n req: http.IncomingMessage;\n}\n\nexport interface HttpRpcResult {\n response: RpcResponse | RpcResponse[];\n}\n\nexport class RpcRegistry {\n private methods = new Map<string, HeliumMethodDef<any, any>>();\n private middleware: HeliumMiddleware | null = null;\n private rateLimiter: RateLimiter | null = null;\n private socketMetadata = new WeakMap<WebSocket, SocketMetadata>();\n private maxBatchSize: number = 20;\n\n register(id: string, def: HeliumMethodDef<any, any>) {\n def.__id = id;\n this.methods.set(id, def);\n }\n\n setMiddleware(middleware: HeliumMiddleware) {\n this.middleware = middleware;\n }\n\n setRateLimiter(rateLimiter: RateLimiter) {\n this.rateLimiter = rateLimiter;\n }\n\n setMaxBatchSize(size: number) {\n this.maxBatchSize = size;\n }\n\n /**\n * Store metadata about a WebSocket connection.\n * Should be called when a new connection is established.\n */\n setSocketMetadata(socket: WebSocket, ip: string, req: http.IncomingMessage) {\n this.socketMetadata.set(socket, { ip, req });\n }\n\n private getStats(socket: WebSocket): RpcStats {\n if (!this.rateLimiter) {\n return { remainingRequests: Infinity, resetInSeconds: 0 };\n }\n\n const stats = this.rateLimiter.getConnectionStats(socket);\n if (!stats) {\n return { remainingRequests: 0, resetInSeconds: 0 };\n }\n\n const now = Date.now();\n const resetInSeconds = Math.ceil((stats.resetTimeMs - now) / 1000);\n\n return {\n remainingRequests: stats.remainingMessages,\n resetInSeconds: Math.max(0, resetInSeconds),\n };\n }\n\n private async processRequest(req: RpcRequest, socket: WebSocket): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const metadata = this.socketMetadata.get(socket);\n const ctx: HeliumContext = {\n req: {\n ip: metadata?.ip || \"unknown\",\n headers: metadata?.req.headers || {},\n url: metadata?.req.url,\n method: metadata?.req.method,\n raw: metadata?.req as http.IncomingMessage,\n },\n };\n let result: any;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n // If next() was not called, the middleware blocked the request\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n // No middleware, execute handler directly\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: this.getStats(socket),\n result,\n };\n } catch (err: any) {\n log(\"error\", `RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n async handleMessage(socket: WebSocket, raw: string | Buffer) {\n let req: RpcRequest | RpcRequest[];\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n return;\n }\n\n // Security: cap batch size\n if (Array.isArray(req) && req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: this.getStats(socket),\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n socket.send(msgpackEncode(prepareForMsgpack(errorResponse)) as Buffer);\n return;\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n response = await Promise.all(req.map((r) => this.processRequest(r, socket)));\n } else {\n response = await this.processRequest(req, socket);\n }\n\n const encoded = msgpackEncode(prepareForMsgpack(response));\n // Compress if larger than 1KB\n if (encoded.length > 1024) {\n const compressed = await gzipAsync(encoded);\n socket.send(compressed);\n } else {\n socket.send(encoded as Buffer);\n }\n }\n\n private async processRequestHttp(req: RpcRequest, ip: string, httpReq: http.IncomingMessage): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const ctx: HeliumContext = {\n req: {\n ip,\n headers: httpReq.headers,\n url: httpReq.url,\n method: httpReq.method,\n raw: httpReq,\n },\n };\n let result: unknown;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n result,\n };\n } catch (err: unknown) {\n log(\"error\", `HTTP RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n /**\n * Handle an HTTP-based RPC request.\n * This is an alternative to WebSocket for environments where HTTP performs better\n * (e.g., mobile networks with high latency where HTTP/2 multiplexing helps).\n */\n async handleHttpRequest(reqBody: Buffer | string, ip: string, httpReq: http.IncomingMessage): Promise<HttpRpcResult> {\n let req: RpcRequest | RpcRequest[];\n\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(reqBody) ? reqBody : Buffer.from(reqBody);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n const errorResponse: RpcResponse = {\n id: \"unknown\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: \"Invalid request format\",\n };\n return {\n response: errorResponse,\n };\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n // Security: cap batch size\n if (req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n return { response: errorResponse };\n }\n response = await Promise.all(req.map((r) => this.processRequestHttp(r, ip, httpReq)));\n } else {\n response = await this.processRequestHttp(req as RpcRequest, ip, httpReq);\n }\n\n return { response };\n }\n}\n\n/**\n * Sanitize error messages before sending to clients.\n * In production, returns a generic message to prevent information leakage.\n * In development, returns the actual error message for debugging.\n */\nfunction sanitizeErrorMessage(err: unknown): string {\n if (process.env.NODE_ENV === \"production\") {\n return \"Server error\";\n }\n if (err instanceof Error) {\n return err.message;\n }\n return \"Server error\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpcRegistry.js","sourceRoot":"","sources":["../../src/server/rpcRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as msgpackDecode, encode as msgpackEncode } from \"@msgpack/msgpack\";\nimport type http from \"http\";\nimport { promisify } from \"util\";\nimport WebSocket from \"ws\";\nimport { gzip } from \"zlib\";\n\nimport type { RpcRequest, RpcResponse, RpcStats } from \"../runtime/protocol.js\";\nimport { log } from \"../utils/logger.js\";\nimport type { HeliumContext } from \"./context.js\";\nimport type { HeliumMethodDef } from \"./defineMethod.js\";\nimport type { HeliumMiddleware } from \"./middleware.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\nimport { prepareForMsgpack } from \"./serializer.js\";\n\nconst gzipAsync = promisify(gzip);\n\ninterface SocketMetadata {\n ip: string;\n req: http.IncomingMessage;\n}\n\nexport interface HttpRpcResult {\n response: RpcResponse | RpcResponse[];\n}\n\nexport class PublicError extends Error {\n public readonly public = true;\n}\n\nexport class RpcRegistry {\n private methods = new Map<string, HeliumMethodDef<any, any>>();\n private middleware: HeliumMiddleware | null = null;\n private rateLimiter: RateLimiter | null = null;\n private socketMetadata = new WeakMap<WebSocket, SocketMetadata>();\n private maxBatchSize: number = 20;\n\n register(id: string, def: HeliumMethodDef<any, any>) {\n def.__id = id;\n this.methods.set(id, def);\n }\n\n setMiddleware(middleware: HeliumMiddleware) {\n this.middleware = middleware;\n }\n\n setRateLimiter(rateLimiter: RateLimiter) {\n this.rateLimiter = rateLimiter;\n }\n\n setMaxBatchSize(size: number) {\n this.maxBatchSize = size;\n }\n\n /**\n * Store metadata about a WebSocket connection.\n * Should be called when a new connection is established.\n */\n setSocketMetadata(socket: WebSocket, ip: string, req: http.IncomingMessage) {\n this.socketMetadata.set(socket, { ip, req });\n }\n\n private getStats(socket: WebSocket): RpcStats {\n if (!this.rateLimiter) {\n return { remainingRequests: Infinity, resetInSeconds: 0 };\n }\n\n const stats = this.rateLimiter.getConnectionStats(socket);\n if (!stats) {\n return { remainingRequests: 0, resetInSeconds: 0 };\n }\n\n const now = Date.now();\n const resetInSeconds = Math.ceil((stats.resetTimeMs - now) / 1000);\n\n return {\n remainingRequests: stats.remainingMessages,\n resetInSeconds: Math.max(0, resetInSeconds),\n };\n }\n\n private async processRequest(req: RpcRequest, socket: WebSocket): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const metadata = this.socketMetadata.get(socket);\n const ctx: HeliumContext = {\n req: {\n ip: metadata?.ip || \"unknown\",\n headers: metadata?.req.headers || {},\n url: metadata?.req.url,\n method: metadata?.req.method,\n raw: metadata?.req as http.IncomingMessage,\n },\n };\n let result: any;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n // If next() was not called, the middleware blocked the request\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n // No middleware, execute handler directly\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: this.getStats(socket),\n result,\n };\n } catch (err: any) {\n log(\"error\", `RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: this.getStats(socket),\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n async handleMessage(socket: WebSocket, raw: string | Buffer) {\n let req: RpcRequest | RpcRequest[];\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n return;\n }\n\n // Security: cap batch size\n if (Array.isArray(req) && req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: this.getStats(socket),\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n socket.send(msgpackEncode(prepareForMsgpack(errorResponse)) as Buffer);\n return;\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n response = await Promise.all(req.map((r) => this.processRequest(r, socket)));\n } else {\n response = await this.processRequest(req, socket);\n }\n\n const encoded = msgpackEncode(prepareForMsgpack(response));\n // Compress if larger than 1KB\n if (encoded.length > 1024) {\n const compressed = await gzipAsync(encoded);\n socket.send(compressed);\n } else {\n socket.send(encoded as Buffer);\n }\n }\n\n private async processRequestHttp(req: RpcRequest, ip: string, httpReq: http.IncomingMessage): Promise<RpcResponse> {\n const def = this.methods.get(req.method);\n if (!def) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: `Unknown method ${req.method}`,\n };\n }\n\n try {\n // Build context with request metadata\n const ctx: HeliumContext = {\n req: {\n ip,\n headers: httpReq.headers,\n url: httpReq.url,\n method: httpReq.method,\n raw: httpReq,\n },\n };\n let result: unknown;\n\n // Execute middleware if present\n if (this.middleware) {\n let nextCalled = false;\n await this.middleware.handler(\n {\n ctx,\n type: \"method\",\n methodName: req.method,\n },\n async () => {\n nextCalled = true;\n result = await def.handler(req.args, ctx);\n }\n );\n\n if (!nextCalled) {\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: \"Request blocked by middleware\",\n };\n }\n } else {\n result = await def.handler(req.args, ctx);\n }\n\n return {\n id: req.id,\n ok: true,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n result,\n };\n } catch (err: unknown) {\n log(\"error\", `HTTP RPC method '${req.method}' failed:`, err);\n return {\n id: req.id,\n ok: false,\n stats: { remainingRequests: Infinity, resetInSeconds: 0 },\n error: sanitizeErrorMessage(err),\n };\n }\n }\n\n /**\n * Handle an HTTP-based RPC request.\n * This is an alternative to WebSocket for environments where HTTP performs better\n * (e.g., mobile networks with high latency where HTTP/2 multiplexing helps).\n */\n async handleHttpRequest(reqBody: Buffer | string, ip: string, httpReq: http.IncomingMessage): Promise<HttpRpcResult> {\n let req: RpcRequest | RpcRequest[];\n\n try {\n // Always expect MessagePack\n const buffer = Buffer.isBuffer(reqBody) ? reqBody : Buffer.from(reqBody);\n req = msgpackDecode(buffer) as RpcRequest | RpcRequest[];\n } catch {\n const errorResponse: RpcResponse = {\n id: \"unknown\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: \"Invalid request format\",\n };\n return {\n response: errorResponse,\n };\n }\n\n let response: RpcResponse | RpcResponse[];\n if (Array.isArray(req)) {\n // Security: cap batch size\n if (req.length > this.maxBatchSize) {\n const errorResponse: RpcResponse = {\n id: \"batch\",\n ok: false,\n stats: { remainingRequests: 0, resetInSeconds: 0 },\n error: `Batch size ${req.length} exceeds maximum of ${this.maxBatchSize}`,\n };\n return { response: errorResponse };\n }\n response = await Promise.all(req.map((r) => this.processRequestHttp(r, ip, httpReq)));\n } else {\n response = await this.processRequestHttp(req as RpcRequest, ip, httpReq);\n }\n\n return { response };\n }\n}\n\n/**\n * Sanitize error messages before sending to clients.\n * In production, returns a generic message to prevent information leakage.\n * In development, returns the actual error message for debugging.\n */\nfunction sanitizeErrorMessage(err: unknown): string {\n const message = getErrorMessage(err);\n const isPublic = isPublicError(err);\n\n if (process.env.NODE_ENV === \"production\") {\n if (isPublic && message) {\n return message;\n }\n return \"Server error\";\n }\n\n if (message) {\n return message;\n }\n\n return \"Server error\";\n}\n\nfunction isPublicError(err: unknown): boolean {\n if (err instanceof PublicError) {\n return true;\n }\n\n if (!err || typeof err !== \"object\") {\n return false;\n }\n\n const publicFlag = (err as { public?: unknown }).public;\n return publicFlag === true;\n}\n\nfunction getErrorMessage(err: unknown): string | null {\n if (err instanceof Error) {\n return err.message;\n }\n\n if (err && typeof err === \"object\") {\n const message = (err as { message?: unknown }).message;\n return typeof message === \"string\" ? message : null;\n }\n\n return null;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=deepEqual.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepEqual.d.ts","sourceRoot":"","sources":["../../src/utils/deepEqual.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepEqual.js","sourceRoot":"","sources":["../../src/utils/deepEqual.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatError.d.ts","sourceRoot":"","sources":["../../src/utils/formatError.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAgBhD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function formatError(err) {
|
|
2
|
+
console.log("🚀 ~ formatError ~ err:", err);
|
|
3
|
+
if (err instanceof Error) {
|
|
4
|
+
return err.message;
|
|
5
|
+
}
|
|
6
|
+
if (typeof err === "object" && err !== null) {
|
|
7
|
+
if ("message" in err) {
|
|
8
|
+
return String(err.message);
|
|
9
|
+
}
|
|
10
|
+
// Format Record<string, string> errors
|
|
11
|
+
return JSON.stringify(err, null, 2);
|
|
12
|
+
}
|
|
13
|
+
if (typeof err === "string") {
|
|
14
|
+
return err;
|
|
15
|
+
}
|
|
16
|
+
return String(err);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=formatError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatError.js","sourceRoot":"","sources":["../../src/utils/formatError.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,GAAY;IACpC,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC5C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC1C,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QACD,uCAAuC;QACvC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC"}
|