@linkshell/gateway 0.4.1 → 0.4.3

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.
Files changed (42) hide show
  1. package/Dockerfile +11 -2
  2. package/dist/gateway/src/auth-middleware.d.ts +7 -0
  3. package/dist/gateway/src/auth-middleware.js +53 -9
  4. package/dist/gateway/src/auth-middleware.js.map +1 -1
  5. package/dist/gateway/src/cors.d.ts +2 -0
  6. package/dist/gateway/src/cors.js +7 -0
  7. package/dist/gateway/src/cors.js.map +1 -0
  8. package/dist/gateway/src/embedded.js +14 -2
  9. package/dist/gateway/src/embedded.js.map +1 -1
  10. package/dist/gateway/src/host-auth.d.ts +24 -0
  11. package/dist/gateway/src/host-auth.js +72 -0
  12. package/dist/gateway/src/host-auth.js.map +1 -0
  13. package/dist/gateway/src/index.js +148 -16
  14. package/dist/gateway/src/index.js.map +1 -1
  15. package/dist/gateway/src/pairings.d.ts +8 -0
  16. package/dist/gateway/src/pairings.js +26 -1
  17. package/dist/gateway/src/pairings.js.map +1 -1
  18. package/dist/gateway/src/relay.js +38 -21
  19. package/dist/gateway/src/relay.js.map +1 -1
  20. package/dist/gateway/src/sessions.d.ts +1 -0
  21. package/dist/gateway/src/sessions.js +24 -2
  22. package/dist/gateway/src/sessions.js.map +1 -1
  23. package/dist/gateway/src/static-web.d.ts +9 -0
  24. package/dist/gateway/src/static-web.js +97 -0
  25. package/dist/gateway/src/static-web.js.map +1 -0
  26. package/dist/gateway/src/tunnel.js +1 -1
  27. package/dist/gateway/src/tunnel.js.map +1 -1
  28. package/dist/gateway/tsconfig.tsbuildinfo +1 -1
  29. package/dist/shared-protocol/src/index.d.ts +3730 -2180
  30. package/dist/shared-protocol/src/index.js +127 -13
  31. package/dist/shared-protocol/src/index.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/auth-middleware.ts +68 -8
  34. package/src/cors.ts +8 -0
  35. package/src/embedded.ts +13 -1
  36. package/src/host-auth.ts +82 -0
  37. package/src/index.ts +159 -18
  38. package/src/pairings.ts +27 -1
  39. package/src/relay.ts +41 -20
  40. package/src/sessions.ts +25 -2
  41. package/src/static-web.ts +101 -0
  42. package/src/tunnel.ts +1 -1
@@ -0,0 +1,101 @@
1
+ import { existsSync } from "node:fs";
2
+ import { stat, readFile } from "node:fs/promises";
3
+ import { resolve, join, extname, normalize } from "node:path";
4
+ import type { IncomingMessage, ServerResponse } from "node:http";
5
+
6
+ // Serves the built web-dashboard SPA from the gateway, so a single deployment
7
+ // (and single origin) hosts both the API/WebSocket and the UI. The web is
8
+ // optional: if WEB_DIST doesn't exist (e.g. local dev using the Vite dev
9
+ // server), the gateway runs API-only and this is a no-op.
10
+
11
+ const WEB_DIST = process.env.WEB_DIST ?? resolve(process.cwd(), "web");
12
+ const hasWeb = existsSync(join(WEB_DIST, "index.html"));
13
+
14
+ const CONTENT_TYPES: Record<string, string> = {
15
+ ".html": "text/html; charset=utf-8",
16
+ ".js": "text/javascript; charset=utf-8",
17
+ ".mjs": "text/javascript; charset=utf-8",
18
+ ".css": "text/css; charset=utf-8",
19
+ ".json": "application/json; charset=utf-8",
20
+ ".svg": "image/svg+xml",
21
+ ".png": "image/png",
22
+ ".jpg": "image/jpeg",
23
+ ".jpeg": "image/jpeg",
24
+ ".gif": "image/gif",
25
+ ".ico": "image/x-icon",
26
+ ".webp": "image/webp",
27
+ ".woff": "font/woff",
28
+ ".woff2": "font/woff2",
29
+ ".ttf": "font/ttf",
30
+ ".map": "application/json; charset=utf-8",
31
+ ".txt": "text/plain; charset=utf-8",
32
+ };
33
+
34
+ export function webEnabled(): boolean {
35
+ return hasWeb;
36
+ }
37
+
38
+ export function webDistPath(): string {
39
+ return WEB_DIST;
40
+ }
41
+
42
+ /**
43
+ * Try to serve a GET/HEAD request from the web dist. Returns true if handled.
44
+ * Place AFTER all API routes so it never shadows them. Unmatched non-asset
45
+ * paths fall back to index.html (SPA client-side routing).
46
+ */
47
+ export async function serveWeb(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
48
+ if (!hasWeb) return false;
49
+ if (req.method !== "GET" && req.method !== "HEAD") return false;
50
+
51
+ const url = new URL(req.url ?? "/", "http://localhost");
52
+ let pathname = decodeURIComponent(url.pathname);
53
+
54
+ // Path-traversal guard: normalize, then confine to WEB_DIST.
55
+ const candidate = resolve(WEB_DIST, "." + normalize(pathname));
56
+ const inRoot = candidate === WEB_DIST || candidate.startsWith(WEB_DIST + "/");
57
+
58
+ let filePath: string | null = null;
59
+ if (inRoot && pathname !== "/") {
60
+ try {
61
+ const s = await stat(candidate);
62
+ if (s.isFile()) filePath = candidate;
63
+ } catch {
64
+ // not a real file
65
+ }
66
+ }
67
+
68
+ // SPA fallback: serve index.html for "/" and for any path without a file
69
+ // extension (a client-side route). Missing assets (with an extension) 404.
70
+ if (!filePath) {
71
+ if (pathname === "/" || extname(pathname) === "") {
72
+ filePath = join(WEB_DIST, "index.html");
73
+ } else {
74
+ return false; // let the caller 404 the missing asset
75
+ }
76
+ }
77
+
78
+ try {
79
+ const ext = extname(filePath).toLowerCase();
80
+ const type = CONTENT_TYPES[ext] ?? "application/octet-stream";
81
+ res.setHeader("Content-Type", type);
82
+ // Hashed assets (Vite emits content-hashed filenames) can cache hard;
83
+ // index.html must never be cached so deploys take effect immediately.
84
+ if (filePath.endsWith("index.html")) {
85
+ res.setHeader("Cache-Control", "no-cache");
86
+ } else {
87
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
88
+ }
89
+ if (req.method === "HEAD") {
90
+ res.writeHead(200);
91
+ res.end();
92
+ return true;
93
+ }
94
+ const body = await readFile(filePath);
95
+ res.writeHead(200);
96
+ res.end(body);
97
+ return true;
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
package/src/tunnel.ts CHANGED
@@ -154,7 +154,7 @@ export async function handleTunnelRequest(
154
154
  const cookieToken = tokenOwns ? token : authJwt;
155
155
  if (cookieToken) {
156
156
  const cookieVal = encodeURIComponent(`${sessionId}:${port}:${cookieToken}`);
157
- res.setHeader("Set-Cookie", `lsh_tunnel=${cookieVal}; Path=/; HttpOnly; SameSite=Lax`);
157
+ res.setHeader("Set-Cookie", `lsh_tunnel=${cookieVal}; Path=/; HttpOnly; Secure; SameSite=Lax`);
158
158
  }
159
159
 
160
160
  // Validate session & host