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 | ;
|
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 | }());
|