@salespark/toolkit 2.1.17 → 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 +47 -3
- package/dist/index.cjs +191 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +190 -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
|
---
|
|
@@ -876,6 +876,50 @@ const decoded = decodeObject(encoded.data, "secret");
|
|
|
876
876
|
|
|
877
877
|
**`decodeObject(encoded: string, secret: string): SalesParkContract<object>`** — Reverses `encodeObject` using the same secret.
|
|
878
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
|
+
|
|
879
923
|
### ✅ Validation Utilities
|
|
880
924
|
|
|
881
925
|
**`isPTTaxId(value: string | number): boolean`** — Validates Portuguese Tax ID (NIF) with MOD-11 algorithm and format checking.
|
|
@@ -1009,5 +1053,5 @@ MIT © [SalesPark](https://salespark.io)
|
|
|
1009
1053
|
|
|
1010
1054
|
---
|
|
1011
1055
|
|
|
1012
|
-
_Document version:
|
|
1013
|
-
_Last update:
|
|
1056
|
+
_Document version: 15_
|
|
1057
|
+
_Last update: 16-02-2026_
|
package/dist/index.cjs
CHANGED
|
@@ -2031,6 +2031,195 @@ var deferAfterResponseNonCritical = (res, fn) => {
|
|
|
2031
2031
|
}
|
|
2032
2032
|
};
|
|
2033
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
|
+
|
|
2034
2223
|
// src/index.ts
|
|
2035
2224
|
var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
2036
2225
|
var isNode = typeof process !== "undefined" && !!process.versions?.node;
|
|
@@ -2051,6 +2240,7 @@ exports.compact = compact;
|
|
|
2051
2240
|
exports.currencyToSymbol = currencyToSymbol;
|
|
2052
2241
|
exports.debounce = debounce;
|
|
2053
2242
|
exports.deburr = deburr;
|
|
2243
|
+
exports.decodeBase36Code = decodeBase36Code;
|
|
2054
2244
|
exports.decodeObject = decodeObject;
|
|
2055
2245
|
exports.decodeString = decodeString;
|
|
2056
2246
|
exports.deferAfterResponse = deferAfterResponse;
|
|
@@ -2059,6 +2249,7 @@ exports.deferNonCritical = deferNonCritical;
|
|
|
2059
2249
|
exports.deferPostReturn = deferPostReturn;
|
|
2060
2250
|
exports.delay = delay;
|
|
2061
2251
|
exports.difference = difference;
|
|
2252
|
+
exports.encodeBase36Code = encodeBase36Code;
|
|
2062
2253
|
exports.encodeObject = encodeObject;
|
|
2063
2254
|
exports.encodeString = encodeString;
|
|
2064
2255
|
exports.fill = fill;
|