UNPKG

@naria2/node

Version:

Cross-platform wrapper of aria2

263 lines (253 loc) 7.98 kB
import path from 'node:path'; import { createRequire } from 'node:module'; import { spawn as spawn$1 } from 'node:child_process'; import { execa } from 'execa'; import { randomUUID } from 'node:crypto'; import getPort from 'get-port'; import { createWebSocket, ReadyState } from 'maria2/transport'; import { resolveGlobalOptions, resolveRPCOptions, stringifyCliOptions, isDef } from '@naria2/options'; import { readFile } from 'fs/promises'; import { readFileSync } from 'fs'; var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$1 = (obj, key, value) => { __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class Naria2NodeError extends Error { constructor(message, detail) { super(`[@naria2/node] ${message}`, { cause: detail?.cause }); __publicField$1(this, "binary"); __publicField$1(this, "url"); this.binary = detail?.binary; this.url = detail?.url; } } function getPackage() { const { platform, arch } = process; switch (platform) { case 'win32': if (['x64', 'ia32'].includes(arch)) { return `@naria2/win32-${arch}`; } case 'darwin': if (['x64', 'arm64'].includes(arch)) { return `@naria2/darwin-${arch}`; } case 'linux': if (['x64', 'arm64'].includes(arch)) { return `@naria2/linux-${arch}`; } } throw new Error('naria2 does not provide aria2 binary of your platform'); } const BINARY = getPackage(); const require = createRequire(import.meta.url); function getNaria2Binary() { const pkg = require.resolve(BINARY + "/package.json"); const { platform } = process; const binary = path.join(path.dirname(pkg), platform === "win32" ? "aria2c.exe" : "aria2c"); return binary; } function run(args, options = {}) { if (!options.binary) { const binary = getNaria2Binary(); return execa(binary, args, options); } else { const binary = options.binary; delete options["binary"]; return execa(binary, args, options); } } function spawn(args, options) { const binary = options?.binary ?? getNaria2Binary(); return spawn$1(binary, args, options ?? {}); } var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class ChildProcessSocket { constructor(url, childProcess, options) { __publicField(this, "url"); __publicField(this, "childProcess"); __publicField(this, "disposables", /* @__PURE__ */ new Set()); __publicField(this, "options"); __publicField(this, "socket"); this.url = url; this.socket = createWebSocket(url, options.ws); this.childProcess = childProcess; this.options = options; this.socket.addEventListener( "error", (e) => { if (this.socket?.readyState === ReadyState.Open) { this.close(e?.code, e?.reason); } }, { once: true } ); } get readyState() { return this.socket.readyState; } getOptions() { return { listenPort: this.options.rpc.listenPort, secret: this.options.rpc.secret, args: this.options.args }; } onClose(fn) { this.disposables.add(fn); return () => { this.disposables.delete(fn); }; } close(code, reason) { for (const dp of [...this.disposables].reverse()) { try { dp(); } catch (error) { } } this.socket.close(code, reason); this.childProcess.kill(); } send(data) { return this.socket.send(data); } addEventListener(type, listener, options) { return this.socket.addEventListener(type, listener, options); } } async function createChildProcess(options = {}) { const resolvedArgs = []; const [environment, proxy] = inferEnv(options.environment); const rpcOptions = { ...options?.rpc, listenPort: options?.rpc?.listenPort ?? await getPort({ port: 6800 }), secret: options?.rpc?.secret ?? randomUUID() }; const resolvedOptions = { ws: options.ws ?? {}, rpc: rpcOptions, args: resolvedArgs, spawn: { ...options.spawn, env: { ...environment, ...proxy } } }; const aria2Args = resolveGlobalOptions(options); const aria2RpcArgs = resolveRPCOptions(rpcOptions); resolvedArgs.push(...stringifyCliOptions({ ...aria2Args, ...aria2RpcArgs })); const child = spawn(resolvedArgs, resolvedOptions.spawn); await new Promise((res, rej) => { let spawn2 = false; if (child.stdout) { child.stdout.once("data", () => { spawn2 = true; res(); }); } else { child.once("spawn", () => { spawn2 = true; res(); }); } child.once("error", (e) => { if (!spawn2) { const binary = resolvedOptions.spawn.binary ?? getNaria2Binary(); const error = new Naria2NodeError(`Failed spawning aria2 child process at ${binary}`, { cause: e, binary }); rej(error); } }); }); const url = `ws://127.0.0.1:${rpcOptions.listenPort}/jsonrpc`; await new Promise(async (res, rej) => { const { retry: maxRetry = 5, retryDelay = 100 } = resolvedOptions.ws; let retry = 0; const connect = () => { let opened = false; const ws = createWebSocket(url); ws.addEventListener( "open", () => { try { opened = true; ws.close(); } finally { res(); } }, { once: true } ); ws.addEventListener( "error", (e) => { if (opened) return; if (ws.readyState === ReadyState.Connecting || ws.readyState === ReadyState.Closing) { if (retry < maxRetry) { retry++; try { ws.close(); } finally { setTimeout(() => { connect(); }, retryDelay); return; } } } else if (ws.readyState === ReadyState.Closed) { return; } const binary = resolvedOptions.spawn.binary ?? getNaria2Binary(); const error = new Naria2NodeError( `Failed connecting to aria2 JSON RPC server at ${url}`, { cause: e, binary, url } ); rej(error); }, { once: true } ); }; connect(); }); return new ChildProcessSocket(url, child, resolvedOptions); } function inferEnv(environment) { const env = { ...process?.env }; const picked = { http_proxy: env["http_proxy"], https_proxy: env["https_proxy"], ftp_proxy: env["ftp_proxy"], all_proxy: env["all_proxy"], no_proxy: env["no_proxy"] }; delete env["http_proxy"]; delete env["https_proxy"]; delete env["ftp_proxy"]; delete env["all_proxy"]; delete env["no_proxy"]; if (!environment) return [env, picked]; const proxy = isDef(environment) ? environment === "inherit" ? picked : environment === "ignore" ? {} : { ...environment, no_proxy: environment?.no_proxy?.join(",") } : picked; return [env, proxy]; } async function readTorrent(file) { const buffer = await readFile(file); return buffer.toString("base64"); } function readTorrentSync(file) { const buffer = readFileSync(file); return buffer.toString("base64"); } async function fetchTorrent(...args) { const resp = await fetch(...args); const buffer = await resp.arrayBuffer(); return Buffer.from(buffer).toString("base64"); } export { ChildProcessSocket, Naria2NodeError, createChildProcess, fetchTorrent, getNaria2Binary, readTorrent, readTorrentSync, run, spawn };