@yz-social/webrtc 0.1.5 → 0.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/index.js +59 -3
- package/package.json +1 -1
- package/spec/portal.js +1 -1
- package/spec/webrtcCapacitySpec.js +26 -9
- package/spec/webrtcSpec.js +69 -6
- package/stun.js +52 -0
package/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export class WebRTC {
|
|
|
20
20
|
];
|
|
21
21
|
cleanup() { // Attempt to allow everything to be garbage-collected.
|
|
22
22
|
if (!this.pc) return;
|
|
23
|
-
this.pc.onicecandidate = this.pc.ondatachannel = this.pc.onnegotiationneeded = this.pc.onconnectionstatechange = null;
|
|
23
|
+
this.pc.onicecandidate = this.pc.ondatachannel = this.pc.onnegotiationneeded = this.pc.onconnectionstatechange = this.pc.oniceconnectionstatechange = null;
|
|
24
24
|
delete this.pc;
|
|
25
25
|
delete this.dataChannelPromises;
|
|
26
26
|
delete this.dataChannelOursPromises;
|
|
@@ -56,11 +56,27 @@ export class WebRTC {
|
|
|
56
56
|
this.ignoreOffer = false;
|
|
57
57
|
|
|
58
58
|
this.pc.onicecandidate = e => {
|
|
59
|
-
if (
|
|
59
|
+
if (e.candidate === null) return;
|
|
60
60
|
//if (this.pc.connectionState === 'connected') return; // Don't waste messages. FIXME
|
|
61
|
+
// Including an empty string and of candidates marker.
|
|
62
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/icecandidate_event#indicating_the_end_of_a_generation_of_candidates
|
|
61
63
|
this.signal({ candidate: e.candidate });
|
|
62
64
|
};
|
|
63
65
|
this.pc.ondatachannel = e => this.ondatachannel(e.channel);
|
|
66
|
+
this.pc.oniceconnectionstatechange = () => {
|
|
67
|
+
if (!this.pc) return;
|
|
68
|
+
//this.flog('iceConnectionState', this.pc.iceConnectionState);
|
|
69
|
+
switch (this.pc.iceConnectionState) {
|
|
70
|
+
case 'completed':
|
|
71
|
+
this._resolveIceCompleted?.(true);
|
|
72
|
+
break;
|
|
73
|
+
case 'failed':
|
|
74
|
+
this.pc.restartIce();
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
64
80
|
this.pc.onnegotiationneeded = async () => {
|
|
65
81
|
try {
|
|
66
82
|
this.makingOffer = true;
|
|
@@ -76,6 +92,23 @@ export class WebRTC {
|
|
|
76
92
|
}
|
|
77
93
|
};
|
|
78
94
|
}
|
|
95
|
+
async renegotiate() { // Trigger negotiationneeded and promise to resolve when completed. Used in testing.
|
|
96
|
+
this._resolveIceCompleted?.(false); // clearing old promise, if any.
|
|
97
|
+
const promise = this.iceConnected;
|
|
98
|
+
this.pc.restartIce();
|
|
99
|
+
return promise;
|
|
100
|
+
}
|
|
101
|
+
get iceConnected() { // Return a promise that resolves when iceConnectionState transitions to connected or completed. Used in testing.
|
|
102
|
+
// I haven't found a reliable way to detect when this happens. If our side was in iceConnectionState 'connected' (but not 'completed')
|
|
103
|
+
// before the renegotiation, then we might never go completed. For testing, what I've been doing is racing between this side resolving,
|
|
104
|
+
// the other side resolving, and a timeout of a few seconds. NodeJS almost always resolves with the first two, and browsers mostly use the last.
|
|
105
|
+
return this._iceConnected ||= new Promise(resolve => {
|
|
106
|
+
this._resolveIceCompleted = value => {
|
|
107
|
+
this._iceConnected = null;
|
|
108
|
+
resolve(value);
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
79
112
|
async close() {
|
|
80
113
|
// Do not try to close or wait for data channels. It confuses Safari.
|
|
81
114
|
const pc = this.pc;
|
|
@@ -186,7 +219,7 @@ export class WebRTC {
|
|
|
186
219
|
// If this peer is responding to the other side, we arrange our waiting respond() to continue with data for the other side.
|
|
187
220
|
//
|
|
188
221
|
// Otherwise, if this side is allowed to initiate an outbound network request, then this side must define transferSignals(signals)
|
|
189
|
-
// to promise otherSide.respond(signals). If so, we call it with all pending signals (including the new one) and handle the
|
|
222
|
+
// to promise otherSide.respond(signals). If so, we call it with all pending signals (including the new one) and handle the
|
|
190
223
|
// response. (Which latter may trigger more calls to signal() on our side.)
|
|
191
224
|
//
|
|
192
225
|
// Otherwise, we just remember the signal for some future respond() on our side.
|
|
@@ -298,5 +331,28 @@ export class WebRTC {
|
|
|
298
331
|
Object.assign(this, {stats, transport, candidatePair, remote, protocol, candidateType, statsTime: now, statsElapsed});
|
|
299
332
|
if (doLogging) console.info(this.name, 'connected', protocol, candidateType, (statsElapsed/1e3).toFixed(1));
|
|
300
333
|
}
|
|
334
|
+
|
|
335
|
+
static getPublicIP(stunServer = "stun:stun.l.google.com:19302") { // Promise external/WAN/public IP addresses for this device.
|
|
336
|
+
// This is the equivalent of whatismyip.com and the like, but using the same stun protocol that
|
|
337
|
+
// webrtc is using. Alas, the stun protocol itself is UDP and so cannot be fetched from a browser,
|
|
338
|
+
// so we use WebRTC itself.
|
|
339
|
+
return new Promise((resolve, reject) => {
|
|
340
|
+
const pc = new wrtc.RTCPeerConnection({iceServers: [{ urls: stunServer }] });
|
|
341
|
+
pc.createDataChannel("");
|
|
342
|
+
pc.onicecandidate = ({candidate}) => {
|
|
343
|
+
if (!candidate) return;
|
|
344
|
+
if (candidate.type === 'host') return;
|
|
345
|
+
// IWBNI we could gather all such addresses and let the app pick an ipV6 if desired.
|
|
346
|
+
// However, we don't always get two addresses, and I haven't been able to get a reliable indication of end of candidates.
|
|
347
|
+
resolve(candidate.address);
|
|
348
|
+
pc.onicecandidate = pc.onicecandidateerror = null;
|
|
349
|
+
pc.close();
|
|
350
|
+
};
|
|
351
|
+
pc.onicecandidateerror = reject;
|
|
352
|
+
pc.createOffer()
|
|
353
|
+
.then((offer) => pc.setLocalDescription(offer))
|
|
354
|
+
.catch(reject);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
301
357
|
}
|
|
302
358
|
|
package/package.json
CHANGED
package/spec/portal.js
CHANGED
|
@@ -64,7 +64,7 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
64
64
|
console.log(new Date(), 'launched bot', cluster.worker.id);
|
|
65
65
|
portal = new WebRTC({name: 'portal'});
|
|
66
66
|
portal.getDataChannelPromise('data').then(dc => {
|
|
67
|
-
console.log('connected', cluster.worker.id);
|
|
67
|
+
console.log(new Date(), 'connected bot', cluster.worker.id);
|
|
68
68
|
dc.send('Welcome!');
|
|
69
69
|
});
|
|
70
70
|
portal.closed.then(() => { // Without any explicit message, this is 15 seconds after the other end goes away.
|
|
@@ -6,8 +6,11 @@ function delay(ms) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
describe("WebRTC capacity", function () {
|
|
9
|
-
let nNodes =
|
|
9
|
+
let nNodes = 75; // When running all webrtc tests at once, it is important to keep this low. (Memory leak?)
|
|
10
10
|
let perPortalDelay = 1e3;
|
|
11
|
+
let portalSlopDelay = 2e3;
|
|
12
|
+
let perConnectionDelay = 100;
|
|
13
|
+
let connectionSlopDelay = 2e3;
|
|
11
14
|
let port = 3000;
|
|
12
15
|
let baseURL = `http://localhost:${port}`;
|
|
13
16
|
// Alas, I can't seem to get more than about 150-160 nodes through ngrok, even on a machine that can handle 200 directly.
|
|
@@ -33,13 +36,14 @@ describe("WebRTC capacity", function () {
|
|
|
33
36
|
const portalProcess = spawn('node', [path.resolve(__dirname, 'portal.js'), nNodes, perPortalDelay, port]);
|
|
34
37
|
portalProcess.stdout.on('data', echo);
|
|
35
38
|
portalProcess.stderr.on('data', echo);
|
|
36
|
-
await delay(perPortalDelay *
|
|
39
|
+
await delay(perPortalDelay * nNodes + portalSlopDelay);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
console.log(new Date(), 'creating', nNodes, 'nodes');
|
|
40
43
|
for (let index = 0; index < nNodes; index++) {
|
|
41
44
|
const node = nodes[index] = new WebRTC({name: 'node' + index});
|
|
42
45
|
console.log('connecting', index);
|
|
46
|
+
node.nFetches = 0;
|
|
43
47
|
node.transferSignals = messages => fetch(`${baseURL}/join/${index}`, {
|
|
44
48
|
method: 'POST',
|
|
45
49
|
headers: { 'Content-Type': 'application/json', 'Connection': 'close' },
|
|
@@ -49,6 +53,7 @@ describe("WebRTC capacity", function () {
|
|
|
49
53
|
console.log('fetch', index, 'failed', response.status, response.statusText);
|
|
50
54
|
return null;
|
|
51
55
|
}
|
|
56
|
+
node.nFetches++;
|
|
52
57
|
return response.json();
|
|
53
58
|
});
|
|
54
59
|
node.closed.then(() => console.log('closed', index)); // Just for debugging.
|
|
@@ -57,15 +62,16 @@ describe("WebRTC capacity", function () {
|
|
|
57
62
|
node.createChannel('data', {negotiated: false});
|
|
58
63
|
await dataOpened;
|
|
59
64
|
console.log('opened', index);
|
|
60
|
-
if (!portalIsLocal) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
65
|
+
// if (!portalIsLocal) {
|
|
66
|
+
// const maxConnectionsPerNode = 3;
|
|
67
|
+
// const maxNgrokConnectionsPerSecond = 120 / 60;
|
|
68
|
+
// const secondsPerNode = maxConnectionsPerNode / maxNgrokConnectionsPerSecond;
|
|
69
|
+
// await delay(secondsPerNode * 1.5e3); // fudge factor milliseconds/second
|
|
70
|
+
// }
|
|
66
71
|
}
|
|
72
|
+
await delay(connectionSlopDelay);
|
|
67
73
|
console.log(new Date(), 'finished setup');
|
|
68
|
-
}, nNodes *
|
|
74
|
+
}, nNodes * perPortalDelay + portalSlopDelay + nNodes * perConnectionDelay + connectionSlopDelay + 1e3);
|
|
69
75
|
for (let index = 0; index < nNodes; index++) {
|
|
70
76
|
it('opened connection ' + index, function () {
|
|
71
77
|
expect(nodes[index].pc.connectionState).toBe('connected');
|
|
@@ -73,6 +79,17 @@ describe("WebRTC capacity", function () {
|
|
|
73
79
|
it('got data ' + index, async function () {
|
|
74
80
|
expect(await nodes[index].dataReceived).toBe('Welcome!');
|
|
75
81
|
});
|
|
82
|
+
it('resignals on restartIce ' + index, async function () {
|
|
83
|
+
const node = nodes[index];
|
|
84
|
+
expect(node.pc.iceConnectionState).toBe('completed');
|
|
85
|
+
node.nFetches = 0;
|
|
86
|
+
node.pc.restartIce();
|
|
87
|
+
await delay(100); // timing will vary
|
|
88
|
+
expect(node.pc.iceConnectionState).not.toBe('completed');
|
|
89
|
+
await delay(2e3); // timing will vary
|
|
90
|
+
expect(node.pc.iceConnectionState).toBe('completed');
|
|
91
|
+
expect(node.nFetches).toBeGreaterThan(0); // We will have re-signalled.
|
|
92
|
+
});
|
|
76
93
|
}
|
|
77
94
|
afterAll(async function () {
|
|
78
95
|
console.log(new Date(), 'starting teardown');
|
package/spec/webrtcSpec.js
CHANGED
|
@@ -6,7 +6,16 @@ describe("WebRTC", function () {
|
|
|
6
6
|
let connections = [];
|
|
7
7
|
describe("direct in-process signaling", function () {
|
|
8
8
|
async function makePair({debug = false, delay = 0, index = 0} = {}) {
|
|
9
|
-
//
|
|
9
|
+
// Make a pair of WebRTC objects (in the same Javascript process) that transfer signals to each other by calling
|
|
10
|
+
// respond(signals) on the other of the pair. In a real application, the WebRTC instances would be in different
|
|
11
|
+
// processes (likely on different devices) and transferSignals would instead involve some sort of InterProcess
|
|
12
|
+
// Communication or network call, ultimately resulting in the same respond(signals) being called on the other
|
|
13
|
+
// end, and the resulting signals being transferred back.
|
|
14
|
+
//
|
|
15
|
+
// connections[index] will contain {A, B, bothOpen}, where A and B are the two WebRTC, and bothOpen resolves
|
|
16
|
+
// when A and B are both open (regardless of how they were triggered, which is different for each test).
|
|
17
|
+
// We also annotate the WebRTC with various flags used in the test, and arranges to set those when the channel
|
|
18
|
+
// named 'data' is opened.
|
|
10
19
|
const configuration = { iceServers: WebRTC.iceServers };
|
|
11
20
|
const A = new WebRTC({name: `A (impolite) ${index}`, polite: false, debug, configuration});
|
|
12
21
|
const B = new WebRTC({name: `B (polite) ${index}`, polite: true, debug, configuration});
|
|
@@ -75,7 +84,11 @@ describe("WebRTC", function () {
|
|
|
75
84
|
await WebRTC.delay(1); // TODO: This is crazy, but without out, the FIRST connection in chrome hangs!
|
|
76
85
|
return connections[index] = {A, B, bothOpen: Promise.all(promises)};
|
|
77
86
|
}
|
|
78
|
-
function standardBehavior(setup, {includeConflictCheck = isBrowser, includeSecondChannel = false} = {}) {
|
|
87
|
+
function standardBehavior(setup, {includeConflictCheck = isBrowser, includeSecondChannel = false, reneg = true} = {}) {
|
|
88
|
+
// Defines a set of tests, intended to be within a suite.
|
|
89
|
+
// A beforeAll is created which calls the given setup({index}) nPairs times. setup() is expected to makePair and
|
|
90
|
+
// open the data channel in some suite-specific way.
|
|
91
|
+
|
|
79
92
|
// The nPairs does NOT seem to be a reliable way to determine how many webrtc peers can be active in the same Javascript.
|
|
80
93
|
// I have had numbers that work for every one of the cases DESCRIBEd below, and even in combinations,
|
|
81
94
|
// but it seems to get upset when all are run together, and it seemms to depend on the state of the machine or phases of the moon.
|
|
@@ -91,6 +104,7 @@ describe("WebRTC", function () {
|
|
|
91
104
|
//
|
|
92
105
|
// webrtcCapacitySpec.js may be a better test for capacity.
|
|
93
106
|
const nPairs = 10;
|
|
107
|
+
|
|
94
108
|
beforeAll(async function () {
|
|
95
109
|
const start = Date.now();
|
|
96
110
|
console.log(new Date(), 'start setup', nPairs, 'pairs');
|
|
@@ -110,7 +124,7 @@ describe("WebRTC", function () {
|
|
|
110
124
|
expect(B.theirs.readyState).toBe('open');
|
|
111
125
|
});
|
|
112
126
|
it(`receives ${index}.`, async function () {
|
|
113
|
-
const {A, B} = connections[index];
|
|
127
|
+
const {A, B} = connections[index];
|
|
114
128
|
await B.gotData;
|
|
115
129
|
expect(B.receivedMessageCount).toBe(A.sentMessageCount);
|
|
116
130
|
await A.gotData;
|
|
@@ -121,6 +135,34 @@ describe("WebRTC", function () {
|
|
|
121
135
|
expect(A.sentMessageCount).toBe(1);
|
|
122
136
|
expect(B.sentMessageCount).toBe(1);
|
|
123
137
|
});
|
|
138
|
+
let waitBefore = Math.random() < 0.5;
|
|
139
|
+
it(`re-negotiates ${index} waiting to settle ${waitBefore ? 'before' : 'after'} sending.`, async function () {
|
|
140
|
+
const {A, B} = connections[index];
|
|
141
|
+
await A.gotData; // if receive test hasn't fired yet, the set setup might not yet have completed capturing the send count.
|
|
142
|
+
await B.gotData;
|
|
143
|
+
|
|
144
|
+
// Capture counts expected by the other tests.
|
|
145
|
+
const {sentMessageCount:aSend, receivedMessageCount:aReceive} = A;
|
|
146
|
+
const {sentMessageCount:bSend, receivedMessageCount:bReceive} = B;
|
|
147
|
+
|
|
148
|
+
async function reneg(A, B) {
|
|
149
|
+
let aIce = A.renegotiate();
|
|
150
|
+
// We're supposed to be able to send and receive during renegotiation, so we flip a coin
|
|
151
|
+
// as to whether the test will wait before sending or after.
|
|
152
|
+
const timeout = 1e3; // NodeJS usually resolves with our side or sometimes the other going to completed, but not browsers.
|
|
153
|
+
if (waitBefore) await Promise.race([aIce, B.iceConnected, WebRTC.delay(timeout)]);
|
|
154
|
+
const gotData = new Promise(resolve => B.ours.addEventListener('message', e => resolve(e.data)));
|
|
155
|
+
A.theirs.send('after');
|
|
156
|
+
expect(await gotData).toBe('after');
|
|
157
|
+
if (!waitBefore) await Promise.race([aIce, B.iceConnected, WebRTC.delay(timeout)]);
|
|
158
|
+
}
|
|
159
|
+
await reneg(A, B);
|
|
160
|
+
await reneg(B, A);
|
|
161
|
+
|
|
162
|
+
// Restore counts expected by the other tests.
|
|
163
|
+
Object.assign(A, {sentMessageCount:aSend, receivedMessageCount:aReceive});
|
|
164
|
+
Object.assign(B, {sentMessageCount:bSend, receivedMessageCount:bReceive});
|
|
165
|
+
}, 10e3);
|
|
124
166
|
if (includeSecondChannel) {
|
|
125
167
|
it(`handles second channel ${index}.`, async function () {
|
|
126
168
|
const {A, B} = connections[index];
|
|
@@ -155,6 +197,9 @@ describe("WebRTC", function () {
|
|
|
155
197
|
let promise = A.close().then(async apc => {
|
|
156
198
|
expect(apc.connectionState).toBe('closed'); // Only on the side that explicitly closed.
|
|
157
199
|
expect(apc.signalingState).toBe('closed');
|
|
200
|
+
|
|
201
|
+
await B.close(); // fixme: B will try to reconnect unless we tell it to stop.
|
|
202
|
+
|
|
158
203
|
const bpc = await B.closed; // Waiting for B to notice.
|
|
159
204
|
await B.close(); // Resources are not necessarilly freed when the other side closes. An explicit close() is needed.
|
|
160
205
|
expect(['closed', 'disconnected', 'failed']).toContain(bpc.connectionState);
|
|
@@ -168,6 +213,12 @@ describe("WebRTC", function () {
|
|
|
168
213
|
}, Math.max(30e3, 1e3 * nPairs));
|
|
169
214
|
}
|
|
170
215
|
describe("one side opens", function () {
|
|
216
|
+
// One of each pair definitively initiates the connection that the other side was waiting for. This can happen
|
|
217
|
+
// when one peer contacts another out of the blue, or when a client contacts a server or portal.
|
|
218
|
+
// We test the usual negotiated=true case (where the initiator names the bidirectional channel and the app
|
|
219
|
+
// arranges for the reciever to actively create a channel with the same app-specific name,
|
|
220
|
+
// and the negotiated=false case.
|
|
221
|
+
|
|
171
222
|
describe('non-negotiated', function () {
|
|
172
223
|
beforeAll(function () {console.log('one-sided non-negotiated'); });
|
|
173
224
|
standardBehavior(async function ({index}) {
|
|
@@ -199,6 +250,18 @@ describe("WebRTC", function () {
|
|
|
199
250
|
});
|
|
200
251
|
|
|
201
252
|
describe("simultaneous two-sided", function () {
|
|
253
|
+
// The two sides both attempt to initiate a connection at the same time. This can happen betwen homogeneous peers.
|
|
254
|
+
// There is a matrix of four possibilities:
|
|
255
|
+
//
|
|
256
|
+
// negotiated=true - the usual case, in which the application expects both sides to be be created with the same
|
|
257
|
+
// app-specific bidirectional channel name...
|
|
258
|
+
// negotiatedl=false - meaning that each side is going to create it's own sending channel which will automatically
|
|
259
|
+
// have a distinct index (even if the channel name is the same).
|
|
260
|
+
//
|
|
261
|
+
// Within these, we test with the "polite" side starting first, or starting second. On the network, we cannot
|
|
262
|
+
// coordinate which app instance will attempt to initiate connection, but we can arrange for any pair to agree
|
|
263
|
+
// on which of the two is the "polite" one (e.g., by sort order of their names or some such).
|
|
264
|
+
|
|
202
265
|
describe("negotiated single full-duplex-channel", function () {
|
|
203
266
|
describe("impolite first", function () {
|
|
204
267
|
beforeAll(function () {console.log('two-sided negotiated impolite-first'); });
|
|
@@ -207,7 +270,7 @@ describe("WebRTC", function () {
|
|
|
207
270
|
A.createChannel("data", {negotiated: true});
|
|
208
271
|
B.createChannel("data", {negotiated: true});
|
|
209
272
|
await bothOpen;
|
|
210
|
-
});
|
|
273
|
+
}, {reneg: true});
|
|
211
274
|
});
|
|
212
275
|
describe("polite first", function () {
|
|
213
276
|
beforeAll(function () {console.log('two-sided negotiated polite-first');});
|
|
@@ -217,10 +280,10 @@ describe("WebRTC", function () {
|
|
|
217
280
|
B.createChannel("data", {negotiated: true});
|
|
218
281
|
A.createChannel("data", {negotiated: true});
|
|
219
282
|
await bothOpen;
|
|
220
|
-
});
|
|
283
|
+
}, {reneg: true});
|
|
221
284
|
});
|
|
222
285
|
});
|
|
223
|
-
describe("non-negotiated dual half-duplex channels", function () {
|
|
286
|
+
describe("non-negotiated dual half-duplex channels", function () { // fixme: sometimes fail to renegotiate
|
|
224
287
|
const delay = 200;
|
|
225
288
|
const debug = false;
|
|
226
289
|
describe("impolite first", function () {
|
package/stun.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const wrtc = (typeof(process) === 'undefined') ? globalThis : (await import('#wrtc')).default;
|
|
2
|
+
|
|
3
|
+
// function getIPs(stunServer = 'stun:stun.l.google.com:19302') { // Promise external/WAN/public IP addresses for this device.
|
|
4
|
+
// // This is the equivalent of whatismyip.com and the like, but using the same stun protocol that
|
|
5
|
+
// // webrtc is using. Alas, the stun protocol itself is UDP and so cannot be fetched from a browser,
|
|
6
|
+
// // so we use WebRTC itself.
|
|
7
|
+
// return new Promise((resolve, reject) => {
|
|
8
|
+
// const addresses = [];
|
|
9
|
+
// const pc = new wrtc.RTCPeerConnection({ iceServers: [ {urls: stunServer} ] });
|
|
10
|
+
// const done = () => {
|
|
11
|
+
// console.log('done');
|
|
12
|
+
// pc.onicecandidateerror = pc.onicegatheringstatechange = pc.onicecandidate = null;
|
|
13
|
+
// pc.close();
|
|
14
|
+
// resolve(addresses);
|
|
15
|
+
// };
|
|
16
|
+
// pc.createDataChannel('');
|
|
17
|
+
// pc.createOffer()
|
|
18
|
+
// .then(offer => pc.setLocalDescription(offer))
|
|
19
|
+
// .catch(reject);
|
|
20
|
+
// pc.onicecandidateerror = e => reject(e);
|
|
21
|
+
// pc.onicegatheringstatechange = e => (pc.iceGatheringState === 'complete') && done();
|
|
22
|
+
// pc.onicecandidate = (ice) => {
|
|
23
|
+
// console.log(ice.candidate.type);
|
|
24
|
+
// if (!ice || !ice.candidate) return done();
|
|
25
|
+
// if (ice.candidate.type === 'host') return null;
|
|
26
|
+
// return addresses.push(ice.candidate.address);
|
|
27
|
+
// };
|
|
28
|
+
// });
|
|
29
|
+
// }
|
|
30
|
+
|
|
31
|
+
export function getPublicIP(stunServer = "stun:stun.l.google.com:19302") {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const pc = new wrtc.RTCPeerConnection({iceServers: [{ urls: stunServer }] });
|
|
34
|
+
pc.createDataChannel("");
|
|
35
|
+
pc.onicecandidate = ({candidate}) => {
|
|
36
|
+
if (!candidate) return;
|
|
37
|
+
if (candidate.type !== "srflx") return;
|
|
38
|
+
resolve(candidate.address);
|
|
39
|
+
pc.onicecandidate = pc.onicecandidateerror = null;
|
|
40
|
+
pc.close();
|
|
41
|
+
};
|
|
42
|
+
pc.onicecandidateerror = reject;
|
|
43
|
+
pc.createOffer()
|
|
44
|
+
.then((offer) => pc.setLocalDescription(offer))
|
|
45
|
+
.catch(reject);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Usage
|
|
50
|
+
//await getPublicIP().then(ip => console.log("Public IP:", ip));
|
|
51
|
+
|
|
52
|
+
|