UNPKG

@baptistecdr/aria2

Version:

Library for aria2, "The next generation download utility."

291 lines (241 loc) 6.84 kB
class JSONRPCError extends Error { constructor({ message, code, data }) { super(message); this.code = code; if (data) this.data = data; this.name = this.constructor.name; } } function promiseEvent(target, event) { const controller = new AbortController(); const { signal } = controller; return new Promise((resolve, reject) => { target.addEventListener(event, resolve, { signal, }); target.addEventListener("error", reject, { signal, }); }).finally(() => controller.abort()); } // https://github.com/nodejs/node/issues/58918 if (!globalThis.ErrorEvent) { globalThis.ErrorEvent = class ErrorEvent extends Event { constructor(type, options) { super(type, options); this.error = options?.error; } }; } class JSONRPCEvent extends Event { constructor(type, options) { super(type, options); this.data = options?.data; } } class JSONRPCNotificationEvent extends Event { constructor(type, options) { super(type, options); this.method = options?.method; this.params = options?.params; } } class JSONRPCClient extends EventTarget { constructor(options) { super(); this.deferreds = Object.create(null); this.lastId = 0; Object.assign(this, this.constructor.defaultOptions, options); } id() { return this.lastId++; } url(protocol) { return `${protocol + (this.secure ? "s" : "")}://${this.host}:${this.port}${this.path}`; } async websocket(message) { this.socket.send(JSON.stringify(message)); } async http(message) { const response = await fetch(this.url("http"), { method: "POST", body: JSON.stringify(message), headers: { Accept: "application/json", "Content-Type": "application/json", }, }); let responseJson; try { responseJson = await response.json(); this._onmessage(responseJson); } catch (error) { this.dispatchEvent(new ErrorEvent("error", { error })); throw error; } return responseJson; } _buildMessage(method, params) { if (typeof method !== "string") { throw new TypeError(`${method} is not a string`); } const message = { method, "json-rpc": "2.0", id: this.id(), }; if (params) Object.assign(message, { params }); return message; } async batch(calls) { const message = calls.map(([method, params]) => { return this._buildMessage(method, params); }); const promises = message.map(({ id }) => { this.deferreds[id] = Promise.withResolvers(); const { promise } = this.deferreds[id]; return promise; }); await this._send(message); return promises; } async call(method, parameters) { const message = this._buildMessage(method, parameters); this.deferreds[message.id] = Promise.withResolvers(); const { promise } = this.deferreds[message.id]; await this._send(message); return promise; } async _send(message) { this.dispatchEvent(new JSONRPCEvent("output", { data: message })); return this.socket?.readyState === 1 ? this.websocket(message) : this.http(message); } _onresponse({ id, error, result }) { const deferred = this.deferreds[id]; if (!deferred) return; if (error) deferred.reject(new JSONRPCError(error)); else deferred.resolve(result); delete this.deferreds[id]; } _onrequest({ method, params }) { return this.onrequest(method, params); } _onnotification({ method, params }) { this.dispatchEvent(new JSONRPCNotificationEvent("notification", { method, params })); } _onmessage(message) { this.dispatchEvent(new JSONRPCEvent("input", { data: message })); if (Array.isArray(message)) { for (const object of message) { this._onobject(object); } } else { this._onobject(message); } } _onobject(message) { if (message.method === undefined) this._onresponse(message); else if (message.id === undefined) this._onnotification(message); else this._onrequest(message); } async open() { this.socket = new WebSocket(this.url("ws")); const { socket } = this; socket.onclose = () => { this.dispatchEvent(new Event("close")); }; socket.onmessage = (event) => { let message; try { message = JSON.parse(event.data); } catch (error) { this.dispatchEvent(new ErrorEvent("error", { error })); return; } this._onmessage(message); }; socket.onopen = () => { this.dispatchEvent(new Event("open")); }; socket.onerror = (evt) => { this.dispatchEvent(new ErrorEvent("error", { error: evt })); }; return promiseEvent(this, "open"); } async close() { const { socket } = this; socket.close(); return promiseEvent(this, "close"); } static defaultOptions = { secure: false, host: "localhost", port: 80, secret: "", path: "/jsonrpc", }; } function prefix(str) { let prefixedStr = str; if (!str.startsWith("system.") && !str.startsWith("aria2.")) { prefixedStr = `aria2.${str}`; } return prefixedStr; } function unprefix(str) { const suffix = str.split("aria2.")[1]; return suffix || str; } class Aria2 extends JSONRPCClient { addSecret(parameters) { let params = this.secret ? [`token:${this.secret}`] : []; if (Array.isArray(parameters)) { params = params.concat(parameters); } return params; } _onnotification(notification) { const { method, params } = notification; const event = unprefix(method); if (event !== method) { this.dispatchEvent(new JSONRPCNotificationEvent(event, { params })); } return super._onnotification(notification); } async call(method, ...params) { return super.call(prefix(method), this.addSecret(params)); } async multicall(calls) { const multi = [ calls.map(([method, ...params]) => { return { methodName: prefix(method), params: this.addSecret(params) }; }), ]; return super.call("system.multicall", multi); } async batch(calls) { return super.batch(calls.map(([method, ...params]) => [prefix(method), this.addSecret(params)])); } async listNotifications() { const events = await this.call("system.listNotifications"); return events.map((event) => unprefix(event)); } async listMethods() { const methods = await this.call("system.listMethods"); return methods.map((method) => unprefix(method)); } static prefix; static unprefix; static defaultOptions = { ...JSONRPCClient.defaultOptions, ...{ secure: false, host: "localhost", port: 6800, secret: "", path: "/jsonrpc", }, }; } export { Aria2 as default }; //# sourceMappingURL=index.js.map