Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Peer extends stream.Duplex {
this.trickle = opts.trickle !== undefined ? opts.trickle : true
this.allowHalfTrickle = opts.allowHalfTrickle !== undefined ? opts.allowHalfTrickle : false
this.iceCompleteTimeout = opts.iceCompleteTimeout || ICECOMPLETE_TIMEOUT
this.iceRestartEnabled = 'iceRestartEnabled' in opts ? opts.iceRestartEnabled : true

this.destroyed = false
this.destroying = false
Expand Down Expand Up @@ -180,8 +181,7 @@ class Peer extends stream.Duplex {
}

signal (data) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot signal after peer is destroyed'), 'ERR_DESTROYED')
if (this.destroyed) throw errCode(new Error('cannot signal after peer is destroyed'), 'ERR_SIGNALING')
if (typeof data === 'string') {
try {
data = JSON.parse(data)
Expand Down Expand Up @@ -244,8 +244,6 @@ class Peer extends stream.Duplex {
* @param {ArrayBufferView|ArrayBuffer|Buffer|string|Blob} chunk
*/
send (chunk) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot send after peer is destroyed'), 'ERR_DESTROYED')
this._channel.send(chunk)
}

Expand All @@ -255,8 +253,6 @@ class Peer extends stream.Duplex {
* @param {Object} init
*/
addTransceiver (kind, init) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot addTransceiver after peer is destroyed'), 'ERR_DESTROYED')
this._debug('addTransceiver()')

if (this.initiator) {
Expand All @@ -279,8 +275,6 @@ class Peer extends stream.Duplex {
* @param {MediaStream} stream
*/
addStream (stream) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot addStream after peer is destroyed'), 'ERR_DESTROYED')
this._debug('addStream()')

stream.getTracks().forEach(track => {
Expand All @@ -294,8 +288,6 @@ class Peer extends stream.Duplex {
* @param {MediaStream} stream
*/
addTrack (track, stream) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot addTrack after peer is destroyed'), 'ERR_DESTROYED')
this._debug('addTrack()')

const submap = this._senderMap.get(track) || new Map() // nested Maps map [track, stream] to sender
Expand All @@ -319,8 +311,6 @@ class Peer extends stream.Duplex {
* @param {MediaStream} stream
*/
replaceTrack (oldTrack, newTrack, stream) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot replaceTrack after peer is destroyed'), 'ERR_DESTROYED')
this._debug('replaceTrack()')

const submap = this._senderMap.get(oldTrack)
Expand All @@ -343,8 +333,6 @@ class Peer extends stream.Duplex {
* @param {MediaStream} stream
*/
removeTrack (track, stream) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot removeTrack after peer is destroyed'), 'ERR_DESTROYED')
this._debug('removeSender()')

const submap = this._senderMap.get(track)
Expand All @@ -370,8 +358,6 @@ class Peer extends stream.Duplex {
* @param {MediaStream} stream
*/
removeStream (stream) {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot removeStream after peer is destroyed'), 'ERR_DESTROYED')
this._debug('removeSenders()')

stream.getTracks().forEach(track => {
Expand All @@ -395,20 +381,18 @@ class Peer extends stream.Duplex {
})
}

negotiate () {
if (this.destroying) return
if (this.destroyed) throw errCode(new Error('cannot negotiate after peer is destroyed'), 'ERR_DESTROYED')

negotiate (restart = false) {
if (this.initiator) {
if (this._isNegotiating) {
this._queuedNegotiation = true
this._debug('already negotiating, queueing')
} else {
this._debug('start negotiation')
setTimeout(() => { // HACK: Chrome crashes if we immediately call createOffer
this._createOffer()
this._createOffer(restart)
}, 0)
}
this._isRestarting = restart
} else {
if (this._isNegotiating) {
this._queuedNegotiation = true
Expand All @@ -424,11 +408,21 @@ class Peer extends stream.Duplex {
this._isNegotiating = true
}

restart () {
if (this.initiator) {
if (this._isRestarting) {
this._debug('already restarting, ignoring')
} else {
this._pc.restartIce()
}
}
}

// TODO: Delete this method once readable-stream is updated to contain a default
// implementation of destroy() that automatically calls _destroy()
// See: https://github.com/nodejs/readable-stream/issues/283
destroy (err) {
this._destroy(err, () => {})
this._destroy(err, () => { })
}

_destroy (err, cb) {
Expand Down Expand Up @@ -469,7 +463,7 @@ class Peer extends stream.Duplex {
if (this._channel) {
try {
this._channel.close()
} catch (err) {}
} catch (err) { }

// allow events concurrent with destruction to be handled
this._channel.onmessage = null
Expand All @@ -480,7 +474,7 @@ class Peer extends stream.Duplex {
if (this._pc) {
try {
this._pc.close()
} catch (err) {}
} catch (err) { }

// allow events concurrent with destruction to be handled
this._pc.oniceconnectionstatechange = null
Expand Down Expand Up @@ -545,7 +539,7 @@ class Peer extends stream.Duplex {
}, CHANNEL_CLOSING_TIMEOUT)
}

_read () {}
_read () { }

_write (chunk, encoding, cb) {
if (this.destroyed) return cb(errCode(new Error('cannot write after peer is destroyed'), 'ERR_DATA_CHANNEL'))
Expand Down Expand Up @@ -692,8 +686,10 @@ class Peer extends stream.Duplex {

_onConnectionStateChange () {
if (this.destroyed) return
if (this._pc.connectionState === 'failed') {
if (this._pc.connectionState === 'failed' && !this.iceRestartEnabled) {
this.destroy(errCode(new Error('Connection failed.'), 'ERR_CONNECTION_FAILURE'))
} else if (this._pc.connectionState === 'failed' && this.iceRestartEnabled) {
this._pc.restartIce()
}
}

Expand All @@ -709,11 +705,20 @@ class Peer extends stream.Duplex {
)
this.emit('iceStateChange', iceConnectionState, iceGatheringState)

if (iceConnectionState === 'connected' || iceConnectionState === 'completed') {
if (iceConnectionState === 'connected' || iceGatheringState === 'completed') {
this._isRestarting = false
this._pcReady = true
this._maybeReady()
}
if (iceConnectionState === 'failed') {

if (iceConnectionState === 'failed' && this.iceRestartEnabled) {
if (this.initiator && !this._isRestarting) {
this._isNegotiating = false
this._isRestarting = true

this._needsNegotiation(true)
}
} else if (iceConnectionState === 'failed' && !this.iceRestartEnabled) {
this.destroy(errCode(new Error('Ice connection failed.'), 'ERR_ICE_CONNECTION_FAILURE'))
}
if (iceConnectionState === 'closed') {
Expand Down Expand Up @@ -743,7 +748,7 @@ class Peer extends stream.Duplex {
cb(null, reports)
}, err => cb(err))

// Single-parameter callback-based getStats() (non-standard)
// Single-parameter callback-based getStats() (non-standard)
} else if (this._pc.getStats.length > 0) {
this._pc.getStats(res => {
// If we destroy connection in `connect` callback this code might happen to run when actual connection is already closed
Expand All @@ -763,16 +768,16 @@ class Peer extends stream.Duplex {
cb(null, reports)
}, err => cb(err))

// Unknown browser, skip getStats() since it's anyone's guess which style of
// getStats() they implement.
// Unknown browser, skip getStats() since it's anyone's guess which style of
// getStats() they implement.
} else {
cb(null, [])
}
}

_maybeReady () {
this._debug('maybeReady pc %s channel %s', this._pcReady, this._channelReady)
if (this._connected || this._connecting || !this._pcReady || !this._channelReady) return
if (((this._connected || this._connecting) && !this._isRestarting) || !this._pcReady || !this._channelReady) return

this._connecting = true

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "simple-peer",
"description": "Simple one-to-one WebRTC video/voice and data channels",
"version": "9.9.4",
"version": "9.10.0",
"author": {
"name": "Feross Aboukhadijeh",
"email": "[email protected]",
Expand Down Expand Up @@ -31,7 +31,7 @@
"speedometer": "^1.1.0",
"standard": "*",
"string-to-stream": "^3.0.1",
"tape": "^5.0.1",
"tape": "^5.3.1",
"thunky": "^1.1.0",
"wrtc": "^0.4.6",
"ws": "^7.3.1"
Expand Down
35 changes: 35 additions & 0 deletions test/negotiation.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,38 @@ test('negotiated channels', function (t) {
t.pass('peer2 connect')
})
})

test('renegotiation after restart', function (t) {
t.plan(4)

const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })
const peer2 = new Peer({ config, wrtc: common.wrtc })

peer1.on('signal', function (data) {
if (!peer2.destroyed) peer2.signal(data)
})
peer2.on('signal', function (data) {
if (!peer1.destroyed) peer1.signal(data)
})

peer1.on('connect', function () {
peer1.addStream(common.getMediaStream())
})
peer2.on('connect', function () {
peer2.addStream(common.getMediaStream())
})

peer1.on('stream', function () {
t.pass('got peer1 stream')
})

peer2.on('stream', function () {
t.pass('got peer2 stream')
peer1.restart()
})

let tracks = 1
peer2.on('track', function () {
t.pass(`got peer2 track ${tracks++}`)
})
})