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 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 `(attempt, error)` after each failed attempt.
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 = 5000, interval = 100)
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(attempt, error);
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 = 5000, interval = 100) {
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(attempt, error);
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 = 5000, interval = 100) {
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, splitTrim, randomInteger, randomHex, seedHex, checkEmpty } from "./index.mjs";
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
@@ -70,5 +70,5 @@
70
70
  "babel-plugin-transform-import-meta": "^2.3.3",
71
71
  "cross-env": "^10.1.0"
72
72
  },
73
- "version": "16.1.0"
73
+ "version": "17.0.0"
74
74
  }