heliumts 0.2.2
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/LICENSE +21 -0
- package/README.md +356 -0
- package/dist/bin/helium.d.ts +3 -0
- package/dist/bin/helium.d.ts.map +1 -0
- package/dist/bin/helium.js +236 -0
- package/dist/bin/helium.js.map +1 -0
- package/dist/client/Router.d.ts +83 -0
- package/dist/client/Router.d.ts.map +1 -0
- package/dist/client/Router.js +329 -0
- package/dist/client/Router.js.map +1 -0
- package/dist/client/cache.d.ts +8 -0
- package/dist/client/cache.d.ts.map +1 -0
- package/dist/client/cache.js +82 -0
- package/dist/client/cache.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/prefetch.d.ts +12 -0
- package/dist/client/prefetch.d.ts.map +1 -0
- package/dist/client/prefetch.js +33 -0
- package/dist/client/prefetch.js.map +1 -0
- package/dist/client/routerManifest.d.ts +28 -0
- package/dist/client/routerManifest.d.ts.map +1 -0
- package/dist/client/routerManifest.js +242 -0
- package/dist/client/routerManifest.js.map +1 -0
- package/dist/client/rpcClient.d.ts +33 -0
- package/dist/client/rpcClient.d.ts.map +1 -0
- package/dist/client/rpcClient.js +383 -0
- package/dist/client/rpcClient.js.map +1 -0
- package/dist/client/transitions.d.ts +85 -0
- package/dist/client/transitions.d.ts.map +1 -0
- package/dist/client/transitions.js +92 -0
- package/dist/client/transitions.js.map +1 -0
- package/dist/client/types.d.ts +6 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/useCall.d.ts +31 -0
- package/dist/client/useCall.d.ts.map +1 -0
- package/dist/client/useCall.js +40 -0
- package/dist/client/useCall.js.map +1 -0
- package/dist/client/useFetch.d.ts +34 -0
- package/dist/client/useFetch.d.ts.map +1 -0
- package/dist/client/useFetch.js +152 -0
- package/dist/client/useFetch.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/protocol.d.ts +25 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +2 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/server/config.d.ts +216 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +130 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/context.d.ts +38 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +2 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/defineHTTPRequest.d.ts +49 -0
- package/dist/server/defineHTTPRequest.d.ts.map +1 -0
- package/dist/server/defineHTTPRequest.js +34 -0
- package/dist/server/defineHTTPRequest.js.map +1 -0
- package/dist/server/defineMethod.d.ts +20 -0
- package/dist/server/defineMethod.d.ts.map +1 -0
- package/dist/server/defineMethod.js +23 -0
- package/dist/server/defineMethod.js.map +1 -0
- package/dist/server/devServer.d.ts +15 -0
- package/dist/server/devServer.d.ts.map +1 -0
- package/dist/server/devServer.js +219 -0
- package/dist/server/devServer.js.map +1 -0
- package/dist/server/handlerLoader.d.ts +1 -0
- package/dist/server/handlerLoader.d.ts.map +1 -0
- package/dist/server/handlerLoader.js +2 -0
- package/dist/server/handlerLoader.js.map +1 -0
- package/dist/server/httpRouter.d.ts +17 -0
- package/dist/server/httpRouter.d.ts.map +1 -0
- package/dist/server/httpRouter.js +227 -0
- package/dist/server/httpRouter.js.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +13 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +30 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +23 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/prodServer.d.ts +27 -0
- package/dist/server/prodServer.d.ts.map +1 -0
- package/dist/server/prodServer.js +301 -0
- package/dist/server/prodServer.js.map +1 -0
- package/dist/server/rateLimiter.d.ts +36 -0
- package/dist/server/rateLimiter.d.ts.map +1 -0
- package/dist/server/rateLimiter.js +113 -0
- package/dist/server/rateLimiter.js.map +1 -0
- package/dist/server/rpcRegistry.d.ts +34 -0
- package/dist/server/rpcRegistry.d.ts.map +1 -0
- package/dist/server/rpcRegistry.js +231 -0
- package/dist/server/rpcRegistry.js.map +1 -0
- package/dist/server/security.d.ts +5 -0
- package/dist/server/security.d.ts.map +1 -0
- package/dist/server/security.js +59 -0
- package/dist/server/security.js.map +1 -0
- package/dist/server/serializer.d.ts +9 -0
- package/dist/server/serializer.d.ts.map +1 -0
- package/dist/server/serializer.js +40 -0
- package/dist/server/serializer.js.map +1 -0
- package/dist/utils/envLoader.d.ts +27 -0
- package/dist/utils/envLoader.d.ts.map +1 -0
- package/dist/utils/envLoader.js +68 -0
- package/dist/utils/envLoader.js.map +1 -0
- package/dist/utils/ipExtractor.d.ts +59 -0
- package/dist/utils/ipExtractor.d.ts.map +1 -0
- package/dist/utils/ipExtractor.js +126 -0
- package/dist/utils/ipExtractor.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/vite/heliumPlugin.d.ts +3 -0
- package/dist/vite/heliumPlugin.d.ts.map +1 -0
- package/dist/vite/heliumPlugin.js +294 -0
- package/dist/vite/heliumPlugin.js.map +1 -0
- package/dist/vite/index.d.ts +3 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/paths.d.ts +8 -0
- package/dist/vite/paths.d.ts.map +1 -0
- package/dist/vite/paths.js +8 -0
- package/dist/vite/paths.js.map +1 -0
- package/dist/vite/scanner.d.ts +35 -0
- package/dist/vite/scanner.d.ts.map +1 -0
- package/dist/vite/scanner.js +167 -0
- package/dist/vite/scanner.js.map +1 -0
- package/dist/vite/ssg.d.ts +22 -0
- package/dist/vite/ssg.d.ts.map +1 -0
- package/dist/vite/ssg.js +547 -0
- package/dist/vite/ssg.js.map +1 -0
- package/dist/vite/virtualServerModule.d.ts +6 -0
- package/dist/vite/virtualServerModule.d.ts.map +1 -0
- package/dist/vite/virtualServerModule.js +82 -0
- package/dist/vite/virtualServerModule.js.map +1 -0
- package/package.json +103 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { log } from "../utils/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert a file path to a route pattern
|
|
5
|
+
* Examples:
|
|
6
|
+
* - /src/pages/index.tsx → /
|
|
7
|
+
* - /src/pages/tasks/index.tsx → /tasks
|
|
8
|
+
* - /src/pages/tasks/[id].tsx → /tasks/:id
|
|
9
|
+
* - /src/pages/settings/profile.tsx → /settings/profile
|
|
10
|
+
* - /src/pages/blog/[...slug].tsx → /blog/*slug
|
|
11
|
+
* - /src/pages/404.tsx → __404__
|
|
12
|
+
* - /src/pages/(website)/contact.tsx → /contact
|
|
13
|
+
* - /src/pages/(portal)/dashboard.tsx → /dashboard
|
|
14
|
+
*/
|
|
15
|
+
function pathFromFile(file) {
|
|
16
|
+
// Remove /src/pages prefix and file extension
|
|
17
|
+
const withoutPrefix = file.replace("/src/pages", "").replace(/\.(tsx|jsx|ts|js)$/, "");
|
|
18
|
+
// Handle special files
|
|
19
|
+
if (withoutPrefix === "/404") {
|
|
20
|
+
return "__404__";
|
|
21
|
+
}
|
|
22
|
+
// Remove route groups (folders in parentheses)
|
|
23
|
+
// E.g., /(website)/contact → /contact
|
|
24
|
+
let pattern = withoutPrefix.replace(/\/\([^)]+\)/g, "");
|
|
25
|
+
// Convert /index to /
|
|
26
|
+
pattern = pattern.replace(/\/index$/, "") || "/";
|
|
27
|
+
// Convert [...param] to *param (catch-all)
|
|
28
|
+
pattern = pattern.replace(/\[\.\.\.(.+?)\]/g, "*$1");
|
|
29
|
+
// Convert [param] to :param (single dynamic segment)
|
|
30
|
+
pattern = pattern.replace(/\[(.+?)\]/g, ":$1");
|
|
31
|
+
return pattern;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a matcher function for a route pattern
|
|
35
|
+
* Supports dynamic segments like :id, :slug, etc.
|
|
36
|
+
* Supports catch-all segments like *slug for [...slug]
|
|
37
|
+
*/
|
|
38
|
+
function createMatcher(pattern) {
|
|
39
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
40
|
+
return (path) => {
|
|
41
|
+
// Remove query string and hash
|
|
42
|
+
const cleanPath = path.split("?")[0].split("#")[0];
|
|
43
|
+
const pathSegments = cleanPath.split("/").filter(Boolean);
|
|
44
|
+
// For root path, handle specially
|
|
45
|
+
if (pattern === "/" && cleanPath === "/") {
|
|
46
|
+
return { params: {} };
|
|
47
|
+
}
|
|
48
|
+
const params = {};
|
|
49
|
+
// Check for catch-all segment (must be last)
|
|
50
|
+
const hasCatchAll = segments.some((seg) => seg.startsWith("*"));
|
|
51
|
+
if (hasCatchAll) {
|
|
52
|
+
const catchAllIndex = segments.findIndex((seg) => seg.startsWith("*"));
|
|
53
|
+
// Catch-all must be the last segment
|
|
54
|
+
if (catchAllIndex !== segments.length - 1) {
|
|
55
|
+
log("warn", `Catch-all segment must be last: ${pattern}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const catchAllParam = segments[catchAllIndex].slice(1); // Remove *
|
|
59
|
+
// Match segments before catch-all
|
|
60
|
+
for (let i = 0; i < catchAllIndex; i++) {
|
|
61
|
+
const seg = segments[i];
|
|
62
|
+
const value = pathSegments[i];
|
|
63
|
+
if (!value) {
|
|
64
|
+
return null;
|
|
65
|
+
} // Not enough path segments
|
|
66
|
+
if (seg.startsWith(":")) {
|
|
67
|
+
params[seg.slice(1)] = decodeURIComponent(value);
|
|
68
|
+
}
|
|
69
|
+
else if (seg !== value) {
|
|
70
|
+
return null; // Static segment must match
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Collect remaining path segments into catch-all param as an array
|
|
74
|
+
const remainingSegments = pathSegments.slice(catchAllIndex);
|
|
75
|
+
params[catchAllParam] = remainingSegments.map((s) => decodeURIComponent(s));
|
|
76
|
+
return { params };
|
|
77
|
+
}
|
|
78
|
+
// Regular matching (no catch-all)
|
|
79
|
+
// If segment counts don't match, no match
|
|
80
|
+
if (segments.length !== pathSegments.length) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
for (let i = 0; i < segments.length; i++) {
|
|
84
|
+
const seg = segments[i];
|
|
85
|
+
const value = pathSegments[i];
|
|
86
|
+
if (seg.startsWith(":")) {
|
|
87
|
+
// Dynamic segment
|
|
88
|
+
params[seg.slice(1)] = decodeURIComponent(value);
|
|
89
|
+
}
|
|
90
|
+
else if (seg !== value) {
|
|
91
|
+
// Static segment must match exactly
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { params };
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get directory path from file path
|
|
100
|
+
* /src/pages/tasks/[id].tsx → /tasks
|
|
101
|
+
* /src/pages/(website)/contact.tsx → /(website)
|
|
102
|
+
*/
|
|
103
|
+
function getDirectoryPath(file) {
|
|
104
|
+
const withoutPrefix = file.replace("/src/pages", "").replace(/\.(tsx|jsx|ts|js)$/, "");
|
|
105
|
+
const parts = withoutPrefix.split("/");
|
|
106
|
+
parts.pop(); // Remove filename
|
|
107
|
+
return parts.join("/") || "/";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build route manifest from pages directory
|
|
111
|
+
* Uses import.meta.glob to discover all page files
|
|
112
|
+
* Supports both eager and lazy loading for Suspense compatibility
|
|
113
|
+
*/
|
|
114
|
+
export function buildRoutes() {
|
|
115
|
+
// SSR check - return empty routes if running server-side
|
|
116
|
+
if (typeof window === "undefined") {
|
|
117
|
+
return { routes: [], NotFound: undefined, AppShell: undefined };
|
|
118
|
+
}
|
|
119
|
+
// Eagerly load all page components (for initial render and layouts)
|
|
120
|
+
const eagerPages = import.meta.glob("/src/pages/**/*.{tsx,jsx,ts,js}", {
|
|
121
|
+
eager: true,
|
|
122
|
+
});
|
|
123
|
+
// Lazy load all page components (for Suspense support)
|
|
124
|
+
const lazyPages = import.meta.glob("/src/pages/**/*.{tsx,jsx,ts,js}");
|
|
125
|
+
// Debug mode: set localStorage.setItem('helium_debug_routes', 'true') to enable
|
|
126
|
+
const debugRoutes = typeof localStorage !== "undefined" && localStorage.getItem("helium_debug_routes") === "true";
|
|
127
|
+
if (debugRoutes) {
|
|
128
|
+
console.group("[Helium Router] Discovered pages:");
|
|
129
|
+
for (const file of Object.keys(eagerPages)) {
|
|
130
|
+
console.log(` ${file} → ${pathFromFile(file)}`);
|
|
131
|
+
}
|
|
132
|
+
console.groupEnd();
|
|
133
|
+
}
|
|
134
|
+
const routes = [];
|
|
135
|
+
let NotFound;
|
|
136
|
+
let AppShell;
|
|
137
|
+
// Build layout map: directory path -> layout component
|
|
138
|
+
const layoutMap = new Map();
|
|
139
|
+
// Track route patterns to detect collisions: pattern -> file path
|
|
140
|
+
const routePatternMap = new Map();
|
|
141
|
+
// First pass: collect all layouts
|
|
142
|
+
for (const [file, mod] of Object.entries(eagerPages)) {
|
|
143
|
+
if (file.includes("/_layout.")) {
|
|
144
|
+
const Component = mod.default;
|
|
145
|
+
if (Component) {
|
|
146
|
+
const dirPath = getDirectoryPath(file);
|
|
147
|
+
layoutMap.set(dirPath, Component);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Second pass: build routes with their layouts
|
|
152
|
+
for (const [file, mod] of Object.entries(eagerPages)) {
|
|
153
|
+
// Skip layout files
|
|
154
|
+
if (file.includes("/_layout.")) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const Component = mod.default;
|
|
158
|
+
if (!Component) {
|
|
159
|
+
log("warn", `No default export found in ${file}`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const pathPattern = pathFromFile(file);
|
|
163
|
+
// Handle special pages
|
|
164
|
+
if (pathPattern === "__404__") {
|
|
165
|
+
NotFound = Component;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Detect route collisions
|
|
169
|
+
const existingFile = routePatternMap.get(pathPattern);
|
|
170
|
+
if (existingFile) {
|
|
171
|
+
log("error", `Route collision detected! Multiple files resolve to the same path "${pathPattern}":`);
|
|
172
|
+
log("error", ` - ${existingFile}`);
|
|
173
|
+
log("error", ` - ${file}`);
|
|
174
|
+
log("error", `Only the first file will be used. Consider using different file names or paths.`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
routePatternMap.set(pathPattern, file);
|
|
178
|
+
}
|
|
179
|
+
// Find all layouts for this route (from root to leaf)
|
|
180
|
+
const layouts = [];
|
|
181
|
+
const dirPath = getDirectoryPath(file);
|
|
182
|
+
const pathParts = dirPath.split("/").filter(Boolean);
|
|
183
|
+
// Always check for root layout first (applies to all pages)
|
|
184
|
+
const rootLayout = layoutMap.get("/");
|
|
185
|
+
if (rootLayout) {
|
|
186
|
+
layouts.push(rootLayout);
|
|
187
|
+
}
|
|
188
|
+
// Then check for layouts at each nested level
|
|
189
|
+
// This will include route group layouts and any nested folder layouts
|
|
190
|
+
for (let i = 1; i <= pathParts.length; i++) {
|
|
191
|
+
const checkPath = "/" + pathParts.slice(0, i).join("/");
|
|
192
|
+
const layout = layoutMap.get(checkPath);
|
|
193
|
+
if (layout) {
|
|
194
|
+
layouts.push(layout);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Get the lazy loader for this page
|
|
198
|
+
const lazyLoader = lazyPages[file];
|
|
199
|
+
const LazyComponent = React.lazy(lazyLoader);
|
|
200
|
+
// Create route entry with both eager and lazy components
|
|
201
|
+
routes.push({
|
|
202
|
+
pathPattern,
|
|
203
|
+
matcher: createMatcher(pathPattern),
|
|
204
|
+
Component,
|
|
205
|
+
LazyComponent,
|
|
206
|
+
layouts,
|
|
207
|
+
preload: lazyLoader,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// Sort routes by specificity (static > dynamic > catch-all)
|
|
211
|
+
routes.sort((a, b) => {
|
|
212
|
+
const aHasCatchAll = a.pathPattern.includes("*");
|
|
213
|
+
const bHasCatchAll = b.pathPattern.includes("*");
|
|
214
|
+
const aHasDynamic = a.pathPattern.includes(":");
|
|
215
|
+
const bHasDynamic = b.pathPattern.includes(":");
|
|
216
|
+
// Catch-all routes should be last (least specific)
|
|
217
|
+
if (aHasCatchAll && !bHasCatchAll) {
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
if (!aHasCatchAll && bHasCatchAll) {
|
|
221
|
+
return -1;
|
|
222
|
+
}
|
|
223
|
+
// Dynamic routes come after static routes
|
|
224
|
+
if (aHasDynamic && !bHasDynamic) {
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
if (!aHasDynamic && bHasDynamic) {
|
|
228
|
+
return -1;
|
|
229
|
+
}
|
|
230
|
+
// Longer paths first (more specific)
|
|
231
|
+
return b.pathPattern.length - a.pathPattern.length;
|
|
232
|
+
});
|
|
233
|
+
if (debugRoutes) {
|
|
234
|
+
console.group("[Helium Router] Final route order:");
|
|
235
|
+
for (const route of routes) {
|
|
236
|
+
console.log(` ${route.pathPattern}`);
|
|
237
|
+
}
|
|
238
|
+
console.groupEnd();
|
|
239
|
+
}
|
|
240
|
+
return { routes, NotFound, AppShell };
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=routerManifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routerManifest.js","sourceRoot":"","sources":["../../src/client/routerManifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RpcStats } from "../runtime/protocol.js";
|
|
2
|
+
export type RpcResult<T> = {
|
|
3
|
+
data: T;
|
|
4
|
+
stats: RpcStats;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Transport mode for RPC calls.
|
|
8
|
+
* - "http": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)
|
|
9
|
+
* - "websocket": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)
|
|
10
|
+
* - "auto": Automatically selects based on connection type
|
|
11
|
+
*/
|
|
12
|
+
export type RpcTransport = "http" | "websocket" | "auto";
|
|
13
|
+
/**
|
|
14
|
+
* Get the configured RPC transport mode (from helium.config.js).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getRpcTransport(): RpcTransport;
|
|
17
|
+
/**
|
|
18
|
+
* Check if auto HTTP on mobile is enabled (from helium.config.js).
|
|
19
|
+
*/
|
|
20
|
+
export declare function isAutoHttpOnMobileEnabled(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Make an RPC call using the appropriate transport.
|
|
23
|
+
* Automatically selects HTTP or WebSocket based on network conditions and configuration.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>>;
|
|
26
|
+
/**
|
|
27
|
+
* Pre-establishes the WebSocket connection.
|
|
28
|
+
* Call this early (e.g., on page load) to avoid connection latency on first RPC call.
|
|
29
|
+
* This is especially beneficial on high-latency networks like mobile LTE.
|
|
30
|
+
* Note: Only effective when using WebSocket transport (not HTTP transport).
|
|
31
|
+
*/
|
|
32
|
+
export declare function preconnect(): void;
|
|
33
|
+
//# sourceMappingURL=rpcClient.d.ts.map
|
|
@@ -0,0 +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;AAEhF,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;AA4XD;;;GAGG;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,CAY7H;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAYjC"}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { decode as msgpackDecode, encode as msgpackEncode } from "@msgpack/msgpack";
|
|
2
|
+
// Read build-time config with fallback defaults
|
|
3
|
+
const configuredTransport = typeof __HELIUM_RPC_TRANSPORT__ !== "undefined" ? __HELIUM_RPC_TRANSPORT__ : "websocket";
|
|
4
|
+
const configuredAutoHttpOnMobile = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== "undefined" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;
|
|
5
|
+
/**
|
|
6
|
+
* Get the configured RPC transport mode (from helium.config.js).
|
|
7
|
+
*/
|
|
8
|
+
export function getRpcTransport() {
|
|
9
|
+
return configuredTransport;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check if auto HTTP on mobile is enabled (from helium.config.js).
|
|
13
|
+
*/
|
|
14
|
+
export function isAutoHttpOnMobileEnabled() {
|
|
15
|
+
return configuredAutoHttpOnMobile;
|
|
16
|
+
}
|
|
17
|
+
// Detect if we should prefer HTTP transport (mobile/slow networks)
|
|
18
|
+
function shouldUseHttpTransport() {
|
|
19
|
+
if (configuredTransport === "http") {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (configuredTransport === "websocket") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// Auto mode: check if mobile HTTP optimization is enabled
|
|
26
|
+
if (!configuredAutoHttpOnMobile) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Prefer HTTP on mobile/slow connections
|
|
30
|
+
if (typeof navigator !== "undefined") {
|
|
31
|
+
const conn = navigator.connection;
|
|
32
|
+
if (conn) {
|
|
33
|
+
// Use HTTP for cellular connections or slow effective types
|
|
34
|
+
const slowTypes = ["slow-2g", "2g", "3g"];
|
|
35
|
+
if (conn.type === "cellular" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
let pendingBatch = [];
|
|
43
|
+
let isBatchScheduled = false;
|
|
44
|
+
function scheduleBatch() {
|
|
45
|
+
if (isBatchScheduled) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
isBatchScheduled = true;
|
|
49
|
+
queueMicrotask(() => {
|
|
50
|
+
isBatchScheduled = false;
|
|
51
|
+
flushBatch();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function flushBatch() {
|
|
55
|
+
const batch = pendingBatch;
|
|
56
|
+
pendingBatch = [];
|
|
57
|
+
if (batch.length === 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
if (shouldUseHttpTransport()) {
|
|
62
|
+
await sendBatchHttp(batch);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await sendBatchWebSocket(batch);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// Transport error, fail all
|
|
70
|
+
for (const item of batch) {
|
|
71
|
+
item.reject(err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function sendBatchHttp(batch) {
|
|
76
|
+
const requests = batch.map((b) => b.req);
|
|
77
|
+
const encoded = msgpackEncode(requests);
|
|
78
|
+
const response = await fetch("/__helium__/rpc", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/msgpack",
|
|
82
|
+
Accept: "application/msgpack",
|
|
83
|
+
},
|
|
84
|
+
body: encoded,
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`HTTP RPC failed: ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
const responseBuffer = await response.arrayBuffer();
|
|
90
|
+
const msg = msgpackDecode(new Uint8Array(responseBuffer));
|
|
91
|
+
const responses = Array.isArray(msg) ? msg : [msg];
|
|
92
|
+
const responseMap = new Map(responses.map((r) => [r.id, r]));
|
|
93
|
+
for (const item of batch) {
|
|
94
|
+
const res = responseMap.get(item.req.id);
|
|
95
|
+
if (res) {
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
item.resolve({ data: res.result, stats: res.stats });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
item.reject({ error: res.error, stats: res.stats });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
item.reject(new Error("No response for request"));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function sendBatchWebSocket(batch) {
|
|
109
|
+
const ws = await ensureSocketReady();
|
|
110
|
+
const requests = batch.map((b) => b.req);
|
|
111
|
+
// Register pending promises
|
|
112
|
+
batch.forEach((item) => {
|
|
113
|
+
pending.set(item.req.id, {
|
|
114
|
+
resolve: (v) => item.resolve(v),
|
|
115
|
+
reject: item.reject,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
// Always use msgpack encoding
|
|
120
|
+
const encoded = msgpackEncode(requests);
|
|
121
|
+
ws.send(encoded);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
batch.forEach((item) => {
|
|
125
|
+
pending.delete(item.req.id);
|
|
126
|
+
item.reject(err);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// WebSocket Transport (original implementation)
|
|
132
|
+
// ============================================================================
|
|
133
|
+
let socket = null;
|
|
134
|
+
let connectionPromise = null;
|
|
135
|
+
const pending = new Map();
|
|
136
|
+
// Clean up WebSocket connection on HMR (Hot Module Replacement)
|
|
137
|
+
if (import.meta.hot) {
|
|
138
|
+
import.meta.hot.dispose(() => {
|
|
139
|
+
if (socket) {
|
|
140
|
+
// Close the socket gracefully
|
|
141
|
+
socket.close();
|
|
142
|
+
socket = null;
|
|
143
|
+
connectionPromise = null;
|
|
144
|
+
}
|
|
145
|
+
// Reject all pending requests
|
|
146
|
+
pending.forEach((entry) => {
|
|
147
|
+
entry.reject(new Error("Module reloaded"));
|
|
148
|
+
});
|
|
149
|
+
pending.clear();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
let msgId = 0;
|
|
153
|
+
function nextId() {
|
|
154
|
+
return msgId++;
|
|
155
|
+
}
|
|
156
|
+
async function fetchFreshToken() {
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch("/__helium__/refresh-token");
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
console.warn("Failed to fetch fresh token:", response.status);
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
const data = await response.json();
|
|
164
|
+
return data.token;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.warn("Error fetching fresh token:", error);
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function createSocket() {
|
|
172
|
+
// Fetch a fresh token before creating the WebSocket connection
|
|
173
|
+
const token = await fetchFreshToken();
|
|
174
|
+
// Use the same protocol, hostname and port as the current page
|
|
175
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
176
|
+
const host = window.location.host; // includes hostname and port
|
|
177
|
+
const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : ""}`;
|
|
178
|
+
const ws = new WebSocket(url);
|
|
179
|
+
ws.binaryType = "arraybuffer";
|
|
180
|
+
ws.onmessage = async (event) => {
|
|
181
|
+
let data = new Uint8Array(event.data);
|
|
182
|
+
// Check for Gzip header (0x1f 0x8b) to detect compressed messages
|
|
183
|
+
if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {
|
|
184
|
+
try {
|
|
185
|
+
// Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)
|
|
186
|
+
if (typeof DecompressionStream !== "undefined") {
|
|
187
|
+
const ds = new DecompressionStream("gzip");
|
|
188
|
+
const stream = new Response(data).body;
|
|
189
|
+
if (stream) {
|
|
190
|
+
const decompressed = stream.pipeThrough(ds);
|
|
191
|
+
const buffer = await new Response(decompressed).arrayBuffer();
|
|
192
|
+
data = new Uint8Array(buffer);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
console.error("Failed to decompress WebSocket message:", err);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Always expect binary MessagePack
|
|
202
|
+
const msg = msgpackDecode(data);
|
|
203
|
+
const handleResponse = (res) => {
|
|
204
|
+
const entry = pending.get(res.id);
|
|
205
|
+
if (!entry) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
pending.delete(res.id);
|
|
209
|
+
if (res.ok) {
|
|
210
|
+
entry.resolve({ data: res.result, stats: res.stats });
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
entry.reject({ error: res.error, stats: res.stats });
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
if (Array.isArray(msg)) {
|
|
217
|
+
msg.forEach(handleResponse);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
handleResponse(msg);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
ws.onclose = () => {
|
|
224
|
+
if (socket === ws) {
|
|
225
|
+
socket = null;
|
|
226
|
+
connectionPromise = null;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
return ws;
|
|
230
|
+
}
|
|
231
|
+
async function ensureSocketReady() {
|
|
232
|
+
// If we have an open socket, return it immediately
|
|
233
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
234
|
+
return socket;
|
|
235
|
+
}
|
|
236
|
+
// If we have a connection in progress, reuse that promise
|
|
237
|
+
if (connectionPromise) {
|
|
238
|
+
return connectionPromise;
|
|
239
|
+
}
|
|
240
|
+
// If we have a socket that's connecting, wait for it
|
|
241
|
+
if (socket && socket.readyState === WebSocket.CONNECTING) {
|
|
242
|
+
connectionPromise = new Promise((resolve, reject) => {
|
|
243
|
+
const cleanup = () => {
|
|
244
|
+
socket.removeEventListener("open", handleOpen);
|
|
245
|
+
socket.removeEventListener("error", handleError);
|
|
246
|
+
socket.removeEventListener("close", handleClose);
|
|
247
|
+
};
|
|
248
|
+
const handleOpen = () => {
|
|
249
|
+
cleanup();
|
|
250
|
+
connectionPromise = null;
|
|
251
|
+
resolve(socket);
|
|
252
|
+
};
|
|
253
|
+
const handleError = () => {
|
|
254
|
+
cleanup();
|
|
255
|
+
socket = null;
|
|
256
|
+
connectionPromise = null;
|
|
257
|
+
reject(new Error("WebSocket connection failed"));
|
|
258
|
+
};
|
|
259
|
+
const handleClose = () => {
|
|
260
|
+
cleanup();
|
|
261
|
+
socket = null;
|
|
262
|
+
connectionPromise = null;
|
|
263
|
+
reject(new Error("WebSocket closed before opening"));
|
|
264
|
+
};
|
|
265
|
+
socket.addEventListener("open", handleOpen);
|
|
266
|
+
socket.addEventListener("error", handleError);
|
|
267
|
+
socket.addEventListener("close", handleClose);
|
|
268
|
+
});
|
|
269
|
+
return connectionPromise;
|
|
270
|
+
}
|
|
271
|
+
// Create a new socket and connection promise
|
|
272
|
+
connectionPromise = (async () => {
|
|
273
|
+
socket = await createSocket();
|
|
274
|
+
return new Promise((resolve, reject) => {
|
|
275
|
+
const cleanup = () => {
|
|
276
|
+
socket.removeEventListener("open", handleOpen);
|
|
277
|
+
socket.removeEventListener("error", handleError);
|
|
278
|
+
socket.removeEventListener("close", handleClose);
|
|
279
|
+
};
|
|
280
|
+
const handleOpen = () => {
|
|
281
|
+
cleanup();
|
|
282
|
+
connectionPromise = null;
|
|
283
|
+
resolve(socket);
|
|
284
|
+
};
|
|
285
|
+
const handleError = () => {
|
|
286
|
+
cleanup();
|
|
287
|
+
socket = null;
|
|
288
|
+
connectionPromise = null;
|
|
289
|
+
reject(new Error("WebSocket connection failed"));
|
|
290
|
+
};
|
|
291
|
+
const handleClose = () => {
|
|
292
|
+
cleanup();
|
|
293
|
+
socket = null;
|
|
294
|
+
connectionPromise = null;
|
|
295
|
+
reject(new Error("WebSocket closed before opening"));
|
|
296
|
+
};
|
|
297
|
+
socket.addEventListener("open", handleOpen);
|
|
298
|
+
socket.addEventListener("error", handleError);
|
|
299
|
+
socket.addEventListener("close", handleClose);
|
|
300
|
+
});
|
|
301
|
+
})();
|
|
302
|
+
return connectionPromise;
|
|
303
|
+
}
|
|
304
|
+
async function rpcCallWebSocket(methodId, args) {
|
|
305
|
+
// Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)
|
|
306
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
307
|
+
const id = nextId();
|
|
308
|
+
const req = { id, method: methodId, args };
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
pending.set(id, {
|
|
311
|
+
resolve: (v) => resolve(v),
|
|
312
|
+
reject,
|
|
313
|
+
});
|
|
314
|
+
try {
|
|
315
|
+
const encoded = msgpackEncode(req);
|
|
316
|
+
socket.send(encoded);
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
pending.delete(id);
|
|
320
|
+
reject(err);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const ws = await ensureSocketReady();
|
|
325
|
+
const id = nextId();
|
|
326
|
+
const req = { id, method: methodId, args };
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
pending.set(id, {
|
|
329
|
+
resolve: (v) => resolve(v),
|
|
330
|
+
reject,
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
// Always use msgpack encoding
|
|
334
|
+
const encoded = msgpackEncode(req);
|
|
335
|
+
ws.send(encoded);
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
pending.delete(id);
|
|
339
|
+
reject(err);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Make an RPC call using the appropriate transport.
|
|
345
|
+
* Automatically selects HTTP or WebSocket based on network conditions and configuration.
|
|
346
|
+
*/
|
|
347
|
+
export async function rpcCall(methodId, args) {
|
|
348
|
+
if (shouldUseHttpTransport()) {
|
|
349
|
+
const id = nextId();
|
|
350
|
+
const req = { id, method: methodId, args };
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
pendingBatch.push({ req, resolve: resolve, reject });
|
|
353
|
+
scheduleBatch();
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return rpcCallWebSocket(methodId, args);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Pre-establishes the WebSocket connection.
|
|
360
|
+
* Call this early (e.g., on page load) to avoid connection latency on first RPC call.
|
|
361
|
+
* This is especially beneficial on high-latency networks like mobile LTE.
|
|
362
|
+
* Note: Only effective when using WebSocket transport (not HTTP transport).
|
|
363
|
+
*/
|
|
364
|
+
export function preconnect() {
|
|
365
|
+
if (typeof window === "undefined") {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// Only preconnect if we're using WebSocket transport
|
|
369
|
+
if (shouldUseHttpTransport()) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Fire and forget - establishes connection in background
|
|
373
|
+
ensureSocketReady().catch(() => {
|
|
374
|
+
// Silently ignore preconnect failures, will retry on actual call
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// Auto-preconnect when the module loads (browser only, WebSocket transport only)
|
|
378
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
379
|
+
// Use requestIdleCallback if available, otherwise setTimeout
|
|
380
|
+
const schedulePreconnect = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
|
|
381
|
+
schedulePreconnect(() => preconnect());
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=rpcClient.js.map
|