ep_webrtc 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/static/js/index.js +26 -62
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git@github.com:ether/ep_webrtc.git",
6
6
  "type": "git"
7
7
  },
8
- "version": "2.0.4",
8
+ "version": "2.1.0",
9
9
  "description": "WebRTC based audio/video chat to Etherpad",
10
10
  "author": "John McLear <john@mclear.co.uk>",
11
11
  "contributors": [],
@@ -135,10 +135,6 @@ class ClosedEvent extends CustomEvent {
135
135
  }
136
136
  }
137
137
 
138
- // The WebRTC negotiation logic used here is based on the "Perfect Negotiation Example" at
139
- // https://www.w3.org/TR/2021/REC-webrtc-20210126/#perfect-negotiation-example. See there for
140
- // details about how it works.
141
- //
142
138
  // Events:
143
139
  // * 'stream' (see StreamEvent): Emitted when the remote stream is ready. For every 'stream' event
144
140
  // there will be a corresponding 'streamgone' event. Once a stream is added another stream will
@@ -148,15 +144,15 @@ class ClosedEvent extends CustomEvent {
148
144
  // * 'closed' (see ClosedEvent): Emitted when the PeerState is closed, except when closed by a
149
145
  // call to close(). The PeerState must not be used after it is closed.
150
146
  class PeerState extends EventTargetPolyfill {
151
- constructor(pcConfig, polite, sendMessage, localTracks, debug) {
147
+ constructor(pcConfig, caller, sendMessage, localTracks, debug) {
152
148
  super();
153
149
  this._pcConfig = pcConfig;
154
- this._polite = polite;
150
+ this._caller = caller;
155
151
  this._sendMessage = (msg) => sendMessage(Object.assign({ids: this._ids}, msg));
156
152
  this._localTracks = localTracks;
157
153
  this._failedSLDAttempts = 0;
158
154
  this._debug = debug;
159
- this._debug(`I am the ${this._polite ? '' : 'im'}polite peer`);
155
+ this._debug(`I am the ${this._caller ? 'calling' : 'answering'} peer`);
160
156
  this._ids = {
161
157
  from: {
162
158
  // Only changes when the user reloads the page.
@@ -224,15 +220,6 @@ class PeerState extends EventTargetPolyfill {
224
220
  this._setRemoteStream(null);
225
221
  this._ids.from.instance = ++nextInstanceId;
226
222
  this._ids.to = peerIds;
227
- // This negotiation state is captured in closures (instead of doing something like
228
- // this._negotiationState) because this._resetConnection() needs to be sure that all of the
229
- // event handlers for the old RTCPeerConnection do not mutate the negotiation state for the new
230
- // RTCPeerConnection.
231
- const negotiationState = {
232
- makingOffer: false,
233
- ignoreOffer: false,
234
- isSettingRemoteAnswerPending: false,
235
- };
236
223
  const pc = new RTCPeerConnection(this._pcConfig);
237
224
  pc.addEventListener('track', ({track, streams}) => {
238
225
  if (streams.length !== 1) throw new Error('Expected track to be in exactly one stream');
@@ -240,8 +227,16 @@ class PeerState extends EventTargetPolyfill {
240
227
  });
241
228
  pc.addEventListener('icecandidate', ({candidate}) => this._sendMessage({candidate}));
242
229
  pc.addEventListener('negotiationneeded', async () => {
230
+ if (!this._caller) {
231
+ this._debug('Waiting for peer to call');
232
+ // It is possible that the last invite sent to the peer was sent before the peer was ready
233
+ // to accept invites. If that is the case and there are no local tracks to trigger a WebRTC
234
+ // message exchange, the peer won't know that it is OK to connect back. Send another invite
235
+ // just in case. The peer will ignore any superfluous invites.
236
+ this._sendMessage({invite: 'invite'});
237
+ return;
238
+ }
243
239
  try {
244
- negotiationState.makingOffer = true;
245
240
  await pc.setLocalDescription();
246
241
  this._failedSLDAttempts = 0;
247
242
  this._sendMessage({description: pc.localDescription});
@@ -250,8 +245,6 @@ class PeerState extends EventTargetPolyfill {
250
245
  if (++this._failedSLDAttempts > 10) throw err; // Avoid an infinite loop.
251
246
  this._resetConnection(err);
252
247
  return;
253
- } finally {
254
- negotiationState.makingOffer = false;
255
248
  }
256
249
  });
257
250
  pc.addEventListener('connectionstatechange', () => {
@@ -299,46 +292,12 @@ class PeerState extends EventTargetPolyfill {
299
292
 
300
293
  if (this._pc != null) this._pc.close();
301
294
  this._pc = pc;
302
- this._setRemoteDescription = async (description) => {
303
- const readyForOffer = !negotiationState.makingOffer &&
304
- (pc.signalingState === 'stable' || negotiationState.isSettingRemoteAnswerPending);
305
- const offerCollision = description.type === 'offer' && !readyForOffer;
306
- negotiationState.ignoreOffer = !this._polite && offerCollision;
307
- if (negotiationState.ignoreOffer) {
308
- this._debug('ignoring offer due to offer collision');
309
- return;
310
- }
311
- negotiationState.isSettingRemoteAnswerPending = description.type === 'answer';
312
- await pc.setRemoteDescription(description);
313
- // The "Perfect Negotiation Example" doesn't put this next line inside a `finally` block. It
314
- // is unclear whether that is intentional. Fortunately it doesn't matter here: If the above
315
- // pc.setRemoteDescription() call throws, _resetConnection() is called to restart the
316
- // negotiation anyway.
317
- negotiationState.isSettingRemoteAnswerPending = false;
318
- if (description.type === 'offer') {
319
- await pc.setLocalDescription();
320
- this._sendMessage({description: pc.localDescription});
321
- }
322
- };
323
- this._addIceCandidate = async (candidate) => {
324
- try {
325
- await pc.addIceCandidate(candidate);
326
- } catch (err) {
327
- if (!negotiationState.ignoreOffer) throw err;
328
- }
329
- };
330
295
 
331
296
  const tracks = this._localTracks.stream.getTracks();
332
297
  for (const track of tracks) {
333
298
  this._debug(`start streaming ${track.kind} track ${track.id}`);
334
299
  pc.addTrack(track, this._localTracks.stream);
335
300
  }
336
- // Creating an RTCPeerConnection doesn't actually generate any control messages until
337
- // RTCPeerConnection.addTrack() is called. It is possible that the last invite sent to the peer
338
- // was sent before the peer was ready to accept invites. If that is the case and there are no
339
- // local tracks to trigger a WebRTC message exchange, the peer won't know that it is OK to
340
- // connect back. Send another invite just in case. The peer will ignore any superfluous invites.
341
- if (tracks.length === 0) this._sendMessage({invite: 'invite'});
342
301
  }
343
302
 
344
303
  async receiveMessage(msg) {
@@ -374,8 +333,14 @@ class PeerState extends EventTargetPolyfill {
374
333
  }
375
334
  if (this._pc == null) this._resetConnection(null, this._ids.to);
376
335
  try {
377
- if (description != null) await this._setRemoteDescription(description);
378
- if (candidate != null) await this._addIceCandidate(candidate);
336
+ if (description != null) {
337
+ await this._pc.setRemoteDescription(description);
338
+ if (description.type === 'offer') {
339
+ await this._pc.setLocalDescription();
340
+ this._sendMessage({description: this._pc.localDescription});
341
+ }
342
+ }
343
+ if (candidate != null) await this._pc.addIceCandidate(candidate);
379
344
  } catch (err) {
380
345
  err.peerMessage = msg;
381
346
  console.error('Error processing message from peer:', err);
@@ -396,14 +361,13 @@ class PeerState extends EventTargetPolyfill {
396
361
  }
397
362
  }
398
363
 
399
- const isPolite = (myId, otherId) => {
400
- // Compare user IDs to determine which of the two users is the "polite" user.
401
- const polite = myId.localeCompare(otherId) < 0;
402
- if ((otherId.localeCompare(myId) < 0) === polite) {
403
- // One peer must be polite and the other must be impolite.
364
+ const isCaller = (myId, otherId) => {
365
+ // Compare user IDs to determine which of the two users will initiate the call.
366
+ const caller = myId.localeCompare(otherId) < 0;
367
+ if ((otherId.localeCompare(myId) < 0) === caller) {
404
368
  throw new Error(`Peer ID ${otherId} compares equivalent to own ID ${myId}`);
405
369
  }
406
- return polite;
370
+ return caller;
407
371
  };
408
372
 
409
373
  // Periods in element IDs make it hard to build a selector string because period is for class match.
@@ -1292,7 +1256,7 @@ exports.rtc = new class {
1292
1256
  _debug('creating PeerState');
1293
1257
  peer = new PeerState(
1294
1258
  {iceServers: this._settings.iceServers},
1295
- isPolite(this.getUserId(), userId),
1259
+ isCaller(this.getUserId(), userId),
1296
1260
  (msg) => this.sendMessage(userId, msg),
1297
1261
  this._localTracks,
1298
1262
  _debug);