@zeroad.network/token 0.13.7 → 0.13.8

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/dist/index.cjs CHANGED
@@ -1,33 +1,37 @@
1
1
  'use strict';
2
2
 
3
- var browser = require('./browser-C2zgnAdK.cjs');
3
+ var browser = require('./browser-CMCq-bkd.cjs');
4
4
  var node_buffer = require('node:buffer');
5
5
  var node_crypto = require('node:crypto');
6
6
 
7
+ const keyCache = /* @__PURE__ */ new Map();
7
8
  const importPrivateKey = (privateKeyBase64) => {
8
- const keyBuffer = node_buffer.Buffer.from(privateKeyBase64, "base64");
9
- return node_crypto.createPrivateKey({
10
- key: keyBuffer,
9
+ if (keyCache.has(privateKeyBase64)) return keyCache.get(privateKeyBase64);
10
+ const key = node_crypto.createPrivateKey({
11
+ key: node_buffer.Buffer.from(privateKeyBase64, "base64"),
11
12
  format: "der",
12
13
  type: "pkcs8"
13
14
  });
15
+ keyCache.set(privateKeyBase64, key);
16
+ return key;
14
17
  };
15
18
  const importPublicKey = (publicKeyBase64) => {
16
- const keyBuffer = node_buffer.Buffer.from(publicKeyBase64, "base64");
17
- return node_crypto.createPublicKey({
18
- key: keyBuffer,
19
+ if (keyCache.has(publicKeyBase64)) return keyCache.get(publicKeyBase64);
20
+ const key = node_crypto.createPublicKey({
21
+ key: node_buffer.Buffer.from(publicKeyBase64, "base64"),
19
22
  format: "der",
20
23
  type: "spki"
21
24
  });
25
+ keyCache.set(publicKeyBase64, key);
26
+ return key;
22
27
  };
23
28
  const sign = (data, privateKey) => {
24
- const buffer = node_buffer.Buffer.from(data);
25
- return node_crypto.sign(null, buffer, privateKey);
29
+ const key = importPrivateKey(privateKey);
30
+ return node_crypto.sign(null, node_buffer.Buffer.from(data), key);
26
31
  };
27
32
  const verify = (data, signature, publicKey) => {
28
- const dataBuffer = node_buffer.Buffer.from(data);
29
- const sigBuffer = node_buffer.Buffer.from(signature);
30
- return node_crypto.verify(null, dataBuffer, publicKey, sigBuffer);
33
+ const key = importPublicKey(publicKey);
34
+ return node_crypto.verify(null, node_buffer.Buffer.from(data), key, node_buffer.Buffer.from(signature));
31
35
  };
32
36
  const nonce = (size) => new Uint8Array(node_crypto.randomBytes(size));
33
37
 
@@ -35,70 +39,59 @@ const VERSION_BYTES = 1;
35
39
  const NONCE_BYTES = 4;
36
40
  const SEPARATOR = ".";
37
41
  const SITE_FEATURES_NATIVE = browser.getSiteFeaturesNative();
38
- const as32BitNumber = (byteArray, begin) => {
39
- const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
40
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
41
- return view.getUint32(0, true);
42
- };
43
- class ClientHeader {
44
- cryptoPublicKey;
45
- cryptoPrivateKey;
46
- publicKey;
47
- privateKey;
48
- NAME = browser.CLIENT_HEADERS.HELLO;
49
- constructor(publicKey, privateKey) {
50
- this.publicKey = publicKey;
51
- this.privateKey = privateKey;
52
- }
53
- parseToken(headerValue) {
54
- const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
55
- const data = this.decode(headerValueAsString);
56
- const result = {};
57
- if (!data || data.expiresAt.getTime() < Date.now()) {
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);
42
+ function parseClientToken(headerValue, clientId, publicKey) {
43
+ const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
44
+ const data = decodeClientHeader(headerValueAsString, publicKey);
45
+ let flags = 0;
46
+ if (data && data.expiresAt.getTime() >= Date.now()) flags = data.flags;
47
+ if (flags && data?.clientId && data.clientId !== clientId) flags = 0;
48
+ const features = SITE_FEATURES_NATIVE.map(([feature, shift]) => browser.hasFlag(flags, shift) && feature).filter((e) => !!e);
49
+ const hasCleanWeb = features.includes("CLEAN_WEB");
50
+ const hasOnePass = features.includes("ONE_PASS");
51
+ return {
52
+ HIDE_ADVERTISEMENTS: hasCleanWeb,
53
+ HIDE_COOKIE_CONSENT_SCREEN: hasCleanWeb,
54
+ HIDE_MARKETING_DIALOGS: hasCleanWeb,
55
+ DISABLE_NON_FUNCTIONAL_TRACKING: hasCleanWeb,
56
+ DISABLE_CONTENT_PAYWALL: hasOnePass,
57
+ ENABLE_SUBSCRIPTION_ACCESS: hasOnePass
58
+ };
59
+ }
60
+ function decodeClientHeader(headerValue, publicKey) {
61
+ if (!headerValue?.length) return;
62
+ try {
63
+ const [data, signature] = headerValue.split(SEPARATOR);
64
+ const dataBytes = browser.fromBase64(data);
65
+ const signatureBytes = browser.fromBase64(signature);
66
+ if (!verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
67
+ throw new Error("Forged header value is provided");
65
68
  }
66
- return result;
67
- }
68
- decode(headerValue) {
69
- if (!headerValue?.length) return;
70
- if (!this.cryptoPublicKey) this.cryptoPublicKey = importPublicKey(this.publicKey);
71
- try {
72
- const [data, signature] = headerValue.split(SEPARATOR);
73
- const dataBytes = browser.fromBase64(data);
74
- const signatureBytes = browser.fromBase64(signature);
75
- if (!verify(dataBytes.buffer, signatureBytes.buffer, this.cryptoPublicKey)) {
76
- throw new Error("Forged header value is provided");
77
- }
78
- const version = dataBytes[0];
79
- if (version === browser.PROTOCOL_VERSION.V_1) {
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 };
69
+ const version = dataBytes[0];
70
+ let clientId;
71
+ if (version === browser.PROTOCOL_VERSION.V_1) {
72
+ const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
73
+ const flags = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT);
74
+ const expectedByteLength = VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT * 2;
75
+ if (dataBytes.byteLength > expectedByteLength) {
76
+ clientId = new TextDecoder().decode(dataBytes.subarray(expectedByteLength));
83
77
  }
84
- } catch (err) {
85
- browser.log("warn", "Could not decode client header value", { reason: err?.message });
78
+ return { version, expiresAt: new Date(expiresAt * 1e3), flags, ...clientId && { clientId } };
86
79
  }
87
- }
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);
80
+ } catch (err) {
81
+ browser.log("warn", "Could not decode client header value", { reason: err?.message });
99
82
  }
100
83
  }
101
- const mergeByteArrays = (arrays) => {
84
+ function encodeClientHeader(data, privateKey) {
85
+ const payload = mergeByteArrays([
86
+ new Uint8Array([data.version]),
87
+ new Uint8Array(nonce(NONCE_BYTES)),
88
+ new Uint32Array([Math.floor(data.expiresAt.getTime() / 1e3)]),
89
+ new Uint32Array([browser.setFlags(data.features)]),
90
+ ...data.clientId?.length ? [new Uint8Array(new TextEncoder().encode(data.clientId))] : []
91
+ ]);
92
+ return [browser.toBase64(payload), browser.toBase64(new Uint8Array(sign(payload.buffer, privateKey)))].join(SEPARATOR);
93
+ }
94
+ function mergeByteArrays(arrays) {
102
95
  const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
103
96
  const data = new Uint8Array(totalLength);
104
97
  let offset = 0;
@@ -111,17 +104,20 @@ const mergeByteArrays = (arrays) => {
111
104
  offset += bytes.byteLength;
112
105
  }
113
106
  return data;
114
- };
107
+ }
108
+ function as32BitNumber(byteArray, begin) {
109
+ const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
110
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
111
+ return view.getUint32(0, true);
112
+ }
115
113
 
116
114
  function Site(options) {
117
- const serverHeader = new browser.ServerHeader(options);
118
- const clientHeader = new ClientHeader(browser.ZEROAD_NETWORK_PUBLIC_KEY);
115
+ const serverHeaderValue = browser.encodeServerHeader(options.clientId, options.features);
119
116
  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
117
+ parseClientToken: (headerValue) => parseClientToken(headerValue, options.clientId, browser.ZEROAD_NETWORK_PUBLIC_KEY),
118
+ CLIENT_HEADER_NAME: browser.CLIENT_HEADERS.HELLO,
119
+ SERVER_HEADER_NAME: browser.SERVER_HEADERS.WELCOME,
120
+ SERVER_HEADER_VALUE: serverHeaderValue
125
121
  };
126
122
  }
127
123
 
@@ -130,8 +126,11 @@ exports.CURRENT_PROTOCOL_VERSION = browser.CURRENT_PROTOCOL_VERSION;
130
126
  exports.FEATURES = browser.FEATURES;
131
127
  exports.PROTOCOL_VERSION = browser.PROTOCOL_VERSION;
132
128
  exports.SERVER_HEADERS = browser.SERVER_HEADERS;
133
- exports.ServerHeader = browser.ServerHeader;
134
129
  exports.ZEROAD_NETWORK_PUBLIC_KEY = browser.ZEROAD_NETWORK_PUBLIC_KEY;
130
+ exports.decodeServerHeader = browser.decodeServerHeader;
131
+ exports.encodeServerHeader = browser.encodeServerHeader;
135
132
  exports.setLogLevel = browser.setLogLevel;
136
- exports.ClientHeader = ClientHeader;
137
133
  exports.Site = Site;
134
+ exports.decodeClientHeader = decodeClientHeader;
135
+ exports.encodeClientHeader = encodeClientHeader;
136
+ exports.parseClientToken = parseClientToken;
package/dist/index.d.cts CHANGED
@@ -1,27 +1,38 @@
1
- import { CLIENT_HEADERS, ClientParsedHeader, PROTOCOL_VERSION, FEATURES, ServerHeaderOptions, SERVER_HEADERS } from './browser.cjs';
2
- export { CURRENT_PROTOCOL_VERSION, ServerHeader, ServerHeaderExtendedOptions, UUID, WelcomeHeader, ZEROAD_NETWORK_PUBLIC_KEY } from './browser.cjs';
3
-
4
- declare class ClientHeader {
5
- private cryptoPublicKey;
6
- private cryptoPrivateKey;
7
- private publicKey;
8
- private privateKey;
9
- NAME: CLIENT_HEADERS;
10
- constructor(publicKey: string, privateKey?: string);
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): ClientParsedHeader | undefined;
13
- encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
14
- }
1
+ import { P as PROTOCOL_VERSION, F as FEATURES, C as CLIENT_HEADERS, S as SERVER_HEADERS } from './browser-BiNZ2c6t.cjs';
2
+ export { a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader, e as encodeServerHeader } from './browser-BiNZ2c6t.cjs';
15
3
 
16
4
  type LogLevel = "error" | "warn" | "info" | "debug";
17
5
  declare function setLogLevel(level: LogLevel): void;
18
6
 
19
- declare function Site(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>;
7
+ type FEATURE_FLAG = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
8
+ type ClientHeaderValue = string | string[] | undefined;
9
+ type FeatureFlags = Record<FEATURE_FLAG, boolean>;
10
+ declare function parseClientToken(headerValue: ClientHeaderValue, clientId: string, publicKey: string): FeatureFlags;
11
+ type DecodedClientHeader = {
12
+ version: PROTOCOL_VERSION;
13
+ expiresAt: Date;
14
+ flags: number;
15
+ clientId?: string;
16
+ };
17
+ declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined;
18
+ type EncodeData = {
19
+ version: PROTOCOL_VERSION;
20
+ expiresAt: Date;
21
+ features: FEATURES[];
22
+ clientId?: string;
23
+ };
24
+ declare function encodeClientHeader(data: EncodeData, privateKey: string): string;
25
+
26
+ type SiteOptions = {
27
+ clientId: string;
28
+ features: FEATURES[];
29
+ };
30
+ declare function Site(options: SiteOptions): {
31
+ parseClientToken: (headerValue: ClientHeaderValue) => FeatureFlags;
22
32
  CLIENT_HEADER_NAME: CLIENT_HEADERS;
23
33
  SERVER_HEADER_NAME: SERVER_HEADERS;
24
34
  SERVER_HEADER_VALUE: string;
25
35
  };
26
36
 
27
- export { CLIENT_HEADERS, ClientHeader, ClientParsedHeader, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, ServerHeaderOptions, Site, setLogLevel };
37
+ export { CLIENT_HEADERS, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
38
+ export type { ClientHeaderValue, DecodedClientHeader, FEATURE_FLAG, FeatureFlags };
package/dist/index.d.mts CHANGED
@@ -1,27 +1,38 @@
1
- import { CLIENT_HEADERS, ClientParsedHeader, PROTOCOL_VERSION, FEATURES, ServerHeaderOptions, SERVER_HEADERS } from './browser.mjs';
2
- export { CURRENT_PROTOCOL_VERSION, ServerHeader, ServerHeaderExtendedOptions, UUID, WelcomeHeader, ZEROAD_NETWORK_PUBLIC_KEY } from './browser.mjs';
3
-
4
- declare class ClientHeader {
5
- private cryptoPublicKey;
6
- private cryptoPrivateKey;
7
- private publicKey;
8
- private privateKey;
9
- NAME: CLIENT_HEADERS;
10
- constructor(publicKey: string, privateKey?: string);
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): ClientParsedHeader | undefined;
13
- encode(version: PROTOCOL_VERSION, expiresAt: Date, features: FEATURES[]): string;
14
- }
1
+ import { P as PROTOCOL_VERSION, F as FEATURES, C as CLIENT_HEADERS, S as SERVER_HEADERS } from './browser-BiNZ2c6t.mjs';
2
+ export { a as CURRENT_PROTOCOL_VERSION, W as WelcomeHeader, Z as ZEROAD_NETWORK_PUBLIC_KEY, d as decodeServerHeader, e as encodeServerHeader } from './browser-BiNZ2c6t.mjs';
15
3
 
16
4
  type LogLevel = "error" | "warn" | "info" | "debug";
17
5
  declare function setLogLevel(level: LogLevel): void;
18
6
 
19
- declare function Site(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>;
7
+ type FEATURE_FLAG = "HIDE_ADVERTISEMENTS" | "HIDE_COOKIE_CONSENT_SCREEN" | "HIDE_MARKETING_DIALOGS" | "DISABLE_NON_FUNCTIONAL_TRACKING" | "DISABLE_CONTENT_PAYWALL" | "ENABLE_SUBSCRIPTION_ACCESS";
8
+ type ClientHeaderValue = string | string[] | undefined;
9
+ type FeatureFlags = Record<FEATURE_FLAG, boolean>;
10
+ declare function parseClientToken(headerValue: ClientHeaderValue, clientId: string, publicKey: string): FeatureFlags;
11
+ type DecodedClientHeader = {
12
+ version: PROTOCOL_VERSION;
13
+ expiresAt: Date;
14
+ flags: number;
15
+ clientId?: string;
16
+ };
17
+ declare function decodeClientHeader(headerValue: string | null | undefined, publicKey: string): DecodedClientHeader | undefined;
18
+ type EncodeData = {
19
+ version: PROTOCOL_VERSION;
20
+ expiresAt: Date;
21
+ features: FEATURES[];
22
+ clientId?: string;
23
+ };
24
+ declare function encodeClientHeader(data: EncodeData, privateKey: string): string;
25
+
26
+ type SiteOptions = {
27
+ clientId: string;
28
+ features: FEATURES[];
29
+ };
30
+ declare function Site(options: SiteOptions): {
31
+ parseClientToken: (headerValue: ClientHeaderValue) => FeatureFlags;
22
32
  CLIENT_HEADER_NAME: CLIENT_HEADERS;
23
33
  SERVER_HEADER_NAME: SERVER_HEADERS;
24
34
  SERVER_HEADER_VALUE: string;
25
35
  };
26
36
 
27
- export { CLIENT_HEADERS, ClientHeader, ClientParsedHeader, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, ServerHeaderOptions, Site, setLogLevel };
37
+ export { CLIENT_HEADERS, FEATURES, PROTOCOL_VERSION, SERVER_HEADERS, Site, decodeClientHeader, encodeClientHeader, parseClientToken, setLogLevel };
38
+ export type { ClientHeaderValue, DecodedClientHeader, FEATURE_FLAG, FeatureFlags };
package/dist/index.mjs CHANGED
@@ -1,32 +1,36 @@
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-DDeUUPbU.mjs';
2
- export { c as CURRENT_PROTOCOL_VERSION, F as FEATURES, b as SERVER_HEADERS } from './browser-DDeUUPbU.mjs';
1
+ import { g as getSiteFeaturesNative, h as hasFlag, f as fromBase64, P as PROTOCOL_VERSION, l as log, s as setFlags, t as toBase64, e as encodeServerHeader, S as SERVER_HEADERS, C as CLIENT_HEADERS, Z as ZEROAD_NETWORK_PUBLIC_KEY } from './browser-Citg0bh9.mjs';
2
+ export { b as CURRENT_PROTOCOL_VERSION, F as FEATURES, d as decodeServerHeader, a as setLogLevel } from './browser-Citg0bh9.mjs';
3
3
  import { Buffer } from 'node:buffer';
4
- import { createPublicKey, verify as verify$1, randomBytes, createPrivateKey, sign as sign$1 } from 'node:crypto';
4
+ import { verify as verify$1, randomBytes, sign as sign$1, createPublicKey, createPrivateKey } from 'node:crypto';
5
5
 
6
+ const keyCache = /* @__PURE__ */ new Map();
6
7
  const importPrivateKey = (privateKeyBase64) => {
7
- const keyBuffer = Buffer.from(privateKeyBase64, "base64");
8
- return createPrivateKey({
9
- key: keyBuffer,
8
+ if (keyCache.has(privateKeyBase64)) return keyCache.get(privateKeyBase64);
9
+ const key = createPrivateKey({
10
+ key: Buffer.from(privateKeyBase64, "base64"),
10
11
  format: "der",
11
12
  type: "pkcs8"
12
13
  });
14
+ keyCache.set(privateKeyBase64, key);
15
+ return key;
13
16
  };
14
17
  const importPublicKey = (publicKeyBase64) => {
15
- const keyBuffer = Buffer.from(publicKeyBase64, "base64");
16
- return createPublicKey({
17
- key: keyBuffer,
18
+ if (keyCache.has(publicKeyBase64)) return keyCache.get(publicKeyBase64);
19
+ const key = createPublicKey({
20
+ key: Buffer.from(publicKeyBase64, "base64"),
18
21
  format: "der",
19
22
  type: "spki"
20
23
  });
24
+ keyCache.set(publicKeyBase64, key);
25
+ return key;
21
26
  };
22
27
  const sign = (data, privateKey) => {
23
- const buffer = Buffer.from(data);
24
- return sign$1(null, buffer, privateKey);
28
+ const key = importPrivateKey(privateKey);
29
+ return sign$1(null, Buffer.from(data), key);
25
30
  };
26
31
  const verify = (data, signature, publicKey) => {
27
- const dataBuffer = Buffer.from(data);
28
- const sigBuffer = Buffer.from(signature);
29
- return verify$1(null, dataBuffer, publicKey, sigBuffer);
32
+ const key = importPublicKey(publicKey);
33
+ return verify$1(null, Buffer.from(data), key, Buffer.from(signature));
30
34
  };
31
35
  const nonce = (size) => new Uint8Array(randomBytes(size));
32
36
 
@@ -34,70 +38,59 @@ const VERSION_BYTES = 1;
34
38
  const NONCE_BYTES = 4;
35
39
  const SEPARATOR = ".";
36
40
  const SITE_FEATURES_NATIVE = 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
- };
42
- class ClientHeader {
43
- cryptoPublicKey;
44
- cryptoPrivateKey;
45
- publicKey;
46
- privateKey;
47
- NAME = CLIENT_HEADERS.HELLO;
48
- constructor(publicKey, privateKey) {
49
- this.publicKey = publicKey;
50
- this.privateKey = privateKey;
51
- }
52
- parseToken(headerValue) {
53
- const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
54
- const data = this.decode(headerValueAsString);
55
- const result = {};
56
- if (!data || data.expiresAt.getTime() < Date.now()) {
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);
41
+ function parseClientToken(headerValue, clientId, publicKey) {
42
+ const headerValueAsString = Array.isArray(headerValue) ? headerValue[0] : headerValue;
43
+ const data = decodeClientHeader(headerValueAsString, publicKey);
44
+ let flags = 0;
45
+ if (data && data.expiresAt.getTime() >= Date.now()) flags = data.flags;
46
+ if (flags && data?.clientId && data.clientId !== clientId) flags = 0;
47
+ const features = SITE_FEATURES_NATIVE.map(([feature, shift]) => hasFlag(flags, shift) && feature).filter((e) => !!e);
48
+ const hasCleanWeb = features.includes("CLEAN_WEB");
49
+ const hasOnePass = features.includes("ONE_PASS");
50
+ return {
51
+ HIDE_ADVERTISEMENTS: hasCleanWeb,
52
+ HIDE_COOKIE_CONSENT_SCREEN: hasCleanWeb,
53
+ HIDE_MARKETING_DIALOGS: hasCleanWeb,
54
+ DISABLE_NON_FUNCTIONAL_TRACKING: hasCleanWeb,
55
+ DISABLE_CONTENT_PAYWALL: hasOnePass,
56
+ ENABLE_SUBSCRIPTION_ACCESS: hasOnePass
57
+ };
58
+ }
59
+ function decodeClientHeader(headerValue, publicKey) {
60
+ if (!headerValue?.length) return;
61
+ try {
62
+ const [data, signature] = headerValue.split(SEPARATOR);
63
+ const dataBytes = fromBase64(data);
64
+ const signatureBytes = fromBase64(signature);
65
+ if (!verify(dataBytes.buffer, signatureBytes.buffer, publicKey)) {
66
+ throw new Error("Forged header value is provided");
64
67
  }
65
- return result;
66
- }
67
- decode(headerValue) {
68
- if (!headerValue?.length) return;
69
- if (!this.cryptoPublicKey) this.cryptoPublicKey = importPublicKey(this.publicKey);
70
- try {
71
- const [data, signature] = headerValue.split(SEPARATOR);
72
- const dataBytes = fromBase64(data);
73
- const signatureBytes = fromBase64(signature);
74
- if (!verify(dataBytes.buffer, signatureBytes.buffer, this.cryptoPublicKey)) {
75
- throw new Error("Forged header value is provided");
76
- }
77
- const version = dataBytes[0];
78
- if (version === PROTOCOL_VERSION.V_1) {
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 };
68
+ const version = dataBytes[0];
69
+ let clientId;
70
+ if (version === PROTOCOL_VERSION.V_1) {
71
+ const expiresAt = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES);
72
+ const flags = as32BitNumber(dataBytes, VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT);
73
+ const expectedByteLength = VERSION_BYTES + NONCE_BYTES + Uint32Array.BYTES_PER_ELEMENT * 2;
74
+ if (dataBytes.byteLength > expectedByteLength) {
75
+ clientId = new TextDecoder().decode(dataBytes.subarray(expectedByteLength));
82
76
  }
83
- } catch (err) {
84
- log("warn", "Could not decode client header value", { reason: err?.message });
77
+ return { version, expiresAt: new Date(expiresAt * 1e3), flags, ...clientId && { clientId } };
85
78
  }
86
- }
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);
79
+ } catch (err) {
80
+ log("warn", "Could not decode client header value", { reason: err?.message });
98
81
  }
99
82
  }
100
- const mergeByteArrays = (arrays) => {
83
+ function encodeClientHeader(data, privateKey) {
84
+ const payload = mergeByteArrays([
85
+ new Uint8Array([data.version]),
86
+ new Uint8Array(nonce(NONCE_BYTES)),
87
+ new Uint32Array([Math.floor(data.expiresAt.getTime() / 1e3)]),
88
+ new Uint32Array([setFlags(data.features)]),
89
+ ...data.clientId?.length ? [new Uint8Array(new TextEncoder().encode(data.clientId))] : []
90
+ ]);
91
+ return [toBase64(payload), toBase64(new Uint8Array(sign(payload.buffer, privateKey)))].join(SEPARATOR);
92
+ }
93
+ function mergeByteArrays(arrays) {
101
94
  const totalLength = arrays.reduce((sum, a) => sum + a.byteLength, 0);
102
95
  const data = new Uint8Array(totalLength);
103
96
  let offset = 0;
@@ -110,18 +103,21 @@ const mergeByteArrays = (arrays) => {
110
103
  offset += bytes.byteLength;
111
104
  }
112
105
  return data;
113
- };
106
+ }
107
+ function as32BitNumber(byteArray, begin) {
108
+ const bytes = byteArray.subarray(begin, begin + Uint32Array.BYTES_PER_ELEMENT);
109
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
110
+ return view.getUint32(0, true);
111
+ }
114
112
 
115
113
  function Site(options) {
116
- const serverHeader = new ServerHeader(options);
117
- const clientHeader = new ClientHeader(ZEROAD_NETWORK_PUBLIC_KEY);
114
+ const serverHeaderValue = encodeServerHeader(options.clientId, options.features);
118
115
  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
116
+ parseClientToken: (headerValue) => parseClientToken(headerValue, options.clientId, ZEROAD_NETWORK_PUBLIC_KEY),
117
+ CLIENT_HEADER_NAME: CLIENT_HEADERS.HELLO,
118
+ SERVER_HEADER_NAME: SERVER_HEADERS.WELCOME,
119
+ SERVER_HEADER_VALUE: serverHeaderValue
124
120
  };
125
121
  }
126
122
 
127
- export { CLIENT_HEADERS, ClientHeader, PROTOCOL_VERSION, ServerHeader, Site, ZEROAD_NETWORK_PUBLIC_KEY, setLogLevel };
123
+ export { CLIENT_HEADERS, PROTOCOL_VERSION, SERVER_HEADERS, Site, ZEROAD_NETWORK_PUBLIC_KEY, decodeClientHeader, encodeClientHeader, encodeServerHeader, parseClientToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroad.network/token",
3
- "version": "0.13.7",
3
+ "version": "0.13.8",
4
4
  "license": "Apache-2.0",
5
5
  "repository": "github:laurynas-karvelis/zeroad-token-typescript",
6
6
  "homepage": "https://zeroad.network",
@@ -77,7 +77,7 @@
77
77
  "@types/bun": "^1.3.3",
78
78
  "@types/node": "^24.10.0",
79
79
  "pkgroll": "^2.21.4",
80
- "prettier": "^3.7.2",
80
+ "prettier": "^3.7.4",
81
81
  "typescript": "^5.9.3"
82
82
  }
83
83
  }