canvas-parent-mcp 2.1.1 → 2.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 +934 -286
- 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;
|
|
@@ -41662,13 +41676,28 @@ var API_KEY_RE = new RegExp([
|
|
|
41662
41676
|
"whsec_[A-Za-z0-9]{16,}\\b"
|
|
41663
41677
|
// webhook signing secret (Stripe-style)
|
|
41664
41678
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
41665
|
-
var QUERY_SECRET_RE = /([?&](?:
|
|
41679
|
+
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;
|
|
41680
|
+
var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
41681
|
+
var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
41682
|
+
var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
|
|
41666
41683
|
var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
|
|
41667
|
-
var JSON_SECRET_KEYS =
|
|
41668
|
-
|
|
41669
|
-
|
|
41684
|
+
var JSON_SECRET_KEYS = [
|
|
41685
|
+
"(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
|
|
41686
|
+
"client[_-]?secret",
|
|
41687
|
+
"api[_-]?secret",
|
|
41688
|
+
"(?:x[_-])?api[_-]?key",
|
|
41689
|
+
"private[_-]?key",
|
|
41690
|
+
"x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
|
|
41691
|
+
"(?:proxy-)?authorization",
|
|
41692
|
+
"password",
|
|
41693
|
+
"passwd",
|
|
41694
|
+
"secret",
|
|
41695
|
+
"token"
|
|
41696
|
+
].join("|");
|
|
41697
|
+
var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
|
|
41698
|
+
var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
|
|
41670
41699
|
function redactSecrets(text) {
|
|
41671
|
-
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]");
|
|
41700
|
+
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]");
|
|
41672
41701
|
}
|
|
41673
41702
|
function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
|
|
41674
41703
|
const str = text === null || text === void 0 ? "" : String(text);
|
|
@@ -61447,17 +61476,73 @@ Hint: ${err.hint}`);
|
|
|
61447
61476
|
}
|
|
61448
61477
|
throw err;
|
|
61449
61478
|
}
|
|
61479
|
+
function mcpRequestFrom(args) {
|
|
61480
|
+
const ctx = args.at(-1);
|
|
61481
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61482
|
+
return void 0;
|
|
61483
|
+
const req = ctx.mcpReq;
|
|
61484
|
+
if (typeof req !== "object" || req === null)
|
|
61485
|
+
return void 0;
|
|
61486
|
+
return typeof req.notify === "function" ? req : void 0;
|
|
61487
|
+
}
|
|
61488
|
+
function callSignalFrom(args) {
|
|
61489
|
+
const ctx = args.at(-1);
|
|
61490
|
+
if (typeof ctx !== "object" || ctx === null)
|
|
61491
|
+
return void 0;
|
|
61492
|
+
const req = ctx.mcpReq;
|
|
61493
|
+
if (typeof req !== "object" || req === null)
|
|
61494
|
+
return void 0;
|
|
61495
|
+
const signal = req.signal;
|
|
61496
|
+
return signal instanceof AbortSignal ? signal : void 0;
|
|
61497
|
+
}
|
|
61498
|
+
function declaredCapabilitiesFrom(server) {
|
|
61499
|
+
const inner = server.server;
|
|
61500
|
+
if (typeof inner !== "object" || inner === null)
|
|
61501
|
+
return void 0;
|
|
61502
|
+
const read = inner.getClientCapabilities;
|
|
61503
|
+
if (typeof read !== "function")
|
|
61504
|
+
return void 0;
|
|
61505
|
+
try {
|
|
61506
|
+
const declared = read.call(inner);
|
|
61507
|
+
return typeof declared === "object" && declared !== null ? declared : void 0;
|
|
61508
|
+
} catch {
|
|
61509
|
+
return void 0;
|
|
61510
|
+
}
|
|
61511
|
+
}
|
|
61450
61512
|
function surfaceToolHints(server) {
|
|
61451
61513
|
const register2 = server.registerTool.bind(server);
|
|
61452
|
-
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) =>
|
|
61453
|
-
|
|
61454
|
-
|
|
61455
|
-
|
|
61456
|
-
|
|
61457
|
-
|
|
61458
|
-
|
|
61459
|
-
|
|
61460
|
-
|
|
61514
|
+
server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
|
|
61515
|
+
// THE CALLER'S CANCELLATION, made ambient for the whole handler
|
|
61516
|
+
// (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
|
|
61517
|
+
// measured against @modelcontextprotocol/server 2.0.0, a cancelled
|
|
61518
|
+
// call aborts `ctx.mcpReq.signal` with the caller's reason and the
|
|
61519
|
+
// handler runs to completion regardless — so the HTTP request stays
|
|
61520
|
+
// in flight, the child keeps burning metered CPU, and the upstream
|
|
61521
|
+
// keeps being hit for somebody who has gone. claude.ai sent 101
|
|
61522
|
+
// cancellations in the week to 2026-09-20.
|
|
61523
|
+
//
|
|
61524
|
+
// Here because this is the one wrapper every tool already passes
|
|
61525
|
+
// through: threading the signal by hand would mean editing several
|
|
61526
|
+
// hundred handlers and missing exactly the ones nobody edits.
|
|
61527
|
+
withCallSignal(callSignalFrom(args), () => (
|
|
61528
|
+
// WHAT THE CALLER CAN DO, made ambient for the same reason and in
|
|
61529
|
+
// the same place (`caller/index.ts`). Only the 2025-era half needs
|
|
61530
|
+
// the wrapper — a 2026-07-28 request answers for itself off its own
|
|
61531
|
+
// envelope — but a guarded tool that cannot tell whether the caller
|
|
61532
|
+
// can be shown a prompt returns one the SDK then refuses to deliver,
|
|
61533
|
+
// and that refusal reaches the caller as an unexplained protocol
|
|
61534
|
+
// error it had no chance to catch.
|
|
61535
|
+
withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
|
|
61536
|
+
let result;
|
|
61537
|
+
try {
|
|
61538
|
+
result = cb(...args);
|
|
61539
|
+
} catch (err) {
|
|
61540
|
+
return hintResultOrRethrow(err);
|
|
61541
|
+
}
|
|
61542
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
61543
|
+
})
|
|
61544
|
+
), mcpRequestFrom(args))
|
|
61545
|
+
));
|
|
61461
61546
|
}
|
|
61462
61547
|
async function createMcpServer(opts) {
|
|
61463
61548
|
const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
|
|
@@ -61472,13 +61557,47 @@ async function createMcpServer(opts) {
|
|
|
61472
61557
|
}
|
|
61473
61558
|
return server;
|
|
61474
61559
|
}
|
|
61560
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
61561
|
+
var DEFAULT_REPEAT_SIGNAL_GRACE_MS = 500;
|
|
61475
61562
|
function withGracefulShutdown(target, opts = {}) {
|
|
61476
61563
|
const shouldExit = opts.exit ?? true;
|
|
61564
|
+
if (opts.timeoutMs !== void 0 && !(Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)) {
|
|
61565
|
+
throw new RangeError("withGracefulShutdown: timeoutMs must be a finite number greater than 0.");
|
|
61566
|
+
}
|
|
61567
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
61568
|
+
if (opts.repeatSignalGraceMs !== void 0 && !(Number.isFinite(opts.repeatSignalGraceMs) && opts.repeatSignalGraceMs >= 0)) {
|
|
61569
|
+
throw new RangeError("withGracefulShutdown: repeatSignalGraceMs must be a finite number >= 0.");
|
|
61570
|
+
}
|
|
61571
|
+
const repeatGraceMs = opts.repeatSignalGraceMs ?? DEFAULT_REPEAT_SIGNAL_GRACE_MS;
|
|
61477
61572
|
let shuttingDown = false;
|
|
61573
|
+
let firstSignalAt = 0;
|
|
61574
|
+
let exited = false;
|
|
61575
|
+
const exitOnce = () => {
|
|
61576
|
+
if (exited)
|
|
61577
|
+
return;
|
|
61578
|
+
exited = true;
|
|
61579
|
+
process.exit(0);
|
|
61580
|
+
};
|
|
61478
61581
|
const handler = (signal) => {
|
|
61479
|
-
if (shuttingDown)
|
|
61582
|
+
if (shuttingDown) {
|
|
61583
|
+
if (Date.now() - firstSignalAt < repeatGraceMs)
|
|
61584
|
+
return;
|
|
61585
|
+
if (shouldExit) {
|
|
61586
|
+
console.error(`[mcp-utils] second ${signal} during shutdown \u2014 exiting now`);
|
|
61587
|
+
exitOnce();
|
|
61588
|
+
}
|
|
61480
61589
|
return;
|
|
61590
|
+
}
|
|
61481
61591
|
shuttingDown = true;
|
|
61592
|
+
firstSignalAt = Date.now();
|
|
61593
|
+
let timer;
|
|
61594
|
+
if (shouldExit) {
|
|
61595
|
+
timer = setTimeout(() => {
|
|
61596
|
+
console.error(`[mcp-utils] graceful shutdown on ${signal} did not finish in ${timeoutMs}ms \u2014 exiting`);
|
|
61597
|
+
exitOnce();
|
|
61598
|
+
}, timeoutMs);
|
|
61599
|
+
timer.unref?.();
|
|
61600
|
+
}
|
|
61482
61601
|
void (async () => {
|
|
61483
61602
|
try {
|
|
61484
61603
|
if (opts.onSignal)
|
|
@@ -61487,8 +61606,10 @@ function withGracefulShutdown(target, opts = {}) {
|
|
|
61487
61606
|
} catch (err) {
|
|
61488
61607
|
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
61489
61608
|
} finally {
|
|
61609
|
+
if (timer !== void 0)
|
|
61610
|
+
clearTimeout(timer);
|
|
61490
61611
|
if (shouldExit)
|
|
61491
|
-
|
|
61612
|
+
exitOnce();
|
|
61492
61613
|
}
|
|
61493
61614
|
})();
|
|
61494
61615
|
};
|
|
@@ -61695,6 +61816,33 @@ var pageSchema = {
|
|
|
61695
61816
|
};
|
|
61696
61817
|
|
|
61697
61818
|
// node_modules/@chrischall/mcp-utils/dist/auth/index.js
|
|
61819
|
+
var OAuth2RefreshError = class extends McpToolError {
|
|
61820
|
+
status;
|
|
61821
|
+
constructor(status, message, opts) {
|
|
61822
|
+
super(message, opts);
|
|
61823
|
+
this.name = "OAuth2RefreshError";
|
|
61824
|
+
this.status = status;
|
|
61825
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
61826
|
+
}
|
|
61827
|
+
};
|
|
61828
|
+
var OAuth2RotationPersistError = class extends McpToolError {
|
|
61829
|
+
persistenceFailure = true;
|
|
61830
|
+
result;
|
|
61831
|
+
constructor(result, cause) {
|
|
61832
|
+
super(`OAuth2 refresh token rotated, but saving it failed: ${truncateErrorMessage(messageOf2(cause), 200)}`, {
|
|
61833
|
+
hint: "The new refresh token is still held in memory; fix the storage problem before the process restarts.",
|
|
61834
|
+
cause
|
|
61835
|
+
});
|
|
61836
|
+
this.name = "OAuth2RotationPersistError";
|
|
61837
|
+
this.result = result;
|
|
61838
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
61839
|
+
}
|
|
61840
|
+
};
|
|
61841
|
+
function isRetryable(err) {
|
|
61842
|
+
if (!(err instanceof OAuth2RefreshError))
|
|
61843
|
+
return true;
|
|
61844
|
+
return err.status >= 500 || err.status === 429 || err.status === 408;
|
|
61845
|
+
}
|
|
61698
61846
|
var sleep2 = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
61699
61847
|
function createOAuth2Refresher(opts) {
|
|
61700
61848
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
@@ -61702,10 +61850,13 @@ function createOAuth2Refresher(opts) {
|
|
|
61702
61850
|
const maxRetries = opts.retry?.count ?? 0;
|
|
61703
61851
|
const retryDelayMs = opts.retry?.delayMs ?? 0;
|
|
61704
61852
|
let inFlight = null;
|
|
61853
|
+
let currentRefreshToken = opts.refreshToken;
|
|
61854
|
+
const spent = /* @__PURE__ */ new Set();
|
|
61855
|
+
let rotatedTo;
|
|
61705
61856
|
async function exchangeOnce() {
|
|
61706
61857
|
const body = new URLSearchParams({
|
|
61707
61858
|
grant_type: grantType,
|
|
61708
|
-
refresh_token:
|
|
61859
|
+
refresh_token: currentRefreshToken,
|
|
61709
61860
|
...opts.params
|
|
61710
61861
|
}).toString();
|
|
61711
61862
|
const res = await doFetch(opts.endpoint, {
|
|
@@ -61718,7 +61869,7 @@ function createOAuth2Refresher(opts) {
|
|
|
61718
61869
|
});
|
|
61719
61870
|
if (!res.ok) {
|
|
61720
61871
|
const errText = await res.text().catch(() => "");
|
|
61721
|
-
throw
|
|
61872
|
+
throw new OAuth2RefreshError(res.status, `OAuth2 token refresh failed: ${res.status} ${res.statusText}: ${truncateErrorMessage(errText, 200)}`, { hint: "The refresh token may be expired or revoked \u2014 re-authenticate." });
|
|
61722
61873
|
}
|
|
61723
61874
|
const data = await res.json().catch(() => null);
|
|
61724
61875
|
const accessToken = data?.access_token;
|
|
@@ -61730,6 +61881,11 @@ function createOAuth2Refresher(opts) {
|
|
|
61730
61881
|
const result = { accessToken };
|
|
61731
61882
|
if (typeof data?.refresh_token === "string" && data.refresh_token.length > 0) {
|
|
61732
61883
|
result.refreshToken = data.refresh_token;
|
|
61884
|
+
if (data.refresh_token !== currentRefreshToken) {
|
|
61885
|
+
spent.add(currentRefreshToken);
|
|
61886
|
+
currentRefreshToken = data.refresh_token;
|
|
61887
|
+
rotatedTo = data.refresh_token;
|
|
61888
|
+
}
|
|
61733
61889
|
}
|
|
61734
61890
|
if (typeof data?.expires_in === "number") {
|
|
61735
61891
|
result.expiresIn = data.expires_in;
|
|
@@ -61744,16 +61900,32 @@ function createOAuth2Refresher(opts) {
|
|
|
61744
61900
|
return await exchangeOnce();
|
|
61745
61901
|
} catch (e) {
|
|
61746
61902
|
lastErr = e;
|
|
61903
|
+
if (!isRetryable(e))
|
|
61904
|
+
break;
|
|
61747
61905
|
if (attempt < maxRetries)
|
|
61748
61906
|
await sleep2(retryDelayMs);
|
|
61749
61907
|
}
|
|
61750
61908
|
}
|
|
61751
61909
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
61752
61910
|
}
|
|
61753
|
-
return function refresh() {
|
|
61911
|
+
return function refresh(refreshToken) {
|
|
61754
61912
|
if (inFlight)
|
|
61755
61913
|
return inFlight;
|
|
61756
|
-
|
|
61914
|
+
if (typeof refreshToken === "string" && refreshToken.length > 0 && !spent.has(refreshToken)) {
|
|
61915
|
+
currentRefreshToken = refreshToken;
|
|
61916
|
+
}
|
|
61917
|
+
const p = (async () => {
|
|
61918
|
+
rotatedTo = void 0;
|
|
61919
|
+
const result = await exchangeWithRetry();
|
|
61920
|
+
if (rotatedTo !== void 0 && opts.onRotate) {
|
|
61921
|
+
try {
|
|
61922
|
+
await opts.onRotate(rotatedTo);
|
|
61923
|
+
} catch (err) {
|
|
61924
|
+
throw new OAuth2RotationPersistError(result, err);
|
|
61925
|
+
}
|
|
61926
|
+
}
|
|
61927
|
+
return result;
|
|
61928
|
+
})().finally(() => {
|
|
61757
61929
|
if (inFlight === p)
|
|
61758
61930
|
inFlight = null;
|
|
61759
61931
|
});
|
|
@@ -61761,6 +61933,9 @@ function createOAuth2Refresher(opts) {
|
|
|
61761
61933
|
return p;
|
|
61762
61934
|
};
|
|
61763
61935
|
}
|
|
61936
|
+
function messageOf2(err) {
|
|
61937
|
+
return err instanceof Error ? err.message : String(err);
|
|
61938
|
+
}
|
|
61764
61939
|
|
|
61765
61940
|
// node_modules/@fetchproxy/protocol/dist/crypto.js
|
|
61766
61941
|
var subtle = globalThis.crypto.subtle;
|
|
@@ -61959,6 +62134,7 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
61959
62134
|
"capture_redirect",
|
|
61960
62135
|
"read_indexed_db",
|
|
61961
62136
|
"read_dom",
|
|
62137
|
+
"read_dom_list",
|
|
61962
62138
|
"download",
|
|
61963
62139
|
"graphql",
|
|
61964
62140
|
"write_cookies"
|
|
@@ -62459,6 +62635,79 @@ function assertDomSelectorsArray(value, label) {
|
|
|
62459
62635
|
}
|
|
62460
62636
|
}
|
|
62461
62637
|
}
|
|
62638
|
+
function assertDomListSelectorsArray(value, label) {
|
|
62639
|
+
if (!Array.isArray(value)) {
|
|
62640
|
+
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
62641
|
+
}
|
|
62642
|
+
const seen = /* @__PURE__ */ new Set();
|
|
62643
|
+
for (let i = 0; i < value.length; i++) {
|
|
62644
|
+
const entry = value[i];
|
|
62645
|
+
assertObject(entry, `${label}[${i}]`);
|
|
62646
|
+
if (entry.name === void 0) {
|
|
62647
|
+
throw new ProtocolError3(`${label}[${i}].name: missing`);
|
|
62648
|
+
}
|
|
62649
|
+
if (entry.itemSelector === void 0) {
|
|
62650
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: missing`);
|
|
62651
|
+
}
|
|
62652
|
+
if (entry.fields === void 0) {
|
|
62653
|
+
throw new ProtocolError3(`${label}[${i}].fields: missing`);
|
|
62654
|
+
}
|
|
62655
|
+
if (typeof entry.name !== "string" || !SCOPE_KEY_RE.test(entry.name)) {
|
|
62656
|
+
throw new ProtocolError3(`${label}[${i}].name: invalid ${JSON.stringify(entry.name)}`);
|
|
62657
|
+
}
|
|
62658
|
+
if (typeof entry.itemSelector !== "string" || !DOM_SELECTOR_RE.test(entry.itemSelector)) {
|
|
62659
|
+
throw new ProtocolError3(`${label}[${i}].itemSelector: invalid ${JSON.stringify(entry.itemSelector)}`);
|
|
62660
|
+
}
|
|
62661
|
+
if (!Array.isArray(entry.fields) || entry.fields.length === 0) {
|
|
62662
|
+
throw new ProtocolError3(`${label}[${i}].fields: expected non-empty array`);
|
|
62663
|
+
}
|
|
62664
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
62665
|
+
for (let j = 0; j < entry.fields.length; j++) {
|
|
62666
|
+
const field = entry.fields[j];
|
|
62667
|
+
const fieldLabel = `${label}[${i}].fields[${j}]`;
|
|
62668
|
+
assertObject(field, fieldLabel);
|
|
62669
|
+
if (field.name === void 0) {
|
|
62670
|
+
throw new ProtocolError3(`${fieldLabel}.name: missing`);
|
|
62671
|
+
}
|
|
62672
|
+
if (typeof field.name !== "string" || !SCOPE_KEY_RE.test(field.name)) {
|
|
62673
|
+
throw new ProtocolError3(`${fieldLabel}.name: invalid ${JSON.stringify(field.name)}`);
|
|
62674
|
+
}
|
|
62675
|
+
if (field.selector !== void 0) {
|
|
62676
|
+
if (typeof field.selector !== "string" || !DOM_SELECTOR_RE.test(field.selector)) {
|
|
62677
|
+
throw new ProtocolError3(`${fieldLabel}.selector: invalid ${JSON.stringify(field.selector)}`);
|
|
62678
|
+
}
|
|
62679
|
+
}
|
|
62680
|
+
if (field.attribute !== void 0) {
|
|
62681
|
+
if (typeof field.attribute !== "string" || !DOM_ATTRIBUTE_RE.test(field.attribute)) {
|
|
62682
|
+
throw new ProtocolError3(`${fieldLabel}.attribute: invalid ${JSON.stringify(field.attribute)}`);
|
|
62683
|
+
}
|
|
62684
|
+
}
|
|
62685
|
+
if (fieldNames.has(field.name)) {
|
|
62686
|
+
throw new ProtocolError3(`${label}[${i}].fields: duplicate name ${JSON.stringify(field.name)}`);
|
|
62687
|
+
}
|
|
62688
|
+
fieldNames.add(field.name);
|
|
62689
|
+
for (const k of Object.keys(field)) {
|
|
62690
|
+
if (k !== "name" && k !== "selector" && k !== "attribute") {
|
|
62691
|
+
throw new ProtocolError3(`${fieldLabel}: unexpected field ${JSON.stringify(k)}`);
|
|
62692
|
+
}
|
|
62693
|
+
}
|
|
62694
|
+
}
|
|
62695
|
+
if (entry.maxItems !== void 0) {
|
|
62696
|
+
if (typeof entry.maxItems !== "number" || !Number.isInteger(entry.maxItems) || entry.maxItems < 1 || entry.maxItems > 1e3) {
|
|
62697
|
+
throw new ProtocolError3(`${label}[${i}].maxItems: invalid ${JSON.stringify(entry.maxItems)}`);
|
|
62698
|
+
}
|
|
62699
|
+
}
|
|
62700
|
+
if (seen.has(entry.name)) {
|
|
62701
|
+
throw new ProtocolError3(`${label}: duplicate name ${JSON.stringify(entry.name)}`);
|
|
62702
|
+
}
|
|
62703
|
+
seen.add(entry.name);
|
|
62704
|
+
for (const k of Object.keys(entry)) {
|
|
62705
|
+
if (k !== "name" && k !== "itemSelector" && k !== "fields" && k !== "maxItems") {
|
|
62706
|
+
throw new ProtocolError3(`${label}[${i}]: unexpected field ${JSON.stringify(k)}`);
|
|
62707
|
+
}
|
|
62708
|
+
}
|
|
62709
|
+
}
|
|
62710
|
+
}
|
|
62462
62711
|
function assertGraphqlOpsArray(value, label) {
|
|
62463
62712
|
if (!Array.isArray(value)) {
|
|
62464
62713
|
throw new ProtocolError3(`${label}: expected array, got ${typeof value}`);
|
|
@@ -62505,6 +62754,8 @@ function validateFrame(raw) {
|
|
|
62505
62754
|
return validateHelloRejected(raw);
|
|
62506
62755
|
if (t === "extension-disconnected")
|
|
62507
62756
|
return { type: "extension-disconnected" };
|
|
62757
|
+
if (t === "peer-gone")
|
|
62758
|
+
return validatePeerGone(raw);
|
|
62508
62759
|
throw new ProtocolError3(`unknown frame type: ${String(t)}`);
|
|
62509
62760
|
}
|
|
62510
62761
|
function validateHello(raw) {
|
|
@@ -62575,16 +62826,11 @@ function validateHello(raw) {
|
|
|
62575
62826
|
if (raw.domSelectors !== void 0) {
|
|
62576
62827
|
assertDomSelectorsArray(raw.domSelectors, "hello.domSelectors");
|
|
62577
62828
|
}
|
|
62578
|
-
if (raw.
|
|
62579
|
-
|
|
62580
|
-
throw new ProtocolError3("hello.accepts: expected array");
|
|
62581
|
-
}
|
|
62582
|
-
for (const a of raw.accepts) {
|
|
62583
|
-
if (typeof a !== "string") {
|
|
62584
|
-
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62585
|
-
}
|
|
62586
|
-
}
|
|
62829
|
+
if (raw.domListSelectors !== void 0) {
|
|
62830
|
+
assertDomListSelectorsArray(raw.domListSelectors, "hello.domListSelectors");
|
|
62587
62831
|
}
|
|
62832
|
+
if (raw.accepts !== void 0)
|
|
62833
|
+
assertAcceptsList(raw.accepts);
|
|
62588
62834
|
if (raw.graphqlOps !== void 0) {
|
|
62589
62835
|
assertGraphqlOpsArray(raw.graphqlOps, "hello.graphqlOps");
|
|
62590
62836
|
}
|
|
@@ -62606,6 +62852,8 @@ function validateHello(raw) {
|
|
|
62606
62852
|
assertBase64(raw.identityX25519Pub, "hello.identityX25519Pub");
|
|
62607
62853
|
assertBase64(raw.identityEd25519Pub, "hello.identityEd25519Pub");
|
|
62608
62854
|
assertBase64(raw.sessionNonce, "hello.sessionNonce");
|
|
62855
|
+
if (raw.accepts !== void 0)
|
|
62856
|
+
assertAcceptsList(raw.accepts);
|
|
62609
62857
|
return raw;
|
|
62610
62858
|
}
|
|
62611
62859
|
throw new ProtocolError3(`hello.role: must be 'server' or 'extension', got ${String(role)}`);
|
|
@@ -62661,6 +62909,27 @@ function validatePairPending(raw) {
|
|
|
62661
62909
|
}
|
|
62662
62910
|
return { type: "pair-pending", mcpId: raw.mcpId, pairCode: raw.pairCode };
|
|
62663
62911
|
}
|
|
62912
|
+
function assertAcceptsList(accepts) {
|
|
62913
|
+
if (!Array.isArray(accepts)) {
|
|
62914
|
+
throw new ProtocolError3("hello.accepts: expected array");
|
|
62915
|
+
}
|
|
62916
|
+
for (const a of accepts) {
|
|
62917
|
+
if (typeof a !== "string") {
|
|
62918
|
+
throw new ProtocolError3(`hello.accepts: entry must be string, got ${typeof a}`);
|
|
62919
|
+
}
|
|
62920
|
+
}
|
|
62921
|
+
}
|
|
62922
|
+
function validatePeerGone(raw) {
|
|
62923
|
+
assertString(raw.mcpId, "peer-gone.mcpId");
|
|
62924
|
+
if (!isValidMcpId(raw.mcpId))
|
|
62925
|
+
throw new ProtocolError3("peer-gone.mcpId: invalid format");
|
|
62926
|
+
for (const k of Object.keys(raw)) {
|
|
62927
|
+
if (k !== "type" && k !== "mcpId") {
|
|
62928
|
+
throw new ProtocolError3(`peer-gone: unexpected field ${JSON.stringify(k)}`);
|
|
62929
|
+
}
|
|
62930
|
+
}
|
|
62931
|
+
return { type: "peer-gone", mcpId: raw.mcpId };
|
|
62932
|
+
}
|
|
62664
62933
|
function validateHelloRejected(raw) {
|
|
62665
62934
|
assertString(raw.mcpId, "hello-rejected.mcpId");
|
|
62666
62935
|
if (!isValidMcpId(raw.mcpId))
|
|
@@ -62910,6 +63179,24 @@ function validateInnerRequest(raw) {
|
|
|
62910
63179
|
}
|
|
62911
63180
|
return raw;
|
|
62912
63181
|
}
|
|
63182
|
+
if (raw.op === "read_dom_list") {
|
|
63183
|
+
assertObject(raw.init, "inner.init");
|
|
63184
|
+
if (raw.init.origin === void 0)
|
|
63185
|
+
throw new ProtocolError3("inner.init.origin: missing");
|
|
63186
|
+
if (raw.init.name === void 0)
|
|
63187
|
+
throw new ProtocolError3("inner.init.name: missing");
|
|
63188
|
+
assertHttpsOriginOnly(raw.init.origin, "inner.init.origin");
|
|
63189
|
+
assertString(raw.init.name, "inner.init.name");
|
|
63190
|
+
if (raw.init.name.length === 0) {
|
|
63191
|
+
throw new ProtocolError3("inner.init.name: must be non-empty");
|
|
63192
|
+
}
|
|
63193
|
+
for (const k of Object.keys(raw.init)) {
|
|
63194
|
+
if (k !== "origin" && k !== "name") {
|
|
63195
|
+
throw new ProtocolError3(`inner.init: unexpected field ${JSON.stringify(k)} on read_dom_list`);
|
|
63196
|
+
}
|
|
63197
|
+
}
|
|
63198
|
+
return raw;
|
|
63199
|
+
}
|
|
62913
63200
|
if (raw.op === "graphql_query") {
|
|
62914
63201
|
assertObject(raw.init, "inner.init");
|
|
62915
63202
|
if (raw.init.name === void 0)
|
|
@@ -62957,7 +63244,7 @@ function validateInnerRequest(raw) {
|
|
|
62957
63244
|
}
|
|
62958
63245
|
return raw;
|
|
62959
63246
|
}
|
|
62960
|
-
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)}`);
|
|
63247
|
+
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)}`);
|
|
62961
63248
|
}
|
|
62962
63249
|
function assertNonEmptyKeyArray(value, label) {
|
|
62963
63250
|
if (!Array.isArray(value)) {
|
|
@@ -63065,6 +63352,18 @@ function validateInnerResponse(raw) {
|
|
|
63065
63352
|
assertStringMap(raw.values, "inner.values");
|
|
63066
63353
|
return raw;
|
|
63067
63354
|
}
|
|
63355
|
+
if (op === "read_dom_list") {
|
|
63356
|
+
if (raw.rows === void 0) {
|
|
63357
|
+
throw new ProtocolError3("inner.rows: missing on read_dom_list response");
|
|
63358
|
+
}
|
|
63359
|
+
if (!Array.isArray(raw.rows)) {
|
|
63360
|
+
throw new ProtocolError3("inner.rows: expected array, got " + typeof raw.rows);
|
|
63361
|
+
}
|
|
63362
|
+
for (const [i, row] of raw.rows.entries()) {
|
|
63363
|
+
assertStringMap(row, `inner.rows[${i}]`);
|
|
63364
|
+
}
|
|
63365
|
+
return raw;
|
|
63366
|
+
}
|
|
63068
63367
|
if (op === "graphql_query") {
|
|
63069
63368
|
if (raw.data === void 0) {
|
|
63070
63369
|
throw new ProtocolError3("inner.data: missing on graphql_query response");
|
|
@@ -63166,12 +63465,6 @@ async function sealInnerFrame(sessionKey, mcpId, seq, inner, direction) {
|
|
|
63166
63465
|
ciphertext: toB64(ct)
|
|
63167
63466
|
};
|
|
63168
63467
|
}
|
|
63169
|
-
async function openEncryptedFrame(sessionKey, frame, direction) {
|
|
63170
|
-
const result = await openEncryptedFrameDetailed(sessionKey, frame, direction);
|
|
63171
|
-
if (result.stage === "ok")
|
|
63172
|
-
return result.inner;
|
|
63173
|
-
throw result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
63174
|
-
}
|
|
63175
63468
|
async function openEncryptedFrameDetailed(sessionKey, frame, direction) {
|
|
63176
63469
|
let pt;
|
|
63177
63470
|
try {
|
|
@@ -63323,6 +63616,18 @@ async function buildServerHello(opts) {
|
|
|
63323
63616
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
63324
63617
|
}));
|
|
63325
63618
|
}
|
|
63619
|
+
if (opts.domListSelectors && opts.domListSelectors.length > 0) {
|
|
63620
|
+
hello.domListSelectors = opts.domListSelectors.map((d) => ({
|
|
63621
|
+
name: d.name,
|
|
63622
|
+
itemSelector: d.itemSelector,
|
|
63623
|
+
fields: d.fields.map((f) => ({
|
|
63624
|
+
name: f.name,
|
|
63625
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
63626
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
63627
|
+
})),
|
|
63628
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
63629
|
+
}));
|
|
63630
|
+
}
|
|
63326
63631
|
if (opts.graphqlOps && opts.graphqlOps.length > 0) {
|
|
63327
63632
|
hello.graphqlOps = opts.graphqlOps.map((d) => ({
|
|
63328
63633
|
name: d.name,
|
|
@@ -63491,11 +63796,12 @@ async function awaitSessionReady(ready, opts) {
|
|
|
63491
63796
|
}
|
|
63492
63797
|
|
|
63493
63798
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
63494
|
-
import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
|
|
63799
|
+
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";
|
|
63800
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
63495
63801
|
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
63496
63802
|
|
|
63497
63803
|
// node_modules/@fetchproxy/server/dist/identity.js
|
|
63498
|
-
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
|
|
63804
|
+
import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink, link, lstat } from "node:fs/promises";
|
|
63499
63805
|
import { randomBytes } from "node:crypto";
|
|
63500
63806
|
import { isAbsolute, join } from "node:path";
|
|
63501
63807
|
import { homedir } from "node:os";
|
|
@@ -63563,7 +63869,7 @@ async function openIdentityDir(dir) {
|
|
|
63563
63869
|
}
|
|
63564
63870
|
}
|
|
63565
63871
|
var STAGING_BYTES = 6;
|
|
63566
|
-
async function sweepStagingFiles(dir, serverName) {
|
|
63872
|
+
async function sweepStagingFiles(dir, serverName, opts = {}) {
|
|
63567
63873
|
const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
|
|
63568
63874
|
const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
|
|
63569
63875
|
let names;
|
|
@@ -63572,35 +63878,80 @@ async function sweepStagingFiles(dir, serverName) {
|
|
|
63572
63878
|
} catch {
|
|
63573
63879
|
return;
|
|
63574
63880
|
}
|
|
63575
|
-
|
|
63881
|
+
const cutoff = opts.olderThanMs === void 0 ? null : Date.now() - opts.olderThanMs;
|
|
63882
|
+
await Promise.all(names.filter((name) => staging.test(name)).map(async (name) => {
|
|
63883
|
+
const path = join(dir, name);
|
|
63884
|
+
if (cutoff !== null) {
|
|
63885
|
+
const st = await lstat(path).catch(() => null);
|
|
63886
|
+
if (st === null || st.mtimeMs > cutoff)
|
|
63887
|
+
return;
|
|
63888
|
+
}
|
|
63889
|
+
await unlink(path).catch(() => void 0);
|
|
63890
|
+
}));
|
|
63576
63891
|
}
|
|
63577
|
-
async function
|
|
63892
|
+
async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
|
|
63578
63893
|
const path = identityFilePath(dir, serverName);
|
|
63579
63894
|
await openIdentityDir(dir);
|
|
63580
|
-
await sweepStagingFiles(dir, serverName);
|
|
63581
|
-
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63582
63895
|
try {
|
|
63583
|
-
|
|
63584
|
-
await chmod(tmp, 384);
|
|
63585
|
-
await rename(tmp, path);
|
|
63896
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63586
63897
|
} catch (e) {
|
|
63587
|
-
|
|
63588
|
-
|
|
63898
|
+
if (e.code !== "ENOENT" && !(e instanceof SyntaxError))
|
|
63899
|
+
throw e;
|
|
63900
|
+
}
|
|
63901
|
+
let unreadable = null;
|
|
63902
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
63903
|
+
const id = await generateIdentity();
|
|
63904
|
+
if (await publishIdentityIfAbsent(dir, serverName, id))
|
|
63905
|
+
return id;
|
|
63906
|
+
try {
|
|
63907
|
+
return parseIdentity(await readFile(path, "utf8"));
|
|
63908
|
+
} catch (e) {
|
|
63909
|
+
if (e.code === "ENOENT") {
|
|
63910
|
+
continue;
|
|
63911
|
+
}
|
|
63912
|
+
if (!(e instanceof SyntaxError))
|
|
63913
|
+
throw e;
|
|
63914
|
+
unreadable = e;
|
|
63915
|
+
await new Promise((r) => setTimeout(r, 20 * (attempt + 1)));
|
|
63916
|
+
}
|
|
63589
63917
|
}
|
|
63590
|
-
|
|
63918
|
+
if (unreadable !== null)
|
|
63919
|
+
throw unreadable;
|
|
63920
|
+
throw new Error(`fetchproxy: could not create an identity for ${JSON.stringify(serverName)} in ${dir}`);
|
|
63591
63921
|
}
|
|
63592
|
-
|
|
63922
|
+
var NO_HARD_LINKS = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
|
|
63923
|
+
async function publishIdentityIfAbsent(dir, serverName, id) {
|
|
63593
63924
|
const path = identityFilePath(dir, serverName);
|
|
63594
|
-
await
|
|
63925
|
+
await sweepStagingFiles(dir, serverName, { olderThanMs: 6e4 });
|
|
63926
|
+
const tmp = `${path}.${randomBytes(STAGING_BYTES).toString("hex")}.tmp`;
|
|
63595
63927
|
try {
|
|
63596
|
-
|
|
63928
|
+
await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63929
|
+
await chmod(tmp, 384);
|
|
63930
|
+
try {
|
|
63931
|
+
await link(tmp, path);
|
|
63932
|
+
} catch (e) {
|
|
63933
|
+
const code = e.code;
|
|
63934
|
+
if (code === void 0 || !NO_HARD_LINKS.has(code))
|
|
63935
|
+
throw e;
|
|
63936
|
+
try {
|
|
63937
|
+
await writeFile(path, serializeIdentity(id), { mode: 384, flag: "wx" });
|
|
63938
|
+
} catch (writeErr) {
|
|
63939
|
+
if (writeErr.code !== "EEXIST") {
|
|
63940
|
+
await rm(path, { force: true }).catch(() => void 0);
|
|
63941
|
+
}
|
|
63942
|
+
throw writeErr;
|
|
63943
|
+
}
|
|
63944
|
+
await chmod(path, 384);
|
|
63945
|
+
}
|
|
63946
|
+
return true;
|
|
63597
63947
|
} catch (e) {
|
|
63598
|
-
|
|
63599
|
-
|
|
63948
|
+
const code = e.code;
|
|
63949
|
+
if (code === "EEXIST" || code === "ENOENT")
|
|
63950
|
+
return false;
|
|
63951
|
+
throw e;
|
|
63952
|
+
} finally {
|
|
63953
|
+
await rm(tmp, { force: true });
|
|
63600
63954
|
}
|
|
63601
|
-
const id = await generateIdentity();
|
|
63602
|
-
await writeIdentityFile(dir, serverName, id);
|
|
63603
|
-
return id;
|
|
63604
63955
|
}
|
|
63605
63956
|
|
|
63606
63957
|
// node_modules/@fetchproxy/server/dist/extension-trust.js
|
|
@@ -63701,10 +64052,15 @@ async function readExtensionPin(serverName, dir = defaultTrustDir()) {
|
|
|
63701
64052
|
async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
|
|
63702
64053
|
const path = extensionTrustPath(serverName, dir);
|
|
63703
64054
|
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
63704
|
-
const tmp = `${path}.tmp`;
|
|
63705
|
-
|
|
63706
|
-
|
|
63707
|
-
|
|
64055
|
+
const tmp = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
64056
|
+
try {
|
|
64057
|
+
await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384, flag: "wx" });
|
|
64058
|
+
await chmod2(tmp, 384);
|
|
64059
|
+
await rename2(tmp, path);
|
|
64060
|
+
} catch (e) {
|
|
64061
|
+
await rm2(tmp, { force: true });
|
|
64062
|
+
throw e;
|
|
64063
|
+
}
|
|
63708
64064
|
}
|
|
63709
64065
|
|
|
63710
64066
|
// node_modules/@fetchproxy/server/dist/host.js
|
|
@@ -63741,6 +64097,7 @@ function originVerdict(origin, allowLocal) {
|
|
|
63741
64097
|
var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
63742
64098
|
var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
|
|
63743
64099
|
var enc4 = new TextEncoder();
|
|
64100
|
+
var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
|
|
63744
64101
|
async function startHost(opts) {
|
|
63745
64102
|
const allowLocalOrigins = envAllowsLocalOrigins();
|
|
63746
64103
|
if (allowLocalOrigins) {
|
|
@@ -63784,6 +64141,7 @@ async function startHost(opts) {
|
|
|
63784
64141
|
localStoragePointers: opts.ownLocalStoragePointers,
|
|
63785
64142
|
sessionStoragePointers: opts.ownSessionStoragePointers,
|
|
63786
64143
|
domSelectors: opts.ownDomSelectors,
|
|
64144
|
+
domListSelectors: opts.ownDomListSelectors,
|
|
63787
64145
|
graphqlOps: opts.ownGraphqlOps
|
|
63788
64146
|
};
|
|
63789
64147
|
const generateSessionKeypair = opts.generateSessionKeypair ?? generateX25519;
|
|
@@ -63854,6 +64212,31 @@ async function startHost(opts) {
|
|
|
63854
64212
|
return null;
|
|
63855
64213
|
}
|
|
63856
64214
|
};
|
|
64215
|
+
let ownDecryptFailures = 0;
|
|
64216
|
+
const rehandshakeOwnSession = async (ws) => {
|
|
64217
|
+
const hello = extensionHello;
|
|
64218
|
+
if (extensionWs !== ws || !hello)
|
|
64219
|
+
return;
|
|
64220
|
+
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).`);
|
|
64221
|
+
ownSession = dropOwnSessionAndEphemeral();
|
|
64222
|
+
resetSessionPromise();
|
|
64223
|
+
const mintedKeypair = await generateSessionKeypair();
|
|
64224
|
+
const mintedHello = await buildServerHello({
|
|
64225
|
+
...ownHelloBase,
|
|
64226
|
+
sessionPub: mintedKeypair.publicKey,
|
|
64227
|
+
answersExtNonce: fromB64(hello.sessionNonce)
|
|
64228
|
+
});
|
|
64229
|
+
if (extensionWs !== ws || extensionHello !== hello || ownSession !== null) {
|
|
64230
|
+
mintedKeypair.privateKey.fill(0);
|
|
64231
|
+
return;
|
|
64232
|
+
}
|
|
64233
|
+
installOwnEphemeral({
|
|
64234
|
+
nonce: fromB64(mintedHello.sessionNonce),
|
|
64235
|
+
pub: mintedKeypair.publicKey,
|
|
64236
|
+
priv: mintedKeypair.privateKey
|
|
64237
|
+
});
|
|
64238
|
+
ws.send(JSON.stringify(mintedHello));
|
|
64239
|
+
};
|
|
63857
64240
|
wss.on("connection", (ws) => {
|
|
63858
64241
|
let identified = null;
|
|
63859
64242
|
let peerMcpId = null;
|
|
@@ -64044,6 +64427,7 @@ async function startHost(opts) {
|
|
|
64044
64427
|
if (extensionWs !== ws)
|
|
64045
64428
|
return;
|
|
64046
64429
|
ownSession = new SessionState(key);
|
|
64430
|
+
ownDecryptFailures = 0;
|
|
64047
64431
|
ownPendingPairCode = null;
|
|
64048
64432
|
resolveOwnSession(ownSession);
|
|
64049
64433
|
} else {
|
|
@@ -64065,15 +64449,42 @@ async function startHost(opts) {
|
|
|
64065
64449
|
}
|
|
64066
64450
|
if (claim2 !== "ok")
|
|
64067
64451
|
return;
|
|
64068
|
-
let
|
|
64452
|
+
let result;
|
|
64069
64453
|
try {
|
|
64070
|
-
|
|
64454
|
+
result = await openEncryptedFrameDetailed(session.sessionKey, frame, "e2s");
|
|
64071
64455
|
} catch (e) {
|
|
64072
64456
|
session.releaseInboundSeq(frame.seq);
|
|
64073
64457
|
throw e;
|
|
64074
64458
|
}
|
|
64459
|
+
if (result.stage === "decrypt-failed") {
|
|
64460
|
+
session.releaseInboundSeq(frame.seq);
|
|
64461
|
+
console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) that failed authentication \u2014 most likely a straggler from a previous session.`);
|
|
64462
|
+
if (session === ownSession) {
|
|
64463
|
+
ownDecryptFailures += 1;
|
|
64464
|
+
if (ownDecryptFailures >= OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE) {
|
|
64465
|
+
ownDecryptFailures = 0;
|
|
64466
|
+
void rehandshakeOwnSession(ws).catch((e) => console.error(`[fetchproxy] ${opts.ownServerName}: re-handshake failed:`, e));
|
|
64467
|
+
}
|
|
64468
|
+
}
|
|
64469
|
+
return;
|
|
64470
|
+
}
|
|
64471
|
+
if (session === ownSession)
|
|
64472
|
+
ownDecryptFailures = 0;
|
|
64075
64473
|
session.commitInboundSeq(frame.seq);
|
|
64076
|
-
|
|
64474
|
+
if (result.stage === "ok") {
|
|
64475
|
+
ownInnerListeners.forEach((cb) => cb(result.inner));
|
|
64476
|
+
} else {
|
|
64477
|
+
console.error("[fetchproxy] host: received a frame that decrypted OK but failed validation:", result.error);
|
|
64478
|
+
const recoveredId = result.recoveredId;
|
|
64479
|
+
if (recoveredId !== void 0) {
|
|
64480
|
+
ownInnerListeners.forEach((cb) => cb({
|
|
64481
|
+
type: "response",
|
|
64482
|
+
id: recoveredId,
|
|
64483
|
+
ok: false,
|
|
64484
|
+
error: `malformed response failed protocol validation: ${String(result.error)}`
|
|
64485
|
+
}));
|
|
64486
|
+
}
|
|
64487
|
+
}
|
|
64077
64488
|
} else {
|
|
64078
64489
|
const slot = peers.get(frame.mcpId);
|
|
64079
64490
|
if (slot)
|
|
@@ -64132,6 +64543,7 @@ async function startHost(opts) {
|
|
|
64132
64543
|
rejectOwnSession(new Error("extension disconnected before ready"));
|
|
64133
64544
|
}
|
|
64134
64545
|
ownSession = dropOwnSessionAndEphemeral();
|
|
64546
|
+
ownDecryptFailures = 0;
|
|
64135
64547
|
ownPendingPairCode = null;
|
|
64136
64548
|
resetSessionPromise();
|
|
64137
64549
|
disconnectListeners.forEach((cb) => cb());
|
|
@@ -64146,8 +64558,15 @@ async function startHost(opts) {
|
|
|
64146
64558
|
}
|
|
64147
64559
|
}
|
|
64148
64560
|
if (identified === "peer" && peerMcpId) {
|
|
64149
|
-
if (peers.get(peerMcpId)?.ws === ws)
|
|
64561
|
+
if (peers.get(peerMcpId)?.ws === ws) {
|
|
64150
64562
|
peers.delete(peerMcpId);
|
|
64563
|
+
if (extensionWs && extensionHello?.accepts?.includes("peer-gone")) {
|
|
64564
|
+
try {
|
|
64565
|
+
extensionWs.send(JSON.stringify({ type: "peer-gone", mcpId: peerMcpId }));
|
|
64566
|
+
} catch {
|
|
64567
|
+
}
|
|
64568
|
+
}
|
|
64569
|
+
}
|
|
64151
64570
|
}
|
|
64152
64571
|
});
|
|
64153
64572
|
});
|
|
@@ -64190,12 +64609,18 @@ async function startHost(opts) {
|
|
|
64190
64609
|
}
|
|
64191
64610
|
|
|
64192
64611
|
// node_modules/@fetchproxy/server/dist/peer.js
|
|
64612
|
+
var PEER_DIAL_TIMEOUT_MS = 5e3;
|
|
64193
64613
|
var enc5 = new TextEncoder();
|
|
64614
|
+
function notSentError() {
|
|
64615
|
+
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.");
|
|
64616
|
+
}
|
|
64194
64617
|
async function startPeer(opts) {
|
|
64195
64618
|
const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
|
|
64196
64619
|
maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
|
|
64197
64620
|
});
|
|
64621
|
+
const dialTimeoutMs = opts.dialTimeoutMs ?? PEER_DIAL_TIMEOUT_MS;
|
|
64198
64622
|
await new Promise((resolve2, reject) => {
|
|
64623
|
+
let timer;
|
|
64199
64624
|
const onOpen = () => {
|
|
64200
64625
|
cleanup();
|
|
64201
64626
|
resolve2();
|
|
@@ -64205,11 +64630,21 @@ async function startPeer(opts) {
|
|
|
64205
64630
|
reject(e);
|
|
64206
64631
|
};
|
|
64207
64632
|
const cleanup = () => {
|
|
64633
|
+
if (timer !== void 0)
|
|
64634
|
+
clearTimeout(timer);
|
|
64208
64635
|
ws.off("open", onOpen);
|
|
64209
64636
|
ws.off("error", onError);
|
|
64210
64637
|
};
|
|
64211
64638
|
ws.once("open", onOpen);
|
|
64212
64639
|
ws.once("error", onError);
|
|
64640
|
+
if (dialTimeoutMs > 0) {
|
|
64641
|
+
timer = setTimeout(() => {
|
|
64642
|
+
cleanup();
|
|
64643
|
+
ws.on("error", () => void 0);
|
|
64644
|
+
ws.terminate();
|
|
64645
|
+
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.`));
|
|
64646
|
+
}, dialTimeoutMs);
|
|
64647
|
+
}
|
|
64213
64648
|
});
|
|
64214
64649
|
ws.on("error", (e) => {
|
|
64215
64650
|
console.warn(`[fetchproxy] peer: socket error: ${String(e)}`);
|
|
@@ -64228,6 +64663,7 @@ async function startPeer(opts) {
|
|
|
64228
64663
|
captureHeaders: opts.captureHeaders,
|
|
64229
64664
|
indexedDbScopes: opts.indexedDbScopes,
|
|
64230
64665
|
domSelectors: opts.domSelectors,
|
|
64666
|
+
domListSelectors: opts.domListSelectors,
|
|
64231
64667
|
localStoragePointers: opts.localStoragePointers,
|
|
64232
64668
|
sessionStoragePointers: opts.sessionStoragePointers,
|
|
64233
64669
|
graphqlOps: opts.graphqlOps,
|
|
@@ -64251,6 +64687,7 @@ async function startPeer(opts) {
|
|
|
64251
64687
|
const renegotiateListeners = [];
|
|
64252
64688
|
const pendingPairListeners = [];
|
|
64253
64689
|
const closeListeners = [];
|
|
64690
|
+
const extensionDisconnectListeners = [];
|
|
64254
64691
|
let session = null;
|
|
64255
64692
|
let sessionEphemeral = null;
|
|
64256
64693
|
function dropSessionEphemeral() {
|
|
@@ -64270,10 +64707,24 @@ async function startPeer(opts) {
|
|
|
64270
64707
|
let warnedUnverifiablePairCode = false;
|
|
64271
64708
|
let resolveFirstReady;
|
|
64272
64709
|
let rejectFirstReady;
|
|
64273
|
-
|
|
64274
|
-
|
|
64275
|
-
|
|
64276
|
-
|
|
64710
|
+
let sessionPromise;
|
|
64711
|
+
let sessionPromiseSettled = false;
|
|
64712
|
+
function resetSessionPromise() {
|
|
64713
|
+
sessionPromiseSettled = false;
|
|
64714
|
+
sessionPromise = new Promise((resolve2, reject) => {
|
|
64715
|
+
resolveFirstReady = (st) => {
|
|
64716
|
+
sessionPromiseSettled = true;
|
|
64717
|
+
resolve2(st);
|
|
64718
|
+
};
|
|
64719
|
+
rejectFirstReady = (e) => {
|
|
64720
|
+
sessionPromiseSettled = true;
|
|
64721
|
+
reject(e);
|
|
64722
|
+
};
|
|
64723
|
+
});
|
|
64724
|
+
sessionPromise.catch(() => {
|
|
64725
|
+
});
|
|
64726
|
+
}
|
|
64727
|
+
resetSessionPromise();
|
|
64277
64728
|
let extensionHello = null;
|
|
64278
64729
|
let extensionGone = false;
|
|
64279
64730
|
let cachedPin = void 0;
|
|
@@ -64370,6 +64821,12 @@ async function startPeer(opts) {
|
|
|
64370
64821
|
extensionGone = true;
|
|
64371
64822
|
dropSessionEphemeral();
|
|
64372
64823
|
pendingPairCode = null;
|
|
64824
|
+
if (session !== null) {
|
|
64825
|
+
session = null;
|
|
64826
|
+
if (sessionPromiseSettled)
|
|
64827
|
+
resetSessionPromise();
|
|
64828
|
+
}
|
|
64829
|
+
extensionDisconnectListeners.forEach((cb) => cb());
|
|
64373
64830
|
return;
|
|
64374
64831
|
}
|
|
64375
64832
|
if (frame.type === "ready" && frame.mcpId === opts.mcpId) {
|
|
@@ -64486,19 +64943,30 @@ async function startPeer(opts) {
|
|
|
64486
64943
|
}
|
|
64487
64944
|
closeListeners.forEach((cb) => cb());
|
|
64488
64945
|
});
|
|
64489
|
-
sessionPromise.catch(() => {
|
|
64490
|
-
});
|
|
64491
64946
|
const handle = {
|
|
64492
64947
|
ws,
|
|
64493
|
-
|
|
64948
|
+
// A getter: `extension-disconnected` replaces the promise (B-BUG-5).
|
|
64949
|
+
get session() {
|
|
64950
|
+
return sessionPromise;
|
|
64951
|
+
},
|
|
64494
64952
|
sendInner: async (inner) => {
|
|
64495
|
-
|
|
64496
|
-
|
|
64497
|
-
|
|
64498
|
-
|
|
64953
|
+
try {
|
|
64954
|
+
await awaitSessionReady(sessionPromise, {
|
|
64955
|
+
mcpId: opts.mcpId,
|
|
64956
|
+
pendingPairCode: () => pendingPairCode
|
|
64957
|
+
});
|
|
64958
|
+
} catch (e) {
|
|
64959
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
64960
|
+
throw notSentError();
|
|
64961
|
+
throw e;
|
|
64962
|
+
}
|
|
64499
64963
|
const s = session;
|
|
64964
|
+
if (s === null)
|
|
64965
|
+
throw new Error("peer: extension disconnected");
|
|
64500
64966
|
const plaintext = encodeOutboundInnerFrame(opts.mcpId, inner);
|
|
64501
64967
|
const sealed = await sealInnerFrame(s.sessionKey, opts.mcpId, s.nextOutboundSeq(), plaintext, "s2e");
|
|
64968
|
+
if (ws.readyState !== import_websocket.default.OPEN)
|
|
64969
|
+
throw notSentError();
|
|
64502
64970
|
ws.send(JSON.stringify(sealed));
|
|
64503
64971
|
},
|
|
64504
64972
|
onInner: (cb) => {
|
|
@@ -64516,6 +64984,9 @@ async function startPeer(opts) {
|
|
|
64516
64984
|
onClose: (cb) => {
|
|
64517
64985
|
closeListeners.push(cb);
|
|
64518
64986
|
},
|
|
64987
|
+
onExtensionDisconnect: (cb) => {
|
|
64988
|
+
extensionDisconnectListeners.push(cb);
|
|
64989
|
+
},
|
|
64519
64990
|
close: () => ws.close()
|
|
64520
64991
|
};
|
|
64521
64992
|
return handle;
|
|
@@ -64523,15 +64994,15 @@ async function startPeer(opts) {
|
|
|
64523
64994
|
|
|
64524
64995
|
// node_modules/@fetchproxy/server/dist/error-kind.js
|
|
64525
64996
|
function classifyFetchError(error62) {
|
|
64997
|
+
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
64998
|
+
return "tab_fetch_failed";
|
|
64999
|
+
}
|
|
64526
65000
|
if (/Could not establish connection/i.test(error62) || /Receiving end does not exist/i.test(error62)) {
|
|
64527
65001
|
return "content_script_unreachable";
|
|
64528
65002
|
}
|
|
64529
65003
|
if (/^tab fetch failed:/.test(error62)) {
|
|
64530
65004
|
return "tab_fetch_failed";
|
|
64531
65005
|
}
|
|
64532
|
-
if (/^(in-page )?fetch threw:/.test(error62)) {
|
|
64533
|
-
return "tab_fetch_failed";
|
|
64534
|
-
}
|
|
64535
65006
|
if (/^no tab matching /.test(error62)) {
|
|
64536
65007
|
return "no_tab";
|
|
64537
65008
|
}
|
|
@@ -64723,6 +65194,17 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64723
65194
|
* the message says which number that was and where it came from.
|
|
64724
65195
|
*/
|
|
64725
65196
|
requestedTimeoutMs;
|
|
65197
|
+
/**
|
|
65198
|
+
* B-BUG-1: whether re-sending this request is safe. A timeout only means
|
|
65199
|
+
* the reply did not arrive in time — the extension may already have run the
|
|
65200
|
+
* request in the tab — so this is `false` for a non-idempotent `fetch`
|
|
65201
|
+
* (anything but GET/HEAD/OPTIONS) the caller did not mark `retryOnTimeout`,
|
|
65202
|
+
* for `download` and `writeCookies`, and for `graphqlQuery` unless the
|
|
65203
|
+
* caller marked it `retryOnTimeout` (a declared operation may be a
|
|
65204
|
+
* mutation). `retryOnceOnTimeout` honours it. Defaults to `true` (the read
|
|
65205
|
+
* verbs).
|
|
65206
|
+
*/
|
|
65207
|
+
retrySafe;
|
|
64726
65208
|
constructor(args) {
|
|
64727
65209
|
const graceMs = args.graceMs ?? VERB_DEADLINE_GRACE_MS;
|
|
64728
65210
|
const callerChose = args.requestedTimeoutMs !== void 0 && args.timeoutMs === args.requestedTimeoutMs + graceMs;
|
|
@@ -64735,6 +65217,7 @@ var FetchproxyTimeoutError = class extends FetchproxyProtocolError {
|
|
|
64735
65217
|
this.elapsedMs = args.elapsedMs ?? args.timeoutMs;
|
|
64736
65218
|
this.retryAttempted = args.retryAttempted ?? false;
|
|
64737
65219
|
this.requestedTimeoutMs = args.requestedTimeoutMs ?? null;
|
|
65220
|
+
this.retrySafe = args.retrySafe ?? true;
|
|
64738
65221
|
}
|
|
64739
65222
|
};
|
|
64740
65223
|
var SUBDOMAIN_LABEL_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
@@ -64774,6 +65257,32 @@ function assertUrlInDomains(field, url4, domains) {
|
|
|
64774
65257
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
64775
65258
|
}
|
|
64776
65259
|
var DEFAULT_JSON_OK_STATUSES = [200, 201, 202, 204];
|
|
65260
|
+
function parseJsonBody(response) {
|
|
65261
|
+
if (response.status === 204 || response.body.trim() === "")
|
|
65262
|
+
return null;
|
|
65263
|
+
return JSON.parse(response.body);
|
|
65264
|
+
}
|
|
65265
|
+
var TIMEOUT_RETRY_SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
65266
|
+
function isRetrySafeOnTimeout(method, retryOnTimeout) {
|
|
65267
|
+
if (retryOnTimeout !== void 0)
|
|
65268
|
+
return retryOnTimeout;
|
|
65269
|
+
return TIMEOUT_RETRY_SAFE_METHODS.has(method.toUpperCase());
|
|
65270
|
+
}
|
|
65271
|
+
function isResendableRequest(inner) {
|
|
65272
|
+
switch (inner.op) {
|
|
65273
|
+
case "fetch":
|
|
65274
|
+
return isRetrySafeOnTimeout(inner.init.method);
|
|
65275
|
+
case "write_cookies":
|
|
65276
|
+
case "download":
|
|
65277
|
+
// A declared GraphQL operation may be a mutation; the server holds only
|
|
65278
|
+
// its name, so it cannot tell. `graphqlQuery({ retryOnTimeout: true })`
|
|
65279
|
+
// passes `resendable` explicitly for a known query.
|
|
65280
|
+
case "graphql_query":
|
|
65281
|
+
return false;
|
|
65282
|
+
default:
|
|
65283
|
+
return true;
|
|
65284
|
+
}
|
|
65285
|
+
}
|
|
64777
65286
|
var FetchproxyServer = class {
|
|
64778
65287
|
/**
|
|
64779
65288
|
* Bridge role. `null` until the first verb call (or an explicit
|
|
@@ -64822,12 +65331,22 @@ var FetchproxyServer = class {
|
|
|
64822
65331
|
pendingRedirect = /* @__PURE__ */ new Map();
|
|
64823
65332
|
// 0.4.0+: read_indexed_db awaiters resolve a JSON-typed values map.
|
|
64824
65333
|
pendingIdb = /* @__PURE__ */ new Map();
|
|
65334
|
+
// 3.1.0+: read_dom_list awaiters resolve an array of field-value rows —
|
|
65335
|
+
// a different shape from `pendingStorage`'s single map, so its own map.
|
|
65336
|
+
pendingDomList = /* @__PURE__ */ new Map();
|
|
64825
65337
|
// download awaiters resolve the saved-file metadata (path + size + mime).
|
|
64826
65338
|
pendingDownload = /* @__PURE__ */ new Map();
|
|
64827
65339
|
// 1.x+: graphql_query awaiters resolve the GraphQL `data` object. Its
|
|
64828
65340
|
// shape is operation-specific, so the awaiter resolves `unknown` and the
|
|
64829
65341
|
// caller narrows.
|
|
64830
65342
|
pendingGraphql = /* @__PURE__ */ new Map();
|
|
65343
|
+
/**
|
|
65344
|
+
* B-BUG-6: the frames of in-flight requests that are safe to send again
|
|
65345
|
+
* if the host that relayed them exits before replying (see
|
|
65346
|
+
* `recoverFromHostLoss`). Keyed by request id; an entry outlives its
|
|
65347
|
+
* request only until the next prune or response.
|
|
65348
|
+
*/
|
|
65349
|
+
resendable = /* @__PURE__ */ new Map();
|
|
64831
65350
|
mcpId = null;
|
|
64832
65351
|
identity = null;
|
|
64833
65352
|
// 0.5.3+: in-flight role-election / handle-start promise. Set the
|
|
@@ -64873,7 +65392,7 @@ var FetchproxyServer = class {
|
|
|
64873
65392
|
}
|
|
64874
65393
|
for (const c of opts.capabilities) {
|
|
64875
65394
|
if (!KNOWN_CAPABILITIES.has(c)) {
|
|
64876
|
-
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: [
|
|
65395
|
+
throw new Error(`FetchproxyServer: unknown capability ${JSON.stringify(c)} \u2014 known values: ${JSON.stringify([...KNOWN_CAPABILITIES])}`);
|
|
64877
65396
|
}
|
|
64878
65397
|
}
|
|
64879
65398
|
capabilities = [...opts.capabilities];
|
|
@@ -64920,6 +65439,16 @@ var FetchproxyServer = class {
|
|
|
64920
65439
|
selector: d.selector,
|
|
64921
65440
|
...d.attribute !== void 0 ? { attribute: d.attribute } : {}
|
|
64922
65441
|
})),
|
|
65442
|
+
domListSelectors: (opts.domListSelectors ?? []).map((d) => ({
|
|
65443
|
+
name: d.name,
|
|
65444
|
+
itemSelector: d.itemSelector,
|
|
65445
|
+
fields: d.fields.map((f) => ({
|
|
65446
|
+
name: f.name,
|
|
65447
|
+
...f.selector !== void 0 ? { selector: f.selector } : {},
|
|
65448
|
+
...f.attribute !== void 0 ? { attribute: f.attribute } : {}
|
|
65449
|
+
})),
|
|
65450
|
+
...d.maxItems !== void 0 ? { maxItems: d.maxItems } : {}
|
|
65451
|
+
})),
|
|
64923
65452
|
graphqlOps: (opts.graphqlOps ?? []).map((d) => ({
|
|
64924
65453
|
name: d.name,
|
|
64925
65454
|
operationName: d.operationName
|
|
@@ -65049,6 +65578,7 @@ var FetchproxyServer = class {
|
|
|
65049
65578
|
ownLocalStoragePointers: this.opts.localStoragePointers,
|
|
65050
65579
|
ownSessionStoragePointers: this.opts.sessionStoragePointers,
|
|
65051
65580
|
ownDomSelectors: this.opts.domSelectors,
|
|
65581
|
+
ownDomListSelectors: this.opts.domListSelectors,
|
|
65052
65582
|
ownGraphqlOps: this.opts.graphqlOps,
|
|
65053
65583
|
onPairCode: this.opts.onPairCode,
|
|
65054
65584
|
extensionTrust: this.extensionTrust()
|
|
@@ -65080,6 +65610,7 @@ var FetchproxyServer = class {
|
|
|
65080
65610
|
localStoragePointers: this.opts.localStoragePointers,
|
|
65081
65611
|
sessionStoragePointers: this.opts.sessionStoragePointers,
|
|
65082
65612
|
domSelectors: this.opts.domSelectors,
|
|
65613
|
+
domListSelectors: this.opts.domListSelectors,
|
|
65083
65614
|
graphqlOps: this.opts.graphqlOps,
|
|
65084
65615
|
extensionTrust: this.extensionTrust(),
|
|
65085
65616
|
requireExtensionIdentity: this.opts.requireExtensionIdentity
|
|
@@ -65089,6 +65620,10 @@ var FetchproxyServer = class {
|
|
|
65089
65620
|
this.stopKeepalive();
|
|
65090
65621
|
this.rejectAllPending();
|
|
65091
65622
|
});
|
|
65623
|
+
this.peerHandle.onExtensionDisconnect(() => {
|
|
65624
|
+
this.stopKeepalive();
|
|
65625
|
+
this.rejectAllPending();
|
|
65626
|
+
});
|
|
65092
65627
|
this.peerHandle.onPendingPair((code) => {
|
|
65093
65628
|
this.rejectAllPending(this.pairingErrorMessage(code));
|
|
65094
65629
|
});
|
|
@@ -65100,9 +65635,9 @@ var FetchproxyServer = class {
|
|
|
65100
65635
|
if (this.closing || this.peerHandle === null)
|
|
65101
65636
|
return;
|
|
65102
65637
|
this.stopKeepalive();
|
|
65103
|
-
this.rejectAllPending();
|
|
65104
65638
|
this.peerHandle = null;
|
|
65105
65639
|
this.role = null;
|
|
65640
|
+
void this.recoverFromHostLoss();
|
|
65106
65641
|
});
|
|
65107
65642
|
}
|
|
65108
65643
|
}
|
|
@@ -65123,7 +65658,7 @@ var FetchproxyServer = class {
|
|
|
65123
65658
|
* only when the bridge itself failed (no signed-in tab, extension
|
|
65124
65659
|
* offline, etc.).
|
|
65125
65660
|
*/
|
|
65126
|
-
async fetch(init) {
|
|
65661
|
+
async fetch(init, fetchOpts = {}) {
|
|
65127
65662
|
await this.ensureConnected();
|
|
65128
65663
|
const pendingCode = this.currentPendingPairCode();
|
|
65129
65664
|
if (pendingCode !== null) {
|
|
@@ -65135,16 +65670,17 @@ var FetchproxyServer = class {
|
|
|
65135
65670
|
retryAttempted: false
|
|
65136
65671
|
};
|
|
65137
65672
|
}
|
|
65138
|
-
const first = await this._fetchOnceWithTimeout(init);
|
|
65673
|
+
const first = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65139
65674
|
const reviveMs = this.opts.bridgeReviveDelayMs;
|
|
65140
65675
|
const isColdStartSymptom = !first.ok && (first.kind === "content_script_unreachable" || first.kind === "timeout");
|
|
65141
65676
|
if (isColdStartSymptom) {
|
|
65142
65677
|
this.lastEvictionDetectedAt = Date.now();
|
|
65143
65678
|
}
|
|
65144
|
-
|
|
65679
|
+
const retryable = isColdStartSymptom && (first.kind === "content_script_unreachable" || isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout));
|
|
65680
|
+
if (retryable && reviveMs !== void 0 && reviveMs > 0) {
|
|
65145
65681
|
this.lazyReviveAttempts += 1;
|
|
65146
65682
|
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65147
|
-
const second = await this._fetchOnceWithTimeout(init);
|
|
65683
|
+
const second = await this._fetchOnceWithTimeout(init, fetchOpts);
|
|
65148
65684
|
if (second.ok) {
|
|
65149
65685
|
this.lazyReviveSuccesses += 1;
|
|
65150
65686
|
this.recordSuccess();
|
|
@@ -65293,13 +65829,16 @@ var FetchproxyServer = class {
|
|
|
65293
65829
|
* one of the op maps — request ids are unique) so it doesn't leak until the
|
|
65294
65830
|
* server closes, then rethrow.
|
|
65295
65831
|
*/
|
|
65296
|
-
async sendInnerFrame(inner) {
|
|
65832
|
+
async sendInnerFrame(inner, opts = {}) {
|
|
65297
65833
|
try {
|
|
65298
65834
|
if (this.hostHandle) {
|
|
65299
65835
|
await this.hostHandle.sendOwnInner(inner);
|
|
65300
65836
|
} else if (this.peerHandle) {
|
|
65301
65837
|
await this.peerHandle.sendInner(inner);
|
|
65302
65838
|
}
|
|
65839
|
+
if (inner.type === "request" && (opts.resendable ?? isResendableRequest(inner))) {
|
|
65840
|
+
this.noteResendable(inner);
|
|
65841
|
+
}
|
|
65303
65842
|
} catch (err) {
|
|
65304
65843
|
if ("id" in inner && typeof inner.id === "number") {
|
|
65305
65844
|
const { id } = inner;
|
|
@@ -65311,6 +65850,7 @@ var FetchproxyServer = class {
|
|
|
65311
65850
|
this.pendingRedirect.delete(id);
|
|
65312
65851
|
this.pendingDownload.delete(id);
|
|
65313
65852
|
this.pendingIdb.delete(id);
|
|
65853
|
+
this.pendingDomList.delete(id);
|
|
65314
65854
|
this.pendingGraphql.delete(id);
|
|
65315
65855
|
}
|
|
65316
65856
|
throw err;
|
|
@@ -65321,13 +65861,25 @@ var FetchproxyServer = class {
|
|
|
65321
65861
|
* On timeout returns the `{ok:false, kind:'timeout'}` envelope —
|
|
65322
65862
|
* the throwing surface is the convenience methods.
|
|
65323
65863
|
*/
|
|
65324
|
-
async _fetchOnceWithTimeout(init) {
|
|
65864
|
+
async _fetchOnceWithTimeout(init, fetchOpts = {}) {
|
|
65325
65865
|
const id = this.nextRequestId++;
|
|
65326
65866
|
const inner = { type: "request", id, op: "fetch", init };
|
|
65327
65867
|
const pending = this.guardPending(new Promise((resolve2) => {
|
|
65328
65868
|
this.pending.set(id, resolve2);
|
|
65329
65869
|
}));
|
|
65330
|
-
|
|
65870
|
+
try {
|
|
65871
|
+
await this.sendInnerFrame(inner, {
|
|
65872
|
+
resendable: isRetrySafeOnTimeout(init.method, fetchOpts.retryOnTimeout)
|
|
65873
|
+
});
|
|
65874
|
+
} catch (err) {
|
|
65875
|
+
return {
|
|
65876
|
+
ok: false,
|
|
65877
|
+
error: err instanceof Error ? err.message : String(err),
|
|
65878
|
+
kind: "session_not_ready",
|
|
65879
|
+
retryAttempted: false,
|
|
65880
|
+
cause: err
|
|
65881
|
+
};
|
|
65882
|
+
}
|
|
65331
65883
|
const timeoutMs = this.opts.fetchTimeoutMs;
|
|
65332
65884
|
if (timeoutMs === void 0 || timeoutMs <= 0)
|
|
65333
65885
|
return pending;
|
|
@@ -65401,7 +65953,7 @@ var FetchproxyServer = class {
|
|
|
65401
65953
|
});
|
|
65402
65954
|
return pending;
|
|
65403
65955
|
}
|
|
65404
|
-
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs) {
|
|
65956
|
+
async _withVerbTimeout(pending, pendingMap, id, url4, requestedTimeoutMs, retrySafe = true) {
|
|
65405
65957
|
const transportMs = this.opts.fetchTimeoutMs;
|
|
65406
65958
|
if (transportMs === void 0 || transportMs <= 0)
|
|
65407
65959
|
return pending;
|
|
@@ -65422,6 +65974,7 @@ var FetchproxyServer = class {
|
|
|
65422
65974
|
port: this.opts.port,
|
|
65423
65975
|
elapsedMs: Date.now() - start,
|
|
65424
65976
|
retryAttempted: false,
|
|
65977
|
+
retrySafe,
|
|
65425
65978
|
graceMs,
|
|
65426
65979
|
...requestedTimeoutMs !== void 0 ? { requestedTimeoutMs } : {}
|
|
65427
65980
|
}));
|
|
@@ -65439,7 +65992,7 @@ var FetchproxyServer = class {
|
|
|
65439
65992
|
* logic re-implemented inline) `captureRequestHeader()` agree on what
|
|
65440
65993
|
* to throw.
|
|
65441
65994
|
*/
|
|
65442
|
-
_typedErrorFor(result, url4, op, retryAttempted) {
|
|
65995
|
+
_typedErrorFor(result, url4, op, retryAttempted, retrySafe = true) {
|
|
65443
65996
|
if (result.kind === "timeout") {
|
|
65444
65997
|
return new FetchproxyTimeoutError({
|
|
65445
65998
|
url: url4,
|
|
@@ -65447,7 +66000,8 @@ var FetchproxyServer = class {
|
|
|
65447
66000
|
role: this.role,
|
|
65448
66001
|
port: this.opts.port,
|
|
65449
66002
|
elapsedMs: result.elapsedMs,
|
|
65450
|
-
retryAttempted
|
|
66003
|
+
retryAttempted,
|
|
66004
|
+
retrySafe
|
|
65451
66005
|
});
|
|
65452
66006
|
}
|
|
65453
66007
|
if (result.kind === "content_script_unreachable") {
|
|
@@ -65521,9 +66075,12 @@ var FetchproxyServer = class {
|
|
|
65521
66075
|
// every version before this option existed.
|
|
65522
66076
|
...opts.credentials === "omit" ? { credentials: "omit" } : {}
|
|
65523
66077
|
};
|
|
65524
|
-
const result = await this.fetch(init);
|
|
66078
|
+
const result = await this.fetch(init, opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {});
|
|
65525
66079
|
if (!result.ok) {
|
|
65526
|
-
|
|
66080
|
+
if (result.kind === "session_not_ready" && result.cause instanceof Error) {
|
|
66081
|
+
throw result.cause;
|
|
66082
|
+
}
|
|
66083
|
+
throw this._typedErrorFor(result, init.url, "fetch", result.retryAttempted ?? false, isRetrySafeOnTimeout(method, opts.retryOnTimeout));
|
|
65527
66084
|
}
|
|
65528
66085
|
const response = {
|
|
65529
66086
|
status: result.status,
|
|
@@ -65563,17 +66120,21 @@ var FetchproxyServer = class {
|
|
|
65563
66120
|
* GET a path and parse the response body as JSON. Throws
|
|
65564
66121
|
* `FetchproxyHttpError` if the status is outside the default 2xx
|
|
65565
66122
|
* happy-path set (`[200, 201, 202, 204]`); pass a custom
|
|
65566
|
-
* `expectStatus` to override.
|
|
66123
|
+
* `expectStatus` to override. A 204 or an empty/whitespace body
|
|
66124
|
+
* resolves to `null` (the declared `T` does not say so, to keep
|
|
66125
|
+
* existing call sites compiling — include `| null` in `T` if the
|
|
66126
|
+
* endpoint can answer empty).
|
|
65567
66127
|
*/
|
|
65568
66128
|
async getJson(path, opts = {}) {
|
|
65569
66129
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
65570
|
-
return
|
|
66130
|
+
return parseJsonBody(response);
|
|
65571
66131
|
}
|
|
65572
66132
|
/**
|
|
65573
66133
|
* POST a JSON body and parse the response body as JSON. The body is
|
|
65574
66134
|
* `JSON.stringify`'d; `Content-Type: application/json` is set unless
|
|
65575
66135
|
* the caller already provided one. Defaults `expectStatus` to the 2xx
|
|
65576
|
-
* happy-path set.
|
|
66136
|
+
* happy-path set. A 204 or an empty/whitespace body resolves to `null`
|
|
66137
|
+
* rather than throwing after the write already happened (see `getJson`).
|
|
65577
66138
|
*/
|
|
65578
66139
|
async postJson(path, body, opts = {}) {
|
|
65579
66140
|
const headers = { ...opts.headers ?? {} };
|
|
@@ -65585,7 +66146,7 @@ var FetchproxyServer = class {
|
|
|
65585
66146
|
headers,
|
|
65586
66147
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
65587
66148
|
});
|
|
65588
|
-
return
|
|
66149
|
+
return parseJsonBody(response);
|
|
65589
66150
|
}
|
|
65590
66151
|
/**
|
|
65591
66152
|
* GET a path and return the response body as a string. Throws
|
|
@@ -65606,7 +66167,8 @@ var FetchproxyServer = class {
|
|
|
65606
66167
|
* - adds `Content-Type: application/json` only for a non-GET request
|
|
65607
66168
|
* that carries a `body` (and only if the caller didn't set one);
|
|
65608
66169
|
* - `JSON.stringify`s the body (GET / no-body sends nothing);
|
|
65609
|
-
* - treats a `204
|
|
66170
|
+
* - treats a `204`, an empty body, or a whitespace-only body as
|
|
66171
|
+
* `data: null` (no parse), the same rule as getJson/postJson;
|
|
65610
66172
|
* - otherwise `JSON.parse`s the body.
|
|
65611
66173
|
*
|
|
65612
66174
|
* Scope is serialization + header defaults + 204-handling +
|
|
@@ -65620,6 +66182,11 @@ var FetchproxyServer = class {
|
|
|
65620
66182
|
* Bridge-level failures (no signed-in tab, SW down, timeout) still
|
|
65621
66183
|
* throw the typed errors via `request()`, exactly like the verb
|
|
65622
66184
|
* helpers — only successful round-trips (any HTTP status) return.
|
|
66185
|
+
*
|
|
66186
|
+
* `retryOnTimeout` is `RequestOpts.retryOnTimeout`: a timed-out POST is
|
|
66187
|
+
* NOT re-sent unless the caller says it is safe to repeat. A read-only POST
|
|
66188
|
+
* (a search, a GraphQL query) should pass `true` to keep the cold-start
|
|
66189
|
+
* timeout retry (#90).
|
|
65623
66190
|
*/
|
|
65624
66191
|
async requestJson(method, path, opts = {}) {
|
|
65625
66192
|
const isGet = method.toUpperCase() === "GET";
|
|
@@ -65633,7 +66200,8 @@ var FetchproxyServer = class {
|
|
|
65633
66200
|
headers,
|
|
65634
66201
|
body: sendBody ? JSON.stringify(opts.body) : void 0,
|
|
65635
66202
|
...opts.subdomain !== void 0 ? { subdomain: opts.subdomain } : {},
|
|
65636
|
-
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
66203
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {},
|
|
66204
|
+
...opts.retryOnTimeout !== void 0 ? { retryOnTimeout: opts.retryOnTimeout } : {}
|
|
65637
66205
|
});
|
|
65638
66206
|
const result = {
|
|
65639
66207
|
ok: true,
|
|
@@ -65641,12 +66209,9 @@ var FetchproxyServer = class {
|
|
|
65641
66209
|
url: response.url,
|
|
65642
66210
|
body: response.body
|
|
65643
66211
|
};
|
|
65644
|
-
if (response.status === 204 || response.body === "") {
|
|
65645
|
-
return { data: null, result };
|
|
65646
|
-
}
|
|
65647
66212
|
let data;
|
|
65648
66213
|
try {
|
|
65649
|
-
data =
|
|
66214
|
+
data = parseJsonBody(response);
|
|
65650
66215
|
} catch (e) {
|
|
65651
66216
|
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
65652
66217
|
}
|
|
@@ -65703,18 +66268,19 @@ var FetchproxyServer = class {
|
|
|
65703
66268
|
};
|
|
65704
66269
|
}
|
|
65705
66270
|
/**
|
|
65706
|
-
*
|
|
66271
|
+
* Read the user's cookies for the chosen domain.
|
|
65707
66272
|
*
|
|
65708
66273
|
* Requires `'read_cookies'` in `FetchproxyServerOpts.capabilities`.
|
|
65709
66274
|
* Throws a developer-facing `Error` at the call site if the MCP did
|
|
65710
66275
|
* not declare the capability — this is a programming mistake, not a
|
|
65711
66276
|
* runtime condition.
|
|
65712
66277
|
*
|
|
65713
|
-
* The returned string is
|
|
65714
|
-
*
|
|
65715
|
-
*
|
|
65716
|
-
*
|
|
65717
|
-
*
|
|
66278
|
+
* The returned string is semicolon-separated `k=v` pairs. With `keys`
|
|
66279
|
+
* (each in the declared `cookieKeys`), the extension reads each name via
|
|
66280
|
+
* `chrome.cookies.get`, which INCLUDES HttpOnly cookies — the login
|
|
66281
|
+
* session cookie among them; that is what cookie-session MCPs use it
|
|
66282
|
+
* for. Without `keys` (legacy), it is the tab's `document.cookie`, which
|
|
66283
|
+
* omits HttpOnly cookies. See docs/SECURITY.md §T-cookie-exfil.
|
|
65718
66284
|
*
|
|
65719
66285
|
* Throws `FetchproxyProtocolError` if the bridge could not deliver
|
|
65720
66286
|
* the request (no signed-in tab, extension offline, etc.).
|
|
@@ -65810,7 +66376,15 @@ var FetchproxyServer = class {
|
|
|
65810
66376
|
this.pendingWriteCookies.set(id, { resolve: resolve2, reject });
|
|
65811
66377
|
}));
|
|
65812
66378
|
await this.sendInnerFrame(inner);
|
|
65813
|
-
return this._withVerbTimeout(
|
|
66379
|
+
return this._withVerbTimeout(
|
|
66380
|
+
pending,
|
|
66381
|
+
this.pendingWriteCookies,
|
|
66382
|
+
id,
|
|
66383
|
+
`https://${host}`,
|
|
66384
|
+
void 0,
|
|
66385
|
+
// A write: never re-sent after an ambiguous timeout.
|
|
66386
|
+
false
|
|
66387
|
+
);
|
|
65814
66388
|
}
|
|
65815
66389
|
/**
|
|
65816
66390
|
* 0.3.0+: read declared localStorage keys from the user's signed-in
|
|
@@ -65913,53 +66487,7 @@ var FetchproxyServer = class {
|
|
|
65913
66487
|
throw new Error("FetchproxyServer.captureRequestHeader: pass both host AND headerName, or neither (which defaults to the single declared entry)");
|
|
65914
66488
|
}
|
|
65915
66489
|
const callOpts = { ...resolved2, ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {} };
|
|
65916
|
-
|
|
65917
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65918
|
-
this.recordSuccess();
|
|
65919
|
-
return result;
|
|
65920
|
-
} catch (err) {
|
|
65921
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
65922
|
-
if (!swDown) {
|
|
65923
|
-
this.recordFailure(`capture_request_header: ${err.message ?? String(err)}`);
|
|
65924
|
-
throw err;
|
|
65925
|
-
}
|
|
65926
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
65927
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
65928
|
-
if (reviveMs > 0) {
|
|
65929
|
-
this.lazyReviveAttempts += 1;
|
|
65930
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
65931
|
-
try {
|
|
65932
|
-
const result = await this._captureRequestHeaderOnce(callOpts);
|
|
65933
|
-
this.lazyReviveSuccesses += 1;
|
|
65934
|
-
this.recordSuccess();
|
|
65935
|
-
return result;
|
|
65936
|
-
} catch (retryErr) {
|
|
65937
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
65938
|
-
if (!stillDown) {
|
|
65939
|
-
this.recordFailure(`capture_request_header: ${retryErr.message ?? String(retryErr)}`);
|
|
65940
|
-
throw retryErr;
|
|
65941
|
-
}
|
|
65942
|
-
this.recordFailure(`capture_request_header bridge-down: ${retryErr.message}`);
|
|
65943
|
-
throw new FetchproxyBridgeDownError({
|
|
65944
|
-
originalError: retryErr.message,
|
|
65945
|
-
retryAttempted: true,
|
|
65946
|
-
op: "capture_request_header",
|
|
65947
|
-
url: `https://${resolved2.host}${resolved2.path ?? "/*"}`,
|
|
65948
|
-
role: this.role,
|
|
65949
|
-
port: this.opts.port
|
|
65950
|
-
});
|
|
65951
|
-
}
|
|
65952
|
-
}
|
|
65953
|
-
this.recordFailure(`capture_request_header bridge-down: ${err.message}`);
|
|
65954
|
-
throw new FetchproxyBridgeDownError({
|
|
65955
|
-
originalError: err.message,
|
|
65956
|
-
retryAttempted: false,
|
|
65957
|
-
op: "capture_request_header",
|
|
65958
|
-
url: `https://${resolved2.host}${resolved2.path ?? "/*"}`,
|
|
65959
|
-
role: this.role,
|
|
65960
|
-
port: this.opts.port
|
|
65961
|
-
});
|
|
65962
|
-
}
|
|
66490
|
+
return this.withLazyRevive("capture_request_header", `https://${resolved2.host}${resolved2.path ?? "/*"}`, () => this._captureRequestHeaderOnce(callOpts));
|
|
65963
66491
|
}
|
|
65964
66492
|
async _captureRequestHeaderOnce(opts) {
|
|
65965
66493
|
const id = this.nextRequestId++;
|
|
@@ -66008,53 +66536,7 @@ var FetchproxyServer = class {
|
|
|
66008
66536
|
}
|
|
66009
66537
|
await this.ensureConnected();
|
|
66010
66538
|
this.throwIfPendingPair();
|
|
66011
|
-
|
|
66012
|
-
const result = await this._captureRedirectOnce(opts);
|
|
66013
|
-
this.recordSuccess();
|
|
66014
|
-
return result;
|
|
66015
|
-
} catch (err) {
|
|
66016
|
-
const swDown = err instanceof FetchproxyProtocolError && classifyFetchError(err.message) === "content_script_unreachable";
|
|
66017
|
-
if (!swDown) {
|
|
66018
|
-
this.recordFailure(`capture_redirect: ${err.message ?? String(err)}`);
|
|
66019
|
-
throw err;
|
|
66020
|
-
}
|
|
66021
|
-
this.lastEvictionDetectedAt = Date.now();
|
|
66022
|
-
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
66023
|
-
if (reviveMs > 0) {
|
|
66024
|
-
this.lazyReviveAttempts += 1;
|
|
66025
|
-
await new Promise((r) => setTimeout(r, reviveMs));
|
|
66026
|
-
try {
|
|
66027
|
-
const result = await this._captureRedirectOnce(opts);
|
|
66028
|
-
this.lazyReviveSuccesses += 1;
|
|
66029
|
-
this.recordSuccess();
|
|
66030
|
-
return result;
|
|
66031
|
-
} catch (retryErr) {
|
|
66032
|
-
const stillDown = retryErr instanceof FetchproxyProtocolError && classifyFetchError(retryErr.message) === "content_script_unreachable";
|
|
66033
|
-
if (!stillDown) {
|
|
66034
|
-
this.recordFailure(`capture_redirect: ${retryErr.message ?? String(retryErr)}`);
|
|
66035
|
-
throw retryErr;
|
|
66036
|
-
}
|
|
66037
|
-
this.recordFailure(`capture_redirect bridge-down: ${retryErr.message}`);
|
|
66038
|
-
throw new FetchproxyBridgeDownError({
|
|
66039
|
-
originalError: retryErr.message,
|
|
66040
|
-
retryAttempted: true,
|
|
66041
|
-
op: "capture_redirect",
|
|
66042
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
66043
|
-
role: this.role,
|
|
66044
|
-
port: this.opts.port
|
|
66045
|
-
});
|
|
66046
|
-
}
|
|
66047
|
-
}
|
|
66048
|
-
this.recordFailure(`capture_redirect bridge-down: ${err.message}`);
|
|
66049
|
-
throw new FetchproxyBridgeDownError({
|
|
66050
|
-
originalError: err.message,
|
|
66051
|
-
retryAttempted: false,
|
|
66052
|
-
op: "capture_redirect",
|
|
66053
|
-
url: `https://${opts.host}${opts.path ?? "/*"}`,
|
|
66054
|
-
role: this.role,
|
|
66055
|
-
port: this.opts.port
|
|
66056
|
-
});
|
|
66057
|
-
}
|
|
66539
|
+
return this.withLazyRevive("capture_redirect", `https://${opts.host}${opts.path ?? "/*"}`, () => this._captureRedirectOnce(opts));
|
|
66058
66540
|
}
|
|
66059
66541
|
async _captureRedirectOnce(opts) {
|
|
66060
66542
|
const id = this.nextRequestId++;
|
|
@@ -66099,52 +66581,57 @@ var FetchproxyServer = class {
|
|
|
66099
66581
|
assertUrlInDomains("download url", opts.url, this.opts.domains);
|
|
66100
66582
|
await this.ensureConnected();
|
|
66101
66583
|
this.throwIfPendingPair();
|
|
66584
|
+
return this.withLazyRevive("download", opts.url, () => this._downloadOnce(opts));
|
|
66585
|
+
}
|
|
66586
|
+
/**
|
|
66587
|
+
* B-QUAL-1: the lazy-revive policy for the throwing verbs
|
|
66588
|
+
* (`capture_request_header`, `capture_redirect`, `download`), in one place.
|
|
66589
|
+
* Runs `once`; on the SW-eviction symptom (`content_script_unreachable` —
|
|
66590
|
+
* the request provably never reached a tab, so re-sending is safe) stamps
|
|
66591
|
+
* the eviction counter, waits `bridgeReviveDelayMs` and retries once, then
|
|
66592
|
+
* surfaces a `FetchproxyBridgeDownError`. Every other error is recorded and
|
|
66593
|
+
* rethrown untouched. `fetch()` keeps its own envelope-shaped variant.
|
|
66594
|
+
*/
|
|
66595
|
+
async withLazyRevive(op, url4, once) {
|
|
66596
|
+
const isSwDown = (e) => e instanceof FetchproxyProtocolError && classifyFetchError(e.message) === "content_script_unreachable";
|
|
66597
|
+
const bridgeDown = (e, retryAttempted) => {
|
|
66598
|
+
this.recordFailure(`${op} bridge-down: ${e.message}`);
|
|
66599
|
+
return new FetchproxyBridgeDownError({
|
|
66600
|
+
originalError: e.message,
|
|
66601
|
+
retryAttempted,
|
|
66602
|
+
op,
|
|
66603
|
+
url: url4,
|
|
66604
|
+
role: this.role,
|
|
66605
|
+
port: this.opts.port
|
|
66606
|
+
});
|
|
66607
|
+
};
|
|
66102
66608
|
try {
|
|
66103
|
-
const result = await
|
|
66609
|
+
const result = await once();
|
|
66104
66610
|
this.recordSuccess();
|
|
66105
66611
|
return result;
|
|
66106
66612
|
} catch (err) {
|
|
66107
|
-
|
|
66108
|
-
|
|
66109
|
-
this.recordFailure(`download: ${err.message ?? String(err)}`);
|
|
66613
|
+
if (!isSwDown(err)) {
|
|
66614
|
+
this.recordFailure(`${op}: ${err.message ?? String(err)}`);
|
|
66110
66615
|
throw err;
|
|
66111
66616
|
}
|
|
66112
66617
|
this.lastEvictionDetectedAt = Date.now();
|
|
66113
66618
|
const reviveMs = this.opts.bridgeReviveDelayMs ?? 0;
|
|
66114
|
-
if (reviveMs
|
|
66115
|
-
|
|
66116
|
-
|
|
66117
|
-
|
|
66118
|
-
|
|
66119
|
-
|
|
66120
|
-
|
|
66121
|
-
|
|
66122
|
-
|
|
66123
|
-
|
|
66124
|
-
|
|
66125
|
-
|
|
66126
|
-
|
|
66127
|
-
}
|
|
66128
|
-
this.recordFailure(`download bridge-down: ${retryErr.message}`);
|
|
66129
|
-
throw new FetchproxyBridgeDownError({
|
|
66130
|
-
originalError: retryErr.message,
|
|
66131
|
-
retryAttempted: true,
|
|
66132
|
-
op: "download",
|
|
66133
|
-
url: opts.url,
|
|
66134
|
-
role: this.role,
|
|
66135
|
-
port: this.opts.port
|
|
66136
|
-
});
|
|
66619
|
+
if (reviveMs <= 0)
|
|
66620
|
+
throw bridgeDown(err, false);
|
|
66621
|
+
this.lazyReviveAttempts += 1;
|
|
66622
|
+
await new Promise((r) => setTimeout(r, reviveMs));
|
|
66623
|
+
try {
|
|
66624
|
+
const result = await once();
|
|
66625
|
+
this.lazyReviveSuccesses += 1;
|
|
66626
|
+
this.recordSuccess();
|
|
66627
|
+
return result;
|
|
66628
|
+
} catch (retryErr) {
|
|
66629
|
+
if (!isSwDown(retryErr)) {
|
|
66630
|
+
this.recordFailure(`${op}: ${retryErr.message ?? String(retryErr)}`);
|
|
66631
|
+
throw retryErr;
|
|
66137
66632
|
}
|
|
66633
|
+
throw bridgeDown(retryErr, true);
|
|
66138
66634
|
}
|
|
66139
|
-
this.recordFailure(`download bridge-down: ${err.message}`);
|
|
66140
|
-
throw new FetchproxyBridgeDownError({
|
|
66141
|
-
originalError: err.message,
|
|
66142
|
-
retryAttempted: false,
|
|
66143
|
-
op: "download",
|
|
66144
|
-
url: opts.url,
|
|
66145
|
-
role: this.role,
|
|
66146
|
-
port: this.opts.port
|
|
66147
|
-
});
|
|
66148
66635
|
}
|
|
66149
66636
|
}
|
|
66150
66637
|
async _downloadOnce(opts) {
|
|
@@ -66173,7 +66660,9 @@ var FetchproxyServer = class {
|
|
|
66173
66660
|
// CAP and need the identical explanation; missing it then was the point
|
|
66174
66661
|
// of #279 — "both verbs" was counted off the two call sites in view
|
|
66175
66662
|
// rather than off the type. Three is still the number.
|
|
66176
|
-
opts.timeoutMs
|
|
66663
|
+
opts.timeoutMs,
|
|
66664
|
+
// A second download saves a duplicate "file (1)".
|
|
66665
|
+
false
|
|
66177
66666
|
);
|
|
66178
66667
|
}
|
|
66179
66668
|
/**
|
|
@@ -66264,6 +66753,52 @@ var FetchproxyServer = class {
|
|
|
66264
66753
|
await this.sendInnerFrame(inner);
|
|
66265
66754
|
return this._withVerbTimeout(pending, this.pendingStorage, id, origin);
|
|
66266
66755
|
}
|
|
66756
|
+
/**
|
|
66757
|
+
* 3.1.0+: read a declared REPEATED DOM structure from the user's
|
|
66758
|
+
* signed-in tab. Requires `'read_dom_list'` in capabilities AND `name` to
|
|
66759
|
+
* match a declared `domListSelectors` entry. The extension resolves the
|
|
66760
|
+
* entry's `itemSelector` via `document.querySelectorAll` in the matched
|
|
66761
|
+
* tab's DOM (isolated world), then resolves each declared field against
|
|
66762
|
+
* every matched item in turn — no page-JS execution, same as `readDom`.
|
|
66763
|
+
*
|
|
66764
|
+
* Returns one row per matched item, in document order, truncated to the
|
|
66765
|
+
* entry's declared `maxItems` when set. A field absent on a given item is
|
|
66766
|
+
* omitted from that item's row (the row itself is still returned). Throws
|
|
66767
|
+
* `FetchproxyProtocolError` on bridge failures and a plain `Error` on
|
|
66768
|
+
* developer mistakes (undeclared capability, undeclared name).
|
|
66769
|
+
*
|
|
66770
|
+
* Reads only what is CURRENTLY rendered — a virtualized or lazily-loaded
|
|
66771
|
+
* list returns only its visible rows. A caller that needs more scrolls
|
|
66772
|
+
* the list (out of band) and calls again.
|
|
66773
|
+
*/
|
|
66774
|
+
async readDomList(opts) {
|
|
66775
|
+
if (!this.opts.capabilities.includes("read_dom_list")) {
|
|
66776
|
+
throw new Error('FetchproxyServer.readDomList(): MCP did not declare "read_dom_list" in capabilities');
|
|
66777
|
+
}
|
|
66778
|
+
await this.ensureConnected();
|
|
66779
|
+
this.throwIfPendingPair();
|
|
66780
|
+
if (typeof opts.name !== "string" || opts.name.length === 0) {
|
|
66781
|
+
throw new Error("FetchproxyServer.readDomList: opts.name must be a non-empty string");
|
|
66782
|
+
}
|
|
66783
|
+
this.assertScopeSubset([opts.name], this.opts.domListSelectors.map((d) => d.name), "domListSelectors");
|
|
66784
|
+
if (opts.subdomain !== void 0)
|
|
66785
|
+
assertSubdomainLabel(opts.subdomain);
|
|
66786
|
+
const baseDomain = this.resolveBaseDomain(opts.domain);
|
|
66787
|
+
const host = opts.subdomain ? `${opts.subdomain}.${baseDomain}` : baseDomain;
|
|
66788
|
+
const origin = `https://${host}`;
|
|
66789
|
+
const id = this.nextRequestId++;
|
|
66790
|
+
const inner = {
|
|
66791
|
+
type: "request",
|
|
66792
|
+
id,
|
|
66793
|
+
op: "read_dom_list",
|
|
66794
|
+
init: { origin, name: opts.name }
|
|
66795
|
+
};
|
|
66796
|
+
const pending = this.guardPending(new Promise((resolve2, reject) => {
|
|
66797
|
+
this.pendingDomList.set(id, { resolve: resolve2, reject });
|
|
66798
|
+
}));
|
|
66799
|
+
await this.sendInnerFrame(inner);
|
|
66800
|
+
return this._withVerbTimeout(pending, this.pendingDomList, id, origin);
|
|
66801
|
+
}
|
|
66267
66802
|
/**
|
|
66268
66803
|
* 1.x+: run a declared GraphQL operation through the page's own Apollo
|
|
66269
66804
|
* client (`window.__APOLLO_CLIENT__`) in the signed-in tab's MAIN world.
|
|
@@ -66307,8 +66842,9 @@ var FetchproxyServer = class {
|
|
|
66307
66842
|
const pending = this.guardPending(new Promise((resolve2, reject) => {
|
|
66308
66843
|
this.pendingGraphql.set(id, { resolve: resolve2, reject });
|
|
66309
66844
|
}));
|
|
66310
|
-
|
|
66311
|
-
|
|
66845
|
+
const retrySafe = opts.retryOnTimeout === true;
|
|
66846
|
+
await this.sendInnerFrame(inner, { resendable: retrySafe });
|
|
66847
|
+
return this._withVerbTimeout(pending, this.pendingGraphql, id, opts.name, void 0, retrySafe);
|
|
66312
66848
|
}
|
|
66313
66849
|
assertScopeSubset(requested, declared, label) {
|
|
66314
66850
|
const undeclared = undeclaredKeys(requested, declared);
|
|
@@ -66345,6 +66881,7 @@ var FetchproxyServer = class {
|
|
|
66345
66881
|
onInner(inner) {
|
|
66346
66882
|
if (inner.type !== "response")
|
|
66347
66883
|
return;
|
|
66884
|
+
this.resendable.delete(inner.id);
|
|
66348
66885
|
this.lastExtensionMessageAt = Date.now();
|
|
66349
66886
|
const fetchCb = this.pending.get(inner.id);
|
|
66350
66887
|
if (fetchCb) {
|
|
@@ -66433,6 +66970,20 @@ var FetchproxyServer = class {
|
|
|
66433
66970
|
}
|
|
66434
66971
|
return;
|
|
66435
66972
|
}
|
|
66973
|
+
const domListCb = this.pendingDomList.get(inner.id);
|
|
66974
|
+
if (domListCb) {
|
|
66975
|
+
this.pendingDomList.delete(inner.id);
|
|
66976
|
+
if (inner.ok) {
|
|
66977
|
+
if (inner.op === "read_dom_list" && inner.rows) {
|
|
66978
|
+
domListCb.resolve(inner.rows.map((row) => ({ ...row })));
|
|
66979
|
+
} else {
|
|
66980
|
+
domListCb.reject(new FetchproxyProtocolError(`unexpected ${String(inner.op)} response on read_dom_list awaiter`));
|
|
66981
|
+
}
|
|
66982
|
+
} else {
|
|
66983
|
+
domListCb.reject(protocolErrorFrom(inner.error));
|
|
66984
|
+
}
|
|
66985
|
+
return;
|
|
66986
|
+
}
|
|
66436
66987
|
const downloadCb = this.pendingDownload.get(inner.id);
|
|
66437
66988
|
if (downloadCb) {
|
|
66438
66989
|
this.pendingDownload.delete(inner.id);
|
|
@@ -66492,42 +67043,133 @@ var FetchproxyServer = class {
|
|
|
66492
67043
|
}
|
|
66493
67044
|
}
|
|
66494
67045
|
}
|
|
66495
|
-
rejectAllPending(reason = "extension disconnected") {
|
|
67046
|
+
rejectAllPending(reason = "extension disconnected", keep = /* @__PURE__ */ new Set()) {
|
|
67047
|
+
for (const id of this.allPendingIds()) {
|
|
67048
|
+
if (!keep.has(id))
|
|
67049
|
+
this.rejectPendingId(id, reason);
|
|
67050
|
+
}
|
|
67051
|
+
if (keep.size === 0)
|
|
67052
|
+
this.resendable.clear();
|
|
67053
|
+
}
|
|
67054
|
+
/** Every request id with a live awaiter, across all op maps. */
|
|
67055
|
+
allPendingIds() {
|
|
67056
|
+
const ids = [];
|
|
67057
|
+
for (const map2 of this.pendingMaps())
|
|
67058
|
+
for (const id of map2.keys())
|
|
67059
|
+
ids.push(id);
|
|
67060
|
+
return ids;
|
|
67061
|
+
}
|
|
67062
|
+
pendingMaps() {
|
|
67063
|
+
return [
|
|
67064
|
+
this.pending,
|
|
67065
|
+
this.pendingReadCookies,
|
|
67066
|
+
this.pendingStorage,
|
|
67067
|
+
this.pendingWriteCookies,
|
|
67068
|
+
this.pendingCapture,
|
|
67069
|
+
this.pendingRedirect,
|
|
67070
|
+
this.pendingIdb,
|
|
67071
|
+
this.pendingDomList,
|
|
67072
|
+
this.pendingDownload,
|
|
67073
|
+
this.pendingGraphql
|
|
67074
|
+
];
|
|
67075
|
+
}
|
|
67076
|
+
isPendingId(id) {
|
|
67077
|
+
return this.pendingMaps().some((m) => m.has(id));
|
|
67078
|
+
}
|
|
67079
|
+
/** Fail the one awaiter registered under `id`, in its own map's shape. */
|
|
67080
|
+
rejectPendingId(id, reason) {
|
|
66496
67081
|
const err = new FetchproxyProtocolError(reason);
|
|
66497
|
-
|
|
66498
|
-
|
|
67082
|
+
this.resendable.delete(id);
|
|
67083
|
+
const fetchCb = this.pending.get(id);
|
|
67084
|
+
if (fetchCb) {
|
|
67085
|
+
this.pending.delete(id);
|
|
67086
|
+
fetchCb({
|
|
66499
67087
|
ok: false,
|
|
66500
67088
|
error: err.message,
|
|
66501
67089
|
kind: classifyFetchError(err.message),
|
|
66502
67090
|
retryAttempted: false
|
|
66503
67091
|
});
|
|
67092
|
+
return;
|
|
67093
|
+
}
|
|
67094
|
+
const cookieCb = this.pendingReadCookies.get(id);
|
|
67095
|
+
if (cookieCb) {
|
|
67096
|
+
this.pendingReadCookies.delete(id);
|
|
67097
|
+
cookieCb({ ok: false, error: err.message });
|
|
67098
|
+
return;
|
|
66504
67099
|
}
|
|
66505
|
-
|
|
66506
|
-
|
|
66507
|
-
|
|
66508
|
-
|
|
66509
|
-
|
|
66510
|
-
|
|
66511
|
-
|
|
66512
|
-
|
|
66513
|
-
|
|
66514
|
-
|
|
66515
|
-
|
|
66516
|
-
|
|
66517
|
-
|
|
66518
|
-
|
|
66519
|
-
|
|
66520
|
-
|
|
66521
|
-
|
|
66522
|
-
|
|
66523
|
-
|
|
66524
|
-
this.
|
|
66525
|
-
|
|
66526
|
-
|
|
66527
|
-
|
|
66528
|
-
|
|
66529
|
-
|
|
66530
|
-
|
|
67100
|
+
for (const map2 of [
|
|
67101
|
+
this.pendingStorage,
|
|
67102
|
+
this.pendingWriteCookies,
|
|
67103
|
+
this.pendingCapture,
|
|
67104
|
+
this.pendingRedirect,
|
|
67105
|
+
this.pendingIdb,
|
|
67106
|
+
this.pendingDomList,
|
|
67107
|
+
this.pendingDownload,
|
|
67108
|
+
this.pendingGraphql
|
|
67109
|
+
]) {
|
|
67110
|
+
const entry = map2.get(id);
|
|
67111
|
+
if (entry) {
|
|
67112
|
+
map2.delete(id);
|
|
67113
|
+
entry.reject(err);
|
|
67114
|
+
return;
|
|
67115
|
+
}
|
|
67116
|
+
}
|
|
67117
|
+
}
|
|
67118
|
+
noteResendable(inner) {
|
|
67119
|
+
this.resendable.set(inner.id, inner);
|
|
67120
|
+
if (this.resendable.size > 256) {
|
|
67121
|
+
for (const id of [...this.resendable.keys()]) {
|
|
67122
|
+
if (!this.isPendingId(id))
|
|
67123
|
+
this.resendable.delete(id);
|
|
67124
|
+
}
|
|
67125
|
+
}
|
|
67126
|
+
}
|
|
67127
|
+
/**
|
|
67128
|
+
* B-BUG-6: the host this peer relayed through has gone — most often a
|
|
67129
|
+
* short-lived process (a bootstrap lift, an `fpx` call) that won the
|
|
67130
|
+
* election and then closed. Fail only the in-flight requests that may
|
|
67131
|
+
* already have run (a non-idempotent fetch, a cookie write, a download);
|
|
67132
|
+
* re-elect and send the rest again. The replies to the originals died with
|
|
67133
|
+
* the old host, and each re-sent request keeps its awaiter and its
|
|
67134
|
+
* original deadline.
|
|
67135
|
+
*/
|
|
67136
|
+
async recoverFromHostLoss() {
|
|
67137
|
+
const toResend = /* @__PURE__ */ new Map();
|
|
67138
|
+
for (const [id, inner] of this.resendable) {
|
|
67139
|
+
if (this.isPendingId(id))
|
|
67140
|
+
toResend.set(id, inner);
|
|
67141
|
+
}
|
|
67142
|
+
this.resendable.clear();
|
|
67143
|
+
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()));
|
|
67144
|
+
if (toResend.size === 0)
|
|
67145
|
+
return;
|
|
67146
|
+
const lost = (id, e) => this.rejectPendingId(id, `fetchproxy: the bridge host exited and reconnecting failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
67147
|
+
try {
|
|
67148
|
+
await this.ensureConnected();
|
|
67149
|
+
} catch (e) {
|
|
67150
|
+
for (const id of toResend.keys())
|
|
67151
|
+
lost(id, e);
|
|
67152
|
+
return;
|
|
67153
|
+
}
|
|
67154
|
+
for (const [id, inner] of toResend) {
|
|
67155
|
+
if (!this.isPendingId(id))
|
|
67156
|
+
continue;
|
|
67157
|
+
try {
|
|
67158
|
+
await this.resendInnerFrame(inner);
|
|
67159
|
+
} catch (e) {
|
|
67160
|
+
lost(id, e);
|
|
67161
|
+
}
|
|
67162
|
+
}
|
|
67163
|
+
}
|
|
67164
|
+
/** Send an already-registered request again, keeping its awaiter on failure. */
|
|
67165
|
+
async resendInnerFrame(inner) {
|
|
67166
|
+
if (this.hostHandle)
|
|
67167
|
+
await this.hostHandle.sendOwnInner(inner);
|
|
67168
|
+
else if (this.peerHandle)
|
|
67169
|
+
await this.peerHandle.sendInner(inner);
|
|
67170
|
+
else
|
|
67171
|
+
throw new Error("no bridge handle after re-election");
|
|
67172
|
+
this.noteResendable(inner);
|
|
66531
67173
|
}
|
|
66532
67174
|
/**
|
|
66533
67175
|
* 0.5.2+: read the current pair-pending pair code from whichever handle
|
|
@@ -66630,6 +67272,12 @@ async function runOneLift(opts) {
|
|
|
66630
67272
|
if (envVal !== void 0 && envVal !== "" && envVal !== "0" && envVal !== "false") {
|
|
66631
67273
|
throw new BootstrapDisabledError(opts.serverName, envVar);
|
|
66632
67274
|
}
|
|
67275
|
+
for (const bucket of ["cookies", "localStorage", "sessionStorage"]) {
|
|
67276
|
+
const glob = opts.declare[bucket].find((k) => k.endsWith("*"));
|
|
67277
|
+
if (glob !== void 0) {
|
|
67278
|
+
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.`);
|
|
67279
|
+
}
|
|
67280
|
+
}
|
|
66633
67281
|
const factory = opts._serverFactory ?? defaultFactory;
|
|
66634
67282
|
const indexedDb = opts.declare.indexedDb ?? [];
|
|
66635
67283
|
const localStoragePointers = opts.declare.localStoragePointers ?? [];
|
|
@@ -66887,7 +67535,7 @@ function loadAccount(env = process.env) {
|
|
|
66887
67535
|
// package.json
|
|
66888
67536
|
var package_default = {
|
|
66889
67537
|
name: "canvas-parent-mcp",
|
|
66890
|
-
version: "2.1.
|
|
67538
|
+
version: "2.1.2",
|
|
66891
67539
|
mcpName: "io.github.chrischall/canvas-parent-mcp",
|
|
66892
67540
|
description: "Canvas LMS (Instructure) MCP server \u2014 student/observer read access with optional file download",
|
|
66893
67541
|
author: "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -66934,12 +67582,12 @@ var package_default = {
|
|
|
66934
67582
|
typecheck: "tsc -p tsconfig.json --noEmit"
|
|
66935
67583
|
},
|
|
66936
67584
|
dependencies: {
|
|
66937
|
-
"@chrischall/mcp-utils": "^
|
|
66938
|
-
"@fetchproxy/bootstrap": "^3.0
|
|
66939
|
-
"@fetchproxy/server": "^3.0
|
|
67585
|
+
"@chrischall/mcp-utils": "^2.4.0",
|
|
67586
|
+
"@fetchproxy/bootstrap": "^3.2.0",
|
|
67587
|
+
"@fetchproxy/server": "^3.2.0",
|
|
66940
67588
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
66941
67589
|
dotenv: "^18.0.0",
|
|
66942
|
-
zod: "^4.6.
|
|
67590
|
+
zod: "^4.6.5"
|
|
66943
67591
|
},
|
|
66944
67592
|
devDependencies: {
|
|
66945
67593
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
@@ -67199,7 +67847,7 @@ import { writeFile as writeFile3, stat } from "fs/promises";
|
|
|
67199
67847
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, renameSync, unlinkSync } from "node:fs";
|
|
67200
67848
|
import { dirname, join as join3, resolve } from "node:path";
|
|
67201
67849
|
import { homedir as homedir2 } from "node:os";
|
|
67202
|
-
import { randomBytes as
|
|
67850
|
+
import { randomBytes as randomBytes3, createHmac } from "node:crypto";
|
|
67203
67851
|
function bindingDigest(secret, salt) {
|
|
67204
67852
|
return createHmac("sha256", salt).update(secret).digest("hex").slice(0, 32);
|
|
67205
67853
|
}
|
|
@@ -67251,7 +67899,7 @@ function createFileStatePersistence(opts) {
|
|
|
67251
67899
|
},
|
|
67252
67900
|
save(state) {
|
|
67253
67901
|
const dir = dirname(filePath);
|
|
67254
|
-
const tmp = `${filePath}.tmp-${
|
|
67902
|
+
const tmp = `${filePath}.tmp-${randomBytes3(6).toString("hex")}`;
|
|
67255
67903
|
const dirExisted = existsSync(dir);
|
|
67256
67904
|
try {
|
|
67257
67905
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
@@ -67259,7 +67907,7 @@ function createFileStatePersistence(opts) {
|
|
|
67259
67907
|
chmodSync(dir, 448);
|
|
67260
67908
|
let boundTo;
|
|
67261
67909
|
if (secret !== void 0) {
|
|
67262
|
-
const salt =
|
|
67910
|
+
const salt = randomBytes3(16).toString("hex");
|
|
67263
67911
|
boundTo = { salt, digest: bindingDigest(secret, salt) };
|
|
67264
67912
|
}
|
|
67265
67913
|
const envelope = { v: 1, ...boundTo !== void 0 ? { boundTo } : {}, state };
|
|
@@ -67729,7 +68377,7 @@ var CanvasClient = class {
|
|
|
67729
68377
|
const res = await this.doRawRequest(path, opts);
|
|
67730
68378
|
const text = await res.text();
|
|
67731
68379
|
if (opts.responseType === "text") return text;
|
|
67732
|
-
return
|
|
68380
|
+
return parseJsonBody2(text) ?? null;
|
|
67733
68381
|
}
|
|
67734
68382
|
async requestPaginated(path, opts = {}) {
|
|
67735
68383
|
const perPage = opts.perPage ?? DEFAULT_PER_PAGE;
|
|
@@ -67739,7 +68387,7 @@ var CanvasClient = class {
|
|
|
67739
68387
|
for (let page = 0; page < maxPages; page++) {
|
|
67740
68388
|
const res = await this.doRawRequest(url4, opts);
|
|
67741
68389
|
const text = await res.text();
|
|
67742
|
-
const parsed =
|
|
68390
|
+
const parsed = parseJsonBody2(text) ?? [];
|
|
67743
68391
|
for (const item of parsed) out.push(item);
|
|
67744
68392
|
const linkHeader = res.headers.get("link");
|
|
67745
68393
|
const next = linkHeader ? parseLinkHeader(linkHeader).next : void 0;
|
|
@@ -67880,7 +68528,7 @@ var CanvasClient = class {
|
|
|
67880
68528
|
};
|
|
67881
68529
|
}
|
|
67882
68530
|
};
|
|
67883
|
-
function
|
|
68531
|
+
function parseJsonBody2(text) {
|
|
67884
68532
|
if (!text) return null;
|
|
67885
68533
|
const stripped = text.replace(/^while\(1\);/, "");
|
|
67886
68534
|
return JSON.parse(stripped);
|
|
@@ -68358,7 +69006,7 @@ try {
|
|
|
68358
69006
|
}
|
|
68359
69007
|
await runMcp({
|
|
68360
69008
|
name: "canvas",
|
|
68361
|
-
version: "2.1.
|
|
69009
|
+
version: "2.1.2",
|
|
68362
69010
|
// x-release-please-version
|
|
68363
69011
|
tools: [
|
|
68364
69012
|
// Outside the `if (resolved)` on purpose: an unconfigured server must
|