@workerdeck/server 0.11.0 → 0.13.0

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/build/index.d.mts CHANGED
@@ -433,7 +433,27 @@ type WorkerServerOptions = {
433
433
  *
434
434
  * Upgrades are not routed here: anything outside `basePath` is still refused.
435
435
  */
436
- fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>; /** Max JSON body size in bytes. Default 1 MiB. */
436
+ fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
437
+ /**
438
+ * Browser origins allowed to call this API cross-origin — for a dashboard
439
+ * served somewhere other than this gateway.
440
+ *
441
+ * Off unless configured, and even then it is *sharing policy, not a
442
+ * credential*: preflights are answered before auth (browsers strip
443
+ * credentials from them, so they would otherwise 401), but every real request
444
+ * still goes through `authenticate`, and an allowlisted page that does not
445
+ * hold the key gets nothing.
446
+ *
447
+ * Two rules the implementation must keep: **exact origins only**, no
448
+ * wildcards or suffix matching; and `Access-Control-Allow-Credentials` is
449
+ * **never** sent, which is what keeps an ambient cookie from becoming
450
+ * cross-origin authority. WebSocket upgrades are exempt from CORS entirely
451
+ * and are unaffected by this — their credential is whatever the host's
452
+ * `authenticate` accepts on the handshake.
453
+ */
454
+ cors?: {
455
+ origins: string[];
456
+ }; /** Max JSON body size in bytes. Default 1 MiB. */
437
457
  maxBodyBytes?: number;
438
458
  /**
439
459
  * Server-wide bypass policy: refuse `permissionMode: 'bypassPermissions'` on
package/build/index.mjs CHANGED
@@ -1454,6 +1454,7 @@ function createWorkerServer(options = {}) {
1454
1454
  if (!options.authenticate && !options.allowUnauthenticated) throw new Error("createWorkerServer: provide `authenticate` or explicitly set `allowUnauthenticated: true`");
1455
1455
  const basePath = options.basePath ?? "/v1";
1456
1456
  const fallback = options.fallback;
1457
+ const corsOrigins = options.cors?.origins.length ? new Set(options.cors.origins) : void 0;
1457
1458
  const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024;
1458
1459
  /** The engine's adapter, honoring the test-only `engines` override. */
1459
1460
  const adapterFor = (engine) => options.engines?.[engine ?? "claude"] ?? getEngineAdapter(engine);
@@ -2545,6 +2546,26 @@ function createWorkerServer(options = {}) {
2545
2546
  };
2546
2547
  const handleRequest = async (req, res) => {
2547
2548
  const pathname = new URL(req.url ?? "/", "http://internal").pathname;
2549
+ const origin = req.headers.origin;
2550
+ const originAllowed = typeof origin === "string" && corsOrigins !== void 0 && corsOrigins.has(origin);
2551
+ if (originAllowed) {
2552
+ res.setHeader("access-control-allow-origin", origin);
2553
+ res.setHeader("vary", "origin");
2554
+ }
2555
+ if (req.method === "OPTIONS" && req.headers["access-control-request-method"] !== void 0) {
2556
+ if (!originAllowed) {
2557
+ res.writeHead(403);
2558
+ res.end();
2559
+ return;
2560
+ }
2561
+ res.setHeader("access-control-allow-methods", "GET, HEAD, POST, PATCH, PUT, DELETE");
2562
+ res.setHeader("access-control-allow-headers", "authorization, content-type, x-workerdeck-key");
2563
+ res.setHeader("access-control-max-age", "600");
2564
+ if (req.headers["access-control-request-private-network"] === "true") res.setHeader("access-control-allow-private-network", "true");
2565
+ res.writeHead(204);
2566
+ res.end();
2567
+ return;
2568
+ }
2548
2569
  if (fallback && pathname !== basePath && !pathname.startsWith(basePath + "/")) {
2549
2570
  await fallback(req, res);
2550
2571
  return;