@zeroad.network/token 0.13.13 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,40 @@
1
- # Introduction
1
+ # @zeroad.network/token
2
2
 
3
- This NPM module is designed for sites running Node.js, Bun, or Deno that participate in the [Zero Ad Network](https://zeroad.network) program.
3
+ The official TypeScript/JavaScript module for integrating websites with the [Zero Ad Network](https://zeroad.network) platform.
4
4
 
5
- The `@zeroad.network/token` module is a lightweight, TypeScript-ready, open-source, and fully tested HTTP-header-based "access/entitlement token" library with no production dependencies.
5
+ ## What is Zero Ad Network?
6
6
 
7
- For detailed guides and implementation instructions, see the [official Zero Ad Network documentation](https://docs.zeroad.network).
7
+ Zero Ad Network is a browser-based platform that creates a better web experience for both users and content creators:
8
+
9
+ **For Users:**
10
+
11
+ - Browse without ads, trackers, cookie consent dialogs, and marketing pop-ups
12
+ - Access paywalled content across multiple sites with a single subscription
13
+ - Support content creators directly through fair revenue distribution
14
+
15
+ **For Publishers:**
16
+
17
+ - Generate revenue from users who would otherwise use ad blockers
18
+ - Provide a cleaner user experience while maintaining income
19
+ - Get paid based on actual user engagement with your content
20
+
21
+ **How It Works:**
22
+
23
+ 1. Users subscribe and install the Zero Ad Network browser extension
24
+ 2. The extension sends cryptographically signed tokens to partner sites
25
+ 3. Partner sites verify tokens and enable premium features (ad-free, paywall-free)
26
+ 4. Monthly revenue is distributed to publishers based on user engagement time
27
+
28
+ ## Features
29
+
30
+ This module provides:
31
+
32
+ - ✅ **Zero dependencies** - Lightweight and secure
33
+ - ✅ **Full TypeScript support** - Complete type definitions included
34
+ - ✅ **Cryptographic verification** - ED25519 signature validation using Node.js crypto
35
+ - ✅ **Performance optimized** - Async crypto operations with intelligent caching
36
+ - ✅ **Universal runtime support** - Works with Node.js, Bun, and Deno
37
+ - ✅ **ESM & CommonJS** - Supports both module systems
8
38
 
9
39
  ## Runtime Compatibility
10
40
 
@@ -14,154 +44,704 @@ For detailed guides and implementation instructions, see the [official Zero Ad N
14
44
  | Bun | 1.1.0+ | ✅ | ✅ |
15
45
  | Deno | 2.0.0+ | ✅ | ✅ |
16
46
 
17
- ## Purpose
47
+ ## Installation
48
+
49
+ ```bash
50
+ # npm
51
+ npm install @zeroad.network/token
52
+
53
+ # yarn
54
+ yarn add @zeroad.network/token
55
+
56
+ # pnpm
57
+ pnpm add @zeroad.network/token
18
58
 
19
- The module helps developers to:
59
+ # bun
60
+ bun add @zeroad.network/token
20
61
 
21
- - Generate a valid "Welcome Header" when `clientId` and `features` are provided.
22
- - Inject a valid site's HTTP Response Header (**Welcome Header**) into every endpoint. Example:
62
+ # deno
63
+ deno add npm:@zeroad.network/token
64
+ ```
23
65
 
24
- ```http
25
- X-Better-Web-Welcome: "Z2CclA8oXIT1e0QmqTWF8w^1^3"
26
- ```
66
+ ## Quick Start
27
67
 
28
- - Detect and parse Zero Ad Network user tokens sent via HTTP Request Header. Example:
68
+ ### 1. Register Your Site
29
69
 
30
- ```http
31
- X-Better-Web-Hello: "Aav2IXRoh0oKBw==.2yZfC2/pM9DWfgX+von4IgWLmN9t67HJHLiee/gx4+pFIHHurwkC3PCHT1Kaz0yUhx3crUaxST+XLlRtJYacAQ=="
32
- ```
70
+ Before implementing, you need to:
33
71
 
34
- - Verify client token integrity locally.
72
+ 1. [Sign up](https://zeroad.network/login) for a Zero Ad Network account
73
+ 2. [Register your site](https://zeroad.network/publisher/sites/add) to receive your unique `Client ID`
35
74
 
36
- ## Implementation Details
75
+ ### 2. Choose Your Features
37
76
 
38
- - Uses `node:crypto` to verify token signatures with Zero Ad Network's public ED25519 key.
39
- - Decodes token payload to extract protocol version, expiration timestamp, and site features.
40
- - Generates a feature map; expired tokens produce all flags as `false`.
77
+ Decide which features your site will support:
41
78
 
42
- Parsed token example:
79
+ - **`FEATURE.CLEAN_WEB`**: Remove ads, cookie consent screens, trackers, and marketing dialogs
80
+ - **`FEATURE.ONE_PASS`**: Provide free access to paywalled content and base subscription plans
43
81
 
44
- ```js
45
- {
46
- HIDE_ADVERTISEMENTS: boolean,
47
- HIDE_COOKIE_CONSENT_SCREEN: boolean,
48
- HIDE_MARKETING_DIALOGS: boolean,
49
- DISABLE_NON_FUNCTIONAL_TRACKING: boolean,
50
- DISABLE_CONTENT_PAYWALL: boolean,
51
- ENABLE_SUBSCRIPTION_ACCESS: boolean,
52
- };
82
+ ### 3. Basic Implementation
83
+
84
+ ```typescript
85
+ import express from "express";
86
+ import { Site, FEATURE } from "@zeroad.network/token";
87
+
88
+ const app = express();
89
+
90
+ // Initialize once at startup - this creates your site instance
91
+ const site = Site({
92
+ clientId: "YOUR_CLIENT_ID_HERE",
93
+ features: [FEATURE.CLEAN_WEB, FEATURE.ONE_PASS],
94
+ });
95
+
96
+ // Middleware: Inject Welcome Header and parse user tokens
97
+ app.use(async (req, res, next) => {
98
+ // Tell the browser extension your site participates
99
+ res.set(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
100
+
101
+ // Parse the user's subscription token
102
+ req.tokenContext = await site.parseClientToken(req.get(site.CLIENT_HEADER_NAME));
103
+
104
+ next();
105
+ });
106
+
107
+ // Use token context in your templates
108
+ app.get("/", async (req, res) => {
109
+ res.render("index", {
110
+ // Pass token context to control what appears in templates
111
+ tokenContext: req.tokenContext,
112
+ });
113
+ });
114
+
115
+ app.listen(3000);
53
116
  ```
54
117
 
55
- - Verification occurs locally; no data leaves your server.
56
- - Parsing and verification adds roughly 0.06ms–0.6ms to endpoint execution time (tested on M1 MacBook Pro). Performance may vary.
57
- - Redis caching tests show local verification is faster than retrieving cached results.
118
+ ### 4. In Your Templates
119
+
120
+ ```ejs
121
+ <!-- index.ejs -->
122
+ <!DOCTYPE html>
123
+ <html>
124
+ <head>
125
+ <title>My Site</title>
126
+ </head>
127
+ <body>
128
+ <h1>Welcome to My Site</h1>
129
+
130
+ <!-- Only show ads to non-subscribers -->
131
+ <% if (!tokenContext.HIDE_ADVERTISEMENTS) { %>
132
+ <div class="advertisement">
133
+ <!-- Ad code here -->
134
+ </div>
135
+ <% } %>
136
+
137
+ <!-- Only show cookie consent to non-subscribers -->
138
+ <% if (!tokenContext.HIDE_COOKIE_CONSENT_SCREEN) { %>
139
+ <div class="cookie-consent">
140
+ <!-- Cookie consent dialog -->
141
+ </div>
142
+ <% } %>
143
+
144
+ <!-- Content everyone sees -->
145
+ <article>
146
+ <h2>Article Title</h2>
147
+
148
+ <!-- Show preview or full content based on subscription -->
149
+ <% if (tokenContext.DISABLE_CONTENT_PAYWALL) { %>
150
+ <p>Full article content for Zero Ad Network subscribers...</p>
151
+ <% } else { %>
152
+ <p>Article preview... <a href="/subscribe">Subscribe to read more</a></p>
153
+ <% } %>
154
+ </article>
155
+ </body>
156
+ </html>
157
+ ```
58
158
 
59
- ## Benefits of Joining
159
+ ## Token Context
60
160
 
61
- Partnering with Zero Ad Network allows your site to:
161
+ After parsing, the token context contains boolean flags for each feature:
62
162
 
63
- - Generate a new revenue stream by:
64
- - Providing a clean, unobstructed user experience
65
- - Removing paywalls and enabling free access to your base subscription plan
66
- - Or both combined
67
- - Contribute to a truly joyful, user-friendly internet experience
163
+ ```typescript
164
+ interface TokenContext {
165
+ // CLEAN_WEB features
166
+ HIDE_ADVERTISEMENTS: boolean;
167
+ HIDE_COOKIE_CONSENT_SCREEN: boolean;
168
+ HIDE_MARKETING_DIALOGS: boolean;
169
+ DISABLE_NON_FUNCTIONAL_TRACKING: boolean;
68
170
 
69
- ## Onboarding Your Site
171
+ // ONE_PASS features
172
+ DISABLE_CONTENT_PAYWALL: boolean;
173
+ ENABLE_SUBSCRIPTION_ACCESS: boolean;
174
+ }
175
+ ```
70
176
 
71
- 1. [Sign up](https://zeroad.network/login) with Zero Ad Network.
72
- 2. [Register your site](https://zeroad.network/publisher/sites/add) to receive your unique `Client ID` value.
177
+ **Important:** All flags default to `false` for:
73
178
 
74
- Your site must include this header on all publicly accessible HTML or RESTful endpoints so that Zero Ad Network users’ browser extensions can recognize participation.
179
+ - Users without subscriptions
180
+ - Expired tokens
181
+ - Invalid/forged tokens
182
+ - Missing tokens
75
183
 
76
- ## Module Installation
184
+ ## Advanced Configuration
77
185
 
78
- - Written entirely in TypeScript with full types and interfaces.
79
- - Supports both ESM (`import`) and CommonJS (`require`). ESM is recommended when possible.
186
+ ### Cache Configuration
80
187
 
81
- To install the module use your favourite package manager:
188
+ The module includes intelligent caching to minimize crypto operations. Configure caching when creating your site instance:
82
189
 
83
- ```shell
84
- # npm
85
- npm add @zeroad.network/token
190
+ ```typescript
191
+ import { Site, FEATURE } from "@zeroad.network/token";
86
192
 
87
- # or yarn
88
- yarn add @zeroad.network/token
193
+ const site = Site({
194
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
195
+ features: [FEATURE.CLEAN_WEB],
196
+ cacheConfig: {
197
+ enabled: true,
198
+ ttl: 10000, // Cache tokens for 10 seconds
199
+ maxSize: 500, // Store up to 500 unique tokens
200
+ },
201
+ });
202
+ ```
89
203
 
90
- # or pnpm
91
- pnpm add @zeroad.network/token
204
+ **Cache Behavior:**
92
205
 
93
- # or Bun
94
- bun add @zeroad.network/token
206
+ - Automatically respects token expiration times
207
+ - Uses LFU+LRU eviction strategy
208
+ - Thread-safe for concurrent requests
95
209
 
96
- # or Deno
97
- deno add npm:@zeroad.network/token
210
+ For more control, you can configure caching globally:
211
+
212
+ ```typescript
213
+ import { configureCaching } from "@zeroad.network/token";
214
+
215
+ // Apply to all Site instances
216
+ configureCaching({
217
+ enabled: true,
218
+ ttl: 5000,
219
+ maxSize: 100,
220
+ });
221
+ ```
222
+
223
+ ## Security
224
+
225
+ ### Token Verification
226
+
227
+ All tokens are cryptographically signed using ED25519 by Zero Ad Network:
228
+
229
+ - **Signature verification** happens locally on your server using Zero Ad Network's official public key
230
+ - **Trusted authority** - Only tokens signed by Zero Ad Network are valid
231
+ - **No external API calls** - verification is instant and offline
232
+ - **Tamper-proof** - modified tokens fail verification automatically
233
+ - **Time-limited** - expired tokens are automatically rejected
234
+
235
+ The module uses a hardcoded public key from Zero Ad Network, ensuring only legitimate subscriber tokens are accepted.
236
+
237
+ ### Token Structure
238
+
239
+ Each token contains:
240
+
241
+ 1. **Protocol version** - Currently v1
242
+ 2. **Expiration timestamp** - Unix timestamp
243
+ 3. **Feature flags** - Bitmask of enabled features
244
+ 4. **Client ID** (optional) - For developer tokens
245
+ 5. **Cryptographic signature** - ED25519 signature
246
+
247
+ Example token:
248
+
249
+ ```
250
+ X-Better-Web-Hello: Aav2IXRoh0oKBw==.2yZfC2/pM9DWfgX+von4IgWLmN9t67HJHLiee/gx4+pFIHHurwkC3PCHT1Kaz0yUhx3crUaxST+XLlRtJYacAQ==
251
+ ```
252
+
253
+ ### Privacy
254
+
255
+ Tokens contain **no personally identifiable information**:
256
+
257
+ - ❌ No user IDs
258
+ - ❌ No email addresses
259
+ - ❌ No tracking data
260
+ - ✅ Only: expiration date and feature flags
261
+
262
+ ## Performance
263
+
264
+ ### Benchmarks
265
+
266
+ Typical performance on modern hardware (M1 MacBook Pro):
267
+
268
+ | Operation | Time | Notes |
269
+ | ------------------ | ------ | ---------------------------- |
270
+ | Parse cached token | ~10μs | Cache hit |
271
+ | Parse new token | ~150μs | Includes crypto verification |
272
+ | Verify signature | ~100μs | ED25519 verification |
273
+ | Build context | ~8μs | Feature flag processing |
274
+
275
+ ### Optimization Tips
276
+
277
+ 1. **Enable caching** - 80-95% performance improvement for repeated tokens
278
+ 2. **Use async operations** - Crypto runs in Node.js threadpool (non-blocking)
279
+ 3. **Cache at edge** - Consider caching at CDN/proxy level
280
+ 4. **Monitor cache hit rate** - Adjust TTL and size based on traffic patterns
281
+
282
+ ### High-Traffic Scenarios
283
+
284
+ For sites with >1000 req/sec:
285
+
286
+ ```typescript
287
+ configureCaching({
288
+ enabled: true,
289
+ ttl: 30000, // 30 seconds
290
+ maxSize: 5000, // ~2.5MB memory
291
+ });
292
+ ```
293
+
294
+ The async crypto operations utilize Node.js libuv threadpool (4 threads by default), allowing ~8000 verifications/sec without blocking the event loop.
295
+
296
+ ## Framework Examples
297
+
298
+ ### Next.js (Pages Router)
299
+
300
+ ```typescript
301
+ // middleware.ts
302
+ import { NextResponse } from "next/server";
303
+ import type { NextRequest } from "next/server";
304
+ import { Site, FEATURE } from "@zeroad.network/token";
305
+
306
+ // Create site instance once
307
+ const site = Site({
308
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
309
+ features: [FEATURE.CLEAN_WEB, FEATURE.ONE_PASS]
310
+ });
311
+
312
+ export async function middleware(request: NextRequest) {
313
+ const response = NextResponse.next();
314
+
315
+ // Add Welcome Header to response
316
+ response.headers.set(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
317
+
318
+ return response;
319
+ }
320
+
321
+ // pages/article/[id].tsx
322
+ import { GetServerSideProps } from "next";
323
+ import { Site, FEATURE } from "@zeroad.network/token";
324
+
325
+ const site = Site({
326
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
327
+ features: [FEATURE.CLEAN_WEB, FEATURE.ONE_PASS]
328
+ });
329
+
330
+ export const getServerSideProps: GetServerSideProps = async ({ req }) => {
331
+ // Parse token in server-side code
332
+ const tokenContext = await site.parseClientToken(
333
+ req.headers[site.CLIENT_HEADER_NAME]
334
+ );
335
+
336
+ const article = await getArticle();
337
+
338
+ return {
339
+ props: {
340
+ article,
341
+ tokenContext
342
+ }
343
+ };
344
+ };
345
+
346
+ export default function Article({ article, tokenContext }) {
347
+ return (
348
+ <div>
349
+ {/* Conditionally render based on token context */}
350
+ {!tokenContext.HIDE_ADVERTISEMENTS && (
351
+ <div className="ad-banner">Ad content</div>
352
+ )}
353
+
354
+ <article>
355
+ <h1>{article.title}</h1>
356
+ {tokenContext.DISABLE_CONTENT_PAYWALL ? (
357
+ <div>{article.fullContent}</div>
358
+ ) : (
359
+ <div>{article.preview}</div>
360
+ )}
361
+ </article>
362
+ </div>
363
+ );
364
+ }
98
365
  ```
99
366
 
100
- ## Examples
367
+ ### Fastify
368
+
369
+ ```typescript
370
+ import Fastify from "fastify";
371
+ import { Site, FEATURE } from "@zeroad.network/token";
101
372
 
102
- For more example implementations using `Express.js` (JavaScript), `Hono`, and `Fastify` (TypeScript), visit the [examples section on our GitHub repository](https://github.com/laurynas-karvelis/zeroad-token-typescript/tree/main/examples/).
373
+ const fastify = Fastify();
374
+
375
+ // Create site instance once
376
+ const site = Site({
377
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
378
+ features: [FEATURE.CLEAN_WEB],
379
+ });
103
380
 
104
- The following JavaScript example provides a quick reference, demonstrating how to:
381
+ fastify.addHook("onRequest", async (request, reply) => {
382
+ reply.header(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
105
383
 
106
- - Inject the "Welcome Header" into responses
107
- - Parse the user's token from the request header
108
- - Use the `tokenContext` in controllers and templates
384
+ request.tokenContext = await site.parseClientToken(request.headers[site.CLIENT_HEADER_NAME]);
385
+ });
386
+
387
+ fastify.get("/", async (request, reply) => {
388
+ return reply.view("index", {
389
+ tokenContext: request.tokenContext,
390
+ });
391
+ });
392
+
393
+ await fastify.listen({ port: 3000 });
394
+ ```
109
395
 
110
- Minimal Express.js v5 app example:
396
+ ### Hono
111
397
 
112
- ```js
398
+ ```typescript
399
+ import { Hono } from "hono";
400
+ import { Site, FEATURE } from "@zeroad.network/token";
401
+
402
+ const app = new Hono();
403
+
404
+ // Create site instance once
405
+ const site = Site({
406
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
407
+ features: [FEATURE.CLEAN_WEB, FEATURE.ONE_PASS],
408
+ });
409
+
410
+ app.use("*", async (c, next) => {
411
+ c.header(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
412
+
413
+ c.set("tokenContext", await site.parseClientToken(c.req.header(site.CLIENT_HEADER_NAME)));
414
+
415
+ await next();
416
+ });
417
+
418
+ app.get("/", (c) => {
419
+ return c.html(
420
+ renderTemplate({
421
+ tokenContext: c.get("tokenContext"),
422
+ })
423
+ );
424
+ });
425
+
426
+ export default app;
427
+ ```
428
+
429
+ ## Complete Usage Example
430
+
431
+ ```typescript
113
432
  import express from "express";
114
- import { Site } from "@zeroad.network/token";
433
+ import { Site, FEATURE } from "@zeroad.network/token";
115
434
 
116
435
  const app = express();
117
436
 
118
- // Initialize the Zero Ad Network module at app startup.
119
- // Your site's `clientId` value is obtained during site registration on the Zero Ad Network platform (https://zeroad.network).
120
- const ZERO_AD_NETWORK_CLIENT_ID = "DEMO-Z2CclA8oXIT1e0Qmq";
437
+ // Create your site instance once at startup
121
438
  const site = Site({
122
- clientId: ZERO_AD_NETWORK_CLIENT_ID,
439
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
123
440
  features: [FEATURE.CLEAN_WEB, FEATURE.ONE_PASS],
441
+ cacheConfig: {
442
+ enabled: true,
443
+ ttl: 10000,
444
+ maxSize: 500,
445
+ },
446
+ });
447
+
448
+ // Global middleware
449
+ app.use(async (req, res, next) => {
450
+ res.set(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
451
+ req.tokenContext = await site.parseClientToken(req.get(site.CLIENT_HEADER_NAME));
452
+ next();
124
453
  });
125
454
 
126
- app
127
- // Apply middleware for all routes
128
- .use((req, res, next) => {
129
- // Inject the "X-Better-Web-Welcome" header into the response
130
- res.set(site.SERVER_HEADER_NAME, site.SERVER_HEADER_VALUE);
131
-
132
- // Parse the incoming user token from the request header
133
- // Attach the parsed token data to the request object for downstream use
134
- req.tokenContext = site.parseClientToken(req.get(site.CLIENT_HEADER_NAME));
135
-
136
- next();
137
- })
138
- .get("/", (req, res) => {
139
- // Access parsed token data for this request
140
- console.log(req.tokenContext);
141
-
142
- // Example structure of tokenContext:
143
- req.tokenContext = {
144
- // If `true`: Hide advertisements on the page
145
- HIDE_ADVERTISEMENTS: boolean,
146
- // If `true`: Disable all Cookie Consent screens (headers, footers, or dialogs)
147
- HIDE_COOKIE_CONSENT_SCREEN: boolean,
148
- // If `true`: Disable all marketing dialogs or popups (newsletters, promotions, etc.)
149
- HIDE_MARKETING_DIALOGS: boolean,
150
- // If `true`: Fully opt out the user of non-functional trackers
151
- DISABLE_NON_FUNCTIONAL_TRACKING: boolean,
152
- // If `true`: Provide Free access to content behind a paywall (news, articles, etc.)
153
- DISABLE_CONTENT_PAYWALL: boolean,
154
- // If `true`: Provide Free access to your base subscription plan (if subscription model is present)
155
- ENABLE_SUBSCRIPTION_ACCESS: boolean,
156
- };
157
-
158
- // Adjust content in your templates based on tokenContext values
159
- res.render("index.ejs", { tokenContext });
455
+ // Homepage with ads
456
+ app.get("/", async (req, res) => {
457
+ res.render("index", {
458
+ tokenContext: req.tokenContext,
160
459
  });
460
+ });
461
+
462
+ // Article page with paywall
463
+ app.get("/article/:id", async (req, res) => {
464
+ const article = await db.articles.findById(req.params.id);
465
+
466
+ res.render("article", {
467
+ article,
468
+ tokenContext: req.tokenContext,
469
+ });
470
+ });
471
+
472
+ // Premium API endpoint
473
+ app.get("/api/premium-data", async (req, res) => {
474
+ if (!req.tokenContext.ENABLE_SUBSCRIPTION_ACCESS) {
475
+ return res.status(403).json({
476
+ error: "Premium subscription required",
477
+ });
478
+ }
479
+
480
+ const data = await getPremiumData();
481
+ res.json(data);
482
+ });
483
+
484
+ app.listen(3000, () => {
485
+ console.log("Server running on http://localhost:3000");
486
+ });
487
+ ```
488
+
489
+ **Template Example:**
490
+
491
+ ```ejs
492
+ <!-- article.ejs -->
493
+ <!DOCTYPE html>
494
+ <html>
495
+ <head>
496
+ <title><%= article.title %></title>
497
+ </head>
498
+ <body>
499
+ <!-- Ads only for non-subscribers -->
500
+ <% if (!tokenContext.HIDE_ADVERTISEMENTS) { %>
501
+ <div class="ad-banner">
502
+ <!-- Google AdSense or other ad code -->
503
+ </div>
504
+ <% } %>
505
+
506
+ <!-- Cookie consent only for non-subscribers -->
507
+ <% if (!tokenContext.HIDE_COOKIE_CONSENT_SCREEN) { %>
508
+ <div class="cookie-consent">
509
+ <p>We use cookies...</p>
510
+ </div>
511
+ <% } %>
512
+
513
+ <article>
514
+ <h1><%= article.title %></h1>
515
+
516
+ <!-- Full content for subscribers, preview for others -->
517
+ <% if (tokenContext.DISABLE_CONTENT_PAYWALL) { %>
518
+ <div class="full-content">
519
+ <%- article.fullContent %>
520
+ </div>
521
+ <% } else { %>
522
+ <div class="preview">
523
+ <%- article.preview %>
524
+ <div class="paywall">
525
+ <p>Subscribe to read the full article</p>
526
+ <a href="/subscribe">Subscribe Now</a>
527
+ </div>
528
+ </div>
529
+ <% } %>
530
+ </article>
531
+
532
+ <!-- Marketing popup only for non-subscribers -->
533
+ <% if (!tokenContext.HIDE_MARKETING_DIALOGS) { %>
534
+ <div class="newsletter-popup">
535
+ <p>Subscribe to our newsletter!</p>
536
+ </div>
537
+ <% } %>
538
+
539
+ <!-- Analytics tracking (only functional cookies for subscribers) -->
540
+ <% if (!tokenContext.DISABLE_NON_FUNCTIONAL_TRACKING) { %>
541
+ <script>
542
+ // Google Analytics or other tracking code
543
+ </script>
544
+ <% } %>
545
+ </body>
546
+ </html>
547
+ ```
548
+
549
+ ## Implementation Requirements
550
+
551
+ When implementing Zero Ad Network features, you **must** fulfill these requirements to remain in good standing:
552
+
553
+ ### CLEAN_WEB Requirements
554
+
555
+ - ✅ Disable **all** advertisements on the page
556
+ - ✅ Disable **all** cookie consent screens (headers, footers, dialogs)
557
+ - ✅ Fully opt out users from **non-functional** trackers
558
+ - ✅ Disable **all** marketing dialogs or popups (newsletters, promotions)
559
+
560
+ ### ONE_PASS Requirements
561
+
562
+ - ✅ Provide free access to content behind paywalls
563
+ - ✅ Provide free access to your base subscription plan (if applicable)
564
+
565
+ **⚠️ Failure to comply will result in removal from the Zero Ad Network platform.**
566
+
567
+ ## Troubleshooting
568
+
569
+ ### Tokens Not Working
161
570
 
162
- const port = 3000;
571
+ ```typescript
572
+ import { Site, setLogLevel } from "@zeroad.network/token";
163
573
 
164
- app.listen(port, () => {
165
- console.log(`Server listening at http://localhost:${port}`);
574
+ // Enable debug logging
575
+ setLogLevel("debug");
576
+
577
+ const site = Site({
578
+ clientId: process.env.ZERO_AD_CLIENT_ID!,
579
+ features: [FEATURE.CLEAN_WEB],
580
+ });
581
+
582
+ // In your route handler
583
+ app.use(async (req, res, next) => {
584
+ // Check if token header is being received
585
+ const headerValue = req.get(site.CLIENT_HEADER_NAME);
586
+ console.log("Header value:", headerValue);
587
+
588
+ // Parse and verify
589
+ const tokenContext = await site.parseClientToken(headerValue);
590
+ console.log("Token context:", tokenContext);
591
+
592
+ req.tokenContext = tokenContext;
593
+ next();
166
594
  });
167
595
  ```
596
+
597
+ ### Cache Issues
598
+
599
+ ```typescript
600
+ import { clearHeaderCache, getCacheConfig } from "@zeroad.network/token";
601
+
602
+ // Check current config
603
+ console.log(getCacheConfig());
604
+
605
+ // Clear cache if needed
606
+ clearHeaderCache();
607
+
608
+ // Disable caching for debugging
609
+ configureCaching({ enabled: false });
610
+ ```
611
+
612
+ ### Common Issues
613
+
614
+ 1. **All flags are false** - Token is expired, invalid, or missing
615
+ 2. **Performance slow** - Enable caching or increase cache size
616
+ 3. **Token rejected** - Verify Client ID matches registered site
617
+ 4. **Headers not sent** - Ensure middleware runs before routes
618
+
619
+ ## API Reference
620
+
621
+ ### `Site(options)`
622
+
623
+ Creates a site instance with helper methods. **This is the recommended way to use the module.**
624
+
625
+ ```typescript
626
+ const site = Site({
627
+ clientId: "YOUR_CLIENT_ID",
628
+ features: [FEATURE.CLEAN_WEB],
629
+ cacheConfig: {
630
+ // optional
631
+ enabled: true,
632
+ ttl: 5000,
633
+ maxSize: 100,
634
+ },
635
+ });
636
+
637
+ // Returns an object with:
638
+ site.parseClientToken(headerValue); // Parse and verify tokens
639
+ site.CLIENT_HEADER_NAME; // "x-better-web-hello"
640
+ site.SERVER_HEADER_NAME; // "X-Better-Web-Welcome"
641
+ site.SERVER_HEADER_VALUE; // Your site's welcome header value
642
+ ```
643
+
644
+ **Options:**
645
+
646
+ - `clientId` (string, required) - Your site's Client ID from Zero Ad Network
647
+ - `features` (FEATURE[], required) - Array of enabled features
648
+ - `cacheConfig` (CacheConfig, optional) - Cache configuration
649
+
650
+ ### `configureCaching(config)`
651
+
652
+ Configure global cache settings (applies to all Site instances).
653
+
654
+ ```typescript
655
+ import { configureCaching } from "@zeroad.network/token";
656
+
657
+ configureCaching({
658
+ enabled: boolean, // Enable/disable caching
659
+ ttl: number, // Time-to-live in milliseconds
660
+ maxSize: number, // Maximum cache entries
661
+ });
662
+ ```
663
+
664
+ ### `clearHeaderCache()`
665
+
666
+ Manually clear the token cache.
667
+
668
+ ```typescript
669
+ import { clearHeaderCache } from "@zeroad.network/token";
670
+
671
+ clearHeaderCache();
672
+ ```
673
+
674
+ ### `setLogLevel(level)`
675
+
676
+ Set logging verbosity for debugging.
677
+
678
+ ```typescript
679
+ import { setLogLevel } from "@zeroad.network/token";
680
+
681
+ setLogLevel("debug"); // "error" | "warn" | "info" | "debug"
682
+ ```
683
+
684
+ ### `setLogTransport(fn)`
685
+
686
+ Customize where logs are sent (useful for production monitoring).
687
+
688
+ ```typescript
689
+ import { setLogTransport } from "@zeroad.network/token";
690
+
691
+ // Send logs to your monitoring service
692
+ setLogTransport((level, ...args) => {
693
+ if (level === "error") {
694
+ yourMonitoringService.captureError(args);
695
+ } else {
696
+ yourLogger.log(level, ...args);
697
+ }
698
+ });
699
+
700
+ // Example: Integrate with Winston
701
+ import winston from "winston";
702
+
703
+ const logger = winston.createLogger({
704
+ transports: [new winston.transports.File({ filename: "zeroad.log" })],
705
+ });
706
+
707
+ setLogTransport((level, ...args) => {
708
+ logger.log(level, args.join(" "));
709
+ });
710
+
711
+ // Example: Disable all logging in production
712
+ if (process.env.NODE_ENV === "production") {
713
+ setLogTransport(() => {}); // No-op function
714
+ }
715
+ ```
716
+
717
+ ## Resources
718
+
719
+ - 📖 [Official Documentation](https://docs.zeroad.network)
720
+ - 🌐 [Zero Ad Network Platform](https://zeroad.network)
721
+ - 💻 [Example Implementations](https://github.com/laurynas-karvelis/zeroad-token-typescript/tree/main/examples/)
722
+ - 📝 [Blog](https://docs.zeroad.network/blog)
723
+
724
+ ## Contributing
725
+
726
+ This module is open-source. Contributions are welcome! Please ensure:
727
+
728
+ - All tests pass
729
+ - Code follows existing style
730
+ - TypeScript types are complete
731
+ - Documentation is updated
732
+
733
+ ## License
734
+
735
+ Apache License 2.0 - see LICENSE file for details
736
+
737
+ ## About Zero Ad Network
738
+
739
+ Zero Ad Network is building a fairer internet where:
740
+
741
+ - Users enjoy cleaner, faster browsing
742
+ - Publishers earn sustainable revenue
743
+ - Privacy is respected by default
744
+
745
+ Join thousands of publishers creating a better web experience.
746
+
747
+ [Get Started →](https://zeroad.network/login)