fetch-tor-proxy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/TorProcessManager.d.ts +21 -0
- package/build/TorProcessManager.d.ts.map +1 -0
- package/build/TorProcessManager.js +102 -0
- package/build/TorProcessManager.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +32 -0
- package/build/index.js.map +1 -0
- package/build/tests/test.d.ts +2 -0
- package/build/tests/test.d.ts.map +1 -0
- package/build/tests/test.js +25 -0
- package/build/tests/test.js.map +1 -0
- package/package.json +19 -0
- package/src/TorProcessManager.ts +126 -0
- package/src/index.ts +37 -0
- package/src/tests/test.ts +27 -0
- package/tor-expert-bundle/geoip +368220 -0
- package/tor-expert-bundle/geoip6 +281492 -0
- package/tor-expert-bundle/start.bat +1 -0
- package/tor-expert-bundle/tor.exe +0 -0
- package/tor-expert-bundle/torrc +11 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
2
|
+
export interface TorProcessManagerOptions {
|
|
3
|
+
torBinaryPath?: string;
|
|
4
|
+
torConfigPath?: string;
|
|
5
|
+
bootstrapTimeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class TorProcessManager {
|
|
8
|
+
private readonly torBinaryPath;
|
|
9
|
+
private readonly torConfigPath;
|
|
10
|
+
private readonly bootstrapTimeoutMs;
|
|
11
|
+
private torProcess;
|
|
12
|
+
private isReady;
|
|
13
|
+
private startupPromise;
|
|
14
|
+
private readonly isWindows;
|
|
15
|
+
constructor(options?: TorProcessManagerOptions);
|
|
16
|
+
start(): Promise<void>;
|
|
17
|
+
stop(): void;
|
|
18
|
+
createAgent(proxyUrl: string): SocksProxyAgent;
|
|
19
|
+
private resetState;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=TorProcessManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TorProcessManager.d.ts","sourceRoot":"","sources":["../src/TorProcessManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,wBAAwB;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C,OAAO,CAAC,UAAU,CAA6B;IAC/C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,OAAO,CAAC,EAAE,wBAAwB;IAMxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0E5B,IAAI,IAAI,IAAI;IAiBZ,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe;IAI9C,OAAO,CAAC,UAAU;CAIrB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TorProcessManager = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const socks_proxy_agent_1 = require("socks-proxy-agent");
|
|
10
|
+
class TorProcessManager {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.torProcess = null;
|
|
13
|
+
this.isReady = false;
|
|
14
|
+
this.startupPromise = null;
|
|
15
|
+
this.isWindows = process.platform === 'win32';
|
|
16
|
+
this.torBinaryPath = options?.torBinaryPath ?? path_1.default.join(__dirname, '..', 'tor-expert-bundle', 'tor');
|
|
17
|
+
this.torConfigPath = options?.torConfigPath ?? path_1.default.join(__dirname, '..', 'tor-expert-bundle', 'torrc');
|
|
18
|
+
this.bootstrapTimeoutMs = options?.bootstrapTimeoutMs ?? 30000;
|
|
19
|
+
}
|
|
20
|
+
async start() {
|
|
21
|
+
if (!this.isWindows) {
|
|
22
|
+
// On Linux/macOS, assume Tor is managed externally (service/container/etc.).
|
|
23
|
+
this.isReady = true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (this.isReady) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (this.startupPromise) {
|
|
30
|
+
return this.startupPromise;
|
|
31
|
+
}
|
|
32
|
+
this.startupPromise = new Promise((resolve, reject) => {
|
|
33
|
+
const torProcess = (0, child_process_1.spawn)(this.torBinaryPath, ['-f', this.torConfigPath]);
|
|
34
|
+
this.torProcess = torProcess;
|
|
35
|
+
let settled = false;
|
|
36
|
+
let timeout;
|
|
37
|
+
const cleanup = () => {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
torProcess.stdout?.removeListener('data', onStdout);
|
|
40
|
+
torProcess.removeListener('error', onError);
|
|
41
|
+
torProcess.removeListener('close', onClose);
|
|
42
|
+
};
|
|
43
|
+
const settle = (fn) => {
|
|
44
|
+
if (settled) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
settled = true;
|
|
48
|
+
cleanup();
|
|
49
|
+
fn();
|
|
50
|
+
};
|
|
51
|
+
const onStdout = (data) => {
|
|
52
|
+
if (data.toString().includes('Bootstrapped 100%')) {
|
|
53
|
+
this.isReady = true;
|
|
54
|
+
settle(() => resolve());
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const onError = (error) => {
|
|
58
|
+
this.resetState();
|
|
59
|
+
settle(() => reject(error));
|
|
60
|
+
};
|
|
61
|
+
const onClose = (code) => {
|
|
62
|
+
const exitedBeforeReady = !this.isReady;
|
|
63
|
+
this.resetState();
|
|
64
|
+
if (exitedBeforeReady) {
|
|
65
|
+
settle(() => reject(new Error(`Tor process exited before bootstrap completed (code: ${code ?? 'unknown'}).`)));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
timeout = setTimeout(() => {
|
|
69
|
+
this.stop();
|
|
70
|
+
settle(() => reject(new Error(`Tor bootstrap timed out after ${this.bootstrapTimeoutMs} ms.`)));
|
|
71
|
+
}, this.bootstrapTimeoutMs);
|
|
72
|
+
torProcess.stdout?.on('data', onStdout);
|
|
73
|
+
torProcess.on('error', onError);
|
|
74
|
+
torProcess.on('close', onClose);
|
|
75
|
+
}).finally(() => {
|
|
76
|
+
this.startupPromise = null;
|
|
77
|
+
});
|
|
78
|
+
return this.startupPromise;
|
|
79
|
+
}
|
|
80
|
+
stop() {
|
|
81
|
+
if (!this.isWindows) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!this.torProcess) {
|
|
85
|
+
this.resetState();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!this.torProcess.killed) {
|
|
89
|
+
this.torProcess.kill();
|
|
90
|
+
}
|
|
91
|
+
this.resetState();
|
|
92
|
+
}
|
|
93
|
+
createAgent(proxyUrl) {
|
|
94
|
+
return new socks_proxy_agent_1.SocksProxyAgent(proxyUrl);
|
|
95
|
+
}
|
|
96
|
+
resetState() {
|
|
97
|
+
this.torProcess = null;
|
|
98
|
+
this.isReady = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.TorProcessManager = TorProcessManager;
|
|
102
|
+
//# sourceMappingURL=TorProcessManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TorProcessManager.js","sourceRoot":"","sources":["../src/TorProcessManager.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RequestInit, Response } from 'node-fetch';
|
|
2
|
+
import { TorProcessManager } from './TorProcessManager';
|
|
3
|
+
export interface ProxiedFetchOptions extends RequestInit {
|
|
4
|
+
killTor?: boolean;
|
|
5
|
+
proxyUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function proxiedFetch(url: string, options?: ProxiedFetchOptions): Promise<Response>;
|
|
8
|
+
export { proxiedFetch as fetch, TorProcessManager };
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAsB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBhG;AAED,OAAO,EAAE,YAAY,IAAI,KAAK,EAAE,iBAAiB,EAAE,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TorProcessManager = void 0;
|
|
7
|
+
exports.proxiedFetch = proxiedFetch;
|
|
8
|
+
exports.fetch = proxiedFetch;
|
|
9
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
10
|
+
const TorProcessManager_1 = require("./TorProcessManager");
|
|
11
|
+
Object.defineProperty(exports, "TorProcessManager", { enumerable: true, get: function () { return TorProcessManager_1.TorProcessManager; } });
|
|
12
|
+
const torProcessManager = new TorProcessManager_1.TorProcessManager();
|
|
13
|
+
async function proxiedFetch(url, options) {
|
|
14
|
+
const { killTor = true, proxyUrl = 'socks5h://127.0.0.1:9050', ...fetchOptions } = options ?? {};
|
|
15
|
+
await torProcessManager.start();
|
|
16
|
+
try {
|
|
17
|
+
return await (0, node_fetch_1.default)(url, {
|
|
18
|
+
...fetchOptions,
|
|
19
|
+
agent: torProcessManager.createAgent(proxyUrl)
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error(`Error fetching ${url} through proxy ${proxyUrl}:`, error);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
if (killTor) {
|
|
28
|
+
torProcessManager.stop();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAWA,oCAsBC;AAEwB,6BAAK;AAnC9B,4DAAkE;AAElE,2DAAwD;AAiCxB,kGAjCvB,qCAAiB,OAiCuB;AA1BjD,MAAM,iBAAiB,GAAG,IAAI,qCAAiB,EAAE,CAAC;AAE3C,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,OAA6B;IACzE,MAAM,EACF,OAAO,GAAG,IAAI,EACd,QAAQ,GAAG,0BAA0B,EACrC,GAAG,YAAY,EAClB,GAAG,OAAO,IAAI,EAAE,CAAC;IAElB,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC;IAEhC,IAAI,CAAC;QACD,OAAO,MAAM,IAAA,oBAAa,EAAC,GAAG,EAAE;YAC5B,GAAG,YAAY;YACf,KAAK,EAAE,iBAAiB,CAAC,WAAW,CAAC,QAAQ,CAAC;SACjD,CAAC,CAAC;IACP,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,kBAAkB,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC;QACzE,MAAM,KAAK,CAAC;IAChB,CAAC;YAAS,CAAC;QACP,IAAI,OAAO,EAAE,CAAC;YACV,iBAAiB,CAAC,IAAI,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/tests/test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// proxiedFetch("https://api.ipify.org?format=json")
|
|
3
|
+
// .then((response) => response.json() as Promise<{ ip: string }>)
|
|
4
|
+
// .then((data: { ip: string }) => {
|
|
5
|
+
// console.log("Your IP address is:", data.ip);
|
|
6
|
+
// })
|
|
7
|
+
// .catch((error) => {
|
|
8
|
+
// console.error("Error fetching IP address:", error);
|
|
9
|
+
// });
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const __1 = require("..");
|
|
12
|
+
async function func() {
|
|
13
|
+
const response = await (0, __1.proxiedFetch)("https://api.ipify.org?format=json", { killTor: false });
|
|
14
|
+
const data = await response.text();
|
|
15
|
+
console.log("Your IP address is:", data);
|
|
16
|
+
}
|
|
17
|
+
async function test() {
|
|
18
|
+
for (let i = 0; i < 5; i++) {
|
|
19
|
+
await func();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
test().catch((error) => {
|
|
23
|
+
console.error("Error in test:", error);
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/tests/test.ts"],"names":[],"mappings":";AACA,oDAAoD;AACpD,kEAAkE;AAClE,oCAAoC;AACpC,mDAAmD;AACnD,KAAK;AACL,sBAAsB;AACtB,0DAA0D;AAC1D,MAAM;;AAEN,0BAAkC;AAElC,KAAK,UAAU,IAAI;IACf,MAAM,QAAQ,GAAG,MAAM,IAAA,gBAAY,EAAC,mCAAmC,EAAE,EAAC,OAAO,EAAE,KAAK,EAAC,CAAC,CAAC;IAC3F,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,KAAK,UAAU,IAAI;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,EAAE,CAAC;IACjB,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fetch-tor-proxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Allows fetching resources through a proxy server, with support for authentication and custom headers.",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"clean": "npx rimraf -rf dist build"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^20.19.35",
|
|
12
|
+
"typescript": "^5.9.3"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"node-fetch": "^3.3.2",
|
|
16
|
+
"socks-proxy-agent": "^8.0.5"
|
|
17
|
+
},
|
|
18
|
+
"type": "module"
|
|
19
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ChildProcess, spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
4
|
+
|
|
5
|
+
export interface TorProcessManagerOptions {
|
|
6
|
+
torBinaryPath?: string;
|
|
7
|
+
torConfigPath?: string;
|
|
8
|
+
bootstrapTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class TorProcessManager {
|
|
12
|
+
private readonly torBinaryPath: string;
|
|
13
|
+
private readonly torConfigPath: string;
|
|
14
|
+
private readonly bootstrapTimeoutMs: number;
|
|
15
|
+
|
|
16
|
+
private torProcess: ChildProcess | null = null;
|
|
17
|
+
private isReady = false;
|
|
18
|
+
private startupPromise: Promise<void> | null = null;
|
|
19
|
+
private readonly isWindows = process.platform === 'win32';
|
|
20
|
+
|
|
21
|
+
constructor(options?: TorProcessManagerOptions) {
|
|
22
|
+
this.torBinaryPath = options?.torBinaryPath ?? path.join(__dirname, '..', 'tor-expert-bundle', 'tor');
|
|
23
|
+
this.torConfigPath = options?.torConfigPath ?? path.join(__dirname, '..', 'tor-expert-bundle', 'torrc');
|
|
24
|
+
this.bootstrapTimeoutMs = options?.bootstrapTimeoutMs ?? 30_000;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async start(): Promise<void> {
|
|
28
|
+
if (!this.isWindows) {
|
|
29
|
+
// On Linux/macOS, assume Tor is managed externally (service/container/etc.).
|
|
30
|
+
this.isReady = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (this.isReady) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (this.startupPromise) {
|
|
39
|
+
return this.startupPromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.startupPromise = new Promise<void>((resolve, reject) => {
|
|
43
|
+
const torProcess = spawn(this.torBinaryPath, ['-f', this.torConfigPath]);
|
|
44
|
+
this.torProcess = torProcess;
|
|
45
|
+
|
|
46
|
+
let settled = false;
|
|
47
|
+
let timeout: NodeJS.Timeout;
|
|
48
|
+
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
torProcess.stdout?.removeListener('data', onStdout);
|
|
52
|
+
torProcess.removeListener('error', onError);
|
|
53
|
+
torProcess.removeListener('close', onClose);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const settle = (fn: () => void) => {
|
|
57
|
+
if (settled) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
settled = true;
|
|
61
|
+
cleanup();
|
|
62
|
+
fn();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const onStdout = (data: Buffer) => {
|
|
66
|
+
if (data.toString().includes('Bootstrapped 100%')) {
|
|
67
|
+
this.isReady = true;
|
|
68
|
+
settle(() => resolve());
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const onError = (error: Error) => {
|
|
73
|
+
this.resetState();
|
|
74
|
+
settle(() => reject(error));
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const onClose = (code: number | null) => {
|
|
78
|
+
const exitedBeforeReady = !this.isReady;
|
|
79
|
+
this.resetState();
|
|
80
|
+
|
|
81
|
+
if (exitedBeforeReady) {
|
|
82
|
+
settle(() => reject(new Error(`Tor process exited before bootstrap completed (code: ${code ?? 'unknown'}).`)));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
timeout = setTimeout(() => {
|
|
87
|
+
this.stop();
|
|
88
|
+
settle(() => reject(new Error(`Tor bootstrap timed out after ${this.bootstrapTimeoutMs} ms.`)));
|
|
89
|
+
}, this.bootstrapTimeoutMs);
|
|
90
|
+
|
|
91
|
+
torProcess.stdout?.on('data', onStdout);
|
|
92
|
+
torProcess.on('error', onError);
|
|
93
|
+
torProcess.on('close', onClose);
|
|
94
|
+
}).finally(() => {
|
|
95
|
+
this.startupPromise = null;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return this.startupPromise;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
stop(): void {
|
|
102
|
+
if (!this.isWindows) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!this.torProcess) {
|
|
107
|
+
this.resetState();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!this.torProcess.killed) {
|
|
112
|
+
this.torProcess.kill();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.resetState();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
createAgent(proxyUrl: string): SocksProxyAgent {
|
|
119
|
+
return new SocksProxyAgent(proxyUrl);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private resetState(): void {
|
|
123
|
+
this.torProcess = null;
|
|
124
|
+
this.isReady = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import originalFetch, { RequestInit, Response } from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
import { TorProcessManager } from './TorProcessManager';
|
|
4
|
+
|
|
5
|
+
export interface ProxiedFetchOptions extends RequestInit {
|
|
6
|
+
killTor?: boolean;
|
|
7
|
+
proxyUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const torProcessManager = new TorProcessManager();
|
|
11
|
+
|
|
12
|
+
export async function proxiedFetch(url: string, options?: ProxiedFetchOptions): Promise<Response> {
|
|
13
|
+
const {
|
|
14
|
+
killTor = true,
|
|
15
|
+
proxyUrl = 'socks5h://127.0.0.1:9050',
|
|
16
|
+
...fetchOptions
|
|
17
|
+
} = options ?? {};
|
|
18
|
+
|
|
19
|
+
await torProcessManager.start();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return await originalFetch(url, {
|
|
23
|
+
...fetchOptions,
|
|
24
|
+
agent: torProcessManager.createAgent(proxyUrl)
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`Error fetching ${url} through proxy ${proxyUrl}:`, error);
|
|
28
|
+
throw error;
|
|
29
|
+
} finally {
|
|
30
|
+
if (killTor) {
|
|
31
|
+
torProcessManager.stop();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { proxiedFetch as fetch, TorProcessManager };
|
|
37
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
// proxiedFetch("https://api.ipify.org?format=json")
|
|
3
|
+
// .then((response) => response.json() as Promise<{ ip: string }>)
|
|
4
|
+
// .then((data: { ip: string }) => {
|
|
5
|
+
// console.log("Your IP address is:", data.ip);
|
|
6
|
+
// })
|
|
7
|
+
// .catch((error) => {
|
|
8
|
+
// console.error("Error fetching IP address:", error);
|
|
9
|
+
// });
|
|
10
|
+
|
|
11
|
+
import { proxiedFetch } from "..";
|
|
12
|
+
|
|
13
|
+
async function func() {
|
|
14
|
+
const response = await proxiedFetch("https://api.ipify.org?format=json", {killTor: false});
|
|
15
|
+
const data = await response.text();
|
|
16
|
+
console.log("Your IP address is:", data);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function test() {
|
|
20
|
+
for (let i = 0; i < 5; i++) {
|
|
21
|
+
await func();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test().catch((error) => {
|
|
26
|
+
console.error("Error in test:", error);
|
|
27
|
+
});
|