@leofcoin/peernet 1.1.5 → 1.1.6

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.
@@ -1,1455 +0,0 @@
1
- import SocketClient from 'socket-request-client';
2
- import '@vandeurenglenn/debug';
3
- import require$$0 from 'debug';
4
- import require$$1 from 'get-browser-rtc';
5
- import require$$2 from 'randombytes';
6
- import require$$3 from 'readable-stream';
7
- import require$$4 from 'queue-microtask';
8
- import require$$5 from 'err-code';
9
- import require$$6 from 'buffer';
10
-
11
- let Peer$1 = class Peer {
12
- #connection;
13
- #connected = false;
14
- #messageQue = [];
15
- #chunksQue = {};
16
- #channel;
17
- id;
18
- #peerId;
19
- #channelName;
20
- #chunkSize = 16 * 1024; // 16384
21
- #queRunning = false;
22
- #MAX_BUFFERED_AMOUNT = 16 * 1024 * 1024;
23
- initiator = false;
24
- state;
25
- #makingOffer = false;
26
- get connection() {
27
- return this.#connection;
28
- }
29
- get connected() {
30
- return this.#connected;
31
- }
32
- get readyState() {
33
- return this.#channel?.readyState;
34
- }
35
- get channelName() {
36
- return this.#channelName;
37
- }
38
- /**
39
- * @params {Object} options
40
- * @params {string} options.channelName - this peerid : otherpeer id
41
- */
42
- constructor(options = {}) {
43
- this._in = this._in.bind(this);
44
- this.offerOptions = options.offerOptions;
45
- this.initiator = options.initiator;
46
- this.streams = options.streams;
47
- this.socketClient = options.socketClient;
48
- this.id = options.id;
49
- this.to = options.to;
50
- this.bw = {
51
- up: 0,
52
- down: 0
53
- };
54
- this.#channelName = options.channelName;
55
- this.#peerId = options.peerId;
56
- this.options = options;
57
- return this.#init();
58
- }
59
- get peerId() {
60
- return this.#peerId;
61
- }
62
- set socketClient(value) {
63
- // this.socketClient?.pubsub.unsubscribe('signal', this._in)
64
- this._socketClient = value;
65
- this._socketClient.pubsub.subscribe('signal', this._in);
66
- }
67
- get socketClient() {
68
- return this._socketClient;
69
- }
70
- splitMessage(message) {
71
- const chunks = [];
72
- message = pako.deflate(message);
73
- const size = message.byteLength || message.length;
74
- let offset = 0;
75
- return new Promise((resolve, reject) => {
76
- const splitMessage = () => {
77
- const chunk = message.slice(offset, offset + this.#chunkSize > size ? size : offset + this.#chunkSize);
78
- offset += this.#chunkSize;
79
- chunks.push(chunk);
80
- if (offset < size)
81
- return splitMessage();
82
- else
83
- resolve({ chunks, size });
84
- };
85
- splitMessage();
86
- });
87
- }
88
- async #runQue() {
89
- this.#queRunning = true;
90
- if (this.#messageQue.length > 0 && this.#channel?.bufferedAmount + this.#messageQue[0]?.length < this.#MAX_BUFFERED_AMOUNT) {
91
- const message = this.#messageQue.shift();
92
- await this.#connection.send(message);
93
- if (this.#messageQue.length > 0)
94
- return this.#runQue();
95
- // switch (this.#channel?.readyState) {
96
- // case 'open':
97
- // await this.#channel.send(message);
98
- // if (this.#messageQue.length > 0) return this.#runQue()
99
- // else this.#queRunning = false
100
- // break;
101
- // case 'closed':
102
- // case 'closing':
103
- // this.#messageQue = []
104
- // this.#queRunning = false
105
- // debug('channel already closed, this usually means a bad implementation, try checking the readyState or check if the peer is connected before sending');
106
- // break;
107
- // case undefined:
108
- // this.#messageQue = []
109
- // this.#queRunning = false
110
- // debug(`trying to send before a channel is created`);
111
- // break;
112
- // }
113
- }
114
- else {
115
- return setTimeout(() => this.#runQue(), 50);
116
- }
117
- }
118
- #trySend({ size, id, chunks }) {
119
- let offset = 0;
120
- for (const chunk of chunks) {
121
- const start = offset;
122
- const end = offset + chunk.length;
123
- const message = new TextEncoder().encode(JSON.stringify({ size, id, chunk, start, end }));
124
- this.#messageQue.push(message);
125
- }
126
- if (!this.queRunning)
127
- return this.#runQue();
128
- }
129
- async send(message, id) {
130
- const { chunks, size } = await this.splitMessage(message);
131
- return this.#trySend({ size, id, chunks });
132
- }
133
- request(data) {
134
- return new Promise((resolve, reject) => {
135
- const id = Math.random().toString(36).slice(-12);
136
- const _onData = message => {
137
- if (message.id === id) {
138
- resolve(message.data);
139
- pubsub.unsubscribe(`peer:data`, _onData);
140
- }
141
- };
142
- pubsub.subscribe(`peer:data`, _onData);
143
- // cleanup subscriptions
144
- // setTimeout(() => {
145
- // pubsub.unsubscribe(`peer:data-request-${id}`, _onData)
146
- // }, 5000);
147
- this.send(data, id);
148
- });
149
- }
150
- async #init() {
151
- try {
152
- if (!globalThis.pako) {
153
- const importee = await import('pako');
154
- globalThis.pako = importee.default;
155
- }
156
- const iceServers = [{
157
- urls: 'stun:stun.l.google.com:19302' // Google's public STUN server
158
- }, {
159
- urls: "stun:openrelay.metered.ca:80",
160
- }, {
161
- urls: "turn:openrelay.metered.ca:443",
162
- username: "openrelayproject",
163
- credential: "openrelayproject",
164
- }, {
165
- urls: "turn:openrelay.metered.ca:443?transport=tcp",
166
- username: "openrelayproject",
167
- credential: "openrelayproject",
168
- }];
169
- const importee = await Promise.resolve().then(function () { return simplePeer$1; });
170
- const SimplePeer = importee.default;
171
- this.#connection = new SimplePeer({
172
- channelName: this.channelName,
173
- initiator: this.initiator,
174
- peerId: this.peerId,
175
- wrtc: globalThis.wrtc,
176
- config: {
177
- iceServers
178
- }
179
- });
180
- this.#connection.on('signal', signal => {
181
- this._sendMessage({ signal });
182
- });
183
- this.#connection.on('connected', () => {
184
- this.#connected = true;
185
- pubsub.publish('peer:connected', this);
186
- });
187
- this.#connection.on('close', () => {
188
- this.close();
189
- });
190
- this.#connection.on('data', data => {
191
- this._handleMessage(data);
192
- });
193
- this.#connection.on('error', (e) => {
194
- pubsub.publish('connection closed', this);
195
- console.log(e);
196
- this.close();
197
- });
198
- }
199
- catch (e) {
200
- console.log(e);
201
- }
202
- return this;
203
- }
204
- _handleMessage(message) {
205
- console.log({ message });
206
- message = JSON.parse(new TextDecoder().decode(message.data));
207
- // allow sharding (multiple peers share data)
208
- pubsub.publish('peernet:shard', message);
209
- const { id } = message;
210
- if (!this.#chunksQue[id])
211
- this.#chunksQue[id] = [];
212
- if (message.size > this.#chunksQue[id].length || message.size === this.#chunksQue[id].length) {
213
- for (const value of Object.values(message.chunk)) {
214
- this.#chunksQue[id].push(value);
215
- }
216
- }
217
- if (message.size === this.#chunksQue[id].length) {
218
- let data = new Uint8Array(Object.values(this.#chunksQue[id]));
219
- delete this.#chunksQue[id];
220
- data = pako.inflate(data);
221
- pubsub.publish('peer:data', { id, data, from: this.peerId });
222
- }
223
- this.bw.down += message.byteLength || message.length;
224
- }
225
- _sendMessage(message) {
226
- this.socketClient.send({ url: 'signal', params: {
227
- to: this.to,
228
- from: this.id,
229
- channelName: this.channelName,
230
- ...message
231
- } });
232
- }
233
- async _in(message, data) {
234
- if (message.signal)
235
- return this.#connection.signal(message.signal);
236
- }
237
- close() {
238
- // debug(`closing ${this.peerId}`)
239
- this.#connected = false;
240
- // this.#channel?.close()
241
- // this.#connection?.exit()
242
- this.socketClient.pubsub.unsubscribe('signal', this._in);
243
- }
244
- };
245
-
246
- class Client {
247
- #peerConnection;
248
- #connections = {};
249
- #stars = {};
250
- id;
251
- networkVersion;
252
- starsConfig;
253
- socketClient;
254
- get connections() {
255
- return { ...this.#connections };
256
- }
257
- get peers() {
258
- return Object.entries(this.#connections);
259
- }
260
- constructor(id, networkVersion = 'peach', stars = ['wss://peach.leofcoin.org']) {
261
- this.id = id || Math.random().toString(36).slice(-12);
262
- this.peerJoined = this.peerJoined.bind(this);
263
- this.peerLeft = this.peerLeft.bind(this);
264
- this.starLeft = this.starLeft.bind(this);
265
- this.starJoined = this.starJoined.bind(this);
266
- this.networkVersion = networkVersion;
267
- this._init(stars);
268
- }
269
- async _init(stars = []) {
270
- this.starsConfig = stars;
271
- // reconnectJob()
272
- if (!globalThis.RTCPeerConnection)
273
- globalThis.wrtc = (await import('@koush/wrtc')).default;
274
- else
275
- globalThis.wrtc = {
276
- RTCPeerConnection,
277
- RTCSessionDescription,
278
- RTCIceCandidate
279
- };
280
- for (const star of stars) {
281
- try {
282
- this.socketClient = await SocketClient(star, this.networkVersion);
283
- const id = await this.socketClient.request({ url: 'id', params: { from: this.id } });
284
- this.socketClient.peerId = id;
285
- this.#stars[id] = this.socketClient;
286
- }
287
- catch (e) {
288
- if (stars.indexOf(star) === stars.length - 1 && !this.socketClient)
289
- throw new Error(`No star available to connect`);
290
- }
291
- }
292
- this.setupListeners();
293
- const peers = await this.socketClient.peernet.join({ id: this.id });
294
- for (const id of peers) {
295
- if (id !== this.id && !this.#connections[id])
296
- this.#connections[id] = await new Peer$1({ channelName: `${id}:${this.id}`, socketClient: this.socketClient, id: this.id, to: id, peerId: id });
297
- }
298
- pubsub.subscribe('connection closed', (peer) => {
299
- this.removePeer(peer.peerId);
300
- setTimeout(() => {
301
- this.peerJoined(peer.peerId);
302
- }, 1000);
303
- });
304
- }
305
- setupListeners() {
306
- this.socketClient.subscribe('peer:joined', this.peerJoined);
307
- this.socketClient.subscribe('peer:left', this.peerLeft);
308
- this.socketClient.subscribe('star:left', this.starLeft);
309
- }
310
- starJoined(id) {
311
- if (this.#stars[id]) {
312
- this.#stars[id].close();
313
- delete this.#stars[id];
314
- }
315
- console.log(`star ${id} joined`);
316
- }
317
- async starLeft(id) {
318
- if (this.#stars[id]) {
319
- this.#stars[id].close();
320
- delete this.#stars[id];
321
- }
322
- if (this.socketClient?.peerId === id) {
323
- this.socketClient.unsubscribe('peer:joined', this.peerJoined);
324
- this.socketClient.unsubscribe('peer:left', this.peerLeft);
325
- this.socketClient.unsubscribe('star:left', this.starLeft);
326
- this.socketClient.close();
327
- this.socketClient = undefined;
328
- for (const star of this.starsConfig) {
329
- try {
330
- this.socketClient = await SocketClient(star, this.networkVersion);
331
- if (!this.socketClient?.client?._connection.connected)
332
- return;
333
- const id = await this.socketClient.request({ url: 'id', params: { from: this.id } });
334
- this.#stars[id] = this.socketClient;
335
- this.socketClient.peerId = id;
336
- const peers = await this.socketClient.peernet.join({ id: this.id });
337
- this.setupListeners();
338
- for (const id of peers) {
339
- if (id !== this.id) {
340
- if (!this.#connections[id]) {
341
- if (id !== this.id)
342
- this.#connections[id] = await new Peer$1({ channelName: `${id}:${this.id}`, socketClient: this.socketClient, id: this.id, to: id, peerId: id });
343
- }
344
- }
345
- }
346
- }
347
- catch (e) {
348
- console.log(e);
349
- if (this.starsConfig.indexOf(star) === this.starsConfig.length - 1 && !this.socketClient)
350
- throw new Error(`No star available to connect`);
351
- }
352
- }
353
- }
354
- globalThis.debug(`star ${id} left`);
355
- }
356
- peerLeft(peer) {
357
- const id = peer.peerId || peer;
358
- if (this.#connections[id]) {
359
- this.#connections[id].close();
360
- delete this.#connections[id];
361
- }
362
- globalThis.debug(`peer ${id} left`);
363
- }
364
- async peerJoined(peer, signal) {
365
- const id = peer.peerId || peer;
366
- if (this.#connections[id]) {
367
- if (this.#connections[id].connected)
368
- this.#connections[id].close();
369
- delete this.#connections[id];
370
- }
371
- // RTCPeerConnection
372
- this.#connections[id] = await new Peer$1({ initiator: true, channelName: `${this.id}:${id}`, socketClient: this.socketClient, id: this.id, to: id, peerId: id });
373
- globalThis.debug(`peer ${id} joined`);
374
- }
375
- removePeer(peer) {
376
- const id = peer.peerId || peer;
377
- if (this.#connections[id]) {
378
- this.#connections[id].connected && this.#connections[id].close();
379
- delete this.#connections[id];
380
- }
381
- globalThis.debug(`peer ${id} removed`);
382
- }
383
- async close() {
384
- this.socketClient.unsubscribe('peer:joined', this.peerJoined);
385
- this.socketClient.unsubscribe('peer:left', this.peerLeft);
386
- this.socketClient.unsubscribe('star:left', this.starLeft);
387
- const promises = [
388
- Object.values(this.#connections).map(connection => connection.close()),
389
- Object.values(this.#stars).map(connection => connection.close()),
390
- this.socketClient.close()
391
- ];
392
- return Promise.allSettled(promises);
393
- }
394
- }
395
-
396
- /*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
397
-
398
- const debug = require$$0('simple-peer');
399
- const getBrowserRTC = require$$1;
400
- const randombytes = require$$2;
401
- const stream = require$$3;
402
- const queueMicrotask = require$$4; // TODO: remove when Node 10 is not supported
403
- const errCode = require$$5;
404
- const { Buffer } = require$$6;
405
-
406
- const MAX_BUFFERED_AMOUNT = 64 * 1024;
407
- const ICECOMPLETE_TIMEOUT = 5 * 1000;
408
- const CHANNEL_CLOSING_TIMEOUT = 5 * 1000;
409
-
410
- // HACK: Filter trickle lines when trickle is disabled #354
411
- function filterTrickle (sdp) {
412
- return sdp.replace(/a=ice-options:trickle\s\n/g, '')
413
- }
414
-
415
- function warn (message) {
416
- console.warn(message);
417
- }
418
-
419
- /**
420
- * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods.
421
- * Duplex stream.
422
- * @param {Object} opts
423
- */
424
- class Peer extends stream.Duplex {
425
- constructor (opts) {
426
- opts = Object.assign({
427
- allowHalfOpen: false
428
- }, opts);
429
-
430
- super(opts);
431
-
432
- this._id = randombytes(4).toString('hex').slice(0, 7);
433
- this._debug('new peer %o', opts);
434
-
435
- this.channelName = opts.initiator
436
- ? opts.channelName || randombytes(20).toString('hex')
437
- : null;
438
-
439
- this.initiator = opts.initiator || false;
440
- this.channelConfig = opts.channelConfig || Peer.channelConfig;
441
- this.channelNegotiated = this.channelConfig.negotiated;
442
- this.config = Object.assign({}, Peer.config, opts.config);
443
- this.offerOptions = opts.offerOptions || {};
444
- this.answerOptions = opts.answerOptions || {};
445
- this.sdpTransform = opts.sdpTransform || (sdp => sdp);
446
- this.streams = opts.streams || (opts.stream ? [opts.stream] : []); // support old "stream" option
447
- this.trickle = opts.trickle !== undefined ? opts.trickle : true;
448
- this.allowHalfTrickle = opts.allowHalfTrickle !== undefined ? opts.allowHalfTrickle : false;
449
- this.iceCompleteTimeout = opts.iceCompleteTimeout || ICECOMPLETE_TIMEOUT;
450
-
451
- this.destroyed = false;
452
- this.destroying = false;
453
- this._connected = false;
454
-
455
- this.remoteAddress = undefined;
456
- this.remoteFamily = undefined;
457
- this.remotePort = undefined;
458
- this.localAddress = undefined;
459
- this.localFamily = undefined;
460
- this.localPort = undefined;
461
-
462
- this._wrtc = (opts.wrtc && typeof opts.wrtc === 'object')
463
- ? opts.wrtc
464
- : getBrowserRTC();
465
-
466
- if (!this._wrtc) {
467
- if (typeof window === 'undefined') {
468
- throw errCode(new Error('No WebRTC support: Specify `opts.wrtc` option in this environment'), 'ERR_WEBRTC_SUPPORT')
469
- } else {
470
- throw errCode(new Error('No WebRTC support: Not a supported browser'), 'ERR_WEBRTC_SUPPORT')
471
- }
472
- }
473
-
474
- this._pcReady = false;
475
- this._channelReady = false;
476
- this._iceComplete = false; // ice candidate trickle done (got null candidate)
477
- this._iceCompleteTimer = null; // send an offer/answer anyway after some timeout
478
- this._channel = null;
479
- this._pendingCandidates = [];
480
-
481
- this._isNegotiating = false; // is this peer waiting for negotiation to complete?
482
- this._firstNegotiation = true;
483
- this._batchedNegotiation = false; // batch synchronous negotiations
484
- this._queuedNegotiation = false; // is there a queued negotiation request?
485
- this._sendersAwaitingStable = [];
486
- this._senderMap = new Map();
487
- this._closingInterval = null;
488
-
489
- this._remoteTracks = [];
490
- this._remoteStreams = [];
491
-
492
- this._chunk = null;
493
- this._cb = null;
494
- this._interval = null;
495
-
496
- try {
497
- this._pc = new (this._wrtc.RTCPeerConnection)(this.config);
498
- } catch (err) {
499
- this.destroy(errCode(err, 'ERR_PC_CONSTRUCTOR'));
500
- return
501
- }
502
-
503
- // We prefer feature detection whenever possible, but sometimes that's not
504
- // possible for certain implementations.
505
- this._isReactNativeWebrtc = typeof this._pc._peerConnectionId === 'number';
506
-
507
- this._pc.oniceconnectionstatechange = () => {
508
- this._onIceStateChange();
509
- };
510
- this._pc.onicegatheringstatechange = () => {
511
- this._onIceStateChange();
512
- };
513
- this._pc.onconnectionstatechange = () => {
514
- this._onConnectionStateChange();
515
- };
516
- this._pc.onsignalingstatechange = () => {
517
- this._onSignalingStateChange();
518
- };
519
- this._pc.onicecandidate = event => {
520
- this._onIceCandidate(event);
521
- };
522
-
523
- // HACK: Fix for odd Firefox behavior, see: https://github.com/feross/simple-peer/pull/783
524
- if (typeof this._pc.peerIdentity === 'object') {
525
- this._pc.peerIdentity.catch(err => {
526
- this.destroy(errCode(err, 'ERR_PC_PEER_IDENTITY'));
527
- });
528
- }
529
-
530
- // Other spec events, unused by this implementation:
531
- // - onconnectionstatechange
532
- // - onicecandidateerror
533
- // - onfingerprintfailure
534
- // - onnegotiationneeded
535
-
536
- if (this.initiator || this.channelNegotiated) {
537
- this._setupData({
538
- channel: this._pc.createDataChannel(this.channelName, this.channelConfig)
539
- });
540
- } else {
541
- this._pc.ondatachannel = event => {
542
- this._setupData(event);
543
- };
544
- }
545
-
546
- if (this.streams) {
547
- this.streams.forEach(stream => {
548
- this.addStream(stream);
549
- });
550
- }
551
- this._pc.ontrack = event => {
552
- this._onTrack(event);
553
- };
554
-
555
- this._debug('initial negotiation');
556
- this._needsNegotiation();
557
-
558
- this._onFinishBound = () => {
559
- this._onFinish();
560
- };
561
- this.once('finish', this._onFinishBound);
562
- }
563
-
564
- get bufferSize () {
565
- return (this._channel && this._channel.bufferedAmount) || 0
566
- }
567
-
568
- // HACK: it's possible channel.readyState is "closing" before peer.destroy() fires
569
- // https://bugs.chromium.org/p/chromium/issues/detail?id=882743
570
- get connected () {
571
- return (this._connected && this._channel.readyState === 'open')
572
- }
573
-
574
- address () {
575
- return { port: this.localPort, family: this.localFamily, address: this.localAddress }
576
- }
577
-
578
- signal (data) {
579
- if (this.destroying) return
580
- if (this.destroyed) throw errCode(new Error('cannot signal after peer is destroyed'), 'ERR_DESTROYED')
581
- if (typeof data === 'string') {
582
- try {
583
- data = JSON.parse(data);
584
- } catch (err) {
585
- data = {};
586
- }
587
- }
588
- this._debug('signal()');
589
-
590
- if (data.renegotiate && this.initiator) {
591
- this._debug('got request to renegotiate');
592
- this._needsNegotiation();
593
- }
594
- if (data.transceiverRequest && this.initiator) {
595
- this._debug('got request for transceiver');
596
- this.addTransceiver(data.transceiverRequest.kind, data.transceiverRequest.init);
597
- }
598
- if (data.candidate) {
599
- if (this._pc.remoteDescription && this._pc.remoteDescription.type) {
600
- this._addIceCandidate(data.candidate);
601
- } else {
602
- this._pendingCandidates.push(data.candidate);
603
- }
604
- }
605
- if (data.sdp) {
606
- this._pc.setRemoteDescription(new (this._wrtc.RTCSessionDescription)(data))
607
- .then(() => {
608
- if (this.destroyed) return
609
-
610
- this._pendingCandidates.forEach(candidate => {
611
- this._addIceCandidate(candidate);
612
- });
613
- this._pendingCandidates = [];
614
-
615
- if (this._pc.remoteDescription.type === 'offer') this._createAnswer();
616
- })
617
- .catch(err => {
618
- this.destroy(errCode(err, 'ERR_SET_REMOTE_DESCRIPTION'));
619
- });
620
- }
621
- if (!data.sdp && !data.candidate && !data.renegotiate && !data.transceiverRequest) {
622
- this.destroy(errCode(new Error('signal() called with invalid signal data'), 'ERR_SIGNALING'));
623
- }
624
- }
625
-
626
- _addIceCandidate (candidate) {
627
- const iceCandidateObj = new this._wrtc.RTCIceCandidate(candidate);
628
- this._pc.addIceCandidate(iceCandidateObj)
629
- .catch(err => {
630
- if (!iceCandidateObj.address || iceCandidateObj.address.endsWith('.local')) {
631
- warn('Ignoring unsupported ICE candidate.');
632
- } else {
633
- this.destroy(errCode(err, 'ERR_ADD_ICE_CANDIDATE'));
634
- }
635
- });
636
- }
637
-
638
- /**
639
- * Send text/binary data to the remote peer.
640
- * @param {ArrayBufferView|ArrayBuffer|Buffer|string|Blob} chunk
641
- */
642
- send (chunk) {
643
- if (this.destroying) return
644
- if (this.destroyed) throw errCode(new Error('cannot send after peer is destroyed'), 'ERR_DESTROYED')
645
- this._channel.send(chunk);
646
- }
647
-
648
- /**
649
- * Add a Transceiver to the connection.
650
- * @param {String} kind
651
- * @param {Object} init
652
- */
653
- addTransceiver (kind, init) {
654
- if (this.destroying) return
655
- if (this.destroyed) throw errCode(new Error('cannot addTransceiver after peer is destroyed'), 'ERR_DESTROYED')
656
- this._debug('addTransceiver()');
657
-
658
- if (this.initiator) {
659
- try {
660
- this._pc.addTransceiver(kind, init);
661
- this._needsNegotiation();
662
- } catch (err) {
663
- this.destroy(errCode(err, 'ERR_ADD_TRANSCEIVER'));
664
- }
665
- } else {
666
- this.emit('signal', { // request initiator to renegotiate
667
- type: 'transceiverRequest',
668
- transceiverRequest: { kind, init }
669
- });
670
- }
671
- }
672
-
673
- /**
674
- * Add a MediaStream to the connection.
675
- * @param {MediaStream} stream
676
- */
677
- addStream (stream) {
678
- if (this.destroying) return
679
- if (this.destroyed) throw errCode(new Error('cannot addStream after peer is destroyed'), 'ERR_DESTROYED')
680
- this._debug('addStream()');
681
-
682
- stream.getTracks().forEach(track => {
683
- this.addTrack(track, stream);
684
- });
685
- }
686
-
687
- /**
688
- * Add a MediaStreamTrack to the connection.
689
- * @param {MediaStreamTrack} track
690
- * @param {MediaStream} stream
691
- */
692
- addTrack (track, stream) {
693
- if (this.destroying) return
694
- if (this.destroyed) throw errCode(new Error('cannot addTrack after peer is destroyed'), 'ERR_DESTROYED')
695
- this._debug('addTrack()');
696
-
697
- const submap = this._senderMap.get(track) || new Map(); // nested Maps map [track, stream] to sender
698
- let sender = submap.get(stream);
699
- if (!sender) {
700
- sender = this._pc.addTrack(track, stream);
701
- submap.set(stream, sender);
702
- this._senderMap.set(track, submap);
703
- this._needsNegotiation();
704
- } else if (sender.removed) {
705
- throw errCode(new Error('Track has been removed. You should enable/disable tracks that you want to re-add.'), 'ERR_SENDER_REMOVED')
706
- } else {
707
- throw errCode(new Error('Track has already been added to that stream.'), 'ERR_SENDER_ALREADY_ADDED')
708
- }
709
- }
710
-
711
- /**
712
- * Replace a MediaStreamTrack by another in the connection.
713
- * @param {MediaStreamTrack} oldTrack
714
- * @param {MediaStreamTrack} newTrack
715
- * @param {MediaStream} stream
716
- */
717
- replaceTrack (oldTrack, newTrack, stream) {
718
- if (this.destroying) return
719
- if (this.destroyed) throw errCode(new Error('cannot replaceTrack after peer is destroyed'), 'ERR_DESTROYED')
720
- this._debug('replaceTrack()');
721
-
722
- const submap = this._senderMap.get(oldTrack);
723
- const sender = submap ? submap.get(stream) : null;
724
- if (!sender) {
725
- throw errCode(new Error('Cannot replace track that was never added.'), 'ERR_TRACK_NOT_ADDED')
726
- }
727
- if (newTrack) this._senderMap.set(newTrack, submap);
728
-
729
- if (sender.replaceTrack != null) {
730
- sender.replaceTrack(newTrack);
731
- } else {
732
- this.destroy(errCode(new Error('replaceTrack is not supported in this browser'), 'ERR_UNSUPPORTED_REPLACETRACK'));
733
- }
734
- }
735
-
736
- /**
737
- * Remove a MediaStreamTrack from the connection.
738
- * @param {MediaStreamTrack} track
739
- * @param {MediaStream} stream
740
- */
741
- removeTrack (track, stream) {
742
- if (this.destroying) return
743
- if (this.destroyed) throw errCode(new Error('cannot removeTrack after peer is destroyed'), 'ERR_DESTROYED')
744
- this._debug('removeSender()');
745
-
746
- const submap = this._senderMap.get(track);
747
- const sender = submap ? submap.get(stream) : null;
748
- if (!sender) {
749
- throw errCode(new Error('Cannot remove track that was never added.'), 'ERR_TRACK_NOT_ADDED')
750
- }
751
- try {
752
- sender.removed = true;
753
- this._pc.removeTrack(sender);
754
- } catch (err) {
755
- if (err.name === 'NS_ERROR_UNEXPECTED') {
756
- this._sendersAwaitingStable.push(sender); // HACK: Firefox must wait until (signalingState === stable) https://bugzilla.mozilla.org/show_bug.cgi?id=1133874
757
- } else {
758
- this.destroy(errCode(err, 'ERR_REMOVE_TRACK'));
759
- }
760
- }
761
- this._needsNegotiation();
762
- }
763
-
764
- /**
765
- * Remove a MediaStream from the connection.
766
- * @param {MediaStream} stream
767
- */
768
- removeStream (stream) {
769
- if (this.destroying) return
770
- if (this.destroyed) throw errCode(new Error('cannot removeStream after peer is destroyed'), 'ERR_DESTROYED')
771
- this._debug('removeSenders()');
772
-
773
- stream.getTracks().forEach(track => {
774
- this.removeTrack(track, stream);
775
- });
776
- }
777
-
778
- _needsNegotiation () {
779
- this._debug('_needsNegotiation');
780
- if (this._batchedNegotiation) return // batch synchronous renegotiations
781
- this._batchedNegotiation = true;
782
- queueMicrotask(() => {
783
- this._batchedNegotiation = false;
784
- if (this.initiator || !this._firstNegotiation) {
785
- this._debug('starting batched negotiation');
786
- this.negotiate();
787
- } else {
788
- this._debug('non-initiator initial negotiation request discarded');
789
- }
790
- this._firstNegotiation = false;
791
- });
792
- }
793
-
794
- negotiate () {
795
- if (this.destroying) return
796
- if (this.destroyed) throw errCode(new Error('cannot negotiate after peer is destroyed'), 'ERR_DESTROYED')
797
-
798
- if (this.initiator) {
799
- if (this._isNegotiating) {
800
- this._queuedNegotiation = true;
801
- this._debug('already negotiating, queueing');
802
- } else {
803
- this._debug('start negotiation');
804
- setTimeout(() => { // HACK: Chrome crashes if we immediately call createOffer
805
- this._createOffer();
806
- }, 0);
807
- }
808
- } else {
809
- if (this._isNegotiating) {
810
- this._queuedNegotiation = true;
811
- this._debug('already negotiating, queueing');
812
- } else {
813
- this._debug('requesting negotiation from initiator');
814
- this.emit('signal', { // request initiator to renegotiate
815
- type: 'renegotiate',
816
- renegotiate: true
817
- });
818
- }
819
- }
820
- this._isNegotiating = true;
821
- }
822
-
823
- // TODO: Delete this method once readable-stream is updated to contain a default
824
- // implementation of destroy() that automatically calls _destroy()
825
- // See: https://github.com/nodejs/readable-stream/issues/283
826
- destroy (err) {
827
- this._destroy(err, () => {});
828
- }
829
-
830
- _destroy (err, cb) {
831
- if (this.destroyed || this.destroying) return
832
- this.destroying = true;
833
-
834
- this._debug('destroying (error: %s)', err && (err.message || err));
835
-
836
- queueMicrotask(() => { // allow events concurrent with the call to _destroy() to fire (see #692)
837
- this.destroyed = true;
838
- this.destroying = false;
839
-
840
- this._debug('destroy (error: %s)', err && (err.message || err));
841
-
842
- this.readable = this.writable = false;
843
-
844
- if (!this._readableState.ended) this.push(null);
845
- if (!this._writableState.finished) this.end();
846
-
847
- this._connected = false;
848
- this._pcReady = false;
849
- this._channelReady = false;
850
- this._remoteTracks = null;
851
- this._remoteStreams = null;
852
- this._senderMap = null;
853
-
854
- clearInterval(this._closingInterval);
855
- this._closingInterval = null;
856
-
857
- clearInterval(this._interval);
858
- this._interval = null;
859
- this._chunk = null;
860
- this._cb = null;
861
-
862
- if (this._onFinishBound) this.removeListener('finish', this._onFinishBound);
863
- this._onFinishBound = null;
864
-
865
- if (this._channel) {
866
- try {
867
- this._channel.close();
868
- } catch (err) {}
869
-
870
- // allow events concurrent with destruction to be handled
871
- this._channel.onmessage = null;
872
- this._channel.onopen = null;
873
- this._channel.onclose = null;
874
- this._channel.onerror = null;
875
- }
876
- if (this._pc) {
877
- try {
878
- this._pc.close();
879
- } catch (err) {}
880
-
881
- // allow events concurrent with destruction to be handled
882
- this._pc.oniceconnectionstatechange = null;
883
- this._pc.onicegatheringstatechange = null;
884
- this._pc.onsignalingstatechange = null;
885
- this._pc.onicecandidate = null;
886
- this._pc.ontrack = null;
887
- this._pc.ondatachannel = null;
888
- }
889
- this._pc = null;
890
- this._channel = null;
891
-
892
- if (err) this.emit('error', err);
893
- this.emit('close');
894
- cb();
895
- });
896
- }
897
-
898
- _setupData (event) {
899
- if (!event.channel) {
900
- // In some situations `pc.createDataChannel()` returns `undefined` (in wrtc),
901
- // which is invalid behavior. Handle it gracefully.
902
- // See: https://github.com/feross/simple-peer/issues/163
903
- return this.destroy(errCode(new Error('Data channel event is missing `channel` property'), 'ERR_DATA_CHANNEL'))
904
- }
905
-
906
- this._channel = event.channel;
907
- this._channel.binaryType = 'arraybuffer';
908
-
909
- if (typeof this._channel.bufferedAmountLowThreshold === 'number') {
910
- this._channel.bufferedAmountLowThreshold = MAX_BUFFERED_AMOUNT;
911
- }
912
-
913
- this.channelName = this._channel.label;
914
-
915
- this._channel.onmessage = event => {
916
- this._onChannelMessage(event);
917
- };
918
- this._channel.onbufferedamountlow = () => {
919
- this._onChannelBufferedAmountLow();
920
- };
921
- this._channel.onopen = () => {
922
- this._onChannelOpen();
923
- };
924
- this._channel.onclose = () => {
925
- this._onChannelClose();
926
- };
927
- this._channel.onerror = event => {
928
- const err = event.error instanceof Error
929
- ? event.error
930
- : new Error(`Datachannel error: ${event.message} ${event.filename}:${event.lineno}:${event.colno}`);
931
- this.destroy(errCode(err, 'ERR_DATA_CHANNEL'));
932
- };
933
-
934
- // HACK: Chrome will sometimes get stuck in readyState "closing", let's check for this condition
935
- // https://bugs.chromium.org/p/chromium/issues/detail?id=882743
936
- let isClosing = false;
937
- this._closingInterval = setInterval(() => { // No "onclosing" event
938
- if (this._channel && this._channel.readyState === 'closing') {
939
- if (isClosing) this._onChannelClose(); // closing timed out: equivalent to onclose firing
940
- isClosing = true;
941
- } else {
942
- isClosing = false;
943
- }
944
- }, CHANNEL_CLOSING_TIMEOUT);
945
- }
946
-
947
- _read () {}
948
-
949
- _write (chunk, encoding, cb) {
950
- if (this.destroyed) return cb(errCode(new Error('cannot write after peer is destroyed'), 'ERR_DATA_CHANNEL'))
951
-
952
- if (this._connected) {
953
- try {
954
- this.send(chunk);
955
- } catch (err) {
956
- return this.destroy(errCode(err, 'ERR_DATA_CHANNEL'))
957
- }
958
- if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
959
- this._debug('start backpressure: bufferedAmount %d', this._channel.bufferedAmount);
960
- this._cb = cb;
961
- } else {
962
- cb(null);
963
- }
964
- } else {
965
- this._debug('write before connect');
966
- this._chunk = chunk;
967
- this._cb = cb;
968
- }
969
- }
970
-
971
- // When stream finishes writing, close socket. Half open connections are not
972
- // supported.
973
- _onFinish () {
974
- if (this.destroyed) return
975
-
976
- // Wait a bit before destroying so the socket flushes.
977
- // TODO: is there a more reliable way to accomplish this?
978
- const destroySoon = () => {
979
- setTimeout(() => this.destroy(), 1000);
980
- };
981
-
982
- if (this._connected) {
983
- destroySoon();
984
- } else {
985
- this.once('connect', destroySoon);
986
- }
987
- }
988
-
989
- _startIceCompleteTimeout () {
990
- if (this.destroyed) return
991
- if (this._iceCompleteTimer) return
992
- this._debug('started iceComplete timeout');
993
- this._iceCompleteTimer = setTimeout(() => {
994
- if (!this._iceComplete) {
995
- this._iceComplete = true;
996
- this._debug('iceComplete timeout completed');
997
- this.emit('iceTimeout');
998
- this.emit('_iceComplete');
999
- }
1000
- }, this.iceCompleteTimeout);
1001
- }
1002
-
1003
- _createOffer () {
1004
- if (this.destroyed) return
1005
-
1006
- this._pc.createOffer(this.offerOptions)
1007
- .then(offer => {
1008
- if (this.destroyed) return
1009
- if (!this.trickle && !this.allowHalfTrickle) offer.sdp = filterTrickle(offer.sdp);
1010
- offer.sdp = this.sdpTransform(offer.sdp);
1011
-
1012
- const sendOffer = () => {
1013
- if (this.destroyed) return
1014
- const signal = this._pc.localDescription || offer;
1015
- this._debug('signal');
1016
- this.emit('signal', {
1017
- type: signal.type,
1018
- sdp: signal.sdp
1019
- });
1020
- };
1021
-
1022
- const onSuccess = () => {
1023
- this._debug('createOffer success');
1024
- if (this.destroyed) return
1025
- if (this.trickle || this._iceComplete) sendOffer();
1026
- else this.once('_iceComplete', sendOffer); // wait for candidates
1027
- };
1028
-
1029
- const onError = err => {
1030
- this.destroy(errCode(err, 'ERR_SET_LOCAL_DESCRIPTION'));
1031
- };
1032
-
1033
- this._pc.setLocalDescription(offer)
1034
- .then(onSuccess)
1035
- .catch(onError);
1036
- })
1037
- .catch(err => {
1038
- this.destroy(errCode(err, 'ERR_CREATE_OFFER'));
1039
- });
1040
- }
1041
-
1042
- _requestMissingTransceivers () {
1043
- if (this._pc.getTransceivers) {
1044
- this._pc.getTransceivers().forEach(transceiver => {
1045
- if (!transceiver.mid && transceiver.sender.track && !transceiver.requested) {
1046
- transceiver.requested = true; // HACK: Safari returns negotiated transceivers with a null mid
1047
- this.addTransceiver(transceiver.sender.track.kind);
1048
- }
1049
- });
1050
- }
1051
- }
1052
-
1053
- _createAnswer () {
1054
- if (this.destroyed) return
1055
-
1056
- this._pc.createAnswer(this.answerOptions)
1057
- .then(answer => {
1058
- if (this.destroyed) return
1059
- if (!this.trickle && !this.allowHalfTrickle) answer.sdp = filterTrickle(answer.sdp);
1060
- answer.sdp = this.sdpTransform(answer.sdp);
1061
-
1062
- const sendAnswer = () => {
1063
- if (this.destroyed) return
1064
- const signal = this._pc.localDescription || answer;
1065
- this._debug('signal');
1066
- this.emit('signal', {
1067
- type: signal.type,
1068
- sdp: signal.sdp
1069
- });
1070
- if (!this.initiator) this._requestMissingTransceivers();
1071
- };
1072
-
1073
- const onSuccess = () => {
1074
- if (this.destroyed) return
1075
- if (this.trickle || this._iceComplete) sendAnswer();
1076
- else this.once('_iceComplete', sendAnswer);
1077
- };
1078
-
1079
- const onError = err => {
1080
- this.destroy(errCode(err, 'ERR_SET_LOCAL_DESCRIPTION'));
1081
- };
1082
-
1083
- this._pc.setLocalDescription(answer)
1084
- .then(onSuccess)
1085
- .catch(onError);
1086
- })
1087
- .catch(err => {
1088
- this.destroy(errCode(err, 'ERR_CREATE_ANSWER'));
1089
- });
1090
- }
1091
-
1092
- _onConnectionStateChange () {
1093
- if (this.destroyed) return
1094
- if (this._pc.connectionState === 'failed') {
1095
- this.destroy(errCode(new Error('Connection failed.'), 'ERR_CONNECTION_FAILURE'));
1096
- }
1097
- }
1098
-
1099
- _onIceStateChange () {
1100
- if (this.destroyed) return
1101
- const iceConnectionState = this._pc.iceConnectionState;
1102
- const iceGatheringState = this._pc.iceGatheringState;
1103
-
1104
- this._debug(
1105
- 'iceStateChange (connection: %s) (gathering: %s)',
1106
- iceConnectionState,
1107
- iceGatheringState
1108
- );
1109
- this.emit('iceStateChange', iceConnectionState, iceGatheringState);
1110
-
1111
- if (iceConnectionState === 'connected' || iceConnectionState === 'completed') {
1112
- this._pcReady = true;
1113
- this._maybeReady();
1114
- }
1115
- if (iceConnectionState === 'failed') {
1116
- this.destroy(errCode(new Error('Ice connection failed.'), 'ERR_ICE_CONNECTION_FAILURE'));
1117
- }
1118
- if (iceConnectionState === 'closed') {
1119
- this.destroy(errCode(new Error('Ice connection closed.'), 'ERR_ICE_CONNECTION_CLOSED'));
1120
- }
1121
- }
1122
-
1123
- getStats (cb) {
1124
- // statreports can come with a value array instead of properties
1125
- const flattenValues = report => {
1126
- if (Object.prototype.toString.call(report.values) === '[object Array]') {
1127
- report.values.forEach(value => {
1128
- Object.assign(report, value);
1129
- });
1130
- }
1131
- return report
1132
- };
1133
-
1134
- // Promise-based getStats() (standard)
1135
- if (this._pc.getStats.length === 0 || this._isReactNativeWebrtc) {
1136
- this._pc.getStats()
1137
- .then(res => {
1138
- const reports = [];
1139
- res.forEach(report => {
1140
- reports.push(flattenValues(report));
1141
- });
1142
- cb(null, reports);
1143
- }, err => cb(err));
1144
-
1145
- // Single-parameter callback-based getStats() (non-standard)
1146
- } else if (this._pc.getStats.length > 0) {
1147
- this._pc.getStats(res => {
1148
- // If we destroy connection in `connect` callback this code might happen to run when actual connection is already closed
1149
- if (this.destroyed) return
1150
-
1151
- const reports = [];
1152
- res.result().forEach(result => {
1153
- const report = {};
1154
- result.names().forEach(name => {
1155
- report[name] = result.stat(name);
1156
- });
1157
- report.id = result.id;
1158
- report.type = result.type;
1159
- report.timestamp = result.timestamp;
1160
- reports.push(flattenValues(report));
1161
- });
1162
- cb(null, reports);
1163
- }, err => cb(err));
1164
-
1165
- // Unknown browser, skip getStats() since it's anyone's guess which style of
1166
- // getStats() they implement.
1167
- } else {
1168
- cb(null, []);
1169
- }
1170
- }
1171
-
1172
- _maybeReady () {
1173
- this._debug('maybeReady pc %s channel %s', this._pcReady, this._channelReady);
1174
- if (this._connected || this._connecting || !this._pcReady || !this._channelReady) return
1175
-
1176
- this._connecting = true;
1177
-
1178
- // HACK: We can't rely on order here, for details see https://github.com/js-platform/node-webrtc/issues/339
1179
- const findCandidatePair = () => {
1180
- if (this.destroyed) return
1181
-
1182
- this.getStats((err, items) => {
1183
- if (this.destroyed) return
1184
-
1185
- // Treat getStats error as non-fatal. It's not essential.
1186
- if (err) items = [];
1187
-
1188
- const remoteCandidates = {};
1189
- const localCandidates = {};
1190
- const candidatePairs = {};
1191
- let foundSelectedCandidatePair = false;
1192
-
1193
- items.forEach(item => {
1194
- // TODO: Once all browsers support the hyphenated stats report types, remove
1195
- // the non-hypenated ones
1196
- if (item.type === 'remotecandidate' || item.type === 'remote-candidate') {
1197
- remoteCandidates[item.id] = item;
1198
- }
1199
- if (item.type === 'localcandidate' || item.type === 'local-candidate') {
1200
- localCandidates[item.id] = item;
1201
- }
1202
- if (item.type === 'candidatepair' || item.type === 'candidate-pair') {
1203
- candidatePairs[item.id] = item;
1204
- }
1205
- });
1206
-
1207
- const setSelectedCandidatePair = selectedCandidatePair => {
1208
- foundSelectedCandidatePair = true;
1209
-
1210
- let local = localCandidates[selectedCandidatePair.localCandidateId];
1211
-
1212
- if (local && (local.ip || local.address)) {
1213
- // Spec
1214
- this.localAddress = local.ip || local.address;
1215
- this.localPort = Number(local.port);
1216
- } else if (local && local.ipAddress) {
1217
- // Firefox
1218
- this.localAddress = local.ipAddress;
1219
- this.localPort = Number(local.portNumber);
1220
- } else if (typeof selectedCandidatePair.googLocalAddress === 'string') {
1221
- // TODO: remove this once Chrome 58 is released
1222
- local = selectedCandidatePair.googLocalAddress.split(':');
1223
- this.localAddress = local[0];
1224
- this.localPort = Number(local[1]);
1225
- }
1226
- if (this.localAddress) {
1227
- this.localFamily = this.localAddress.includes(':') ? 'IPv6' : 'IPv4';
1228
- }
1229
-
1230
- let remote = remoteCandidates[selectedCandidatePair.remoteCandidateId];
1231
-
1232
- if (remote && (remote.ip || remote.address)) {
1233
- // Spec
1234
- this.remoteAddress = remote.ip || remote.address;
1235
- this.remotePort = Number(remote.port);
1236
- } else if (remote && remote.ipAddress) {
1237
- // Firefox
1238
- this.remoteAddress = remote.ipAddress;
1239
- this.remotePort = Number(remote.portNumber);
1240
- } else if (typeof selectedCandidatePair.googRemoteAddress === 'string') {
1241
- // TODO: remove this once Chrome 58 is released
1242
- remote = selectedCandidatePair.googRemoteAddress.split(':');
1243
- this.remoteAddress = remote[0];
1244
- this.remotePort = Number(remote[1]);
1245
- }
1246
- if (this.remoteAddress) {
1247
- this.remoteFamily = this.remoteAddress.includes(':') ? 'IPv6' : 'IPv4';
1248
- }
1249
-
1250
- this._debug(
1251
- 'connect local: %s:%s remote: %s:%s',
1252
- this.localAddress,
1253
- this.localPort,
1254
- this.remoteAddress,
1255
- this.remotePort
1256
- );
1257
- };
1258
-
1259
- items.forEach(item => {
1260
- // Spec-compliant
1261
- if (item.type === 'transport' && item.selectedCandidatePairId) {
1262
- setSelectedCandidatePair(candidatePairs[item.selectedCandidatePairId]);
1263
- }
1264
-
1265
- // Old implementations
1266
- if (
1267
- (item.type === 'googCandidatePair' && item.googActiveConnection === 'true') ||
1268
- ((item.type === 'candidatepair' || item.type === 'candidate-pair') && item.selected)
1269
- ) {
1270
- setSelectedCandidatePair(item);
1271
- }
1272
- });
1273
-
1274
- // Ignore candidate pair selection in browsers like Safari 11 that do not have any local or remote candidates
1275
- // But wait until at least 1 candidate pair is available
1276
- if (!foundSelectedCandidatePair && (!Object.keys(candidatePairs).length || Object.keys(localCandidates).length)) {
1277
- setTimeout(findCandidatePair, 100);
1278
- return
1279
- } else {
1280
- this._connecting = false;
1281
- this._connected = true;
1282
- }
1283
-
1284
- if (this._chunk) {
1285
- try {
1286
- this.send(this._chunk);
1287
- } catch (err) {
1288
- return this.destroy(errCode(err, 'ERR_DATA_CHANNEL'))
1289
- }
1290
- this._chunk = null;
1291
- this._debug('sent chunk from "write before connect"');
1292
-
1293
- const cb = this._cb;
1294
- this._cb = null;
1295
- cb(null);
1296
- }
1297
-
1298
- // If `bufferedAmountLowThreshold` and 'onbufferedamountlow' are unsupported,
1299
- // fallback to using setInterval to implement backpressure.
1300
- if (typeof this._channel.bufferedAmountLowThreshold !== 'number') {
1301
- this._interval = setInterval(() => this._onInterval(), 150);
1302
- if (this._interval.unref) this._interval.unref();
1303
- }
1304
-
1305
- this._debug('connect');
1306
- this.emit('connect');
1307
- });
1308
- };
1309
- findCandidatePair();
1310
- }
1311
-
1312
- _onInterval () {
1313
- if (!this._cb || !this._channel || this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
1314
- return
1315
- }
1316
- this._onChannelBufferedAmountLow();
1317
- }
1318
-
1319
- _onSignalingStateChange () {
1320
- if (this.destroyed) return
1321
-
1322
- if (this._pc.signalingState === 'stable') {
1323
- this._isNegotiating = false;
1324
-
1325
- // HACK: Firefox doesn't yet support removing tracks when signalingState !== 'stable'
1326
- this._debug('flushing sender queue', this._sendersAwaitingStable);
1327
- this._sendersAwaitingStable.forEach(sender => {
1328
- this._pc.removeTrack(sender);
1329
- this._queuedNegotiation = true;
1330
- });
1331
- this._sendersAwaitingStable = [];
1332
-
1333
- if (this._queuedNegotiation) {
1334
- this._debug('flushing negotiation queue');
1335
- this._queuedNegotiation = false;
1336
- this._needsNegotiation(); // negotiate again
1337
- } else {
1338
- this._debug('negotiated');
1339
- this.emit('negotiated');
1340
- }
1341
- }
1342
-
1343
- this._debug('signalingStateChange %s', this._pc.signalingState);
1344
- this.emit('signalingStateChange', this._pc.signalingState);
1345
- }
1346
-
1347
- _onIceCandidate (event) {
1348
- if (this.destroyed) return
1349
- if (event.candidate && this.trickle) {
1350
- this.emit('signal', {
1351
- type: 'candidate',
1352
- candidate: {
1353
- candidate: event.candidate.candidate,
1354
- sdpMLineIndex: event.candidate.sdpMLineIndex,
1355
- sdpMid: event.candidate.sdpMid
1356
- }
1357
- });
1358
- } else if (!event.candidate && !this._iceComplete) {
1359
- this._iceComplete = true;
1360
- this.emit('_iceComplete');
1361
- }
1362
- // as soon as we've received one valid candidate start timeout
1363
- if (event.candidate) {
1364
- this._startIceCompleteTimeout();
1365
- }
1366
- }
1367
-
1368
- _onChannelMessage (event) {
1369
- if (this.destroyed) return
1370
- let data = event.data;
1371
- if (data instanceof ArrayBuffer) data = Buffer.from(data);
1372
- this.push(data);
1373
- }
1374
-
1375
- _onChannelBufferedAmountLow () {
1376
- if (this.destroyed || !this._cb) return
1377
- this._debug('ending backpressure: bufferedAmount %d', this._channel.bufferedAmount);
1378
- const cb = this._cb;
1379
- this._cb = null;
1380
- cb(null);
1381
- }
1382
-
1383
- _onChannelOpen () {
1384
- if (this._connected || this.destroyed) return
1385
- this._debug('on channel open');
1386
- this._channelReady = true;
1387
- this._maybeReady();
1388
- }
1389
-
1390
- _onChannelClose () {
1391
- if (this.destroyed) return
1392
- this._debug('on channel close');
1393
- this.destroy();
1394
- }
1395
-
1396
- _onTrack (event) {
1397
- if (this.destroyed) return
1398
-
1399
- event.streams.forEach(eventStream => {
1400
- this._debug('on track');
1401
- this.emit('track', event.track, eventStream);
1402
-
1403
- this._remoteTracks.push({
1404
- track: event.track,
1405
- stream: eventStream
1406
- });
1407
-
1408
- if (this._remoteStreams.some(remoteStream => {
1409
- return remoteStream.id === eventStream.id
1410
- })) return // Only fire one 'stream' event, even though there may be multiple tracks per stream
1411
-
1412
- this._remoteStreams.push(eventStream);
1413
- queueMicrotask(() => {
1414
- this._debug('on stream');
1415
- this.emit('stream', eventStream); // ensure all tracks have been added
1416
- });
1417
- });
1418
- }
1419
-
1420
- _debug () {
1421
- const args = [].slice.call(arguments);
1422
- args[0] = '[' + this._id + '] ' + args[0];
1423
- debug.apply(null, args);
1424
- }
1425
- }
1426
-
1427
- Peer.WEBRTC_SUPPORT = !!getBrowserRTC();
1428
-
1429
- /**
1430
- * Expose peer and data channel config for overriding all Peer
1431
- * instances. Otherwise, just set opts.config or opts.channelConfig
1432
- * when constructing a Peer.
1433
- */
1434
- Peer.config = {
1435
- iceServers: [
1436
- {
1437
- urls: [
1438
- 'stun:stun.l.google.com:19302',
1439
- 'stun:global.stun.twilio.com:3478'
1440
- ]
1441
- }
1442
- ],
1443
- sdpSemantics: 'unified-plan'
1444
- };
1445
-
1446
- Peer.channelConfig = {};
1447
-
1448
- var simplePeer = Peer;
1449
-
1450
- var simplePeer$1 = /*#__PURE__*/Object.freeze({
1451
- __proto__: null,
1452
- default: simplePeer
1453
- });
1454
-
1455
- export { Client as default };