infinitecampus-mcp 3.1.0 → 3.1.2
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +879 -280
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -25548,7 +25548,7 @@ var require_websocket = __commonJS({
|
|
|
25548
25548
|
var http = __require("http");
|
|
25549
25549
|
var net = __require("net");
|
|
25550
25550
|
var tls = __require("tls");
|
|
25551
|
-
var { randomBytes:
|
|
25551
|
+
var { randomBytes: randomBytes4, createHash } = __require("crypto");
|
|
25552
25552
|
var { Duplex, Readable } = __require("stream");
|
|
25553
25553
|
var { URL: URL2 } = __require("url");
|
|
25554
25554
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -26086,7 +26086,7 @@ var require_websocket = __commonJS({
|
|
|
26086
26086
|
}
|
|
26087
26087
|
}
|
|
26088
26088
|
const defaultPort = isSecure ? 443 : 80;
|
|
26089
|
-
const key =
|
|
26089
|
+
const key = randomBytes4(16).toString("base64");
|
|
26090
26090
|
const request = isSecure ? https.request : http.request;
|
|
26091
26091
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
26092
26092
|
let perMessageDeflate;
|
|
@@ -26983,6 +26983,20 @@ var require_websocket_server = __commonJS({
|
|
|
26983
26983
|
import { dirname as dirname3, join as join4 } from "path";
|
|
26984
26984
|
import { fileURLToPath } from "url";
|
|
26985
26985
|
|
|
26986
|
+
// node_modules/@chrischall/mcp-utils/dist/cancel/index.js
|
|
26987
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
26988
|
+
var storage = new AsyncLocalStorage();
|
|
26989
|
+
function withCallSignal(signal, fn, request) {
|
|
26990
|
+
return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
|
|
26991
|
+
}
|
|
26992
|
+
|
|
26993
|
+
// node_modules/@chrischall/mcp-utils/dist/caller/index.js
|
|
26994
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
26995
|
+
var storage2 = new AsyncLocalStorage2();
|
|
26996
|
+
function withCallerCapabilities(capabilities, fn) {
|
|
26997
|
+
return capabilities ? storage2.run(capabilities, fn) : fn();
|
|
26998
|
+
}
|
|
26999
|
+
|
|
26986
27000
|
// node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
26987
27001
|
var __create2 = Object.create;
|
|
26988
27002
|
var __defProp2 = Object.defineProperty;
|
|
@@ -41659,13 +41673,28 @@ var API_KEY_RE = new RegExp([
|
|
|
41659
41673
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
41660
41674
|
// webhook signing secret (Stripe-style)
|
|
41661
41675
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
41662
|
-
var QUERY_SECRET_RE = /([?&](?:
|
|
41676
|
+
var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
|
|
41677
|
+
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
41678
|
+
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
41679
|
+
var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
|
|
41663
41680
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
41664
|
-
var JSON_SECRET_KEYS =
|
|
41665
|
-
|
|
41666
|
-
|
|
41681
|
+
var JSON_SECRET_KEYS = [
|
|
41682
|
+
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
41683
|
+
"client[_-]?secret",
|
|
41684
|
+
"api[_-]?secret",
|
|
41685
|
+
"(?:x[_-])?api[_-]?key",
|
|
41686
|
+
"private[_-]?key",
|
|
41687
|
+
"x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
|
|
41688
|
+
"(?:proxy-)?authorization",
|
|
41689
|
+
"password",
|
|
41690
|
+
"passwd",
|
|
41691
|
+
"secret",
|
|
41692
|
+
"token"
|
|
41693
|
+
].join("|");
|
|
41694
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
41695
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
41667
41696
|
function redactSecrets(text) {
|
|
41668
|
-
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
41697
|
+
return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
|
|
41669
41698
|
}
|
|
41670
41699
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
41671
41700
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -61444,17 +61473,73 @@ Hint: ${err.hint}`);
|
|
|
61444
61473
|
}
|
|
61445
61474
|
throw err;
|
|
61446
61475
|
}
|
|
61476
|
+
function mcpRequestFrom(args) {
|
|
61477
|
+
const ctx = args.at(-1);
|
|
61478
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61479
|
+
return void 0;
|
|
61480
|
+
const req = ctx.mcpReq;
|
|
61481
|
+
if (typeof req !== "object" || req === null)
|
|
61482
|
+
return void 0;
|
|
61483
|
+
return typeof req.notify === "function" ? req : void 0;
|
|
61484
|
+
}
|
|
61485
|
+
function callSignalFrom(args) {
|
|
61486
|
+
const ctx = args.at(-1);
|
|
61487
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61488
|
+
return void 0;
|
|
61489
|
+
const req = ctx.mcpReq;
|
|
61490
|
+
if (typeof req !== "object" || req === null)
|
|
61491
|
+
return void 0;
|
|
61492
|
+
const signal = req.signal;
|
|
61493
|
+
return signal instanceof AbortSignal ? signal : void 0;
|
|
61494
|
+
}
|
|
61495
|
+
function declaredCapabilitiesFrom(server) {
|
|
61496
|
+
const inner = server.server;
|
|
61497
|
+
if (typeof inner !== "object" || inner === null)
|
|
61498
|
+
return void 0;
|
|
61499
|
+
const read = inner.getClientCapabilities;
|
|
61500
|
+
if (typeof read !== "function")
|
|
61501
|
+
return void 0;
|
|
61502
|
+
try {
|
|
61503
|
+
const declared = read.call(inner);
|
|
61504
|
+
return typeof declared === "object" && declared !== null ? declared : void 0;
|
|
61505
|
+
} catch {
|
|
61506
|
+
return void 0;
|
|
61507
|
+
}
|
|
61508
|
+
}
|
|
61447
61509
|
function surfaceToolHints(server) {
|
|
61448
61510
|
const register2 = server.registerTool.bind(server);
|
|
61449
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
61450
|
-
|
|
61451
|
-
|
|
61452
|
-
|
|
61453
|
-
|
|
61454
|
-
|
|
61455
|
-
|
|
61456
|
-
|
|
61457
|
-
|
|
61511
|
+
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
|
|
61512
|
+
// THE CALLER'S CANCELLATION, made ambient for the whole handler
|
|
61513
|
+
// (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
|
|
61514
|
+
// measured against @modelcontextprotocol/server 2.0.0, a cancelled
|
|
61515
|
+
// call aborts `ctx.mcpReq.signal` with the caller's reason and the
|
|
61516
|
+
// handler runs to completion regardless — so the HTTP request stays
|
|
61517
|
+
// in flight, the child keeps burning metered CPU, and the upstream
|
|
61518
|
+
// keeps being hit for somebody who has gone. claude.ai sent 101
|
|
61519
|
+
// cancellations in the week to 2026-09-20.
|
|
61520
|
+
//
|
|
61521
|
+
// Here because this is the one wrapper every tool already passes
|
|
61522
|
+
// through: threading the signal by hand would mean editing several
|
|
61523
|
+
// hundred handlers and missing exactly the ones nobody edits.
|
|
61524
|
+
withCallSignal(callSignalFrom(args), () => (
|
|
61525
|
+
// WHAT THE CALLER CAN DO, made ambient for the same reason and in
|
|
61526
|
+
// the same place (`caller/index.ts`). Only the 2025-era half needs
|
|
61527
|
+
// the wrapper — a 2026-07-28 request answers for itself off its own
|
|
61528
|
+
// envelope — but a guarded tool that cannot tell whether the caller
|
|
61529
|
+
// can be shown a prompt returns one the SDK then refuses to deliver,
|
|
61530
|
+
// and that refusal reaches the caller as an unexplained protocol
|
|
61531
|
+
// error it had no chance to catch.
|
|
61532
|
+
withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
|
|
61533
|
+
let result;
|
|
61534
|
+
try {
|
|
61535
|
+
result = cb(...args);
|
|
61536
|
+
} catch (err) {
|
|
61537
|
+
return hintResultOrRethrow(err);
|
|
61538
|
+
}
|
|
61539
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
61540
|
+
})
|
|
61541
|
+
), mcpRequestFrom(args))
|
|
61542
|
+
));
|
|
61458
61543
|
}
|
|
61459
61544
|
async function createMcpServer(opts) {
|
|
61460
61545
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -61469,13 +61554,47 @@ async function createMcpServer(opts) {
|
|
|
61469
61554
|
}
|
|
61470
61555
|
return server;
|
|
61471
61556
|
}
|
|
61557
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
61558
|
+
var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
|
|
61472
61559
|
function withGracefulShutdown(target, opts = {}) {
|
|
61473
61560
|
const shouldExit = opts.exit ?? true;
|
|
61561
|
+
if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
|
|
61562
|
+
throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
|
|
61563
|
+
}
|
|
61564
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
61565
|
+
if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
|
|
61566
|
+
throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
|
|
61567
|
+
}
|
|
61568
|
+
const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
|
|
61474
61569
|
let shuttingDown = false;
|
|
61570
|
+
let firstSignalAt = 0;
|
|
61571
|
+
let exited = false;
|
|
61572
|
+
const exitOnce = () => {
|
|
61573
|
+
if (exited)
|
|
61574
|
+
return;
|
|
61575
|
+
exited = true;
|
|
61576
|
+
process.exit(0);
|
|
61577
|
+
};
|
|
61475
61578
|
const handler = (signal) => {
|
|
61476
|
-
if (shuttingDown)
|
|
61579
|
+
if (shuttingDown) {
|
|
61580
|
+
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
61581
|
+
return;
|
|
61582
|
+
if (shouldExit) {
|
|
61583
|
+
console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
|
|
61584
|
+
exitOnce();
|
|
61585
|
+
}
|
|
61477
61586
|
return;
|
|
61587
|
+
}
|
|
61478
61588
|
shuttingDown = true;
|
|
61589
|
+
firstSignalAt = Date.now();
|
|
61590
|
+
let timer;
|
|
61591
|
+
if (shouldExit) {
|
|
61592
|
+
timer = setTimeout(() => {
|
|
61593
|
+
console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
|
|
61594
|
+
exitOnce();
|
|
61595
|
+
}, timeoutMs);
|
|
61596
|
+
timer.unref?.();
|
|
61597
|
+
}
|
|
61479
61598
|
void (async () => {
|
|
61480
61599
|
try {
|
|
61481
61600
|
if (opts.onSignal)
|
|
@@ -61484,8 +61603,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
61484
61603
|
} catch (err) {
|
|
61485
61604
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
61486
61605
|
} finally {
|
|
61606
|
+
if (timer !== void 0)
|
|
61607
|
+
clearTimeout(timer);
|
|
61487
61608
|
if (shouldExit)
|
|
61488
|
-
|
|
61609
|
+
exitOnce();
|
|
61489
61610
|
}
|
|
61490
61611
|
})();
|
|
61491
61612
|
};
|
|
@@ -61633,6 +61754,11 @@ var pageSchema = {
|
|
|
61633
61754
|
page_size: external_exports.number().int().min(1).max(200).default(50).describe("Number of items per page (1-200).")
|
|
61634
61755
|
};
|
|
61635
61756
|
|
|
61757
|
+
// node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
|
|
61758
|
+
function asciiLower(s) {
|
|
61759
|
+
return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
|
|
61760
|
+
}
|
|
61761
|
+
|
|
61636
61762
|
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
61637
61763
|
var subtle = globalThis.crypto.subtle;
|
|
61638
61764
|
async function exportRaw(key, format) {
|
|
@@ -61830,6 +61956,7 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
61830
61956
|
"capture_redirect",
|
|
61831
61957
|
"read_indexed_db",
|
|
61832
61958
|
"read_dom",
|
|
61959
|
+
"read_dom_list",
|
|
61833
61960
|
"download",
|
|
61834
61961
|
"graphql",
|
|
61835
61962
|
"write_cookies"
|
|
@@ -62330,6 +62457,79 @@ function assertDomSelectorsArray(value, label) {
|
|
|
62330
62457
|
}
|
|
62331
62458
|
}
|
|
62332
62459
|
}
|
|
62460
|
+
function assertDomListSelectorsArray(value, label) {
|
|
62461
|
+
if (!Array.isArray(value)) {
|
|
62462
|
+
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
62463
|
+
}
|
|
62464
|
+
const seen = /* @__PURE__ */ new Set();
|
|
62465
|
+
for (let i = 0; i < value.length; i++) {
|
|
62466
|
+
const entry = value[i];
|
|
62467
|
+
assertObject(entry, `${label}[${i}]`);
|
|
62468
|
+
if (entry.name === void 0) {
|
|
62469
|
+
throw new ProtocolError3(`${label}[${i}].name: missing`);
|
|
62470
|
+
}
|
|
62471
|
+
if (entry.itemSelector === void 0) {
|
|
62472
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: missing`);
|
|
62473
|
+
}
|
|
62474
|
+
if (entry.fields === void 0) {
|
|
62475
|
+
throw new ProtocolError3(`${label}[${i}].fields: missing`);
|
|
62476
|
+
}
|
|
62477
|
+
if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
|
|
62478
|
+
throw new ProtocolError3(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
|
|
62479
|
+
}
|
|
62480
|
+
if (typeof entry.itemSelector !== "string" || !DOM_SELECTOR_RE.test(entry.itemSelector)) {
|
|
62481
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: invalid ${JSON.stringify(entry.itemSelector)}`);
|
|
62482
|
+
}
|
|
62483
|
+
if (!Array.isArray(entry.fields) || entry.fields.length === 0) {
|
|
62484
|
+
throw new ProtocolError3(`${label}[${i}].fields: expected non-empty array`);
|
|
62485
|
+
}
|
|
62486
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
62487
|
+
for (let j = 0; j < entry.fields.length; j++) {
|
|
62488
|
+
const field = entry.fields[j];
|
|
62489
|
+
const fieldLabel = `${label}[${i}].fields[${j}]`;
|
|
62490
|
+
assertObject(field, fieldLabel);
|
|
62491
|
+
if (field.name === void 0) {
|
|
62492
|
+
throw new ProtocolError3(`${fieldLabel}.name: missing`);
|
|
62493
|
+
}
|
|
62494
|
+
if (typeof field.name !== "string" || !SCOPE_KEY_RE.test(field.name)) {
|
|
62495
|
+
throw new ProtocolError3(`${fieldLabel}.name: invalid ${JSON.stringify(field.name)}`);
|
|
62496
|
+
}
|
|
62497
|
+
if (field.selector !== void 0) {
|
|
62498
|
+
if (typeof field.selector !== "string" || !DOM_SELECTOR_RE.test(field.selector)) {
|
|
62499
|
+
throw new ProtocolError3(`${fieldLabel}.selector: invalid ${JSON.stringify(field.selector)}`);
|
|
62500
|
+
}
|
|
62501
|
+
}
|
|
62502
|
+
if (field.attribute !== void 0) {
|
|
62503
|
+
if (typeof field.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(field.attribute)) {
|
|
62504
|
+
throw new ProtocolError3(`${fieldLabel}.attribute: invalid ${JSON.stringify(field.attribute)}`);
|
|
62505
|
+
}
|
|
62506
|
+
}
|
|
62507
|
+
if (fieldNames.has(field.name)) {
|
|
62508
|
+
throw new ProtocolError3(`${label}[${i}].fields: duplicate name ${JSON.stringify(field.name)}`);
|
|
62509
|
+
}
|
|
62510
|
+
fieldNames.add(field.name);
|
|
62511
|
+
for (const k of Object.keys(field)) {
|
|
62512
|
+
if (k !== "name" && k !== "selector" && k !== "attribute") {
|
|
62513
|
+
throw new ProtocolError3(`${fieldLabel}: unexpected field ${JSON.stringify(k)}`);
|
|
62514
|
+
}
|
|
62515
|
+
}
|
|
62516
|
+
}
|
|
62517
|
+
if (entry.maxItems !== void 0) {
|
|
62518
|
+
if (typeof entry.maxItems !== "number" || !Number.isInteger(entry.maxItems) || entry.maxItems < 1 || entry.maxItems > 1e3) {
|
|
62519
|
+
throw new ProtocolError3(`${label}[${i}].maxItems: invalid ${JSON.stringify(entry.maxItems)}`);
|
|
62520
|
+
}
|
|
62521
|
+
}
|
|
62522
|
+
if (seen.has(entry.name)) {
|
|
62523
|
+
throw new ProtocolError3(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
|
|
62524
|
+
}
|
|
62525
|
+
seen.add(entry.name);
|
|
62526
|
+
for (const k of Object.keys(entry)) {
|
|
62527
|
+
if (k !== "name" && k !== "itemSelector" && k !== "fields" && k !== "maxItems") {
|
|
62528
|
+
throw new ProtocolError3(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
62529
|
+
}
|
|
62530
|
+
}
|
|
62531
|
+
}
|
|
62532
|
+
}
|
|
62333
62533
|
function assertGraphqlOpsArray(value, label) {
|
|
62334
62534
|
if (!Array.isArray(value)) {
|
|
62335
62535
|
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
@@ -62376,6 +62576,8 @@ function validateFrame(raw) {
|
|
|
62376
62576
|
return validateHelloRejected(raw);
|
|
62377
62577
|
if (t === "extension-disconnected")
|
|
62378
62578
|
return { type: "extension-disconnected" };
|
|
62579
|
+
if (t === "peer-gone")
|
|
62580
|
+
return validatePeerGone(raw);
|
|
62379
62581
|
throw new ProtocolError3(`unknown frame type: ${String(t)}`);
|
|
62380
62582
|
}
|
|
62381
62583
|
function validateHello(raw) {
|
|
@@ -62446,16 +62648,11 @@ function validateHello(raw) {
|
|
|
62446
62648
|
if (raw.domSelectors !== void 0) {
|
|
62447
62649
|
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
62448
62650
|
}
|
|
62449
|
-
if (raw.
|
|
62450
|
-
|
|
62451
|
-
throw new ProtocolError3("hello.accepts: expected array");
|
|
62452
|
-
}
|
|
62453
|
-
for (const a of raw.accepts) {
|
|
62454
|
-
if (typeof a !== "string") {
|
|
62455
|
-
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62456
|
-
}
|
|
62457
|
-
}
|
|
62651
|
+
if (raw.domListSelectors !== void 0) {
|
|
62652
|
+
assertDomListSelectorsArray(raw.domListSelectors, "hello.domListSelectors");
|
|
62458
62653
|
}
|
|
62654
|
+
if (raw.accepts !== void 0)
|
|
62655
|
+
assertAcceptsList(raw.accepts);
|
|
62459
62656
|
if (raw.graphqlOps !== void 0) {
|
|
62460
62657
|
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
62461
62658
|
}
|
|
@@ -62477,6 +62674,8 @@ function validateHello(raw) {
|
|
|
62477
62674
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
62478
62675
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
62479
62676
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
62677
|
+
if (raw.accepts !== void 0)
|
|
62678
|
+
assertAcceptsList(raw.accepts);
|
|
62480
62679
|
return raw;
|
|
62481
62680
|
}
|
|
62482
62681
|
throw new ProtocolError3(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
@@ -62532,6 +62731,27 @@ function validatePairPending(raw) {
|
|
|
62532
62731
|
}
|
|
62533
62732
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
62534
62733
|
}
|
|
62734
|
+
function assertAcceptsList(accepts) {
|
|
62735
|
+
if (!Array.isArray(accepts)) {
|
|
62736
|
+
throw new ProtocolError3("hello.accepts: expected array");
|
|
62737
|
+
}
|
|
62738
|
+
for (const a of accepts) {
|
|
62739
|
+
if (typeof a !== "string") {
|
|
62740
|
+
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62741
|
+
}
|
|
62742
|
+
}
|
|
62743
|
+
}
|
|
62744
|
+
function validatePeerGone(raw) {
|
|
62745
|
+
assertString(raw.mcpId, "peer-gone.mcpId");
|
|
62746
|
+
if (!isValidMcpId(raw.mcpId))
|
|
62747
|
+
throw new ProtocolError3("peer-gone.mcpId: invalid format");
|
|
62748
|
+
for (const k of Object.keys(raw)) {
|
|
62749
|
+
if (k !== "type" && k !== "mcpId") {
|
|
62750
|
+
throw new ProtocolError3(`peer-gone: unexpected field ${JSON.stringify(k)}`);
|
|
62751
|
+
}
|
|
62752
|
+
}
|
|
62753
|
+
return { type: "peer-gone", mcpId: raw.mcpId };
|
|
62754
|
+
}
|
|
62535
62755
|
function validateHelloRejected(raw) {
|
|
62536
62756
|
assertString(raw.mcpId, "hello-rejected.mcpId");
|
|
62537
62757
|
if (!isValidMcpId(raw.mcpId))
|
|
@@ -62781,6 +63001,24 @@ function validateInnerRequest(raw) {
|
|
|
62781
63001
|
}
|
|
62782
63002
|
return raw;
|
|
62783
63003
|
}
|
|
63004
|
+
if (raw.op === "read_dom_list") {
|
|
63005
|
+
assertObject(raw.init, "inner.init");
|
|
63006
|
+
if (raw.init.origin === void 0)
|
|
63007
|
+
throw new ProtocolError3("inner.init.origin: missing");
|
|
63008
|
+
if (raw.init.name === void 0)
|
|
63009
|
+
throw new ProtocolError3("inner.init.name: missing");
|
|
63010
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
63011
|
+
assertString(raw.init.name, "inner.init.name");
|
|
63012
|
+
if (raw.init.name.length === 0) {
|
|
63013
|
+
throw new ProtocolError3("inner.init.name: must be non-empty");
|
|
63014
|
+
}
|
|
63015
|
+
for (const k of Object.keys(raw.init)) {
|
|
63016
|
+
if (k !== "origin" && k !== "name") {
|
|
63017
|
+
throw new ProtocolError3(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom_list`);
|
|
63018
|
+
}
|
|
63019
|
+
}
|
|
63020
|
+
return raw;
|
|
63021
|
+
}
|
|
62784
63022
|
if (raw.op === "graphql_query") {
|
|
62785
63023
|
assertObject(raw.init, "inner.init");
|
|
62786
63024
|
if (raw.init.name === void 0)
|
|
@@ -62828,7 +63066,7 @@ function validateInnerRequest(raw) {
|
|
|
62828
63066
|
}
|
|
62829
63067
|
return raw;
|
|
62830
63068
|
}
|
|
62831
|
-
throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
63069
|
+
throw new ProtocolError3(`inner.op: must be one of "fetch", "read_cookies", "read_local_storage", "read_session_storage", "capture_request_header", "capture_redirect", "read_indexed_db", "read_dom", "read_dom_list", "download", "graphql_query", "write_cookies"; got ${JSON.stringify(raw.op)}`);
|
|
62832
63070
|
}
|
|
62833
63071
|
function assertNonEmptyKeyArray(value, label) {
|
|
62834
63072
|
if (!Array.isArray(value)) {
|
|
@@ -62936,6 +63174,18 @@ function validateInnerResponse(raw) {
|
|
|
62936
63174
|
assertStringMap(raw.values, "inner.values");
|
|
62937
63175
|
return raw;
|
|
62938
63176
|
}
|
|
63177
|
+
if (op === "read_dom_list") {
|
|
63178
|
+
if (raw.rows === void 0) {
|
|
63179
|
+
throw new ProtocolError3("inner.rows: missing on read_dom_list response");
|
|
63180
|
+
}
|
|
63181
|
+
if (!Array.isArray(raw.rows)) {
|
|
63182
|
+
throw new ProtocolError3("inner.rows: expected array, got " + typeof raw.rows);
|
|
63183
|
+
}
|
|
63184
|
+
for (const [i, row] of raw.rows.entries()) {
|
|
63185
|
+
assertStringMap(row, `inner.rows[${i}]`);
|
|
63186
|
+
}
|
|
63187
|
+
return raw;
|
|
63188
|
+
}
|
|
62939
63189
|
if (op === "graphql_query") {
|
|
62940
63190
|
if (raw.data === void 0) {
|
|
62941
63191
|
throw new ProtocolError3("inner.data: missing on graphql_query response");
|
|
@@ -63037,12 +63287,6 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
|
|
|
63037
63287
|
ciphertext: toB64(ct)
|
|
63038
63288
|
};
|
|
63039
63289
|
}
|
|
63040
|
-
async function openEncryptedFrame(sessionKey, frame, direction) {
|
|
63041
|
-
const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
|
|
63042
|
-
if (result.stage === "ok")
|
|
63043
|
-
return result.inner;
|
|
63044
|
-
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
63045
|
-
}
|
|
63046
63290
|
async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
63047
63291
|
let pt;
|
|
63048
63292
|
try {
|
|
@@ -63194,6 +63438,18 @@ async function buildServerHello(opts) {
|
|
|
63194
63438
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
63195
63439
|
}));
|
|
63196
63440
|
}
|
|
63441
|
+
if (opts.domListSelectors && opts.domListSelectors.length > 0) {
|
|
63442
|
+
hello.domListSelectors = opts.domListSelectors.map((d) => ({
|
|
63443
|
+
name: d.name,
|
|
63444
|
+
itemSelector: d.itemSelector,
|
|
63445
|
+
fields: d.fields.map((f) => ({
|
|
63446
|
+
name: f.name,
|
|
63447
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
63448
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
63449
|
+
})),
|
|
63450
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
63451
|
+
}));
|
|
63452
|
+
}
|
|
63197
63453
|
if (opts.graphqlOps && opts.graphqlOps.length > 0) {
|
|
63198
63454
|
hello.graphqlOps = opts.graphqlOps.map((d) => ({
|
|
63199
63455
|
name: d.name,
|
|
@@ -63362,11 +63618,12 @@ async function awaitSessionReady(ready, opts) {
|
|
|
63362
63618
|
}
|
|
63363
63619
|
|
|
63364
63620
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
63365
|
-
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
63621
|
+
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2, rm as rm2 } from "node:fs/promises";
|
|
63622
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
63366
63623
|
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
63367
63624
|
|
|
63368
63625
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
63369
|
-
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
63626
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink, link, lstat } from "node:fs/promises";
|
|
63370
63627
|
import { randomBytes } from "node:crypto";
|
|
63371
63628
|
import { isAbsolute, join } from "node:path";
|
|
63372
63629
|
import { homedir } from "node:os";
|
|
@@ -63434,7 +63691,7 @@ async function openIdentityDir(dir) {
|
|
|
63434
63691
|
}
|
|
63435
63692
|
}
|
|
63436
63693
|
var STAGING_BYTES = 6;
|
|
63437
|
-
async function sweepStagingFiles(dir, serverName) {
|
|
63694
|
+
async function sweepStagingFiles(dir, serverName, opts = {}) {
|
|
63438
63695
|
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
63439
63696
|
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
63440
63697
|
let names;
|
|
@@ -63443,35 +63700,80 @@ async function sweepStagingFiles(dir, serverName) {
|
|
|
63443
63700
|
} catch {
|
|
63444
63701
|
return;
|
|
63445
63702
|
}
|
|
63446
|
-
|
|
63703
|
+
const cutoff = opts.olderThanMs === void 0 ? null : Date.now() - opts.olderThanMs;
|
|
63704
|
+
await Promise.all(names.filter((name) => staging.test(name)).map(async (name) => {
|
|
63705
|
+
const path = join(dir, name);
|
|
63706
|
+
if (cutoff !== null) {
|
|
63707
|
+
const st = await lstat(path).catch(() => null);
|
|
63708
|
+
if (st === null || st.mtimeMs > cutoff)
|
|
63709
|
+
return;
|
|
63710
|
+
}
|
|
63711
|
+
await unlink(path).catch(() => void 0);
|
|
63712
|
+
}));
|
|
63447
63713
|
}
|
|
63448
|
-
async function
|
|
63714
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
63449
63715
|
const path = identityFilePath(dir, serverName);
|
|
63450
63716
|
await openIdentityDir(dir);
|
|
63451
|
-
await sweepStagingFiles(dir, serverName);
|
|
63452
|
-
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63453
63717
|
try {
|
|
63454
|
-
|
|
63455
|
-
await chmod(tmp, 384);
|
|
63456
|
-
await rename(tmp, path);
|
|
63718
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63457
63719
|
} catch (e) {
|
|
63458
|
-
|
|
63459
|
-
|
|
63720
|
+
if (e.code !== "ENOENT" && !(e instanceof SyntaxError))
|
|
63721
|
+
throw e;
|
|
63722
|
+
}
|
|
63723
|
+
let unreadable = null;
|
|
63724
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
63725
|
+
const id = await generateIdentity();
|
|
63726
|
+
if (await publishIdentityIfAbsent(dir, serverName, id))
|
|
63727
|
+
return id;
|
|
63728
|
+
try {
|
|
63729
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63730
|
+
} catch (e) {
|
|
63731
|
+
if (e.code === "ENOENT") {
|
|
63732
|
+
continue;
|
|
63733
|
+
}
|
|
63734
|
+
if (!(e instanceof SyntaxError))
|
|
63735
|
+
throw e;
|
|
63736
|
+
unreadable = e;
|
|
63737
|
+
await new Promise((r) => setTimeout(r, 20 * (attempt + 1)));
|
|
63738
|
+
}
|
|
63460
63739
|
}
|
|
63461
|
-
|
|
63740
|
+
if (unreadable !== null)
|
|
63741
|
+
throw unreadable;
|
|
63742
|
+
throw new Error(`fetchproxy: could not create an identity for ${JSON.stringify(serverName)} in ${dir}`);
|
|
63462
63743
|
}
|
|
63463
|
-
|
|
63744
|
+
var NO_HARD_LINKS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
|
|
63745
|
+
async function publishIdentityIfAbsent(dir, serverName, id) {
|
|
63464
63746
|
const path = identityFilePath(dir, serverName);
|
|
63465
|
-
await
|
|
63747
|
+
await sweepStagingFiles(dir, serverName, { olderThanMs: 6e4 });
|
|
63748
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63466
63749
|
try {
|
|
63467
|
-
|
|
63750
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63751
|
+
await chmod(tmp, 384);
|
|
63752
|
+
try {
|
|
63753
|
+
await link(tmp, path);
|
|
63754
|
+
} catch (e) {
|
|
63755
|
+
const code = e.code;
|
|
63756
|
+
if (code === void 0 || !NO_HARD_LINKS.has(code))
|
|
63757
|
+
throw e;
|
|
63758
|
+
try {
|
|
63759
|
+
await writeFile(path, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63760
|
+
} catch (writeErr) {
|
|
63761
|
+
if (writeErr.code !== "EEXIST") {
|
|
63762
|
+
await rm(path, { force: true }).catch(() => void 0);
|
|
63763
|
+
}
|
|
63764
|
+
throw writeErr;
|
|
63765
|
+
}
|
|
63766
|
+
await chmod(path, 384);
|
|
63767
|
+
}
|
|
63768
|
+
return true;
|
|
63468
63769
|
} catch (e) {
|
|
63469
|
-
|
|
63470
|
-
|
|
63770
|
+
const code = e.code;
|
|
63771
|
+
if (code === "EEXIST" || code === "ENOENT")
|
|
63772
|
+
return false;
|
|
63773
|
+
throw e;
|
|
63774
|
+
} finally {
|
|
63775
|
+
await rm(tmp, { force: true });
|
|
63471
63776
|
}
|
|
63472
|
-
const id = await generateIdentity();
|
|
63473
|
-
await writeIdentityFile(dir, serverName, id);
|
|
63474
|
-
return id;
|
|
63475
63777
|
}
|
|
63476
63778
|
|
|
63477
63779
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
@@ -63572,10 +63874,15 @@ async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
|
63572
63874
|
async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
63573
63875
|
const path = extensionTrustPath(serverName, dir);
|
|
63574
63876
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
63575
|
-
const tmp = `${path}.tmp`;
|
|
63576
|
-
|
|
63577
|
-
|
|
63578
|
-
|
|
63877
|
+
const tmp = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
63878
|
+
try {
|
|
63879
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384, flag: "wx" });
|
|
63880
|
+
await chmod2(tmp, 384);
|
|
63881
|
+
await rename2(tmp, path);
|
|
63882
|
+
} catch (e) {
|
|
63883
|
+
await rm2(tmp, { force: true });
|
|
63884
|
+
throw e;
|
|
63885
|
+
}
|
|
63579
63886
|
}
|
|
63580
63887
|
|
|
63581
63888
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
@@ -63612,6 +63919,7 @@ function originVerdict(origin, allowLocal) {
|
|
|
63612
63919
|
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
63613
63920
|
var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
|
|
63614
63921
|
var enc4 = new TextEncoder();
|
|
63922
|
+
var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
|
|
63615
63923
|
async function startHost(opts) {
|
|
63616
63924
|
const allowLocalOrigins = envAllowsLocalOrigins();
|
|
63617
63925
|
if (allowLocalOrigins) {
|
|
@@ -63655,6 +63963,7 @@ async function startHost(opts) {
|
|
|
63655
63963
|
localStoragePointers: opts.ownLocalStoragePointers,
|
|
63656
63964
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
63657
63965
|
domSelectors: opts.ownDomSelectors,
|
|
63966
|
+
domListSelectors: opts.ownDomListSelectors,
|
|
63658
63967
|
graphqlOps: opts.ownGraphqlOps
|
|
63659
63968
|
};
|
|
63660
63969
|
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
@@ -63725,6 +64034,31 @@ async function startHost(opts) {
|
|
|
63725
64034
|
return null;
|
|
63726
64035
|
}
|
|
63727
64036
|
};
|
|
64037
|
+
let ownDecryptFailures = 0;
|
|
64038
|
+
const rehandshakeOwnSession = async (ws) => {
|
|
64039
|
+
const hello = extensionHello;
|
|
64040
|
+
if (extensionWs !== ws || !hello)
|
|
64041
|
+
return;
|
|
64042
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: ${OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE} frames in a row from the extension failed authentication under this MCP's session key \u2014 re-handshaking this MCP's session (the extension stays connected).`);
|
|
64043
|
+
ownSession = dropOwnSessionAndEphemeral();
|
|
64044
|
+
resetSessionPromise();
|
|
64045
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
64046
|
+
const mintedHello = await buildServerHello({
|
|
64047
|
+
...ownHelloBase,
|
|
64048
|
+
sessionPub: mintedKeypair.publicKey,
|
|
64049
|
+
answersExtNonce: fromB64(hello.sessionNonce)
|
|
64050
|
+
});
|
|
64051
|
+
if (extensionWs !== ws || extensionHello !== hello || ownSession !== null) {
|
|
64052
|
+
mintedKeypair.privateKey.fill(0);
|
|
64053
|
+
return;
|
|
64054
|
+
}
|
|
64055
|
+
installOwnEphemeral({
|
|
64056
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
64057
|
+
pub: mintedKeypair.publicKey,
|
|
64058
|
+
priv: mintedKeypair.privateKey
|
|
64059
|
+
});
|
|
64060
|
+
ws.send(JSON.stringify(mintedHello));
|
|
64061
|
+
};
|
|
63728
64062
|
wss.on("connection", (ws) => {
|
|
63729
64063
|
let identified = null;
|
|
63730
64064
|
let peerMcpId = null;
|
|
@@ -63915,6 +64249,7 @@ async function startHost(opts) {
|
|
|
63915
64249
|
if (extensionWs !== ws)
|
|
63916
64250
|
return;
|
|
63917
64251
|
ownSession = new SessionState(key);
|
|
64252
|
+
ownDecryptFailures = 0;
|
|
63918
64253
|
ownPendingPairCode = null;
|
|
63919
64254
|
resolveOwnSession(ownSession);
|
|
63920
64255
|
} else {
|
|
@@ -63936,15 +64271,42 @@ async function startHost(opts) {
|
|
|
63936
64271
|
}
|
|
63937
64272
|
if (claim2 !== "ok")
|
|
63938
64273
|
return;
|
|
63939
|
-
let
|
|
64274
|
+
let result;
|
|
63940
64275
|
try {
|
|
63941
|
-
|
|
64276
|
+
result = await openEncryptedFrameDetailed(session.sessionKey, frame, "e2s");
|
|
63942
64277
|
} catch (e) {
|
|
63943
64278
|
session.releaseInboundSeq(frame.seq);
|
|
63944
64279
|
throw e;
|
|
63945
64280
|
}
|
|
64281
|
+
if (result.stage === "decrypt-failed") {
|
|
64282
|
+
session.releaseInboundSeq(frame.seq);
|
|
64283
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) that failed authentication \u2014 most likely a straggler from a previous session.`);
|
|
64284
|
+
if (session === ownSession) {
|
|
64285
|
+
ownDecryptFailures += 1;
|
|
64286
|
+
if (ownDecryptFailures >= OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE) {
|
|
64287
|
+
ownDecryptFailures = 0;
|
|
64288
|
+
void rehandshakeOwnSession(ws).catch((e) => console.error(`[fetchproxy] ${opts.ownServerName}: re-handshake failed:`, e));
|
|
64289
|
+
}
|
|
64290
|
+
}
|
|
64291
|
+
return;
|
|
64292
|
+
}
|
|
64293
|
+
if (session === ownSession)
|
|
64294
|
+
ownDecryptFailures = 0;
|
|
63946
64295
|
session.commitInboundSeq(frame.seq);
|
|
63947
|
-
|
|
64296
|
+
if (result.stage === "ok") {
|
|
64297
|
+
ownInnerListeners.forEach((cb) => cb(result.inner));
|
|
64298
|
+
} else {
|
|
64299
|
+
console.error("[fetchproxy] host: received a frame that decrypted OK but failed validation:", result.error);
|
|
64300
|
+
const recoveredId = result.recoveredId;
|
|
64301
|
+
if (recoveredId !== void 0) {
|
|
64302
|
+
ownInnerListeners.forEach((cb) => cb({
|
|
64303
|
+
type: "response",
|
|
64304
|
+
id: recoveredId,
|
|
64305
|
+
ok: false,
|
|
64306
|
+
error: `malformed response failed protocol validation: ${String(result.error)}`
|
|
64307
|
+
}));
|
|
64308
|
+
}
|
|
64309
|
+
}
|
|
63948
64310
|
} else {
|
|
63949
64311
|
const slot = peers.get(frame.mcpId);
|
|
63950
64312
|
if (slot)
|
|
@@ -64003,6 +64365,7 @@ async function startHost(opts) {
|
|
|
64003
64365
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
64004
64366
|
}
|
|
64005
64367
|
ownSession = dropOwnSessionAndEphemeral();
|
|
64368
|
+
ownDecryptFailures = 0;
|
|
64006
64369
|
ownPendingPairCode = null;
|
|
64007
64370
|
resetSessionPromise();
|
|
64008
64371
|
disconnectListeners.forEach((cb) => cb());
|
|
@@ -64017,8 +64380,15 @@ async function startHost(opts) {
|
|
|
64017
64380
|
}
|
|
64018
64381
|
}
|
|
64019
64382
|
if (identified === "peer" && peerMcpId) {
|
|
64020
|
-
if (peers.get(peerMcpId)?.ws === ws)
|
|
64383
|
+
if (peers.get(peerMcpId)?.ws === ws) {
|
|
64021
64384
|
peers.delete(peerMcpId);
|
|
64385
|
+
if (extensionWs && extensionHello?.accepts?.includes("peer-gone")) {
|
|
64386
|
+
try {
|
|
64387
|
+
extensionWs.send(JSON.stringify({ type: "peer-gone", mcpId: peerMcpId }));
|
|
64388
|
+
} catch {
|
|
64389
|
+
}
|
|
64390
|
+
}
|
|
64391
|
+
}
|
|
64022
64392
|
}
|
|
64023
64393
|
});
|
|
64024
64394
|
});
|
|
@@ -64061,12 +64431,18 @@ async function startHost(opts) {
|
|
|
64061
64431
|
}
|
|
64062
64432
|
|
|
64063
64433
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
64434
|
+
var PEER_DIAL_TIMEOUT_MS = 5e3;
|
|
64064
64435
|
var enc5 = new TextEncoder();
|
|
64436
|
+
function notSentError() {
|
|
64437
|
+
return new Error("fetchproxy: request not sent \u2014 the connection to the bridge host closed before it went out, so nothing reached the browser. It is safe to retry.");
|
|
64438
|
+
}
|
|
64065
64439
|
async function startPeer(opts) {
|
|
64066
64440
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
64067
64441
|
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
64068
64442
|
});
|
|
64443
|
+
const dialTimeoutMs = opts.dialTimeoutMs ?? PEER_DIAL_TIMEOUT_MS;
|
|
64069
64444
|
await new Promise((resolve2, reject) => {
|
|
64445
|
+
let timer;
|
|
64070
64446
|
const onOpen = () => {
|
|
64071
64447
|
cleanup();
|
|
64072
64448
|
resolve2();
|
|
@@ -64076,11 +64452,21 @@ async function startPeer(opts) {
|
|
|
64076
64452
|
reject(e);
|
|
64077
64453
|
};
|
|
64078
64454
|
const cleanup = () => {
|
|
64455
|
+
if (timer !== void 0)
|
|
64456
|
+
clearTimeout(timer);
|
|
64079
64457
|
ws.off("open", onOpen);
|
|
64080
64458
|
ws.off("error", onError);
|
|
64081
64459
|
};
|
|
64082
64460
|
ws.once("open", onOpen);
|
|
64083
64461
|
ws.once("error", onError);
|
|
64462
|
+
if (dialTimeoutMs > 0) {
|
|
64463
|
+
timer = setTimeout(() => {
|
|
64464
|
+
cleanup();
|
|
64465
|
+
ws.on("error", () => void 0);
|
|
64466
|
+
ws.terminate();
|
|
64467
|
+
reject(new Error(`fetchproxy: the process holding ${opts.host}:${opts.port} did not answer the WebSocket upgrade within ${dialTimeoutMs}ms \u2014 it may be wedged or not a fetchproxy host. Restart the MCP that owns the port (or free it) and retry.`));
|
|
64468
|
+
}, dialTimeoutMs);
|
|
64469
|
+
}
|
|
64084
64470
|
});
|
|
64085
64471
|
ws.on("error", (e) => {
|
|
64086
64472
|
console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
|
|
@@ -64099,6 +64485,7 @@ async function startPeer(opts) {
|
|
|
64099
64485
|
captureHeaders: opts.captureHeaders,
|
|
64100
64486
|
indexedDbScopes: opts.indexedDbScopes,
|
|
64101
64487
|
domSelectors: opts.domSelectors,
|
|
64488
|
+
domListSelectors: opts.domListSelectors,
|
|
64102
64489
|
localStoragePointers: opts.localStoragePointers,
|
|
64103
64490
|
sessionStoragePointers: opts.sessionStoragePointers,
|
|
64104
64491
|
graphqlOps: opts.graphqlOps,
|
|
@@ -64122,6 +64509,7 @@ async function startPeer(opts) {
|
|
|
64122
64509
|
const renegotiateListeners = [];
|
|
64123
64510
|
const pendingPairListeners = [];
|
|
64124
64511
|
const closeListeners = [];
|
|
64512
|
+
const extensionDisconnectListeners = [];
|
|
64125
64513
|
let session = null;
|
|
64126
64514
|
let sessionEphemeral = null;
|
|
64127
64515
|
function dropSessionEphemeral() {
|
|
@@ -64141,10 +64529,24 @@ async function startPeer(opts) {
|
|
|
64141
64529
|
let warnedUnverifiablePairCode = false;
|
|
64142
64530
|
let resolveFirstReady;
|
|
64143
64531
|
let rejectFirstReady;
|
|
64144
|
-
|
|
64145
|
-
|
|
64146
|
-
|
|
64147
|
-
|
|
64532
|
+
let sessionPromise;
|
|
64533
|
+
let sessionPromiseSettled = false;
|
|
64534
|
+
function resetSessionPromise() {
|
|
64535
|
+
sessionPromiseSettled = false;
|
|
64536
|
+
sessionPromise = new Promise((resolve2, reject) => {
|
|
64537
|
+
resolveFirstReady = (st) => {
|
|
64538
|
+
sessionPromiseSettled = true;
|
|
64539
|
+
resolve2(st);
|
|
64540
|
+
};
|
|
64541
|
+
rejectFirstReady = (e) => {
|
|
64542
|
+
sessionPromiseSettled = true;
|
|
64543
|
+
reject(e);
|
|
64544
|
+
};
|
|
64545
|
+
});
|
|
64546
|
+
sessionPromise.catch(() => {
|
|
64547
|
+
});
|
|
64548
|
+
}
|
|
64549
|
+
resetSessionPromise();
|
|
64148
64550
|
let extensionHello = null;
|
|
64149
64551
|
let extensionGone = false;
|
|
64150
64552
|
let cachedPin = void 0;
|
|
@@ -64241,6 +64643,12 @@ async function startPeer(opts) {
|
|
|
64241
64643
|
extensionGone = true;
|
|
64242
64644
|
dropSessionEphemeral();
|
|
64243
64645
|
pendingPairCode = null;
|
|
64646
|
+
if (session !== null) {
|
|
64647
|
+
session = null;
|
|
64648
|
+
if (sessionPromiseSettled)
|
|
64649
|
+
resetSessionPromise();
|
|
64650
|
+
}
|
|
64651
|
+
extensionDisconnectListeners.forEach((cb) => cb());
|
|
64244
64652
|
return;
|
|
64245
64653
|
}
|
|
64246
64654
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
@@ -64357,19 +64765,30 @@ async function startPeer(opts) {
|
|
|
64357
64765
|
}
|
|
64358
64766
|
closeListeners.forEach((cb) => cb());
|
|
64359
64767
|
});
|
|
64360
|
-
sessionPromise.catch(() => {
|
|
64361
|
-
});
|
|
64362
64768
|
const handle = {
|
|
64363
64769
|
ws,
|
|
64364
|
-
|
|
64770
|
+
// A getter: `extension-disconnected` replaces the promise (B-BUG-5).
|
|
64771
|
+
get session() {
|
|
64772
|
+
return sessionPromise;
|
|
64773
|
+
},
|
|
64365
64774
|
sendInner: async (inner) => {
|
|
64366
|
-
|
|
64367
|
-
|
|
64368
|
-
|
|
64369
|
-
|
|
64775
|
+
try {
|
|
64776
|
+
await awaitSessionReady(sessionPromise, {
|
|
64777
|
+
mcpId: opts.mcpId,
|
|
64778
|
+
pendingPairCode: () => pendingPairCode
|
|
64779
|
+
});
|
|
64780
|
+
} catch (e) {
|
|
64781
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
64782
|
+
throw notSentError();
|
|
64783
|
+
throw e;
|
|
64784
|
+
}
|
|
64370
64785
|
const s = session;
|
|
64786
|
+
if (s === null)
|
|
64787
|
+
throw new Error("peer: extension disconnected");
|
|
64371
64788
|
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
64372
64789
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
64790
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
64791
|
+
throw notSentError();
|
|
64373
64792
|
ws.send(JSON.stringify(sealed));
|
|
64374
64793
|
},
|
|
64375
64794
|
onInner: (cb) => {
|
|
@@ -64387,6 +64806,9 @@ async function startPeer(opts) {
|
|
|
64387
64806
|
onClose: (cb) => {
|
|
64388
64807
|
closeListeners.push(cb);
|
|
64389
64808
|
},
|
|
64809
|
+
onExtensionDisconnect: (cb) => {
|
|
64810
|
+
extensionDisconnectListeners.push(cb);
|
|
64811
|
+
},
|
|
64390
64812
|
close: () => ws.close()
|
|
64391
64813
|
};
|
|
64392
64814
|
return handle;
|
|
@@ -64394,15 +64816,15 @@ async function startPeer(opts) {
|
|
|
64394
64816
|
|
|
64395
64817
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
64396
64818
|
function classifyFetchError(error62) {
|
|
64819
|
+
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
64820
|
+
return "tab_fetch_failed";
|
|
64821
|
+
}
|
|
64397
64822
|
if (/Could not establish connection/i.test(error62) || /Receiving end does not exist/i.test(error62)) {
|
|
64398
64823
|
return "content_script_unreachable";
|
|
64399
64824
|
}
|
|
64400
64825
|
if (/^tab fetch failed:/.test(error62)) {
|
|
64401
64826
|
return "tab_fetch_failed";
|
|
64402
64827
|
}
|
|
64403
|
-
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
64404
|
-
return "tab_fetch_failed";
|
|
64405
|
-
}
|
|
64406
64828
|
if (/^no tab matching /.test(error62)) {
|
|
64407
64829
|
return "no_tab";
|
|
64408
64830
|
}
|
|
@@ -64594,6 +65016,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64594
65016
|
* the message says which number that was and where it came from.
|
|
64595
65017
|
*/
|
|
64596
65018
|
requestedTimeoutMs;
|
|
65019
|
+
/**
|
|
65020
|
+
* B-BUG-1: whether re-sending this request is safe. A timeout only means
|
|
65021
|
+
* the reply did not arrive in time — the extension may already have run the
|
|
65022
|
+
* request in the tab — so this is `false` for a non-idempotent `fetch`
|
|
65023
|
+
* (anything but GET/HEAD/OPTIONS) the caller did not mark `retryOnTimeout`,
|
|
65024
|
+
* for `download` and `writeCookies`, and for `graphqlQuery` unless the
|
|
65025
|
+
* caller marked it `retryOnTimeout` (a declared operation may be a
|
|
65026
|
+
* mutation). `retryOnceOnTimeout` honours it. Defaults to `true` (the read
|
|
65027
|
+
* verbs).
|
|
65028
|
+
*/
|
|
65029
|
+
retrySafe;
|
|
64597
65030
|
constructor(args) {
|
|
64598
65031
|
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
64599
65032
|
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
@@ -64606,6 +65039,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64606
65039
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
64607
65040
|
this.retryAttempted = args.retryAttempted ?? false;
|
|
64608
65041
|
this.requestedTimeoutMs = args.requestedTimeoutMs ?? null;
|
|
65042
|
+
this.retrySafe = args.retrySafe ?? true;
|
|
64609
65043
|
}
|
|
64610
65044
|
};
|
|
64611
65045
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
@@ -64645,6 +65079,32 @@ function assertUrlInDomains(field, url4, domains) {
|
|
|
64645
65079
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
64646
65080
|
}
|
|
64647
65081
|
var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
|
|
65082
|
+
function parseJsonBody(response) {
|
|
65083
|
+
if (response.status === 204 || response.body.trim() === "")
|
|
65084
|
+
return null;
|
|
65085
|
+
return JSON.parse(response.body);
|
|
65086
|
+
}
|
|
65087
|
+
var TIMEOUT_RETRY_SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
65088
|
+
function isRetrySafeOnTimeout(method, retryOnTimeout) {
|
|
65089
|
+
if (retryOnTimeout !== void 0)
|
|
65090
|
+
return retryOnTimeout;
|
|
65091
|
+
return TIMEOUT_RETRY_SAFE_METHODS.has(method.toUpperCase());
|
|
65092
|
+
}
|
|
65093
|
+
function isResendableRequest(inner) {
|
|
65094
|
+
switch (inner.op) {
|
|
65095
|
+
case "fetch":
|
|
65096
|
+
return isRetrySafeOnTimeout(inner.init.method);
|
|
65097
|
+
case "write_cookies":
|
|
65098
|
+
case "download":
|
|
65099
|
+
// A declared GraphQL operation may be a mutation; the server holds only
|
|
65100
|
+
// its name, so it cannot tell. `graphqlQuery({ retryOnTimeout: true })`
|
|
65101
|
+
// passes `resendable` explicitly for a known query.
|
|
65102
|
+
case "graphql_query":
|
|
65103
|
+
return false;
|
|
65104
|
+
default:
|
|
65105
|
+
return true;
|
|
65106
|
+
}
|
|
65107
|
+
}
|
|
64648
65108
|
var FetchproxyServer = class {
|
|
64649
65109
|
/**
|
|
64650
65110
|
* Bridge role. `null` until the first verb call (or an explicit
|
|
@@ -64693,12 +65153,22 @@ var FetchproxyServer = class {
|
|
|
64693
65153
|
pendingRedirect = /* @__PURE__ */ new Map();
|
|
64694
65154
|
// 0.4.0+: read_indexed_db awaiters resolve a JSON-typed values map.
|
|
64695
65155
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
65156
|
+
// 3.1.0+: read_dom_list awaiters resolve an array of field-value rows —
|
|
65157
|
+
// a different shape from `pendingStorage`'s single map, so its own map.
|
|
65158
|
+
pendingDomList = /* @__PURE__ */ new Map();
|
|
64696
65159
|
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
64697
65160
|
pendingDownload = /* @__PURE__ */ new Map();
|
|
64698
65161
|
// 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
|
|
64699
65162
|
// shape is operation-specific, so the awaiter resolves `unknown` and the
|
|
64700
65163
|
// caller narrows.
|
|
64701
65164
|
pendingGraphql = /* @__PURE__ */ new Map();
|
|
65165
|
+
/**
|
|
65166
|
+
* B-BUG-6: the frames of in-flight requests that are safe to send again
|
|
65167
|
+
* if the host that relayed them exits before replying (see
|
|
65168
|
+
* `recoverFromHostLoss`). Keyed by request id; an entry outlives its
|
|
65169
|
+
* request only until the next prune or response.
|
|
65170
|
+
*/
|
|
65171
|
+
resendable = /* @__PURE__ */ new Map();
|
|
64702
65172
|
mcpId = null;
|
|
64703
65173
|
identity = null;
|
|
64704
65174
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -64744,7 +65214,7 @@ var FetchproxyServer = class {
|
|
|
64744
65214
|
}
|
|
64745
65215
|
for (const c of opts.capabilities) {
|
|
64746
65216
|
if (!KNOWN_CAPABILITIES.has(c)) {
|
|
64747
|
-
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: [
|
|
65217
|
+
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: ${JSON.stringify([...KNOWN_CAPABILITIES])}`);
|
|
64748
65218
|
}
|
|
64749
65219
|
}
|
|
64750
65220
|
capabilities = [...opts.capabilities];
|
|
@@ -64791,6 +65261,16 @@ var FetchproxyServer = class {
|
|
|
64791
65261
|
selector: d.selector,
|
|
64792
65262
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
64793
65263
|
})),
|
|
65264
|
+
domListSelectors: (opts.domListSelectors ?? []).map((d) => ({
|
|
65265
|
+
name: d.name,
|
|
65266
|
+
itemSelector: d.itemSelector,
|
|
65267
|
+
fields: d.fields.map((f) => ({
|
|
65268
|
+
name: f.name,
|
|
65269
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
65270
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
65271
|
+
})),
|
|
65272
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
65273
|
+
})),
|
|
64794
65274
|
graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
|
|
64795
65275
|
name: d.name,
|
|
64796
65276
|
operationName: d.operationName
|
|
@@ -64920,6 +65400,7 @@ var FetchproxyServer = class {
|
|
|
64920
65400
|
ownLocalStoragePointers: this.opts.localStoragePointers,
|
|
64921
65401
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
64922
65402
|
ownDomSelectors: this.opts.domSelectors,
|
|
65403
|
+
ownDomListSelectors: this.opts.domListSelectors,
|
|
64923
65404
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
64924
65405
|
onPairCode: this.opts.onPairCode,
|
|
64925
65406
|
extensionTrust: this.extensionTrust()
|
|
@@ -64951,6 +65432,7 @@ var FetchproxyServer = class {
|
|
|
64951
65432
|
localStoragePointers: this.opts.localStoragePointers,
|
|
64952
65433
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
64953
65434
|
domSelectors: this.opts.domSelectors,
|
|
65435
|
+
domListSelectors: this.opts.domListSelectors,
|
|
64954
65436
|
graphqlOps: this.opts.graphqlOps,
|
|
64955
65437
|
extensionTrust: this.extensionTrust(),
|
|
64956
65438
|
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
@@ -64960,6 +65442,10 @@ var FetchproxyServer = class {
|
|
|
64960
65442
|
this.stopKeepalive();
|
|
64961
65443
|
this.rejectAllPending();
|
|
64962
65444
|
});
|
|
65445
|
+
this.peerHandle.onExtensionDisconnect(() => {
|
|
65446
|
+
this.stopKeepalive();
|
|
65447
|
+
this.rejectAllPending();
|
|
65448
|
+
});
|
|
64963
65449
|
this.peerHandle.onPendingPair((code) => {
|
|
64964
65450
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
64965
65451
|
});
|
|
@@ -64971,9 +65457,9 @@ var FetchproxyServer = class {
|
|
|
64971
65457
|
if (this.closing || this.peerHandle === null)
|
|
64972
65458
|
return;
|
|
64973
65459
|
this.stopKeepalive();
|
|
64974
|
-
this.rejectAllPending();
|
|
64975
65460
|
this.peerHandle = null;
|
|
64976
65461
|
this.role = null;
|
|
65462
|
+
void this.recoverFromHostLoss();
|
|
64977
65463
|
});
|
|
64978
65464
|
}
|
|
64979
65465
|
}
|
|
@@ -64994,7 +65480,7 @@ var FetchproxyServer = class {
|
|
|
64994
65480
|
* only when the bridge itself failed (no signed-in tab, extension
|
|
64995
65481
|
* offline, etc.).
|
|
64996
65482
|
*/
|
|
64997
|
-
async fetch(init) {
|
|
65483
|
+
async fetch(init, fetchOpts = {}) {
|
|
64998
65484
|
await this.ensureConnected();
|
|
64999
65485
|
const pendingCode = this.currentPendingPairCode();
|
|
65000
65486
|
if (pendingCode !== null) {
|
|
@@ -65006,16 +65492,17 @@ var FetchproxyServer = class {
|
|
|
65006
65492
|
retryAttempted: false
|
|
65007
65493
|
};
|
|
65008
65494
|
}
|
|
65009
|
-
const first = await this._fetchOnceWithTimeout(init);
|
|
65495
|
+
const first = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65010
65496
|
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
65011
65497
|
const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
|
|
65012
65498
|
if (isColdStartSymptom) {
|
|
65013
65499
|
this.lastEvictionDetectedAt = Date.now();
|
|
65014
65500
|
}
|
|
65015
|
-
|
|
65501
|
+
const retryable = isColdStartSymptom && (first.kind === "content_script_unreachable" || isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout));
|
|
65502
|
+
if (retryable && reviveMs !== void 0 && reviveMs > 0) {
|
|
65016
65503
|
this.lazyReviveAttempts += 1;
|
|
65017
65504
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65018
|
-
const second = await this._fetchOnceWithTimeout(init);
|
|
65505
|
+
const second = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65019
65506
|
if (second.ok) {
|
|
65020
65507
|
this.lazyReviveSuccesses += 1;
|
|
65021
65508
|
this.recordSuccess();
|
|
@@ -65164,13 +65651,16 @@ var FetchproxyServer = class {
|
|
|
65164
65651
|
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
65165
65652
|
* server closes, then rethrow.
|
|
65166
65653
|
*/
|
|
65167
|
-
async sendInnerFrame(inner) {
|
|
65654
|
+
async sendInnerFrame(inner, opts = {}) {
|
|
65168
65655
|
try {
|
|
65169
65656
|
if (this.hostHandle) {
|
|
65170
65657
|
await this.hostHandle.sendOwnInner(inner);
|
|
65171
65658
|
} else if (this.peerHandle) {
|
|
65172
65659
|
await this.peerHandle.sendInner(inner);
|
|
65173
65660
|
}
|
|
65661
|
+
if (inner.type === "request" && (opts.resendable ?? isResendableRequest(inner))) {
|
|
65662
|
+
this.noteResendable(inner);
|
|
65663
|
+
}
|
|
65174
65664
|
} catch (err) {
|
|
65175
65665
|
if ("id" in inner && typeof inner.id === "number") {
|
|
65176
65666
|
const { id } = inner;
|
|
@@ -65182,6 +65672,7 @@ var FetchproxyServer = class {
|
|
|
65182
65672
|
this.pendingRedirect.delete(id);
|
|
65183
65673
|
this.pendingDownload.delete(id);
|
|
65184
65674
|
this.pendingIdb.delete(id);
|
|
65675
|
+
this.pendingDomList.delete(id);
|
|
65185
65676
|
this.pendingGraphql.delete(id);
|
|
65186
65677
|
}
|
|
65187
65678
|
throw err;
|
|
@@ -65192,13 +65683,25 @@ var FetchproxyServer = class {
|
|
|
65192
65683
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
65193
65684
|
* the throwing surface is the convenience methods.
|
|
65194
65685
|
*/
|
|
65195
|
-
async _fetchOnceWithTimeout(init) {
|
|
65686
|
+
async _fetchOnceWithTimeout(init, fetchOpts = {}) {
|
|
65196
65687
|
const id = this.nextRequestId++;
|
|
65197
65688
|
const inner = { type: "request", id, op: "fetch", init };
|
|
65198
65689
|
const pending = this.guardPending(new Promise((resolve2) => {
|
|
65199
65690
|
this.pending.set(id, resolve2);
|
|
65200
65691
|
}));
|
|
65201
|
-
|
|
65692
|
+
try {
|
|
65693
|
+
await this.sendInnerFrame(inner, {
|
|
65694
|
+
resendable: isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout)
|
|
65695
|
+
});
|
|
65696
|
+
} catch (err) {
|
|
65697
|
+
return {
|
|
65698
|
+
ok: false,
|
|
65699
|
+
error: err instanceof Error ? err.message : String(err),
|
|
65700
|
+
kind: "session_not_ready",
|
|
65701
|
+
retryAttempted: false,
|
|
65702
|
+
cause: err
|
|
65703
|
+
};
|
|
65704
|
+
}
|
|
65202
65705
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
65203
65706
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
65204
65707
|
return pending;
|
|
@@ -65272,7 +65775,7 @@ var FetchproxyServer = class {
|
|
|
65272
65775
|
});
|
|
65273
65776
|
return pending;
|
|
65274
65777
|
}
|
|
65275
|
-
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs) {
|
|
65778
|
+
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs, retrySafe = true) {
|
|
65276
65779
|
const transportMs = this.opts.fetchTimeoutMs;
|
|
65277
65780
|
if (transportMs === void 0 || transportMs <= 0)
|
|
65278
65781
|
return pending;
|
|
@@ -65293,6 +65796,7 @@ var FetchproxyServer = class {
|
|
|
65293
65796
|
port: this.opts.port,
|
|
65294
65797
|
elapsedMs: Date.now() - start,
|
|
65295
65798
|
retryAttempted: false,
|
|
65799
|
+
retrySafe,
|
|
65296
65800
|
graceMs,
|
|
65297
65801
|
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
65298
65802
|
}));
|
|
@@ -65310,7 +65814,7 @@ var FetchproxyServer = class {
|
|
|
65310
65814
|
* logic re-implemented inline) `captureRequestHeader()` agree on what
|
|
65311
65815
|
* to throw.
|
|
65312
65816
|
*/
|
|
65313
|
-
_typedErrorFor(result, url4, op, retryAttempted) {
|
|
65817
|
+
_typedErrorFor(result, url4, op, retryAttempted, retrySafe = true) {
|
|
65314
65818
|
if (result.kind === "timeout") {
|
|
65315
65819
|
return new FetchproxyTimeoutError({
|
|
65316
65820
|
url: url4,
|
|
@@ -65318,7 +65822,8 @@ var FetchproxyServer = class {
|
|
|
65318
65822
|
role: this.role,
|
|
65319
65823
|
port: this.opts.port,
|
|
65320
65824
|
elapsedMs: result.elapsedMs,
|
|
65321
|
-
retryAttempted
|
|
65825
|
+
retryAttempted,
|
|
65826
|
+
retrySafe
|
|
65322
65827
|
});
|
|
65323
65828
|
}
|
|
65324
65829
|
if (result.kind === "content_script_unreachable") {
|
|
@@ -65392,9 +65897,12 @@ var FetchproxyServer = class {
|
|
|
65392
65897
|
// every version before this option existed.
|
|
65393
65898
|
...opts.credentials === "omit" ? { credentials: "omit" } : {}
|
|
65394
65899
|
};
|
|
65395
|
-
const result = await this.fetch(init);
|
|
65900
|
+
const result = await this.fetch(init, opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {});
|
|
65396
65901
|
if (!result.ok) {
|
|
65397
|
-
|
|
65902
|
+
if (result.kind === "session_not_ready" && result.cause instanceof Error) {
|
|
65903
|
+
throw result.cause;
|
|
65904
|
+
}
|
|
65905
|
+
throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false, isRetrySafeOnTimeout(method, opts.retryOnTimeout));
|
|
65398
65906
|
}
|
|
65399
65907
|
const response = {
|
|
65400
65908
|
status: result.status,
|
|
@@ -65434,17 +65942,21 @@ var FetchproxyServer = class {
|
|
|
65434
65942
|
* GET a path and parse the response body as JSON. Throws
|
|
65435
65943
|
* `FetchproxyHttpError` if the status is outside the default 2xx
|
|
65436
65944
|
* happy-path set (`[200, 201, 202, 204]`); pass a custom
|
|
65437
|
-
* `expectStatus` to override.
|
|
65945
|
+
* `expectStatus` to override. A 204 or an empty/whitespace body
|
|
65946
|
+
* resolves to `null` (the declared `T` does not say so, to keep
|
|
65947
|
+
* existing call sites compiling — include `| null` in `T` if the
|
|
65948
|
+
* endpoint can answer empty).
|
|
65438
65949
|
*/
|
|
65439
65950
|
async getJson(path, opts = {}) {
|
|
65440
65951
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
65441
|
-
return
|
|
65952
|
+
return parseJsonBody(response);
|
|
65442
65953
|
}
|
|
65443
65954
|
/**
|
|
65444
65955
|
* POST a JSON body and parse the response body as JSON. The body is
|
|
65445
65956
|
* `JSON.stringify`'d; `Content-Type: application/json` is set unless
|
|
65446
65957
|
* the caller already provided one. Defaults `expectStatus` to the 2xx
|
|
65447
|
-
* happy-path set.
|
|
65958
|
+
* happy-path set. A 204 or an empty/whitespace body resolves to `null`
|
|
65959
|
+
* rather than throwing after the write already happened (see `getJson`).
|
|
65448
65960
|
*/
|
|
65449
65961
|
async postJson(path, body, opts = {}) {
|
|
65450
65962
|
const headers = { ...opts.headers ?? {} };
|
|
@@ -65456,7 +65968,7 @@ var FetchproxyServer = class {
|
|
|
65456
65968
|
headers,
|
|
65457
65969
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
65458
65970
|
});
|
|
65459
|
-
return
|
|
65971
|
+
return parseJsonBody(response);
|
|
65460
65972
|
}
|
|
65461
65973
|
/**
|
|
65462
65974
|
* GET a path and return the response body as a string. Throws
|
|
@@ -65477,7 +65989,8 @@ var FetchproxyServer = class {
|
|
|
65477
65989
|
* - adds `Content-Type: application/json` only for a non-GET request
|
|
65478
65990
|
* that carries a `body` (and only if the caller didn't set one);
|
|
65479
65991
|
* - `JSON.stringify`s the body (GET / no-body sends nothing);
|
|
65480
|
-
* - treats a `204
|
|
65992
|
+
* - treats a `204`, an empty body, or a whitespace-only body as
|
|
65993
|
+
* `data: null` (no parse), the same rule as getJson/postJson;
|
|
65481
65994
|
* - otherwise `JSON.parse`s the body.
|
|
65482
65995
|
*
|
|
65483
65996
|
* Scope is serialization + header defaults + 204-handling +
|
|
@@ -65491,6 +66004,11 @@ var FetchproxyServer = class {
|
|
|
65491
66004
|
* Bridge-level failures (no signed-in tab, SW down, timeout) still
|
|
65492
66005
|
* throw the typed errors via `request()`, exactly like the verb
|
|
65493
66006
|
* helpers — only successful round-trips (any HTTP status) return.
|
|
66007
|
+
*
|
|
66008
|
+
* `retryOnTimeout` is `RequestOpts.retryOnTimeout`: a timed-out POST is
|
|
66009
|
+
* NOT re-sent unless the caller says it is safe to repeat. A read-only POST
|
|
66010
|
+
* (a search, a GraphQL query) should pass `true` to keep the cold-start
|
|
66011
|
+
* timeout retry (#90).
|
|
65494
66012
|
*/
|
|
65495
66013
|
async requestJson(method, path, opts = {}) {
|
|
65496
66014
|
const isGet = method.toUpperCase() === "GET";
|
|
@@ -65504,7 +66022,8 @@ var FetchproxyServer = class {
|
|
|
65504
66022
|
headers,
|
|
65505
66023
|
body: sendBody ? JSON.stringify(opts.body) : void 0,
|
|
65506
66024
|
...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
|
|
65507
|
-
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
66025
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {},
|
|
66026
|
+
...opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {}
|
|
65508
66027
|
});
|
|
65509
66028
|
const result = {
|
|
65510
66029
|
ok: true,
|
|
@@ -65512,12 +66031,9 @@ var FetchproxyServer = class {
|
|
|
65512
66031
|
url: response.url,
|
|
65513
66032
|
body: response.body
|
|
65514
66033
|
};
|
|
65515
|
-
if (response.status === 204 || response.body === "") {
|
|
65516
|
-
return { data: null, result };
|
|
65517
|
-
}
|
|
65518
66034
|
let data;
|
|
65519
66035
|
try {
|
|
65520
|
-
data =
|
|
66036
|
+
data = parseJsonBody(response);
|
|
65521
66037
|
} catch (e) {
|
|
65522
66038
|
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
65523
66039
|
}
|
|
@@ -65574,18 +66090,19 @@ var FetchproxyServer = class {
|
|
|
65574
66090
|
};
|
|
65575
66091
|
}
|
|
65576
66092
|
/**
|
|
65577
|
-
*
|
|
66093
|
+
* Read the user's cookies for the chosen domain.
|
|
65578
66094
|
*
|
|
65579
66095
|
* Requires `'read_cookies'` in `FetchproxyServerOpts.capabilities`.
|
|
65580
66096
|
* Throws a developer-facing `Error` at the call site if the MCP did
|
|
65581
66097
|
* not declare the capability — this is a programming mistake, not a
|
|
65582
66098
|
* runtime condition.
|
|
65583
66099
|
*
|
|
65584
|
-
* The returned string is
|
|
65585
|
-
*
|
|
65586
|
-
*
|
|
65587
|
-
*
|
|
65588
|
-
*
|
|
66100
|
+
* The returned string is semicolon-separated `k=v` pairs. With `keys`
|
|
66101
|
+
* (each in the declared `cookieKeys`), the extension reads each name via
|
|
66102
|
+
* `chrome.cookies.get`, which INCLUDES HttpOnly cookies — the login
|
|
66103
|
+
* session cookie among them; that is what cookie-session MCPs use it
|
|
66104
|
+
* for. Without `keys` (legacy), it is the tab's `document.cookie`, which
|
|
66105
|
+
* omits HttpOnly cookies. See docs/SECURITY.md §T-cookie-exfil.
|
|
65589
66106
|
*
|
|
65590
66107
|
* Throws `FetchproxyProtocolError` if the bridge could not deliver
|
|
65591
66108
|
* the request (no signed-in tab, extension offline, etc.).
|
|
@@ -65681,7 +66198,15 @@ var FetchproxyServer = class {
|
|
|
65681
66198
|
this.pendingWriteCookies.set(id, { resolve: resolve2, reject });
|
|
65682
66199
|
}));
|
|
65683
66200
|
await this.sendInnerFrame(inner);
|
|
65684
|
-
return this._withVerbTimeout(
|
|
66201
|
+
return this._withVerbTimeout(
|
|
66202
|
+
pending,
|
|
66203
|
+
this.pendingWriteCookies,
|
|
66204
|
+
id,
|
|
66205
|
+
`https://${host}`,
|
|
66206
|
+
void 0,
|
|
66207
|
+
// A write: never re-sent after an ambiguous timeout.
|
|
66208
|
+
false
|
|
66209
|
+
);
|
|
65685
66210
|
}
|
|
65686
66211
|
/**
|
|
65687
66212
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
@@ -65784,53 +66309,7 @@ var FetchproxyServer = class {
|
|
|
65784
66309
|
throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
|
|
65785
66310
|
}
|
|
65786
66311
|
const callOpts = { ...resolved, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
65787
|
-
|
|
65788
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65789
|
-
this.recordSuccess();
|
|
65790
|
-
return result;
|
|
65791
|
-
} catch (err) {
|
|
65792
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
65793
|
-
if (!swDown) {
|
|
65794
|
-
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
65795
|
-
throw err;
|
|
65796
|
-
}
|
|
65797
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
65798
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
65799
|
-
if (reviveMs > 0) {
|
|
65800
|
-
this.lazyReviveAttempts += 1;
|
|
65801
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65802
|
-
try {
|
|
65803
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65804
|
-
this.lazyReviveSuccesses += 1;
|
|
65805
|
-
this.recordSuccess();
|
|
65806
|
-
return result;
|
|
65807
|
-
} catch (retryErr) {
|
|
65808
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
65809
|
-
if (!stillDown) {
|
|
65810
|
-
this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
|
|
65811
|
-
throw retryErr;
|
|
65812
|
-
}
|
|
65813
|
-
this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
|
|
65814
|
-
throw new FetchproxyBridgeDownError({
|
|
65815
|
-
originalError: retryErr.message,
|
|
65816
|
-
retryAttempted: true,
|
|
65817
|
-
op: "capture_request_header",
|
|
65818
|
-
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
65819
|
-
role: this.role,
|
|
65820
|
-
port: this.opts.port
|
|
65821
|
-
});
|
|
65822
|
-
}
|
|
65823
|
-
}
|
|
65824
|
-
this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
|
|
65825
|
-
throw new FetchproxyBridgeDownError({
|
|
65826
|
-
originalError: err.message,
|
|
65827
|
-
retryAttempted: false,
|
|
65828
|
-
op: "capture_request_header",
|
|
65829
|
-
url: `https://${resolved.host}${resolved.path ?? "/*"}`,
|
|
65830
|
-
role: this.role,
|
|
65831
|
-
port: this.opts.port
|
|
65832
|
-
});
|
|
65833
|
-
}
|
|
66312
|
+
return this.withLazyRevive("capture_request_header", `https://${resolved.host}${resolved.path ?? "/*"}`, () => this._captureRequestHeaderOnce(callOpts));
|
|
65834
66313
|
}
|
|
65835
66314
|
async _captureRequestHeaderOnce(opts) {
|
|
65836
66315
|
const id = this.nextRequestId++;
|
|
@@ -65879,53 +66358,7 @@ var FetchproxyServer = class {
|
|
|
65879
66358
|
}
|
|
65880
66359
|
await this.ensureConnected();
|
|
65881
66360
|
this.throwIfPendingPair();
|
|
65882
|
-
|
|
65883
|
-
const result = await this._captureRedirectOnce(opts);
|
|
65884
|
-
this.recordSuccess();
|
|
65885
|
-
return result;
|
|
65886
|
-
} catch (err) {
|
|
65887
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
65888
|
-
if (!swDown) {
|
|
65889
|
-
this.recordFailure(`capture_redirect: ${err.message ?? String(err)}`);
|
|
65890
|
-
throw err;
|
|
65891
|
-
}
|
|
65892
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
65893
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
65894
|
-
if (reviveMs > 0) {
|
|
65895
|
-
this.lazyReviveAttempts += 1;
|
|
65896
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65897
|
-
try {
|
|
65898
|
-
const result = await this._captureRedirectOnce(opts);
|
|
65899
|
-
this.lazyReviveSuccesses += 1;
|
|
65900
|
-
this.recordSuccess();
|
|
65901
|
-
return result;
|
|
65902
|
-
} catch (retryErr) {
|
|
65903
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
65904
|
-
if (!stillDown) {
|
|
65905
|
-
this.recordFailure(`capture_redirect: ${retryErr.message ?? String(retryErr)}`);
|
|
65906
|
-
throw retryErr;
|
|
65907
|
-
}
|
|
65908
|
-
this.recordFailure(`capture_redirect bridge-down: ${retryErr.message}`);
|
|
65909
|
-
throw new FetchproxyBridgeDownError({
|
|
65910
|
-
originalError: retryErr.message,
|
|
65911
|
-
retryAttempted: true,
|
|
65912
|
-
op: "capture_redirect",
|
|
65913
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
65914
|
-
role: this.role,
|
|
65915
|
-
port: this.opts.port
|
|
65916
|
-
});
|
|
65917
|
-
}
|
|
65918
|
-
}
|
|
65919
|
-
this.recordFailure(`capture_redirect bridge-down: ${err.message}`);
|
|
65920
|
-
throw new FetchproxyBridgeDownError({
|
|
65921
|
-
originalError: err.message,
|
|
65922
|
-
retryAttempted: false,
|
|
65923
|
-
op: "capture_redirect",
|
|
65924
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
65925
|
-
role: this.role,
|
|
65926
|
-
port: this.opts.port
|
|
65927
|
-
});
|
|
65928
|
-
}
|
|
66361
|
+
return this.withLazyRevive("capture_redirect", `https://${opts.host}${opts.path ?? "/*"}`, () => this._captureRedirectOnce(opts));
|
|
65929
66362
|
}
|
|
65930
66363
|
async _captureRedirectOnce(opts) {
|
|
65931
66364
|
const id = this.nextRequestId++;
|
|
@@ -65970,52 +66403,57 @@ var FetchproxyServer = class {
|
|
|
65970
66403
|
assertUrlInDomains("download url", opts.url, this.opts.domains);
|
|
65971
66404
|
await this.ensureConnected();
|
|
65972
66405
|
this.throwIfPendingPair();
|
|
66406
|
+
return this.withLazyRevive("download", opts.url, () => this._downloadOnce(opts));
|
|
66407
|
+
}
|
|
66408
|
+
/**
|
|
66409
|
+
* B-QUAL-1: the lazy-revive policy for the throwing verbs
|
|
66410
|
+
* (`capture_request_header`, `capture_redirect`, `download`), in one place.
|
|
66411
|
+
* Runs `once`; on the SW-eviction symptom (`content_script_unreachable` —
|
|
66412
|
+
* the request provably never reached a tab, so re-sending is safe) stamps
|
|
66413
|
+
* the eviction counter, waits `bridgeReviveDelayMs` and retries once, then
|
|
66414
|
+
* surfaces a `FetchproxyBridgeDownError`. Every other error is recorded and
|
|
66415
|
+
* rethrown untouched. `fetch()` keeps its own envelope-shaped variant.
|
|
66416
|
+
*/
|
|
66417
|
+
async withLazyRevive(op, url4, once) {
|
|
66418
|
+
const isSwDown = (e) => e instanceof FetchproxyProtocolError && classifyFetchError(e.message) === "content_script_unreachable";
|
|
66419
|
+
const bridgeDown = (e, retryAttempted) => {
|
|
66420
|
+
this.recordFailure(`${op} bridge-down: ${e.message}`);
|
|
66421
|
+
return new FetchproxyBridgeDownError({
|
|
66422
|
+
originalError: e.message,
|
|
66423
|
+
retryAttempted,
|
|
66424
|
+
op,
|
|
66425
|
+
url: url4,
|
|
66426
|
+
role: this.role,
|
|
66427
|
+
port: this.opts.port
|
|
66428
|
+
});
|
|
66429
|
+
};
|
|
65973
66430
|
try {
|
|
65974
|
-
const result = await
|
|
66431
|
+
const result = await once();
|
|
65975
66432
|
this.recordSuccess();
|
|
65976
66433
|
return result;
|
|
65977
66434
|
} catch (err) {
|
|
65978
|
-
|
|
65979
|
-
|
|
65980
|
-
this.recordFailure(`download: ${err.message ?? String(err)}`);
|
|
66435
|
+
if (!isSwDown(err)) {
|
|
66436
|
+
this.recordFailure(`${op}: ${err.message ?? String(err)}`);
|
|
65981
66437
|
throw err;
|
|
65982
66438
|
}
|
|
65983
66439
|
this.lastEvictionDetectedAt = Date.now();
|
|
65984
66440
|
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
65985
|
-
if (reviveMs
|
|
65986
|
-
|
|
65987
|
-
|
|
65988
|
-
|
|
65989
|
-
|
|
65990
|
-
|
|
65991
|
-
|
|
65992
|
-
|
|
65993
|
-
|
|
65994
|
-
|
|
65995
|
-
|
|
65996
|
-
|
|
65997
|
-
|
|
65998
|
-
}
|
|
65999
|
-
this.recordFailure(`download bridge-down: ${retryErr.message}`);
|
|
66000
|
-
throw new FetchproxyBridgeDownError({
|
|
66001
|
-
originalError: retryErr.message,
|
|
66002
|
-
retryAttempted: true,
|
|
66003
|
-
op: "download",
|
|
66004
|
-
url: opts.url,
|
|
66005
|
-
role: this.role,
|
|
66006
|
-
port: this.opts.port
|
|
66007
|
-
});
|
|
66441
|
+
if (reviveMs <= 0)
|
|
66442
|
+
throw bridgeDown(err, false);
|
|
66443
|
+
this.lazyReviveAttempts += 1;
|
|
66444
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
66445
|
+
try {
|
|
66446
|
+
const result = await once();
|
|
66447
|
+
this.lazyReviveSuccesses += 1;
|
|
66448
|
+
this.recordSuccess();
|
|
66449
|
+
return result;
|
|
66450
|
+
} catch (retryErr) {
|
|
66451
|
+
if (!isSwDown(retryErr)) {
|
|
66452
|
+
this.recordFailure(`${op}: ${retryErr.message ?? String(retryErr)}`);
|
|
66453
|
+
throw retryErr;
|
|
66008
66454
|
}
|
|
66455
|
+
throw bridgeDown(retryErr, true);
|
|
66009
66456
|
}
|
|
66010
|
-
this.recordFailure(`download bridge-down: ${err.message}`);
|
|
66011
|
-
throw new FetchproxyBridgeDownError({
|
|
66012
|
-
originalError: err.message,
|
|
66013
|
-
retryAttempted: false,
|
|
66014
|
-
op: "download",
|
|
66015
|
-
url: opts.url,
|
|
66016
|
-
role: this.role,
|
|
66017
|
-
port: this.opts.port
|
|
66018
|
-
});
|
|
66019
66457
|
}
|
|
66020
66458
|
}
|
|
66021
66459
|
async _downloadOnce(opts) {
|
|
@@ -66044,7 +66482,9 @@ var FetchproxyServer = class {
|
|
|
66044
66482
|
// CAP and need the identical explanation; missing it then was the point
|
|
66045
66483
|
// of #279 — "both verbs" was counted off the two call sites in view
|
|
66046
66484
|
// rather than off the type. Three is still the number.
|
|
66047
|
-
opts.timeoutMs
|
|
66485
|
+
opts.timeoutMs,
|
|
66486
|
+
// A second download saves a duplicate "file (1)".
|
|
66487
|
+
false
|
|
66048
66488
|
);
|
|
66049
66489
|
}
|
|
66050
66490
|
/**
|
|
@@ -66135,6 +66575,52 @@ var FetchproxyServer = class {
|
|
|
66135
66575
|
await this.sendInnerFrame(inner);
|
|
66136
66576
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
66137
66577
|
}
|
|
66578
|
+
/**
|
|
66579
|
+
* 3.1.0+: read a declared REPEATED DOM structure from the user's
|
|
66580
|
+
* signed-in tab. Requires `'read_dom_list'` in capabilities AND `name` to
|
|
66581
|
+
* match a declared `domListSelectors` entry. The extension resolves the
|
|
66582
|
+
* entry's `itemSelector` via `document.querySelectorAll` in the matched
|
|
66583
|
+
* tab's DOM (isolated world), then resolves each declared field against
|
|
66584
|
+
* every matched item in turn — no page-JS execution, same as `readDom`.
|
|
66585
|
+
*
|
|
66586
|
+
* Returns one row per matched item, in document order, truncated to the
|
|
66587
|
+
* entry's declared `maxItems` when set. A field absent on a given item is
|
|
66588
|
+
* omitted from that item's row (the row itself is still returned). Throws
|
|
66589
|
+
* `FetchproxyProtocolError` on bridge failures and a plain `Error` on
|
|
66590
|
+
* developer mistakes (undeclared capability, undeclared name).
|
|
66591
|
+
*
|
|
66592
|
+
* Reads only what is CURRENTLY rendered — a virtualized or lazily-loaded
|
|
66593
|
+
* list returns only its visible rows. A caller that needs more scrolls
|
|
66594
|
+
* the list (out of band) and calls again.
|
|
66595
|
+
*/
|
|
66596
|
+
async readDomList(opts) {
|
|
66597
|
+
if (!this.opts.capabilities.includes("read_dom_list")) {
|
|
66598
|
+
throw new Error('FetchproxyServer.readDomList(): MCP did not declare "read_dom_list" in capabilities');
|
|
66599
|
+
}
|
|
66600
|
+
await this.ensureConnected();
|
|
66601
|
+
this.throwIfPendingPair();
|
|
66602
|
+
if (typeof opts.name !== "string" || opts.name.length === 0) {
|
|
66603
|
+
throw new Error("FetchproxyServer.readDomList: opts.name must be a non-empty string");
|
|
66604
|
+
}
|
|
66605
|
+
this.assertScopeSubset([opts.name], this.opts.domListSelectors.map((d) => d.name), "domListSelectors");
|
|
66606
|
+
if (opts.subdomain !== void 0)
|
|
66607
|
+
assertSubdomainLabel(opts.subdomain);
|
|
66608
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
66609
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
66610
|
+
const origin = `https://${host}`;
|
|
66611
|
+
const id = this.nextRequestId++;
|
|
66612
|
+
const inner = {
|
|
66613
|
+
type: "request",
|
|
66614
|
+
id,
|
|
66615
|
+
op: "read_dom_list",
|
|
66616
|
+
init: { origin, name: opts.name }
|
|
66617
|
+
};
|
|
66618
|
+
const pending = this.guardPending(new Promise((resolve2, reject) => {
|
|
66619
|
+
this.pendingDomList.set(id, { resolve: resolve2, reject });
|
|
66620
|
+
}));
|
|
66621
|
+
await this.sendInnerFrame(inner);
|
|
66622
|
+
return this._withVerbTimeout(pending, this.pendingDomList, id, origin);
|
|
66623
|
+
}
|
|
66138
66624
|
/**
|
|
66139
66625
|
* 1.x+: run a declared GraphQL operation through the page's own Apollo
|
|
66140
66626
|
* client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
|
|
@@ -66178,8 +66664,9 @@ var FetchproxyServer = class {
|
|
|
66178
66664
|
const pending = this.guardPending(new Promise((resolve2, reject) => {
|
|
66179
66665
|
this.pendingGraphql.set(id, { resolve: resolve2, reject });
|
|
66180
66666
|
}));
|
|
66181
|
-
|
|
66182
|
-
|
|
66667
|
+
const retrySafe = opts.retryOnTimeout === true;
|
|
66668
|
+
await this.sendInnerFrame(inner, { resendable: retrySafe });
|
|
66669
|
+
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name, void 0, retrySafe);
|
|
66183
66670
|
}
|
|
66184
66671
|
assertScopeSubset(requested, declared, label) {
|
|
66185
66672
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -66216,6 +66703,7 @@ var FetchproxyServer = class {
|
|
|
66216
66703
|
onInner(inner) {
|
|
66217
66704
|
if (inner.type !== "response")
|
|
66218
66705
|
return;
|
|
66706
|
+
this.resendable.delete(inner.id);
|
|
66219
66707
|
this.lastExtensionMessageAt = Date.now();
|
|
66220
66708
|
const fetchCb = this.pending.get(inner.id);
|
|
66221
66709
|
if (fetchCb) {
|
|
@@ -66304,6 +66792,20 @@ var FetchproxyServer = class {
|
|
|
66304
66792
|
}
|
|
66305
66793
|
return;
|
|
66306
66794
|
}
|
|
66795
|
+
const domListCb = this.pendingDomList.get(inner.id);
|
|
66796
|
+
if (domListCb) {
|
|
66797
|
+
this.pendingDomList.delete(inner.id);
|
|
66798
|
+
if (inner.ok) {
|
|
66799
|
+
if (inner.op === "read_dom_list" && inner.rows) {
|
|
66800
|
+
domListCb.resolve(inner.rows.map((row) => ({ ...row })));
|
|
66801
|
+
} else {
|
|
66802
|
+
domListCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_dom_list awaiter`));
|
|
66803
|
+
}
|
|
66804
|
+
} else {
|
|
66805
|
+
domListCb.reject(protocolErrorFrom(inner.error));
|
|
66806
|
+
}
|
|
66807
|
+
return;
|
|
66808
|
+
}
|
|
66307
66809
|
const downloadCb = this.pendingDownload.get(inner.id);
|
|
66308
66810
|
if (downloadCb) {
|
|
66309
66811
|
this.pendingDownload.delete(inner.id);
|
|
@@ -66363,42 +66865,133 @@ var FetchproxyServer = class {
|
|
|
66363
66865
|
}
|
|
66364
66866
|
}
|
|
66365
66867
|
}
|
|
66366
|
-
rejectAllPending(reason = "extension disconnected") {
|
|
66868
|
+
rejectAllPending(reason = "extension disconnected", keep = /* @__PURE__ */ new Set()) {
|
|
66869
|
+
for (const id of this.allPendingIds()) {
|
|
66870
|
+
if (!keep.has(id))
|
|
66871
|
+
this.rejectPendingId(id, reason);
|
|
66872
|
+
}
|
|
66873
|
+
if (keep.size === 0)
|
|
66874
|
+
this.resendable.clear();
|
|
66875
|
+
}
|
|
66876
|
+
/** Every request id with a live awaiter, across all op maps. */
|
|
66877
|
+
allPendingIds() {
|
|
66878
|
+
const ids = [];
|
|
66879
|
+
for (const map2 of this.pendingMaps())
|
|
66880
|
+
for (const id of map2.keys())
|
|
66881
|
+
ids.push(id);
|
|
66882
|
+
return ids;
|
|
66883
|
+
}
|
|
66884
|
+
pendingMaps() {
|
|
66885
|
+
return [
|
|
66886
|
+
this.pending,
|
|
66887
|
+
this.pendingReadCookies,
|
|
66888
|
+
this.pendingStorage,
|
|
66889
|
+
this.pendingWriteCookies,
|
|
66890
|
+
this.pendingCapture,
|
|
66891
|
+
this.pendingRedirect,
|
|
66892
|
+
this.pendingIdb,
|
|
66893
|
+
this.pendingDomList,
|
|
66894
|
+
this.pendingDownload,
|
|
66895
|
+
this.pendingGraphql
|
|
66896
|
+
];
|
|
66897
|
+
}
|
|
66898
|
+
isPendingId(id) {
|
|
66899
|
+
return this.pendingMaps().some((m) => m.has(id));
|
|
66900
|
+
}
|
|
66901
|
+
/** Fail the one awaiter registered under `id`, in its own map's shape. */
|
|
66902
|
+
rejectPendingId(id, reason) {
|
|
66367
66903
|
const err = new FetchproxyProtocolError(reason);
|
|
66368
|
-
|
|
66369
|
-
|
|
66904
|
+
this.resendable.delete(id);
|
|
66905
|
+
const fetchCb = this.pending.get(id);
|
|
66906
|
+
if (fetchCb) {
|
|
66907
|
+
this.pending.delete(id);
|
|
66908
|
+
fetchCb({
|
|
66370
66909
|
ok: false,
|
|
66371
66910
|
error: err.message,
|
|
66372
66911
|
kind: classifyFetchError(err.message),
|
|
66373
66912
|
retryAttempted: false
|
|
66374
66913
|
});
|
|
66914
|
+
return;
|
|
66915
|
+
}
|
|
66916
|
+
const cookieCb = this.pendingReadCookies.get(id);
|
|
66917
|
+
if (cookieCb) {
|
|
66918
|
+
this.pendingReadCookies.delete(id);
|
|
66919
|
+
cookieCb({ ok: false, error: err.message });
|
|
66920
|
+
return;
|
|
66921
|
+
}
|
|
66922
|
+
for (const map2 of [
|
|
66923
|
+
this.pendingStorage,
|
|
66924
|
+
this.pendingWriteCookies,
|
|
66925
|
+
this.pendingCapture,
|
|
66926
|
+
this.pendingRedirect,
|
|
66927
|
+
this.pendingIdb,
|
|
66928
|
+
this.pendingDomList,
|
|
66929
|
+
this.pendingDownload,
|
|
66930
|
+
this.pendingGraphql
|
|
66931
|
+
]) {
|
|
66932
|
+
const entry = map2.get(id);
|
|
66933
|
+
if (entry) {
|
|
66934
|
+
map2.delete(id);
|
|
66935
|
+
entry.reject(err);
|
|
66936
|
+
return;
|
|
66937
|
+
}
|
|
66938
|
+
}
|
|
66939
|
+
}
|
|
66940
|
+
noteResendable(inner) {
|
|
66941
|
+
this.resendable.set(inner.id, inner);
|
|
66942
|
+
if (this.resendable.size > 256) {
|
|
66943
|
+
for (const id of [...this.resendable.keys()]) {
|
|
66944
|
+
if (!this.isPendingId(id))
|
|
66945
|
+
this.resendable.delete(id);
|
|
66946
|
+
}
|
|
66375
66947
|
}
|
|
66376
|
-
|
|
66377
|
-
|
|
66378
|
-
|
|
66379
|
-
|
|
66380
|
-
|
|
66381
|
-
|
|
66382
|
-
|
|
66383
|
-
|
|
66384
|
-
|
|
66385
|
-
|
|
66386
|
-
|
|
66387
|
-
|
|
66388
|
-
|
|
66389
|
-
|
|
66390
|
-
|
|
66391
|
-
|
|
66392
|
-
this.
|
|
66393
|
-
|
|
66394
|
-
|
|
66395
|
-
|
|
66396
|
-
|
|
66397
|
-
|
|
66398
|
-
|
|
66399
|
-
|
|
66400
|
-
|
|
66401
|
-
|
|
66948
|
+
}
|
|
66949
|
+
/**
|
|
66950
|
+
* B-BUG-6: the host this peer relayed through has gone — most often a
|
|
66951
|
+
* short-lived process (a bootstrap lift, an `fpx` call) that won the
|
|
66952
|
+
* election and then closed. Fail only the in-flight requests that may
|
|
66953
|
+
* already have run (a non-idempotent fetch, a cookie write, a download);
|
|
66954
|
+
* re-elect and send the rest again. The replies to the originals died with
|
|
66955
|
+
* the old host, and each re-sent request keeps its awaiter and its
|
|
66956
|
+
* original deadline.
|
|
66957
|
+
*/
|
|
66958
|
+
async recoverFromHostLoss() {
|
|
66959
|
+
const toResend = /* @__PURE__ */ new Map();
|
|
66960
|
+
for (const [id, inner] of this.resendable) {
|
|
66961
|
+
if (this.isPendingId(id))
|
|
66962
|
+
toResend.set(id, inner);
|
|
66963
|
+
}
|
|
66964
|
+
this.resendable.clear();
|
|
66965
|
+
this.rejectAllPending("fetchproxy: the MCP process holding the bridge port exited while this request was in flight. It was not re-sent because it may already have run in the browser \u2014 check before retrying.", new Set(toResend.keys()));
|
|
66966
|
+
if (toResend.size === 0)
|
|
66967
|
+
return;
|
|
66968
|
+
const lost = (id, e) => this.rejectPendingId(id, `fetchproxy: the bridge host exited and reconnecting failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
66969
|
+
try {
|
|
66970
|
+
await this.ensureConnected();
|
|
66971
|
+
} catch (e) {
|
|
66972
|
+
for (const id of toResend.keys())
|
|
66973
|
+
lost(id, e);
|
|
66974
|
+
return;
|
|
66975
|
+
}
|
|
66976
|
+
for (const [id, inner] of toResend) {
|
|
66977
|
+
if (!this.isPendingId(id))
|
|
66978
|
+
continue;
|
|
66979
|
+
try {
|
|
66980
|
+
await this.resendInnerFrame(inner);
|
|
66981
|
+
} catch (e) {
|
|
66982
|
+
lost(id, e);
|
|
66983
|
+
}
|
|
66984
|
+
}
|
|
66985
|
+
}
|
|
66986
|
+
/** Send an already-registered request again, keeping its awaiter on failure. */
|
|
66987
|
+
async resendInnerFrame(inner) {
|
|
66988
|
+
if (this.hostHandle)
|
|
66989
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
66990
|
+
else if (this.peerHandle)
|
|
66991
|
+
await this.peerHandle.sendInner(inner);
|
|
66992
|
+
else
|
|
66993
|
+
throw new Error("no bridge handle after re-election");
|
|
66994
|
+
this.noteResendable(inner);
|
|
66402
66995
|
}
|
|
66403
66996
|
/**
|
|
66404
66997
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -66501,6 +67094,12 @@ async function runOneLift(opts) {
|
|
|
66501
67094
|
if (envVal !== void 0 && envVal !== "" && envVal !== "0" && envVal !== "false") {
|
|
66502
67095
|
throw new BootstrapDisabledError(opts.serverName, envVar);
|
|
66503
67096
|
}
|
|
67097
|
+
for (const bucket of ["cookies", "localStorage", "sessionStorage"]) {
|
|
67098
|
+
const glob = opts.declare[bucket].find((k) => k.endsWith("*"));
|
|
67099
|
+
if (glob !== void 0) {
|
|
67100
|
+
throw new Error(`fetchproxy bootstrap: declare.${bucket} contains ${JSON.stringify(glob)}, a glob. A lift reads each key by its exact name, so a glob can never match \u2014 list the concrete key names instead.`);
|
|
67101
|
+
}
|
|
67102
|
+
}
|
|
66504
67103
|
const factory = opts._serverFactory ?? defaultFactory;
|
|
66505
67104
|
const indexedDb = opts.declare.indexedDb ?? [];
|
|
66506
67105
|
const localStoragePointers = opts.declare.localStoragePointers ?? [];
|
|
@@ -66863,7 +67462,7 @@ function loadAccount(env = process.env) {
|
|
|
66863
67462
|
// package.json
|
|
66864
67463
|
var package_default = {
|
|
66865
67464
|
name: "infinitecampus-mcp",
|
|
66866
|
-
version: "3.1.
|
|
67465
|
+
version: "3.1.2",
|
|
66867
67466
|
mcpName: "io.github.chrischall/infinitecampus-mcp",
|
|
66868
67467
|
description: "Infinite Campus (Campus Parent) MCP server \u2014 multi-district read + message/document write",
|
|
66869
67468
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -66908,13 +67507,13 @@ var package_default = {
|
|
|
66908
67507
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
66909
67508
|
},
|
|
66910
67509
|
dependencies: {
|
|
66911
|
-
"@chrischall/mcp-utils": "^
|
|
66912
|
-
"@fetchproxy/bootstrap": "^3.0
|
|
66913
|
-
"@fetchproxy/server": "^3.0
|
|
67510
|
+
"@chrischall/mcp-utils": "^2.4.0",
|
|
67511
|
+
"@fetchproxy/bootstrap": "^3.2.0",
|
|
67512
|
+
"@fetchproxy/server": "^3.2.0",
|
|
66914
67513
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
66915
67514
|
dotenv: "^17.4.2",
|
|
66916
67515
|
"node-html-parser": "^9.0.0",
|
|
66917
|
-
zod: "^4.6.
|
|
67516
|
+
zod: "^4.6.5"
|
|
66918
67517
|
},
|
|
66919
67518
|
devDependencies: {
|
|
66920
67519
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
@@ -67026,7 +67625,7 @@ import { dirname as dirname2 } from "path";
|
|
|
67026
67625
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
67027
67626
|
import { dirname, join as join3, resolve } from "node:path";
|
|
67028
67627
|
import { homedir as homedir2 } from "node:os";
|
|
67029
|
-
import { randomBytes as
|
|
67628
|
+
import { randomBytes as randomBytes3, createHmac } from "node:crypto";
|
|
67030
67629
|
function bindingDigest(secret, salt) {
|
|
67031
67630
|
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
67032
67631
|
}
|
|
@@ -67078,7 +67677,7 @@ function createFileStatePersistence(opts) {
|
|
|
67078
67677
|
},
|
|
67079
67678
|
save(state) {
|
|
67080
67679
|
const dir = dirname(filePath);
|
|
67081
|
-
const tmp = `${filePath}.tmp-${
|
|
67680
|
+
const tmp = `${filePath}.tmp-${randomBytes3(6).toString("hex")}`;
|
|
67082
67681
|
const dirExisted = existsSync(dir);
|
|
67083
67682
|
try {
|
|
67084
67683
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -67086,7 +67685,7 @@ function createFileStatePersistence(opts) {
|
|
|
67086
67685
|
chmodSync(dir, 448);
|
|
67087
67686
|
let boundTo;
|
|
67088
67687
|
if (secret !== void 0) {
|
|
67089
|
-
const salt =
|
|
67688
|
+
const salt = randomBytes3(16).toString("hex");
|
|
67090
67689
|
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
67091
67690
|
}
|
|
67092
67691
|
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
@@ -68280,7 +68879,7 @@ function extractPlainTextFromHtml(html) {
|
|
|
68280
68879
|
return text.replace(/\s+/g, " ").trim();
|
|
68281
68880
|
}
|
|
68282
68881
|
function stripElementContent(html, tag) {
|
|
68283
|
-
const lower = html
|
|
68882
|
+
const lower = asciiLower(html);
|
|
68284
68883
|
const open2 = `<${tag}`;
|
|
68285
68884
|
const close = `</${tag}>`;
|
|
68286
68885
|
let out = "";
|
|
@@ -68882,7 +69481,7 @@ try {
|
|
|
68882
69481
|
}
|
|
68883
69482
|
var COMMON = {
|
|
68884
69483
|
name: "infinitecampus",
|
|
68885
|
-
version: "3.1.
|
|
69484
|
+
version: "3.1.2"
|
|
68886
69485
|
// x-release-please-version
|
|
68887
69486
|
};
|
|
68888
69487
|
var healthState = { account, source, configError, client: null };
|