WebRTC Between two pages in the same machine
Asked Answered
B

1

5

I'm trying to implement a mechanism to send textual data (JSON for instance) in from page to page, using javascript at the same machine.
I found some code and wrapped it but it only works at the same page.
At the moment I don't want to use a WwebRTC framework, only adapter.js.

//Must include adapter.js before

var WebRTCManager = (function () {

    'use strict';

    //Ctor
    function WebRTCManagerFn() {

      console.log('WebRTCManagerFn ctor reached');

      this._events = {};

      this._localConnection = null
      this._remoteConnection = null;
      this._sendChannel = null;
      this._receiveChannel = null;
    }

    WebRTCManagerFn.prototype.addEventListener = function (name, handler)        {
      if (this._events.hasOwnProperty(name))
          this._events[name].push(handler);
      else
          this._events[name] = [handler];
    };

    WebRTCManagerFn.prototype._fireEvent = function (name, event) {
       if (!this._events.hasOwnProperty(name))
          return;

       if (!event)
          event = {};

       var listeners = this._events[name], l = listeners.length;
       for (var i = 0; i < l; i++) {
          listeners[i].call(null, event);
       }
    };

WebRTCManagerFn.prototype.createConnection = function () {
    var servers = null;
    var pcConstraint = null;
    var dataConstraint = null;

    console.log('Using SCTP based data channels');

    // SCTP is supported from Chrome 31 and is supported in FF.
    // No need to pass DTLS constraint as it is on by default in Chrome 31.
    // For SCTP, reliable and ordered is true by default.
    // Add localConnection to global scope to make it visible
    // from the browser console.
    window.localConnection = this._localConnection =
        new RTCPeerConnection(servers, pcConstraint);
    console.log('Created local peer connection object localConnection');

    this._sendChannel = this._localConnection.createDataChannel('sendDataChannel',
            dataConstraint);
    console.log('Created send data channel');
    this._localConnection.onicecandidate = this._localIceCallback.bind(this);
    this._sendChannel.onopen = this._onSendChannelStateChange.bind(this);
    this._sendChannel.onclose = this._onSendChannelStateChange.bind(this);

    // Add remoteConnection to global scope to make it visible
    // from the browser console.
    window.remoteConnection = this._remoteConnection =
        new RTCPeerConnection(servers, pcConstraint);
    console.log('Created remote peer connection object remoteConnection');
    this._remoteConnection.onicecandidate = this._remoteIceCallback.bind(this);
    this._remoteConnection.ondatachannel = this._receiveChannelCallback.bind(this);

    this._localConnection.createOffer(this._gotOfferFromLocalConnection.bind(this), this._onCreateSessionDescriptionError.bind(this));
}

WebRTCManagerFn.prototype._onCreateSessionDescriptionError = function (error) {
    console.log('Failed to create session description: ' + error.toString());
}

WebRTCManagerFn.prototype.sendMessage = function (msgText) {
    var msg = new Message(msgText);

    // Send the msg object as a JSON-formatted string.
    var data = JSON.stringify(msg);
    this._sendChannel.send(data);

    console.log('Sent Data: ' + data);
}

WebRTCManagerFn.prototype.closeDataChannels = function () {
    console.log('Closing data channels');
    this._sendChannel.close();
    console.log('Closed data channel with label: ' + this._sendChannel.label);
    this._receiveChannel.close();
    console.log('Closed data channel with label: ' + this._receiveChannel.label);
    this._localConnection.close();
    this._remoteConnection.close();
    this._localConnection = null;
    this._remoteConnection = null;
    console.log('Closed peer connections');
}

WebRTCManagerFn.prototype._gotOfferFromLocalConnection = function (desc) {
    console.log('reached _gotOfferFromLocalConnection');
    if (this && this._localConnection != 'undefined' && this._remoteConnection != 'undefined') {
        this._localConnection.setLocalDescription(desc);
        console.log('Offer from localConnection \n' + desc.sdp);
        this._remoteConnection.setRemoteDescription(desc);
        this._remoteConnection.createAnswer(this._gotAnswerFromRemoteConnection.bind(this),
            this._onCreateSessionDescriptionError.bind(this));
    }
}

WebRTCManagerFn.prototype._gotAnswerFromRemoteConnection = function (desc) {
    console.log('reached _gotAnswerFromRemoteConnection');
    if (this && this._localConnection != 'undefined' && this._remoteConnection != 'undefined') {
        this._remoteConnection.setLocalDescription(desc);
        console.log('Answer from remoteConnection \n' + desc.sdp);
        this._localConnection.setRemoteDescription(desc);
    }
}

WebRTCManagerFn.prototype._localIceCallback = function (event) {
    console.log('local ice callback');
    if (event.candidate) {
        this._remoteConnection.addIceCandidate(event.candidate,
            this._onAddIceCandidateSuccess.bind(this), this._onAddIceCandidateError.bind(this));
        console.log('Local ICE candidate: \n' + event.candidate.candidate);
    }
}

WebRTCManagerFn.prototype._remoteIceCallback = function (event) {
    console.log('remote ice callback');
    if (event.candidate) {
        this._localConnection.addIceCandidate(event.candidate,
            this._onAddIceCandidateSuccess.bind(this), this._onAddIceCandidateError.bind(this));
        console.log('Remote ICE candidate: \n ' + event.candidate.candidate);
    }
}

WebRTCManagerFn.prototype._onAddIceCandidateSuccess = function (evt) {
    debugger;
    console.log('AddIceCandidate success. evt: '+ evt);
}

WebRTCManagerFn.prototype._onAddIceCandidateError = function (error) {
    console.log('Failed to add Ice Candidate: ' + error.toString());
}

WebRTCManagerFn.prototype._receiveChannelCallback = function (event) {
    console.log('Receive Channel Callback');
    this._receiveChannel = event.channel;
    this._receiveChannel.onmessage = this._onReceiveMessageCallback.bind(this);
    this._receiveChannel.onopen = this._onReceiveChannelStateChange.bind(this);
    this._receiveChannel.onclose = this._onReceiveChannelStateChange.bind(this);
}

WebRTCManagerFn.prototype._onReceiveMessageCallback = function (event) {
    console.log('Received Message: ' + event.data);
    console.log('Received Message this is: ' + this);

    var msgObj = JSON.parse(event.data);

    this._fireEvent("messageRecieved", {
        details: {
            msg: msgObj
        }
    });
}

WebRTCManagerFn.prototype._onSendChannelStateChange = function () {
    console.log('_onSendChannelStateChange');
    var readyState = this._sendChannel.readyState;
    console.log('Send channel state is: ' + readyState);
}

WebRTCManagerFn.prototype._onReceiveChannelStateChange = function () {
    var readyState = this._receiveChannel.readyState;
    console.log('Receive channel state is: ' + readyState);
}

return WebRTCManagerFn;
})();

My question is how to pass data between two pages on the same machine using WebRTC?

Birdie answered 8/2, 2016 at 11:33 Comment(9)
Do you have a signalling server? Do you have a more concrete problem description of what the issue is with communicating cross-page?Commines
In my implementation the message is received inside the page. I would like to have two way communication between two different pages.. The signaling server is the local host.. I think I'm missing something...Birdie
That means you need a way to exchange messages; it's almost irrelevant whether that's between two pages on the same computer or on different computers. A signalling server is the best choice. If you can clarify the exact restrictions and situation perhaps a different alternative could be suggested.Commines
I just look for a way to perform IPC between two different pages (Applications). I.E pass textual data between them.Birdie
Just a nit: adapter.js does not replace WebRTC, it merely accounts for differences in browsers that implement WebRTC or ORTC, so if you use adapter.js you are using WebRTC.Newspaperman
I know. I looked for natural webrtc at the moment. As you said adapter.js juat unified the interface..Birdie
@Newspaperman I did see this answer, and initially ignored it thinking it was simply faking WebRTC using localstorage. Now I read it again I see you're only using localstorage to exchange SDP. Having said that, I tried the jsfiddle at jsfiddle.net/f5y48hcd and can't get any transfer to take place. In the console I see Uncaught DOMException: Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open' at RTCDataChannel.send (https://webrtc.github.io/adapter/adapter-latest.js:1458:34) at HTMLInputElement.chat.onkeypressMenendez
@Menendez You have to open it in two windows before you hit the Connect button.Newspaperman
hmm. that's what I was doing. However, it's working now so many thanks.Menendez
N
10

This WebRTC tab chat demo works across tabs or windows in the same browser without a server: https://jsfiddle.net/f5y48hcd/ (I gave up making it work in a code snippet due to a SecurityError.)

Open the fiddle in two windows and try it out. For reference, here's the WebRTC code:

var pc = new RTCPeerConnection(), dc, enterPressed = e => e.keyCode == 13;

var connect = () => init(dc = pc.createDataChannel("chat"));
pc.ondatachannel = e => init(dc = e.channel);

var init = dc => {
  dc.onopen = e => (dc.send("Hi!"), chat.select());
  dc.onclose = e => log("Bye!");
  dc.onmessage = e => log(e.data);
};

chat.onkeypress = e => {
  if (!enterPressed(e)) return;
  dc.send(chat.value);
  log("> " + chat.value);
  chat.value = "";
};

var sc = new localSocket(), send = obj => sc.send(JSON.stringify(obj));
var incoming = msg => msg.sdp &&
  pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
  .then(() => pc.signalingState == "stable" || pc.createAnswer()
    .then(answer => pc.setLocalDescription(answer))
    .then(() => send({ sdp: pc.localDescription })))
  .catch(log) || msg.candidate &&
  pc.addIceCandidate(new RTCIceCandidate(msg.candidate)).catch(log);
sc.onmessage = e => incoming(JSON.parse(e.data));

pc.oniceconnectionstatechange = e => log(pc.iceConnectionState);
pc.onicecandidate = e => send({ candidate: e.candidate });
pc.onnegotiationneeded = e => pc.createOffer()
  .then(offer => pc.setLocalDescription(offer))
  .then(() => send({ sdp: pc.localDescription }))
  .catch(log);

var log = msg => div.innerHTML += "<br>" + msg;

I use this for demoing WebRTC data channels. Note that the secret sauce is the localSocket.js that I wrote for this, which looks like this:

function localSocket() {
  localStorage.a = localStorage.b = JSON.stringify([]);
  this.index = 0;
  this.interval = setInterval(() => {
    if (!this.in) {
      if (!JSON.parse(localStorage.a).length) return;
      this.in = "a"; this.out = "b";
    }
    var arr = JSON.parse(localStorage[this.in]);
    if (arr.length <= this.index) return;
    if (this.onmessage) this.onmessage({ data: arr[this.index] });
    this.index++;
  }, 200);
  setTimeout(() => this.onopen && this.onopen({}));
}
localSocket.prototype = {
  send: function(msg) {
    if (!this.out) {
      this.out = "a"; this.in = "b";
    }
    var arr = JSON.parse(localStorage[this.out]);
    arr.push(msg);
    localStorage[this.out] = JSON.stringify(arr);
  },
  close: function() {
    clearInterval(this.interval);
  }
};

It basically uses localStorage to simulate web sockets locally between two tabs. If this is all you want to do, then you don't even need WebRTC data channels.

Disclaimer: It's not very robust, and relies on two pages being ready to communicate, so not production-ready by any means.

Newspaperman answered 9/2, 2016 at 19:8 Comment(5)
That's very nice but it's not necasseraly two tabs and not necasseraly textual messages only. I'm trying to investigate skylake chat. It's quite close to what I'm trying to achieve..Birdie
This seems to do everything you asked for. Please clarify what you are looking for that it doesn' t do.Newspaperman
First of all, I really appreciate your response. The local storage is problematic since it limits the solution only to applications that run on Chrome browser. WebRTC is supported on MS edge and FireFox and there might be messages exchange between them. I'm on the search for a real cross browser IPC.Birdie
Local storage works in all the browsers you mention. I tested this fiddle in Firefox and Chrome, and it worked for me in both. You might want to update your question to reflect what you are looking for. adapter.js, which you mention wishing to use, requires WebRTC, and is thus limited to the browsers you just mentioned.Newspaperman
To make this work between different browsers on the same machine, I would either install a signaling server on localhost, as I explain here, or maybe modify the localSocket hack to use cookies (though I wonder if all the SDP will fit in 4K)Newspaperman

© 2022 - 2024 — McMap. All rights reserved.