agents 0.23.0 → 0.24.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/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-B2XLNMxq.d.ts} +196 -189
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-Dj5Mzlw7.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
- package/dist/capability-B4WbF81e.js.map +1 -0
- package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-BUBa6Ake.d.ts} +40 -15
- package/dist/chat/index.d.ts +14 -11
- package/dist/chat/index.js +11 -7
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +3 -3
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client-BxdkCuVy.d.ts +411 -0
- package/dist/client-C9DAjZIY.js +518 -0
- package/dist/client-C9DAjZIY.js.map +1 -0
- package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
- package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
- package/dist/client.d.ts +27 -283
- package/dist/client.js +2 -274
- package/dist/current-agent-C4w86kcN.d.ts +93 -0
- package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-ei7M7lqB.d.ts} +13 -122
- package/dist/index-Dlgtd3Mj.d.ts +78 -0
- package/dist/index.d.ts +59 -59
- package/dist/index.js +2 -2
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +19 -17
- package/dist/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CMRGjZdw.js → lifecycle-Mm_jQh7r.js} +36 -21
- package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +2 -2
- package/dist/queue/index.d.ts +123 -0
- package/dist/queue/index.js +2 -0
- package/dist/queue-BC9F5cUo.js +406 -0
- package/dist/queue-BC9F5cUo.js.map +1 -0
- package/dist/react.d.ts +33 -15
- package/dist/react.js +31 -8
- package/dist/react.js.map +1 -1
- package/dist/{retries-D9Ds-1lz.d.ts → retries-D9B2UCq3.d.ts} +9 -3
- package/dist/retries.d.ts +5 -3
- package/dist/retries.js +9 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-BGq6M5Kd.d.ts} +6 -6
- package/dist/{scheduler-DD9NdYbF.js → scheduler-D_KHqxBa.js} +4 -11
- package/dist/scheduler-D_KHqxBa.js.map +1 -0
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/{src-DlSHshb2.js → src-BNU3ZiJM.js} +126 -393
- package/dist/src-BNU3ZiJM.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +137 -0
- package/dist/state/index.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-D6tJ0NN9.d.ts → streams-BcE8JP2X.d.ts} +2 -2
- package/dist/{streams-DZKgAj9b.js → streams-CCPRV6dt.js} +2 -2
- package/dist/{streams-DZKgAj9b.js.map → streams-CCPRV6dt.js.map} +1 -1
- package/dist/sub-routing.d.ts +12 -12
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-ylZgBjhj.js → tasks-D4nLqVSI.js} +3 -3
- package/dist/{tasks-ylZgBjhj.js.map → tasks-D4nLqVSI.js.map} +1 -1
- package/dist/{tasks-BRJ5zgya.d.ts → tasks-V2lCT8ZV.d.ts} +5 -5
- package/dist/transport-protocol-BxyngQ_R.js +25 -0
- package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
- package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
- package/dist/types-DCD6BNZH.d.ts +70 -0
- package/dist/voice/index.d.ts +2 -2
- package/dist/websockets/index.d.ts +178 -69
- package/dist/websockets/index.js +3 -2
- package/dist/websockets-D7IzWbZb.js +1005 -0
- package/dist/websockets-D7IzWbZb.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/client-sdk.md +30 -0
- package/docs/index.md +1 -1
- package/docs/lifecycle.md +97 -22
- package/docs/queue.md +135 -241
- package/docs/retries.md +1 -1
- package/docs/sub-agents.md +3 -3
- package/package.json +12 -2
- package/dist/capability-BjSKYpzg.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/lifecycle-CMRGjZdw.js.map +0 -1
- package/dist/scheduler-DD9NdYbF.js.map +0 -1
- package/dist/src-DlSHshb2.js.map +0 -1
- package/dist/websockets-DUfRHPRq.js +0 -502
- package/dist/websockets-DUfRHPRq.js.map +0 -1
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
import { camelCaseToKebabCase } from "./utils.js";
|
|
2
|
+
import "./types.js";
|
|
3
|
+
import { a as isCapnWebTransportUpgrade, n as CAPNWEB_TRANSPORT_SEND } from "./transport-protocol-BxyngQ_R.js";
|
|
4
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
5
|
+
import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
|
|
6
|
+
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
7
|
+
import "./lifecycle-Mm_jQh7r.js";
|
|
8
|
+
import { newWebSocketRpcSession } from "capnweb";
|
|
9
|
+
import { RpcTarget as RpcTarget$1 } from "cloudflare:workers";
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
11
|
+
//#region src/websockets/connection-flags.ts
|
|
12
|
+
/**
|
|
13
|
+
* Internal per-connection flags, stored inside the connection's own state
|
|
14
|
+
* under `_cf_`-prefixed keys so they ride the hibernation attachment (or a
|
|
15
|
+
* bridged connection's synced state) and survive a wake. The state wrapper
|
|
16
|
+
* installed by {@link ensureConnectionWrapped} hides them from
|
|
17
|
+
* `connection.state` and preserves them across user `setState` calls.
|
|
18
|
+
*
|
|
19
|
+
* Owned by the WebSockets capability; `Agent` and framework mixins reach it
|
|
20
|
+
* through the capability or, for their own keys, through
|
|
21
|
+
* {@link registerInternalConnectionKeys}.
|
|
22
|
+
*/
|
|
23
|
+
/** A readonly connection may not update host state. */
|
|
24
|
+
const CF_READONLY_KEY = "_cf_readonly";
|
|
25
|
+
/**
|
|
26
|
+
* A no-protocol connection receives no protocol text frames (identity,
|
|
27
|
+
* state sync, MCP servers) — neither on connect nor via broadcasts.
|
|
28
|
+
*/
|
|
29
|
+
const CF_NO_PROTOCOL_KEY = "_cf_no_protocol";
|
|
30
|
+
const internalKeys = /* @__PURE__ */ new Set([CF_READONLY_KEY, CF_NO_PROTOCOL_KEY]);
|
|
31
|
+
/**
|
|
32
|
+
* Register additional `_cf_`-prefixed keys a host or mixin stores in
|
|
33
|
+
* connection state, so they are hidden from `connection.state` and kept
|
|
34
|
+
* across user `setState` calls like the capability's own flags.
|
|
35
|
+
*/
|
|
36
|
+
function registerInternalConnectionKeys(...keys) {
|
|
37
|
+
for (const key of keys) internalKeys.add(key);
|
|
38
|
+
}
|
|
39
|
+
function rawHasInternalKeys(raw) {
|
|
40
|
+
for (const key of Object.keys(raw)) if (internalKeys.has(key)) return true;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/** A copy of `raw` without internal keys, or null when no user keys remain. */
|
|
44
|
+
function stripInternalKeys(raw) {
|
|
45
|
+
const result = {};
|
|
46
|
+
let hasUserKeys = false;
|
|
47
|
+
for (const key of Object.keys(raw)) if (!internalKeys.has(key)) {
|
|
48
|
+
result[key] = raw[key];
|
|
49
|
+
hasUserKeys = true;
|
|
50
|
+
}
|
|
51
|
+
return hasUserKeys ? result : null;
|
|
52
|
+
}
|
|
53
|
+
/** A copy containing only the internal keys present in `raw`. */
|
|
54
|
+
function extractInternalFlags(raw) {
|
|
55
|
+
const result = {};
|
|
56
|
+
for (const key of Object.keys(raw)) if (internalKeys.has(key)) result[key] = raw[key];
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Per-connection raw accessors, keyed by the live connection object. The
|
|
61
|
+
* map is in-memory: after hibernation it is empty, and every entry point
|
|
62
|
+
* re-wraps the connection on first use.
|
|
63
|
+
*/
|
|
64
|
+
const rawStateAccessors = /* @__PURE__ */ new WeakMap();
|
|
65
|
+
/**
|
|
66
|
+
* Wrap `connection.state` / `connection.setState` so internal flags are
|
|
67
|
+
* hidden from user code and preserved when user code sets state.
|
|
68
|
+
* Idempotent, and safe to call after hibernation.
|
|
69
|
+
*/
|
|
70
|
+
function ensureConnectionWrapped(connection) {
|
|
71
|
+
if (rawStateAccessors.has(connection)) return;
|
|
72
|
+
const descriptor = Object.getOwnPropertyDescriptor(connection, "state");
|
|
73
|
+
let getRaw;
|
|
74
|
+
let setRaw;
|
|
75
|
+
if (descriptor?.get) {
|
|
76
|
+
getRaw = descriptor.get.bind(connection);
|
|
77
|
+
setRaw = connection.setState.bind(connection);
|
|
78
|
+
} else {
|
|
79
|
+
let rawState = connection.state ?? null;
|
|
80
|
+
getRaw = () => rawState;
|
|
81
|
+
setRaw = (state) => {
|
|
82
|
+
rawState = state;
|
|
83
|
+
return rawState;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
rawStateAccessors.set(connection, {
|
|
87
|
+
getRaw,
|
|
88
|
+
setRaw
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(connection, "state", {
|
|
91
|
+
configurable: true,
|
|
92
|
+
enumerable: true,
|
|
93
|
+
get() {
|
|
94
|
+
const raw = getRaw();
|
|
95
|
+
if (raw != null && typeof raw === "object" && rawHasInternalKeys(raw)) return stripInternalKeys(raw);
|
|
96
|
+
return raw;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
Object.defineProperty(connection, "setState", {
|
|
100
|
+
configurable: true,
|
|
101
|
+
writable: true,
|
|
102
|
+
value(stateOrFn) {
|
|
103
|
+
const raw = getRaw();
|
|
104
|
+
const flags = raw != null && typeof raw === "object" ? extractInternalFlags(raw) : {};
|
|
105
|
+
const hasFlags = Object.keys(flags).length > 0;
|
|
106
|
+
let newUserState;
|
|
107
|
+
if (typeof stateOrFn === "function") newUserState = stateOrFn(hasFlags ? stripInternalKeys(raw) : raw);
|
|
108
|
+
else newUserState = stateOrFn;
|
|
109
|
+
if (hasFlags) {
|
|
110
|
+
if (newUserState != null && typeof newUserState === "object") return setRaw({
|
|
111
|
+
...newUserState,
|
|
112
|
+
...flags
|
|
113
|
+
});
|
|
114
|
+
return setRaw(flags);
|
|
115
|
+
}
|
|
116
|
+
return setRaw(newUserState);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** The raw connection state, internal flags included. */
|
|
121
|
+
function getConnectionRawState(connection) {
|
|
122
|
+
ensureConnectionWrapped(connection);
|
|
123
|
+
return rawStateAccessors.get(connection).getRaw();
|
|
124
|
+
}
|
|
125
|
+
/** Read an internal flag from the raw connection state. */
|
|
126
|
+
function getConnectionFlag(connection, key) {
|
|
127
|
+
ensureConnectionWrapped(connection);
|
|
128
|
+
return rawStateAccessors.get(connection).getRaw()?.[key];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Write an internal flag to the raw connection state. `undefined` removes
|
|
132
|
+
* the key rather than storing a dead value; the last key removed leaves
|
|
133
|
+
* `null`.
|
|
134
|
+
*/
|
|
135
|
+
function setConnectionFlag(connection, key, value) {
|
|
136
|
+
ensureConnectionWrapped(connection);
|
|
137
|
+
const accessors = rawStateAccessors.get(connection);
|
|
138
|
+
const raw = accessors.getRaw() ?? {};
|
|
139
|
+
if (value === void 0) {
|
|
140
|
+
const { [key]: _, ...rest } = raw;
|
|
141
|
+
accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
|
|
142
|
+
} else accessors.setRaw({
|
|
143
|
+
...raw,
|
|
144
|
+
[key]: value
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function isConnectionReadonly(connection) {
|
|
148
|
+
return !!getConnectionFlag(connection, CF_READONLY_KEY);
|
|
149
|
+
}
|
|
150
|
+
function setConnectionReadonly(connection, readonly) {
|
|
151
|
+
setConnectionFlag(connection, CF_READONLY_KEY, readonly ? true : void 0);
|
|
152
|
+
}
|
|
153
|
+
function isConnectionProtocolEnabled(connection) {
|
|
154
|
+
return !getConnectionFlag(connection, CF_NO_PROTOCOL_KEY);
|
|
155
|
+
}
|
|
156
|
+
function setConnectionProtocolEnabled(connection, enabled) {
|
|
157
|
+
setConnectionFlag(connection, CF_NO_PROTOCOL_KEY, enabled ? void 0 : true);
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/websockets/connection.ts
|
|
161
|
+
let _Symbol$iterator;
|
|
162
|
+
if (!("OPEN" in WebSocket)) {
|
|
163
|
+
const WebSocketStatus = {
|
|
164
|
+
CONNECTING: WebSocket.READY_STATE_CONNECTING,
|
|
165
|
+
OPEN: WebSocket.READY_STATE_OPEN,
|
|
166
|
+
CLOSING: WebSocket.READY_STATE_CLOSING,
|
|
167
|
+
CLOSED: WebSocket.READY_STATE_CLOSED
|
|
168
|
+
};
|
|
169
|
+
Object.assign(WebSocket, WebSocketStatus);
|
|
170
|
+
Object.assign(WebSocket.prototype, WebSocketStatus);
|
|
171
|
+
}
|
|
172
|
+
function tryGetManagedWebSocketMeta(ws) {
|
|
173
|
+
try {
|
|
174
|
+
const attachment = WebSocket.prototype.deserializeAttachment.call(ws);
|
|
175
|
+
if (!attachment || typeof attachment !== "object") return null;
|
|
176
|
+
if (!("__pk" in attachment)) return null;
|
|
177
|
+
const pk = attachment.__pk;
|
|
178
|
+
if (!pk || typeof pk !== "object") return null;
|
|
179
|
+
const { id, tags } = pk;
|
|
180
|
+
if (typeof id !== "string") return null;
|
|
181
|
+
const { uri } = pk;
|
|
182
|
+
return {
|
|
183
|
+
id,
|
|
184
|
+
tags: Array.isArray(tags) ? tags : [],
|
|
185
|
+
uri: typeof uri === "string" ? uri : void 0
|
|
186
|
+
};
|
|
187
|
+
} catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function isManagedWebSocket(ws) {
|
|
192
|
+
return tryGetManagedWebSocketMeta(ws) !== null;
|
|
193
|
+
}
|
|
194
|
+
var _cache = /* @__PURE__ */ new WeakMap();
|
|
195
|
+
/**
|
|
196
|
+
* Cache websocket attachments to avoid having to rehydrate them on every property access.
|
|
197
|
+
*/
|
|
198
|
+
var AttachmentCache = class {
|
|
199
|
+
constructor() {
|
|
200
|
+
_classPrivateFieldInitSpec(this, _cache, /* @__PURE__ */ new WeakMap());
|
|
201
|
+
}
|
|
202
|
+
get(ws) {
|
|
203
|
+
let attachment = _classPrivateFieldGet2(_cache, this).get(ws);
|
|
204
|
+
if (!attachment) {
|
|
205
|
+
attachment = WebSocket.prototype.deserializeAttachment.call(ws);
|
|
206
|
+
if (attachment !== void 0) _classPrivateFieldGet2(_cache, this).set(ws, attachment);
|
|
207
|
+
else throw new Error("Missing managed WebSocket lifecycle attachment");
|
|
208
|
+
}
|
|
209
|
+
return attachment;
|
|
210
|
+
}
|
|
211
|
+
set(ws, attachment) {
|
|
212
|
+
_classPrivateFieldGet2(_cache, this).set(ws, attachment);
|
|
213
|
+
WebSocket.prototype.serializeAttachment.call(ws, attachment);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const attachments = new AttachmentCache();
|
|
217
|
+
const connections = /* @__PURE__ */ new WeakSet();
|
|
218
|
+
const isWrapped = (ws) => {
|
|
219
|
+
return connections.has(ws);
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Wraps a WebSocket with Connection fields that rehydrate the
|
|
223
|
+
* socket attachments lazily only when requested.
|
|
224
|
+
*/
|
|
225
|
+
const createConnection = (ws) => {
|
|
226
|
+
if (isWrapped(ws)) return ws;
|
|
227
|
+
let initialState;
|
|
228
|
+
if ("state" in ws) {
|
|
229
|
+
initialState = ws.state;
|
|
230
|
+
delete ws.state;
|
|
231
|
+
}
|
|
232
|
+
const connection = Object.defineProperties(ws, {
|
|
233
|
+
id: {
|
|
234
|
+
configurable: true,
|
|
235
|
+
get() {
|
|
236
|
+
return attachments.get(ws).__pk.id;
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
uri: {
|
|
240
|
+
configurable: true,
|
|
241
|
+
get() {
|
|
242
|
+
return attachments.get(ws).__pk.uri ?? null;
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
tags: {
|
|
246
|
+
configurable: true,
|
|
247
|
+
get() {
|
|
248
|
+
return attachments.get(ws).__pk.tags ?? [];
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
state: {
|
|
252
|
+
configurable: true,
|
|
253
|
+
get() {
|
|
254
|
+
return attachments.get(ws).__user ?? null;
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
setState: {
|
|
258
|
+
configurable: true,
|
|
259
|
+
value: function setState(setState) {
|
|
260
|
+
const state = setState instanceof Function ? setState(this.state) : setState;
|
|
261
|
+
attachments.set(ws, {
|
|
262
|
+
...attachments.get(ws),
|
|
263
|
+
__user: state ?? null
|
|
264
|
+
});
|
|
265
|
+
return state;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
if (initialState) connection.setState(initialState);
|
|
270
|
+
connections.add(connection);
|
|
271
|
+
return connection;
|
|
272
|
+
};
|
|
273
|
+
var _sockets = /* @__PURE__ */ new WeakMap();
|
|
274
|
+
_Symbol$iterator = Symbol.iterator;
|
|
275
|
+
var ConnectionIterator = class {
|
|
276
|
+
constructor(sockets, tag) {
|
|
277
|
+
this.sockets = sockets;
|
|
278
|
+
this.tag = tag;
|
|
279
|
+
this.index = 0;
|
|
280
|
+
_classPrivateFieldInitSpec(this, _sockets, void 0);
|
|
281
|
+
}
|
|
282
|
+
[_Symbol$iterator]() {
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
next() {
|
|
286
|
+
const sockets = _classPrivateFieldGet2(_sockets, this) ?? _classPrivateFieldSet2(_sockets, this, this.sockets.get(this.tag));
|
|
287
|
+
let socket;
|
|
288
|
+
while (socket = sockets[this.index++]) if (socket.readyState === WebSocket.OPEN) {
|
|
289
|
+
if (!isManagedWebSocket(socket)) continue;
|
|
290
|
+
return {
|
|
291
|
+
done: false,
|
|
292
|
+
value: createConnection(socket)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
done: true,
|
|
297
|
+
value: void 0
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Deduplicate and validate connection tags.
|
|
303
|
+
* Returns the final tag array (always includes the connection id as the first tag).
|
|
304
|
+
*/
|
|
305
|
+
function prepareTags(connectionId, userTags) {
|
|
306
|
+
const tags = [connectionId, ...userTags.filter((t) => t !== connectionId)];
|
|
307
|
+
if (tags.length > 10) throw new Error("A connection can only have 10 tags, including the default id tag.");
|
|
308
|
+
for (const tag of tags) {
|
|
309
|
+
if (typeof tag !== "string") throw new Error(`A connection tag must be a string. Received: ${tag}`);
|
|
310
|
+
if (tag === "") throw new Error("A connection tag must not be an empty string.");
|
|
311
|
+
if (tag.length > 256) throw new Error("A connection tag must not exceed 256 characters");
|
|
312
|
+
}
|
|
313
|
+
return tags;
|
|
314
|
+
}
|
|
315
|
+
/** The platform-backed manager for hibernating WebSockets. */
|
|
316
|
+
var ConnectionManager = class {
|
|
317
|
+
constructor(controller) {
|
|
318
|
+
this.controller = controller;
|
|
319
|
+
}
|
|
320
|
+
getConnection(id) {
|
|
321
|
+
const matching = this.controller.get(id).filter((ws) => {
|
|
322
|
+
return tryGetManagedWebSocketMeta(ws)?.id === id;
|
|
323
|
+
});
|
|
324
|
+
if (matching.length === 0) return void 0;
|
|
325
|
+
if (matching.length === 1) return createConnection(matching[0]);
|
|
326
|
+
throw new Error(`More than one connection found for id ${id}. Did you mean to use getConnections(tag) instead?`);
|
|
327
|
+
}
|
|
328
|
+
getConnections(tag) {
|
|
329
|
+
return new ConnectionIterator(this.controller, tag);
|
|
330
|
+
}
|
|
331
|
+
accept(connection, options) {
|
|
332
|
+
const tags = prepareTags(connection.id, options.tags);
|
|
333
|
+
this.controller.accept(connection, tags);
|
|
334
|
+
attachments.set(connection, {
|
|
335
|
+
__pk: {
|
|
336
|
+
id: connection.id,
|
|
337
|
+
tags,
|
|
338
|
+
uri: connection.uri ?? void 0
|
|
339
|
+
},
|
|
340
|
+
__user: null
|
|
341
|
+
});
|
|
342
|
+
return createConnection(connection);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/websockets/callables-target.ts
|
|
347
|
+
/**
|
|
348
|
+
* The single exposure policy for callable names. `Object.prototype` and
|
|
349
|
+
* `RpcTarget.prototype` members are unreachable over Cap'n Web anyway
|
|
350
|
+
* and are silently excluded; `then` is rejected loudly because exposing
|
|
351
|
+
* it would make the remote stub thenable.
|
|
352
|
+
*/
|
|
353
|
+
function assertExposable(name) {
|
|
354
|
+
if (name === "then") throw new Error("A callables target cannot expose a method named \"then\" — it would make the remote stub thenable");
|
|
355
|
+
if (name === "__cf_agent_send") throw new Error(`A callables target cannot expose "${CAPNWEB_TRANSPORT_SEND}"; it is the transport's frame pipe`);
|
|
356
|
+
return !(name === "constructor" || name in Object.prototype || name in RpcTarget$1.prototype);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The exposable prototype methods of a callables target, each bound to
|
|
360
|
+
* invoke on the real instance (so private fields and `this` behave).
|
|
361
|
+
*
|
|
362
|
+
* Cap'n Web resolves methods on the prototype chain and rejects own
|
|
363
|
+
* instance properties, so only prototype methods participate. The
|
|
364
|
+
* nearest declaration wins for overridden names.
|
|
365
|
+
*
|
|
366
|
+
* @param target - The callables target to enumerate.
|
|
367
|
+
* @returns Method names mapped to invokers on the target.
|
|
368
|
+
*/
|
|
369
|
+
function exposableMethods(target) {
|
|
370
|
+
const methods = /* @__PURE__ */ new Map();
|
|
371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
372
|
+
let prototype = Object.getPrototypeOf(target);
|
|
373
|
+
while (prototype && prototype !== RpcTarget$1.prototype && prototype !== Object.prototype) {
|
|
374
|
+
for (const name of Object.getOwnPropertyNames(prototype)) {
|
|
375
|
+
if (seen.has(name)) continue;
|
|
376
|
+
seen.add(name);
|
|
377
|
+
if (!assertExposable(name)) continue;
|
|
378
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
379
|
+
if (!descriptor || typeof descriptor.value !== "function") continue;
|
|
380
|
+
const method = descriptor.value;
|
|
381
|
+
methods.set(name, (...args) => Reflect.apply(method, target, args));
|
|
382
|
+
}
|
|
383
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
384
|
+
}
|
|
385
|
+
return methods;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Build a Cap'n Web session root exposing exactly the given methods.
|
|
389
|
+
*
|
|
390
|
+
* Cap'n Web resolves methods on the prototype chain, rejects own
|
|
391
|
+
* instance properties, and breaks on Proxy-wrapped roots — so the root
|
|
392
|
+
* is a private `RpcTarget` subclass whose prototype carries the methods
|
|
393
|
+
* and nothing else.
|
|
394
|
+
*/
|
|
395
|
+
function buildRoot(methods) {
|
|
396
|
+
class Root extends RpcTarget$1 {}
|
|
397
|
+
for (const [name, invoke] of methods) Object.defineProperty(Root.prototype, name, {
|
|
398
|
+
value: invoke,
|
|
399
|
+
writable: true,
|
|
400
|
+
configurable: true,
|
|
401
|
+
enumerable: false
|
|
402
|
+
});
|
|
403
|
+
return new Root();
|
|
404
|
+
}
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/websockets/transport.ts
|
|
407
|
+
/**
|
|
408
|
+
* A non-hibernating connection with the public `Connection` contract,
|
|
409
|
+
* backed by a Cap'n Web session instead of a hibernating socket. It lives
|
|
410
|
+
* only in memory and disappears with the isolate.
|
|
411
|
+
*/
|
|
412
|
+
var CapnWebConnection = class extends EventTarget {
|
|
413
|
+
constructor(id, uri, pipe) {
|
|
414
|
+
super();
|
|
415
|
+
this.id = id;
|
|
416
|
+
this.uri = uri;
|
|
417
|
+
this.pipe = pipe;
|
|
418
|
+
this.CONNECTING = WebSocket.CONNECTING;
|
|
419
|
+
this.OPEN = WebSocket.OPEN;
|
|
420
|
+
this.CLOSING = WebSocket.CLOSING;
|
|
421
|
+
this.CLOSED = WebSocket.CLOSED;
|
|
422
|
+
this.readyState = WebSocket.OPEN;
|
|
423
|
+
this.state = null;
|
|
424
|
+
this.tags = [];
|
|
425
|
+
}
|
|
426
|
+
send(message) {
|
|
427
|
+
if (this.readyState !== WebSocket.OPEN) throw new TypeError("WebSocket send() after close");
|
|
428
|
+
this.pipe.send(message);
|
|
429
|
+
}
|
|
430
|
+
close(code, reason) {
|
|
431
|
+
if (this.readyState >= WebSocket.CLOSING) return;
|
|
432
|
+
this.pipe.close(code, reason);
|
|
433
|
+
this.readyState = WebSocket.CLOSING;
|
|
434
|
+
}
|
|
435
|
+
setState(next) {
|
|
436
|
+
const state = typeof next === "function" ? next(this.state) : next;
|
|
437
|
+
this.state = state;
|
|
438
|
+
return state;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
/**
|
|
442
|
+
* Accept a Cap'n Web transport upgrade.
|
|
443
|
+
*
|
|
444
|
+
* The session root carries the frame pipe plus the host's native
|
|
445
|
+
* callables. The client's protocol frames arrive through the pipe and are
|
|
446
|
+
* handed to `onMessage`; the host's frames go out through the client's
|
|
447
|
+
* `message` callback; `useAgent().stub` calls hit the callables directly.
|
|
448
|
+
* The socket is a plain in-memory `WebSocketPair`, so it pins the Durable
|
|
449
|
+
* Object and does not survive hibernation.
|
|
450
|
+
*/
|
|
451
|
+
async function openCapnWebSession(options) {
|
|
452
|
+
const { request, connectionId } = options;
|
|
453
|
+
const pair = new WebSocketPair();
|
|
454
|
+
const server = pair[0];
|
|
455
|
+
server.accept();
|
|
456
|
+
let client;
|
|
457
|
+
let closed = false;
|
|
458
|
+
let close = {
|
|
459
|
+
code: 1e3,
|
|
460
|
+
reason: "",
|
|
461
|
+
wasClean: true
|
|
462
|
+
};
|
|
463
|
+
const connection = new CapnWebConnection(connectionId, request.url, {
|
|
464
|
+
send: (message) => {
|
|
465
|
+
client?.message(message).catch((error) => {
|
|
466
|
+
if (!closed) console.error("Cap'n Web frame delivery failed:", error);
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
close: (code = 1e3, reason = "") => {
|
|
470
|
+
server.close(code, reason);
|
|
471
|
+
close = {
|
|
472
|
+
code,
|
|
473
|
+
reason,
|
|
474
|
+
wasClean: true
|
|
475
|
+
};
|
|
476
|
+
finish();
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
const session = {
|
|
480
|
+
connection,
|
|
481
|
+
dispose: () => connection.close(1001, "Session replaced")
|
|
482
|
+
};
|
|
483
|
+
const finish = async () => {
|
|
484
|
+
if (closed) return;
|
|
485
|
+
closed = true;
|
|
486
|
+
connection.readyState = WebSocket.CLOSED;
|
|
487
|
+
try {
|
|
488
|
+
client?.[Symbol.dispose]();
|
|
489
|
+
} catch {}
|
|
490
|
+
options.onDispose(session);
|
|
491
|
+
await options.onClose(connection, close.code, close.reason, close.wasClean);
|
|
492
|
+
};
|
|
493
|
+
const rootMethods = new Map(options.callables(connection));
|
|
494
|
+
rootMethods.set(CAPNWEB_TRANSPORT_SEND, (message) => options.onMessage(connection, message));
|
|
495
|
+
client = newWebSocketRpcSession(server, buildRoot(rootMethods));
|
|
496
|
+
server.addEventListener("close", (event) => {
|
|
497
|
+
close = {
|
|
498
|
+
code: event.code,
|
|
499
|
+
reason: event.reason,
|
|
500
|
+
wasClean: event.wasClean
|
|
501
|
+
};
|
|
502
|
+
finish();
|
|
503
|
+
}, { once: true });
|
|
504
|
+
server.addEventListener("error", (event) => {
|
|
505
|
+
if (closed) return;
|
|
506
|
+
close = {
|
|
507
|
+
...close,
|
|
508
|
+
wasClean: false
|
|
509
|
+
};
|
|
510
|
+
const error = event instanceof ErrorEvent ? event.error ?? new Error(event.message) : /* @__PURE__ */ new Error("Cap'n Web transport socket error");
|
|
511
|
+
options.onError(connection, error).catch((handlerError) => {
|
|
512
|
+
console.error("Cap'n Web onError handler failed:", handlerError);
|
|
513
|
+
}).finally(finish);
|
|
514
|
+
}, { once: true });
|
|
515
|
+
const ctx = { request };
|
|
516
|
+
connection.tags = prepareTags(connectionId, await options.tags(connection, ctx));
|
|
517
|
+
options.onOpen(session);
|
|
518
|
+
try {
|
|
519
|
+
await options.onConnect(connection, ctx);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
connection.close(1011, "onConnect failed");
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
response: new Response(null, {
|
|
526
|
+
status: 101,
|
|
527
|
+
webSocket: pair[1]
|
|
528
|
+
}),
|
|
529
|
+
session
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/websockets/websockets.ts
|
|
534
|
+
/**
|
|
535
|
+
* Reserved close codes the runtime synthesizes when there was no real
|
|
536
|
+
* Close frame from the peer (1005 NoStatusReceived, 1006 AbnormalClosure,
|
|
537
|
+
* 1015 TLSHandshake). They cannot appear in an outgoing Close frame, and
|
|
538
|
+
* there is no peer left to receive a reciprocation.
|
|
539
|
+
*/
|
|
540
|
+
function isReservedCloseCode(code) {
|
|
541
|
+
return code === 1005 || code === 1006 || code === 1015;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Reciprocate a peer-initiated Close frame to complete the handshake, as
|
|
545
|
+
* the Hibernation API contract requires. Best-effort: swallows errors
|
|
546
|
+
* from already-closed sockets or invalid codes/reasons, and skips
|
|
547
|
+
* reciprocation entirely for reserved codes (dead transport).
|
|
548
|
+
*/
|
|
549
|
+
function reciprocateClose(ws, code, reason) {
|
|
550
|
+
if (isReservedCloseCode(code)) return;
|
|
551
|
+
try {
|
|
552
|
+
ws.close(code, reason);
|
|
553
|
+
} catch {}
|
|
554
|
+
}
|
|
555
|
+
function isStateFrame(value) {
|
|
556
|
+
if (typeof value !== "object" || value === null) return false;
|
|
557
|
+
const frame = value;
|
|
558
|
+
return frame.type === "cf_agent_state" && "state" in frame;
|
|
559
|
+
}
|
|
560
|
+
/** Parse a text frame; anything that is not JSON is `undefined`. */
|
|
561
|
+
function parseJson(text) {
|
|
562
|
+
try {
|
|
563
|
+
return JSON.parse(text);
|
|
564
|
+
} catch {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function isRpcRequest(value) {
|
|
569
|
+
if (typeof value !== "object" || value === null) return false;
|
|
570
|
+
const frame = value;
|
|
571
|
+
return frame.type === "rpc" && typeof frame.id === "string" && typeof frame.method === "string" && Array.isArray(frame.args);
|
|
572
|
+
}
|
|
573
|
+
var _handlers = /* @__PURE__ */ new WeakMap();
|
|
574
|
+
var _getConnectionTags = /* @__PURE__ */ new WeakMap();
|
|
575
|
+
var _protocol = /* @__PURE__ */ new WeakMap();
|
|
576
|
+
var _readonly = /* @__PURE__ */ new WeakMap();
|
|
577
|
+
var _state = /* @__PURE__ */ new WeakMap();
|
|
578
|
+
var _connecting = /* @__PURE__ */ new WeakMap();
|
|
579
|
+
var _callables = /* @__PURE__ */ new WeakMap();
|
|
580
|
+
var _sessions = /* @__PURE__ */ new WeakMap();
|
|
581
|
+
var _manager = /* @__PURE__ */ new WeakMap();
|
|
582
|
+
var _WebSockets_brand = /* @__PURE__ */ new WeakSet();
|
|
583
|
+
/**
|
|
584
|
+
* Opt-in WebSocket support for Lifecycle Objects.
|
|
585
|
+
*
|
|
586
|
+
* Lifecycle itself does not model WebSockets — hosts that want them
|
|
587
|
+
* install this capability, which owns the connection subsystem end to
|
|
588
|
+
* end: it claims upgrades, dispatches `onConnect`/`onMessage`/`onClose`
|
|
589
|
+
* inside the host invocation boundary, reciprocates close handshakes,
|
|
590
|
+
* and answers `getConnections()`/`getConnection()`.
|
|
591
|
+
*
|
|
592
|
+
* ```ts
|
|
593
|
+
* class Room extends DurableObject<Env> {
|
|
594
|
+
* readonly webSockets = new WebSockets({
|
|
595
|
+
* handlers: {
|
|
596
|
+
* onConnect: (connection) => connection.send("welcome"),
|
|
597
|
+
* onMessage: (connection, message) => { ... }
|
|
598
|
+
* },
|
|
599
|
+
* callables: new RoomCallables()
|
|
600
|
+
* });
|
|
601
|
+
* readonly lifecycle = Lifecycle.install(this).use(this.webSockets);
|
|
602
|
+
* }
|
|
603
|
+
* ```
|
|
604
|
+
*
|
|
605
|
+
* Connections arrive on one of two wires, chosen by the client:
|
|
606
|
+
*
|
|
607
|
+
* - **cf-websocket** (default): accepted with the Hibernation API. Idle
|
|
608
|
+
* clients stay connected while the Durable Object leaves memory.
|
|
609
|
+
* - **capnweb** (`?__agents_transport=capnweb`): protocol frames through
|
|
610
|
+
* one pipe method on a Cap'n Web session whose root also carries the
|
|
611
|
+
* host's callables natively. Non-hibernating — the object stays pinned
|
|
612
|
+
* while the connection is open.
|
|
613
|
+
*
|
|
614
|
+
* Both wires dispatch the same handlers and appear in `getConnections()`.
|
|
615
|
+
* On both, the capability speaks the Agent protocol a plain host needs
|
|
616
|
+
* for `useAgent` and `AgentClient`: it sends the identity frame on
|
|
617
|
+
* connect, and it serves `callables` — as `rpc` JSON frames on the
|
|
618
|
+
* WebSocket wire, and natively on the Cap'n Web session root, where an
|
|
619
|
+
* `RpcTarget` result becomes a live stub and calls pipeline. `call()`
|
|
620
|
+
* and `stub` work against a plain Durable Object exactly as against an
|
|
621
|
+
* `Agent`. Pass a `State` capability as `state` and the hook's
|
|
622
|
+
* `state`/`setState` work too: the current value is pushed on connect
|
|
623
|
+
* and client updates are validated and applied; the state owner
|
|
624
|
+
* broadcasts changes with `broadcastState()`. Per-connection readonly
|
|
625
|
+
* and no-protocol flags live here as well, for every host.
|
|
626
|
+
*
|
|
627
|
+
* @experimental The API surface may change before stabilizing.
|
|
628
|
+
*/
|
|
629
|
+
var WebSockets = class extends LifecycleCapability {
|
|
630
|
+
constructor(options = {}) {
|
|
631
|
+
super("websockets");
|
|
632
|
+
_classPrivateMethodInitSpec(this, _WebSockets_brand);
|
|
633
|
+
this.claims = "catch-all";
|
|
634
|
+
_classPrivateFieldInitSpec(this, _handlers, void 0);
|
|
635
|
+
_classPrivateFieldInitSpec(this, _getConnectionTags, void 0);
|
|
636
|
+
_classPrivateFieldInitSpec(this, _protocol, void 0);
|
|
637
|
+
_classPrivateFieldInitSpec(this, _readonly, void 0);
|
|
638
|
+
_classPrivateFieldInitSpec(this, _state, void 0);
|
|
639
|
+
_classPrivateFieldInitSpec(this, _connecting, /* @__PURE__ */ new Set());
|
|
640
|
+
_classPrivateFieldInitSpec(this, _callables, void 0);
|
|
641
|
+
_classPrivateFieldInitSpec(this, _sessions, /* @__PURE__ */ new Map());
|
|
642
|
+
_classPrivateFieldInitSpec(this, _manager, void 0);
|
|
643
|
+
_classPrivateFieldSet2(_handlers, this, options.handlers);
|
|
644
|
+
_classPrivateFieldSet2(_getConnectionTags, this, options.getConnectionTags);
|
|
645
|
+
_classPrivateFieldSet2(_protocol, this, options.protocol ?? true);
|
|
646
|
+
_classPrivateFieldSet2(_readonly, this, options.readonly);
|
|
647
|
+
_classPrivateFieldSet2(_state, this, options.state);
|
|
648
|
+
_classPrivateFieldSet2(_callables, this, options.callables ? exposableMethods(options.callables) : /* @__PURE__ */ new Map());
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Claim every upgrade, never declining: a Cap'n Web transport upgrade
|
|
652
|
+
* becomes a session, everything else a tracked hibernating connection,
|
|
653
|
+
* whether or not handlers or callables are configured. Handlers only add
|
|
654
|
+
* behavior on connect, message, close, and error.
|
|
655
|
+
*/
|
|
656
|
+
onWebSocketUpgrade({ request }) {
|
|
657
|
+
return isCapnWebTransportUpgrade(request) ? _assertClassBrand(_WebSockets_brand, this, _acceptCapnWebSession).call(this, request) : _assertClassBrand(_WebSockets_brand, this, _acceptConnection).call(this, request);
|
|
658
|
+
}
|
|
659
|
+
/** Dispatch a platform message wake for a capability-owned socket. */
|
|
660
|
+
async onWebSocketMessage(ws, message) {
|
|
661
|
+
if (!isManagedWebSocket(ws)) return false;
|
|
662
|
+
await _assertClassBrand(_WebSockets_brand, this, _message).call(this, createConnection(ws), message);
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
/** Dispatch and reciprocate a close wake for an owned socket. */
|
|
666
|
+
async onWebSocketClose(ws, code, reason, wasClean) {
|
|
667
|
+
if (!isManagedWebSocket(ws)) return false;
|
|
668
|
+
try {
|
|
669
|
+
await _assertClassBrand(_WebSockets_brand, this, _close).call(this, createConnection(ws), code, reason, wasClean);
|
|
670
|
+
} finally {
|
|
671
|
+
reciprocateClose(ws, code, reason);
|
|
672
|
+
}
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
/** Dispatch an error wake for an owned socket. */
|
|
676
|
+
async onWebSocketError(ws, error) {
|
|
677
|
+
if (!isManagedWebSocket(ws)) return false;
|
|
678
|
+
await _assertClassBrand(_WebSockets_brand, this, _error).call(this, createConnection(ws), error);
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Close every owned connection during explicit host destruction. The
|
|
683
|
+
* capability owns its sockets' lifetimes, so it also owns tearing
|
|
684
|
+
* them down.
|
|
685
|
+
*/
|
|
686
|
+
dispose() {
|
|
687
|
+
for (const connection of this.getConnections()) try {
|
|
688
|
+
connection.close(1001, "Durable Object destroyed");
|
|
689
|
+
} catch {}
|
|
690
|
+
}
|
|
691
|
+
/** Open connections on either wire, optionally by tag. */
|
|
692
|
+
*getConnections(tag) {
|
|
693
|
+
for (const { connection } of _classPrivateFieldGet2(_sessions, this).values()) {
|
|
694
|
+
if (connection.readyState !== WebSocket.OPEN) continue;
|
|
695
|
+
if (!tag || connection.tags.includes(tag)) yield connection;
|
|
696
|
+
}
|
|
697
|
+
yield* _get_connectionManager.call(_assertClassBrand(_WebSockets_brand, this)).getConnections(tag);
|
|
698
|
+
}
|
|
699
|
+
/** One connection on either wire, by id. */
|
|
700
|
+
getConnection(id) {
|
|
701
|
+
const session = _classPrivateFieldGet2(_sessions, this).get(id)?.connection;
|
|
702
|
+
if (session && session.readyState === WebSocket.OPEN) return session;
|
|
703
|
+
return _get_connectionManager.call(_assertClassBrand(_WebSockets_brand, this)).getConnection(id);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Send the identity frame, unless the connection is no-protocol. The
|
|
707
|
+
* defaults are the Durable Object's routed name and host class; a host
|
|
708
|
+
* whose public identity differs — an `Agent` facet, whose routed name
|
|
709
|
+
* is an internal encoding of its logical name — passes its own.
|
|
710
|
+
*/
|
|
711
|
+
sendIdentity(connection, identity = {
|
|
712
|
+
name: this.lifecycle.name,
|
|
713
|
+
agent: camelCaseToKebabCase(this.lifecycle.className)
|
|
714
|
+
}) {
|
|
715
|
+
if (!isConnectionProtocolEnabled(connection)) return;
|
|
716
|
+
_assertClassBrand(_WebSockets_brand, this, _sendFrame).call(this, connection, {
|
|
717
|
+
type: "cf_agent_identity",
|
|
718
|
+
name: identity.name,
|
|
719
|
+
agent: identity.agent
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Send the current state to one connection, unless nothing is stored
|
|
724
|
+
* or the connection is no-protocol. Reading the state may seed the
|
|
725
|
+
* initial value; see `#connecting`.
|
|
726
|
+
*/
|
|
727
|
+
sendState(connection) {
|
|
728
|
+
if (!_classPrivateFieldGet2(_state, this) || !isConnectionProtocolEnabled(connection)) return;
|
|
729
|
+
const current = _classPrivateFieldGet2(_state, this).get();
|
|
730
|
+
if (current === void 0) return;
|
|
731
|
+
_assertClassBrand(_WebSockets_brand, this, _sendFrame).call(this, connection, {
|
|
732
|
+
type: "cf_agent_state",
|
|
733
|
+
state: current
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Apply a parsed `cf_agent_state` frame from a client. A readonly
|
|
738
|
+
* connection is refused; a change the host's validator rejects is
|
|
739
|
+
* logged in full server-side and answered with a generic
|
|
740
|
+
* `cf_agent_state_error`. Broadcasting the accepted change is the state
|
|
741
|
+
* owner's call, through its `onChanged` hook. Callers that drive the
|
|
742
|
+
* protocol themselves call this inside their own invocation context;
|
|
743
|
+
* the capability's automatic path does so via `runInHostContext`.
|
|
744
|
+
*
|
|
745
|
+
* @returns Whether the frame was a state frame (handled either way).
|
|
746
|
+
*/
|
|
747
|
+
applyStateFrame(connection, frame) {
|
|
748
|
+
if (!_classPrivateFieldGet2(_state, this) || !isStateFrame(frame)) return false;
|
|
749
|
+
if (isConnectionReadonly(connection)) {
|
|
750
|
+
_assertClassBrand(_WebSockets_brand, this, _sendFrame).call(this, connection, {
|
|
751
|
+
type: "cf_agent_state_error",
|
|
752
|
+
error: "Connection is readonly"
|
|
753
|
+
});
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
_classPrivateFieldGet2(_state, this).set(frame.state, connection);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
console.error("[WebSockets] State update rejected:", error);
|
|
760
|
+
_assertClassBrand(_WebSockets_brand, this, _sendFrame).call(this, connection, {
|
|
761
|
+
type: "cf_agent_state_error",
|
|
762
|
+
error: "State update rejected"
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Push the current state to every protocol-enabled connection except
|
|
769
|
+
* the one a change came from, which already has the value it sent.
|
|
770
|
+
* Wire a `State`'s `onChanged` to this.
|
|
771
|
+
*/
|
|
772
|
+
broadcastState(except) {
|
|
773
|
+
if (!_classPrivateFieldGet2(_state, this)) return;
|
|
774
|
+
const current = _classPrivateFieldGet2(_state, this).get();
|
|
775
|
+
if (current === void 0) return;
|
|
776
|
+
const frame = {
|
|
777
|
+
type: "cf_agent_state",
|
|
778
|
+
state: current
|
|
779
|
+
};
|
|
780
|
+
for (const connection of this.getConnections()) {
|
|
781
|
+
if (connection === except) continue;
|
|
782
|
+
if (_classPrivateFieldGet2(_connecting, this).has(connection)) continue;
|
|
783
|
+
if (!isConnectionProtocolEnabled(connection)) continue;
|
|
784
|
+
_assertClassBrand(_WebSockets_brand, this, _sendFrame).call(this, connection, frame);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/** Whether the connection may update host state over the wire. */
|
|
788
|
+
isReadonly(connection) {
|
|
789
|
+
return isConnectionReadonly(connection);
|
|
790
|
+
}
|
|
791
|
+
/** Mark a connection readonly, or writable again. */
|
|
792
|
+
setReadonly(connection, readonly = true) {
|
|
793
|
+
setConnectionReadonly(connection, readonly);
|
|
794
|
+
}
|
|
795
|
+
/** Whether protocol text frames reach the connection. */
|
|
796
|
+
isProtocolEnabled(connection) {
|
|
797
|
+
return isConnectionProtocolEnabled(connection);
|
|
798
|
+
}
|
|
799
|
+
/** Enable or suppress protocol text frames for a connection. */
|
|
800
|
+
setProtocolEnabled(connection, enabled) {
|
|
801
|
+
setConnectionProtocolEnabled(connection, enabled);
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
function _get_connectionManager() {
|
|
805
|
+
_classPrivateFieldGet2(_manager, this) ?? _classPrivateFieldSet2(_manager, this, new ConnectionManager(this.lifecycle.sockets));
|
|
806
|
+
return _classPrivateFieldGet2(_manager, this);
|
|
807
|
+
}
|
|
808
|
+
async function _connect(connection, ctx) {
|
|
809
|
+
ensureConnectionWrapped(connection);
|
|
810
|
+
if (_classPrivateFieldGet2(_readonly, this)?.call(this, connection, ctx)) setConnectionReadonly(connection, true);
|
|
811
|
+
if (_classPrivateFieldGet2(_protocol, this) !== false) if (typeof _classPrivateFieldGet2(_protocol, this) === "function" ? _classPrivateFieldGet2(_protocol, this).call(this, connection, ctx) : true) {
|
|
812
|
+
_classPrivateFieldGet2(_connecting, this).add(connection);
|
|
813
|
+
try {
|
|
814
|
+
this.sendIdentity(connection);
|
|
815
|
+
this.sendState(connection);
|
|
816
|
+
} finally {
|
|
817
|
+
_classPrivateFieldGet2(_connecting, this).delete(connection);
|
|
818
|
+
}
|
|
819
|
+
} else setConnectionProtocolEnabled(connection, false);
|
|
820
|
+
await this.lifecycle.runInHostContext(() => _classPrivateFieldGet2(_handlers, this)?.onConnect?.(connection, ctx), {
|
|
821
|
+
connection,
|
|
822
|
+
request: ctx.request
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
async function _message(connection, message) {
|
|
826
|
+
if (_classPrivateFieldGet2(_state, this) && _classPrivateFieldGet2(_protocol, this) !== false && typeof message === "string") {
|
|
827
|
+
const frame = parseJson(message);
|
|
828
|
+
if (isStateFrame(frame)) {
|
|
829
|
+
await this.lifecycle.runInHostContext(() => this.applyStateFrame(connection, frame), { connection });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (_classPrivateFieldGet2(_callables, this).size > 0 && await _assertClassBrand(_WebSockets_brand, this, _answerRpc).call(this, connection, message)) return;
|
|
834
|
+
await this.lifecycle.runInHostContext(() => _classPrivateFieldGet2(_handlers, this)?.onMessage?.(connection, message), { connection });
|
|
835
|
+
}
|
|
836
|
+
function _close(connection, code, reason, wasClean) {
|
|
837
|
+
return this.lifecycle.runInHostContext(() => _classPrivateFieldGet2(_handlers, this)?.onClose?.(connection, code, reason, wasClean), { connection });
|
|
838
|
+
}
|
|
839
|
+
function _error(connection, error) {
|
|
840
|
+
return this.lifecycle.runInHostContext(() => _classPrivateFieldGet2(_handlers, this)?.onError?.(connection, error), { connection });
|
|
841
|
+
}
|
|
842
|
+
async function _acceptConnection(request) {
|
|
843
|
+
const { 0: clientWebSocket, 1: serverWebSocket } = new WebSocketPair();
|
|
844
|
+
const connectionId = new URL(request.url).searchParams.get("_pk") || nanoid();
|
|
845
|
+
let connection = Object.assign(serverWebSocket, {
|
|
846
|
+
id: connectionId,
|
|
847
|
+
uri: request.url,
|
|
848
|
+
tags: [],
|
|
849
|
+
state: null,
|
|
850
|
+
setState(setState) {
|
|
851
|
+
const state = setState instanceof Function ? setState(this.state) : setState;
|
|
852
|
+
this.state = state;
|
|
853
|
+
return this.state;
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
const ctx = { request };
|
|
857
|
+
const tags = _classPrivateFieldGet2(_getConnectionTags, this) ? await _classPrivateFieldGet2(_getConnectionTags, this).call(this, connection, ctx) : [];
|
|
858
|
+
connection = _get_connectionManager.call(_assertClassBrand(_WebSockets_brand, this)).accept(connection, { tags });
|
|
859
|
+
await _assertClassBrand(_WebSockets_brand, this, _connect).call(this, connection, ctx);
|
|
860
|
+
return new Response(null, {
|
|
861
|
+
status: 101,
|
|
862
|
+
webSocket: clientWebSocket
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
async function _acceptCapnWebSession(request) {
|
|
866
|
+
const connectionId = new URL(request.url).searchParams.get("_pk") || nanoid();
|
|
867
|
+
_classPrivateFieldGet2(_sessions, this).get(connectionId)?.dispose();
|
|
868
|
+
const { response } = await openCapnWebSession({
|
|
869
|
+
request,
|
|
870
|
+
connectionId,
|
|
871
|
+
tags: (connection, ctx) => _classPrivateFieldGet2(_getConnectionTags, this)?.call(this, connection, ctx) ?? [],
|
|
872
|
+
onConnect: (connection, ctx) => _assertClassBrand(_WebSockets_brand, this, _connect).call(this, connection, ctx),
|
|
873
|
+
onMessage: (connection, message) => _assertClassBrand(_WebSockets_brand, this, _message).call(this, connection, message),
|
|
874
|
+
onClose: (connection, code, reason, wasClean) => _assertClassBrand(_WebSockets_brand, this, _close).call(this, connection, code, reason, wasClean).then(() => void 0),
|
|
875
|
+
onError: (connection, error) => _assertClassBrand(_WebSockets_brand, this, _error).call(this, connection, error).then(() => void 0),
|
|
876
|
+
callables: (connection) => _assertClassBrand(_WebSockets_brand, this, _nativeCallables).call(this, connection),
|
|
877
|
+
onOpen: (session) => _classPrivateFieldGet2(_sessions, this).set(connectionId, session),
|
|
878
|
+
onDispose: (ended) => {
|
|
879
|
+
if (_classPrivateFieldGet2(_sessions, this).get(connectionId) === ended) _classPrivateFieldGet2(_sessions, this).delete(connectionId);
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
return response;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Send one protocol frame. Serialization is the caller's contract — every
|
|
886
|
+
* frame built here is plain JSON — while a send failure is tolerated: the
|
|
887
|
+
* peer may have disconnected between the wake and the send.
|
|
888
|
+
*/
|
|
889
|
+
function _sendFrame(connection, frame) {
|
|
890
|
+
_assertClassBrand(_WebSockets_brand, this, _send).call(this, connection, JSON.stringify(frame));
|
|
891
|
+
}
|
|
892
|
+
/** Write one already-serialized frame, tolerating a closed peer. */
|
|
893
|
+
function _send(connection, text) {
|
|
894
|
+
try {
|
|
895
|
+
connection.send(text);
|
|
896
|
+
} catch {}
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* The `callables` target as native Cap'n Web methods for one session:
|
|
900
|
+
* every method dispatches through the host boundary with this
|
|
901
|
+
* connection in scope and emits `rpc`/`rpc:error` events. Return values
|
|
902
|
+
* keep Cap'n Web semantics — an `RpcTarget` comes back as a live stub.
|
|
903
|
+
*/
|
|
904
|
+
function _nativeCallables(connection) {
|
|
905
|
+
const methods = /* @__PURE__ */ new Map();
|
|
906
|
+
for (const [name, invoke] of _classPrivateFieldGet2(_callables, this)) methods.set(name, (...args) => _assertClassBrand(_WebSockets_brand, this, _dispatchCallable).call(this, name, () => invoke(...args), connection));
|
|
907
|
+
return methods;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Answer one `rpc` frame against `callables`. A `ReadableStream`
|
|
911
|
+
* result streams as `done: false` chunks followed by a final
|
|
912
|
+
* `done: true` frame, matching the client's stream callbacks.
|
|
913
|
+
*
|
|
914
|
+
* @returns Whether the message was an `rpc` frame (answered or not).
|
|
915
|
+
*/
|
|
916
|
+
async function _answerRpc(connection, raw) {
|
|
917
|
+
if (typeof raw !== "string") return false;
|
|
918
|
+
let frame;
|
|
919
|
+
try {
|
|
920
|
+
frame = JSON.parse(raw);
|
|
921
|
+
} catch {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
if (!isRpcRequest(frame)) return false;
|
|
925
|
+
const { id, method, args } = frame;
|
|
926
|
+
const invoke = _classPrivateFieldGet2(_callables, this).get(method);
|
|
927
|
+
if (!invoke) {
|
|
928
|
+
_assertClassBrand(_WebSockets_brand, this, _reply).call(this, connection, {
|
|
929
|
+
type: "rpc",
|
|
930
|
+
id,
|
|
931
|
+
success: false,
|
|
932
|
+
error: `Method ${method} does not exist`
|
|
933
|
+
});
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
const result = await _assertClassBrand(_WebSockets_brand, this, _dispatchCallable).call(this, method, () => invoke(...args), connection);
|
|
938
|
+
if (result instanceof RpcTarget$1) throw new Error(`Method ${method} returns an RpcTarget, which only the capnweb transport can carry`);
|
|
939
|
+
if (result instanceof ReadableStream) {
|
|
940
|
+
for await (const chunk of result) _assertClassBrand(_WebSockets_brand, this, _reply).call(this, connection, {
|
|
941
|
+
type: "rpc",
|
|
942
|
+
id,
|
|
943
|
+
success: true,
|
|
944
|
+
done: false,
|
|
945
|
+
result: chunk
|
|
946
|
+
});
|
|
947
|
+
_assertClassBrand(_WebSockets_brand, this, _reply).call(this, connection, {
|
|
948
|
+
type: "rpc",
|
|
949
|
+
id,
|
|
950
|
+
success: true,
|
|
951
|
+
done: true,
|
|
952
|
+
result: void 0
|
|
953
|
+
});
|
|
954
|
+
} else _assertClassBrand(_WebSockets_brand, this, _reply).call(this, connection, {
|
|
955
|
+
type: "rpc",
|
|
956
|
+
id,
|
|
957
|
+
success: true,
|
|
958
|
+
done: true,
|
|
959
|
+
result
|
|
960
|
+
});
|
|
961
|
+
} catch (error) {
|
|
962
|
+
_assertClassBrand(_WebSockets_brand, this, _reply).call(this, connection, {
|
|
963
|
+
type: "rpc",
|
|
964
|
+
id,
|
|
965
|
+
success: false,
|
|
966
|
+
error: error instanceof Error ? error.message : String(error)
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
function _reply(connection, response) {
|
|
972
|
+
let text;
|
|
973
|
+
try {
|
|
974
|
+
text = JSON.stringify(response);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
text = JSON.stringify({
|
|
977
|
+
type: "rpc",
|
|
978
|
+
id: response.id,
|
|
979
|
+
success: false,
|
|
980
|
+
error: `Result is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
_assertClassBrand(_WebSockets_brand, this, _send).call(this, connection, text);
|
|
984
|
+
}
|
|
985
|
+
async function _dispatchCallable(name, invoke, connection) {
|
|
986
|
+
const services = this.lifecycle;
|
|
987
|
+
try {
|
|
988
|
+
const result = await services.runInHostContext(invoke, { connection });
|
|
989
|
+
services.events.emit("rpc", {
|
|
990
|
+
method: name,
|
|
991
|
+
streaming: result instanceof ReadableStream
|
|
992
|
+
});
|
|
993
|
+
return result;
|
|
994
|
+
} catch (error) {
|
|
995
|
+
services.events.emit("rpc:error", {
|
|
996
|
+
method: name,
|
|
997
|
+
error: error instanceof Error ? error.message : String(error)
|
|
998
|
+
});
|
|
999
|
+
throw error;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
//#endregion
|
|
1003
|
+
export { getConnectionFlag as a, isConnectionReadonly as c, setConnectionProtocolEnabled as d, setConnectionReadonly as f, ensureConnectionWrapped as i, registerInternalConnectionKeys as l, CF_NO_PROTOCOL_KEY as n, getConnectionRawState as o, CF_READONLY_KEY as r, isConnectionProtocolEnabled as s, WebSockets as t, setConnectionFlag as u };
|
|
1004
|
+
|
|
1005
|
+
//# sourceMappingURL=websockets-D7IzWbZb.js.map
|