@ww-ai-lab/openclaw-office 2026.3.8 → 2026.3.21

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/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>OpenClaw Office</title>
7
7
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
- <script type="module" crossorigin src="/assets/index-QZYyP0qG.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DweWoR1-.css">
8
+ <script type="module" crossorigin src="/assets/index-DqQHpfxy.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DehfYC23.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww-ai-lab/openclaw-office",
3
- "version": "2026.3.8",
3
+ "version": "2026.3.21",
4
4
  "description": "Visual monitoring & management frontend for OpenClaw Multi-Agent system",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -1,44 +0,0 @@
1
- export const DEFAULT_GATEWAY_URL: string;
2
- export const DEFAULT_PORT: number;
3
- export const DEFAULT_HOST: string;
4
- export const DEFAULT_PROXY_PATH: string;
5
-
6
- export interface ParsedArgs {
7
- token: string;
8
- gatewayUrl: string;
9
- port: number;
10
- host: string;
11
- help?: boolean;
12
- }
13
-
14
- export interface ResolvedConfig {
15
- token: string;
16
- tokenSource: string;
17
- gatewayUrl: string;
18
- gatewayUrlSource: string;
19
- port: number;
20
- host: string;
21
- officeConfigPath: string;
22
- browserGatewayUrl: string;
23
- shouldPersistGatewayUrl: boolean;
24
- }
25
-
26
- export function getOfficeConfigPath(homeDir?: string): string;
27
- export function parseArgs(argv?: string[]): ParsedArgs;
28
- export function printHelp(): void;
29
- export function readTokenFromConfig(
30
- homeDir?: string,
31
- ): { token: string; source: string } | null;
32
- export function readPersistedOfficeConfig(
33
- configPath?: string,
34
- ): { gatewayUrl: string } | null;
35
- export function writePersistedOfficeConfig(gatewayUrl: string, configPath?: string): void;
36
- export function normalizeGatewayAccessUrl(rawGatewayUrl: string): {
37
- gatewayUrl: string;
38
- token: string;
39
- };
40
- export function resolveConfig(options?: {
41
- argv?: string[];
42
- env?: NodeJS.ProcessEnv | Record<string, string>;
43
- homeDir?: string;
44
- }): ResolvedConfig;
@@ -1,219 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { dirname, join } from "node:path";
4
-
5
- export const DEFAULT_GATEWAY_URL = "ws://localhost:18789";
6
- export const DEFAULT_PORT = 5180;
7
- export const DEFAULT_HOST = "0.0.0.0";
8
- export const DEFAULT_PROXY_PATH = "/gateway-ws";
9
- const TOKEN_QUERY_PARAM = "token";
10
-
11
- export function getOfficeConfigPath(homeDir = homedir()) {
12
- return join(homeDir, ".openclaw", "openclaw-office.json");
13
- }
14
-
15
- export function parseArgs(argv = process.argv.slice(2)) {
16
- const result = { token: "", gatewayUrl: "", port: 0, host: "" };
17
- for (let i = 0; i < argv.length; i++) {
18
- const arg = argv[i];
19
- const next = argv[i + 1];
20
- if ((arg === "--token" || arg === "-t") && next) {
21
- result.token = next;
22
- i++;
23
- } else if ((arg === "--gateway" || arg === "-g") && next) {
24
- result.gatewayUrl = next;
25
- i++;
26
- } else if ((arg === "--port" || arg === "-p") && next) {
27
- result.port = parseInt(next, 10);
28
- i++;
29
- } else if (arg === "--host" && next) {
30
- result.host = next;
31
- i++;
32
- } else if (arg === "--help" || arg === "-h") {
33
- result.help = true;
34
- }
35
- }
36
- return result;
37
- }
38
-
39
- export function printHelp() {
40
- console.log(`
41
- \x1b[36mOpenClaw Office\x1b[0m — Visual monitoring frontend for OpenClaw
42
-
43
- \x1b[1mUsage:\x1b[0m
44
- openclaw-office [options]
45
-
46
- \x1b[1mOptions:\x1b[0m
47
- -t, --token <token> Gateway auth token
48
- -g, --gateway <url> Gateway WebSocket URL (default: ws://localhost:18789)
49
- -p, --port <port> Server port (default: 5180, or PORT env)
50
- --host <host> Bind address (default: 0.0.0.0)
51
- -h, --help Show this help
52
-
53
- \x1b[1mGateway URL persistence:\x1b[0m
54
- The upstream Gateway URL is resolved in this order:
55
- 1. --gateway flag
56
- 2. OPENCLAW_GATEWAY_URL environment variable
57
- 3. Persisted Office config at ~/.openclaw/openclaw-office.json
58
- 4. Default ws://localhost:18789
59
-
60
- \x1b[1mToken auto-detection:\x1b[0m
61
- The token is resolved in this order:
62
- 1. --token flag
63
- 2. OPENCLAW_GATEWAY_TOKEN environment variable
64
- 3. Auto-read from ~/.openclaw/openclaw.json
65
-
66
- \x1b[1mExamples:\x1b[0m
67
- openclaw-office
68
- openclaw-office --token my-secret-token
69
- openclaw-office --gateway ws://192.168.1.100:18789
70
- PORT=3000 openclaw-office
71
- `);
72
- }
73
-
74
- export function readTokenFromConfig(homeDir = homedir()) {
75
- const candidates = [
76
- join(homeDir, ".openclaw", "openclaw.json"),
77
- join(homeDir, ".clawdbot", "clawdbot.json"),
78
- ];
79
-
80
- for (const filePath of candidates) {
81
- try {
82
- const raw = readFileSync(filePath, "utf-8");
83
- const config = JSON.parse(raw);
84
- const token = config?.gateway?.auth?.token;
85
- if (token && typeof token === "string") {
86
- return { token, source: filePath };
87
- }
88
- } catch {
89
- // file not found or parse error
90
- }
91
- }
92
-
93
- return null;
94
- }
95
-
96
- export function readPersistedOfficeConfig(configPath = getOfficeConfigPath()) {
97
- if (!existsSync(configPath)) {
98
- return null;
99
- }
100
-
101
- try {
102
- const raw = readFileSync(configPath, "utf-8");
103
- const parsed = JSON.parse(raw);
104
- const gatewayUrl = parsed?.gatewayUrl;
105
- if (typeof gatewayUrl !== "string" || gatewayUrl.length === 0) {
106
- return null;
107
- }
108
- return { gatewayUrl };
109
- } catch {
110
- return null;
111
- }
112
- }
113
-
114
- export function writePersistedOfficeConfig(
115
- gatewayUrl,
116
- configPath = getOfficeConfigPath(),
117
- ) {
118
- mkdirSync(dirname(configPath), { recursive: true });
119
- writeFileSync(configPath, `${JSON.stringify({ gatewayUrl }, null, 2)}\n`, "utf-8");
120
- }
121
-
122
- function formatParsedUrl(parsed, original) {
123
- const serialized = parsed.toString();
124
- if (/^[a-z]+:\/\/[^/?#]+(?:\?[^#]*)?(?:#.*)?$/i.test(original)) {
125
- return serialized.replace(/\/$/, "");
126
- }
127
- return serialized;
128
- }
129
-
130
- export function normalizeGatewayAccessUrl(rawGatewayUrl) {
131
- if (!rawGatewayUrl) {
132
- return { gatewayUrl: rawGatewayUrl, token: "" };
133
- }
134
-
135
- try {
136
- const parsed = new URL(rawGatewayUrl);
137
- const token = parsed.searchParams.get(TOKEN_QUERY_PARAM) ?? "";
138
- parsed.searchParams.delete(TOKEN_QUERY_PARAM);
139
-
140
- if (parsed.protocol === "http:") {
141
- parsed.protocol = "ws:";
142
- } else if (parsed.protocol === "https:") {
143
- parsed.protocol = "wss:";
144
- }
145
-
146
- return { gatewayUrl: formatParsedUrl(parsed, rawGatewayUrl), token };
147
- } catch {
148
- return { gatewayUrl: rawGatewayUrl, token: "" };
149
- }
150
- }
151
-
152
- export function resolveConfig({
153
- argv = process.argv.slice(2),
154
- env = process.env,
155
- homeDir = homedir(),
156
- } = {}) {
157
- const args = parseArgs(argv);
158
- const officeConfigPath = getOfficeConfigPath(homeDir);
159
- const persisted = readPersistedOfficeConfig(officeConfigPath);
160
-
161
- let token = "";
162
- let tokenSource = "";
163
-
164
- if (args.token) {
165
- token = args.token;
166
- tokenSource = "command line --token";
167
- } else if (env.OPENCLAW_GATEWAY_TOKEN) {
168
- token = env.OPENCLAW_GATEWAY_TOKEN;
169
- tokenSource = "OPENCLAW_GATEWAY_TOKEN env";
170
- } else {
171
- const fromFile = readTokenFromConfig(homeDir);
172
- if (fromFile) {
173
- token = fromFile.token;
174
- tokenSource = fromFile.source;
175
- }
176
- }
177
-
178
- let gatewayUrl = DEFAULT_GATEWAY_URL;
179
- let gatewayUrlSource = "default";
180
- if (persisted?.gatewayUrl) {
181
- gatewayUrl = persisted.gatewayUrl;
182
- gatewayUrlSource = officeConfigPath;
183
- }
184
- if (env.OPENCLAW_GATEWAY_URL) {
185
- gatewayUrl = env.OPENCLAW_GATEWAY_URL;
186
- gatewayUrlSource = "OPENCLAW_GATEWAY_URL env";
187
- }
188
- if (args.gatewayUrl) {
189
- gatewayUrl = args.gatewayUrl;
190
- gatewayUrlSource = "command line --gateway";
191
- }
192
-
193
- const normalizedGateway = normalizeGatewayAccessUrl(gatewayUrl);
194
- gatewayUrl = normalizedGateway.gatewayUrl || gatewayUrl;
195
-
196
- if (!token && normalizedGateway.token) {
197
- token = normalizedGateway.token;
198
- tokenSource = `${gatewayUrlSource} token query`;
199
- }
200
-
201
- const port = args.port || parseInt(env.PORT || `${DEFAULT_PORT}`, 10);
202
- const host = args.host || env.HOST || DEFAULT_HOST;
203
- const shouldPersistGatewayUrl =
204
- !!gatewayUrl &&
205
- (gatewayUrlSource === "command line --gateway" || gatewayUrlSource === "OPENCLAW_GATEWAY_URL env") &&
206
- gatewayUrl !== persisted?.gatewayUrl;
207
-
208
- return {
209
- token,
210
- tokenSource,
211
- gatewayUrl,
212
- gatewayUrlSource,
213
- port,
214
- host,
215
- officeConfigPath,
216
- browserGatewayUrl: DEFAULT_PROXY_PATH,
217
- shouldPersistGatewayUrl,
218
- };
219
- }
@@ -1,34 +0,0 @@
1
- import type { Server as HttpServer, IncomingMessage } from "node:http";
2
- import type { Duplex } from "node:stream";
3
-
4
- export const MIME_TYPES: Record<string, string>;
5
-
6
- export interface OfficeServerConfig {
7
- gatewayUrl: string;
8
- browserGatewayUrl: string;
9
- token: string;
10
- port?: number;
11
- host?: string;
12
- gatewayUrlSource?: string;
13
- tokenSource?: string;
14
- }
15
-
16
- export function createRuntimeConfigScript(config: {
17
- browserGatewayUrl: string;
18
- token: string;
19
- }): string;
20
- export function formatStartupSummary(config: OfficeServerConfig): string;
21
- export function proxyWebSocketUpgrade(
22
- req: IncomingMessage,
23
- downstreamSocket: Duplex,
24
- downstreamHead: Buffer,
25
- config: OfficeServerConfig,
26
- ): void;
27
- export function createOfficeServer(options: {
28
- config: OfficeServerConfig;
29
- distDir: string;
30
- createHttpServer?: typeof import("node:http").createServer;
31
- }): {
32
- server: HttpServer;
33
- getIndexHtml: () => Promise<string>;
34
- };
@@ -1,342 +0,0 @@
1
- import { createServer } from "node:http";
2
- import { access, readFile } from "node:fs/promises";
3
- import { request as httpRequest } from "node:http";
4
- import { request as httpsRequest } from "node:https";
5
- import { networkInterfaces } from "node:os";
6
- import { extname, join } from "node:path";
7
-
8
- const RUNTIME_CONNECTION_PATH = "/__openclaw/connection";
9
-
10
- export const MIME_TYPES = {
11
- ".html": "text/html; charset=utf-8",
12
- ".js": "application/javascript; charset=utf-8",
13
- ".css": "text/css; charset=utf-8",
14
- ".json": "application/json; charset=utf-8",
15
- ".svg": "image/svg+xml",
16
- ".png": "image/png",
17
- ".jpg": "image/jpeg",
18
- ".jpeg": "image/jpeg",
19
- ".gif": "image/gif",
20
- ".ico": "image/x-icon",
21
- ".woff": "font/woff",
22
- ".woff2": "font/woff2",
23
- ".ttf": "font/ttf",
24
- ".webp": "image/webp",
25
- ".glb": "model/gltf-binary",
26
- ".gltf": "model/gltf+json",
27
- };
28
-
29
- export function createRuntimeConfigScript(config) {
30
- const runtimeConfig = JSON.stringify({
31
- gatewayUrl: config.browserGatewayUrl,
32
- gatewayToken: config.token,
33
- });
34
- return `<script>window.__OPENCLAW_CONFIG__=${runtimeConfig};</script>`;
35
- }
36
-
37
- export function formatStartupSummary(config) {
38
- const lines = [
39
- "",
40
- " \x1b[36m\u{1F3E2} OpenClaw Office\x1b[0m",
41
- "",
42
- ` \x1b[32m\u{27A1}\x1b[0m Local: \x1b[36mhttp://localhost:${config.port}\x1b[0m`,
43
- ];
44
-
45
- if (config.host === "0.0.0.0") {
46
- const nets = networkInterfaces();
47
- for (const name of Object.keys(nets)) {
48
- for (const net of nets[name] || []) {
49
- if (net.family === "IPv4" && !net.internal) {
50
- lines.push(` \x1b[32m\u{27A1}\x1b[0m Network: \x1b[36mhttp://${net.address}:${config.port}\x1b[0m`);
51
- }
52
- }
53
- }
54
- }
55
-
56
- lines.push("");
57
- lines.push(
58
- ` \x1b[32m\u{27A1}\x1b[0m Gateway: \x1b[33m${config.gatewayUrl}\x1b[0m \x1b[90m(from ${config.gatewayUrlSource})\x1b[0m`,
59
- );
60
- lines.push(
61
- ` \x1b[32m\u{27A1}\x1b[0m Proxy: \x1b[36m${config.browserGatewayUrl}\x1b[0m`,
62
- );
63
-
64
- if (config.token) {
65
- lines.push(
66
- ` \x1b[32m\u{2713}\x1b[0m Token: \x1b[32mloaded\x1b[0m \x1b[90m(from ${config.tokenSource})\x1b[0m`,
67
- );
68
- } else {
69
- lines.push(` \x1b[33m\u{26A0}\x1b[0m Token: \x1b[33mnot found\x1b[0m`);
70
- lines.push("");
71
- lines.push(" \x1b[90mTo connect to Gateway, provide a token:\x1b[0m");
72
- lines.push(" \x1b[90m openclaw-office --token <your-token>\x1b[0m");
73
- lines.push(" \x1b[90m or install openclaw CLI and the token will be auto-detected\x1b[0m");
74
- }
75
-
76
- lines.push("");
77
- lines.push(" Press \x1b[1mCtrl+C\x1b[0m to stop");
78
- lines.push("");
79
-
80
- return lines.join("\n");
81
- }
82
-
83
- function buildSocketErrorResponse(statusCode, message) {
84
- return `HTTP/1.1 ${statusCode} ${message}\r\nConnection: close\r\n\r\n`;
85
- }
86
-
87
- function toUpstreamHttpUrl(gatewayUrl) {
88
- const upstreamUrl = new URL(gatewayUrl);
89
- if (upstreamUrl.protocol === "ws:") {
90
- upstreamUrl.protocol = "http:";
91
- } else if (upstreamUrl.protocol === "wss:") {
92
- upstreamUrl.protocol = "https:";
93
- } else if (upstreamUrl.protocol !== "http:" && upstreamUrl.protocol !== "https:") {
94
- throw new Error(`Unsupported Gateway protocol: ${upstreamUrl.protocol}`);
95
- }
96
- return upstreamUrl;
97
- }
98
-
99
- function toUpstreamOrigin(gatewayUrl) {
100
- return toUpstreamHttpUrl(gatewayUrl).origin;
101
- }
102
-
103
- function serializeUpgradeResponse(statusCode, statusMessage, headers) {
104
- const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
105
- for (const [key, value] of Object.entries(headers)) {
106
- if (value === undefined) {
107
- continue;
108
- }
109
- if (Array.isArray(value)) {
110
- for (const item of value) {
111
- lines.push(`${key}: ${item}`);
112
- }
113
- continue;
114
- }
115
- lines.push(`${key}: ${value}`);
116
- }
117
- lines.push("\r\n");
118
- return lines.join("\r\n");
119
- }
120
-
121
- function pipeProxySockets(downstreamSocket, upstreamSocket, downstreamHead, upstreamHead) {
122
- if (downstreamHead.length > 0) {
123
- upstreamSocket.write(downstreamHead);
124
- }
125
- if (upstreamHead.length > 0) {
126
- downstreamSocket.write(upstreamHead);
127
- }
128
-
129
- downstreamSocket.pipe(upstreamSocket);
130
- upstreamSocket.pipe(downstreamSocket);
131
-
132
- const closeBoth = () => {
133
- if (!downstreamSocket.destroyed) {
134
- downstreamSocket.destroy();
135
- }
136
- if (!upstreamSocket.destroyed) {
137
- upstreamSocket.destroy();
138
- }
139
- };
140
-
141
- downstreamSocket.on("error", closeBoth);
142
- upstreamSocket.on("error", closeBoth);
143
- downstreamSocket.on("close", closeBoth);
144
- upstreamSocket.on("close", closeBoth);
145
- }
146
-
147
- export function proxyWebSocketUpgrade(req, downstreamSocket, downstreamHead, config) {
148
- const pathname = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`).pathname;
149
- if (pathname !== config.browserGatewayUrl) {
150
- downstreamSocket.end(buildSocketErrorResponse(404, "Not Found"));
151
- return;
152
- }
153
-
154
- let upstreamUrl;
155
- try {
156
- upstreamUrl = toUpstreamHttpUrl(config.gatewayUrl);
157
- } catch (error) {
158
- downstreamSocket.end(buildSocketErrorResponse(502, "Bad Gateway"));
159
- return;
160
- }
161
-
162
- const requestImpl = upstreamUrl.protocol === "https:" ? httpsRequest : httpRequest;
163
- const upstreamReq = requestImpl(upstreamUrl, {
164
- method: "GET",
165
- headers: {
166
- ...req.headers,
167
- host: upstreamUrl.host,
168
- origin: toUpstreamOrigin(config.gatewayUrl),
169
- connection: "Upgrade",
170
- upgrade: "websocket",
171
- },
172
- });
173
-
174
- let settled = false;
175
- const fail = (statusCode, message) => {
176
- if (settled) {
177
- return;
178
- }
179
- settled = true;
180
- downstreamSocket.end(buildSocketErrorResponse(statusCode, message));
181
- };
182
-
183
- upstreamReq.on("upgrade", (upstreamRes, upstreamSocket, upstreamHead) => {
184
- if (settled) {
185
- upstreamSocket.destroy();
186
- return;
187
- }
188
- settled = true;
189
- downstreamSocket.write(
190
- serializeUpgradeResponse(
191
- upstreamRes.statusCode || 101,
192
- upstreamRes.statusMessage || "Switching Protocols",
193
- upstreamRes.headers,
194
- ),
195
- );
196
- pipeProxySockets(downstreamSocket, upstreamSocket, downstreamHead, upstreamHead);
197
- });
198
-
199
- upstreamReq.on("response", (upstreamRes) => {
200
- upstreamRes.resume();
201
- fail(upstreamRes.statusCode || 502, upstreamRes.statusMessage || "Bad Gateway");
202
- });
203
-
204
- upstreamReq.on("error", () => {
205
- fail(502, "Bad Gateway");
206
- });
207
-
208
- downstreamSocket.on("error", () => {
209
- upstreamReq.destroy();
210
- });
211
-
212
- downstreamSocket.on("close", () => {
213
- upstreamReq.destroy();
214
- });
215
-
216
- upstreamReq.end();
217
- }
218
-
219
- async function tryReadFile(filePath) {
220
- try {
221
- await access(filePath);
222
- return await readFile(filePath);
223
- } catch {
224
- return null;
225
- }
226
- }
227
-
228
- async function readJsonBody(req) {
229
- const chunks = [];
230
- for await (const chunk of req) {
231
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
232
- }
233
-
234
- if (chunks.length === 0) {
235
- return {};
236
- }
237
-
238
- return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
239
- }
240
-
241
- export function createOfficeServer({
242
- config,
243
- distDir,
244
- createHttpServer = createServer,
245
- }) {
246
- const configScript = createRuntimeConfigScript(config);
247
- const runtimeState = {
248
- currentGatewayUrl: config.gatewayUrl,
249
- defaultGatewayUrl: config.gatewayUrl,
250
- };
251
- let indexHtmlCache = null;
252
-
253
- async function getIndexHtml() {
254
- if (indexHtmlCache) {
255
- return indexHtmlCache;
256
- }
257
- const raw = await readFile(join(distDir, "index.html"), "utf-8");
258
- indexHtmlCache = raw.replace("</head>", `${configScript}\n</head>`);
259
- return indexHtmlCache;
260
- }
261
-
262
- const server = createHttpServer(async (req, res) => {
263
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
264
- const pathname = decodeURIComponent(url.pathname);
265
-
266
- if (pathname === RUNTIME_CONNECTION_PATH) {
267
- if (req.method === "GET") {
268
- const mode =
269
- runtimeState.currentGatewayUrl === runtimeState.defaultGatewayUrl ? "local" : "remote";
270
- res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
271
- res.end(
272
- JSON.stringify({
273
- mode,
274
- gatewayUrl: runtimeState.currentGatewayUrl,
275
- }),
276
- );
277
- return;
278
- }
279
-
280
- if (req.method === "POST") {
281
- try {
282
- const body = await readJsonBody(req);
283
- if (body?.mode === "remote" && typeof body.gatewayUrl === "string" && body.gatewayUrl) {
284
- runtimeState.currentGatewayUrl = body.gatewayUrl;
285
- } else if (body?.mode === "local") {
286
- runtimeState.currentGatewayUrl = runtimeState.defaultGatewayUrl;
287
- } else {
288
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
289
- res.end("Invalid connection config payload");
290
- return;
291
- }
292
-
293
- res.writeHead(204);
294
- res.end();
295
- return;
296
- } catch {
297
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
298
- res.end("Invalid JSON payload");
299
- return;
300
- }
301
- }
302
-
303
- res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" });
304
- res.end("Method Not Allowed");
305
- return;
306
- }
307
-
308
- if (pathname === "/" || pathname === "/index.html") {
309
- const html = await getIndexHtml();
310
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
311
- res.end(html);
312
- return;
313
- }
314
-
315
- const filePath = join(distDir, pathname);
316
- const content = await tryReadFile(filePath);
317
-
318
- if (content) {
319
- const ext = extname(filePath).toLowerCase();
320
- const mime = MIME_TYPES[ext] || "application/octet-stream";
321
- res.writeHead(200, { "Content-Type": mime });
322
- res.end(content);
323
- return;
324
- }
325
-
326
- const html = await getIndexHtml();
327
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
328
- res.end(html);
329
- });
330
-
331
- server.on("upgrade", (req, socket, head) => {
332
- proxyWebSocketUpgrade(req, socket, head, {
333
- ...config,
334
- gatewayUrl: runtimeState.currentGatewayUrl,
335
- });
336
- });
337
-
338
- return {
339
- server,
340
- getIndexHtml,
341
- };
342
- }