@zeroad.network/token 0.13.12 → 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 +692 -112
- package/dist/{browser-BiNZ2c6t.d.cts → browser-Bl2JEJGW.d.cts} +6 -6
- package/dist/{browser-BiNZ2c6t.d.mts → browser-Bl2JEJGW.d.mts} +6 -6
- package/dist/{browser-A5-3SGMN.cjs → browser-D3BXUIiT.cjs} +38 -39
- package/dist/{browser--AdrX2EX.mjs → browser-VjxLaeuu.mjs} +35 -35
- package/dist/browser.d.mts +1 -1
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +179 -41
- package/dist/index.d.cts +36 -14
- package/dist/index.d.mts +36 -14
- package/dist/index.mjs +172 -40
- package/package.json +3 -4
|
@@ -8,7 +8,7 @@ declare const ZEROAD_NETWORK_PUBLIC_KEY: string;
|
|
|
8
8
|
* IMPORTANT: Requirements listed for each feature class MUST be fulfilled fully.
|
|
9
9
|
* Failure to comply will result in the site getting banned from Zero Ad Network platform.
|
|
10
10
|
*/
|
|
11
|
-
declare enum
|
|
11
|
+
declare enum FEATURE {
|
|
12
12
|
/**
|
|
13
13
|
* Feature requirements:
|
|
14
14
|
* - Disable all advertisements on the page;
|
|
@@ -24,10 +24,10 @@ declare enum FEATURES {
|
|
|
24
24
|
*/
|
|
25
25
|
ONE_PASS = 2
|
|
26
26
|
}
|
|
27
|
-
declare enum
|
|
27
|
+
declare enum SERVER_HEADER {
|
|
28
28
|
WELCOME = "X-Better-Web-Welcome"
|
|
29
29
|
}
|
|
30
|
-
declare enum
|
|
30
|
+
declare enum CLIENT_HEADER {
|
|
31
31
|
HELLO = "X-Better-Web-Hello"
|
|
32
32
|
}
|
|
33
33
|
declare enum PROTOCOL_VERSION {
|
|
@@ -35,13 +35,13 @@ declare enum PROTOCOL_VERSION {
|
|
|
35
35
|
}
|
|
36
36
|
declare const CURRENT_PROTOCOL_VERSION = PROTOCOL_VERSION.V_1;
|
|
37
37
|
|
|
38
|
-
declare function encodeServerHeader(clientId: string, features:
|
|
38
|
+
declare function encodeServerHeader(clientId: string, features: FEATURE[]): string;
|
|
39
39
|
type WelcomeHeader = {
|
|
40
40
|
clientId: string;
|
|
41
41
|
version: PROTOCOL_VERSION;
|
|
42
|
-
features: (keyof typeof
|
|
42
|
+
features: (keyof typeof FEATURE)[];
|
|
43
43
|
};
|
|
44
44
|
declare function decodeServerHeader(headerValue: string | null | undefined): WelcomeHeader | undefined;
|
|
45
45
|
|
|
46
|
-
export {
|
|
46
|
+
export { CLIENT_HEADER as C, FEATURE as F, PROTOCOL_VERSION as P, SERVER_HEADER as S, ZEROAD_NETWORK_PUBLIC_KEY as Z, CURRENT_PROTOCOL_VERSION as a, decodeServerHeader as d, encodeServerHeader as e };
|
|
47
47
|
export type { WelcomeHeader as W };
|
|
@@ -8,7 +8,7 @@ declare const ZEROAD_NETWORK_PUBLIC_KEY: string;
|
|
|
8
8
|
* IMPORTANT: Requirements listed for each feature class MUST be fulfilled fully.
|
|
9
9
|
* Failure to comply will result in the site getting banned from Zero Ad Network platform.
|
|
10
10
|
*/
|
|
11
|
-
declare enum
|
|
11
|
+
declare enum FEATURE {
|
|
12
12
|
/**
|
|
13
13
|
* Feature requirements:
|
|
14
14
|
* - Disable all advertisements on the page;
|
|
@@ -24,10 +24,10 @@ declare enum FEATURES {
|
|
|
24
24
|
*/
|
|
25
25
|
ONE_PASS = 2
|
|
26
26
|
}
|
|
27
|
-
declare enum
|
|
27
|
+
declare enum SERVER_HEADER {
|
|
28
28
|
WELCOME = "X-Better-Web-Welcome"
|
|
29
29
|
}
|
|
30
|
-
declare enum
|
|
30
|
+
declare enum CLIENT_HEADER {
|
|
31
31
|
HELLO = "X-Better-Web-Hello"
|
|
32
32
|
}
|
|
33
33
|
declare enum PROTOCOL_VERSION {
|
|
@@ -35,13 +35,13 @@ declare enum PROTOCOL_VERSION {
|
|
|
35
35
|
}
|
|
36
36
|
declare const CURRENT_PROTOCOL_VERSION = PROTOCOL_VERSION.V_1;
|
|
37
37
|
|
|
38
|
-
declare function encodeServerHeader(clientId: string, features:
|
|
38
|
+
declare function encodeServerHeader(clientId: string, features: FEATURE[]): string;
|
|
39
39
|
type WelcomeHeader = {
|
|
40
40
|
clientId: string;
|
|
41
41
|
version: PROTOCOL_VERSION;
|
|
42
|
-
features: (keyof typeof
|
|
42
|
+
features: (keyof typeof FEATURE)[];
|
|
43
43
|
};
|
|
44
44
|
declare function decodeServerHeader(headerValue: string | null | undefined): WelcomeHeader | undefined;
|
|
45
45
|
|
|
46
|
-
export {
|
|
46
|
+
export { CLIENT_HEADER as C, FEATURE as F, PROTOCOL_VERSION as P, SERVER_HEADER as S, ZEROAD_NETWORK_PUBLIC_KEY as Z, CURRENT_PROTOCOL_VERSION as a, decodeServerHeader as d, encodeServerHeader as e };
|
|
47
47
|
export type { WelcomeHeader as W };
|
|
@@ -12,43 +12,41 @@ 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
|
|
|
21
27
|
const ZEROAD_NETWORK_PUBLIC_KEY = "MCowBQYDK2VwAyEAignXRaTQtxEDl4ThULucKNQKEEO2Lo5bEO8qKwjSDVs=";
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
-
})(
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
return
|
|
30
|
-
})(
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
return
|
|
34
|
-
})(
|
|
28
|
+
var FEATURE = /* @__PURE__ */ ((FEATURE2) => {
|
|
29
|
+
FEATURE2[FEATURE2["CLEAN_WEB"] = 1] = "CLEAN_WEB";
|
|
30
|
+
FEATURE2[FEATURE2["ONE_PASS"] = 2] = "ONE_PASS";
|
|
31
|
+
return FEATURE2;
|
|
32
|
+
})(FEATURE || {});
|
|
33
|
+
var SERVER_HEADER = /* @__PURE__ */ ((SERVER_HEADER2) => {
|
|
34
|
+
SERVER_HEADER2["WELCOME"] = "X-Better-Web-Welcome";
|
|
35
|
+
return SERVER_HEADER2;
|
|
36
|
+
})(SERVER_HEADER || {});
|
|
37
|
+
var CLIENT_HEADER = /* @__PURE__ */ ((CLIENT_HEADER2) => {
|
|
38
|
+
CLIENT_HEADER2["HELLO"] = "X-Better-Web-Hello";
|
|
39
|
+
return CLIENT_HEADER2;
|
|
40
|
+
})(CLIENT_HEADER || {});
|
|
35
41
|
var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
36
42
|
PROTOCOL_VERSION2[PROTOCOL_VERSION2["V_1"] = 1] = "V_1";
|
|
37
43
|
return 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(FEATURES)) {
|
|
46
|
-
if (!isNaN(Number(key))) continue;
|
|
47
|
-
const typedKey = key;
|
|
48
|
-
cachedFeatures.set(typedKey, FEATURES[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,28 +59,30 @@ 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 = "^";
|
|
84
|
-
const validFeatureValues = Object.values(
|
|
85
|
-
const validFeatureKeys = Object.values(
|
|
84
|
+
const validFeatureValues = Object.values(FEATURE).filter((key) => !isNaN(Number(key)));
|
|
85
|
+
const validFeatureKeys = Object.values(FEATURE).filter((key) => isNaN(Number(key)));
|
|
86
86
|
function encodeServerHeader(clientId, features) {
|
|
87
87
|
if (!clientId?.length) {
|
|
88
88
|
throw new Error("The provided `clientId` value cannot be an empty string");
|
|
@@ -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 {
|
|
@@ -120,17 +120,16 @@ function decodeServerHeader(headerValue) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
exports.
|
|
123
|
+
exports.CLIENT_HEADER = CLIENT_HEADER;
|
|
124
124
|
exports.CURRENT_PROTOCOL_VERSION = CURRENT_PROTOCOL_VERSION;
|
|
125
|
-
exports.
|
|
125
|
+
exports.FEATURE = FEATURE;
|
|
126
126
|
exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
|
|
127
|
-
exports.
|
|
127
|
+
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,43 +10,41 @@ 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
|
|
|
19
25
|
const ZEROAD_NETWORK_PUBLIC_KEY = "MCowBQYDK2VwAyEAignXRaTQtxEDl4ThULucKNQKEEO2Lo5bEO8qKwjSDVs=";
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return
|
|
24
|
-
})(
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
-
})(
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
return
|
|
32
|
-
})(
|
|
26
|
+
var FEATURE = /* @__PURE__ */ ((FEATURE2) => {
|
|
27
|
+
FEATURE2[FEATURE2["CLEAN_WEB"] = 1] = "CLEAN_WEB";
|
|
28
|
+
FEATURE2[FEATURE2["ONE_PASS"] = 2] = "ONE_PASS";
|
|
29
|
+
return FEATURE2;
|
|
30
|
+
})(FEATURE || {});
|
|
31
|
+
var SERVER_HEADER = /* @__PURE__ */ ((SERVER_HEADER2) => {
|
|
32
|
+
SERVER_HEADER2["WELCOME"] = "X-Better-Web-Welcome";
|
|
33
|
+
return SERVER_HEADER2;
|
|
34
|
+
})(SERVER_HEADER || {});
|
|
35
|
+
var CLIENT_HEADER = /* @__PURE__ */ ((CLIENT_HEADER2) => {
|
|
36
|
+
CLIENT_HEADER2["HELLO"] = "X-Better-Web-Hello";
|
|
37
|
+
return CLIENT_HEADER2;
|
|
38
|
+
})(CLIENT_HEADER || {});
|
|
33
39
|
var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
34
40
|
PROTOCOL_VERSION2[PROTOCOL_VERSION2["V_1"] = 1] = "V_1";
|
|
35
41
|
return 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(FEATURES)) {
|
|
44
|
-
if (!isNaN(Number(key))) continue;
|
|
45
|
-
const typedKey = key;
|
|
46
|
-
cachedFeatures.set(typedKey, FEATURES[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,28 +57,30 @@ 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 = "^";
|
|
82
|
-
const validFeatureValues = Object.values(
|
|
83
|
-
const validFeatureKeys = Object.values(
|
|
82
|
+
const validFeatureValues = Object.values(FEATURE).filter((key) => !isNaN(Number(key)));
|
|
83
|
+
const validFeatureKeys = Object.values(FEATURE).filter((key) => isNaN(Number(key)));
|
|
84
84
|
function encodeServerHeader(clientId, features) {
|
|
85
85
|
if (!clientId?.length) {
|
|
86
86
|
throw new Error("The provided `clientId` value cannot be an empty string");
|
|
@@ -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 {
|
|
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.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as
|
|
1
|
+
export { C as CLIENT_HEADER, a as CURRENT_PROTOCOL_VERSION, F as FEATURE, P as PROTOCOL_VERSION, S as SERVER_HEADER, W as WelcomeHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader } from './browser-Bl2JEJGW.mjs';
|
package/dist/browser.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as
|
|
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.
|
|
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(FEATURES_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,32 +244,36 @@ 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
|
-
CLIENT_HEADER_NAME: browser.
|
|
124
|
-
SERVER_HEADER_NAME: browser.
|
|
255
|
+
CLIENT_HEADER_NAME: browser.CLIENT_HEADER.HELLO.toLowerCase(),
|
|
256
|
+
SERVER_HEADER_NAME: browser.SERVER_HEADER.WELCOME,
|
|
125
257
|
SERVER_HEADER_VALUE: serverHeaderValue
|
|
126
258
|
};
|
|
127
259
|
}
|
|
128
260
|
|
|
129
|
-
exports.
|
|
261
|
+
exports.CLIENT_HEADER = browser.CLIENT_HEADER;
|
|
130
262
|
exports.CURRENT_PROTOCOL_VERSION = browser.CURRENT_PROTOCOL_VERSION;
|
|
131
|
-
exports.
|
|
263
|
+
exports.FEATURE = browser.FEATURE;
|
|
132
264
|
exports.PROTOCOL_VERSION = browser.PROTOCOL_VERSION;
|
|
133
|
-
exports.
|
|
265
|
+
exports.SERVER_HEADER = browser.SERVER_HEADER;
|
|
134
266
|
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;
|