chrome-relay 0.5.14 → 0.5.15

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/cli.js CHANGED
@@ -63,6 +63,86 @@ function optNumber(obj, key, tool) {
63
63
  rejectWrongType(obj, key, "a finite number", tool);
64
64
  return v;
65
65
  }
66
+ function optPositiveNumber(obj, key, tool) {
67
+ const v = optNumber(obj, key, tool);
68
+ if (v === void 0)
69
+ return void 0;
70
+ if (v <= 0) {
71
+ throw new RelayError({
72
+ code: "invalid_arguments",
73
+ message: `${tool ?? "<unknown tool>"}: \`${key}\` must be > 0 (got ${v}).`,
74
+ tool,
75
+ phase: "parse_arguments",
76
+ details: { field: key, expected: "> 0", received: v },
77
+ retryable: false
78
+ });
79
+ }
80
+ return v;
81
+ }
82
+ function optNonNegativeNumber(obj, key, tool) {
83
+ const v = optNumber(obj, key, tool);
84
+ if (v === void 0)
85
+ return void 0;
86
+ if (v < 0) {
87
+ throw new RelayError({
88
+ code: "invalid_arguments",
89
+ message: `${tool ?? "<unknown tool>"}: \`${key}\` must be >= 0 (got ${v}).`,
90
+ tool,
91
+ phase: "parse_arguments",
92
+ details: { field: key, expected: ">= 0", received: v },
93
+ retryable: false
94
+ });
95
+ }
96
+ return v;
97
+ }
98
+ function coerceTabId(raw, tool) {
99
+ if (typeof raw === "number") {
100
+ if (!Number.isFinite(raw)) {
101
+ throw new RelayError({
102
+ code: "invalid_arguments",
103
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a finite number.`,
104
+ tool,
105
+ phase: "parse_arguments",
106
+ details: { received: raw },
107
+ retryable: false
108
+ });
109
+ }
110
+ return raw;
111
+ }
112
+ if (typeof raw === "string") {
113
+ const trimmed = raw.trim();
114
+ if (!trimmed) {
115
+ throw new RelayError({
116
+ code: "invalid_arguments",
117
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a number; got a blank string.`,
118
+ tool,
119
+ phase: "parse_arguments",
120
+ details: { received: raw },
121
+ retryable: false
122
+ });
123
+ }
124
+ const n = Number(trimmed);
125
+ if (!Number.isFinite(n)) {
126
+ throw new RelayError({
127
+ code: "invalid_arguments",
128
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a numeric string.`,
129
+ tool,
130
+ phase: "parse_arguments",
131
+ details: { received: raw },
132
+ retryable: false
133
+ });
134
+ }
135
+ return n;
136
+ }
137
+ throw new RelayError({
138
+ code: "invalid_arguments",
139
+ message: `${tool}: invalid tabId ${JSON.stringify(raw)}. Expected a number or numeric string.`,
140
+ tool,
141
+ phase: "parse_arguments",
142
+ details: { received: raw },
143
+ retryable: false
144
+ });
145
+ }
66
146
  function optBool(obj, key, tool) {
67
147
  const v = obj[key];
68
148
  if (v === void 0 || v === null)
@@ -234,7 +314,7 @@ function parseChromeNetworkArgs(input) {
234
314
  const full = optBool(obj, "full");
235
315
  if (full !== void 0)
236
316
  out.full = full;
237
- const head = optNumber(obj, "head");
317
+ const head = optPositiveNumber(obj, "head", TOOL_NAMES.NETWORK);
238
318
  if (head !== void 0)
239
319
  out.head = head;
240
320
  return out;
@@ -273,10 +353,14 @@ var init_network = __esm({
273
353
  });
274
354
 
275
355
  // ../protocol/dist/args/simple.js
276
- function parseGetWindowsAndTabsArgs(_input) {
356
+ function parseGetWindowsAndTabsArgs(input) {
357
+ if (input !== void 0 && input !== null)
358
+ asObject(input, TOOL_NAMES.GET_WINDOWS_AND_TABS);
277
359
  return {};
278
360
  }
279
- function parseChromeSelfReloadArgs(_input) {
361
+ function parseChromeSelfReloadArgs(input) {
362
+ if (input !== void 0 && input !== null)
363
+ asObject(input, TOOL_NAMES.SELF_RELOAD);
280
364
  return {};
281
365
  }
282
366
  function parseChromeReadPageArgs(input) {
@@ -334,27 +418,25 @@ function parseChromeEvaluateArgs(input) {
334
418
  const obj = asObject(input, TOOL_NAMES.EVALUATE);
335
419
  const out = {
336
420
  code: requireString(obj, "code", TOOL_NAMES.EVALUATE),
337
- ...parseTargetArgs(obj)
421
+ ...parseTargetArgs(obj, TOOL_NAMES.EVALUATE)
338
422
  };
339
- const t = optNumber(obj, "timeoutMs");
423
+ const t = optPositiveNumber(obj, "timeoutMs", TOOL_NAMES.EVALUATE);
340
424
  if (t !== void 0)
341
425
  out.timeoutMs = t;
342
426
  return out;
343
427
  }
344
428
  function parseChromeSwitchTabArgs(input) {
345
429
  const obj = asObject(input, TOOL_NAMES.SWITCH_TAB);
346
- const tabId = Number(obj.tabId);
347
- if (!Number.isFinite(tabId)) {
430
+ if (obj.tabId === void 0 || obj.tabId === null) {
348
431
  throw new RelayError({
349
432
  code: "invalid_arguments",
350
433
  message: `${TOOL_NAMES.SWITCH_TAB} requires a numeric tabId.`,
351
434
  tool: TOOL_NAMES.SWITCH_TAB,
352
435
  phase: "parse_arguments",
353
- details: { received: obj.tabId },
354
436
  retryable: false
355
437
  });
356
438
  }
357
- return { tabId };
439
+ return { tabId: coerceTabId(obj.tabId, TOOL_NAMES.SWITCH_TAB) };
358
440
  }
359
441
  function parseChromeCloseTabsArgs(input) {
360
442
  const obj = asObject(input, TOOL_NAMES.CLOSE_TABS);
@@ -368,18 +450,17 @@ function parseChromeCloseTabsArgs(input) {
368
450
  retryable: false
369
451
  });
370
452
  }
371
- const tabIds = obj.tabIds.map((v) => Number(v));
372
- if (tabIds.length === 0 || tabIds.some((n) => !Number.isFinite(n))) {
453
+ if (obj.tabIds.length === 0) {
373
454
  throw new RelayError({
374
455
  code: "invalid_arguments",
375
- message: `${TOOL_NAMES.CLOSE_TABS} requires a non-empty array of numeric tab IDs.`,
456
+ message: `${TOOL_NAMES.CLOSE_TABS} requires a non-empty array of tab IDs.`,
376
457
  tool: TOOL_NAMES.CLOSE_TABS,
377
458
  phase: "parse_arguments",
378
459
  details: { received: obj.tabIds },
379
460
  retryable: false
380
461
  });
381
462
  }
382
- return { tabIds };
463
+ return { tabIds: obj.tabIds.map((v) => coerceTabId(v, TOOL_NAMES.CLOSE_TABS)) };
383
464
  }
384
465
  function parseChromeAxArgs(input) {
385
466
  const obj = asObject(input, TOOL_NAMES.AX);
@@ -412,21 +493,21 @@ function parseChromeClickAxArgs(input) {
412
493
  }
413
494
  function parseChromeScreenshotArgs(input) {
414
495
  const obj = asObject(input, TOOL_NAMES.SCREENSHOT);
415
- const out = { ...parseTargetArgs(obj) };
416
- const fp = optBool(obj, "fullPage");
496
+ const out = { ...parseTargetArgs(obj, TOOL_NAMES.SCREENSHOT) };
497
+ const fp = optBool(obj, "fullPage", TOOL_NAMES.SCREENSHOT);
417
498
  if (fp !== void 0)
418
499
  out.fullPage = fp;
419
- const bbox = optString(obj, "bbox");
500
+ const bbox = optString(obj, "bbox", TOOL_NAMES.SCREENSHOT);
420
501
  if (bbox)
421
502
  out.bbox = bbox;
422
- const sel = optString(obj, "selector");
503
+ const sel = optString(obj, "selector", TOOL_NAMES.SCREENSHOT);
423
504
  if (sel)
424
505
  out.selector = sel;
425
- const pad = optNumber(obj, "padding");
506
+ const pad = optNonNegativeNumber(obj, "padding", TOOL_NAMES.SCREENSHOT);
426
507
  if (pad !== void 0)
427
508
  out.padding = pad;
428
- const me = optNumber(obj, "maxEdge");
429
- if (me !== void 0 && me > 0)
509
+ const me = optPositiveNumber(obj, "maxEdge", TOOL_NAMES.SCREENSHOT);
510
+ if (me !== void 0)
430
511
  out.maxEdge = me;
431
512
  return out;
432
513
  }
@@ -569,28 +650,12 @@ function parseChromeWorkspaceArgs(input) {
569
650
  return out;
570
651
  }
571
652
  function parseTabIds(raw) {
572
- const reject = (bad) => {
573
- throw new RelayError({
574
- code: "invalid_arguments",
575
- message: `${TOOL_NAMES.GROUP}: invalid tabId ${JSON.stringify(bad)}. Expected a number or a comma-separated list of numbers.`,
576
- tool: TOOL_NAMES.GROUP,
577
- phase: "parse_tab_ids",
578
- details: { received: bad },
579
- retryable: false
580
- });
581
- };
582
- const coerce = (v) => {
583
- const n = Number(typeof v === "string" ? v.trim() : v);
584
- if (!Number.isFinite(n))
585
- reject(v);
586
- return n;
587
- };
588
653
  if (Array.isArray(raw))
589
- return raw.map(coerce);
654
+ return raw.map((v) => coerceTabId(v, TOOL_NAMES.GROUP));
590
655
  if (typeof raw === "string")
591
- return raw.split(",").map(coerce);
656
+ return raw.split(",").map((s) => coerceTabId(s, TOOL_NAMES.GROUP));
592
657
  if (typeof raw === "number")
593
- return [raw];
658
+ return [coerceTabId(raw, TOOL_NAMES.GROUP)];
594
659
  return [];
595
660
  }
596
661
  function parseColor(raw) {
@@ -710,16 +775,27 @@ function parseChromeScreencastArgs(input) {
710
775
  }
711
776
  out.format = obj.format;
712
777
  }
713
- const q = optNumber(obj, "quality");
714
- if (q !== void 0)
778
+ const q = optNonNegativeNumber(obj, "quality", TOOL_NAMES.SCREENCAST);
779
+ if (q !== void 0) {
780
+ if (q > 100) {
781
+ throw new RelayError({
782
+ code: "invalid_arguments",
783
+ message: `${TOOL_NAMES.SCREENCAST}: quality must be 0-100 (got ${q}).`,
784
+ tool: TOOL_NAMES.SCREENCAST,
785
+ phase: "parse_arguments",
786
+ details: { field: "quality", received: q, range: [0, 100] },
787
+ retryable: false
788
+ });
789
+ }
715
790
  out.quality = q;
716
- const mw = optNumber(obj, "maxWidth");
791
+ }
792
+ const mw = optPositiveNumber(obj, "maxWidth", TOOL_NAMES.SCREENCAST);
717
793
  if (mw !== void 0)
718
794
  out.maxWidth = mw;
719
- const mh = optNumber(obj, "maxHeight");
795
+ const mh = optPositiveNumber(obj, "maxHeight", TOOL_NAMES.SCREENCAST);
720
796
  if (mh !== void 0)
721
797
  out.maxHeight = mh;
722
- const en = optNumber(obj, "everyNthFrame");
798
+ const en = optPositiveNumber(obj, "everyNthFrame", TOOL_NAMES.SCREENCAST);
723
799
  if (en !== void 0)
724
800
  out.everyNthFrame = en;
725
801
  return out;
@@ -754,21 +830,50 @@ var init_args = __esm({
754
830
  }
755
831
  });
756
832
 
833
+ // ../protocol/dist/limits.js
834
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS, DEFAULT_PING_TIMEOUT_MS, DEFAULT_READY_TIMEOUT_MS, DEFAULT_EVAL_TIMEOUT_MS, DEFAULT_BODY_PREVIEW_BYTES, NETWORK_BUFFER_MAX_ENTRIES, NETWORK_BUFFER_MAX_BYTES, CONSOLE_BUFFER_MAX_ENTRIES, CONSOLE_BUFFER_MAX_BYTES;
835
+ var init_limits = __esm({
836
+ "../protocol/dist/limits.js"() {
837
+ "use strict";
838
+ DEFAULT_TOOL_CALL_TIMEOUT_MS = 3e4;
839
+ DEFAULT_PING_TIMEOUT_MS = 2e3;
840
+ DEFAULT_READY_TIMEOUT_MS = 15e3;
841
+ DEFAULT_EVAL_TIMEOUT_MS = 15e3;
842
+ DEFAULT_BODY_PREVIEW_BYTES = 8 * 1024;
843
+ NETWORK_BUFFER_MAX_ENTRIES = 200;
844
+ NETWORK_BUFFER_MAX_BYTES = 512 * 1024;
845
+ CONSOLE_BUFFER_MAX_ENTRIES = 200;
846
+ CONSOLE_BUFFER_MAX_BYTES = 256 * 1024;
847
+ }
848
+ });
849
+
757
850
  // ../protocol/dist/index.js
758
851
  var dist_exports = {};
759
852
  __export(dist_exports, {
760
853
  CHROME_WEB_STORE_EXTENSION_ID: () => CHROME_WEB_STORE_EXTENSION_ID,
854
+ CONSOLE_BUFFER_MAX_BYTES: () => CONSOLE_BUFFER_MAX_BYTES,
855
+ CONSOLE_BUFFER_MAX_ENTRIES: () => CONSOLE_BUFFER_MAX_ENTRIES,
856
+ DEFAULT_BODY_PREVIEW_BYTES: () => DEFAULT_BODY_PREVIEW_BYTES,
857
+ DEFAULT_EVAL_TIMEOUT_MS: () => DEFAULT_EVAL_TIMEOUT_MS,
761
858
  DEFAULT_EXTENSION_ID: () => DEFAULT_EXTENSION_ID,
762
859
  DEFAULT_EXTENSION_IDS: () => DEFAULT_EXTENSION_IDS,
763
860
  DEFAULT_HTTP_PORT: () => DEFAULT_HTTP_PORT,
861
+ DEFAULT_PING_TIMEOUT_MS: () => DEFAULT_PING_TIMEOUT_MS,
862
+ DEFAULT_READY_TIMEOUT_MS: () => DEFAULT_READY_TIMEOUT_MS,
863
+ DEFAULT_TOOL_CALL_TIMEOUT_MS: () => DEFAULT_TOOL_CALL_TIMEOUT_MS,
764
864
  LEGACY_DEV_EXTENSION_ID: () => LEGACY_DEV_EXTENSION_ID,
765
865
  LOCAL_UNPACKED_EXTENSION_ID: () => LOCAL_UNPACKED_EXTENSION_ID,
766
866
  NATIVE_HOST_NAME: () => NATIVE_HOST_NAME,
867
+ NETWORK_BUFFER_MAX_BYTES: () => NETWORK_BUFFER_MAX_BYTES,
868
+ NETWORK_BUFFER_MAX_ENTRIES: () => NETWORK_BUFFER_MAX_ENTRIES,
767
869
  RelayError: () => RelayError,
768
870
  TOOL_NAMES: () => TOOL_NAMES,
769
871
  asObject: () => asObject,
872
+ coerceTabId: () => coerceTabId,
770
873
  optBool: () => optBool,
874
+ optNonNegativeNumber: () => optNonNegativeNumber,
771
875
  optNumber: () => optNumber,
876
+ optPositiveNumber: () => optPositiveNumber,
772
877
  optString: () => optString,
773
878
  parseChromeAxArgs: () => parseChromeAxArgs,
774
879
  parseChromeClickArgs: () => parseChromeClickArgs,
@@ -812,6 +917,7 @@ var init_dist = __esm({
812
917
  "../protocol/dist/index.js"() {
813
918
  "use strict";
814
919
  init_args();
920
+ init_limits();
815
921
  NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
816
922
  DEFAULT_HTTP_PORT = 12122;
817
923
  CHROME_WEB_STORE_EXTENSION_ID = "cpdiapbifblhlcpnmlmfpgfjlacebokb";
@@ -903,7 +1009,7 @@ var init_dist = __esm({
903
1009
  import { Command } from "commander";
904
1010
 
905
1011
  // src/index.ts
906
- var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
1012
+ var CHROME_RELAY_VERSION = true ? "0.5.15" : "0.0.0-dev";
907
1013
 
908
1014
  // src/commands/shared.ts
909
1015
  init_dist();
@@ -1142,6 +1248,14 @@ async function runDoctor() {
1142
1248
 
1143
1249
  // src/release-notes.ts
1144
1250
  var RELEASE_NOTES = {
1251
+ "0.5.15": [
1252
+ "Tab-id coercion is now strict against blank strings. `--tabs '1,,3'` (or `[\"\"]`, or `[' ']`) used to silently become `[1, 0, 3]` because Number('') === 0 \u2014 and tab 0 is a real Chrome target. Now throws RelayError(invalid_arguments). Affects chrome_group create/add/remove, chrome_switch_tab, chrome_close_tabs.",
1253
+ "Numeric range validation added across the parsers. `optPositiveNumber` (> 0) and `optNonNegativeNumber` (>= 0) helpers reject out-of-range values at the parser boundary instead of letting nonsense through to CDP / handler logic. Affects: chrome_evaluate timeoutMs, chrome_screenshot maxEdge + padding, chrome_screencast quality/maxWidth/maxHeight/everyNthFrame, chrome_network body head.",
1254
+ "chrome_screenshot maxEdge <= 0 now throws instead of being silently ignored (was the one remaining `if (x > 0)` silent-drop in the parsers).",
1255
+ "No-args parser comment + behavior aligned. parseGetWindowsAndTabsArgs / parseChromeSelfReloadArgs validate the input is at least an object (rejects strings/arrays) but accept any extra fields. Both handlers now call their parser at the top for consistency with every other tool.",
1256
+ "New @chrome-relay/protocol exports: shared numeric limits in `packages/protocol/src/limits.ts`. Constants (DEFAULT_TOOL_CALL_TIMEOUT_MS, DEFAULT_EVAL_TIMEOUT_MS, DEFAULT_BODY_PREVIEW_BYTES, NETWORK_BUFFER_MAX_ENTRIES/BYTES, CONSOLE_BUFFER_MAX_ENTRIES/BYTES, etc.) now flow from one source \u2014 bridge.ts, console-buffer.ts, network-buffer.ts, and the network/evaluate handlers all import. Future doc/help drift is structurally prevented.",
1257
+ "Tests: +9 in args-all.test.ts covering blank-string rejection, range validation across screenshot/evaluate/screencast/network. Total 416 (was 407)."
1258
+ ],
1145
1259
  "0.5.14": [
1146
1260
  "Optional parser fields are now strict \u2014 passing a present-but-wrong-type value (e.g. `{newTab: 'yes'}` instead of `true`) throws RelayError(invalid_arguments) instead of silently being dropped. undefined/null still means 'caller omitted the field' and the handler uses its default.",
1147
1261
  "parseTargetArgs is strict too: `{tabId: '5'}` rejects (numeric tabId required). Navigate is the one exception \u2014 it coerces string tabId for back-compat since it's used as a 'reference window' rather than a strict target.",
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.5.15" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -6,6 +6,14 @@ import process from "process";
6
6
  // src/http/server.ts
7
7
  import Fastify from "fastify";
8
8
 
9
+ // ../protocol/dist/limits.js
10
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS = 3e4;
11
+ var DEFAULT_PING_TIMEOUT_MS = 2e3;
12
+ var DEFAULT_READY_TIMEOUT_MS = 15e3;
13
+ var DEFAULT_BODY_PREVIEW_BYTES = 8 * 1024;
14
+ var NETWORK_BUFFER_MAX_BYTES = 512 * 1024;
15
+ var CONSOLE_BUFFER_MAX_BYTES = 256 * 1024;
16
+
9
17
  // ../protocol/dist/index.js
10
18
  var DEFAULT_HTTP_PORT = 12122;
11
19
  var RelayError = class extends Error {
@@ -48,7 +56,7 @@ function toBridgeError(unknownErr, fallbackTool) {
48
56
  }
49
57
 
50
58
  // src/index.ts
51
- var CHROME_RELAY_VERSION = true ? "0.5.14" : "0.0.0-dev";
59
+ var CHROME_RELAY_VERSION = true ? "0.5.15" : "0.0.0-dev";
52
60
 
53
61
  // src/release-notes.ts
54
62
  function compareSemver(a, b) {
@@ -197,7 +205,7 @@ var ExtensionBridge = class {
197
205
  pending.reject(new Error(message.payload.error));
198
206
  }
199
207
  }
200
- async waitUntilReady(timeoutMs = 15e3) {
208
+ async waitUntilReady(timeoutMs = DEFAULT_READY_TIMEOUT_MS) {
201
209
  if (this.ready) {
202
210
  return;
203
211
  }
@@ -213,7 +221,7 @@ var ExtensionBridge = class {
213
221
  this.readyWaiters.add(onReady);
214
222
  });
215
223
  }
216
- async ping(timeoutMs = 2e3) {
224
+ async ping(timeoutMs = DEFAULT_PING_TIMEOUT_MS) {
217
225
  const id = randomUUID();
218
226
  const message = { type: "bridge.ping", id };
219
227
  return new Promise((resolve) => {
@@ -229,7 +237,7 @@ var ExtensionBridge = class {
229
237
  this.send(message);
230
238
  });
231
239
  }
232
- async callTool(name, args, timeoutMs = 3e4) {
240
+ async callTool(name, args, timeoutMs = DEFAULT_TOOL_CALL_TIMEOUT_MS) {
233
241
  await this.waitUntilReady();
234
242
  const id = randomUUID();
235
243
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",