1 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 ChromeUtils.defineESModuleGetters(lazy, {
9 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
10 Log: "chrome://remote/content/shared/Log.sys.mjs",
11 truncate: "chrome://remote/content/shared/Format.sys.mjs",
13 "chrome://remote/content/server/WebSocketTransport.sys.mjs",
16 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
18 const MAX_LOG_LENGTH = 2500;
20 export class WebSocketConnection {
22 * @param {WebSocket} webSocket
23 * The WebSocket server connection to wrap.
24 * @param {Connection} httpdConnection
25 * Reference to the httpd.js's connection needed for clean-up.
27 constructor(webSocket, httpdConnection) {
28 this.id = lazy.generateUUID();
30 this.httpdConnection = httpdConnection;
32 this.transport = new lazy.WebSocketTransport(webSocket);
33 this.transport.hooks = this;
34 this.transport.ready();
36 lazy.logger.debug(`${this.constructor.name} ${this.id} accepted`);
39 _log(direction, data) {
40 if (lazy.Log.isDebugLevelOrMore) {
41 function replacer(key, value) {
42 if (typeof value === "string") {
43 return lazy.truncate`${value}`;
48 let payload = JSON.stringify(
51 lazy.Log.verbose ? "\t" : null
54 if (payload.length > MAX_LOG_LENGTH) {
55 // Even if we truncate individual values, the resulting message might be
56 // huge if we are serializing big objects with many properties or items.
57 // Truncate the overall message to avoid issues in logs.
58 const truncated = payload.substring(0, MAX_LOG_LENGTH);
59 payload = `${truncated} [... truncated after ${MAX_LOG_LENGTH} characters]`;
63 `${this.constructor.name} ${this.id} ${direction} ${payload}`
69 * Close the WebSocket connection.
72 this.transport.close();
76 * Register a new Session to forward the messages to.
78 * Needs to be implemented in the sub class.
80 * @param {Session} session
81 * The session to register.
83 registerSession(session) {
84 throw new Error("Not implemented");
88 * Send the JSON-serializable object to the client.
90 * @param {object} data
91 * The object to be sent.
94 this._log("<-", data);
95 this.transport.send(data);
99 * Send an error back to the client.
101 * Needs to be implemented in the sub class.
104 throw new Error("Not implemented");
108 * Send an event back to the client.
110 * Needs to be implemented in the sub class.
113 throw new Error("Not implemented");
117 * Send the result of a call to a method back to the client.
119 * Needs to be implemented in the sub class.
122 throw new Error("Not implemented");
126 return `[object ${this.constructor.name} ${this.id}]`;
132 * Called by the `transport` when the connection is closed.
134 onConnectionClose(status) {
135 lazy.logger.debug(`${this.constructor.name} ${this.id} closed`);
139 * Called when the socket is closed.
142 // In addition to the WebSocket transport, we also have to close the
143 // connection used internally within httpd.js. Otherwise the server doesn't
144 // shut down correctly, and keeps these Connection instances alive.
145 this.httpdConnection.close();
149 * Receive a packet from the WebSocket layer.
151 * This packet is sent by a WebSocket client and is meant to execute
152 * a particular method.
154 * Needs to be implemented in the sub class.
156 * @param {object} packet
157 * JSON-serializable object sent by the client.
159 async onPacket(packet) {
160 this._log("->", packet);