@salespark/toolkit 2.1.16 → 2.1.18
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 +67 -7
- package/dist/index.cjs +241 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -31
- package/dist/index.d.ts +79 -31
- package/dist/index.js +237 -1
- 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, obfuscation helpers
|
|
35
|
+
- **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, obfuscation helpers, reversible base36 code encoding/decoding
|
|
36
36
|
- **Environment detection**: `isBrowser`, `isNode` runtime checks
|
|
37
37
|
|
|
38
38
|
---
|
|
@@ -544,6 +544,22 @@ isNilOrEmpty(undefined);
|
|
|
544
544
|
// Result: true
|
|
545
545
|
```
|
|
546
546
|
|
|
547
|
+
**`isNilEmptyOrEmptyObject(value: unknown): boolean`** — Checks if value is nil, empty string, or an empty plain object (no own keys).
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
isNilEmptyOrEmptyObject(null);
|
|
551
|
+
// Result: true
|
|
552
|
+
|
|
553
|
+
isNilEmptyOrEmptyObject("");
|
|
554
|
+
// Result: true
|
|
555
|
+
|
|
556
|
+
isNilEmptyOrEmptyObject({});
|
|
557
|
+
// Result: true
|
|
558
|
+
|
|
559
|
+
isNilEmptyOrEmptyObject({ a: 1 });
|
|
560
|
+
// Result: false
|
|
561
|
+
```
|
|
562
|
+
|
|
547
563
|
**`hasNilOrEmpty(array: unknown): boolean`** — Checks if any element in array is nil or empty.
|
|
548
564
|
|
|
549
565
|
```javascript
|
|
@@ -836,17 +852,17 @@ assessSecurityRisks([]);
|
|
|
836
852
|
// Result: { score: 0, level: "safe", recommendations: ["Content appears safe to use"] }
|
|
837
853
|
```
|
|
838
854
|
|
|
839
|
-
**`
|
|
855
|
+
**`encodeString(input: string, secret: string): SalesParkContract<any>`** — Base64-encodes a string and scrambles it with the provided secret (obfuscation only).
|
|
840
856
|
|
|
841
857
|
```javascript
|
|
842
|
-
const
|
|
858
|
+
const encoded = encodeString("Hello", "secret");
|
|
843
859
|
// Result: { status: true, data: "..." }
|
|
844
860
|
|
|
845
|
-
const
|
|
861
|
+
const decoded = decodeString(encoded.data, "secret");
|
|
846
862
|
// Result: { status: true, data: "Hello" }
|
|
847
863
|
```
|
|
848
864
|
|
|
849
|
-
**`
|
|
865
|
+
**`decodeString(encoded: string, secret: string): SalesParkContract<any>`** — Reverses `encodeString` using the same secret.
|
|
850
866
|
|
|
851
867
|
**`encodeObject(input: object, secret: string): SalesParkContract<object>`** — JSON-stringifies an object, Base64-encodes it, and scrambles the result (obfuscation only).
|
|
852
868
|
|
|
@@ -860,6 +876,50 @@ const decoded = decodeObject(encoded.data, "secret");
|
|
|
860
876
|
|
|
861
877
|
**`decodeObject(encoded: string, secret: string): SalesParkContract<object>`** — Reverses `encodeObject` using the same secret.
|
|
862
878
|
|
|
879
|
+
**`encodeBase36Code(identifier: string, config: EncodeDecodeConfig): SalesParkContract<{ code: string }>`** — Encodes a base36 identifier into a reversible lower-case base36 code using secret-based XOR + rotation.
|
|
880
|
+
|
|
881
|
+
```typescript
|
|
882
|
+
import { encodeBase36Code, decodeBase36Code } from "@salespark/toolkit";
|
|
883
|
+
|
|
884
|
+
const config = {
|
|
885
|
+
secret: "my-super-secret-key",
|
|
886
|
+
bitSize: 80,
|
|
887
|
+
rotateBits: 17,
|
|
888
|
+
addConstant: "0x1fd0a5b7c3",
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
const encoded = encodeBase36Code("AB12CD34", config);
|
|
892
|
+
// Result: { status: true, data: { code: "..." } }
|
|
893
|
+
|
|
894
|
+
const decoded = decodeBase36Code(encoded.data.code, config);
|
|
895
|
+
// Result: { status: true, data: { identifier: "AB12CD34" } }
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
**`decodeBase36Code(code: string, config: EncodeDecodeConfig): SalesParkContract<{ identifier: string }>`** — Decodes a previously encoded base36 code back to the original identifier (upper-case).
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
const bad = encodeBase36Code("AB-12", {
|
|
902
|
+
secret: "my-super-secret-key",
|
|
903
|
+
});
|
|
904
|
+
// Result: { status: false, data: { message: "Identifier must be base36 (0-9, A-Z)" } }
|
|
905
|
+
|
|
906
|
+
const weakSecret = decodeBase36Code("abc123", {
|
|
907
|
+
secret: "short",
|
|
908
|
+
});
|
|
909
|
+
// Result: { status: false, data: { message: "Missing or weak secret" } }
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
**`EncodeDecodeConfig`** — Configuration object used by `encodeBase36Code` and `decodeBase36Code`.
|
|
913
|
+
|
|
914
|
+
```typescript
|
|
915
|
+
type EncodeDecodeConfig = {
|
|
916
|
+
secret: string; // required, minimum 12 chars
|
|
917
|
+
bitSize?: number; // default: 80
|
|
918
|
+
rotateBits?: number; // default: 17
|
|
919
|
+
addConstant?: string; // default: "0x1fd0a5b7c3"
|
|
920
|
+
};
|
|
921
|
+
```
|
|
922
|
+
|
|
863
923
|
### ✅ Validation Utilities
|
|
864
924
|
|
|
865
925
|
**`isPTTaxId(value: string | number): boolean`** — Validates Portuguese Tax ID (NIF) with MOD-11 algorithm and format checking.
|
|
@@ -993,5 +1053,5 @@ MIT © [SalesPark](https://salespark.io)
|
|
|
993
1053
|
|
|
994
1054
|
---
|
|
995
1055
|
|
|
996
|
-
_Document version:
|
|
997
|
-
_Last update:
|
|
1056
|
+
_Document version: 15_
|
|
1057
|
+
_Last update: 16-02-2026_
|
package/dist/index.cjs
CHANGED
|
@@ -506,6 +506,17 @@ var isNilOrEmpty = (value) => {
|
|
|
506
506
|
return true;
|
|
507
507
|
}
|
|
508
508
|
};
|
|
509
|
+
var isNilEmptyOrEmptyObject = (value) => {
|
|
510
|
+
try {
|
|
511
|
+
if (isNilOrEmpty(value)) return true;
|
|
512
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
} catch {
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
509
520
|
var hasNilOrEmpty = (array) => {
|
|
510
521
|
try {
|
|
511
522
|
if (!Array.isArray(array)) return true;
|
|
@@ -1881,6 +1892,24 @@ var encodeObject = (input, secret) => {
|
|
|
1881
1892
|
return { status: false, data: error };
|
|
1882
1893
|
}
|
|
1883
1894
|
};
|
|
1895
|
+
var encodeString = (input, secret) => {
|
|
1896
|
+
try {
|
|
1897
|
+
if (typeof input !== "string") {
|
|
1898
|
+
return { status: false, data: "Input must be a string" };
|
|
1899
|
+
}
|
|
1900
|
+
if (!secret || typeof secret !== "string") {
|
|
1901
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1902
|
+
}
|
|
1903
|
+
const base64 = toBase64(input);
|
|
1904
|
+
const scrambledResponse = scrambleString(base64, secret);
|
|
1905
|
+
if (!scrambledResponse.status) {
|
|
1906
|
+
return { status: false, data: "Scrambling failed" };
|
|
1907
|
+
}
|
|
1908
|
+
return { status: true, data: scrambledResponse.data };
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
return { status: false, data: error };
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1884
1913
|
var decodeObject = (encoded, secret) => {
|
|
1885
1914
|
try {
|
|
1886
1915
|
if (typeof encoded !== "string") {
|
|
@@ -1899,6 +1928,24 @@ var decodeObject = (encoded, secret) => {
|
|
|
1899
1928
|
return { status: false, data: error };
|
|
1900
1929
|
}
|
|
1901
1930
|
};
|
|
1931
|
+
var decodeString = (encoded, secret) => {
|
|
1932
|
+
try {
|
|
1933
|
+
if (typeof encoded !== "string") {
|
|
1934
|
+
return { status: false, data: "Encoded value must be a string" };
|
|
1935
|
+
}
|
|
1936
|
+
if (!secret || typeof secret !== "string") {
|
|
1937
|
+
return { status: false, data: "Secret must be a non-empty string" };
|
|
1938
|
+
}
|
|
1939
|
+
const descrambledResponse = descrambleString(encoded, secret);
|
|
1940
|
+
if (!descrambledResponse.status) {
|
|
1941
|
+
return { status: false, data: "Descrambling failed" };
|
|
1942
|
+
}
|
|
1943
|
+
const value = fromBase64(descrambledResponse.data);
|
|
1944
|
+
return { status: true, data: value };
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
return { status: false, data: error };
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1902
1949
|
|
|
1903
1950
|
// src/utils/defer.ts
|
|
1904
1951
|
var swallow = (p) => p.catch(() => {
|
|
@@ -1984,6 +2031,195 @@ var deferAfterResponseNonCritical = (res, fn) => {
|
|
|
1984
2031
|
}
|
|
1985
2032
|
};
|
|
1986
2033
|
|
|
2034
|
+
// src/utils/base36.ts
|
|
2035
|
+
var DEFAULTS = {
|
|
2036
|
+
bitSize: 80,
|
|
2037
|
+
rotateBits: 17,
|
|
2038
|
+
addConstant: "0x1fd0a5b7c3"
|
|
2039
|
+
};
|
|
2040
|
+
var isValidBase36 = (value) => /^[a-z0-9]+$/i.test(value);
|
|
2041
|
+
var normalizeInput = (value) => (value || "").trim();
|
|
2042
|
+
var assertSecret = (secret) => {
|
|
2043
|
+
if (typeof secret !== "string" || secret.trim().length < 12) {
|
|
2044
|
+
return { status: false, data: { message: "Missing or weak secret" } };
|
|
2045
|
+
}
|
|
2046
|
+
return { status: true, data: true };
|
|
2047
|
+
};
|
|
2048
|
+
var parseBase36BigInt = (value) => {
|
|
2049
|
+
try {
|
|
2050
|
+
const normalizedValue = normalizeInput(value);
|
|
2051
|
+
if (!normalizedValue) {
|
|
2052
|
+
return { status: false, data: { message: "Empty input" } };
|
|
2053
|
+
}
|
|
2054
|
+
if (!isValidBase36(normalizedValue)) {
|
|
2055
|
+
return { status: false, data: { message: "Invalid base36 input" } };
|
|
2056
|
+
}
|
|
2057
|
+
const safeValue = normalizedValue.toLowerCase();
|
|
2058
|
+
let output = 0n;
|
|
2059
|
+
for (let i = 0; i < safeValue.length; i++) {
|
|
2060
|
+
const char = safeValue[i];
|
|
2061
|
+
const digit = char >= "0" && char <= "9" ? BigInt(char.charCodeAt(0) - 48) : BigInt(char.charCodeAt(0) - 87);
|
|
2062
|
+
output = output * 36n + digit;
|
|
2063
|
+
}
|
|
2064
|
+
return { status: true, data: output };
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
return {
|
|
2067
|
+
status: false,
|
|
2068
|
+
data: { message: "Failed to parse base36 input", error }
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
var toBase36Lower = (value) => {
|
|
2073
|
+
try {
|
|
2074
|
+
if (value < 0n) {
|
|
2075
|
+
return {
|
|
2076
|
+
status: false,
|
|
2077
|
+
data: { message: "Negative values are not supported" }
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
return { status: true, data: value.toString(36) };
|
|
2081
|
+
} catch (error) {
|
|
2082
|
+
return {
|
|
2083
|
+
status: false,
|
|
2084
|
+
data: { message: "Failed to convert to base36", error }
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
var getParams = (config) => {
|
|
2089
|
+
try {
|
|
2090
|
+
const bitSizeRaw = BigInt(config.bitSize ?? DEFAULTS.bitSize);
|
|
2091
|
+
if (bitSizeRaw <= 0n) {
|
|
2092
|
+
return {
|
|
2093
|
+
status: false,
|
|
2094
|
+
data: { message: "bitSize must be greater than 0" }
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
const rotateRaw = BigInt(config.rotateBits ?? DEFAULTS.rotateBits);
|
|
2098
|
+
const rotateBits = (rotateRaw % bitSizeRaw + bitSizeRaw) % bitSizeRaw;
|
|
2099
|
+
const addConstant = BigInt(config.addConstant ?? DEFAULTS.addConstant);
|
|
2100
|
+
const mask = (1n << bitSizeRaw) - 1n;
|
|
2101
|
+
return {
|
|
2102
|
+
status: true,
|
|
2103
|
+
data: { bitSize: bitSizeRaw, rotateBits, addConstant, mask }
|
|
2104
|
+
};
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
return { status: false, data: { message: "Invalid configuration", error } };
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
var secretToKey = (secret, mask) => {
|
|
2110
|
+
let key = 0n;
|
|
2111
|
+
const safeSecret = secret.trim();
|
|
2112
|
+
for (let i = 0; i < safeSecret.length; i++) {
|
|
2113
|
+
key = key * 131n + BigInt(safeSecret.charCodeAt(i)) & mask;
|
|
2114
|
+
}
|
|
2115
|
+
return key;
|
|
2116
|
+
};
|
|
2117
|
+
var rotl = (x, r, bitSize, mask) => {
|
|
2118
|
+
if (r === 0n) return x & mask;
|
|
2119
|
+
return (x << r | x >> bitSize - r) & mask;
|
|
2120
|
+
};
|
|
2121
|
+
var rotr = (x, r, bitSize, mask) => {
|
|
2122
|
+
if (r === 0n) return x & mask;
|
|
2123
|
+
return (x >> r | x << bitSize - r) & mask;
|
|
2124
|
+
};
|
|
2125
|
+
var encodeBase36Code = (identifier, config) => {
|
|
2126
|
+
try {
|
|
2127
|
+
const secretCheck = assertSecret(config?.secret);
|
|
2128
|
+
if (!secretCheck.status) {
|
|
2129
|
+
return { status: false, data: secretCheck.data };
|
|
2130
|
+
}
|
|
2131
|
+
const input = normalizeInput(identifier);
|
|
2132
|
+
if (!input) {
|
|
2133
|
+
return { status: false, data: { message: "Identifier is required" } };
|
|
2134
|
+
}
|
|
2135
|
+
if (!isValidBase36(input)) {
|
|
2136
|
+
return {
|
|
2137
|
+
status: false,
|
|
2138
|
+
data: { message: "Identifier must be base36 (0-9, A-Z)" }
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
const parsed = parseBase36BigInt(input);
|
|
2142
|
+
if (!parsed.status) {
|
|
2143
|
+
return { status: false, data: parsed.data };
|
|
2144
|
+
}
|
|
2145
|
+
const params = getParams(config);
|
|
2146
|
+
if (!params.status) {
|
|
2147
|
+
return { status: false, data: params.data };
|
|
2148
|
+
}
|
|
2149
|
+
const {
|
|
2150
|
+
bitSize,
|
|
2151
|
+
rotateBits,
|
|
2152
|
+
addConstant,
|
|
2153
|
+
mask
|
|
2154
|
+
} = params.data;
|
|
2155
|
+
const key = secretToKey(config.secret, mask);
|
|
2156
|
+
let value = parsed.data & mask;
|
|
2157
|
+
value = value ^ key;
|
|
2158
|
+
value = value + addConstant & mask;
|
|
2159
|
+
value = rotl(value, rotateBits, bitSize, mask);
|
|
2160
|
+
const codeResult = toBase36Lower(value);
|
|
2161
|
+
if (!codeResult.status) {
|
|
2162
|
+
return { status: false, data: codeResult.data };
|
|
2163
|
+
}
|
|
2164
|
+
return { status: true, data: { code: codeResult.data } };
|
|
2165
|
+
} catch (error) {
|
|
2166
|
+
return {
|
|
2167
|
+
status: false,
|
|
2168
|
+
data: { message: "Failed to encode code", error }
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
var decodeBase36Code = (code, config) => {
|
|
2173
|
+
try {
|
|
2174
|
+
const secretCheck = assertSecret(config?.secret);
|
|
2175
|
+
if (!secretCheck.status) {
|
|
2176
|
+
return { status: false, data: secretCheck.data };
|
|
2177
|
+
}
|
|
2178
|
+
const input = normalizeInput(code);
|
|
2179
|
+
if (!input) {
|
|
2180
|
+
return { status: false, data: { message: "Code is required" } };
|
|
2181
|
+
}
|
|
2182
|
+
if (!isValidBase36(input)) {
|
|
2183
|
+
return {
|
|
2184
|
+
status: false,
|
|
2185
|
+
data: { message: "Code must be base36 (0-9, a-z)" }
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
const parsed = parseBase36BigInt(input);
|
|
2189
|
+
if (!parsed.status) {
|
|
2190
|
+
return { status: false, data: parsed.data };
|
|
2191
|
+
}
|
|
2192
|
+
const params = getParams(config);
|
|
2193
|
+
if (!params.status) {
|
|
2194
|
+
return { status: false, data: params.data };
|
|
2195
|
+
}
|
|
2196
|
+
const {
|
|
2197
|
+
bitSize,
|
|
2198
|
+
rotateBits,
|
|
2199
|
+
addConstant,
|
|
2200
|
+
mask
|
|
2201
|
+
} = params.data;
|
|
2202
|
+
const key = secretToKey(config.secret, mask);
|
|
2203
|
+
let value = parsed.data & mask;
|
|
2204
|
+
value = rotr(value, rotateBits, bitSize, mask);
|
|
2205
|
+
value = value - addConstant & mask;
|
|
2206
|
+
value = value ^ key;
|
|
2207
|
+
const identifierResult = toBase36Lower(value);
|
|
2208
|
+
if (!identifierResult.status) {
|
|
2209
|
+
return { status: false, data: identifierResult.data };
|
|
2210
|
+
}
|
|
2211
|
+
return {
|
|
2212
|
+
status: true,
|
|
2213
|
+
data: { identifier: identifierResult.data.toUpperCase() }
|
|
2214
|
+
};
|
|
2215
|
+
} catch (error) {
|
|
2216
|
+
return {
|
|
2217
|
+
status: false,
|
|
2218
|
+
data: { message: "Failed to decode code", error }
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
|
|
1987
2223
|
// src/index.ts
|
|
1988
2224
|
var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
1989
2225
|
var isNode = typeof process !== "undefined" && !!process.versions?.node;
|
|
@@ -2004,15 +2240,18 @@ exports.compact = compact;
|
|
|
2004
2240
|
exports.currencyToSymbol = currencyToSymbol;
|
|
2005
2241
|
exports.debounce = debounce;
|
|
2006
2242
|
exports.deburr = deburr;
|
|
2243
|
+
exports.decodeBase36Code = decodeBase36Code;
|
|
2007
2244
|
exports.decodeObject = decodeObject;
|
|
2245
|
+
exports.decodeString = decodeString;
|
|
2008
2246
|
exports.deferAfterResponse = deferAfterResponse;
|
|
2009
2247
|
exports.deferAfterResponseNonCritical = deferAfterResponseNonCritical;
|
|
2010
2248
|
exports.deferNonCritical = deferNonCritical;
|
|
2011
2249
|
exports.deferPostReturn = deferPostReturn;
|
|
2012
2250
|
exports.delay = delay;
|
|
2013
|
-
exports.descrambleString = descrambleString;
|
|
2014
2251
|
exports.difference = difference;
|
|
2252
|
+
exports.encodeBase36Code = encodeBase36Code;
|
|
2015
2253
|
exports.encodeObject = encodeObject;
|
|
2254
|
+
exports.encodeString = encodeString;
|
|
2016
2255
|
exports.fill = fill;
|
|
2017
2256
|
exports.flatten = flatten;
|
|
2018
2257
|
exports.flattenDepth = flattenDepth;
|
|
@@ -2030,6 +2269,7 @@ exports.intersection = intersection;
|
|
|
2030
2269
|
exports.isBrowser = isBrowser;
|
|
2031
2270
|
exports.isFlattenable = isFlattenable;
|
|
2032
2271
|
exports.isNil = isNil;
|
|
2272
|
+
exports.isNilEmptyOrEmptyObject = isNilEmptyOrEmptyObject;
|
|
2033
2273
|
exports.isNilEmptyOrZeroLen = isNilEmptyOrZeroLen;
|
|
2034
2274
|
exports.isNilEmptyOrZeroLength = isNilEmptyOrZeroLength;
|
|
2035
2275
|
exports.isNilOrEmpty = isNilOrEmpty;
|
|
@@ -2072,7 +2312,6 @@ exports.safeParseInt = safeParseInt;
|
|
|
2072
2312
|
exports.safeSubtract = safeSubtract;
|
|
2073
2313
|
exports.sanitize = sanitize;
|
|
2074
2314
|
exports.sanitizeMarkdown = sanitizeMarkdown;
|
|
2075
|
-
exports.scrambleString = scrambleString;
|
|
2076
2315
|
exports.sentenceCase = sentenceCase;
|
|
2077
2316
|
exports.shuffle = shuffle;
|
|
2078
2317
|
exports.slugify = slugify;
|