@zeroad.network/token 0.11.2 → 0.12.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 +36 -34
- package/dist/{browser-CEwtQQBM.mjs → browser-ByaVwJr6.mjs} +37 -25
- package/dist/{browser-DhviIs25.cjs → browser-C50SBRBw.cjs} +38 -27
- package/dist/browser.d.cts +19 -16
- package/dist/browser.d.mts +19 -16
- package/dist/browser.mjs +1 -1
- package/dist/index.cjs +57 -63
- package/dist/index.d.cts +13 -26
- package/dist/index.d.mts +13 -26
- package/dist/index.mjs +58 -58
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
## Introduction
|
|
2
2
|
|
|
3
|
-
**@zeroad.network/token** is a module meant to be used by partnering sites of [Zero Ad Network](https://zeroad.network) platform. It's a lightweight module that works on Nodejs 18 and above, and Bun 1.3 and above runtimes.
|
|
3
|
+
**@zeroad.network/token** is a TypeScript ready module meant to be used by partnering sites of [Zero Ad Network](https://zeroad.network) platform. It's a lightweight module that works on Nodejs 18 and above, and Bun 1.3 and above runtimes.
|
|
4
4
|
|
|
5
|
-
This
|
|
5
|
+
This module allows a Zero Ad Network program partnering sites and Web APIs to verify determine if incoming web requests are coming from our browser extension users with active subscription.
|
|
6
6
|
|
|
7
7
|
Their browser extension will send the `X-Better-Web-Hello` Request Header which will let our module to verify it's our actively subscribed user and will allow your site to make a decision whether to disable ads, paywalls or enable access to otherwise paid content of yours.
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ In the second step of the Site registration process you'll be presented with you
|
|
|
20
20
|
|
|
21
21
|
## Module Installation
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
As it is written in TypeScript, all types and interfaces are available. Also, this package works well in `mjs` (ESM) and `cjs` (CJS - older node versions) environments. You choose to either use `import` or `require()` statements. Imports are always preferred.
|
|
24
24
|
|
|
25
25
|
Install this package using your favourite package manager:
|
|
26
26
|
|
|
@@ -28,10 +28,10 @@ Install this package using your favourite package manager:
|
|
|
28
28
|
# npm
|
|
29
29
|
npm add @zeroad.network/token
|
|
30
30
|
|
|
31
|
-
# yarn
|
|
31
|
+
# or yarn
|
|
32
32
|
yarn add @zeroad.network/token
|
|
33
33
|
|
|
34
|
-
# pnpm
|
|
34
|
+
# or pnpm
|
|
35
35
|
pnpm add @zeroad.network/token
|
|
36
36
|
|
|
37
37
|
# or Bun
|
|
@@ -40,60 +40,62 @@ bun add @zeroad.network/token
|
|
|
40
40
|
|
|
41
41
|
# Examples
|
|
42
42
|
|
|
43
|
-
Take the example as a reference
|
|
43
|
+
Take the simplest JavaScript example as a reference. The most basic, and honestly, quite complete use with `express` v.5 could look similar to this:
|
|
44
44
|
|
|
45
45
|
```js
|
|
46
46
|
import express from "express";
|
|
47
|
-
import {
|
|
48
|
-
init,
|
|
49
|
-
getServerHeaderName,
|
|
50
|
-
getServerHeaderValue,
|
|
51
|
-
processRequest,
|
|
52
|
-
getClientHeaderName,
|
|
53
|
-
} from "@zeroad.network/token";
|
|
47
|
+
import { ZeroAdNetwork } from "@zeroad.network/token";
|
|
54
48
|
|
|
55
49
|
const app = express();
|
|
56
|
-
const port = 3000;
|
|
57
50
|
|
|
51
|
+
// Initialize your Zero Ad Network module
|
|
58
52
|
// Welcome Header Value acquired during Site Registration process at Zero Ad Network platform
|
|
59
53
|
const ZERO_AD_NETWORK_WELCOME_HEADER_VALUE = "AZqnKU56eIC7vCD1PPlwHg^1^3";
|
|
60
|
-
|
|
61
|
-
// Initialize your Zero Ad Network module
|
|
62
|
-
init({ value: ZERO_AD_NETWORK_WELCOME_HEADER_VALUE });
|
|
54
|
+
const zeroAd = ZeroAdNetwork(ZERO_AD_NETWORK_WELCOME_HEADER_VALUE);
|
|
63
55
|
|
|
64
56
|
app
|
|
65
57
|
.use((req, res, next) => {
|
|
66
58
|
// X-Better-Web-Welcome header injection can could have it's own simple middleware like this:
|
|
67
|
-
res.set(
|
|
59
|
+
res.set(zeroAd.SERVER_HEADER_NAME, zeroAd.SERVER_HEADER_VALUE);
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const tokenContext = processRequest(req.get(getClientHeaderName()));
|
|
73
|
-
res.locals.tokenContext = tokenContext;
|
|
61
|
+
// Process request token from incoming client token header value.
|
|
62
|
+
// And attach processed token info to request for downstream usage.
|
|
63
|
+
req.tokenContext = zeroAd.parseToken(req.get[zeroAd.CLIENT_HEADER_NAME]);
|
|
74
64
|
|
|
75
65
|
next();
|
|
76
66
|
})
|
|
77
67
|
.get("/", (req, res) => {
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
// For example:
|
|
69
|
+
console.log(req.tokenContext);
|
|
70
|
+
|
|
71
|
+
// Will produce the following structure:
|
|
72
|
+
req.tokenContext = {
|
|
73
|
+
// If set to true: Render no advertisements anywhere on the page
|
|
74
|
+
ADS_OFF: boolean,
|
|
75
|
+
// If set to true: Render no Cookie Consent screens (headers, footers or dialogs) on the page with complete OPT-OUT for non-functional trackers
|
|
76
|
+
COOKIE_CONSENT_OFF: boolean,
|
|
77
|
+
// If set to true: Render no marketing dialogs or popups such as newsletter, promotion etc. on the page
|
|
78
|
+
MARKETING_DIALOG_OFF: boolean,
|
|
79
|
+
// If set to true: Provide automatic access to otherwise paywalled content such as articles, news etc.
|
|
80
|
+
CONTENT_PAYWALL_OFF: boolean,
|
|
81
|
+
// If set to true: Provide automatic access to site features provided behind a SaaS at least the basic subscription plan
|
|
82
|
+
SUBSCRIPTION_ACCESS_ON: boolean,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// In your template adjust your content depending on tokenContext values
|
|
86
|
+
res.render("index.ejs", { tokenContext });
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
const port = 3000;
|
|
90
|
+
|
|
89
91
|
app.listen(port, () => {
|
|
90
92
|
console.log(`Server listening at http://localhost:${port}`);
|
|
91
93
|
});
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
For
|
|
96
|
+
For all example implementations such as `Express.js` (JavaScript), `Hono` and `Fastify` (both are TypeScript), please go to [see more examples](https://github.com/laurynas-karvelis/zeroad-token-ts/tree/main/examples/).
|
|
95
97
|
|
|
96
|
-
P.S.: Each web request coming from active subscriber using their Zero Ad Network browser extension will incur a tiny fraction of CPU computation cost to verify the token data matches its encrypted signature. On modern web infrastructure a request execution time will increase roughly by ~0.
|
|
98
|
+
P.S.: Each web request coming from active subscriber using their Zero Ad Network browser extension will incur a tiny fraction of CPU computation cost to verify the token data matches its encrypted signature. On modern web infrastructure a request execution time will increase roughly by ~0.06ms to 0.2ms or so. Mileage might vary, but the impact is minimal.
|
|
97
99
|
|
|
98
100
|
# Final thoughts
|
|
99
101
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const ZEROAD_NETWORK_PUBLIC_KEY = "MCowBQYDK2VwAyEAignXRaTQtxEDl4ThULucKNQKEEO2Lo5bEO8qKwjSDVs=";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
var FEATURES = /* @__PURE__ */ ((FEATURES2) => {
|
|
3
|
+
FEATURES2[FEATURES2["ADS_OFF"] = 1] = "ADS_OFF";
|
|
4
|
+
FEATURES2[FEATURES2["COOKIE_CONSENT_OFF"] = 2] = "COOKIE_CONSENT_OFF";
|
|
5
|
+
FEATURES2[FEATURES2["MARKETING_DIALOG_OFF"] = 4] = "MARKETING_DIALOG_OFF";
|
|
6
|
+
FEATURES2[FEATURES2["CONTENT_PAYWALL_OFF"] = 8] = "CONTENT_PAYWALL_OFF";
|
|
7
|
+
FEATURES2[FEATURES2["SUBSCRIPTION_ACCESS_ON"] = 16] = "SUBSCRIPTION_ACCESS_ON";
|
|
8
|
+
return FEATURES2;
|
|
9
|
+
})(FEATURES || {});
|
|
8
10
|
var SERVER_HEADERS = /* @__PURE__ */ ((SERVER_HEADERS2) => {
|
|
9
11
|
SERVER_HEADERS2["WELCOME"] = "X-Better-Web-Welcome";
|
|
10
12
|
return SERVER_HEADERS2;
|
|
@@ -19,6 +21,12 @@ var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
|
19
21
|
})(PROTOCOL_VERSION || {});
|
|
20
22
|
const CURRENT_PROTOCOL_VERSION = 1 /* V_1 */;
|
|
21
23
|
|
|
24
|
+
let SITE_FEATURES_NATIVE$1;
|
|
25
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26
|
+
const getSiteFeaturesNative = () => {
|
|
27
|
+
if (SITE_FEATURES_NATIVE$1?.length) return SITE_FEATURES_NATIVE$1;
|
|
28
|
+
return SITE_FEATURES_NATIVE$1 = Object.entries(FEATURES).filter(([key]) => isNaN(Number(key)));
|
|
29
|
+
};
|
|
22
30
|
const toBase64 = (data) => {
|
|
23
31
|
if (typeof data.toBase64 === "function") return data.toBase64();
|
|
24
32
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
@@ -65,15 +73,6 @@ const base64ToUuid = (input) => {
|
|
|
65
73
|
hex.slice(20, 20 + 12)
|
|
66
74
|
].join("-");
|
|
67
75
|
};
|
|
68
|
-
const unixTimestampToBytes = (date) => {
|
|
69
|
-
const buffer = new ArrayBuffer(4);
|
|
70
|
-
new DataView(buffer).setUint32(0, Math.floor(date.getTime() / 1e3));
|
|
71
|
-
return new Uint8Array(buffer);
|
|
72
|
-
};
|
|
73
|
-
const bytesToUnixTimestamp = (bytes) => {
|
|
74
|
-
const u32int = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
|
|
75
|
-
return new Date(u32int * 1e3);
|
|
76
|
-
};
|
|
77
76
|
const assert = (value, message) => {
|
|
78
77
|
if (!value) throw new Error(message);
|
|
79
78
|
};
|
|
@@ -99,15 +98,26 @@ function log(level, ...args) {
|
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
const SEPARATOR = "^";
|
|
101
|
+
const SITE_FEATURES_NATIVE = getSiteFeaturesNative();
|
|
102
102
|
class ServerHeader {
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
NAME = SERVER_HEADERS.WELCOME.toLowerCase();
|
|
104
|
+
VALUE;
|
|
105
105
|
constructor(options) {
|
|
106
|
-
if ("
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
if (typeof options === "string") {
|
|
107
|
+
if (!options.length) {
|
|
108
|
+
throw new Error("ServerHeader: non-empty welcome header string value must be provided");
|
|
109
|
+
}
|
|
110
|
+
this.VALUE = options;
|
|
111
|
+
} else if (isObject(options) && "siteId" in options && "features" in options) {
|
|
109
112
|
const { siteId, features } = options;
|
|
110
|
-
|
|
113
|
+
if (typeof siteId !== "string" || !siteId.length || !Array.isArray(features) || !features.length) {
|
|
114
|
+
throw new Error("ServerHeader options must be provided");
|
|
115
|
+
}
|
|
116
|
+
this.VALUE = this.encode(siteId, features);
|
|
117
|
+
} else {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"ServerHeader: non-empty welcome header string value or { siteId, features } options must be provided"
|
|
120
|
+
);
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
encode(siteId, features) {
|
|
@@ -127,12 +137,14 @@ class ServerHeader {
|
|
|
127
137
|
);
|
|
128
138
|
const siteId = base64ToUuid(encodedSiteId);
|
|
129
139
|
assert(siteId.length === 36, "Invalid siteId value");
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
assert(Number(flags).toFixed(0).toString() === flags, "Invalid flags number");
|
|
141
|
+
let features = [];
|
|
142
|
+
for (const [feature, shift] of SITE_FEATURES_NATIVE) {
|
|
143
|
+
if (hasFeature(Number(flags), shift)) features.push(feature);
|
|
132
144
|
}
|
|
133
145
|
return {
|
|
134
146
|
version: Number(protocolVersion),
|
|
135
|
-
|
|
147
|
+
features,
|
|
136
148
|
siteId
|
|
137
149
|
};
|
|
138
150
|
} catch (err) {
|
|
@@ -141,4 +153,4 @@ class ServerHeader {
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
|
|
144
|
-
export { CLIENT_HEADERS as C, PROTOCOL_VERSION as P,
|
|
156
|
+
export { CLIENT_HEADERS as C, FEATURES as F, PROTOCOL_VERSION as P, ServerHeader as S, ZEROAD_NETWORK_PUBLIC_KEY as Z, setLogLevel as a, SERVER_HEADERS as b, CURRENT_PROTOCOL_VERSION as c, fromBase64 as f, getSiteFeaturesNative as g, hasFeature as h, log as l, setFeatures as s, toBase64 as t };
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ZEROAD_NETWORK_PUBLIC_KEY = "MCowBQYDK2VwAyEAignXRaTQtxEDl4ThULucKNQKEEO2Lo5bEO8qKwjSDVs=";
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
var FEATURES = /* @__PURE__ */ ((FEATURES2) => {
|
|
5
|
+
FEATURES2[FEATURES2["ADS_OFF"] = 1] = "ADS_OFF";
|
|
6
|
+
FEATURES2[FEATURES2["COOKIE_CONSENT_OFF"] = 2] = "COOKIE_CONSENT_OFF";
|
|
7
|
+
FEATURES2[FEATURES2["MARKETING_DIALOG_OFF"] = 4] = "MARKETING_DIALOG_OFF";
|
|
8
|
+
FEATURES2[FEATURES2["CONTENT_PAYWALL_OFF"] = 8] = "CONTENT_PAYWALL_OFF";
|
|
9
|
+
FEATURES2[FEATURES2["SUBSCRIPTION_ACCESS_ON"] = 16] = "SUBSCRIPTION_ACCESS_ON";
|
|
10
|
+
return FEATURES2;
|
|
11
|
+
})(FEATURES || {});
|
|
10
12
|
var SERVER_HEADERS = /* @__PURE__ */ ((SERVER_HEADERS2) => {
|
|
11
13
|
SERVER_HEADERS2["WELCOME"] = "X-Better-Web-Welcome";
|
|
12
14
|
return SERVER_HEADERS2;
|
|
@@ -21,6 +23,12 @@ var PROTOCOL_VERSION = /* @__PURE__ */ ((PROTOCOL_VERSION2) => {
|
|
|
21
23
|
})(PROTOCOL_VERSION || {});
|
|
22
24
|
const CURRENT_PROTOCOL_VERSION = 1 /* V_1 */;
|
|
23
25
|
|
|
26
|
+
let SITE_FEATURES_NATIVE$1;
|
|
27
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
const getSiteFeaturesNative = () => {
|
|
29
|
+
if (SITE_FEATURES_NATIVE$1?.length) return SITE_FEATURES_NATIVE$1;
|
|
30
|
+
return SITE_FEATURES_NATIVE$1 = Object.entries(FEATURES).filter(([key]) => isNaN(Number(key)));
|
|
31
|
+
};
|
|
24
32
|
const toBase64 = (data) => {
|
|
25
33
|
if (typeof data.toBase64 === "function") return data.toBase64();
|
|
26
34
|
if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
|
|
@@ -67,15 +75,6 @@ const base64ToUuid = (input) => {
|
|
|
67
75
|
hex.slice(20, 20 + 12)
|
|
68
76
|
].join("-");
|
|
69
77
|
};
|
|
70
|
-
const unixTimestampToBytes = (date) => {
|
|
71
|
-
const buffer = new ArrayBuffer(4);
|
|
72
|
-
new DataView(buffer).setUint32(0, Math.floor(date.getTime() / 1e3));
|
|
73
|
-
return new Uint8Array(buffer);
|
|
74
|
-
};
|
|
75
|
-
const bytesToUnixTimestamp = (bytes) => {
|
|
76
|
-
const u32int = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
|
|
77
|
-
return new Date(u32int * 1e3);
|
|
78
|
-
};
|
|
79
78
|
const assert = (value, message) => {
|
|
80
79
|
if (!value) throw new Error(message);
|
|
81
80
|
};
|
|
@@ -101,15 +100,26 @@ function log(level, ...args) {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
const SEPARATOR = "^";
|
|
103
|
+
const SITE_FEATURES_NATIVE = getSiteFeaturesNative();
|
|
104
104
|
class ServerHeader {
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
NAME = SERVER_HEADERS.WELCOME.toLowerCase();
|
|
106
|
+
VALUE;
|
|
107
107
|
constructor(options) {
|
|
108
|
-
if ("
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
if (typeof options === "string") {
|
|
109
|
+
if (!options.length) {
|
|
110
|
+
throw new Error("ServerHeader: non-empty welcome header string value must be provided");
|
|
111
|
+
}
|
|
112
|
+
this.VALUE = options;
|
|
113
|
+
} else if (isObject(options) && "siteId" in options && "features" in options) {
|
|
111
114
|
const { siteId, features } = options;
|
|
112
|
-
|
|
115
|
+
if (typeof siteId !== "string" || !siteId.length || !Array.isArray(features) || !features.length) {
|
|
116
|
+
throw new Error("ServerHeader options must be provided");
|
|
117
|
+
}
|
|
118
|
+
this.VALUE = this.encode(siteId, features);
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(
|
|
121
|
+
"ServerHeader: non-empty welcome header string value or { siteId, features } options must be provided"
|
|
122
|
+
);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
encode(siteId, features) {
|
|
@@ -129,12 +139,14 @@ class ServerHeader {
|
|
|
129
139
|
);
|
|
130
140
|
const siteId = base64ToUuid(encodedSiteId);
|
|
131
141
|
assert(siteId.length === 36, "Invalid siteId value");
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
assert(Number(flags).toFixed(0).toString() === flags, "Invalid flags number");
|
|
143
|
+
let features = [];
|
|
144
|
+
for (const [feature, shift] of SITE_FEATURES_NATIVE) {
|
|
145
|
+
if (hasFeature(Number(flags), shift)) features.push(feature);
|
|
134
146
|
}
|
|
135
147
|
return {
|
|
136
148
|
version: Number(protocolVersion),
|
|
137
|
-
|
|
149
|
+
features,
|
|
138
150
|
siteId
|
|
139
151
|
};
|
|
140
152
|
} catch (err) {
|
|
@@ -145,16 +157,15 @@ class ServerHeader {
|
|
|
145
157
|
|
|
146
158
|
exports.CLIENT_HEADERS = CLIENT_HEADERS;
|
|
147
159
|
exports.CURRENT_PROTOCOL_VERSION = CURRENT_PROTOCOL_VERSION;
|
|
160
|
+
exports.FEATURES = FEATURES;
|
|
148
161
|
exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
|
|
149
162
|
exports.SERVER_HEADERS = SERVER_HEADERS;
|
|
150
|
-
exports.SITE_FEATURES = SITE_FEATURES;
|
|
151
163
|
exports.ServerHeader = ServerHeader;
|
|
152
164
|
exports.ZEROAD_NETWORK_PUBLIC_KEY = ZEROAD_NETWORK_PUBLIC_KEY;
|
|
153
|
-
exports.bytesToUnixTimestamp = bytesToUnixTimestamp;
|
|
154
165
|
exports.fromBase64 = fromBase64;
|
|
166
|
+
exports.getSiteFeaturesNative = getSiteFeaturesNative;
|
|
155
167
|
exports.hasFeature = hasFeature;
|
|
156
168
|
exports.log = log;
|
|
157
169
|
exports.setFeatures = setFeatures;
|
|
158
170
|
exports.setLogLevel = setLogLevel;
|
|
159
171
|
exports.toBase64 = toBase64;
|
|
160
|
-
exports.unixTimestampToBytes = unixTimestampToBytes;
|
package/dist/browser.d.cts
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
* Used to verify `X-Better-Web-User` header values are not tampered with.
|
|
4
4
|
*/
|
|
5
5
|
declare const ZEROAD_NETWORK_PUBLIC_KEY: string;
|
|
6
|
-
declare enum
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
declare enum FEATURES {
|
|
7
|
+
/** Render no advertisements anywhere on the page */
|
|
8
|
+
ADS_OFF = 1,
|
|
9
|
+
/** Render no Cookie Consent screens (headers, footers or dialogs) on the page with complete OPT-OUT for non-functional trackers */
|
|
10
|
+
COOKIE_CONSENT_OFF = 2,
|
|
11
|
+
/** Render no marketing dialogs or popups such as newsletter, promotion etc. on the page */
|
|
12
|
+
MARKETING_DIALOG_OFF = 4,
|
|
13
|
+
/** Provide automatic access to otherwise paywalled content such as articles, news etc. */
|
|
14
|
+
CONTENT_PAYWALL_OFF = 8,
|
|
15
|
+
/** Provide automatic access to site features provided behind a SaaS at least the basic subscription plan */
|
|
16
|
+
SUBSCRIPTION_ACCESS_ON = 16
|
|
10
17
|
}
|
|
11
18
|
type UUID = string;
|
|
12
19
|
declare enum SERVER_HEADERS {
|
|
@@ -19,35 +26,31 @@ declare enum PROTOCOL_VERSION {
|
|
|
19
26
|
V_1 = 1
|
|
20
27
|
}
|
|
21
28
|
declare const CURRENT_PROTOCOL_VERSION = PROTOCOL_VERSION.V_1;
|
|
22
|
-
type ServerHeaderSimpleOptions = {
|
|
23
|
-
value: string;
|
|
24
|
-
};
|
|
25
29
|
type ServerHeaderExtendedOptions = {
|
|
26
30
|
siteId: UUID;
|
|
27
|
-
features:
|
|
31
|
+
features: FEATURES[];
|
|
28
32
|
};
|
|
29
|
-
type ServerHeaderOptions =
|
|
33
|
+
type ServerHeaderOptions = NonNullable<string | ServerHeaderExtendedOptions>;
|
|
30
34
|
type WelcomeHeaderParseResult = WelcomeHeader | undefined;
|
|
31
35
|
type WelcomeHeader = {
|
|
32
36
|
version: PROTOCOL_VERSION;
|
|
37
|
+
features: (keyof typeof FEATURES)[];
|
|
33
38
|
siteId: UUID;
|
|
34
|
-
flags: number;
|
|
35
39
|
};
|
|
36
40
|
type ClientHeaderParseResult = ClientParsedHeader | undefined;
|
|
37
41
|
type ClientParsedHeader = {
|
|
38
42
|
version: PROTOCOL_VERSION;
|
|
39
43
|
expiresAt: Date;
|
|
40
|
-
expired: boolean;
|
|
41
44
|
flags: number;
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
declare class ServerHeader {
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
NAME: string;
|
|
49
|
+
VALUE: string;
|
|
47
50
|
constructor(options: ServerHeaderOptions);
|
|
48
|
-
encode(siteId: UUID, features:
|
|
51
|
+
encode(siteId: UUID, features: FEATURES[]): string;
|
|
49
52
|
static decode(headerValue: string | undefined): WelcomeHeaderParseResult;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, PROTOCOL_VERSION, SERVER_HEADERS,
|
|
53
|
-
export type { ClientHeaderParseResult, ClientParsedHeader, ServerHeaderExtendedOptions, ServerHeaderOptions,
|
|
55
|
+
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, ServerHeader, ZEROAD_NETWORK_PUBLIC_KEY };
|
|
56
|
+
export type { ClientHeaderParseResult, ClientParsedHeader, ServerHeaderExtendedOptions, ServerHeaderOptions, UUID, WelcomeHeader, WelcomeHeaderParseResult };
|
package/dist/browser.d.mts
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
* Used to verify `X-Better-Web-User` header values are not tampered with.
|
|
4
4
|
*/
|
|
5
5
|
declare const ZEROAD_NETWORK_PUBLIC_KEY: string;
|
|
6
|
-
declare enum
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
declare enum FEATURES {
|
|
7
|
+
/** Render no advertisements anywhere on the page */
|
|
8
|
+
ADS_OFF = 1,
|
|
9
|
+
/** Render no Cookie Consent screens (headers, footers or dialogs) on the page with complete OPT-OUT for non-functional trackers */
|
|
10
|
+
COOKIE_CONSENT_OFF = 2,
|
|
11
|
+
/** Render no marketing dialogs or popups such as newsletter, promotion etc. on the page */
|
|
12
|
+
MARKETING_DIALOG_OFF = 4,
|
|
13
|
+
/** Provide automatic access to otherwise paywalled content such as articles, news etc. */
|
|
14
|
+
CONTENT_PAYWALL_OFF = 8,
|
|
15
|
+
/** Provide automatic access to site features provided behind a SaaS at least the basic subscription plan */
|
|
16
|
+
SUBSCRIPTION_ACCESS_ON = 16
|
|
10
17
|
}
|
|
11
18
|
type UUID = string;
|
|
12
19
|
declare enum SERVER_HEADERS {
|
|
@@ -19,35 +26,31 @@ declare enum PROTOCOL_VERSION {
|
|
|
19
26
|
V_1 = 1
|
|
20
27
|
}
|
|
21
28
|
declare const CURRENT_PROTOCOL_VERSION = PROTOCOL_VERSION.V_1;
|
|
22
|
-
type ServerHeaderSimpleOptions = {
|
|
23
|
-
value: string;
|
|
24
|
-
};
|
|
25
29
|
type ServerHeaderExtendedOptions = {
|
|
26
30
|
siteId: UUID;
|
|
27
|
-
features:
|
|
31
|
+
features: FEATURES[];
|
|
28
32
|
};
|
|
29
|
-
type ServerHeaderOptions =
|
|
33
|
+
type ServerHeaderOptions = NonNullable<string | ServerHeaderExtendedOptions>;
|
|
30
34
|
type WelcomeHeaderParseResult = WelcomeHeader | undefined;
|
|
31
35
|
type WelcomeHeader = {
|
|
32
36
|
version: PROTOCOL_VERSION;
|
|
37
|
+
features: (keyof typeof FEATURES)[];
|
|
33
38
|
siteId: UUID;
|
|
34
|
-
flags: number;
|
|
35
39
|
};
|
|
36
40
|
type ClientHeaderParseResult = ClientParsedHeader | undefined;
|
|
37
41
|
type ClientParsedHeader = {
|
|
38
42
|
version: PROTOCOL_VERSION;
|
|
39
43
|
expiresAt: Date;
|
|
40
|
-
expired: boolean;
|
|
41
44
|
flags: number;
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
declare class ServerHeader {
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
NAME: string;
|
|
49
|
+
VALUE: string;
|
|
47
50
|
constructor(options: ServerHeaderOptions);
|
|
48
|
-
encode(siteId: UUID, features:
|
|
51
|
+
encode(siteId: UUID, features: FEATURES[]): string;
|
|
49
52
|
static decode(headerValue: string | undefined): WelcomeHeaderParseResult;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, PROTOCOL_VERSION, SERVER_HEADERS,
|
|
53
|
-
export type { ClientHeaderParseResult, ClientParsedHeader, ServerHeaderExtendedOptions, ServerHeaderOptions,
|
|
55
|
+
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, ServerHeader, ZEROAD_NETWORK_PUBLIC_KEY };
|
|
56
|
+
export type { ClientHeaderParseResult, ClientParsedHeader, ServerHeaderExtendedOptions, ServerHeaderOptions, UUID, WelcomeHeader, WelcomeHeaderParseResult };
|
package/dist/browser.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { C as CLIENT_HEADERS,
|
|
1
|
+
export { C as CLIENT_HEADERS, c as CURRENT_PROTOCOL_VERSION, F as FEATURES, P as PROTOCOL_VERSION, b as SERVER_HEADERS, S as ServerHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY } from './browser-ByaVwJr6.mjs';
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var browser = require('./browser-
|
|
3
|
+
var browser = require('./browser-C50SBRBw.cjs');
|
|
4
4
|
var crypto = require('crypto');
|
|
5
5
|
|
|
6
6
|
const importPrivateKey = (privateKeyBase64) => {
|
|
@@ -30,30 +30,40 @@ const verify = (data, signature, publicKey) => {
|
|
|
30
30
|
};
|
|
31
31
|
const nonce = (size) => new Uint8Array(crypto.randomBytes(size));
|
|
32
32
|
|
|
33
|
+
const VERSION_BYTES = 1;
|
|
33
34
|
const NONCE_BYTES = 4;
|
|
34
35
|
const SEPARATOR = ".";
|
|
36
|
+
const SITE_FEATURES_NATIVE = browser.getSiteFeaturesNative();
|
|
37
|
+
const as32BitNumber = (byteArray, begin) => {
|
|
38
|
+
const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
|
|
39
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
40
|
+
return view.getUint32(0, true);
|
|
41
|
+
};
|
|
35
42
|
class ClientHeader {
|
|
36
43
|
cryptoPublicKey;
|
|
37
44
|
cryptoPrivateKey;
|
|
38
45
|
publicKey;
|
|
39
46
|
privateKey;
|
|
40
|
-
|
|
47
|
+
NAME = browser.CLIENT_HEADERS.HELLO.toLowerCase();
|
|
41
48
|
constructor(publicKey, privateKey) {
|
|
42
49
|
this.publicKey = publicKey;
|
|
43
50
|
this.privateKey = privateKey;
|
|
44
51
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
parseToken(headerValue) {
|
|
53
|
+
const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
54
|
+
const data = this.decode(headerValueAsString);
|
|
55
|
+
const expired = !data || data.expiresAt.getTime() < Date.now();
|
|
56
|
+
const result = {};
|
|
57
|
+
if (!data || expired) {
|
|
58
|
+
for (const [feature] of SITE_FEATURES_NATIVE) {
|
|
59
|
+
result[feature] = false;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
for (const [feature, shift] of SITE_FEATURES_NATIVE) {
|
|
64
|
+
result[feature] = browser.hasFeature(data.flags, shift);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
57
67
|
}
|
|
58
68
|
decode(headerValue) {
|
|
59
69
|
if (!headerValue?.length) return;
|
|
@@ -67,76 +77,60 @@ class ClientHeader {
|
|
|
67
77
|
}
|
|
68
78
|
const version = dataBytes[0];
|
|
69
79
|
if (version === browser.PROTOCOL_VERSION.V_1) {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const expired = expiresAt.getTime() < Date.now();
|
|
74
|
-
return { version, expiresAt, expired, flags: flagsBytes[0] };
|
|
80
|
+
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
81
|
+
const flags = as32BitNumber(dataBytes, dataBytes.length - Uint32Array.BYTES_PER_ELEMENT);
|
|
82
|
+
return { version, expiresAt: new Date(expiresAt * 1e3), flags };
|
|
75
83
|
}
|
|
76
84
|
} catch (err) {
|
|
77
85
|
browser.log("warn", "Could not decode client header value", { reason: err?.message });
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return shouldEnableVipExperience(data);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
88
|
+
encode(version, expiresAt, features) {
|
|
89
|
+
if (!this.privateKey) throw new Error("Private key is required");
|
|
90
|
+
const data = mergeByteArrays([
|
|
91
|
+
new Uint8Array([version]),
|
|
92
|
+
new Uint8Array(nonce(NONCE_BYTES)),
|
|
93
|
+
new Uint32Array([Math.floor(expiresAt.getTime() / 1e3)]),
|
|
94
|
+
new Uint32Array([browser.setFeatures(0, features)])
|
|
95
|
+
]);
|
|
96
|
+
if (!this.cryptoPrivateKey) this.cryptoPrivateKey = importPrivateKey(this.privateKey);
|
|
97
|
+
const signature = sign(data.buffer, this.cryptoPrivateKey);
|
|
98
|
+
return [browser.toBase64(data), browser.toBase64(new Uint8Array(signature))].join(SEPARATOR);
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
|
-
const test = (data, feature) => {
|
|
97
|
-
return data && !data?.expired && browser.hasFeature(data?.flags, feature) || false;
|
|
98
|
-
};
|
|
99
|
-
const shouldRemoveAds = (data) => test(data, browser.SITE_FEATURES.AD_LESS_EXPERIENCE);
|
|
100
|
-
const shouldEnableVipExperience = (data) => test(data, browser.SITE_FEATURES.VIP_EXPERIENCE);
|
|
101
|
-
const shouldEnablePremiumContentAccess = (data) => test(data, browser.SITE_FEATURES.PREMIUM_CONTENT_ACCESS);
|
|
102
101
|
const mergeByteArrays = (arrays) => {
|
|
103
|
-
const totalLength = arrays.reduce((sum,
|
|
102
|
+
const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
|
|
104
103
|
const data = new Uint8Array(totalLength);
|
|
105
104
|
let offset = 0;
|
|
106
105
|
for (const arr of arrays) {
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
let bytes;
|
|
107
|
+
if (arr instanceof Uint8Array) bytes = arr;
|
|
108
|
+
else if (arr instanceof Uint32Array) bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
109
|
+
else throw new Error("Unsupported type");
|
|
110
|
+
data.set(bytes, offset);
|
|
111
|
+
offset += bytes.byteLength;
|
|
109
112
|
}
|
|
110
113
|
return data;
|
|
111
114
|
};
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
serverHeader;
|
|
115
|
-
clientHeader;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
function ZeroAdNetwork(options) {
|
|
117
|
+
const serverHeader = new browser.ServerHeader(options);
|
|
118
|
+
const clientHeader = new ClientHeader(browser.ZEROAD_NETWORK_PUBLIC_KEY);
|
|
119
|
+
return {
|
|
120
|
+
setLogLevel: browser.setLogLevel,
|
|
121
|
+
parseToken: clientHeader.parseToken.bind(clientHeader),
|
|
122
|
+
CLIENT_HEADER_NAME: clientHeader.NAME,
|
|
123
|
+
SERVER_HEADER_NAME: serverHeader.NAME,
|
|
124
|
+
SERVER_HEADER_VALUE: serverHeader.VALUE
|
|
125
|
+
};
|
|
120
126
|
}
|
|
121
|
-
let defaultSite;
|
|
122
|
-
const init = (options) => defaultSite = new Site(options);
|
|
123
|
-
const processRequest = (headerValue) => defaultSite.clientHeader.processRequest(headerValue);
|
|
124
|
-
const getClientHeaderName = () => defaultSite.clientHeader.name;
|
|
125
|
-
const getServerHeaderName = () => defaultSite.serverHeader.name;
|
|
126
|
-
const getServerHeaderValue = () => defaultSite.serverHeader.value;
|
|
127
127
|
|
|
128
128
|
exports.CLIENT_HEADERS = browser.CLIENT_HEADERS;
|
|
129
129
|
exports.CURRENT_PROTOCOL_VERSION = browser.CURRENT_PROTOCOL_VERSION;
|
|
130
|
+
exports.FEATURES = browser.FEATURES;
|
|
130
131
|
exports.PROTOCOL_VERSION = browser.PROTOCOL_VERSION;
|
|
131
132
|
exports.SERVER_HEADERS = browser.SERVER_HEADERS;
|
|
132
|
-
exports.SITE_FEATURES = browser.SITE_FEATURES;
|
|
133
133
|
exports.ServerHeader = browser.ServerHeader;
|
|
134
134
|
exports.ZEROAD_NETWORK_PUBLIC_KEY = browser.ZEROAD_NETWORK_PUBLIC_KEY;
|
|
135
|
-
exports.setLogLevel = browser.setLogLevel;
|
|
136
135
|
exports.ClientHeader = ClientHeader;
|
|
137
|
-
exports.
|
|
138
|
-
exports.getClientHeaderName = getClientHeaderName;
|
|
139
|
-
exports.getServerHeaderName = getServerHeaderName;
|
|
140
|
-
exports.getServerHeaderValue = getServerHeaderValue;
|
|
141
|
-
exports.init = init;
|
|
142
|
-
exports.processRequest = processRequest;
|
|
136
|
+
exports.ZeroAdNetwork = ZeroAdNetwork;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,40 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CURRENT_PROTOCOL_VERSION, ClientParsedHeader,
|
|
1
|
+
import { ClientHeaderParseResult, PROTOCOL_VERSION, FEATURES, ServerHeaderOptions } from './browser.cjs';
|
|
2
|
+
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, ClientParsedHeader, SERVER_HEADERS, ServerHeader, ServerHeaderExtendedOptions, UUID, WelcomeHeader, WelcomeHeaderParseResult, ZEROAD_NETWORK_PUBLIC_KEY } from './browser.cjs';
|
|
3
3
|
|
|
4
4
|
declare class ClientHeader {
|
|
5
5
|
private cryptoPublicKey;
|
|
6
6
|
private cryptoPrivateKey;
|
|
7
7
|
private publicKey;
|
|
8
8
|
private privateKey;
|
|
9
|
-
|
|
9
|
+
NAME: string;
|
|
10
10
|
constructor(publicKey: string, privateKey?: string);
|
|
11
|
-
|
|
12
|
-
decode(headerValue: string): ClientHeaderParseResult;
|
|
13
|
-
|
|
14
|
-
_raw: ClientHeaderParseResult;
|
|
15
|
-
readonly shouldRemoveAds: boolean;
|
|
16
|
-
readonly shouldEnablePremiumContentAccess: boolean;
|
|
17
|
-
readonly shouldEnableVipExperience: boolean;
|
|
18
|
-
};
|
|
11
|
+
parseToken(headerValue: string | string[] | undefined): Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
12
|
+
decode(headerValue: string | undefined): ClientHeaderParseResult;
|
|
13
|
+
encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
22
17
|
declare function setLogLevel(level: LogLevel): void;
|
|
23
18
|
|
|
24
|
-
declare
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
declare const processRequest: (headerValue: string | undefined) => {
|
|
31
|
-
_raw: ClientHeaderParseResult;
|
|
32
|
-
readonly shouldRemoveAds: boolean;
|
|
33
|
-
readonly shouldEnablePremiumContentAccess: boolean;
|
|
34
|
-
readonly shouldEnableVipExperience: boolean;
|
|
19
|
+
declare function ZeroAdNetwork(options: ServerHeaderOptions): {
|
|
20
|
+
setLogLevel: typeof setLogLevel;
|
|
21
|
+
parseToken: (headerValue: string | string[] | undefined) => Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
22
|
+
CLIENT_HEADER_NAME: string;
|
|
23
|
+
SERVER_HEADER_NAME: string;
|
|
24
|
+
SERVER_HEADER_VALUE: string;
|
|
35
25
|
};
|
|
36
|
-
declare const getClientHeaderName: () => CLIENT_HEADERS;
|
|
37
|
-
declare const getServerHeaderName: () => SERVER_HEADERS;
|
|
38
|
-
declare const getServerHeaderValue: () => string;
|
|
39
26
|
|
|
40
|
-
export {
|
|
27
|
+
export { ClientHeader, ClientHeaderParseResult, FEATURES, PROTOCOL_VERSION, ServerHeaderOptions, ZeroAdNetwork };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,40 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CURRENT_PROTOCOL_VERSION, ClientParsedHeader,
|
|
1
|
+
import { ClientHeaderParseResult, PROTOCOL_VERSION, FEATURES, ServerHeaderOptions } from './browser.mjs';
|
|
2
|
+
export { CLIENT_HEADERS, CURRENT_PROTOCOL_VERSION, ClientParsedHeader, SERVER_HEADERS, ServerHeader, ServerHeaderExtendedOptions, UUID, WelcomeHeader, WelcomeHeaderParseResult, ZEROAD_NETWORK_PUBLIC_KEY } from './browser.mjs';
|
|
3
3
|
|
|
4
4
|
declare class ClientHeader {
|
|
5
5
|
private cryptoPublicKey;
|
|
6
6
|
private cryptoPrivateKey;
|
|
7
7
|
private publicKey;
|
|
8
8
|
private privateKey;
|
|
9
|
-
|
|
9
|
+
NAME: string;
|
|
10
10
|
constructor(publicKey: string, privateKey?: string);
|
|
11
|
-
|
|
12
|
-
decode(headerValue: string): ClientHeaderParseResult;
|
|
13
|
-
|
|
14
|
-
_raw: ClientHeaderParseResult;
|
|
15
|
-
readonly shouldRemoveAds: boolean;
|
|
16
|
-
readonly shouldEnablePremiumContentAccess: boolean;
|
|
17
|
-
readonly shouldEnableVipExperience: boolean;
|
|
18
|
-
};
|
|
11
|
+
parseToken(headerValue: string | string[] | undefined): Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
12
|
+
decode(headerValue: string | undefined): ClientHeaderParseResult;
|
|
13
|
+
encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
type LogLevel = "error" | "warn" | "info" | "debug";
|
|
22
17
|
declare function setLogLevel(level: LogLevel): void;
|
|
23
18
|
|
|
24
|
-
declare
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
declare const processRequest: (headerValue: string | undefined) => {
|
|
31
|
-
_raw: ClientHeaderParseResult;
|
|
32
|
-
readonly shouldRemoveAds: boolean;
|
|
33
|
-
readonly shouldEnablePremiumContentAccess: boolean;
|
|
34
|
-
readonly shouldEnableVipExperience: boolean;
|
|
19
|
+
declare function ZeroAdNetwork(options: ServerHeaderOptions): {
|
|
20
|
+
setLogLevel: typeof setLogLevel;
|
|
21
|
+
parseToken: (headerValue: string | string[] | undefined) => Record<"ADS_OFF" | "COOKIE_CONSENT_OFF" | "MARKETING_DIALOG_OFF" | "CONTENT_PAYWALL_OFF" | "SUBSCRIPTION_ACCESS_ON", boolean>;
|
|
22
|
+
CLIENT_HEADER_NAME: string;
|
|
23
|
+
SERVER_HEADER_NAME: string;
|
|
24
|
+
SERVER_HEADER_VALUE: string;
|
|
35
25
|
};
|
|
36
|
-
declare const getClientHeaderName: () => CLIENT_HEADERS;
|
|
37
|
-
declare const getServerHeaderName: () => SERVER_HEADERS;
|
|
38
|
-
declare const getServerHeaderValue: () => string;
|
|
39
26
|
|
|
40
|
-
export {
|
|
27
|
+
export { ClientHeader, ClientHeaderParseResult, FEATURES, PROTOCOL_VERSION, ServerHeaderOptions, ZeroAdNetwork };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
1
|
+
import { g as getSiteFeaturesNative, C as CLIENT_HEADERS, h as hasFeature, f as fromBase64, P as PROTOCOL_VERSION, l as log, s as setFeatures, t as toBase64, S as ServerHeader, a as setLogLevel, Z as ZEROAD_NETWORK_PUBLIC_KEY } from './browser-ByaVwJr6.mjs';
|
|
2
|
+
export { c as CURRENT_PROTOCOL_VERSION, F as FEATURES, b as SERVER_HEADERS } from './browser-ByaVwJr6.mjs';
|
|
3
|
+
import { createPublicKey, verify as verify$1, randomBytes, createPrivateKey, sign as sign$1 } from 'crypto';
|
|
4
4
|
|
|
5
5
|
const importPrivateKey = (privateKeyBase64) => {
|
|
6
6
|
const keyBuffer = Buffer.from(privateKeyBase64, "base64");
|
|
@@ -29,30 +29,40 @@ const verify = (data, signature, publicKey) => {
|
|
|
29
29
|
};
|
|
30
30
|
const nonce = (size) => new Uint8Array(randomBytes(size));
|
|
31
31
|
|
|
32
|
+
const VERSION_BYTES = 1;
|
|
32
33
|
const NONCE_BYTES = 4;
|
|
33
34
|
const SEPARATOR = ".";
|
|
35
|
+
const SITE_FEATURES_NATIVE = getSiteFeaturesNative();
|
|
36
|
+
const as32BitNumber = (byteArray, begin) => {
|
|
37
|
+
const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
|
|
38
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
39
|
+
return view.getUint32(0, true);
|
|
40
|
+
};
|
|
34
41
|
class ClientHeader {
|
|
35
42
|
cryptoPublicKey;
|
|
36
43
|
cryptoPrivateKey;
|
|
37
44
|
publicKey;
|
|
38
45
|
privateKey;
|
|
39
|
-
|
|
46
|
+
NAME = CLIENT_HEADERS.HELLO.toLowerCase();
|
|
40
47
|
constructor(publicKey, privateKey) {
|
|
41
48
|
this.publicKey = publicKey;
|
|
42
49
|
this.privateKey = privateKey;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
parseToken(headerValue) {
|
|
52
|
+
const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
53
|
+
const data = this.decode(headerValueAsString);
|
|
54
|
+
const expired = !data || data.expiresAt.getTime() < Date.now();
|
|
55
|
+
const result = {};
|
|
56
|
+
if (!data || expired) {
|
|
57
|
+
for (const [feature] of SITE_FEATURES_NATIVE) {
|
|
58
|
+
result[feature] = false;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
for (const [feature, shift] of SITE_FEATURES_NATIVE) {
|
|
63
|
+
result[feature] = hasFeature(data.flags, shift);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
56
66
|
}
|
|
57
67
|
decode(headerValue) {
|
|
58
68
|
if (!headerValue?.length) return;
|
|
@@ -66,62 +76,52 @@ class ClientHeader {
|
|
|
66
76
|
}
|
|
67
77
|
const version = dataBytes[0];
|
|
68
78
|
if (version === PROTOCOL_VERSION.V_1) {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const expired = expiresAt.getTime() < Date.now();
|
|
73
|
-
return { version, expiresAt, expired, flags: flagsBytes[0] };
|
|
79
|
+
const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
|
|
80
|
+
const flags = as32BitNumber(dataBytes, dataBytes.length - Uint32Array.BYTES_PER_ELEMENT);
|
|
81
|
+
return { version, expiresAt: new Date(expiresAt * 1e3), flags };
|
|
74
82
|
}
|
|
75
83
|
} catch (err) {
|
|
76
84
|
log("warn", "Could not decode client header value", { reason: err?.message });
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return shouldEnableVipExperience(data);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
87
|
+
encode(version, expiresAt, features) {
|
|
88
|
+
if (!this.privateKey) throw new Error("Private key is required");
|
|
89
|
+
const data = mergeByteArrays([
|
|
90
|
+
new Uint8Array([version]),
|
|
91
|
+
new Uint8Array(nonce(NONCE_BYTES)),
|
|
92
|
+
new Uint32Array([Math.floor(expiresAt.getTime() / 1e3)]),
|
|
93
|
+
new Uint32Array([setFeatures(0, features)])
|
|
94
|
+
]);
|
|
95
|
+
if (!this.cryptoPrivateKey) this.cryptoPrivateKey = importPrivateKey(this.privateKey);
|
|
96
|
+
const signature = sign(data.buffer, this.cryptoPrivateKey);
|
|
97
|
+
return [toBase64(data), toBase64(new Uint8Array(signature))].join(SEPARATOR);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
const test = (data, feature) => {
|
|
96
|
-
return data && !data?.expired && hasFeature(data?.flags, feature) || false;
|
|
97
|
-
};
|
|
98
|
-
const shouldRemoveAds = (data) => test(data, SITE_FEATURES.AD_LESS_EXPERIENCE);
|
|
99
|
-
const shouldEnableVipExperience = (data) => test(data, SITE_FEATURES.VIP_EXPERIENCE);
|
|
100
|
-
const shouldEnablePremiumContentAccess = (data) => test(data, SITE_FEATURES.PREMIUM_CONTENT_ACCESS);
|
|
101
100
|
const mergeByteArrays = (arrays) => {
|
|
102
|
-
const totalLength = arrays.reduce((sum,
|
|
101
|
+
const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
|
|
103
102
|
const data = new Uint8Array(totalLength);
|
|
104
103
|
let offset = 0;
|
|
105
104
|
for (const arr of arrays) {
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
let bytes;
|
|
106
|
+
if (arr instanceof Uint8Array) bytes = arr;
|
|
107
|
+
else if (arr instanceof Uint32Array) bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
108
|
+
else throw new Error("Unsupported type");
|
|
109
|
+
data.set(bytes, offset);
|
|
110
|
+
offset += bytes.byteLength;
|
|
108
111
|
}
|
|
109
112
|
return data;
|
|
110
113
|
};
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
serverHeader;
|
|
114
|
-
clientHeader;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
function ZeroAdNetwork(options) {
|
|
116
|
+
const serverHeader = new ServerHeader(options);
|
|
117
|
+
const clientHeader = new ClientHeader(ZEROAD_NETWORK_PUBLIC_KEY);
|
|
118
|
+
return {
|
|
119
|
+
setLogLevel,
|
|
120
|
+
parseToken: clientHeader.parseToken.bind(clientHeader),
|
|
121
|
+
CLIENT_HEADER_NAME: clientHeader.NAME,
|
|
122
|
+
SERVER_HEADER_NAME: serverHeader.NAME,
|
|
123
|
+
SERVER_HEADER_VALUE: serverHeader.VALUE
|
|
124
|
+
};
|
|
119
125
|
}
|
|
120
|
-
let defaultSite;
|
|
121
|
-
const init = (options) => defaultSite = new Site(options);
|
|
122
|
-
const processRequest = (headerValue) => defaultSite.clientHeader.processRequest(headerValue);
|
|
123
|
-
const getClientHeaderName = () => defaultSite.clientHeader.name;
|
|
124
|
-
const getServerHeaderName = () => defaultSite.serverHeader.name;
|
|
125
|
-
const getServerHeaderValue = () => defaultSite.serverHeader.value;
|
|
126
126
|
|
|
127
|
-
export { CLIENT_HEADERS, ClientHeader, PROTOCOL_VERSION,
|
|
127
|
+
export { CLIENT_HEADERS, ClientHeader, PROTOCOL_VERSION, ServerHeader, ZEROAD_NETWORK_PUBLIC_KEY, ZeroAdNetwork };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroad.network/token",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"repository": "github:laurynas-karvelis/zeroad-token",
|
|
5
|
+
"repository": "github:laurynas-karvelis/zeroad-token-ts",
|
|
6
6
|
"homepage": "https://zeroad.network",
|
|
7
7
|
"private": false,
|
|
8
8
|
"author": {
|
|
@@ -42,17 +42,21 @@
|
|
|
42
42
|
"default": "./dist/browser.mjs"
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
|
+
"output": {
|
|
46
|
+
"exports": "named"
|
|
47
|
+
},
|
|
45
48
|
"scripts": {
|
|
46
49
|
"keys:generate": "bun run ./src/tools/cli.ts",
|
|
47
50
|
"prettier": "prettier . --write",
|
|
48
|
-
"build": "pkgroll --target=node18"
|
|
51
|
+
"build": "pkgroll --target=node18",
|
|
52
|
+
"test": "bun test"
|
|
49
53
|
},
|
|
50
54
|
"dependencies": {},
|
|
51
55
|
"devDependencies": {
|
|
52
56
|
"@types/bun": "^1.3.3",
|
|
53
57
|
"@types/node": "^24.10.0",
|
|
54
58
|
"pkgroll": "^2.21.4",
|
|
55
|
-
"prettier": "^3.7.
|
|
59
|
+
"prettier": "^3.7.2",
|
|
56
60
|
"typescript": "^5.9.3"
|
|
57
61
|
}
|
|
58
62
|
}
|