@appsurify-testmap/rrweb-plugin-canvas-webrtc-record 2.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +74 -0
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.cjs +1154 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.cjs.map +1 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.js +1154 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.js.map +1 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.umd.cjs +1184 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.umd.cjs.map +7 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.umd.min.cjs +32 -0
- package/dist/rrweb-plugin-canvas-webrtc-record.umd.min.cjs.map +7 -0
- package/package.json +54 -0
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
/*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
5
|
+
const MAX_BUFFERED_AMOUNT = 64 * 1024;
|
|
6
|
+
const ICECOMPLETE_TIMEOUT = 5 * 1e3;
|
|
7
|
+
const CHANNEL_CLOSING_TIMEOUT = 5 * 1e3;
|
|
8
|
+
function randombytes(size) {
|
|
9
|
+
const array = new Uint8Array(size);
|
|
10
|
+
for (let i = 0; i < size; i++) {
|
|
11
|
+
array[i] = Math.random() * 256 | 0;
|
|
12
|
+
}
|
|
13
|
+
return array;
|
|
14
|
+
}
|
|
15
|
+
function getBrowserRTC() {
|
|
16
|
+
if (typeof globalThis === "undefined") return null;
|
|
17
|
+
const wrtc = {
|
|
18
|
+
RTCPeerConnection: globalThis.RTCPeerConnection || globalThis.mozRTCPeerConnection || globalThis.webkitRTCPeerConnection,
|
|
19
|
+
RTCSessionDescription: globalThis.RTCSessionDescription || globalThis.mozRTCSessionDescription || globalThis.webkitRTCSessionDescription,
|
|
20
|
+
RTCIceCandidate: globalThis.RTCIceCandidate || globalThis.mozRTCIceCandidate || globalThis.webkitRTCIceCandidate
|
|
21
|
+
};
|
|
22
|
+
if (!wrtc.RTCPeerConnection) return null;
|
|
23
|
+
return wrtc;
|
|
24
|
+
}
|
|
25
|
+
function errCode(err, code) {
|
|
26
|
+
Object.defineProperty(err, "code", {
|
|
27
|
+
value: code,
|
|
28
|
+
enumerable: true,
|
|
29
|
+
configurable: true
|
|
30
|
+
});
|
|
31
|
+
return err;
|
|
32
|
+
}
|
|
33
|
+
function filterTrickle(sdp) {
|
|
34
|
+
return sdp.replace(/a=ice-options:trickle\s\n/g, "");
|
|
35
|
+
}
|
|
36
|
+
function warn(message) {
|
|
37
|
+
console.warn(message);
|
|
38
|
+
}
|
|
39
|
+
class Peer {
|
|
40
|
+
constructor(opts = {}) {
|
|
41
|
+
this._map = /* @__PURE__ */ new Map();
|
|
42
|
+
this._id = randombytes(4).toString("hex").slice(0, 7);
|
|
43
|
+
this._doDebug = opts.debug;
|
|
44
|
+
this._debug("new peer %o", opts);
|
|
45
|
+
this.channelName = opts.initiator ? opts.channelName || randombytes(20).toString("hex") : null;
|
|
46
|
+
this.initiator = opts.initiator || false;
|
|
47
|
+
this.channelConfig = opts.channelConfig || Peer.channelConfig;
|
|
48
|
+
this.channelNegotiated = this.channelConfig.negotiated;
|
|
49
|
+
this.config = Object.assign({}, Peer.config, opts.config);
|
|
50
|
+
this.offerOptions = opts.offerOptions || {};
|
|
51
|
+
this.answerOptions = opts.answerOptions || {};
|
|
52
|
+
this.sdpTransform = opts.sdpTransform || ((sdp) => sdp);
|
|
53
|
+
this.streams = opts.streams || (opts.stream ? [opts.stream] : []);
|
|
54
|
+
this.trickle = opts.trickle !== void 0 ? opts.trickle : true;
|
|
55
|
+
this.allowHalfTrickle = opts.allowHalfTrickle !== void 0 ? opts.allowHalfTrickle : false;
|
|
56
|
+
this.iceCompleteTimeout = opts.iceCompleteTimeout || ICECOMPLETE_TIMEOUT;
|
|
57
|
+
this.destroyed = false;
|
|
58
|
+
this.destroying = false;
|
|
59
|
+
this._connected = false;
|
|
60
|
+
this.remoteAddress = void 0;
|
|
61
|
+
this.remoteFamily = void 0;
|
|
62
|
+
this.remotePort = void 0;
|
|
63
|
+
this.localAddress = void 0;
|
|
64
|
+
this.localFamily = void 0;
|
|
65
|
+
this.localPort = void 0;
|
|
66
|
+
this._wrtc = opts.wrtc && typeof opts.wrtc === "object" ? opts.wrtc : getBrowserRTC();
|
|
67
|
+
if (!this._wrtc) {
|
|
68
|
+
if (typeof window === "undefined") {
|
|
69
|
+
throw errCode(
|
|
70
|
+
new Error(
|
|
71
|
+
"No WebRTC support: Specify `opts.wrtc` option in this environment"
|
|
72
|
+
),
|
|
73
|
+
"ERR_WEBRTC_SUPPORT"
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
throw errCode(
|
|
77
|
+
new Error("No WebRTC support: Not a supported browser"),
|
|
78
|
+
"ERR_WEBRTC_SUPPORT"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this._pcReady = false;
|
|
83
|
+
this._channelReady = false;
|
|
84
|
+
this._iceComplete = false;
|
|
85
|
+
this._iceCompleteTimer = null;
|
|
86
|
+
this._channel = null;
|
|
87
|
+
this._pendingCandidates = [];
|
|
88
|
+
this._isNegotiating = false;
|
|
89
|
+
this._firstNegotiation = true;
|
|
90
|
+
this._batchedNegotiation = false;
|
|
91
|
+
this._queuedNegotiation = false;
|
|
92
|
+
this._sendersAwaitingStable = [];
|
|
93
|
+
this._senderMap = /* @__PURE__ */ new Map();
|
|
94
|
+
this._closingInterval = null;
|
|
95
|
+
this._remoteTracks = [];
|
|
96
|
+
this._remoteStreams = [];
|
|
97
|
+
this._chunk = null;
|
|
98
|
+
this._cb = null;
|
|
99
|
+
this._interval = null;
|
|
100
|
+
try {
|
|
101
|
+
this._pc = new this._wrtc.RTCPeerConnection(this.config);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
this.destroy(errCode(err, "ERR_PC_CONSTRUCTOR"));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this._isReactNativeWebrtc = typeof this._pc._peerConnectionId === "number";
|
|
107
|
+
this._pc.oniceconnectionstatechange = () => {
|
|
108
|
+
this._onIceStateChange();
|
|
109
|
+
};
|
|
110
|
+
this._pc.onicegatheringstatechange = () => {
|
|
111
|
+
this._onIceStateChange();
|
|
112
|
+
};
|
|
113
|
+
this._pc.onconnectionstatechange = () => {
|
|
114
|
+
this._onConnectionStateChange();
|
|
115
|
+
};
|
|
116
|
+
this._pc.onsignalingstatechange = () => {
|
|
117
|
+
this._onSignalingStateChange();
|
|
118
|
+
};
|
|
119
|
+
this._pc.onicecandidate = (event) => {
|
|
120
|
+
this._onIceCandidate(event);
|
|
121
|
+
};
|
|
122
|
+
if (typeof this._pc.peerIdentity === "object") {
|
|
123
|
+
this._pc.peerIdentity.catch((err) => {
|
|
124
|
+
this.destroy(errCode(err, "ERR_PC_PEER_IDENTITY"));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (this.initiator || this.channelNegotiated) {
|
|
128
|
+
this._setupData({
|
|
129
|
+
channel: this._pc.createDataChannel(
|
|
130
|
+
this.channelName,
|
|
131
|
+
this.channelConfig
|
|
132
|
+
)
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
this._pc.ondatachannel = (event) => {
|
|
136
|
+
this._setupData(event);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (this.streams) {
|
|
140
|
+
this.streams.forEach((stream) => {
|
|
141
|
+
this.addStream(stream);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
this._pc.ontrack = (event) => {
|
|
145
|
+
this._onTrack(event);
|
|
146
|
+
};
|
|
147
|
+
this._debug("initial negotiation");
|
|
148
|
+
this._needsNegotiation();
|
|
149
|
+
}
|
|
150
|
+
get bufferSize() {
|
|
151
|
+
return this._channel && this._channel.bufferedAmount || 0;
|
|
152
|
+
}
|
|
153
|
+
// HACK: it's possible channel.readyState is "closing" before peer.destroy() fires
|
|
154
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=882743
|
|
155
|
+
get connected() {
|
|
156
|
+
return this._connected && this._channel.readyState === "open";
|
|
157
|
+
}
|
|
158
|
+
address() {
|
|
159
|
+
return {
|
|
160
|
+
port: this.localPort,
|
|
161
|
+
family: this.localFamily,
|
|
162
|
+
address: this.localAddress
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
signal(data) {
|
|
166
|
+
if (this.destroying) return;
|
|
167
|
+
if (this.destroyed) throw errCode(new Error("cannot signal after peer is destroyed"), "ERR_DESTROYED");
|
|
168
|
+
if (typeof data === "string") {
|
|
169
|
+
try {
|
|
170
|
+
data = JSON.parse(data);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
data = {};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this._debug("signal()");
|
|
176
|
+
if (data.renegotiate && this.initiator) {
|
|
177
|
+
this._debug("got request to renegotiate");
|
|
178
|
+
this._needsNegotiation();
|
|
179
|
+
}
|
|
180
|
+
if (data.transceiverRequest && this.initiator) {
|
|
181
|
+
this._debug("got request for transceiver");
|
|
182
|
+
this.addTransceiver(
|
|
183
|
+
data.transceiverRequest.kind,
|
|
184
|
+
data.transceiverRequest.init
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (data.candidate) {
|
|
188
|
+
if (this._pc.remoteDescription && this._pc.remoteDescription.type) {
|
|
189
|
+
this._addIceCandidate(data.candidate);
|
|
190
|
+
} else {
|
|
191
|
+
this._pendingCandidates.push(data.candidate);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (data.sdp) {
|
|
195
|
+
this._pc.setRemoteDescription(new this._wrtc.RTCSessionDescription(data)).then(() => {
|
|
196
|
+
if (this.destroyed) return;
|
|
197
|
+
this._pendingCandidates.forEach((candidate) => {
|
|
198
|
+
this._addIceCandidate(candidate);
|
|
199
|
+
});
|
|
200
|
+
this._pendingCandidates = [];
|
|
201
|
+
if (this._pc.remoteDescription.type === "offer") this._createAnswer();
|
|
202
|
+
}).catch((err) => {
|
|
203
|
+
this.destroy(errCode(err, "ERR_SET_REMOTE_DESCRIPTION"));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (!data.sdp && !data.candidate && !data.renegotiate && !data.transceiverRequest) {
|
|
207
|
+
this.destroy(
|
|
208
|
+
errCode(
|
|
209
|
+
new Error("signal() called with invalid signal data"),
|
|
210
|
+
"ERR_SIGNALING"
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
_addIceCandidate(candidate) {
|
|
216
|
+
const iceCandidateObj = new this._wrtc.RTCIceCandidate(candidate);
|
|
217
|
+
this._pc.addIceCandidate(iceCandidateObj).catch((err) => {
|
|
218
|
+
if (!iceCandidateObj.address || iceCandidateObj.address.endsWith(".local")) {
|
|
219
|
+
warn("Ignoring unsupported ICE candidate.");
|
|
220
|
+
} else {
|
|
221
|
+
this.destroy(errCode(err, "ERR_ADD_ICE_CANDIDATE"));
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Send text/binary data to the remote peer.
|
|
227
|
+
* @param {ArrayBufferView|ArrayBuffer|string|Blob} chunk
|
|
228
|
+
*/
|
|
229
|
+
send(chunk) {
|
|
230
|
+
if (this.destroying) return;
|
|
231
|
+
if (this.destroyed) throw errCode(new Error("cannot send after peer is destroyed"), "ERR_DESTROYED");
|
|
232
|
+
this._channel.send(chunk);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Add a Transceiver to the connection.
|
|
236
|
+
* @param {String} kind
|
|
237
|
+
* @param {Object} init
|
|
238
|
+
*/
|
|
239
|
+
addTransceiver(kind, init) {
|
|
240
|
+
if (this.destroying) return;
|
|
241
|
+
if (this.destroyed) throw errCode(new Error("cannot addTransceiver after peer is destroyed"), "ERR_DESTROYED");
|
|
242
|
+
this._debug("addTransceiver()");
|
|
243
|
+
if (this.initiator) {
|
|
244
|
+
try {
|
|
245
|
+
this._pc.addTransceiver(kind, init);
|
|
246
|
+
this._needsNegotiation();
|
|
247
|
+
} catch (err) {
|
|
248
|
+
this.destroy(errCode(err, "ERR_ADD_TRANSCEIVER"));
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
this.emit("signal", {
|
|
252
|
+
// request initiator to renegotiate
|
|
253
|
+
type: "transceiverRequest",
|
|
254
|
+
transceiverRequest: { kind, init }
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Add a MediaStream to the connection.
|
|
260
|
+
* @param {MediaStream} stream
|
|
261
|
+
*/
|
|
262
|
+
addStream(stream) {
|
|
263
|
+
if (this.destroying) return;
|
|
264
|
+
if (this.destroyed) throw errCode(new Error("cannot addStream after peer is destroyed"), "ERR_DESTROYED");
|
|
265
|
+
this._debug("addStream()");
|
|
266
|
+
stream.getTracks().forEach((track) => {
|
|
267
|
+
this.addTrack(track, stream);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Add a MediaStreamTrack to the connection.
|
|
272
|
+
* @param {MediaStreamTrack} track
|
|
273
|
+
* @param {MediaStream} stream
|
|
274
|
+
*/
|
|
275
|
+
addTrack(track, stream) {
|
|
276
|
+
if (this.destroying) return;
|
|
277
|
+
if (this.destroyed) throw errCode(new Error("cannot addTrack after peer is destroyed"), "ERR_DESTROYED");
|
|
278
|
+
this._debug("addTrack()");
|
|
279
|
+
const submap = this._senderMap.get(track) || /* @__PURE__ */ new Map();
|
|
280
|
+
let sender = submap.get(stream);
|
|
281
|
+
if (!sender) {
|
|
282
|
+
sender = this._pc.addTrack(track, stream);
|
|
283
|
+
submap.set(stream, sender);
|
|
284
|
+
this._senderMap.set(track, submap);
|
|
285
|
+
this._needsNegotiation();
|
|
286
|
+
} else if (sender.removed) {
|
|
287
|
+
throw errCode(
|
|
288
|
+
new Error(
|
|
289
|
+
"Track has been removed. You should enable/disable tracks that you want to re-add."
|
|
290
|
+
),
|
|
291
|
+
"ERR_SENDER_REMOVED"
|
|
292
|
+
);
|
|
293
|
+
} else {
|
|
294
|
+
throw errCode(
|
|
295
|
+
new Error("Track has already been added to that stream."),
|
|
296
|
+
"ERR_SENDER_ALREADY_ADDED"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Replace a MediaStreamTrack by another in the connection.
|
|
302
|
+
* @param {MediaStreamTrack} oldTrack
|
|
303
|
+
* @param {MediaStreamTrack} newTrack
|
|
304
|
+
* @param {MediaStream} stream
|
|
305
|
+
*/
|
|
306
|
+
replaceTrack(oldTrack, newTrack, stream) {
|
|
307
|
+
if (this.destroying) return;
|
|
308
|
+
if (this.destroyed) throw errCode(new Error("cannot replaceTrack after peer is destroyed"), "ERR_DESTROYED");
|
|
309
|
+
this._debug("replaceTrack()");
|
|
310
|
+
const submap = this._senderMap.get(oldTrack);
|
|
311
|
+
const sender = submap ? submap.get(stream) : null;
|
|
312
|
+
if (!sender) {
|
|
313
|
+
throw errCode(
|
|
314
|
+
new Error("Cannot replace track that was never added."),
|
|
315
|
+
"ERR_TRACK_NOT_ADDED"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (newTrack) this._senderMap.set(newTrack, submap);
|
|
319
|
+
if (sender.replaceTrack != null) {
|
|
320
|
+
sender.replaceTrack(newTrack);
|
|
321
|
+
} else {
|
|
322
|
+
this.destroy(
|
|
323
|
+
errCode(
|
|
324
|
+
new Error("replaceTrack is not supported in this browser"),
|
|
325
|
+
"ERR_UNSUPPORTED_REPLACETRACK"
|
|
326
|
+
)
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Remove a MediaStreamTrack from the connection.
|
|
332
|
+
* @param {MediaStreamTrack} track
|
|
333
|
+
* @param {MediaStream} stream
|
|
334
|
+
*/
|
|
335
|
+
removeTrack(track, stream) {
|
|
336
|
+
if (this.destroying) return;
|
|
337
|
+
if (this.destroyed) throw errCode(new Error("cannot removeTrack after peer is destroyed"), "ERR_DESTROYED");
|
|
338
|
+
this._debug("removeSender()");
|
|
339
|
+
const submap = this._senderMap.get(track);
|
|
340
|
+
const sender = submap ? submap.get(stream) : null;
|
|
341
|
+
if (!sender) {
|
|
342
|
+
throw errCode(
|
|
343
|
+
new Error("Cannot remove track that was never added."),
|
|
344
|
+
"ERR_TRACK_NOT_ADDED"
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
sender.removed = true;
|
|
349
|
+
this._pc.removeTrack(sender);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
if (err.name === "NS_ERROR_UNEXPECTED") {
|
|
352
|
+
this._sendersAwaitingStable.push(sender);
|
|
353
|
+
} else {
|
|
354
|
+
this.destroy(errCode(err, "ERR_REMOVE_TRACK"));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this._needsNegotiation();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Remove a MediaStream from the connection.
|
|
361
|
+
* @param {MediaStream} stream
|
|
362
|
+
*/
|
|
363
|
+
removeStream(stream) {
|
|
364
|
+
if (this.destroying) return;
|
|
365
|
+
if (this.destroyed) throw errCode(new Error("cannot removeStream after peer is destroyed"), "ERR_DESTROYED");
|
|
366
|
+
this._debug("removeSenders()");
|
|
367
|
+
stream.getTracks().forEach((track) => {
|
|
368
|
+
this.removeTrack(track, stream);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
_needsNegotiation() {
|
|
372
|
+
this._debug("_needsNegotiation");
|
|
373
|
+
if (this._batchedNegotiation) return;
|
|
374
|
+
this._batchedNegotiation = true;
|
|
375
|
+
queueMicrotask(() => {
|
|
376
|
+
this._batchedNegotiation = false;
|
|
377
|
+
if (this.initiator || !this._firstNegotiation) {
|
|
378
|
+
this._debug("starting batched negotiation");
|
|
379
|
+
this.negotiate();
|
|
380
|
+
} else {
|
|
381
|
+
this._debug("non-initiator initial negotiation request discarded");
|
|
382
|
+
}
|
|
383
|
+
this._firstNegotiation = false;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
negotiate() {
|
|
387
|
+
if (this.destroying) return;
|
|
388
|
+
if (this.destroyed) throw errCode(new Error("cannot negotiate after peer is destroyed"), "ERR_DESTROYED");
|
|
389
|
+
if (this.initiator) {
|
|
390
|
+
if (this._isNegotiating) {
|
|
391
|
+
this._queuedNegotiation = true;
|
|
392
|
+
this._debug("already negotiating, queueing");
|
|
393
|
+
} else {
|
|
394
|
+
this._debug("start negotiation");
|
|
395
|
+
setTimeout(() => {
|
|
396
|
+
this._createOffer();
|
|
397
|
+
}, 0);
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
if (this._isNegotiating) {
|
|
401
|
+
this._queuedNegotiation = true;
|
|
402
|
+
this._debug("already negotiating, queueing");
|
|
403
|
+
} else {
|
|
404
|
+
this._debug("requesting negotiation from initiator");
|
|
405
|
+
this.emit("signal", {
|
|
406
|
+
// request initiator to renegotiate
|
|
407
|
+
type: "renegotiate",
|
|
408
|
+
renegotiate: true
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
this._isNegotiating = true;
|
|
413
|
+
}
|
|
414
|
+
destroy(err) {
|
|
415
|
+
if (this.destroyed || this.destroying) return;
|
|
416
|
+
this.destroying = true;
|
|
417
|
+
this._debug("destroying (error: %s)", err && (err.message || err));
|
|
418
|
+
queueMicrotask(() => {
|
|
419
|
+
this.destroyed = true;
|
|
420
|
+
this.destroying = false;
|
|
421
|
+
this._debug("destroy (error: %s)", err && (err.message || err));
|
|
422
|
+
this._connected = false;
|
|
423
|
+
this._pcReady = false;
|
|
424
|
+
this._channelReady = false;
|
|
425
|
+
this._remoteTracks = null;
|
|
426
|
+
this._remoteStreams = null;
|
|
427
|
+
this._senderMap = null;
|
|
428
|
+
clearInterval(this._closingInterval);
|
|
429
|
+
this._closingInterval = null;
|
|
430
|
+
clearInterval(this._interval);
|
|
431
|
+
this._interval = null;
|
|
432
|
+
this._chunk = null;
|
|
433
|
+
this._cb = null;
|
|
434
|
+
if (this._channel) {
|
|
435
|
+
try {
|
|
436
|
+
this._channel.close();
|
|
437
|
+
} catch (err2) {
|
|
438
|
+
}
|
|
439
|
+
this._channel.onmessage = null;
|
|
440
|
+
this._channel.onopen = null;
|
|
441
|
+
this._channel.onclose = null;
|
|
442
|
+
this._channel.onerror = null;
|
|
443
|
+
}
|
|
444
|
+
if (this._pc) {
|
|
445
|
+
try {
|
|
446
|
+
this._pc.close();
|
|
447
|
+
} catch (err2) {
|
|
448
|
+
}
|
|
449
|
+
this._pc.oniceconnectionstatechange = null;
|
|
450
|
+
this._pc.onicegatheringstatechange = null;
|
|
451
|
+
this._pc.onsignalingstatechange = null;
|
|
452
|
+
this._pc.onicecandidate = null;
|
|
453
|
+
this._pc.ontrack = null;
|
|
454
|
+
this._pc.ondatachannel = null;
|
|
455
|
+
}
|
|
456
|
+
this._pc = null;
|
|
457
|
+
this._channel = null;
|
|
458
|
+
if (err) this.emit("error", err);
|
|
459
|
+
this.emit("close");
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
_setupData(event) {
|
|
463
|
+
if (!event.channel) {
|
|
464
|
+
return this.destroy(
|
|
465
|
+
errCode(
|
|
466
|
+
new Error("Data channel event is missing `channel` property"),
|
|
467
|
+
"ERR_DATA_CHANNEL"
|
|
468
|
+
)
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
this._channel = event.channel;
|
|
472
|
+
this._channel.binaryType = "arraybuffer";
|
|
473
|
+
if (typeof this._channel.bufferedAmountLowThreshold === "number") {
|
|
474
|
+
this._channel.bufferedAmountLowThreshold = MAX_BUFFERED_AMOUNT;
|
|
475
|
+
}
|
|
476
|
+
this.channelName = this._channel.label;
|
|
477
|
+
this._channel.onmessage = (event2) => {
|
|
478
|
+
this._onChannelMessage(event2);
|
|
479
|
+
};
|
|
480
|
+
this._channel.onbufferedamountlow = () => {
|
|
481
|
+
this._onChannelBufferedAmountLow();
|
|
482
|
+
};
|
|
483
|
+
this._channel.onopen = () => {
|
|
484
|
+
this._onChannelOpen();
|
|
485
|
+
};
|
|
486
|
+
this._channel.onclose = () => {
|
|
487
|
+
this._onChannelClose();
|
|
488
|
+
};
|
|
489
|
+
this._channel.onerror = (err) => {
|
|
490
|
+
this.destroy(errCode(err, "ERR_DATA_CHANNEL"));
|
|
491
|
+
};
|
|
492
|
+
let isClosing = false;
|
|
493
|
+
this._closingInterval = setInterval(() => {
|
|
494
|
+
if (this._channel && this._channel.readyState === "closing") {
|
|
495
|
+
if (isClosing) this._onChannelClose();
|
|
496
|
+
isClosing = true;
|
|
497
|
+
} else {
|
|
498
|
+
isClosing = false;
|
|
499
|
+
}
|
|
500
|
+
}, CHANNEL_CLOSING_TIMEOUT);
|
|
501
|
+
}
|
|
502
|
+
_startIceCompleteTimeout() {
|
|
503
|
+
if (this.destroyed) return;
|
|
504
|
+
if (this._iceCompleteTimer) return;
|
|
505
|
+
this._debug("started iceComplete timeout");
|
|
506
|
+
this._iceCompleteTimer = setTimeout(() => {
|
|
507
|
+
if (!this._iceComplete) {
|
|
508
|
+
this._iceComplete = true;
|
|
509
|
+
this._debug("iceComplete timeout completed");
|
|
510
|
+
this.emit("iceTimeout");
|
|
511
|
+
this.emit("_iceComplete");
|
|
512
|
+
}
|
|
513
|
+
}, this.iceCompleteTimeout);
|
|
514
|
+
}
|
|
515
|
+
_createOffer() {
|
|
516
|
+
if (this.destroyed) return;
|
|
517
|
+
this._pc.createOffer(this.offerOptions).then((offer) => {
|
|
518
|
+
if (this.destroyed) return;
|
|
519
|
+
if (!this.trickle && !this.allowHalfTrickle) {
|
|
520
|
+
offer.sdp = filterTrickle(offer.sdp);
|
|
521
|
+
}
|
|
522
|
+
offer.sdp = this.sdpTransform(offer.sdp);
|
|
523
|
+
const sendOffer = () => {
|
|
524
|
+
if (this.destroyed) return;
|
|
525
|
+
const signal = this._pc.localDescription || offer;
|
|
526
|
+
this._debug("signal");
|
|
527
|
+
this.emit("signal", {
|
|
528
|
+
type: signal.type,
|
|
529
|
+
sdp: signal.sdp
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
const onSuccess = () => {
|
|
533
|
+
this._debug("createOffer success");
|
|
534
|
+
if (this.destroyed) return;
|
|
535
|
+
if (this.trickle || this._iceComplete) sendOffer();
|
|
536
|
+
else this.once("_iceComplete", sendOffer);
|
|
537
|
+
};
|
|
538
|
+
const onError = (err) => {
|
|
539
|
+
this.destroy(errCode(err, "ERR_SET_LOCAL_DESCRIPTION"));
|
|
540
|
+
};
|
|
541
|
+
this._pc.setLocalDescription(offer).then(onSuccess).catch(onError);
|
|
542
|
+
}).catch((err) => {
|
|
543
|
+
this.destroy(errCode(err, "ERR_CREATE_OFFER"));
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
_requestMissingTransceivers() {
|
|
547
|
+
if (this._pc.getTransceivers) {
|
|
548
|
+
this._pc.getTransceivers().forEach((transceiver) => {
|
|
549
|
+
if (!transceiver.mid && transceiver.sender.track && !transceiver.requested) {
|
|
550
|
+
transceiver.requested = true;
|
|
551
|
+
this.addTransceiver(transceiver.sender.track.kind);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
_createAnswer() {
|
|
557
|
+
if (this.destroyed) return;
|
|
558
|
+
this._pc.createAnswer(this.answerOptions).then((answer) => {
|
|
559
|
+
if (this.destroyed) return;
|
|
560
|
+
if (!this.trickle && !this.allowHalfTrickle) {
|
|
561
|
+
answer.sdp = filterTrickle(answer.sdp);
|
|
562
|
+
}
|
|
563
|
+
answer.sdp = this.sdpTransform(answer.sdp);
|
|
564
|
+
const sendAnswer = () => {
|
|
565
|
+
if (this.destroyed) return;
|
|
566
|
+
const signal = this._pc.localDescription || answer;
|
|
567
|
+
this._debug("signal");
|
|
568
|
+
this.emit("signal", {
|
|
569
|
+
type: signal.type,
|
|
570
|
+
sdp: signal.sdp
|
|
571
|
+
});
|
|
572
|
+
if (!this.initiator) this._requestMissingTransceivers();
|
|
573
|
+
};
|
|
574
|
+
const onSuccess = () => {
|
|
575
|
+
if (this.destroyed) return;
|
|
576
|
+
if (this.trickle || this._iceComplete) sendAnswer();
|
|
577
|
+
else this.once("_iceComplete", sendAnswer);
|
|
578
|
+
};
|
|
579
|
+
const onError = (err) => {
|
|
580
|
+
this.destroy(errCode(err, "ERR_SET_LOCAL_DESCRIPTION"));
|
|
581
|
+
};
|
|
582
|
+
this._pc.setLocalDescription(answer).then(onSuccess).catch(onError);
|
|
583
|
+
}).catch((err) => {
|
|
584
|
+
this.destroy(errCode(err, "ERR_CREATE_ANSWER"));
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
_onConnectionStateChange() {
|
|
588
|
+
if (this.destroyed) return;
|
|
589
|
+
if (this._pc.connectionState === "failed") {
|
|
590
|
+
this.destroy(
|
|
591
|
+
errCode(new Error("Connection failed."), "ERR_CONNECTION_FAILURE")
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
_onIceStateChange() {
|
|
596
|
+
if (this.destroyed) return;
|
|
597
|
+
const iceConnectionState = this._pc.iceConnectionState;
|
|
598
|
+
const iceGatheringState = this._pc.iceGatheringState;
|
|
599
|
+
this._debug(
|
|
600
|
+
"iceStateChange (connection: %s) (gathering: %s)",
|
|
601
|
+
iceConnectionState,
|
|
602
|
+
iceGatheringState
|
|
603
|
+
);
|
|
604
|
+
this.emit("iceStateChange", iceConnectionState, iceGatheringState);
|
|
605
|
+
if (iceConnectionState === "connected" || iceConnectionState === "completed") {
|
|
606
|
+
this._pcReady = true;
|
|
607
|
+
this._maybeReady();
|
|
608
|
+
}
|
|
609
|
+
if (iceConnectionState === "failed") {
|
|
610
|
+
this.destroy(
|
|
611
|
+
errCode(
|
|
612
|
+
new Error("Ice connection failed."),
|
|
613
|
+
"ERR_ICE_CONNECTION_FAILURE"
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (iceConnectionState === "closed") {
|
|
618
|
+
this.destroy(
|
|
619
|
+
errCode(
|
|
620
|
+
new Error("Ice connection closed."),
|
|
621
|
+
"ERR_ICE_CONNECTION_CLOSED"
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
getStats(cb) {
|
|
627
|
+
const flattenValues = (report) => {
|
|
628
|
+
if (Object.prototype.toString.call(report.values) === "[object Array]") {
|
|
629
|
+
report.values.forEach((value) => {
|
|
630
|
+
Object.assign(report, value);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
return report;
|
|
634
|
+
};
|
|
635
|
+
if (this._pc.getStats.length === 0 || this._isReactNativeWebrtc) {
|
|
636
|
+
this._pc.getStats().then(
|
|
637
|
+
(res) => {
|
|
638
|
+
const reports = [];
|
|
639
|
+
res.forEach((report) => {
|
|
640
|
+
reports.push(flattenValues(report));
|
|
641
|
+
});
|
|
642
|
+
cb(null, reports);
|
|
643
|
+
},
|
|
644
|
+
(err) => cb(err)
|
|
645
|
+
);
|
|
646
|
+
} else if (this._pc.getStats.length > 0) {
|
|
647
|
+
this._pc.getStats(
|
|
648
|
+
(res) => {
|
|
649
|
+
if (this.destroyed) return;
|
|
650
|
+
const reports = [];
|
|
651
|
+
res.result().forEach((result) => {
|
|
652
|
+
const report = {};
|
|
653
|
+
result.names().forEach((name) => {
|
|
654
|
+
report[name] = result.stat(name);
|
|
655
|
+
});
|
|
656
|
+
report.id = result.id;
|
|
657
|
+
report.type = result.type;
|
|
658
|
+
report.timestamp = result.timestamp;
|
|
659
|
+
reports.push(flattenValues(report));
|
|
660
|
+
});
|
|
661
|
+
cb(null, reports);
|
|
662
|
+
},
|
|
663
|
+
(err) => cb(err)
|
|
664
|
+
);
|
|
665
|
+
} else {
|
|
666
|
+
cb(null, []);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
_maybeReady() {
|
|
670
|
+
this._debug(
|
|
671
|
+
"maybeReady pc %s channel %s",
|
|
672
|
+
this._pcReady,
|
|
673
|
+
this._channelReady
|
|
674
|
+
);
|
|
675
|
+
if (this._connected || this._connecting || !this._pcReady || !this._channelReady) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
this._connecting = true;
|
|
679
|
+
const findCandidatePair = () => {
|
|
680
|
+
if (this.destroyed) return;
|
|
681
|
+
this.getStats((err, items) => {
|
|
682
|
+
if (this.destroyed) return;
|
|
683
|
+
if (err) items = [];
|
|
684
|
+
const remoteCandidates = {};
|
|
685
|
+
const localCandidates = {};
|
|
686
|
+
const candidatePairs = {};
|
|
687
|
+
let foundSelectedCandidatePair = false;
|
|
688
|
+
items.forEach((item) => {
|
|
689
|
+
if (item.type === "remotecandidate" || item.type === "remote-candidate") {
|
|
690
|
+
remoteCandidates[item.id] = item;
|
|
691
|
+
}
|
|
692
|
+
if (item.type === "localcandidate" || item.type === "local-candidate") {
|
|
693
|
+
localCandidates[item.id] = item;
|
|
694
|
+
}
|
|
695
|
+
if (item.type === "candidatepair" || item.type === "candidate-pair") {
|
|
696
|
+
candidatePairs[item.id] = item;
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
const setSelectedCandidatePair = (selectedCandidatePair) => {
|
|
700
|
+
foundSelectedCandidatePair = true;
|
|
701
|
+
let local = localCandidates[selectedCandidatePair.localCandidateId];
|
|
702
|
+
if (local && (local.ip || local.address)) {
|
|
703
|
+
this.localAddress = local.ip || local.address;
|
|
704
|
+
this.localPort = Number(local.port);
|
|
705
|
+
} else if (local && local.ipAddress) {
|
|
706
|
+
this.localAddress = local.ipAddress;
|
|
707
|
+
this.localPort = Number(local.portNumber);
|
|
708
|
+
} else if (typeof selectedCandidatePair.googLocalAddress === "string") {
|
|
709
|
+
local = selectedCandidatePair.googLocalAddress.split(":");
|
|
710
|
+
this.localAddress = local[0];
|
|
711
|
+
this.localPort = Number(local[1]);
|
|
712
|
+
}
|
|
713
|
+
if (this.localAddress) {
|
|
714
|
+
this.localFamily = this.localAddress.includes(":") ? "IPv6" : "IPv4";
|
|
715
|
+
}
|
|
716
|
+
let remote = remoteCandidates[selectedCandidatePair.remoteCandidateId];
|
|
717
|
+
if (remote && (remote.ip || remote.address)) {
|
|
718
|
+
this.remoteAddress = remote.ip || remote.address;
|
|
719
|
+
this.remotePort = Number(remote.port);
|
|
720
|
+
} else if (remote && remote.ipAddress) {
|
|
721
|
+
this.remoteAddress = remote.ipAddress;
|
|
722
|
+
this.remotePort = Number(remote.portNumber);
|
|
723
|
+
} else if (typeof selectedCandidatePair.googRemoteAddress === "string") {
|
|
724
|
+
remote = selectedCandidatePair.googRemoteAddress.split(":");
|
|
725
|
+
this.remoteAddress = remote[0];
|
|
726
|
+
this.remotePort = Number(remote[1]);
|
|
727
|
+
}
|
|
728
|
+
if (this.remoteAddress) {
|
|
729
|
+
this.remoteFamily = this.remoteAddress.includes(":") ? "IPv6" : "IPv4";
|
|
730
|
+
}
|
|
731
|
+
this._debug(
|
|
732
|
+
"connect local: %s:%s remote: %s:%s",
|
|
733
|
+
this.localAddress,
|
|
734
|
+
this.localPort,
|
|
735
|
+
this.remoteAddress,
|
|
736
|
+
this.remotePort
|
|
737
|
+
);
|
|
738
|
+
};
|
|
739
|
+
items.forEach((item) => {
|
|
740
|
+
if (item.type === "transport" && item.selectedCandidatePairId) {
|
|
741
|
+
setSelectedCandidatePair(
|
|
742
|
+
candidatePairs[item.selectedCandidatePairId]
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
if (item.type === "googCandidatePair" && item.googActiveConnection === "true" || (item.type === "candidatepair" || item.type === "candidate-pair") && item.selected) {
|
|
746
|
+
setSelectedCandidatePair(item);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
if (!foundSelectedCandidatePair && (!Object.keys(candidatePairs).length || Object.keys(localCandidates).length)) {
|
|
750
|
+
setTimeout(findCandidatePair, 100);
|
|
751
|
+
return;
|
|
752
|
+
} else {
|
|
753
|
+
this._connecting = false;
|
|
754
|
+
this._connected = true;
|
|
755
|
+
}
|
|
756
|
+
if (this._chunk) {
|
|
757
|
+
try {
|
|
758
|
+
this.send(this._chunk);
|
|
759
|
+
} catch (err2) {
|
|
760
|
+
return this.destroy(errCode(err2, "ERR_DATA_CHANNEL"));
|
|
761
|
+
}
|
|
762
|
+
this._chunk = null;
|
|
763
|
+
this._debug('sent chunk from "write before connect"');
|
|
764
|
+
const cb = this._cb;
|
|
765
|
+
this._cb = null;
|
|
766
|
+
cb(null);
|
|
767
|
+
}
|
|
768
|
+
if (typeof this._channel.bufferedAmountLowThreshold !== "number") {
|
|
769
|
+
this._interval = setInterval(() => this._onInterval(), 150);
|
|
770
|
+
if (this._interval.unref) this._interval.unref();
|
|
771
|
+
}
|
|
772
|
+
this._debug("connect");
|
|
773
|
+
this.emit("connect");
|
|
774
|
+
});
|
|
775
|
+
};
|
|
776
|
+
findCandidatePair();
|
|
777
|
+
}
|
|
778
|
+
_onInterval() {
|
|
779
|
+
if (!this._cb || !this._channel || this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
this._onChannelBufferedAmountLow();
|
|
783
|
+
}
|
|
784
|
+
_onSignalingStateChange() {
|
|
785
|
+
if (this.destroyed) return;
|
|
786
|
+
if (this._pc.signalingState === "stable") {
|
|
787
|
+
this._isNegotiating = false;
|
|
788
|
+
this._debug("flushing sender queue", this._sendersAwaitingStable);
|
|
789
|
+
this._sendersAwaitingStable.forEach((sender) => {
|
|
790
|
+
this._pc.removeTrack(sender);
|
|
791
|
+
this._queuedNegotiation = true;
|
|
792
|
+
});
|
|
793
|
+
this._sendersAwaitingStable = [];
|
|
794
|
+
if (this._queuedNegotiation) {
|
|
795
|
+
this._debug("flushing negotiation queue");
|
|
796
|
+
this._queuedNegotiation = false;
|
|
797
|
+
this._needsNegotiation();
|
|
798
|
+
} else {
|
|
799
|
+
this._debug("negotiated");
|
|
800
|
+
this.emit("negotiated");
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
this._debug("signalingStateChange %s", this._pc.signalingState);
|
|
804
|
+
this.emit("signalingStateChange", this._pc.signalingState);
|
|
805
|
+
}
|
|
806
|
+
_onIceCandidate(event) {
|
|
807
|
+
if (this.destroyed) return;
|
|
808
|
+
if (event.candidate && this.trickle) {
|
|
809
|
+
this.emit("signal", {
|
|
810
|
+
type: "candidate",
|
|
811
|
+
candidate: {
|
|
812
|
+
candidate: event.candidate.candidate,
|
|
813
|
+
sdpMLineIndex: event.candidate.sdpMLineIndex,
|
|
814
|
+
sdpMid: event.candidate.sdpMid
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
} else if (!event.candidate && !this._iceComplete) {
|
|
818
|
+
this._iceComplete = true;
|
|
819
|
+
this.emit("_iceComplete");
|
|
820
|
+
}
|
|
821
|
+
if (event.candidate) {
|
|
822
|
+
this._startIceCompleteTimeout();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
_onChannelMessage(event) {
|
|
826
|
+
if (this.destroyed) return;
|
|
827
|
+
let data = event.data;
|
|
828
|
+
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
|
|
829
|
+
this.emit("data", data);
|
|
830
|
+
}
|
|
831
|
+
_onChannelBufferedAmountLow() {
|
|
832
|
+
if (this.destroyed || !this._cb) return;
|
|
833
|
+
this._debug(
|
|
834
|
+
"ending backpressure: bufferedAmount %d",
|
|
835
|
+
this._channel.bufferedAmount
|
|
836
|
+
);
|
|
837
|
+
const cb = this._cb;
|
|
838
|
+
this._cb = null;
|
|
839
|
+
cb(null);
|
|
840
|
+
}
|
|
841
|
+
_onChannelOpen() {
|
|
842
|
+
if (this._connected || this.destroyed) return;
|
|
843
|
+
this._debug("on channel open");
|
|
844
|
+
this._channelReady = true;
|
|
845
|
+
this._maybeReady();
|
|
846
|
+
}
|
|
847
|
+
_onChannelClose() {
|
|
848
|
+
if (this.destroyed) return;
|
|
849
|
+
this._debug("on channel close");
|
|
850
|
+
this.destroy();
|
|
851
|
+
}
|
|
852
|
+
_onTrack(event) {
|
|
853
|
+
if (this.destroyed) return;
|
|
854
|
+
event.streams.forEach((eventStream) => {
|
|
855
|
+
this._debug("on track");
|
|
856
|
+
this.emit("track", event.track, eventStream);
|
|
857
|
+
this._remoteTracks.push({
|
|
858
|
+
track: event.track,
|
|
859
|
+
stream: eventStream
|
|
860
|
+
});
|
|
861
|
+
if (this._remoteStreams.some((remoteStream) => {
|
|
862
|
+
return remoteStream.id === eventStream.id;
|
|
863
|
+
})) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
this._remoteStreams.push(eventStream);
|
|
867
|
+
queueMicrotask(() => {
|
|
868
|
+
this._debug("on stream");
|
|
869
|
+
this.emit("stream", eventStream);
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
_debug(...args) {
|
|
874
|
+
if (!this._doDebug) return;
|
|
875
|
+
args[0] = "[" + this._id + "] " + args[0];
|
|
876
|
+
console.log(...args);
|
|
877
|
+
}
|
|
878
|
+
// event emitter
|
|
879
|
+
on(key, listener) {
|
|
880
|
+
const map = this._map;
|
|
881
|
+
if (!map.has(key)) map.set(key, /* @__PURE__ */ new Set());
|
|
882
|
+
map.get(key).add(listener);
|
|
883
|
+
}
|
|
884
|
+
off(key, listener) {
|
|
885
|
+
const map = this._map;
|
|
886
|
+
const listeners = map.get(key);
|
|
887
|
+
if (!listeners) return;
|
|
888
|
+
listeners.delete(listener);
|
|
889
|
+
if (listeners.size === 0) map.delete(key);
|
|
890
|
+
}
|
|
891
|
+
once(key, listener) {
|
|
892
|
+
const listener_ = (...args) => {
|
|
893
|
+
this.off(key, listener_);
|
|
894
|
+
listener(...args);
|
|
895
|
+
};
|
|
896
|
+
this.on(key, listener_);
|
|
897
|
+
}
|
|
898
|
+
emit(key, ...args) {
|
|
899
|
+
const map = this._map;
|
|
900
|
+
if (!map.has(key)) return;
|
|
901
|
+
for (const listener of map.get(key)) {
|
|
902
|
+
try {
|
|
903
|
+
listener(...args);
|
|
904
|
+
} catch (err) {
|
|
905
|
+
console.error(err);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
Peer.WEBRTC_SUPPORT = !!getBrowserRTC();
|
|
911
|
+
Peer.config = {
|
|
912
|
+
iceServers: [
|
|
913
|
+
{
|
|
914
|
+
urls: [
|
|
915
|
+
"stun:stun.l.google.com:19302",
|
|
916
|
+
"stun:global.stun.twilio.com:3478"
|
|
917
|
+
]
|
|
918
|
+
}
|
|
919
|
+
],
|
|
920
|
+
sdpSemantics: "unified-plan"
|
|
921
|
+
};
|
|
922
|
+
Peer.channelConfig = {};
|
|
923
|
+
const PLUGIN_NAME = "rrweb/canvas-webrtc@1";
|
|
924
|
+
class RRWebPluginCanvasWebRTCRecord {
|
|
925
|
+
constructor({
|
|
926
|
+
signalSendCallback,
|
|
927
|
+
peer
|
|
928
|
+
}) {
|
|
929
|
+
__publicField(this, "peer", null);
|
|
930
|
+
__publicField(this, "mirror");
|
|
931
|
+
__publicField(this, "crossOriginIframeMirror");
|
|
932
|
+
__publicField(this, "streamMap", /* @__PURE__ */ new Map());
|
|
933
|
+
__publicField(this, "incomingStreams", /* @__PURE__ */ new Set());
|
|
934
|
+
__publicField(this, "outgoingStreams", /* @__PURE__ */ new Set());
|
|
935
|
+
__publicField(this, "streamNodeMap", /* @__PURE__ */ new Map());
|
|
936
|
+
__publicField(this, "canvasWindowMap", /* @__PURE__ */ new Map());
|
|
937
|
+
__publicField(this, "windowPeerMap", /* @__PURE__ */ new WeakMap());
|
|
938
|
+
__publicField(this, "peerWindowMap", /* @__PURE__ */ new WeakMap());
|
|
939
|
+
__publicField(this, "signalSendCallback");
|
|
940
|
+
this.signalSendCallback = signalSendCallback;
|
|
941
|
+
window.addEventListener(
|
|
942
|
+
"message",
|
|
943
|
+
(event) => this.windowPostMessageHandler(event)
|
|
944
|
+
);
|
|
945
|
+
if (peer) this.peer = peer;
|
|
946
|
+
}
|
|
947
|
+
initPlugin() {
|
|
948
|
+
return {
|
|
949
|
+
name: PLUGIN_NAME,
|
|
950
|
+
getMirror: ({ nodeMirror, crossOriginIframeMirror }) => {
|
|
951
|
+
this.mirror = nodeMirror;
|
|
952
|
+
this.crossOriginIframeMirror = crossOriginIframeMirror;
|
|
953
|
+
},
|
|
954
|
+
options: {}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
signalReceive(signal) {
|
|
958
|
+
var _a;
|
|
959
|
+
if (!this.peer) this.setupPeer();
|
|
960
|
+
(_a = this.peer) == null ? void 0 : _a.signal(signal);
|
|
961
|
+
}
|
|
962
|
+
signalReceiveFromCrossOriginIframe(signal, source) {
|
|
963
|
+
const peer = this.setupPeer(source);
|
|
964
|
+
peer.signal(signal);
|
|
965
|
+
}
|
|
966
|
+
startStream(id, stream) {
|
|
967
|
+
var _a, _b;
|
|
968
|
+
if (!this.peer) this.setupPeer();
|
|
969
|
+
const data = {
|
|
970
|
+
nodeId: id,
|
|
971
|
+
streamId: stream.id
|
|
972
|
+
};
|
|
973
|
+
(_a = this.peer) == null ? void 0 : _a.send(JSON.stringify(data));
|
|
974
|
+
if (!this.outgoingStreams.has(stream)) (_b = this.peer) == null ? void 0 : _b.addStream(stream);
|
|
975
|
+
this.outgoingStreams.add(stream);
|
|
976
|
+
}
|
|
977
|
+
setupPeer(source) {
|
|
978
|
+
let peer;
|
|
979
|
+
if (!source) {
|
|
980
|
+
if (this.peer) return this.peer;
|
|
981
|
+
peer = this.peer = new Peer({
|
|
982
|
+
initiator: true
|
|
983
|
+
// trickle: false, // only create one WebRTC offer per session
|
|
984
|
+
});
|
|
985
|
+
} else {
|
|
986
|
+
const peerFromMap = this.windowPeerMap.get(source);
|
|
987
|
+
if (peerFromMap) return peerFromMap;
|
|
988
|
+
peer = new Peer({
|
|
989
|
+
initiator: false
|
|
990
|
+
// trickle: false, // only create one WebRTC offer per session
|
|
991
|
+
});
|
|
992
|
+
this.windowPeerMap.set(source, peer);
|
|
993
|
+
this.peerWindowMap.set(peer, source);
|
|
994
|
+
}
|
|
995
|
+
const resetPeer = (source2) => {
|
|
996
|
+
if (!source2) return this.peer = null;
|
|
997
|
+
this.windowPeerMap.delete(source2);
|
|
998
|
+
this.peerWindowMap.delete(peer);
|
|
999
|
+
};
|
|
1000
|
+
peer.on("error", (err) => {
|
|
1001
|
+
resetPeer(source);
|
|
1002
|
+
console.log("error", err);
|
|
1003
|
+
});
|
|
1004
|
+
peer.on("close", () => {
|
|
1005
|
+
resetPeer(source);
|
|
1006
|
+
console.log("closing");
|
|
1007
|
+
});
|
|
1008
|
+
peer.on("signal", (data) => {
|
|
1009
|
+
var _a, _b;
|
|
1010
|
+
if (this.inRootFrame()) {
|
|
1011
|
+
if (peer === this.peer) {
|
|
1012
|
+
this.signalSendCallback(data);
|
|
1013
|
+
} else {
|
|
1014
|
+
(_a = this.peerWindowMap.get(peer)) == null ? void 0 : _a.postMessage(
|
|
1015
|
+
{
|
|
1016
|
+
type: "rrweb-canvas-webrtc",
|
|
1017
|
+
data: {
|
|
1018
|
+
type: "signal",
|
|
1019
|
+
signal: data
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
"*"
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
(_b = window.top) == null ? void 0 : _b.postMessage(
|
|
1027
|
+
{
|
|
1028
|
+
type: "rrweb-canvas-webrtc",
|
|
1029
|
+
data: {
|
|
1030
|
+
type: "signal",
|
|
1031
|
+
signal: data
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
"*"
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
peer.on("connect", () => {
|
|
1039
|
+
if (this.inRootFrame() && peer !== this.peer) return;
|
|
1040
|
+
for (const [id, stream] of this.streamMap) {
|
|
1041
|
+
this.startStream(id, stream);
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
if (!this.inRootFrame()) return peer;
|
|
1045
|
+
peer.on("data", (data) => {
|
|
1046
|
+
try {
|
|
1047
|
+
const json = JSON.parse(data);
|
|
1048
|
+
this.streamNodeMap.set(json.streamId, json.nodeId);
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
console.error("Could not parse data", error);
|
|
1051
|
+
}
|
|
1052
|
+
this.flushStreams();
|
|
1053
|
+
});
|
|
1054
|
+
peer.on("stream", (stream) => {
|
|
1055
|
+
this.incomingStreams.add(stream);
|
|
1056
|
+
this.flushStreams();
|
|
1057
|
+
});
|
|
1058
|
+
return peer;
|
|
1059
|
+
}
|
|
1060
|
+
setupStream(id, rootId) {
|
|
1061
|
+
var _a;
|
|
1062
|
+
if (id === -1 || !this.mirror) return false;
|
|
1063
|
+
let stream = this.streamMap.get(rootId || id);
|
|
1064
|
+
if (stream) return stream;
|
|
1065
|
+
const el = this.mirror.getNode(id);
|
|
1066
|
+
if (!el || !("captureStream" in el))
|
|
1067
|
+
return this.setupStreamInCrossOriginIframe(id, rootId || id);
|
|
1068
|
+
if (!this.inRootFrame()) {
|
|
1069
|
+
(_a = window.top) == null ? void 0 : _a.postMessage(
|
|
1070
|
+
{
|
|
1071
|
+
type: "rrweb-canvas-webrtc",
|
|
1072
|
+
data: {
|
|
1073
|
+
type: "i-have-canvas",
|
|
1074
|
+
rootId: rootId || id
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
"*"
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
stream = el.captureStream();
|
|
1081
|
+
this.streamMap.set(rootId || id, stream);
|
|
1082
|
+
this.setupPeer();
|
|
1083
|
+
return stream;
|
|
1084
|
+
}
|
|
1085
|
+
flushStreams() {
|
|
1086
|
+
this.incomingStreams.forEach((stream) => {
|
|
1087
|
+
const nodeId = this.streamNodeMap.get(stream.id);
|
|
1088
|
+
if (!nodeId) return;
|
|
1089
|
+
this.startStream(nodeId, stream);
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
inRootFrame() {
|
|
1093
|
+
return Boolean(window.top && window.top === window);
|
|
1094
|
+
}
|
|
1095
|
+
setupStreamInCrossOriginIframe(id, rootId) {
|
|
1096
|
+
let found = false;
|
|
1097
|
+
document.querySelectorAll("iframe").forEach((iframe) => {
|
|
1098
|
+
var _a;
|
|
1099
|
+
if (found) return;
|
|
1100
|
+
if (!this.crossOriginIframeMirror) return;
|
|
1101
|
+
const remoteId = this.crossOriginIframeMirror.getRemoteId(iframe, id);
|
|
1102
|
+
if (remoteId === -1) return;
|
|
1103
|
+
found = true;
|
|
1104
|
+
(_a = iframe.contentWindow) == null ? void 0 : _a.postMessage(
|
|
1105
|
+
{
|
|
1106
|
+
type: "rrweb-canvas-webrtc",
|
|
1107
|
+
data: {
|
|
1108
|
+
type: "who-has-canvas",
|
|
1109
|
+
id: remoteId,
|
|
1110
|
+
rootId
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
"*"
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
return found;
|
|
1117
|
+
}
|
|
1118
|
+
isCrossOriginIframeMessageEventContent(event) {
|
|
1119
|
+
return Boolean(
|
|
1120
|
+
event.data && typeof event.data === "object" && "type" in event.data && "data" in event.data && event.data.type === "rrweb-canvas-webrtc" && event.data.data
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* All messages being sent to the (root or sub) frame are received through `windowPostMessageHandler`.
|
|
1125
|
+
* @param event - The message event
|
|
1126
|
+
*/
|
|
1127
|
+
windowPostMessageHandler(event) {
|
|
1128
|
+
if (!this.isCrossOriginIframeMessageEventContent(event)) return;
|
|
1129
|
+
const { type } = event.data.data;
|
|
1130
|
+
if (type === "who-has-canvas") {
|
|
1131
|
+
const { id, rootId } = event.data.data;
|
|
1132
|
+
this.setupStream(id, rootId);
|
|
1133
|
+
} else if (type === "signal") {
|
|
1134
|
+
const { signal } = event.data.data;
|
|
1135
|
+
const { source } = event;
|
|
1136
|
+
if (!source || !("self" in source)) return;
|
|
1137
|
+
if (this.inRootFrame()) {
|
|
1138
|
+
this.signalReceiveFromCrossOriginIframe(signal, source);
|
|
1139
|
+
} else {
|
|
1140
|
+
this.signalReceive(signal);
|
|
1141
|
+
}
|
|
1142
|
+
} else if (type === "i-have-canvas") {
|
|
1143
|
+
const { rootId } = event.data.data;
|
|
1144
|
+
const { source } = event;
|
|
1145
|
+
if (!source || !("self" in source)) return;
|
|
1146
|
+
this.canvasWindowMap.set(rootId, source);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
export {
|
|
1151
|
+
PLUGIN_NAME,
|
|
1152
|
+
RRWebPluginCanvasWebRTCRecord
|
|
1153
|
+
};
|
|
1154
|
+
//# sourceMappingURL=rrweb-plugin-canvas-webrtc-record.js.map
|