@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.
- package/exports/browser/client-1d0234a7.js +624 -0
- package/exports/browser/{index-3d3f56ca.js → index-1ce30988.js} +1 -1
- package/exports/browser/{messages-af41e873.js → messages-e025829d.js} +1 -1
- package/exports/browser/{peernet-f349ddaf.js → peernet-8a51792a.js} +9 -13
- package/exports/browser/peernet.js +1 -1
- package/package.json +1 -1
- package/rollup.config.js +3 -20
- package/exports/browser/client-111c93a3.js +0 -612
- package/exports/browser/simple-peer-743c19fe.js +0 -7856
- package/exports/browser/swarm-client.js +0 -7643
- package/exports/swarm-client.js +0 -1455
package/exports/swarm-client.js
DELETED
|
@@ -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 };
|