@jaw.id/cli 0.0.7 → 0.0.8
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/README.md +24 -24
- package/dist/base-command.js +2 -6
- package/dist/base-command.js.map +1 -1
- package/dist/commands/config/set.js +10 -32
- package/dist/commands/config/set.js.map +1 -1
- package/dist/commands/config/show.js +3 -10
- package/dist/commands/config/show.js.map +1 -1
- package/dist/commands/disconnect.js +13 -49
- package/dist/commands/disconnect.js.map +1 -1
- package/dist/commands/mcp/index.js +28 -85
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/rpc/call.js +15 -57
- package/dist/commands/rpc/call.js.map +1 -1
- package/dist/commands/version.js +2 -6
- package/dist/commands/version.js.map +1 -1
- package/dist/index.js +14 -54
- package/dist/index.js.map +1 -1
- package/dist/lib/bridge-singleton.js +12 -48
- package/dist/lib/bridge-singleton.js.map +1 -1
- package/dist/lib/config.js +2 -6
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/crypto.js +3 -15
- package/dist/lib/crypto.js.map +1 -1
- package/dist/lib/output.js +2 -6
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/paths.js.map +1 -1
- package/dist/lib/session-store.js.map +1 -1
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/ws-bridge.js +11 -43
- package/dist/lib/ws-bridge.js.map +1 -1
- package/dist/mcp/handlers/config.js +12 -25
- package/dist/mcp/handlers/config.js.map +1 -1
- package/dist/mcp/handlers/daemon.js +12 -46
- package/dist/mcp/handlers/daemon.js.map +1 -1
- package/dist/mcp/handlers/resources.js +1 -3
- package/dist/mcp/handlers/resources.js.map +1 -1
- package/dist/mcp/handlers/rpc.js +14 -54
- package/dist/mcp/handlers/rpc.js.map +1 -1
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/server.js +27 -82
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +1 -3
- package/dist/mcp/tools.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
package/dist/lib/ws-bridge.js
CHANGED
|
@@ -17,11 +17,7 @@ async function deriveSharedSecret(privateKey, peerPublicKey) {
|
|
|
17
17
|
async function encryptMessage(sharedSecret, payload) {
|
|
18
18
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
19
19
|
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
20
|
-
const cipherBuf = await subtle.encrypt(
|
|
21
|
-
{ name: "AES-GCM", iv },
|
|
22
|
-
sharedSecret,
|
|
23
|
-
plaintext
|
|
24
|
-
);
|
|
20
|
+
const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv }, sharedSecret, plaintext);
|
|
25
21
|
return {
|
|
26
22
|
iv: bufferToBase64(iv),
|
|
27
23
|
ciphertext: bufferToBase64(new Uint8Array(cipherBuf))
|
|
@@ -30,11 +26,7 @@ async function encryptMessage(sharedSecret, payload) {
|
|
|
30
26
|
async function decryptMessage(sharedSecret, envelope) {
|
|
31
27
|
const iv = Buffer.from(envelope.iv, "base64");
|
|
32
28
|
const ciphertext = Buffer.from(envelope.ciphertext, "base64");
|
|
33
|
-
const plainBuf = await subtle.decrypt(
|
|
34
|
-
{ name: "AES-GCM", iv },
|
|
35
|
-
sharedSecret,
|
|
36
|
-
ciphertext
|
|
37
|
-
);
|
|
29
|
+
const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv }, sharedSecret, ciphertext);
|
|
38
30
|
return JSON.parse(new TextDecoder().decode(plainBuf));
|
|
39
31
|
}
|
|
40
32
|
async function importKeyFromHex(type, hex) {
|
|
@@ -118,11 +110,7 @@ var WSBridge = class {
|
|
|
118
110
|
let expectingKeyExchange = !this.peerPublicKeyHex;
|
|
119
111
|
const timer = setTimeout(() => {
|
|
120
112
|
ws.close();
|
|
121
|
-
reject(
|
|
122
|
-
new Error(
|
|
123
|
-
"Browser did not connect in time.\nRun `jaw disconnect` then try again."
|
|
124
|
-
)
|
|
125
|
-
);
|
|
113
|
+
reject(new Error("Browser did not connect in time.\nRun `jaw disconnect` then try again."));
|
|
126
114
|
}, 3e4);
|
|
127
115
|
const sendEncryptedInit = async () => {
|
|
128
116
|
if (!this.sharedSecret) return;
|
|
@@ -145,10 +133,7 @@ var WSBridge = class {
|
|
|
145
133
|
if (!msg) return;
|
|
146
134
|
if (msg.type === "encrypted" && this.sharedSecret) {
|
|
147
135
|
try {
|
|
148
|
-
const inner = await decryptMessage(
|
|
149
|
-
this.sharedSecret,
|
|
150
|
-
msg
|
|
151
|
-
);
|
|
136
|
+
const inner = await decryptMessage(this.sharedSecret, msg);
|
|
152
137
|
if (inner.type === "ready") {
|
|
153
138
|
clearTimeout(readyTimer);
|
|
154
139
|
ws.off("message", onMsg);
|
|
@@ -235,9 +220,7 @@ var WSBridge = class {
|
|
|
235
220
|
return new Promise((resolve, reject) => {
|
|
236
221
|
const timer = setTimeout(() => {
|
|
237
222
|
reject(
|
|
238
|
-
new Error(
|
|
239
|
-
`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
|
|
240
|
-
)
|
|
223
|
+
new Error(`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`)
|
|
241
224
|
);
|
|
242
225
|
this.close();
|
|
243
226
|
}, this.timeout);
|
|
@@ -245,10 +228,7 @@ var WSBridge = class {
|
|
|
245
228
|
const msg = safeParse(data);
|
|
246
229
|
if (!msg || msg.type !== "encrypted" || !this.sharedSecret) return;
|
|
247
230
|
try {
|
|
248
|
-
const inner = await decryptMessage(
|
|
249
|
-
this.sharedSecret,
|
|
250
|
-
msg
|
|
251
|
-
);
|
|
231
|
+
const inner = await decryptMessage(this.sharedSecret, msg);
|
|
252
232
|
if (inner.type === "rpc_response" && inner.id === id) {
|
|
253
233
|
clearTimeout(timer);
|
|
254
234
|
ws.off("message", onMessage);
|
|
@@ -256,11 +236,7 @@ var WSBridge = class {
|
|
|
256
236
|
resolve(inner.data);
|
|
257
237
|
} else {
|
|
258
238
|
const err = inner.error;
|
|
259
|
-
reject(
|
|
260
|
-
new Error(
|
|
261
|
-
err ? `[${err.code}] ${err.message}` : "Request failed"
|
|
262
|
-
)
|
|
263
|
-
);
|
|
239
|
+
reject(new Error(err ? `[${err.code}] ${err.message}` : "Request failed"));
|
|
264
240
|
}
|
|
265
241
|
}
|
|
266
242
|
} catch {
|
|
@@ -280,10 +256,7 @@ var WSBridge = class {
|
|
|
280
256
|
const envelope = await encryptMessage(this.sharedSecret, {
|
|
281
257
|
type: "shutdown"
|
|
282
258
|
});
|
|
283
|
-
this.sendRaw(
|
|
284
|
-
this.ws,
|
|
285
|
-
JSON.stringify({ type: "encrypted", ...envelope })
|
|
286
|
-
);
|
|
259
|
+
this.sendRaw(this.ws, JSON.stringify({ type: "encrypted", ...envelope }));
|
|
287
260
|
} catch {
|
|
288
261
|
}
|
|
289
262
|
}
|
|
@@ -361,10 +334,8 @@ var WSBridge = class {
|
|
|
361
334
|
this.reconnectAttempts++;
|
|
362
335
|
setTimeout(() => {
|
|
363
336
|
if (this.disposed) return;
|
|
364
|
-
this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
);
|
|
337
|
+
this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(() => {
|
|
338
|
+
});
|
|
368
339
|
}, delay);
|
|
369
340
|
}
|
|
370
341
|
/** Send a raw string over the WebSocket, enforcing message size limits. */
|
|
@@ -374,10 +345,7 @@ var WSBridge = class {
|
|
|
374
345
|
async deriveSecret() {
|
|
375
346
|
if (!this.peerPublicKeyHex) return;
|
|
376
347
|
const privateKey = await importKeyFromHex("private", this.privateKeyHex);
|
|
377
|
-
const peerPublicKey = await importKeyFromHex(
|
|
378
|
-
"public",
|
|
379
|
-
this.peerPublicKeyHex
|
|
380
|
-
);
|
|
348
|
+
const peerPublicKey = await importKeyFromHex("public", this.peerPublicKeyHex);
|
|
381
349
|
this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);
|
|
382
350
|
}
|
|
383
351
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/crypto.ts","../../src/lib/ws-bridge.ts"],"names":[],"mappings":";;;;;;AAaA,IAAM,MAAA,GAAS,WAAW,MAAA,CAAO,MAAA;AAYjC,eAAsB,kBAAA,CACpB,YACA,aAAA,EACe;AACf,EAAA,OAAO,MAAA,CAAO,SAAA;AAAA,IACZ,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,aAAA,EAAc;AAAA,IACtC,UAAA;AAAA,IACA,EAAE,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,GAAA,EAAI;AAAA,IAC/B,KAAA;AAAA,IACA,CAAC,WAAW,SAAS;AAAA,GACvB;AACF;AASA,eAAsB,cAAA,CACpB,cACA,OAAA,EAC4B;AAC5B,EAAA,MAAM,KAAK,UAAA,CAAW,MAAA,CAAO,gBAAgB,IAAI,UAAA,CAAW,EAAE,CAAC,CAAA;AAC/D,EAAA,MAAM,SAAA,GAAY,IAAI,WAAA,EAAY,CAAE,OAAO,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAClE,EAAA,MAAM,SAAA,GAAY,MAAM,MAAA,CAAO,OAAA;AAAA,IAC7B,EAAE,IAAA,EAAM,SAAA,EAAW,EAAA,EAAG;AAAA,IACtB,YAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,eAAe,EAAE,CAAA;AAAA,IACrB,UAAA,EAAY,cAAA,CAAe,IAAI,UAAA,CAAW,SAAS,CAAC;AAAA,GACtD;AACF;AAEA,eAAsB,cAAA,CACpB,cACA,QAAA,EACkC;AAClC,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,IAAI,QAAQ,CAAA;AAC5C,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,YAAY,QAAQ,CAAA;AAC5D,EAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,OAAA;AAAA,IAC5B,EAAE,IAAA,EAAM,SAAA,EAAW,EAAA,EAAG;AAAA,IACtB,YAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAO,KAAK,KAAA,CAAM,IAAI,aAAY,CAAE,MAAA,CAAO,QAAQ,CAAC,CAAA;AACtD;AAaA,eAAsB,gBAAA,CACpB,MACA,GAAA,EACe;AACf,EAAA,MAAM,MAAA,GAAS,IAAA,KAAS,SAAA,GAAY,OAAA,GAAU,MAAA;AAC9C,EAAA,OAAO,MAAA,CAAO,SAAA;AAAA,IACZ,MAAA;AAAA,IACA,MAAA,CAAO,IAAA,CAAK,UAAA,CAAW,GAAG,CAAC,CAAA;AAAA,IAC3B,EAAE,IAAA,EAAM,MAAA,EAAQ,UAAA,EAAY,OAAA,EAAQ;AAAA,IACpC,IAAA;AAAA,IACA,IAAA,KAAS,SAAA,GAAY,CAAC,WAAW,IAAI;AAAC,GACxC;AACF;AAUA,SAAS,WAAW,GAAA,EAAyB;AAC3C,EAAA,IAAI,IAAI,MAAA,GAAS,CAAA,KAAM,GAAG,MAAM,IAAI,MAAM,yBAAyB,CAAA;AACnE,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,GAAA,CAAI,SAAS,CAAC,CAAA;AAC3C,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,MAAA,EAAQ,KAAK,CAAA,EAAG;AACtC,IAAA,KAAA,CAAM,CAAA,GAAI,CAAC,CAAA,GAAI,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA,EAAG,CAAA,GAAI,CAAC,CAAA,EAAG,EAAE,CAAA;AAAA,EACrD;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,eAAe,GAAA,EAAyB;AAC/C,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,CAAE,SAAS,QAAQ,CAAA;AAC3C;;;AChFA,IAAM,kBAAA,GAAqB,IAAA;AAU3B,IAAM,iBAAA,GAAoB,IAAI,IAAA,GAAO,IAAA;AAGrC,IAAM,0BAAA,GAA6B,GAAA;AAGnC,IAAM,sBAAA,GAAyB,CAAA;AAG/B,IAAM,uBAAA,GAA0B,GAAA;AAEzB,IAAM,WAAN,MAAe;AAAA,EACH,QAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACR,YAAA;AAAA,EACD,gBAAA;AAAA,EACA,YAAA,GAA4B,IAAA;AAAA,EAC5B,EAAA,GAAuB,IAAA;AAAA,EACvB,QAAA,GAAW,KAAA;AAAA;AAAA,EAGX,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,mBAAA,GAAsB,CAAA;AAAA;AAAA,EAGtB,iBAAA,GAAoB,CAAA;AAAA;AAAA,EAG5B,IAAI,aAAA,GAA+B;AACjC,IAAA,OAAO,IAAA,CAAK,gBAAA;AAAA,EACd;AAAA,EAEA,YAAY,OAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AACxB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,OAAA,GAAU,QAAQ,OAAA,IAAW,kBAAA;AAClC,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,aAAA;AAC7B,IAAA,IAAA,CAAK,eAAe,OAAA,CAAQ,YAAA;AAC5B,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,gBAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAA,CACJ,eAAA,EACA,gBAAA,EACe;AAEf,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AACvB,IAAA,IAAA,CAAK,gBAAA,GAAmB,gBAAA;AAGxB,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,MAAM,KAAK,YAAA,EAAa;AAAA,IAC1B;AAEA,IAAA,OAAO,IAAA,CAAK,eAAA,CAAgB,eAAA,EAAiB,gBAAgB,CAAA;AAAA,EAC/D;AAAA,EAEA,MAAc,eAAA,CACZ,eAAA,EACA,gBAAA,EACe;AACf,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,GAAA,GAAM,GAAG,IAAA,CAAK,QAAQ,YAAY,kBAAA,CAAmB,IAAA,CAAK,OAAO,CAAC,CAAA,SAAA,CAAA;AACxE,MAAA,MAAM,EAAA,GAAK,IAAI,SAAA,CAAU,GAAG,CAAA;AAE5B,MAAA,IAAI,aAAA,GAAgB,KAAA;AACpB,MAAA,IAAI,QAAA,GAAW,KAAA;AACf,MAAA,IAAI,oBAAA,GAAuB,CAAC,IAAA,CAAK,gBAAA;AAEjC,MAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,QAAA,EAAA,CAAG,KAAA,EAAM;AACT,QAAA,MAAA;AAAA,UACE,IAAI,KAAA;AAAA,YACF;AAAA;AAEF,SACF;AAAA,MACF,GAAG,GAAM,CAAA;AAET,MAAA,MAAM,oBAAoB,YAAY;AACpC,QAAA,IAAI,CAAC,KAAK,YAAA,EAAc;AACxB,QAAA,MAAM,QAAA,GAAW,MAAM,cAAA,CAAe,IAAA,CAAK,YAAA,EAAc;AAAA,UACvD,IAAA,EAAM,MAAA;AAAA,UACN,MAAA,EAAQ,KAAK,MAAA,CAAO,MAAA;AAAA,UACpB,OAAA,EAAS,KAAK,MAAA,CAAO,OAAA;AAAA,UACrB,GAAA,EAAK,KAAK,MAAA,CAAO,GAAA;AAAA,UACjB,YAAA,EAAc,KAAK,MAAA,CAAO;AAAA,SAC3B,CAAA;AACD,QAAA,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,EAAE,MAAM,WAAA,EAAa,GAAG,QAAA,EAAU,CAAC,CAAA;AAAA,MACrE,CAAA;AAEA,MAAA,MAAM,eAAe,MAAM;AACzB,QAAA,MAAM,UAAA,GAAa,WAAW,MAAM;AAClC,UAAA,EAAA,CAAG,KAAA,EAAM;AACT,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,2CAA2C,CAAC,CAAA;AAAA,QAC/D,GAAG,IAAM,CAAA;AAET,QAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,KAAyB;AAC5C,UAAA,MAAM,GAAA,GAAM,UAAU,IAAI,CAAA;AAC1B,UAAA,IAAI,CAAC,GAAA,EAAK;AAEV,UAAA,IAAI,GAAA,CAAI,IAAA,KAAS,WAAA,IAAe,IAAA,CAAK,YAAA,EAAc;AACjD,YAAA,IAAI;AACF,cAAA,MAAM,QAAQ,MAAM,cAAA;AAAA,gBAClB,IAAA,CAAK,YAAA;AAAA,gBACL;AAAA,eACF;AACA,cAAA,IAAI,KAAA,CAAM,SAAS,OAAA,EAAS;AAC1B,gBAAA,YAAA,CAAa,UAAU,CAAA;AACvB,gBAAA,EAAA,CAAG,GAAA,CAAI,WAAW,KAAK,CAAA;AACvB,gBAAA,IAAA,CAAK,iBAAA,GAAoB,CAAA;AACzB,gBAAA,OAAA,EAAQ;AAAA,cACV;AAAA,YACF,CAAA,CAAA,MAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF,CAAA;AACA,QAAA,EAAA,CAAG,EAAA,CAAG,WAAW,KAAK,CAAA;AAAA,MACxB,CAAA;AAEA,MAAA,MAAM,iBAAiB,YAAY;AACjC,QAAA,IAAI,QAAA,EAAU;AACd,QAAA,QAAA,GAAW,IAAA;AACX,QAAA,YAAA,CAAa,KAAK,CAAA;AAElB,QAAA,YAAA,EAAa;AACb,QAAA,MAAM,iBAAA,EAAkB;AAAA,MAC1B,CAAA;AAEA,MAAA,EAAA,CAAG,EAAA,CAAG,QAAQ,MAAM;AAClB,QAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AAAA,MACZ,CAAC,CAAA;AAED,MAAA,EAAA,CAAG,EAAA,CAAG,SAAA,EAAW,OAAO,IAAA,KAAS;AAC/B,QAAA,MAAM,GAAA,GAAM,UAAU,IAAI,CAAA;AAC1B,QAAA,IAAI,CAAC,GAAA,EAAK;AAEV,QAAA,IAAI,GAAA,CAAI,SAAS,QAAA,EAAU;AACzB,UAAA,IAAI,IAAI,gBAAA,EAAkB;AACxB,YAAA,IAAI,KAAK,YAAA,EAAc;AAErB,cAAA,MAAM,cAAA,EAAe;AAAA,YACvB,CAAA,MAAO;AAEL,cAAA,oBAAA,GAAuB,IAAA;AAAA,YACzB;AAAA,UACF,CAAA,MAAA,IAAW,CAAC,aAAA,IAAiB,eAAA,EAAiB;AAC5C,YAAA,aAAA,GAAgB,IAAA;AAChB,YAAA,oBAAA,GAAuB,IAAA;AACvB,YAAA,eAAA,EAAgB,CAAE,MAAM,MAAM;AAAA,YAE9B,CAAC,CAAA;AAAA,UACH;AAAA,QACF,CAAA,MAAA,IAAW,GAAA,CAAI,IAAA,KAAS,mBAAA,EAAqB;AAC3C,UAAA,oBAAA,GAAuB,IAAA;AAAA,QAEzB,CAAA,MAAA,IAAW,GAAA,CAAI,IAAA,KAAS,sBAAA,EAAwB;AAE9C,UAAA,IAAA,CAAK,uBAAA,EAAwB;AAAA,QAC/B,CAAA,MAAA,IAAW,GAAA,CAAI,IAAA,KAAS,cAAA,IAAkB,oBAAA,EAAsB;AAC9D,UAAA,oBAAA,GAAuB,KAAA;AACvB,UAAA,MAAM,UAAU,GAAA,CAAI,SAAA;AACpB,UAAA,IAAA,CAAK,gBAAA,GAAmB,OAAA;AACxB,UAAA,MAAM,KAAK,YAAA,EAAa;AACxB,UAAA,gBAAA,GAAmB,OAAO,CAAA;AAC1B,UAAA,MAAM,cAAA,EAAe;AAAA,QACvB;AAAA,MACF,CAAC,CAAA;AAED,MAAA,EAAA,CAAG,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAQ;AACtB,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,MAAA,CAAO,GAAG,CAAA;AAAA,MACZ,CAAC,CAAA;AAED,MAAA,EAAA,CAAG,EAAA,CAAG,SAAS,MAAM;AACnB,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAClB,UAAA,IAAA,CAAK,qBAAA,EAAsB;AAAA,QAC7B;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,CAAQ,MAAA,EAAgB,MAAA,EAAoC;AAChE,IAAA,MAAM,KAAK,IAAA,CAAK,EAAA;AAChB,IAAA,IAAI,CAAC,EAAA,IAAM,EAAA,CAAG,UAAA,KAAe,UAAU,IAAA,EAAM;AAC3C,MAAA,MAAM,IAAI,MAAM,wBAAwB,CAAA;AAAA,IAC1C;AACA,IAAA,IAAI,CAAC,KAAK,YAAA,EAAc;AACtB,MAAA,MAAM,IAAI,MAAM,oDAA+C,CAAA;AAAA,IACjE;AAEA,IAAA,MAAM,KAAY,MAAA,CAAA,UAAA,EAAW;AAE7B,IAAA,MAAM,QAAA,GAAW,MAAM,cAAA,CAAe,IAAA,CAAK,YAAA,EAAc;AAAA,MACvD,IAAA,EAAM,aAAA;AAAA,MACN,EAAA;AAAA,MACA,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,MAAM,UAAA,GAAa,KAAK,SAAA,CAAU,EAAE,MAAM,WAAA,EAAa,GAAG,UAAU,CAAA;AACpE,IAAA,iBAAA,CAAkB,YAAY,MAAM,CAAA;AAEpC,IAAA,OAAO,IAAI,OAAA,CAAiB,CAAC,OAAA,EAAS,MAAA,KAAW;AAC/C,MAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,QAAA,MAAA;AAAA,UACE,IAAI,KAAA;AAAA,YACF,CAAA,wBAAA,EAA2B,IAAA,CAAK,OAAA,GAAU,GAAI,CAAA,8CAAA;AAAA;AAEhD,SACF;AACA,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb,CAAA,EAAG,KAAK,OAAO,CAAA;AAEf,MAAA,MAAM,SAAA,GAAY,OAAO,IAAA,KAAyB;AAChD,QAAA,MAAM,GAAA,GAAM,UAAU,IAAI,CAAA;AAC1B,QAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,SAAS,WAAA,IAAe,CAAC,KAAK,YAAA,EAAc;AAE5D,QAAA,IAAI;AACF,UAAA,MAAM,QAAQ,MAAM,cAAA;AAAA,YAClB,IAAA,CAAK,YAAA;AAAA,YACL;AAAA,WACF;AACA,UAAA,IAAI,KAAA,CAAM,IAAA,KAAS,cAAA,IAAkB,KAAA,CAAM,OAAO,EAAA,EAAI;AACpD,YAAA,YAAA,CAAa,KAAK,CAAA;AAClB,YAAA,EAAA,CAAG,GAAA,CAAI,WAAW,SAAS,CAAA;AAE3B,YAAA,IAAI,MAAM,OAAA,EAAS;AACjB,cAAA,OAAA,CAAQ,MAAM,IAAI,CAAA;AAAA,YACpB,CAAA,MAAO;AACL,cAAA,MAAM,MAAM,KAAA,CAAM,KAAA;AAGlB,cAAA,MAAA;AAAA,gBACE,IAAI,KAAA;AAAA,kBACF,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAA,GAAK;AAAA;AACzC,eACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF,CAAA;AAEA,MAAA,EAAA,CAAG,EAAA,CAAG,WAAW,SAAS,CAAA;AAC1B,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,UAAU,CAAA;AAAA,IAC7B,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAA,GAAkB;AAChB,IAAA,OAAO,IAAA,CAAK,EAAA,EAAI,UAAA,KAAe,SAAA,CAAU,IAAA;AAAA,EAC3C;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAI,KAAK,EAAA,EAAI,UAAA,KAAe,SAAA,CAAU,IAAA,IAAQ,KAAK,YAAA,EAAc;AAC/D,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,cAAA,CAAe,IAAA,CAAK,YAAA,EAAc;AAAA,UACvD,IAAA,EAAM;AAAA,SACP,CAAA;AACD,QAAA,IAAA,CAAK,OAAA;AAAA,UACH,IAAA,CAAK,EAAA;AAAA,UACL,KAAK,SAAA,CAAU,EAAE,MAAM,WAAA,EAAa,GAAG,UAAU;AAAA,SACnD;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,kBAAA,GAAoC;AACxC,IAAA,IAAI,CAAC,KAAK,gBAAA,EAAkB;AAE1B,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,MAAM,KAAK,YAAA,EAAa;AAExB,IAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AACpC,MAAA,MAAM,GAAA,GAAM,GAAG,IAAA,CAAK,QAAQ,YAAY,kBAAA,CAAmB,IAAA,CAAK,OAAO,CAAC,CAAA,SAAA,CAAA;AACxE,MAAA,MAAM,EAAA,GAAK,IAAI,SAAA,CAAU,GAAG,CAAA;AAE5B,MAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,QAAA,IAAI;AACF,UAAA,EAAA,CAAG,KAAA,EAAM;AAAA,QACX,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,OAAA,EAAQ;AAAA,MACV,GAAG,GAAI,CAAA;AAEP,MAAA,EAAA,CAAG,EAAA,CAAG,QAAQ,YAAY;AACxB,QAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,QAAA,IAAI;AACF,UAAA,MAAM,KAAK,QAAA,EAAS;AAAA,QACtB,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AAED,MAAA,EAAA,CAAG,EAAA,CAAG,SAAS,MAAM;AACnB,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAI,KAAK,EAAA,EAAI;AACX,MAAA,IAAI;AACF,QAAA,IAAA,CAAK,GAAG,KAAA,EAAM;AAAA,MAChB,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,IAAA,CAAK,EAAA,GAAK,IAAA;AAAA,IACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAAA,GAAgC;AACtC,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,CAAC,IAAA,CAAK,eAAA,EAAiB;AAE5C,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,GAAA,GAAM,IAAA,CAAK,mBAAA,GAAsB,0BAAA,EAA4B;AAC/D,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,mBAAA,GAAsB,GAAA;AAG3B,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,IAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AAExB,IAAA,IAAA,CAAK,eAAA,EAAgB,CAAE,KAAA,CAAM,MAAM;AAAA,IAEnC,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBAAA,GAA8B;AACpC,IAAA,IAAI,KAAK,QAAA,EAAU;AACnB,IAAA,IAAI,IAAA,CAAK,qBAAqB,sBAAA,EAAwB;AAEtD,IAAA,MAAM,QAAQ,uBAAA,GAA0B,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,iBAAiB,CAAA;AAC1E,IAAA,IAAA,CAAK,iBAAA,EAAA;AAEL,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAI,KAAK,QAAA,EAAU;AACnB,MAAA,IAAA,CAAK,eAAA,CAAgB,IAAA,CAAK,eAAA,EAAiB,IAAA,CAAK,gBAAgB,CAAA,CAAE,KAAA;AAAA,QAChE,MAAM;AAAA,QAEN;AAAA,OACF;AAAA,IACF,GAAG,KAAK,CAAA;AAAA,EACV;AAAA;AAAA,EAGQ,OAAA,CAAQ,IAAe,IAAA,EAAoB;AACjD,IAAA,EAAA,CAAG,KAAK,IAAI,CAAA;AAAA,EACd;AAAA,EAEA,MAAc,YAAA,GAA8B;AAC1C,IAAA,IAAI,CAAC,KAAK,gBAAA,EAAkB;AAC5B,IAAA,MAAM,UAAA,GAAa,MAAM,gBAAA,CAAiB,SAAA,EAAW,KAAK,aAAa,CAAA;AACvE,IAAA,MAAM,gBAAgB,MAAM,gBAAA;AAAA,MAC1B,QAAA;AAAA,MACA,IAAA,CAAK;AAAA,KACP;AACA,IAAA,IAAA,CAAK,YAAA,GAAe,MAAM,kBAAA,CAAmB,UAAA,EAAY,aAAa,CAAA;AAAA,EACxE;AACF;AAMA,SAAS,iBAAA,CAAkB,YAAoB,MAAA,EAAsB;AACnE,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,UAAA,CAAW,UAAA,EAAY,OAAO,CAAA;AACxD,EAAA,IAAI,aAAa,iBAAA,EAAmB;AAClC,IAAA,MAAM,MAAA,GAAA,CAAU,UAAA,IAAc,IAAA,GAAO,IAAA,CAAA,EAAO,QAAQ,CAAC,CAAA;AACrD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,eAAe,MAAM,CAAA,eAAA,EAAkB,MAAM,CAAA,WAAA,EAAc,iBAAA,IAAqB,OAAO,IAAA,CAAK,CAAA,qDAAA;AAAA,KAE9F;AAAA,EACF;AACF;AAEA,SAAS,UAAU,IAAA,EAAsD;AACvE,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,QAAA,EAAU,CAAA;AAAA,EACnC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF","file":"ws-bridge.js","sourcesContent":["/**\n * E2E encryption primitives for CLI ↔ browser communication.\n *\n * Uses ECDH P-256 for key exchange and AES-256-GCM for message encryption.\n * Mirrors the same crypto operations in @jaw.id/core but uses only\n * Node.js built-in crypto.subtle — no external dependencies.\n */\n\nimport type { webcrypto } from \"node:crypto\";\n\ntype CKey = webcrypto.CryptoKey;\ntype CKeyPair = webcrypto.CryptoKeyPair;\n\nconst subtle = globalThis.crypto.subtle;\n\n// ── Key generation ───────────────────────────────────────────────\n\nexport async function generateKeyPair(): Promise<CKeyPair> {\n return subtle.generateKey(\n { name: \"ECDH\", namedCurve: \"P-256\" },\n true,\n [\"deriveKey\"],\n ) as Promise<CKeyPair>;\n}\n\nexport async function deriveSharedSecret(\n privateKey: CKey,\n peerPublicKey: CKey,\n): Promise<CKey> {\n return subtle.deriveKey(\n { name: \"ECDH\", public: peerPublicKey },\n privateKey,\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\n// ── Encrypt / Decrypt ────────────────────────────────────────────\n\nexport interface EncryptedEnvelope {\n iv: string; // base64\n ciphertext: string; // base64\n}\n\nexport async function encryptMessage(\n sharedSecret: CKey,\n payload: Record<string, unknown>,\n): Promise<EncryptedEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const plaintext = new TextEncoder().encode(JSON.stringify(payload));\n const cipherBuf = await subtle.encrypt(\n { name: \"AES-GCM\", iv },\n sharedSecret,\n plaintext,\n );\n return {\n iv: bufferToBase64(iv),\n ciphertext: bufferToBase64(new Uint8Array(cipherBuf)),\n };\n}\n\nexport async function decryptMessage(\n sharedSecret: CKey,\n envelope: EncryptedEnvelope,\n): Promise<Record<string, unknown>> {\n const iv = Buffer.from(envelope.iv, \"base64\");\n const ciphertext = Buffer.from(envelope.ciphertext, \"base64\");\n const plainBuf = await subtle.decrypt(\n { name: \"AES-GCM\", iv },\n sharedSecret,\n ciphertext,\n );\n return JSON.parse(new TextDecoder().decode(plainBuf));\n}\n\n// ── Key import / export (hex) ────────────────────────────────────\n\nexport async function exportKeyToHex(\n type: \"private\" | \"public\",\n key: CKey,\n): Promise<string> {\n const format = type === \"private\" ? \"pkcs8\" : \"spki\";\n const buf = await subtle.exportKey(format, key);\n return bytesToHex(new Uint8Array(buf));\n}\n\nexport async function importKeyFromHex(\n type: \"private\" | \"public\",\n hex: string,\n): Promise<CKey> {\n const format = type === \"private\" ? \"pkcs8\" : \"spki\";\n return subtle.importKey(\n format,\n Buffer.from(hexToBytes(hex)),\n { name: \"ECDH\", namedCurve: \"P-256\" },\n true,\n type === \"private\" ? [\"deriveKey\"] : [],\n );\n}\n\n// ── Encoding helpers ─────────────────────────────────────────────\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error(\"Invalid hex: odd length\");\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\n\nfunction bufferToBase64(buf: Uint8Array): string {\n return Buffer.from(buf).toString(\"base64\");\n}\n","/**\n * WSBridge — CLI-side relay client\n *\n * Connects to the cloud relay (wss://relay.jaw.id) instead of a local daemon.\n * All messages (except key_exchange) are E2E encrypted via ECDH + AES-256-GCM.\n */\n\nimport * as crypto from \"node:crypto\";\nimport type { webcrypto } from \"node:crypto\";\nimport WebSocket from \"ws\";\nimport {\n deriveSharedSecret,\n encryptMessage,\n decryptMessage,\n importKeyFromHex,\n type EncryptedEnvelope,\n} from \"./crypto.js\";\n\ntype CKey = webcrypto.CryptoKey;\n\nexport interface WSBridgeConfig {\n apiKey: string;\n chainId: number;\n ens?: string;\n paymasterUrl?: string;\n}\n\nexport interface WSBridgeOptions {\n relayUrl: string;\n session: string;\n timeout?: number;\n config: WSBridgeConfig;\n /** CLI's ECDH private key (hex). Loaded from relay.json for existing sessions. */\n privateKeyHex: string;\n /** CLI's ECDH public key (hex). Sent to browser for key derivation. */\n publicKeyHex: string;\n /** Browser's ECDH public key (hex). Null if new session (key exchange needed). */\n peerPublicKeyHex: string | null;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n/**\n * Maximum outbound message size (5 MB).\n *\n * wallet_sendCalls with large batches (50+ calls, complex calldata) can reach\n * hundreds of KB. Base64 encoding of the AES-GCM ciphertext adds ~33% overhead.\n * 5 MB is generous enough for any realistic batch while still preventing\n * accidental memory issues.\n */\nconst MAX_MESSAGE_BYTES = 5 * 1024 * 1024;\n\n/** Minimum time between browser reopen attempts (ms). */\nconst BROWSER_REOPEN_COOLDOWN_MS = 5_000;\n\n/** Maximum reconnection attempts before giving up. */\nconst MAX_RECONNECT_ATTEMPTS = 3;\n\n/** Base delay for exponential backoff (ms). */\nconst RECONNECT_BASE_DELAY_MS = 1_000;\n\nexport class WSBridge {\n private readonly relayUrl: string;\n private readonly session: string;\n private readonly timeout: number;\n private readonly config: WSBridgeConfig;\n private readonly privateKeyHex: string;\n readonly publicKeyHex: string;\n private peerPublicKeyHex: string | null;\n private sharedSecret: CKey | null = null;\n private ws: WebSocket | null = null;\n private disposed = false;\n\n // Auto-reopen browser state\n private onBrowserNeeded: (() => Promise<void>) | undefined;\n private onPeerKeyChanged: ((key: string) => void) | undefined;\n private lastBrowserOpenTime = 0;\n\n // Reconnection state\n private reconnectAttempts = 0;\n\n /** Updated after key exchange — caller should persist this. */\n get peerPublicKey(): string | null {\n return this.peerPublicKeyHex;\n }\n\n constructor(options: WSBridgeOptions) {\n this.relayUrl = options.relayUrl;\n this.session = options.session;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n this.config = options.config;\n this.privateKeyHex = options.privateKeyHex;\n this.publicKeyHex = options.publicKeyHex;\n this.peerPublicKeyHex = options.peerPublicKeyHex;\n }\n\n /**\n * Connect to the relay and wait for the browser to be ready.\n *\n * @param onBrowserNeeded — called when the relay reports no browser connected.\n * @param onPeerKeyChanged — called when a key_exchange updates the peer key.\n */\n async connect(\n onBrowserNeeded?: () => Promise<void>,\n onPeerKeyChanged?: (newPeerPublicKeyHex: string) => void,\n ): Promise<void> {\n // Store callbacks for auto-reopen on browser disconnect\n this.onBrowserNeeded = onBrowserNeeded;\n this.onPeerKeyChanged = onPeerKeyChanged;\n\n // Pre-derive shared secret if we already have the peer key\n if (this.peerPublicKeyHex) {\n await this.deriveSecret();\n }\n\n return this.connectInternal(onBrowserNeeded, onPeerKeyChanged);\n }\n\n private async connectInternal(\n onBrowserNeeded?: () => Promise<void>,\n onPeerKeyChanged?: (newPeerPublicKeyHex: string) => void,\n ): Promise<void> {\n return new Promise((resolve, reject) => {\n const url = `${this.relayUrl}?session=${encodeURIComponent(this.session)}&role=cli`;\n const ws = new WebSocket(url);\n\n let browserOpened = false;\n let resolved = false;\n let expectingKeyExchange = !this.peerPublicKeyHex;\n\n const timer = setTimeout(() => {\n ws.close();\n reject(\n new Error(\n \"Browser did not connect in time.\\n\" +\n \"Run `jaw disconnect` then try again.\",\n ),\n );\n }, 30_000);\n\n const sendEncryptedInit = async () => {\n if (!this.sharedSecret) return;\n const envelope = await encryptMessage(this.sharedSecret, {\n type: \"init\",\n apiKey: this.config.apiKey,\n chainId: this.config.chainId,\n ens: this.config.ens,\n paymasterUrl: this.config.paymasterUrl,\n });\n this.sendRaw(ws, JSON.stringify({ type: \"encrypted\", ...envelope }));\n };\n\n const waitForReady = () => {\n const readyTimer = setTimeout(() => {\n ws.close();\n reject(new Error(\"Browser SDK did not become ready in time.\"));\n }, 15_000);\n\n const onMsg = async (data: WebSocket.Data) => {\n const msg = safeParse(data);\n if (!msg) return;\n\n if (msg.type === \"encrypted\" && this.sharedSecret) {\n try {\n const inner = await decryptMessage(\n this.sharedSecret,\n msg as unknown as EncryptedEnvelope,\n );\n if (inner.type === \"ready\") {\n clearTimeout(readyTimer);\n ws.off(\"message\", onMsg);\n this.reconnectAttempts = 0; // Reset on successful connect\n resolve();\n }\n } catch {\n // Not a valid encrypted message for us, ignore\n }\n }\n };\n ws.on(\"message\", onMsg);\n };\n\n const onBrowserReady = async () => {\n if (resolved) return;\n resolved = true;\n clearTimeout(timer);\n // Set up listener BEFORE sending init to avoid missing the ready response\n waitForReady();\n await sendEncryptedInit();\n };\n\n ws.on(\"open\", () => {\n this.ws = ws;\n });\n\n ws.on(\"message\", async (data) => {\n const msg = safeParse(data);\n if (!msg) return;\n\n if (msg.type === \"status\") {\n if (msg.browserConnected) {\n if (this.sharedSecret) {\n // Already have shared secret — skip key exchange\n await onBrowserReady();\n } else {\n // Browser is connected but we don't have its key yet.\n expectingKeyExchange = true;\n }\n } else if (!browserOpened && onBrowserNeeded) {\n browserOpened = true;\n expectingKeyExchange = true;\n onBrowserNeeded().catch(() => {\n /* best effort */\n });\n }\n } else if (msg.type === \"browser_connected\") {\n expectingKeyExchange = true;\n // Wait for key_exchange from browser\n } else if (msg.type === \"browser_disconnected\") {\n // Browser tab closed — attempt to reopen after cooldown\n this.handleBrowserDisconnect();\n } else if (msg.type === \"key_exchange\" && expectingKeyExchange) {\n expectingKeyExchange = false;\n const peerKey = msg.publicKey as string;\n this.peerPublicKeyHex = peerKey;\n await this.deriveSecret();\n onPeerKeyChanged?.(peerKey);\n await onBrowserReady();\n }\n });\n\n ws.on(\"error\", (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n ws.on(\"close\", () => {\n clearTimeout(timer);\n if (!this.disposed) {\n this.handleRelayDisconnect();\n }\n });\n });\n }\n\n /**\n * Send an encrypted RPC request through the relay to the browser SDK.\n */\n async request(method: string, params?: unknown): Promise<unknown> {\n const ws = this.ws;\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"Not connected to relay\");\n }\n if (!this.sharedSecret) {\n throw new Error(\"No shared secret — key exchange not completed\");\n }\n\n const id = crypto.randomUUID();\n\n const envelope = await encryptMessage(this.sharedSecret, {\n type: \"rpc_request\",\n id,\n method,\n params,\n });\n\n const serialized = JSON.stringify({ type: \"encrypted\", ...envelope });\n assertMessageSize(serialized, method);\n\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(\n `Request timed out after ${this.timeout / 1000}s. ` +\n \"Did you complete the action in the browser?\",\n ),\n );\n this.close();\n }, this.timeout);\n\n const onMessage = async (data: WebSocket.Data) => {\n const msg = safeParse(data);\n if (!msg || msg.type !== \"encrypted\" || !this.sharedSecret) return;\n\n try {\n const inner = await decryptMessage(\n this.sharedSecret,\n msg as unknown as EncryptedEnvelope,\n );\n if (inner.type === \"rpc_response\" && inner.id === id) {\n clearTimeout(timer);\n ws.off(\"message\", onMessage);\n\n if (inner.success) {\n resolve(inner.data);\n } else {\n const err = inner.error as\n | { code: number; message: string }\n | undefined;\n reject(\n new Error(\n err ? `[${err.code}] ${err.message}` : \"Request failed\",\n ),\n );\n }\n }\n } catch {\n // Decryption failed — not our message or tampered, ignore\n }\n };\n\n ws.on(\"message\", onMessage);\n this.sendRaw(ws, serialized);\n });\n }\n\n isOpen(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n async shutdown(): Promise<void> {\n this.disposed = true;\n if (this.ws?.readyState === WebSocket.OPEN && this.sharedSecret) {\n try {\n const envelope = await encryptMessage(this.sharedSecret, {\n type: \"shutdown\",\n });\n this.sendRaw(\n this.ws,\n JSON.stringify({ type: \"encrypted\", ...envelope }),\n );\n } catch {\n // Best effort\n }\n }\n this.close();\n }\n\n /**\n * Connect to relay and send shutdown directly — no init/ready handshake.\n * Used by `jaw disconnect` when we just need to tell the browser to close.\n */\n async connectAndShutdown(): Promise<void> {\n if (!this.peerPublicKeyHex) {\n // No peer key means browser never connected — nothing to shut down\n return;\n }\n\n this.disposed = true;\n await this.deriveSecret();\n\n return new Promise<void>((resolve) => {\n const url = `${this.relayUrl}?session=${encodeURIComponent(this.session)}&role=cli`;\n const ws = new WebSocket(url);\n\n const timer = setTimeout(() => {\n try {\n ws.close();\n } catch {\n /* ignore */\n }\n resolve();\n }, 3000);\n\n ws.on(\"open\", async () => {\n this.ws = ws;\n try {\n await this.shutdown();\n } catch {\n // Best effort\n }\n clearTimeout(timer);\n resolve();\n });\n\n ws.on(\"error\", () => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n close(): void {\n this.disposed = true;\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n // ignore\n }\n this.ws = null;\n }\n }\n\n /**\n * Auto-reopen browser when browser_disconnected is received from relay.\n * Respects a cooldown to prevent rapid re-opening.\n */\n private handleBrowserDisconnect(): void {\n if (this.disposed || !this.onBrowserNeeded) return;\n\n const now = Date.now();\n if (now - this.lastBrowserOpenTime < BROWSER_REOPEN_COOLDOWN_MS) {\n return; // Too soon — skip this reopen\n }\n\n this.lastBrowserOpenTime = now;\n\n // Reset shared secret — the new browser tab will do a fresh key exchange\n this.sharedSecret = null;\n this.peerPublicKeyHex = null;\n\n this.onBrowserNeeded().catch(() => {\n // Best effort — if browser open fails, the next request will error\n });\n }\n\n /**\n * Attempt to reconnect to the relay with exponential backoff\n * when the WebSocket connection drops unexpectedly.\n */\n private handleRelayDisconnect(): void {\n if (this.disposed) return;\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;\n\n const delay = RECONNECT_BASE_DELAY_MS * Math.pow(2, this.reconnectAttempts);\n this.reconnectAttempts++;\n\n setTimeout(() => {\n if (this.disposed) return;\n this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(\n () => {\n // Reconnection failed — will retry if attempts remain\n },\n );\n }, delay);\n }\n\n /** Send a raw string over the WebSocket, enforcing message size limits. */\n private sendRaw(ws: WebSocket, data: string): void {\n ws.send(data);\n }\n\n private async deriveSecret(): Promise<void> {\n if (!this.peerPublicKeyHex) return;\n const privateKey = await importKeyFromHex(\"private\", this.privateKeyHex);\n const peerPublicKey = await importKeyFromHex(\n \"public\",\n this.peerPublicKeyHex,\n );\n this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);\n }\n}\n\n/**\n * Validate message size before sending to the relay.\n * Throws with a descriptive error if the message is too large.\n */\nfunction assertMessageSize(serialized: string, method: string): void {\n const byteLength = Buffer.byteLength(serialized, \"utf-8\");\n if (byteLength > MAX_MESSAGE_BYTES) {\n const sizeMB = (byteLength / (1024 * 1024)).toFixed(2);\n throw new Error(\n `Message for ${method} is too large (${sizeMB} MB, limit ${MAX_MESSAGE_BYTES / (1024 * 1024)} MB). ` +\n \"Try reducing the number of calls in your batch.\",\n );\n }\n}\n\nfunction safeParse(data: WebSocket.Data): Record<string, unknown> | null {\n try {\n return JSON.parse(data.toString()) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/crypto.ts","../../src/lib/ws-bridge.ts"],"names":[],"mappings":"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","file":"ws-bridge.js","sourcesContent":["/**\n * E2E encryption primitives for CLI ↔ browser communication.\n *\n * Uses ECDH P-256 for key exchange and AES-256-GCM for message encryption.\n * Mirrors the same crypto operations in @jaw.id/core but uses only\n * Node.js built-in crypto.subtle — no external dependencies.\n */\n\nimport type { webcrypto } from 'node:crypto';\n\ntype CKey = webcrypto.CryptoKey;\ntype CKeyPair = webcrypto.CryptoKeyPair;\n\nconst subtle = globalThis.crypto.subtle;\n\n// ── Key generation ───────────────────────────────────────────────\n\nexport async function generateKeyPair(): Promise<CKeyPair> {\n return subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']) as Promise<CKeyPair>;\n}\n\nexport async function deriveSharedSecret(privateKey: CKey, peerPublicKey: CKey): Promise<CKey> {\n return subtle.deriveKey(\n { name: 'ECDH', public: peerPublicKey },\n privateKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt']\n );\n}\n\n// ── Encrypt / Decrypt ────────────────────────────────────────────\n\nexport interface EncryptedEnvelope {\n iv: string; // base64\n ciphertext: string; // base64\n}\n\nexport async function encryptMessage(sharedSecret: CKey, payload: Record<string, unknown>): Promise<EncryptedEnvelope> {\n const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));\n const plaintext = new TextEncoder().encode(JSON.stringify(payload));\n const cipherBuf = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedSecret, plaintext);\n return {\n iv: bufferToBase64(iv),\n ciphertext: bufferToBase64(new Uint8Array(cipherBuf)),\n };\n}\n\nexport async function decryptMessage(\n sharedSecret: CKey,\n envelope: EncryptedEnvelope\n): Promise<Record<string, unknown>> {\n const iv = Buffer.from(envelope.iv, 'base64');\n const ciphertext = Buffer.from(envelope.ciphertext, 'base64');\n const plainBuf = await subtle.decrypt({ name: 'AES-GCM', iv }, sharedSecret, ciphertext);\n return JSON.parse(new TextDecoder().decode(plainBuf));\n}\n\n// ── Key import / export (hex) ────────────────────────────────────\n\nexport async function exportKeyToHex(type: 'private' | 'public', key: CKey): Promise<string> {\n const format = type === 'private' ? 'pkcs8' : 'spki';\n const buf = await subtle.exportKey(format, key);\n return bytesToHex(new Uint8Array(buf));\n}\n\nexport async function importKeyFromHex(type: 'private' | 'public', hex: string): Promise<CKey> {\n const format = type === 'private' ? 'pkcs8' : 'spki';\n return subtle.importKey(\n format,\n Buffer.from(hexToBytes(hex)),\n { name: 'ECDH', namedCurve: 'P-256' },\n true,\n type === 'private' ? ['deriveKey'] : []\n );\n}\n\n// ── Encoding helpers ─────────────────────────────────────────────\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n if (hex.length % 2 !== 0) throw new Error('Invalid hex: odd length');\n const bytes = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n }\n return bytes;\n}\n\nfunction bufferToBase64(buf: Uint8Array): string {\n return Buffer.from(buf).toString('base64');\n}\n","/**\n * WSBridge — CLI-side relay client\n *\n * Connects to the cloud relay (wss://relay.jaw.id) instead of a local daemon.\n * All messages (except key_exchange) are E2E encrypted via ECDH + AES-256-GCM.\n */\n\nimport * as crypto from 'node:crypto';\nimport type { webcrypto } from 'node:crypto';\nimport WebSocket from 'ws';\nimport {\n deriveSharedSecret,\n encryptMessage,\n decryptMessage,\n importKeyFromHex,\n type EncryptedEnvelope,\n} from './crypto.js';\n\ntype CKey = webcrypto.CryptoKey;\n\nexport interface WSBridgeConfig {\n apiKey: string;\n chainId: number;\n ens?: string;\n paymasterUrl?: string;\n}\n\nexport interface WSBridgeOptions {\n relayUrl: string;\n session: string;\n timeout?: number;\n config: WSBridgeConfig;\n /** CLI's ECDH private key (hex). Loaded from relay.json for existing sessions. */\n privateKeyHex: string;\n /** CLI's ECDH public key (hex). Sent to browser for key derivation. */\n publicKeyHex: string;\n /** Browser's ECDH public key (hex). Null if new session (key exchange needed). */\n peerPublicKeyHex: string | null;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n/**\n * Maximum outbound message size (5 MB).\n *\n * wallet_sendCalls with large batches (50+ calls, complex calldata) can reach\n * hundreds of KB. Base64 encoding of the AES-GCM ciphertext adds ~33% overhead.\n * 5 MB is generous enough for any realistic batch while still preventing\n * accidental memory issues.\n */\nconst MAX_MESSAGE_BYTES = 5 * 1024 * 1024;\n\n/** Minimum time between browser reopen attempts (ms). */\nconst BROWSER_REOPEN_COOLDOWN_MS = 5_000;\n\n/** Maximum reconnection attempts before giving up. */\nconst MAX_RECONNECT_ATTEMPTS = 3;\n\n/** Base delay for exponential backoff (ms). */\nconst RECONNECT_BASE_DELAY_MS = 1_000;\n\nexport class WSBridge {\n private readonly relayUrl: string;\n private readonly session: string;\n private readonly timeout: number;\n private readonly config: WSBridgeConfig;\n private readonly privateKeyHex: string;\n readonly publicKeyHex: string;\n private peerPublicKeyHex: string | null;\n private sharedSecret: CKey | null = null;\n private ws: WebSocket | null = null;\n private disposed = false;\n\n // Auto-reopen browser state\n private onBrowserNeeded: (() => Promise<void>) | undefined;\n private onPeerKeyChanged: ((key: string) => void) | undefined;\n private lastBrowserOpenTime = 0;\n\n // Reconnection state\n private reconnectAttempts = 0;\n\n /** Updated after key exchange — caller should persist this. */\n get peerPublicKey(): string | null {\n return this.peerPublicKeyHex;\n }\n\n constructor(options: WSBridgeOptions) {\n this.relayUrl = options.relayUrl;\n this.session = options.session;\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n this.config = options.config;\n this.privateKeyHex = options.privateKeyHex;\n this.publicKeyHex = options.publicKeyHex;\n this.peerPublicKeyHex = options.peerPublicKeyHex;\n }\n\n /**\n * Connect to the relay and wait for the browser to be ready.\n *\n * @param onBrowserNeeded — called when the relay reports no browser connected.\n * @param onPeerKeyChanged — called when a key_exchange updates the peer key.\n */\n async connect(\n onBrowserNeeded?: () => Promise<void>,\n onPeerKeyChanged?: (newPeerPublicKeyHex: string) => void\n ): Promise<void> {\n // Store callbacks for auto-reopen on browser disconnect\n this.onBrowserNeeded = onBrowserNeeded;\n this.onPeerKeyChanged = onPeerKeyChanged;\n\n // Pre-derive shared secret if we already have the peer key\n if (this.peerPublicKeyHex) {\n await this.deriveSecret();\n }\n\n return this.connectInternal(onBrowserNeeded, onPeerKeyChanged);\n }\n\n private async connectInternal(\n onBrowserNeeded?: () => Promise<void>,\n onPeerKeyChanged?: (newPeerPublicKeyHex: string) => void\n ): Promise<void> {\n return new Promise((resolve, reject) => {\n const url = `${this.relayUrl}?session=${encodeURIComponent(this.session)}&role=cli`;\n const ws = new WebSocket(url);\n\n let browserOpened = false;\n let resolved = false;\n let expectingKeyExchange = !this.peerPublicKeyHex;\n\n const timer = setTimeout(() => {\n ws.close();\n reject(new Error('Browser did not connect in time.\\n' + 'Run `jaw disconnect` then try again.'));\n }, 30_000);\n\n const sendEncryptedInit = async () => {\n if (!this.sharedSecret) return;\n const envelope = await encryptMessage(this.sharedSecret, {\n type: 'init',\n apiKey: this.config.apiKey,\n chainId: this.config.chainId,\n ens: this.config.ens,\n paymasterUrl: this.config.paymasterUrl,\n });\n this.sendRaw(ws, JSON.stringify({ type: 'encrypted', ...envelope }));\n };\n\n const waitForReady = () => {\n const readyTimer = setTimeout(() => {\n ws.close();\n reject(new Error('Browser SDK did not become ready in time.'));\n }, 15_000);\n\n const onMsg = async (data: WebSocket.Data) => {\n const msg = safeParse(data);\n if (!msg) return;\n\n if (msg.type === 'encrypted' && this.sharedSecret) {\n try {\n const inner = await decryptMessage(this.sharedSecret, msg as unknown as EncryptedEnvelope);\n if (inner.type === 'ready') {\n clearTimeout(readyTimer);\n ws.off('message', onMsg);\n this.reconnectAttempts = 0; // Reset on successful connect\n resolve();\n }\n } catch {\n // Not a valid encrypted message for us, ignore\n }\n }\n };\n ws.on('message', onMsg);\n };\n\n const onBrowserReady = async () => {\n if (resolved) return;\n resolved = true;\n clearTimeout(timer);\n // Set up listener BEFORE sending init to avoid missing the ready response\n waitForReady();\n await sendEncryptedInit();\n };\n\n ws.on('open', () => {\n this.ws = ws;\n });\n\n ws.on('message', async (data) => {\n const msg = safeParse(data);\n if (!msg) return;\n\n if (msg.type === 'status') {\n if (msg.browserConnected) {\n if (this.sharedSecret) {\n // Already have shared secret — skip key exchange\n await onBrowserReady();\n } else {\n // Browser is connected but we don't have its key yet.\n expectingKeyExchange = true;\n }\n } else if (!browserOpened && onBrowserNeeded) {\n browserOpened = true;\n expectingKeyExchange = true;\n onBrowserNeeded().catch(() => {\n /* best effort */\n });\n }\n } else if (msg.type === 'browser_connected') {\n expectingKeyExchange = true;\n // Wait for key_exchange from browser\n } else if (msg.type === 'browser_disconnected') {\n // Browser tab closed — attempt to reopen after cooldown\n this.handleBrowserDisconnect();\n } else if (msg.type === 'key_exchange' && expectingKeyExchange) {\n expectingKeyExchange = false;\n const peerKey = msg.publicKey as string;\n this.peerPublicKeyHex = peerKey;\n await this.deriveSecret();\n onPeerKeyChanged?.(peerKey);\n await onBrowserReady();\n }\n });\n\n ws.on('error', (err) => {\n clearTimeout(timer);\n reject(err);\n });\n\n ws.on('close', () => {\n clearTimeout(timer);\n if (!this.disposed) {\n this.handleRelayDisconnect();\n }\n });\n });\n }\n\n /**\n * Send an encrypted RPC request through the relay to the browser SDK.\n */\n async request(method: string, params?: unknown): Promise<unknown> {\n const ws = this.ws;\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('Not connected to relay');\n }\n if (!this.sharedSecret) {\n throw new Error('No shared secret — key exchange not completed');\n }\n\n const id = crypto.randomUUID();\n\n const envelope = await encryptMessage(this.sharedSecret, {\n type: 'rpc_request',\n id,\n method,\n params,\n });\n\n const serialized = JSON.stringify({ type: 'encrypted', ...envelope });\n assertMessageSize(serialized, method);\n\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new Error(`Request timed out after ${this.timeout / 1000}s. ` + 'Did you complete the action in the browser?')\n );\n this.close();\n }, this.timeout);\n\n const onMessage = async (data: WebSocket.Data) => {\n const msg = safeParse(data);\n if (!msg || msg.type !== 'encrypted' || !this.sharedSecret) return;\n\n try {\n const inner = await decryptMessage(this.sharedSecret, msg as unknown as EncryptedEnvelope);\n if (inner.type === 'rpc_response' && inner.id === id) {\n clearTimeout(timer);\n ws.off('message', onMessage);\n\n if (inner.success) {\n resolve(inner.data);\n } else {\n const err = inner.error as { code: number; message: string } | undefined;\n reject(new Error(err ? `[${err.code}] ${err.message}` : 'Request failed'));\n }\n }\n } catch {\n // Decryption failed — not our message or tampered, ignore\n }\n };\n\n ws.on('message', onMessage);\n this.sendRaw(ws, serialized);\n });\n }\n\n isOpen(): boolean {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n\n async shutdown(): Promise<void> {\n this.disposed = true;\n if (this.ws?.readyState === WebSocket.OPEN && this.sharedSecret) {\n try {\n const envelope = await encryptMessage(this.sharedSecret, {\n type: 'shutdown',\n });\n this.sendRaw(this.ws, JSON.stringify({ type: 'encrypted', ...envelope }));\n } catch {\n // Best effort\n }\n }\n this.close();\n }\n\n /**\n * Connect to relay and send shutdown directly — no init/ready handshake.\n * Used by `jaw disconnect` when we just need to tell the browser to close.\n */\n async connectAndShutdown(): Promise<void> {\n if (!this.peerPublicKeyHex) {\n // No peer key means browser never connected — nothing to shut down\n return;\n }\n\n this.disposed = true;\n await this.deriveSecret();\n\n return new Promise<void>((resolve) => {\n const url = `${this.relayUrl}?session=${encodeURIComponent(this.session)}&role=cli`;\n const ws = new WebSocket(url);\n\n const timer = setTimeout(() => {\n try {\n ws.close();\n } catch {\n /* ignore */\n }\n resolve();\n }, 3000);\n\n ws.on('open', async () => {\n this.ws = ws;\n try {\n await this.shutdown();\n } catch {\n // Best effort\n }\n clearTimeout(timer);\n resolve();\n });\n\n ws.on('error', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n }\n\n close(): void {\n this.disposed = true;\n if (this.ws) {\n try {\n this.ws.close();\n } catch {\n // ignore\n }\n this.ws = null;\n }\n }\n\n /**\n * Auto-reopen browser when browser_disconnected is received from relay.\n * Respects a cooldown to prevent rapid re-opening.\n */\n private handleBrowserDisconnect(): void {\n if (this.disposed || !this.onBrowserNeeded) return;\n\n const now = Date.now();\n if (now - this.lastBrowserOpenTime < BROWSER_REOPEN_COOLDOWN_MS) {\n return; // Too soon — skip this reopen\n }\n\n this.lastBrowserOpenTime = now;\n\n // Reset shared secret — the new browser tab will do a fresh key exchange\n this.sharedSecret = null;\n this.peerPublicKeyHex = null;\n\n this.onBrowserNeeded().catch(() => {\n // Best effort — if browser open fails, the next request will error\n });\n }\n\n /**\n * Attempt to reconnect to the relay with exponential backoff\n * when the WebSocket connection drops unexpectedly.\n */\n private handleRelayDisconnect(): void {\n if (this.disposed) return;\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;\n\n const delay = RECONNECT_BASE_DELAY_MS * Math.pow(2, this.reconnectAttempts);\n this.reconnectAttempts++;\n\n setTimeout(() => {\n if (this.disposed) return;\n this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(() => {\n // Reconnection failed — will retry if attempts remain\n });\n }, delay);\n }\n\n /** Send a raw string over the WebSocket, enforcing message size limits. */\n private sendRaw(ws: WebSocket, data: string): void {\n ws.send(data);\n }\n\n private async deriveSecret(): Promise<void> {\n if (!this.peerPublicKeyHex) return;\n const privateKey = await importKeyFromHex('private', this.privateKeyHex);\n const peerPublicKey = await importKeyFromHex('public', this.peerPublicKeyHex);\n this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);\n }\n}\n\n/**\n * Validate message size before sending to the relay.\n * Throws with a descriptive error if the message is too large.\n */\nfunction assertMessageSize(serialized: string, method: string): void {\n const byteLength = Buffer.byteLength(serialized, 'utf-8');\n if (byteLength > MAX_MESSAGE_BYTES) {\n const sizeMB = (byteLength / (1024 * 1024)).toFixed(2);\n throw new Error(\n `Message for ${method} is too large (${sizeMB} MB, limit ${MAX_MESSAGE_BYTES / (1024 * 1024)} MB). ` +\n 'Try reducing the number of calls in your batch.'\n );\n }\n}\n\nfunction safeParse(data: WebSocket.Data): Record<string, unknown> | null {\n try {\n return JSON.parse(data.toString()) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -11,9 +11,7 @@ import * as os from 'os';
|
|
|
11
11
|
params: z.any().optional().describe(
|
|
12
12
|
"Method parameters \u2014 structure varies by method. Read the jaw://api-reference/{method} resource for the expected format."
|
|
13
13
|
),
|
|
14
|
-
chainId: z.number().optional().describe(
|
|
15
|
-
"Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia"
|
|
16
|
-
)
|
|
14
|
+
chainId: z.number().optional().describe("Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia")
|
|
17
15
|
});
|
|
18
16
|
var configSetSchema = {
|
|
19
17
|
key: z.enum(["apiKey", "defaultChain", "keysUrl", "paymasterUrl", "ens", "relayUrl"]).describe("Config key"),
|
|
@@ -96,14 +94,10 @@ function redactConfig(config) {
|
|
|
96
94
|
}
|
|
97
95
|
function setConfigValue(key, value) {
|
|
98
96
|
if (key === "keysUrl" && typeof value === "string" && !isValidKeysUrl(value)) {
|
|
99
|
-
throw new Error(
|
|
100
|
-
`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`
|
|
101
|
-
);
|
|
97
|
+
throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);
|
|
102
98
|
}
|
|
103
99
|
if (key === "relayUrl" && typeof value === "string" && !isValidRelayUrl(value)) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`
|
|
106
|
-
);
|
|
100
|
+
throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);
|
|
107
101
|
}
|
|
108
102
|
const config = loadConfig();
|
|
109
103
|
const updated = { ...config, [key]: value };
|
|
@@ -112,23 +106,16 @@ function setConfigValue(key, value) {
|
|
|
112
106
|
|
|
113
107
|
// src/mcp/handlers/config.ts
|
|
114
108
|
function registerConfigTools(server) {
|
|
115
|
-
server.tool(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
content: [
|
|
124
|
-
{ type: "text", text: JSON.stringify(config) }
|
|
125
|
-
]
|
|
126
|
-
};
|
|
127
|
-
} catch (err) {
|
|
128
|
-
return mcpError(err);
|
|
129
|
-
}
|
|
109
|
+
server.tool("jaw_config_show", "Show current CLI configuration (API key redacted).", {}, async () => {
|
|
110
|
+
try {
|
|
111
|
+
const config = redactConfig(loadConfig());
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: "text", text: JSON.stringify(config) }]
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return mcpError(err);
|
|
130
117
|
}
|
|
131
|
-
);
|
|
118
|
+
});
|
|
132
119
|
server.tool(
|
|
133
120
|
"jaw_config_set",
|
|
134
121
|
"Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens, relayUrl).",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/mcp/tools.ts","../../../src/mcp/helpers.ts","../../../src/lib/paths.ts","../../../src/lib/validation.ts","../../../src/lib/config.ts","../../../src/mcp/handlers/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["import { z } from \"zod\";\n\n/**\n * Single generic RPC method schema.\n * Accepts any EIP-1193 RPC method and forwards to JAWProvider.\n */\nexport const rpcMethodSchema = {\n method: z\n .string()\n .describe(\n \"EIP-1193 RPC method name (e.g. wallet_connect, wallet_sendCalls, personal_sign). \" +\n \"Read the jaw://api-reference resource for the full list and jaw://api-reference/{method} for parameter details.\",\n ),\n params: z\n .any()\n .optional()\n .describe(\n \"Method parameters — structure varies by method. \" +\n \"Read the jaw://api-reference/{method} resource for the expected format.\",\n ),\n chainId: z\n .number()\n .optional()\n .describe(\n \"Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia\",\n ),\n};\n\nexport const configSetSchema = {\n key: z\n .enum([\"apiKey\", \"defaultChain\", \"keysUrl\", \"paymasterUrl\", \"ens\", \"relayUrl\"])\n .describe(\"Config key\"),\n value: z.string().describe(\"Config value\"),\n};\n","export function mcpError(err: unknown) {\n return {\n isError: true as const,\n content: [\n {\n type: \"text\" as const,\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n}\n\nexport function mcpResult(data: unknown) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(data),\n },\n ],\n };\n}","import * as path from \"node:path\";\nimport * as os from \"node:os\";\n\nconst JAW_DIR = path.join(os.homedir(), \".jaw\");\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, \"config.json\"),\n session: path.join(JAW_DIR, \"session.json\"),\n relay: path.join(JAW_DIR, \"relay.json\"),\n} as const;\n","export function isValidAddress(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nexport function isValidHex(value: string): boolean {\n return /^0x[0-9a-fA-F]*$/.test(value);\n}\n\nexport function isValidChainId(value: string | number): boolean {\n const num = typeof value === \"string\" ? parseInt(value, 10) : value;\n return Number.isInteger(num) && num > 0;\n}\n\nexport function parseChainId(value: string): number {\n const num = parseInt(value, 10);\n if (!isValidChainId(num)) {\n throw new Error(`Invalid chain ID: ${value}`);\n }\n return num;\n}\n\nexport function assertAddress(value: string, label = \"address\"): `0x${string}` {\n if (!isValidAddress(value)) {\n throw new Error(`Invalid ${label}: ${value}`);\n }\n return value as `0x${string}`;\n}\n\nexport function parseWei(raw: string, label = \"value\"): bigint {\n try {\n return BigInt(raw);\n } catch {\n throw new Error(`Invalid ${label}: \"${raw}\" is not a valid wei amount`);\n }\n}\n\nexport function isValidKeysUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith(\".jaw.id\") ||\n parsed.hostname === \"jaw.id\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n const isSecure =\n parsed.protocol === \"https:\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n return isTrustedHost && isSecure;\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith(\".jaw.id\") ||\n parsed.hostname === \"jaw.id\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n const isSecure =\n parsed.protocol === \"wss:\" ||\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\";\n const isWebSocket =\n parsed.protocol === \"wss:\" || parsed.protocol === \"ws:\";\n return isTrustedHost && isSecure && isWebSocket;\n } catch {\n return false;\n }\n}\n","import * as fs from \"node:fs\";\nimport { PATHS } from \"./paths.js\";\nimport type { JawConfig } from \"./types.js\";\nimport { isValidKeysUrl, isValidRelayUrl } from \"./validation.js\";\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, \"utf-8\");\n try {\n return JSON.parse(raw) as JawConfig;\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`,\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + \"\\n\", {\n encoding: \"utf-8\",\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function getConfigValue(\n key: keyof JawConfig,\n): string | number | undefined {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue(\n key: keyof JawConfig,\n value: string | number,\n): void {\n if (\n key === \"keysUrl\" &&\n typeof value === \"string\" &&\n !isValidKeysUrl(value)\n ) {\n throw new Error(\n `Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`,\n );\n }\n if (\n key === \"relayUrl\" &&\n typeof value === \"string\" &&\n !isValidRelayUrl(value)\n ) {\n throw new Error(\n `Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`,\n );\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { configSetSchema } from \"../tools.js\";\nimport { mcpError } from \"../helpers.js\";\nimport { loadConfig, setConfigValue, redactConfig } from \"../../lib/config.js\";\n\nexport function registerConfigTools(server: McpServer): void {\n server.tool(\n \"jaw_config_show\",\n \"Show current CLI configuration (API key redacted).\",\n {},\n async () => {\n try {\n const config = redactConfig(loadConfig());\n return {\n content: [\n { type: \"text\" as const, text: JSON.stringify(config) },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n\n server.tool(\n \"jaw_config_set\",\n \"Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens, relayUrl).\",\n configSetSchema,\n async (params) => {\n try {\n if (params.key === \"defaultChain\") {\n const num = parseInt(params.value, 10);\n if (isNaN(num) || num <= 0) {\n throw new Error(`Invalid chain ID: ${params.value}`);\n }\n setConfigValue(params.key, num);\n } else {\n setConfigValue(params.key, params.value);\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Set ${params.key} successfully`,\n },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n },\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/mcp/tools.ts","../../../src/mcp/helpers.ts","../../../src/lib/paths.ts","../../../src/lib/validation.ts","../../../src/lib/config.ts","../../../src/mcp/handlers/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["import { z } from 'zod';\n\n/**\n * Single generic RPC method schema.\n * Accepts any EIP-1193 RPC method and forwards to JAWProvider.\n */\nexport const rpcMethodSchema = {\n method: z\n .string()\n .describe(\n 'EIP-1193 RPC method name (e.g. wallet_connect, wallet_sendCalls, personal_sign). ' +\n 'Read the jaw://api-reference resource for the full list and jaw://api-reference/{method} for parameter details.'\n ),\n params: z\n .any()\n .optional()\n .describe(\n 'Method parameters — structure varies by method. ' +\n 'Read the jaw://api-reference/{method} resource for the expected format.'\n ),\n chainId: z\n .number()\n .optional()\n .describe('Target chain ID (overrides default). E.g., 1 for Ethereum, 8453 for Base, 84532 for Base Sepolia'),\n};\n\nexport const configSetSchema = {\n key: z.enum(['apiKey', 'defaultChain', 'keysUrl', 'paymasterUrl', 'ens', 'relayUrl']).describe('Config key'),\n value: z.string().describe('Config value'),\n};\n","export function mcpError(err: unknown) {\n return {\n isError: true as const,\n content: [\n {\n type: 'text' as const,\n text: `Error: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n}\n\nexport function mcpResult(data: unknown) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(data),\n },\n ],\n };\n}\n","import * as path from 'node:path';\nimport * as os from 'node:os';\n\nconst JAW_DIR = path.join(os.homedir(), '.jaw');\n\nexport const PATHS = {\n root: JAW_DIR,\n config: path.join(JAW_DIR, 'config.json'),\n session: path.join(JAW_DIR, 'session.json'),\n relay: path.join(JAW_DIR, 'relay.json'),\n} as const;\n","export function isValidAddress(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nexport function isValidHex(value: string): boolean {\n return /^0x[0-9a-fA-F]*$/.test(value);\n}\n\nexport function isValidChainId(value: string | number): boolean {\n const num = typeof value === 'string' ? parseInt(value, 10) : value;\n return Number.isInteger(num) && num > 0;\n}\n\nexport function parseChainId(value: string): number {\n const num = parseInt(value, 10);\n if (!isValidChainId(num)) {\n throw new Error(`Invalid chain ID: ${value}`);\n }\n return num;\n}\n\nexport function assertAddress(value: string, label = 'address'): `0x${string}` {\n if (!isValidAddress(value)) {\n throw new Error(`Invalid ${label}: ${value}`);\n }\n return value as `0x${string}`;\n}\n\nexport function parseWei(raw: string, label = 'value'): bigint {\n try {\n return BigInt(raw);\n } catch {\n throw new Error(`Invalid ${label}: \"${raw}\" is not a valid wei amount`);\n }\n}\n\nexport function isValidKeysUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith('.jaw.id') ||\n parsed.hostname === 'jaw.id' ||\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1';\n const isSecure = parsed.protocol === 'https:' || parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n return isTrustedHost && isSecure;\n } catch {\n return false;\n }\n}\n\nexport function isValidRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n const isTrustedHost =\n parsed.hostname.endsWith('.jaw.id') ||\n parsed.hostname === 'jaw.id' ||\n parsed.hostname === 'localhost' ||\n parsed.hostname === '127.0.0.1';\n const isSecure = parsed.protocol === 'wss:' || parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';\n const isWebSocket = parsed.protocol === 'wss:' || parsed.protocol === 'ws:';\n return isTrustedHost && isSecure && isWebSocket;\n } catch {\n return false;\n }\n}\n","import * as fs from 'node:fs';\nimport { PATHS } from './paths.js';\nimport type { JawConfig } from './types.js';\nimport { isValidKeysUrl, isValidRelayUrl } from './validation.js';\n\nexport function ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n fs.chmodSync(dir, 0o700);\n}\n\nexport function loadConfig(): JawConfig {\n if (!fs.existsSync(PATHS.config)) {\n return {};\n }\n const raw = fs.readFileSync(PATHS.config, 'utf-8');\n try {\n return JSON.parse(raw) as JawConfig;\n } catch {\n throw new Error(\n `Config file at ${PATHS.config} is not valid JSON. Run \\`jaw config set apiKey=<key>\\` to reset it.`\n );\n }\n}\n\nexport function saveConfig(config: JawConfig): void {\n ensureDir(PATHS.root);\n fs.writeFileSync(PATHS.config, JSON.stringify(config, null, 2) + '\\n', {\n encoding: 'utf-8',\n mode: 0o600,\n });\n}\n\nexport function redactConfig(config: JawConfig): Record<string, unknown> {\n return {\n ...config,\n apiKey: config.apiKey ? `${config.apiKey.slice(0, 8)}...` : undefined,\n };\n}\n\nexport function getConfigValue(key: keyof JawConfig): string | number | undefined {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue(key: keyof JawConfig, value: string | number): void {\n if (key === 'keysUrl' && typeof value === 'string' && !isValidKeysUrl(value)) {\n throw new Error(`Untrusted keysUrl: ${value}. Must be a *.jaw.id domain (HTTPS) or localhost.`);\n }\n if (key === 'relayUrl' && typeof value === 'string' && !isValidRelayUrl(value)) {\n throw new Error(`Untrusted relayUrl: ${value}. Must be wss://*.jaw.id or ws://localhost.`);\n }\n const config = loadConfig();\n const updated = { ...config, [key]: value };\n saveConfig(updated);\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { configSetSchema } from '../tools.js';\nimport { mcpError } from '../helpers.js';\nimport { loadConfig, setConfigValue, redactConfig } from '../../lib/config.js';\n\nexport function registerConfigTools(server: McpServer): void {\n server.tool('jaw_config_show', 'Show current CLI configuration (API key redacted).', {}, async () => {\n try {\n const config = redactConfig(loadConfig());\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(config) }],\n };\n } catch (err) {\n return mcpError(err);\n }\n });\n\n server.tool(\n 'jaw_config_set',\n 'Set a CLI configuration value (apiKey, defaultChain, keysUrl, paymasterUrl, ens, relayUrl).',\n configSetSchema,\n async (params) => {\n try {\n if (params.key === 'defaultChain') {\n const num = parseInt(params.value, 10);\n if (isNaN(num) || num <= 0) {\n throw new Error(`Invalid chain ID: ${params.value}`);\n }\n setConfigValue(params.key, num);\n } else {\n setConfigValue(params.key, params.value);\n }\n return {\n content: [\n {\n type: 'text' as const,\n text: `Set ${params.key} successfully`,\n },\n ],\n };\n } catch (err) {\n return mcpError(err);\n }\n }\n );\n}\n"]}
|
|
@@ -59,11 +59,7 @@ async function deriveSharedSecret(privateKey, peerPublicKey) {
|
|
|
59
59
|
async function encryptMessage(sharedSecret, payload) {
|
|
60
60
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
|
61
61
|
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
62
|
-
const cipherBuf = await subtle.encrypt(
|
|
63
|
-
{ name: "AES-GCM", iv },
|
|
64
|
-
sharedSecret,
|
|
65
|
-
plaintext
|
|
66
|
-
);
|
|
62
|
+
const cipherBuf = await subtle.encrypt({ name: "AES-GCM", iv }, sharedSecret, plaintext);
|
|
67
63
|
return {
|
|
68
64
|
iv: bufferToBase64(iv),
|
|
69
65
|
ciphertext: bufferToBase64(new Uint8Array(cipherBuf))
|
|
@@ -72,11 +68,7 @@ async function encryptMessage(sharedSecret, payload) {
|
|
|
72
68
|
async function decryptMessage(sharedSecret, envelope) {
|
|
73
69
|
const iv = Buffer.from(envelope.iv, "base64");
|
|
74
70
|
const ciphertext = Buffer.from(envelope.ciphertext, "base64");
|
|
75
|
-
const plainBuf = await subtle.decrypt(
|
|
76
|
-
{ name: "AES-GCM", iv },
|
|
77
|
-
sharedSecret,
|
|
78
|
-
ciphertext
|
|
79
|
-
);
|
|
71
|
+
const plainBuf = await subtle.decrypt({ name: "AES-GCM", iv }, sharedSecret, ciphertext);
|
|
80
72
|
return JSON.parse(new TextDecoder().decode(plainBuf));
|
|
81
73
|
}
|
|
82
74
|
async function importKeyFromHex(type, hex) {
|
|
@@ -160,11 +152,7 @@ var WSBridge = class {
|
|
|
160
152
|
let expectingKeyExchange = !this.peerPublicKeyHex;
|
|
161
153
|
const timer = setTimeout(() => {
|
|
162
154
|
ws.close();
|
|
163
|
-
reject(
|
|
164
|
-
new Error(
|
|
165
|
-
"Browser did not connect in time.\nRun `jaw disconnect` then try again."
|
|
166
|
-
)
|
|
167
|
-
);
|
|
155
|
+
reject(new Error("Browser did not connect in time.\nRun `jaw disconnect` then try again."));
|
|
168
156
|
}, 3e4);
|
|
169
157
|
const sendEncryptedInit = async () => {
|
|
170
158
|
if (!this.sharedSecret) return;
|
|
@@ -187,10 +175,7 @@ var WSBridge = class {
|
|
|
187
175
|
if (!msg) return;
|
|
188
176
|
if (msg.type === "encrypted" && this.sharedSecret) {
|
|
189
177
|
try {
|
|
190
|
-
const inner = await decryptMessage(
|
|
191
|
-
this.sharedSecret,
|
|
192
|
-
msg
|
|
193
|
-
);
|
|
178
|
+
const inner = await decryptMessage(this.sharedSecret, msg);
|
|
194
179
|
if (inner.type === "ready") {
|
|
195
180
|
clearTimeout(readyTimer);
|
|
196
181
|
ws.off("message", onMsg);
|
|
@@ -277,9 +262,7 @@ var WSBridge = class {
|
|
|
277
262
|
return new Promise((resolve, reject) => {
|
|
278
263
|
const timer = setTimeout(() => {
|
|
279
264
|
reject(
|
|
280
|
-
new Error(
|
|
281
|
-
`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`
|
|
282
|
-
)
|
|
265
|
+
new Error(`Request timed out after ${this.timeout / 1e3}s. Did you complete the action in the browser?`)
|
|
283
266
|
);
|
|
284
267
|
this.close();
|
|
285
268
|
}, this.timeout);
|
|
@@ -287,10 +270,7 @@ var WSBridge = class {
|
|
|
287
270
|
const msg = safeParse(data);
|
|
288
271
|
if (!msg || msg.type !== "encrypted" || !this.sharedSecret) return;
|
|
289
272
|
try {
|
|
290
|
-
const inner = await decryptMessage(
|
|
291
|
-
this.sharedSecret,
|
|
292
|
-
msg
|
|
293
|
-
);
|
|
273
|
+
const inner = await decryptMessage(this.sharedSecret, msg);
|
|
294
274
|
if (inner.type === "rpc_response" && inner.id === id) {
|
|
295
275
|
clearTimeout(timer);
|
|
296
276
|
ws.off("message", onMessage);
|
|
@@ -298,11 +278,7 @@ var WSBridge = class {
|
|
|
298
278
|
resolve(inner.data);
|
|
299
279
|
} else {
|
|
300
280
|
const err = inner.error;
|
|
301
|
-
reject(
|
|
302
|
-
new Error(
|
|
303
|
-
err ? `[${err.code}] ${err.message}` : "Request failed"
|
|
304
|
-
)
|
|
305
|
-
);
|
|
281
|
+
reject(new Error(err ? `[${err.code}] ${err.message}` : "Request failed"));
|
|
306
282
|
}
|
|
307
283
|
}
|
|
308
284
|
} catch {
|
|
@@ -322,10 +298,7 @@ var WSBridge = class {
|
|
|
322
298
|
const envelope = await encryptMessage(this.sharedSecret, {
|
|
323
299
|
type: "shutdown"
|
|
324
300
|
});
|
|
325
|
-
this.sendRaw(
|
|
326
|
-
this.ws,
|
|
327
|
-
JSON.stringify({ type: "encrypted", ...envelope })
|
|
328
|
-
);
|
|
301
|
+
this.sendRaw(this.ws, JSON.stringify({ type: "encrypted", ...envelope }));
|
|
329
302
|
} catch {
|
|
330
303
|
}
|
|
331
304
|
}
|
|
@@ -403,10 +376,8 @@ var WSBridge = class {
|
|
|
403
376
|
this.reconnectAttempts++;
|
|
404
377
|
setTimeout(() => {
|
|
405
378
|
if (this.disposed) return;
|
|
406
|
-
this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
);
|
|
379
|
+
this.connectInternal(this.onBrowserNeeded, this.onPeerKeyChanged).catch(() => {
|
|
380
|
+
});
|
|
410
381
|
}, delay);
|
|
411
382
|
}
|
|
412
383
|
/** Send a raw string over the WebSocket, enforcing message size limits. */
|
|
@@ -416,10 +387,7 @@ var WSBridge = class {
|
|
|
416
387
|
async deriveSecret() {
|
|
417
388
|
if (!this.peerPublicKeyHex) return;
|
|
418
389
|
const privateKey = await importKeyFromHex("private", this.privateKeyHex);
|
|
419
|
-
const peerPublicKey = await importKeyFromHex(
|
|
420
|
-
"public",
|
|
421
|
-
this.peerPublicKeyHex
|
|
422
|
-
);
|
|
390
|
+
const peerPublicKey = await importKeyFromHex("public", this.peerPublicKeyHex);
|
|
423
391
|
this.sharedSecret = await deriveSharedSecret(privateKey, peerPublicKey);
|
|
424
392
|
}
|
|
425
393
|
};
|
|
@@ -524,9 +492,7 @@ function registerDaemonTools(server) {
|
|
|
524
492
|
config
|
|
525
493
|
};
|
|
526
494
|
return {
|
|
527
|
-
content: [
|
|
528
|
-
{ type: "text", text: JSON.stringify(status, null, 2) }
|
|
529
|
-
]
|
|
495
|
+
content: [{ type: "text", text: JSON.stringify(status, null, 2) }]
|
|
530
496
|
};
|
|
531
497
|
} catch (err) {
|
|
532
498
|
return mcpError(err);
|