melperjs 16.1.0 → 17.1.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/lib/esm/index.mjs DELETED
@@ -1,307 +0,0 @@
1
- import xss from "xss";
2
- import setCookieParser from "set-cookie-parser";
3
- import camelCase from "lodash/camelCase.js";
4
- import upperFirst from "lodash/upperFirst.js";
5
- import isEmpty from "lodash/isEmpty.js";
6
- import shuffle from "lodash/shuffle.js";
7
- export const CONSTANTS = {
8
- LOWER_CASE: "abcdefghijklmnopqrstuvwxyz",
9
- UPPER_CASE: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
10
- HEXADECIMAL: "0123456789abcdef",
11
- NUMBERS: "0123456789",
12
- INT32_MIN: -2147483648,
13
- INT32_MAX: 2147483647
14
- };
15
- const NUMBER_PATTERN = /^-?\d+(\.\d+)?(e[+-]?\d+)?$/i;
16
- const INTEGER_PATTERN = /^-?\d+$/;
17
- export function Exception(message, response = {}, name = null) {
18
- const error = new Error(message);
19
- error.name = name || "Exception";
20
- error.response = response;
21
- if (checkEmpty(response)) {
22
- error.response = {};
23
- }
24
- return error;
25
- }
26
- export function time() {
27
- return Math.floor(Date.now() / 1000);
28
- }
29
- export function sleepMs(milliseconds) {
30
- return new Promise(resolve => setTimeout(resolve, milliseconds));
31
- }
32
- export function sleep(seconds) {
33
- return sleepMs(seconds * 1000);
34
- }
35
- export function promiseTimeout(milliseconds, promise) {
36
- let timer;
37
- const timeout = new Promise((_, reject) => {
38
- timer = setTimeout(() => reject(new Error(`Promise timed out after ${milliseconds}ms`)), milliseconds);
39
- });
40
- return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
41
- }
42
- export function promiseSilent(promise) {
43
- return promise.then(() => {}).catch(() => {});
44
- }
45
- export async function forever(delayMs, task, onError = null, onFinally = null) {
46
- if (!isPositiveNumber(delayMs)) throw new Error("delayMs must be a positive number");
47
- const update = value => {
48
- if (isPositiveNumber(value)) delayMs = value;
49
- };
50
- while (true) {
51
- try {
52
- update(await task());
53
- } catch (error) {
54
- if (onError) update(await onError(error));
55
- } finally {
56
- if (onFinally) {
57
- try {
58
- update(await onFinally());
59
- } catch {}
60
- }
61
- await sleepMs(delayMs);
62
- }
63
- }
64
- }
65
- export async function retry(task, maxAttempts = 1, onError = null, {
66
- delayMs = 0,
67
- backoffFactor = 1
68
- } = {}) {
69
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
70
- try {
71
- return await task();
72
- } catch (error) {
73
- if (onError) await onError(attempt, error);
74
- if (attempt >= maxAttempts) throw error;
75
- if (delayMs > 0) await sleepMs(delayMs * backoffFactor ** (attempt - 1));
76
- }
77
- }
78
- }
79
- export function isValidURL(url) {
80
- try {
81
- new URL(url);
82
- return true;
83
- } catch {
84
- return false;
85
- }
86
- }
87
- export function splitTrim(string, separator = null) {
88
- return string.split(separator ?? /\r?\n/).map(item => item.trim()).filter(Boolean);
89
- }
90
- export function checkEmpty(value) {
91
- if (typeof value === "number") return value === 0;
92
- return isEmpty(value);
93
- }
94
- export function pascalCase(string) {
95
- return upperFirst(camelCase(string));
96
- }
97
- export function titleCase(string, separator = " ") {
98
- return (string || "").split(separator).map(upperFirst).join(separator);
99
- }
100
- export function isInt32(value) {
101
- return Number.isInteger(value) && value >= CONSTANTS.INT32_MIN && value <= CONSTANTS.INT32_MAX;
102
- }
103
- export function isPositiveNumber(value) {
104
- return Number.isFinite(value) && value > 0;
105
- }
106
- export function coerceObjectNumbers(object) {
107
- for (const key of Object.keys(object)) {
108
- const value = object[key];
109
- if (typeof value === 'string' && NUMBER_PATTERN.test(value)) {
110
- object[key] = parseFloat(value);
111
- }
112
- }
113
- return object;
114
- }
115
- export function coerceObjectIntegers(object) {
116
- for (const key of Object.keys(object)) {
117
- const value = object[key];
118
- if (typeof value === 'string' && INTEGER_PATTERN.test(value)) {
119
- object[key] = parseInt(value);
120
- }
121
- }
122
- return object;
123
- }
124
- export function findNodeByKey(key, node, pair = null) {
125
- if (node && typeof node === 'object') {
126
- if (Object.hasOwn(node, key) && (pair === null || node[key] === pair)) {
127
- return node;
128
- }
129
- for (const childKey of Object.keys(node)) {
130
- const result = findNodeByKey(key, node[childKey], pair);
131
- if (result) return result;
132
- }
133
- }
134
- return null;
135
- }
136
- export function waitForProperty(object, property, timeout = 5000, interval = 100) {
137
- return new Promise((resolve, reject) => {
138
- if (Object.hasOwn(object, property)) {
139
- resolve(object[property]);
140
- return;
141
- }
142
- const startTime = Date.now();
143
- const checkProperty = setInterval(() => {
144
- if (Object.hasOwn(object, property)) {
145
- clearInterval(checkProperty);
146
- resolve(object[property]);
147
- } else if (Date.now() - startTime >= timeout) {
148
- clearInterval(checkProperty);
149
- reject(new Error(`Property "${property}" did not appear within ${timeout}ms`));
150
- }
151
- }, interval);
152
- });
153
- }
154
- export function shuffleObject(object) {
155
- return Object.fromEntries(shuffle(Object.entries(object)));
156
- }
157
- export function objectStringify(object) {
158
- for (const key of Object.keys(object)) {
159
- const value = object[key];
160
- if (value !== null && typeof value === 'object') {
161
- objectStringify(value);
162
- } else {
163
- object[key] = String(value);
164
- }
165
- }
166
- return object;
167
- }
168
- export function limitString(string, limit = 35, omission = "...") {
169
- string = string || "";
170
- if (string.length <= limit) return string;
171
- return string.slice(0, limit - omission.length) + omission;
172
- }
173
- export function safeString(string) {
174
- return xss(string || "", {
175
- whiteList: {},
176
- stripIgnoreTag: true,
177
- stripIgnoreTagBody: ["script", "style", "iframe", "object", "embed", "form"],
178
- css: false
179
- });
180
- }
181
- export function shuffleString(string) {
182
- return shuffle(string.split('')).join('');
183
- }
184
- export function randomBoolean() {
185
- return Math.random() < 0.5;
186
- }
187
- export function randomString(length, useNumbers = true, useUppercase = false) {
188
- let characters = CONSTANTS.LOWER_CASE;
189
- if (useUppercase) characters += CONSTANTS.UPPER_CASE;
190
- if (useNumbers) characters += CONSTANTS.NUMBERS;
191
- let result = '';
192
- for (let i = 0; i < length; i++) {
193
- result += characters[Math.random() * characters.length | 0];
194
- }
195
- return result;
196
- }
197
- export function randomHex(length) {
198
- let result = '';
199
- for (let i = 0; i < length; i++) {
200
- result += CONSTANTS.HEXADECIMAL[Math.random() * 16 | 0];
201
- }
202
- return result;
203
- }
204
- export function randomInteger(min, max = undefined) {
205
- if (typeof max === 'undefined') {
206
- max = min;
207
- min = 0;
208
- }
209
- if (typeof min !== 'number' || typeof max !== 'number') {
210
- throw new Error('min and max must be numerical values');
211
- }
212
- if (max <= min) {
213
- throw new Error('max must be greater than min');
214
- }
215
- return Math.floor(Math.random() * (max - min)) + min;
216
- }
217
- export function randomUuid(useDashes = true) {
218
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
219
- const r = Math.random() * 16 | 0;
220
- return (c === 'x' ? r : r & 0x3 | 0x8).toString(16);
221
- });
222
- return useDashes ? uuid : uuid.replaceAll("-", "");
223
- }
224
- export function randomWeighted(object) {
225
- if (checkEmpty(object)) return undefined;
226
- const elements = Object.keys(object);
227
- const weights = Object.values(object);
228
- const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
229
- const randomNum = Math.random() * totalWeight;
230
- let weightSum = 0;
231
- for (let i = 0; i < elements.length; i++) {
232
- weightSum += weights[i];
233
- if (randomNum < weightSum) {
234
- return elements[i];
235
- }
236
- }
237
- }
238
- export function randomElement(object) {
239
- if (checkEmpty(object)) return undefined;
240
- const values = Array.isArray(object) ? object : Object.values(object);
241
- if (values.length === 0) return undefined;
242
- return values[Math.floor(Math.random() * values.length)];
243
- }
244
- export function mulberry32(seed) {
245
- if (typeof seed === "string") {
246
- let h = 0;
247
- for (let i = 0; i < seed.length; i++) {
248
- h = Math.imul(h ^ seed.charCodeAt(i), 2654435761);
249
- }
250
- seed = h >>> 0;
251
- }
252
- return function () {
253
- seed = seed + 0x6D2B79F5 | 0;
254
- let t = seed;
255
- t = Math.imul(t ^ t >>> 15, t | 1);
256
- t ^= t + Math.imul(t ^ t >>> 7, t | 61);
257
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
258
- };
259
- }
260
- export function seedHex(seed, length) {
261
- const rng = mulberry32(String(seed));
262
- let result = '';
263
- while (result.length < length) {
264
- result += Math.floor(rng() * 0x100000000).toString(16).padStart(8, '0');
265
- }
266
- return result.slice(0, length);
267
- }
268
- export function cookiesFromResponse(response, decodeValues = false) {
269
- const obj = {};
270
- const cookies = setCookieParser.parse(response, {
271
- decodeValues
272
- });
273
- for (const cookie of cookies) {
274
- obj[cookie.name] = cookie.value;
275
- }
276
- return obj;
277
- }
278
- export function cookiesToHeader(cookies) {
279
- if (!cookies) return "";
280
- return Object.entries(cookies).filter(([, value]) => value !== null && value !== undefined).map(([key, value]) => `${key}=${value}`).join("; ");
281
- }
282
- export function cookiesFromHeader(header) {
283
- const cookies = {};
284
- if (!header) return cookies;
285
- header.split(';').forEach(cookie => {
286
- const trimmed = cookie.trim();
287
- if (!trimmed.includes('=')) return;
288
- const [key, ...valueParts] = trimmed.split('=');
289
- const trimmedKey = key.trim();
290
- if (trimmedKey) {
291
- cookies[trimmedKey] = valueParts.join('=').trim();
292
- }
293
- });
294
- return cookies;
295
- }
296
- export function isTransientHttpCode(httpCode) {
297
- return !httpCode || isNaN(httpCode) || httpCode === 100 || httpCode === 402 || httpCode === 407 || 460 <= httpCode && httpCode < 470 || 500 <= httpCode;
298
- }
299
- export function getResponseError(error, limit = 200) {
300
- let response;
301
- if (error?.response?.status && error.response.data) {
302
- response = `${error.response.status}|${error.response.data}`;
303
- } else if (error?.response?.data) {
304
- response = error.response.data;
305
- }
306
- return limitString(response || error.message, limit).trim();
307
- }
package/lib/esm/node.mjs DELETED
@@ -1,269 +0,0 @@
1
- import fs from "fs";
2
- import { promises as fsp } from "fs";
3
- import path from "path";
4
- import crypto from "crypto";
5
- import { networkInterfaces } from "os";
6
- import { exec, execFileSync } from "child_process";
7
- import { promisify } from "util";
8
- import bcrypt from "bcryptjs";
9
- import { CONSTANTS, splitTrim, randomInteger, randomHex, seedHex, checkEmpty } from "./index.mjs";
10
- const execAsync = promisify(exec);
11
- export function secureRandomBoolean() {
12
- return secureRandomInteger(2) === 1;
13
- }
14
- export function secureRandomString(length, useNumbers = true, useUppercase = false) {
15
- let characters = CONSTANTS.LOWER_CASE;
16
- if (useUppercase) characters += CONSTANTS.UPPER_CASE;
17
- if (useNumbers) characters += CONSTANTS.NUMBERS;
18
- let result = '';
19
- for (let i = 0; i < length; i++) {
20
- result += characters[secureRandomInteger(0, characters.length)];
21
- }
22
- return result;
23
- }
24
- export function secureRandomHex(length) {
25
- return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length);
26
- }
27
- export function secureRandomInteger(min, max = undefined) {
28
- return crypto.randomInt(min, max);
29
- }
30
- export function secureRandomUuid(useDashes = true) {
31
- const uuid = crypto.randomUUID();
32
- return useDashes ? uuid : uuid.replaceAll("-", "");
33
- }
34
- export function secureRandomWeighted(object) {
35
- if (checkEmpty(object)) return undefined;
36
- const elements = Object.keys(object);
37
- const weights = Object.values(object);
38
- const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
39
- const randomNum = secureRandomInteger(0, totalWeight);
40
- let weightSum = 0;
41
- for (let i = 0; i < elements.length; i++) {
42
- weightSum += weights[i];
43
- if (randomNum < weightSum) {
44
- return elements[i];
45
- }
46
- }
47
- }
48
- export function secureRandomElement(object) {
49
- if (checkEmpty(object)) return undefined;
50
- const values = Array.isArray(object) ? object : Object.values(object);
51
- if (values.length === 0) return undefined;
52
- return values[secureRandomInteger(0, values.length)];
53
- }
54
- export function uuidFromSeed(seed, useDashes = true) {
55
- const hash = crypto.createHash('md5').update(seed).digest();
56
- hash[6] = hash[6] & 0x0f | 0x30;
57
- hash[8] = hash[8] & 0x3f | 0x80;
58
- const hex = hash.toString('hex');
59
- if (!useDashes) return hex;
60
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
61
- }
62
- export function hash(algorithm, data) {
63
- return crypto.createHash(algorithm).update(data).digest("hex");
64
- }
65
- export function md5(data) {
66
- return hash("md5", data);
67
- }
68
- export function sha256(data) {
69
- return hash("sha256", data);
70
- }
71
- export function base64Encode(data) {
72
- return Buffer.from(data).toString('base64');
73
- }
74
- export function base64Decode(data, encoding = 'utf8') {
75
- return Buffer.from(data, 'base64').toString(encoding);
76
- }
77
- export function bcryptHash(plainText, {
78
- key = "",
79
- strength = 12,
80
- preHash = true
81
- } = {}) {
82
- let input = plainText + key;
83
- if (preHash) {
84
- input = sha256(input);
85
- }
86
- return bcrypt.hashSync(input, strength);
87
- }
88
- export function bcryptVerify(plainText, hash, {
89
- key = "",
90
- preHash = true
91
- } = {}) {
92
- let input = plainText + key;
93
- if (preHash) {
94
- input = sha256(input);
95
- }
96
- return bcrypt.compareSync(input, hash);
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
- export async function readJsonFile(filePath, defaultValue = {}) {
199
- try {
200
- const data = await fsp.readFile(filePath, 'utf8');
201
- return JSON.parse(data);
202
- } catch {
203
- return defaultValue;
204
- }
205
- }
206
- export function readJsonFileSync(filePath, defaultValue = {}) {
207
- try {
208
- const data = fs.readFileSync(filePath, 'utf8');
209
- return JSON.parse(data);
210
- } catch {
211
- return defaultValue;
212
- }
213
- }
214
- export function writeJsonFile(filePath, data) {
215
- const jsonData = JSON.stringify(data);
216
- return fsp.writeFile(filePath, jsonData, 'utf8');
217
- }
218
- export function writeJsonFileSync(filePath, data) {
219
- const jsonData = JSON.stringify(data);
220
- return fs.writeFileSync(filePath, jsonData, 'utf8');
221
- }
222
- export async function clearDirectory(directoryPath, keepDir = true) {
223
- await fsp.rm(directoryPath, {
224
- recursive: true,
225
- force: true
226
- });
227
- if (keepDir) await fsp.mkdir(directoryPath, {
228
- recursive: true
229
- });
230
- }
231
- export function createNumberedDirs(mainDirectory, start = 0, end = 9) {
232
- fs.mkdirSync(mainDirectory, {
233
- recursive: true
234
- });
235
- for (let i = start; i <= end; i++) {
236
- fs.mkdirSync(path.join(mainDirectory, `${i}`), {
237
- recursive: true
238
- });
239
- }
240
- }
241
- export async function executeCommand(command) {
242
- const {
243
- stdout
244
- } = await execAsync(command);
245
- return stdout.trim();
246
- }
247
- export function hostIp() {
248
- for (const list of Object.values(networkInterfaces())) {
249
- for (const alias of list) {
250
- if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.address.startsWith('192.168.') && !alias.internal) {
251
- return alias.address;
252
- }
253
- }
254
- }
255
- return '127.0.0.1';
256
- }
257
- export function gitVersion() {
258
- try {
259
- const raw = execFileSync('git', ['show', '-s', '--format=%ct', 'HEAD'], {
260
- encoding: 'utf8'
261
- }).trim();
262
- const timestamp = parseInt(raw, 10);
263
- if (isNaN(timestamp)) return "1.0";
264
- const iso = new Date(timestamp * 1000).toISOString();
265
- return `${iso.slice(2, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}.${iso.slice(11, 13)}${iso.slice(14, 16)}`;
266
- } catch {
267
- return "1.0";
268
- }
269
- }