@llui/agent 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +59 -0
- package/dist/client/factory.js.map +1 -1
- package/dist/client/rpc/observe.d.ts +20 -0
- package/dist/client/rpc/observe.d.ts.map +1 -0
- package/dist/client/rpc/observe.js +9 -0
- package/dist/client/rpc/observe.js.map +1 -0
- package/dist/client/rpc/send-message.d.ts +32 -3
- package/dist/client/rpc/send-message.d.ts.map +1 -1
- package/dist/client/rpc/send-message.js +94 -4
- package/dist/client/rpc/send-message.js.map +1 -1
- package/dist/client/ws-client.d.ts +2 -1
- package/dist/client/ws-client.d.ts.map +1 -1
- package/dist/client/ws-client.js +4 -0
- package/dist/client/ws-client.js.map +1 -1
- package/dist/protocol.d.ts +64 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/server/cloudflare/durable-object.d.ts +58 -0
- package/dist/server/cloudflare/durable-object.d.ts.map +1 -0
- package/dist/server/cloudflare/durable-object.js +33 -0
- package/dist/server/cloudflare/durable-object.js.map +1 -0
- package/dist/server/cloudflare/index.d.ts +49 -0
- package/dist/server/cloudflare/index.d.ts.map +1 -0
- package/dist/server/cloudflare/index.js +49 -0
- package/dist/server/cloudflare/index.js.map +1 -0
- package/dist/server/cloudflare/worker.d.ts +40 -0
- package/dist/server/cloudflare/worker.d.ts.map +1 -0
- package/dist/server/cloudflare/worker.js +55 -0
- package/dist/server/cloudflare/worker.js.map +1 -0
- package/dist/server/core-entry.d.ts +27 -0
- package/dist/server/core-entry.d.ts.map +1 -0
- package/dist/server/core-entry.js +19 -0
- package/dist/server/core-entry.js.map +1 -0
- package/dist/server/core.d.ts +78 -0
- package/dist/server/core.d.ts.map +1 -0
- package/dist/server/core.js +84 -0
- package/dist/server/core.js.map +1 -0
- package/dist/server/factory.d.ts +5 -3
- package/dist/server/factory.d.ts.map +1 -1
- package/dist/server/factory.js +18 -58
- package/dist/server/factory.js.map +1 -1
- package/dist/server/http/mint.d.ts.map +1 -1
- package/dist/server/http/mint.js +2 -3
- package/dist/server/http/mint.js.map +1 -1
- package/dist/server/http/resume.js +1 -1
- package/dist/server/http/resume.js.map +1 -1
- package/dist/server/identity.d.ts +5 -1
- package/dist/server/identity.d.ts.map +1 -1
- package/dist/server/identity.js +49 -11
- package/dist/server/identity.js.map +1 -1
- package/dist/server/index.d.ts +16 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lap/confirm-result.d.ts +2 -2
- package/dist/server/lap/confirm-result.d.ts.map +1 -1
- package/dist/server/lap/confirm-result.js +1 -1
- package/dist/server/lap/confirm-result.js.map +1 -1
- package/dist/server/lap/describe.d.ts +4 -4
- package/dist/server/lap/describe.d.ts.map +1 -1
- package/dist/server/lap/describe.js +4 -4
- package/dist/server/lap/describe.js.map +1 -1
- package/dist/server/lap/forward.d.ts +2 -2
- package/dist/server/lap/forward.d.ts.map +1 -1
- package/dist/server/lap/forward.js +1 -1
- package/dist/server/lap/forward.js.map +1 -1
- package/dist/server/lap/message.d.ts +2 -2
- package/dist/server/lap/message.d.ts.map +1 -1
- package/dist/server/lap/message.js +7 -3
- package/dist/server/lap/message.js.map +1 -1
- package/dist/server/lap/observe.d.ts +27 -0
- package/dist/server/lap/observe.d.ts.map +1 -0
- package/dist/server/lap/observe.js +77 -0
- package/dist/server/lap/observe.js.map +1 -0
- package/dist/server/lap/router.d.ts.map +1 -1
- package/dist/server/lap/router.js +3 -0
- package/dist/server/lap/router.js.map +1 -1
- package/dist/server/lap/wait.d.ts +2 -2
- package/dist/server/lap/wait.d.ts.map +1 -1
- package/dist/server/lap/wait.js +1 -1
- package/dist/server/lap/wait.js.map +1 -1
- package/dist/server/options.d.ts +25 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js.map +1 -1
- package/dist/server/token.d.ts +7 -3
- package/dist/server/token.d.ts.map +1 -1
- package/dist/server/token.js +66 -26
- package/dist/server/token.js.map +1 -1
- package/dist/server/web/adapter.d.ts +16 -0
- package/dist/server/web/adapter.d.ts.map +1 -0
- package/dist/server/web/adapter.js +45 -0
- package/dist/server/web/adapter.js.map +1 -0
- package/dist/server/web/index.d.ts +12 -0
- package/dist/server/web/index.d.ts.map +1 -0
- package/dist/server/web/index.js +12 -0
- package/dist/server/web/index.js.map +1 -0
- package/dist/server/web/upgrade.d.ts +41 -0
- package/dist/server/web/upgrade.d.ts.map +1 -0
- package/dist/server/web/upgrade.js +96 -0
- package/dist/server/web/upgrade.js.map +1 -0
- package/dist/server/ws/pairing-registry.d.ts +84 -21
- package/dist/server/ws/pairing-registry.d.ts.map +1 -1
- package/dist/server/ws/pairing-registry.js +89 -151
- package/dist/server/ws/pairing-registry.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +39 -0
- package/dist/server/ws/rpc.d.ts.map +1 -0
- package/dist/server/ws/rpc.js +126 -0
- package/dist/server/ws/rpc.js.map +1 -0
- package/dist/server/ws/upgrade.d.ts +3 -3
- package/dist/server/ws/upgrade.d.ts.map +1 -1
- package/dist/server/ws/upgrade.js +2 -2
- package/dist/server/ws/upgrade.js.map +1 -1
- package/package.json +14 -2
package/dist/server/token.js
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
1
|
const PREFIX = 'llui-agent_';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Normalize key + payload to `Uint8Array<ArrayBuffer>`, the shape
|
|
4
|
+
* WebCrypto wants. Newer TS lib types parameterize `Uint8Array` over the
|
|
5
|
+
* underlying buffer, and `TextEncoder.encode()` returns
|
|
6
|
+
* `Uint8Array<ArrayBufferLike>` — which `crypto.subtle.*` won't accept
|
|
7
|
+
* directly. A one-shot copy is cheap (HMAC inputs are bytes-small) and
|
|
8
|
+
* keeps the types honest without `as BufferSource` scattered at call sites.
|
|
9
|
+
*/
|
|
10
|
+
function toBytes(input) {
|
|
11
|
+
const raw = typeof input === 'string' ? new TextEncoder().encode(input) : input;
|
|
12
|
+
const buf = new ArrayBuffer(raw.byteLength);
|
|
13
|
+
const out = new Uint8Array(buf);
|
|
14
|
+
out.set(raw);
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function toKeyBytes(key) {
|
|
18
|
+
if (typeof key === 'string') {
|
|
19
|
+
if (key.length < 32)
|
|
20
|
+
throw new Error('signingKey must be at least 32 bytes');
|
|
21
|
+
}
|
|
22
|
+
else if (key.byteLength < 32) {
|
|
6
23
|
throw new Error('signingKey must be at least 32 bytes');
|
|
7
|
-
|
|
24
|
+
}
|
|
25
|
+
return toBytes(key);
|
|
8
26
|
}
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Import a signing key as a WebCrypto `CryptoKey`. Done per call so the
|
|
29
|
+
* caller doesn't have to pre-import and pass it around; the cost is a
|
|
30
|
+
* microtask per sign/verify, which is negligible for our call volume
|
|
31
|
+
* (tokens verified once per LAP HTTP request).
|
|
32
|
+
*/
|
|
33
|
+
async function importHmacKey(key, usages) {
|
|
34
|
+
return crypto.subtle.importKey('raw', toKeyBytes(key), { name: 'HMAC', hash: 'SHA-256' }, false, usages);
|
|
11
35
|
}
|
|
12
|
-
function
|
|
36
|
+
function toBase64Url(bytes) {
|
|
37
|
+
// btoa needs a binary string; build it manually to avoid ArrayBuffer/Uint8Array quirks.
|
|
38
|
+
let bin = '';
|
|
39
|
+
for (let i = 0; i < bytes.byteLength; i++)
|
|
40
|
+
bin += String.fromCharCode(bytes[i]);
|
|
41
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
42
|
+
}
|
|
43
|
+
function fromBase64Url(s) {
|
|
13
44
|
try {
|
|
14
|
-
|
|
45
|
+
const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4);
|
|
46
|
+
const bin = atob(b64);
|
|
47
|
+
const buf = new ArrayBuffer(bin.length);
|
|
48
|
+
const bytes = new Uint8Array(buf);
|
|
49
|
+
for (let i = 0; i < bin.length; i++)
|
|
50
|
+
bytes[i] = bin.charCodeAt(i);
|
|
51
|
+
return bytes;
|
|
15
52
|
}
|
|
16
53
|
catch {
|
|
17
54
|
return null;
|
|
@@ -19,20 +56,24 @@ function b64urlDecode(s) {
|
|
|
19
56
|
}
|
|
20
57
|
/**
|
|
21
58
|
* Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.
|
|
22
|
-
* See spec §6.1.
|
|
59
|
+
* See spec §6.1. Async because WebCrypto's HMAC sign/verify is the
|
|
60
|
+
* cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose
|
|
61
|
+
* `crypto.subtle` identically.
|
|
23
62
|
*/
|
|
24
|
-
export function signToken(payload, key) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const payloadPart =
|
|
28
|
-
const
|
|
29
|
-
const sigPart =
|
|
63
|
+
export async function signToken(payload, key) {
|
|
64
|
+
const cryptoKey = await importHmacKey(key, ['sign']);
|
|
65
|
+
const jsonBytes = toBytes(JSON.stringify(payload));
|
|
66
|
+
const payloadPart = toBase64Url(jsonBytes);
|
|
67
|
+
const macBuf = await crypto.subtle.sign('HMAC', cryptoKey, toBytes(payloadPart));
|
|
68
|
+
const sigPart = toBase64Url(new Uint8Array(macBuf));
|
|
30
69
|
return (PREFIX + payloadPart + '.' + sigPart);
|
|
31
70
|
}
|
|
32
71
|
/**
|
|
33
72
|
* Verify the signature, parse the payload, and check expiry.
|
|
73
|
+
* `crypto.subtle.verify` does the constant-time compare internally,
|
|
74
|
+
* so we don't need a separate `timingSafeEqual`.
|
|
34
75
|
*/
|
|
35
|
-
export function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000)) {
|
|
76
|
+
export async function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000)) {
|
|
36
77
|
if (!token.startsWith(PREFIX))
|
|
37
78
|
return { kind: 'invalid', reason: 'malformed' };
|
|
38
79
|
const body = token.slice(PREFIX.length);
|
|
@@ -41,20 +82,19 @@ export function verifyToken(token, key, nowSec = Math.floor(Date.now() / 1000))
|
|
|
41
82
|
return { kind: 'invalid', reason: 'malformed' };
|
|
42
83
|
const payloadPart = body.slice(0, dot);
|
|
43
84
|
const sigPart = body.slice(dot + 1);
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
85
|
+
const sigBytes = fromBase64Url(sigPart);
|
|
86
|
+
if (!sigBytes)
|
|
46
87
|
return { kind: 'invalid', reason: 'malformed' };
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
88
|
+
const cryptoKey = await importHmacKey(key, ['verify']);
|
|
89
|
+
const ok = await crypto.subtle.verify('HMAC', cryptoKey, sigBytes, toBytes(payloadPart));
|
|
90
|
+
if (!ok)
|
|
50
91
|
return { kind: 'invalid', reason: 'bad-signature' };
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!jsonBuf)
|
|
92
|
+
const jsonBytes = fromBase64Url(payloadPart);
|
|
93
|
+
if (!jsonBytes)
|
|
54
94
|
return { kind: 'invalid', reason: 'malformed' };
|
|
55
95
|
let parsed;
|
|
56
96
|
try {
|
|
57
|
-
parsed = JSON.parse(
|
|
97
|
+
parsed = JSON.parse(new TextDecoder().decode(jsonBytes));
|
|
58
98
|
}
|
|
59
99
|
catch {
|
|
60
100
|
return { kind: 'invalid', reason: 'malformed' };
|
package/dist/server/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/server/token.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentToken } from '../protocol.js'\n\nexport type TokenPayload = {\n tid: string\n iat: number\n exp: number\n scope: 'agent'\n}\n\nexport type VerifyResult =\n | { kind: 'ok'; payload: TokenPayload }\n | { kind: 'invalid'; reason: 'malformed' | 'bad-signature' | 'expired' }\n\nconst PREFIX = 'llui-agent_'\n\n/**\n * Normalize key + payload to `Uint8Array<ArrayBuffer>`, the shape\n * WebCrypto wants. Newer TS lib types parameterize `Uint8Array` over the\n * underlying buffer, and `TextEncoder.encode()` returns\n * `Uint8Array<ArrayBufferLike>` — which `crypto.subtle.*` won't accept\n * directly. A one-shot copy is cheap (HMAC inputs are bytes-small) and\n * keeps the types honest without `as BufferSource` scattered at call sites.\n */\nfunction toBytes(input: string | Uint8Array): Uint8Array<ArrayBuffer> {\n const raw = typeof input === 'string' ? new TextEncoder().encode(input) : input\n const buf = new ArrayBuffer(raw.byteLength)\n const out = new Uint8Array(buf)\n out.set(raw)\n return out\n}\n\nfunction toKeyBytes(key: string | Uint8Array): Uint8Array<ArrayBuffer> {\n if (typeof key === 'string') {\n if (key.length < 32) throw new Error('signingKey must be at least 32 bytes')\n } else if (key.byteLength < 32) {\n throw new Error('signingKey must be at least 32 bytes')\n }\n return toBytes(key)\n}\n\n/**\n * Import a signing key as a WebCrypto `CryptoKey`. Done per call so the\n * caller doesn't have to pre-import and pass it around; the cost is a\n * microtask per sign/verify, which is negligible for our call volume\n * (tokens verified once per LAP HTTP request).\n */\nasync function importHmacKey(key: string | Uint8Array, usages: KeyUsage[]): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n 'raw',\n toKeyBytes(key),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n usages,\n )\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n // btoa needs a binary string; build it manually to avoid ArrayBuffer/Uint8Array quirks.\n let bin = ''\n for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]!)\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\nfunction fromBase64Url(s: string): Uint8Array<ArrayBuffer> | null {\n try {\n const b64 = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4)\n const bin = atob(b64)\n const buf = new ArrayBuffer(bin.length)\n const bytes = new Uint8Array(buf)\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)\n return bytes\n } catch {\n return null\n }\n}\n\n/**\n * Serialize a payload to `llui-agent_<base64url(json)>.<base64url(hmac)>`.\n * See spec §6.1. Async because WebCrypto's HMAC sign/verify is the\n * cross-runtime standard; Node, Cloudflare, Deno, and Bun all expose\n * `crypto.subtle` identically.\n */\nexport async function signToken(\n payload: TokenPayload,\n key: string | Uint8Array,\n): Promise<AgentToken> {\n const cryptoKey = await importHmacKey(key, ['sign'])\n const jsonBytes = toBytes(JSON.stringify(payload))\n const payloadPart = toBase64Url(jsonBytes)\n const macBuf = await crypto.subtle.sign('HMAC', cryptoKey, toBytes(payloadPart))\n const sigPart = toBase64Url(new Uint8Array(macBuf))\n return (PREFIX + payloadPart + '.' + sigPart) as AgentToken\n}\n\n/**\n * Verify the signature, parse the payload, and check expiry.\n * `crypto.subtle.verify` does the constant-time compare internally,\n * so we don't need a separate `timingSafeEqual`.\n */\nexport async function verifyToken(\n token: string,\n key: string | Uint8Array,\n nowSec: number = Math.floor(Date.now() / 1000),\n): Promise<VerifyResult> {\n if (!token.startsWith(PREFIX)) return { kind: 'invalid', reason: 'malformed' }\n const body = token.slice(PREFIX.length)\n const dot = body.indexOf('.')\n if (dot < 0) return { kind: 'invalid', reason: 'malformed' }\n\n const payloadPart = body.slice(0, dot)\n const sigPart = body.slice(dot + 1)\n const sigBytes = fromBase64Url(sigPart)\n if (!sigBytes) return { kind: 'invalid', reason: 'malformed' }\n\n const cryptoKey = await importHmacKey(key, ['verify'])\n const ok = await crypto.subtle.verify('HMAC', cryptoKey, sigBytes, toBytes(payloadPart))\n if (!ok) return { kind: 'invalid', reason: 'bad-signature' }\n\n const jsonBytes = fromBase64Url(payloadPart)\n if (!jsonBytes) return { kind: 'invalid', reason: 'malformed' }\n let parsed: unknown\n try {\n parsed = JSON.parse(new TextDecoder().decode(jsonBytes))\n } catch {\n return { kind: 'invalid', reason: 'malformed' }\n }\n\n if (!isTokenPayload(parsed)) return { kind: 'invalid', reason: 'malformed' }\n if (parsed.exp <= nowSec) return { kind: 'invalid', reason: 'expired' }\n return { kind: 'ok', payload: parsed }\n}\n\nfunction isTokenPayload(x: unknown): x is TokenPayload {\n if (!x || typeof x !== 'object') return false\n const o = x as Record<string, unknown>\n return (\n typeof o.tid === 'string' &&\n typeof o.iat === 'number' &&\n typeof o.exp === 'number' &&\n o.scope === 'agent'\n )\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PairingConnection } from '../ws/pairing-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the
|
|
4
|
+
* common denominator across Cloudflare Workers (`WebSocketPair`
|
|
5
|
+
* server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's
|
|
6
|
+
* upgraded socket, and any other runtime that exposes a
|
|
7
|
+
* standards-compliant WebSocket object.
|
|
8
|
+
*
|
|
9
|
+
* The input type is intentionally the browser/global `WebSocket`
|
|
10
|
+
* interface — *not* the Node `ws` library's variant, which uses an
|
|
11
|
+
* EventEmitter API (`on('message', ...)`) rather than
|
|
12
|
+
* `addEventListener('message', ...)`. Use `./node/upgrade.ts` for
|
|
13
|
+
* the `ws` library path.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createWHATWGPairingConnection(socket: WebSocket): PairingConnection;
|
|
16
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/server/web/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAGlE;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,iBAAiB,CA4BlF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the
|
|
3
|
+
* common denominator across Cloudflare Workers (`WebSocketPair`
|
|
4
|
+
* server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's
|
|
5
|
+
* upgraded socket, and any other runtime that exposes a
|
|
6
|
+
* standards-compliant WebSocket object.
|
|
7
|
+
*
|
|
8
|
+
* The input type is intentionally the browser/global `WebSocket`
|
|
9
|
+
* interface — *not* the Node `ws` library's variant, which uses an
|
|
10
|
+
* EventEmitter API (`on('message', ...)`) rather than
|
|
11
|
+
* `addEventListener('message', ...)`. Use `./node/upgrade.ts` for
|
|
12
|
+
* the `ws` library path.
|
|
13
|
+
*/
|
|
14
|
+
export function createWHATWGPairingConnection(socket) {
|
|
15
|
+
return {
|
|
16
|
+
send(frame) {
|
|
17
|
+
socket.send(JSON.stringify(frame));
|
|
18
|
+
},
|
|
19
|
+
onFrame(handler) {
|
|
20
|
+
socket.addEventListener('message', (ev) => {
|
|
21
|
+
const data = ev.data;
|
|
22
|
+
const raw = typeof data === 'string' ? data : new TextDecoder().decode(data);
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
handler(parsed);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Ignore malformed frames — one bad frame shouldn't tear down the pairing.
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
onClose(handler) {
|
|
33
|
+
socket.addEventListener('close', () => handler());
|
|
34
|
+
},
|
|
35
|
+
close() {
|
|
36
|
+
try {
|
|
37
|
+
socket.close();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Some runtimes throw if you close twice; swallow.
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/server/web/adapter.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAiB;IAC7D,OAAO;QACL,IAAI,CAAC,KAAkB;YACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,CAAC,OAAO;YACb,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;gBACxC,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAA;gBACpB,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAmB,CAAC,CAAA;gBAC3F,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAA;oBAC7C,OAAO,CAAC,MAAM,CAAC,CAAA;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,2EAA2E;gBAC7E,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,OAAO;YACb,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QACnD,CAAC;QACD,KAAK;YACH,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,EAAE,CAAA;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import type { PairingConnection } from '../ws/pairing-registry.js'\nimport type { ClientFrame, ServerFrame } from '../../protocol.js'\n\n/**\n * Wrap a WHATWG `WebSocket` in a `PairingConnection`. This is the\n * common denominator across Cloudflare Workers (`WebSocketPair`\n * server half), Deno (`Deno.upgradeWebSocket().socket`), Bun's\n * upgraded socket, and any other runtime that exposes a\n * standards-compliant WebSocket object.\n *\n * The input type is intentionally the browser/global `WebSocket`\n * interface — *not* the Node `ws` library's variant, which uses an\n * EventEmitter API (`on('message', ...)`) rather than\n * `addEventListener('message', ...)`. Use `./node/upgrade.ts` for\n * the `ws` library path.\n */\nexport function createWHATWGPairingConnection(socket: WebSocket): PairingConnection {\n return {\n send(frame: ServerFrame) {\n socket.send(JSON.stringify(frame))\n },\n onFrame(handler) {\n socket.addEventListener('message', (ev) => {\n const data = ev.data\n const raw = typeof data === 'string' ? data : new TextDecoder().decode(data as ArrayBuffer)\n try {\n const parsed = JSON.parse(raw) as ClientFrame\n handler(parsed)\n } catch {\n // Ignore malformed frames — one bad frame shouldn't tear down the pairing.\n }\n })\n },\n onClose(handler) {\n socket.addEventListener('close', () => handler())\n },\n close() {\n try {\n socket.close()\n } catch {\n // Some runtimes throw if you close twice; swallow.\n }\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-runtime adapters. Use this sub-path from Cloudflare Workers,
|
|
3
|
+
* Deno, Bun, or any other runtime that speaks WHATWG `Request` /
|
|
4
|
+
* `Response` and exposes native WebSocket upgrade primitives.
|
|
5
|
+
*
|
|
6
|
+
* Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that
|
|
7
|
+
* builds the runtime-neutral router and registry; the handlers
|
|
8
|
+
* exported here handle the WebSocket upgrade half.
|
|
9
|
+
*/
|
|
10
|
+
export { createWHATWGPairingConnection } from './adapter.js';
|
|
11
|
+
export { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/web/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-runtime adapters. Use this sub-path from Cloudflare Workers,
|
|
3
|
+
* Deno, Bun, or any other runtime that speaks WHATWG `Request` /
|
|
4
|
+
* `Response` and exposes native WebSocket upgrade primitives.
|
|
5
|
+
*
|
|
6
|
+
* Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that
|
|
7
|
+
* builds the runtime-neutral router and registry; the handlers
|
|
8
|
+
* exported here handle the WebSocket upgrade half.
|
|
9
|
+
*/
|
|
10
|
+
export { createWHATWGPairingConnection } from './adapter.js';
|
|
11
|
+
export { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/web/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA","sourcesContent":["/**\n * Web-runtime adapters. Use this sub-path from Cloudflare Workers,\n * Deno, Bun, or any other runtime that speaks WHATWG `Request` /\n * `Response` and exposes native WebSocket upgrade primitives.\n *\n * Pair with `@llui/agent/server/core`'s `createLluiAgentCore` — that\n * builds the runtime-neutral router and registry; the handlers\n * exported here handle the WebSocket upgrade half.\n */\nexport { createWHATWGPairingConnection } from './adapter.js'\nexport { handleCloudflareUpgrade, handleDenoUpgrade, extractToken } from './upgrade.js'\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AgentCoreHandle } from '../core.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the bearer token from a LAP WebSocket upgrade request.
|
|
4
|
+
* Accepts the token on either `?token=` or `Authorization: Bearer` —
|
|
5
|
+
* query-string is the common pattern because browsers can't set
|
|
6
|
+
* arbitrary headers on WebSocket construction.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractToken(req: Request): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare Workers handler. Accepts a WebSocket upgrade using
|
|
11
|
+
* `WebSocketPair`, validates the token via
|
|
12
|
+
* `agent.acceptConnection`, and returns the 101 upgrade Response.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```ts
|
|
16
|
+
* const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })
|
|
17
|
+
* export default {
|
|
18
|
+
* async fetch(req, env) {
|
|
19
|
+
* const url = new URL(req.url)
|
|
20
|
+
* if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)
|
|
21
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
22
|
+
* },
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function handleCloudflareUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response>;
|
|
27
|
+
/**
|
|
28
|
+
* Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the
|
|
29
|
+
* response + socket pair, then plugs the socket into the registry.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* ```ts
|
|
33
|
+
* Deno.serve(async (req) => {
|
|
34
|
+
* const url = new URL(req.url)
|
|
35
|
+
* if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)
|
|
36
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function handleDenoUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response>;
|
|
41
|
+
//# sourceMappingURL=upgrade.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../src/server/web/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGjD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAOxD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,QAAQ,CAAC,CAmCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CA+B/F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createWHATWGPairingConnection } from './adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the bearer token from a LAP WebSocket upgrade request.
|
|
4
|
+
* Accepts the token on either `?token=` or `Authorization: Bearer` —
|
|
5
|
+
* query-string is the common pattern because browsers can't set
|
|
6
|
+
* arbitrary headers on WebSocket construction.
|
|
7
|
+
*/
|
|
8
|
+
export function extractToken(req) {
|
|
9
|
+
const url = new URL(req.url);
|
|
10
|
+
const q = url.searchParams.get('token');
|
|
11
|
+
if (q)
|
|
12
|
+
return q;
|
|
13
|
+
const auth = req.headers.get('authorization');
|
|
14
|
+
if (auth?.startsWith('Bearer '))
|
|
15
|
+
return auth.slice('Bearer '.length);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Cloudflare Workers handler. Accepts a WebSocket upgrade using
|
|
20
|
+
* `WebSocketPair`, validates the token via
|
|
21
|
+
* `agent.acceptConnection`, and returns the 101 upgrade Response.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })
|
|
26
|
+
* export default {
|
|
27
|
+
* async fetch(req, env) {
|
|
28
|
+
* const url = new URL(req.url)
|
|
29
|
+
* if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)
|
|
30
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
31
|
+
* },
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export async function handleCloudflareUpgrade(req, agent) {
|
|
36
|
+
if (req.headers.get('upgrade') !== 'websocket') {
|
|
37
|
+
return new Response('Expected upgrade: websocket', { status: 426 });
|
|
38
|
+
}
|
|
39
|
+
const token = extractToken(req);
|
|
40
|
+
if (!token)
|
|
41
|
+
return new Response('Unauthorized', { status: 401 });
|
|
42
|
+
// `WebSocketPair` is a Cloudflare Workers global. We reference it
|
|
43
|
+
// through `globalThis` so importing this module in non-CF runtimes
|
|
44
|
+
// (e.g. during type-checking on Node) doesn't crash.
|
|
45
|
+
const Pair = globalThis.WebSocketPair;
|
|
46
|
+
if (!Pair) {
|
|
47
|
+
return new Response('WebSocketPair unavailable in this runtime', { status: 501 });
|
|
48
|
+
}
|
|
49
|
+
const pair = new Pair();
|
|
50
|
+
const client = pair[0];
|
|
51
|
+
const server = pair[1];
|
|
52
|
+
server.accept();
|
|
53
|
+
const conn = createWHATWGPairingConnection(server);
|
|
54
|
+
const result = await agent.acceptConnection(token, conn);
|
|
55
|
+
if (!result.ok) {
|
|
56
|
+
conn.close();
|
|
57
|
+
return new Response(result.code, { status: result.status });
|
|
58
|
+
}
|
|
59
|
+
// `webSocket` on ResponseInit is Cloudflare-specific; cast to satisfy
|
|
60
|
+
// the standard lib types.
|
|
61
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the
|
|
65
|
+
* response + socket pair, then plugs the socket into the registry.
|
|
66
|
+
*
|
|
67
|
+
* Usage:
|
|
68
|
+
* ```ts
|
|
69
|
+
* Deno.serve(async (req) => {
|
|
70
|
+
* const url = new URL(req.url)
|
|
71
|
+
* if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)
|
|
72
|
+
* return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })
|
|
73
|
+
* })
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export async function handleDenoUpgrade(req, agent) {
|
|
77
|
+
const token = extractToken(req);
|
|
78
|
+
if (!token)
|
|
79
|
+
return new Response('Unauthorized', { status: 401 });
|
|
80
|
+
const Deno_ = globalThis.Deno;
|
|
81
|
+
if (!Deno_) {
|
|
82
|
+
return new Response('Deno.upgradeWebSocket unavailable in this runtime', { status: 501 });
|
|
83
|
+
}
|
|
84
|
+
const { socket, response } = Deno_.upgradeWebSocket(req);
|
|
85
|
+
const conn = createWHATWGPairingConnection(socket);
|
|
86
|
+
// Deno opens the socket asynchronously; validate the token first,
|
|
87
|
+
// then register on `open` so frames aren't missed.
|
|
88
|
+
socket.addEventListener('open', () => {
|
|
89
|
+
void agent.acceptConnection(token, conn).then((result) => {
|
|
90
|
+
if (!result.ok)
|
|
91
|
+
conn.close();
|
|
92
|
+
});
|
|
93
|
+
}, { once: true });
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../../src/server/web/upgrade.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentCoreHandle } from '../core.js'\nimport { createWHATWGPairingConnection } from './adapter.js'\n\n/**\n * Extract the bearer token from a LAP WebSocket upgrade request.\n * Accepts the token on either `?token=` or `Authorization: Bearer` —\n * query-string is the common pattern because browsers can't set\n * arbitrary headers on WebSocket construction.\n */\nexport function extractToken(req: Request): string | null {\n const url = new URL(req.url)\n const q = url.searchParams.get('token')\n if (q) return q\n const auth = req.headers.get('authorization')\n if (auth?.startsWith('Bearer ')) return auth.slice('Bearer '.length)\n return null\n}\n\n/**\n * Cloudflare Workers handler. Accepts a WebSocket upgrade using\n * `WebSocketPair`, validates the token via\n * `agent.acceptConnection`, and returns the 101 upgrade Response.\n *\n * Usage:\n * ```ts\n * const agent = createLluiAgentCore({ signingKey: env.AGENT_KEY })\n * export default {\n * async fetch(req, env) {\n * const url = new URL(req.url)\n * if (url.pathname === '/agent/ws') return handleCloudflareUpgrade(req, agent)\n * return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })\n * },\n * }\n * ```\n */\nexport async function handleCloudflareUpgrade(\n req: Request,\n agent: AgentCoreHandle,\n): Promise<Response> {\n if (req.headers.get('upgrade') !== 'websocket') {\n return new Response('Expected upgrade: websocket', { status: 426 })\n }\n const token = extractToken(req)\n if (!token) return new Response('Unauthorized', { status: 401 })\n\n // `WebSocketPair` is a Cloudflare Workers global. We reference it\n // through `globalThis` so importing this module in non-CF runtimes\n // (e.g. during type-checking on Node) doesn't crash.\n const Pair = (\n globalThis as unknown as { WebSocketPair?: new () => { 0: WebSocket; 1: WebSocket } }\n ).WebSocketPair\n if (!Pair) {\n return new Response('WebSocketPair unavailable in this runtime', { status: 501 })\n }\n const pair = new Pair()\n const client = pair[0]\n const server = pair[1]!\n // `accept()` on the server half is Cloudflare-specific — it tells\n // the runtime the Worker will handle the WebSocket itself.\n ;(server as unknown as { accept: () => void }).accept()\n\n const conn = createWHATWGPairingConnection(server)\n const result = await agent.acceptConnection(token, conn)\n if (!result.ok) {\n conn.close()\n return new Response(result.code, { status: result.status })\n }\n\n // `webSocket` on ResponseInit is Cloudflare-specific; cast to satisfy\n // the standard lib types.\n return new Response(null, { status: 101, webSocket: client } as ResponseInit & {\n webSocket: WebSocket\n })\n}\n\n/**\n * Deno handler. Uses `Deno.upgradeWebSocket(req)` to produce the\n * response + socket pair, then plugs the socket into the registry.\n *\n * Usage:\n * ```ts\n * Deno.serve(async (req) => {\n * const url = new URL(req.url)\n * if (url.pathname === '/agent/ws') return handleDenoUpgrade(req, agent)\n * return (await agent.router(req)) ?? new Response('Not Found', { status: 404 })\n * })\n * ```\n */\nexport async function handleDenoUpgrade(req: Request, agent: AgentCoreHandle): Promise<Response> {\n const token = extractToken(req)\n if (!token) return new Response('Unauthorized', { status: 401 })\n\n const Deno_ = (\n globalThis as unknown as {\n Deno?: {\n upgradeWebSocket: (req: Request) => { socket: WebSocket; response: Response }\n }\n }\n ).Deno\n if (!Deno_) {\n return new Response('Deno.upgradeWebSocket unavailable in this runtime', { status: 501 })\n }\n\n const { socket, response } = Deno_.upgradeWebSocket(req)\n const conn = createWHATWGPairingConnection(socket)\n\n // Deno opens the socket asynchronously; validate the token first,\n // then register on `open` so frames aren't missed.\n socket.addEventListener(\n 'open',\n () => {\n void agent.acceptConnection(token, conn).then((result) => {\n if (!result.ok) conn.close()\n })\n },\n { once: true },\n )\n\n return response\n}\n"]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { ClientFrame, ServerFrame, HelloFrame, LogEntry } from '../../protocol.js';
|
|
2
|
+
import { type RpcOptions, type RpcError } from './rpc.js';
|
|
3
|
+
export type { RpcOptions, RpcError };
|
|
2
4
|
/**
|
|
3
|
-
* Thin abstraction over a
|
|
4
|
-
*
|
|
5
|
+
* Thin abstraction over a single paired WebSocket. Consumed by the
|
|
6
|
+
* registry implementations; runtime-specific adapters (`ws`-lib,
|
|
7
|
+
* `WebSocketPair`, `Deno.upgradeWebSocket`, `Bun.serve` upgrade) build
|
|
8
|
+
* one of these and pass it to `registry.register()`.
|
|
5
9
|
*/
|
|
6
10
|
export interface PairingConnection {
|
|
7
11
|
send(frame: ServerFrame): void;
|
|
@@ -9,35 +13,85 @@ export interface PairingConnection {
|
|
|
9
13
|
onClose(handler: () => void): void;
|
|
10
14
|
close(): void;
|
|
11
15
|
}
|
|
12
|
-
export type RpcError = {
|
|
13
|
-
code: 'paused' | 'invalid' | 'timeout' | 'schema-error' | 'internal' | string;
|
|
14
|
-
detail?: string;
|
|
15
|
-
};
|
|
16
|
-
export type RpcOptions = {
|
|
17
|
-
timeoutMs?: number;
|
|
18
|
-
};
|
|
19
16
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* A per-call frame subscriber. Return `true` to remove this
|
|
18
|
+
* subscriber (one-shot), or `false` to keep receiving. The registry
|
|
19
|
+
* dispatches every inbound `ClientFrame` to every active subscriber
|
|
20
|
+
* for the given `tid`; subscribers filter by `frame.t` + identifiers
|
|
21
|
+
* (correlation id, confirm id, state path) to find the one that
|
|
22
|
+
* belongs to their request.
|
|
23
23
|
*/
|
|
24
|
-
export
|
|
24
|
+
export type FrameSubscriber = (frame: ClientFrame) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Registry of live browser pairings. Pure routing + hello cache —
|
|
27
|
+
* request-lifecycle state (in-flight RPC promises, confirm waits,
|
|
28
|
+
* long-polls) lives in the LAP handlers that need it, not here.
|
|
29
|
+
*
|
|
30
|
+
* Two implementations ship today:
|
|
31
|
+
* - `InMemoryPairingRegistry` for long-lived server processes
|
|
32
|
+
* (Node, Bun, Deno, Deno Deploy).
|
|
33
|
+
* - A Cloudflare Durable Object implementation (see
|
|
34
|
+
* `server/cloudflare`) for stateless Worker runtimes.
|
|
35
|
+
*
|
|
36
|
+
* Other runtimes can implement this interface the same way; the
|
|
37
|
+
* contract is intentionally small.
|
|
38
|
+
*/
|
|
39
|
+
export interface PairingRegistry {
|
|
40
|
+
register(tid: string, conn: PairingConnection): void;
|
|
41
|
+
unregister(tid: string): void;
|
|
42
|
+
isPaired(tid: string): boolean;
|
|
43
|
+
getHello(tid: string): HelloFrame | null;
|
|
44
|
+
/** Send a frame. No-op when the pairing is absent or closed. */
|
|
45
|
+
send(tid: string, frame: ServerFrame): void;
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to frames from the paired browser. Returns an
|
|
48
|
+
* unsubscribe function. A subscriber can remove itself mid-dispatch
|
|
49
|
+
* by returning `true` from its callback — useful for one-shot
|
|
50
|
+
* request/response correlation.
|
|
51
|
+
*/
|
|
52
|
+
subscribe(tid: string, handler: FrameSubscriber): () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Observe the pairing closing (WebSocket drop, `unregister`, etc.).
|
|
55
|
+
* Handlers registered before close fire; handlers registered after
|
|
56
|
+
* close fire synchronously. Returns an unsubscribe function.
|
|
57
|
+
*/
|
|
58
|
+
onClose(tid: string, handler: () => void): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Send a typed rpc frame and await its matching reply. See
|
|
61
|
+
* `./rpc.ts::rpc` for the full contract.
|
|
62
|
+
*/
|
|
63
|
+
rpc(tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>;
|
|
64
|
+
/** See `./rpc.ts::waitForConfirm`. */
|
|
65
|
+
waitForConfirm(tid: string, confirmId: string, timeoutMs: number): Promise<{
|
|
66
|
+
outcome: 'confirmed' | 'user-cancelled';
|
|
67
|
+
stateAfter?: unknown;
|
|
68
|
+
}>;
|
|
69
|
+
/** See `./rpc.ts::waitForChange`. */
|
|
70
|
+
waitForChange(tid: string, path: string | undefined, timeoutMs: number): Promise<{
|
|
71
|
+
status: 'changed' | 'timeout';
|
|
72
|
+
stateAfter: unknown;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Single-process in-memory registry. Correct for Node/Bun/Deno/Deno
|
|
77
|
+
* Deploy — anywhere the server process can hold a long-lived
|
|
78
|
+
* WebSocket. Not suitable for stateless Worker isolates; use the
|
|
79
|
+
* Durable Object registry for Cloudflare.
|
|
80
|
+
*/
|
|
81
|
+
export declare class InMemoryPairingRegistry implements PairingRegistry {
|
|
25
82
|
private pairings;
|
|
26
|
-
private now;
|
|
27
83
|
private onLogAppend;
|
|
28
84
|
constructor(opts?: {
|
|
29
|
-
now?: () => number;
|
|
30
85
|
onLogAppend?: (tid: string, entry: LogEntry) => void;
|
|
31
86
|
});
|
|
32
87
|
register(tid: string, conn: PairingConnection): void;
|
|
33
88
|
unregister(tid: string): void;
|
|
34
89
|
isPaired(tid: string): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Send a ServerFrame to the paired browser connection, if one is live.
|
|
37
|
-
* No-op when unpaired or closed.
|
|
38
|
-
*/
|
|
39
|
-
notify(tid: string, frame: ServerFrame): void;
|
|
40
90
|
getHello(tid: string): HelloFrame | null;
|
|
91
|
+
send(tid: string, frame: ServerFrame): void;
|
|
92
|
+
subscribe(tid: string, handler: FrameSubscriber): () => void;
|
|
93
|
+
onClose(tid: string, handler: () => void): () => void;
|
|
94
|
+
private dispatch;
|
|
41
95
|
rpc(tid: string, tool: string, args: unknown, opts?: RpcOptions): Promise<unknown>;
|
|
42
96
|
waitForConfirm(tid: string, confirmId: string, timeoutMs: number): Promise<{
|
|
43
97
|
outcome: 'confirmed' | 'user-cancelled';
|
|
@@ -47,7 +101,16 @@ export declare class WsPairingRegistry {
|
|
|
47
101
|
status: 'changed' | 'timeout';
|
|
48
102
|
stateAfter: unknown;
|
|
49
103
|
}>;
|
|
50
|
-
|
|
104
|
+
/** @deprecated Use `send(tid, frame)` directly; semantics are identical. */
|
|
105
|
+
notify(tid: string, frame: ServerFrame): void;
|
|
51
106
|
private handleClose;
|
|
52
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Back-compat alias for the prior class name. New code should use
|
|
110
|
+
* `InMemoryPairingRegistry`. Removed in a future major.
|
|
111
|
+
*
|
|
112
|
+
* @deprecated Use `InMemoryPairingRegistry` directly.
|
|
113
|
+
*/
|
|
114
|
+
export declare const WsPairingRegistry: typeof InMemoryPairingRegistry;
|
|
115
|
+
export type WsPairingRegistry = InMemoryPairingRegistry;
|
|
53
116
|
//# sourceMappingURL=pairing-registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pairing-registry.d.ts","sourceRoot":"","sources":["../../../src/server/ws/pairing-registry.ts"],"names":[],"mappings":"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"}
|