sealbridge 0.0.1 → 0.1.0
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/NOTICE +43 -0
- package/README.ja.md +375 -0
- package/README.md +466 -1
- package/dist/cli/dist/acp.d.ts +2 -0
- package/dist/cli/dist/acp.js +6 -0
- package/dist/cli/dist/acp.js.map +1 -0
- package/dist/cli/dist/board.d.ts +47 -0
- package/dist/cli/dist/board.js +255 -0
- package/dist/cli/dist/board.js.map +1 -0
- package/dist/cli/dist/contacts.d.ts +18 -0
- package/dist/cli/dist/contacts.js +154 -0
- package/dist/cli/dist/contacts.js.map +1 -0
- package/dist/cli/dist/doctor.d.ts +13 -0
- package/dist/cli/dist/doctor.js +30 -0
- package/dist/cli/dist/doctor.js.map +1 -0
- package/dist/cli/dist/index.d.ts +2 -0
- package/dist/cli/dist/index.js +193 -0
- package/dist/cli/dist/index.js.map +1 -0
- package/dist/cli/dist/invite-share.d.ts +16 -0
- package/dist/cli/dist/invite-share.js +169 -0
- package/dist/cli/dist/invite-share.js.map +1 -0
- package/dist/cli/dist/mcp.d.ts +17 -0
- package/dist/cli/dist/mcp.js +105 -0
- package/dist/cli/dist/mcp.js.map +1 -0
- package/dist/cli/dist/network.d.ts +1 -0
- package/dist/cli/dist/network.js +320 -0
- package/dist/cli/dist/network.js.map +1 -0
- package/dist/cli/dist/public-settings.d.ts +27 -0
- package/dist/cli/dist/public-settings.js +33 -0
- package/dist/cli/dist/public-settings.js.map +1 -0
- package/dist/cli/dist/receipt.d.ts +39 -0
- package/dist/cli/dist/receipt.js +90 -0
- package/dist/cli/dist/receipt.js.map +1 -0
- package/dist/cli/dist/receive-hook.d.ts +2 -0
- package/dist/cli/dist/receive-hook.js +35 -0
- package/dist/cli/dist/receive-hook.js.map +1 -0
- package/dist/examples/two-owner-demo/alice-policy.json +4 -0
- package/dist/examples/two-owner-demo/bob-policy.json +4 -0
- package/dist/examples/two-owner-demo/dist/run-demo.d.ts +71 -0
- package/dist/examples/two-owner-demo/dist/run-demo.js +383 -0
- package/dist/examples/two-owner-demo/dist/run-demo.js.map +1 -0
- package/dist/package.json +1 -0
- package/dist/packages/adapters/dist/command.d.ts +120 -0
- package/dist/packages/adapters/dist/command.js +365 -0
- package/dist/packages/adapters/dist/command.js.map +1 -0
- package/dist/packages/adapters/dist/credential-proxy.d.ts +37 -0
- package/dist/packages/adapters/dist/credential-proxy.js +363 -0
- package/dist/packages/adapters/dist/credential-proxy.js.map +1 -0
- package/dist/packages/adapters/dist/index.d.ts +6 -0
- package/dist/packages/adapters/dist/index.js +6 -0
- package/dist/packages/adapters/dist/index.js.map +1 -0
- package/dist/packages/adapters/dist/mcp.d.ts +32 -0
- package/dist/packages/adapters/dist/mcp.js +87 -0
- package/dist/packages/adapters/dist/mcp.js.map +1 -0
- package/dist/packages/adapters/dist/openai.d.ts +23 -0
- package/dist/packages/adapters/dist/openai.js +81 -0
- package/dist/packages/adapters/dist/openai.js.map +1 -0
- package/dist/packages/adapters/dist/sandbox.d.ts +45 -0
- package/dist/packages/adapters/dist/sandbox.js +60 -0
- package/dist/packages/adapters/dist/sandbox.js.map +1 -0
- package/dist/packages/core/dist/capability.d.ts +36 -0
- package/dist/packages/core/dist/capability.js +198 -0
- package/dist/packages/core/dist/capability.js.map +1 -0
- package/dist/packages/core/dist/contract.d.ts +33 -0
- package/dist/packages/core/dist/contract.js +97 -0
- package/dist/packages/core/dist/contract.js.map +1 -0
- package/dist/packages/core/dist/crypto.d.ts +34 -0
- package/dist/packages/core/dist/crypto.js +323 -0
- package/dist/packages/core/dist/crypto.js.map +1 -0
- package/dist/packages/core/dist/discovery.d.ts +124 -0
- package/dist/packages/core/dist/discovery.js +511 -0
- package/dist/packages/core/dist/discovery.js.map +1 -0
- package/dist/packages/core/dist/edge.d.ts +4 -0
- package/dist/packages/core/dist/edge.js +6 -0
- package/dist/packages/core/dist/edge.js.map +1 -0
- package/dist/packages/core/dist/index.d.ts +9 -0
- package/dist/packages/core/dist/index.js +10 -0
- package/dist/packages/core/dist/index.js.map +1 -0
- package/dist/packages/core/dist/receipts.d.ts +9 -0
- package/dist/packages/core/dist/receipts.js +67 -0
- package/dist/packages/core/dist/receipts.js.map +1 -0
- package/dist/packages/core/dist/relay-auth.d.ts +40 -0
- package/dist/packages/core/dist/relay-auth.js +106 -0
- package/dist/packages/core/dist/relay-auth.js.map +1 -0
- package/dist/packages/core/dist/revocation.d.ts +55 -0
- package/dist/packages/core/dist/revocation.js +88 -0
- package/dist/packages/core/dist/revocation.js.map +1 -0
- package/dist/packages/core/dist/schema.d.ts +11 -0
- package/dist/packages/core/dist/schema.js +48 -0
- package/dist/packages/core/dist/schema.js.map +1 -0
- package/dist/packages/core/dist/types.d.ts +172 -0
- package/dist/packages/core/dist/types.js +2 -0
- package/dist/packages/core/dist/types.js.map +1 -0
- package/dist/packages/ledger/dist/index.d.ts +84 -0
- package/dist/packages/ledger/dist/index.js +269 -0
- package/dist/packages/ledger/dist/index.js.map +1 -0
- package/dist/packages/relay/dist/fetch.d.ts +223 -0
- package/dist/packages/relay/dist/fetch.js +1210 -0
- package/dist/packages/relay/dist/fetch.js.map +1 -0
- package/dist/packages/relay/dist/index.d.ts +92 -0
- package/dist/packages/relay/dist/index.js +707 -0
- package/dist/packages/relay/dist/index.js.map +1 -0
- package/dist/packages/relay/dist/journal.d.ts +70 -0
- package/dist/packages/relay/dist/journal.js +310 -0
- package/dist/packages/relay/dist/journal.js.map +1 -0
- package/dist/packages/relay/dist/retry.d.ts +9 -0
- package/dist/packages/relay/dist/retry.js +39 -0
- package/dist/packages/relay/dist/retry.js.map +1 -0
- package/dist/packages/relay/dist/ssrf.d.ts +32 -0
- package/dist/packages/relay/dist/ssrf.js +213 -0
- package/dist/packages/relay/dist/ssrf.js.map +1 -0
- package/dist/packages/relay/dist/worker.d.ts +85 -0
- package/dist/packages/relay/dist/worker.js +411 -0
- package/dist/packages/relay/dist/worker.js.map +1 -0
- package/dist/packages/runtime/dist/a2a.d.ts +8 -0
- package/dist/packages/runtime/dist/a2a.js +69 -0
- package/dist/packages/runtime/dist/a2a.js.map +1 -0
- package/dist/packages/runtime/dist/adapter.d.ts +51 -0
- package/dist/packages/runtime/dist/adapter.js +2 -0
- package/dist/packages/runtime/dist/adapter.js.map +1 -0
- package/dist/packages/runtime/dist/approval-providers.d.ts +97 -0
- package/dist/packages/runtime/dist/approval-providers.js +269 -0
- package/dist/packages/runtime/dist/approval-providers.js.map +1 -0
- package/dist/packages/runtime/dist/daily-limit.d.ts +2 -0
- package/dist/packages/runtime/dist/daily-limit.js +54 -0
- package/dist/packages/runtime/dist/daily-limit.js.map +1 -0
- package/dist/packages/runtime/dist/gateway.d.ts +35 -0
- package/dist/packages/runtime/dist/gateway.js +92 -0
- package/dist/packages/runtime/dist/gateway.js.map +1 -0
- package/dist/packages/runtime/dist/inbox.d.ts +10 -0
- package/dist/packages/runtime/dist/inbox.js +24 -0
- package/dist/packages/runtime/dist/inbox.js.map +1 -0
- package/dist/packages/runtime/dist/index.d.ts +13 -0
- package/dist/packages/runtime/dist/index.js +14 -0
- package/dist/packages/runtime/dist/index.js.map +1 -0
- package/dist/packages/runtime/dist/mcp-server.d.ts +90 -0
- package/dist/packages/runtime/dist/mcp-server.js +631 -0
- package/dist/packages/runtime/dist/mcp-server.js.map +1 -0
- package/dist/packages/runtime/dist/mcp-tool-catalog.d.ts +3 -0
- package/dist/packages/runtime/dist/mcp-tool-catalog.js +491 -0
- package/dist/packages/runtime/dist/mcp-tool-catalog.js.map +1 -0
- package/dist/packages/runtime/dist/mcp.d.ts +73 -0
- package/dist/packages/runtime/dist/mcp.js +67 -0
- package/dist/packages/runtime/dist/mcp.js.map +1 -0
- package/dist/packages/runtime/dist/policy.d.ts +19 -0
- package/dist/packages/runtime/dist/policy.js +41 -0
- package/dist/packages/runtime/dist/policy.js.map +1 -0
- package/dist/packages/runtime/dist/quota.d.ts +39 -0
- package/dist/packages/runtime/dist/quota.js +147 -0
- package/dist/packages/runtime/dist/quota.js.map +1 -0
- package/dist/packages/runtime/dist/session-audit.d.ts +88 -0
- package/dist/packages/runtime/dist/session-audit.js +415 -0
- package/dist/packages/runtime/dist/session-audit.js.map +1 -0
- package/dist/packages/runtime/dist/session.d.ts +288 -0
- package/dist/packages/runtime/dist/session.js +1880 -0
- package/dist/packages/runtime/dist/session.js.map +1 -0
- package/dist/packages/runtime/dist/verifier.d.ts +64 -0
- package/dist/packages/runtime/dist/verifier.js +109 -0
- package/dist/packages/runtime/dist/verifier.js.map +1 -0
- package/dist/packages/sandbox/dist/container.d.ts +33 -0
- package/dist/packages/sandbox/dist/container.js +189 -0
- package/dist/packages/sandbox/dist/container.js.map +1 -0
- package/dist/packages/sandbox/dist/index.d.ts +34 -0
- package/dist/packages/sandbox/dist/index.js +179 -0
- package/dist/packages/sandbox/dist/index.js.map +1 -0
- package/dist/packages/sandbox/dist/permission.d.ts +58 -0
- package/dist/packages/sandbox/dist/permission.js +90 -0
- package/dist/packages/sandbox/dist/permission.js.map +1 -0
- package/dist/spec/README.ja.md +76 -0
- package/dist/spec/README.md +135 -0
- package/dist/spec/ack-receipt.schema.json +77 -0
- package/dist/spec/agent-card.schema.json +83 -0
- package/dist/spec/approval-receipt.schema.json +94 -0
- package/dist/spec/audit-entry.schema.json +101 -0
- package/dist/spec/capability-token.schema.json +133 -0
- package/dist/spec/envelope.schema.json +197 -0
- package/dist/spec/examples/sample-ack-receipt.json +16 -0
- package/dist/spec/examples/sample-agent-card.json +35 -0
- package/dist/spec/examples/sample-approval-receipt.json +18 -0
- package/dist/spec/examples/sample-audit-entry.json +37 -0
- package/dist/spec/examples/sample-capability-token.json +39 -0
- package/dist/spec/examples/sample-envelope.json +21 -0
- package/dist/spec/examples/sample-task-manifest.json +141 -0
- package/dist/spec/examples/sample-usage-receipt.json +19 -0
- package/dist/spec/task-manifest.schema.json +422 -0
- package/dist/spec/usage-receipt.schema.json +91 -0
- package/package.json +61 -5
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
4
|
+
import { verifyEnvelope, createRelayAuthToken, normalizeRelayOrigin, validateRelayEndpointUrl, } from '../../core/dist/index.js';
|
|
5
|
+
import { DEFAULT_MAX_ENVELOPE_BYTES, InMemoryRelay, createRelayFetchHandler, mapRelayError, normalizeReceiveFilter, relayHttpHeaders, sanitizeRelayErrorCode, } from './fetch.js';
|
|
6
|
+
import { createPinnedRelayFetch, resolveAndValidateRelayEndpoint } from './ssrf.js';
|
|
7
|
+
export * from './fetch.js';
|
|
8
|
+
export * from './retry.js';
|
|
9
|
+
import { isTransientRelayError, relayRetryDelay, waitForRelayRetry, relayDeadline } from './retry.js';
|
|
10
|
+
export { createPinnedRelayFetch, resolveAndValidateRelayEndpoint } from './ssrf.js';
|
|
11
|
+
export { createSignedAgentCard, verifySignedAgentCard, findDiscoveredAgentCard, toA2AAgentCard, createInviteToken, parseAndVerifyInviteToken, } from '../../core/dist/index.js';
|
|
12
|
+
export async function startLocalRelay(options = {}) {
|
|
13
|
+
const relay = new InMemoryRelay({
|
|
14
|
+
tamper: options.tamper,
|
|
15
|
+
allowUnboundRelayAuth: options.allowUnboundRelayAuth,
|
|
16
|
+
});
|
|
17
|
+
const handler = createRelayFetchHandler({
|
|
18
|
+
relay,
|
|
19
|
+
allowQueryAuth: options.allowQueryAuth,
|
|
20
|
+
allowUnboundRelayAuth: options.allowUnboundRelayAuth,
|
|
21
|
+
});
|
|
22
|
+
let boundOrigin = 'http://127.0.0.1';
|
|
23
|
+
const server = createServer(async (request, response) => {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
response.once('close', () => controller.abort());
|
|
26
|
+
try {
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let size = 0;
|
|
29
|
+
for await (const chunk of request) {
|
|
30
|
+
size += chunk.length;
|
|
31
|
+
if (size > DEFAULT_MAX_ENVELOPE_BYTES)
|
|
32
|
+
throw new Error('BODY_TOO_LARGE');
|
|
33
|
+
chunks.push(chunk);
|
|
34
|
+
}
|
|
35
|
+
const result = await handler(new Request(new URL(request.url ?? '/', boundOrigin), {
|
|
36
|
+
method: request.method,
|
|
37
|
+
headers: Object.entries(request.headers).flatMap(([name, value]) => value === undefined
|
|
38
|
+
? []
|
|
39
|
+
: (Array.isArray(value) ? value : [value]).map((v) => [name, v])),
|
|
40
|
+
...(chunks.length ? { body: Buffer.concat(chunks) } : {}),
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
}));
|
|
43
|
+
const kind = result.headers.get('content-type')?.startsWith('text/event-stream')
|
|
44
|
+
? 'sse'
|
|
45
|
+
: result.body == null
|
|
46
|
+
? 'empty'
|
|
47
|
+
: 'json';
|
|
48
|
+
response.writeHead(result.status, {
|
|
49
|
+
...Object.fromEntries(result.headers),
|
|
50
|
+
...relayHttpHeaders(kind),
|
|
51
|
+
});
|
|
52
|
+
if (!result.body) {
|
|
53
|
+
response.end();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const reader = result.body.getReader();
|
|
57
|
+
try {
|
|
58
|
+
while (!response.destroyed && !response.writableEnded) {
|
|
59
|
+
const { value, done } = await reader.read();
|
|
60
|
+
if (done)
|
|
61
|
+
break;
|
|
62
|
+
if (!response.write(value)) {
|
|
63
|
+
// A disconnected client never emits drain. Close and error must unblock the write.
|
|
64
|
+
if (response.destroyed || response.writableEnded)
|
|
65
|
+
break;
|
|
66
|
+
await new Promise((resolve) => {
|
|
67
|
+
const doneWriting = () => {
|
|
68
|
+
response.off('drain', doneWriting);
|
|
69
|
+
response.off('close', doneWriting);
|
|
70
|
+
response.off('error', doneWriting);
|
|
71
|
+
resolve();
|
|
72
|
+
};
|
|
73
|
+
response.once('drain', doneWriting);
|
|
74
|
+
response.once('close', doneWriting);
|
|
75
|
+
response.once('error', doneWriting);
|
|
76
|
+
if (response.destroyed || response.writableEnded)
|
|
77
|
+
doneWriting();
|
|
78
|
+
});
|
|
79
|
+
if (response.destroyed || response.writableEnded)
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await reader.cancel();
|
|
86
|
+
if (!response.destroyed && !response.writableEnded)
|
|
87
|
+
response.end();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
const mapped = mapRelayError(e);
|
|
92
|
+
if (!response.headersSent)
|
|
93
|
+
response.writeHead(mapped.status, relayHttpHeaders('json'));
|
|
94
|
+
if (!response.destroyed)
|
|
95
|
+
response.end(JSON.stringify({ error: mapped.error }));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
server.requestTimeout = 10000;
|
|
99
|
+
const wsServer = new WebSocketServer({ server, path: '/ws', maxPayload: 1048576 });
|
|
100
|
+
const wsAuth = () => ({
|
|
101
|
+
expectedOrigin: boundOrigin,
|
|
102
|
+
requireOrigin: options.allowUnboundRelayAuth !== true,
|
|
103
|
+
});
|
|
104
|
+
wsServer.on('connection', (socket) => {
|
|
105
|
+
let subscription = { disposed: true, stop: () => { } };
|
|
106
|
+
const disposeSubscription = () => {
|
|
107
|
+
subscription.disposed = true;
|
|
108
|
+
if (subscription.timer !== undefined) {
|
|
109
|
+
clearTimeout(subscription.timer);
|
|
110
|
+
subscription.timer = undefined;
|
|
111
|
+
}
|
|
112
|
+
subscription.stop();
|
|
113
|
+
subscription.stop = () => { };
|
|
114
|
+
};
|
|
115
|
+
socket.once('close', () => disposeSubscription());
|
|
116
|
+
socket.on('message', (raw) => {
|
|
117
|
+
try {
|
|
118
|
+
const request = JSON.parse(raw.toString());
|
|
119
|
+
if (request.method === 'send' && request.envelope) {
|
|
120
|
+
relay.send(request.envelope);
|
|
121
|
+
socket.send(JSON.stringify({ ok: true }));
|
|
122
|
+
}
|
|
123
|
+
else if (request.method === 'receive' && request.recipientDid) {
|
|
124
|
+
const receive = relay.openInbox(request.recipientDid, request.authToken ?? request.auth, 'receive', wsAuth(), { role: request.role, taskId: request.taskId, peek: request.peek });
|
|
125
|
+
socket.send(JSON.stringify({ envelope: receive() ?? null }));
|
|
126
|
+
}
|
|
127
|
+
else if (request.method === 'subscribe' && request.recipientDid) {
|
|
128
|
+
const did = request.recipientDid, receive = relay.openInbox(did, request.authToken ?? request.auth, 'subscribe', wsAuth(), { role: request.role, taskId: request.taskId });
|
|
129
|
+
disposeSubscription();
|
|
130
|
+
const current = { disposed: false, stop: () => { } };
|
|
131
|
+
subscription = current;
|
|
132
|
+
const flush = () => {
|
|
133
|
+
if (current.disposed)
|
|
134
|
+
return;
|
|
135
|
+
let envelope;
|
|
136
|
+
// A slow socket must not pull the recipient queue ahead of maxQueueSize.
|
|
137
|
+
while (!current.disposed &&
|
|
138
|
+
socket.readyState === WebSocket.OPEN &&
|
|
139
|
+
socket.bufferedAmount < 1048576 &&
|
|
140
|
+
(envelope = receive()))
|
|
141
|
+
socket.send(JSON.stringify({ envelope }));
|
|
142
|
+
if (!current.disposed &&
|
|
143
|
+
socket.readyState === WebSocket.OPEN &&
|
|
144
|
+
socket.bufferedAmount >= 1048576) {
|
|
145
|
+
if (current.timer !== undefined)
|
|
146
|
+
clearTimeout(current.timer);
|
|
147
|
+
current.timer = setTimeout(() => {
|
|
148
|
+
current.timer = undefined;
|
|
149
|
+
flush();
|
|
150
|
+
}, 20);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
current.stop = relay.subscribe(did, flush);
|
|
154
|
+
flush();
|
|
155
|
+
}
|
|
156
|
+
else if (request.method === 'unsubscribe') {
|
|
157
|
+
disposeSubscription();
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
throw new Error('INVALID_REQUEST');
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
socket.send(JSON.stringify({ ok: false, error: sanitizeRelayErrorCode(e) }));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
await new Promise((resolve, reject) => {
|
|
168
|
+
server.once('error', reject);
|
|
169
|
+
server.listen(options.port ?? 0, options.host ?? '127.0.0.1', () => {
|
|
170
|
+
const address = server.address();
|
|
171
|
+
if (!address || typeof address === 'string') {
|
|
172
|
+
reject(new Error('RELAY_BIND'));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const hostname = address.address.includes(':')
|
|
176
|
+
? '[' + address.address + ']'
|
|
177
|
+
: address.address;
|
|
178
|
+
boundOrigin = 'http://' + hostname + ':' + address.port;
|
|
179
|
+
resolve();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
relay,
|
|
184
|
+
url: boundOrigin,
|
|
185
|
+
wsUrl: boundOrigin.replace(/^http:/, 'ws:') + '/ws',
|
|
186
|
+
close: async () => {
|
|
187
|
+
for (const client of wsServer.clients)
|
|
188
|
+
client.terminate();
|
|
189
|
+
await new Promise((resolve) => wsServer.close(() => resolve()));
|
|
190
|
+
server.closeAllConnections();
|
|
191
|
+
await new Promise((resolve, reject) => server.close((e) => (e ? reject(e) : resolve())));
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
class RelayCredentials {
|
|
196
|
+
authSource;
|
|
197
|
+
relayUrl;
|
|
198
|
+
recipientKeys = new Map();
|
|
199
|
+
constructor(authSource, relayUrl) {
|
|
200
|
+
this.authSource = authSource;
|
|
201
|
+
this.relayUrl = relayUrl;
|
|
202
|
+
}
|
|
203
|
+
setRecipientKey(key) {
|
|
204
|
+
this.recipientKeys.set(key.did, key);
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
async authToken(did, action) {
|
|
208
|
+
const origin = normalizeRelayOrigin(this.relayUrl);
|
|
209
|
+
const key = this.recipientKeys.get(did);
|
|
210
|
+
if (key)
|
|
211
|
+
return createRelayAuthToken(key, { action, origin });
|
|
212
|
+
if (typeof this.authSource === 'function')
|
|
213
|
+
return this.authSource(did, action, origin);
|
|
214
|
+
if (this.authSource?.did === did)
|
|
215
|
+
return createRelayAuthToken(this.authSource, { action, origin });
|
|
216
|
+
throw new Error('RELAY_AUTH_REQUIRED');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const nativeFetch = globalThis.fetch;
|
|
220
|
+
function isKeyPair(value) {
|
|
221
|
+
return (!!value &&
|
|
222
|
+
typeof value === 'object' &&
|
|
223
|
+
'did' in value &&
|
|
224
|
+
'privateKey' in value &&
|
|
225
|
+
typeof value.did === 'string' &&
|
|
226
|
+
typeof value.privateKey === 'string');
|
|
227
|
+
}
|
|
228
|
+
function splitRelayArgs(second, third, fourth) {
|
|
229
|
+
let fetcher = fetch;
|
|
230
|
+
let auth;
|
|
231
|
+
let security = {};
|
|
232
|
+
if (typeof second === 'function')
|
|
233
|
+
fetcher = second;
|
|
234
|
+
else if (isKeyPair(second))
|
|
235
|
+
auth = second;
|
|
236
|
+
else if (second)
|
|
237
|
+
security = { ...second };
|
|
238
|
+
if (typeof third === 'function' || isKeyPair(third))
|
|
239
|
+
auth = third;
|
|
240
|
+
else if (third)
|
|
241
|
+
security = { ...security, ...third };
|
|
242
|
+
if (fourth)
|
|
243
|
+
security = { ...security, ...fourth };
|
|
244
|
+
return { fetcher, auth, security };
|
|
245
|
+
}
|
|
246
|
+
export class HttpRelayClient extends RelayCredentials {
|
|
247
|
+
url;
|
|
248
|
+
buffered = new Map();
|
|
249
|
+
delivered = new Map();
|
|
250
|
+
baseFetcher;
|
|
251
|
+
security;
|
|
252
|
+
transportPromise;
|
|
253
|
+
fetcher;
|
|
254
|
+
constructor(url, fetcherOrKey = fetch, authOrOptions, options) {
|
|
255
|
+
const parsed = splitRelayArgs(fetcherOrKey, authOrOptions, options);
|
|
256
|
+
super(parsed.auth, url);
|
|
257
|
+
this.url = url;
|
|
258
|
+
this.baseFetcher = parsed.fetcher;
|
|
259
|
+
this.security = parsed.security;
|
|
260
|
+
this.fetcher = ((input, init) => this.dispatch(input, init));
|
|
261
|
+
}
|
|
262
|
+
async dispatch(input, init) {
|
|
263
|
+
const transport = await this.prepare();
|
|
264
|
+
return transport(input, init);
|
|
265
|
+
}
|
|
266
|
+
prepare() {
|
|
267
|
+
// Cache successful pins and security failures; transient DNS failures can be retried.
|
|
268
|
+
this.transportPromise ??= this.buildTransport().catch((error) => {
|
|
269
|
+
if (isTransientRelayError(error))
|
|
270
|
+
this.transportPromise = undefined;
|
|
271
|
+
throw error;
|
|
272
|
+
});
|
|
273
|
+
return this.transportPromise;
|
|
274
|
+
}
|
|
275
|
+
async buildTransport() {
|
|
276
|
+
const custom = this.baseFetcher !== nativeFetch;
|
|
277
|
+
if (custom && !this.security.lookup) {
|
|
278
|
+
validateRelayEndpointUrl(this.url, { allowPrivateRelay: this.security.allowPrivateRelay });
|
|
279
|
+
return this.baseFetcher;
|
|
280
|
+
}
|
|
281
|
+
const resolved = await resolveAndValidateRelayEndpoint(this.url, this.security);
|
|
282
|
+
if (custom)
|
|
283
|
+
return this.baseFetcher;
|
|
284
|
+
return createPinnedRelayFetch(resolved, this.security);
|
|
285
|
+
}
|
|
286
|
+
async send(envelope) {
|
|
287
|
+
const result = await this.fetcher(this.url + '/envelopes', {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
body: JSON.stringify(envelope),
|
|
290
|
+
headers: { 'content-type': 'application/json' },
|
|
291
|
+
});
|
|
292
|
+
if (!result.ok)
|
|
293
|
+
throw new Error('RELAY_REJECTED: ' + (await result.text()));
|
|
294
|
+
}
|
|
295
|
+
async register(card) {
|
|
296
|
+
for (let attempt = 0;; attempt++) {
|
|
297
|
+
try {
|
|
298
|
+
const timeout = this.security.requestTimeoutMs ?? 45_000;
|
|
299
|
+
await relayDeadline((async () => {
|
|
300
|
+
const r = await this.fetcher(this.url + '/agents', {
|
|
301
|
+
method: 'POST', body: JSON.stringify(card),
|
|
302
|
+
headers: { 'content-type': 'application/json' },
|
|
303
|
+
signal: AbortSignal.timeout(timeout),
|
|
304
|
+
});
|
|
305
|
+
if (!r.ok) {
|
|
306
|
+
await r.body?.cancel();
|
|
307
|
+
throw new Error('RELAY_REGISTRATION_REJECTED: ' + r.status);
|
|
308
|
+
}
|
|
309
|
+
await r.arrayBuffer();
|
|
310
|
+
})(), timeout);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (attempt >= 4 || !isTransientRelayError(error))
|
|
315
|
+
throw error;
|
|
316
|
+
console.warn('Relay registration retry ' + (attempt + 1));
|
|
317
|
+
await waitForRelayRetry(relayRetryDelay(attempt));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async discover() {
|
|
322
|
+
const r = await this.fetcher(this.url + '/agents');
|
|
323
|
+
if (!r.ok)
|
|
324
|
+
throw new Error('DISCOVERY_FAILED');
|
|
325
|
+
const cards = (await r.json());
|
|
326
|
+
if (!Array.isArray(cards))
|
|
327
|
+
throw new Error('INVALID_AGENT_CARD');
|
|
328
|
+
const { verifySignedAgentCard } = await import('../../core/dist/index.js');
|
|
329
|
+
const now = Date.now();
|
|
330
|
+
return cards.filter((c) => verifySignedAgentCard(c, now));
|
|
331
|
+
}
|
|
332
|
+
async receive(recipientDid, waitMs, signal, filter) {
|
|
333
|
+
const normalized = normalizeReceiveFilter(filter);
|
|
334
|
+
const bufferKey = recipientDid +
|
|
335
|
+
'\0' +
|
|
336
|
+
(normalized.lease ? 'lease:' : '') + (normalized.role ?? '') +
|
|
337
|
+
'\0' +
|
|
338
|
+
(normalized.taskIds ? [...normalized.taskIds].sort().join(',') : '');
|
|
339
|
+
if (!normalized.peek) {
|
|
340
|
+
const queue = this.buffered.get(bufferKey) ?? [];
|
|
341
|
+
if (queue.length)
|
|
342
|
+
return queue.shift();
|
|
343
|
+
}
|
|
344
|
+
const params = new URLSearchParams({ recipient: recipientDid });
|
|
345
|
+
if (waitMs !== undefined)
|
|
346
|
+
params.set('waitMs', String(waitMs));
|
|
347
|
+
if (normalized.role)
|
|
348
|
+
params.set('role', normalized.role);
|
|
349
|
+
if (normalized.taskIds) {
|
|
350
|
+
for (const id of normalized.taskIds)
|
|
351
|
+
params.append('taskId', id);
|
|
352
|
+
}
|
|
353
|
+
if (normalized.peek)
|
|
354
|
+
params.set('peek', 'true');
|
|
355
|
+
else if (normalized.lease)
|
|
356
|
+
params.set('lease', 'true');
|
|
357
|
+
const r = await this.fetcher(this.url + '/envelopes?' + params.toString(), {
|
|
358
|
+
signal,
|
|
359
|
+
headers: { authorization: 'Bearer ' + (await this.authToken(recipientDid, 'receive')) },
|
|
360
|
+
});
|
|
361
|
+
if (r.status === 204)
|
|
362
|
+
return;
|
|
363
|
+
if (!r.ok) {
|
|
364
|
+
await r.body?.cancel();
|
|
365
|
+
throw new Error('RELAY_REJECTED: ' + r.status);
|
|
366
|
+
}
|
|
367
|
+
const body = (await r.json());
|
|
368
|
+
let values = 'envelopes' in body ? body.envelopes : [body];
|
|
369
|
+
if (!Array.isArray(values) || values.some((e) => !verifyEnvelope(e, recipientDid)))
|
|
370
|
+
throw new Error('RELAY_TAMPERING_DETECTED');
|
|
371
|
+
if (normalized.peek)
|
|
372
|
+
return values[0];
|
|
373
|
+
if (normalized.lease) {
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
for (const [id, entry] of this.delivered)
|
|
376
|
+
if (entry.expires <= now)
|
|
377
|
+
this.delivered.delete(id);
|
|
378
|
+
const fresh = [];
|
|
379
|
+
for (const envelope of values) {
|
|
380
|
+
const id = recipientDid + ':' + envelope.payload.envelopeId;
|
|
381
|
+
const prior = this.delivered.get(id);
|
|
382
|
+
if (prior) {
|
|
383
|
+
if (prior.acknowledged) {
|
|
384
|
+
try {
|
|
385
|
+
await this.acknowledgeDelivery(recipientDid, envelope.payload.envelopeId);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
if (!isTransientRelayError(error))
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (this.delivered.size >= 16384)
|
|
395
|
+
throw new Error('RELAY_DEDUPE_CAPACITY');
|
|
396
|
+
this.delivered.set(id, { expires: now + 10 * 60_000, acknowledged: false });
|
|
397
|
+
fresh.push(envelope);
|
|
398
|
+
}
|
|
399
|
+
values = fresh;
|
|
400
|
+
}
|
|
401
|
+
this.buffered.set(bufferKey, values);
|
|
402
|
+
return values.shift();
|
|
403
|
+
}
|
|
404
|
+
async acknowledgeDelivery(recipientDid, envelopeId) {
|
|
405
|
+
const entry = this.delivered.get(recipientDid + ':' + envelopeId);
|
|
406
|
+
if (!entry)
|
|
407
|
+
return;
|
|
408
|
+
// Processing has completed even if the DELETE response is lost. Redelivery retries the ack.
|
|
409
|
+
entry.acknowledged = true;
|
|
410
|
+
const r = await this.fetcher(this.url + '/envelopes/' + encodeURIComponent(envelopeId) +
|
|
411
|
+
'?recipient=' + encodeURIComponent(recipientDid), {
|
|
412
|
+
method: 'DELETE',
|
|
413
|
+
headers: { authorization: 'Bearer ' + await this.authToken(recipientDid, 'receive') },
|
|
414
|
+
});
|
|
415
|
+
await r.arrayBuffer();
|
|
416
|
+
if (!r.ok && r.status !== 404)
|
|
417
|
+
throw new Error('RELAY_REJECTED: ' + r.status);
|
|
418
|
+
}
|
|
419
|
+
async peek(recipientDid, filter) {
|
|
420
|
+
return this.receive(recipientDid, undefined, undefined, { ...filter, peek: true });
|
|
421
|
+
}
|
|
422
|
+
async pollInbox(recipientDid, filter) {
|
|
423
|
+
return this.receive(recipientDid, filter?.waitMs, filter?.signal, filter);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Persistent SSE transport with a bounded per-recipient inbox. */
|
|
427
|
+
export class SseRelayClient extends HttpRelayClient {
|
|
428
|
+
maxBufferBytes = 2 * 1024 * 1024;
|
|
429
|
+
streams = new Map();
|
|
430
|
+
async receive(recipientDid, waitMs = 30000, signal) {
|
|
431
|
+
signal?.throwIfAborted();
|
|
432
|
+
let state = this.streams.get(recipientDid);
|
|
433
|
+
if (!state) {
|
|
434
|
+
state = { controller: new AbortController(), queue: [], notify: new Set() };
|
|
435
|
+
this.streams.set(recipientDid, state);
|
|
436
|
+
const current = state;
|
|
437
|
+
current.pump = (async () => {
|
|
438
|
+
try {
|
|
439
|
+
for await (const e of this.stream(recipientDid, current.controller.signal)) {
|
|
440
|
+
if (current.queue.length >= 1000)
|
|
441
|
+
throw new Error('SSE_CAPACITY');
|
|
442
|
+
current.queue.push(e);
|
|
443
|
+
for (const notify of [...current.notify])
|
|
444
|
+
notify();
|
|
445
|
+
}
|
|
446
|
+
if (!current.controller.signal.aborted)
|
|
447
|
+
throw new Error('SSE_CLOSED');
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
current.error = error;
|
|
451
|
+
for (const notify of [...current.notify])
|
|
452
|
+
notify();
|
|
453
|
+
}
|
|
454
|
+
})();
|
|
455
|
+
}
|
|
456
|
+
if (state.queue.length)
|
|
457
|
+
return state.queue.shift();
|
|
458
|
+
if (state.error)
|
|
459
|
+
throw state.error;
|
|
460
|
+
const current = state;
|
|
461
|
+
await new Promise((resolve, reject) => {
|
|
462
|
+
const cleanup = () => {
|
|
463
|
+
clearTimeout(timer);
|
|
464
|
+
current.notify.delete(wake);
|
|
465
|
+
signal?.removeEventListener('abort', abort);
|
|
466
|
+
};
|
|
467
|
+
const wake = () => {
|
|
468
|
+
cleanup();
|
|
469
|
+
resolve();
|
|
470
|
+
}, abort = () => {
|
|
471
|
+
cleanup();
|
|
472
|
+
reject(new Error('SSE_ABORTED'));
|
|
473
|
+
};
|
|
474
|
+
const timer = setTimeout(wake, Math.max(0, Math.min(waitMs, 30000)));
|
|
475
|
+
current.notify.add(wake);
|
|
476
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
477
|
+
if (signal?.aborted)
|
|
478
|
+
abort();
|
|
479
|
+
});
|
|
480
|
+
if (current.error)
|
|
481
|
+
throw current.error;
|
|
482
|
+
return current.queue.shift();
|
|
483
|
+
}
|
|
484
|
+
async close() {
|
|
485
|
+
const states = [...this.streams.values()];
|
|
486
|
+
this.streams.clear();
|
|
487
|
+
for (const state of states)
|
|
488
|
+
state.controller.abort();
|
|
489
|
+
await Promise.all(states.map((s) => s.pump));
|
|
490
|
+
}
|
|
491
|
+
async *stream(recipientDid, signal) {
|
|
492
|
+
const r = await this.fetcher(this.url + '/stream?recipient=' + encodeURIComponent(recipientDid), {
|
|
493
|
+
signal,
|
|
494
|
+
headers: { authorization: 'Bearer ' + (await this.authToken(recipientDid, 'stream')) },
|
|
495
|
+
});
|
|
496
|
+
if (!r.ok || !r.body)
|
|
497
|
+
throw new Error('SSE_FAILED');
|
|
498
|
+
const reader = r.body.getReader(), decoder = new TextDecoder();
|
|
499
|
+
let buffer = '';
|
|
500
|
+
try {
|
|
501
|
+
while (true) {
|
|
502
|
+
const { value, done } = await reader.read();
|
|
503
|
+
if (done)
|
|
504
|
+
break;
|
|
505
|
+
buffer += decoder.decode(value, { stream: true });
|
|
506
|
+
if (Buffer.byteLength(buffer, 'utf8') > this.maxBufferBytes)
|
|
507
|
+
throw new Error('SSE_BUFFER_OVERFLOW');
|
|
508
|
+
let index;
|
|
509
|
+
while ((index = buffer.indexOf('\n\n')) >= 0) {
|
|
510
|
+
const frame = buffer.slice(0, index);
|
|
511
|
+
buffer = buffer.slice(index + 2);
|
|
512
|
+
for (const line of frame.split('\n'))
|
|
513
|
+
if (line.startsWith('data: ')) {
|
|
514
|
+
const e = JSON.parse(line.slice(6));
|
|
515
|
+
if (!verifyEnvelope(e, recipientDid))
|
|
516
|
+
throw new Error('RELAY_TAMPERING_DETECTED');
|
|
517
|
+
yield e;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
await reader.cancel();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function socketIsOpen(socket) {
|
|
528
|
+
return socket?.readyState === WebSocket.OPEN;
|
|
529
|
+
}
|
|
530
|
+
export class WebSocketRelayClient extends RelayCredentials {
|
|
531
|
+
url;
|
|
532
|
+
socket;
|
|
533
|
+
pending = false;
|
|
534
|
+
stopped = false;
|
|
535
|
+
opening;
|
|
536
|
+
security;
|
|
537
|
+
constructor(url, authOrOptions, options) {
|
|
538
|
+
const parsed = splitRelayArgs(undefined, authOrOptions, options);
|
|
539
|
+
super(parsed.auth, url);
|
|
540
|
+
this.url = url;
|
|
541
|
+
this.security = parsed.security;
|
|
542
|
+
}
|
|
543
|
+
requireSocket() {
|
|
544
|
+
if (!this.socket)
|
|
545
|
+
throw new Error('RELAY_CLOSED');
|
|
546
|
+
return this.socket;
|
|
547
|
+
}
|
|
548
|
+
async open() {
|
|
549
|
+
const resolved = await resolveAndValidateRelayEndpoint(this.url, this.security);
|
|
550
|
+
if (this.stopped)
|
|
551
|
+
return;
|
|
552
|
+
const hostname = resolved.url.hostname.replace(/^\[|\]$/g, '');
|
|
553
|
+
const secure = resolved.url.protocol === 'wss:';
|
|
554
|
+
const wsOptions = {
|
|
555
|
+
lookup: resolved.lookup,
|
|
556
|
+
...(secure ? { servername: hostname } : {}),
|
|
557
|
+
};
|
|
558
|
+
const socket = new WebSocket(this.url, wsOptions);
|
|
559
|
+
this.socket = socket;
|
|
560
|
+
if (this.stopped) {
|
|
561
|
+
socket.close();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (socket.readyState === WebSocket.OPEN)
|
|
565
|
+
return;
|
|
566
|
+
await new Promise((resolve, reject) => {
|
|
567
|
+
const fail = (error) => reject(error);
|
|
568
|
+
socket.once('open', () => {
|
|
569
|
+
socket.off('error', fail);
|
|
570
|
+
resolve();
|
|
571
|
+
});
|
|
572
|
+
socket.once('error', fail);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
async ready() {
|
|
576
|
+
if (this.stopped)
|
|
577
|
+
throw new Error('RELAY_CLOSED');
|
|
578
|
+
// Read readyState through a helper so the early check does not exclude OPEN later.
|
|
579
|
+
if (!socketIsOpen(this.socket)) {
|
|
580
|
+
this.opening ??= this.open();
|
|
581
|
+
await this.opening;
|
|
582
|
+
}
|
|
583
|
+
if (this.stopped || !socketIsOpen(this.socket))
|
|
584
|
+
throw new Error('RELAY_CLOSED');
|
|
585
|
+
}
|
|
586
|
+
async request(body) {
|
|
587
|
+
await this.ready();
|
|
588
|
+
if (this.pending)
|
|
589
|
+
throw new Error('CONCURRENT_RELAY_REQUEST');
|
|
590
|
+
this.pending = true;
|
|
591
|
+
try {
|
|
592
|
+
const socket = this.requireSocket();
|
|
593
|
+
return await new Promise((resolve, reject) => {
|
|
594
|
+
const timer = setTimeout(() => {
|
|
595
|
+
socket.removeListener('message', receive);
|
|
596
|
+
reject(new Error('RELAY_TIMEOUT'));
|
|
597
|
+
}, 5000);
|
|
598
|
+
const receive = (raw) => {
|
|
599
|
+
clearTimeout(timer);
|
|
600
|
+
try {
|
|
601
|
+
const r = JSON.parse(raw.toString());
|
|
602
|
+
r.error ? reject(new Error(r.error)) : resolve(r);
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
reject(e);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
socket.once('message', receive);
|
|
609
|
+
socket.send(JSON.stringify(body));
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
finally {
|
|
613
|
+
this.pending = false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async send(envelope) {
|
|
617
|
+
await this.request({ method: 'send', envelope });
|
|
618
|
+
}
|
|
619
|
+
async receive(recipientDid, _waitMs, _signal, filter) {
|
|
620
|
+
const r = await this.request({
|
|
621
|
+
method: 'receive',
|
|
622
|
+
recipientDid,
|
|
623
|
+
authToken: await this.authToken(recipientDid, 'receive'),
|
|
624
|
+
...(filter?.role ? { role: filter.role } : {}),
|
|
625
|
+
...(filter?.taskId ? { taskId: filter.taskId } : {}),
|
|
626
|
+
...(filter?.peek ? { peek: true } : {}),
|
|
627
|
+
});
|
|
628
|
+
if (r.envelope && !verifyEnvelope(r.envelope, recipientDid))
|
|
629
|
+
throw new Error('RELAY_TAMPERING_DETECTED');
|
|
630
|
+
return r.envelope ?? undefined;
|
|
631
|
+
}
|
|
632
|
+
async *subscribe(recipientDid, signal) {
|
|
633
|
+
signal?.throwIfAborted();
|
|
634
|
+
await this.ready();
|
|
635
|
+
const authToken = await this.authToken(recipientDid, 'subscribe');
|
|
636
|
+
if (this.pending)
|
|
637
|
+
throw new Error('CONCURRENT_RELAY_REQUEST');
|
|
638
|
+
this.pending = true;
|
|
639
|
+
const queue = [];
|
|
640
|
+
let failure, wake = () => { };
|
|
641
|
+
const receive = (raw) => {
|
|
642
|
+
try {
|
|
643
|
+
const r = JSON.parse(raw.toString());
|
|
644
|
+
if (r.error)
|
|
645
|
+
throw new Error(r.error);
|
|
646
|
+
if (!r.envelope || !verifyEnvelope(r.envelope, recipientDid))
|
|
647
|
+
throw new Error('RELAY_TAMPERING_DETECTED');
|
|
648
|
+
if (queue.length >= 1000)
|
|
649
|
+
throw new Error('RELAY_CAPACITY');
|
|
650
|
+
queue.push(r.envelope);
|
|
651
|
+
}
|
|
652
|
+
catch (e) {
|
|
653
|
+
failure = e;
|
|
654
|
+
}
|
|
655
|
+
wake();
|
|
656
|
+
};
|
|
657
|
+
const close = () => {
|
|
658
|
+
failure = new Error('RELAY_CLOSED');
|
|
659
|
+
wake();
|
|
660
|
+
}, error = (e) => {
|
|
661
|
+
failure = e;
|
|
662
|
+
wake();
|
|
663
|
+
};
|
|
664
|
+
const abort = () => wake();
|
|
665
|
+
const socket = this.requireSocket();
|
|
666
|
+
socket.on('message', receive);
|
|
667
|
+
socket.once('close', close);
|
|
668
|
+
socket.once('error', error);
|
|
669
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
670
|
+
try {
|
|
671
|
+
socket.send(JSON.stringify({ method: 'subscribe', recipientDid, authToken }));
|
|
672
|
+
while (!signal?.aborted) {
|
|
673
|
+
if (failure)
|
|
674
|
+
throw failure;
|
|
675
|
+
if (queue.length) {
|
|
676
|
+
yield queue.shift();
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
await new Promise((resolve) => {
|
|
680
|
+
wake = resolve;
|
|
681
|
+
if (queue.length || failure || signal?.aborted)
|
|
682
|
+
resolve();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
finally {
|
|
687
|
+
socket.off('message', receive);
|
|
688
|
+
socket.off('close', close);
|
|
689
|
+
socket.off('error', error);
|
|
690
|
+
signal?.removeEventListener('abort', abort);
|
|
691
|
+
if (socket.readyState === WebSocket.OPEN)
|
|
692
|
+
socket.send(JSON.stringify({ method: 'unsubscribe' }));
|
|
693
|
+
this.pending = false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async close() {
|
|
697
|
+
this.stopped = true;
|
|
698
|
+
const socket = this.socket;
|
|
699
|
+
if (!socket || socket.readyState === WebSocket.CLOSED)
|
|
700
|
+
return;
|
|
701
|
+
await new Promise((resolve) => {
|
|
702
|
+
socket.once('close', resolve);
|
|
703
|
+
socket.close();
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
//# sourceMappingURL=index.js.map
|