@swf2es/desktop 0.1.22 → 0.1.24

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 (42) hide show
  1. package/dist/main/connections.d.ts +14 -2
  2. package/dist/main/connections.d.ts.map +1 -1
  3. package/dist/main/connections.js +44 -8
  4. package/dist/main/connections.js.map +1 -1
  5. package/dist/main/network.d.ts.map +1 -1
  6. package/dist/main/network.js +2 -1
  7. package/dist/main/network.js.map +1 -1
  8. package/dist/main/proxy.d.ts +16 -1
  9. package/dist/main/proxy.d.ts.map +1 -1
  10. package/dist/main/proxy.js +67 -12
  11. package/dist/main/proxy.js.map +1 -1
  12. package/dist/main/relay.d.ts +55 -0
  13. package/dist/main/relay.d.ts.map +1 -0
  14. package/dist/main/relay.js +140 -0
  15. package/dist/main/relay.js.map +1 -0
  16. package/dist/main/sockets.d.ts.map +1 -1
  17. package/dist/main/sockets.js +21 -5
  18. package/dist/main/sockets.js.map +1 -1
  19. package/dist/main/websockets.d.ts.map +1 -1
  20. package/dist/main/websockets.js +14 -4
  21. package/dist/main/websockets.js.map +1 -1
  22. package/dist/preload/preload.cjs +7 -6
  23. package/dist/preload/preload.cjs.map +1 -1
  24. package/dist/renderer/sockets.d.ts.map +1 -1
  25. package/dist/renderer/sockets.js +31 -21
  26. package/dist/renderer/sockets.js.map +1 -1
  27. package/dist/renderer/websockets.d.ts.map +1 -1
  28. package/dist/renderer/websockets.js +29 -19
  29. package/dist/renderer/websockets.js.map +1 -1
  30. package/dist/shared/api.d.ts +12 -4
  31. package/dist/shared/api.d.ts.map +1 -1
  32. package/package.json +7 -7
  33. package/src/main/connections.ts +55 -10
  34. package/src/main/network.ts +3 -1
  35. package/src/main/proxy.ts +87 -14
  36. package/src/main/relay.ts +169 -0
  37. package/src/main/sockets.ts +49 -29
  38. package/src/main/websockets.ts +17 -4
  39. package/src/preload/preload.cts +10 -8
  40. package/src/renderer/sockets.ts +30 -21
  41. package/src/renderer/websockets.ts +28 -19
  42. package/src/shared/api.ts +13 -4
@@ -147,13 +147,21 @@ export type SocketEvent = {
147
147
  type: "error";
148
148
  message: string;
149
149
  };
150
+ /**
151
+ * Events as the shell sends them, gathered over a millisecond at most:
152
+ * each with its connection's id, in the order they came.
153
+ */
154
+ export type ConnectionEvents<E> = readonly (readonly [id: number, event: E])[];
150
155
  /** flash.net.Socket's TCP connections, opened by the shell. */
151
156
  export interface DesktopSockets {
152
- /** A connection's id; its events follow, an "error" one if the shell refuses it. */
153
- connect(host: string, port: number): number;
157
+ /**
158
+ * A connection's id; its events follow, an "error" one if the shell refuses it. `noDelay` is
159
+ * tcpNoDelay as the SWF set it, absent where it did not: the shell then sends at once.
160
+ */
161
+ connect(host: string, port: number, noDelay?: boolean): number;
154
162
  send(id: number, bytes: Uint8Array): void;
155
163
  close(id: number): void;
156
- onEvent(listener: (id: number, event: SocketEvent) => void): void;
164
+ onEvents(listener: (events: ConnectionEvents<SocketEvent>) => void): void;
157
165
  }
158
166
  /** What befalls a WebSocket, as player's WebSocketEvents has it. */
159
167
  export type WebSocketEvent = {
@@ -175,7 +183,7 @@ export interface DesktopWebSockets {
175
183
  /** A text message for a string, a binary one for bytes. */
176
184
  send(id: number, data: string | Uint8Array): void;
177
185
  close(id: number, code?: number): void;
178
- onEvent(listener: (id: number, event: WebSocketEvent) => void): void;
186
+ onEvents(listener: (events: ConnectionEvents<WebSocketEvent>) => void): void;
179
187
  }
180
188
  /** A request of a SWF's, as player's FetchRequest has it. */
181
189
  export interface NetworkRequest {
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/shared/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/shared/api.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swf2es/desktop",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "swf2es's desktop app: local and remote SWFs, and AIR applications, in an Electron window",
5
5
  "keywords": [
6
6
  "swf",
@@ -35,12 +35,12 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "electron": "44.10.2",
38
- "@swf2es/codegen": "0.1.22",
39
- "@swf2es/context3d": "0.1.22",
40
- "@swf2es/format": "0.1.22",
41
- "@swf2es/player": "0.1.22",
42
- "@swf2es/player-hosts": "0.1.22",
43
- "@swf2es/web": "0.1.22"
38
+ "@swf2es/format": "0.1.24",
39
+ "@swf2es/codegen": "0.1.24",
40
+ "@swf2es/player": "0.1.24",
41
+ "@swf2es/context3d": "0.1.24",
42
+ "@swf2es/player-hosts": "0.1.24",
43
+ "@swf2es/web": "0.1.24"
44
44
  },
45
45
  "scripts": {
46
46
  "fetch-electron": "node node_modules/electron/install.js",
@@ -2,9 +2,9 @@
2
2
  // the proxy (--proxy, proxy.ts): what the socket bridge (sockets.ts) holds
3
3
  // for each page, apart from Electron so that node runs it in the tests
4
4
  // (tests/desktop/connections.test.ts).
5
- import { connect, type Socket } from "node:net";
5
+ import { connect, type OnReadOpts, type Socket } from "node:net";
6
6
  import type { SocketEvent } from "../shared/api.js";
7
- import { connectThrough, type ProxyConfig, type ProxyError } from "./proxy.ts";
7
+ import { type ProxyConfig, type ProxyError, tunnelThrough } from "./proxy.ts";
8
8
 
9
9
  /** As many as a page may hold open at once; past it a connection is refused. */
10
10
  const MAX_OPEN = 64;
@@ -17,6 +17,13 @@ const MAX_OPEN = 64;
17
17
  */
18
18
  export const MAX_BUFFERED = 4 * 1024 * 1024;
19
19
 
20
+ /**
21
+ * Each connection's read buffer, read into again and again, so that a read
22
+ * makes one store, its copy for the page, where Node's stream would make a
23
+ * Buffer for it besides.
24
+ */
25
+ const READ_BUFFER = 64 * 1024;
26
+
20
27
  const SOCKET_ERROR = "Error #2031: Socket Error.";
21
28
 
22
29
  /** Whether the page asked for a host name of DNS's length and a TCP port. */
@@ -64,8 +71,12 @@ export class Connections {
64
71
  /**
65
72
  * Connect `id` to `host`:`port`, each checked, as the page asks, at `address` where the host's
66
73
  * was pinned (a remote SWF's socket policy came from there); a refusal is an error, then a close.
74
+ * It sends at once (TCP_NODELAY) unless `noDelay` is false, as a SWF's tcpNoDelay set so asks:
75
+ * with Nagle's algorithm, node's default, the second of two small writes waits for the peer to
76
+ * acknowledge the first, which a peer that has nothing to send back delays (some 40 ms on Linux,
77
+ * up to 200 ms on Windows), and a chatty protocol's every exchange with it.
67
78
  */
68
- connect(id: number, host: unknown, port: unknown, address?: string): void {
79
+ connect(id: number, host: unknown, port: unknown, address?: string, noDelay = true): void {
69
80
  if (
70
81
  !validEndpoint(host, port) ||
71
82
  this.sockets.has(id) ||
@@ -78,11 +89,16 @@ export class Connections {
78
89
 
79
90
  const target = address ?? host;
80
91
  if (this.proxy) {
81
- this.connectThrough(this.proxy, id, target, port as number);
92
+ this.connectThrough(this.proxy, id, target, port as number, noDelay);
82
93
  return;
83
94
  }
84
95
 
85
- const socket = connect({ host: target, port: port as number });
96
+ const socket = connect({
97
+ host: target,
98
+ port: port as number,
99
+ noDelay,
100
+ onread: this.reader(id),
101
+ });
86
102
  socket.on("connect", () =>
87
103
  this.tell(id, {
88
104
  type: "open",
@@ -99,19 +115,28 @@ export class Connections {
99
115
  * Connect `id` through `proxy`, by the address where one was pinned, else
100
116
  * by name for the proxy to resolve. Its peer is the target: the proxy's
101
117
  * address is no business of the SWF's. A proxy that fails is a refusal,
102
- * said with why.
118
+ * said with why. Nagle's algorithm is left on through the handshake,
119
+ * which never writes twice without an answer between, so nothing in it
120
+ * waits; it goes off as the tunnel opens, before the SWF's first byte.
103
121
  */
104
- private connectThrough(proxy: ProxyConfig, id: number, target: string, port: number): void {
122
+ private connectThrough(
123
+ proxy: ProxyConfig,
124
+ id: number,
125
+ target: string,
126
+ port: number,
127
+ noDelay: boolean,
128
+ ): void {
105
129
  const opening = new AbortController();
106
130
  this.opening.set(id, opening);
107
- connectThrough(proxy, target, port, { signal: opening.signal }).then(
108
- (socket) => {
131
+ tunnelThrough(proxy, target, port, { signal: opening.signal, onread: this.reader(id) }).then(
132
+ ({ socket, rest }) => {
109
133
  if (this.opening.get(id) !== opening) {
110
134
  socket.destroy();
111
135
  return;
112
136
  }
113
137
 
114
138
  this.opening.delete(id);
139
+ socket.setNoDelay(noDelay);
115
140
  this.attach(id, socket);
116
141
  this.tell(id, {
117
142
  type: "open",
@@ -120,6 +145,12 @@ export class Connections {
120
145
  remoteAddress: target,
121
146
  remotePort: port,
122
147
  });
148
+ // What came with the proxy's reply is the connection's first.
149
+ if (rest.length > 0) {
150
+ this.tell(id, { type: "data", bytes: rest });
151
+ }
152
+
153
+ socket.resume();
123
154
  },
124
155
  (error: ProxyError) => {
125
156
  if (this.opening.get(id) !== opening) {
@@ -133,9 +164,23 @@ export class Connections {
133
164
  );
134
165
  }
135
166
 
167
+ /**
168
+ * How `id`'s socket is read: into a buffer of its own, each read copied
169
+ * out at its length for the page. It never pauses the socket, as its
170
+ * stream never did.
171
+ */
172
+ private reader(id: number): OnReadOpts {
173
+ return {
174
+ buffer: new Uint8Array(READ_BUFFER),
175
+ callback: (length, buffer) => {
176
+ this.tell(id, { type: "data", bytes: new Uint8Array(buffer.subarray(0, length)) });
177
+ return true;
178
+ },
179
+ };
180
+ }
181
+
136
182
  private attach(id: number, socket: Socket): void {
137
183
  this.sockets.set(id, socket);
138
- socket.on("data", (bytes) => this.tell(id, { type: "data", bytes: new Uint8Array(bytes) }));
139
184
  socket.on("error", () => this.tell(id, { type: "error", message: SOCKET_ERROR }));
140
185
  socket.on("close", () => {
141
186
  // Only the socket under this id: a page that reused it has another by now.
@@ -78,6 +78,8 @@ import {
78
78
  } from "./policy.ts";
79
79
  import { connectThrough, type ProxyConfig, type ProxyError } from "./proxy.ts";
80
80
 
81
+ const textDecoder = new TextDecoder();
82
+
81
83
  /** What Flash Player 32 reported, sent as it sent it. */
82
84
  const FLASH_VERSION = "32,0,0,465";
83
85
  const USER_AGENT = "Mozilla/5.0 (compatible; swf2es) Shockwave Flash";
@@ -644,7 +646,7 @@ export class Network {
644
646
  return null;
645
647
  }
646
648
 
647
- const policy = parsePolicy(new TextDecoder().decode(response.bytes));
649
+ const policy = parsePolicy(textDecoder.decode(response.bytes));
648
650
  return policy && { policy, url, contentType, permitted };
649
651
  }
650
652
 
package/src/main/proxy.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  // command line's alone: never kept, and never said.
8
8
  //
9
9
  // Without Electron, so that node tests it (tests/desktop/proxy.test.ts).
10
- import { connect, isIP, isIPv4, type Socket } from "node:net";
10
+ import { connect, isIP, isIPv4, type OnReadOpts, type Socket } from "node:net";
11
11
  import { domainToASCII } from "node:url";
12
12
 
13
13
  export interface ProxyConfig {
@@ -200,26 +200,39 @@ function ipv6Bytes(address: string): Buffer {
200
200
  return bytes;
201
201
  }
202
202
 
203
- /** Reads a socket's bytes as a handshake needs them, and gives back what it did not need. */
203
+ /**
204
+ * Reads a socket's bytes as a handshake needs them, and gives back what it
205
+ * did not need. A socket read by its stream is read here by "readable"; one
206
+ * read by `onread` (net.connect's option) is fed what it reads.
207
+ */
204
208
  class Reader {
205
209
  private buffered = Buffer.alloc(0);
206
210
  private wake: (() => void) | null = null;
207
211
  private ended = false;
208
212
  private readonly socket: Socket;
213
+ private readonly streamed: boolean;
209
214
 
210
- constructor(socket: Socket) {
215
+ constructor(socket: Socket, streamed: boolean) {
211
216
  this.socket = socket;
212
- socket.on("readable", this.readable);
217
+ this.streamed = streamed;
218
+ if (streamed) {
219
+ socket.on("readable", this.readable);
220
+ }
221
+
213
222
  socket.on("end", this.end);
214
223
  }
215
224
 
216
225
  private readonly readable = () => {
217
226
  for (let chunk = this.socket.read() as Buffer | null; chunk; chunk = this.socket.read()) {
218
- this.buffered = Buffer.concat([this.buffered, chunk]);
227
+ this.feed(chunk);
219
228
  }
229
+ };
220
230
 
231
+ /** Bytes read, copied: an `onread` buffer is read into again. */
232
+ feed(bytes: Uint8Array): void {
233
+ this.buffered = Buffer.concat([this.buffered, bytes]);
221
234
  this.wake?.();
222
- };
235
+ }
223
236
 
224
237
  private readonly end = () => {
225
238
  this.ended = true;
@@ -263,13 +276,25 @@ class Reader {
263
276
  }
264
277
  }
265
278
 
266
- /** Stop reading; what came past the handshake goes back, for the socket's next reader. */
267
- release(): void {
279
+ /**
280
+ * Stop reading. What came past the handshake goes back to the stream for
281
+ * its next reader, or, for a socket read by `onread`, is given back, a
282
+ * copy of its own.
283
+ */
284
+ release(): Uint8Array {
268
285
  this.socket.removeListener("readable", this.readable);
269
286
  this.socket.removeListener("end", this.end);
270
- if (this.buffered.length > 0) {
271
- this.socket.unshift(this.buffered);
287
+ const rest = this.buffered;
288
+ this.buffered = Buffer.alloc(0);
289
+ if (!this.streamed) {
290
+ return new Uint8Array(rest);
291
+ }
292
+
293
+ if (rest.length > 0) {
294
+ this.socket.unshift(rest);
272
295
  }
296
+
297
+ return new Uint8Array(0);
273
298
  }
274
299
  }
275
300
 
@@ -427,6 +452,32 @@ export function connectThrough(
427
452
  port: number,
428
453
  options: { signal?: AbortSignal; timeout?: number } = {},
429
454
  ): Promise<Socket> {
455
+ return tunnel(proxy, host, port, options, null).then(({ socket }) => socket);
456
+ }
457
+
458
+ /**
459
+ * As connectThrough, the tunnel's bytes read by `onread` (net.connect's
460
+ * option) rather than its stream: the socket comes paused, with the bytes
461
+ * that came with the proxy's reply, for its reader to take before it
462
+ * resumes the socket, so that nothing is read before whoever reads is
463
+ * ready. The handshake reads through `onread`'s buffer too.
464
+ */
465
+ export function tunnelThrough(
466
+ proxy: ProxyConfig,
467
+ host: string,
468
+ port: number,
469
+ options: { signal?: AbortSignal; timeout?: number; onread: OnReadOpts },
470
+ ): Promise<{ socket: Socket; rest: Uint8Array }> {
471
+ return tunnel(proxy, host, port, options, options.onread);
472
+ }
473
+
474
+ function tunnel(
475
+ proxy: ProxyConfig,
476
+ host: string,
477
+ port: number,
478
+ options: { signal?: AbortSignal; timeout?: number },
479
+ onread: OnReadOpts | null,
480
+ ): Promise<{ socket: Socket; rest: Uint8Array }> {
430
481
  const where = describeProxy(proxy);
431
482
  return new Promise((resolve, reject) => {
432
483
  let target: ReturnType<typeof targetHost>;
@@ -448,8 +499,26 @@ export function connectThrough(
448
499
  return;
449
500
  }
450
501
 
451
- const socket = connect({ host: proxy.host, port: proxy.port });
452
- const reader = new Reader(socket);
502
+ // Until the tunnel is made its bytes are the handshake's; then onread's own.
503
+ let tunnelled = false;
504
+ const socket = onread
505
+ ? connect({
506
+ host: proxy.host,
507
+ port: proxy.port,
508
+ onread: {
509
+ buffer: onread.buffer,
510
+ callback: (length, buffer) => {
511
+ if (tunnelled) {
512
+ return onread.callback(length, buffer);
513
+ }
514
+
515
+ reader.feed(buffer.subarray(0, length));
516
+ return true;
517
+ },
518
+ },
519
+ })
520
+ : connect({ host: proxy.host, port: proxy.port });
521
+ const reader = new Reader(socket, !onread);
453
522
  let settled = false;
454
523
  const finish = (error: ProxyError | null) => {
455
524
  if (settled) {
@@ -466,8 +535,12 @@ export function connectThrough(
466
535
  socket.destroy();
467
536
  reject(error);
468
537
  } else {
469
- reader.release();
470
- resolve(socket);
538
+ if (onread) {
539
+ socket.pause();
540
+ tunnelled = true;
541
+ }
542
+
543
+ resolve({ socket, rest: reader.release() });
471
544
  }
472
545
  };
473
546
  const abort = () => finish(new ProxyError("aborted", false));
@@ -0,0 +1,169 @@
1
+ // What a page's connections say to it, gathered into one IPC message a
2
+ // millisecond at most: each message is serialized, posted and run as a
3
+ // task of the page's on its own, so a socket read or a WebSocket message
4
+ // apiece cost a page with busy connections tens of thousands a second.
5
+ // Apart from Electron so that node runs it in the tests
6
+ // (tests/desktop/relay.test.ts).
7
+
8
+ /** Each connection's events, by its id, in the order they came. */
9
+ export type Batch<E> = [id: number, event: E][];
10
+
11
+ /**
12
+ * A batch that holds this many bytes goes at once, not when it would: a
13
+ * busy socket is not kept from its page, and neither the main process nor
14
+ * one message grows past it by more than a read.
15
+ */
16
+ export const FLUSH_AT = 256 * 1024;
17
+
18
+ /**
19
+ * The least time between two batches, in milliseconds. The first event
20
+ * after a quiet spell goes at the end of its turn, so that a request's
21
+ * answer waits for nothing; what follows within the window waits for its
22
+ * end, and goes with the rest that came by then. Socket reads that cost
23
+ * little come many to a millisecond, each with a few bytes: a turn's
24
+ * batch alone left one message for most of them. On Windows the timer
25
+ * may wait for the system's resolution, about 16 ms: docs/architecture/
26
+ * desktop.md says why that does.
27
+ */
28
+ export const WINDOW = 1;
29
+
30
+ interface Joining<E> {
31
+ /** Where its event is in the batch. */
32
+ at: number;
33
+ chunks: Uint8Array[];
34
+ length: number;
35
+ wrap: (bytes: Uint8Array) => E;
36
+ }
37
+
38
+ export class Relay<E> {
39
+ private readonly send: (batch: Batch<E>) => void;
40
+ private readonly flushAt: number;
41
+ private batch: Batch<E> = [];
42
+ private size = 0;
43
+ /** Each connection's bytes, while nothing else of it has come after them in the batch. */
44
+ private readonly joining = new Map<number, Joining<E>>();
45
+ private readonly window: number;
46
+ private immediate: ReturnType<typeof setImmediate> | null = null;
47
+ private timer: ReturnType<typeof setTimeout> | null = null;
48
+ /** When the last batch went, by performance.now(). */
49
+ private sent = Number.NEGATIVE_INFINITY;
50
+
51
+ /** `send` takes each batch, never an empty one. */
52
+ constructor(send: (batch: Batch<E>) => void, flushAt = FLUSH_AT, window = WINDOW) {
53
+ this.send = send;
54
+ this.flushAt = flushAt;
55
+ this.window = window;
56
+ }
57
+
58
+ /** `event` for `id`, after whatever came for it before; `size` of its bytes count to FLUSH_AT. */
59
+ push(id: number, event: E, size = 0): void {
60
+ this.seal(id);
61
+ this.batch.push([id, event]);
62
+ this.added(size);
63
+ }
64
+
65
+ /**
66
+ * Bytes read for `id`, which it no longer holds: one event, as `wrap`
67
+ * makes it, with the bytes read for it before them in this batch.
68
+ */
69
+ bytes(id: number, chunk: Uint8Array, wrap: (bytes: Uint8Array) => E): void {
70
+ const joining = this.joining.get(id);
71
+ if (joining) {
72
+ joining.chunks.push(chunk);
73
+ joining.length += chunk.length;
74
+ } else {
75
+ this.joining.set(id, { at: this.batch.length, chunks: [chunk], length: chunk.length, wrap });
76
+ this.batch.push([id, wrap(chunk)]);
77
+ }
78
+
79
+ this.added(chunk.length);
80
+ }
81
+
82
+ /** Send what is gathered now. */
83
+ flush(): void {
84
+ this.unschedule();
85
+ if (this.batch.length === 0) {
86
+ return;
87
+ }
88
+
89
+ for (const id of this.joining.keys()) {
90
+ this.seal(id);
91
+ }
92
+
93
+ const batch = this.batch;
94
+ this.batch = [];
95
+ this.size = 0;
96
+ this.sent = performance.now();
97
+ this.send(batch);
98
+ }
99
+
100
+ /** Forget what is gathered: its page is gone, and the next numbers its connections from 1. */
101
+ drop(): void {
102
+ this.unschedule();
103
+ this.batch = [];
104
+ this.size = 0;
105
+ this.joining.clear();
106
+ }
107
+
108
+ private added(size: number): void {
109
+ this.size += size;
110
+ if (this.size >= this.flushAt) {
111
+ this.flush();
112
+ return;
113
+ }
114
+
115
+ if (this.immediate || this.timer) {
116
+ return;
117
+ }
118
+
119
+ const wait = this.sent + this.window - performance.now();
120
+ if (wait > 0) {
121
+ this.timer = setTimeout(this.flushing, wait);
122
+ } else {
123
+ this.immediate = setImmediate(this.flushing);
124
+ }
125
+ }
126
+
127
+ private readonly flushing = () => {
128
+ this.immediate = null;
129
+ this.timer = null;
130
+ this.flush();
131
+ };
132
+
133
+ private unschedule(): void {
134
+ if (this.immediate) {
135
+ clearImmediate(this.immediate);
136
+ this.immediate = null;
137
+ }
138
+
139
+ if (this.timer) {
140
+ clearTimeout(this.timer);
141
+ this.timer = null;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * `id`'s bytes as one event, the chunks joined where there are more than
147
+ * one: a second copy of theirs, but one event for the SWF rather than one
148
+ * for each read, which costs it far more than the copy.
149
+ */
150
+ private seal(id: number): void {
151
+ const joining = this.joining.get(id);
152
+ if (!joining) {
153
+ return;
154
+ }
155
+
156
+ this.joining.delete(id);
157
+
158
+ if (joining.chunks.length > 1) {
159
+ const bytes = new Uint8Array(joining.length);
160
+ let offset = 0;
161
+ for (let i = 0; i < joining.chunks.length; i++) {
162
+ bytes.set(joining.chunks[i], offset);
163
+ offset += joining.chunks[i].length;
164
+ }
165
+
166
+ this.batch[joining.at][1] = joining.wrap(bytes);
167
+ }
168
+ }
169
+ }