@aamp/protocol 1.1.6 → 1.1.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/express.js DELETED
@@ -1,64 +0,0 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Zero-friction integration for Express/Node.js.
4
- */
5
- import { AAMPPublisher } from './publisher.js';
6
- import { ContentOrigin } from './types.js';
7
- import { generateKeyPair } from './crypto.js';
8
- export class AAMP {
9
- constructor(config) {
10
- this.publisher = new AAMPPublisher({ version: '1.1', ...config.policy }, config.strategy || 'PASSIVE', config.cache);
11
- this.origin = ContentOrigin[config.meta.origin];
12
- this.ready = generateKeyPair().then(keys => {
13
- return this.publisher.initialize(keys);
14
- });
15
- }
16
- static init(config) {
17
- return new AAMP(config);
18
- }
19
- /**
20
- * Express Middleware
21
- */
22
- middleware() {
23
- return async (req, res, next) => {
24
- await this.ready;
25
- // Normalize headers to lowercase dictionary
26
- const headers = {};
27
- Object.keys(req.headers).forEach(key => {
28
- headers[key.toLowerCase()] = req.headers[key];
29
- });
30
- // Retrieve Raw Payload if available (optional but good for crypto)
31
- // Note: Express body parsing might interfere, so we usually rely on the header content.
32
- const rawPayload = headers['x-aamp-payload'];
33
- // Evaluate Visitor
34
- const result = await this.publisher.evaluateVisitor(headers, rawPayload);
35
- // Enforce Decision
36
- if (!result.allowed) {
37
- res.status(result.status).json({
38
- error: result.reason,
39
- visitor_type: result.visitorType,
40
- proof_used: result.proofUsed
41
- });
42
- return;
43
- }
44
- // Inject Provenance Headers (For the humans/agents that got through)
45
- const respHeaders = await this.publisher.generateResponseHeaders(this.origin);
46
- Object.entries(respHeaders).forEach(([k, v]) => {
47
- res.setHeader(k, v);
48
- });
49
- // Attach metadata to request for downstream use
50
- req.aamp = {
51
- verified: result.visitorType === 'VERIFIED_AGENT',
52
- type: result.visitorType,
53
- ...result.metadata
54
- };
55
- next();
56
- };
57
- }
58
- discoveryHandler() {
59
- return (req, res) => {
60
- res.setHeader('Content-Type', 'application/json');
61
- res.send(JSON.stringify(this.publisher.getPolicy(), null, 2));
62
- };
63
- }
64
- }
package/dist/index.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * AAMP SDK Public API
3
- *
4
- * This is the main entry point for the library.
5
- */
6
- export * from './types.js';
7
- export * from './constants.js';
8
- export * from './agent.js';
9
- export * from './publisher.js';
10
- export * from './crypto.js';
11
- export * from './express.js';
12
- export { AAMPNext } from './nextjs.js';
package/dist/index.js DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * AAMP SDK Public API
3
- *
4
- * This is the main entry point for the library.
5
- */
6
- export * from './types.js';
7
- export * from './constants.js';
8
- export * from './agent.js';
9
- export * from './publisher.js';
10
- export * from './crypto.js';
11
- export * from './express.js'; // Node.js / Express Adapter
12
- export { AAMPNext } from './nextjs.js'; // Serverless / Next.js Adapter
package/dist/nextjs.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types.js';
2
- type NextRequest = any;
3
- type NextResponse = any;
4
- export interface AAMPConfig {
5
- policy: Omit<AccessPolicy, 'version'>;
6
- meta: {
7
- origin: keyof typeof ContentOrigin;
8
- paymentPointer?: string;
9
- };
10
- strategy?: UnauthenticatedStrategy;
11
- cache?: IdentityCache;
12
- }
13
- export declare class AAMPNext {
14
- private publisher;
15
- private origin;
16
- private ready;
17
- private constructor();
18
- static init(config: AAMPConfig): AAMPNext;
19
- /**
20
- * Serverless Route Wrapper
21
- */
22
- withProtection(handler: (req: NextRequest) => Promise<NextResponse>): (req: NextRequest) => Promise<any>;
23
- discoveryHandler(): () => Promise<Response>;
24
- }
25
- export {};
package/dist/nextjs.js DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Layer 3: Framework Adapters
3
- * Serverless integration for Next.js (App Router & API Routes).
4
- */
5
- import { AAMPPublisher } from './publisher.js';
6
- import { ContentOrigin } from './types.js';
7
- import { generateKeyPair } from './crypto.js';
8
- const createJsonResponse = (body, status = 200) => {
9
- return new Response(JSON.stringify(body), {
10
- status,
11
- headers: { 'Content-Type': 'application/json' }
12
- });
13
- };
14
- export class AAMPNext {
15
- constructor(config) {
16
- this.publisher = new AAMPPublisher({ version: '1.1', ...config.policy }, config.strategy || 'PASSIVE', config.cache);
17
- this.origin = ContentOrigin[config.meta.origin];
18
- this.ready = generateKeyPair().then(keys => this.publisher.initialize(keys));
19
- }
20
- static init(config) {
21
- return new AAMPNext(config);
22
- }
23
- /**
24
- * Serverless Route Wrapper
25
- */
26
- withProtection(handler) {
27
- return async (req) => {
28
- await this.ready;
29
- // Extract Headers map
30
- const headers = {};
31
- req.headers.forEach((value, key) => {
32
- headers[key.toLowerCase()] = value;
33
- });
34
- // Evaluate
35
- const result = await this.publisher.evaluateVisitor(headers, headers['x-aamp-payload']);
36
- if (!result.allowed) {
37
- return createJsonResponse({
38
- error: result.reason,
39
- visitor_type: result.visitorType,
40
- proof_used: result.proofUsed
41
- }, result.status);
42
- }
43
- // Execute Handler
44
- const response = await handler(req);
45
- // Inject Provenance
46
- const aampHeaders = await this.publisher.generateResponseHeaders(this.origin);
47
- if (response && response.headers) {
48
- Object.entries(aampHeaders).forEach(([k, v]) => {
49
- response.headers.set(k, v);
50
- });
51
- }
52
- return response;
53
- };
54
- }
55
- discoveryHandler() {
56
- return async () => {
57
- return createJsonResponse(this.publisher.getPolicy());
58
- };
59
- }
60
- }
package/dist/proof.d.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Verifies a JWT (Proof Token or Payment Credential) using JWKS.
3
- *
4
- * @param token The JWT string
5
- * @param jwksUrl The URL to fetch Public Keys
6
- * @param issuer The expected issuer
7
- * @param audience The expected audience range
8
- */
9
- export declare function verifyJwt(token: string, jwksUrl: string, issuer: string, audience?: string): Promise<boolean>;
package/dist/proof.js DELETED
@@ -1,27 +0,0 @@
1
- import { createRemoteJWKSet, jwtVerify } from 'jose';
2
- // In-memory cache for JWKS to avoid repeated fetches
3
- // Jose's createRemoteJWKSet handles caching/cooldowns internally.
4
- /**
5
- * Verifies a JWT (Proof Token or Payment Credential) using JWKS.
6
- *
7
- * @param token The JWT string
8
- * @param jwksUrl The URL to fetch Public Keys
9
- * @param issuer The expected issuer
10
- * @param audience The expected audience range
11
- */
12
- export async function verifyJwt(token, jwksUrl, issuer, audience) {
13
- try {
14
- const JWKS = createRemoteJWKSet(new URL(jwksUrl));
15
- const { payload } = await jwtVerify(token, JWKS, {
16
- issuer: issuer,
17
- audience: audience // specific audience check if provided
18
- });
19
- // Check specific AAMP claims if we standardize them
20
- // if (payload.type !== 'AD_IMPRESSION') return false;
21
- return true;
22
- }
23
- catch (error) {
24
- // console.error("Ad Proof Verification Failed:", error);
25
- return false;
26
- }
27
- }
@@ -1,35 +0,0 @@
1
- import { AccessPolicy, ContentOrigin, EvaluationResult, IdentityCache, UnauthenticatedStrategy } from './types.js';
2
- export declare class AAMPPublisher {
3
- private policy;
4
- private keyPair;
5
- private unauthenticatedStrategy;
6
- private cache;
7
- private readonly CACHE_TTL_SECONDS;
8
- constructor(policy: AccessPolicy, strategy?: UnauthenticatedStrategy, cacheImpl?: IdentityCache);
9
- initialize(keyPair: CryptoKeyPair): Promise<void>;
10
- getPolicy(): AccessPolicy;
11
- /**
12
- * Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
13
- */
14
- evaluateVisitor(reqHeaders: Record<string, string | undefined>, rawPayload?: string): Promise<EvaluationResult>;
15
- /**
16
- * Browser Heuristics (Hardened)
17
- * 1. Checks Known Bot Signatures (Fast Fail)
18
- * 2. Checks Trusted Upstream Signals (Cloudflare/Vercel)
19
- * 3. Checks Browser Header Consistency
20
- */
21
- private performBrowserHeuristics;
22
- /**
23
- * Handle AAMP Protocol Logic
24
- */
25
- private handleAgent;
26
- private verifyRequestLogic;
27
- private verifyDnsBinding;
28
- private isDomain;
29
- generateResponseHeaders(origin: ContentOrigin): Promise<Record<string, string>>;
30
- /**
31
- * Handling Quality Feedback (The "Dispute" Layer)
32
- * This runs when an Agent sends 'x-aamp-feedback'.
33
- */
34
- private handleFeedback;
35
- }
package/dist/publisher.js DELETED
@@ -1,338 +0,0 @@
1
- /**
2
- * Layer 2: Publisher Middleware
3
- * Used by content owners to enforce policy, log access, and filter bots.
4
- */
5
- import { HEADERS, MAX_CLOCK_SKEW_MS, WELL_KNOWN_AGENT_PATH } from './constants.js';
6
- import { exportPublicKey, signData, verifySignature } from './crypto.js';
7
- import { AccessPurpose } from './types.js';
8
- import { verifyJwt } from './proof.js';
9
- /**
10
- * Default In-Memory Cache (Fallback only)
11
- * NOT recommended for high-traffic Serverless production.
12
- */
13
- class MemoryCache {
14
- constructor() {
15
- this.store = new Map();
16
- }
17
- async get(key) {
18
- const item = this.store.get(key);
19
- if (!item)
20
- return null;
21
- if (Date.now() > item.exp) {
22
- this.store.delete(key);
23
- return null;
24
- }
25
- return item.val;
26
- }
27
- async set(key, value, ttlSeconds) {
28
- this.store.set(key, {
29
- val: value,
30
- exp: Date.now() + (ttlSeconds * 1000)
31
- });
32
- }
33
- }
34
- export class AAMPPublisher {
35
- constructor(policy, strategy = 'PASSIVE', cacheImpl) {
36
- this.keyPair = null;
37
- // Default TTL: 1 Hour
38
- this.CACHE_TTL_SECONDS = 3600;
39
- this.policy = policy;
40
- this.unauthenticatedStrategy = strategy;
41
- this.cache = cacheImpl || new MemoryCache();
42
- }
43
- async initialize(keyPair) {
44
- this.keyPair = keyPair;
45
- }
46
- getPolicy() {
47
- return this.policy;
48
- }
49
- /**
50
- * Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
51
- */
52
- async evaluateVisitor(reqHeaders, rawPayload) {
53
- // 1. Check for AAMP Headers
54
- const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
55
- const feedbackToken = reqHeaders[HEADERS.FEEDBACK];
56
- if (feedbackToken) {
57
- await this.handleFeedback(feedbackToken, reqHeaders);
58
- }
59
- if (hasAamp) {
60
- console.log("\nšŸ”Ž [AAMP Middleware] Detected Agent Headers. Starting Verification...");
61
- // It claims to be an Agent. Verify it.
62
- return await this.handleAgent(reqHeaders, rawPayload);
63
- }
64
- // 2. It's not an AAMP Agent. Apply Strategy.
65
- if (this.unauthenticatedStrategy === 'STRICT') {
66
- return {
67
- allowed: false,
68
- status: 401,
69
- reason: "STRICT_MODE: Only AAMP verified agents allowed.",
70
- visitorType: 'UNIDENTIFIED_BOT'
71
- };
72
- }
73
- if (this.unauthenticatedStrategy === 'PASSIVE') {
74
- return {
75
- allowed: true,
76
- status: 200,
77
- reason: "PASSIVE_MODE: Allowed without verification.",
78
- visitorType: 'LIKELY_HUMAN'
79
- };
80
- }
81
- // 3. HYBRID MODE: Heuristic Analysis (The "Lazy Bot" Filter)
82
- const isHuman = this.performBrowserHeuristics(reqHeaders);
83
- if (isHuman) {
84
- return {
85
- allowed: true,
86
- status: 200,
87
- reason: "BROWSER_VERIFIED: Heuristics passed.",
88
- visitorType: 'LIKELY_HUMAN'
89
- };
90
- }
91
- else {
92
- return {
93
- allowed: false,
94
- status: 403,
95
- reason: "BOT_DETECTED: Request lacks browser signatures and AAMP headers.",
96
- visitorType: 'UNIDENTIFIED_BOT'
97
- };
98
- }
99
- }
100
- /**
101
- * Browser Heuristics (Hardened)
102
- * 1. Checks Known Bot Signatures (Fast Fail)
103
- * 2. Checks Trusted Upstream Signals (Cloudflare/Vercel)
104
- * 3. Checks Browser Header Consistency
105
- */
106
- performBrowserHeuristics(headers) {
107
- const userAgent = headers['user-agent'] || '';
108
- // A. The "Obvious Bot" Blocklist (Fast Fail)
109
- const botSignatures = ['python-requests', 'curl', 'wget', 'scrapy', 'bot', 'crawler', 'spider'];
110
- if (botSignatures.some(sig => userAgent.toLowerCase().includes(sig))) {
111
- return false;
112
- }
113
- // B. Trusted Infrastructure Signals (The Real World Solution)
114
- if (headers['cf-visitor'] || headers['cf-ray'])
115
- return true;
116
- if (headers['x-vercel-id'])
117
- return true;
118
- if (headers['cloudfront-viewer-address'])
119
- return true;
120
- // C. The "Browser Fingerprint" (Fallback for direct connections)
121
- const hasAcceptLanguage = !!headers['accept-language'];
122
- const hasSecFetchDest = !!headers['sec-fetch-dest'];
123
- const hasUpgradeInsecure = !!headers['upgrade-insecure-requests'];
124
- if (hasAcceptLanguage && (hasSecFetchDest || hasUpgradeInsecure)) {
125
- return true;
126
- }
127
- return false;
128
- }
129
- /**
130
- * Handle AAMP Protocol Logic
131
- */
132
- async handleAgent(reqHeaders, rawPayload) {
133
- try {
134
- const payloadHeader = reqHeaders[HEADERS.PAYLOAD];
135
- const sigHeader = reqHeaders[HEADERS.SIGNATURE];
136
- const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY];
137
- const headerJson = atob(payloadHeader);
138
- const requestHeader = JSON.parse(headerJson);
139
- const signedRequest = {
140
- header: requestHeader,
141
- signature: sigHeader,
142
- publicKey: keyHeader
143
- };
144
- const agentKey = await crypto.subtle.importKey("spki", new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
145
- const proofToken = reqHeaders[HEADERS.PROOF_TOKEN];
146
- const paymentCredential = reqHeaders[HEADERS.PAYMENT_CREDENTIAL];
147
- // Verify Core Logic
148
- const result = await this.verifyRequestLogic(signedRequest, agentKey, proofToken, paymentCredential, headerJson);
149
- if (!result.allowed) {
150
- console.log(`ā›” [AAMP BLOCK]
151
- Agent: ${requestHeader.agent_id}
152
- Reason: ${result.reason}
153
- VisitorType: ${result.visitorType}
154
- Proof: ${result.proofUsed || 'None'}
155
- Identity Verified: ${result.identityVerified}`);
156
- return {
157
- allowed: false,
158
- status: 403,
159
- reason: result.reason,
160
- visitorType: 'VERIFIED_AGENT'
161
- };
162
- }
163
- console.log(`āœ… [AAMP ALLOW]
164
- Agent: ${requestHeader.agent_id}
165
- Reason: AAMP_VERIFIED
166
- Payment Method: ${result.proofUsed}
167
- Identity Verified: ${result.identityVerified}`);
168
- return {
169
- allowed: true,
170
- status: 200,
171
- reason: "AAMP_VERIFIED",
172
- visitorType: 'VERIFIED_AGENT',
173
- metadata: requestHeader
174
- };
175
- }
176
- catch (e) {
177
- console.error(e);
178
- return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
179
- }
180
- }
181
- async verifyRequestLogic(request, requestPublicKey, proofToken, paymentCredential, rawPayload) {
182
- // 1. Replay Attack Prevention
183
- const requestTime = new Date(request.header.ts).getTime();
184
- if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
185
- return { allowed: false, reason: 'TIMESTAMP_INVALID', identityVerified: false };
186
- }
187
- // 2. Crypto Verification
188
- const signableString = rawPayload || JSON.stringify(request.header);
189
- const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
190
- if (!isCryptoValid)
191
- return { allowed: false, reason: 'CRYPTO_FAIL', identityVerified: false };
192
- // 3. Identity Verification (DNS Binding) with Cache
193
- let identityVerified = false;
194
- const claimedDomain = request.header.agent_id;
195
- const pubKeyString = await exportPublicKey(requestPublicKey);
196
- console.log(` šŸ†” [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
197
- // Check Cache First
198
- const cachedKey = await this.cache.get(claimedDomain);
199
- if (cachedKey === pubKeyString) {
200
- console.log(" ⚔ [AAMP Cache] Identity found in cache.");
201
- identityVerified = true;
202
- }
203
- else if (this.isDomain(claimedDomain)) {
204
- // Cache Miss: Perform DNS Fetch
205
- identityVerified = await this.verifyDnsBinding(claimedDomain, pubKeyString);
206
- if (identityVerified) {
207
- await this.cache.set(claimedDomain, pubKeyString, this.CACHE_TTL_SECONDS);
208
- }
209
- }
210
- if (this.policy.requireIdentityBinding && !identityVerified) {
211
- return { allowed: false, reason: 'IDENTITY_FAIL: DNS Binding could not be verified.', identityVerified: false };
212
- }
213
- // 4. Policy Check: Purpose
214
- if (request.header.purpose === AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
215
- return { allowed: false, reason: 'POLICY_DENIED: Training not allowed.', identityVerified };
216
- }
217
- if (request.header.purpose === AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
218
- return { allowed: false, reason: 'POLICY_DENIED: RAG not allowed.', identityVerified };
219
- }
220
- // 5. Policy Check: Economics (v1.2)
221
- if (this.policy.requiresPayment) {
222
- let paymentSatisfied = false;
223
- // Method A: Flexible Payment Callback (DB / Custom Logic)
224
- if (this.policy.monetization?.checkPayment) {
225
- const isPaid = await this.policy.monetization.checkPayment(request.header.agent_id, request.header.purpose);
226
- if (isPaid) {
227
- console.log(` šŸ’° [AAMP Audit] Whitelist Check Passed via Callback.`);
228
- return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'WHITELIST_CALLBACK' };
229
- }
230
- }
231
- // Method B: Payment Credentials (Unified JWT)
232
- if (!paymentSatisfied && this.policy.monetization?.paymentConfig && paymentCredential) {
233
- const { jwksUrl, issuer } = this.policy.monetization.paymentConfig;
234
- console.log(` šŸ” [AAMP Audit] Verifying Payment Credential (Issuer: ${issuer})...`);
235
- const isValidCredential = await verifyJwt(paymentCredential, jwksUrl, issuer);
236
- if (isValidCredential) {
237
- console.log(` āœ… [AAMP Audit] Credential Signature VALID.`);
238
- return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'PAYMENT_CREDENTIAL_JWT' };
239
- }
240
- else {
241
- console.log(` āŒ [AAMP Audit] Credential Signature INVALID.`);
242
- }
243
- }
244
- // Method C: Ad-Supported (Proof Verification)
245
- if (!paymentSatisfied && this.policy.allowAdSupportedAccess && request.header.context.ads_displayed) {
246
- if (proofToken && this.policy.monetization?.adNetwork) {
247
- const { jwksUrl, issuer } = this.policy.monetization.adNetwork;
248
- console.log(` šŸ“ŗ [AAMP Audit] Verifying Ad Proof (Issuer: ${issuer})...`);
249
- const isValidProof = await verifyJwt(proofToken, jwksUrl, issuer);
250
- if (isValidProof) {
251
- console.log(` āœ… [AAMP Audit] Ad Proof Signature VALID.`);
252
- return { allowed: true, reason: 'OK', identityVerified, proofUsed: 'AD_PROOF_JWT' };
253
- }
254
- else {
255
- console.log(` āŒ [AAMP Audit] Ad Proof Signature INVALID.`);
256
- }
257
- }
258
- else {
259
- console.log(` āš ļø [AAMP Audit] Ad Proof MISSING.`);
260
- }
261
- }
262
- if (!paymentSatisfied) {
263
- return { allowed: false, reason: 'PAYMENT_REQUIRED: Whitelist, Credential, and Ad Proof checks ALL failed.', identityVerified, proofUsed: 'NONE', visitorType: 'VERIFIED_AGENT' };
264
- }
265
- }
266
- return { allowed: true, reason: 'OK', identityVerified };
267
- }
268
- async verifyDnsBinding(domain, requestKeySpki) {
269
- try {
270
- // Allow HTTP for localhost testing
271
- const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
272
- const url = `${protocol}://${domain}${WELL_KNOWN_AGENT_PATH}`;
273
- console.log(` šŸŒ [AAMP DNS] Fetching Manifest: ${url} ...`);
274
- // In production, we need a short timeout to prevent hanging
275
- const controller = new AbortController();
276
- const timeoutId = setTimeout(() => controller.abort(), 1500); // 1.5s max for DNS check
277
- const response = await fetch(url, { signal: controller.signal });
278
- clearTimeout(timeoutId);
279
- if (!response.ok) {
280
- console.log(` āŒ [AAMP DNS] Fetch Failed: ${response.status}`);
281
- return false;
282
- }
283
- const manifest = await response.json();
284
- console.log(` šŸ“„ [AAMP DNS] Manifest received. Agent ID: ${manifest.agent_id}`);
285
- // CHECK 1: Does the manifest actually belong to the domain?
286
- if (manifest.agent_id !== domain) {
287
- console.log(` āŒ [AAMP DNS] Mismatch: Manifest ID ${manifest.agent_id} != Claimed ${domain}`);
288
- return false;
289
- }
290
- // CHECK 2: Does the key match?
291
- if (manifest.public_key !== requestKeySpki) {
292
- console.log(` āŒ [AAMP DNS] Key Mismatch: DNS Key != Request Key`);
293
- return false;
294
- }
295
- console.log(` āœ… [AAMP DNS] Identity Confirmed.`);
296
- return true;
297
- }
298
- catch (e) {
299
- console.log(` āŒ [AAMP DNS] Error: ${e.message}`);
300
- return false;
301
- }
302
- }
303
- isDomain(s) {
304
- // Basic regex, allows localhost with ports
305
- return /^[a-zA-Z0-9.-]+(:\d+)?$/.test(s) || /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(s);
306
- }
307
- async generateResponseHeaders(origin) {
308
- if (!this.keyPair)
309
- throw new Error("Publisher keys not initialized");
310
- const payload = JSON.stringify({ origin, ts: Date.now() });
311
- const signature = await signData(this.keyPair.privateKey, payload);
312
- return {
313
- [HEADERS.CONTENT_ORIGIN]: origin,
314
- [HEADERS.PROVENANCE_SIG]: signature
315
- };
316
- }
317
- /**
318
- * Handling Quality Feedback (The "Dispute" Layer)
319
- * This runs when an Agent sends 'x-aamp-feedback'.
320
- */
321
- async handleFeedback(token, headers) {
322
- // NOTE: In production, you would fetch the Agent's specific key.
323
- // For now, we assume standard Discovery or a centralized Key Set (like adNetwork).
324
- // Ideally, the SDK config should have a 'qualityOracle' key set.
325
- // 1. We just Decode it to Log it (Verification is optional but recommended)
326
- try {
327
- const parts = token.split('.');
328
- const payload = JSON.parse(atob(parts[1]));
329
- console.log(`\nšŸ“¢ [AAMP QUALITY ALERT] Feedback Received from ${payload.agent_id}`);
330
- console.log(` Reason: ${payload.reason} | Score: ${payload.quality_score}`);
331
- console.log(` Resource: ${payload.url}`);
332
- console.log(` (Signature available for dispute evidence)`);
333
- }
334
- catch (e) {
335
- console.log(` āš ļø [AAMP Warning] Malformed Feedback Token.`);
336
- }
337
- }
338
- }