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 // pending messages awaiting a successful websocket connection
31 var pending_ws_messages = [];
33 function send_msg_to_port(ident, msg) {
34 console.debug('sending msg to port ' + ident + ' : ' + msg.action);
36 connected_ports[ident].postMessage(msg);
38 // some browsers (Opera) throw an exception when messaging
39 // a disconnected port.
40 console.debug('unable to send msg to port ' + ident);
41 delete connected_ports[ident];
45 // send a message to all listeners
46 function broadcast(msg) {
47 for (var ident in connected_ports)
48 send_msg_to_port(ident, msg);
52 // opens the websocket connection
53 // port_ident refers to the requesting port
54 function send_to_websocket(message) {
56 if (websocket && websocket.readyState == websocket.OPEN) {
57 // websocket connection is viable. send our message.
58 websocket.send(message);
62 // no viable connection. queue our outbound messages for future delivery.
63 pending_ws_messages.push(message);
65 if (websocket && websocket.readyState == websocket.CONNECTING) {
66 // we are already in the middle of a setup call.
67 // our queued message will be delivered after setup completes.
71 // we have no websocket or an invalid websocket. build a new one.
74 // assume non-SSL for now. SSL silently dies if the cert is
75 // invalid and has not been added as an exception. need to
76 // explain / document / avoid this better.
77 var path = 'ws://' + location.host + ':' + WEBSOCKET_PORT + WEBSOCKET_URL_PATH;
79 console.debug('connecting websocket to ' + path);
82 websocket = new WebSocket(path);
84 console.log('Error creating WebSocket for path ' + path + ' : ' + E);
88 websocket.onopen = function() {
89 console.debug('websocket.onopen()');
90 // deliver any queued messages
92 while ( (msg = pending_ws_messages.shift()) )
96 websocket.onmessage = function(evt) {
97 var message = evt.data;
99 // this is a hack to avoid having to run JSON2js multiple
100 // times on the same message. Hopefully match() is faster.
101 // We can't use JSON_v1 within a shared worker for marshalling
102 // messages, because it has no knowledge of application-level
103 // class hints in this environment.
105 var match = message.match(/"thread":"(.*?)"/);
106 if (!match || !(thread = match[1])) {
107 throw new Error("Websocket message malformed; no thread: " + message);
110 console.debug('websocket received message for thread ' + thread);
112 var port_msg = {action: 'message', message : message};
113 var port_ident = thread_port_map[thread];
116 send_msg_to_port(port_ident, port_msg);
118 // don't know who it's for, broadcast and let the ports
119 // sort it out for themselves.
123 /* poor man's memory management. We are not cleaning up our
124 * thread_port_map as we go, because that would require parsing
125 * and analyzing every message to look for opensrf statuses.
126 * parsing messages adds overhead (see also above comments about
127 * JSON_v1.js). So, instead, after the map has reached a certain
128 * size, clear it. If any pending messages are afield that depend
129 * on the map, they will be broadcast to all ports on arrival
130 * (see above). Only the port expecting a message with the given
131 * thread will honor the message, all other ports will drop it
132 * silently. We could just broadcastfor every messsage, but this
135 if (Object.keys(thread_port_map).length > 1000)
136 thread_port_map = {};
139 websocket.onerror = function(evt) {
140 var err = "WebSocket Error " + evt + ' : ' + evt.data;
141 // propagate to all ports so it can be logged, etc.
142 broadcast({action : 'error', message : err});
143 throw new Error(err);
146 websocket.onclose = function() {
147 console.debug('closing websocket');
152 // called when a new port (tab) is opened
153 onconnect = function(e) {
154 var port = e.ports[0];
156 // we have no way of identifying ports within the message handler,
157 // so we apply an identifier to each and toss that into a closer.
158 var port_ident = port_identifier++;
159 connected_ports[port_ident] = port;
162 port.addEventListener('message', function(e) {
165 if (data.action == 'message') {
166 thread_port_map[data.thread] = port_ident;
167 send_to_websocket(data.message);
171 if (messsage.action == 'close') {
172 // TODO: add me to body onunload in calling pages.
173 delete connected_ports[port_ident];
174 console.debug('closed port ' + port_ident +
175 '; ' + Object.keys(connected_ports).length + ' remaining');
183 console.debug('added port ' + port_ident +
184 '; ' + Object.keys(connected_ports).length + ' total');