buninu 0.4.2 → 0.4.5

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/apps/bunx/bunx.js CHANGED
@@ -144,7 +144,11 @@ if (needsInstall(bunInstallRoot, target, version, pkgName)) {
144
144
  const install = Bun.spawnSync(installArgs, {
145
145
  stdio: ["inherit", "inherit", "inherit"],
146
146
  });
147
- if (install.exitCode !== 0) process.exit(install.exitCode ?? 1);
147
+
148
+ // Don't check exit code so even if
149
+ // bun install killed by SIGSYS @issue #39060
150
+ // bunx still runs with actually existing bin
151
+ //if (install.exitCode !== 0) process.exit(install.exitCode ?? 1);
148
152
  }
149
153
 
150
154
  if (!fs.existsSync(target)) {
@@ -800,6 +800,28 @@ function basicAuthAuthorized(req, credential) {
800
800
  return payload === credential;
801
801
  }
802
802
 
803
+ // Match gorilla/websocket's default policy used by upstream GoTTY: non-browser
804
+ // clients without Origin are accepted, while browser connections must come
805
+ // from the same host. --ws-origin intentionally replaces this default check.
806
+ function webSocketOriginAllowed(req, originMatcher = null) {
807
+ const origin = req.headers.origin;
808
+
809
+ if (originMatcher) {
810
+ return originMatcher.test(origin || "");
811
+ }
812
+ if (!origin) {
813
+ return true;
814
+ }
815
+
816
+ try {
817
+ const originHost = new URL(origin).host;
818
+ const requestHost = req.headers.host || "";
819
+ return originHost !== "" && originHost.toLowerCase() === requestHost.toLowerCase();
820
+ } catch {
821
+ return false;
822
+ }
823
+ }
824
+
803
825
  function sendBasicAuthRequired(res) {
804
826
  res.writeHead(401, {
805
827
  "WWW-Authenticate": 'Basic realm="GoTTY"',
@@ -2940,12 +2962,9 @@ function createServerRuntime(command, argv, options) {
2940
2962
  return;
2941
2963
  }
2942
2964
 
2943
- if (originMatcher) {
2944
- const origin = req.headers.origin || "";
2945
- if (!originMatcher.test(origin)) {
2946
- socket.destroy();
2947
- return;
2948
- }
2965
+ if (!webSocketOriginAllowed(req, originMatcher)) {
2966
+ socket.destroy();
2967
+ return;
2949
2968
  }
2950
2969
 
2951
2970
  if (options.once && acceptedOnce && !options.reconnect) {
@@ -3295,6 +3314,7 @@ async function bootstrap() {
3295
3314
 
3296
3315
  module.exports.CursorStateTracker = CursorStateTracker;
3297
3316
  module.exports.KittyGraphicsParser = KittyGraphicsParser;
3317
+ module.exports.webSocketOriginAllowed = webSocketOriginAllowed;
3298
3318
 
3299
3319
  if (require.main === module) {
3300
3320
  if (KITTY_TRACE) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.5",
3
+ "version": "1.1.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jsgotty",
9
- "version": "1.1.5",
9
+ "version": "1.1.11",
10
10
  "dependencies": {
11
11
  "node-addon-api": "^7.1.1",
12
12
  "ws": "^8.18.3"
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Share your terminal as a web application: A JavaScript rewrite of GoTTY",
5
+ "license": "MIT",
5
6
  "bin": {
6
7
  "jsgotty": "./gotty.js"
7
8
  },
@@ -0,0 +1,54 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+ const { webSocketOriginAllowed } = require("../gotty.js");
4
+
5
+ function request(host, origin) {
6
+ return {
7
+ headers: {
8
+ host,
9
+ ...(origin === undefined ? {} : { origin }),
10
+ },
11
+ };
12
+ }
13
+
14
+ test("WebSocket permits clients without Origin", () => {
15
+ assert.equal(webSocketOriginAllowed(request("127.0.0.1:8080")), true);
16
+ });
17
+
18
+ test("WebSocket permits same-origin browsers", () => {
19
+ assert.equal(
20
+ webSocketOriginAllowed(request("127.0.0.1:8080", "http://127.0.0.1:8080")),
21
+ true,
22
+ );
23
+ assert.equal(
24
+ webSocketOriginAllowed(request("Example.COM:8080", "https://example.com:8080")),
25
+ true,
26
+ );
27
+ });
28
+
29
+ test("WebSocket rejects cross-origin and malformed browser origins", () => {
30
+ assert.equal(
31
+ webSocketOriginAllowed(request("127.0.0.1:8080", "https://attacker.example")),
32
+ false,
33
+ );
34
+ assert.equal(webSocketOriginAllowed(request("127.0.0.1:8080", "null")), false);
35
+ assert.equal(webSocketOriginAllowed(request("127.0.0.1:8080", "not a URL")), false);
36
+ });
37
+
38
+ test("--ws-origin regex replaces the same-origin default", () => {
39
+ const matcher = /^https:\/\/trusted\.example$/;
40
+ assert.equal(
41
+ webSocketOriginAllowed(
42
+ request("127.0.0.1:8080", "https://trusted.example"),
43
+ matcher,
44
+ ),
45
+ true,
46
+ );
47
+ assert.equal(
48
+ webSocketOriginAllowed(
49
+ request("127.0.0.1:8080", "http://127.0.0.1:8080"),
50
+ matcher,
51
+ ),
52
+ false,
53
+ );
54
+ });
@@ -867,6 +867,20 @@ Ctrl-E cdp 9000 --public
867
867
  The default bind address is `127.0.0.1` and the default port is `9222`.
868
868
  Use `--public` or `--address=0.0.0.0` only on a trusted network.
869
869
 
870
+ Like Chromium, jsmdcui rejects WebSocket connections carrying an `Origin`
871
+ header by default. This prevents ordinary web pages from controlling the TUI.
872
+ Non-browser automation clients, which omit `Origin`, continue to work. To
873
+ explicitly allow a trusted browser origin, use a comma-separated allowlist:
874
+
875
+ ```sh
876
+ bun src/index.js --remote-debugging-port=9222 \
877
+ --remote-allow-origins=http://localhost:8000 demos/maze.md
878
+ ```
879
+
880
+ `--remote-allow-origins=*` disables this protection and should only be used in
881
+ a trusted environment. CDP can evaluate JavaScript and has the same authority
882
+ as the jsmdcui process.
883
+
870
884
  Once CDP is running, control the TUI with `Bun.WebView`. For more info, enter
871
885
  jsmdcui and use `Ctrl-E` or `€` → `help cdp`.
872
886
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsmdcui",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Markdown as a Common UI for Terminals and Web Browsers",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -18,6 +18,12 @@
18
18
  // From the command line (Chrome-style):
19
19
  // bunmicro --remote-debugging-port=9222 file.txt
20
20
  // bunmicro --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 file.txt
21
+ // bunmicro --remote-debugging-port=9222 --remote-allow-origins=http://localhost:8000 file.txt
22
+ //
23
+ // Browser WebSocket origins are rejected by default, as in Chromium.
24
+ // --remote-allow-origins accepts a comma-separated allowlist; `*` allows all
25
+ // origins and disables this protection. Non-browser clients omit Origin and
26
+ // continue to work without this flag.
21
27
  //
22
28
  // Once running, status bar shows: CDP@<port> server running
23
29
  // Running cdp again shows: CDP@<port> already running
@@ -867,6 +867,20 @@ Ctrl-E cdp 9000 --public
867
867
  The default bind address is `127.0.0.1` and the default port is `9222`.
868
868
  Use `--public` or `--address=0.0.0.0` only on a trusted network.
869
869
 
870
+ Like Chromium, jsmdcui rejects WebSocket connections carrying an `Origin`
871
+ header by default. This prevents ordinary web pages from controlling the TUI.
872
+ Non-browser automation clients, which omit `Origin`, continue to work. To
873
+ explicitly allow a trusted browser origin, use a comma-separated allowlist:
874
+
875
+ ```sh
876
+ bun src/index.js --remote-debugging-port=9222 \
877
+ --remote-allow-origins=http://localhost:8000 demos/maze.md
878
+ ```
879
+
880
+ `--remote-allow-origins=*` disables this protection and should only be used in
881
+ a trusted environment. CDP can evaluate JavaScript and has the same authority
882
+ as the jsmdcui process.
883
+
870
884
  Once CDP is running, control the TUI with `Bun.WebView`. For more info, enter
871
885
  jsmdcui and use `Ctrl-E` or `€` → `help cdp`.
872
886
 
@@ -1,13 +1,22 @@
1
1
  const CDP_DEFAULT_PORT = 9222;
2
2
  const CDP_PROTOCOL_VERSION = "1.3";
3
3
  const CDP_BROWSER_ID = "cdp-server";
4
+ const CDP_BROWSER_PATH = `/devtools/browser/${CDP_BROWSER_ID}`;
4
5
 
5
6
  let serverLogEnabled = 0//true;
6
7
  function serverLog(...args) {
7
- if (serverLogEnabled) console.log(...args);
8
+ if ( process.env.JSMDCUI_CDP_DEBUG ||
9
+ serverLogEnabled )
10
+ {
11
+ console.log(...args);
12
+ }
8
13
  }
9
14
  function serverError(...args) {
10
- if (serverLogEnabled) console.error(...args);
15
+ if ( process.env.JSMDCUI_CDP_DEBUG ||
16
+ serverLogEnabled )
17
+ {
18
+ console.error(...args);
19
+ }
11
20
  }
12
21
 
13
22
  export class CdpServer {
@@ -21,9 +30,10 @@ export class CdpServer {
21
30
  return new CdpServer(context);
22
31
  }
23
32
 
24
- listen(port = CDP_DEFAULT_PORT, hostname = "127.0.0.1") {
33
+ listen(port = CDP_DEFAULT_PORT, hostname = "127.0.0.1", options = {}) {
25
34
  const context = this.#context;
26
35
  const state = createCdpState();
36
+ const remoteAllowOrigins = parseAllowedOrigins(options.remoteAllowOrigins);
27
37
 
28
38
  const server = Bun.serve({
29
39
  port,
@@ -31,25 +41,65 @@ export class CdpServer {
31
41
  fetch(req, server) {
32
42
  const url = new URL(req.url);
33
43
  const pathname = url.pathname.replace(/\/+$/, "") || "/";
44
+
45
+ const origin = req.headers.get("origin");
46
+ const respond = async (response) => {
47
+ const text = await response.clone().text();
48
+ serverLog(
49
+ `[CdpServer] HTTP ${req.method} ${pathname} -> ${response.status}`,
50
+ text,
51
+ );
52
+ return response;
53
+ };
54
+
34
55
  serverLog(`[CdpServer] HTTP ${req.method} ${pathname}`);
35
- if (server.upgrade(req)) return;
56
+
57
+ serverLog(`[CdpServer] Origin ${origin}`);
58
+
59
+ if (!isSafeHostHeader(req.headers.get("host"))) {
60
+ return respond(new Response("Host header must be an IP address or localhost", {
61
+ status: 500,
62
+ }));
63
+ }
64
+
65
+ if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
66
+ if (!isOriginAllowed(origin, remoteAllowOrigins)) {
67
+ return respond(new Response(
68
+ `Rejected an incoming WebSocket connection from the ${origin} origin. `
69
+ + `Use --remote-allow-origins=${origin} to allow it, or `
70
+ + "--remote-allow-origins=* to allow all origins.",
71
+ { status: 403 },
72
+ ));
73
+ }
74
+ if (pathname !== CDP_BROWSER_PATH) {
75
+ return respond(new Response("Not Found", { status: 404 }));
76
+ }
77
+ if (server.upgrade(req)) {
78
+ serverLog(
79
+ `[CdpServer] HTTP ${req.method} ${pathname} -> 101`,
80
+ "Switching Protocols",
81
+ );
82
+ return;
83
+ }
84
+ return respond(new Response("WebSocket upgrade failed", { status: 500 }));
85
+ }
36
86
 
37
87
  const host = req.headers.get("host") ?? `127.0.0.1:${port}`;
38
88
  const webSocketDebuggerUrl = `ws://${host}/devtools/browser/${CDP_BROWSER_ID}`;
39
89
 
40
90
  if (pathname === "/json/version") {
41
- return jsonResponse({
91
+ return respond(jsonResponse({
42
92
  Browser: "CdpServer/1.0",
43
93
  "Protocol-Version": CDP_PROTOCOL_VERSION,
44
94
  "User-Agent": "CdpServer/1.0",
45
95
  "V8-Version": Bun.version,
46
96
  "WebKit-Version": "537.36",
47
97
  webSocketDebuggerUrl,
48
- });
98
+ }));
49
99
  }
50
100
 
51
101
  if (pathname === "/json" || pathname === "/json/list") {
52
- return jsonResponse(
102
+ return respond(jsonResponse(
53
103
  [...state.targets.values()].map((target) => ({
54
104
  id: target.targetId,
55
105
  type: target.type,
@@ -57,14 +107,14 @@ export class CdpServer {
57
107
  url: target.url,
58
108
  webSocketDebuggerUrl,
59
109
  }))
60
- );
110
+ ));
61
111
  }
62
112
 
63
- return jsonResponse({
113
+ return respond(jsonResponse({
64
114
  server: "CdpServer",
65
115
  port,
66
116
  webSocketDebuggerUrl,
67
- });
117
+ }));
68
118
  },
69
119
  websocket: {
70
120
  open(ws) {
@@ -73,6 +123,11 @@ export class CdpServer {
73
123
  async message(ws, raw) {
74
124
  let id;
75
125
  let sessionId;
126
+ const send = (message) => {
127
+ const text = JSON.stringify(message);
128
+ serverLog("[CdpServer] WebSocket response", text);
129
+ ws.send(text);
130
+ };
76
131
  try {
77
132
  const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
78
133
  const msg = JSON.parse(text);
@@ -82,13 +137,12 @@ export class CdpServer {
82
137
  serverLog(`[CdpServer] -> ${method}`, params ?? "");
83
138
 
84
139
  const emit = (eventMethod, eventParams) => {
85
- ws.send(
86
- JSON.stringify({
87
- method: eventMethod,
88
- params: eventParams,
89
- ...(sessionId ? { sessionId } : {}),
90
- })
91
- );
140
+ serverLog(`[CdpServer] <- ${eventMethod} EVENT`, eventParams ?? {});
141
+ send({
142
+ method: eventMethod,
143
+ params: eventParams,
144
+ ...(sessionId ? { sessionId } : {}),
145
+ });
92
146
  };
93
147
  const result = await dispatch(
94
148
  context,
@@ -99,25 +153,22 @@ export class CdpServer {
99
153
  emit
100
154
  );
101
155
  serverLog(`[CdpServer] <- ${method} OK`, result ?? "");
102
- ws.send(
103
- JSON.stringify({
104
- id,
105
- result: result ?? {},
106
- ...(sessionId ? { sessionId } : {}),
107
- })
108
- );
156
+ send({
157
+ id,
158
+ result: result ?? {},
159
+ ...(sessionId ? { sessionId } : {}),
160
+ });
109
161
  } catch (err) {
110
162
  serverError(`[CdpServer] <- ERROR ${err.message}`);
111
- ws.send(
112
- JSON.stringify({
113
- id,
114
- error: {
115
- code: -32601,
116
- message: err.message,
117
- },
118
- ...(sessionId ? { sessionId } : {}),
119
- })
120
- );
163
+ serverLog("[CdpServer] <- ERROR", { id, sessionId, message: err.message });
164
+ send({
165
+ id,
166
+ error: {
167
+ code: -32601,
168
+ message: err.message,
169
+ },
170
+ ...(sessionId ? { sessionId } : {}),
171
+ });
121
172
  }
122
173
  },
123
174
  close(ws) {
@@ -131,6 +182,33 @@ export class CdpServer {
131
182
  }
132
183
  }
133
184
 
185
+ export function parseAllowedOrigins(value) {
186
+ const origins = Array.isArray(value) ? value : String(value ?? "").split(",");
187
+ return new Set(origins.map((origin) => origin.trim().toLowerCase()).filter(Boolean));
188
+ }
189
+
190
+ export function isOriginAllowed(origin, allowedOrigins = new Set()) {
191
+ if (origin == null) return true;
192
+ const normalized = origin.toLowerCase();
193
+ return allowedOrigins.has("*") || allowedOrigins.has(normalized);
194
+ }
195
+
196
+ export function isSafeHostHeader(header) {
197
+ if (!header) return true;
198
+ try {
199
+ let hostname = new URL(`http://${header}`).hostname.toLowerCase();
200
+ if (hostname.startsWith("[") && hostname.endsWith("]")) {
201
+ hostname = hostname.slice(1, -1);
202
+ }
203
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
204
+ if (hostname.includes(":")) return /^[0-9a-f:.]+$/i.test(hostname);
205
+ return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname)
206
+ && hostname.split(".").every((part) => Number(part) <= 255);
207
+ } catch {
208
+ return false;
209
+ }
210
+ }
211
+
134
212
  function jsonResponse(value, status = 200) {
135
213
  return new Response(JSON.stringify(value), {
136
214
  status,
@@ -65,6 +65,8 @@ micro.on("init", () => {
65
65
  micro.MakeCommand("cdp", async (bp, args) =>
66
66
  {
67
67
  const addrFlag = args.find(a => a.startsWith("--address="))?.slice("--address=".length);
68
+ const allowOrigins = args.find(a => a.startsWith("--remote-allow-origins="))
69
+ ?.slice("--remote-allow-origins=".length);
68
70
  const isPublic = args.includes("--public");
69
71
  const port = parseInt(args.find(a => /^\d+$/.test(a))) || parseInt(Bun.env.CDP_PORT) || 9222;
70
72
  const hostname = addrFlag ?? (isPublic ? "0.0.0.0" : "127.0.0.1");
@@ -123,7 +125,7 @@ micro.on("init", () => {
123
125
  micro.cdpPort = port;
124
126
  CdpServer
125
127
  .create(micro.cdpContext)
126
- .listen(port, hostname);
128
+ .listen(port, hostname, { remoteAllowOrigins: allowOrigins });
127
129
 
128
130
  const addr = isPublic ? `0.0.0.0:${port}` : `127.0.0.1:${port}`;
129
131
  micro.TermMessage(`CDP@${addr} server running 伺服器啟動了`)
@@ -1119,6 +1119,7 @@ function parseArgs(argv) {
1119
1119
  cdpMaze: false,
1120
1120
  cdpPort: 0,
1121
1121
  cdpAddress: "",
1122
+ cdpAllowOrigins: "",
1122
1123
  kittyMode: kittyModeFromEnvironment(process.env.JSMDCUI_KITTY_MODE),
1123
1124
  settings: new Map(),
1124
1125
  };
@@ -1197,6 +1198,10 @@ function parseArgs(argv) {
1197
1198
  flags.cdpAddress = arg.slice("--remote-debugging-address=".length);
1198
1199
  } else if (arg === "--remote-debugging-address") {
1199
1200
  flags.cdpAddress = argv[++i] ?? "";
1201
+ } else if (arg.startsWith("--remote-allow-origins=")) {
1202
+ flags.cdpAllowOrigins = arg.slice("--remote-allow-origins=".length);
1203
+ } else if (arg === "--remote-allow-origins") {
1204
+ flags.cdpAllowOrigins = argv[++i] ?? "";
1200
1205
  } else if (arg.startsWith("-") && arg.length > 1 && i + 1 < argv.length) {
1201
1206
  flags.settings.set(arg.slice(1), argv[++i]);
1202
1207
  } else {
@@ -1306,6 +1311,8 @@ CDP(Chrome DevTools Protocol):
1306
1311
  Start CDP (Chrome DevTools Protocol) server on PORT at launch
1307
1312
  --remote-debugging-address=ADDRESS
1308
1313
  Bind CDP server to ADDRESS (default: 127.0.0.1); use 0.0.0.0 for all interfaces
1314
+ --remote-allow-origins=ORIGINS
1315
+ Allow comma-separated browser WebSocket origins; no browser origins are allowed by default
1309
1316
 
1310
1317
  Information:
1311
1318
  --help, -h, -help
@@ -9072,6 +9079,9 @@ async function main() {
9072
9079
  if (flags.cdpPort) {
9073
9080
  const cdpArgs = [flags.cdpPort];
9074
9081
  if (flags.cdpAddress) cdpArgs.push(`--address=${flags.cdpAddress}`);
9082
+ if (flags.cdpAllowOrigins) {
9083
+ cdpArgs.push(`--remote-allow-origins=${flags.cdpAllowOrigins}`);
9084
+ }
9075
9085
  await app.handleCommand(`cdp ${cdpArgs.join(" ")}`);
9076
9086
  }
9077
9087
 
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ isOriginAllowed,
4
+ isSafeHostHeader,
5
+ parseAllowedOrigins,
6
+ } from "../runtime/jsplugins/cdp/cdp-server.js";
7
+
8
+ describe("Chromium-compatible CDP request checks", () => {
9
+ test("rejects browser origins by default but permits clients without Origin", () => {
10
+ expect(isOriginAllowed(null)).toBe(true);
11
+ expect(isOriginAllowed(undefined)).toBe(true);
12
+ expect(isOriginAllowed("http://localhost:8000")).toBe(false);
13
+ expect(isOriginAllowed("null")).toBe(false);
14
+ });
15
+
16
+ test("allows only explicitly configured origins or wildcard", () => {
17
+ const exact = parseAllowedOrigins("http://localhost:8000, HTTPS://EXAMPLE.COM");
18
+ expect(isOriginAllowed("http://localhost:8000", exact)).toBe(true);
19
+ expect(isOriginAllowed("https://example.com", exact)).toBe(true);
20
+ expect(isOriginAllowed("http://127.0.0.1:8000", exact)).toBe(false);
21
+ expect(isOriginAllowed("https://evil.example", parseAllowedOrigins("*"))).toBe(true);
22
+ });
23
+
24
+ test("accepts IP and localhost Host headers but rejects DNS names", () => {
25
+ expect(isSafeHostHeader("127.0.0.1:9222")).toBe(true);
26
+ expect(isSafeHostHeader("[::1]:9222")).toBe(true);
27
+ expect(isSafeHostHeader("localhost:9222")).toBe(true);
28
+ expect(isSafeHostHeader("devtools.localhost:9222")).toBe(true);
29
+ expect(isSafeHostHeader("attacker.example:9222")).toBe(false);
30
+ expect(isSafeHostHeader("127.0.0.999:9222")).toBe(false);
31
+ });
32
+ });
@@ -0,0 +1,46 @@
1
+ <!doctype html>
2
+ <meta charset="utf-8">
3
+ <title>jsmdcui CDP WebSocket test</title>
4
+
5
+ <button id="test">Try CDP connection</button>
6
+ <pre id="status">Ready.</pre>
7
+
8
+ <script>
9
+ const status = document.querySelector("#status");
10
+ const endpoint = "ws://127.0.0.1:9222/devtools/browser/cdp-server";
11
+
12
+ function log(message) {
13
+ status.textContent += `\n${message}`;
14
+ }
15
+
16
+ function testCdp() {
17
+ status.textContent = `Connecting to ${endpoint}`;
18
+ const socket = new WebSocket(endpoint);
19
+
20
+ socket.addEventListener("open", () => {
21
+ log("WebSocket connected. Sending Input.insertText...");
22
+
23
+ socket.send(JSON.stringify({
24
+ id: 1,
25
+ method: "Input.insertText",
26
+ params: { text: "hello from web page" },
27
+ }));
28
+ });
29
+
30
+ socket.addEventListener("message", (event) => {
31
+ log(`CDP response: ${event.data}`);
32
+ socket.close();
33
+ });
34
+
35
+ socket.addEventListener("error", () => {
36
+ log("WebSocket error or browser blocked the connection.");
37
+ });
38
+
39
+ socket.addEventListener("close", (event) => {
40
+ log(`WebSocket closed (code ${event.code}).`);
41
+ });
42
+ }
43
+
44
+ document.querySelector("#test").addEventListener("click", testCdp);
45
+ testCdp();
46
+ </script>
@@ -26,6 +26,7 @@ test("--help describes the non-overwriting demo behavior", () => {
26
26
  expect(output.match(/Open it in the TUI and write 5 generated files beside it/g)?.length).toBe(2);
27
27
  expect(output).not.toContain("Alias for --demo-image-processor");
28
28
  expect(output).toContain("--cdp-maze");
29
+ expect(output).toContain("--remote-allow-origins=ORIGINS");
29
30
  });
30
31
 
31
32
  test("--demo-list lists root and automatically discovered demos", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",
@@ -25,7 +25,8 @@
25
25
  },
26
26
  "exitAfterCmd": false,
27
27
  "backToConsole": true,
28
- "xdgDataHome": null
28
+ "xdgDataHome": null,
29
+ "hideExtraKeys": false
29
30
  },
30
31
  "scripts": {
31
32
  "start": "bun ./apps/jsgotty/gotty.js --reconnect -r -w --webgl -a 127.0.0.1",