Skip to content
Steve Belovarich edited this page Sep 4, 2018 · 6 revisions

A library for p2p communication over WebRTC DataChannel with fallback to WebSocket

Prerequisites

WebRTC DataChannel requires a handshake in order to establish a peer to peer connection. This handshake is done with a combination of announce and signaling WebSocket servers. If one of the peers cannot connect over WebRTC due to lack of support for the protocol, a fallback WebSocket server is used for messaging. Once the peers announce and signal back and forth a WebRTC DataChannel connection is established peer to peer.

This package only includes the front end implementation. Below is an example of a simple node WebSocket server.

Setup

DataChannelModule accepts a config that establishes the addresses of the WebSocket servers, a key and id. DataChannelModule can be injected in the root or a child module. Use forChild instead of forRoot when not injecting DataChannelModule in app.module.ts.

Think of the key as if it were a "room". It is the shared space the peers will communicate. The id of each peer must be unique. In this example, a uuid is assigned to each peer. When debug is set to true, the service will print messages in the console.

const uuid = function () {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
};

const DataChannelConfig = {
    key: 'openSeasame',
    id: uuid(),
    signalServer: `ws://localhost:5555`,
    announceServer: `ws://localhost:5556`,
    messageServer: `ws://localhost:5557`,
    debug: true
}

@NgModule({
    imports: [
       DataChannelModule.forRoot(DataChannelConfig)
    ]
})

Usage

In your @Component, inject NgFxDataChannel. Once injected the service will send an announce signal. NgFxDataChannel exposes an emitter for listening to announce and signaling events. This emitter will emit when the connection is finally opened. Messages that are sent peer to peer are available when subscribing to the observer.

constructor(private _client: NgFxDataChannel)

...

     this._client.emitter.subscribe((msg) => {

        if (msg === 'open') {

          this.messages.push(msg);
          this._ref.detectChanges();

          this._client.observer.subscribe((res) => {

            this.messages.push(res[res.length - 1].data);
            this._ref.detectChanges();

          });

        }

    });

Example WebSocket Server

const WebSocket = require('ws');

const signalport = 5555;
const wss = new WebSocket.Server({ port: signalport });

const announceport = 5556;
const wssa = new WebSocket.Server({ port: announceport });

const wsport = 5557;
const wssw = new WebSocket.Server({ port: wsport });

wss.on('connection', function connection(ws) {
  ws.on('error', (err) => {});
  ws.on('message', function incoming(message) {
    if (debug === true) console.log('SIGNAL: ', JSON.parse(JSON.stringify(message), null, 4));
    //ws.send(JSON.stringify(message));
    wss.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

wssa.on('connection', function connection(ws) {
  ws.on('error', (err) => {});
  ws.on('message', function incoming(message) {
    if (debug === true) console.log('ANNOUNCE: ', JSON.parse(JSON.stringify(message), null, 4));
    //ws.send(JSON.stringify(message));
    wssa.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});

wssw.on('connection', function connection(ws) {
  ws.on('error', (err) => {});
  ws.on('message', function incoming(message) {
    if (debug === true) console.log('MESSAGE: ', JSON.parse(JSON.stringify(message), null, 4));
   // ws.send(JSON.stringify(message));
    wssw.clients.forEach(function each(client) {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    });
  });
});



Clone this wiki locally