@dxos/edge-client 0.10.0 → 0.11.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.
Files changed (57) hide show
  1. package/dist/lib/chunk-edge-ws-muxer.mjs +322 -0
  2. package/dist/lib/chunk-edge-ws-muxer.mjs.map +1 -0
  3. package/dist/lib/cors-proxy.mjs +29 -0
  4. package/dist/lib/cors-proxy.mjs.map +1 -0
  5. package/dist/lib/edge-ws-muxer.mjs +2 -0
  6. package/dist/lib/index.mjs +1769 -0
  7. package/dist/lib/index.mjs.map +1 -0
  8. package/dist/lib/service.mjs +138 -0
  9. package/dist/lib/service.mjs.map +1 -0
  10. package/dist/lib/testing.mjs +160 -0
  11. package/dist/lib/testing.mjs.map +1 -0
  12. package/dist/types/src/base-http-client.d.ts +18 -0
  13. package/dist/types/src/base-http-client.d.ts.map +1 -1
  14. package/dist/types/src/browser-rendering.d.ts +0 -7
  15. package/dist/types/src/browser-rendering.d.ts.map +1 -1
  16. package/dist/types/src/edge-client.d.ts +22 -2
  17. package/dist/types/src/edge-client.d.ts.map +1 -1
  18. package/dist/types/src/edge-http-client.d.ts +86 -2
  19. package/dist/types/src/edge-http-client.d.ts.map +1 -1
  20. package/dist/types/src/edge-ws-connection.d.ts +19 -0
  21. package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
  22. package/dist/types/src/edge-ws-connection.test.d.ts +2 -0
  23. package/dist/types/src/edge-ws-connection.test.d.ts.map +1 -0
  24. package/dist/types/src/index.d.ts +1 -0
  25. package/dist/types/src/index.d.ts.map +1 -1
  26. package/dist/types/src/protocol.d.ts +2 -1
  27. package/dist/types/src/protocol.d.ts.map +1 -1
  28. package/dist/types/src/testing/test-utils.d.ts.map +1 -1
  29. package/dist/types/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +20 -20
  31. package/src/base-http-client.ts +71 -3
  32. package/src/browser-rendering.ts +0 -9
  33. package/src/edge-client.ts +21 -3
  34. package/src/edge-http-client.test.ts +121 -0
  35. package/src/edge-http-client.ts +156 -2
  36. package/src/edge-ws-connection.test.ts +219 -0
  37. package/src/edge-ws-connection.ts +97 -28
  38. package/src/index.ts +1 -0
  39. package/src/protocol.ts +11 -2
  40. package/src/testing/test-utils.ts +5 -1
  41. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
  42. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
  43. package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs +0 -310
  44. package/dist/lib/neutral/chunk-L5ZHLJ4B.mjs.map +0 -7
  45. package/dist/lib/neutral/chunk-WQKMEZJR.mjs +0 -30
  46. package/dist/lib/neutral/chunk-WQKMEZJR.mjs.map +0 -7
  47. package/dist/lib/neutral/cors-proxy.mjs +0 -8
  48. package/dist/lib/neutral/cors-proxy.mjs.map +0 -7
  49. package/dist/lib/neutral/edge-ws-muxer.mjs +0 -12
  50. package/dist/lib/neutral/edge-ws-muxer.mjs.map +0 -7
  51. package/dist/lib/neutral/index.mjs +0 -1524
  52. package/dist/lib/neutral/index.mjs.map +0 -7
  53. package/dist/lib/neutral/meta.json +0 -1
  54. package/dist/lib/neutral/service/index.mjs +0 -134
  55. package/dist/lib/neutral/service/index.mjs.map +0 -7
  56. package/dist/lib/neutral/testing/index.mjs +0 -161
  57. package/dist/lib/neutral/testing/index.mjs.map +0 -7
@@ -0,0 +1,322 @@
1
+ import { Trigger } from "@dxos/async";
2
+ import { log } from "@dxos/log";
3
+ import { buf, bufWkt } from "@dxos/protocols/buf";
4
+ import { MessageSchema, SwarmRequestSchema, SwarmResponseSchema, TextMessageSchema } from "@dxos/protocols/buf/dxos/edge/messenger_pb";
5
+ import { invariant } from "@dxos/invariant";
6
+ import { bufferToArray } from "@dxos/util";
7
+ //#region src/protocol.ts
8
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/protocol.ts";
9
+ var getTypename = (typeName) => `type.googleapis.com/${typeName}`;
10
+ /**
11
+ * NOTE: The type registry should be extended with all message types.
12
+ */
13
+ var Protocol = class {
14
+ _typeRegistry;
15
+ constructor(types) {
16
+ this._typeRegistry = buf.createRegistry(...types);
17
+ }
18
+ get typeRegistry() {
19
+ return this._typeRegistry;
20
+ }
21
+ toJson(message) {
22
+ try {
23
+ return buf.toJson(MessageSchema, message, { registry: this.typeRegistry });
24
+ } catch (err) {
25
+ return { type: this.getPayloadType(message) };
26
+ }
27
+ }
28
+ /**
29
+ * Return the payload with the given type.
30
+ */
31
+ getPayload(message, type) {
32
+ invariant(message.payload, void 0, {
33
+ "~LogMeta": "~LogMeta",
34
+ F: __dxlog_file$1,
35
+ L: 40,
36
+ S: this,
37
+ A: ["message.payload", ""]
38
+ });
39
+ const payloadTypename = this.getPayloadType(message);
40
+ if (type && type.typeName !== payloadTypename) throw new Error(`Unexpected payload type: ${payloadTypename}; expected ${type.typeName}`);
41
+ invariant(bufWkt.anyIs(message.payload, type), `Unexpected payload type: ${payloadTypename}}`, {
42
+ "~LogMeta": "~LogMeta",
43
+ F: __dxlog_file$1,
44
+ L: 46,
45
+ S: this,
46
+ A: ["bufWkt.anyIs(message.payload, type)", "`Unexpected payload type: ${payloadTypename}}`"]
47
+ });
48
+ const payload = bufWkt.anyUnpack(message.payload, this.typeRegistry);
49
+ invariant(payload, `Empty payload: ${payloadTypename}}`, {
50
+ "~LogMeta": "~LogMeta",
51
+ F: __dxlog_file$1,
52
+ L: 48,
53
+ S: this,
54
+ A: ["payload", "`Empty payload: ${payloadTypename}}`"]
55
+ });
56
+ return payload;
57
+ }
58
+ /**
59
+ * Get the payload type.
60
+ */
61
+ getPayloadType(message) {
62
+ if (!message.payload) return;
63
+ const [, type] = message.payload.typeUrl.split("/");
64
+ return type;
65
+ }
66
+ /**
67
+ * Create a packed message.
68
+ */
69
+ createMessage(type, { source, target, tags, payload, serviceId }) {
70
+ return buf.create(MessageSchema, {
71
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
72
+ source,
73
+ target,
74
+ tags,
75
+ serviceId,
76
+ payload: payload ? bufWkt.anyPack(type, buf.create(type, payload)) : void 0
77
+ });
78
+ }
79
+ };
80
+ /**
81
+ * Convert websocket data to Uint8Array.
82
+ */
83
+ var toUint8Array = async (data) => {
84
+ if (data instanceof Buffer) return bufferToArray(data);
85
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
86
+ if (data instanceof Blob) return new Uint8Array(await data.arrayBuffer());
87
+ throw new Error(`Unexpected datatype: ${data}`);
88
+ };
89
+ //#endregion
90
+ //#region src/defs.ts
91
+ var protocol = new Protocol([
92
+ SwarmRequestSchema,
93
+ SwarmResponseSchema,
94
+ TextMessageSchema,
95
+ bufWkt.AnySchema
96
+ ]);
97
+ //#endregion
98
+ //#region src/edge-ws-muxer.ts
99
+ var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/edge-client/src/edge-ws-muxer.ts";
100
+ /**
101
+ * 0000 0001 - message contains a part of segmented message chunk sequence.
102
+ * The next byte defines a channel id and the rest of the message contains a part of Message proto binary.
103
+ * Messages from different channels might interleave.
104
+ * When the flag is NOT set the rest of the message should be interpreted as the valid Message proto binary.
105
+ */
106
+ var FLAG_SEGMENT_SEQ = 1;
107
+ /**
108
+ * 0000 0010 - message terminates a segmented message chunk sequence.
109
+ * All the chunks accumulated for the channel specified by the second byte can be concatenated
110
+ * and interpreted as a valid Message proto binary.
111
+ */
112
+ var FLAG_SEGMENT_SEQ_TERMINATED = 2;
113
+ /**
114
+ * https://developers.cloudflare.com/durable-objects/platform/limits/
115
+ */
116
+ var CLOUDFLARE_MESSAGE_MAX_BYTES = 1e3 * 1e3;
117
+ var CLOUDFLARE_RPC_MAX_BYTES = 32 * 1e3 * 1e3;
118
+ var MAX_CHUNK_LENGTH = 16384;
119
+ var MAX_BUFFERED_AMOUNT = CLOUDFLARE_MESSAGE_MAX_BYTES;
120
+ var BUFFER_FULL_BACKOFF_TIMEOUT = 100;
121
+ var WebSocketMuxer = class {
122
+ _ws;
123
+ _inMessageAccumulator = /* @__PURE__ */ new Map();
124
+ _outMessageChunks = /* @__PURE__ */ new Map();
125
+ _outMessageChannelByService = /* @__PURE__ */ new Map();
126
+ _sendTimeout;
127
+ _maxChunkLength;
128
+ constructor(_ws, config) {
129
+ this._ws = _ws;
130
+ this._maxChunkLength = config?.maxChunkLength ?? MAX_CHUNK_LENGTH;
131
+ }
132
+ /**
133
+ * Resolves when all the message chunks get enqueued for sending.
134
+ */
135
+ async send(message) {
136
+ const binary = buf.toBinary(MessageSchema, message);
137
+ const channelId = this._resolveChannel(message);
138
+ if (channelId == null && binary.byteLength > 1e6 || binary.byteLength > 32e6) {
139
+ log.error("Large message dropped", {
140
+ byteLength: binary.byteLength,
141
+ serviceId: message.serviceId,
142
+ payload: protocol.getPayloadType(message),
143
+ channelId
144
+ }, {
145
+ "~LogMeta": "~LogMeta",
146
+ F: __dxlog_file,
147
+ L: 62,
148
+ S: this
149
+ });
150
+ return;
151
+ }
152
+ if (channelId == null || binary.length < this._maxChunkLength) {
153
+ const flags = Buffer.from([0]);
154
+ this._ws.send(Buffer.concat([flags, binary]));
155
+ return;
156
+ }
157
+ const chunkCount = Math.ceil(binary.length / this._maxChunkLength);
158
+ log("muxer sending segmented message", {
159
+ byteLength: binary.byteLength,
160
+ chunkCount,
161
+ channelId,
162
+ serviceId: message.serviceId,
163
+ payload: protocol.getPayloadType(message)
164
+ }, {
165
+ "~LogMeta": "~LogMeta",
166
+ F: __dxlog_file,
167
+ L: 78,
168
+ S: this
169
+ });
170
+ const terminatorSentTrigger = new Trigger();
171
+ const messageChunks = [];
172
+ for (let i = 0; i < binary.length; i += this._maxChunkLength) {
173
+ const chunk = binary.slice(i, i + this._maxChunkLength);
174
+ if (i + this._maxChunkLength >= binary.length) {
175
+ const flags = Buffer.from([3, channelId]);
176
+ messageChunks.push({
177
+ payload: Buffer.concat([flags, chunk]),
178
+ trigger: terminatorSentTrigger
179
+ });
180
+ } else {
181
+ const flags = Buffer.from([FLAG_SEGMENT_SEQ, channelId]);
182
+ messageChunks.push({ payload: Buffer.concat([flags, chunk]) });
183
+ }
184
+ }
185
+ const queuedMessages = this._outMessageChunks.get(channelId);
186
+ if (queuedMessages) queuedMessages.push(...messageChunks);
187
+ else this._outMessageChunks.set(channelId, messageChunks);
188
+ this._sendChunkedMessages();
189
+ await terminatorSentTrigger.wait();
190
+ log.debug("muxer segmented message send enqueued", {
191
+ byteLength: binary.byteLength,
192
+ chunkCount,
193
+ channelId,
194
+ serviceId: message.serviceId
195
+ }, {
196
+ "~LogMeta": "~LogMeta",
197
+ F: __dxlog_file,
198
+ L: 110,
199
+ S: this
200
+ });
201
+ }
202
+ receiveData(data) {
203
+ if ((data[0] & FLAG_SEGMENT_SEQ) === 0) return buf.fromBinary(MessageSchema, data.slice(1));
204
+ const [flags, channelId, ...payload] = data;
205
+ const chunkPayload = Buffer.from(payload);
206
+ let chunkAccumulator = this._inMessageAccumulator.get(channelId);
207
+ if (chunkAccumulator) chunkAccumulator.push(chunkPayload);
208
+ else {
209
+ chunkAccumulator = [chunkPayload];
210
+ this._inMessageAccumulator.set(channelId, chunkAccumulator);
211
+ log.debug("muxer started receiving segmented message", {
212
+ channelId,
213
+ firstChunkBytes: chunkPayload.byteLength
214
+ }, {
215
+ "~LogMeta": "~LogMeta",
216
+ F: __dxlog_file,
217
+ L: 131,
218
+ S: this
219
+ });
220
+ }
221
+ if ((flags & FLAG_SEGMENT_SEQ_TERMINATED) === 0) return;
222
+ const reassembled = Buffer.concat(chunkAccumulator);
223
+ const chunkCount = chunkAccumulator.length;
224
+ this._inMessageAccumulator.delete(channelId);
225
+ try {
226
+ const message = buf.fromBinary(MessageSchema, reassembled);
227
+ log("muxer reassembled segmented message", {
228
+ channelId,
229
+ chunkCount,
230
+ byteLength: reassembled.byteLength,
231
+ serviceId: message.serviceId,
232
+ payloadBytes: message.payload?.value?.byteLength
233
+ }, {
234
+ "~LogMeta": "~LogMeta",
235
+ F: __dxlog_file,
236
+ L: 146,
237
+ S: this
238
+ });
239
+ return message;
240
+ } catch (error) {
241
+ log.error("muxer failed to decode reassembled message", {
242
+ channelId,
243
+ chunkCount,
244
+ byteLength: reassembled.byteLength,
245
+ cause: error instanceof Error ? error.message : String(error)
246
+ }, {
247
+ "~LogMeta": "~LogMeta",
248
+ F: __dxlog_file,
249
+ L: 155,
250
+ S: this
251
+ });
252
+ throw error;
253
+ }
254
+ }
255
+ destroy() {
256
+ if (this._sendTimeout) {
257
+ clearTimeout(this._sendTimeout);
258
+ this._sendTimeout = void 0;
259
+ }
260
+ for (const channelChunks of this._outMessageChunks.values()) channelChunks.forEach((chunk) => chunk.trigger?.wake());
261
+ this._outMessageChunks.clear();
262
+ this._inMessageAccumulator.clear();
263
+ this._outMessageChannelByService.clear();
264
+ }
265
+ _sendChunkedMessages() {
266
+ if (this._sendTimeout) return;
267
+ const send = () => {
268
+ if (this._ws.readyState === 2 || this._ws.readyState === 3) {
269
+ log.warn("send called for closed websocket", void 0, {
270
+ "~LogMeta": "~LogMeta",
271
+ F: __dxlog_file,
272
+ L: 185,
273
+ S: this
274
+ });
275
+ this._sendTimeout = void 0;
276
+ return;
277
+ }
278
+ let timeout = 0;
279
+ const emptyChannels = [];
280
+ for (const [channelId, messages] of this._outMessageChunks.entries()) {
281
+ if (this._ws.bufferedAmount != null) {
282
+ if (this._ws.bufferedAmount + MAX_CHUNK_LENGTH > MAX_BUFFERED_AMOUNT) {
283
+ log.debug("muxer send paused (websocket buffer full)", {
284
+ channelId,
285
+ bufferedAmount: this._ws.bufferedAmount,
286
+ pendingChannels: this._outMessageChunks.size
287
+ }, {
288
+ "~LogMeta": "~LogMeta",
289
+ F: __dxlog_file,
290
+ L: 195,
291
+ S: this
292
+ });
293
+ timeout = BUFFER_FULL_BACKOFF_TIMEOUT;
294
+ break;
295
+ }
296
+ }
297
+ const nextMessage = messages.shift();
298
+ if (nextMessage) {
299
+ this._ws.send(nextMessage.payload);
300
+ nextMessage.trigger?.wake();
301
+ } else emptyChannels.push(channelId);
302
+ }
303
+ emptyChannels.forEach((channelId) => this._outMessageChunks.delete(channelId));
304
+ if (this._outMessageChunks.size > 0) this._sendTimeout = setTimeout(send, timeout);
305
+ else this._sendTimeout = void 0;
306
+ };
307
+ this._sendTimeout = setTimeout(send);
308
+ }
309
+ _resolveChannel(message) {
310
+ if (!message.serviceId) return;
311
+ let id = this._outMessageChannelByService.get(message.serviceId);
312
+ if (!id) {
313
+ id = this._outMessageChannelByService.size + 1;
314
+ this._outMessageChannelByService.set(message.serviceId, id);
315
+ }
316
+ return id;
317
+ }
318
+ };
319
+ //#endregion
320
+ export { Protocol as a, protocol as i, CLOUDFLARE_RPC_MAX_BYTES as n, getTypename as o, WebSocketMuxer as r, toUint8Array as s, CLOUDFLARE_MESSAGE_MAX_BYTES as t };
321
+
322
+ //# sourceMappingURL=chunk-edge-ws-muxer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-edge-ws-muxer.mjs","names":[],"sources":["../../src/protocol.ts","../../src/defs.ts","../../src/edge-ws-muxer.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { invariant } from '@dxos/invariant';\nimport { buf, bufWkt } from '@dxos/protocols/buf';\nimport { type Message, MessageSchema, type PeerSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\nimport { bufferToArray } from '@dxos/util';\n\nexport type PeerData = buf.MessageInitShape<typeof PeerSchema>;\n\nexport const getTypename = (typeName: string) => `type.googleapis.com/${typeName}`;\n\n/**\n * NOTE: The type registry should be extended with all message types.\n */\nexport class Protocol {\n private readonly _typeRegistry: buf.Registry;\n\n constructor(types: buf.DescMessage[]) {\n this._typeRegistry = buf.createRegistry(...types);\n }\n\n get typeRegistry(): buf.Registry {\n return this._typeRegistry;\n }\n\n toJson(message: Message): any {\n try {\n return buf.toJson(MessageSchema, message, { registry: this.typeRegistry });\n } catch (err) {\n return { type: this.getPayloadType(message) };\n }\n }\n\n /**\n * Return the payload with the given type.\n */\n getPayload<Desc extends buf.DescMessage>(message: Message, type: Desc): buf.MessageShape<Desc> {\n invariant(message.payload);\n const payloadTypename = this.getPayloadType(message);\n if (type && type.typeName !== payloadTypename) {\n throw new Error(`Unexpected payload type: ${payloadTypename}; expected ${type.typeName}`);\n }\n\n invariant(bufWkt.anyIs(message.payload, type), `Unexpected payload type: ${payloadTypename}}`);\n const payload = bufWkt.anyUnpack(message.payload, this.typeRegistry) as buf.MessageShape<Desc>;\n invariant(payload, `Empty payload: ${payloadTypename}}`);\n return payload;\n }\n\n /**\n * Get the payload type.\n */\n getPayloadType(message: Message): string | undefined {\n if (!message.payload) {\n return undefined;\n }\n\n const [, type] = message.payload.typeUrl.split('/');\n return type;\n }\n\n /**\n * Create a packed message.\n */\n createMessage<Desc extends buf.DescMessage>(\n type: Desc,\n {\n source,\n target,\n tags,\n payload,\n serviceId,\n }: {\n source?: PeerData;\n target?: PeerData[];\n // Broadcast tags (DX-1125). Set with no resolvable `target` to publish to the swarm.\n tags?: string[];\n payload?: buf.MessageInitShape<Desc>;\n serviceId?: string;\n },\n ): Message {\n return buf.create(MessageSchema, {\n timestamp: new Date().toISOString(),\n source,\n target,\n tags,\n serviceId,\n payload: payload ? bufWkt.anyPack(type, buf.create(type, payload)) : undefined,\n });\n }\n}\n\n/**\n * Convert websocket data to Uint8Array.\n */\nexport const toUint8Array = async (data: any): Promise<Uint8Array> => {\n // Node.\n if (data instanceof Buffer) {\n return bufferToArray(data);\n }\n\n // Browser with `binaryType = 'arraybuffer'`.\n if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n\n // Browser fallback (`binaryType = 'blob'`, the WebSocket default).\n if (data instanceof Blob) {\n return new Uint8Array(await data.arrayBuffer());\n }\n\n throw new Error(`Unexpected datatype: ${data}`);\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { bufWkt } from '@dxos/protocols/buf';\nimport { SwarmRequestSchema, SwarmResponseSchema, TextMessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\n\nimport { Protocol } from './protocol';\n\nexport const protocol = new Protocol([SwarmRequestSchema, SwarmResponseSchema, TextMessageSchema, bufWkt.AnySchema]);\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Trigger } from '@dxos/async';\nimport { log } from '@dxos/log';\nimport { buf } from '@dxos/protocols/buf';\nimport { type Message, MessageSchema } from '@dxos/protocols/buf/dxos/edge/messenger_pb';\n\nimport { protocol } from './defs';\n\n/**\n * 0000 0001 - message contains a part of segmented message chunk sequence.\n * The next byte defines a channel id and the rest of the message contains a part of Message proto binary.\n * Messages from different channels might interleave.\n * When the flag is NOT set the rest of the message should be interpreted as the valid Message proto binary.\n */\nconst FLAG_SEGMENT_SEQ = 1;\n/**\n * 0000 0010 - message terminates a segmented message chunk sequence.\n * All the chunks accumulated for the channel specified by the second byte can be concatenated\n * and interpreted as a valid Message proto binary.\n */\nconst FLAG_SEGMENT_SEQ_TERMINATED = 1 << 1;\n\n/**\n * https://developers.cloudflare.com/durable-objects/platform/limits/\n */\nexport const CLOUDFLARE_MESSAGE_MAX_BYTES = 1000 * 1000; // 1MB\nexport const CLOUDFLARE_RPC_MAX_BYTES = 32 * 1000 * 1000; // 32MB\n\nconst MAX_CHUNK_LENGTH = 16384;\nconst MAX_BUFFERED_AMOUNT = CLOUDFLARE_MESSAGE_MAX_BYTES;\nconst BUFFER_FULL_BACKOFF_TIMEOUT = 100;\n\nexport class WebSocketMuxer {\n private readonly _inMessageAccumulator = new Map<number, Buffer[]>();\n private readonly _outMessageChunks = new Map<number, MessageChunk[]>();\n private readonly _outMessageChannelByService = new Map<string, number>();\n\n private _sendTimeout: any | undefined;\n\n private readonly _maxChunkLength: number;\n\n constructor(\n private readonly _ws: WebSocketCompat,\n config?: { maxChunkLength: number },\n ) {\n this._maxChunkLength = config?.maxChunkLength ?? MAX_CHUNK_LENGTH;\n }\n\n /**\n * Resolves when all the message chunks get enqueued for sending.\n */\n public async send(message: Message): Promise<void> {\n const binary = buf.toBinary(MessageSchema, message);\n const channelId = this._resolveChannel(message);\n if (\n (channelId == null && binary.byteLength > CLOUDFLARE_MESSAGE_MAX_BYTES) ||\n binary.byteLength > CLOUDFLARE_RPC_MAX_BYTES\n ) {\n log.error('Large message dropped', {\n byteLength: binary.byteLength,\n serviceId: message.serviceId,\n payload: protocol.getPayloadType(message),\n channelId,\n });\n return;\n }\n\n if (channelId == null || binary.length < this._maxChunkLength) {\n const flags = Buffer.from([0]);\n this._ws.send(Buffer.concat([flags, binary]));\n return;\n }\n\n const chunkCount = Math.ceil(binary.length / this._maxChunkLength);\n log('muxer sending segmented message', {\n byteLength: binary.byteLength,\n chunkCount,\n channelId,\n serviceId: message.serviceId,\n payload: protocol.getPayloadType(message),\n });\n\n const terminatorSentTrigger = new Trigger();\n const messageChunks: MessageChunk[] = [];\n for (let i = 0; i < binary.length; i += this._maxChunkLength) {\n const chunk = binary.slice(i, i + this._maxChunkLength);\n const isLastChunk = i + this._maxChunkLength >= binary.length;\n if (isLastChunk) {\n const flags = Buffer.from([FLAG_SEGMENT_SEQ | FLAG_SEGMENT_SEQ_TERMINATED, channelId]);\n messageChunks.push({ payload: Buffer.concat([flags, chunk]), trigger: terminatorSentTrigger });\n } else {\n const flags = Buffer.from([FLAG_SEGMENT_SEQ, channelId]);\n messageChunks.push({ payload: Buffer.concat([flags, chunk]) });\n }\n }\n\n const queuedMessages = this._outMessageChunks.get(channelId);\n if (queuedMessages) {\n queuedMessages.push(...messageChunks);\n } else {\n this._outMessageChunks.set(channelId, messageChunks);\n }\n\n this._sendChunkedMessages();\n\n await terminatorSentTrigger.wait();\n log.debug('muxer segmented message send enqueued', {\n byteLength: binary.byteLength,\n chunkCount,\n channelId,\n serviceId: message.serviceId,\n });\n }\n\n public receiveData(data: Uint8Array): Message | undefined {\n if ((data[0] & FLAG_SEGMENT_SEQ) === 0) {\n return buf.fromBinary(MessageSchema, data.slice(1));\n }\n\n const [flags, channelId, ...payload] = data;\n const chunkPayload = Buffer.from(payload);\n let chunkAccumulator = this._inMessageAccumulator.get(channelId);\n if (chunkAccumulator) {\n chunkAccumulator.push(chunkPayload);\n } else {\n chunkAccumulator = [chunkPayload];\n this._inMessageAccumulator.set(channelId, chunkAccumulator);\n log.debug('muxer started receiving segmented message', {\n channelId,\n firstChunkBytes: chunkPayload.byteLength,\n });\n }\n\n if ((flags & FLAG_SEGMENT_SEQ_TERMINATED) === 0) {\n return undefined;\n }\n\n const reassembled = Buffer.concat(chunkAccumulator);\n const chunkCount = chunkAccumulator.length;\n this._inMessageAccumulator.delete(channelId);\n try {\n const message = buf.fromBinary(MessageSchema, reassembled);\n log('muxer reassembled segmented message', {\n channelId,\n chunkCount,\n byteLength: reassembled.byteLength,\n serviceId: message.serviceId,\n payloadBytes: message.payload?.value?.byteLength,\n });\n return message;\n } catch (error) {\n log.error('muxer failed to decode reassembled message', {\n channelId,\n chunkCount,\n byteLength: reassembled.byteLength,\n cause: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n public destroy(): void {\n if (this._sendTimeout) {\n clearTimeout(this._sendTimeout);\n this._sendTimeout = undefined;\n }\n for (const channelChunks of this._outMessageChunks.values()) {\n channelChunks.forEach((chunk) => chunk.trigger?.wake());\n }\n this._outMessageChunks.clear();\n this._inMessageAccumulator.clear();\n this._outMessageChannelByService.clear();\n }\n\n private _sendChunkedMessages(): void {\n if (this._sendTimeout) {\n return;\n }\n\n const send = () => {\n if (this._ws.readyState === WebSocket.CLOSING || this._ws.readyState === WebSocket.CLOSED) {\n log.warn('send called for closed websocket');\n this._sendTimeout = undefined;\n return;\n }\n\n let timeout = 0;\n const emptyChannels: number[] = [];\n for (const [channelId, messages] of this._outMessageChunks.entries()) {\n if (this._ws.bufferedAmount != null) {\n if (this._ws.bufferedAmount + MAX_CHUNK_LENGTH > MAX_BUFFERED_AMOUNT) {\n log.debug('muxer send paused (websocket buffer full)', {\n channelId,\n bufferedAmount: this._ws.bufferedAmount,\n pendingChannels: this._outMessageChunks.size,\n });\n timeout = BUFFER_FULL_BACKOFF_TIMEOUT;\n break;\n }\n }\n\n const nextMessage = messages.shift();\n if (nextMessage) {\n this._ws.send(nextMessage.payload);\n nextMessage.trigger?.wake();\n } else {\n emptyChannels.push(channelId);\n }\n }\n\n emptyChannels.forEach((channelId) => this._outMessageChunks.delete(channelId));\n\n if (this._outMessageChunks.size > 0) {\n this._sendTimeout = setTimeout(send, timeout);\n } else {\n this._sendTimeout = undefined;\n }\n };\n this._sendTimeout = setTimeout(send);\n }\n\n private _resolveChannel(message: Message): number | undefined {\n if (!message.serviceId) {\n return undefined;\n }\n let id = this._outMessageChannelByService.get(message.serviceId);\n if (!id) {\n id = this._outMessageChannelByService.size + 1;\n this._outMessageChannelByService.set(message.serviceId, id);\n }\n return id;\n }\n}\n\ntype WebSocketCompat = {\n readonly readyState: number;\n /**\n * Not available in workerd.\n */\n bufferedAmount?: number;\n send(message: (ArrayBuffer | ArrayBufferView) | string): void;\n};\n\ntype MessageChunk = {\n payload: Buffer;\n /**\n * Wakes when the payload is enqueued by WebSocket.\n */\n trigger?: Trigger;\n};\n\n/**\n * To avoid using isomorphic-ws on edge.\n */\nenum WebSocket {\n CLOSING = 2,\n CLOSED = 3,\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ //#region src/cors-proxy.ts
2
+ var LEGACY_CORS_PROXY_URL = "https://cors-proxy.dxos.workers.dev";
3
+ var EDGE_CLIENT_TAG_HEADER = "X-DXOS-Client-Tag";
4
+ var remapAuthorizationForProxy = (headers) => {
5
+ const callerAuth = headers.get("Authorization");
6
+ if (callerAuth !== null) {
7
+ headers.delete("Authorization");
8
+ headers.set("X-Cors-Proxy-Authorization", callerAuth);
9
+ }
10
+ return headers;
11
+ };
12
+ /**
13
+ * Fetch through the legacy standalone open proxy at `cors-proxy.dxos.workers.dev`.
14
+ * TEMPORARY — delete when the authenticated `/proxy/*` route on edge ships.
15
+ */
16
+ var proxyFetchLegacy = (target, init = {}, clientTag) => {
17
+ const proxyUrl = new URL(`/${target.host}${target.pathname}${target.search}`, LEGACY_CORS_PROXY_URL);
18
+ if (target.protocol === "http:") proxyUrl.searchParams.set("scheme", "http");
19
+ const requestHeaders = remapAuthorizationForProxy(new Headers(init.headers ?? void 0));
20
+ if (clientTag) requestHeaders.set(EDGE_CLIENT_TAG_HEADER, clientTag);
21
+ return fetch(proxyUrl, {
22
+ ...init,
23
+ headers: requestHeaders
24
+ });
25
+ };
26
+ //#endregion
27
+ export { proxyFetchLegacy };
28
+
29
+ //# sourceMappingURL=cors-proxy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors-proxy.mjs","names":[],"sources":["../../src/cors-proxy.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\n// Lightweight CORS-proxy helpers — intentionally free of heavy transitive\n// dependencies so they can be bundled into workerd / browser environments\n// without pulling in protobufjs or similar node-only packages.\n\nconst LEGACY_CORS_PROXY_URL = 'https://cors-proxy.dxos.workers.dev';\n\n// Matches EDGE_CLIENT_TAG_HEADER from @dxos/protocols.\n// Duplicated here to avoid importing the heavy protocols bundle in edge environments.\nconst EDGE_CLIENT_TAG_HEADER = 'X-DXOS-Client-Tag';\n\nconst remapAuthorizationForProxy = (headers: Headers): Headers => {\n const callerAuth = headers.get('Authorization');\n if (callerAuth !== null) {\n headers.delete('Authorization');\n headers.set('X-Cors-Proxy-Authorization', callerAuth);\n }\n return headers;\n};\n\n/**\n * Fetch through the legacy standalone open proxy at `cors-proxy.dxos.workers.dev`.\n * TEMPORARY — delete when the authenticated `/proxy/*` route on edge ships.\n */\nexport const proxyFetchLegacy = (target: URL, init: RequestInit = {}, clientTag?: string): Promise<Response> => {\n const proxyUrl = new URL(`/${target.host}${target.pathname}${target.search}`, LEGACY_CORS_PROXY_URL);\n if (target.protocol === 'http:') {\n proxyUrl.searchParams.set('scheme', 'http');\n }\n const requestHeaders = remapAuthorizationForProxy(new Headers(init.headers ?? undefined));\n if (clientTag) {\n requestHeaders.set(EDGE_CLIENT_TAG_HEADER, clientTag);\n }\n return fetch(proxyUrl, { ...init, headers: requestHeaders });\n};\n"],"mappings":";AAQA,IAAM,wBAAwB;AAI9B,IAAM,yBAAyB;AAE/B,IAAM,8BAA8B,YAA8B;CAChE,MAAM,aAAa,QAAQ,IAAI,eAAe;CAC9C,IAAI,eAAe,MAAM;EACvB,QAAQ,OAAO,eAAe;EAC9B,QAAQ,IAAI,8BAA8B,UAAU;CACtD;CACA,OAAO;AACT;;;;;AAMA,IAAa,oBAAoB,QAAa,OAAoB,CAAC,GAAG,cAA0C;CAC9G,MAAM,WAAW,IAAI,IAAI,IAAI,OAAO,OAAO,OAAO,WAAW,OAAO,UAAU,qBAAqB;CACnG,IAAI,OAAO,aAAa,SACtB,SAAS,aAAa,IAAI,UAAU,MAAM;CAE5C,MAAM,iBAAiB,2BAA2B,IAAI,QAAQ,KAAK,WAAW,KAAA,CAAS,CAAC;CACxF,IAAI,WACF,eAAe,IAAI,wBAAwB,SAAS;CAEtD,OAAO,MAAM,UAAU;EAAE,GAAG;EAAM,SAAS;CAAe,CAAC;AAC7D"}
@@ -0,0 +1,2 @@
1
+ import { n as CLOUDFLARE_RPC_MAX_BYTES, r as WebSocketMuxer, t as CLOUDFLARE_MESSAGE_MAX_BYTES } from "./chunk-edge-ws-muxer.mjs";
2
+ export { CLOUDFLARE_MESSAGE_MAX_BYTES, CLOUDFLARE_RPC_MAX_BYTES, WebSocketMuxer };