@zeroad.network/token 0.13.13 → 0.14.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/README.md +689 -109
- package/dist/{browser-Do--x9Sv.cjs → browser-D3BXUIiT.cjs} +20 -21
- package/dist/{browser-DiH4sEBn.mjs → browser-VjxLaeuu.mjs} +20 -20
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +174 -36
- package/dist/index.d.cts +28 -6
- package/dist/index.d.mts +28 -6
- package/dist/index.mjs +170 -38
- package/package.json +2 -3
|
@@ -12,9 +12,15 @@ function setLogLevel(level) {
|
|
|
12
12
|
currentLevel = level;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
let transport = (level, ...args) => {
|
|
16
|
+
console.log(`[${level.toUpperCase()}]`, ...args);
|
|
17
|
+
};
|
|
18
|
+
function setLogTransport(fn) {
|
|
19
|
+
transport = fn;
|
|
20
|
+
}
|
|
15
21
|
function log(level, ...args) {
|
|
16
22
|
if (levels[level] <= levels[currentLevel]) {
|
|
17
|
-
|
|
23
|
+
transport(level, ...args);
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -38,17 +44,9 @@ var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
|
38
44
|
})(PROTOCOL_VERSION || {});
|
|
39
45
|
const CURRENT_PROTOCOL_VERSION = 1 /* V_1 */;
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
cachedFeatures = /* @__PURE__ */ new Map();
|
|
45
|
-
for (const key of Object.keys(FEATURE)) {
|
|
46
|
-
if (!isNaN(Number(key))) continue;
|
|
47
|
-
const typedKey = key;
|
|
48
|
-
cachedFeatures.set(typedKey, FEATURE[typedKey]);
|
|
49
|
-
}
|
|
50
|
-
return cachedFeatures;
|
|
51
|
-
}
|
|
47
|
+
const FEATURE_MAP = new Map(
|
|
48
|
+
Object.entries(FEATURE).filter(([k]) => isNaN(Number(k)))
|
|
49
|
+
);
|
|
52
50
|
function toBase64(data) {
|
|
53
51
|
if (typeof data.toBase64 === "function") return data.toBase64();
|
|
54
52
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
@@ -61,23 +59,25 @@ function toBase64(data) {
|
|
|
61
59
|
}
|
|
62
60
|
throw new Error("Base64 encoding not supported in this environment");
|
|
63
61
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
exports.fromBase64 = void 0;
|
|
63
|
+
if (typeof Uint8Array.fromBase64 === "function") exports.fromBase64 = (input) => Uint8Array.fromBase64(input);
|
|
64
|
+
else if (typeof Buffer !== "undefined") exports.fromBase64 = (input) => new Uint8Array(Buffer.from(input, "base64"));
|
|
65
|
+
else if (typeof atob === "function") {
|
|
66
|
+
exports.fromBase64 = (input) => {
|
|
68
67
|
const binary = atob(input);
|
|
69
68
|
const bytes = new Uint8Array(binary.length);
|
|
70
69
|
for (let i = 0; i < binary.length; i++) {
|
|
71
70
|
bytes[i] = binary.charCodeAt(i);
|
|
72
71
|
}
|
|
73
72
|
return bytes;
|
|
74
|
-
}
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
75
|
throw new Error("Base64 decoding not supported in this environment");
|
|
76
76
|
}
|
|
77
77
|
function assert(value, message) {
|
|
78
78
|
if (!value) throw new Error(message);
|
|
79
79
|
}
|
|
80
|
-
const hasFlag = (bit, flags) =>
|
|
80
|
+
const hasFlag = (bit, flags) => (bit & flags) !== 0;
|
|
81
81
|
const setFlags = (features = []) => features.reduce((acc, feature) => acc | feature, 0);
|
|
82
82
|
|
|
83
83
|
const SEPARATOR = "^";
|
|
@@ -107,7 +107,7 @@ function decodeServerHeader(headerValue) {
|
|
|
107
107
|
);
|
|
108
108
|
assert(Number(flags).toFixed(0).toString() === flags, "Invalid flags number");
|
|
109
109
|
const features = [];
|
|
110
|
-
for (const [feature, bit] of FEATURE_MAP
|
|
110
|
+
for (const [feature, bit] of FEATURE_MAP) {
|
|
111
111
|
if (hasFlag(Number(flags), bit)) features.push(feature);
|
|
112
112
|
}
|
|
113
113
|
return {
|
|
@@ -128,9 +128,8 @@ exports.SERVER_HEADER = SERVER_HEADER;
|
|
|
128
128
|
exports.ZEROAD_NETWORK_PUBLIC_KEY = ZEROAD_NETWORK_PUBLIC_KEY;
|
|
129
129
|
exports.decodeServerHeader = decodeServerHeader;
|
|
130
130
|
exports.encodeServerHeader = encodeServerHeader;
|
|
131
|
-
exports.fromBase64 = fromBase64;
|
|
132
|
-
exports.hasFlag = hasFlag;
|
|
133
131
|
exports.log = log;
|
|
134
132
|
exports.setFlags = setFlags;
|
|
135
133
|
exports.setLogLevel = setLogLevel;
|
|
134
|
+
exports.setLogTransport = setLogTransport;
|
|
136
135
|
exports.toBase64 = toBase64;
|
|
@@ -10,9 +10,15 @@ function setLogLevel(level) {
|
|
|
10
10
|
currentLevel = level;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
let transport = (level, ...args) => {
|
|
14
|
+
console.log(`[${level.toUpperCase()}]`, ...args);
|
|
15
|
+
};
|
|
16
|
+
function setLogTransport(fn) {
|
|
17
|
+
transport = fn;
|
|
18
|
+
}
|
|
13
19
|
function log(level, ...args) {
|
|
14
20
|
if (levels[level] <= levels[currentLevel]) {
|
|
15
|
-
|
|
21
|
+
transport(level, ...args);
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -36,17 +42,9 @@ var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
|
36
42
|
})(PROTOCOL_VERSION || {});
|
|
37
43
|
const CURRENT_PROTOCOL_VERSION = 1 /* V_1 */;
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cachedFeatures = /* @__PURE__ */ new Map();
|
|
43
|
-
for (const key of Object.keys(FEATURE)) {
|
|
44
|
-
if (!isNaN(Number(key))) continue;
|
|
45
|
-
const typedKey = key;
|
|
46
|
-
cachedFeatures.set(typedKey, FEATURE[typedKey]);
|
|
47
|
-
}
|
|
48
|
-
return cachedFeatures;
|
|
49
|
-
}
|
|
45
|
+
const FEATURE_MAP = new Map(
|
|
46
|
+
Object.entries(FEATURE).filter(([k]) => isNaN(Number(k)))
|
|
47
|
+
);
|
|
50
48
|
function toBase64(data) {
|
|
51
49
|
if (typeof data.toBase64 === "function") return data.toBase64();
|
|
52
50
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
@@ -59,23 +57,25 @@ function toBase64(data) {
|
|
|
59
57
|
}
|
|
60
58
|
throw new Error("Base64 encoding not supported in this environment");
|
|
61
59
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
let fromBase64;
|
|
61
|
+
if (typeof Uint8Array.fromBase64 === "function") fromBase64 = (input) => Uint8Array.fromBase64(input);
|
|
62
|
+
else if (typeof Buffer !== "undefined") fromBase64 = (input) => new Uint8Array(Buffer.from(input, "base64"));
|
|
63
|
+
else if (typeof atob === "function") {
|
|
64
|
+
fromBase64 = (input) => {
|
|
66
65
|
const binary = atob(input);
|
|
67
66
|
const bytes = new Uint8Array(binary.length);
|
|
68
67
|
for (let i = 0; i < binary.length; i++) {
|
|
69
68
|
bytes[i] = binary.charCodeAt(i);
|
|
70
69
|
}
|
|
71
70
|
return bytes;
|
|
72
|
-
}
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
73
|
throw new Error("Base64 decoding not supported in this environment");
|
|
74
74
|
}
|
|
75
75
|
function assert(value, message) {
|
|
76
76
|
if (!value) throw new Error(message);
|
|
77
77
|
}
|
|
78
|
-
const hasFlag = (bit, flags) =>
|
|
78
|
+
const hasFlag = (bit, flags) => (bit & flags) !== 0;
|
|
79
79
|
const setFlags = (features = []) => features.reduce((acc, feature) => acc | feature, 0);
|
|
80
80
|
|
|
81
81
|
const SEPARATOR = "^";
|
|
@@ -105,7 +105,7 @@ function decodeServerHeader(headerValue) {
|
|
|
105
105
|
);
|
|
106
106
|
assert(Number(flags).toFixed(0).toString() === flags, "Invalid flags number");
|
|
107
107
|
const features = [];
|
|
108
|
-
for (const [feature, bit] of FEATURE_MAP
|
|
108
|
+
for (const [feature, bit] of FEATURE_MAP) {
|
|
109
109
|
if (hasFlag(Number(flags), bit)) features.push(feature);
|
|
110
110
|
}
|
|
111
111
|
return {
|
|
@@ -118,4 +118,4 @@ function decodeServerHeader(headerValue) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
export { CLIENT_HEADER as C, FEATURE as F, PROTOCOL_VERSION as P, SERVER_HEADER as S, ZEROAD_NETWORK_PUBLIC_KEY as Z, setLogLevel as a,
|
|
121
|
+
export { CLIENT_HEADER as C, FEATURE as F, PROTOCOL_VERSION as P, SERVER_HEADER as S, ZEROAD_NETWORK_PUBLIC_KEY as Z, setLogLevel as a, setLogTransport as b, CURRENT_PROTOCOL_VERSION as c, decodeServerHeader as d, encodeServerHeader as e, fromBase64 as f, log as l, setFlags as s, toBase64 as t };
|
package/dist/browser.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as CLIENT_HEADER,
|
|
1
|
+
export { C as CLIENT_HEADER, c as CURRENT_PROTOCOL_VERSION, F as FEATURE, P as PROTOCOL_VERSION, S as SERVER_HEADER, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader } from './browser-VjxLaeuu.mjs';
|
package/dist/index.cjs
CHANGED
|
@@ -1,17 +1,74 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var browser = require('./browser-
|
|
3
|
+
var browser = require('./browser-D3BXUIiT.cjs');
|
|
4
4
|
var node_buffer = require('node:buffer');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
|
|
7
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
maxSize: 100,
|
|
10
|
+
ttl: 5e3
|
|
11
|
+
// 5 seconds
|
|
12
|
+
};
|
|
13
|
+
exports.cacheConfig = { ...DEFAULT_CACHE_CONFIG };
|
|
14
|
+
function configureCaching(config) {
|
|
15
|
+
if (config.ttl !== void 0 && config.ttl < 0) {
|
|
16
|
+
throw new Error("Cache TTL must be >= 0");
|
|
17
|
+
}
|
|
18
|
+
if (config.maxSize !== void 0 && config.maxSize < 1) {
|
|
19
|
+
throw new Error("Cache maxSize must be >= 1");
|
|
20
|
+
}
|
|
21
|
+
exports.cacheConfig = { ...exports.cacheConfig, ...config };
|
|
22
|
+
if (!exports.cacheConfig.enabled) {
|
|
23
|
+
headerCache.clear();
|
|
24
|
+
}
|
|
25
|
+
trimCache();
|
|
26
|
+
browser.log("debug", "Cache configuration updated", exports.cacheConfig);
|
|
27
|
+
}
|
|
28
|
+
function getCacheConfig() {
|
|
29
|
+
return { ...exports.cacheConfig };
|
|
30
|
+
}
|
|
31
|
+
const headerCache = /* @__PURE__ */ new Map();
|
|
32
|
+
function trimCache() {
|
|
33
|
+
if (headerCache.size <= exports.cacheConfig.maxSize) return;
|
|
34
|
+
const entriesToRemove = headerCache.size - exports.cacheConfig.maxSize;
|
|
35
|
+
const entries = Array.from(headerCache.entries());
|
|
36
|
+
entries.sort((a, b) => {
|
|
37
|
+
if (a[1].accessCount !== b[1].accessCount) {
|
|
38
|
+
return a[1].accessCount - b[1].accessCount;
|
|
39
|
+
}
|
|
40
|
+
return a[1].timestamp - b[1].timestamp;
|
|
41
|
+
});
|
|
42
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
43
|
+
headerCache.delete(entries[i][0]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function cleanExpiredEntries(now) {
|
|
47
|
+
for (const [key, entry] of headerCache.entries()) {
|
|
48
|
+
if (entry.effectiveExpiry <= now) {
|
|
49
|
+
headerCache.delete(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
7
54
|
const keyCache = /* @__PURE__ */ new Map();
|
|
8
55
|
function sign(data, privateKey) {
|
|
9
56
|
const key = importPrivateKey(privateKey);
|
|
10
|
-
return
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
node_crypto.sign(null, node_buffer.Buffer.from(data), key, (err, result) => {
|
|
59
|
+
if (err) reject(err);
|
|
60
|
+
else resolve(result);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
11
63
|
}
|
|
12
64
|
function verify(data, signature, publicKey) {
|
|
13
65
|
const key = importPublicKey(publicKey);
|
|
14
|
-
return
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
node_crypto.verify(null, node_buffer.Buffer.from(data), key, node_buffer.Buffer.from(signature), (err, result) => {
|
|
68
|
+
if (err) reject(err);
|
|
69
|
+
else resolve(result);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
15
72
|
}
|
|
16
73
|
const nonce = (size) => new Uint8Array(node_crypto.randomBytes(size));
|
|
17
74
|
function importPrivateKey(privateKeyBase64) {
|
|
@@ -38,55 +95,132 @@ function importPublicKey(publicKeyBase64) {
|
|
|
38
95
|
const VERSION_BYTES = 1;
|
|
39
96
|
const NONCE_BYTES = 4;
|
|
40
97
|
const SEPARATOR = ".";
|
|
41
|
-
const
|
|
42
|
-
|
|
98
|
+
const UINT32_BYTES = 4;
|
|
99
|
+
const FEATURE_TO_ACTIONS = Object.freeze({
|
|
100
|
+
[browser.FEATURE.CLEAN_WEB]: Object.freeze([
|
|
43
101
|
"HIDE_ADVERTISEMENTS",
|
|
44
102
|
"HIDE_COOKIE_CONSENT_SCREEN",
|
|
45
103
|
"HIDE_MARKETING_DIALOGS",
|
|
46
104
|
"DISABLE_NON_FUNCTIONAL_TRACKING"
|
|
47
|
-
],
|
|
48
|
-
[browser.FEATURE.ONE_PASS]: [
|
|
49
|
-
|
|
50
|
-
|
|
105
|
+
]),
|
|
106
|
+
[browser.FEATURE.ONE_PASS]: Object.freeze([
|
|
107
|
+
"DISABLE_CONTENT_PAYWALL",
|
|
108
|
+
"ENABLE_SUBSCRIPTION_ACCESS"
|
|
109
|
+
])
|
|
110
|
+
});
|
|
111
|
+
const FEATURE_NUMBERS = Object.freeze([browser.FEATURE.CLEAN_WEB, browser.FEATURE.ONE_PASS]);
|
|
112
|
+
const EMPTY_CONTEXT = Object.freeze({
|
|
113
|
+
HIDE_ADVERTISEMENTS: false,
|
|
114
|
+
HIDE_COOKIE_CONSENT_SCREEN: false,
|
|
115
|
+
HIDE_MARKETING_DIALOGS: false,
|
|
116
|
+
DISABLE_NON_FUNCTIONAL_TRACKING: false,
|
|
117
|
+
DISABLE_CONTENT_PAYWALL: false,
|
|
118
|
+
ENABLE_SUBSCRIPTION_ACCESS: false
|
|
119
|
+
});
|
|
120
|
+
function createEmptyContext() {
|
|
121
|
+
return EMPTY_CONTEXT;
|
|
122
|
+
}
|
|
123
|
+
async function parseClientToken(headerValue, options) {
|
|
124
|
+
if (!headerValue || Array.isArray(headerValue) && !headerValue.length) {
|
|
125
|
+
return createEmptyContext();
|
|
126
|
+
}
|
|
51
127
|
const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
52
|
-
const
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
if (exports.cacheConfig.enabled && !options.bypassCache) {
|
|
130
|
+
const cached = headerCache.get(headerValueAsString);
|
|
131
|
+
if (cached && cached.effectiveExpiry > now) {
|
|
132
|
+
cached.accessCount++;
|
|
133
|
+
return buildContext(cached.data, options, now);
|
|
134
|
+
} else if (cached) {
|
|
135
|
+
headerCache.delete(headerValueAsString);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const data = await decodeClientHeader(headerValueAsString, options.publicKey || browser.ZEROAD_NETWORK_PUBLIC_KEY);
|
|
139
|
+
if (exports.cacheConfig.enabled && !options.bypassCache) {
|
|
140
|
+
const cacheTTLExpiry = now + exports.cacheConfig.ttl;
|
|
141
|
+
const tokenExpiry = data?.expiresAt.getTime() ?? 0;
|
|
142
|
+
const effectiveExpiry = tokenExpiry > 0 ? Math.min(cacheTTLExpiry, tokenExpiry) : cacheTTLExpiry;
|
|
143
|
+
headerCache.set(headerValueAsString, {
|
|
144
|
+
data,
|
|
145
|
+
timestamp: now,
|
|
146
|
+
accessCount: 1,
|
|
147
|
+
effectiveExpiry
|
|
148
|
+
});
|
|
149
|
+
if (headerCache.size > 0 && headerCache.size % 100 === 0) {
|
|
150
|
+
cleanExpiredEntries(now);
|
|
151
|
+
}
|
|
152
|
+
trimCache();
|
|
153
|
+
}
|
|
154
|
+
return buildContext(data, options, now);
|
|
155
|
+
}
|
|
156
|
+
function buildContext(data, options, now) {
|
|
53
157
|
let flags = 0;
|
|
54
|
-
if (data && data.expiresAt.getTime() >=
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const [feature, actionNames] of Object.entries(FEATURE_TO_ACTIONS)) {
|
|
58
|
-
const decision = options.features.includes(Number(feature)) && browser.hasFlag(Number(feature), flags);
|
|
59
|
-
for (const actionName of actionNames) {
|
|
60
|
-
context.set(actionName, decision);
|
|
158
|
+
if (data && data.expiresAt.getTime() >= now) {
|
|
159
|
+
if (!data.clientId || data.clientId === options.clientId) {
|
|
160
|
+
flags = data.flags;
|
|
61
161
|
}
|
|
62
162
|
}
|
|
63
|
-
|
|
163
|
+
if (!flags) {
|
|
164
|
+
return createEmptyContext();
|
|
165
|
+
}
|
|
166
|
+
const featuresSet = options.features.length <= 2 ? options.features : new Set(options.features);
|
|
167
|
+
const context = {};
|
|
168
|
+
for (let i = 0; i < FEATURE_NUMBERS.length; i++) {
|
|
169
|
+
const feature = FEATURE_NUMBERS[i];
|
|
170
|
+
const actionNames = FEATURE_TO_ACTIONS[feature];
|
|
171
|
+
const isEnabled = (Array.isArray(featuresSet) ? featuresSet.includes(feature) : featuresSet.has(feature)) && (flags & feature) !== 0;
|
|
172
|
+
const len = actionNames.length;
|
|
173
|
+
if (len > 0) context[actionNames[0]] = isEnabled;
|
|
174
|
+
if (len > 1) context[actionNames[1]] = isEnabled;
|
|
175
|
+
if (len > 2) context[actionNames[2]] = isEnabled;
|
|
176
|
+
if (len > 3) context[actionNames[3]] = isEnabled;
|
|
177
|
+
}
|
|
178
|
+
return context;
|
|
64
179
|
}
|
|
65
|
-
function decodeClientHeader(headerValue, publicKey) {
|
|
66
|
-
if (!headerValue?.length) return;
|
|
180
|
+
async function decodeClientHeader(headerValue, publicKey) {
|
|
181
|
+
if (!headerValue?.length) return void 0;
|
|
67
182
|
try {
|
|
68
|
-
const
|
|
183
|
+
const separatorIndex = headerValue.indexOf(SEPARATOR);
|
|
184
|
+
if (separatorIndex === -1) {
|
|
185
|
+
throw new Error("Invalid header format: missing separator");
|
|
186
|
+
}
|
|
187
|
+
const data = headerValue.substring(0, separatorIndex);
|
|
188
|
+
const signature = headerValue.substring(separatorIndex + 1);
|
|
69
189
|
const dataBytes = browser.fromBase64(data);
|
|
70
190
|
const signatureBytes = browser.fromBase64(signature);
|
|
71
|
-
if (!verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
|
|
191
|
+
if (!await verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
|
|
72
192
|
throw new Error("Forged header value is provided");
|
|
73
193
|
}
|
|
74
194
|
const version = dataBytes[0];
|
|
75
|
-
let clientId;
|
|
76
195
|
if (version === browser.PROTOCOL_VERSION.V_1) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (dataBytes.byteLength > expectedByteLength) {
|
|
81
|
-
clientId = new TextDecoder().decode(dataBytes.subarray(expectedByteLength));
|
|
196
|
+
const expectedMinLength = VERSION_BYTES + NONCE_BYTES + UINT32_BYTES * 2;
|
|
197
|
+
if (dataBytes.byteLength < expectedMinLength) {
|
|
198
|
+
throw new Error("Invalid data length");
|
|
82
199
|
}
|
|
83
|
-
|
|
200
|
+
const view = new DataView(dataBytes.buffer, dataBytes.byteOffset, dataBytes.byteLength);
|
|
201
|
+
const expiresAtOffset = VERSION_BYTES + NONCE_BYTES;
|
|
202
|
+
const flagsOffset = expiresAtOffset + UINT32_BYTES;
|
|
203
|
+
const expiresAt = view.getUint32(expiresAtOffset, true);
|
|
204
|
+
const flags = view.getUint32(flagsOffset, true);
|
|
205
|
+
let clientId;
|
|
206
|
+
if (dataBytes.byteLength > expectedMinLength) {
|
|
207
|
+
const clientIdBytes = dataBytes.subarray(expectedMinLength);
|
|
208
|
+
clientId = new TextDecoder().decode(clientIdBytes);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
version,
|
|
212
|
+
expiresAt: new Date(expiresAt * 1e3),
|
|
213
|
+
flags,
|
|
214
|
+
...clientId && { clientId }
|
|
215
|
+
};
|
|
84
216
|
}
|
|
217
|
+
throw new Error(`Unsupported protocol version: ${version}`);
|
|
85
218
|
} catch (err) {
|
|
86
219
|
browser.log("warn", "Could not decode client header value", { reason: err?.message });
|
|
220
|
+
return void 0;
|
|
87
221
|
}
|
|
88
222
|
}
|
|
89
|
-
function encodeClientHeader(data, privateKey) {
|
|
223
|
+
async function encodeClientHeader(data, privateKey) {
|
|
90
224
|
const payload = mergeByteArrays([
|
|
91
225
|
new Uint8Array([data.version]),
|
|
92
226
|
new Uint8Array(nonce(NONCE_BYTES)),
|
|
@@ -94,7 +228,7 @@ function encodeClientHeader(data, privateKey) {
|
|
|
94
228
|
new Uint32Array([browser.setFlags(data.features)]),
|
|
95
229
|
...data.clientId?.length ? [new Uint8Array(new TextEncoder().encode(data.clientId))] : []
|
|
96
230
|
]);
|
|
97
|
-
return [browser.toBase64(payload), browser.toBase64(new Uint8Array(sign(payload.buffer, privateKey)))].join(SEPARATOR);
|
|
231
|
+
return [browser.toBase64(payload), browser.toBase64(new Uint8Array(await sign(payload.buffer, privateKey)))].join(SEPARATOR);
|
|
98
232
|
}
|
|
99
233
|
function mergeByteArrays(arrays) {
|
|
100
234
|
const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
|
|
@@ -110,14 +244,12 @@ function mergeByteArrays(arrays) {
|
|
|
110
244
|
}
|
|
111
245
|
return data;
|
|
112
246
|
}
|
|
113
|
-
function as32BitNumber(byteArray, begin) {
|
|
114
|
-
const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
|
|
115
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
116
|
-
return view.getUint32(0, true);
|
|
117
|
-
}
|
|
118
247
|
|
|
119
248
|
function Site(options) {
|
|
120
249
|
const serverHeaderValue = browser.encodeServerHeader(options.clientId, options.features);
|
|
250
|
+
if (options.cacheConfig) {
|
|
251
|
+
configureCaching(options.cacheConfig);
|
|
252
|
+
}
|
|
121
253
|
return {
|
|
122
254
|
parseClientToken: (headerValue) => parseClientToken(headerValue, { clientId: options.clientId, features: options.features }),
|
|
123
255
|
CLIENT_HEADER_NAME: browser.CLIENT_HEADER.HELLO.toLowerCase(),
|
|
@@ -135,7 +267,13 @@ exports.ZEROAD_NETWORK_PUBLIC_KEY = browser.ZEROAD_NETWORK_PUBLIC_KEY;
|
|
|
135
267
|
exports.decodeServerHeader = browser.decodeServerHeader;
|
|
136
268
|
exports.encodeServerHeader = browser.encodeServerHeader;
|
|
137
269
|
exports.setLogLevel = browser.setLogLevel;
|
|
270
|
+
exports.setLogTransport = browser.setLogTransport;
|
|
138
271
|
exports.Site = Site;
|
|
272
|
+
exports.cleanExpiredEntries = cleanExpiredEntries;
|
|
273
|
+
exports.configureCaching = configureCaching;
|
|
139
274
|
exports.decodeClientHeader = decodeClientHeader;
|
|
140
275
|
exports.encodeClientHeader = encodeClientHeader;
|
|
276
|
+
exports.getCacheConfig = getCacheConfig;
|
|
277
|
+
exports.headerCache = headerCache;
|
|
141
278
|
exports.parseClientToken = parseClientToken;
|
|
279
|
+
exports.trimCache = trimCache;
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,8 @@ export { C as CLIENT_HEADER, a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader,
|
|
|
3
3
|
|
|
4
4
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
5
5
|
declare function setLogLevel(level: LogLevel): void;
|
|
6
|
+
type LogTransport = (level: LogLevel, ...args: unknown[]) => void;
|
|
7
|
+
declare function setLogTransport(fn: LogTransport): void;
|
|
6
8
|
|
|
7
9
|
type FEATURE_ACTION = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
|
|
8
10
|
type ClientHeaderValue = string | string[] | undefined;
|
|
@@ -11,33 +13,53 @@ type ParseClientTokenOptions = {
|
|
|
11
13
|
clientId: string;
|
|
12
14
|
features: FEATURE[];
|
|
13
15
|
publicKey?: string;
|
|
16
|
+
bypassCache?: boolean;
|
|
14
17
|
};
|
|
15
|
-
declare function parseClientToken(headerValue: ClientHeaderValue, options: ParseClientTokenOptions): TokenContext
|
|
18
|
+
declare function parseClientToken(headerValue: ClientHeaderValue, options: ParseClientTokenOptions): Promise<TokenContext>;
|
|
16
19
|
type DecodedClientHeader = {
|
|
17
20
|
version: PROTOCOL_VERSION;
|
|
18
21
|
expiresAt: Date;
|
|
19
22
|
flags: number;
|
|
20
23
|
clientId?: string;
|
|
21
24
|
};
|
|
22
|
-
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined
|
|
25
|
+
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): Promise<DecodedClientHeader | undefined>;
|
|
23
26
|
type EncodeData = {
|
|
24
27
|
version: PROTOCOL_VERSION;
|
|
25
28
|
expiresAt: Date;
|
|
26
29
|
features: FEATURE[];
|
|
27
30
|
clientId?: string;
|
|
28
31
|
};
|
|
29
|
-
declare function encodeClientHeader(data: EncodeData, privateKey: string): string
|
|
32
|
+
declare function encodeClientHeader(data: EncodeData, privateKey: string): Promise<string>;
|
|
33
|
+
|
|
34
|
+
interface CacheConfig {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
maxSize: number;
|
|
37
|
+
ttl: number;
|
|
38
|
+
}
|
|
39
|
+
declare let cacheConfig: CacheConfig;
|
|
40
|
+
declare function configureCaching(config: Partial<CacheConfig>): void;
|
|
41
|
+
declare function getCacheConfig(): Readonly<CacheConfig>;
|
|
42
|
+
interface CacheEntry {
|
|
43
|
+
data: DecodedClientHeader | undefined;
|
|
44
|
+
effectiveExpiry: number;
|
|
45
|
+
accessCount: number;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
}
|
|
48
|
+
declare const headerCache: Map<string, CacheEntry>;
|
|
49
|
+
declare function trimCache(): void;
|
|
50
|
+
declare function cleanExpiredEntries(now: number): void;
|
|
30
51
|
|
|
31
52
|
type SiteOptions = {
|
|
32
53
|
clientId: string;
|
|
33
54
|
features: FEATURE[];
|
|
55
|
+
cacheConfig?: CacheConfig;
|
|
34
56
|
};
|
|
35
57
|
declare function Site(options: SiteOptions): {
|
|
36
|
-
parseClientToken: (headerValue: ClientHeaderValue) => TokenContext
|
|
58
|
+
parseClientToken: (headerValue: ClientHeaderValue) => Promise<TokenContext>;
|
|
37
59
|
CLIENT_HEADER_NAME: string;
|
|
38
60
|
SERVER_HEADER_NAME: SERVER_HEADER;
|
|
39
61
|
SERVER_HEADER_VALUE: string;
|
|
40
62
|
};
|
|
41
63
|
|
|
42
|
-
export { FEATURE, PROTOCOL_VERSION, SERVER_HEADER, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
|
|
43
|
-
export type { ClientHeaderValue, DecodedClientHeader, FEATURE_ACTION, ParseClientTokenOptions, TokenContext };
|
|
64
|
+
export { FEATURE, PROTOCOL_VERSION, SERVER_HEADER, Site, cacheConfig, cleanExpiredEntries, configureCaching, decodeClientHeader, encodeClientHeader, getCacheConfig, headerCache, parseClientToken, setLogLevel, setLogTransport, trimCache };
|
|
65
|
+
export type { CacheConfig, ClientHeaderValue, DecodedClientHeader, FEATURE_ACTION, ParseClientTokenOptions, TokenContext };
|
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,8 @@ export { C as CLIENT_HEADER, a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader,
|
|
|
3
3
|
|
|
4
4
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
5
5
|
declare function setLogLevel(level: LogLevel): void;
|
|
6
|
+
type LogTransport = (level: LogLevel, ...args: unknown[]) => void;
|
|
7
|
+
declare function setLogTransport(fn: LogTransport): void;
|
|
6
8
|
|
|
7
9
|
type FEATURE_ACTION = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
|
|
8
10
|
type ClientHeaderValue = string | string[] | undefined;
|
|
@@ -11,33 +13,53 @@ type ParseClientTokenOptions = {
|
|
|
11
13
|
clientId: string;
|
|
12
14
|
features: FEATURE[];
|
|
13
15
|
publicKey?: string;
|
|
16
|
+
bypassCache?: boolean;
|
|
14
17
|
};
|
|
15
|
-
declare function parseClientToken(headerValue: ClientHeaderValue, options: ParseClientTokenOptions): TokenContext
|
|
18
|
+
declare function parseClientToken(headerValue: ClientHeaderValue, options: ParseClientTokenOptions): Promise<TokenContext>;
|
|
16
19
|
type DecodedClientHeader = {
|
|
17
20
|
version: PROTOCOL_VERSION;
|
|
18
21
|
expiresAt: Date;
|
|
19
22
|
flags: number;
|
|
20
23
|
clientId?: string;
|
|
21
24
|
};
|
|
22
|
-
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined
|
|
25
|
+
declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): Promise<DecodedClientHeader | undefined>;
|
|
23
26
|
type EncodeData = {
|
|
24
27
|
version: PROTOCOL_VERSION;
|
|
25
28
|
expiresAt: Date;
|
|
26
29
|
features: FEATURE[];
|
|
27
30
|
clientId?: string;
|
|
28
31
|
};
|
|
29
|
-
declare function encodeClientHeader(data: EncodeData, privateKey: string): string
|
|
32
|
+
declare function encodeClientHeader(data: EncodeData, privateKey: string): Promise<string>;
|
|
33
|
+
|
|
34
|
+
interface CacheConfig {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
maxSize: number;
|
|
37
|
+
ttl: number;
|
|
38
|
+
}
|
|
39
|
+
declare let cacheConfig: CacheConfig;
|
|
40
|
+
declare function configureCaching(config: Partial<CacheConfig>): void;
|
|
41
|
+
declare function getCacheConfig(): Readonly<CacheConfig>;
|
|
42
|
+
interface CacheEntry {
|
|
43
|
+
data: DecodedClientHeader | undefined;
|
|
44
|
+
effectiveExpiry: number;
|
|
45
|
+
accessCount: number;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
}
|
|
48
|
+
declare const headerCache: Map<string, CacheEntry>;
|
|
49
|
+
declare function trimCache(): void;
|
|
50
|
+
declare function cleanExpiredEntries(now: number): void;
|
|
30
51
|
|
|
31
52
|
type SiteOptions = {
|
|
32
53
|
clientId: string;
|
|
33
54
|
features: FEATURE[];
|
|
55
|
+
cacheConfig?: CacheConfig;
|
|
34
56
|
};
|
|
35
57
|
declare function Site(options: SiteOptions): {
|
|
36
|
-
parseClientToken: (headerValue: ClientHeaderValue) => TokenContext
|
|
58
|
+
parseClientToken: (headerValue: ClientHeaderValue) => Promise<TokenContext>;
|
|
37
59
|
CLIENT_HEADER_NAME: string;
|
|
38
60
|
SERVER_HEADER_NAME: SERVER_HEADER;
|
|
39
61
|
SERVER_HEADER_VALUE: string;
|
|
40
62
|
};
|
|
41
63
|
|
|
42
|
-
export { FEATURE, PROTOCOL_VERSION, SERVER_HEADER, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
|
|
43
|
-
export type { ClientHeaderValue, DecodedClientHeader, FEATURE_ACTION, ParseClientTokenOptions, TokenContext };
|
|
64
|
+
export { FEATURE, PROTOCOL_VERSION, SERVER_HEADER, Site, cacheConfig, cleanExpiredEntries, configureCaching, decodeClientHeader, encodeClientHeader, getCacheConfig, headerCache, parseClientToken, setLogLevel, setLogTransport, trimCache };
|
|
65
|
+
export type { CacheConfig, ClientHeaderValue, DecodedClientHeader, FEATURE_ACTION, ParseClientTokenOptions, TokenContext };
|