1 /* -----------------------------------------------------------------------
2 * Copyright (C) 2014 Equinox Software, Inc.
3 * Bill Erickson <berick@esilibrary.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * ----------------------------------------------------------------------- */
16 var WEBSOCKET_URL_PATH = '/osrf-websocket-translator';
17 var WEBSOCKET_PORT = 7680;
18 var WEBSOCKET_PORT_SSL = 7682;
20 // set of shared ports (i.e. browser tabs)
21 var connected_ports = {};
22 var port_identifier = 0;
24 // maps osrf message threads to a port index in connected_ports
25 var thread_port_map = {};
27 // our shared websocket
30 function send_msg_to_port(ident, msg) {
31 console.debug('sending msg to port ' + ident + ' : ' + msg.action);
33 connected_ports[ident].postMessage(msg);
35 // some browsers (Opera) throw an exception when messaging
36 // a disconnected port.
37 console.log('unable to send msg to port ' + ident);
38 delete connected_ports[ident];
42 // send a message to all listeners
43 function broadcast(msg) {
44 for (var ident in connected_ports)
45 send_msg_to_port(ident, msg);
49 // opens the websocket connection
50 // port_ident refers to the requesting port
51 function open_websocket(port_ident) {
52 var port = connected_ports[port_ident];
55 switch (websocket.readyState) {
57 case websocket.CONNECTING:
58 // nothing to do. The port will get notified on open
62 // websocket is already open, let the connecting.
63 // other ports have been notified already, so
64 // no broadcast is required.
65 send_msg_to_port(port_ident, {action : 'socket_connected'});
69 // websocket is no longer connected. We need a new socket.
75 // assume non-SSL for now. SSL silently dies if the cert is
76 // invalid and has not been added as an exception.
77 var path = 'ws://' + location.host + ':' +
78 WEBSOCKET_PORT + WEBSOCKET_URL_PATH
80 console.log('connecting websocket to ' + path);
82 websocket = new WebSocket(path);
84 websocket.onopen = function() {
85 // tell all ports the websocket is open and ready
86 console.log('websocket.onopen()');
87 broadcast({action : 'socket_connected'});
90 websocket.onmessage = function(evt) {
91 var message = evt.data;
93 // this is a hack to avoid having to run JSON2js multiple
94 // times on the same message. Hopefully match() is faster.
96 var match = message.match(/"thread":"(.*?)"/);
97 if (!match || !(thread = match[1])) {
98 throw new Error("Websocket message malformed; no thread: " + message);
101 console.debug('websocket received message for thread ' + thread);
103 var port_msg = {action: 'message', message : message};
104 var port_ident = thread_port_map[thread];
107 send_msg_to_port(port_ident, port_msg);
109 // don't know who it's for, broadcast and let the ports
110 // sort it out for themselves.
114 /* poor man's memory management. We are not cleaning up our
115 * thread_port_map as we go, because that gets messy. Instead,
116 * after the map has reached a certain size, clear it. If any
117 * pending messages are afield that depend on the map, they
118 * will be broadcast to all ports on arrival (see above). Only the
119 * port expecting a message with the given thread will honor the
120 * message, all other ports will drop it silently. We could just
121 * do that for every messsage, but this is more efficient.
123 if (Object.keys(thread_port_map).length > 1000) {
124 thread_port_map = {};
128 websocket.onerror = function(evt) {
129 var err = "WebSocket Error " + evt + ' : ' + evt.data;
130 // propagate to all ports so it can be logged, etc.
131 broadcast({action : 'error', message : err});
132 throw new Error(err);
135 websocket.onclose = function() {
136 console.log('closing websocket');
140 // called when a new port (tab) is opened
141 onconnect = function(e) {
142 var port = e.ports[0];
144 // we have no way of identifying ports within the message handler,
145 // so we apply an identifier to each and toss that into a closer.
146 var port_ident = port_identifier++;
147 connected_ports[port_ident] = port;
150 port.addEventListener('message', function(e) {
153 if (data.action == 'message') {
154 thread_port_map[data.thread] = port_ident;
155 websocket.send(data.message);
159 if (messsage.action == 'close') {
160 // TODO: add me to body onunload in calling pages.
161 delete connected_ports[port_ident];
162 console.log('closed port ' + port_ident +
163 '; ' + Object.keys(connected_ports).length + ' remaining');
171 console.log('added port ' + port_ident +
172 '; ' + Object.keys(connected_ports).length + ' total');
174 open_websocket(port_ident);