shadowrocket-node-switch
v0.1.0
Published
Switch Shadowrocket nodes from Node.js.
Readme
shadowrocket-node-switch
Switch Shadowrocket nodes from Node.js on macOS.
This package only wraps Shadowrocket's URL scheme. Shadowrocket does not expose a public API for reading the full node list, so random switching requires you to pass the node names yourself.
Install
npm install shadowrocket-node-switchUsage
CommonJS:
var shadowrocket = require("shadowrocket-node-switch");
shadowrocket.selectNode("Node1").then(function () {
return shadowrocket.selectRandomNode([
"Node1",
"Node2",
]);
}).then(function (selected) {
console.log("selected:", selected);
});ES modules:
import { selectNode, selectRandomNode } from "shadowrocket-node-switch";
await selectNode("Node1");
const selected = await selectRandomNode([
"Node1",
"Node2",
]);
console.log("selected:", selected);API
selectNode(name)
Switches Shadowrocket to the exact node name.
shadowrocket.selectNode("Node1");The name must match the node name shown in Shadowrocket.
The package uses URLSearchParams and execFile, equivalent to:
open -g "shadowrocket://select?s=%E5%B0%8A%E4%BA%AB%E9%A6%99%E6%B8%AF16"selectRandomNode(names)
Picks one name from names, switches to it, and returns the selected name.
shadowrocket.selectRandomNode(nodes).then(function (selected) {
console.log("selected:", selected);
});This package intentionally does not keep a node list. Keep the list in your business code, config file, database, or subscription parser.
HTTP keep-alive after switching nodes
Switching Shadowrocket only affects new network connections. Existing TCP/TLS/HTTP2 connections can keep using the old route. The most common cause is HTTP2 keep-alive: one HTTP2 session can carry many requests, so changing the Shadowrocket node does not affect that already-open session.
The usual fix is simple: close the old client/agent after finishing one account, switch Shadowrocket, wait briefly, then create a new client/agent for the next account.
const { selectNode } = require("shadowrocket-node-switch");
for (const item of accounts) {
// close previous HTTP client/agent before this point
await selectNode(item.nodeName);
await new Promise((resolve) => setTimeout(resolve, 1500));
// create a fresh HTTP client/agent here
await runAccount(item);
}axios
Axios uses Node's http/https agents. Use one agent pair per account and destroy it before switching to the next node.
const http = require("http");
const https = require("https");
const axios = require("axios");
async function runWithAxios(account) {
const httpAgent = new http.Agent({ keepAlive: true });
const httpsAgent = new https.Agent({ keepAlive: true });
try {
await axios.get("https://example.com/ip", {
httpAgent,
httpsAgent,
proxy: false,
});
} finally {
httpAgent.destroy();
httpsAgent.destroy();
}
}fetch
Node's built-in fetch uses Undici. Give each account its own dispatcher and close it before switching nodes.
const { Agent } = require("undici");
async function runWithFetch(account) {
const dispatcher = new Agent();
try {
await fetch("https://example.com/ip", { dispatcher });
} finally {
await dispatcher.close();
}
}If you do not want to import undici, the fallback is process-level isolation: finish one account, exit Node, switch node, then start a new Node process.
got-scraping
For got-scraping, create a fresh sessionToken and fresh agents per account. Destroy all agents before switching nodes.
const http = require("http");
const https = require("https");
const { gotScraping } = require("got-scraping");
const { Agent: Http2Agent } = require("http2-wrapper");
async function runWithGotScraping(account) {
const agent = {
http: new http.Agent({ keepAlive: true }),
https: new https.Agent({ keepAlive: true }),
http2: new Http2Agent(),
};
try {
await gotScraping("https://example.com/ip", {
agent,
sessionToken: {},
retry: { limit: 0 },
});
} finally {
agent.http.destroy();
agent.https.destroy();
agent.http2.destroy();
}
}Limitations
This package cannot read Shadowrocket's node list. Shadowrocket currently exposes node selection through URL schemes, but not a public list API.
