@yz-social/webrtc 0.0.5 → 0.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.
- package/README.md +10 -0
- package/index.js +268 -2
- package/jasmine-standalone-5.7.1/LICENSE +21 -0
- package/jasmine-standalone-5.7.1/SpecRunner.html +28 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/boot0.js +66 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/boot1.js +134 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/jasmine-html.js +971 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/jasmine.css +301 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/jasmine.js +11399 -0
- package/jasmine-standalone-5.7.1/lib/jasmine-5.7.1/jasmine_favicon.png +0 -0
- package/jasmine-standalone-5.7.1/spec/PlayerSpec.js +58 -0
- package/jasmine-standalone-5.7.1/spec/SpecHelper.js +15 -0
- package/jasmine-standalone-5.7.1/src/Player.js +22 -0
- package/jasmine-standalone-5.7.1/src/Song.js +6 -0
- package/package.json +2 -5
- package/spec/test.html +14 -0
- package/spec/webrtcSpec.js +198 -143
- package/lib/datachannels.js +0 -144
- package/lib/peerevents.js +0 -56
- package/lib/utilities.js +0 -92
- package/lib/webrtc.js +0 -57
- package/lib/webrtcbase.js +0 -42
package/lib/peerevents.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { WebRTCUtilities } from './utilities.js';
|
|
2
|
-
|
|
3
|
-
// Extendible methods for events on the RTCPeerConnection.
|
|
4
|
-
export class WebRTCPeerEvents extends WebRTCUtilities {
|
|
5
|
-
resetPeer() {
|
|
6
|
-
const peer = super.resetPeer();
|
|
7
|
-
|
|
8
|
-
peer.onnegotiationneeded = event => this.negotiationneeded(event);
|
|
9
|
-
peer.onicecandidate = event => this.onLocalIceCandidate(event);
|
|
10
|
-
// I don't think anyone actually signals this. Instead, they reject from addIceCandidate, which we handle the same.
|
|
11
|
-
peer.onicecandidateerror = error => this.icecandidateError(error);
|
|
12
|
-
// I think this is redundant because no implementation fires this event any significant time ahead of emitting icecandidate with an empty event.candidate.
|
|
13
|
-
peer.onicegatheringstatechange = event => (peer.iceGatheringState === 'complete') && this.onLocalEndIce;
|
|
14
|
-
peer.onconnectionstatechange = event => this.connectionStateChange(this.peer.connectionState);
|
|
15
|
-
|
|
16
|
-
return peer;
|
|
17
|
-
}
|
|
18
|
-
negotiationneeded() { // Something has changed locally (new stream, or network change), such that we have to start negotiation.
|
|
19
|
-
this.log('negotiationnneeded');
|
|
20
|
-
this.peer.createOffer()
|
|
21
|
-
.then(offer => {
|
|
22
|
-
this.peer.setLocalDescription(offer); // promise does not resolve to offer
|
|
23
|
-
return offer;
|
|
24
|
-
})
|
|
25
|
-
.then(offer => this.signal('offer', offer))
|
|
26
|
-
.catch(error => this.negotiationneededError(error));
|
|
27
|
-
}
|
|
28
|
-
negotiationneededError(eventOrException) {
|
|
29
|
-
this.logError('negotiationneeded', eventOrException);
|
|
30
|
-
}
|
|
31
|
-
icecandidateError(eventOrException) { // For errors on this peer during gathering.
|
|
32
|
-
// Can be overridden or extended by applications.
|
|
33
|
-
|
|
34
|
-
// STUN errors are in the range 300-699. See RFC 5389, section 15.6
|
|
35
|
-
// for a list of codes. TURN adds a few more error codes; see
|
|
36
|
-
// RFC 5766, section 15 for details.
|
|
37
|
-
// Server could not be reached are in the range 700-799.
|
|
38
|
-
const code = eventOrException.code || eventOrException.errorCode || eventOrException.status;
|
|
39
|
-
// Chrome gives 701 errors for some turn servers that it does not give for other turn servers.
|
|
40
|
-
// This isn't good, but it's way too noisy to slog through such errors, and I don't know how to fix our turn configuration.
|
|
41
|
-
if (code === 701) return;
|
|
42
|
-
this.logError('ice', eventOrException);
|
|
43
|
-
}
|
|
44
|
-
onLocalIceCandidate(event) {
|
|
45
|
-
// The spec says that a null candidate should not be sent, but that an empty string candidate should. Safari (used to?) get errors either way.
|
|
46
|
-
if (!event.candidate || !event.candidate.candidate) this.onLocalEndIce();
|
|
47
|
-
else this.signal('icecandidate', event.candidate);
|
|
48
|
-
}
|
|
49
|
-
onLocalEndIce() { // Triggered on our side by any/all of onicecandidate with no event.candidate, iceGatheringState === 'complete'.
|
|
50
|
-
// I.e., can happen multiple times. Subclasses might do something.
|
|
51
|
-
}
|
|
52
|
-
connectionStateChange(state) {
|
|
53
|
-
this.log('state change:', state);
|
|
54
|
-
if (['disconnected', 'failed', 'closed'].includes(state)) this.close(); // Other behavior are reasonable, tolo.
|
|
55
|
-
}
|
|
56
|
-
}
|
package/lib/utilities.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import wrtc from '#wrtc';
|
|
2
|
-
|
|
3
|
-
const iceServers = [ // Some default stun and even turn servers.
|
|
4
|
-
|
|
5
|
-
{ urls: 'stun:stun.l.google.com:19302'},
|
|
6
|
-
// https://freestun.net/ Currently 50 KBit/s. (2.5 MBit/s fors $9/month)
|
|
7
|
-
{ urls: 'stun:freestun.net:3478' },
|
|
8
|
-
|
|
9
|
-
//{ urls: 'turn:freestun.net:3478', username: 'free', credential: 'free' },
|
|
10
|
-
// Presumably traffic limited. Can generate new credentials at https://speed.cloudflare.com/turn-creds
|
|
11
|
-
// Also https://developers.cloudflare.com/calls/ 1 TB/month, and $0.05 /GB after that.
|
|
12
|
-
{ urls: 'turn:turn.speed.cloudflare.com:50000', username: '826226244cd6e5edb3f55749b796235f420fe5ee78895e0dd7d2baa45e1f7a8f49e9239e78691ab38b72ce016471f7746f5277dcef84ad79fc60f8020b132c73', credential: 'aba9b169546eb6dcc7bfb1cdf34544cf95b5161d602e3b5fa7c8342b2e9802fb' }
|
|
13
|
-
|
|
14
|
-
// See also:
|
|
15
|
-
// https://fastturn.net/ Currently 500MB/month? (25 GB/month for $9/month)
|
|
16
|
-
// https://xirsys.com/pricing/ 500 MB/month (50 GB/month for $33/month)
|
|
17
|
-
// Also https://www.npmjs.com/package/node-turn or https://meetrix.io/blog/webrtc/coturn/installation.html
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
// Basic logging and configuration wrapper around an instance of RTCPeerConnection.
|
|
21
|
-
export class WebRTCUtilities {
|
|
22
|
-
constructor({label = '', configuration = null, debug = false, error = console.error, ...rest} = {}) {
|
|
23
|
-
configuration ??= {iceServers}; // If configuration can be ommitted or explicitly as null, to use our default. But if {}, leave it be.
|
|
24
|
-
Object.assign(this, {label, configuration, debug, error, ...rest});
|
|
25
|
-
this.resetPeer();
|
|
26
|
-
this.connectionStartTime = Date.now();
|
|
27
|
-
}
|
|
28
|
-
signal(type, message) { // Subclasses must override or extend. Default just logs.
|
|
29
|
-
this.log('sending', type, type.length, JSON.stringify(message).length);
|
|
30
|
-
}
|
|
31
|
-
close() {
|
|
32
|
-
if ((this.peer.connectionState === 'new') && (this.peer.signalingState === 'stable')) return;
|
|
33
|
-
this.resetPeer();
|
|
34
|
-
}
|
|
35
|
-
instanceVersion = 0;
|
|
36
|
-
resetPeer() { // Set up a new RTCPeerConnection.
|
|
37
|
-
const old = this.peer;
|
|
38
|
-
if (old) {
|
|
39
|
-
old.onnegotiationneeded = old.onicecandidate = old.onicecandidateerror = old.onconnectionstatechange = null;
|
|
40
|
-
// Don't close unless it's been opened, because there are likely handlers that we don't want to fire.
|
|
41
|
-
if (old.connectionState !== 'new') old.close();
|
|
42
|
-
}
|
|
43
|
-
const peer = this.peer = new wrtc.RTCPeerConnection(this.configuration);
|
|
44
|
-
peer.instanceVersion = this.instanceVersion++;
|
|
45
|
-
|
|
46
|
-
return peer;
|
|
47
|
-
}
|
|
48
|
-
async reportConnection(doLogging = false) { // Update self with latest wrtc stats (and log them if doLogging true). See Object.assign for properties.
|
|
49
|
-
const stats = await this.peer.getStats();
|
|
50
|
-
let transport;
|
|
51
|
-
for (const report of stats.values()) {
|
|
52
|
-
if (report.type === 'transport') {
|
|
53
|
-
transport = report;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
let candidatePair = transport && stats.get(transport.selectedCandidatePairId);
|
|
58
|
-
if (!candidatePair) { // Safari doesn't follow the standard.
|
|
59
|
-
for (const report of stats.values()) {
|
|
60
|
-
if ((report.type === 'candidate-pair') && report.selected) {
|
|
61
|
-
candidatePair = report;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (!candidatePair) {
|
|
67
|
-
console.warn(this.label, 'got stats without candidatePair', Array.from(stats.values()));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const remote = stats.get(candidatePair.remoteCandidateId);
|
|
71
|
-
const {protocol, candidateType} = remote;
|
|
72
|
-
const now = Date.now();
|
|
73
|
-
Object.assign(this, {stats, transport, candidatePair, remote, protocol, candidateType, statsTime: now});
|
|
74
|
-
if (doLogging) console.info(this.label, 'connected', protocol, candidateType, ((now - this.connectionStartTime)/1e3).toFixed(1));
|
|
75
|
-
}
|
|
76
|
-
log(...rest) { // console.log(...rest) ONLY if debug is set.
|
|
77
|
-
if (this.debug) console.log(this.label, this.peer.instanceVersion, ...rest);
|
|
78
|
-
}
|
|
79
|
-
logError(label, eventOrException) { // Call error with the gathered platform-specific data. Returns the gatheredata.
|
|
80
|
-
const data = [this.label, this.peer.instanceVersion, ...this.constructor.gatherErrorData(label, eventOrException)];
|
|
81
|
-
this.error(...data);
|
|
82
|
-
return data;
|
|
83
|
-
}
|
|
84
|
-
static gatherErrorData(label, eventOrException) { // Normalize several context- or platform-specific properties.
|
|
85
|
-
return [
|
|
86
|
-
label + " error:",
|
|
87
|
-
eventOrException.code || eventOrException.errorCode || eventOrException.status || "", // First is deprecated, but still useful.
|
|
88
|
-
eventOrException.url || eventOrException.name || '',
|
|
89
|
-
eventOrException.message || eventOrException.errorText || eventOrException.statusText || eventOrException
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
}
|
package/lib/webrtc.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { WebRTCBase } from './webrtcbase.js';
|
|
2
|
-
|
|
3
|
-
export class WebRTC extends WebRTCBase {
|
|
4
|
-
|
|
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
|
-
if (this.peer.iceGatheringState !== 'gathering') return; // All done.
|
|
20
|
-
this.signals = returning;
|
|
21
|
-
await this.connectVia(responder); // Keep "long-polling" the other side until it has nothing left to say.
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
sending = [];
|
|
25
|
-
get signals() { // Answer the signals that have fired on this end since the last time this.signals was asked for.
|
|
26
|
-
let pending = this.sending;
|
|
27
|
-
this.sending = [];
|
|
28
|
-
return pending;
|
|
29
|
-
}
|
|
30
|
-
set signals(data) { // Set with the signals received from the other end.
|
|
31
|
-
data.forEach(([type, message]) => this[type](message));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
signalResolvers = [];
|
|
35
|
-
resetAndResolveSignals() { // Any pending.
|
|
36
|
-
let resolvers = this.signalResolvers; // Reset and resolve all pending resolvers.
|
|
37
|
-
this.signalResolvers = [];
|
|
38
|
-
for (const resolve of resolvers) resolve(null);
|
|
39
|
-
}
|
|
40
|
-
get signalsReady() { // Return a promise that resolves as soon as there any ready.
|
|
41
|
-
// Each time that a client calls get signalsReady, a new promise is generated.
|
|
42
|
-
if (this.sending.length) return Promise.resolve(null);
|
|
43
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
44
|
-
this.signalResolvers.push(resolve);
|
|
45
|
-
return promise;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
signal(type, message) { // Handle signals being generated on this side, by accumulating them for get signals
|
|
49
|
-
super.signal(type, message);
|
|
50
|
-
this.sending.push([type, message]);
|
|
51
|
-
this.resetAndResolveSignals();
|
|
52
|
-
}
|
|
53
|
-
connectionStateChange(state) { // When we connect, resetAndResolveSignals.
|
|
54
|
-
super.connectionStateChange(state);
|
|
55
|
-
if ('connected' === state) this.resetAndResolveSignals();
|
|
56
|
-
}
|
|
57
|
-
}
|
package/lib/webrtcbase.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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
|
-
}
|