@adcp/sdk 16.0.0-beta.6 → 16.0.0-beta.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.
Files changed (137) hide show
  1. package/README.md +17 -0
  2. package/dist/lib/conformance/index.d.mts +2 -0
  3. package/dist/lib/conformance/index.d.ts +2 -0
  4. package/dist/lib/conformance/index.js +3 -0
  5. package/dist/lib/conformance/index.mjs +2 -0
  6. package/dist/lib/conformance/property-lists.d.mts +14 -0
  7. package/dist/lib/conformance/property-lists.d.ts +14 -0
  8. package/dist/lib/conformance/property-lists.js +45 -0
  9. package/dist/lib/conformance/property-lists.mjs +21 -0
  10. package/dist/lib/conformance/runConformance.js +20 -1
  11. package/dist/lib/conformance/runConformance.mjs +20 -1
  12. package/dist/lib/conformance/types.d.mts +3 -0
  13. package/dist/lib/conformance/types.d.ts +3 -0
  14. package/dist/lib/core/SingleAgentClient.d.mts +13 -3
  15. package/dist/lib/core/SingleAgentClient.d.ts +13 -3
  16. package/dist/lib/core/SingleAgentClient.js +109 -21
  17. package/dist/lib/core/SingleAgentClient.mjs +112 -22
  18. package/dist/lib/core/TaskExecutor.d.mts +1 -0
  19. package/dist/lib/core/TaskExecutor.d.ts +1 -0
  20. package/dist/lib/core/TaskExecutor.js +48 -34
  21. package/dist/lib/core/TaskExecutor.mjs +49 -35
  22. package/dist/lib/identifiers.d.mts +53 -0
  23. package/dist/lib/identifiers.d.ts +53 -0
  24. package/dist/lib/identifiers.js +212 -0
  25. package/dist/lib/identifiers.mjs +163 -0
  26. package/dist/lib/identity-3.3/revocation.js +5 -4
  27. package/dist/lib/identity-3.3/revocation.mjs +5 -4
  28. package/dist/lib/index.d.mts +3 -1
  29. package/dist/lib/index.d.ts +3 -1
  30. package/dist/lib/index.js +2 -0
  31. package/dist/lib/index.mjs +1 -0
  32. package/dist/lib/media-buy/index.d.mts +1 -1
  33. package/dist/lib/media-buy/index.d.ts +1 -1
  34. package/dist/lib/media-buy/property-policy.d.mts +15 -1
  35. package/dist/lib/media-buy/property-policy.d.ts +15 -1
  36. package/dist/lib/media-buy/property-policy.js +135 -3
  37. package/dist/lib/media-buy/property-policy.mjs +134 -3
  38. package/dist/lib/registry/index.d.mts +12 -0
  39. package/dist/lib/registry/index.d.ts +12 -0
  40. package/dist/lib/registry/index.js +159 -5
  41. package/dist/lib/registry/index.mjs +156 -4
  42. package/dist/lib/registry/types.d.mts +23 -0
  43. package/dist/lib/registry/types.d.ts +23 -0
  44. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  45. package/dist/lib/server/auth-signature.js +23 -2
  46. package/dist/lib/server/auth-signature.mjs +26 -2
  47. package/dist/lib/server/auth.d.mts +6 -0
  48. package/dist/lib/server/auth.d.ts +6 -0
  49. package/dist/lib/server/auth.js +6 -0
  50. package/dist/lib/server/auth.mjs +6 -0
  51. package/dist/lib/server/create-adcp-server.js +19 -3
  52. package/dist/lib/server/create-adcp-server.mjs +23 -4
  53. package/dist/lib/server/decisioning/runtime/from-platform.js +6 -2
  54. package/dist/lib/server/decisioning/runtime/from-platform.mjs +6 -2
  55. package/dist/lib/server/decisioning/specialisms/lists.d.mts +4 -4
  56. package/dist/lib/server/decisioning/specialisms/lists.d.ts +4 -4
  57. package/dist/lib/server/index.d.mts +3 -0
  58. package/dist/lib/server/index.d.ts +3 -0
  59. package/dist/lib/server/index.js +8 -1
  60. package/dist/lib/server/index.mjs +4 -0
  61. package/dist/lib/server/property-lists.d.mts +36 -0
  62. package/dist/lib/server/property-lists.d.ts +36 -0
  63. package/dist/lib/server/property-lists.js +154 -0
  64. package/dist/lib/server/property-lists.mjs +129 -0
  65. package/dist/lib/server/serve.js +2 -1
  66. package/dist/lib/server/serve.mjs +2 -1
  67. package/dist/lib/server/targeting-helpers.d.mts +2 -0
  68. package/dist/lib/server/targeting-helpers.d.ts +2 -0
  69. package/dist/lib/server/targeting-helpers.js +28 -1
  70. package/dist/lib/server/targeting-helpers.mjs +18 -1
  71. package/dist/lib/server/webhook-emitter.js +1 -0
  72. package/dist/lib/server/webhook-emitter.mjs +2 -1
  73. package/dist/lib/signing/crypto.d.mts +1 -1
  74. package/dist/lib/signing/crypto.d.ts +1 -1
  75. package/dist/lib/signing/crypto.js +37 -2
  76. package/dist/lib/signing/crypto.mjs +37 -2
  77. package/dist/lib/signing/parser.d.mts +6 -0
  78. package/dist/lib/signing/parser.d.ts +6 -0
  79. package/dist/lib/signing/parser.js +7 -3
  80. package/dist/lib/signing/parser.mjs +6 -3
  81. package/dist/lib/signing/server.d.mts +1 -1
  82. package/dist/lib/signing/server.d.ts +1 -1
  83. package/dist/lib/signing/server.mjs +5 -1
  84. package/dist/lib/signing/web-bot-auth.d.mts +61 -22
  85. package/dist/lib/signing/web-bot-auth.d.ts +61 -22
  86. package/dist/lib/signing/web-bot-auth.js +286 -266
  87. package/dist/lib/signing/web-bot-auth.mjs +288 -277
  88. package/dist/lib/signing/webhook-verifier.d.mts +11 -0
  89. package/dist/lib/signing/webhook-verifier.d.ts +11 -0
  90. package/dist/lib/signing/webhook-verifier.js +11 -3
  91. package/dist/lib/signing/webhook-verifier.mjs +11 -3
  92. package/dist/lib/supply-path/verify.js +12 -2
  93. package/dist/lib/supply-path/verify.mjs +2 -2
  94. package/dist/lib/testing/client.js +3 -5
  95. package/dist/lib/testing/client.mjs +4 -6
  96. package/dist/lib/testing/compliance/comply.js +7 -2
  97. package/dist/lib/testing/compliance/comply.mjs +8 -3
  98. package/dist/lib/testing/index.d.mts +1 -1
  99. package/dist/lib/testing/index.d.ts +1 -1
  100. package/dist/lib/testing/index.js +4 -2
  101. package/dist/lib/testing/index.mjs +8 -2
  102. package/dist/lib/testing/storyboard/runner.js +12 -12
  103. package/dist/lib/testing/storyboard/runner.mjs +14 -13
  104. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  105. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  106. package/dist/lib/testing/storyboard/validations.js +23 -1
  107. package/dist/lib/testing/storyboard/validations.mjs +28 -2
  108. package/dist/lib/testing/types.d.mts +39 -0
  109. package/dist/lib/testing/types.d.ts +39 -0
  110. package/dist/lib/v2/projection/augment-response.js +6 -3
  111. package/dist/lib/v2/projection/augment-response.mjs +8 -5
  112. package/dist/lib/v2/projection/creative-delivery.js +29 -17
  113. package/dist/lib/v2/projection/creative-delivery.mjs +31 -18
  114. package/dist/lib/v2/projection/v1-to-v2.d.mts +7 -0
  115. package/dist/lib/v2/projection/v1-to-v2.d.ts +7 -0
  116. package/dist/lib/v2/projection/v1-to-v2.js +70 -2
  117. package/dist/lib/v2/projection/v1-to-v2.mjs +72 -1
  118. package/dist/lib/validation/index.d.mts +1 -1
  119. package/dist/lib/validation/index.d.ts +1 -1
  120. package/dist/lib/validation/index.js +4 -2
  121. package/dist/lib/validation/index.mjs +4 -2
  122. package/dist/lib/validation/schema-loader.d.mts +26 -0
  123. package/dist/lib/validation/schema-loader.d.ts +26 -0
  124. package/dist/lib/validation/schema-loader.js +98 -14
  125. package/dist/lib/validation/schema-loader.mjs +95 -13
  126. package/dist/lib/version.d.mts +3 -3
  127. package/dist/lib/version.d.ts +3 -3
  128. package/dist/lib/version.js +3 -3
  129. package/dist/lib/version.mjs +3 -3
  130. package/docs/TYPE-SUMMARY.md +2 -2
  131. package/docs/guides/PROPERTY-LISTS.md +264 -0
  132. package/docs/guides/SIGNING-GUIDE.md +25 -0
  133. package/docs/guides/VALIDATE-YOUR-AGENT.md +34 -0
  134. package/docs/llms.txt +2 -2
  135. package/docs/migration-14.x-rc-worksheet.md +4 -4
  136. package/docs/migration-agent-identity-3.3.md +60 -23
  137. package/package.json +1 -1
@@ -0,0 +1,129 @@
1
+ import { z } from "zod";
2
+ import { GetPropertyListRequestSchema, GetPropertyListResponseSchema } from "../types/schemas.generated.mjs";
3
+ import { stripCtxMetadata } from "./ctx-metadata/wire-shape.mjs";
4
+ function registerPropertyListTool(server, options) {
5
+ return server.registerTool(
6
+ "get_property_list",
7
+ {
8
+ description: "Retrieve an authorized property list and a page of resolved identifiers.",
9
+ inputSchema: GetPropertyListRequestSchema,
10
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
11
+ },
12
+ async (request, extra) => {
13
+ const denied = () => toolError("PERMISSION_DENIED", "Property list access denied.", request.context);
14
+ const headers = extra.requestInfo?.headers ?? {};
15
+ for (const name of ["authorization", "x-adcp-auth"]) {
16
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
17
+ if (values.length > 1 || values.length === 1 && typeof values[0][1] !== "string") return denied();
18
+ }
19
+ const readHeader = (name) => {
20
+ const values = Object.entries(headers).filter(([key]) => key.toLowerCase() === name);
21
+ if (values.length !== 1 || typeof values[0][1] !== "string") return void 0;
22
+ return values[0][1];
23
+ };
24
+ const authorization = readHeader("authorization");
25
+ const bearer = authorization?.match(/^Bearer ([^\s]+)$/i)?.[1];
26
+ const adcpToken = readHeader("x-adcp-auth");
27
+ const authenticatedToken = extra.authInfo?.token;
28
+ const tokens = [bearer, adcpToken, authenticatedToken].filter((token) => token !== void 0);
29
+ if (!tokens.length || tokens.some((token) => !token || token !== tokens[0]) || authorization !== void 0 && !bearer)
30
+ return denied();
31
+ let principal;
32
+ try {
33
+ principal = await options.verifyToken({ listId: request.list_id, token: tokens[0] });
34
+ } catch {
35
+ return denied();
36
+ }
37
+ if (principal === null || principal === false || principal === void 0) return denied();
38
+ try {
39
+ const page = await options.loadPage(request, principal);
40
+ if (page.list.list_id !== request.list_id) throw new Error("Invalid list identity");
41
+ if (request.resolve !== false && !Array.isArray(page.identifiers))
42
+ throw new Error("Missing resolved identifiers");
43
+ const limit = request.pagination?.max_results ?? 1e3;
44
+ if (request.resolve !== false && (page.identifiers?.length ?? 0) > limit) throw new Error("Invalid page size");
45
+ if (page.pagination?.has_more && !page.pagination.cursor) throw new Error("Missing continuation cursor");
46
+ if (!page.pagination?.has_more && page.pagination?.cursor) throw new Error("Unexpected continuation cursor");
47
+ const payload = GetPropertyListResponseSchema.parse({
48
+ list: Object.fromEntries(
49
+ [
50
+ "list_id",
51
+ "name",
52
+ "description",
53
+ "account",
54
+ "base_properties",
55
+ "filters",
56
+ "brand",
57
+ "webhook_url",
58
+ "cache_duration_hours",
59
+ "created_at",
60
+ "updated_at",
61
+ "property_count",
62
+ "pricing_options"
63
+ ].filter((key) => Object.hasOwn(page.list, key)).map((key) => [key, page.list[key]])
64
+ ),
65
+ ...page.identifiers !== void 0 ? { identifiers: page.identifiers.map((identifier) => ({ type: identifier.type, value: identifier.value })) } : {},
66
+ ...page.pagination !== void 0 ? {
67
+ pagination: {
68
+ has_more: page.pagination.has_more,
69
+ cursor: page.pagination.cursor,
70
+ total_count: page.pagination.total_count
71
+ }
72
+ } : {},
73
+ ...page.resolved_at !== void 0 ? { resolved_at: page.resolved_at } : {},
74
+ ...page.cache_valid_until !== void 0 ? { cache_valid_until: page.cache_valid_until } : {},
75
+ ...page.coverage_gaps !== void 0 ? { coverage_gaps: page.coverage_gaps } : {},
76
+ status: "completed",
77
+ ...request.resolve === false ? { identifiers: void 0, pagination: void 0 } : {},
78
+ ...request.context ? { context: request.context } : {}
79
+ });
80
+ stripCtxMetadata({ property_list: payload.list });
81
+ return { content: [{ type: "text", text: JSON.stringify(payload) }], structuredContent: payload };
82
+ } catch {
83
+ return toolError("INTERNAL_ERROR", "Property list could not be loaded.", request.context);
84
+ }
85
+ }
86
+ );
87
+ }
88
+ function toolError(code, message, context) {
89
+ const payload = { status: "failed", errors: [{ code, message }], ...context !== void 0 ? { context } : {} };
90
+ return {
91
+ isError: true,
92
+ content: [{ type: "text", text: JSON.stringify(payload) }],
93
+ structuredContent: payload
94
+ };
95
+ }
96
+ async function sendPropertyListChanged(emitter, options) {
97
+ const { notification, ...delivery } = options;
98
+ const dateTime = z.string().datetime({ offset: true });
99
+ if (!notification.list_id || !dateTime.safeParse(notification.resolved_at).success || notification.cache_valid_until !== void 0 && !dateTime.safeParse(notification.cache_valid_until).success) {
100
+ throw new TypeError("Property-list notifications require a list_id and valid timestamps");
101
+ }
102
+ const summary = notification.change_summary === void 0 ? void 0 : Object.fromEntries(
103
+ ["properties_added", "properties_removed", "total_properties"].filter(
104
+ (key) => Object.hasOwn(notification.change_summary, key) && notification.change_summary[key] !== void 0
105
+ ).map((key) => [key, notification.change_summary[key]])
106
+ );
107
+ for (const count of Object.values(summary ?? {})) {
108
+ if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0)
109
+ throw new TypeError("Property-list change counts must be nonnegative integers");
110
+ }
111
+ return emitter.emit({
112
+ ...delivery,
113
+ authentication: null,
114
+ signingProfile: "adcp/webhook-signing/v1",
115
+ payload: {
116
+ event: "property_list_changed",
117
+ list_id: notification.list_id,
118
+ resolved_at: notification.resolved_at,
119
+ ...notification.list_name !== void 0 ? { list_name: notification.list_name } : {},
120
+ ...notification.cache_valid_until !== void 0 ? { cache_valid_until: notification.cache_valid_until } : {},
121
+ ...summary !== void 0 ? { change_summary: summary } : {},
122
+ signature: "rfc9421"
123
+ }
124
+ });
125
+ }
126
+ export {
127
+ registerPropertyListTool,
128
+ sendPropertyListChanged
129
+ };
@@ -322,7 +322,8 @@ function serve(createAgent, options) {
322
322
  (0, import_auth.respondUnauthorized)(req, res, {
323
323
  signatureError: signatureCode,
324
324
  errorDescription: publicMessage,
325
- resourceMetadata: resourceMetadataUrl
325
+ resourceMetadata: resourceMetadataUrl,
326
+ ...err instanceof import_auth.AuthError && err.headers && { headers: err.headers }
326
327
  });
327
328
  return;
328
329
  }
@@ -305,7 +305,8 @@ function serve(createAgent, options) {
305
305
  respondUnauthorized(req, res, {
306
306
  signatureError: signatureCode,
307
307
  errorDescription: publicMessage,
308
- resourceMetadata: resourceMetadataUrl
308
+ resourceMetadata: resourceMetadataUrl,
309
+ ...err instanceof AuthError && err.headers && { headers: err.headers }
309
310
  });
310
311
  return;
311
312
  }
@@ -56,6 +56,8 @@ export declare const defaultResolvedListCache: ResolvedListCache;
56
56
  export declare function clearDefaultResolvedListCache(): void;
57
57
  export type ResolveListCallTool = (agentUrl: string, toolName: 'get_property_list' | 'get_collection_list', args: Record<string, unknown>, authToken?: string) => Promise<unknown>;
58
58
  export interface ResolveListOptions {
59
+ /** Require schema-valid property-list pages with identifiers when resolving. Used for certification. */
60
+ strictResponseValidation?: boolean;
59
61
  /** Maximum page size when calling get_property_list / get_collection_list. Defaults to 1000. */
60
62
  pageSize?: number;
61
63
  /** Maximum number of pages to walk before treating pagination as unsafe. Defaults to 100. */
@@ -56,6 +56,8 @@ export declare const defaultResolvedListCache: ResolvedListCache;
56
56
  export declare function clearDefaultResolvedListCache(): void;
57
57
  export type ResolveListCallTool = (agentUrl: string, toolName: 'get_property_list' | 'get_collection_list', args: Record<string, unknown>, authToken?: string) => Promise<unknown>;
58
58
  export interface ResolveListOptions {
59
+ /** Require schema-valid property-list pages with identifiers when resolving. Used for certification. */
60
+ strictResponseValidation?: boolean;
59
61
  /** Maximum page size when calling get_property_list / get_collection_list. Defaults to 1000. */
60
62
  pageSize?: number;
61
63
  /** Maximum number of pages to walk before treating pagination as unsafe. Defaults to 100. */
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var targeting_helpers_exports = {};
20
30
  __export(targeting_helpers_exports, {
@@ -75,7 +85,7 @@ async function resolvePropertyList(ref, options = {}) {
75
85
  const cache = cacheForOptions(options);
76
86
  const nowMs = nowMsForOptions(options);
77
87
  const cacheKey = resolvedListCacheKey("property", structurallyNormalizedRef, options);
78
- const cached = cacheKey ? cache?.get(cacheKey, nowMs) : void 0;
88
+ const cached = cacheKey && !options.strictResponseValidation ? cache?.get(cacheKey, nowMs) : void 0;
79
89
  if (cached) return cached;
80
90
  const normalizedRef = options.callTool ? structurallyNormalizedRef : await normalizePropertyListReference(structurallyNormalizedRef, { resolveDns: false });
81
91
  const result = await fetchResolvedPropertyList(normalizedRef, options);
@@ -89,6 +99,7 @@ async function fetchResolvedPropertyList(ref, options) {
89
99
  let cacheValidUntil;
90
100
  let missingCacheValidUntil = false;
91
101
  let cursor;
102
+ const seenCursors = /* @__PURE__ */ new Set();
92
103
  for (let pageIndex = 0; pageIndex < maxPagesForOptions(options); pageIndex += 1) {
93
104
  const args = buildResolveListArgs(ref.list_id, pageSizeForOptions(options), cursor);
94
105
  const raw = await callTool(ref.agent_url, "get_property_list", args, ref.auth_token);
@@ -96,6 +107,20 @@ async function fetchResolvedPropertyList(ref, options) {
96
107
  throw new Error("property_list_invalid_response");
97
108
  }
98
109
  const response = raw;
110
+ if (response.status !== void 0 && response.status !== "completed" || raw.errors !== void 0 && (!Array.isArray(raw.errors) || raw.errors.some((error) => !error || typeof error !== "object" || error.severity !== "warning")) || raw.adcp_error !== void 0 || !Array.isArray(response.identifiers)) {
111
+ throw new Error("property_list_invalid_response");
112
+ }
113
+ if (options.strictResponseValidation) {
114
+ const { GetPropertyListResponseSchema } = await import("../types/schemas.generated.js");
115
+ if (!GetPropertyListResponseSchema.safeParse(raw).success || response.status !== "completed" || response.identifiers.length > pageSizeForOptions(options)) {
116
+ throw new Error("property_list_invalid_response");
117
+ }
118
+ }
119
+ if (!response.list || response.list.list_id !== ref.list_id || response.identifiers !== void 0 && (!Array.isArray(response.identifiers) || response.identifiers.some(
120
+ (identifier) => !identifier || typeof identifier !== "object" || typeof identifier.type !== "string" || typeof identifier.value !== "string"
121
+ ))) {
122
+ throw new Error("property_list_invalid_response");
123
+ }
99
124
  listId = response.list.list_id;
100
125
  identifiers.push(...response.identifiers ?? []);
101
126
  if (typeof response.cache_valid_until === "string") {
@@ -118,6 +143,8 @@ async function fetchResolvedPropertyList(ref, options) {
118
143
  if (!pagination.cursor) {
119
144
  throw new Error("property_list_invalid_pagination");
120
145
  }
146
+ if (seenCursors.has(pagination.cursor)) throw new Error("property_list_invalid_pagination");
147
+ seenCursors.add(pagination.cursor);
121
148
  cursor = pagination.cursor;
122
149
  }
123
150
  throw new Error("property_list_pagination_limit_exceeded");
@@ -44,7 +44,7 @@ async function resolvePropertyList(ref, options = {}) {
44
44
  const cache = cacheForOptions(options);
45
45
  const nowMs = nowMsForOptions(options);
46
46
  const cacheKey = resolvedListCacheKey("property", structurallyNormalizedRef, options);
47
- const cached = cacheKey ? cache?.get(cacheKey, nowMs) : void 0;
47
+ const cached = cacheKey && !options.strictResponseValidation ? cache?.get(cacheKey, nowMs) : void 0;
48
48
  if (cached) return cached;
49
49
  const normalizedRef = options.callTool ? structurallyNormalizedRef : await normalizePropertyListReference(structurallyNormalizedRef, { resolveDns: false });
50
50
  const result = await fetchResolvedPropertyList(normalizedRef, options);
@@ -58,6 +58,7 @@ async function fetchResolvedPropertyList(ref, options) {
58
58
  let cacheValidUntil;
59
59
  let missingCacheValidUntil = false;
60
60
  let cursor;
61
+ const seenCursors = /* @__PURE__ */ new Set();
61
62
  for (let pageIndex = 0; pageIndex < maxPagesForOptions(options); pageIndex += 1) {
62
63
  const args = buildResolveListArgs(ref.list_id, pageSizeForOptions(options), cursor);
63
64
  const raw = await callTool(ref.agent_url, "get_property_list", args, ref.auth_token);
@@ -65,6 +66,20 @@ async function fetchResolvedPropertyList(ref, options) {
65
66
  throw new Error("property_list_invalid_response");
66
67
  }
67
68
  const response = raw;
69
+ if (response.status !== void 0 && response.status !== "completed" || raw.errors !== void 0 && (!Array.isArray(raw.errors) || raw.errors.some((error) => !error || typeof error !== "object" || error.severity !== "warning")) || raw.adcp_error !== void 0 || !Array.isArray(response.identifiers)) {
70
+ throw new Error("property_list_invalid_response");
71
+ }
72
+ if (options.strictResponseValidation) {
73
+ const { GetPropertyListResponseSchema } = await import("../types/schemas.generated.mjs");
74
+ if (!GetPropertyListResponseSchema.safeParse(raw).success || response.status !== "completed" || response.identifiers.length > pageSizeForOptions(options)) {
75
+ throw new Error("property_list_invalid_response");
76
+ }
77
+ }
78
+ if (!response.list || response.list.list_id !== ref.list_id || response.identifiers !== void 0 && (!Array.isArray(response.identifiers) || response.identifiers.some(
79
+ (identifier) => !identifier || typeof identifier !== "object" || typeof identifier.type !== "string" || typeof identifier.value !== "string"
80
+ ))) {
81
+ throw new Error("property_list_invalid_response");
82
+ }
68
83
  listId = response.list.list_id;
69
84
  identifiers.push(...response.identifiers ?? []);
70
85
  if (typeof response.cache_valid_until === "string") {
@@ -87,6 +102,8 @@ async function fetchResolvedPropertyList(ref, options) {
87
102
  if (!pagination.cursor) {
88
103
  throw new Error("property_list_invalid_pagination");
89
104
  }
105
+ if (seenCursors.has(pagination.cursor)) throw new Error("property_list_invalid_pagination");
106
+ seenCursors.add(pagination.cursor);
90
107
  cursor = pagination.cursor;
91
108
  }
92
109
  throw new Error("property_list_pagination_limit_exceeded");
@@ -83,6 +83,7 @@ function createWebhookEmitter(options) {
83
83
  if (options.deliveryStore && options.idempotencyKeyStore) {
84
84
  throw new TypeError("createWebhookEmitter: provide deliveryStore, not both deliveryStore and idempotencyKeyStore");
85
85
  }
86
+ if (options.webBotAuth?.key) (0, import_web_bot_auth.assertWebBotAuthSigningKey)(options.webBotAuth.key);
86
87
  const retryHorizonSeconds = resolveRetryHorizonSeconds(options.deliveryRetryHorizonSeconds);
87
88
  const explicitStore = options.deliveryStore ?? options.idempotencyKeyStore;
88
89
  const store = explicitStore ?? memoryWebhookDeliveryStore({ now: options.now });
@@ -1,4 +1,4 @@
1
- import { signWebBotAuth } from "../signing/web-bot-auth.mjs";
1
+ import { assertWebBotAuthSigningKey, signWebBotAuth } from "../signing/web-bot-auth.mjs";
2
2
  import { signWebhook } from "../signing/signer.mjs";
3
3
  import { signWebhookAsync } from "../signing/signer-async.mjs";
4
4
  import { canonicalJsonSha256 } from "../utils/jcs.mjs";
@@ -64,6 +64,7 @@ function createWebhookEmitter(options) {
64
64
  if (options.deliveryStore && options.idempotencyKeyStore) {
65
65
  throw new TypeError("createWebhookEmitter: provide deliveryStore, not both deliveryStore and idempotencyKeyStore");
66
66
  }
67
+ if (options.webBotAuth?.key) assertWebBotAuthSigningKey(options.webBotAuth.key);
67
68
  const retryHorizonSeconds = resolveRetryHorizonSeconds(options.deliveryRetryHorizonSeconds);
68
69
  const explicitStore = options.deliveryStore ?? options.idempotencyKeyStore;
69
70
  const store = explicitStore ?? memoryWebhookDeliveryStore({ now: options.now });
@@ -15,5 +15,5 @@ export declare function verifySignatureAsync(alg: string, publicKey: KeyObject |
15
15
  * path passes the key as a JWK so workerd accepts it (#3243).
16
16
  */
17
17
  export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
18
- /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available. */
18
+ /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 and RSA-PSS when available (workerd, #3243). */
19
19
  export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
@@ -15,5 +15,5 @@ export declare function verifySignatureAsync(alg: string, publicKey: KeyObject |
15
15
  * path passes the key as a JWK so workerd accepts it (#3243).
16
16
  */
17
17
  export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
18
- /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available. */
18
+ /** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 and RSA-PSS when available (workerd, #3243). */
19
19
  export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
@@ -87,15 +87,36 @@ function privateP256Jwk(jwk) {
87
87
  async function verifySignatureAsync(alg, publicKey, data, signature) {
88
88
  const subtle = subtleCrypto();
89
89
  const keyObject = () => isKeyObject(publicKey) ? publicKey : jwkToPublicKey(publicKey);
90
- if (alg !== "ecdsa-p256-sha256" || !subtle) return verifySignature(alg, keyObject(), data, signature);
90
+ if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
91
+ return verifySignature(alg, keyObject(), data, signature);
91
92
  try {
92
93
  const jwk = isKeyObject(publicKey) ? publicKey.export({ format: "jwk" }) : publicKey;
94
+ if (alg === "rsa-pss-sha512") {
95
+ const key2 = await importRsaPssVerifyKey(subtle, jwk);
96
+ return await subtle.verify(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(signature), toArrayBuffer(data));
97
+ }
93
98
  const key = await importP256VerifyKey(subtle, jwk);
94
99
  return await subtle.verify(P256_SHA256, key, toArrayBuffer(signature), toArrayBuffer(data));
95
100
  } catch {
96
101
  return false;
97
102
  }
98
103
  }
104
+ const RSA_PSS_SHA512 = { name: "RSA-PSS", hash: "SHA-512" };
105
+ const RSA_PSS_SHA512_SIGN = { name: "RSA-PSS", saltLength: 64 };
106
+ const rsaPssVerifyKeys = /* @__PURE__ */ new Map();
107
+ function importRsaPssVerifyKey(subtle, jwk) {
108
+ if (jwk.kty !== "RSA" || typeof jwk.n !== "string" || typeof jwk.e !== "string")
109
+ return Promise.reject(new TypeError("Not an RSA public key"));
110
+ const cacheKey = `${jwk.e}.${jwk.n}`;
111
+ let key = rsaPssVerifyKeys.get(cacheKey);
112
+ if (!key) {
113
+ key = subtle.importKey("jwk", { kty: "RSA", n: jwk.n, e: jwk.e }, RSA_PSS_SHA512, false, ["verify"]);
114
+ key.catch(() => rsaPssVerifyKeys.delete(cacheKey));
115
+ if (rsaPssVerifyKeys.size >= 256) rsaPssVerifyKeys.delete(rsaPssVerifyKeys.keys().next().value);
116
+ rsaPssVerifyKeys.set(cacheKey, key);
117
+ }
118
+ return key;
119
+ }
99
120
  function isKeyObject(value) {
100
121
  return typeof value?.export === "function" && "asymmetricKeyType" in value;
101
122
  }
@@ -118,11 +139,25 @@ function signWithPrivateJwk(alg, privateJwk, data) {
118
139
  return new Uint8Array((0, import_crypto.sign)(null, data, (0, import_crypto.createPrivateKey)({ key: privateJwk, format: "jwk" })));
119
140
  if (alg === "ecdsa-p256-sha256")
120
141
  return new Uint8Array((0, import_crypto.sign)("sha256", data, { key: privateJwk, format: "jwk", dsaEncoding: "ieee-p1363" }));
142
+ if (alg === "rsa-pss-sha512")
143
+ return new Uint8Array(
144
+ (0, import_crypto.sign)("sha512", data, {
145
+ key: (0, import_crypto.createPrivateKey)({ key: privateJwk, format: "jwk" }),
146
+ padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING,
147
+ saltLength: 64
148
+ })
149
+ );
121
150
  throw new TypeError(`Unsupported signing algorithm: ${alg}`);
122
151
  }
123
152
  async function signWithPrivateJwkAsync(alg, privateJwk, data) {
124
153
  const subtle = subtleCrypto();
125
- if (alg !== "ecdsa-p256-sha256" || !subtle) return signWithPrivateJwk(alg, privateJwk, data);
154
+ if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
155
+ return signWithPrivateJwk(alg, privateJwk, data);
156
+ if (alg === "rsa-pss-sha512") {
157
+ const { kty, n, e, d, p, q, dp, dq, qi } = privateJwk;
158
+ const key2 = await subtle.importKey("jwk", { kty, n, e, d, p, q, dp, dq, qi }, RSA_PSS_SHA512, false, ["sign"]);
159
+ return new Uint8Array(await subtle.sign(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(data)));
160
+ }
126
161
  const key = await subtle.importKey("jwk", privateP256Jwk(privateJwk), P256, false, ["sign"]);
127
162
  return new Uint8Array(await subtle.sign(P256_SHA256, key, toArrayBuffer(data)));
128
163
  }
@@ -66,15 +66,36 @@ function privateP256Jwk(jwk) {
66
66
  async function verifySignatureAsync(alg, publicKey, data, signature) {
67
67
  const subtle = subtleCrypto();
68
68
  const keyObject = () => isKeyObject(publicKey) ? publicKey : jwkToPublicKey(publicKey);
69
- if (alg !== "ecdsa-p256-sha256" || !subtle) return verifySignature(alg, keyObject(), data, signature);
69
+ if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
70
+ return verifySignature(alg, keyObject(), data, signature);
70
71
  try {
71
72
  const jwk = isKeyObject(publicKey) ? publicKey.export({ format: "jwk" }) : publicKey;
73
+ if (alg === "rsa-pss-sha512") {
74
+ const key2 = await importRsaPssVerifyKey(subtle, jwk);
75
+ return await subtle.verify(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(signature), toArrayBuffer(data));
76
+ }
72
77
  const key = await importP256VerifyKey(subtle, jwk);
73
78
  return await subtle.verify(P256_SHA256, key, toArrayBuffer(signature), toArrayBuffer(data));
74
79
  } catch {
75
80
  return false;
76
81
  }
77
82
  }
83
+ const RSA_PSS_SHA512 = { name: "RSA-PSS", hash: "SHA-512" };
84
+ const RSA_PSS_SHA512_SIGN = { name: "RSA-PSS", saltLength: 64 };
85
+ const rsaPssVerifyKeys = /* @__PURE__ */ new Map();
86
+ function importRsaPssVerifyKey(subtle, jwk) {
87
+ if (jwk.kty !== "RSA" || typeof jwk.n !== "string" || typeof jwk.e !== "string")
88
+ return Promise.reject(new TypeError("Not an RSA public key"));
89
+ const cacheKey = `${jwk.e}.${jwk.n}`;
90
+ let key = rsaPssVerifyKeys.get(cacheKey);
91
+ if (!key) {
92
+ key = subtle.importKey("jwk", { kty: "RSA", n: jwk.n, e: jwk.e }, RSA_PSS_SHA512, false, ["verify"]);
93
+ key.catch(() => rsaPssVerifyKeys.delete(cacheKey));
94
+ if (rsaPssVerifyKeys.size >= 256) rsaPssVerifyKeys.delete(rsaPssVerifyKeys.keys().next().value);
95
+ rsaPssVerifyKeys.set(cacheKey, key);
96
+ }
97
+ return key;
98
+ }
78
99
  function isKeyObject(value) {
79
100
  return typeof value?.export === "function" && "asymmetricKeyType" in value;
80
101
  }
@@ -97,11 +118,25 @@ function signWithPrivateJwk(alg, privateJwk, data) {
97
118
  return new Uint8Array(nodeSign(null, data, createPrivateKey({ key: privateJwk, format: "jwk" })));
98
119
  if (alg === "ecdsa-p256-sha256")
99
120
  return new Uint8Array(nodeSign("sha256", data, { key: privateJwk, format: "jwk", dsaEncoding: "ieee-p1363" }));
121
+ if (alg === "rsa-pss-sha512")
122
+ return new Uint8Array(
123
+ nodeSign("sha512", data, {
124
+ key: createPrivateKey({ key: privateJwk, format: "jwk" }),
125
+ padding: constants.RSA_PKCS1_PSS_PADDING,
126
+ saltLength: 64
127
+ })
128
+ );
100
129
  throw new TypeError(`Unsupported signing algorithm: ${alg}`);
101
130
  }
102
131
  async function signWithPrivateJwkAsync(alg, privateJwk, data) {
103
132
  const subtle = subtleCrypto();
104
- if (alg !== "ecdsa-p256-sha256" || !subtle) return signWithPrivateJwk(alg, privateJwk, data);
133
+ if (alg !== "ecdsa-p256-sha256" && alg !== "rsa-pss-sha512" || !subtle)
134
+ return signWithPrivateJwk(alg, privateJwk, data);
135
+ if (alg === "rsa-pss-sha512") {
136
+ const { kty, n, e, d, p, q, dp, dq, qi } = privateJwk;
137
+ const key2 = await subtle.importKey("jwk", { kty, n, e, d, p, q, dp, dq, qi }, RSA_PSS_SHA512, false, ["sign"]);
138
+ return new Uint8Array(await subtle.sign(RSA_PSS_SHA512_SIGN, key2, toArrayBuffer(data)));
139
+ }
105
140
  const key = await subtle.importKey("jwk", privateP256Jwk(privateJwk), P256, false, ["sign"]);
106
141
  return new Uint8Array(await subtle.sign(P256_SHA256, key, toArrayBuffer(data)));
107
142
  }
@@ -24,6 +24,12 @@ export interface ParsedSignature {
24
24
  bytes: Uint8Array;
25
25
  }
26
26
  export declare function parseSignatureInput(headerValue: string, expectedLabel?: string): ParsedSignatureInput;
27
+ /**
28
+ * {@link parseSignatureInput} for one member of an already parsed (and
29
+ * duplicate-checked) `Signature-Input` dictionary, so a caller that needs
30
+ * every label parses the field once.
31
+ */
32
+ export declare function parseSignatureInputMember(label: string, entry: unknown): ParsedSignatureInput;
27
33
  export declare function parseSignature(headerValue: string, expectedLabel: string, encoding?: SfBinaryEncoding): ParsedSignature;
28
34
  /**
29
35
  * RFC 8941 / RFC 9651 parsers deduplicate dictionary keys and parameter keys
@@ -24,6 +24,12 @@ export interface ParsedSignature {
24
24
  bytes: Uint8Array;
25
25
  }
26
26
  export declare function parseSignatureInput(headerValue: string, expectedLabel?: string): ParsedSignatureInput;
27
+ /**
28
+ * {@link parseSignatureInput} for one member of an already parsed (and
29
+ * duplicate-checked) `Signature-Input` dictionary, so a caller that needs
30
+ * every label parses the field once.
31
+ */
32
+ export declare function parseSignatureInputMember(label: string, entry: unknown): ParsedSignatureInput;
27
33
  export declare function parseSignature(headerValue: string, expectedLabel: string, encoding?: SfBinaryEncoding): ParsedSignature;
28
34
  /**
29
35
  * RFC 8941 / RFC 9651 parsers deduplicate dictionary keys and parameter keys
@@ -20,6 +20,7 @@ var parser_exports = {};
20
20
  __export(parser_exports, {
21
21
  parseSignature: () => parseSignature,
22
22
  parseSignatureInput: () => parseSignatureInput,
23
+ parseSignatureInputMember: () => parseSignatureInputMember,
23
24
  rejectDuplicateDictionaryKeys: () => rejectDuplicateDictionaryKeys
24
25
  });
25
26
  module.exports = __toCommonJS(parser_exports);
@@ -43,7 +44,9 @@ function parseSignatureInput(headerValue, expectedLabel) {
43
44
  if (dict.size === 0) malformed("Signature-Input header is empty");
44
45
  const label = expectedLabel ?? (dict.has("sig1") ? "sig1" : dict.keys().next().value);
45
46
  if (!dict.has(label)) malformed("Signature-Input label is missing");
46
- const entry = dict.get(label);
47
+ return parseSignatureInputMember(label, dict.get(label));
48
+ }
49
+ function parseSignatureInputMember(label, entry) {
47
50
  if (!isInnerList(entry)) {
48
51
  malformed("Signature-Input value must be a parenthesized component list");
49
52
  }
@@ -83,10 +86,10 @@ function parseSignature(headerValue, expectedLabel, encoding = "legacy-base64url
83
86
  if (!rawBytes) malformed(`Signature header does not contain a byte sequence for label "${expectedLabel}"`);
84
87
  if (encoding === "rfc8941-base64") {
85
88
  if (!(0, import_content_digest.isPaddedStandardBase64)(rawBytes)) {
86
- malformed("Signature value must use padded standard Base64 for AdCP 3.2+");
89
+ malformed("Signature value must use padded standard Base64 for the selected signing profile");
87
90
  }
88
91
  } else if (!/^[A-Za-z0-9_-]+$/.test(rawBytes)) {
89
- malformed("Signature value must use unpadded Base64URL for AdCP 3.0/3.1");
92
+ malformed("Signature value must use unpadded Base64URL for the selected signing profile");
90
93
  }
91
94
  let dict;
92
95
  try {
@@ -255,5 +258,6 @@ function normalizeByteSequenceBase64(input) {
255
258
  0 && (module.exports = {
256
259
  parseSignature,
257
260
  parseSignatureInput,
261
+ parseSignatureInputMember,
258
262
  rejectDuplicateDictionaryKeys
259
263
  });
@@ -18,7 +18,9 @@ function parseSignatureInput(headerValue, expectedLabel) {
18
18
  if (dict.size === 0) malformed("Signature-Input header is empty");
19
19
  const label = expectedLabel ?? (dict.has("sig1") ? "sig1" : dict.keys().next().value);
20
20
  if (!dict.has(label)) malformed("Signature-Input label is missing");
21
- const entry = dict.get(label);
21
+ return parseSignatureInputMember(label, dict.get(label));
22
+ }
23
+ function parseSignatureInputMember(label, entry) {
22
24
  if (!isInnerList(entry)) {
23
25
  malformed("Signature-Input value must be a parenthesized component list");
24
26
  }
@@ -58,10 +60,10 @@ function parseSignature(headerValue, expectedLabel, encoding = "legacy-base64url
58
60
  if (!rawBytes) malformed(`Signature header does not contain a byte sequence for label "${expectedLabel}"`);
59
61
  if (encoding === "rfc8941-base64") {
60
62
  if (!isPaddedStandardBase64(rawBytes)) {
61
- malformed("Signature value must use padded standard Base64 for AdCP 3.2+");
63
+ malformed("Signature value must use padded standard Base64 for the selected signing profile");
62
64
  }
63
65
  } else if (!/^[A-Za-z0-9_-]+$/.test(rawBytes)) {
64
- malformed("Signature value must use unpadded Base64URL for AdCP 3.0/3.1");
66
+ malformed("Signature value must use unpadded Base64URL for the selected signing profile");
65
67
  }
66
68
  let dict;
67
69
  try {
@@ -229,5 +231,6 @@ function normalizeByteSequenceBase64(input) {
229
231
  export {
230
232
  parseSignature,
231
233
  parseSignatureInput,
234
+ parseSignatureInputMember,
232
235
  rejectDuplicateDictionaryKeys
233
236
  };
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export type { SsrfDnsLookup } from '../net/index.mjs';
12
12
  export { buildResponseSignatureBase, buildSignatureBase, canonicalAuthority, canonicalMethod, canonicalTargetUri, formatSignatureParams, getHeaderValue, type RequestLike, type RequestCanonicalizationProfile, type ResponseLike, type SignatureParams, } from './canonicalize.mjs';
13
- export { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest.mjs';
13
+ export { computeContentDigest, contentDigestMatches, parseContentDigest, type SfBinaryEncoding, } from './content-digest.mjs';
14
14
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context.mjs';
15
15
  export { jwkToPublicKey, verifySignature, verifySignatureAsync } from './crypto.mjs';
16
16
  export { RequestSignatureError, RequestSigningErrorCodeMetadata, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors.mjs';
@@ -10,7 +10,7 @@
10
10
  */
11
11
  export type { SsrfDnsLookup } from '../net';
12
12
  export { buildResponseSignatureBase, buildSignatureBase, canonicalAuthority, canonicalMethod, canonicalTargetUri, formatSignatureParams, getHeaderValue, type RequestLike, type RequestCanonicalizationProfile, type ResponseLike, type SignatureParams, } from './canonicalize';
13
- export { computeContentDigest, contentDigestMatches, parseContentDigest } from './content-digest';
13
+ export { computeContentDigest, contentDigestMatches, parseContentDigest, type SfBinaryEncoding, } from './content-digest';
14
14
  export { requestContextFromExpress, requestContextFromFetch, requestContextFromLambda, type ExpressRequestLike, type FetchRequestLike, type LambdaRequestEvent, type RequestContextFromExpressOptions, type RequestContextFromLambdaOptions, } from './request-context';
15
15
  export { jwkToPublicKey, verifySignature, verifySignatureAsync } from './crypto';
16
16
  export { RequestSignatureError, RequestSigningErrorCodeMetadata, type RequestSignatureErrorCode, ResponseSignatureError, type ResponseSignatureErrorCode, WebhookSignatureError, type WebhookSignatureErrorCode, } from './errors';
@@ -7,7 +7,11 @@ import {
7
7
  formatSignatureParams,
8
8
  getHeaderValue
9
9
  } from "./canonicalize.mjs";
10
- import { computeContentDigest, contentDigestMatches, parseContentDigest } from "./content-digest.mjs";
10
+ import {
11
+ computeContentDigest,
12
+ contentDigestMatches,
13
+ parseContentDigest
14
+ } from "./content-digest.mjs";
11
15
  import {
12
16
  requestContextFromExpress,
13
17
  requestContextFromFetch,