Viewing File: /home/ubuntu/efiexchange-node-base/node_modules/rpc-websockets/dist/index.mjs

import WebSocketImpl, { WebSocketServer } from 'ws';
import { EventEmitter } from 'eventemitter3';
import url from 'node:url';
import { v1 } from 'uuid';

// src/lib/client/websocket.ts
function WebSocket(address, options) {
  return new WebSocketImpl(address, options);
}

// src/lib/utils.ts
var DefaultDataPack = class {
  encode(value) {
    return JSON.stringify(value);
  }
  decode(value) {
    return JSON.parse(value);
  }
};

// src/lib/client.ts
var CommonClient = class extends EventEmitter {
  address;
  rpc_id;
  queue;
  options;
  autoconnect;
  ready;
  reconnect;
  reconnect_timer_id;
  reconnect_interval;
  max_reconnects;
  rest_options;
  current_reconnects;
  generate_request_id;
  socket;
  webSocketFactory;
  dataPack;
  /**
  * Instantiate a Client class.
  * @constructor
  * @param {webSocketFactory} webSocketFactory - factory method for WebSocket
  * @param {String} address - url to a websocket server
  * @param {Object} options - ws options object with reconnect parameters
  * @param {Function} generate_request_id - custom generation request Id
  * @param {DataPack} dataPack - data pack contains encoder and decoder
  * @return {CommonClient}
  */
  constructor(webSocketFactory, address = "ws://localhost:8080", {
    autoconnect = true,
    reconnect = true,
    reconnect_interval = 1e3,
    max_reconnects = 5,
    ...rest_options
  } = {}, generate_request_id, dataPack) {
    super();
    this.webSocketFactory = webSocketFactory;
    this.queue = {};
    this.rpc_id = 0;
    this.address = address;
    this.autoconnect = autoconnect;
    this.ready = false;
    this.reconnect = reconnect;
    this.reconnect_timer_id = void 0;
    this.reconnect_interval = reconnect_interval;
    this.max_reconnects = max_reconnects;
    this.rest_options = rest_options;
    this.current_reconnects = 0;
    this.generate_request_id = generate_request_id || (() => typeof this.rpc_id === "number" ? ++this.rpc_id : Number(this.rpc_id) + 1);
    if (!dataPack) this.dataPack = new DefaultDataPack();
    else this.dataPack = dataPack;
    if (this.autoconnect)
      this._connect(this.address, {
        autoconnect: this.autoconnect,
        reconnect: this.reconnect,
        reconnect_interval: this.reconnect_interval,
        max_reconnects: this.max_reconnects,
        ...this.rest_options
      });
  }
  /**
  * Connects to a defined server if not connected already.
  * @method
  * @return {Undefined}
  */
  connect() {
    if (this.socket) return;
    this._connect(this.address, {
      autoconnect: this.autoconnect,
      reconnect: this.reconnect,
      reconnect_interval: this.reconnect_interval,
      max_reconnects: this.max_reconnects,
      ...this.rest_options
    });
  }
  /**
  * Calls a registered RPC method on server.
  * @method
  * @param {String} method - RPC method name
  * @param {Object|Array} params - optional method parameters
  * @param {Number} timeout - RPC reply timeout value
  * @param {Object} ws_opts - options passed to ws
  * @return {Promise}
  */
  call(method, params, timeout, ws_opts) {
    if (!ws_opts && "object" === typeof timeout) {
      ws_opts = timeout;
      timeout = null;
    }
    return new Promise((resolve, reject) => {
      if (!this.ready) return reject(new Error("socket not ready"));
      const rpc_id = this.generate_request_id(method, params);
      const message = {
        jsonrpc: "2.0",
        method,
        params: params || void 0,
        id: rpc_id
      };
      this.socket.send(this.dataPack.encode(message), ws_opts, (error) => {
        if (error) return reject(error);
        this.queue[rpc_id] = { promise: [resolve, reject] };
        if (timeout) {
          this.queue[rpc_id].timeout = setTimeout(() => {
            delete this.queue[rpc_id];
            reject(new Error("reply timeout"));
          }, timeout);
        }
      });
    });
  }
  /**
  * Logins with the other side of the connection.
  * @method
  * @param {Object} params - Login credentials object
  * @return {Promise}
  */
  async login(params) {
    const resp = await this.call("rpc.login", params);
    if (!resp) throw new Error("authentication failed");
    return resp;
  }
  /**
  * Fetches a list of client's methods registered on server.
  * @method
  * @return {Array}
  */
  async listMethods() {
    return await this.call("__listMethods");
  }
  /**
  * Sends a JSON-RPC 2.0 notification to server.
  * @method
  * @param {String} method - RPC method name
  * @param {Object} params - optional method parameters
  * @return {Promise}
  */
  notify(method, params) {
    return new Promise((resolve, reject) => {
      if (!this.ready) return reject(new Error("socket not ready"));
      const message = {
        jsonrpc: "2.0",
        method,
        params
      };
      this.socket.send(this.dataPack.encode(message), (error) => {
        if (error) return reject(error);
        resolve();
      });
    });
  }
  /**
  * Subscribes for a defined event.
  * @method
  * @param {String|Array} event - event name
  * @return {Undefined}
  * @throws {Error}
  */
  async subscribe(event) {
    if (typeof event === "string") event = [event];
    const result = await this.call("rpc.on", event);
    if (typeof event === "string" && result[event] !== "ok")
      throw new Error(
        "Failed subscribing to an event '" + event + "' with: " + result[event]
      );
    return result;
  }
  /**
  * Unsubscribes from a defined event.
  * @method
  * @param {String|Array} event - event name
  * @return {Undefined}
  * @throws {Error}
  */
  async unsubscribe(event) {
    if (typeof event === "string") event = [event];
    const result = await this.call("rpc.off", event);
    if (typeof event === "string" && result[event] !== "ok")
      throw new Error("Failed unsubscribing from an event with: " + result);
    return result;
  }
  /**
  * Closes a WebSocket connection gracefully.
  * @method
  * @param {Number} code - socket close code
  * @param {String} data - optional data to be sent before closing
  * @return {Undefined}
  */
  close(code, data) {
    this.socket.close(code || 1e3, data);
  }
  /**
  * Enable / disable automatic reconnection.
  * @method
  * @param {Boolean} reconnect - enable / disable reconnection
  * @return {Undefined}
  */
  setAutoReconnect(reconnect) {
    this.reconnect = reconnect;
  }
  /**
  * Set the interval between reconnection attempts.
  * @method
  * @param {Number} interval - reconnection interval in milliseconds
  * @return {Undefined}
  */
  setReconnectInterval(interval) {
    this.reconnect_interval = interval;
  }
  /**
  * Set the maximum number of reconnection attempts.
  * @method
  * @param {Number} max_reconnects - maximum reconnection attempts
  * @return {Undefined}
  */
  setMaxReconnects(max_reconnects) {
    this.max_reconnects = max_reconnects;
  }
  /**
  * Connection/Message handler.
  * @method
  * @private
  * @param {String} address - WebSocket API address
  * @param {Object} options - ws options object
  * @return {Undefined}
  */
  _connect(address, options) {
    clearTimeout(this.reconnect_timer_id);
    this.socket = this.webSocketFactory(address, options);
    this.socket.addEventListener("open", () => {
      this.ready = true;
      this.emit("open");
      this.current_reconnects = 0;
    });
    this.socket.addEventListener("message", ({ data: message }) => {
      if (message instanceof ArrayBuffer)
        message = Buffer.from(message).toString();
      try {
        message = this.dataPack.decode(message);
      } catch (error) {
        return;
      }
      if (message.notification && this.listeners(message.notification).length) {
        if (!Object.keys(message.params).length)
          return this.emit(message.notification);
        const args = [message.notification];
        if (message.params.constructor === Object) args.push(message.params);
        else
          for (let i = 0; i < message.params.length; i++)
            args.push(message.params[i]);
        return Promise.resolve().then(() => {
          this.emit.apply(this, args);
        });
      }
      if (!this.queue[message.id]) {
        if (message.method) {
          return Promise.resolve().then(() => {
            this.emit(message.method, message?.params);
          });
        }
        return;
      }
      if ("error" in message === "result" in message)
        this.queue[message.id].promise[1](
          new Error(
            'Server response malformed. Response must include either "result" or "error", but not both.'
          )
        );
      if (this.queue[message.id].timeout)
        clearTimeout(this.queue[message.id].timeout);
      if (message.error) this.queue[message.id].promise[1](message.error);
      else this.queue[message.id].promise[0](message.result);
      delete this.queue[message.id];
    });
    this.socket.addEventListener("error", (error) => this.emit("error", error));
    this.socket.addEventListener("close", ({ code, reason }) => {
      if (this.ready)
        setTimeout(() => this.emit("close", code, reason), 0);
      this.ready = false;
      this.socket = void 0;
      if (code === 1e3) return;
      this.current_reconnects++;
      if (this.reconnect && (this.max_reconnects > this.current_reconnects || this.max_reconnects === 0))
        this.reconnect_timer_id = setTimeout(
          () => this._connect(address, options),
          this.reconnect_interval
        );
    });
  }
};
var Server = class extends EventEmitter {
  namespaces;
  dataPack;
  wss;
  /**
  * Instantiate a Server class.
  * @constructor
  * @param {Object} options - ws constructor's parameters with rpc
  * @param {DataPack} dataPack - data pack contains encoder and decoder
  * @return {Server} - returns a new Server instance
  */
  constructor(options, dataPack) {
    super();
    this.namespaces = {};
    if (!dataPack) this.dataPack = new DefaultDataPack();
    else this.dataPack = dataPack;
    this.wss = new WebSocketServer(options);
    this.wss.on("listening", () => this.emit("listening"));
    this.wss.on("connection", (socket, request) => {
      const u = url.parse(request.url, true);
      const ns = u.pathname;
      if (u.query.socket_id) socket._id = u.query.socket_id;
      else socket._id = v1();
      socket["_authenticated"] = false;
      socket.on("error", (error) => this.emit("socket-error", socket, error));
      socket.on("close", () => {
        this.namespaces[ns].clients.delete(socket._id);
        for (const event of Object.keys(this.namespaces[ns].events)) {
          const index = this.namespaces[ns].events[event].sockets.indexOf(
            socket._id
          );
          if (index >= 0)
            this.namespaces[ns].events[event].sockets.splice(index, 1);
        }
        this.emit("disconnection", socket);
      });
      if (!this.namespaces[ns]) this._generateNamespace(ns);
      this.namespaces[ns].clients.set(socket._id, socket);
      this.emit("connection", socket, request);
      return this._handleRPC(socket, ns);
    });
    this.wss.on("error", (error) => this.emit("error", error));
  }
  /**
  * Registers an RPC method.
  * @method
  * @param {String} name - method name
  * @param {Function} fn - a callee function
  * @param {String} ns - namespace identifier
  * @throws {TypeError}
  * @return {Object} - returns an IMethod object
  */
  register(name, fn, ns = "/") {
    if (!this.namespaces[ns]) this._generateNamespace(ns);
    this.namespaces[ns].rpc_methods[name] = {
      fn,
      protected: false
    };
    return {
      protected: () => this._makeProtectedMethod(name, ns),
      public: () => this._makePublicMethod(name, ns)
    };
  }
  /**
  * Sets an auth method.
  * @method
  * @param {Function} fn - an arbitrary auth method
  * @param {String} ns - namespace identifier
  * @throws {TypeError}
  * @return {Undefined}
  */
  setAuth(fn, ns = "/") {
    this.register("rpc.login", fn, ns);
  }
  /**
  * Marks an RPC method as protected.
  * @method
  * @param {String} name - method name
  * @param {String} ns - namespace identifier
  * @return {Undefined}
  */
  _makeProtectedMethod(name, ns = "/") {
    this.namespaces[ns].rpc_methods[name].protected = true;
  }
  /**
  * Marks an RPC method as public.
  * @method
  * @param {String} name - method name
  * @param {String} ns - namespace identifier
  * @return {Undefined}
  */
  _makePublicMethod(name, ns = "/") {
    this.namespaces[ns].rpc_methods[name].protected = false;
  }
  /**
  * Marks an event as protected.
  * @method
  * @param {String} name - event name
  * @param {String} ns - namespace identifier
  * @return {Undefined}
  */
  _makeProtectedEvent(name, ns = "/") {
    this.namespaces[ns].events[name].protected = true;
  }
  /**
  * Marks an event as public.
  * @method
  * @param {String} name - event name
  * @param {String} ns - namespace identifier
  * @return {Undefined}
  */
  _makePublicEvent(name, ns = "/") {
    this.namespaces[ns].events[name].protected = false;
  }
  /**
  * Removes a namespace and closes all connections
  * @method
  * @param {String} ns - namespace identifier
  * @throws {TypeError}
  * @return {Undefined}
  */
  closeNamespace(ns) {
    const namespace = this.namespaces[ns];
    if (namespace) {
      delete namespace.rpc_methods;
      delete namespace.events;
      for (const socket of namespace.clients.values()) socket.close();
      delete this.namespaces[ns];
    }
  }
  /**
  * Creates a new event that can be emitted to clients.
  * @method
  * @param {String} name - event name
  * @param {String} ns - namespace identifier
  * @throws {TypeError}
  * @return {Object} - returns an IEvent object
  */
  event(name, ns = "/") {
    if (!this.namespaces[ns]) this._generateNamespace(ns);
    else {
      const index = this.namespaces[ns].events[name];
      if (index !== void 0)
        throw new Error(`Already registered event ${ns}${name}`);
    }
    this.namespaces[ns].events[name] = {
      sockets: [],
      protected: false
    };
    this.on(name, (...params) => {
      if (params.length === 1 && params[0] instanceof Object)
        params = params[0];
      for (const socket_id of this.namespaces[ns].events[name].sockets) {
        const socket = this.namespaces[ns].clients.get(socket_id);
        if (!socket) continue;
        socket.send(
          this.dataPack.encode({
            notification: name,
            params
          })
        );
      }
    });
    return {
      protected: () => this._makeProtectedEvent(name, ns),
      public: () => this._makePublicEvent(name, ns)
    };
  }
  /**
  * Returns a requested namespace object
  * @method
  * @param {String} name - namespace identifier
  * @throws {TypeError}
  * @return {Object} - namespace object
  */
  of(name) {
    if (!this.namespaces[name]) this._generateNamespace(name);
    const self = this;
    return {
      // self.register convenience method
      register(fn_name, fn) {
        if (arguments.length !== 2)
          throw new Error("must provide exactly two arguments");
        if (typeof fn_name !== "string")
          throw new Error("name must be a string");
        if (typeof fn !== "function")
          throw new Error("handler must be a function");
        return self.register(fn_name, fn, name);
      },
      // self.event convenience method
      event(ev_name) {
        if (arguments.length !== 1)
          throw new Error("must provide exactly one argument");
        if (typeof ev_name !== "string")
          throw new Error("name must be a string");
        return self.event(ev_name, name);
      },
      // self.eventList convenience method
      get eventList() {
        return Object.keys(self.namespaces[name].events);
      },
      /**
      * Emits a specified event to this namespace.
      * @inner
      * @method
      * @param {String} event - event name
      * @param {Array} params - event parameters
      * @return {Undefined}
      */
      emit(event, ...params) {
        const socket_ids = [...self.namespaces[name].clients.keys()];
        for (let i = 0, id; id = socket_ids[i]; ++i) {
          self.namespaces[name].clients.get(id).send(
            self.dataPack.encode({
              notification: event,
              params: params || []
            })
          );
        }
      },
      /**
      * Returns a name of this namespace.
      * @inner
      * @method
      * @kind constant
      * @return {String}
      */
      get name() {
        return name;
      },
      /**
      * Returns a hash of websocket objects connected to this namespace.
      * @inner
      * @method
      * @return {Object}
      */
      connected() {
        const socket_ids = [...self.namespaces[name].clients.keys()];
        return socket_ids.reduce(
          (acc, curr) => ({
            ...acc,
            [curr]: self.namespaces[name].clients.get(curr)
          }),
          {}
        );
      },
      /**
      * Returns a list of client unique identifiers connected to this namespace.
      * @inner
      * @method
      * @return {Array}
      */
      clients() {
        return self.namespaces[name];
      }
    };
  }
  /**
  * Lists all created events in a given namespace. Defaults to "/".
  * @method
  * @param {String} ns - namespaces identifier
  * @readonly
  * @return {Array} - returns a list of created events
  */
  eventList(ns = "/") {
    if (!this.namespaces[ns]) return [];
    return Object.keys(this.namespaces[ns].events);
  }
  /**
  * Creates a JSON-RPC 2.0 compliant error
  * @method
  * @param {Number} code - indicates the error type that occurred
  * @param {String} message - provides a short description of the error
  * @param {String|Object} data - details containing additional information about the error
  * @return {Object}
  */
  createError(code, message, data) {
    return {
      code,
      message,
      data: data || null
    };
  }
  /**
  * Closes the server and terminates all clients.
  * @method
  * @return {Promise}
  */
  close() {
    return new Promise((resolve, reject) => {
      try {
        this.wss.close();
        this.emit("close");
        resolve();
      } catch (error) {
        reject(error);
      }
    });
  }
  /**
  * Handles all WebSocket JSON RPC 2.0 requests.
  * @private
  * @param {Object} socket - ws socket instance
  * @param {String} ns - namespaces identifier
  * @return {Undefined}
  */
  _handleRPC(socket, ns = "/") {
    socket.on("message", async (data) => {
      const msg_options = {};
      if (data instanceof ArrayBuffer) {
        msg_options.binary = true;
        data = Buffer.from(data).toString();
      }
      if (socket.readyState !== 1) return;
      let parsedData;
      try {
        parsedData = this.dataPack.decode(data);
      } catch (error) {
        return socket.send(
          this.dataPack.encode({
            jsonrpc: "2.0",
            error: createError(-32700, error.toString()),
            id: null
          }),
          msg_options
        );
      }
      if (Array.isArray(parsedData)) {
        if (!parsedData.length)
          return socket.send(
            this.dataPack.encode({
              jsonrpc: "2.0",
              error: createError(-32600, "Invalid array"),
              id: null
            }),
            msg_options
          );
        const responses = [];
        for (const message of parsedData) {
          const response2 = await this._runMethod(message, socket._id, ns);
          if (!response2) continue;
          responses.push(response2);
        }
        if (!responses.length) return;
        return socket.send(this.dataPack.encode(responses), msg_options);
      }
      const response = await this._runMethod(parsedData, socket._id, ns);
      if (!response) return;
      return socket.send(this.dataPack.encode(response), msg_options);
    });
  }
  /**
  * Runs a defined RPC method.
  * @private
  * @param {Object} message - a message received
  * @param {Object} socket_id - user's socket id
  * @param {String} ns - namespaces identifier
  * @return {Object|undefined}
  */
  async _runMethod(message, socket_id, ns = "/") {
    if (typeof message !== "object" || message === null)
      return {
        jsonrpc: "2.0",
        error: createError(-32600),
        id: null
      };
    if (message.jsonrpc !== "2.0")
      return {
        jsonrpc: "2.0",
        error: createError(-32600, "Invalid JSON RPC version"),
        id: message.id || null
      };
    if (!message.method)
      return {
        jsonrpc: "2.0",
        error: createError(-32602, "Method not specified"),
        id: message.id || null
      };
    if (typeof message.method !== "string")
      return {
        jsonrpc: "2.0",
        error: createError(-32600, "Invalid method name"),
        id: message.id || null
      };
    if (message.params && typeof message.params === "string")
      return {
        jsonrpc: "2.0",
        error: createError(-32600),
        id: message.id || null
      };
    if (message.method === "rpc.on") {
      if (!message.params)
        return {
          jsonrpc: "2.0",
          error: createError(-32e3),
          id: message.id || null
        };
      const results = {};
      const event_names = Object.keys(this.namespaces[ns].events);
      for (const name of message.params) {
        const index = event_names.indexOf(name);
        const namespace = this.namespaces[ns];
        if (index === -1) {
          results[name] = "provided event invalid";
          continue;
        }
        if (namespace.events[event_names[index]].protected === true && namespace.clients.get(socket_id)["_authenticated"] === false) {
          return {
            jsonrpc: "2.0",
            error: createError(-32606),
            id: message.id || null
          };
        }
        const socket_index = namespace.events[event_names[index]].sockets.indexOf(socket_id);
        if (socket_index >= 0) {
          results[name] = "socket has already been subscribed to event";
          continue;
        }
        namespace.events[event_names[index]].sockets.push(socket_id);
        results[name] = "ok";
      }
      return {
        jsonrpc: "2.0",
        result: results,
        id: message.id || null
      };
    } else if (message.method === "rpc.off") {
      if (!message.params)
        return {
          jsonrpc: "2.0",
          error: createError(-32e3),
          id: message.id || null
        };
      const results = {};
      for (const name of message.params) {
        if (!this.namespaces[ns].events[name]) {
          results[name] = "provided event invalid";
          continue;
        }
        const index = this.namespaces[ns].events[name].sockets.indexOf(socket_id);
        if (index === -1) {
          results[name] = "not subscribed";
          continue;
        }
        this.namespaces[ns].events[name].sockets.splice(index, 1);
        results[name] = "ok";
      }
      return {
        jsonrpc: "2.0",
        result: results,
        id: message.id || null
      };
    } else if (message.method === "rpc.login") {
      if (!message.params)
        return {
          jsonrpc: "2.0",
          error: createError(-32604),
          id: message.id || null
        };
    }
    if (!this.namespaces[ns].rpc_methods[message.method]) {
      return {
        jsonrpc: "2.0",
        error: createError(-32601),
        id: message.id || null
      };
    }
    let response = null;
    if (this.namespaces[ns].rpc_methods[message.method].protected === true && this.namespaces[ns].clients.get(socket_id)["_authenticated"] === false) {
      return {
        jsonrpc: "2.0",
        error: createError(-32605),
        id: message.id || null
      };
    }
    try {
      response = await this.namespaces[ns].rpc_methods[message.method].fn(
        message.params,
        socket_id
      );
    } catch (error) {
      if (!message.id) return;
      if (error instanceof Error)
        return {
          jsonrpc: "2.0",
          error: {
            code: -32e3,
            message: error.name,
            data: error.message
          },
          id: message.id
        };
      return {
        jsonrpc: "2.0",
        error,
        id: message.id
      };
    }
    if (!message.id) return;
    if (message.method === "rpc.login" && response === true) {
      const s = this.namespaces[ns].clients.get(socket_id);
      s["_authenticated"] = true;
      this.namespaces[ns].clients.set(socket_id, s);
    }
    return {
      jsonrpc: "2.0",
      result: response,
      id: message.id
    };
  }
  /**
  * Generate a new namespace store.
  * Also preregister some special namespace methods.
  * @private
  * @param {String} name - namespaces identifier
  * @return {undefined}
  */
  _generateNamespace(name) {
    this.namespaces[name] = {
      rpc_methods: {
        __listMethods: {
          fn: () => Object.keys(this.namespaces[name].rpc_methods),
          protected: false
        }
      },
      clients: /* @__PURE__ */ new Map(),
      events: {}
    };
  }
};
var RPC_ERRORS = /* @__PURE__ */ new Map([
  [-32e3, "Event not provided"],
  [-32600, "Invalid Request"],
  [-32601, "Method not found"],
  [-32602, "Invalid params"],
  [-32603, "Internal error"],
  [-32604, "Params not found"],
  [-32605, "Method forbidden"],
  [-32606, "Event forbidden"],
  [-32700, "Parse error"]
]);
function createError(code, details) {
  const error = {
    code,
    message: RPC_ERRORS.get(code) || "Internal Server Error"
  };
  if (details) error["data"] = details;
  return error;
}

// src/index.ts
var Client = class extends CommonClient {
  constructor(address = "ws://localhost:8080", {
    autoconnect = true,
    reconnect = true,
    reconnect_interval = 1e3,
    max_reconnects = 5,
    ...rest_options
  } = {}, generate_request_id) {
    super(
      WebSocket,
      address,
      {
        autoconnect,
        reconnect,
        reconnect_interval,
        max_reconnects,
        ...rest_options
      },
      generate_request_id
    );
  }
};

export { Client, CommonClient, DefaultDataPack, Server, WebSocket, createError };
//# sourceMappingURL=out.js.map
//# sourceMappingURL=index.mjs.map
Back to Directory File Manager