summaryrefslogtreecommitdiffstats
path: root/remote/server/WebSocketTransport.sys.mjs
blob: 0ac6a6399a5312cebef6a70b214bca85fe231642 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* 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/. */

// This is an XPCOM service-ified copy of ../devtools/shared/transport/websocket-transport.js.

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

export function WebSocketTransport(socket) {
  lazy.EventEmitter.decorate(this);

  this.active = false;
  this.hooks = null;
  this.socket = socket;
}

WebSocketTransport.prototype = {
  ready() {
    if (this.active) {
      return;
    }

    this.socket.addEventListener("message", this);
    this.socket.addEventListener("close", this);

    this.active = true;
  },

  send(object) {
    this.emit("send", object);
    if (this.socket) {
      this.socket.send(JSON.stringify(object));
    }
  },

  startBulkSend() {
    throw new Error("Bulk send is not supported by WebSocket transport");
  },

  /**
   * Force closing the active connection and WebSocket.
   */
  close() {
    if (!this.socket) {
      return;
    }
    this.emit("close");

    if (this.hooks) {
      this.hooks.onConnectionClose();
    }

    this.active = false;

    this.socket.removeEventListener("message", this);

    // Remove the listener that is used when the connection
    // is closed because we already emitted the 'close' event.
    // Instead listen for the closing of the WebSocket.
    this.socket.removeEventListener("close", this);
    this.socket.addEventListener("close", this.onSocketClose.bind(this));

    // Close socket with code `1000` for a normal closure.
    this.socket.close(1000);
  },

  /**
   * Callback for socket on close event,
   * it is used in case websocket was closed by the client.
   */
  onClose() {
    if (!this.socket) {
      return;
    }
    this.emit("close");

    if (this.hooks) {
      this.hooks.onConnectionClose();
      this.hooks.onSocketClose();
      this.hooks = null;
    }

    this.active = false;

    this.socket.removeEventListener("message", this);
    this.socket.removeEventListener("close", this);
    this.socket = null;
  },

  /**
   * Callback which is called when we can be sure that websocket is closed.
   */
  onSocketClose() {
    this.socket = null;

    if (this.hooks) {
      this.hooks.onSocketClose();
      this.hooks = null;
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "message":
        this.onMessage(event);
        break;
      case "close":
        this.onClose();
        break;
    }
  },

  onMessage({ data }) {
    if (typeof data !== "string") {
      throw new Error(
        "Binary messages are not supported by WebSocket transport"
      );
    }

    const object = JSON.parse(data);
    this.emit("packet", object);
    if (this.hooks) {
      this.hooks.onPacket(object);
    }
  },
};