@salespark/toolkit 2.1.19 → 2.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -3
- package/dist/index.cjs +460 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -1
- package/dist/index.d.ts +76 -1
- package/dist/index.js +455 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,13 +26,13 @@ npm i @salespark/toolkit
|
|
|
26
26
|
|
|
27
27
|
- **Array utilities**: chunk, uniqBy, deep equality, flatten, groupBy, etc.
|
|
28
28
|
- **Object utilities**: pick, omit, clean objects, deep merge, etc.
|
|
29
|
-
- **String utilities**: slugify, template fill, deburr, sanitize, capitalize words/sentences.
|
|
29
|
+
- **String utilities**: slugify, template fill, deburr, sanitize, capitalize words/sentences, SMS length.
|
|
30
30
|
- **Number utilities**: clamp, round, safe arithmetic/comparisons, safe parse (locale-aware), random digits, etc.
|
|
31
31
|
- **Function utilities**: debounce, throttle, safeJSONParse, formatCurrency, parseName, currency conversions, etc.
|
|
32
32
|
- **Defer utilities**: post-return microtask scheduling, non-critical timers, after-response hooks.
|
|
33
33
|
- **Boolean utilities**: safe boolean conversion with common representations
|
|
34
34
|
- **Validation utilities**: IBAN validator (ISO 13616), Portuguese tax ID validator
|
|
35
|
-
- **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, obfuscation helpers, reversible base36 code encoding/decoding
|
|
35
|
+
- **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, password generation, obfuscation helpers, reversible base36 code encoding/decoding
|
|
36
36
|
- **UUID utilities**: uuidv4 generator (RFC 4122)
|
|
37
37
|
- **Environment detection**: `isBrowser`, `isNode` runtime checks
|
|
38
38
|
|
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
clamp,
|
|
54
54
|
isBrowser,
|
|
55
55
|
toBool,
|
|
56
|
+
generatePassword,
|
|
57
|
+
smsLength,
|
|
56
58
|
uuidv4,
|
|
57
59
|
} from "@salespark/toolkit";
|
|
58
60
|
|
|
@@ -76,6 +78,12 @@ const safe = clamp(15, 0, 10); // 10
|
|
|
76
78
|
// Convert to boolean
|
|
77
79
|
const bool = toBool("yes"); // true
|
|
78
80
|
|
|
81
|
+
// Generate a password (sync by default)
|
|
82
|
+
const recoveryToken = generatePassword(96, false);
|
|
83
|
+
|
|
84
|
+
// SMS length and segmentation
|
|
85
|
+
const sms = smsLength("hello");
|
|
86
|
+
|
|
79
87
|
// Generate UUID v4
|
|
80
88
|
const id = uuidv4();
|
|
81
89
|
|
|
@@ -357,6 +365,16 @@ sanitize("<script>alert('hack')</script>Hello World!", 20);
|
|
|
357
365
|
// Result: "Hello World!"
|
|
358
366
|
```
|
|
359
367
|
|
|
368
|
+
**`smsLength(text: string, singleOverrides?, multiOverrides?): SmsLengthResult`** — Calculates SMS encoding, length, and segmentation details (overrides are optional).
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const sms = smsLength("hello");
|
|
372
|
+
// Result: { encoding: "GSM_7BIT", length: 5, messages: 1, remaining: 155, ... }
|
|
373
|
+
|
|
374
|
+
const custom = smsLength("hello", { GSM_7BIT: 10 }, { GSM_7BIT: 5 });
|
|
375
|
+
// Result: characterPerMessage uses overrides
|
|
376
|
+
```
|
|
377
|
+
|
|
360
378
|
### 🔢 Number Utilities
|
|
361
379
|
|
|
362
380
|
**`clamp(n: number, min: number, max: number): number`** — Restricts a number to be within the min and max bounds.
|
|
@@ -860,6 +878,34 @@ assessSecurityRisks([]);
|
|
|
860
878
|
// Result: { score: 0, level: "safe", recommendations: ["Content appears safe to use"] }
|
|
861
879
|
```
|
|
862
880
|
|
|
881
|
+
**`generatePassword(...)`** — Password generator with sync defaults and async-only options (`words`, `entropy`).
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
import { generatePassword } from "@salespark/toolkit";
|
|
885
|
+
|
|
886
|
+
// Sync usage (current default behavior)
|
|
887
|
+
const recoveryToken = generatePassword(96, false);
|
|
888
|
+
|
|
889
|
+
// Memorable password (override security recommendations if needed)
|
|
890
|
+
const memorable = generatePassword({
|
|
891
|
+
length: 12,
|
|
892
|
+
memorable: true,
|
|
893
|
+
ignoreSecurityRecommendations: true,
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// Async options (passphrase / deterministic entropy)
|
|
897
|
+
const passphrase = await generatePassword({
|
|
898
|
+
words: 4,
|
|
899
|
+
ignoreSecurityRecommendations: true,
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
const deterministic = await generatePassword({
|
|
903
|
+
length: 24,
|
|
904
|
+
entropy: "seed-123",
|
|
905
|
+
ignoreSecurityRecommendations: true,
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
863
909
|
**`encodeString(input: string, secret: string): SalesParkContract<any>`** — Base64-encodes a string and scrambles it with the provided secret (obfuscation only).
|
|
864
910
|
|
|
865
911
|
```javascript
|
|
@@ -1061,5 +1107,5 @@ MIT © [SalesPark](https://salespark.io)
|
|
|
1061
1107
|
|
|
1062
1108
|
---
|
|
1063
1109
|
|
|
1064
|
-
_Document version:
|
|
1110
|
+
_Document version: 18_
|
|
1065
1111
|
_Last update: 14-03-2026_
|
package/dist/index.cjs
CHANGED
|
@@ -2222,6 +2222,460 @@ var decodeBase36Code = (code, config) => {
|
|
|
2222
2222
|
}
|
|
2223
2223
|
};
|
|
2224
2224
|
|
|
2225
|
+
// src/utils/password.ts
|
|
2226
|
+
var hasCrypto = typeof globalThis !== "undefined" && typeof globalThis.crypto !== "undefined";
|
|
2227
|
+
var cryptoSource = hasCrypto ? globalThis.crypto : void 0;
|
|
2228
|
+
var textEncoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
2229
|
+
var MAX_RANDOM_BYTES = 65536;
|
|
2230
|
+
var VOWELS = "aeiou";
|
|
2231
|
+
var CONSONANTS = "bcdfghjklmnpqrstvwxyz";
|
|
2232
|
+
var CONSONANT = new RegExp(`[${CONSONANTS}]$`, "i");
|
|
2233
|
+
var DEFAULT_PATTERN = /\w/;
|
|
2234
|
+
var DEFAULT_LENGTH = 12;
|
|
2235
|
+
var MIN_ENTROPY_BITS = 64;
|
|
2236
|
+
var MIN_WORD_LENGTH = 3;
|
|
2237
|
+
var MAX_WORD_LENGTH = 7;
|
|
2238
|
+
var MIN_MEMORABLE_LENGTH = (() => {
|
|
2239
|
+
let bits = 0;
|
|
2240
|
+
let len = 0;
|
|
2241
|
+
let vowel = false;
|
|
2242
|
+
while (bits < MIN_ENTROPY_BITS) {
|
|
2243
|
+
bits += Math.log2(vowel ? VOWELS.length : CONSONANTS.length);
|
|
2244
|
+
vowel = !vowel;
|
|
2245
|
+
len += 1;
|
|
2246
|
+
}
|
|
2247
|
+
return len;
|
|
2248
|
+
})();
|
|
2249
|
+
var ensureCrypto = () => {
|
|
2250
|
+
if (!cryptoSource) {
|
|
2251
|
+
throw new Error("WebCrypto is required for password generation");
|
|
2252
|
+
}
|
|
2253
|
+
return cryptoSource;
|
|
2254
|
+
};
|
|
2255
|
+
var getRandomBytesSync = (length) => {
|
|
2256
|
+
if (!Number.isFinite(length) || length < 0) {
|
|
2257
|
+
throw new RangeError("length must be a non-negative finite number");
|
|
2258
|
+
}
|
|
2259
|
+
const crypto = ensureCrypto();
|
|
2260
|
+
const buffer = new Uint8Array(length);
|
|
2261
|
+
for (let offset = 0; offset < length; offset += MAX_RANDOM_BYTES) {
|
|
2262
|
+
const end = Math.min(offset + MAX_RANDOM_BYTES, length);
|
|
2263
|
+
crypto.getRandomValues(buffer.subarray(offset, end));
|
|
2264
|
+
}
|
|
2265
|
+
return buffer;
|
|
2266
|
+
};
|
|
2267
|
+
var getRandomBytes = async (length) => getRandomBytesSync(length);
|
|
2268
|
+
var createDeterministicRandomBytes = async (entropy, source = ensureCrypto()) => {
|
|
2269
|
+
if (entropy.length === 0) {
|
|
2270
|
+
throw new RangeError("entropy must not be empty");
|
|
2271
|
+
}
|
|
2272
|
+
if (!source.subtle) {
|
|
2273
|
+
throw new Error("WebCrypto subtle is required for deterministic entropy");
|
|
2274
|
+
}
|
|
2275
|
+
const keyData = new Uint8Array(entropy).buffer;
|
|
2276
|
+
const key = await source.subtle.importKey(
|
|
2277
|
+
"raw",
|
|
2278
|
+
keyData,
|
|
2279
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
2280
|
+
false,
|
|
2281
|
+
["sign"]
|
|
2282
|
+
);
|
|
2283
|
+
let counter = 0n;
|
|
2284
|
+
const counterBytes = new Uint8Array(8);
|
|
2285
|
+
const counterView = new DataView(counterBytes.buffer);
|
|
2286
|
+
const deterministicRandomBytes = async (length) => {
|
|
2287
|
+
if (!Number.isFinite(length) || length < 0) {
|
|
2288
|
+
throw new RangeError("length must be a non-negative finite number");
|
|
2289
|
+
}
|
|
2290
|
+
const buffer = new Uint8Array(length);
|
|
2291
|
+
let offset = 0;
|
|
2292
|
+
while (offset < length) {
|
|
2293
|
+
counterView.setBigUint64(0, counter, false);
|
|
2294
|
+
counter += 1n;
|
|
2295
|
+
const block = new Uint8Array(
|
|
2296
|
+
await source.subtle.sign("HMAC", key, counterBytes)
|
|
2297
|
+
);
|
|
2298
|
+
const take = Math.min(block.length, length - offset);
|
|
2299
|
+
buffer.set(block.subarray(0, take), offset);
|
|
2300
|
+
offset += take;
|
|
2301
|
+
}
|
|
2302
|
+
return buffer;
|
|
2303
|
+
};
|
|
2304
|
+
return deterministicRandomBytes;
|
|
2305
|
+
};
|
|
2306
|
+
var randomIntSync = (min, max, randomBytes = getRandomBytesSync) => {
|
|
2307
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
2308
|
+
throw new RangeError("min and max must be finite numbers");
|
|
2309
|
+
}
|
|
2310
|
+
if (max <= min) {
|
|
2311
|
+
throw new RangeError("max must be greater than min");
|
|
2312
|
+
}
|
|
2313
|
+
const range = max - min;
|
|
2314
|
+
if (range === 1) {
|
|
2315
|
+
return min;
|
|
2316
|
+
}
|
|
2317
|
+
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
|
|
2318
|
+
const maxValue = 256 ** bytesNeeded;
|
|
2319
|
+
const limit = maxValue - maxValue % range;
|
|
2320
|
+
let value = limit;
|
|
2321
|
+
while (value >= limit) {
|
|
2322
|
+
const bytes = randomBytes(bytesNeeded);
|
|
2323
|
+
value = 0;
|
|
2324
|
+
for (const byte of bytes) {
|
|
2325
|
+
value = value * 256 + byte;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return min + value % range;
|
|
2329
|
+
};
|
|
2330
|
+
var randomInt = async (min, max, randomBytes = getRandomBytes) => {
|
|
2331
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
2332
|
+
throw new RangeError("min and max must be finite numbers");
|
|
2333
|
+
}
|
|
2334
|
+
if (max <= min) {
|
|
2335
|
+
throw new RangeError("max must be greater than min");
|
|
2336
|
+
}
|
|
2337
|
+
const range = max - min;
|
|
2338
|
+
if (range === 1) {
|
|
2339
|
+
return min;
|
|
2340
|
+
}
|
|
2341
|
+
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
|
|
2342
|
+
const maxValue = 256 ** bytesNeeded;
|
|
2343
|
+
const limit = maxValue - maxValue % range;
|
|
2344
|
+
let value = limit;
|
|
2345
|
+
while (value >= limit) {
|
|
2346
|
+
const bytes = await randomBytes(bytesNeeded);
|
|
2347
|
+
value = 0;
|
|
2348
|
+
for (const byte of bytes) {
|
|
2349
|
+
value = value * 256 + byte;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return min + value % range;
|
|
2353
|
+
};
|
|
2354
|
+
var buildValidChars = (pattern) => {
|
|
2355
|
+
const chars = [];
|
|
2356
|
+
for (let i = 33; i <= 126; i += 1) {
|
|
2357
|
+
const char = String.fromCharCode(i);
|
|
2358
|
+
if (pattern.test(char)) {
|
|
2359
|
+
chars.push(char);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (chars.length === 0) {
|
|
2363
|
+
throw new Error(
|
|
2364
|
+
`Could not find characters that match the password pattern ${pattern}. Patterns must match individual characters, not the password as a whole.`
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
return chars;
|
|
2368
|
+
};
|
|
2369
|
+
var estimatePatternEntropy = (alphabetSize, length, prefixLength) => {
|
|
2370
|
+
const bitsPerChar = alphabetSize > 1 ? Math.log2(alphabetSize) : 0;
|
|
2371
|
+
return {
|
|
2372
|
+
entropyBits: bitsPerChar * Math.max(0, length - prefixLength),
|
|
2373
|
+
recommendedLength: bitsPerChar > 0 ? prefixLength + Math.ceil(MIN_ENTROPY_BITS / bitsPerChar) : null
|
|
2374
|
+
};
|
|
2375
|
+
};
|
|
2376
|
+
var estimateMemorableEntropy = (length, prefix) => {
|
|
2377
|
+
const effectiveLength = Math.max(0, length - prefix.length);
|
|
2378
|
+
let entropyBits = 0;
|
|
2379
|
+
let expectsVowel = CONSONANT.test(prefix);
|
|
2380
|
+
for (let i = 0; i < effectiveLength; i += 1) {
|
|
2381
|
+
entropyBits += Math.log2(expectsVowel ? VOWELS.length : CONSONANTS.length);
|
|
2382
|
+
expectsVowel = !expectsVowel;
|
|
2383
|
+
}
|
|
2384
|
+
let recommendedLength = prefix.length;
|
|
2385
|
+
let bits = 0;
|
|
2386
|
+
expectsVowel = CONSONANT.test(prefix);
|
|
2387
|
+
while (bits < MIN_ENTROPY_BITS) {
|
|
2388
|
+
bits += Math.log2(expectsVowel ? VOWELS.length : CONSONANTS.length);
|
|
2389
|
+
expectsVowel = !expectsVowel;
|
|
2390
|
+
recommendedLength += 1;
|
|
2391
|
+
}
|
|
2392
|
+
return { entropyBits, recommendedLength };
|
|
2393
|
+
};
|
|
2394
|
+
var buildMemorableSync = (length, startsWithVowel, nextInt) => {
|
|
2395
|
+
let expectsVowel = startsWithVowel;
|
|
2396
|
+
let result = "";
|
|
2397
|
+
for (let i = 0; i < length; i += 1) {
|
|
2398
|
+
const alphabet = expectsVowel ? VOWELS : CONSONANTS;
|
|
2399
|
+
result += alphabet[nextInt(0, alphabet.length)];
|
|
2400
|
+
expectsVowel = !expectsVowel;
|
|
2401
|
+
}
|
|
2402
|
+
return result;
|
|
2403
|
+
};
|
|
2404
|
+
var buildMemorable = async (length, startsWithVowel, nextInt) => {
|
|
2405
|
+
let expectsVowel = startsWithVowel;
|
|
2406
|
+
let result = "";
|
|
2407
|
+
for (let i = 0; i < length; i += 1) {
|
|
2408
|
+
const alphabet = expectsVowel ? VOWELS : CONSONANTS;
|
|
2409
|
+
result += alphabet[await nextInt(0, alphabet.length)];
|
|
2410
|
+
expectsVowel = !expectsVowel;
|
|
2411
|
+
}
|
|
2412
|
+
return result;
|
|
2413
|
+
};
|
|
2414
|
+
var buildWordLengths = async (count, nextInt, targetLength) => {
|
|
2415
|
+
const lengths = [];
|
|
2416
|
+
let total = 0;
|
|
2417
|
+
for (let i = 0; i < count; i += 1) {
|
|
2418
|
+
const len = await nextInt(MIN_WORD_LENGTH, MAX_WORD_LENGTH + 1);
|
|
2419
|
+
lengths.push(len);
|
|
2420
|
+
total += len;
|
|
2421
|
+
}
|
|
2422
|
+
if (targetLength !== void 0 && total < targetLength) {
|
|
2423
|
+
const adjustable = [];
|
|
2424
|
+
for (let i = 0; i < count; i += 1) {
|
|
2425
|
+
if (lengths[i] < MAX_WORD_LENGTH) adjustable.push(i);
|
|
2426
|
+
}
|
|
2427
|
+
let remaining = targetLength - total;
|
|
2428
|
+
while (remaining > 0 && adjustable.length > 0) {
|
|
2429
|
+
const pick2 = await nextInt(0, adjustable.length);
|
|
2430
|
+
const wordIdx = adjustable[pick2];
|
|
2431
|
+
lengths[wordIdx] = lengths[wordIdx] + 1;
|
|
2432
|
+
remaining -= 1;
|
|
2433
|
+
if (lengths[wordIdx] >= MAX_WORD_LENGTH) {
|
|
2434
|
+
adjustable.splice(pick2, 1);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
return lengths;
|
|
2439
|
+
};
|
|
2440
|
+
var parseEntropy = (entropy) => {
|
|
2441
|
+
if (typeof entropy === "string") {
|
|
2442
|
+
if (textEncoder) return textEncoder.encode(entropy);
|
|
2443
|
+
if (typeof Buffer !== "undefined") return Buffer.from(entropy, "utf8");
|
|
2444
|
+
throw new Error("TextEncoder is required for entropy strings");
|
|
2445
|
+
}
|
|
2446
|
+
if (entropy instanceof Uint8Array) {
|
|
2447
|
+
return entropy;
|
|
2448
|
+
}
|
|
2449
|
+
throw new TypeError("entropy must be a Uint8Array or string");
|
|
2450
|
+
};
|
|
2451
|
+
var validateOptions = (options) => {
|
|
2452
|
+
const length = options?.length ?? DEFAULT_LENGTH;
|
|
2453
|
+
const memorable = options?.memorable ?? false;
|
|
2454
|
+
const pattern = options?.pattern ?? DEFAULT_PATTERN;
|
|
2455
|
+
const prefix = String(options?.prefix ?? "");
|
|
2456
|
+
const ignoreSecurityRecommendations = options?.ignoreSecurityRecommendations ?? false;
|
|
2457
|
+
const words = options?.words;
|
|
2458
|
+
if (!Number.isSafeInteger(length)) {
|
|
2459
|
+
throw new RangeError("length must be a safe integer");
|
|
2460
|
+
}
|
|
2461
|
+
if (length < 0) {
|
|
2462
|
+
throw new RangeError("length must be a non-negative integer");
|
|
2463
|
+
}
|
|
2464
|
+
if (!(pattern instanceof RegExp)) {
|
|
2465
|
+
throw new TypeError("pattern must be a RegExp");
|
|
2466
|
+
}
|
|
2467
|
+
if (words !== void 0) {
|
|
2468
|
+
if (!Number.isSafeInteger(words)) {
|
|
2469
|
+
throw new RangeError("words must be a safe integer");
|
|
2470
|
+
}
|
|
2471
|
+
if (words <= 0) {
|
|
2472
|
+
throw new RangeError("words must be a positive integer");
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (words !== void 0 && prefix !== "") {
|
|
2476
|
+
throw new Error("prefix is not supported when words are enabled");
|
|
2477
|
+
}
|
|
2478
|
+
return {
|
|
2479
|
+
length,
|
|
2480
|
+
memorable,
|
|
2481
|
+
pattern,
|
|
2482
|
+
prefix,
|
|
2483
|
+
ignoreSecurityRecommendations,
|
|
2484
|
+
words
|
|
2485
|
+
};
|
|
2486
|
+
};
|
|
2487
|
+
var generatePasswordSync = (options) => {
|
|
2488
|
+
const {
|
|
2489
|
+
length,
|
|
2490
|
+
memorable,
|
|
2491
|
+
pattern,
|
|
2492
|
+
prefix,
|
|
2493
|
+
ignoreSecurityRecommendations,
|
|
2494
|
+
words
|
|
2495
|
+
} = validateOptions(options);
|
|
2496
|
+
if (words !== void 0) {
|
|
2497
|
+
throw new Error("words requires async password generation");
|
|
2498
|
+
}
|
|
2499
|
+
const nextInt = (min, max) => randomIntSync(min, max, getRandomBytesSync);
|
|
2500
|
+
if (memorable) {
|
|
2501
|
+
if (!ignoreSecurityRecommendations) {
|
|
2502
|
+
const estimate = estimateMemorableEntropy(length, prefix);
|
|
2503
|
+
if (estimate.entropyBits < MIN_ENTROPY_BITS) {
|
|
2504
|
+
throw new Error(
|
|
2505
|
+
`Security recommendation: estimated entropy ${estimate.entropyBits.toFixed(
|
|
2506
|
+
1
|
|
2507
|
+
)} bits is below ${MIN_ENTROPY_BITS} bits. Use length >= ${estimate.recommendedLength} or set memorable: false. To override, pass { ignoreSecurityRecommendations: true }.`
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
const charCount = Math.max(0, length - prefix.length);
|
|
2512
|
+
return prefix + buildMemorableSync(charCount, CONSONANT.test(prefix), nextInt);
|
|
2513
|
+
}
|
|
2514
|
+
const validChars = buildValidChars(pattern);
|
|
2515
|
+
if (!ignoreSecurityRecommendations) {
|
|
2516
|
+
const estimate = estimatePatternEntropy(
|
|
2517
|
+
validChars.length,
|
|
2518
|
+
length,
|
|
2519
|
+
prefix.length
|
|
2520
|
+
);
|
|
2521
|
+
if (estimate.entropyBits < MIN_ENTROPY_BITS) {
|
|
2522
|
+
const recommendation = estimate.recommendedLength === null ? "Use a broader pattern to increase the character set." : `Use length >= ${estimate.recommendedLength} or broaden the pattern.`;
|
|
2523
|
+
throw new Error(
|
|
2524
|
+
`Security recommendation: estimated entropy ${estimate.entropyBits.toFixed(
|
|
2525
|
+
1
|
|
2526
|
+
)} bits is below ${MIN_ENTROPY_BITS} bits. ${recommendation} To override, pass { ignoreSecurityRecommendations: true }.`
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
let result = prefix;
|
|
2531
|
+
while (result.length < length) {
|
|
2532
|
+
result += validChars[nextInt(0, validChars.length)];
|
|
2533
|
+
}
|
|
2534
|
+
return result;
|
|
2535
|
+
};
|
|
2536
|
+
var generatePasswordAsync = async (options) => {
|
|
2537
|
+
const {
|
|
2538
|
+
length,
|
|
2539
|
+
memorable,
|
|
2540
|
+
pattern,
|
|
2541
|
+
prefix,
|
|
2542
|
+
ignoreSecurityRecommendations,
|
|
2543
|
+
words
|
|
2544
|
+
} = validateOptions(options);
|
|
2545
|
+
const entropy = options?.entropy;
|
|
2546
|
+
let entropyBytes;
|
|
2547
|
+
if (entropy !== void 0) {
|
|
2548
|
+
entropyBytes = parseEntropy(entropy);
|
|
2549
|
+
}
|
|
2550
|
+
const randomBytes = entropyBytes ? await createDeterministicRandomBytes(entropyBytes) : getRandomBytes;
|
|
2551
|
+
const nextInt = (min, max) => randomInt(min, max, randomBytes);
|
|
2552
|
+
if (words !== void 0) {
|
|
2553
|
+
if (!ignoreSecurityRecommendations && words * MAX_WORD_LENGTH < MIN_MEMORABLE_LENGTH) {
|
|
2554
|
+
const recommendedWords = Math.ceil(
|
|
2555
|
+
MIN_MEMORABLE_LENGTH / MAX_WORD_LENGTH
|
|
2556
|
+
);
|
|
2557
|
+
throw new Error(
|
|
2558
|
+
`Security recommendation: word count ${words} cannot reach ${MIN_ENTROPY_BITS} bits with ${MIN_WORD_LENGTH}-${MAX_WORD_LENGTH} letter words. Use words >= ${recommendedWords}. To override, pass { ignoreSecurityRecommendations: true }.`
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
const targetLength = ignoreSecurityRecommendations ? void 0 : MIN_MEMORABLE_LENGTH;
|
|
2562
|
+
const lengths = await buildWordLengths(words, nextInt, targetLength);
|
|
2563
|
+
const wordsList = [];
|
|
2564
|
+
for (const wordLength of lengths) {
|
|
2565
|
+
wordsList.push(await buildMemorable(wordLength, false, nextInt));
|
|
2566
|
+
}
|
|
2567
|
+
return wordsList.join(" ");
|
|
2568
|
+
}
|
|
2569
|
+
if (memorable) {
|
|
2570
|
+
if (!ignoreSecurityRecommendations) {
|
|
2571
|
+
const estimate = estimateMemorableEntropy(length, prefix);
|
|
2572
|
+
if (estimate.entropyBits < MIN_ENTROPY_BITS) {
|
|
2573
|
+
throw new Error(
|
|
2574
|
+
`Security recommendation: estimated entropy ${estimate.entropyBits.toFixed(
|
|
2575
|
+
1
|
|
2576
|
+
)} bits is below ${MIN_ENTROPY_BITS} bits. Use length >= ${estimate.recommendedLength} or set memorable: false. To override, pass { ignoreSecurityRecommendations: true }.`
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const charCount = Math.max(0, length - prefix.length);
|
|
2581
|
+
return prefix + await buildMemorable(charCount, CONSONANT.test(prefix), nextInt);
|
|
2582
|
+
}
|
|
2583
|
+
const validChars = buildValidChars(pattern);
|
|
2584
|
+
if (!ignoreSecurityRecommendations) {
|
|
2585
|
+
const estimate = estimatePatternEntropy(
|
|
2586
|
+
validChars.length,
|
|
2587
|
+
length,
|
|
2588
|
+
prefix.length
|
|
2589
|
+
);
|
|
2590
|
+
if (estimate.entropyBits < MIN_ENTROPY_BITS) {
|
|
2591
|
+
const recommendation = estimate.recommendedLength === null ? "Use a broader pattern to increase the character set." : `Use length >= ${estimate.recommendedLength} or broaden the pattern.`;
|
|
2592
|
+
throw new Error(
|
|
2593
|
+
`Security recommendation: estimated entropy ${estimate.entropyBits.toFixed(
|
|
2594
|
+
1
|
|
2595
|
+
)} bits is below ${MIN_ENTROPY_BITS} bits. ${recommendation} To override, pass { ignoreSecurityRecommendations: true }.`
|
|
2596
|
+
);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
let result = prefix;
|
|
2600
|
+
while (result.length < length) {
|
|
2601
|
+
result += validChars[await nextInt(0, validChars.length)];
|
|
2602
|
+
}
|
|
2603
|
+
return result;
|
|
2604
|
+
};
|
|
2605
|
+
function generatePassword(lengthOrOptions, memorable, pattern, prefix) {
|
|
2606
|
+
if (typeof lengthOrOptions === "object") {
|
|
2607
|
+
const options2 = lengthOrOptions;
|
|
2608
|
+
const requiresAsync = options2.words !== void 0 || options2.entropy !== void 0;
|
|
2609
|
+
return requiresAsync ? generatePasswordAsync(options2) : generatePasswordSync(options2);
|
|
2610
|
+
}
|
|
2611
|
+
const options = {};
|
|
2612
|
+
if (lengthOrOptions !== void 0) options.length = lengthOrOptions;
|
|
2613
|
+
if (memorable !== void 0) options.memorable = memorable;
|
|
2614
|
+
if (pattern !== void 0) options.pattern = pattern;
|
|
2615
|
+
if (prefix !== void 0) options.prefix = prefix;
|
|
2616
|
+
return generatePasswordSync(options);
|
|
2617
|
+
}
|
|
2618
|
+
var generatePasswordWithOptions = (options) => {
|
|
2619
|
+
const requiresAsync = options?.words !== void 0 || options?.entropy !== void 0;
|
|
2620
|
+
return requiresAsync ? generatePasswordAsync(options) : generatePasswordSync(options);
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2623
|
+
// src/utils/sms.ts
|
|
2624
|
+
var GSM_7BIT_REGEXP = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$/;
|
|
2625
|
+
var GSM_7BIT_EXT_REGEXP = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà\^{}\\\[\]~|€]*$/;
|
|
2626
|
+
var GSM_7BIT_EXT_CHAR_REGEXP = /[\^{}\\\[\]~|€]/g;
|
|
2627
|
+
var messageLength = {
|
|
2628
|
+
GSM_7BIT: 160,
|
|
2629
|
+
GSM_7BIT_EXT: 160,
|
|
2630
|
+
UTF16: 70
|
|
2631
|
+
};
|
|
2632
|
+
var multiMessageLength = {
|
|
2633
|
+
GSM_7BIT: 153,
|
|
2634
|
+
GSM_7BIT_EXT: 153,
|
|
2635
|
+
UTF16: 67
|
|
2636
|
+
};
|
|
2637
|
+
var detectEncoding = (text) => {
|
|
2638
|
+
if (GSM_7BIT_REGEXP.test(text)) {
|
|
2639
|
+
return "GSM_7BIT";
|
|
2640
|
+
}
|
|
2641
|
+
if (GSM_7BIT_EXT_REGEXP.test(text)) {
|
|
2642
|
+
return "GSM_7BIT_EXT";
|
|
2643
|
+
}
|
|
2644
|
+
return "UTF16";
|
|
2645
|
+
};
|
|
2646
|
+
var smsLength = (text, singleOverrides, multiOverrides) => {
|
|
2647
|
+
const singleLengths = { ...messageLength, ...singleOverrides };
|
|
2648
|
+
const multiLengths = { ...multiMessageLength, ...multiOverrides };
|
|
2649
|
+
if (text.length === 0) {
|
|
2650
|
+
return {
|
|
2651
|
+
encoding: "GSM_7BIT",
|
|
2652
|
+
length: 0,
|
|
2653
|
+
characterPerMessage: singleLengths.GSM_7BIT,
|
|
2654
|
+
inCurrentMessage: 0,
|
|
2655
|
+
remaining: singleLengths.GSM_7BIT,
|
|
2656
|
+
messages: 0
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
const encoding = detectEncoding(text);
|
|
2660
|
+
const extCharCount = encoding === "GSM_7BIT_EXT" ? (text.match(GSM_7BIT_EXT_CHAR_REGEXP) ?? []).length : 0;
|
|
2661
|
+
const length = text.length + extCharCount;
|
|
2662
|
+
let characterPerMessage = singleLengths[encoding];
|
|
2663
|
+
if (length > characterPerMessage) {
|
|
2664
|
+
characterPerMessage = multiLengths[encoding];
|
|
2665
|
+
}
|
|
2666
|
+
const messages = Math.ceil(length / characterPerMessage);
|
|
2667
|
+
const inCurrentMessage = length - characterPerMessage * (messages - 1);
|
|
2668
|
+
const remaining = characterPerMessage * messages - length;
|
|
2669
|
+
return {
|
|
2670
|
+
encoding,
|
|
2671
|
+
length,
|
|
2672
|
+
characterPerMessage,
|
|
2673
|
+
inCurrentMessage,
|
|
2674
|
+
remaining,
|
|
2675
|
+
messages
|
|
2676
|
+
};
|
|
2677
|
+
};
|
|
2678
|
+
|
|
2225
2679
|
// src/index.ts
|
|
2226
2680
|
var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
2227
2681
|
var isNode = typeof process !== "undefined" && !!process.versions?.node;
|
|
@@ -2230,6 +2684,9 @@ Object.defineProperty(exports, "uuidv4", {
|
|
|
2230
2684
|
enumerable: true,
|
|
2231
2685
|
get: function () { return uuid.v4; }
|
|
2232
2686
|
});
|
|
2687
|
+
exports.GSM_7BIT_EXT_CHAR_REGEXP = GSM_7BIT_EXT_CHAR_REGEXP;
|
|
2688
|
+
exports.GSM_7BIT_EXT_REGEXP = GSM_7BIT_EXT_REGEXP;
|
|
2689
|
+
exports.GSM_7BIT_REGEXP = GSM_7BIT_REGEXP;
|
|
2233
2690
|
exports.addSpaceBetweenNumbers = addSpaceBetweenNumbers;
|
|
2234
2691
|
exports.addThousandsSpace = addThousandsSpace;
|
|
2235
2692
|
exports.areArraysDeepEqualUnordered = areArraysDeepEqualUnordered;
|
|
@@ -2267,6 +2724,8 @@ exports.formatBytes = formatBytes;
|
|
|
2267
2724
|
exports.formatCurrency = formatCurrency;
|
|
2268
2725
|
exports.formatCurrencyPro = formatCurrencyPro;
|
|
2269
2726
|
exports.formatDecimalNumber = formatDecimalNumber;
|
|
2727
|
+
exports.generatePassword = generatePassword;
|
|
2728
|
+
exports.generatePasswordWithOptions = generatePasswordWithOptions;
|
|
2270
2729
|
exports.getStringSimilarity = getStringSimilarity;
|
|
2271
2730
|
exports.groupBy = groupBy;
|
|
2272
2731
|
exports.hasNilOrEmpty = hasNilOrEmpty;
|
|
@@ -2321,6 +2780,7 @@ exports.sanitizeMarkdown = sanitizeMarkdown;
|
|
|
2321
2780
|
exports.sentenceCase = sentenceCase;
|
|
2322
2781
|
exports.shuffle = shuffle;
|
|
2323
2782
|
exports.slugify = slugify;
|
|
2783
|
+
exports.smsLength = smsLength;
|
|
2324
2784
|
exports.sortBy = sortBy;
|
|
2325
2785
|
exports.stringSimilarity = stringSimilarity;
|
|
2326
2786
|
exports.symbolToCurrency = symbolToCurrency;
|