@aamp/protocol 1.1.5 → 1.1.7

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/publisher.js CHANGED
@@ -1,13 +1,11 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AAMPPublisher = void 0;
4
1
  /**
5
2
  * Layer 2: Publisher Middleware
6
3
  * Used by content owners to enforce policy, log access, and filter bots.
7
4
  */
8
- const constants_1 = require("./constants");
9
- const crypto_1 = require("./crypto");
10
- const types_1 = require("./types");
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';
11
9
  /**
12
10
  * Default In-Memory Cache (Fallback only)
13
11
  * NOT recommended for high-traffic Serverless production.
@@ -33,7 +31,7 @@ class MemoryCache {
33
31
  });
34
32
  }
35
33
  }
36
- class AAMPPublisher {
34
+ export class AAMPPublisher {
37
35
  constructor(policy, strategy = 'PASSIVE', cacheImpl) {
38
36
  this.keyPair = null;
39
37
  // Default TTL: 1 Hour
@@ -50,50 +48,46 @@ class AAMPPublisher {
50
48
  }
51
49
  /**
52
50
  * Main Entry Point: Evaluate ANY visitor (Human, Bot, or Agent)
51
+ * STAGE 1: IDENTITY (Strict)
52
+ * STAGE 2: POLICY (Permissions)
53
+ * STAGE 3: ACCESS (HQ Content)
53
54
  */
54
55
  async evaluateVisitor(reqHeaders, rawPayload) {
55
- // 1. Check for AAMP Headers
56
- const hasAamp = reqHeaders[constants_1.HEADERS.PAYLOAD] && reqHeaders[constants_1.HEADERS.SIGNATURE] && reqHeaders[constants_1.HEADERS.PUBLIC_KEY];
56
+ console.log(`\n--- [AAMP LOG START] New Request ---`);
57
+ // --- STAGE 1: IDENTITY VERIFICATION ---
58
+ console.log(`[IDENTITY] šŸ” Checking Identity Headers...`);
59
+ const hasAamp = reqHeaders[HEADERS.PAYLOAD] && reqHeaders[HEADERS.SIGNATURE] && reqHeaders[HEADERS.PUBLIC_KEY];
57
60
  if (hasAamp) {
58
- console.log("\nšŸ”Ž [AAMP Middleware] Detected Agent Headers. Starting Verification...");
59
- // It claims to be an Agent. Verify it.
60
- return await this.handleAgent(reqHeaders, rawPayload);
61
+ // It claims to be an Agent. Verify it STRICTLY.
62
+ return await this.handleAgentStrict(reqHeaders, rawPayload);
61
63
  }
62
- // 2. It's not an AAMP Agent. Apply Strategy.
64
+ // If NO AAMP Headers -> FAIL IDENTITY immediately.
65
+ console.log(`[IDENTITY] āŒ FAILED. No AAMP Headers found.`);
66
+ // For now, retaining the legacy "Passive/Hybrid" switch just to avoid breaking browser demos completely
67
+ // BUT logging it as a specific "Identity Fail" flow.
63
68
  if (this.unauthenticatedStrategy === 'STRICT') {
69
+ console.log(`[IDENTITY] ā›” BLOCKING. Strategy is STRICT.`);
64
70
  return {
65
71
  allowed: false,
66
72
  status: 401,
67
- reason: "STRICT_MODE: Only AAMP verified agents allowed.",
73
+ reason: "IDENTITY_REQUIRED: Missing AAMP Headers.",
68
74
  visitorType: 'UNIDENTIFIED_BOT'
69
75
  };
70
76
  }
71
- if (this.unauthenticatedStrategy === 'PASSIVE') {
72
- return {
73
- allowed: true,
74
- status: 200,
75
- reason: "PASSIVE_MODE: Allowed without verification.",
76
- visitorType: 'LIKELY_HUMAN'
77
- };
78
- }
79
- // 3. HYBRID MODE: Heuristic Analysis (The "Lazy Bot" Filter)
77
+ console.log(`[IDENTITY] āš ļø SKIPPED (Legacy Mode). Checking Browser Heuristics...`);
80
78
  const isHuman = this.performBrowserHeuristics(reqHeaders);
81
79
  if (isHuman) {
82
- return {
83
- allowed: true,
84
- status: 200,
85
- reason: "BROWSER_VERIFIED: Heuristics passed.",
86
- visitorType: 'LIKELY_HUMAN'
87
- };
88
- }
89
- else {
90
- return {
91
- allowed: false,
92
- status: 403,
93
- reason: "BOT_DETECTED: Request lacks browser signatures and AAMP headers.",
94
- visitorType: 'UNIDENTIFIED_BOT'
95
- };
80
+ console.log(`[POLICY] šŸ‘¤ ALLOWED. Browser Heuristics Passed.`);
81
+ return { allowed: true, status: 200, reason: "BROWSER_VERIFIED", visitorType: 'LIKELY_HUMAN' };
96
82
  }
83
+ console.log(`[IDENTITY] āŒ FAILED. Not a Browser, No Headers.`);
84
+ console.log(`[ACCESS] ā›” BLOCKED.`);
85
+ return {
86
+ allowed: false,
87
+ status: 403,
88
+ reason: "IDENTITY_FAIL: No Identity, No Browser.",
89
+ visitorType: 'UNIDENTIFIED_BOT'
90
+ };
97
91
  }
98
92
  /**
99
93
  * Browser Heuristics (Hardened)
@@ -125,66 +119,149 @@ class AAMPPublisher {
125
119
  return false;
126
120
  }
127
121
  /**
128
- * Handle AAMP Protocol Logic
122
+ * Handle AAMP Protocol Logic (Strict Mode)
129
123
  */
130
- async handleAgent(reqHeaders, rawPayload) {
124
+ async handleAgentStrict(reqHeaders, rawPayload) {
125
+ let agentId = "UNKNOWN";
131
126
  try {
132
- const payloadHeader = reqHeaders[constants_1.HEADERS.PAYLOAD];
133
- const sigHeader = reqHeaders[constants_1.HEADERS.SIGNATURE];
134
- const keyHeader = reqHeaders[constants_1.HEADERS.PUBLIC_KEY];
127
+ // 1. Decode Headers
128
+ const payloadHeader = reqHeaders[HEADERS.PAYLOAD];
129
+ const sigHeader = reqHeaders[HEADERS.SIGNATURE];
130
+ const keyHeader = reqHeaders[HEADERS.PUBLIC_KEY];
135
131
  const headerJson = atob(payloadHeader);
136
132
  const requestHeader = JSON.parse(headerJson);
133
+ agentId = requestHeader.agent_id;
134
+ console.log(`[IDENTITY] šŸ†” Claimed ID: ${agentId}`);
135
+ // 2. Crypto & DNS Verification
137
136
  const signedRequest = {
138
137
  header: requestHeader,
139
138
  signature: sigHeader,
140
139
  publicKey: keyHeader
141
140
  };
142
141
  const agentKey = await crypto.subtle.importKey("spki", new Uint8Array(atob(keyHeader).split('').map(c => c.charCodeAt(0))), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
143
- // Verify Core Logic
144
- const result = await this.verifyRequestLogic(signedRequest, agentKey, headerJson);
145
- if (!result.allowed) {
146
- console.log(`ā›” [AAMP Deny] Reason: ${result.reason}`);
147
- return {
148
- allowed: false,
149
- status: 403,
150
- reason: result.reason,
151
- visitorType: 'VERIFIED_AGENT'
152
- };
142
+ // Verify Core Logic (DNS + Crypto)
143
+ const verification = await this.verifyRequestLogic(signedRequest, agentKey);
144
+ if (!verification.identityVerified) {
145
+ console.log(`[IDENTITY] āŒ FAILED. Reason: ${verification.reason}`);
146
+ console.log(`[ACCESS] ā›” BLOCKED.`);
147
+ return { allowed: false, status: 403, reason: verification.reason, visitorType: 'UNIDENTIFIED_BOT' };
148
+ }
149
+ console.log(`[IDENTITY] āœ… PASSED. DNS Binding Verified.`);
150
+ // --- STAGE 2: POLICY ENFORCEMENT ---
151
+ console.log(`[POLICY] šŸ“œ Checking Permissions for ${agentId}...`);
152
+ const proofToken = reqHeaders[HEADERS.PROOF_TOKEN];
153
+ const paymentCredential = reqHeaders[HEADERS.PAYMENT_CREDENTIAL];
154
+ const policyResult = await this.checkPolicyStrict(requestHeader, proofToken, paymentCredential);
155
+ if (!policyResult.allowed) {
156
+ console.log(`[POLICY] ā›” DENIED. Reason: ${policyResult.reason}`);
157
+ console.log(`[ACCESS] ā›” BLOCKED.`);
158
+ return policyResult;
153
159
  }
154
- console.log(`āœ… [AAMP Allow] Verified Agent: ${requestHeader.agent_id}`);
160
+ // --- STAGE 3: ACCESS GRANT ---
161
+ console.log(`[POLICY] āœ… PASSED. Requirements Met.`);
162
+ console.log(`[ACCESS] šŸ”“ GRANTED. Unlocking HQ Content.`);
155
163
  return {
156
164
  allowed: true,
157
165
  status: 200,
158
166
  reason: "AAMP_VERIFIED",
159
167
  visitorType: 'VERIFIED_AGENT',
160
- metadata: requestHeader
168
+ metadata: requestHeader,
169
+ proofUsed: policyResult.proofUsed
161
170
  };
162
171
  }
163
172
  catch (e) {
164
- console.error(e);
173
+ console.error(`[AAMP ERROR]`, e);
165
174
  return { allowed: false, status: 400, reason: "INVALID_SIGNATURE", visitorType: 'UNIDENTIFIED_BOT' };
166
175
  }
167
176
  }
168
- async verifyRequestLogic(request, requestPublicKey, rawPayload) {
177
+ // Legacy handler kept for interface compatibility (deprecated)
178
+ async handleAgent(reqHeaders, rawPayload) {
179
+ return this.handleAgentStrict(reqHeaders, rawPayload);
180
+ }
181
+ /**
182
+ * STAGE 2: POLICY ENFORCEMENT CHECK
183
+ */
184
+ async checkPolicyStrict(requestHeader, proofToken, paymentCredential) {
185
+ // 1. Policy Check: Purpose Ban (e.g. No Training)
186
+ if (requestHeader.purpose === AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
187
+ return { allowed: false, status: 403, reason: 'POLICY_DENIED: Training not allowed.', visitorType: 'VERIFIED_AGENT' };
188
+ }
189
+ if (requestHeader.purpose === AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
190
+ return { allowed: false, status: 403, reason: 'POLICY_DENIED: RAG not allowed.', visitorType: 'VERIFIED_AGENT' };
191
+ }
192
+ // 2. Policy Check: Economics (v1.2) - Payment & Ads
193
+ if (this.policy.requiresPayment) {
194
+ let paymentSatisfied = false;
195
+ // Method A: Flexible Payment Callback (DB / Custom Logic)
196
+ if (this.policy.monetization?.checkPayment) {
197
+ const isPaid = await this.policy.monetization.checkPayment(requestHeader.agent_id, requestHeader.purpose);
198
+ if (isPaid) {
199
+ console.log(`[POLICY] šŸ’° Payment Verified via Callback.`);
200
+ return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'WHITELIST_CALLBACK' };
201
+ }
202
+ }
203
+ // Method B: Payment Credentials (Unified JWT)
204
+ if (!paymentSatisfied && this.policy.monetization?.paymentConfig && paymentCredential) {
205
+ const { jwksUrl, issuer } = this.policy.monetization.paymentConfig;
206
+ console.log(`[POLICY] šŸ” Verifying Payment Credential (Issuer: ${issuer})...`);
207
+ const isValidCredential = await verifyJwt(paymentCredential, jwksUrl, issuer);
208
+ if (isValidCredential) {
209
+ console.log(`[POLICY] āœ… Credential Signature VALID.`);
210
+ return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'PAYMENT_CREDENTIAL_JWT' };
211
+ }
212
+ else {
213
+ console.log(`[POLICY] āŒ Credential Signature INVALID.`);
214
+ }
215
+ }
216
+ // Method C: Ad-Supported (Proof Verification)
217
+ if (!paymentSatisfied && this.policy.allowAdSupportedAccess && requestHeader.context?.ads_displayed) {
218
+ if (proofToken && this.policy.monetization?.adNetwork) {
219
+ const { jwksUrl, issuer } = this.policy.monetization.adNetwork;
220
+ console.log(`[POLICY] šŸ“ŗ Verifying Ad Proof (Issuer: ${issuer})...`);
221
+ const isValidProof = await verifyJwt(proofToken, jwksUrl, issuer);
222
+ if (isValidProof) {
223
+ console.log(`[POLICY] āœ… Ad Proof Signature VALID.`);
224
+ return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT', proofUsed: 'AD_PROOF_JWT' };
225
+ }
226
+ else {
227
+ console.log(`[POLICY] āŒ Ad Proof Signature INVALID.`);
228
+ }
229
+ }
230
+ else {
231
+ console.log(`[POLICY] āš ļø Ad Proof MISSING.`);
232
+ }
233
+ }
234
+ return {
235
+ allowed: false,
236
+ status: 402,
237
+ reason: 'PAYMENT_REQUIRED: Whitelist, Credential, and Ad Proof checks ALL failed.',
238
+ visitorType: 'VERIFIED_AGENT',
239
+ proofUsed: 'NONE'
240
+ };
241
+ }
242
+ // If no payment required, allow.
243
+ return { allowed: true, status: 200, reason: 'OK', visitorType: 'VERIFIED_AGENT' };
244
+ }
245
+ async verifyRequestLogic(request, requestPublicKey) {
169
246
  // 1. Replay Attack Prevention
170
247
  const requestTime = new Date(request.header.ts).getTime();
171
- if (Math.abs(Date.now() - requestTime) > constants_1.MAX_CLOCK_SKEW_MS) {
172
- return { allowed: false, reason: 'TIMESTAMP_INVALID', identityVerified: false };
248
+ if (Math.abs(Date.now() - requestTime) > MAX_CLOCK_SKEW_MS) {
249
+ return { allowed: false, reason: 'TIMESTAMP_INVALID: Clock skew too large.', identityVerified: false };
173
250
  }
174
251
  // 2. Crypto Verification
175
- const signableString = rawPayload || JSON.stringify(request.header);
176
- const isCryptoValid = await (0, crypto_1.verifySignature)(requestPublicKey, signableString, request.signature);
252
+ const signableString = JSON.stringify(request.header);
253
+ const isCryptoValid = await verifySignature(requestPublicKey, signableString, request.signature);
177
254
  if (!isCryptoValid)
178
- return { allowed: false, reason: 'CRYPTO_FAIL', identityVerified: false };
255
+ return { allowed: false, reason: 'CRYPTO_FAIL: Signature invalid.', identityVerified: false };
179
256
  // 3. Identity Verification (DNS Binding) with Cache
180
257
  let identityVerified = false;
181
258
  const claimedDomain = request.header.agent_id;
182
- const pubKeyString = await (0, crypto_1.exportPublicKey)(requestPublicKey);
183
- console.log(` šŸ†” [AAMP Identity] Verifying DNS Binding for: ${claimedDomain}`);
259
+ const pubKeyString = await exportPublicKey(requestPublicKey);
260
+ console.log(`[IDENTITY] šŸ” Verifying DNS Binding for: ${claimedDomain}`);
184
261
  // Check Cache First
185
262
  const cachedKey = await this.cache.get(claimedDomain);
186
263
  if (cachedKey === pubKeyString) {
187
- console.log(" ⚔ [AAMP Cache] Identity found in cache.");
264
+ console.log("[IDENTITY] ⚔ Cache Hit. Identity Verified.");
188
265
  identityVerified = true;
189
266
  }
190
267
  else if (this.isDomain(claimedDomain)) {
@@ -197,27 +274,14 @@ class AAMPPublisher {
197
274
  if (this.policy.requireIdentityBinding && !identityVerified) {
198
275
  return { allowed: false, reason: 'IDENTITY_FAIL: DNS Binding could not be verified.', identityVerified: false };
199
276
  }
200
- // 4. Policy Check: Purpose
201
- if (request.header.purpose === types_1.AccessPurpose.CRAWL_TRAINING && !this.policy.allowTraining) {
202
- return { allowed: false, reason: 'POLICY_DENIED: Training not allowed.', identityVerified };
203
- }
204
- if (request.header.purpose === types_1.AccessPurpose.RAG_RETRIEVAL && !this.policy.allowRAG) {
205
- return { allowed: false, reason: 'POLICY_DENIED: RAG not allowed.', identityVerified };
206
- }
207
- // 5. Policy Check: Economics
208
- if (this.policy.requiresPayment) {
209
- const isAdExempt = this.policy.allowAdSupportedAccess && request.header.context.ads_displayed;
210
- if (!isAdExempt) {
211
- return { allowed: false, reason: 'PAYMENT_REQUIRED: Content requires payment or ads.', identityVerified };
212
- }
213
- }
214
- return { allowed: true, reason: 'OK', identityVerified };
277
+ // Return verified status so handleAgentStrict can proceed to Policy Check
278
+ return { allowed: true, reason: 'OK', identityVerified: true };
215
279
  }
216
280
  async verifyDnsBinding(domain, requestKeySpki) {
217
281
  try {
218
282
  // Allow HTTP for localhost testing
219
283
  const protocol = (domain.includes('localhost') || domain.match(/:\d+$/)) ? 'http' : 'https';
220
- const url = `${protocol}://${domain}${constants_1.WELL_KNOWN_AGENT_PATH}`;
284
+ const url = `${protocol}://${domain}${WELL_KNOWN_AGENT_PATH}`;
221
285
  console.log(` šŸŒ [AAMP DNS] Fetching Manifest: ${url} ...`);
222
286
  // In production, we need a short timeout to prevent hanging
223
287
  const controller = new AbortController();
@@ -256,11 +320,31 @@ class AAMPPublisher {
256
320
  if (!this.keyPair)
257
321
  throw new Error("Publisher keys not initialized");
258
322
  const payload = JSON.stringify({ origin, ts: Date.now() });
259
- const signature = await (0, crypto_1.signData)(this.keyPair.privateKey, payload);
323
+ const signature = await signData(this.keyPair.privateKey, payload);
260
324
  return {
261
- [constants_1.HEADERS.CONTENT_ORIGIN]: origin,
262
- [constants_1.HEADERS.PROVENANCE_SIG]: signature
325
+ [HEADERS.CONTENT_ORIGIN]: origin,
326
+ [HEADERS.PROVENANCE_SIG]: signature
263
327
  };
264
328
  }
329
+ /**
330
+ * Handling Quality Feedback (The "Dispute" Layer)
331
+ * This runs when an Agent sends 'x-aamp-feedback'.
332
+ */
333
+ async handleFeedback(token, headers) {
334
+ // NOTE: In production, you would fetch the Agent's specific key.
335
+ // For now, we assume standard Discovery or a centralized Key Set (like adNetwork).
336
+ // Ideally, the SDK config should have a 'qualityOracle' key set.
337
+ // 1. We just Decode it to Log it (Verification is optional but recommended)
338
+ try {
339
+ const parts = token.split('.');
340
+ const payload = JSON.parse(atob(parts[1]));
341
+ console.log(`\nšŸ“¢ [AAMP QUALITY ALERT] Feedback Received from ${payload.agent_id}`);
342
+ console.log(` Reason: ${payload.reason} | Score: ${payload.quality_score}`);
343
+ console.log(` Resource: ${payload.url}`);
344
+ console.log(` (Signature available for dispute evidence)`);
345
+ }
346
+ catch (e) {
347
+ console.log(` āš ļø [AAMP Warning] Malformed Feedback Token.`);
348
+ }
349
+ }
265
350
  }
266
- exports.AAMPPublisher = AAMPPublisher;
package/dist/types.d.ts CHANGED
@@ -37,12 +37,22 @@ export interface IdentityCache {
37
37
  get(key: string): Promise<string | null>;
38
38
  set(key: string, value: string, ttlSeconds: number): Promise<void>;
39
39
  }
40
+ /**
41
+ * Optional Monetization (The Settlement Layer)
42
+ */
40
43
  /**
41
44
  * Optional Monetization (The Settlement Layer)
42
45
  */
43
46
  export interface MonetizationConfig {
44
- method: 'BROKER' | 'CRYPTO' | 'PRIVATE_TREATY';
45
- location: string;
47
+ checkPayment?: (agentId: string, purpose: string) => boolean | Promise<boolean>;
48
+ adNetwork?: {
49
+ jwksUrl: string;
50
+ issuer: string;
51
+ };
52
+ paymentConfig?: {
53
+ jwksUrl: string;
54
+ issuer: string;
55
+ };
46
56
  }
47
57
  /**
48
58
  * Handling Non-AAMP Visitors
@@ -87,8 +97,17 @@ export interface FeedbackSignal {
87
97
  }
88
98
  export interface EvaluationResult {
89
99
  allowed: boolean;
90
- status: 200 | 400 | 401 | 403;
100
+ status: 200 | 400 | 401 | 402 | 403;
91
101
  reason: string;
92
102
  visitorType: 'VERIFIED_AGENT' | 'LIKELY_HUMAN' | 'UNIDENTIFIED_BOT';
93
103
  metadata?: any;
104
+ payment_status?: 'PAID_SUBSCRIBER' | 'AD_FUNDED' | 'UNPAID';
105
+ proofUsed?: string;
106
+ }
107
+ export interface FeedbackSignalToken {
108
+ url: string;
109
+ agent_id: string;
110
+ quality_score: number;
111
+ reason: string;
112
+ timestamp: number;
94
113
  }
package/dist/types.js CHANGED
@@ -1,28 +1,25 @@
1
- "use strict";
2
1
  /**
3
2
  * Layer 1: Protocol Definitions
4
3
  * Shared types used by both Agent and Publisher.
5
4
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.QualityFlag = exports.ContentOrigin = exports.AccessPurpose = void 0;
8
- var AccessPurpose;
5
+ export var AccessPurpose;
9
6
  (function (AccessPurpose) {
10
7
  AccessPurpose["CRAWL_TRAINING"] = "CRAWL_TRAINING";
11
8
  AccessPurpose["RAG_RETRIEVAL"] = "RAG_RETRIEVAL";
12
9
  AccessPurpose["SUMMARY"] = "SUMMARY";
13
10
  AccessPurpose["QUOTATION"] = "QUOTATION";
14
11
  AccessPurpose["EMBEDDING"] = "EMBEDDING";
15
- })(AccessPurpose || (exports.AccessPurpose = AccessPurpose = {}));
16
- var ContentOrigin;
12
+ })(AccessPurpose || (AccessPurpose = {}));
13
+ export var ContentOrigin;
17
14
  (function (ContentOrigin) {
18
15
  ContentOrigin["HUMAN"] = "HUMAN";
19
16
  ContentOrigin["SYNTHETIC"] = "SYNTHETIC";
20
17
  ContentOrigin["HYBRID"] = "HYBRID"; // Edited by humans, drafted by AI.
21
- })(ContentOrigin || (exports.ContentOrigin = ContentOrigin = {}));
22
- var QualityFlag;
18
+ })(ContentOrigin || (ContentOrigin = {}));
19
+ export var QualityFlag;
23
20
  (function (QualityFlag) {
24
21
  QualityFlag["SEO_SPAM"] = "SEO_SPAM";
25
22
  QualityFlag["INACCURATE"] = "INACCURATE";
26
23
  QualityFlag["HATE_SPEECH"] = "HATE_SPEECH";
27
24
  QualityFlag["HIGH_QUALITY"] = "HIGH_QUALITY";
28
- })(QualityFlag || (exports.QualityFlag = QualityFlag = {}));
25
+ })(QualityFlag || (QualityFlag = {}));
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@aamp/protocol",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "TypeScript reference implementation of AAMP v1.1",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "type": "module",
7
8
  "scripts": {
8
9
  "build": "tsc",
9
- "test": "ts-node test/handshake.spec.ts",
10
- "prepublishOnly": "npm run test && npm run build"
10
+ "test": "node --loader ts-node/esm test/handshake.spec.ts"
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
@@ -17,8 +17,13 @@
17
17
  "access": "public"
18
18
  },
19
19
  "devDependencies": {
20
- "typescript": "^5.0.0",
20
+ "@types/node": "^20.0.0",
21
+ "@types/node-fetch": "^2.6.13",
21
22
  "ts-node": "^10.9.0",
22
- "@types/node": "^20.0.0"
23
+ "typescript": "^5.0.0"
24
+ },
25
+ "dependencies": {
26
+ "jose": "^6.1.3",
27
+ "node-fetch": "^2.7.0"
23
28
  }
24
29
  }
package/src/agent.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Layer 2: Agent SDK
3
3
  */
4
- import { AccessPurpose, ProtocolHeader, SignedAccessRequest, FeedbackSignal, QualityFlag, AgentIdentityManifest } from './types';
5
- import { generateKeyPair, signData, exportPublicKey } from './crypto';
6
- import { AAMP_VERSION } from './constants';
4
+ import { AccessPurpose, ProtocolHeader, SignedAccessRequest, FeedbackSignal, QualityFlag, AgentIdentityManifest } from './types.js';
5
+ import { generateKeyPair, signData, exportPublicKey } from './crypto.js';
6
+ import { AAMP_VERSION } from './constants.js';
7
7
 
8
8
  export interface AccessOptions {
9
9
  adsDisplayed?: boolean;
@@ -24,8 +24,8 @@ export class AAMPAgent {
24
24
  }
25
25
 
26
26
  async createAccessRequest(
27
- resource: string,
28
- purpose: AccessPurpose,
27
+ resource: string,
28
+ purpose: AccessPurpose,
29
29
  options: AccessOptions = {}
30
30
  ): Promise<SignedAccessRequest> {
31
31
  if (!this.keyPair) throw new Error("Agent not initialized. Call initialize() first.");
@@ -53,9 +53,9 @@ export class AAMPAgent {
53
53
  */
54
54
  async getIdentityManifest(contactEmail?: string): Promise<AgentIdentityManifest> {
55
55
  if (!this.keyPair) throw new Error("Agent not initialized.");
56
-
56
+
57
57
  const publicKey = await exportPublicKey(this.keyPair.publicKey);
58
-
58
+
59
59
  return {
60
60
  agent_id: this.agentId,
61
61
  public_key: publicKey,
@@ -73,7 +73,7 @@ export class AAMPAgent {
73
73
  flags: QualityFlag[]
74
74
  ): Promise<{ signal: FeedbackSignal, signature: string }> {
75
75
  if (!this.keyPair) throw new Error("Agent not initialized.");
76
-
76
+
77
77
  const signal: FeedbackSignal = {
78
78
  target_resource: resource,
79
79
  agent_id: this.agentId,
package/src/constants.ts CHANGED
@@ -17,15 +17,24 @@ export const HEADERS = {
17
17
  SIGNATURE: 'x-aamp-signature',
18
18
  // Transport: The Agent's Public Key (Base64 SPKI)
19
19
  PUBLIC_KEY: 'x-aamp-public-key',
20
-
20
+
21
21
  // Informational / Legacy (Optional if Payload is present)
22
22
  AGENT_ID: 'x-aamp-agent-id',
23
23
  TIMESTAMP: 'x-aamp-timestamp',
24
- ALGORITHM: 'x-aamp-alg',
25
-
24
+ ALGORITHM: 'x-aamp-alg',
25
+
26
26
  // v1.1 Addition: Provenance (Server to Agent)
27
27
  CONTENT_ORIGIN: 'x-aamp-content-origin',
28
- PROVENANCE_SIG: 'x-aamp-provenance-sig'
28
+ PROVENANCE_SIG: 'x-aamp-provenance-sig',
29
+
30
+ // v1.2 Proof of Value
31
+ PROOF_TOKEN: 'x-aamp-proof',
32
+
33
+ // v1.2 Payment Credential (The "Digital Receipt")
34
+ PAYMENT_CREDENTIAL: 'x-aamp-credential',
35
+
36
+ // v1.2 Quality Feedback (The "Dispute Token")
37
+ FEEDBACK: 'x-aamp-feedback'
29
38
  } as const;
30
39
 
31
40
  // Cryptographic Settings
package/src/express.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  * Layer 3: Framework Adapters
3
3
  * Zero-friction integration for Express/Node.js.
4
4
  */
5
- import { AAMPPublisher } from './publisher';
6
- import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types';
7
- import { generateKeyPair } from './crypto';
5
+ import { AAMPPublisher } from './publisher.js';
6
+ import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types.js';
7
+ import { generateKeyPair } from './crypto.js';
8
8
 
9
9
  export interface AAMPConfig {
10
10
  policy: Omit<AccessPolicy, 'version'>;
@@ -28,9 +28,9 @@ export class AAMP {
28
28
  config.strategy || 'PASSIVE',
29
29
  config.cache
30
30
  );
31
-
31
+
32
32
  this.origin = ContentOrigin[config.meta.origin];
33
-
33
+
34
34
  this.ready = generateKeyPair().then(keys => {
35
35
  return this.publisher.initialize(keys);
36
36
  });
@@ -62,9 +62,10 @@ export class AAMP {
62
62
 
63
63
  // Enforce Decision
64
64
  if (!result.allowed) {
65
- res.status(result.status).json({
65
+ res.status(result.status).json({
66
66
  error: result.reason,
67
- visitor_type: result.visitorType
67
+ visitor_type: result.visitorType,
68
+ proof_used: result.proofUsed
68
69
  });
69
70
  return;
70
71
  }
package/src/index.ts CHANGED
@@ -4,10 +4,10 @@
4
4
  * This is the main entry point for the library.
5
5
  */
6
6
 
7
- export * from './types';
8
- export * from './constants';
9
- export * from './agent';
10
- export * from './publisher';
11
- export * from './crypto';
12
- export * from './express'; // Node.js / Express Adapter
13
- export { AAMPNext } from './nextjs'; // Serverless / Next.js Adapter
7
+ export * from './types.js';
8
+ export * from './constants.js';
9
+ export * from './agent.js';
10
+ export * from './publisher.js';
11
+ export * from './crypto.js';
12
+ export * from './express.js'; // Node.js / Express Adapter
13
+ export { AAMPNext } from './nextjs.js'; // Serverless / Next.js Adapter
package/src/nextjs.ts CHANGED
@@ -2,12 +2,12 @@
2
2
  * Layer 3: Framework Adapters
3
3
  * Serverless integration for Next.js (App Router & API Routes).
4
4
  */
5
- import { AAMPPublisher } from './publisher';
6
- import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types';
7
- import { generateKeyPair } from './crypto';
5
+ import { AAMPPublisher } from './publisher.js';
6
+ import { AccessPolicy, ContentOrigin, UnauthenticatedStrategy, IdentityCache } from './types.js';
7
+ import { generateKeyPair } from './crypto.js';
8
8
 
9
- type NextRequest = any;
10
- type NextResponse = any;
9
+ type NextRequest = any;
10
+ type NextResponse = any;
11
11
 
12
12
  const createJsonResponse = (body: any, status = 200) => {
13
13
  return new Response(JSON.stringify(body), {
@@ -63,9 +63,10 @@ export class AAMPNext {
63
63
  const result = await this.publisher.evaluateVisitor(headers, headers['x-aamp-payload']);
64
64
 
65
65
  if (!result.allowed) {
66
- return createJsonResponse({
66
+ return createJsonResponse({
67
67
  error: result.reason,
68
- visitor_type: result.visitorType
68
+ visitor_type: result.visitorType,
69
+ proof_used: result.proofUsed
69
70
  }, result.status);
70
71
  }
71
72
 
@@ -75,9 +76,9 @@ export class AAMPNext {
75
76
  // Inject Provenance
76
77
  const aampHeaders = await this.publisher.generateResponseHeaders(this.origin);
77
78
  if (response && response.headers) {
78
- Object.entries(aampHeaders).forEach(([k, v]) => {
79
- response.headers.set(k, v);
80
- });
79
+ Object.entries(aampHeaders).forEach(([k, v]) => {
80
+ response.headers.set(k, v);
81
+ });
81
82
  }
82
83
 
83
84
  return response;