diff options
author | Joachim Bauch <bauch@struktur.de> | 2017-07-20 14:43:41 +0300 |
---|---|---|
committer | Joachim Bauch <bauch@struktur.de> | 2017-11-02 13:23:09 +0300 |
commit | 547c69c91ca76cb2642d84235f10683833a8bd83 (patch) | |
tree | b6ba003411b7e7d7791025a25e3ac19058fd1626 | |
parent | be33ec8d9c30b1acd0336976fd64cc0dfff96c27 (diff) |
Add frontend support for the standalone signaling server.
See #339 for a description of the API.
Signed-off-by: Joachim Bauch <bauch@struktur.de>
-rw-r--r-- | js/signaling.js | 302 | ||||
-rw-r--r-- | templates/index-public.php | 2 | ||||
-rw-r--r-- | templates/index.php | 2 |
3 files changed, 301 insertions, 5 deletions
diff --git a/js/signaling.js b/js/signaling.js index 1bac8b641..70ebf021e 100644 --- a/js/signaling.js +++ b/js/signaling.js @@ -431,10 +431,306 @@ }.bind(this)); }; + function StandaloneSignaling(url) { + SignalingBase.prototype.constructor.apply(this, arguments); + // Make sure we are using websocket urls. + if (url.indexOf("https://") === 0) { + url = "wss://" + url.substr(8); + } else if (url.indexOf("http://") === 0) { + url = "ws://" + url.substr(7); + } + if (url[url.length - 1] === "/") { + url = url.substr(0, url.length - 1); + } + this.url = url + "/spreed"; + this.initialReconnectIntervalMs = 1000; + this.maxReconnectIntervalMs = 16000; + this.reconnectIntervalMs = this.initialReconnectIntervalMs; + this.connect(); + } + + StandaloneSignaling.prototype = new SignalingBase(); + StandaloneSignaling.prototype.constructor = StandaloneSignaling; + + StandaloneSignaling.prototype.reconnect = function() { + if (this.reconnectTimer) { + return; + } + + // Wiggle interval a little bit to prevent all clients from connecting + // simultaneously in case the server connection is interrupted. + var interval = this.reconnectIntervalMs - (this.reconnectIntervalMs / 2) + (this.reconnectIntervalMs * Math.random()); + console.log("Reconnect in", interval); + this.reconnectTimer = window.setTimeout(function() { + this.reconnectTimer = null; + this.connect(); + }.bind(this), interval); + this.reconnectIntervalMs = this.reconnectIntervalMs * 2; + if (this.reconnectIntervalMs > this.maxReconnectIntervalMs) { + this.reconnectIntervalMs = this.maxReconnectIntervalMs; + } + if (this.socket) { + this.socket.close(); + this.socket = null; + } + }; + + StandaloneSignaling.prototype.connect = function() { + console.log("Connecting to", this.url); + this.callbacks = {}; + this.id = 1; + this.pendingMessages = []; + this.connected = false; + this.socket = new WebSocket(this.url); + window.signalingSocket = this.socket; + this.socket.onopen = function(event) { + console.log("Connected", event); + this.reconnectIntervalMs = this.initialReconnectIntervalMs; + this.sendHello(); + }.bind(this); + this.socket.onerror = function(event) { + console.log("Error", event); + this.reconnect(); + }.bind(this); + this.socket.onclose = function(event) { + console.log("Close", event); + this.reconnect(); + }.bind(this); + this.socket.onmessage = function(event) { + var data = event.data; + if (typeof(data) === "string") { + data = JSON.parse(data); + } + console.log("Received", data); + var id = data.id; + if (id && this.callbacks.hasOwnProperty(id)) { + var cb = this.callbacks[id]; + delete this.callbacks[id]; + cb(data); + } + switch (data.type) { + case "hello": + if (!id) { + // Only process if not received as result of our "hello". + this.helloResponseReceived(data); + } + break; + case "room": + // No special processing required for now. + break; + case "event": + this.processEvent(data); + break; + case "message": + data.message.data.from = data.message.sender.sessionid; + this._trigger("message", [data.message.data]); + break; + default: + if (!id) { + console.log("Ignore unknown event", data); + } + break; + } + }.bind(this); + }; + + StandaloneSignaling.prototype.disconnect = function() { + if (this.socket) { + this.doSend({ + "type": "bye", + "bye": {} + }); + this.socket.close(); + this.socket = null; + } + SignalingBase.prototype.disconnect.apply(this, arguments); + }; + + StandaloneSignaling.prototype.on = function(ev/*, handler*/) { + SignalingBase.prototype.on.apply(this, arguments); + + switch (ev) { + case "stunservers": + case "turnservers": + // TODO(fancycode): Implement getting STUN/TURN settings. + break; + } + }; + + StandaloneSignaling.prototype.sendCallMessage = function(data) { + this.doSend({ + "type": "message", + "message": { + "recipient": { + "type": "session", + "sessionid": data.to + }, + "data": data + } + }); + }; + + StandaloneSignaling.prototype.doSend = function(msg, callback) { + if (!this.connected && msg.type !== "hello") { + // Defer sending any messages until the hello rsponse has been + // received. + this.pendingMessages.push([msg, callback]); + return; + } + + if (callback) { + var id = this.id++; + this.callbacks[id] = callback; + msg["id"] = ""+id; + } + console.log("Sending", msg); + this.socket.send(JSON.stringify(msg)); + }; + + StandaloneSignaling.prototype.sendHello = function() { + var msg; + if (this.sessionId) { + console.log("Trying to resume session", this.sessionId); + msg = { + "type": "hello", + "hello": { + "version": "1.0", + "sessionid": this.sessionId + } + }; + } else { + var user = OC.getCurrentUser(); + var url = OC.generateUrl("/apps/spreed/signalling/backend"); + var ticket = $("#app").attr("data-signalingticket"); + msg = { + "type": "hello", + "hello": { + "version": "1.0", + "auth": { + "url": OC.getProtocol() + "://" + OC.getHost() + url, + "params": { + "userid": user.uid, + "ticket": ticket, + } + } + } + }; + } + this.doSend(msg, this.helloResponseReceived.bind(this)); + }; + + StandaloneSignaling.prototype.helloResponseReceived = function(data) { + console.log("Hello response received", data); + if (data.type !== "hello") { + if (this.sessionId) { + // Resuming the session failed, reconnect as new session. + this.sessionId = ''; + this.sendHello(); + return; + } + + // TODO(fancycode): How should this be handled better? + console.error("Could not connect to server", data); + this.reconnect(); + return; + } + + var resumedSession = !!this.sessionId; + this.connected = true; + this.sessionId = data.hello.sessionid; + + var messages = this.pendingMessages; + this.pendingMessages = []; + for (var i = 0; i < messages.length; i++) { + var msg = messages[i][0]; + var callback = messages[i][1]; + this.doSend(msg, callback); + } + + this._trigger("connect"); + if (!resumedSession && this.currentCallToken) { + this.joinCall(this.currentCallToken); + } + }; + + StandaloneSignaling.prototype.joinCall = function(token, callback) { + console.log("Join call", token); + this.doSend({ + "type": "room", + "room": { + "roomid": token + } + }, function(data) { + this.joinResponseReceived(data, token, callback); + }.bind(this)); + }; + + StandaloneSignaling.prototype.joinResponseReceived = function(data, token, callback) { + console.log("Joined", data, token); + this.currentCallToken = token; + if (callback) { + var roomDescription = { + "clients": {} + }; + callback('', roomDescription); + } + }; + + StandaloneSignaling.prototype.leaveCall = function(token) { + console.log("Leave call", token); + this.doSend({ + "type": "room", + "room": { + "roomid": "" + } + }, function(data) { + console.log("Left", data); + this.currentCallToken = null; + }.bind(this)); + }; + + StandaloneSignaling.prototype.processEvent = function(data) { + switch (data.event.target) { + case "room": + this.processRoomEvent(data); + break; + case "roomlist": + this.processRoomListEvent(data); + break; + default: + console.log("Unsupported event target", data); + break; + } + }; + + StandaloneSignaling.prototype.processRoomEvent = function(data) { + switch (data.event.type) { + case "join": + console.log("Users joined", data.event.join); + this._trigger("usersJoined", [data.event.join]); + break; + case "leave": + console.log("Users left", data.event.leave); + this._trigger("usersLeft", [data.event.leave]); + break; + default: + console.log("Unknown room event", data); + break; + } + }; + + StandaloneSignaling.prototype.processRoomListEvent = function(data) { + console.log("Room list event", data); + this.syncRooms(); + }; + OCA.SpreedMe.createSignalingConnection = function() { - // TODO(fancycode): Create different type of signaling connection - // depending on configuration. - return new InternalSignaling(); + var url = $("#app").attr("data-signalingserver"); + if (url) { + return new StandaloneSignaling(url); + } else { + return new InternalSignaling(); + } }; })(OCA, OC); diff --git a/templates/index-public.php b/templates/index-public.php index 08d44f78e..2b909976c 100644 --- a/templates/index-public.php +++ b/templates/index-public.php @@ -33,7 +33,7 @@ script( <div id="notification-container"> <div id="notification" style="display: none;"></div> </div> -<div id="app" class="nc-enable-screensharing-extension" data-token="<?php p($_['token']) ?>"> +<div id="app" class="nc-enable-screensharing-extension" data-token="<?php p($_['token']) ?>" data-signalingServer="<?php p($_['signaling-server']) ?>" data-signalingTicket="<?php p($_['signaling-ticket']) ?>"> <div id="app-content" class="participants-1"> <header> diff --git a/templates/index.php b/templates/index.php index 85082363e..b53d2cba6 100644 --- a/templates/index.php +++ b/templates/index.php @@ -33,7 +33,7 @@ script( ); ?> -<div id="app" class="nc-enable-screensharing-extension" data-token="<?php p($_['token']) ?>"> +<div id="app" class="nc-enable-screensharing-extension" data-token="<?php p($_['token']) ?>" data-signalingServer="<?php p($_['signaling-server']) ?>" data-signalingTicket="<?php p($_['signaling-ticket']) ?>"> <div id="app-navigation" class="icon-loading"> <form id="oca-spreedme-add-room"> <input id="select-participants" class="select2-offscreen" type="text" placeholder="<?php p($l->t('Choose person …')) ?>"/> |