Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
truncate: "chrome://remote/content/shared/Format.sys.mjs",
WebSocketTransport:
"chrome://remote/content/server/WebSocketTransport.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"truncateLog",
"remote.log.truncate",
false
);
const MAX_LOG_LENGTH = 2500;
export class WebSocketConnection {
/**
* @param {WebSocket} webSocket
* The WebSocket server connection to wrap.
* @param {Connection} httpdConnection
* Reference to the httpd.js's connection needed for clean-up.
*/
constructor(webSocket, httpdConnection) {
this.id = lazy.generateUUID();
this.httpdConnection = httpdConnection;
this.transport = new lazy.WebSocketTransport(webSocket);
this.transport.hooks = this;
this.transport.ready();
lazy.logger.debug(`${this.constructor.name} ${this.id} accepted`);
}
#log(direction, data) {
if (lazy.Log.isDebugLevelOrMore) {
function replacer(key, value) {
if (typeof value === "string") {
return lazy.truncate`${value}`;
}
return value;
}
let payload = JSON.stringify(
data,
replacer,
lazy.Log.verbose ? "\t" : null
);
if (lazy.truncateLog && payload.length > MAX_LOG_LENGTH) {
// Even if we truncate individual values, the resulting message might be
// huge if we are serializing big objects with many properties or items.
// Truncate the overall message to avoid issues in logs.
const truncated = payload.substring(0, MAX_LOG_LENGTH);
payload = `${truncated} [... truncated after ${MAX_LOG_LENGTH} characters]`;
}
lazy.logger.debug(
`${this.constructor.name} ${this.id} ${direction} ${payload}`
);
}
}
/**
* Close the WebSocket connection.
*/
close() {
this.transport.close();
}
/**
* Register a new Session to forward the messages to.
*
* Needs to be implemented in the sub class.
*/
registerSession() {
throw new Error("Not implemented");
}
/**
* Send the JSON-serializable object to the client.
*
* @param {object} data
* The object to be sent.
*/
send(data) {
this.#log("<-", data);
this.transport.send(data);
}
/**
* Send an error back to the client.
*
* Needs to be implemented in the sub class.
*/
sendError() {
throw new Error("Not implemented");
}
/**
* Send an event back to the client.
*
* Needs to be implemented in the sub class.
*/
sendEvent() {
throw new Error("Not implemented");
}
/**
* Send the result of a call to a method back to the client.
*
* Needs to be implemented in the sub class.
*/
sendResult() {
throw new Error("Not implemented");
}
toString() {
return `[object ${this.constructor.name} ${this.id}]`;
}
// Transport hooks
/**
* Called by the `transport` when the connection is closed.
*/
onConnectionClose() {
lazy.logger.debug(`${this.constructor.name} ${this.id} closed`);
}
/**
* Called when the socket is closed.
*/
onSocketClose() {
// In addition to the WebSocket transport, we also have to close the
// connection used internally within httpd.js. Otherwise the server doesn't
// shut down correctly, and keeps these Connection instances alive.
this.httpdConnection.close();
}
/**
* Receive a packet from the WebSocket layer.
*
* This packet is sent by a WebSocket client and is meant to execute
* a particular method.
*
* Needs to be implemented in the sub class.
*
* @param {object} packet
* JSON-serializable object sent by the client.
*/
async onPacket(packet) {
this.#log("->", packet);
}
}