fbp-protocol-client
Version:
Client library for the FBP protocol
195 lines (179 loc) • 5.2 kB
JavaScript
const debug = require('debug')('fbp-protocol-client:webrtc');
const Peer = require('simple-peer');
const { v4: uuid } = require('uuid');
const Base = require('./base');
const { isBrowser } = require('../helpers/platform');
const Signaller = require('../helpers/signaller');
class WebRTCRuntime extends Base {
constructor(definition) {
super(definition);
this.id = uuid();
this.handleMessage = this.handleMessage.bind(this);
this.signaller = null;
this.connecting = false;
this.protocol = 'webrtc';
this.buffer = [];
this.peers = {};
}
getElement() {
return null;
}
isConnected() {
// TODO: Only consider runtime peers
const connectedPeers = Object.keys(this.peers).filter((p) => this.peers[p].connected);
if (connectedPeers.length) {
return true;
}
return false;
}
maybeDisconnected() {
if (this.signaller && !this.signaller.isConnected()) {
this.signaller = null;
this.connecting = false;
}
if (this.isConnected()) {
return;
}
this.emit('status', {
online: false,
label: 'disconnected',
});
this.emit('disconnected');
}
connect() {
let roomId; let signaller;
if (this.isConnected() || this.connecting) { return; }
if (this.signaller && this.signaller.isConnected()) { return; }
const address = this.getAddress();
if (address.indexOf('#') !== -1) {
[signaller, roomId] = address.split('#');
if (signaller === 'webrtc://') {
signaller = null;
}
} else {
roomId = address;
}
if (!signaller) {
signaller = 'wss://api.flowhub.io/';
}
this.signaller = new Signaller(this.id, 'client', signaller);
const options = {
channelName: roomId,
initiator: true,
};
if (!isBrowser()) {
// eslint-disable-next-line
options.wrtc = require('wrtc');
}
this.signaller.connect();
this.signaller.once('connected', () => {
// Join the Runtime ID room
this.signaller.join(roomId);
});
this.signaller.on('join', (member) => {
if (this.peers[member.id]) {
return;
}
// Another peer has joined. Likely the runtime
this.signaller.joinReply(member.id, this.id);
this.connectPeer(member, options);
});
this.signaller.on('signal', (data, member) => {
// Getting signalling information for a peer
const peer = this.peers[member.id];
if (!peer && !peer.destroyed) {
debug(`${this.id} received signalling data for unknown/destroyed peer ${member.id}`);
return;
}
debug(`${this.id} received signalling data for peer ${member.id}`);
peer.signal(data);
});
this.signaller.on('error', (error) => {
debug(`${this.id} signaller errored`, error);
this.connecting = false;
this.signaller = null;
this.maybeDisconnected();
this.emit('error', error);
});
this.signaller.on('disconnected', () => {
this.signaller = null;
this.connecting = false;
// We may retain peer connections even without signaller
this.maybeDisconnected();
});
this.connecting = true;
}
connectPeer(member, options) {
debug(`${this.id} connecting to peer ${member.id}`);
const peer = new Peer(options);
this.peers[member.id] = peer;
peer.on('signal', (data) => {
// Send connection details to peer via signalling server
debug(`${this.id} sending signalling data to peer ${member.id}`);
this.signaller.signal(member.id, data);
});
peer.on('connect', () => {
debug(`${this.id} connected to peer ${member.id}`);
this.connecting = false;
this.emit('status', {
online: true,
label: 'connected',
});
this.emit('connected');
this.sendRuntime('getruntime', {});
this.flush();
});
peer.on('data', this.handleMessage);
peer.on('close', () => {
debug(`${this.id} disconnected from peer ${member.id}`);
delete this.peers[member.id];
this.maybeDisconnected();
});
peer.on('error', (error) => {
debug(`${this.id} peer ${member.id} errored`, error);
delete this.peers[member.id];
this.maybeDisconnected();
this.emit('error', error);
});
}
disconnect() {
this.connecting = false;
Object.keys(this.peers).forEach((p) => {
this.peers[p].destroy();
delete this.peers[p];
});
if (this.signaller) {
this.signaller.disconnect();
this.signaller = null;
}
}
send(protocol, command, payload) {
const m = {
protocol,
command,
payload,
};
if (!this.isConnected()) {
this.buffer.push(m);
return;
}
Object.keys(this.peers).forEach((p) => {
const peer = this.peers[p];
if (!peer.connected) {
return;
}
peer.send(JSON.stringify(m));
});
}
handleMessage(message) {
const msg = JSON.parse(message);
return this.recvMessage(msg);
}
flush() {
this.buffer.forEach((item) => {
this.send(item.protocol, item.command, item.payload);
});
this.buffer = [];
}
}
module.exports = WebRTCRuntime;