opendevbrowser 0.0.10 → 0.0.11

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.
@@ -2276,21 +2276,30 @@ function buildContinuityNudgeMessage(filePath) {
2276
2276
  import { createServer } from "http";
2277
2277
  import { timingSafeEqual } from "crypto";
2278
2278
  import { WebSocket, WebSocketServer } from "ws";
2279
+ var DEFAULT_DISCOVERY_PORT = 8787;
2280
+ var CONFIG_PATH = "/config";
2281
+ var PAIR_PATH = "/pair";
2279
2282
  var RelayServer = class _RelayServer {
2280
2283
  running = false;
2281
2284
  baseUrl = null;
2282
2285
  port = null;
2283
2286
  server = null;
2287
+ discoveryServer = null;
2284
2288
  extensionWss = null;
2285
2289
  cdpWss = null;
2286
2290
  extensionSocket = null;
2287
2291
  cdpSocket = null;
2288
2292
  extensionInfo = null;
2289
2293
  pairingToken = null;
2294
+ configuredDiscoveryPort;
2295
+ discoveryPort = null;
2290
2296
  handshakeAttempts = /* @__PURE__ */ new Map();
2291
2297
  cdpAllowlist = null;
2292
2298
  static MAX_HANDSHAKE_ATTEMPTS = 5;
2293
2299
  static RATE_LIMIT_WINDOW_MS = 6e4;
2300
+ constructor(options = {}) {
2301
+ this.configuredDiscoveryPort = options.discoveryPort ?? DEFAULT_DISCOVERY_PORT;
2302
+ }
2294
2303
  async start(port = 8787) {
2295
2304
  if (this.running && this.baseUrl && this.port !== null) {
2296
2305
  return { url: this.baseUrl, port: this.port };
@@ -2335,7 +2344,15 @@ var RelayServer = class _RelayServer {
2335
2344
  this.server.on("request", (request, response) => {
2336
2345
  const pathname = new URL(request.url ?? "", "http://127.0.0.1").pathname;
2337
2346
  const origin = request.headers.origin;
2338
- if (pathname === "/pair" && request.method === "OPTIONS") {
2347
+ if (pathname === CONFIG_PATH && request.method === "OPTIONS") {
2348
+ this.handleConfigPreflight(origin, response);
2349
+ return;
2350
+ }
2351
+ if (pathname === CONFIG_PATH && request.method === "GET") {
2352
+ this.handleConfigRequest(origin, response);
2353
+ return;
2354
+ }
2355
+ if (pathname === PAIR_PATH && request.method === "OPTIONS") {
2339
2356
  if (origin && origin.startsWith("chrome-extension://")) {
2340
2357
  response.setHeader("Access-Control-Allow-Origin", origin);
2341
2358
  response.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -2345,7 +2362,7 @@ var RelayServer = class _RelayServer {
2345
2362
  response.end();
2346
2363
  return;
2347
2364
  }
2348
- if (pathname === "/pair" && request.method === "GET") {
2365
+ if (pathname === PAIR_PATH && request.method === "GET") {
2349
2366
  const isLocalhost = !origin || origin.startsWith("chrome-extension://");
2350
2367
  if (!isLocalhost) {
2351
2368
  response.writeHead(403, { "Content-Type": "application/json" });
@@ -2405,6 +2422,13 @@ var RelayServer = class _RelayServer {
2405
2422
  this.port = address.port;
2406
2423
  this.baseUrl = `ws://127.0.0.1:${address.port}`;
2407
2424
  this.running = true;
2425
+ try {
2426
+ await this.startDiscoveryServer();
2427
+ } catch (error) {
2428
+ const message = error instanceof Error ? error.message : String(error);
2429
+ console.warn(`[opendevbrowser] Discovery server failed to start: ${message}`);
2430
+ this.stopDiscoveryServer();
2431
+ }
2408
2432
  return { url: this.baseUrl, port: address.port };
2409
2433
  }
2410
2434
  stop() {
@@ -2412,6 +2436,7 @@ var RelayServer = class _RelayServer {
2412
2436
  this.baseUrl = null;
2413
2437
  this.port = null;
2414
2438
  this.extensionInfo = null;
2439
+ this.stopDiscoveryServer();
2415
2440
  if (this.extensionSocket) {
2416
2441
  this.extensionSocket.close(1e3, "Relay stopped");
2417
2442
  this.extensionSocket = null;
@@ -2440,6 +2465,12 @@ var RelayServer = class _RelayServer {
2440
2465
  getCdpUrl() {
2441
2466
  return this.baseUrl ? `${this.baseUrl}/cdp` : null;
2442
2467
  }
2468
+ getDiscoveryPort() {
2469
+ if (this.port !== null && this.port === this.configuredDiscoveryPort) {
2470
+ return this.port;
2471
+ }
2472
+ return this.discoveryPort;
2473
+ }
2443
2474
  setToken(token) {
2444
2475
  const trimmed = typeof token === "string" ? token.trim() : "";
2445
2476
  this.pairingToken = trimmed.length ? trimmed : null;
@@ -2460,6 +2491,81 @@ var RelayServer = class _RelayServer {
2460
2491
  }
2461
2492
  return false;
2462
2493
  }
2494
+ isExtensionOrigin(origin) {
2495
+ return Boolean(origin && origin.startsWith("chrome-extension://"));
2496
+ }
2497
+ handleConfigPreflight(origin, response) {
2498
+ if (this.isExtensionOrigin(origin)) {
2499
+ response.setHeader("Access-Control-Allow-Origin", origin);
2500
+ response.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
2501
+ response.setHeader("Access-Control-Allow-Headers", "Content-Type");
2502
+ }
2503
+ response.writeHead(204);
2504
+ response.end();
2505
+ }
2506
+ handleConfigRequest(origin, response) {
2507
+ if (!this.isExtensionOrigin(origin)) {
2508
+ response.writeHead(403, { "Content-Type": "application/json" });
2509
+ response.end(JSON.stringify({ error: "Forbidden: extension origin required" }));
2510
+ return;
2511
+ }
2512
+ if (origin) {
2513
+ response.setHeader("Access-Control-Allow-Origin", origin);
2514
+ }
2515
+ if (this.port === null) {
2516
+ response.writeHead(503, { "Content-Type": "application/json" });
2517
+ response.end(JSON.stringify({ error: "Relay not running" }));
2518
+ return;
2519
+ }
2520
+ response.writeHead(200, {
2521
+ "Content-Type": "application/json",
2522
+ "Cache-Control": "no-store"
2523
+ });
2524
+ response.end(JSON.stringify({
2525
+ relayPort: this.port,
2526
+ pairingRequired: Boolean(this.pairingToken)
2527
+ }));
2528
+ }
2529
+ async startDiscoveryServer() {
2530
+ if (this.port === null || this.discoveryServer) {
2531
+ return;
2532
+ }
2533
+ if (this.configuredDiscoveryPort > 0 && this.configuredDiscoveryPort === this.port) {
2534
+ return;
2535
+ }
2536
+ this.discoveryServer = createServer((request, response) => {
2537
+ const pathname = new URL(request.url ?? "", "http://127.0.0.1").pathname;
2538
+ const origin = request.headers.origin;
2539
+ if (pathname === CONFIG_PATH && request.method === "OPTIONS") {
2540
+ this.handleConfigPreflight(origin, response);
2541
+ return;
2542
+ }
2543
+ if (pathname === CONFIG_PATH && request.method === "GET") {
2544
+ this.handleConfigRequest(origin, response);
2545
+ return;
2546
+ }
2547
+ response.writeHead(404);
2548
+ response.end();
2549
+ });
2550
+ await new Promise((resolve, reject) => {
2551
+ this.discoveryServer?.once("error", reject);
2552
+ this.discoveryServer?.listen(this.configuredDiscoveryPort, "127.0.0.1", () => {
2553
+ resolve();
2554
+ });
2555
+ });
2556
+ const address = this.discoveryServer.address();
2557
+ if (!address) {
2558
+ throw new Error("Discovery server did not expose a port");
2559
+ }
2560
+ this.discoveryPort = address.port;
2561
+ }
2562
+ stopDiscoveryServer() {
2563
+ if (this.discoveryServer) {
2564
+ this.discoveryServer.close();
2565
+ this.discoveryServer = null;
2566
+ }
2567
+ this.discoveryPort = null;
2568
+ }
2463
2569
  isRateLimited(ip) {
2464
2570
  const now = Date.now();
2465
2571
  const record = this.handshakeAttempts.get(ip);