@yz-social/webrtc 0.0.1 → 0.0.3

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/index.js CHANGED
@@ -1,3 +1,2 @@
1
+ export { WebRTCBase } from './lib/webrtcbase.js';
1
2
  export { WebRTC } from './lib/webrtc.js';
2
- export { PromiseWebRTC } from './lib/promisewebrtc.js';
3
- export { TrickleWebRTC } from './lib/tricklewebrtc.js';
package/lib/webrtc.js CHANGED
@@ -1,42 +1,56 @@
1
- import { WebRTCDataChannels } from './datachannels.js';
1
+ import { WebRTCBase } from './webrtcbase.js';
2
2
 
3
- // Basics.
4
- export class WebRTC extends WebRTCDataChannels {
5
- static connections = new Map();
6
- static ensure({serviceLabel, multiplex = true, ...rest}) { // Answer the named connection, creating it if there isn't already a CONNECTED one by this name.
7
- // The serviceLabel is used as the log label throughout.
8
- // E.g., if a node has connections to many other nodes, but with multiple channels on each connection, then it is
9
- // convenient to name the shared connection with the id of the other side.
10
- //
11
- // If running both ends in the same Javascript, be sure to give them different names!
3
+ export class WebRTC extends WebRTCBase {
12
4
 
13
- let connection = this.connections.get(serviceLabel);
14
- // It is possible that we were backgrounded before we had a chance to act on a closing connection and remove it.
15
- if (connection) {
16
- const {connectionState, signalingState} = connection.peer;
17
- if ((connectionState === 'new') || (connectionState === 'closed') || (signalingState === 'closed')) connection = null;
18
- }
19
- if (!connection) {
20
- connection = new this({label: serviceLabel, multiplex, ...rest});
21
- if (multiplex) this.connections.set(serviceLabel, connection);
22
- }
23
- return connection;
5
+ async respond(signals) { // When a peer sends an offer or ice, this can be used to respond.
6
+ this.signals = signals;
7
+ await this.signalsReady;
8
+ return this.signals;
9
+ }
10
+ async connectVia(responder) { // Resolves when connected to the peer signaled via responder.
11
+ // Use like this (see test suite):
12
+ // let promise = this.signalsReady;
13
+ // let dataChannelPromise = this.ensureDataChannel(channelName); // Or something else that triggers negotiation.
14
+ // const aSignals = await promise;
15
+ // await this.connectVia(somethingThatCreatesPeerAndExchangesSigansl)); // Typically through peer.respond(signals).
16
+
17
+ const returning = await responder(this.signals); // this.signals might be an empty list.
18
+ if (!returning?.length) return;
19
+ this.signals = returning;
20
+ await this.connectVia(responder); // Keep "long-polling" the other side until it has nothing left to say.
21
+ }
22
+
23
+ sending = [];
24
+ get signals() { // Answer the signals that have fired on this end since the last time this.signals was asked for.
25
+ let pending = this.sending;
26
+ this.sending = [];
27
+ return pending;
28
+ }
29
+ set signals(data) { // Set with the signals received from the other end.
30
+ data.forEach(([type, message]) => this[type](message));
24
31
  }
25
32
 
26
- // Handlers for signal messages from the peer.
27
- // Note that one peer will receive offer(), and the other will receive answer(). Both receive icecandidate.
28
- offer(offer) { // Handler for receiving an offer from the other user (who started the signaling process).
29
- // Note that during signaling, we will receive negotiationneeded/answer, or offer, but not both, depending
30
- // on whether we were the one that started the signaling process.
31
- this.peer.setRemoteDescription(offer)
32
- .then(_ => this.peer.createAnswer())
33
- .then(answer => this.peer.setLocalDescription(answer)) // promise does not resolve to answer
34
- .then(_ => this.signal('answer', this.peer.localDescription));
33
+ signalResolvers = [];
34
+ resetAndResolveSignals() { // Any pending.
35
+ let resolvers = this.signalResolvers; // Reset and resolve all pending resolvers.
36
+ this.signalResolvers = [];
37
+ for (const resolve of resolvers) resolve(null);
38
+ }
39
+ get signalsReady() { // Return a promise that resolves as soon as there any ready.
40
+ // Each time that a client calls get signalsReady, a new promise is generated.
41
+ if (this.sending.length) return Promise.resolve(null);
42
+ const { promise, resolve } = Promise.withResolvers();
43
+ this.signalResolvers.push(resolve);
44
+ return promise;
35
45
  }
36
- answer(answer) { // Handler for finishing the signaling process that we started.
37
- this.peer.setRemoteDescription(answer);
46
+
47
+ signal(type, message) { // Handle signals being generated on this side, by accumulating them for get signals
48
+ super.signal(type, message);
49
+ this.sending.push([type, message]);
50
+ this.resetAndResolveSignals();
38
51
  }
39
- icecandidate(iceCandidate) { // Handler for a new candidate received from the other end through signaling.
40
- this.peer.addIceCandidate(iceCandidate).catch(error => this.icecandidateError(error));
52
+ connectionStateChange(state) { // When we connect, resetAndResolveSignals.
53
+ super.connectionStateChange(state);
54
+ if ('connected' === state) this.resetAndResolveSignals();
41
55
  }
42
56
  }
@@ -0,0 +1,42 @@
1
+ import { WebRTCDataChannels } from './datachannels.js';
2
+
3
+ // Basics.
4
+ export class WebRTCBase extends WebRTCDataChannels {
5
+ static connections = new Map();
6
+ static ensure({serviceLabel, multiplex = true, ...rest}) { // Answer the named connection, creating it if there isn't already a CONNECTED one by this name.
7
+ // The serviceLabel is used as the log label throughout.
8
+ // E.g., if a node has connections to many other nodes, but with multiple channels on each connection, then it is
9
+ // convenient to name the shared connection with the id of the other side.
10
+ //
11
+ // If running both ends in the same Javascript, be sure to give them different names!
12
+
13
+ let connection = this.connections.get(serviceLabel);
14
+ // It is possible that we were backgrounded before we had a chance to act on a closing connection and remove it.
15
+ if (connection) {
16
+ const {connectionState, signalingState} = connection.peer;
17
+ if ((connectionState === 'new') || (connectionState === 'closed') || (signalingState === 'closed')) connection = null;
18
+ }
19
+ if (!connection) {
20
+ connection = new this({label: serviceLabel, multiplex, ...rest});
21
+ if (multiplex) this.connections.set(serviceLabel, connection);
22
+ }
23
+ return connection;
24
+ }
25
+
26
+ // Handlers for signal messages from the peer.
27
+ // Note that one peer will receive offer(), and the other will receive answer(). Both receive icecandidate.
28
+ offer(offer) { // Handler for receiving an offer from the other user (who started the signaling process).
29
+ // Note that during signaling, we will receive negotiationneeded/answer, or offer, but not both, depending
30
+ // on whether we were the one that started the signaling process.
31
+ this.peer.setRemoteDescription(offer)
32
+ .then(_ => this.peer.createAnswer())
33
+ .then(answer => this.peer.setLocalDescription(answer)) // promise does not resolve to answer
34
+ .then(_ => this.signal('answer', this.peer.localDescription));
35
+ }
36
+ answer(answer) { // Handler for finishing the signaling process that we started.
37
+ this.peer.setRemoteDescription(answer);
38
+ }
39
+ icecandidate(iceCandidate) { // Handler for a new candidate received from the other end through signaling.
40
+ this.peer.addIceCandidate(iceCandidate).catch(error => this.icecandidateError(error));
41
+ }
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yz-social/webrtc",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Streamlined portable webrtc management of p2p and client2server.",
5
5
  "keywords": [
6
6
  "webrtc",
@@ -16,13 +16,14 @@
16
16
  }
17
17
  },
18
18
  "exports": {
19
- ".": "./index.mjs",
20
- "./router": "./lib/router.js"
19
+ ".": "./index.js",
20
+ "./router": "./routes/index.js"
21
21
  },
22
22
  "scripts": {
23
- "test": "npx jasmine",
24
- "testServer": "node testServer.js",
25
- "stopServer": "pkill webrtcTestServer"
23
+ "test": "(npm run test-server &); npm run test-only; npm run stop-server",
24
+ "test-only": "npx jasmine",
25
+ "test-server": "node testServer.js",
26
+ "stop-server": "pkill webrtcTestServer"
26
27
  },
27
28
  "dependencies": {
28
29
  "@roamhq/wrtc": "^0.9.1"
@@ -33,7 +34,7 @@
33
34
  },
34
35
  "repository": {
35
36
  "type": "git",
36
- "url": "git+https://github.com/kilroy-code/flexstore.git"
37
+ "url": "git+https://github.com/YZ-social/webrtc.git"
37
38
  },
38
39
  "publishConfig": {
39
40
  "registry": "https://registry.npmjs.org"
package/routes/index.js CHANGED
@@ -1,47 +1,19 @@
1
1
  import express from 'express';
2
- import { PromiseWebRTC, TrickleWebRTC } from '../index.js';
2
+ import { WebRTC } from '../index.js';
3
3
  export const router = express.Router();
4
-
5
- const testConnections = {}; // Is it really necessary to keep these around, against garbage collection?
6
- router.post('/promise/echo/:tag', async (req, res, next) => { // Test endpoint for WebRTC.
7
- // Client posts collected signal data to us.
8
- // We create a WebRTC connection here with the given tag, and answer our response signals.
9
- // We also create message handler on the channel that just sends back whatever comes over
10
- // from the other side.
4
+
5
+ const testConnections = {}; // Is it really necessary to keep these around, against garbage collection?
6
+ router.post('/data/echo/:tag', async (req, res, next) => { // Test endpoint for WebRTC.
11
7
  const {params, body} = req;
12
8
  const tag = params.tag;
13
- const signals = body;
14
- const connection = testConnections[tag] = new PromiseWebRTC({label: tag});
15
- const timer = setTimeout(() => connection.close(), 15e3);
16
- const dataPromise = connection.getDataChannelPromise('echo');
17
- dataPromise.then(dataChannel => {
18
- dataChannel.onclose = () => {
19
- clearTimeout(timer);
20
- connection.close();
21
- delete testConnections[tag];
22
- };
23
- dataChannel.onmessage = event => dataChannel.send(event.data); // Just echo what we are given.
24
- });
25
- connection.signals = signals; // Convey the posted offer+ice signals to our connection.
26
- res.send(await connection.signals); // Send back our signalling answer+ice.
27
- });
28
- router.post('/trickle/echo/:tag', async (req, res, next) => { // Test endpoint for WebRTC.
29
- // Same as above, but using trickle ice. The server "peer" is created first and given
30
- // the initial offer as above. But then the client reposts (long polling) to give
31
- // ice candidates and the server waits until there are any to give back (or connection).
32
- const {params, body} = req;
33
- const tag = params.tag;
34
- const incomingSignals = body;
9
+ let incomingSignals = body;
35
10
  let connection = testConnections[tag];
36
- console.log('post connection:', !!connection, 'incoming:', incomingSignals.map(s => s[0]));
37
11
  if (!connection) {
38
- connection = testConnections[tag] = TrickleWebRTC.ensure({serviceLabel: tag});
39
- const timer = setTimeout(() => connection.close(), 15e3);
40
- console.log('created connection and applying', incomingSignals.map(s => s[0]));
41
- connection.next = connection.signals;
12
+ connection = testConnections[tag] = new WebRTC({label: tag});
13
+ const timer = setTimeout(() => connection.close(), 15e3); // Enough to complete test, then cleanup.
42
14
  const dataPromise = connection.dataChannelPromise = connection.ensureDataChannel('echo', {}, incomingSignals);
15
+ incomingSignals = [];
43
16
  dataPromise.then(dataChannel => {
44
- connection.reportConnection(true);
45
17
  dataChannel.onclose = () => {
46
18
  clearTimeout(timer);
47
19
  connection.close();
@@ -49,13 +21,7 @@ router.post('/trickle/echo/:tag', async (req, res, next) => { // Test endpoint f
49
21
  };
50
22
  dataChannel.onmessage = event => dataChannel.send(event.data); // Just echo what we are given.
51
23
  });
52
- } else {
53
- console.log('applying incoming signals');
54
- connection.signals = incomingSignals;
55
24
  }
56
- console.log('awaiting response');
57
- const responseSignals = await Promise.race([connection.next, connection.dataChannelPromise]);
58
- connection.next = connection.signals;
59
- console.log('responding', responseSignals.map(s => s[0]));
60
- res.send(responseSignals); // Send back our signalling answer+ice.
25
+ res.send(await connection.respond(incomingSignals));
61
26
  });
27
+
@@ -1,7 +1,7 @@
1
1
  const { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach} = globalThis; // For linters.
2
- import { WebRTC, PromiseWebRTC, TrickleWebRTC } from '../index.js';
2
+ import { WebRTC, WebRTCBase } from '../index.js';
3
3
 
4
- class DirectSignaling extends WebRTC {
4
+ class DirectSignaling extends WebRTCBase {
5
5
  signal(type, message) { // Just invoke the method directly on the otherSide.
6
6
  this.otherSide[type](message);
7
7
  }
@@ -18,162 +18,26 @@ async function fetchSignals(url, signalsToSend) {
18
18
  });
19
19
  return await response.json();
20
20
  }
21
- function map(signals) { return signals?.map?.(s => s[0]); }
22
21
 
23
22
  describe("WebRTC", function () {
24
- describe("connection to server", function () {
25
- describe("using PromiseWebRTC", function () {
26
- let connection, dataChannel;
27
- beforeAll(async function () {
28
- connection = PromiseWebRTC.ensure({serviceLabel: 'PromiseClient'});
29
- const dataChannelPromise = connection.ensureDataChannel('echo');
30
- connection.signals = await fetchSignals("http://localhost:3000/test/promise/echo/foo", await connection.signals);
31
- dataChannel = await dataChannelPromise;
32
- connection.reportConnection(true);
33
- });
34
- afterAll(function () {
35
- connection.close();
36
- });
37
- it("sends and receives data", async function () {
38
- const echoPromise = new Promise(resolve => dataChannel.onmessage = event => resolve(event.data));
39
- dataChannel.send('hello');
40
- expect(await echoPromise).toBe('hello');
41
- });
42
- });
43
- describe("using trickle ICE", function () {
44
- let connection, dataChannel;
45
- beforeAll(async function () {
46
- connection = TrickleWebRTC.ensure({serviceLabel: 'Client'});
47
- const dataChannelPromise = connection.ensureDataChannel('echo');
48
- await connection.signals;
49
- async function exchange() {
50
- if (connection.peer.iceGatheringState !== 'gathering') return;
51
- const sending = connection.sending;
52
- connection.sending = [];
53
-
54
- console.log('client sending', map(sending));
55
- const returning = await fetchSignals("http://localhost:3000/test/trickle/echo/foo", sending);
56
-
57
- if (!returning?.length) return;
58
- //if (a.connection.peer.iceGatheringState !== 'gathering') return;
59
- console.log('client', 'received', map(returning), connection.peer.iceGatheringState);
60
- connection.signals = returning;
61
- exchange();
62
- }
63
- exchange();
64
23
 
65
- dataChannel = await dataChannelPromise;
66
- connection.reportConnection(true);
67
- });
68
- afterAll(function () {
69
- connection.close();
70
- });
71
- it("sends and receives data", async function () {
72
- const echoPromise = new Promise(resolve => dataChannel.onmessage = event => resolve(event.data));
73
- dataChannel.send('hello');
74
- expect(await echoPromise).toBe('hello');
75
- });
76
- });
77
- });
78
-
79
- describe('connection between two peers on the same computer', function () {
80
- function test(Kind) {
24
+ describe("connection between two peers on the same computer", function () {
25
+ const channelName = 'test';
26
+ function test(Kind, connect) {
81
27
  describe(Kind.name, function () {
82
28
  let a = {}, b = {};
83
- const channelName = 'test';
84
- const channelOptions = {};
85
29
  beforeAll(async function () {
86
30
  const debug = false;
87
31
  a.connection = Kind.ensure({serviceLabel: 'A'+Kind.name, debug});
88
32
  b.connection = Kind.ensure({serviceLabel: 'B'+Kind.name, debug});
89
33
 
90
- // Required only for DirectSignaling signal(), above. Ignored for others.
91
- a.connection.otherSide = b.connection;
92
- b.connection.otherSide = a.connection;
34
+ await connect(a, b);
93
35
 
94
- let aPromise = a.connection.signals;
95
- a.dataChannelPromise = a.connection.ensureDataChannel(channelName, channelOptions);
96
- const aSignals = await aPromise;
97
- a.connection.sending = [];
98
- //console.log(Kind.name, 'aSignals:', map(aSignals));
99
-
100
- // The second peer on the initial negotiation must specify a non-empty signals --
101
- // either an empty list for trickle-ice, or a list of the actual signals from the PromiseWebRTC.
102
-
103
- b.next = b.connection.signals;
104
- // let bPromise = b.connection.signals;
105
- // aPromise = a.connection.signals;
106
- b.dataChannelPromise = b.connection.ensureDataChannel(channelName, channelOptions, aSignals);
107
- // const bSignals = await bPromise;
108
- // console.log(Kind.name, 'bSignals:', map(bSignals));
109
-
110
- // bPromise = b.connection.signals;
111
- // a.connection.signals = bSignals;
112
-
113
- async function exchange() {
114
- if (a.connection.peer.iceGatheringState !== 'gathering') return;
115
- const sending = a.connection.sending;
116
- a.connection.sending = [];
117
-
118
- console.log(Kind.name, 'sending', map(sending), b.connection.peer.iceGatheringState);
119
- b.connection.signals = sending;
120
- const returning = await Promise.race([b.next, b.dataChannelPromise]);
121
- b.next = b.connection.signals;
122
-
123
- if (!returning?.length) return;
124
- //if (a.connection.peer.iceGatheringState !== 'gathering') return;
125
- console.log(Kind.name, 'received', map(returning), a.connection.peer.iceGatheringState);
126
- a.connection.signals = returning;
127
- exchange();
128
- }
129
- exchange();
130
-
131
- // Only needed in the trickle ice case. Harmless otherwise.
132
- async function conveySignals(from, to, promise) {
133
- const state = from.peer.iceGatheringState;
134
- if (state !== 'gathering') {
135
- console.log(Kind.name, from.label, state, 'skipping');
136
- return;
137
- }
138
- console.log(Kind.name, from.label, state, from.peer.iceConnectionState, from.peer.connectionState, from.peer.signalingState, 'waiting');
139
- const signals = await Promise.race([promise,
140
- from === a.connection ? a.dataChannelPromise : b.dataChannelPromise,
141
- ]); // 7 | f
142
- console.log(Kind.name, from.label, 'resolved:', map(signals));
143
- const next = from.signals; // 8 | g
144
- if (!signals?.length) return;
145
- to.signals = signals; // d | h
146
- conveySignals(from, to, next);
147
- }
148
- // conveySignals(a.connection, b.connection, aNext); //6.5
149
- // conveySignals(b.connection, a.connection, bNext);
150
-
151
- async function push() { // Await signals in a and send them to b.
152
- if (a.connection.peer.iceGatheringState !== 'gathering') return;
153
- const signals = await Promise.race([aPromise, a.dataChannelPromise]);
154
- aPromise = a.connection.signals;
155
- if (!signals.length) return;
156
- console.log(Kind.name, 'pushing', map(signals));
157
- b.connection.signals = signals;
158
- push();
159
- }
160
- async function pull(promise) { // Request signals from b, setting whatever we get.
161
- if (a.connection.peer.iceGatheringState !== 'gathering') return;
162
- const signals = await Promise.race([bPromise, b.dataChannelPromise]);
163
- bPromise = b.connection.signals;
164
- if (!signals.length) return;
165
- console.log(Kind.name, 'pulling', map(signals));
166
- a.connection.signals = signals;
167
- pull();
168
- }
169
- //push();
170
- //pull();
171
-
172
36
  a.testChannel = await a.dataChannelPromise;
173
37
  b.testChannel = await b.dataChannelPromise;
174
38
 
175
- await a.connection.reportConnection(true);
176
- await b.connection.reportConnection(true);
39
+ await a.connection.reportConnection();
40
+ await b.connection.reportConnection();
177
41
  });
178
42
  afterAll(async function () {
179
43
  a.connection.close();
@@ -204,8 +68,8 @@ describe("WebRTC", function () {
204
68
  describe("second channel", function () {
205
69
  beforeAll(async function () {
206
70
  const name2 = 'channel2';
207
- a.promise2 = a.connection.ensureDataChannel(name2, channelOptions);
208
- b.promise2 = b.connection.ensureDataChannel(name2, channelOptions);
71
+ a.promise2 = a.connection.ensureDataChannel(name2);
72
+ b.promise2 = b.connection.ensureDataChannel(name2);
209
73
  a.channel2 = await a.promise2;
210
74
  b.channel2 = await b.promise2;
211
75
  });
@@ -223,8 +87,68 @@ describe("WebRTC", function () {
223
87
  });
224
88
  });
225
89
  }
226
- test(DirectSignaling);
227
- test(PromiseWebRTC);
228
- test(TrickleWebRTC);
90
+ test(DirectSignaling, async (a, b) => {
91
+ a.connection.otherSide = b.connection;
92
+ b.connection.otherSide = a.connection;
93
+ a.dataChannelPromise = a.connection.ensureDataChannel(channelName);
94
+ // Give the other side an empty list of signals (else we get two offers and no answer),
95
+ // but yield for the a's offer to be directly transmitted.
96
+ b.dataChannelPromise = b.connection.ensureDataChannel(channelName, {}, await []);
97
+ });
98
+ test(WebRTC, async (a, b) => {
99
+ a.dataChannelPromise = a.connection.ensureDataChannel(channelName);
100
+ await a.connection.signalsReady; // await for offer
101
+ b.dataChannelPromise = b.connection.ensureDataChannel(channelName, {}, a.connection.signals);
102
+ await b.connection.signalsReady; // await answer
103
+ await a.connection.connectVia(signals => b.connection.respond(signals)); // Transmits signals back and forth.
104
+ });
105
+ });
106
+
107
+ describe("connection to server", function () {
108
+ describe("using NG ICE", function () {
109
+ let connection, dataChannel;
110
+ beforeAll(async function () {
111
+ connection = WebRTC.ensure({serviceLabel: 'Client'});
112
+ const ready = connection.signalsReady;
113
+ const dataChannelPromise = connection.ensureDataChannel('echo');
114
+ await ready;
115
+ connection.connectVia(signals => fetchSignals("http://localhost:3000/test/data/echo/foo", signals));
116
+ dataChannel = await dataChannelPromise;
117
+ });
118
+ afterAll(function () {
119
+ connection.close();
120
+ });
121
+ it("sends and receives data", async function () {
122
+ const echoPromise = new Promise(resolve => dataChannel.onmessage = event => resolve(event.data));
123
+ dataChannel.send('hello');
124
+ expect(await echoPromise).toBe('hello');
125
+ });
126
+ });
127
+ });
128
+
129
+ describe("capacity", function () {
130
+ const pairs = [];
131
+ const nPairs = 76; // NodeJS fails after 152 webrtc peers (76 pairs) in series. In parallel, it is only up through 67 pairs.
132
+ beforeAll(async function () {
133
+ const promises = [];
134
+ for (let i = 0; i < nPairs; i++) {
135
+ const a = {}, b = {};
136
+ a.connection = new WebRTC({label: `initiator-${i}`});
137
+ b.connection = new WebRTC({label: `receiver-${i}`});
138
+ let aPromise = a.connection.signalsReady;
139
+ a.dataChannelPromise = a.connection.ensureDataChannel('capacity');
140
+ const aSignals = await aPromise;
141
+ await a.connection.connectVia(signals => b.connection.respond(signals))
142
+ .then(() => pairs.push([a, b]));
143
+ }
144
+ await Promise.all(promises);
145
+ });
146
+ afterAll(async function () {
147
+ for (const [a] of pairs) a.connection.close();
148
+ await delay(2e3);
149
+ });
150
+ it("is pretty big.", function () {
151
+ expect(pairs.length).toBe(nPairs);
152
+ });
229
153
  });
230
154
  });
@@ -1,58 +0,0 @@
1
- import { WebRTC } from './webrtc.js';
2
-
3
- export class PromiseWebRTC extends WebRTC {
4
- // Extends WebRTC.signal() such that:
5
- // - instance.signals answers a promise that will resolve with an array of signal messages.
6
- // - instance.signals = [...signalMessages] will dispatch those messages.
7
- //
8
- // For example, suppose peer1 and peer2 are instances of this.
9
- // 0. Something triggers negotiation on peer1 (such as calling peer1.createDataChannel()).
10
- // 1. peer1.signals resolves with <signal1>, a POJO to be conveyed to peer2.
11
- // 2. Set peer2.signals = <signal1>.
12
- // 3. peer2.signals resolves with <signal2>, a POJO to be conveyed to peer1.
13
- // 4. Set peer1.signals = <signal2>.
14
- // 5. Data flows, but each side whould grab a new signals promise and be prepared to act if it resolves.
15
- //
16
- constructor({iceTimeout = 2e3, ...properties}) {
17
- super(properties);
18
- this.iceTimeout = iceTimeout;
19
- }
20
- get signals() { // Returns a promise that resolve to the signal messaging when ice candidate gathering is complete.
21
- return this._signalPromise ||= new Promise((resolve, reject) => this._signalReady = {resolve, reject});
22
- }
23
- set signals(data) { // Set with the signals received from the other end.
24
- data.forEach(([type, message]) => this[type](message));
25
- }
26
- onLocalIceCandidate(event) {
27
- // Each wrtc implementation has its own ideas as to what ice candidates to try before emitting them in icecanddiate.
28
- // Most will try things that cannot be reached, and give up when they hit the OS network timeout. Forty seconds is a long time to wait.
29
- // If the wrtc is still waiting after our iceTimeout (2 seconds), lets just go with what we have.
30
- this.timer ||= setTimeout(() => this.onLocalEndIce(), this.iceTimeout);
31
- super.onLocalIceCandidate(event);
32
- }
33
- clearIceTimer() {
34
- clearTimeout(this.timer);
35
- this.timer = null;
36
- }
37
- async onLocalEndIce() { // Resolve the promise with what we've been gathering.
38
- this.clearIceTimer();
39
- if (!this._signalPromise) {
40
- //this.logError('ice', "End of ICE without anything waiting on signals."); // Not helpful when there are three ways to receive this message.
41
- return;
42
- }
43
- this._signalReady.resolve(this.sending);
44
- this.sending = [];
45
- }
46
- sending = [];
47
- signal(type, message) {
48
- super.signal(type, message);
49
- this.sending.push([type, message]);
50
- }
51
- close() {
52
- if (this.peer.connectionState === 'failed') this._signalPromise?.reject?.();
53
- super.close();
54
- this.clearIceTimer();
55
- this._signalPromise = this._signalReady = null;
56
- this.sending = [];
57
- }
58
- }
@@ -1,49 +0,0 @@
1
- import { WebRTC } from './webrtc.js';
2
-
3
- export class TrickleWebRTC extends WebRTC {
4
- // Same idea as PromiseWebRTC, but instead of waiting for End of Ice (or timeout), signals will resolve as soon as there
5
- // are any signals ready to be picked up, and a new call to get signals will start collecting a new set of promises.
6
- // Signals stops collecting new signals when we connect (even if there are more ice candidates, and even if signals is
7
- // an empty array at that point).
8
-
9
- get signals() { // Returns a promise that collects signals until the next time someone calls this.signals,
10
- // but which resolves as soon as it is not empty (and continues to accumulate).
11
- // Clients must be careful to no grab a new signals promise until the previous one has resolved.
12
- const { xx = 0, sending } = this;
13
- const { promise, resolve } = Promise.withResolvers();
14
- const counter = xx;
15
- Object.assign(promise, {resolve, counter});
16
- this._signalPromise = promise;
17
- this.sending = [];
18
- this.xx = counter + 1;
19
- //console.log(this.label, 'get signals', {xx, sending, promise, counter, promise, sending: this.sending});
20
- return promise;
21
- }
22
- set signals(data) { // Set with the signals received from the other end.
23
- data.forEach(([type, message]) => this[type](message));
24
- }
25
- // onLocalEndIce() {
26
- // console.log(this.label, 'end ice');
27
- // this.signal('icecandidate', null); // So that clients can recognize that they should not await more signals.
28
- // }
29
- // connectionStateChange(state) {
30
- // //console.log(this.label, 'connection state', state);
31
- // super.connectionStateChange(state);
32
- // //this._signalPromise.resolve(this.sending);
33
- // }
34
- sending = [];
35
- signal(type, message) {
36
- super.signal(type, message);
37
- this.sending.push([type, message]);
38
- // Until another call to get signals (which replaces _signalPromise), we will continue to accumulate into
39
- // the same sending array, and subsequent signals will re-resolve (which has no effect on the promise).
40
- this._signalPromise.resolve(this.sending);
41
- //console.log(this.label, 'signal', type, 'resolving', this._signalPromise);
42
- }
43
- // close() {
44
- // if (this.peer.connectionState === 'failed') this._signalPromise?.reject?.();
45
- // super.close();
46
- // // this._signalPromise = null;
47
- // // this.sending = [];
48
- // }
49
- }