src/utils/simple-peer-moc.js
const EventEmitter = require('events')
const lmerge = require('lodash.merge')
const uniqid = require('uuid/v4')
const debug = require('debug')
const debugManager = debug('spa')
const DEFAULT_OPTIONS = () => {
return {
id: uniqid(),
initiator: false,
channelConfig: {},
channelName: uniqid(),
config: { iceServers: [ { urls: 'stun:stun.l.google.com:19302' } ] },
constraints: {},
offerConstraints: {},
answerConstraints: {},
reconnectTimer: false,
sdpTransform: function (sdp) { return sdp },
stream: false,
streams: [],
trickle: true,
wrtc: {}, // RTCPeerConnection/RTCSessionDescription/RTCIceCandidate
objectMode: false
}
}
class Manager {
constructor () {
this._statistics = {
message: 0
}
this.manager = new Map()
this._options = {
latency: (send) => { setTimeout(send, 0) }
}
debugManager('manager initialized')
}
get stats () {
return this._statistics
}
newPeer (peer) {
debugManager('new peer added. Size:', this.manager.size)
this.manager.set(peer.id, peer)
}
connect (from, to) {
debugManager('peer connected from/to: ', from, to)
this.manager.get(to)._connectWith(from)
this.manager.get(from)._connectWith(to)
}
destroy (from, to) {
debugManager('peer disconnected from/to: ', from, to)
if (this.manager.get(from)) {
this.manager.get(from)._close()
this.manager.delete(from)
}
if (this.manager.get(to)) {
this.manager.get(to)._close()
this.manager.delete(to)
}
}
send (from, to, msg, retry = 0) {
// this._options.latency(() => {
this._send(from, to, msg, retry)
// })
}
_send (from, to, msg, retry = 0) {
try {
if (!this.manager.has(from) || !this.manager.has(to)) throw new Error('need a from and to peer.')
this.manager.get(to).emit('data', msg)
this._statistics.message++
} catch (e) {
throw new Error('cannot send the message. perhaps your destination is not reachable.', e)
}
}
}
const manager = new Manager()
module.exports = class SimplePeerAbstract extends EventEmitter {
constructor (options) {
super()
this._manager = manager
this._options = lmerge(DEFAULT_OPTIONS(), options)
this.id = this._options.id
this.WEBRTC_SUPPORT = true // yes but this a fake
this._isNegotiating = false
this.connected = false
this.disconnected = false
this.connectedWith = undefined
this.messageBuffer = []
debugManager('peer initiated:', this.id, this._options.initiator)
if (this._options.initiator) {
// workaround to wait for a listener on 'signal'
process.nextTick(() => {
this._init()
})
}
this._manager.newPeer(this)
}
static get manager () {
return manager
}
send (data) {
if (!this.connectedWith) {
this.messageBuffer.push(data)
} else {
if (this.messageBuffer.length > 0) {
this._reviewMessageBuffer()
}
if (this.connectedWith) {
this._send(this.connectedWith, data)
} else {
this.messageBuffer.push(data)
}
}
}
destroy () {
this._manager.destroy(this.id, this.connectedWith)
}
signal (data) {
if (data.type === 'init') {
this._isNegotiating = true
debugManager('offer-init received:', data)
this.emit('signal', this._createAccept(data))
} else if (data.type === 'accept') {
debugManager('offer-accept received:', data)
this._connect(data)
}
}
_error (error) {
debugManager(error)
this.emit('error', error)
}
_close () {
debugManager('[%s] is closed.', this.id)
this.emit('close')
}
_init () {
this._isNegotiating = true
const offer = this._createOffer()
this.emit('signal', offer)
}
_createOffer () {
const newOffer = {
offerId: uniqid(),
type: 'init',
offer: {
initiator: this.id
}
}
return newOffer
}
_createAccept (offer) {
const acceptedOffer = this._createOffer()
acceptedOffer.type = 'accept'
acceptedOffer.offerId = offer.offerId
acceptedOffer.offer.initiator = offer.offer.initiator
acceptedOffer.offer.acceptor = this.id
return acceptedOffer
}
_reviewMessageBuffer () {
debugManager('Review the buffer: ', this.messageBuffer.length)
while (this.connectedWith && this.messageBuffer.length !== 0) {
this._send(this.messageBuffer.pop())
}
}
_send (to = this.connectedWith, data) {
if (!to) throw new Error('It must have a destination.')
this._manager.send(this.id, to, data)
}
_connect (offer) {
if (!offer.offer.acceptor) throw new Error('It must have an acceptor')
this._manager.connect(offer.offer.initiator, offer.offer.acceptor)
}
_connectWith (connectedWith) {
this.connected = true
this._isNegotiating = false
this.connectedWith = connectedWith
this.emit('connect')
}
}