@naria2/node
Version:
Cross-platform wrapper of aria2
263 lines (253 loc) • 7.98 kB
JavaScript
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 };