@adcp/sdk 16.0.0-beta.2 → 16.0.0-beta.3

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 (87) hide show
  1. package/README.md +54 -0
  2. package/dist/lib/core/ConversationTypes.d.mts +8 -3
  3. package/dist/lib/core/ConversationTypes.d.ts +8 -3
  4. package/dist/lib/core/SingleAgentClient.d.mts +5 -3
  5. package/dist/lib/core/SingleAgentClient.d.ts +5 -3
  6. package/dist/lib/core/TaskExecutor.js +5 -1
  7. package/dist/lib/core/TaskExecutor.mjs +5 -1
  8. package/dist/lib/errors/index.d.mts +9 -4
  9. package/dist/lib/errors/index.d.ts +9 -4
  10. package/dist/lib/index.d.mts +2 -2
  11. package/dist/lib/index.d.ts +2 -2
  12. package/dist/lib/index.js +6 -0
  13. package/dist/lib/index.mjs +7 -1
  14. package/dist/lib/negotiation/types.d.mts +20 -39
  15. package/dist/lib/negotiation/types.d.ts +20 -39
  16. package/dist/lib/protocols/index.d.mts +60 -24
  17. package/dist/lib/protocols/index.d.ts +60 -24
  18. package/dist/lib/protocols/index.js +9 -4
  19. package/dist/lib/protocols/index.mjs +3 -0
  20. package/dist/lib/protocols/mcp-modern.js +1 -0
  21. package/dist/lib/protocols/mcp-modern.mjs +2 -1
  22. package/dist/lib/protocols/mcp.js +1 -0
  23. package/dist/lib/protocols/mcp.mjs +2 -1
  24. package/dist/lib/protocols/responseSizeLimit.d.mts +91 -5
  25. package/dist/lib/protocols/responseSizeLimit.d.ts +91 -5
  26. package/dist/lib/protocols/responseSizeLimit.js +195 -31
  27. package/dist/lib/protocols/responseSizeLimit.mjs +192 -31
  28. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  29. package/dist/lib/signing/crypto.d.mts +15 -0
  30. package/dist/lib/signing/crypto.d.ts +15 -0
  31. package/dist/lib/signing/crypto.js +75 -3
  32. package/dist/lib/signing/crypto.mjs +78 -3
  33. package/dist/lib/signing/server.d.mts +1 -1
  34. package/dist/lib/signing/server.d.ts +1 -1
  35. package/dist/lib/signing/server.js +2 -0
  36. package/dist/lib/signing/server.mjs +2 -1
  37. package/dist/lib/signing/signer.js +2 -8
  38. package/dist/lib/signing/signer.mjs +3 -9
  39. package/dist/lib/signing/testing.js +2 -6
  40. package/dist/lib/signing/testing.mjs +3 -7
  41. package/dist/lib/signing/verifier.js +2 -2
  42. package/dist/lib/signing/verifier.mjs +3 -3
  43. package/dist/lib/signing/web-bot-auth.js +20 -7
  44. package/dist/lib/signing/web-bot-auth.mjs +21 -8
  45. package/dist/lib/signing/webhook-verifier.js +2 -2
  46. package/dist/lib/signing/webhook-verifier.mjs +3 -3
  47. package/dist/lib/substitution/compile.js +2 -1
  48. package/dist/lib/substitution/compile.mjs +2 -1
  49. package/dist/lib/substitution/consent-macros.d.mts +4 -0
  50. package/dist/lib/substitution/consent-macros.d.ts +4 -0
  51. package/dist/lib/substitution/consent-macros.js +45 -0
  52. package/dist/lib/substitution/consent-macros.mjs +20 -0
  53. package/dist/lib/substitution/index.d.mts +1 -1
  54. package/dist/lib/substitution/index.d.ts +1 -1
  55. package/dist/lib/substitution/index.js +2 -0
  56. package/dist/lib/substitution/index.mjs +2 -1
  57. package/dist/lib/substitution/translate.d.mts +15 -2
  58. package/dist/lib/substitution/translate.d.ts +15 -2
  59. package/dist/lib/substitution/translate.js +148 -26
  60. package/dist/lib/substitution/translate.mjs +147 -26
  61. package/dist/lib/testing/client.js +2 -1
  62. package/dist/lib/testing/client.mjs +2 -1
  63. package/dist/lib/testing/scenarios/capabilities.js +1 -1
  64. package/dist/lib/testing/scenarios/capabilities.mjs +1 -1
  65. package/dist/lib/testing/storyboard/request-signing/builder.js +2 -5
  66. package/dist/lib/testing/storyboard/request-signing/builder.mjs +3 -6
  67. package/dist/lib/testing/storyboard/signer-grader/grader.js +2 -7
  68. package/dist/lib/testing/storyboard/signer-grader/grader.mjs +2 -7
  69. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  70. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  71. package/dist/lib/types/enums.generated.d.mts +3 -0
  72. package/dist/lib/types/enums.generated.d.ts +3 -0
  73. package/dist/lib/types/enums.generated.js +9 -0
  74. package/dist/lib/types/enums.generated.mjs +6 -0
  75. package/dist/lib/validation/schema-loader.js +25 -0
  76. package/dist/lib/validation/schema-loader.mjs +24 -0
  77. package/dist/lib/version.d.mts +3 -3
  78. package/dist/lib/version.d.ts +3 -3
  79. package/dist/lib/version.js +3 -3
  80. package/dist/lib/version.mjs +3 -3
  81. package/docs/TYPE-SUMMARY.md +6 -1
  82. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +3 -2
  83. package/docs/guides/UNIVERSAL-MACRO-COMPILATION.md +38 -0
  84. package/docs/guides/VALIDATE-YOUR-AGENT.md +12 -0
  85. package/docs/llms.txt +1 -1
  86. package/docs/migration-14.x-rc-worksheet.md +4 -4
  87. package/package.json +2 -1
@@ -0,0 +1,20 @@
1
+ const VENDOR_SCOPED_CONSENT_MACRO = /^\{GDPR_CONSENT_[1-9][0-9]*\}$/;
2
+ const CONSENT_MACROS = /* @__PURE__ */ new Set([
3
+ "{GDPR}",
4
+ "{GDPR_CONSENT}",
5
+ "{ADDTL_CONSENT}",
6
+ "{US_PRIVACY}",
7
+ "{GPP_STRING}",
8
+ "{GPP_SID}",
9
+ "{LIMIT_AD_TRACKING}"
10
+ ]);
11
+ function isVendorScopedConsentMacro(token) {
12
+ return VENDOR_SCOPED_CONSENT_MACRO.test(token);
13
+ }
14
+ function isConsentMacro(token) {
15
+ return CONSENT_MACROS.has(token) || isVendorScopedConsentMacro(token);
16
+ }
17
+ export {
18
+ isConsentMacro,
19
+ isVendorScopedConsentMacro
20
+ };
@@ -33,7 +33,7 @@ export type { CatalogMacroVectorName } from './vectors.mjs';
33
33
  export { extractTrackerUrls, matchBindings, assertNoNestedExpansion, assertRfc3986Safe, assertSchemePreserved, assertUnreservedOnly, DEFAULT_MACRO_PROHIBITED_PATTERN, enforceSsrfPolicy, enforceSsrfPolicyResolved, DEFAULT_SSRF_POLICY, } from './observer/index.mjs';
34
34
  export { encodeUnreserved, equalUnderHexCasePolicy, isUnreservedOnly, divergenceOffset } from './rfc3986.mjs';
35
35
  export type { AssertionOptions, AssertionResult, BindingMatch, CatalogBinding, CatalogMacroVector, PolicyResult, SsrfPolicy, TrackerUrlRecord, } from './types.mjs';
36
- export { translateUniversalMacros, UnsafeNativeMappingError } from './translate.mjs';
36
+ export { translateUniversalMacros, UnsafeNativeMappingError, MultipleVendorConsentMacrosError } from './translate.mjs';
37
37
  export type { MacroMapping, TranslateResult } from './translate.mjs';
38
38
  export { compileUniversalMacroTemplate } from './compile.mjs';
39
39
  export type { BuiltInSourceMacroSyntax, CompileUniversalMacroTemplateInput, CompileUniversalMacroTemplateResult, CustomSourceMacroSyntax, MacroDocumentationReference, SourceMacroMapping, SourceMacroRequirement, SourceMacroRequirementSatisfaction, SourceMacroSyntax, SourceMacroSyntaxDeclaration, UniversalMacroCompileDiagnostic, UniversalMacroOccurrence, } from './compile.mjs';
@@ -33,7 +33,7 @@ export type { CatalogMacroVectorName } from './vectors';
33
33
  export { extractTrackerUrls, matchBindings, assertNoNestedExpansion, assertRfc3986Safe, assertSchemePreserved, assertUnreservedOnly, DEFAULT_MACRO_PROHIBITED_PATTERN, enforceSsrfPolicy, enforceSsrfPolicyResolved, DEFAULT_SSRF_POLICY, } from './observer';
34
34
  export { encodeUnreserved, equalUnderHexCasePolicy, isUnreservedOnly, divergenceOffset } from './rfc3986';
35
35
  export type { AssertionOptions, AssertionResult, BindingMatch, CatalogBinding, CatalogMacroVector, PolicyResult, SsrfPolicy, TrackerUrlRecord, } from './types';
36
- export { translateUniversalMacros, UnsafeNativeMappingError } from './translate';
36
+ export { translateUniversalMacros, UnsafeNativeMappingError, MultipleVendorConsentMacrosError } from './translate';
37
37
  export type { MacroMapping, TranslateResult } from './translate';
38
38
  export { compileUniversalMacroTemplate } from './compile';
39
39
  export type { BuiltInSourceMacroSyntax, CompileUniversalMacroTemplateInput, CompileUniversalMacroTemplateResult, CustomSourceMacroSyntax, MacroDocumentationReference, SourceMacroMapping, SourceMacroRequirement, SourceMacroRequirementSatisfaction, SourceMacroSyntax, SourceMacroSyntaxDeclaration, UniversalMacroCompileDiagnostic, UniversalMacroOccurrence, } from './compile';
@@ -22,6 +22,7 @@ __export(substitution_exports, {
22
22
  DEFAULT_MACRO_PROHIBITED_PATTERN: () => import_observer.DEFAULT_MACRO_PROHIBITED_PATTERN,
23
23
  DEFAULT_SSRF_POLICY: () => import_observer.DEFAULT_SSRF_POLICY,
24
24
  MacroInRawValueError: () => import_SubstitutionEncoder.MacroInRawValueError,
25
+ MultipleVendorConsentMacrosError: () => import_translate.MultipleVendorConsentMacrosError,
25
26
  PreviewFetchError: () => import_SubstitutionObserver.PreviewFetchError,
26
27
  SubstitutionEncoder: () => import_SubstitutionEncoder.SubstitutionEncoder,
27
28
  SubstitutionObserver: () => import_SubstitutionObserver.SubstitutionObserver,
@@ -56,6 +57,7 @@ var import_compile = require('./compile.js');
56
57
  DEFAULT_MACRO_PROHIBITED_PATTERN,
57
58
  DEFAULT_SSRF_POLICY,
58
59
  MacroInRawValueError,
60
+ MultipleVendorConsentMacrosError,
59
61
  PreviewFetchError,
60
62
  SubstitutionEncoder,
61
63
  SubstitutionObserver,
@@ -14,13 +14,14 @@ import {
14
14
  DEFAULT_SSRF_POLICY
15
15
  } from "./observer/index.mjs";
16
16
  import { encodeUnreserved, equalUnderHexCasePolicy, isUnreservedOnly, divergenceOffset } from "./rfc3986.mjs";
17
- import { translateUniversalMacros, UnsafeNativeMappingError } from "./translate.mjs";
17
+ import { translateUniversalMacros, UnsafeNativeMappingError, MultipleVendorConsentMacrosError } from "./translate.mjs";
18
18
  import { compileUniversalMacroTemplate } from "./compile.mjs";
19
19
  export {
20
20
  CATALOG_MACRO_VECTORS,
21
21
  DEFAULT_MACRO_PROHIBITED_PATTERN,
22
22
  DEFAULT_SSRF_POLICY,
23
23
  MacroInRawValueError,
24
+ MultipleVendorConsentMacrosError,
24
25
  PreviewFetchError,
25
26
  SubstitutionEncoder,
26
27
  SubstitutionObserver,
@@ -4,7 +4,8 @@
4
4
  * position pass through untouched — only query-parameter values are translated.
5
5
  *
6
6
  * Universal macros match `\{[A-Z][A-Z0-9_]*\}` (upper-snake, single braces).
7
- * Native ad-server tokens (`%%X%%`, `{{x}}`) are not matched and pass through.
7
+ * Native ad-server tokens (`%%X%%`, `{{x}}`, `${x}`, `[X]`) pass through
8
+ * without translating their interiors.
8
9
  *
9
10
  * Per query parameter:
10
11
  * - No universal macro → left untouched (literal param passes through).
@@ -64,6 +65,15 @@ export declare class UnsafeNativeMappingError extends Error {
64
65
  readonly macro: string;
65
66
  constructor(macro: string);
66
67
  }
68
+ /**
69
+ * Thrown before emitting a URL containing a second vendor-scoped TCF macro.
70
+ * Only the canonical second token is retained, never the URL or consent data.
71
+ */
72
+ export declare class MultipleVendorConsentMacrosError extends Error {
73
+ readonly code: "multiple_vendor_consent_macros";
74
+ readonly macro: string;
75
+ constructor(macro: string);
76
+ }
67
77
  /** Result of {@link translateUniversalMacros}. */
68
78
  export interface TranslateResult {
69
79
  /** The translated URL. Base, path, and fragment are unchanged. */
@@ -82,7 +92,8 @@ export interface TranslateResult {
82
92
  unmapped_macros: string[];
83
93
  /**
84
94
  * Subset of `unmapped_macros` that are consent/privacy signals (`{GDPR}`,
85
- * `{GDPR_CONSENT}`, `{US_PRIVACY}`, `{GPP_STRING}`, `{GPP_SID}`,
95
+ * `{GDPR_CONSENT}`, `{GDPR_CONSENT_<gvl_id>}`, `{ADDTL_CONSENT}`,
96
+ * `{US_PRIVACY}`, `{GPP_STRING}`, `{GPP_SID}`,
86
97
  * `{LIMIT_AD_TRACKING}`). Surfaced separately so a forgotten-mapping drop of
87
98
  * a compliance-relevant macro isn't lost among benign drops. Deduplicated.
88
99
  */
@@ -115,5 +126,7 @@ export interface TranslateResult {
115
126
  * @throws {UnsafeNativeMappingError} If any native mapping entry contains
116
127
  * U+0000-U+001F or U+007F. The whole mapping is checked before translation,
117
128
  * including entries not referenced by `input_pixel_url`.
129
+ * @throws {MultipleVendorConsentMacrosError} If the final URL contains more
130
+ * than one scoped TCF consent macro, after unsupported parameters are dropped.
118
131
  */
119
132
  export declare function translateUniversalMacros(input_pixel_url: string, mapping: MacroMapping): TranslateResult;
@@ -4,7 +4,8 @@
4
4
  * position pass through untouched — only query-parameter values are translated.
5
5
  *
6
6
  * Universal macros match `\{[A-Z][A-Z0-9_]*\}` (upper-snake, single braces).
7
- * Native ad-server tokens (`%%X%%`, `{{x}}`) are not matched and pass through.
7
+ * Native ad-server tokens (`%%X%%`, `{{x}}`, `${x}`, `[X]`) pass through
8
+ * without translating their interiors.
8
9
  *
9
10
  * Per query parameter:
10
11
  * - No universal macro → left untouched (literal param passes through).
@@ -64,6 +65,15 @@ export declare class UnsafeNativeMappingError extends Error {
64
65
  readonly macro: string;
65
66
  constructor(macro: string);
66
67
  }
68
+ /**
69
+ * Thrown before emitting a URL containing a second vendor-scoped TCF macro.
70
+ * Only the canonical second token is retained, never the URL or consent data.
71
+ */
72
+ export declare class MultipleVendorConsentMacrosError extends Error {
73
+ readonly code: "multiple_vendor_consent_macros";
74
+ readonly macro: string;
75
+ constructor(macro: string);
76
+ }
67
77
  /** Result of {@link translateUniversalMacros}. */
68
78
  export interface TranslateResult {
69
79
  /** The translated URL. Base, path, and fragment are unchanged. */
@@ -82,7 +92,8 @@ export interface TranslateResult {
82
92
  unmapped_macros: string[];
83
93
  /**
84
94
  * Subset of `unmapped_macros` that are consent/privacy signals (`{GDPR}`,
85
- * `{GDPR_CONSENT}`, `{US_PRIVACY}`, `{GPP_STRING}`, `{GPP_SID}`,
95
+ * `{GDPR_CONSENT}`, `{GDPR_CONSENT_<gvl_id>}`, `{ADDTL_CONSENT}`,
96
+ * `{US_PRIVACY}`, `{GPP_STRING}`, `{GPP_SID}`,
86
97
  * `{LIMIT_AD_TRACKING}`). Surfaced separately so a forgotten-mapping drop of
87
98
  * a compliance-relevant macro isn't lost among benign drops. Deduplicated.
88
99
  */
@@ -115,5 +126,7 @@ export interface TranslateResult {
115
126
  * @throws {UnsafeNativeMappingError} If any native mapping entry contains
116
127
  * U+0000-U+001F or U+007F. The whole mapping is checked before translation,
117
128
  * including entries not referenced by `input_pixel_url`.
129
+ * @throws {MultipleVendorConsentMacrosError} If the final URL contains more
130
+ * than one scoped TCF consent macro, after unsupported parameters are dropped.
118
131
  */
119
132
  export declare function translateUniversalMacros(input_pixel_url: string, mapping: MacroMapping): TranslateResult;
@@ -18,21 +18,51 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var translate_exports = {};
20
20
  __export(translate_exports, {
21
+ MultipleVendorConsentMacrosError: () => MultipleVendorConsentMacrosError,
21
22
  UnsafeNativeMappingError: () => UnsafeNativeMappingError,
22
23
  translateUniversalMacros: () => translateUniversalMacros
23
24
  });
24
25
  module.exports = __toCommonJS(translate_exports);
25
26
  var import_rfc3986 = require('./rfc3986.js');
26
- const UNIVERSAL_MACRO = /\{[A-Z][A-Z0-9_]*\}/g;
27
- const NATIVE_TOKEN_SHAPE = /^(?:%%.+%%|\{\{.+\}\}|\$\{.+\}|\[[A-Z][A-Z0-9_]*\])$/;
28
- const CONSENT_MACROS = /* @__PURE__ */ new Set([
29
- "{GDPR}",
30
- "{GDPR_CONSENT}",
31
- "{US_PRIVACY}",
32
- "{GPP_STRING}",
33
- "{GPP_SID}",
34
- "{LIMIT_AD_TRACKING}"
35
- ]);
27
+ var import_consent_macros = require('./consent-macros.js');
28
+ function* macroTokens(text) {
29
+ const missingClosers = /* @__PURE__ */ new Set();
30
+ let cursor = 0;
31
+ while (cursor < text.length) {
32
+ let end = -1;
33
+ let native = true;
34
+ const close = text.startsWith("%%", cursor) ? "%%" : text.startsWith("{{", cursor) ? "}}" : text.startsWith("${", cursor) ? "}" : null;
35
+ if (close && !missingClosers.has(close)) {
36
+ const closer = text.indexOf(close, cursor + 2);
37
+ if (closer === -1) missingClosers.add(close);
38
+ else end = closer + close.length;
39
+ }
40
+ if (end === -1 && (text[cursor] === "{" || text[cursor] === "[")) {
41
+ let nameEnd = cursor + 1;
42
+ if (/[A-Z]/.test(text[nameEnd] ?? "")) {
43
+ nameEnd += 1;
44
+ while (/[A-Z0-9_]/.test(text[nameEnd] ?? "")) nameEnd += 1;
45
+ if (text[nameEnd] === (text[cursor] === "{" ? "}" : "]")) {
46
+ end = nameEnd + 1;
47
+ native = text[cursor] === "[";
48
+ }
49
+ }
50
+ }
51
+ if (end === -1) {
52
+ cursor += 1;
53
+ continue;
54
+ }
55
+ yield { text: text.slice(cursor, end), start: cursor, end, native };
56
+ cursor = end;
57
+ }
58
+ }
59
+ function hasNativeTokenSyntax(value) {
60
+ for (const token of macroTokens(value)) {
61
+ const minimumLength = token.text.startsWith("${") ? 3 : token.text.startsWith("[") ? 2 : 4;
62
+ if (token.native && token.text.length > minimumLength) return true;
63
+ }
64
+ return false;
65
+ }
36
66
  const UNSAFE_NATIVE_CHARACTER = /[\u0000-\u001F\u007F]/;
37
67
  class UnsafeNativeMappingError extends Error {
38
68
  code = "unsafe_native_mapping";
@@ -46,6 +76,83 @@ class UnsafeNativeMappingError extends Error {
46
76
  this.macro = macro;
47
77
  }
48
78
  }
79
+ class MultipleVendorConsentMacrosError extends Error {
80
+ code = "multiple_vendor_consent_macros";
81
+ macro;
82
+ constructor(macro) {
83
+ super(`Multiple vendor-scoped consent macros; second token is ${JSON.stringify(macro)}`);
84
+ this.name = "MultipleVendorConsentMacrosError";
85
+ this.macro = macro;
86
+ }
87
+ }
88
+ function overlappingRange(ranges, candidate) {
89
+ let low = 0;
90
+ let high = ranges.length;
91
+ while (low < high) {
92
+ const middle = Math.floor((low + high) / 2);
93
+ if (ranges[middle].end <= candidate.start) low = middle + 1;
94
+ else high = middle;
95
+ }
96
+ const range = ranges[low];
97
+ return range && range.start < candidate.end ? range : void 0;
98
+ }
99
+ function assertSingleScopedConsent(spans, mapping) {
100
+ const url = spans.map((span) => span.text).join("");
101
+ const concrete = [];
102
+ const sources = [];
103
+ let offset = 0;
104
+ for (const span of spans) {
105
+ const range = { start: offset, end: offset + span.text.length };
106
+ if (span.concrete && range.start < range.end) {
107
+ const previous = concrete[concrete.length - 1];
108
+ if (previous?.end === range.start) previous.end = range.end;
109
+ else concrete.push(range);
110
+ }
111
+ if (span.scopedSource && span.text.length > 0) sources.push({ ...range, macro: span.scopedSource });
112
+ offset = range.end;
113
+ }
114
+ const occurrences = [];
115
+ const shells = [];
116
+ for (const token of macroTokens(url)) {
117
+ const range = { start: token.start, end: token.end };
118
+ shells.push(range);
119
+ const canonical = token.text.startsWith("${") ? token.text.slice(1) : token.text;
120
+ if ((0, import_consent_macros.isVendorScopedConsentMacro)(canonical)) {
121
+ occurrences.push({ ...range, macro: canonical });
122
+ }
123
+ }
124
+ const aliases = [];
125
+ for (const [macro, entry] of Object.entries(mapping)) {
126
+ if (!(0, import_consent_macros.isVendorScopedConsentMacro)(macro) || !("native" in entry) || entry.native.length === 0) continue;
127
+ let start = url.indexOf(entry.native);
128
+ while (start !== -1) {
129
+ const range = { start, end: start + entry.native.length };
130
+ const shell = overlappingRange(shells, range);
131
+ const completeShell = shell?.start === range.start && shell.end === range.end;
132
+ const insideShell = shell && shell.start <= range.start && range.end <= shell.end && !completeShell;
133
+ const data = overlappingRange(concrete, range);
134
+ const entirelyConcrete = data && data.start <= range.start && range.end <= data.end;
135
+ if (!insideShell && (completeShell || !entirelyConcrete) && !overlappingRange(occurrences, range)) {
136
+ aliases.push({ ...range, macro });
137
+ }
138
+ start = url.indexOf(entry.native, range.end);
139
+ }
140
+ }
141
+ aliases.sort((a, b) => a.start - b.start || b.end - a.end);
142
+ const selectedAliases = [];
143
+ for (const alias of aliases) {
144
+ const previous = selectedAliases[selectedAliases.length - 1];
145
+ if (!previous || previous.end <= alias.start) selectedAliases.push(alias);
146
+ }
147
+ for (const alias of selectedAliases) occurrences.push(alias);
148
+ occurrences.sort((a, b) => a.start - b.start);
149
+ const unmatchedSources = sources.filter((source) => !overlappingRange(occurrences, source));
150
+ for (const source of unmatchedSources) occurrences.push(source);
151
+ occurrences.sort((a, b) => a.start - b.start);
152
+ const second = occurrences[1];
153
+ if (second) throw new MultipleVendorConsentMacrosError(second.macro);
154
+ return url;
155
+ }
49
156
  function translateUniversalMacros(input_pixel_url, mapping) {
50
157
  const suspect_native_values = [];
51
158
  const suspectSeen = /* @__PURE__ */ new Set();
@@ -58,11 +165,11 @@ function translateUniversalMacros(input_pixel_url, mapping) {
58
165
  }
59
166
  continue;
60
167
  }
61
- if (CONSENT_MACROS.has(macro) && !frozenConsentSeen.has(macro)) {
168
+ if ((0, import_consent_macros.isConsentMacro)(macro) && !frozenConsentSeen.has(macro)) {
62
169
  frozenConsentSeen.add(macro);
63
170
  frozen_consent_macros.push(macro);
64
171
  }
65
- if (NATIVE_TOKEN_SHAPE.test(entry.value) && !suspectSeen.has(macro)) {
172
+ if (hasNativeTokenSyntax(entry.value) && !suspectSeen.has(macro)) {
66
173
  suspectSeen.add(macro);
67
174
  suspect_native_values.push(macro);
68
175
  }
@@ -73,7 +180,7 @@ function translateUniversalMacros(input_pixel_url, mapping) {
73
180
  const queryIdx = withoutFragment.indexOf("?");
74
181
  if (queryIdx === -1) {
75
182
  return {
76
- url: input_pixel_url,
183
+ url: assertSingleScopedConsent([{ text: input_pixel_url }], mapping),
77
184
  dropped_params: [],
78
185
  unmapped_macros: [],
79
186
  dropped_consent_macros: [],
@@ -93,12 +200,12 @@ function translateUniversalMacros(input_pixel_url, mapping) {
93
200
  const eqIdx = rawParam.indexOf("=");
94
201
  const key = eqIdx === -1 ? rawParam : rawParam.slice(0, eqIdx);
95
202
  const value = eqIdx === -1 ? "" : rawParam.slice(eqIdx + 1);
96
- const tokens = value.match(UNIVERSAL_MACRO);
97
- if (!tokens) {
98
- outputParts.push(rawParam);
203
+ const tokens = [...macroTokens(value)].filter((token) => !token.native);
204
+ if (tokens.length === 0) {
205
+ outputParts.push([{ text: rawParam }]);
99
206
  continue;
100
207
  }
101
- const missing = tokens.filter((t) => !(t in mapping));
208
+ const missing = tokens.map((token) => token.text).filter((token) => !(token in mapping));
102
209
  if (missing.length > 0) {
103
210
  dropped_params.push(key);
104
211
  for (const m of missing) {
@@ -106,24 +213,38 @@ function translateUniversalMacros(input_pixel_url, mapping) {
106
213
  unmappedSeen.add(m);
107
214
  unmapped_macros.push(m);
108
215
  }
109
- if (CONSENT_MACROS.has(m) && !consentSeen.has(m)) {
216
+ if ((0, import_consent_macros.isConsentMacro)(m) && !consentSeen.has(m)) {
110
217
  consentSeen.add(m);
111
218
  dropped_consent_macros.push(m);
112
219
  }
113
220
  }
114
221
  continue;
115
222
  }
116
- const translated = value.replace(UNIVERSAL_MACRO, (token) => {
223
+ const translated = [{ text: `${key}=` }];
224
+ let cursor = 0;
225
+ for (const match of tokens) {
226
+ const token = match.text;
227
+ translated.push({ text: value.slice(cursor, match.start) });
117
228
  const entry = mapping[token];
118
- if (!entry) {
119
- return token;
229
+ if ("native" in entry) {
230
+ translated.push({
231
+ text: entry.native,
232
+ ...(0, import_consent_macros.isVendorScopedConsentMacro)(token) ? { scopedSource: token } : {}
233
+ });
234
+ } else {
235
+ translated.push({ text: (0, import_rfc3986.encodeUnreserved)(entry.value), concrete: true });
120
236
  }
121
- return "native" in entry ? entry.native : (0, import_rfc3986.encodeUnreserved)(entry.value);
122
- });
123
- outputParts.push(`${key}=${translated}`);
237
+ cursor = match.end;
238
+ }
239
+ translated.push({ text: value.slice(cursor) });
240
+ outputParts.push(translated);
124
241
  }
125
- const newQuery = outputParts.join("&");
126
- const url = newQuery ? `${base}?${newQuery}${fragment}` : `${base}${fragment}`;
242
+ const querySpans = outputParts.flatMap((part, index) => index === 0 ? part : [{ text: "&" }, ...part]);
243
+ const hasQuery = querySpans.some((span) => span.text.length > 0);
244
+ const url = assertSingleScopedConsent(
245
+ [{ text: base }, ...hasQuery ? [{ text: "?" }, ...querySpans] : [], { text: fragment }],
246
+ mapping
247
+ );
127
248
  return {
128
249
  url,
129
250
  dropped_params,
@@ -135,6 +256,7 @@ function translateUniversalMacros(input_pixel_url, mapping) {
135
256
  }
136
257
  // Annotate the CommonJS export names for ESM import in node:
137
258
  0 && (module.exports = {
259
+ MultipleVendorConsentMacrosError,
138
260
  UnsafeNativeMappingError,
139
261
  translateUniversalMacros
140
262
  });
@@ -1,14 +1,43 @@
1
1
  import { encodeUnreserved } from "./rfc3986.mjs";
2
- const UNIVERSAL_MACRO = /\{[A-Z][A-Z0-9_]*\}/g;
3
- const NATIVE_TOKEN_SHAPE = /^(?:%%.+%%|\{\{.+\}\}|\$\{.+\}|\[[A-Z][A-Z0-9_]*\])$/;
4
- const CONSENT_MACROS = /* @__PURE__ */ new Set([
5
- "{GDPR}",
6
- "{GDPR_CONSENT}",
7
- "{US_PRIVACY}",
8
- "{GPP_STRING}",
9
- "{GPP_SID}",
10
- "{LIMIT_AD_TRACKING}"
11
- ]);
2
+ import { isConsentMacro, isVendorScopedConsentMacro } from "./consent-macros.mjs";
3
+ function* macroTokens(text) {
4
+ const missingClosers = /* @__PURE__ */ new Set();
5
+ let cursor = 0;
6
+ while (cursor < text.length) {
7
+ let end = -1;
8
+ let native = true;
9
+ const close = text.startsWith("%%", cursor) ? "%%" : text.startsWith("{{", cursor) ? "}}" : text.startsWith("${", cursor) ? "}" : null;
10
+ if (close && !missingClosers.has(close)) {
11
+ const closer = text.indexOf(close, cursor + 2);
12
+ if (closer === -1) missingClosers.add(close);
13
+ else end = closer + close.length;
14
+ }
15
+ if (end === -1 && (text[cursor] === "{" || text[cursor] === "[")) {
16
+ let nameEnd = cursor + 1;
17
+ if (/[A-Z]/.test(text[nameEnd] ?? "")) {
18
+ nameEnd += 1;
19
+ while (/[A-Z0-9_]/.test(text[nameEnd] ?? "")) nameEnd += 1;
20
+ if (text[nameEnd] === (text[cursor] === "{" ? "}" : "]")) {
21
+ end = nameEnd + 1;
22
+ native = text[cursor] === "[";
23
+ }
24
+ }
25
+ }
26
+ if (end === -1) {
27
+ cursor += 1;
28
+ continue;
29
+ }
30
+ yield { text: text.slice(cursor, end), start: cursor, end, native };
31
+ cursor = end;
32
+ }
33
+ }
34
+ function hasNativeTokenSyntax(value) {
35
+ for (const token of macroTokens(value)) {
36
+ const minimumLength = token.text.startsWith("${") ? 3 : token.text.startsWith("[") ? 2 : 4;
37
+ if (token.native && token.text.length > minimumLength) return true;
38
+ }
39
+ return false;
40
+ }
12
41
  const UNSAFE_NATIVE_CHARACTER = /[\u0000-\u001F\u007F]/;
13
42
  class UnsafeNativeMappingError extends Error {
14
43
  code = "unsafe_native_mapping";
@@ -22,6 +51,83 @@ class UnsafeNativeMappingError extends Error {
22
51
  this.macro = macro;
23
52
  }
24
53
  }
54
+ class MultipleVendorConsentMacrosError extends Error {
55
+ code = "multiple_vendor_consent_macros";
56
+ macro;
57
+ constructor(macro) {
58
+ super(`Multiple vendor-scoped consent macros; second token is ${JSON.stringify(macro)}`);
59
+ this.name = "MultipleVendorConsentMacrosError";
60
+ this.macro = macro;
61
+ }
62
+ }
63
+ function overlappingRange(ranges, candidate) {
64
+ let low = 0;
65
+ let high = ranges.length;
66
+ while (low < high) {
67
+ const middle = Math.floor((low + high) / 2);
68
+ if (ranges[middle].end <= candidate.start) low = middle + 1;
69
+ else high = middle;
70
+ }
71
+ const range = ranges[low];
72
+ return range && range.start < candidate.end ? range : void 0;
73
+ }
74
+ function assertSingleScopedConsent(spans, mapping) {
75
+ const url = spans.map((span) => span.text).join("");
76
+ const concrete = [];
77
+ const sources = [];
78
+ let offset = 0;
79
+ for (const span of spans) {
80
+ const range = { start: offset, end: offset + span.text.length };
81
+ if (span.concrete && range.start < range.end) {
82
+ const previous = concrete[concrete.length - 1];
83
+ if (previous?.end === range.start) previous.end = range.end;
84
+ else concrete.push(range);
85
+ }
86
+ if (span.scopedSource && span.text.length > 0) sources.push({ ...range, macro: span.scopedSource });
87
+ offset = range.end;
88
+ }
89
+ const occurrences = [];
90
+ const shells = [];
91
+ for (const token of macroTokens(url)) {
92
+ const range = { start: token.start, end: token.end };
93
+ shells.push(range);
94
+ const canonical = token.text.startsWith("${") ? token.text.slice(1) : token.text;
95
+ if (isVendorScopedConsentMacro(canonical)) {
96
+ occurrences.push({ ...range, macro: canonical });
97
+ }
98
+ }
99
+ const aliases = [];
100
+ for (const [macro, entry] of Object.entries(mapping)) {
101
+ if (!isVendorScopedConsentMacro(macro) || !("native" in entry) || entry.native.length === 0) continue;
102
+ let start = url.indexOf(entry.native);
103
+ while (start !== -1) {
104
+ const range = { start, end: start + entry.native.length };
105
+ const shell = overlappingRange(shells, range);
106
+ const completeShell = shell?.start === range.start && shell.end === range.end;
107
+ const insideShell = shell && shell.start <= range.start && range.end <= shell.end && !completeShell;
108
+ const data = overlappingRange(concrete, range);
109
+ const entirelyConcrete = data && data.start <= range.start && range.end <= data.end;
110
+ if (!insideShell && (completeShell || !entirelyConcrete) && !overlappingRange(occurrences, range)) {
111
+ aliases.push({ ...range, macro });
112
+ }
113
+ start = url.indexOf(entry.native, range.end);
114
+ }
115
+ }
116
+ aliases.sort((a, b) => a.start - b.start || b.end - a.end);
117
+ const selectedAliases = [];
118
+ for (const alias of aliases) {
119
+ const previous = selectedAliases[selectedAliases.length - 1];
120
+ if (!previous || previous.end <= alias.start) selectedAliases.push(alias);
121
+ }
122
+ for (const alias of selectedAliases) occurrences.push(alias);
123
+ occurrences.sort((a, b) => a.start - b.start);
124
+ const unmatchedSources = sources.filter((source) => !overlappingRange(occurrences, source));
125
+ for (const source of unmatchedSources) occurrences.push(source);
126
+ occurrences.sort((a, b) => a.start - b.start);
127
+ const second = occurrences[1];
128
+ if (second) throw new MultipleVendorConsentMacrosError(second.macro);
129
+ return url;
130
+ }
25
131
  function translateUniversalMacros(input_pixel_url, mapping) {
26
132
  const suspect_native_values = [];
27
133
  const suspectSeen = /* @__PURE__ */ new Set();
@@ -34,11 +140,11 @@ function translateUniversalMacros(input_pixel_url, mapping) {
34
140
  }
35
141
  continue;
36
142
  }
37
- if (CONSENT_MACROS.has(macro) && !frozenConsentSeen.has(macro)) {
143
+ if (isConsentMacro(macro) && !frozenConsentSeen.has(macro)) {
38
144
  frozenConsentSeen.add(macro);
39
145
  frozen_consent_macros.push(macro);
40
146
  }
41
- if (NATIVE_TOKEN_SHAPE.test(entry.value) && !suspectSeen.has(macro)) {
147
+ if (hasNativeTokenSyntax(entry.value) && !suspectSeen.has(macro)) {
42
148
  suspectSeen.add(macro);
43
149
  suspect_native_values.push(macro);
44
150
  }
@@ -49,7 +155,7 @@ function translateUniversalMacros(input_pixel_url, mapping) {
49
155
  const queryIdx = withoutFragment.indexOf("?");
50
156
  if (queryIdx === -1) {
51
157
  return {
52
- url: input_pixel_url,
158
+ url: assertSingleScopedConsent([{ text: input_pixel_url }], mapping),
53
159
  dropped_params: [],
54
160
  unmapped_macros: [],
55
161
  dropped_consent_macros: [],
@@ -69,12 +175,12 @@ function translateUniversalMacros(input_pixel_url, mapping) {
69
175
  const eqIdx = rawParam.indexOf("=");
70
176
  const key = eqIdx === -1 ? rawParam : rawParam.slice(0, eqIdx);
71
177
  const value = eqIdx === -1 ? "" : rawParam.slice(eqIdx + 1);
72
- const tokens = value.match(UNIVERSAL_MACRO);
73
- if (!tokens) {
74
- outputParts.push(rawParam);
178
+ const tokens = [...macroTokens(value)].filter((token) => !token.native);
179
+ if (tokens.length === 0) {
180
+ outputParts.push([{ text: rawParam }]);
75
181
  continue;
76
182
  }
77
- const missing = tokens.filter((t) => !(t in mapping));
183
+ const missing = tokens.map((token) => token.text).filter((token) => !(token in mapping));
78
184
  if (missing.length > 0) {
79
185
  dropped_params.push(key);
80
186
  for (const m of missing) {
@@ -82,24 +188,38 @@ function translateUniversalMacros(input_pixel_url, mapping) {
82
188
  unmappedSeen.add(m);
83
189
  unmapped_macros.push(m);
84
190
  }
85
- if (CONSENT_MACROS.has(m) && !consentSeen.has(m)) {
191
+ if (isConsentMacro(m) && !consentSeen.has(m)) {
86
192
  consentSeen.add(m);
87
193
  dropped_consent_macros.push(m);
88
194
  }
89
195
  }
90
196
  continue;
91
197
  }
92
- const translated = value.replace(UNIVERSAL_MACRO, (token) => {
198
+ const translated = [{ text: `${key}=` }];
199
+ let cursor = 0;
200
+ for (const match of tokens) {
201
+ const token = match.text;
202
+ translated.push({ text: value.slice(cursor, match.start) });
93
203
  const entry = mapping[token];
94
- if (!entry) {
95
- return token;
204
+ if ("native" in entry) {
205
+ translated.push({
206
+ text: entry.native,
207
+ ...isVendorScopedConsentMacro(token) ? { scopedSource: token } : {}
208
+ });
209
+ } else {
210
+ translated.push({ text: encodeUnreserved(entry.value), concrete: true });
96
211
  }
97
- return "native" in entry ? entry.native : encodeUnreserved(entry.value);
98
- });
99
- outputParts.push(`${key}=${translated}`);
212
+ cursor = match.end;
213
+ }
214
+ translated.push({ text: value.slice(cursor) });
215
+ outputParts.push(translated);
100
216
  }
101
- const newQuery = outputParts.join("&");
102
- const url = newQuery ? `${base}?${newQuery}${fragment}` : `${base}${fragment}`;
217
+ const querySpans = outputParts.flatMap((part, index) => index === 0 ? part : [{ text: "&" }, ...part]);
218
+ const hasQuery = querySpans.some((span) => span.text.length > 0);
219
+ const url = assertSingleScopedConsent(
220
+ [{ text: base }, ...hasQuery ? [{ text: "?" }, ...querySpans] : [], { text: fragment }],
221
+ mapping
222
+ );
103
223
  return {
104
224
  url,
105
225
  dropped_params,
@@ -110,6 +230,7 @@ function translateUniversalMacros(input_pixel_url, mapping) {
110
230
  };
111
231
  }
112
232
  export {
233
+ MultipleVendorConsentMacrosError,
113
234
  UnsafeNativeMappingError,
114
235
  translateUniversalMacros
115
236
  };
@@ -55,6 +55,7 @@ var import_protocols = require('../protocols/index.js');
55
55
  var import_node_crypto = require("node:crypto");
56
56
  var import_node_async_hooks = require("node:async_hooks");
57
57
  var import_client_hooks = require('../validation/client-hooks.js');
58
+ var import_schema_loader = require('../validation/schema-loader.js');
58
59
  var import_error_extraction = require('../utils/error-extraction.js');
59
60
  var import_client = require('../signing/client.js');
60
61
  var import_storage_registry = require('../auth/oauth/storage-registry.js');
@@ -534,7 +535,7 @@ async function discoverAgentProfile(client, signal, schemaAdcpVersion) {
534
535
  caps.data,
535
536
  "strict",
536
537
  void 0,
537
- schemaAdcpVersion ?? client.getAdcpVersion()
538
+ (0, import_schema_loader.resolveServedValidationVersion)(caps.data, schemaAdcpVersion ?? client.getAdcpVersion())
538
539
  );
539
540
  if (!validation.valid) {
540
541
  profile.capabilities_schema_issues = validation.issues.map((issue) => ({