melperjs 16.1.0 → 17.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/docs/index.md +50 -2
- package/docs/node.md +0 -51
- package/lib/cjs/index.cjs +124 -2
- package/lib/cjs/node.cjs +0 -103
- package/lib/esm/index.mjs +121 -2
- package/lib/esm/node.mjs +1 -101
- package/package.json +1 -1
package/docs/index.md
CHANGED
|
@@ -87,7 +87,7 @@ Calls `task` up to `maxAttempts` times, returning the first successful result. O
|
|
|
87
87
|
- **Parameters:**
|
|
88
88
|
- `task` (Function): Async function to attempt.
|
|
89
89
|
- `maxAttempts` (Number): Total attempt count (1 = no retries). Default `1`.
|
|
90
|
-
- `onError` (Function): Called as `(
|
|
90
|
+
- `onError` (Function): Called as `(error, attempt)` after each failed attempt.
|
|
91
91
|
- `options.delayMs` (Number): Base delay between retries in ms. `0` disables delay.
|
|
92
92
|
- `options.backoffFactor` (Number): Multiplier applied per attempt. `1` keeps delay constant; `2` doubles each retry.
|
|
93
93
|
- **Returns:** The first non-throwing result of `task`.
|
|
@@ -288,7 +288,7 @@ Depth-first search through a nested object for the first node that owns `key`. I
|
|
|
288
288
|
- `pair` (Any): Optional value constraint. `null` means "match any value".
|
|
289
289
|
- **Returns:** The matching node, or `null` if not found.
|
|
290
290
|
|
|
291
|
-
### waitForProperty(object, property, timeout
|
|
291
|
+
### waitForProperty(object, property, timeout, interval = 100)
|
|
292
292
|
|
|
293
293
|
Polls `object` until it owns `property`, then resolves with the property's value. Rejects after `timeout` milliseconds.
|
|
294
294
|
|
|
@@ -361,3 +361,51 @@ Extracts a short error description from an HTTP error-like object. Prefers `erro
|
|
|
361
361
|
- `error` (Error): Error from an HTTP client.
|
|
362
362
|
- `limit` (Number): Maximum length of the returned string.
|
|
363
363
|
- **Returns:** Trimmed error description.
|
|
364
|
+
|
|
365
|
+
## Proxy Helpers
|
|
366
|
+
|
|
367
|
+
### normalizeProxy(proxy, protocol = "http")
|
|
368
|
+
|
|
369
|
+
Normalizes a wide range of proxy formats into a canonical `protocol://[user:pass@]host:port` URL. Supports:
|
|
370
|
+
|
|
371
|
+
- `host:port`
|
|
372
|
+
- `host:port:user:pass` (auth appended)
|
|
373
|
+
- `user:pass:host:port` (auth prepended; auto-detected via numeric port pattern)
|
|
374
|
+
- `host:portStart:portEnd:user:pass` (random port in range, inclusive)
|
|
375
|
+
- `user:pass:host:portStart:portEnd` (auth prepended, random port in range)
|
|
376
|
+
- `user:pass@host:port`
|
|
377
|
+
- `user:pass@host:portStart:portEnd` (random port in range)
|
|
378
|
+
- Any of the above prefixed with `scheme://` (`http`, `https`, `socks5`, `socks5h`, …)
|
|
379
|
+
|
|
380
|
+
Returns `null` for empty or non-string input. Does not crash on unparseable input — returns it as-is wrapped with `protocol://`.
|
|
381
|
+
|
|
382
|
+
- **Parameters:**
|
|
383
|
+
- `proxy` (String): Source proxy string.
|
|
384
|
+
- `protocol` (String): Default protocol when none is present in the input.
|
|
385
|
+
- **Returns:** Canonical proxy URL, or `null` when input is empty/missing.
|
|
386
|
+
|
|
387
|
+
### parseProxy(proxy, protocol = "http")
|
|
388
|
+
|
|
389
|
+
Normalizes `proxy` via `normalizeProxy`, then decomposes it into structured fields. Returns `null` when normalization fails (empty input).
|
|
390
|
+
|
|
391
|
+
- **Parameters:**
|
|
392
|
+
- `proxy` (String): Source proxy string.
|
|
393
|
+
- `protocol` (String): Default protocol when none is present in the input.
|
|
394
|
+
- **Returns:** `{protocol, host, port, auth?: {username, password}}` or `null`.
|
|
395
|
+
|
|
396
|
+
### proxyValue(rawProxy, replacements = {})
|
|
397
|
+
|
|
398
|
+
Picks a random proxy from a newline-separated list, normalizes it, and applies placeholder substitution.
|
|
399
|
+
|
|
400
|
+
`{SESSION}` is a built-in placeholder:
|
|
401
|
+
|
|
402
|
+
- If `SESSION` is not provided, it is autofilled with a non-secure `randomHex(8)`.
|
|
403
|
+
- If `SESSION` is a string, it is treated as a seed and replaced via `seedHex(seed, 8)` (deterministic).
|
|
404
|
+
- If `SESSION` is a function, the function is called per invocation.
|
|
405
|
+
|
|
406
|
+
Any other key in `replacements` is also substituted (`{KEY}` → value). For non-SESSION entries: functions are called, strings are used literally.
|
|
407
|
+
|
|
408
|
+
- **Parameters:**
|
|
409
|
+
- `rawProxy` (String): Newline-separated proxy list.
|
|
410
|
+
- `replacements` (Object): Placeholder values keyed by placeholder name.
|
|
411
|
+
- **Returns:** Final proxy URL string, or `null` if the list is empty.
|
package/docs/node.md
CHANGED
|
@@ -145,57 +145,6 @@ Verifies that `plainText` (with the same `key`) matches a previously generated b
|
|
|
145
145
|
- `options.preHash` (Boolean): Must match the `preHash` used for `bcryptHash`. Default `true`.
|
|
146
146
|
- **Returns:** `Boolean` indicating match.
|
|
147
147
|
|
|
148
|
-
## Proxy Helpers
|
|
149
|
-
|
|
150
|
-
### normalizeProxy(proxy, protocol = "http")
|
|
151
|
-
|
|
152
|
-
Normalizes a wide range of proxy formats into a canonical `protocol://[user:pass@]host:port` URL. Supports:
|
|
153
|
-
|
|
154
|
-
- `host:port`
|
|
155
|
-
- `host:port:user:pass` (auth appended)
|
|
156
|
-
- `user:pass:host:port` (auth prepended; auto-detected via numeric port pattern)
|
|
157
|
-
- `host:portStart:portEnd:user:pass` (random port in range, inclusive)
|
|
158
|
-
- `user:pass:host:portStart:portEnd` (auth prepended, random port in range)
|
|
159
|
-
- `user:pass@host:port`
|
|
160
|
-
- `user:pass@host:portStart:portEnd` (random port in range)
|
|
161
|
-
- Any of the above prefixed with `scheme://` (`http`, `https`, `socks5`, `socks5h`, …)
|
|
162
|
-
|
|
163
|
-
Returns `null` for empty or non-string input. Does not crash on unparseable input — returns it as-is wrapped with
|
|
164
|
-
`protocol://`.
|
|
165
|
-
|
|
166
|
-
- **Parameters:**
|
|
167
|
-
- `proxy` (String): Source proxy string.
|
|
168
|
-
- `protocol` (String): Default protocol when none is present in the input.
|
|
169
|
-
- **Returns:** Canonical proxy URL, or `null` when input is empty/missing.
|
|
170
|
-
|
|
171
|
-
### parseProxy(proxy, protocol = "http")
|
|
172
|
-
|
|
173
|
-
Normalizes `proxy` via `normalizeProxy`, then decomposes it into structured fields. Returns `null` when normalization
|
|
174
|
-
fails (empty input).
|
|
175
|
-
|
|
176
|
-
- **Parameters:**
|
|
177
|
-
- `proxy` (String): Source proxy string.
|
|
178
|
-
- `protocol` (String): Default protocol when none is present in the input.
|
|
179
|
-
- **Returns:** `{protocol, host, port, auth?: {username, password}}` or `null`.
|
|
180
|
-
|
|
181
|
-
### proxyValue(rawProxy, replacements = {})
|
|
182
|
-
|
|
183
|
-
Picks a random proxy from a newline-separated list, normalizes it, and applies placeholder substitution.
|
|
184
|
-
|
|
185
|
-
`{SESSION}` is a built-in placeholder:
|
|
186
|
-
|
|
187
|
-
- If `SESSION` is not provided, it is autofilled with a non-secure `randomHex(8)`.
|
|
188
|
-
- If `SESSION` is a string, it is treated as a seed and replaced via `seedHex(seed, 8)` (deterministic).
|
|
189
|
-
- If `SESSION` is a function, the function is called per invocation.
|
|
190
|
-
|
|
191
|
-
Any other key in `replacements` is also substituted (`{KEY}` → value). For non-SESSION entries: functions are called,
|
|
192
|
-
strings are used literally.
|
|
193
|
-
|
|
194
|
-
- **Parameters:**
|
|
195
|
-
- `rawProxy` (String): Newline-separated proxy list.
|
|
196
|
-
- `replacements` (Object): Placeholder values keyed by placeholder name.
|
|
197
|
-
- **Returns:** Final proxy URL string, or `null` if the list is empty.
|
|
198
|
-
|
|
199
148
|
## File I/O (JSON)
|
|
200
149
|
|
|
201
150
|
### readJsonFile(filePath, defaultValue = {})
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -20,10 +20,13 @@ exports.isTransientHttpCode = isTransientHttpCode;
|
|
|
20
20
|
exports.isValidURL = isValidURL;
|
|
21
21
|
exports.limitString = limitString;
|
|
22
22
|
exports.mulberry32 = mulberry32;
|
|
23
|
+
exports.normalizeProxy = normalizeProxy;
|
|
23
24
|
exports.objectStringify = objectStringify;
|
|
25
|
+
exports.parseProxy = parseProxy;
|
|
24
26
|
exports.pascalCase = pascalCase;
|
|
25
27
|
exports.promiseSilent = promiseSilent;
|
|
26
28
|
exports.promiseTimeout = promiseTimeout;
|
|
29
|
+
exports.proxyValue = proxyValue;
|
|
27
30
|
exports.randomBoolean = randomBoolean;
|
|
28
31
|
exports.randomElement = randomElement;
|
|
29
32
|
exports.randomHex = randomHex;
|
|
@@ -115,7 +118,7 @@ async function retry(task, maxAttempts = 1, onError = null, {
|
|
|
115
118
|
try {
|
|
116
119
|
return await task();
|
|
117
120
|
} catch (error) {
|
|
118
|
-
if (onError) await onError(
|
|
121
|
+
if (onError) await onError(error, attempt);
|
|
119
122
|
if (attempt >= maxAttempts) throw error;
|
|
120
123
|
if (delayMs > 0) await sleepMs(delayMs * backoffFactor ** (attempt - 1));
|
|
121
124
|
}
|
|
@@ -178,7 +181,7 @@ function findNodeByKey(key, node, pair = null) {
|
|
|
178
181
|
}
|
|
179
182
|
return null;
|
|
180
183
|
}
|
|
181
|
-
function waitForProperty(object, property, timeout
|
|
184
|
+
function waitForProperty(object, property, timeout, interval = 100) {
|
|
182
185
|
return new Promise((resolve, reject) => {
|
|
183
186
|
if (Object.hasOwn(object, property)) {
|
|
184
187
|
resolve(object[property]);
|
|
@@ -349,4 +352,123 @@ function getResponseError(error, limit = 200) {
|
|
|
349
352
|
response = error.response.data;
|
|
350
353
|
}
|
|
351
354
|
return limitString(response || error.message, limit).trim();
|
|
355
|
+
}
|
|
356
|
+
function normalizeProxy(proxy, protocol = "http") {
|
|
357
|
+
proxy = proxy?.trim();
|
|
358
|
+
if (!proxy) return null;
|
|
359
|
+
const schemeMatch = proxy.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/i);
|
|
360
|
+
if (schemeMatch) {
|
|
361
|
+
protocol = schemeMatch[1];
|
|
362
|
+
proxy = schemeMatch[2];
|
|
363
|
+
}
|
|
364
|
+
let auth = "";
|
|
365
|
+
let body = proxy;
|
|
366
|
+
const atIdx = body.lastIndexOf("@");
|
|
367
|
+
if (atIdx !== -1) {
|
|
368
|
+
auth = body.slice(0, atIdx) + "@";
|
|
369
|
+
body = body.slice(atIdx + 1);
|
|
370
|
+
}
|
|
371
|
+
if (!auth) {
|
|
372
|
+
/* Note: when host is single-token (e.g. "localhost") AND password is all-digit port-shaped,
|
|
373
|
+
the heuristic stays ambiguous; prefer `user:pass@host:port` for those cases. */
|
|
374
|
+
const parts = body.split(":");
|
|
375
|
+
const isPort = s => /^\d+$/.test(s) && +s >= 1 && +s <= 65535;
|
|
376
|
+
const isHost = s => s.includes(".") || /[a-z]/i.test(s);
|
|
377
|
+
if (parts.length === 4) {
|
|
378
|
+
if (isPort(parts[3]) && !isPort(parts[1])) {
|
|
379
|
+
// user:pass:host:port
|
|
380
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
381
|
+
body = `${parts[2]}:${parts[3]}`;
|
|
382
|
+
} else if (isPort(parts[1]) && !isPort(parts[3])) {
|
|
383
|
+
// host:port:user:pass
|
|
384
|
+
auth = `${parts[2]}:${parts[3]}@`;
|
|
385
|
+
body = `${parts[0]}:${parts[1]}`;
|
|
386
|
+
} else if (isHost(parts[2]) && !isHost(parts[0])) {
|
|
387
|
+
// user:pass:host:port (ambiguous; host detected at parts[2])
|
|
388
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
389
|
+
body = `${parts[2]}:${parts[3]}`;
|
|
390
|
+
} else {
|
|
391
|
+
// host:port:user:pass (ambiguous fallback)
|
|
392
|
+
auth = `${parts[2]}:${parts[3]}@`;
|
|
393
|
+
body = `${parts[0]}:${parts[1]}`;
|
|
394
|
+
}
|
|
395
|
+
} else if (parts.length === 5) {
|
|
396
|
+
if (isPort(parts[3]) && isPort(parts[4]) && !isPort(parts[1])) {
|
|
397
|
+
// user:pass:host:portStart:portEnd
|
|
398
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
399
|
+
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
400
|
+
} else if (isPort(parts[1]) && isPort(parts[2]) && !isPort(parts[3])) {
|
|
401
|
+
// host:portStart:portEnd:user:pass
|
|
402
|
+
auth = `${parts[3]}:${parts[4]}@`;
|
|
403
|
+
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
404
|
+
} else if (isHost(parts[2]) && !isHost(parts[0])) {
|
|
405
|
+
// user:pass:host:portStart:portEnd (ambiguous; host detected at parts[2])
|
|
406
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
407
|
+
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
408
|
+
} else {
|
|
409
|
+
// host:portStart:portEnd:user:pass (ambiguous fallback)
|
|
410
|
+
auth = `${parts[3]}:${parts[4]}@`;
|
|
411
|
+
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const parts = body.split(":");
|
|
416
|
+
if (parts.length === 3) {
|
|
417
|
+
const start = Number(parts[1]);
|
|
418
|
+
const end = Number(parts[2]);
|
|
419
|
+
if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
|
|
420
|
+
body = `${parts[0]}:${randomInteger(start, end + 1)}`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return `${protocol}://${auth}${body}`;
|
|
424
|
+
}
|
|
425
|
+
function parseProxy(proxy, protocol = "http") {
|
|
426
|
+
const normalized = normalizeProxy(proxy, protocol);
|
|
427
|
+
if (!normalized) return null;
|
|
428
|
+
const [scheme, rest] = normalized.split("://");
|
|
429
|
+
const atIdx = rest.lastIndexOf("@");
|
|
430
|
+
const authPart = atIdx === -1 ? null : rest.slice(0, atIdx);
|
|
431
|
+
const hostPart = atIdx === -1 ? rest : rest.slice(atIdx + 1);
|
|
432
|
+
const [host, port] = hostPart.split(":");
|
|
433
|
+
const result = {
|
|
434
|
+
protocol: scheme,
|
|
435
|
+
host,
|
|
436
|
+
port: parseInt(port, 10)
|
|
437
|
+
};
|
|
438
|
+
if (authPart !== null) {
|
|
439
|
+
const colonIdx = authPart.indexOf(":");
|
|
440
|
+
const [username, password] = colonIdx === -1 ? [authPart, ""] : [authPart.slice(0, colonIdx), authPart.slice(colonIdx + 1)];
|
|
441
|
+
result.auth = {
|
|
442
|
+
username,
|
|
443
|
+
password
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
function proxyValue(rawProxy, replacements = {}) {
|
|
449
|
+
const list = splitTrim(rawProxy || "");
|
|
450
|
+
if (list.length === 0) return null;
|
|
451
|
+
const picked = list[randomInteger(0, list.length)];
|
|
452
|
+
let result = normalizeProxy(picked);
|
|
453
|
+
if (!result) return null;
|
|
454
|
+
if (result.includes("{")) {
|
|
455
|
+
const {
|
|
456
|
+
SESSION,
|
|
457
|
+
...rest
|
|
458
|
+
} = replacements;
|
|
459
|
+
let sessionValue;
|
|
460
|
+
if (SESSION === undefined) {
|
|
461
|
+
sessionValue = randomHex(8);
|
|
462
|
+
} else if (typeof SESSION === "function") {
|
|
463
|
+
sessionValue = SESSION();
|
|
464
|
+
} else {
|
|
465
|
+
sessionValue = seedHex(String(SESSION), 8);
|
|
466
|
+
}
|
|
467
|
+
result = result.replace("{SESSION}", sessionValue);
|
|
468
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
469
|
+
const v = typeof value === "function" ? value() : String(value);
|
|
470
|
+
result = result.replace(`{${key}}`, v);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
352
474
|
}
|
package/lib/cjs/node.cjs
CHANGED
|
@@ -14,9 +14,6 @@ exports.gitVersion = gitVersion;
|
|
|
14
14
|
exports.hash = hash;
|
|
15
15
|
exports.hostIp = hostIp;
|
|
16
16
|
exports.md5 = md5;
|
|
17
|
-
exports.normalizeProxy = normalizeProxy;
|
|
18
|
-
exports.parseProxy = parseProxy;
|
|
19
|
-
exports.proxyValue = proxyValue;
|
|
20
17
|
exports.readJsonFile = readJsonFile;
|
|
21
18
|
exports.readJsonFileSync = readJsonFileSync;
|
|
22
19
|
exports.secureRandomBoolean = secureRandomBoolean;
|
|
@@ -128,106 +125,6 @@ function bcryptVerify(plainText, hash, {
|
|
|
128
125
|
}
|
|
129
126
|
return _bcryptjs.default.compareSync(input, hash);
|
|
130
127
|
}
|
|
131
|
-
function normalizeProxy(proxy, protocol = "http") {
|
|
132
|
-
proxy = proxy?.trim();
|
|
133
|
-
if (!proxy) return null;
|
|
134
|
-
const schemeMatch = proxy.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/i);
|
|
135
|
-
if (schemeMatch) {
|
|
136
|
-
protocol = schemeMatch[1];
|
|
137
|
-
proxy = schemeMatch[2];
|
|
138
|
-
}
|
|
139
|
-
let auth = "";
|
|
140
|
-
let body = proxy;
|
|
141
|
-
const atIdx = body.lastIndexOf("@");
|
|
142
|
-
if (atIdx !== -1) {
|
|
143
|
-
auth = body.slice(0, atIdx) + "@";
|
|
144
|
-
body = body.slice(atIdx + 1);
|
|
145
|
-
}
|
|
146
|
-
if (!auth) {
|
|
147
|
-
// Note: when the password itself is all-digit and port-shaped (e.g. "admin:1234:host:port"),
|
|
148
|
-
// the heuristic cannot distinguish auth-first from host-first ordering and may pick the wrong branch.
|
|
149
|
-
const parts = body.split(":");
|
|
150
|
-
const isPort = s => /^\d+$/.test(s) && +s >= 1 && +s <= 65535;
|
|
151
|
-
if (parts.length === 4) {
|
|
152
|
-
if (isPort(parts[3]) && !isPort(parts[1])) {
|
|
153
|
-
// user:pass:host:port
|
|
154
|
-
auth = `${parts[0]}:${parts[1]}@`;
|
|
155
|
-
body = `${parts[2]}:${parts[3]}`;
|
|
156
|
-
} else {
|
|
157
|
-
// host:port:user:pass (default)
|
|
158
|
-
auth = `${parts[2]}:${parts[3]}@`;
|
|
159
|
-
body = `${parts[0]}:${parts[1]}`;
|
|
160
|
-
}
|
|
161
|
-
} else if (parts.length === 5) {
|
|
162
|
-
if (isPort(parts[3]) && isPort(parts[4]) && !isPort(parts[1])) {
|
|
163
|
-
// user:pass:host:portStart:portEnd
|
|
164
|
-
auth = `${parts[0]}:${parts[1]}@`;
|
|
165
|
-
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
166
|
-
} else {
|
|
167
|
-
// host:portStart:portEnd:user:pass (default)
|
|
168
|
-
auth = `${parts[3]}:${parts[4]}@`;
|
|
169
|
-
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const parts = body.split(":");
|
|
174
|
-
if (parts.length === 3) {
|
|
175
|
-
const start = Number(parts[1]);
|
|
176
|
-
const end = Number(parts[2]);
|
|
177
|
-
if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
|
|
178
|
-
body = `${parts[0]}:${(0, _index.randomInteger)(start, end + 1)}`;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return `${protocol}://${auth}${body}`;
|
|
182
|
-
}
|
|
183
|
-
function parseProxy(proxy, protocol = "http") {
|
|
184
|
-
const normalized = normalizeProxy(proxy, protocol);
|
|
185
|
-
if (!normalized) return null;
|
|
186
|
-
const [scheme, rest] = normalized.split("://");
|
|
187
|
-
const atIdx = rest.lastIndexOf("@");
|
|
188
|
-
const authPart = atIdx === -1 ? null : rest.slice(0, atIdx);
|
|
189
|
-
const hostPart = atIdx === -1 ? rest : rest.slice(atIdx + 1);
|
|
190
|
-
const [host, port] = hostPart.split(":");
|
|
191
|
-
const result = {
|
|
192
|
-
protocol: scheme,
|
|
193
|
-
host,
|
|
194
|
-
port: parseInt(port, 10)
|
|
195
|
-
};
|
|
196
|
-
if (authPart !== null) {
|
|
197
|
-
const colonIdx = authPart.indexOf(":");
|
|
198
|
-
const [username, password] = colonIdx === -1 ? [authPart, ""] : [authPart.slice(0, colonIdx), authPart.slice(colonIdx + 1)];
|
|
199
|
-
result.auth = {
|
|
200
|
-
username,
|
|
201
|
-
password
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
function proxyValue(rawProxy, replacements = {}) {
|
|
207
|
-
const list = (0, _index.splitTrim)(rawProxy || "");
|
|
208
|
-
if (list.length === 0) return null;
|
|
209
|
-
const picked = list[(0, _index.randomInteger)(0, list.length)];
|
|
210
|
-
const {
|
|
211
|
-
SESSION,
|
|
212
|
-
...rest
|
|
213
|
-
} = replacements;
|
|
214
|
-
let sessionValue;
|
|
215
|
-
if (SESSION === undefined) {
|
|
216
|
-
sessionValue = (0, _index.randomHex)(8);
|
|
217
|
-
} else if (typeof SESSION === "function") {
|
|
218
|
-
sessionValue = SESSION();
|
|
219
|
-
} else {
|
|
220
|
-
sessionValue = (0, _index.seedHex)(String(SESSION), 8);
|
|
221
|
-
}
|
|
222
|
-
let result = normalizeProxy(picked);
|
|
223
|
-
if (!result) return null;
|
|
224
|
-
result = result.replace("{SESSION}", sessionValue);
|
|
225
|
-
for (const [key, value] of Object.entries(rest)) {
|
|
226
|
-
const v = typeof value === "function" ? value() : String(value);
|
|
227
|
-
result = result.replace(`{${key}}`, v);
|
|
228
|
-
}
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
128
|
async function readJsonFile(filePath, defaultValue = {}) {
|
|
232
129
|
try {
|
|
233
130
|
const data = await _fs.promises.readFile(filePath, 'utf8');
|
package/lib/esm/index.mjs
CHANGED
|
@@ -70,7 +70,7 @@ export async function retry(task, maxAttempts = 1, onError = null, {
|
|
|
70
70
|
try {
|
|
71
71
|
return await task();
|
|
72
72
|
} catch (error) {
|
|
73
|
-
if (onError) await onError(
|
|
73
|
+
if (onError) await onError(error, attempt);
|
|
74
74
|
if (attempt >= maxAttempts) throw error;
|
|
75
75
|
if (delayMs > 0) await sleepMs(delayMs * backoffFactor ** (attempt - 1));
|
|
76
76
|
}
|
|
@@ -133,7 +133,7 @@ export function findNodeByKey(key, node, pair = null) {
|
|
|
133
133
|
}
|
|
134
134
|
return null;
|
|
135
135
|
}
|
|
136
|
-
export function waitForProperty(object, property, timeout
|
|
136
|
+
export function waitForProperty(object, property, timeout, interval = 100) {
|
|
137
137
|
return new Promise((resolve, reject) => {
|
|
138
138
|
if (Object.hasOwn(object, property)) {
|
|
139
139
|
resolve(object[property]);
|
|
@@ -304,4 +304,123 @@ export function getResponseError(error, limit = 200) {
|
|
|
304
304
|
response = error.response.data;
|
|
305
305
|
}
|
|
306
306
|
return limitString(response || error.message, limit).trim();
|
|
307
|
+
}
|
|
308
|
+
export function normalizeProxy(proxy, protocol = "http") {
|
|
309
|
+
proxy = proxy?.trim();
|
|
310
|
+
if (!proxy) return null;
|
|
311
|
+
const schemeMatch = proxy.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/i);
|
|
312
|
+
if (schemeMatch) {
|
|
313
|
+
protocol = schemeMatch[1];
|
|
314
|
+
proxy = schemeMatch[2];
|
|
315
|
+
}
|
|
316
|
+
let auth = "";
|
|
317
|
+
let body = proxy;
|
|
318
|
+
const atIdx = body.lastIndexOf("@");
|
|
319
|
+
if (atIdx !== -1) {
|
|
320
|
+
auth = body.slice(0, atIdx) + "@";
|
|
321
|
+
body = body.slice(atIdx + 1);
|
|
322
|
+
}
|
|
323
|
+
if (!auth) {
|
|
324
|
+
/* Note: when host is single-token (e.g. "localhost") AND password is all-digit port-shaped,
|
|
325
|
+
the heuristic stays ambiguous; prefer `user:pass@host:port` for those cases. */
|
|
326
|
+
const parts = body.split(":");
|
|
327
|
+
const isPort = s => /^\d+$/.test(s) && +s >= 1 && +s <= 65535;
|
|
328
|
+
const isHost = s => s.includes(".") || /[a-z]/i.test(s);
|
|
329
|
+
if (parts.length === 4) {
|
|
330
|
+
if (isPort(parts[3]) && !isPort(parts[1])) {
|
|
331
|
+
// user:pass:host:port
|
|
332
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
333
|
+
body = `${parts[2]}:${parts[3]}`;
|
|
334
|
+
} else if (isPort(parts[1]) && !isPort(parts[3])) {
|
|
335
|
+
// host:port:user:pass
|
|
336
|
+
auth = `${parts[2]}:${parts[3]}@`;
|
|
337
|
+
body = `${parts[0]}:${parts[1]}`;
|
|
338
|
+
} else if (isHost(parts[2]) && !isHost(parts[0])) {
|
|
339
|
+
// user:pass:host:port (ambiguous; host detected at parts[2])
|
|
340
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
341
|
+
body = `${parts[2]}:${parts[3]}`;
|
|
342
|
+
} else {
|
|
343
|
+
// host:port:user:pass (ambiguous fallback)
|
|
344
|
+
auth = `${parts[2]}:${parts[3]}@`;
|
|
345
|
+
body = `${parts[0]}:${parts[1]}`;
|
|
346
|
+
}
|
|
347
|
+
} else if (parts.length === 5) {
|
|
348
|
+
if (isPort(parts[3]) && isPort(parts[4]) && !isPort(parts[1])) {
|
|
349
|
+
// user:pass:host:portStart:portEnd
|
|
350
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
351
|
+
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
352
|
+
} else if (isPort(parts[1]) && isPort(parts[2]) && !isPort(parts[3])) {
|
|
353
|
+
// host:portStart:portEnd:user:pass
|
|
354
|
+
auth = `${parts[3]}:${parts[4]}@`;
|
|
355
|
+
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
356
|
+
} else if (isHost(parts[2]) && !isHost(parts[0])) {
|
|
357
|
+
// user:pass:host:portStart:portEnd (ambiguous; host detected at parts[2])
|
|
358
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
359
|
+
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
360
|
+
} else {
|
|
361
|
+
// host:portStart:portEnd:user:pass (ambiguous fallback)
|
|
362
|
+
auth = `${parts[3]}:${parts[4]}@`;
|
|
363
|
+
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const parts = body.split(":");
|
|
368
|
+
if (parts.length === 3) {
|
|
369
|
+
const start = Number(parts[1]);
|
|
370
|
+
const end = Number(parts[2]);
|
|
371
|
+
if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
|
|
372
|
+
body = `${parts[0]}:${randomInteger(start, end + 1)}`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return `${protocol}://${auth}${body}`;
|
|
376
|
+
}
|
|
377
|
+
export function parseProxy(proxy, protocol = "http") {
|
|
378
|
+
const normalized = normalizeProxy(proxy, protocol);
|
|
379
|
+
if (!normalized) return null;
|
|
380
|
+
const [scheme, rest] = normalized.split("://");
|
|
381
|
+
const atIdx = rest.lastIndexOf("@");
|
|
382
|
+
const authPart = atIdx === -1 ? null : rest.slice(0, atIdx);
|
|
383
|
+
const hostPart = atIdx === -1 ? rest : rest.slice(atIdx + 1);
|
|
384
|
+
const [host, port] = hostPart.split(":");
|
|
385
|
+
const result = {
|
|
386
|
+
protocol: scheme,
|
|
387
|
+
host,
|
|
388
|
+
port: parseInt(port, 10)
|
|
389
|
+
};
|
|
390
|
+
if (authPart !== null) {
|
|
391
|
+
const colonIdx = authPart.indexOf(":");
|
|
392
|
+
const [username, password] = colonIdx === -1 ? [authPart, ""] : [authPart.slice(0, colonIdx), authPart.slice(colonIdx + 1)];
|
|
393
|
+
result.auth = {
|
|
394
|
+
username,
|
|
395
|
+
password
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
export function proxyValue(rawProxy, replacements = {}) {
|
|
401
|
+
const list = splitTrim(rawProxy || "");
|
|
402
|
+
if (list.length === 0) return null;
|
|
403
|
+
const picked = list[randomInteger(0, list.length)];
|
|
404
|
+
let result = normalizeProxy(picked);
|
|
405
|
+
if (!result) return null;
|
|
406
|
+
if (result.includes("{")) {
|
|
407
|
+
const {
|
|
408
|
+
SESSION,
|
|
409
|
+
...rest
|
|
410
|
+
} = replacements;
|
|
411
|
+
let sessionValue;
|
|
412
|
+
if (SESSION === undefined) {
|
|
413
|
+
sessionValue = randomHex(8);
|
|
414
|
+
} else if (typeof SESSION === "function") {
|
|
415
|
+
sessionValue = SESSION();
|
|
416
|
+
} else {
|
|
417
|
+
sessionValue = seedHex(String(SESSION), 8);
|
|
418
|
+
}
|
|
419
|
+
result = result.replace("{SESSION}", sessionValue);
|
|
420
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
421
|
+
const v = typeof value === "function" ? value() : String(value);
|
|
422
|
+
result = result.replace(`{${key}}`, v);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
307
426
|
}
|
package/lib/esm/node.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { networkInterfaces } from "os";
|
|
|
6
6
|
import { exec, execFileSync } from "child_process";
|
|
7
7
|
import { promisify } from "util";
|
|
8
8
|
import bcrypt from "bcryptjs";
|
|
9
|
-
import { CONSTANTS,
|
|
9
|
+
import { CONSTANTS, checkEmpty } from "./index.mjs";
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
11
|
export function secureRandomBoolean() {
|
|
12
12
|
return secureRandomInteger(2) === 1;
|
|
@@ -95,106 +95,6 @@ export function bcryptVerify(plainText, hash, {
|
|
|
95
95
|
}
|
|
96
96
|
return bcrypt.compareSync(input, hash);
|
|
97
97
|
}
|
|
98
|
-
export function normalizeProxy(proxy, protocol = "http") {
|
|
99
|
-
proxy = proxy?.trim();
|
|
100
|
-
if (!proxy) return null;
|
|
101
|
-
const schemeMatch = proxy.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/i);
|
|
102
|
-
if (schemeMatch) {
|
|
103
|
-
protocol = schemeMatch[1];
|
|
104
|
-
proxy = schemeMatch[2];
|
|
105
|
-
}
|
|
106
|
-
let auth = "";
|
|
107
|
-
let body = proxy;
|
|
108
|
-
const atIdx = body.lastIndexOf("@");
|
|
109
|
-
if (atIdx !== -1) {
|
|
110
|
-
auth = body.slice(0, atIdx) + "@";
|
|
111
|
-
body = body.slice(atIdx + 1);
|
|
112
|
-
}
|
|
113
|
-
if (!auth) {
|
|
114
|
-
// Note: when the password itself is all-digit and port-shaped (e.g. "admin:1234:host:port"),
|
|
115
|
-
// the heuristic cannot distinguish auth-first from host-first ordering and may pick the wrong branch.
|
|
116
|
-
const parts = body.split(":");
|
|
117
|
-
const isPort = s => /^\d+$/.test(s) && +s >= 1 && +s <= 65535;
|
|
118
|
-
if (parts.length === 4) {
|
|
119
|
-
if (isPort(parts[3]) && !isPort(parts[1])) {
|
|
120
|
-
// user:pass:host:port
|
|
121
|
-
auth = `${parts[0]}:${parts[1]}@`;
|
|
122
|
-
body = `${parts[2]}:${parts[3]}`;
|
|
123
|
-
} else {
|
|
124
|
-
// host:port:user:pass (default)
|
|
125
|
-
auth = `${parts[2]}:${parts[3]}@`;
|
|
126
|
-
body = `${parts[0]}:${parts[1]}`;
|
|
127
|
-
}
|
|
128
|
-
} else if (parts.length === 5) {
|
|
129
|
-
if (isPort(parts[3]) && isPort(parts[4]) && !isPort(parts[1])) {
|
|
130
|
-
// user:pass:host:portStart:portEnd
|
|
131
|
-
auth = `${parts[0]}:${parts[1]}@`;
|
|
132
|
-
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
133
|
-
} else {
|
|
134
|
-
// host:portStart:portEnd:user:pass (default)
|
|
135
|
-
auth = `${parts[3]}:${parts[4]}@`;
|
|
136
|
-
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const parts = body.split(":");
|
|
141
|
-
if (parts.length === 3) {
|
|
142
|
-
const start = Number(parts[1]);
|
|
143
|
-
const end = Number(parts[2]);
|
|
144
|
-
if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
|
|
145
|
-
body = `${parts[0]}:${randomInteger(start, end + 1)}`;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return `${protocol}://${auth}${body}`;
|
|
149
|
-
}
|
|
150
|
-
export function parseProxy(proxy, protocol = "http") {
|
|
151
|
-
const normalized = normalizeProxy(proxy, protocol);
|
|
152
|
-
if (!normalized) return null;
|
|
153
|
-
const [scheme, rest] = normalized.split("://");
|
|
154
|
-
const atIdx = rest.lastIndexOf("@");
|
|
155
|
-
const authPart = atIdx === -1 ? null : rest.slice(0, atIdx);
|
|
156
|
-
const hostPart = atIdx === -1 ? rest : rest.slice(atIdx + 1);
|
|
157
|
-
const [host, port] = hostPart.split(":");
|
|
158
|
-
const result = {
|
|
159
|
-
protocol: scheme,
|
|
160
|
-
host,
|
|
161
|
-
port: parseInt(port, 10)
|
|
162
|
-
};
|
|
163
|
-
if (authPart !== null) {
|
|
164
|
-
const colonIdx = authPart.indexOf(":");
|
|
165
|
-
const [username, password] = colonIdx === -1 ? [authPart, ""] : [authPart.slice(0, colonIdx), authPart.slice(colonIdx + 1)];
|
|
166
|
-
result.auth = {
|
|
167
|
-
username,
|
|
168
|
-
password
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
return result;
|
|
172
|
-
}
|
|
173
|
-
export function proxyValue(rawProxy, replacements = {}) {
|
|
174
|
-
const list = splitTrim(rawProxy || "");
|
|
175
|
-
if (list.length === 0) return null;
|
|
176
|
-
const picked = list[randomInteger(0, list.length)];
|
|
177
|
-
const {
|
|
178
|
-
SESSION,
|
|
179
|
-
...rest
|
|
180
|
-
} = replacements;
|
|
181
|
-
let sessionValue;
|
|
182
|
-
if (SESSION === undefined) {
|
|
183
|
-
sessionValue = randomHex(8);
|
|
184
|
-
} else if (typeof SESSION === "function") {
|
|
185
|
-
sessionValue = SESSION();
|
|
186
|
-
} else {
|
|
187
|
-
sessionValue = seedHex(String(SESSION), 8);
|
|
188
|
-
}
|
|
189
|
-
let result = normalizeProxy(picked);
|
|
190
|
-
if (!result) return null;
|
|
191
|
-
result = result.replace("{SESSION}", sessionValue);
|
|
192
|
-
for (const [key, value] of Object.entries(rest)) {
|
|
193
|
-
const v = typeof value === "function" ? value() : String(value);
|
|
194
|
-
result = result.replace(`{${key}}`, v);
|
|
195
|
-
}
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
98
|
export async function readJsonFile(filePath, defaultValue = {}) {
|
|
199
99
|
try {
|
|
200
100
|
const data = await fsp.readFile(filePath, 'utf8');
|
package/package.json
CHANGED