melperjs 15.0.0 → 16.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/LICENSE +20 -20
- package/README.md +22 -21
- package/docs/general.md +363 -0
- package/docs/index.md +27 -0
- package/docs/node.md +285 -0
- package/lib/cjs/index.cjs +351 -0
- package/lib/cjs/node.cjs +301 -0
- package/lib/esm/index.mjs +306 -0
- package/lib/esm/node.mjs +268 -0
- package/package.json +74 -55
- package/lib/cjs/index.js +0 -386
- package/lib/cjs/node.js +0 -269
- package/lib/cjs/package.json +0 -3
- package/lib/esm/index.js +0 -341
- package/lib/esm/node.js +0 -236
package/lib/esm/node.mjs
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
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 } from "./index.mjs";
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
export function secureRandomBoolean() {
|
|
12
|
+
return crypto.randomInt(2) === 0;
|
|
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[crypto.randomInt(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) {
|
|
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
|
+
const elements = Object.keys(object);
|
|
36
|
+
const weights = Object.values(object);
|
|
37
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
38
|
+
const randomNum = secureRandomInteger(0, totalWeight);
|
|
39
|
+
let weightSum = 0;
|
|
40
|
+
for (let i = 0; i < elements.length; i++) {
|
|
41
|
+
weightSum += weights[i];
|
|
42
|
+
if (randomNum < weightSum) {
|
|
43
|
+
return elements[i];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function secureRandomElement(object) {
|
|
48
|
+
if (!object) return undefined;
|
|
49
|
+
const values = Array.isArray(object) ? object : Object.values(object);
|
|
50
|
+
if (values.length === 0) return undefined;
|
|
51
|
+
return values[crypto.randomInt(0, values.length)];
|
|
52
|
+
}
|
|
53
|
+
export function uuidFromSeed(seed, useDashes = true) {
|
|
54
|
+
const hash = crypto.createHash('md5').update(seed).digest();
|
|
55
|
+
hash[6] = hash[6] & 0x0f | 0x30;
|
|
56
|
+
hash[8] = hash[8] & 0x3f | 0x80;
|
|
57
|
+
const hex = hash.toString('hex');
|
|
58
|
+
if (!useDashes) return hex;
|
|
59
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
60
|
+
}
|
|
61
|
+
export function hash(algorithm, data) {
|
|
62
|
+
return crypto.createHash(algorithm).update(data).digest("hex");
|
|
63
|
+
}
|
|
64
|
+
export function md5(data) {
|
|
65
|
+
return hash("md5", data);
|
|
66
|
+
}
|
|
67
|
+
export function sha256(data) {
|
|
68
|
+
return hash("sha256", data);
|
|
69
|
+
}
|
|
70
|
+
export function base64Encode(data) {
|
|
71
|
+
return Buffer.from(data).toString('base64');
|
|
72
|
+
}
|
|
73
|
+
export function base64Decode(data, encoding = 'utf8') {
|
|
74
|
+
return Buffer.from(data, 'base64').toString(encoding);
|
|
75
|
+
}
|
|
76
|
+
export function bcryptHash(plainText, {
|
|
77
|
+
key = "",
|
|
78
|
+
strength = 12,
|
|
79
|
+
preHash = true
|
|
80
|
+
} = {}) {
|
|
81
|
+
let input = plainText + key;
|
|
82
|
+
if (preHash) {
|
|
83
|
+
input = sha256(input);
|
|
84
|
+
}
|
|
85
|
+
return bcrypt.hashSync(input, strength);
|
|
86
|
+
}
|
|
87
|
+
export function bcryptVerify(plainText, hash, {
|
|
88
|
+
key = "",
|
|
89
|
+
preHash = true
|
|
90
|
+
} = {}) {
|
|
91
|
+
let input = plainText + key;
|
|
92
|
+
if (preHash) {
|
|
93
|
+
input = sha256(input);
|
|
94
|
+
}
|
|
95
|
+
return bcrypt.compareSync(input, hash);
|
|
96
|
+
}
|
|
97
|
+
export function normalizeProxy(proxy, protocol = "http") {
|
|
98
|
+
proxy = proxy?.trim();
|
|
99
|
+
if (!proxy) return null;
|
|
100
|
+
const schemeMatch = proxy.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/i);
|
|
101
|
+
if (schemeMatch) {
|
|
102
|
+
protocol = schemeMatch[1];
|
|
103
|
+
proxy = schemeMatch[2];
|
|
104
|
+
}
|
|
105
|
+
let auth = "";
|
|
106
|
+
let body = proxy;
|
|
107
|
+
const atIdx = body.lastIndexOf("@");
|
|
108
|
+
if (atIdx !== -1) {
|
|
109
|
+
auth = body.slice(0, atIdx) + "@";
|
|
110
|
+
body = body.slice(atIdx + 1);
|
|
111
|
+
}
|
|
112
|
+
if (!auth) {
|
|
113
|
+
// Note: when the password itself is all-digit and port-shaped (e.g. "admin:1234:host:port"),
|
|
114
|
+
// the heuristic cannot distinguish auth-first from host-first ordering and may pick the wrong branch.
|
|
115
|
+
const parts = body.split(":");
|
|
116
|
+
const isPort = s => /^\d+$/.test(s) && +s >= 1 && +s <= 65535;
|
|
117
|
+
if (parts.length === 4) {
|
|
118
|
+
if (isPort(parts[3]) && !isPort(parts[1])) {
|
|
119
|
+
// user:pass:host:port
|
|
120
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
121
|
+
body = `${parts[2]}:${parts[3]}`;
|
|
122
|
+
} else {
|
|
123
|
+
// host:port:user:pass (default)
|
|
124
|
+
auth = `${parts[2]}:${parts[3]}@`;
|
|
125
|
+
body = `${parts[0]}:${parts[1]}`;
|
|
126
|
+
}
|
|
127
|
+
} else if (parts.length === 5) {
|
|
128
|
+
if (isPort(parts[3]) && isPort(parts[4]) && !isPort(parts[1])) {
|
|
129
|
+
// user:pass:host:portStart:portEnd
|
|
130
|
+
auth = `${parts[0]}:${parts[1]}@`;
|
|
131
|
+
body = `${parts[2]}:${parts[3]}:${parts[4]}`;
|
|
132
|
+
} else {
|
|
133
|
+
// host:portStart:portEnd:user:pass (default)
|
|
134
|
+
auth = `${parts[3]}:${parts[4]}@`;
|
|
135
|
+
body = `${parts[0]}:${parts[1]}:${parts[2]}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const parts = body.split(":");
|
|
140
|
+
if (parts.length === 3) {
|
|
141
|
+
const start = Number(parts[1]);
|
|
142
|
+
const end = Number(parts[2]);
|
|
143
|
+
if (Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end) {
|
|
144
|
+
body = `${parts[0]}:${crypto.randomInt(start, end + 1)}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return `${protocol}://${auth}${body}`;
|
|
148
|
+
}
|
|
149
|
+
export function parseProxy(proxy, protocol = "http") {
|
|
150
|
+
const normalized = normalizeProxy(proxy, protocol);
|
|
151
|
+
if (!normalized) return null;
|
|
152
|
+
const [scheme, rest] = normalized.split("://");
|
|
153
|
+
const atIdx = rest.lastIndexOf("@");
|
|
154
|
+
const authPart = atIdx === -1 ? null : rest.slice(0, atIdx);
|
|
155
|
+
const hostPart = atIdx === -1 ? rest : rest.slice(atIdx + 1);
|
|
156
|
+
const [host, port] = hostPart.split(":");
|
|
157
|
+
const result = {
|
|
158
|
+
protocol: scheme,
|
|
159
|
+
host,
|
|
160
|
+
port: parseInt(port, 10)
|
|
161
|
+
};
|
|
162
|
+
if (authPart !== null) {
|
|
163
|
+
const colonIdx = authPart.indexOf(":");
|
|
164
|
+
const [username, password] = colonIdx === -1 ? [authPart, ""] : [authPart.slice(0, colonIdx), authPart.slice(colonIdx + 1)];
|
|
165
|
+
result.auth = {
|
|
166
|
+
username,
|
|
167
|
+
password
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
export function proxyValue(rawProxy, replacements = {}) {
|
|
173
|
+
const list = splitTrim(rawProxy || "");
|
|
174
|
+
if (list.length === 0) return null;
|
|
175
|
+
const picked = list[randomInteger(0, list.length)];
|
|
176
|
+
const {
|
|
177
|
+
SESSION,
|
|
178
|
+
...rest
|
|
179
|
+
} = replacements;
|
|
180
|
+
let sessionValue;
|
|
181
|
+
if (SESSION === undefined) {
|
|
182
|
+
sessionValue = randomHex(8);
|
|
183
|
+
} else if (typeof SESSION === "function") {
|
|
184
|
+
sessionValue = SESSION();
|
|
185
|
+
} else {
|
|
186
|
+
sessionValue = seedHex(String(SESSION), 8);
|
|
187
|
+
}
|
|
188
|
+
let result = normalizeProxy(picked);
|
|
189
|
+
if (!result) return null;
|
|
190
|
+
result = result.replace("{SESSION}", sessionValue);
|
|
191
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
192
|
+
const v = typeof value === "function" ? value() : String(value);
|
|
193
|
+
result = result.replace(`{${key}}`, v);
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
export async function readJsonFile(filePath, defaultValue = {}) {
|
|
198
|
+
try {
|
|
199
|
+
const data = await fsp.readFile(filePath, 'utf8');
|
|
200
|
+
return JSON.parse(data);
|
|
201
|
+
} catch {
|
|
202
|
+
return defaultValue;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export function readJsonFileSync(filePath, defaultValue = {}) {
|
|
206
|
+
try {
|
|
207
|
+
const data = fs.readFileSync(filePath, 'utf8');
|
|
208
|
+
return JSON.parse(data);
|
|
209
|
+
} catch {
|
|
210
|
+
return defaultValue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export function writeJsonFile(filePath, data) {
|
|
214
|
+
const jsonData = JSON.stringify(data);
|
|
215
|
+
return fsp.writeFile(filePath, jsonData, 'utf8');
|
|
216
|
+
}
|
|
217
|
+
export function writeJsonFileSync(filePath, data) {
|
|
218
|
+
const jsonData = JSON.stringify(data);
|
|
219
|
+
return fs.writeFileSync(filePath, jsonData, 'utf8');
|
|
220
|
+
}
|
|
221
|
+
export async function clearDirectory(directoryPath, keepDir = true) {
|
|
222
|
+
await fsp.rm(directoryPath, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
});
|
|
226
|
+
if (keepDir) await fsp.mkdir(directoryPath, {
|
|
227
|
+
recursive: true
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
export function createNumberedDirs(mainDirectory, start = 0, end = 9) {
|
|
231
|
+
fs.mkdirSync(mainDirectory, {
|
|
232
|
+
recursive: true
|
|
233
|
+
});
|
|
234
|
+
for (let i = start; i <= end; i++) {
|
|
235
|
+
fs.mkdirSync(path.join(mainDirectory, `${i}`), {
|
|
236
|
+
recursive: true
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export async function executeCommand(command) {
|
|
241
|
+
const {
|
|
242
|
+
stdout
|
|
243
|
+
} = await execAsync(command);
|
|
244
|
+
return stdout.trim();
|
|
245
|
+
}
|
|
246
|
+
export function hostIp() {
|
|
247
|
+
for (const list of Object.values(networkInterfaces())) {
|
|
248
|
+
for (const alias of list) {
|
|
249
|
+
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.address.startsWith('192.168.') && !alias.internal) {
|
|
250
|
+
return alias.address;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return '127.0.0.1';
|
|
255
|
+
}
|
|
256
|
+
export function gitVersion() {
|
|
257
|
+
try {
|
|
258
|
+
const raw = execFileSync('git', ['show', '-s', '--format=%ct', 'HEAD'], {
|
|
259
|
+
encoding: 'utf8'
|
|
260
|
+
}).trim();
|
|
261
|
+
const timestamp = parseInt(raw, 10);
|
|
262
|
+
if (isNaN(timestamp)) return "1.0";
|
|
263
|
+
const iso = new Date(timestamp * 1000).toISOString();
|
|
264
|
+
return `${iso.slice(2, 4)}${iso.slice(5, 7)}${iso.slice(8, 10)}.${iso.slice(11, 13)}${iso.slice(14, 16)}`;
|
|
265
|
+
} catch {
|
|
266
|
+
return "1.0";
|
|
267
|
+
}
|
|
268
|
+
}
|
package/package.json
CHANGED
|
@@ -1,55 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "melperjs",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"cross-env
|
|
54
|
-
|
|
55
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "melperjs",
|
|
3
|
+
"description": "A compact JavaScript utility library for strings, objects, tokens, hashing, cookies, proxies, file I/O, shell, and more.",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"melperjs",
|
|
6
|
+
"utilities",
|
|
7
|
+
"utils",
|
|
8
|
+
"helpers",
|
|
9
|
+
"javascript",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"esm",
|
|
12
|
+
"cjs",
|
|
13
|
+
"string",
|
|
14
|
+
"object",
|
|
15
|
+
"random",
|
|
16
|
+
"token",
|
|
17
|
+
"uuid",
|
|
18
|
+
"bcrypt",
|
|
19
|
+
"hash",
|
|
20
|
+
"crypto",
|
|
21
|
+
"cookie",
|
|
22
|
+
"base64",
|
|
23
|
+
"proxy",
|
|
24
|
+
"file-system",
|
|
25
|
+
"shell"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/mahelbir/melperjs.git"
|
|
30
|
+
},
|
|
31
|
+
"author": "Mahmuthan Elbir",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"type": "module",
|
|
34
|
+
"files": [
|
|
35
|
+
"lib/**/*",
|
|
36
|
+
"docs/**/*"
|
|
37
|
+
],
|
|
38
|
+
"private": false,
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"import": "./lib/esm/index.mjs",
|
|
45
|
+
"require": "./lib/cjs/index.cjs"
|
|
46
|
+
},
|
|
47
|
+
"./node": {
|
|
48
|
+
"import": "./lib/esm/node.mjs",
|
|
49
|
+
"require": "./lib/cjs/node.cjs"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build:esm": "npx cross-env BABEL_ENV=esm babel ./src --out-dir ./lib/esm --out-file-extension .mjs",
|
|
54
|
+
"build:cjs": "npx cross-env BABEL_ENV=cjs babel ./src --out-dir ./lib/cjs --out-file-extension .cjs",
|
|
55
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
56
|
+
"test": "node --test 'tests/**/*.test.js'",
|
|
57
|
+
"prepublishOnly": "npm run build && npm test"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"bcryptjs": "^3.0.3",
|
|
61
|
+
"lodash": "4.18.1",
|
|
62
|
+
"set-cookie-parser": "^3.1.0",
|
|
63
|
+
"xss": "^1.0.15"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@babel/cli": "^7.28.6",
|
|
67
|
+
"@babel/core": "^7.29.0",
|
|
68
|
+
"@babel/preset-env": "^7.29.5",
|
|
69
|
+
"babel-plugin-replace-import-extension": "^1.1.5",
|
|
70
|
+
"babel-plugin-transform-import-meta": "^2.3.3",
|
|
71
|
+
"cross-env": "^10.1.0"
|
|
72
|
+
},
|
|
73
|
+
"version": "16.0.0"
|
|
74
|
+
}
|