@salespark/toolkit 2.1.14 → 2.1.16
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 +50 -6
- package/dist/index.cjs +161 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js +157 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ npm i @salespark/toolkit
|
|
|
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
|
|
35
|
+
- **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, obfuscation helpers
|
|
36
36
|
- **Environment detection**: `isBrowser`, `isNode` runtime checks
|
|
37
37
|
|
|
38
38
|
---
|
|
@@ -135,7 +135,7 @@ uniqBy(
|
|
|
135
135
|
{ id: 2, name: "Jane" },
|
|
136
136
|
{ id: 1, name: "John" },
|
|
137
137
|
],
|
|
138
|
-
(x) => x.id
|
|
138
|
+
(x) => x.id,
|
|
139
139
|
);
|
|
140
140
|
// Result: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]
|
|
141
141
|
```
|
|
@@ -177,7 +177,7 @@ groupBy(
|
|
|
177
177
|
{ type: "fruit", name: "banana" },
|
|
178
178
|
{ type: "veggie", name: "carrot" },
|
|
179
179
|
],
|
|
180
|
-
"type"
|
|
180
|
+
"type",
|
|
181
181
|
);
|
|
182
182
|
// Result: {fruit: [{type: 'fruit', name: 'apple'}, {type: 'fruit', name: 'banana'}], veggie: [{type: 'veggie', name: 'carrot'}]}
|
|
183
183
|
```
|
|
@@ -218,7 +218,7 @@ pluck(
|
|
|
218
218
|
{ name: "John", age: 30 },
|
|
219
219
|
{ name: "Jane", age: 25 },
|
|
220
220
|
],
|
|
221
|
-
"name"
|
|
221
|
+
"name",
|
|
222
222
|
);
|
|
223
223
|
// Result: ['John', 'Jane']
|
|
224
224
|
```
|
|
@@ -290,7 +290,7 @@ cleanObject(
|
|
|
290
290
|
note: "",
|
|
291
291
|
tags: ["", "ok"],
|
|
292
292
|
},
|
|
293
|
-
true
|
|
293
|
+
true,
|
|
294
294
|
);
|
|
295
295
|
// Result: {name: 'John', tags: ['ok']}
|
|
296
296
|
```
|
|
@@ -651,6 +651,26 @@ formatCurrency(null);
|
|
|
651
651
|
// Result: "0,00 €"
|
|
652
652
|
```
|
|
653
653
|
|
|
654
|
+
**`formatCurrencyPro(value: number | string | null | undefined, options?: { withoutCurrencySymbol?: boolean; currency?: string; locale?: string; redact?: boolean; redactChar?: string }): string`** — Same as `formatCurrency`, but uses a single options object and supports redaction.
|
|
655
|
+
|
|
656
|
+
```javascript
|
|
657
|
+
// Same defaults as formatCurrency
|
|
658
|
+
formatCurrencyPro(1234.56);
|
|
659
|
+
// Result: "1234,56 €"
|
|
660
|
+
|
|
661
|
+
// Options object
|
|
662
|
+
formatCurrencyPro(1234.56, { currency: "USD", locale: "en-US" });
|
|
663
|
+
// Result: "$1,234.56"
|
|
664
|
+
|
|
665
|
+
// Redacted output (mask digits)
|
|
666
|
+
formatCurrencyPro(1234.56, { redact: true });
|
|
667
|
+
// Result: "****,** €"
|
|
668
|
+
|
|
669
|
+
// Custom redaction character
|
|
670
|
+
formatCurrencyPro(1234.56, { redact: true, redactChar: "•" });
|
|
671
|
+
// Result: "••••,•• €"
|
|
672
|
+
```
|
|
673
|
+
|
|
654
674
|
**`parseName(name: string | null | undefined): {firstName: string, lastName: string}`** — Extracts first and last name from a full name string. Handles single names, empty inputs, and multi-word names intelligently.
|
|
655
675
|
|
|
656
676
|
```javascript
|
|
@@ -767,7 +787,7 @@ checkMarkdownSecurity('<script>alert("xss")</script>');
|
|
|
767
787
|
|
|
768
788
|
// Content with multiple threats
|
|
769
789
|
checkMarkdownSecurity(
|
|
770
|
-
'<iframe src="evil.com"></iframe><div onclick="bad()">test</div>'
|
|
790
|
+
'<iframe src="evil.com"></iframe><div onclick="bad()">test</div>',
|
|
771
791
|
);
|
|
772
792
|
// Result: Multiple risks detected, sorted by severity
|
|
773
793
|
```
|
|
@@ -816,6 +836,30 @@ assessSecurityRisks([]);
|
|
|
816
836
|
// Result: { score: 0, level: "safe", recommendations: ["Content appears safe to use"] }
|
|
817
837
|
```
|
|
818
838
|
|
|
839
|
+
**`scrambleString(value: string, secret: string): SalesParkContract<object>`** — Scrambles a string using a repeating secret (XOR) and Base64. Reversible obfuscation only (not crypto).
|
|
840
|
+
|
|
841
|
+
```javascript
|
|
842
|
+
const scrambled = scrambleString("Hello", "secret");
|
|
843
|
+
// Result: { status: true, data: "..." }
|
|
844
|
+
|
|
845
|
+
const original = descrambleString(scrambled.data, "secret");
|
|
846
|
+
// Result: { status: true, data: "Hello" }
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
**`descrambleString(value: string, secret: string): SalesParkContract<object>`** — Reverses `scrambleString` using the same secret.
|
|
850
|
+
|
|
851
|
+
**`encodeObject(input: object, secret: string): SalesParkContract<object>`** — JSON-stringifies an object, Base64-encodes it, and scrambles the result (obfuscation only).
|
|
852
|
+
|
|
853
|
+
```javascript
|
|
854
|
+
const encoded = encodeObject({ id: 1, name: "Ana" }, "secret");
|
|
855
|
+
// Result: { status: true, data: "..." }
|
|
856
|
+
|
|
857
|
+
const decoded = decodeObject(encoded.data, "secret");
|
|
858
|
+
// Result: { status: true, data: { id: 1, name: "Ana" } }
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**`decodeObject(encoded: string, secret: string): SalesParkContract<object>`** — Reverses `encodeObject` using the same secret.
|
|
862
|
+
|
|
819
863
|
### ✅ Validation Utilities
|
|
820
864
|
|
|
821
865
|
**`isPTTaxId(value: string | number): boolean`** — Validates Portuguese Tax ID (NIF) with MOD-11 algorithm and format checking.
|
package/dist/index.cjs
CHANGED
|
@@ -566,7 +566,7 @@ var formatBytes = (bytes, si = false, dp = 1, noSpace = false) => {
|
|
|
566
566
|
value /= thresh;
|
|
567
567
|
++u;
|
|
568
568
|
} while (Math.round(Math.abs(value) * r) / r >= thresh && u < units.length - 1);
|
|
569
|
-
return `${value.toFixed(dp)}${noSpace ? "" : " "}${units[u]}`;
|
|
569
|
+
return `${value.toFixed(dp)}${noSpace === true ? "" : " "}${units[u]}`;
|
|
570
570
|
};
|
|
571
571
|
var humanFileSize = formatBytes;
|
|
572
572
|
var levenshtein = (a, b) => {
|
|
@@ -658,6 +658,35 @@ var formatCurrency = (value, withoutCurrencySymbol = false, currency = "EUR", lo
|
|
|
658
658
|
return withoutCurrencySymbol ? formatted : `${formatted} \u20AC`;
|
|
659
659
|
}
|
|
660
660
|
};
|
|
661
|
+
var formatCurrencyPro = (value, options = {}) => {
|
|
662
|
+
const resolvedWithoutSymbol = options.withoutCurrencySymbol === true;
|
|
663
|
+
const resolvedCurrency = options.currency ?? "EUR";
|
|
664
|
+
const resolvedLocale = options.locale ?? "pt-PT";
|
|
665
|
+
const redact = options.redact === true;
|
|
666
|
+
const redactChar = options.redactChar ? options.redactChar : "*";
|
|
667
|
+
try {
|
|
668
|
+
const numValue = value === void 0 || value === null || value === "" ? 0 : Number(value);
|
|
669
|
+
if (isNaN(numValue) || !isFinite(numValue)) {
|
|
670
|
+
const fallback = resolvedWithoutSymbol ? "0,00" : "0,00 \u20AC";
|
|
671
|
+
return redact ? fallback.replace(/\d/g, redactChar) : fallback;
|
|
672
|
+
}
|
|
673
|
+
const intlOptions = {
|
|
674
|
+
style: resolvedWithoutSymbol ? "decimal" : "currency",
|
|
675
|
+
currency: resolvedCurrency,
|
|
676
|
+
minimumFractionDigits: 2,
|
|
677
|
+
maximumFractionDigits: 2
|
|
678
|
+
};
|
|
679
|
+
const formatted = new Intl.NumberFormat(resolvedLocale, intlOptions).format(
|
|
680
|
+
numValue
|
|
681
|
+
);
|
|
682
|
+
return redact ? formatted.replace(/\d/g, redactChar) : formatted;
|
|
683
|
+
} catch (error) {
|
|
684
|
+
const numValue = Number(value) || 0;
|
|
685
|
+
const formatted = numValue.toFixed(2).replace(".", ",");
|
|
686
|
+
const fallback = resolvedWithoutSymbol ? formatted : `${formatted} \u20AC`;
|
|
687
|
+
return redact ? fallback.replace(/\d/g, redactChar) : fallback;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
661
690
|
var parseName = (name) => {
|
|
662
691
|
try {
|
|
663
692
|
if (name === void 0 || name === null || name === "") {
|
|
@@ -1745,6 +1774,132 @@ var assessSecurityRisks = (risks) => {
|
|
|
1745
1774
|
return { score, level, recommendations };
|
|
1746
1775
|
};
|
|
1747
1776
|
|
|
1777
|
+
// src/utils/scramble.ts
|
|
1778
|
+
var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.from === "function";
|
|
1779
|
+
var base64EncodeBinary = (binary) => {
|
|
1780
|
+
if (hasBuffer) {
|
|
1781
|
+
return Buffer.from(binary, "binary").toString("base64");
|
|
1782
|
+
}
|
|
1783
|
+
if (typeof btoa === "function") {
|
|
1784
|
+
return btoa(binary);
|
|
1785
|
+
}
|
|
1786
|
+
throw new Error("Base64 encoder not available");
|
|
1787
|
+
};
|
|
1788
|
+
var base64DecodeToBinary = (base64) => {
|
|
1789
|
+
if (hasBuffer) {
|
|
1790
|
+
return Buffer.from(base64, "base64").toString("binary");
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof atob === "function") {
|
|
1793
|
+
return atob(base64);
|
|
1794
|
+
}
|
|
1795
|
+
throw new Error("Base64 decoder not available");
|
|
1796
|
+
};
|
|
1797
|
+
var utf8ToBinary = (value) => {
|
|
1798
|
+
if (hasBuffer) {
|
|
1799
|
+
return Buffer.from(value, "utf8").toString("binary");
|
|
1800
|
+
}
|
|
1801
|
+
if (typeof TextEncoder !== "undefined") {
|
|
1802
|
+
const bytes = new TextEncoder().encode(value);
|
|
1803
|
+
let binary = "";
|
|
1804
|
+
for (const b of bytes) {
|
|
1805
|
+
binary += String.fromCharCode(b);
|
|
1806
|
+
}
|
|
1807
|
+
return binary;
|
|
1808
|
+
}
|
|
1809
|
+
return value;
|
|
1810
|
+
};
|
|
1811
|
+
var binaryToUtf8 = (binary) => {
|
|
1812
|
+
if (hasBuffer) {
|
|
1813
|
+
return Buffer.from(binary, "binary").toString("utf8");
|
|
1814
|
+
}
|
|
1815
|
+
if (typeof TextDecoder !== "undefined") {
|
|
1816
|
+
const bytes = new Uint8Array(binary.length);
|
|
1817
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1818
|
+
bytes[i] = binary.charCodeAt(i) & 255;
|
|
1819
|
+
}
|
|
1820
|
+
return new TextDecoder().decode(bytes);
|
|
1821
|
+
}
|
|
1822
|
+
return binary;
|
|
1823
|
+
};
|
|
1824
|
+
var toBase64 = (value) => base64EncodeBinary(utf8ToBinary(value));
|
|
1825
|
+
var fromBase64 = (value) => binaryToUtf8(base64DecodeToBinary(value));
|
|
1826
|
+
var scrambleString = (value, secret) => {
|
|
1827
|
+
try {
|
|
1828
|
+
if (typeof value !== "string") {
|
|
1829
|
+
return { status: false, data: "Value must be a string" };
|
|
1830
|
+
}
|
|
1831
|
+
if (!secret || typeof secret !== "string") {
|
|
1832
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1833
|
+
}
|
|
1834
|
+
let result = "";
|
|
1835
|
+
for (let i = 0; i < value.length; i++) {
|
|
1836
|
+
const charCode = value.charCodeAt(i) & 255;
|
|
1837
|
+
const keyCode = secret.charCodeAt(i % secret.length) & 255;
|
|
1838
|
+
result += String.fromCharCode(charCode ^ keyCode);
|
|
1839
|
+
}
|
|
1840
|
+
return { status: true, data: base64EncodeBinary(result) };
|
|
1841
|
+
} catch (error) {
|
|
1842
|
+
return { status: false, data: error };
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
var descrambleString = (value, secret) => {
|
|
1846
|
+
try {
|
|
1847
|
+
if (typeof value !== "string") {
|
|
1848
|
+
return { status: false, data: "Value must be a string" };
|
|
1849
|
+
}
|
|
1850
|
+
if (!secret || typeof secret !== "string") {
|
|
1851
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1852
|
+
}
|
|
1853
|
+
const decoded = base64DecodeToBinary(value);
|
|
1854
|
+
let result = "";
|
|
1855
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
1856
|
+
const charCode = decoded.charCodeAt(i) & 255;
|
|
1857
|
+
const keyCode = secret.charCodeAt(i % secret.length) & 255;
|
|
1858
|
+
result += String.fromCharCode(charCode ^ keyCode);
|
|
1859
|
+
}
|
|
1860
|
+
return { status: true, data: result };
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
return { status: false, data: error };
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
var encodeObject = (input, secret) => {
|
|
1866
|
+
try {
|
|
1867
|
+
if (!input || typeof input !== "object") {
|
|
1868
|
+
return { status: false, data: "Input must be an object" };
|
|
1869
|
+
}
|
|
1870
|
+
if (!secret || typeof secret !== "string") {
|
|
1871
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1872
|
+
}
|
|
1873
|
+
const jsonString = JSON.stringify(input);
|
|
1874
|
+
const base64 = toBase64(jsonString);
|
|
1875
|
+
const scrambledResponse = scrambleString(base64, secret);
|
|
1876
|
+
if (!scrambledResponse.status) {
|
|
1877
|
+
return { status: false, data: "Scrambling failed" };
|
|
1878
|
+
}
|
|
1879
|
+
return { status: true, data: scrambledResponse.data };
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
return { status: false, data: error };
|
|
1882
|
+
}
|
|
1883
|
+
};
|
|
1884
|
+
var decodeObject = (encoded, secret) => {
|
|
1885
|
+
try {
|
|
1886
|
+
if (typeof encoded !== "string") {
|
|
1887
|
+
return { status: false, data: "Encoded value must be a string" };
|
|
1888
|
+
}
|
|
1889
|
+
if (!secret || typeof secret !== "string") {
|
|
1890
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1891
|
+
}
|
|
1892
|
+
const descrambledResponse = descrambleString(encoded, secret);
|
|
1893
|
+
if (!descrambledResponse.status) {
|
|
1894
|
+
return { status: false, data: "Descrambling failed" };
|
|
1895
|
+
}
|
|
1896
|
+
const jsonString = fromBase64(descrambledResponse.data);
|
|
1897
|
+
return { status: true, data: JSON.parse(jsonString) };
|
|
1898
|
+
} catch (error) {
|
|
1899
|
+
return { status: false, data: error };
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
|
|
1748
1903
|
// src/utils/defer.ts
|
|
1749
1904
|
var swallow = (p) => p.catch(() => {
|
|
1750
1905
|
});
|
|
@@ -1849,12 +2004,15 @@ exports.compact = compact;
|
|
|
1849
2004
|
exports.currencyToSymbol = currencyToSymbol;
|
|
1850
2005
|
exports.debounce = debounce;
|
|
1851
2006
|
exports.deburr = deburr;
|
|
2007
|
+
exports.decodeObject = decodeObject;
|
|
1852
2008
|
exports.deferAfterResponse = deferAfterResponse;
|
|
1853
2009
|
exports.deferAfterResponseNonCritical = deferAfterResponseNonCritical;
|
|
1854
2010
|
exports.deferNonCritical = deferNonCritical;
|
|
1855
2011
|
exports.deferPostReturn = deferPostReturn;
|
|
1856
2012
|
exports.delay = delay;
|
|
2013
|
+
exports.descrambleString = descrambleString;
|
|
1857
2014
|
exports.difference = difference;
|
|
2015
|
+
exports.encodeObject = encodeObject;
|
|
1858
2016
|
exports.fill = fill;
|
|
1859
2017
|
exports.flatten = flatten;
|
|
1860
2018
|
exports.flattenDepth = flattenDepth;
|
|
@@ -1862,6 +2020,7 @@ exports.flattenDepthBase = flattenDepthBase;
|
|
|
1862
2020
|
exports.flattenOnce = flattenOnce;
|
|
1863
2021
|
exports.formatBytes = formatBytes;
|
|
1864
2022
|
exports.formatCurrency = formatCurrency;
|
|
2023
|
+
exports.formatCurrencyPro = formatCurrencyPro;
|
|
1865
2024
|
exports.formatDecimalNumber = formatDecimalNumber;
|
|
1866
2025
|
exports.getStringSimilarity = getStringSimilarity;
|
|
1867
2026
|
exports.groupBy = groupBy;
|
|
@@ -1913,6 +2072,7 @@ exports.safeParseInt = safeParseInt;
|
|
|
1913
2072
|
exports.safeSubtract = safeSubtract;
|
|
1914
2073
|
exports.sanitize = sanitize;
|
|
1915
2074
|
exports.sanitizeMarkdown = sanitizeMarkdown;
|
|
2075
|
+
exports.scrambleString = scrambleString;
|
|
1916
2076
|
exports.sentenceCase = sentenceCase;
|
|
1917
2077
|
exports.shuffle = shuffle;
|
|
1918
2078
|
exports.slugify = slugify;
|