@doppelgangerdev/doppelganger 0.4.0 → 0.5.2
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/README.md +1 -1
- package/agent.js +446 -240
- package/dist/assets/index-BXRKDZ1_.css +1 -0
- package/dist/assets/index-Deb2QMGx.js +19 -0
- package/dist/captures/run_1769042882403_589_agent_1769042884446_initial.png +0 -0
- package/dist/captures/run_1769042882403_589_agent_1769042887058.png +0 -0
- package/dist/captures/run_1769042882403_589_agent_1769042888468.webm +0 -0
- package/dist/captures/run_1769043202318_943_agent_1769043206237.png +0 -0
- package/dist/captures/run_1769043202318_943_agent_1769043207415.webm +0 -0
- package/dist/captures/run_1769043449517_97_agent_1769043451350_initial.png +0 -0
- package/dist/captures/run_1769043449517_97_agent_1769043455038.png +0 -0
- package/dist/captures/run_1769043449517_97_agent_1769043456476.webm +0 -0
- package/dist/captures/run_1769043471164_239_agent_1769043472720_initial.png +0 -0
- package/dist/captures/run_1769043471164_239_agent_1769043474022.png +0 -0
- package/dist/captures/run_1769043471164_239_agent_1769043476419.png +0 -0
- package/dist/captures/run_1769043471164_239_agent_1769043477795.webm +0 -0
- package/dist/captures/run_1769080585290_151_agent_1769080595110.png +0 -0
- package/dist/captures/run_1769080585290_151_agent_1769080596335.webm +0 -0
- package/dist/index.html +3 -3
- package/dist/screenshots/agent_1769037343598.png +0 -0
- package/dist/screenshots/agent_1769037357541.png +0 -0
- package/dist/screenshots/scrape_1769037382254.png +0 -0
- package/dist/screenshots/scrape_1769037413189.png +0 -0
- package/dist/screenshots/scrape_1769037449707.png +0 -0
- package/dist/screenshots/scrape_1769037461756.png +0 -0
- package/dist/screenshots/scrape_1769037490581.png +0 -0
- package/dist/screenshots/scrape_1769038242368.png +0 -0
- package/headful.js +97 -42
- package/package.json +3 -1
- package/proxy-rotation.js +212 -154
- package/public/captures/run_1769042882403_589_agent_1769042884446_initial.png +0 -0
- package/public/captures/run_1769042882403_589_agent_1769042887058.png +0 -0
- package/public/captures/run_1769042882403_589_agent_1769042888468.webm +0 -0
- package/public/captures/run_1769043202318_943_agent_1769043206237.png +0 -0
- package/public/captures/run_1769043202318_943_agent_1769043207415.webm +0 -0
- package/public/captures/run_1769043449517_97_agent_1769043451350_initial.png +0 -0
- package/public/captures/run_1769043449517_97_agent_1769043455038.png +0 -0
- package/public/captures/run_1769043449517_97_agent_1769043456476.webm +0 -0
- package/public/captures/run_1769043471164_239_agent_1769043472720_initial.png +0 -0
- package/public/captures/run_1769043471164_239_agent_1769043474022.png +0 -0
- package/public/captures/run_1769043471164_239_agent_1769043476419.png +0 -0
- package/public/captures/run_1769043471164_239_agent_1769043477795.webm +0 -0
- package/public/captures/run_1769080585290_151_agent_1769080595110.png +0 -0
- package/public/captures/run_1769080585290_151_agent_1769080596335.webm +0 -0
- package/public/screenshots/agent_1769037343598.png +0 -0
- package/public/screenshots/agent_1769037357541.png +0 -0
- package/public/screenshots/scrape_1769037382254.png +0 -0
- package/public/screenshots/scrape_1769037413189.png +0 -0
- package/public/screenshots/scrape_1769037449707.png +0 -0
- package/public/screenshots/scrape_1769037461756.png +0 -0
- package/public/screenshots/scrape_1769037490581.png +0 -0
- package/public/screenshots/scrape_1769038242368.png +0 -0
- package/scrape.js +170 -73
- package/server.js +437 -335
- package/dist/assets/index-B7ntJ0pF.js +0 -18
- package/dist/assets/index-OOTrc7_f.css +0 -1
- package/dist/screenshots/agent_1768764625684.png +0 -0
- package/dist/screenshots/agent_1768765565909.png +0 -0
- package/dist/screenshots/agent_1768765581979.png +0 -0
- package/dist/screenshots/scrape_1768764416615.png +0 -0
- package/dist/screenshots/scrape_1768764452033.png +0 -0
- package/dist/screenshots/scrape_1768765064688.png +0 -0
- package/dist/screenshots/scrape_1768765090587.png +0 -0
- package/dist/screenshots/scrape_1768765100798.png +0 -0
- package/dist/screenshots/scrape_1768765114782.png +0 -0
- package/dist/screenshots/scrape_1768765127463.png +0 -0
- package/public/screenshots/agent_1768764625684.png +0 -0
- package/public/screenshots/agent_1768765565909.png +0 -0
- package/public/screenshots/agent_1768765581979.png +0 -0
- package/public/screenshots/scrape_1768764416615.png +0 -0
- package/public/screenshots/scrape_1768764452033.png +0 -0
- package/public/screenshots/scrape_1768765064688.png +0 -0
- package/public/screenshots/scrape_1768765090587.png +0 -0
- package/public/screenshots/scrape_1768765100798.png +0 -0
- package/public/screenshots/scrape_1768765114782.png +0 -0
- package/public/screenshots/scrape_1768765127463.png +0 -0
- package/public/screenshots/scrape_1768769463201.png +0 -0
package/proxy-rotation.js
CHANGED
|
@@ -1,74 +1,81 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
|
|
5
|
-
const DATA_PROXY_FILE = path.join(__dirname, 'data', 'proxies.json');
|
|
6
|
-
const PROXY_FILES = [
|
|
7
|
-
DATA_PROXY_FILE,
|
|
8
|
-
path.join(__dirname, 'proxies.json')
|
|
9
|
-
];
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const DATA_PROXY_FILE = path.join(__dirname, 'data', 'proxies.json');
|
|
6
|
+
const PROXY_FILES = [
|
|
7
|
+
DATA_PROXY_FILE,
|
|
8
|
+
path.join(__dirname, 'proxies.json')
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const ROTATION_MODES = new Set(['round-robin', 'random']);
|
|
10
12
|
|
|
11
13
|
let cached = {
|
|
12
14
|
file: null,
|
|
13
15
|
mtimeMs: 0,
|
|
14
|
-
config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false }
|
|
16
|
+
config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' }
|
|
15
17
|
};
|
|
16
18
|
let rotationIndex = 0;
|
|
17
|
-
|
|
18
|
-
const normalizeServer = (raw) => {
|
|
19
|
-
if (!raw) return '';
|
|
20
|
-
let server = String(raw).trim();
|
|
21
|
-
if (!server) return '';
|
|
22
|
-
if (!server.includes('://')) {
|
|
23
|
-
server = `http://${server}`;
|
|
24
|
-
}
|
|
25
|
-
return server;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const createProxyId = (seed) => {
|
|
29
|
-
const hash = crypto.createHash('sha1').update(String(seed)).digest('hex').slice(0, 12);
|
|
30
|
-
return `proxy_${hash}`;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const normalizeProxy = (entry) => {
|
|
34
|
-
if (!entry) return null;
|
|
35
|
-
if (typeof entry === 'string') {
|
|
36
|
-
let raw = entry.trim();
|
|
37
|
-
if (!raw) return null;
|
|
38
|
-
if (!raw.includes('://')) {
|
|
39
|
-
raw = `http://${raw}`;
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const parsed = new URL(raw);
|
|
43
|
-
const server = `${parsed.protocol}//${parsed.host}`;
|
|
44
|
-
const username = parsed.username ? decodeURIComponent(parsed.username) : undefined;
|
|
45
|
-
const password = parsed.password ? decodeURIComponent(parsed.password) : undefined;
|
|
46
|
-
return {
|
|
47
|
-
id: createProxyId(`${server}|${username || ''}|${password || ''}`),
|
|
48
|
-
server,
|
|
49
|
-
username,
|
|
50
|
-
password
|
|
51
|
-
};
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (typeof entry === 'object') {
|
|
57
|
-
const serverRaw = entry.server || entry.url || entry.proxy;
|
|
58
|
-
const server = normalizeServer(serverRaw);
|
|
59
|
-
if (!server) return null;
|
|
60
|
-
const username = entry.username || entry.user;
|
|
61
|
-
const password = entry.password || entry.pass;
|
|
62
|
-
const id = entry.id || createProxyId(`${server}|${username || ''}|${password || ''}`);
|
|
63
|
-
return {
|
|
64
|
-
id,
|
|
65
|
-
server,
|
|
66
|
-
username,
|
|
67
|
-
password,
|
|
68
|
-
label: entry.label
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
19
|
+
|
|
20
|
+
const normalizeServer = (raw) => {
|
|
21
|
+
if (!raw) return '';
|
|
22
|
+
let server = String(raw).trim();
|
|
23
|
+
if (!server) return '';
|
|
24
|
+
if (!server.includes('://')) {
|
|
25
|
+
server = `http://${server}`;
|
|
26
|
+
}
|
|
27
|
+
return server;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const createProxyId = (seed) => {
|
|
31
|
+
const hash = crypto.createHash('sha1').update(String(seed)).digest('hex').slice(0, 12);
|
|
32
|
+
return `proxy_${hash}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const normalizeProxy = (entry) => {
|
|
36
|
+
if (!entry) return null;
|
|
37
|
+
if (typeof entry === 'string') {
|
|
38
|
+
let raw = entry.trim();
|
|
39
|
+
if (!raw) return null;
|
|
40
|
+
if (!raw.includes('://')) {
|
|
41
|
+
raw = `http://${raw}`;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const parsed = new URL(raw);
|
|
45
|
+
const server = `${parsed.protocol}//${parsed.host}`;
|
|
46
|
+
const username = parsed.username ? decodeURIComponent(parsed.username) : undefined;
|
|
47
|
+
const password = parsed.password ? decodeURIComponent(parsed.password) : undefined;
|
|
48
|
+
return {
|
|
49
|
+
id: createProxyId(`${server}|${username || ''}|${password || ''}`),
|
|
50
|
+
server,
|
|
51
|
+
username,
|
|
52
|
+
password
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (typeof entry === 'object') {
|
|
59
|
+
const serverRaw = entry.server || entry.url || entry.proxy;
|
|
60
|
+
const server = normalizeServer(serverRaw);
|
|
61
|
+
if (!server) return null;
|
|
62
|
+
const username = entry.username || entry.user;
|
|
63
|
+
const password = entry.password || entry.pass;
|
|
64
|
+
const id = entry.id || createProxyId(`${server}|${username || ''}|${password || ''}`);
|
|
65
|
+
return {
|
|
66
|
+
id,
|
|
67
|
+
server,
|
|
68
|
+
username,
|
|
69
|
+
password,
|
|
70
|
+
label: entry.label
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const normalizeRotationMode = (mode) => {
|
|
77
|
+
if (ROTATION_MODES.has(mode)) return mode;
|
|
78
|
+
return 'round-robin';
|
|
72
79
|
};
|
|
73
80
|
|
|
74
81
|
const loadProxyFile = (filePath) => {
|
|
@@ -76,51 +83,53 @@ const loadProxyFile = (filePath) => {
|
|
|
76
83
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
77
84
|
const parsed = JSON.parse(raw);
|
|
78
85
|
if (Array.isArray(parsed)) {
|
|
79
|
-
return { proxies: parsed, defaultProxyId: null, includeDefaultInRotation: false };
|
|
86
|
+
return { proxies: parsed, defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
|
|
80
87
|
}
|
|
81
88
|
const proxies = Array.isArray(parsed.proxies) ? parsed.proxies : [];
|
|
82
89
|
const defaultProxyId = parsed.defaultProxyId || null;
|
|
83
90
|
const includeDefaultInRotation = !!parsed.includeDefaultInRotation;
|
|
84
|
-
|
|
91
|
+
const rotationMode = normalizeRotationMode(parsed.rotationMode);
|
|
92
|
+
return { proxies, defaultProxyId, includeDefaultInRotation, rotationMode };
|
|
85
93
|
} catch {
|
|
86
|
-
return { proxies: [], defaultProxyId: null, includeDefaultInRotation: false };
|
|
94
|
+
return { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' };
|
|
87
95
|
}
|
|
88
96
|
};
|
|
89
|
-
|
|
90
|
-
const loadProxyConfig = () => {
|
|
91
|
-
const filePath = PROXY_FILES.find((candidate) => {
|
|
92
|
-
try {
|
|
93
|
-
return fs.existsSync(candidate);
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (!filePath) {
|
|
100
|
-
cached = { file: null, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false } };
|
|
97
|
+
|
|
98
|
+
const loadProxyConfig = () => {
|
|
99
|
+
const filePath = PROXY_FILES.find((candidate) => {
|
|
100
|
+
try {
|
|
101
|
+
return fs.existsSync(candidate);
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (!filePath) {
|
|
108
|
+
cached = { file: null, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
|
|
101
109
|
return cached.config;
|
|
102
110
|
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const stat = fs.statSync(filePath);
|
|
106
|
-
const mtimeMs = stat.mtimeMs || 0;
|
|
107
|
-
if (cached.file === filePath && cached.mtimeMs === mtimeMs) {
|
|
108
|
-
return cached.config;
|
|
109
|
-
}
|
|
110
|
-
const rawConfig = loadProxyFile(filePath);
|
|
111
|
-
const proxies = rawConfig.proxies.map(normalizeProxy).filter(Boolean);
|
|
112
|
-
const defaultProxyId = rawConfig.defaultProxyId && proxies.some((proxy) => proxy.id === rawConfig.defaultProxyId)
|
|
113
|
-
? rawConfig.defaultProxyId
|
|
114
|
-
: null;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const stat = fs.statSync(filePath);
|
|
114
|
+
const mtimeMs = stat.mtimeMs || 0;
|
|
115
|
+
if (cached.file === filePath && cached.mtimeMs === mtimeMs) {
|
|
116
|
+
return cached.config;
|
|
117
|
+
}
|
|
118
|
+
const rawConfig = loadProxyFile(filePath);
|
|
119
|
+
const proxies = rawConfig.proxies.map(normalizeProxy).filter(Boolean);
|
|
120
|
+
const defaultProxyId = rawConfig.defaultProxyId && proxies.some((proxy) => proxy.id === rawConfig.defaultProxyId)
|
|
121
|
+
? rawConfig.defaultProxyId
|
|
122
|
+
: null;
|
|
115
123
|
const config = {
|
|
116
124
|
proxies,
|
|
117
125
|
defaultProxyId,
|
|
118
|
-
includeDefaultInRotation: !!rawConfig.includeDefaultInRotation
|
|
126
|
+
includeDefaultInRotation: !!rawConfig.includeDefaultInRotation,
|
|
127
|
+
rotationMode: normalizeRotationMode(rawConfig.rotationMode)
|
|
119
128
|
};
|
|
120
129
|
cached = { file: filePath, mtimeMs, config };
|
|
121
130
|
return config;
|
|
122
131
|
} catch {
|
|
123
|
-
cached = { file: filePath, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false } };
|
|
132
|
+
cached = { file: filePath, mtimeMs: 0, config: { proxies: [], defaultProxyId: null, includeDefaultInRotation: false, rotationMode: 'round-robin' } };
|
|
124
133
|
return cached.config;
|
|
125
134
|
}
|
|
126
135
|
};
|
|
@@ -130,32 +139,34 @@ const saveProxyConfig = (config) => {
|
|
|
130
139
|
const payload = {
|
|
131
140
|
defaultProxyId: config.defaultProxyId || null,
|
|
132
141
|
proxies: Array.isArray(config.proxies) ? config.proxies : [],
|
|
133
|
-
includeDefaultInRotation: !!config.includeDefaultInRotation
|
|
134
|
-
|
|
135
|
-
fs.writeFileSync(target, JSON.stringify(payload, null, 2));
|
|
136
|
-
try {
|
|
137
|
-
const stat = fs.statSync(target);
|
|
138
|
-
cached = { file: target, mtimeMs: stat.mtimeMs || 0, config: payload };
|
|
139
|
-
} catch {
|
|
140
|
-
cached = { file: target, mtimeMs: 0, config: payload };
|
|
141
|
-
}
|
|
142
|
-
return payload;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const listProxies = () => {
|
|
146
|
-
const config = loadProxyConfig();
|
|
147
|
-
const hostEntry = {
|
|
148
|
-
id: 'host',
|
|
149
|
-
server: 'host_ip',
|
|
150
|
-
label: 'Host IP (no proxy)'
|
|
142
|
+
includeDefaultInRotation: !!config.includeDefaultInRotation,
|
|
143
|
+
rotationMode: normalizeRotationMode(config.rotationMode)
|
|
151
144
|
};
|
|
145
|
+
fs.writeFileSync(target, JSON.stringify(payload, null, 2));
|
|
146
|
+
try {
|
|
147
|
+
const stat = fs.statSync(target);
|
|
148
|
+
cached = { file: target, mtimeMs: stat.mtimeMs || 0, config: payload };
|
|
149
|
+
} catch {
|
|
150
|
+
cached = { file: target, mtimeMs: 0, config: payload };
|
|
151
|
+
}
|
|
152
|
+
return payload;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const listProxies = () => {
|
|
156
|
+
const config = loadProxyConfig();
|
|
157
|
+
const hostEntry = {
|
|
158
|
+
id: 'host',
|
|
159
|
+
server: 'host_ip',
|
|
160
|
+
label: 'Host IP (no proxy)'
|
|
161
|
+
};
|
|
152
162
|
return {
|
|
153
163
|
proxies: [hostEntry, ...(config.proxies || [])],
|
|
154
164
|
defaultProxyId: config.defaultProxyId || 'host',
|
|
155
|
-
includeDefaultInRotation: !!config.includeDefaultInRotation
|
|
165
|
+
includeDefaultInRotation: !!config.includeDefaultInRotation,
|
|
166
|
+
rotationMode: normalizeRotationMode(config.rotationMode)
|
|
156
167
|
};
|
|
157
168
|
};
|
|
158
|
-
|
|
169
|
+
|
|
159
170
|
const addProxy = (entry) => {
|
|
160
171
|
const normalized = normalizeProxy(entry);
|
|
161
172
|
if (!normalized) return null;
|
|
@@ -165,86 +176,133 @@ const addProxy = (entry) => {
|
|
|
165
176
|
return saveProxyConfig(next);
|
|
166
177
|
};
|
|
167
178
|
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
179
|
+
const addProxies = (entries) => {
|
|
180
|
+
if (!Array.isArray(entries)) return null;
|
|
181
|
+
const normalizedEntries = entries.map(normalizeProxy).filter(Boolean);
|
|
182
|
+
if (normalizedEntries.length === 0) return null;
|
|
172
183
|
const config = loadProxyConfig();
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
const existingByServer = new Map(
|
|
185
|
+
config.proxies.map((proxy) => [String(proxy.server || '').toLowerCase(), proxy])
|
|
186
|
+
);
|
|
187
|
+
const seenServers = new Set();
|
|
188
|
+
const updates = [];
|
|
189
|
+
const additions = [];
|
|
190
|
+
|
|
191
|
+
normalizedEntries.forEach((proxy) => {
|
|
192
|
+
const serverKey = String(proxy.server || '').toLowerCase();
|
|
193
|
+
if (!serverKey || seenServers.has(serverKey)) return;
|
|
194
|
+
seenServers.add(serverKey);
|
|
195
|
+
const existing = existingByServer.get(serverKey);
|
|
196
|
+
if (existing) {
|
|
197
|
+
updates.push({ ...existing, ...proxy, id: existing.id });
|
|
198
|
+
} else {
|
|
199
|
+
additions.push({ ...proxy, id: `proxy_${crypto.randomBytes(6).toString('hex')}` });
|
|
200
|
+
}
|
|
176
201
|
});
|
|
177
|
-
if (!proxies.some((proxy) => proxy.id === id)) return null;
|
|
178
|
-
return saveProxyConfig({ ...config, proxies });
|
|
179
|
-
};
|
|
180
202
|
|
|
203
|
+
const merged = config.proxies.map((proxy) => {
|
|
204
|
+
const serverKey = String(proxy.server || '').toLowerCase();
|
|
205
|
+
const replacement = updates.find((item) => String(item.server || '').toLowerCase() === serverKey);
|
|
206
|
+
return replacement || proxy;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const proxies = [...merged, ...additions];
|
|
210
|
+
const next = { ...config, proxies };
|
|
211
|
+
return saveProxyConfig(next);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const updateProxy = (id, entry) => {
|
|
215
|
+
if (!id) return null;
|
|
216
|
+
const normalized = normalizeProxy(entry);
|
|
217
|
+
if (!normalized) return null;
|
|
218
|
+
const config = loadProxyConfig();
|
|
219
|
+
const proxies = config.proxies.map((proxy) => {
|
|
220
|
+
if (proxy.id !== id) return proxy;
|
|
221
|
+
return { ...proxy, ...normalized, id };
|
|
222
|
+
});
|
|
223
|
+
if (!proxies.some((proxy) => proxy.id === id)) return null;
|
|
224
|
+
return saveProxyConfig({ ...config, proxies });
|
|
225
|
+
};
|
|
226
|
+
|
|
181
227
|
const deleteProxy = (id) => {
|
|
182
228
|
if (!id) return null;
|
|
183
229
|
const config = loadProxyConfig();
|
|
184
230
|
const proxies = config.proxies.filter((proxy) => proxy.id !== id);
|
|
185
231
|
const defaultProxyId = config.defaultProxyId === id ? null : config.defaultProxyId;
|
|
186
|
-
return saveProxyConfig({ proxies, defaultProxyId });
|
|
232
|
+
return saveProxyConfig({ ...config, proxies, defaultProxyId });
|
|
187
233
|
};
|
|
188
|
-
|
|
189
|
-
const setDefaultProxy = (id) => {
|
|
234
|
+
|
|
235
|
+
const setDefaultProxy = (id) => {
|
|
236
|
+
const config = loadProxyConfig();
|
|
237
|
+
if (!id) {
|
|
238
|
+
return saveProxyConfig({ ...config, defaultProxyId: null });
|
|
239
|
+
}
|
|
240
|
+
if (!config.proxies.some((proxy) => proxy.id === id)) return null;
|
|
241
|
+
return saveProxyConfig({ ...config, defaultProxyId: id });
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const setIncludeDefaultInRotation = (enabled) => {
|
|
190
245
|
const config = loadProxyConfig();
|
|
191
|
-
|
|
192
|
-
return saveProxyConfig({ ...config, defaultProxyId: null });
|
|
193
|
-
}
|
|
194
|
-
if (!config.proxies.some((proxy) => proxy.id === id)) return null;
|
|
195
|
-
return saveProxyConfig({ ...config, defaultProxyId: id });
|
|
246
|
+
return saveProxyConfig({ ...config, includeDefaultInRotation: !!enabled });
|
|
196
247
|
};
|
|
197
248
|
|
|
198
|
-
const
|
|
249
|
+
const setRotationMode = (mode) => {
|
|
199
250
|
const config = loadProxyConfig();
|
|
200
|
-
return saveProxyConfig({ ...config,
|
|
251
|
+
return saveProxyConfig({ ...config, rotationMode: normalizeRotationMode(mode) });
|
|
201
252
|
};
|
|
202
253
|
|
|
203
|
-
const getNextProxy = (proxies) => {
|
|
254
|
+
const getNextProxy = (proxies, mode) => {
|
|
204
255
|
if (!proxies.length) return null;
|
|
256
|
+
if (mode === 'random') {
|
|
257
|
+
const index = Math.floor(Math.random() * proxies.length);
|
|
258
|
+
return proxies[index];
|
|
259
|
+
}
|
|
205
260
|
const selected = proxies[rotationIndex % proxies.length];
|
|
206
261
|
rotationIndex += 1;
|
|
207
262
|
return selected;
|
|
208
263
|
};
|
|
209
|
-
|
|
210
|
-
const getProxySelection = (rotateProxies) => {
|
|
211
|
-
const config = loadProxyConfig();
|
|
212
|
-
const proxies = config.proxies || [];
|
|
213
|
-
const hostEntry = { id: 'host', server: 'host_ip', label: 'Host IP (no proxy)' };
|
|
214
|
-
const pool = [hostEntry, ...proxies];
|
|
215
|
-
const defaultProxy = config.defaultProxyId
|
|
216
|
-
? proxies.find((proxy) => proxy.id === config.defaultProxyId) || null
|
|
217
|
-
: null;
|
|
264
|
+
|
|
265
|
+
const getProxySelection = (rotateProxies) => {
|
|
266
|
+
const config = loadProxyConfig();
|
|
267
|
+
const proxies = config.proxies || [];
|
|
268
|
+
const hostEntry = { id: 'host', server: 'host_ip', label: 'Host IP (no proxy)' };
|
|
269
|
+
const pool = [hostEntry, ...proxies];
|
|
270
|
+
const defaultProxy = config.defaultProxyId
|
|
271
|
+
? proxies.find((proxy) => proxy.id === config.defaultProxyId) || null
|
|
272
|
+
: null;
|
|
218
273
|
const defaultIsHost = !config.defaultProxyId;
|
|
219
274
|
const includeDefaultInRotation = !!config.includeDefaultInRotation;
|
|
275
|
+
const rotationMode = normalizeRotationMode(config.rotationMode);
|
|
220
276
|
|
|
221
277
|
if (rotateProxies) {
|
|
222
278
|
let rotationPool = pool;
|
|
223
279
|
if (!includeDefaultInRotation) {
|
|
224
280
|
if (defaultIsHost) {
|
|
225
|
-
rotationPool = pool.filter((proxy) => proxy.id !== 'host');
|
|
226
|
-
} else {
|
|
281
|
+
rotationPool = pool.filter((proxy) => proxy.id !== 'host');
|
|
282
|
+
} else {
|
|
227
283
|
rotationPool = pool.filter((proxy) => proxy.id !== config.defaultProxyId);
|
|
228
284
|
}
|
|
229
285
|
}
|
|
230
286
|
if (rotationPool.length > 0) {
|
|
231
|
-
const picked = getNextProxy(rotationPool);
|
|
287
|
+
const picked = getNextProxy(rotationPool, rotationMode);
|
|
232
288
|
return { proxy: picked && picked.id !== 'host' ? picked : null, mode: 'rotate' };
|
|
233
289
|
}
|
|
234
290
|
if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
|
|
235
291
|
return { proxy: null, mode: 'host' };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
|
|
239
|
-
return { proxy: null, mode: 'host' };
|
|
240
|
-
};
|
|
241
|
-
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (defaultProxy) return { proxy: defaultProxy, mode: 'default' };
|
|
295
|
+
return { proxy: null, mode: 'host' };
|
|
296
|
+
};
|
|
297
|
+
|
|
242
298
|
module.exports = {
|
|
243
299
|
getProxySelection,
|
|
244
300
|
listProxies,
|
|
245
301
|
addProxy,
|
|
302
|
+
addProxies,
|
|
246
303
|
updateProxy,
|
|
247
304
|
deleteProxy,
|
|
248
305
|
setDefaultProxy,
|
|
249
|
-
setIncludeDefaultInRotation
|
|
306
|
+
setIncludeDefaultInRotation,
|
|
307
|
+
setRotationMode
|
|
250
308
|
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|