UNPKG

8.18 kBJavaScriptView Raw
1/*
2 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *
22 */
23
24(function () {
25 "use strict";
26
27 var DomainManager = require("./DomainManager");
28
29 /**
30 * @private
31 * @type{Array.<Connection>}
32 * Currently active connections
33 */
34 var _connections = [];
35
36 /**
37 * @private
38 * @constructor
39 * A WebSocket connection to a client. This is a private constructor.
40 * Callers should use the ConnectionManager.createConnection function
41 * instead.
42 * @param {WebSocket} ws The WebSocket representing the client
43 */
44 function Connection(ws) {
45 this._ws = ws;
46 this._connected = true;
47 this._ws.on("message", this._receive.bind(this));
48 this._ws.on("close", this.close.bind(this));
49 }
50
51 /**
52 * @private
53 * @type {boolean}
54 * Whether the connection is connected.
55 */
56 Connection.prototype._connected = false;
57
58 /**
59 * @private
60 * @type {WebSocket}
61 * The connection's WebSocket
62 */
63 Connection.prototype._ws = null;
64
65 /**
66 * @private
67 * Sends a message over the WebSocket. Called by public sendX commands.
68 * @param {string} type Message type. Currently supported types are
69 "event", "commandResponse", "commandError", "error"
70 * @param {object} message Message body, must be JSON.stringify-able
71 */
72 Connection.prototype._send = function (type, message) {
73 if (this._ws && this._connected) {
74 try {
75 this._ws.send(JSON.stringify({type: type, message: message}));
76 } catch (e) {
77 console.error("[Connection] Unable to stringify message: " + e.message);
78 }
79 }
80 };
81
82 /**
83 * @private
84 * Sends a binary message over the WebSocket. Implicitly interpreted as a
85 * message of type "commandResponse".
86 * @param {Buffer} message
87 */
88 Connection.prototype._sendBinary = function (message) {
89 if (this._ws && this._connected) {
90 this._ws.send(message, {binary: true, mask: false});
91 }
92 };
93
94 /**
95 * @private
96 * Receive event handler for the WebSocket. Responsible for parsing
97 * message and handing it off to the appropriate handler.
98 * @param {string} message Message received by WebSocket
99 */
100 Connection.prototype._receive = function (message) {
101 var m;
102 try {
103 m = JSON.parse(message);
104 } catch (parseError) {
105 this.sendError("Unable to parse message: " + message);
106 return;
107 }
108
109 if (m.id !== null && m.id !== undefined && m.domain && m.command) {
110 // okay if m.parameters is null/undefined
111 try {
112 DomainManager.executeCommand(this, m.id, m.domain,
113 m.command, m.parameters);
114 } catch (executionError) {
115 this.sendCommandError(m.id, executionError.message,
116 executionError.stack);
117 }
118 } else {
119 this.sendError("Malformed message: " + message);
120 }
121 };
122
123 /**
124 * Closes the connection and does necessary cleanup
125 */
126 Connection.prototype.close = function () {
127 if (this._ws) {
128 try {
129 this._ws.close();
130 } catch (e) { }
131 }
132 this._connected = false;
133 _connections.splice(_connections.indexOf(this), 1);
134 };
135
136 /**
137 * Sends an Error message
138 * @param {object} message Error message. Must be JSON.stringify-able.
139 */
140 Connection.prototype.sendError = function (message) {
141 this._send("error", {message: message});
142 };
143
144 /**
145 * Sends a response to a command execution
146 * @param {number} id unique ID of the command that was executed. ID is
147 * generated by the client when the command is issued.
148 * @param {object|Buffer} response Result of the command execution. Must
149 * either be JSON.stringify-able or a raw Buffer. In the latter case,
150 * the result will be sent as a binary response.
151 */
152 Connection.prototype.sendCommandResponse = function (id, response) {
153 if (Buffer.isBuffer(response)) {
154 // Assume the id is an unsigned 32-bit integer, which is encoded
155 // as a four-byte header
156 var header = new Buffer(4);
157
158 header.writeUInt32LE(id, 0);
159
160 // Prepend the header to the message
161 var message = Buffer.concat([header, response], response.length + 4);
162
163 this._sendBinary(message);
164 } else {
165 this._send("commandResponse", {id: id, response: response });
166 }
167 };
168
169 /**
170 * Sends a response indicating that an error occurred during command
171 * execution
172 * @param {number} id unique ID of the command that was executed. ID is
173 * generated by the client when the command is issued.
174 * @param {string} message Error message
175 * @param {?object} stack Call stack from the exception, if possible. Must
176 * be JSON.stringify-able.
177 */
178 Connection.prototype.sendCommandError = function (id, message, stack) {
179 this._send("commandError", {id: id, message: message, stack: stack});
180 };
181
182 /**
183 * Sends an event message
184 * @param {number} id unique ID for the event.
185 * @param {string} domain Domain of the event.
186 * @param {string} event Name of the event
187 * @param {object} parameters Event parameters. Must be JSON.stringify-able.
188 */
189 Connection.prototype.sendEventMessage =
190 function (id, domain, event, parameters) {
191 this._send("event", {id: id,
192 domain: domain,
193 event: event,
194 parameters: parameters
195 });
196 };
197
198 /**
199 * Factory function for creating a new Connection
200 * @param {WebSocket} ws The WebSocket connected to the client.
201 */
202 function createConnection(ws) {
203 _connections.push(new Connection(ws));
204 }
205
206 /**
207 * Closes all connections gracefully. Should be called during shutdown.
208 */
209 function closeAllConnections() {
210 var i;
211 for (i = 0; i < _connections.length; i++) {
212 try {
213 _connections[i].close();
214 } catch (err) { }
215 }
216 _connections = [];
217 }
218
219 /**
220 * Sends all open connections the specified event
221 * @param {number} id unique ID for the event.
222 * @param {string} domain Domain of the event.
223 * @param {string} event Name of the event
224 * @param {object} parameters Event parameters. Must be JSON.stringify-able.
225 */
226 function sendEventToAllConnections(id, domain, event, parameters) {
227 _connections.forEach(function (c) {
228 c.sendEventMessage(id, domain, event, parameters);
229 });
230 }
231
232 exports.createConnection = createConnection;
233 exports.closeAllConnections = closeAllConnections;
234 exports.sendEventToAllConnections = sendEventToAllConnections;
235}());