@adcp/sdk 16.0.0-beta.7 → 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.
- package/README.md +17 -0
- package/dist/lib/conformance/index.d.mts +2 -0
- package/dist/lib/conformance/index.d.ts +2 -0
- package/dist/lib/conformance/index.js +3 -0
- package/dist/lib/conformance/index.mjs +2 -0
- package/dist/lib/conformance/property-lists.d.mts +14 -0
- package/dist/lib/conformance/property-lists.d.ts +14 -0
- package/dist/lib/conformance/property-lists.js +45 -0
- package/dist/lib/conformance/property-lists.mjs +21 -0
- package/dist/lib/conformance/runConformance.js +20 -1
- package/dist/lib/conformance/runConformance.mjs +20 -1
- package/dist/lib/conformance/types.d.mts +3 -0
- package/dist/lib/conformance/types.d.ts +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -3
- package/dist/lib/core/SingleAgentClient.d.ts +13 -3
- package/dist/lib/core/SingleAgentClient.js +109 -21
- package/dist/lib/core/SingleAgentClient.mjs +112 -22
- package/dist/lib/identifiers.d.mts +53 -0
- package/dist/lib/identifiers.d.ts +53 -0
- package/dist/lib/identifiers.js +212 -0
- package/dist/lib/identifiers.mjs +163 -0
- package/dist/lib/index.d.mts +3 -1
- package/dist/lib/index.d.ts +3 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/media-buy/index.d.mts +1 -1
- package/dist/lib/media-buy/index.d.ts +1 -1
- package/dist/lib/media-buy/property-policy.d.mts +15 -1
- package/dist/lib/media-buy/property-policy.d.ts +15 -1
- package/dist/lib/media-buy/property-policy.js +135 -3
- package/dist/lib/media-buy/property-policy.mjs +134 -3
- package/dist/lib/registry/index.d.mts +12 -0
- package/dist/lib/registry/index.d.ts +12 -0
- package/dist/lib/registry/index.js +159 -5
- package/dist/lib/registry/index.mjs +156 -4
- package/dist/lib/registry/types.d.mts +23 -0
- package/dist/lib/registry/types.d.ts +23 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/decisioning/specialisms/lists.d.mts +4 -4
- package/dist/lib/server/decisioning/specialisms/lists.d.ts +4 -4
- package/dist/lib/server/index.d.mts +3 -0
- package/dist/lib/server/index.d.ts +3 -0
- package/dist/lib/server/index.js +8 -1
- package/dist/lib/server/index.mjs +4 -0
- package/dist/lib/server/property-lists.d.mts +36 -0
- package/dist/lib/server/property-lists.d.ts +36 -0
- package/dist/lib/server/property-lists.js +154 -0
- package/dist/lib/server/property-lists.mjs +129 -0
- package/dist/lib/server/targeting-helpers.d.mts +2 -0
- package/dist/lib/server/targeting-helpers.d.ts +2 -0
- package/dist/lib/server/targeting-helpers.js +28 -1
- package/dist/lib/server/targeting-helpers.mjs +18 -1
- package/dist/lib/signing/parser.js +2 -2
- package/dist/lib/signing/parser.mjs +2 -2
- package/dist/lib/signing/server.d.mts +1 -1
- package/dist/lib/signing/server.d.ts +1 -1
- package/dist/lib/signing/server.mjs +5 -1
- package/dist/lib/signing/webhook-verifier.d.mts +11 -0
- package/dist/lib/signing/webhook-verifier.d.ts +11 -0
- package/dist/lib/signing/webhook-verifier.js +11 -3
- package/dist/lib/signing/webhook-verifier.mjs +11 -3
- package/dist/lib/supply-path/verify.js +12 -2
- package/dist/lib/supply-path/verify.mjs +2 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/PROPERTY-LISTS.md +264 -0
- package/docs/guides/SIGNING-GUIDE.md +25 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +1 -1
|
@@ -163,7 +163,9 @@ import { resolveWebhookUrl, selectWebhookTemplate } from "./webhook-url.mjs";
|
|
|
163
163
|
import { getV25Adapter } from "../adapters/legacy/v2-5/index.mjs";
|
|
164
164
|
import {
|
|
165
165
|
ProductPropertyPolicyError,
|
|
166
|
-
validateProductsAgainstPropertyPolicy
|
|
166
|
+
validateProductsAgainstPropertyPolicy,
|
|
167
|
+
isSupportedPropertyIdentifierType,
|
|
168
|
+
normalizeDomainForPropertyPolicy
|
|
167
169
|
} from "../media-buy/property-policy.mjs";
|
|
168
170
|
import { resolvePropertyList } from "../server/targeting-helpers.mjs";
|
|
169
171
|
import {
|
|
@@ -787,7 +789,9 @@ function productIdForPricingDiagnostics(product) {
|
|
|
787
789
|
return typeof id === "string" ? id : void 0;
|
|
788
790
|
}
|
|
789
791
|
function propertyListReferenceFromRequest(params) {
|
|
790
|
-
const
|
|
792
|
+
const filters = params.filters;
|
|
793
|
+
const nested = filters && typeof filters === "object" && !Array.isArray(filters) && Object.hasOwn(filters, "property_list") && filters.property_list !== void 0;
|
|
794
|
+
const value = nested ? filters.property_list : params.property_list;
|
|
791
795
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
792
796
|
const ref = value;
|
|
793
797
|
if (typeof ref.agent_url !== "string" || typeof ref.list_id !== "string") return void 0;
|
|
@@ -800,21 +804,37 @@ function propertyListReferenceFromRequest(params) {
|
|
|
800
804
|
function productPolicyRequestSnapshot(requestParams) {
|
|
801
805
|
const account = canonicalAccountRoutingSnapshot(requestParams.account);
|
|
802
806
|
const propertyList = propertyListReferenceFromRequest(requestParams);
|
|
807
|
+
const referenceError = propertyListReferenceError(requestParams);
|
|
803
808
|
return Object.freeze({
|
|
804
809
|
...account ? { account } : {},
|
|
805
|
-
...propertyList ? { property_list: Object.freeze(propertyList) } : {}
|
|
810
|
+
...propertyList ? { property_list: Object.freeze(propertyList) } : {},
|
|
811
|
+
...referenceError ? { property_list_error: referenceError } : {}
|
|
806
812
|
});
|
|
807
813
|
}
|
|
814
|
+
function propertyListReferenceError(params) {
|
|
815
|
+
if (typeof params.property_list_error === "string") return params.property_list_error;
|
|
816
|
+
const filters = params.filters;
|
|
817
|
+
if (params.property_list !== void 0 && !propertyListReferenceFromRequest({ property_list: params.property_list }))
|
|
818
|
+
return "property_list_invalid_reference";
|
|
819
|
+
if (!filters || typeof filters !== "object" || Array.isArray(filters) || !Object.hasOwn(filters, "property_list") || filters.property_list === void 0)
|
|
820
|
+
return void 0;
|
|
821
|
+
const nested = propertyListReferenceFromRequest(params);
|
|
822
|
+
if (!nested) return "property_list_invalid_reference";
|
|
823
|
+
if (params.property_list !== void 0) {
|
|
824
|
+
const legacy = propertyListReferenceFromRequest({ property_list: params.property_list });
|
|
825
|
+
if (!legacy || nested.agent_url !== legacy.agent_url || nested.list_id !== legacy.list_id || nested.auth_token !== legacy.auth_token) {
|
|
826
|
+
return "property_list_conflicting_references";
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return void 0;
|
|
830
|
+
}
|
|
808
831
|
function hasProductPropertyPolicyRules(policy) {
|
|
809
832
|
return Boolean(
|
|
810
833
|
policy.allowedDomains?.length || policy.allowedPropertyIdentifiers?.length || policy.requireAllowedPropertyMatch || policy.excludedDomains?.length || policy.excludedPropertyIds?.length || policy.strict || policy.unknownSelectorBehavior || policy.missingPublisherPropertiesBehavior
|
|
811
834
|
);
|
|
812
835
|
}
|
|
813
|
-
function comparablePropertyIdentifiers(identifiers) {
|
|
814
|
-
return identifiers.filter((identifier) => identifier.type === "domain" || identifier.type === "subdomain").map((identifier) => ({ type: identifier.type, value: identifier.value }));
|
|
815
|
-
}
|
|
816
836
|
function unsupportedPropertyIdentifiers(identifiers) {
|
|
817
|
-
return identifiers.filter((identifier) => identifier.type
|
|
837
|
+
return identifiers.filter((identifier) => !isSupportedPropertyIdentifierType(identifier.type)).map((identifier) => ({ type: identifier.type }));
|
|
818
838
|
}
|
|
819
839
|
function sanitizeDiagnosticUrl(value) {
|
|
820
840
|
try {
|
|
@@ -1411,8 +1431,17 @@ class SingleAgentClient {
|
|
|
1411
1431
|
}
|
|
1412
1432
|
assertDurablePropertyListCredentialSupported(taskType, requestParams) {
|
|
1413
1433
|
if (!this.config.deferredStorage || taskType !== "get_products") return;
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1434
|
+
const filters = requestParams.filters;
|
|
1435
|
+
const references = [
|
|
1436
|
+
requestParams.property_list,
|
|
1437
|
+
filters && typeof filters === "object" ? filters.property_list : void 0
|
|
1438
|
+
];
|
|
1439
|
+
const hasCredential = references.some((reference) => {
|
|
1440
|
+
if (!reference || typeof reference !== "object") return false;
|
|
1441
|
+
const token = reference.auth_token;
|
|
1442
|
+
return token !== void 0 && token !== null && token !== "";
|
|
1443
|
+
});
|
|
1444
|
+
if (!hasCredential) return;
|
|
1416
1445
|
const policyConfig = this.config.validation?.productPropertyPolicy;
|
|
1417
1446
|
if (policyConfig === false || policyConfig?.enforceRequestPropertyList === false) return;
|
|
1418
1447
|
throw new ConfigurationError(
|
|
@@ -4223,39 +4252,40 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4223
4252
|
message = "Property list not adhered to",
|
|
4224
4253
|
enforceRequestPropertyList = true,
|
|
4225
4254
|
propertyListResolveOptions,
|
|
4255
|
+
resolvePublisherProperties,
|
|
4256
|
+
maxPublisherLookups = 100,
|
|
4257
|
+
publisherLookupTimeoutMs = 1e4,
|
|
4258
|
+
publisherLookupBudgetMs = 1e4,
|
|
4226
4259
|
...explicitPolicy
|
|
4227
4260
|
} = policyConfig || {};
|
|
4228
4261
|
const policy = {
|
|
4229
4262
|
...explicitPolicy
|
|
4230
4263
|
};
|
|
4231
4264
|
let resolvedRequestPropertyList;
|
|
4232
|
-
|
|
4265
|
+
const referenceError = propertyListReferenceError(requestParams);
|
|
4266
|
+
if ((requestPropertyList || referenceError) && enforceRequestPropertyList) {
|
|
4233
4267
|
try {
|
|
4268
|
+
if (referenceError) throw new Error(referenceError);
|
|
4234
4269
|
const resolved = await resolvePropertyList(requestPropertyList, propertyListResolveOptions);
|
|
4235
|
-
const comparableIdentifiers = comparablePropertyIdentifiers(resolved.identifiers);
|
|
4236
4270
|
const unsupportedIdentifiers = unsupportedPropertyIdentifiers(resolved.identifiers);
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
}
|
|
4240
|
-
if (comparableIdentifiers.length > 0 || resolved.identifiers.length === 0) {
|
|
4241
|
-
policy.allowedPropertyIdentifiers = [...policy.allowedPropertyIdentifiers ?? [], ...comparableIdentifiers];
|
|
4242
|
-
policy.requireAllowedPropertyMatch = true;
|
|
4243
|
-
}
|
|
4271
|
+
policy.allowedPropertyIdentifiers = [...policy.allowedPropertyIdentifiers ?? [], ...resolved.identifiers];
|
|
4272
|
+
policy.requireAllowedPropertyMatch = true;
|
|
4244
4273
|
policy.strict = policy.strict ?? true;
|
|
4245
4274
|
resolvedRequestPropertyList = {
|
|
4246
4275
|
listId: resolved.listId,
|
|
4247
4276
|
agentUrl: resolved.agentUrl,
|
|
4248
4277
|
identifierCount: resolved.identifiers.length,
|
|
4278
|
+
unsupportedIdentifierTypes: [...new Set(unsupportedIdentifiers.map((identifier) => identifier.type))],
|
|
4249
4279
|
...resolved.cacheValidUntil ? { cacheValidUntil: resolved.cacheValidUntil } : {}
|
|
4250
4280
|
};
|
|
4251
4281
|
} catch (err) {
|
|
4252
4282
|
const errorCode = propertyListResolutionErrorCode(err);
|
|
4253
|
-
const diagnosticAgentUrl = sanitizeDiagnosticUrl(requestPropertyList.agent_url);
|
|
4283
|
+
const diagnosticAgentUrl = requestPropertyList ? sanitizeDiagnosticUrl(requestPropertyList.agent_url) : "invalid_url";
|
|
4254
4284
|
return attachMatch({
|
|
4255
4285
|
success: false,
|
|
4256
4286
|
status: "failed",
|
|
4257
4287
|
data: response,
|
|
4258
|
-
error: `${message}: could not resolve property_list
|
|
4288
|
+
error: `${message}: could not resolve property_list`,
|
|
4259
4289
|
metadata: {
|
|
4260
4290
|
...result.metadata,
|
|
4261
4291
|
status: "failed",
|
|
@@ -4268,7 +4298,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4268
4298
|
message,
|
|
4269
4299
|
diagnostics: [],
|
|
4270
4300
|
request_property_list: {
|
|
4271
|
-
list_id: requestPropertyList
|
|
4301
|
+
list_id: requestPropertyList?.list_id ?? "invalid_reference",
|
|
4272
4302
|
agent_url: diagnosticAgentUrl,
|
|
4273
4303
|
resolution_error: errorCode
|
|
4274
4304
|
}
|
|
@@ -4283,7 +4313,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4283
4313
|
mode,
|
|
4284
4314
|
ok: false,
|
|
4285
4315
|
request_property_list: {
|
|
4286
|
-
list_id: requestPropertyList
|
|
4316
|
+
list_id: requestPropertyList?.list_id,
|
|
4287
4317
|
agent_url: diagnosticAgentUrl,
|
|
4288
4318
|
resolution_error: errorCode
|
|
4289
4319
|
}
|
|
@@ -4293,6 +4323,65 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4293
4323
|
}
|
|
4294
4324
|
}
|
|
4295
4325
|
if (!hasProductPropertyPolicyRules(policy)) return result;
|
|
4326
|
+
if (resolvePublisherProperties && policy.allowedPropertyIdentifiers?.some(
|
|
4327
|
+
(identifier) => identifier.type !== "domain" && identifier.type !== "subdomain"
|
|
4328
|
+
)) {
|
|
4329
|
+
if (!Number.isSafeInteger(maxPublisherLookups) || maxPublisherLookups < 1 || !Number.isSafeInteger(publisherLookupTimeoutMs) || publisherLookupTimeoutMs < 1 || publisherLookupTimeoutMs > 2147483647 || !Number.isSafeInteger(publisherLookupBudgetMs) || publisherLookupBudgetMs < 1 || publisherLookupBudgetMs > 2147483647) {
|
|
4330
|
+
throw new ConfigurationError("Publisher lookup limits must be positive bounded integers");
|
|
4331
|
+
}
|
|
4332
|
+
const { validatePublisherDomain } = await import("../identifiers.mjs");
|
|
4333
|
+
const catalogs = Object.assign(
|
|
4334
|
+
/* @__PURE__ */ Object.create(null),
|
|
4335
|
+
policy.publisherProperties
|
|
4336
|
+
);
|
|
4337
|
+
const domains = new Set(
|
|
4338
|
+
response.products.flatMap(
|
|
4339
|
+
(product) => Array.isArray(product.publisher_properties) ? product.publisher_properties.flatMap(
|
|
4340
|
+
(selector) => selector && typeof selector === "object" && "publisher_domain" in selector ? [selector.publisher_domain] : []
|
|
4341
|
+
) : []
|
|
4342
|
+
)
|
|
4343
|
+
);
|
|
4344
|
+
const hosts = [
|
|
4345
|
+
...new Set(
|
|
4346
|
+
[...domains].flatMap(
|
|
4347
|
+
(domain) => typeof domain === "string" ? [normalizeDomainForPropertyPolicy(domain)?.host] : []
|
|
4348
|
+
)
|
|
4349
|
+
)
|
|
4350
|
+
].filter((host) => typeof host === "string" && !Object.hasOwn(catalogs, host));
|
|
4351
|
+
for (const host of hosts) catalogs[host] = void 0;
|
|
4352
|
+
const lookupStartedAt = performance.now();
|
|
4353
|
+
for (const host of hosts.slice(0, maxPublisherLookups)) {
|
|
4354
|
+
throwIfAborted(taskSignal);
|
|
4355
|
+
const remainingBudgetMs = publisherLookupBudgetMs - (performance.now() - lookupStartedAt);
|
|
4356
|
+
if (remainingBudgetMs <= 0) break;
|
|
4357
|
+
if (!validatePublisherDomain(host).ok || isLikelyPrivateUrl(`https://${host}`)) continue;
|
|
4358
|
+
const controller = new AbortController();
|
|
4359
|
+
const signal = taskSignal ? AbortSignal.any([taskSignal, controller.signal]) : controller.signal;
|
|
4360
|
+
let timeout;
|
|
4361
|
+
let budgetExpired = false;
|
|
4362
|
+
try {
|
|
4363
|
+
catalogs[host] = await Promise.race([
|
|
4364
|
+
resolvePublisherProperties(host, { signal }),
|
|
4365
|
+
new Promise((_, reject) => {
|
|
4366
|
+
timeout = setTimeout(
|
|
4367
|
+
() => {
|
|
4368
|
+
if (remainingBudgetMs <= publisherLookupTimeoutMs) budgetExpired = true;
|
|
4369
|
+
controller.abort();
|
|
4370
|
+
reject(new Error("publisher_catalog_timeout"));
|
|
4371
|
+
},
|
|
4372
|
+
Math.min(publisherLookupTimeoutMs, remainingBudgetMs)
|
|
4373
|
+
);
|
|
4374
|
+
})
|
|
4375
|
+
]);
|
|
4376
|
+
} catch {
|
|
4377
|
+
catalogs[host] = void 0;
|
|
4378
|
+
} finally {
|
|
4379
|
+
if (timeout) clearTimeout(timeout);
|
|
4380
|
+
}
|
|
4381
|
+
if (budgetExpired) break;
|
|
4382
|
+
}
|
|
4383
|
+
policy.publisherProperties = catalogs;
|
|
4384
|
+
}
|
|
4296
4385
|
let validation;
|
|
4297
4386
|
try {
|
|
4298
4387
|
validation = validateProductsAgainstPropertyPolicy({
|
|
@@ -4316,6 +4405,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4316
4405
|
list_id: resolvedRequestPropertyList.listId,
|
|
4317
4406
|
agent_url: sanitizeDiagnosticUrl(resolvedRequestPropertyList.agentUrl),
|
|
4318
4407
|
identifier_count: resolvedRequestPropertyList.identifierCount,
|
|
4408
|
+
unsupported_identifier_types: resolvedRequestPropertyList.unsupportedIdentifierTypes,
|
|
4319
4409
|
...resolvedRequestPropertyList.cacheValidUntil ? { cache_valid_until: resolvedRequestPropertyList.cacheValidUntil } : {}
|
|
4320
4410
|
}
|
|
4321
4411
|
} : {},
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PropertyIdentifierTypes } from './types/core.generated.mjs';
|
|
2
|
+
export type IdentifierValidationErrorCode = 'invalid_identifier' | 'unsupported_identifier_type' | 'invalid_publisher_domain';
|
|
3
|
+
export declare class IdentifierValidationError extends TypeError {
|
|
4
|
+
readonly code: IdentifierValidationErrorCode;
|
|
5
|
+
readonly identifierType: string;
|
|
6
|
+
readonly name = "IdentifierValidationError";
|
|
7
|
+
constructor(code: IdentifierValidationErrorCode, identifierType: string, message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface IdentifierRewrite {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
}
|
|
13
|
+
export type IdentifierValidationResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
value: string;
|
|
16
|
+
rewrite?: IdentifierRewrite;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
error: IdentifierValidationError;
|
|
20
|
+
};
|
|
21
|
+
/** Validate a registration hostname as supplied. This deliberately does not repair URLs or wildcards. */
|
|
22
|
+
export declare function validatePublisherDomain(value: string): IdentifierValidationResult;
|
|
23
|
+
export declare const normalizeDomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
24
|
+
export declare const normalizeSubdomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
25
|
+
/** Bundle identifiers are case-sensitive; normalization preserves their case. */
|
|
26
|
+
export declare const normalizeIosBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
27
|
+
export declare const normalizeAndroidPackageIdentifier: (value: string) => IdentifierValidationResult;
|
|
28
|
+
export declare const normalizeAppleAppStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
29
|
+
export declare const normalizeGooglePlayIdentifier: (value: string) => IdentifierValidationResult;
|
|
30
|
+
export declare const normalizeRokuStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
31
|
+
export declare const normalizeFireTvAsinIdentifier: (value: string) => IdentifierValidationResult;
|
|
32
|
+
export declare const normalizeSamsungAppIdentifier: (value: string) => IdentifierValidationResult;
|
|
33
|
+
export declare const normalizeAppleTvBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
34
|
+
export declare const normalizeBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
35
|
+
/**
|
|
36
|
+
* Validate and canonicalize an AdCP identifier. Opaque identifier families keep
|
|
37
|
+
* their spelling and case; only surrounding whitespace is removed. No network
|
|
38
|
+
* lookup or existence/ownership assertion is performed.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizePropertyIdentifier(type: PropertyIdentifierTypes | string, value: string): IdentifierValidationResult;
|
|
41
|
+
/** Validation includes the same explicit rewrite report as normalization. */
|
|
42
|
+
export declare const validatePropertyIdentifier: typeof normalizePropertyIdentifier;
|
|
43
|
+
export declare const validateDomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
44
|
+
export declare const validateSubdomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
45
|
+
export declare const validateIosBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
46
|
+
export declare const validateAndroidPackageIdentifier: (value: string) => IdentifierValidationResult;
|
|
47
|
+
export declare const validateAppleAppStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
48
|
+
export declare const validateGooglePlayIdentifier: (value: string) => IdentifierValidationResult;
|
|
49
|
+
export declare const validateRokuStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
50
|
+
export declare const validateFireTvAsinIdentifier: (value: string) => IdentifierValidationResult;
|
|
51
|
+
export declare const validateSamsungAppIdentifier: (value: string) => IdentifierValidationResult;
|
|
52
|
+
export declare const validateAppleTvBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
53
|
+
export declare const validateBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PropertyIdentifierTypes } from './types/core.generated';
|
|
2
|
+
export type IdentifierValidationErrorCode = 'invalid_identifier' | 'unsupported_identifier_type' | 'invalid_publisher_domain';
|
|
3
|
+
export declare class IdentifierValidationError extends TypeError {
|
|
4
|
+
readonly code: IdentifierValidationErrorCode;
|
|
5
|
+
readonly identifierType: string;
|
|
6
|
+
readonly name = "IdentifierValidationError";
|
|
7
|
+
constructor(code: IdentifierValidationErrorCode, identifierType: string, message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface IdentifierRewrite {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
}
|
|
13
|
+
export type IdentifierValidationResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
value: string;
|
|
16
|
+
rewrite?: IdentifierRewrite;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
error: IdentifierValidationError;
|
|
20
|
+
};
|
|
21
|
+
/** Validate a registration hostname as supplied. This deliberately does not repair URLs or wildcards. */
|
|
22
|
+
export declare function validatePublisherDomain(value: string): IdentifierValidationResult;
|
|
23
|
+
export declare const normalizeDomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
24
|
+
export declare const normalizeSubdomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
25
|
+
/** Bundle identifiers are case-sensitive; normalization preserves their case. */
|
|
26
|
+
export declare const normalizeIosBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
27
|
+
export declare const normalizeAndroidPackageIdentifier: (value: string) => IdentifierValidationResult;
|
|
28
|
+
export declare const normalizeAppleAppStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
29
|
+
export declare const normalizeGooglePlayIdentifier: (value: string) => IdentifierValidationResult;
|
|
30
|
+
export declare const normalizeRokuStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
31
|
+
export declare const normalizeFireTvAsinIdentifier: (value: string) => IdentifierValidationResult;
|
|
32
|
+
export declare const normalizeSamsungAppIdentifier: (value: string) => IdentifierValidationResult;
|
|
33
|
+
export declare const normalizeAppleTvBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
34
|
+
export declare const normalizeBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
35
|
+
/**
|
|
36
|
+
* Validate and canonicalize an AdCP identifier. Opaque identifier families keep
|
|
37
|
+
* their spelling and case; only surrounding whitespace is removed. No network
|
|
38
|
+
* lookup or existence/ownership assertion is performed.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizePropertyIdentifier(type: PropertyIdentifierTypes | string, value: string): IdentifierValidationResult;
|
|
41
|
+
/** Validation includes the same explicit rewrite report as normalization. */
|
|
42
|
+
export declare const validatePropertyIdentifier: typeof normalizePropertyIdentifier;
|
|
43
|
+
export declare const validateDomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
44
|
+
export declare const validateSubdomainIdentifier: (value: string) => IdentifierValidationResult;
|
|
45
|
+
export declare const validateIosBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
46
|
+
export declare const validateAndroidPackageIdentifier: (value: string) => IdentifierValidationResult;
|
|
47
|
+
export declare const validateAppleAppStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
48
|
+
export declare const validateGooglePlayIdentifier: (value: string) => IdentifierValidationResult;
|
|
49
|
+
export declare const validateRokuStoreIdentifier: (value: string) => IdentifierValidationResult;
|
|
50
|
+
export declare const validateFireTvAsinIdentifier: (value: string) => IdentifierValidationResult;
|
|
51
|
+
export declare const validateSamsungAppIdentifier: (value: string) => IdentifierValidationResult;
|
|
52
|
+
export declare const validateAppleTvBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
53
|
+
export declare const validateBundleIdentifier: (value: string) => IdentifierValidationResult;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var identifiers_exports = {};
|
|
20
|
+
__export(identifiers_exports, {
|
|
21
|
+
IdentifierValidationError: () => IdentifierValidationError,
|
|
22
|
+
normalizeAndroidPackageIdentifier: () => normalizeAndroidPackageIdentifier,
|
|
23
|
+
normalizeAppleAppStoreIdentifier: () => normalizeAppleAppStoreIdentifier,
|
|
24
|
+
normalizeAppleTvBundleIdentifier: () => normalizeAppleTvBundleIdentifier,
|
|
25
|
+
normalizeBundleIdentifier: () => normalizeBundleIdentifier,
|
|
26
|
+
normalizeDomainIdentifier: () => normalizeDomainIdentifier,
|
|
27
|
+
normalizeFireTvAsinIdentifier: () => normalizeFireTvAsinIdentifier,
|
|
28
|
+
normalizeGooglePlayIdentifier: () => normalizeGooglePlayIdentifier,
|
|
29
|
+
normalizeIosBundleIdentifier: () => normalizeIosBundleIdentifier,
|
|
30
|
+
normalizePropertyIdentifier: () => normalizePropertyIdentifier,
|
|
31
|
+
normalizeRokuStoreIdentifier: () => normalizeRokuStoreIdentifier,
|
|
32
|
+
normalizeSamsungAppIdentifier: () => normalizeSamsungAppIdentifier,
|
|
33
|
+
normalizeSubdomainIdentifier: () => normalizeSubdomainIdentifier,
|
|
34
|
+
validateAndroidPackageIdentifier: () => validateAndroidPackageIdentifier,
|
|
35
|
+
validateAppleAppStoreIdentifier: () => validateAppleAppStoreIdentifier,
|
|
36
|
+
validateAppleTvBundleIdentifier: () => validateAppleTvBundleIdentifier,
|
|
37
|
+
validateBundleIdentifier: () => validateBundleIdentifier,
|
|
38
|
+
validateDomainIdentifier: () => validateDomainIdentifier,
|
|
39
|
+
validateFireTvAsinIdentifier: () => validateFireTvAsinIdentifier,
|
|
40
|
+
validateGooglePlayIdentifier: () => validateGooglePlayIdentifier,
|
|
41
|
+
validateIosBundleIdentifier: () => validateIosBundleIdentifier,
|
|
42
|
+
validatePropertyIdentifier: () => validatePropertyIdentifier,
|
|
43
|
+
validatePublisherDomain: () => validatePublisherDomain,
|
|
44
|
+
validateRokuStoreIdentifier: () => validateRokuStoreIdentifier,
|
|
45
|
+
validateSamsungAppIdentifier: () => validateSamsungAppIdentifier,
|
|
46
|
+
validateSubdomainIdentifier: () => validateSubdomainIdentifier
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(identifiers_exports);
|
|
49
|
+
var import_node_net = require("node:net");
|
|
50
|
+
var import_node_url = require("node:url");
|
|
51
|
+
class IdentifierValidationError extends TypeError {
|
|
52
|
+
constructor(code, identifierType, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.code = code;
|
|
55
|
+
this.identifierType = identifierType;
|
|
56
|
+
}
|
|
57
|
+
code;
|
|
58
|
+
identifierType;
|
|
59
|
+
name = "IdentifierValidationError";
|
|
60
|
+
}
|
|
61
|
+
const PUBLISHER_DOMAIN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
|
|
62
|
+
function isDomain(value) {
|
|
63
|
+
if (value.length > 253 || !PUBLISHER_DOMAIN.test(value) || value.split(".").some((label) => label.length > 63) || (0, import_node_net.isIP)(value))
|
|
64
|
+
return false;
|
|
65
|
+
try {
|
|
66
|
+
return !(0, import_node_net.isIP)(new URL(`https://${value}`).hostname);
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function accepted(input, value) {
|
|
72
|
+
return { ok: true, value, ...input === value ? {} : { rewrite: { from: input, to: value } } };
|
|
73
|
+
}
|
|
74
|
+
function rejected(type, code = "invalid_identifier") {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: new IdentifierValidationError(
|
|
78
|
+
code,
|
|
79
|
+
type,
|
|
80
|
+
code === "invalid_publisher_domain" ? "publisher_domain must be a canonical lowercase hostname without wildcards, URLs, or IP addresses" : `Invalid ${type} identifier`
|
|
81
|
+
)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function validatePublisherDomain(value) {
|
|
85
|
+
return typeof value === "string" && isDomain(value) ? accepted(value, value) : rejected("publisher_domain", "invalid_publisher_domain");
|
|
86
|
+
}
|
|
87
|
+
function normalizeHostname(input, type) {
|
|
88
|
+
if (typeof input !== "string") return rejected(type);
|
|
89
|
+
let value = input.trim();
|
|
90
|
+
if (/^https?:\/\//i.test(value)) {
|
|
91
|
+
try {
|
|
92
|
+
const url = new URL(value);
|
|
93
|
+
if (url.username || url.password) return rejected(type);
|
|
94
|
+
value = url.hostname;
|
|
95
|
+
} catch {
|
|
96
|
+
return rejected(type);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const wildcard = value.startsWith("*.");
|
|
100
|
+
if (wildcard && type !== "domain") return rejected(type);
|
|
101
|
+
if (/[\s/\\:@?#%]/.test(wildcard ? value.slice(2) : value)) return rejected(type);
|
|
102
|
+
const host = (0, import_node_url.domainToASCII)((wildcard ? value.slice(2) : value).toLowerCase().replace(/\.$/, ""));
|
|
103
|
+
if (!isDomain(host)) return rejected(type);
|
|
104
|
+
return accepted(input, `${wildcard ? "*." : ""}${host}`);
|
|
105
|
+
}
|
|
106
|
+
const normalizeDomainIdentifier = (value) => normalizeHostname(value, "domain");
|
|
107
|
+
const normalizeSubdomainIdentifier = (value) => normalizeHostname(value, "subdomain");
|
|
108
|
+
function normalizePattern(type, input, pattern, uppercase = false) {
|
|
109
|
+
if (typeof input !== "string") return rejected(type);
|
|
110
|
+
const value = uppercase ? input.trim().toUpperCase() : input.trim();
|
|
111
|
+
return pattern.test(value) ? accepted(input, value) : rejected(type);
|
|
112
|
+
}
|
|
113
|
+
const normalizeIosBundleIdentifier = (value) => normalizePattern("ios_bundle", value, /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/);
|
|
114
|
+
const normalizeAndroidPackageIdentifier = (value) => normalizePattern("android_package", value, /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/);
|
|
115
|
+
const normalizeAppleAppStoreIdentifier = (value) => normalizePattern("apple_app_store_id", value, /^[1-9][0-9]*$/);
|
|
116
|
+
const normalizeGooglePlayIdentifier = (value) => normalizePattern("google_play_id", value, /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/);
|
|
117
|
+
const normalizeRokuStoreIdentifier = (value) => normalizePattern("roku_store_id", value, /^[1-9][0-9]*$/);
|
|
118
|
+
const normalizeFireTvAsinIdentifier = (value) => normalizePattern("fire_tv_asin", value, /^[A-Z0-9]{10}$/, true);
|
|
119
|
+
const normalizeSamsungAppIdentifier = (value) => normalizePattern("samsung_app_id", value, /^[A-Za-z0-9._-]+$/);
|
|
120
|
+
const normalizeAppleTvBundleIdentifier = (value) => normalizePattern("apple_tv_bundle", value, /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/);
|
|
121
|
+
const normalizeBundleIdentifier = (value) => normalizePattern("bundle_id", value, /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+$/);
|
|
122
|
+
function normalizePropertyIdentifier(type, value) {
|
|
123
|
+
switch (type) {
|
|
124
|
+
case "domain":
|
|
125
|
+
return normalizeDomainIdentifier(value);
|
|
126
|
+
case "subdomain":
|
|
127
|
+
return normalizeSubdomainIdentifier(value);
|
|
128
|
+
case "ios_bundle":
|
|
129
|
+
return normalizeIosBundleIdentifier(value);
|
|
130
|
+
case "android_package":
|
|
131
|
+
return normalizeAndroidPackageIdentifier(value);
|
|
132
|
+
case "apple_app_store_id":
|
|
133
|
+
return normalizeAppleAppStoreIdentifier(value);
|
|
134
|
+
case "google_play_id":
|
|
135
|
+
return normalizeGooglePlayIdentifier(value);
|
|
136
|
+
case "roku_store_id":
|
|
137
|
+
return normalizeRokuStoreIdentifier(value);
|
|
138
|
+
case "fire_tv_asin":
|
|
139
|
+
return normalizeFireTvAsinIdentifier(value);
|
|
140
|
+
case "samsung_app_id":
|
|
141
|
+
return normalizeSamsungAppIdentifier(value);
|
|
142
|
+
case "apple_tv_bundle":
|
|
143
|
+
return normalizeAppleTvBundleIdentifier(value);
|
|
144
|
+
case "bundle_id":
|
|
145
|
+
return normalizeBundleIdentifier(value);
|
|
146
|
+
case "apple_podcast_id":
|
|
147
|
+
return normalizePattern(type, value, /^[1-9][0-9]*$/);
|
|
148
|
+
case "rss_url": {
|
|
149
|
+
if (typeof value !== "string") return rejected(type);
|
|
150
|
+
try {
|
|
151
|
+
const url = new URL(value.trim());
|
|
152
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || !isDomain(url.hostname))
|
|
153
|
+
return rejected(type);
|
|
154
|
+
return accepted(value, url.toString());
|
|
155
|
+
} catch {
|
|
156
|
+
return rejected(type);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
case "network_id":
|
|
160
|
+
case "venue_id":
|
|
161
|
+
case "screen_id":
|
|
162
|
+
case "openooh_venue_type":
|
|
163
|
+
case "spotify_collection_id":
|
|
164
|
+
case "podcast_guid":
|
|
165
|
+
case "station_id":
|
|
166
|
+
case "facility_id":
|
|
167
|
+
return normalizePattern(type, value, /^[^\x00-\x1f\x7f]+$/);
|
|
168
|
+
default:
|
|
169
|
+
return rejected(type, "unsupported_identifier_type");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const validatePropertyIdentifier = normalizePropertyIdentifier;
|
|
173
|
+
const validateDomainIdentifier = normalizeDomainIdentifier;
|
|
174
|
+
const validateSubdomainIdentifier = normalizeSubdomainIdentifier;
|
|
175
|
+
const validateIosBundleIdentifier = normalizeIosBundleIdentifier;
|
|
176
|
+
const validateAndroidPackageIdentifier = normalizeAndroidPackageIdentifier;
|
|
177
|
+
const validateAppleAppStoreIdentifier = normalizeAppleAppStoreIdentifier;
|
|
178
|
+
const validateGooglePlayIdentifier = normalizeGooglePlayIdentifier;
|
|
179
|
+
const validateRokuStoreIdentifier = normalizeRokuStoreIdentifier;
|
|
180
|
+
const validateFireTvAsinIdentifier = normalizeFireTvAsinIdentifier;
|
|
181
|
+
const validateSamsungAppIdentifier = normalizeSamsungAppIdentifier;
|
|
182
|
+
const validateAppleTvBundleIdentifier = normalizeAppleTvBundleIdentifier;
|
|
183
|
+
const validateBundleIdentifier = normalizeBundleIdentifier;
|
|
184
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
185
|
+
0 && (module.exports = {
|
|
186
|
+
IdentifierValidationError,
|
|
187
|
+
normalizeAndroidPackageIdentifier,
|
|
188
|
+
normalizeAppleAppStoreIdentifier,
|
|
189
|
+
normalizeAppleTvBundleIdentifier,
|
|
190
|
+
normalizeBundleIdentifier,
|
|
191
|
+
normalizeDomainIdentifier,
|
|
192
|
+
normalizeFireTvAsinIdentifier,
|
|
193
|
+
normalizeGooglePlayIdentifier,
|
|
194
|
+
normalizeIosBundleIdentifier,
|
|
195
|
+
normalizePropertyIdentifier,
|
|
196
|
+
normalizeRokuStoreIdentifier,
|
|
197
|
+
normalizeSamsungAppIdentifier,
|
|
198
|
+
normalizeSubdomainIdentifier,
|
|
199
|
+
validateAndroidPackageIdentifier,
|
|
200
|
+
validateAppleAppStoreIdentifier,
|
|
201
|
+
validateAppleTvBundleIdentifier,
|
|
202
|
+
validateBundleIdentifier,
|
|
203
|
+
validateDomainIdentifier,
|
|
204
|
+
validateFireTvAsinIdentifier,
|
|
205
|
+
validateGooglePlayIdentifier,
|
|
206
|
+
validateIosBundleIdentifier,
|
|
207
|
+
validatePropertyIdentifier,
|
|
208
|
+
validatePublisherDomain,
|
|
209
|
+
validateRokuStoreIdentifier,
|
|
210
|
+
validateSamsungAppIdentifier,
|
|
211
|
+
validateSubdomainIdentifier
|
|
212
|
+
});
|