mockrtc 0.2.0 → 0.3.1

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 (84) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/dist/client/mockrtc-admin-request-builder.d.ts +7 -1
  3. package/dist/client/mockrtc-admin-request-builder.js +30 -0
  4. package/dist/client/mockrtc-admin-request-builder.js.map +1 -1
  5. package/dist/client/mockrtc-client.d.ts +8 -4
  6. package/dist/client/mockrtc-client.js +21 -9
  7. package/dist/client/mockrtc-client.js.map +1 -1
  8. package/dist/handling/handler-builder.d.ts +16 -9
  9. package/dist/handling/handler-builder.js +11 -1
  10. package/dist/handling/handler-builder.js.map +1 -1
  11. package/dist/handling/handler-step-definitions.d.ts +40 -23
  12. package/dist/handling/handler-step-definitions.js +61 -19
  13. package/dist/handling/handler-step-definitions.js.map +1 -1
  14. package/dist/handling/handler-steps.d.ts +11 -1
  15. package/dist/handling/handler-steps.js +33 -16
  16. package/dist/handling/handler-steps.js.map +1 -1
  17. package/dist/main-browser.d.ts +1 -0
  18. package/dist/main-browser.js +2 -1
  19. package/dist/main-browser.js.map +1 -1
  20. package/dist/main.d.ts +5 -3
  21. package/dist/main.js +2 -1
  22. package/dist/main.js.map +1 -1
  23. package/dist/matching/matcher-definitions.d.ts +51 -0
  24. package/dist/matching/matcher-definitions.js +94 -0
  25. package/dist/matching/matcher-definitions.js.map +1 -0
  26. package/dist/matching/matchers.d.ts +27 -0
  27. package/dist/matching/matchers.js +87 -0
  28. package/dist/matching/matchers.js.map +1 -0
  29. package/dist/mockrtc-base.d.ts +16 -0
  30. package/dist/mockrtc-base.js +19 -0
  31. package/dist/mockrtc-base.js.map +1 -0
  32. package/dist/mockrtc-peer.d.ts +9 -2
  33. package/dist/mockrtc.d.ts +62 -0
  34. package/dist/mockrtc.js.map +1 -1
  35. package/dist/rule-builder.d.ts +86 -0
  36. package/dist/rule-builder.js +113 -0
  37. package/dist/rule-builder.js.map +1 -0
  38. package/dist/server/mockrtc-admin-plugin.js +18 -1
  39. package/dist/server/mockrtc-admin-plugin.js.map +1 -1
  40. package/dist/server/mockrtc-server-peer.d.ts +1 -1
  41. package/dist/server/mockrtc-server-peer.js +19 -7
  42. package/dist/server/mockrtc-server-peer.js.map +1 -1
  43. package/dist/server/mockrtc-server.d.ts +12 -5
  44. package/dist/server/mockrtc-server.js +53 -18
  45. package/dist/server/mockrtc-server.js.map +1 -1
  46. package/dist/webrtc/datachannel-stream.d.ts +2 -0
  47. package/dist/webrtc/datachannel-stream.js +15 -1
  48. package/dist/webrtc/datachannel-stream.js.map +1 -1
  49. package/dist/webrtc/mediatrack-stream.d.ts +2 -0
  50. package/dist/webrtc/mediatrack-stream.js +15 -1
  51. package/dist/webrtc/mediatrack-stream.js.map +1 -1
  52. package/dist/webrtc/mockrtc-connection.js +1 -1
  53. package/dist/webrtc/mockrtc-connection.js.map +1 -1
  54. package/dist/webrtc/rtc-connection.d.ts +6 -2
  55. package/dist/webrtc/rtc-connection.js +21 -21
  56. package/dist/webrtc/rtc-connection.js.map +1 -1
  57. package/dist/webrtc-hooks.js +8 -2
  58. package/dist/webrtc-hooks.js.map +1 -1
  59. package/package.json +2 -2
  60. package/src/client/mockrtc-admin-request-builder.ts +49 -1
  61. package/src/client/mockrtc-client.ts +28 -10
  62. package/src/handling/handler-builder.ts +22 -10
  63. package/src/handling/handler-step-definitions.ts +81 -23
  64. package/src/handling/handler-steps.ts +42 -15
  65. package/src/main-browser.ts +1 -0
  66. package/src/main.ts +5 -1
  67. package/src/matching/matcher-definitions.ts +109 -0
  68. package/src/matching/matchers.ts +118 -0
  69. package/src/mockrtc-base.ts +49 -0
  70. package/src/mockrtc-peer.ts +9 -2
  71. package/src/mockrtc.ts +72 -0
  72. package/src/rule-builder.ts +142 -0
  73. package/src/server/mockrtc-admin-plugin.ts +41 -3
  74. package/src/server/mockrtc-server-peer.ts +28 -18
  75. package/src/server/mockrtc-server.ts +71 -15
  76. package/src/webrtc/datachannel-stream.ts +15 -1
  77. package/src/webrtc/mediatrack-stream.ts +15 -1
  78. package/src/webrtc/mockrtc-connection.ts +1 -1
  79. package/src/webrtc/rtc-connection.ts +37 -20
  80. package/src/webrtc-hooks.ts +8 -2
  81. package/test/integration/events.spec.ts +3 -1
  82. package/test/integration/matching.spec.ts +189 -0
  83. package/test/integration/proxy.spec.ts +4 -2
  84. package/test/integration/send-steps.spec.ts +51 -0
@@ -20,6 +20,10 @@ import {
20
20
  import { DataChannelStream } from './datachannel-stream';
21
21
  import { MediaTrackStream } from './mediatrack-stream';
22
22
 
23
+ export type ParsedSDP = {
24
+ parsedSdp: SDP.SessionDescription;
25
+ };
26
+
23
27
  /**
24
28
  * An RTC connection is a single connection. This base class defines the raw connection management and
25
29
  * tracking logic for a generic connection. The MockRTCConnection subclass extends this and adds
@@ -34,8 +38,8 @@ export class RTCConnection extends EventEmitter {
34
38
  private rawConn: NodeDataChannel.PeerConnection | null
35
39
  = new NodeDataChannel.PeerConnection("MockRTCConnection", { iceServers: [] });
36
40
 
37
- private remoteDescription: RTCSessionDescriptionInit | undefined;
38
- private localDescription: MockRTCSessionDescription | undefined;
41
+ private remoteDescription: RTCSessionDescriptionInit & ParsedSDP | undefined;
42
+ private localDescription: MockRTCSessionDescription & ParsedSDP | undefined;
39
43
 
40
44
  private _connectionMetadata: ConnectionMetadata = {};
41
45
  public get metadata() {
@@ -132,13 +136,13 @@ export class RTCConnection extends EventEmitter {
132
136
  channelStream.on('error', (error) => {
133
137
  console.error('Channel error:', error);
134
138
  });
139
+ this.emit('channel-created', channelStream);
140
+ this.emit(`${options.isLocal ? 'local' : 'remote'}-channel-created`, channelStream);
135
141
 
136
- this.emit('channel-open', channelStream);
137
- if (options.isLocal) {
138
- this.emit('local-channel-open', channelStream);
139
- } else {
140
- this.emit('remote-channel-open', channelStream);
141
- }
142
+ channelStream.once('channel-open', () => {
143
+ this.emit('channel-open', channelStream);
144
+ this.emit(`${options.isLocal ? 'local' : 'remote'}-channel-open`, channelStream);
145
+ });
142
146
 
143
147
  return channelStream;
144
148
  }
@@ -150,7 +154,7 @@ export class RTCConnection extends EventEmitter {
150
154
  trackStream.on('close', () => {
151
155
  const trackIndex = this.trackedMediaTracks.findIndex(c => c.stream === trackStream);
152
156
  if (trackIndex !== -1) {
153
- this.trackedChannels.splice(trackIndex, 1);
157
+ this.trackedMediaTracks.splice(trackIndex, 1);
154
158
  }
155
159
  });
156
160
 
@@ -158,12 +162,13 @@ export class RTCConnection extends EventEmitter {
158
162
  console.error('Media track error:', error);
159
163
  });
160
164
 
161
- this.emit('track-open', trackStream);
162
- if (options.isLocal) {
163
- this.emit('local-track-open', trackStream);
164
- } else {
165
- this.emit('remote-track-open', trackStream);
166
- }
165
+ this.emit('track-created', trackStream);
166
+ this.emit(`${options.isLocal ? 'local' : 'remote'}-track-created`, trackStream);
167
+
168
+ trackStream.once('track-open', () => {
169
+ this.emit('track-open', trackStream);
170
+ this.emit(`${options.isLocal ? 'local' : 'remote'}-track-open`, trackStream);
171
+ });
167
172
 
168
173
  return trackStream;
169
174
  }
@@ -171,7 +176,10 @@ export class RTCConnection extends EventEmitter {
171
176
  setRemoteDescription(description: RTCSessionDescriptionInit) {
172
177
  if (!this.rawConn) throw new Error("Can't set remote description after connection is closed");
173
178
 
174
- this.remoteDescription = description;
179
+ this.remoteDescription = {
180
+ ...description,
181
+ parsedSdp: SDP.parse(description.sdp ?? '')
182
+ };
175
183
  const { type: offerType, sdp: offerSdp } = description;
176
184
  if (!offerSdp) throw new Error("Cannot set MockRTC peer description without providing an SDP");
177
185
  this.rawConn.setRemoteDescription(offerSdp, offerType[0].toUpperCase() + offerType.slice(1) as any);
@@ -207,7 +215,10 @@ export class RTCConnection extends EventEmitter {
207
215
 
208
216
  const sessionDescription = this.rawConn.localDescription() as MockRTCSessionDescription;
209
217
  setupChannel?.close(); // Close the temporary setup channel, if we created one
210
- this.localDescription = sessionDescription;
218
+ this.localDescription = {
219
+ ...sessionDescription,
220
+ parsedSdp: SDP.parse(sessionDescription.sdp ?? '')
221
+ };
211
222
  return sessionDescription;
212
223
  }
213
224
 
@@ -324,7 +335,10 @@ export class RTCConnection extends EventEmitter {
324
335
  mirrorMediaParams(offerToMirror, offerSDP);
325
336
  localDesc.sdp = SDP.write(offerSDP);
326
337
 
327
- this.localDescription = localDesc as MockRTCSessionDescription;
338
+ this.localDescription = {
339
+ ...localDesc as MockRTCSessionDescription,
340
+ parsedSdp: offerSDP
341
+ };
328
342
  return this.localDescription;
329
343
  }
330
344
 
@@ -337,7 +351,10 @@ export class RTCConnection extends EventEmitter {
337
351
 
338
352
  localDesc.sdp = SDP.write(answerSDP);
339
353
 
340
- this.localDescription = localDesc as MockRTCSessionDescription;
354
+ this.localDescription = {
355
+ ...localDesc as MockRTCSessionDescription,
356
+ parsedSdp: answerSDP
357
+ };
341
358
  return this.localDescription;
342
359
  }
343
360
 
@@ -413,7 +430,7 @@ export class RTCConnection extends EventEmitter {
413
430
  this.localDescription = undefined;
414
431
 
415
432
  if (rawConn.state() === 'closed') return;
416
- rawConn.close();
433
+ rawConn.destroy();
417
434
  this.emit('connection-closed');
418
435
  }
419
436
 
@@ -163,7 +163,10 @@ export function hookWebRTCConnection(conn: RTCPeerConnection, mockPeer: MockRTCP
163
163
  mockOffer = mockPeer.createOffer({
164
164
  mirrorSDP: remoteDescription.sdp,
165
165
  addDataStream: true,
166
- connectionMetadata: { userAgent: navigator.userAgent }
166
+ connectionMetadata: {
167
+ userAgent: navigator.userAgent,
168
+ sourceURL: window.location.href
169
+ }
167
170
  });
168
171
 
169
172
  await _setRemoteDescription((await mockOffer).offer);
@@ -176,7 +179,10 @@ export function hookWebRTCConnection(conn: RTCPeerConnection, mockPeer: MockRTCP
176
179
  // Complete the internal <-> mock connection:
177
180
  mockPeer.answerOffer(realOffer, {
178
181
  mirrorSDP: remoteDescription.sdp,
179
- connectionMetadata: { userAgent: navigator.userAgent }
182
+ connectionMetadata: {
183
+ userAgent: navigator.userAgent,
184
+ sourceURL: window.location.href
185
+ }
180
186
  }).then(({ answer }) => _setRemoteDescription(answer))
181
187
  ]);
182
188
 
@@ -32,7 +32,8 @@ describe("MockRTC event subscriptions", function () {
32
32
 
33
33
  const { offer, setAnswer } = await mockPeer.createOffer({
34
34
  connectionMetadata: {
35
- userAgent: navigator.userAgent
35
+ userAgent: navigator.userAgent,
36
+ sourceURL: 'https://example.com/'
36
37
  }
37
38
  });
38
39
  await localConnection.setRemoteDescription(offer);
@@ -58,6 +59,7 @@ describe("MockRTC event subscriptions", function () {
58
59
  expect(connectionEvent.timingEvents.disconnectTimestamp).to.equal(undefined);
59
60
 
60
61
  expect(connectionEvent.metadata.userAgent).to.equal(navigator.userAgent);
62
+ expect(connectionEvent.metadata.sourceURL).to.equal('https://example.com/');
61
63
 
62
64
  const { selectedLocalCandidate, selectedRemoteCandidate } = connectionEvent;
63
65
  [selectedLocalCandidate, selectedRemoteCandidate].forEach((candidate) => {
@@ -1,3 +1,8 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
1
6
  import {
2
7
  MockRTC,
3
8
  expect,
@@ -65,4 +70,188 @@ describe("Connection rule matching", () => {
65
70
  'local message 3',
66
71
  ]);
67
72
  });
73
+
74
+ it("can match data connections", async () => {
75
+ // Explicitly hard-close media connections:
76
+ await mockRTC.forConnections()
77
+ .withMedia()
78
+ .thenClose();
79
+
80
+ // Send a message on data channels only:
81
+ await mockRTC.forConnections()
82
+ .withDataChannels()
83
+ .waitForChannel()
84
+ .thenSend('bye');
85
+
86
+ const matchingPeer = await mockRTC.getMatchingPeer();
87
+
88
+ // Create a local data connection:
89
+ const localConn = new RTCPeerConnection();
90
+
91
+ const dataChannel = localConn.createDataChannel("dataChannel");
92
+
93
+ const messagePromise = new Promise((resolve) => {
94
+ dataChannel.addEventListener('message', ({ data }) => resolve(data));
95
+ });
96
+
97
+ const localOffer = await localConn.createOffer();
98
+ await localConn.setLocalDescription(localOffer);
99
+ const { answer } = await matchingPeer.answerOffer(localOffer);
100
+ await localConn.setRemoteDescription(answer);
101
+
102
+ // Wait until the matching handler sends the configured message:
103
+ const receivedMessage = await messagePromise;
104
+ expect(receivedMessage).to.equal('bye');
105
+ });
106
+
107
+ it("can match media connections", async () => {
108
+ // Explicitly hard-close data connections:
109
+ await mockRTC.forConnections()
110
+ .withDataChannels()
111
+ .thenClose();
112
+
113
+ // Send a message on media connections only:
114
+ await mockRTC.forConnections()
115
+ .withMedia()
116
+ .thenEcho();
117
+
118
+ const matchingPeer = await mockRTC.getMatchingPeer();
119
+
120
+ // Create a local connection:
121
+ const localConn = new RTCPeerConnection();
122
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
123
+ localConn.addTrack(stream.getTracks()[0], stream);
124
+
125
+ // Turn incoming tracks into readable streams of frames:
126
+ const mediaStreamPromise = new Promise<ReadableStream<VideoFrame>>((resolve) => {
127
+ localConn.addEventListener('track', ({ track }) => {
128
+ const streamProcessor = new MediaStreamTrackProcessor({
129
+ track: track as MediaStreamVideoTrack
130
+ });
131
+ resolve(streamProcessor.readable);
132
+ });
133
+ });
134
+
135
+ // Connect the local connection to the matching peer:
136
+ const localOffer = await localConn.createOffer();
137
+ await localConn.setLocalDescription(localOffer);
138
+ const { answer } = await matchingPeer.answerOffer(localOffer);
139
+ await localConn.setRemoteDescription(answer);
140
+
141
+ // Check that our video is mirrored as expected:
142
+ const localMedia = await mediaStreamPromise;
143
+ const { value: localFrame } = await localMedia!.getReader().read();
144
+ expect(localFrame!.displayHeight).to.be.greaterThanOrEqual(240);
145
+ expect(localFrame!.displayWidth).to.be.greaterThanOrEqual(320);
146
+ });
147
+
148
+ it("can match connections by host", async () => {
149
+ // Send a message for connections made from example.com pages:
150
+ await mockRTC.forConnections()
151
+ .fromPageHostname('example.com')
152
+ .waitForChannel()
153
+ .thenSend('hello example.com');
154
+
155
+ // Close any other connections:
156
+ await mockRTC.forConnections()
157
+ .thenClose();
158
+
159
+ const matchingPeer = await mockRTC.getMatchingPeer();
160
+
161
+ // Create a local data connection:
162
+ const localConn = new RTCPeerConnection();
163
+
164
+ const dataChannel = localConn.createDataChannel("dataChannel");
165
+
166
+ const messagePromise = new Promise((resolve) => {
167
+ dataChannel.addEventListener('message', ({ data }) => resolve(data));
168
+ });
169
+
170
+ const localOffer = await localConn.createOffer();
171
+ await localConn.setLocalDescription(localOffer);
172
+ const { answer } = await matchingPeer.answerOffer(localOffer, {
173
+ connectionMetadata: {
174
+ sourceURL: 'https://example.com/abc?x=y#123'
175
+ }
176
+ });
177
+ await localConn.setRemoteDescription(answer);
178
+
179
+ // Wait until the matching handler sends the configured message:
180
+ const receivedMessage = await messagePromise;
181
+ expect(receivedMessage).to.equal('hello example.com');
182
+ });
183
+
184
+ it("can match connections by URL regex", async () => {
185
+ // Close some other connections:
186
+ await mockRTC.forConnections()
187
+ .fromPageUrlMatching(/\?1+1=3/)
188
+ .thenClose();
189
+
190
+ // Send a message for connections made from matching pages:
191
+ await mockRTC.forConnections()
192
+ .fromPageUrlMatching(/\?x=y/)
193
+ .waitForChannel()
194
+ .thenSend('hello x=y');
195
+
196
+ const matchingPeer = await mockRTC.getMatchingPeer();
197
+
198
+ // Create a local data connection:
199
+ const localConn = new RTCPeerConnection();
200
+
201
+ const dataChannel = localConn.createDataChannel("dataChannel");
202
+
203
+ const messagePromise = new Promise((resolve) => {
204
+ dataChannel.addEventListener('message', ({ data }) => resolve(data));
205
+ });
206
+
207
+ const localOffer = await localConn.createOffer();
208
+ await localConn.setLocalDescription(localOffer);
209
+ const { answer } = await matchingPeer.answerOffer(localOffer, {
210
+ connectionMetadata: {
211
+ sourceURL: 'https://example.com/abc?x=y#123'
212
+ }
213
+ });
214
+ await localConn.setRemoteDescription(answer);
215
+
216
+ // Wait until the matching handler sends the configured message:
217
+ const receivedMessage = await messagePromise;
218
+ expect(receivedMessage).to.equal('hello x=y');
219
+ });
220
+
221
+ it("can match connections by user agent regex", async () => {
222
+ // Close some other connections:
223
+ await mockRTC.forConnections()
224
+ .fromUserAgentMatching(/IE6/)
225
+ .thenClose();
226
+
227
+ // Send a message for connections made from Firefox:
228
+ await mockRTC.forConnections()
229
+ .fromUserAgentMatching(/Firefox/)
230
+ .waitForChannel()
231
+ .thenSend('hello Firefox');
232
+
233
+ const matchingPeer = await mockRTC.getMatchingPeer();
234
+
235
+ // Create a local data connection:
236
+ const localConn = new RTCPeerConnection();
237
+
238
+ const dataChannel = localConn.createDataChannel("dataChannel");
239
+
240
+ const messagePromise = new Promise((resolve) => {
241
+ dataChannel.addEventListener('message', ({ data }) => resolve(data));
242
+ });
243
+
244
+ const localOffer = await localConn.createOffer();
245
+ await localConn.setLocalDescription(localOffer);
246
+ const { answer } = await matchingPeer.answerOffer(localOffer, {
247
+ connectionMetadata: {
248
+ userAgent: 'Mozilla/5.0 (Firefox/123)'
249
+ }
250
+ });
251
+ await localConn.setRemoteDescription(answer);
252
+
253
+ // Wait until the matching handler sends the configured message:
254
+ const receivedMessage = await messagePromise;
255
+ expect(receivedMessage).to.equal('hello Firefox');
256
+ });
68
257
  });
@@ -525,7 +525,7 @@ describe("When proxying WebRTC traffic", () => {
525
525
  expect(remoteFrame!.displayWidth).to.be.greaterThanOrEqual(320);
526
526
  });
527
527
 
528
- it("should include user-agent metadata when creating a hooked offer", async () => {
528
+ it("should include user-agent & URL metadata when creating a hooked offer", async () => {
529
529
  const eventPromise = getDeferred<MockRTCEventData['peer-connected']>();
530
530
  mockRTC.on('peer-connected', (peer) => eventPromise.resolve(peer));
531
531
 
@@ -555,9 +555,10 @@ describe("When proxying WebRTC traffic", () => {
555
555
  // Check that the connection event automatically includes the user agent:
556
556
  const connectEvent = await eventPromise;
557
557
  expect(connectEvent.metadata.userAgent).to.equal(navigator.userAgent);
558
+ expect(connectEvent.metadata.sourceURL).to.equal(window.location.href);
558
559
  });
559
560
 
560
- it("should include user-agent metadata when creating a hooked answer", async () => {
561
+ it("should include user-agent & URL metadata when creating a hooked answer", async () => {
561
562
  const eventPromise = getDeferred<MockRTCEventData['peer-connected']>();
562
563
  mockRTC.on('peer-connected', (peer) => eventPromise.resolve(peer));
563
564
 
@@ -586,6 +587,7 @@ describe("When proxying WebRTC traffic", () => {
586
587
  // Check that the connection event automatically includes the user agent:
587
588
  const connectEvent = await eventPromise;
588
589
  expect(connectEvent.metadata.userAgent).to.equal(navigator.userAgent);
590
+ expect(connectEvent.metadata.sourceURL).to.equal(window.location.href);
589
591
  });
590
592
 
591
593
  });
@@ -73,4 +73,55 @@ describe("Send steps", function () {
73
73
  ]);
74
74
  });
75
75
 
76
+ it("should be able to create a new data channel, and send a message there", async () => {
77
+ const mockPeer = await mockRTC.buildPeer()
78
+ .createDataChannel('new-channel')
79
+ .thenSend('Hello from new channel');
80
+
81
+ const localConnection = new RTCPeerConnection();
82
+
83
+ const messagePromise = new Promise((resolve) => {
84
+ localConnection.addEventListener('datachannel', ({ channel }) => {
85
+ expect(channel.label).to.equal('new-channel');
86
+ channel.addEventListener('message', ({ data }) => resolve(data));
87
+ });
88
+ });
89
+
90
+ const { offer, setAnswer } = await mockPeer.createOffer();
91
+ await localConnection.setRemoteDescription(offer);
92
+ const localAnswer = await localConnection.createAnswer();
93
+ await localConnection.setLocalDescription(localAnswer);
94
+ await setAnswer(localAnswer);
95
+
96
+ // Wait for a response:
97
+ const message = await messagePromise;
98
+ expect(message).to.equal('Hello from new channel');
99
+ });
100
+
101
+ it("should be able to send a Buffer-based message", async () => {
102
+ const mockPeer = await mockRTC.buildPeer()
103
+ .waitForChannel('dataChannel1')
104
+ .thenSend(Buffer.from('Hello from buffer'));
105
+
106
+ const localConnection = new RTCPeerConnection();
107
+ const dataChannel1 = localConnection.createDataChannel("dataChannel1");
108
+
109
+ const localOffer = await localConnection.createOffer();
110
+ await localConnection.setLocalDescription(localOffer);
111
+ const { answer } = await mockPeer.answerOffer(localOffer);
112
+ await localConnection.setRemoteDescription(answer);
113
+
114
+ // Wait for a response:
115
+ let messages: Array<any> = [];
116
+ dataChannel1.addEventListener('message', (event) => {
117
+ messages.push(Buffer.from(event.data)); // ArrayBuffer -> node Buffer
118
+ });
119
+
120
+ await waitForChannelClose(dataChannel1);
121
+
122
+ expect(messages.map(m => m.toString('utf8'))).to.deep.equal([
123
+ 'Hello from buffer'
124
+ ]);
125
+ });
126
+
76
127
  });