@naria2/node
Version:
Cross-platform wrapper of aria2
279 lines (266 loc) • 8.9 kB
JavaScript
;
const path = require('node:path');
const node_module = require('node:module');
const node_child_process = require('node:child_process');
const execa = require('execa');
const node_crypto = require('node:crypto');
const getPort = require('get-port');
const transport = require('maria2/transport');
const options = require('@naria2/options');
const promises = require('fs/promises');
const fs = require('fs');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
const getPort__default = /*#__PURE__*/_interopDefaultCompat(getPort);
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$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
function getNaria2Binary() {
const pkg = require$1.resolve(BINARY + "/package.json");
const { platform } = process;
const binary = path__default.join(path__default.dirname(pkg), platform === "win32" ? "aria2c.exe" : "aria2c");
return binary;
}
function run(args, options = {}) {
if (!options.binary) {
const binary = getNaria2Binary();
return execa.execa(binary, args, options);
} else {
const binary = options.binary;
delete options["binary"];
return execa.execa(binary, args, options);
}
}
function spawn(args, options) {
const binary = options?.binary ?? getNaria2Binary();
return node_child_process.spawn(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 = transport.createWebSocket(url, options.ws);
this.childProcess = childProcess;
this.options = options;
this.socket.addEventListener(
"error",
(e) => {
if (this.socket?.readyState === transport.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$1 = {}) {
const resolvedArgs = [];
const [environment, proxy] = inferEnv(options$1.environment);
const rpcOptions = {
...options$1?.rpc,
listenPort: options$1?.rpc?.listenPort ?? await getPort__default({ port: 6800 }),
secret: options$1?.rpc?.secret ?? node_crypto.randomUUID()
};
const resolvedOptions = {
ws: options$1.ws ?? {},
rpc: rpcOptions,
args: resolvedArgs,
spawn: { ...options$1.spawn, env: { ...environment, ...proxy } }
};
const aria2Args = options.resolveGlobalOptions(options$1);
const aria2RpcArgs = options.resolveRPCOptions(rpcOptions);
resolvedArgs.push(...options.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 = transport.createWebSocket(url);
ws.addEventListener(
"open",
() => {
try {
opened = true;
ws.close();
} finally {
res();
}
},
{ once: true }
);
ws.addEventListener(
"error",
(e) => {
if (opened)
return;
if (ws.readyState === transport.ReadyState.Connecting || ws.readyState === transport.ReadyState.Closing) {
if (retry < maxRetry) {
retry++;
try {
ws.close();
} finally {
setTimeout(() => {
connect();
}, retryDelay);
return;
}
}
} else if (ws.readyState === transport.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 = options.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 promises.readFile(file);
return buffer.toString("base64");
}
function readTorrentSync(file) {
const buffer = fs.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");
}
exports.ChildProcessSocket = ChildProcessSocket;
exports.Naria2NodeError = Naria2NodeError;
exports.createChildProcess = createChildProcess;
exports.fetchTorrent = fetchTorrent;
exports.getNaria2Binary = getNaria2Binary;
exports.readTorrent = readTorrent;
exports.readTorrentSync = readTorrentSync;
exports.run = run;
exports.spawn = spawn;