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