@adcp/sdk 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/adapters/version/3.0/get-products.d.ts +3 -0
- package/dist/lib/adapters/version/3.0/get-products.d.ts.map +1 -0
- package/dist/lib/adapters/version/3.0/get-products.js +48 -0
- package/dist/lib/adapters/version/3.0/get-products.js.map +1 -0
- package/dist/lib/adapters/version/3.0/index.d.ts.map +1 -1
- package/dist/lib/adapters/version/3.0/index.js +2 -1
- package/dist/lib/adapters/version/3.0/index.js.map +1 -1
- package/dist/lib/brand/index.d.ts +32 -0
- package/dist/lib/brand/index.d.ts.map +1 -1
- package/dist/lib/brand/index.js +137 -0
- package/dist/lib/brand/index.js.map +1 -1
- package/dist/lib/discovery/validate-adagents.js +1 -1
- package/dist/lib/discovery/validate-adagents.js.map +1 -1
- package/dist/lib/index.d.ts +4 -4
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +15 -13
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/registry/index.d.ts +6 -9
- package/dist/lib/registry/index.d.ts.map +1 -1
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/property-registry.d.ts +6 -0
- package/dist/lib/registry/property-registry.d.ts.map +1 -1
- package/dist/lib/registry/property-registry.js +10 -1
- package/dist/lib/registry/property-registry.js.map +1 -1
- package/dist/lib/registry/sync.d.ts +77 -4
- package/dist/lib/registry/sync.d.ts.map +1 -1
- package/dist/lib/registry/sync.js +480 -64
- package/dist/lib/registry/sync.js.map +1 -1
- package/dist/lib/registry/types.d.ts +21 -2
- package/dist/lib/registry/types.d.ts.map +1 -1
- package/dist/lib/registry/types.generated.d.ts +4 -8
- package/dist/lib/registry/types.generated.d.ts.map +1 -1
- package/dist/lib/registry/types.generated.js +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +43 -1
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/utils/creative-adapter.d.ts +16 -10
- package/dist/lib/utils/creative-adapter.d.ts.map +1 -1
- package/dist/lib/utils/creative-adapter.js +40 -24
- package/dist/lib/utils/creative-adapter.js.map +1 -1
- package/dist/lib/utils/request-normalizer.d.ts +8 -1
- package/dist/lib/utils/request-normalizer.d.ts.map +1 -1
- package/dist/lib/utils/request-normalizer.js +14 -16
- package/dist/lib/utils/request-normalizer.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -56,7 +56,9 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
56
56
|
maxStreamFailures;
|
|
57
57
|
indexAgents;
|
|
58
58
|
indexAuthorizations;
|
|
59
|
+
indexProperties;
|
|
59
60
|
indexBrandHierarchies;
|
|
61
|
+
ignoredEventHandler;
|
|
60
62
|
errorHandler;
|
|
61
63
|
cursorStore;
|
|
62
64
|
_state = 'idle';
|
|
@@ -78,6 +80,10 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
78
80
|
agents = new Map();
|
|
79
81
|
authByDomain = new Map();
|
|
80
82
|
authByAgent = new Map();
|
|
83
|
+
authLocationsById = new Map();
|
|
84
|
+
propertiesByRid = new Map();
|
|
85
|
+
propertyRidsByDomain = new Map();
|
|
86
|
+
propertyAliasesByRid = new Map();
|
|
81
87
|
brandAncestorsByDomain = new Map();
|
|
82
88
|
brandHierarchyByDomain = new Map();
|
|
83
89
|
brandHierarchyKeysByEntity = new Map();
|
|
@@ -106,8 +112,10 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
106
112
|
this.maxStreamFailures = config.maxStreamFailures ?? 3;
|
|
107
113
|
this.indexAgents = config.indexes?.agents !== false;
|
|
108
114
|
this.indexAuthorizations = config.indexes?.authorizations !== false;
|
|
115
|
+
this.indexProperties = config.indexes?.properties !== false;
|
|
109
116
|
this.indexBrandHierarchies = config.indexes?.brandHierarchies !== false;
|
|
110
117
|
this.cursorStore = config.cursorStore ?? new cursor_store_1.InMemoryCursorStore();
|
|
118
|
+
this.ignoredEventHandler = config.onIgnoredEvent;
|
|
111
119
|
this.errorHandler = config.onError;
|
|
112
120
|
}
|
|
113
121
|
// ====== Lifecycle ======
|
|
@@ -196,7 +204,7 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
196
204
|
if (filter.min_properties != null && p.property_count < filter.min_properties)
|
|
197
205
|
return false;
|
|
198
206
|
if (filter.compliance_status?.length) {
|
|
199
|
-
const status = agent.compliance_summary?.status ?? 'unknown';
|
|
207
|
+
const status = (agent.compliance_summary?.status ?? 'unknown');
|
|
200
208
|
if (!filter.compliance_status.includes(status))
|
|
201
209
|
return false;
|
|
202
210
|
}
|
|
@@ -206,11 +214,18 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
206
214
|
// ====== Authorization Lookups ======
|
|
207
215
|
/** Get all authorizations for a publisher domain. */
|
|
208
216
|
getAuthorizationsForDomain(domain) {
|
|
209
|
-
return this.authByDomain.get(domain) ?? [];
|
|
217
|
+
return this.authByDomain.get(this.authDomainKey(domain)) ?? [];
|
|
210
218
|
}
|
|
211
219
|
/** Get all authorizations for an agent. */
|
|
212
220
|
getAuthorizationsForAgent(agentUrl) {
|
|
213
|
-
|
|
221
|
+
const entries = [];
|
|
222
|
+
for (const key of this.authAgentKeysForUrl(agentUrl)) {
|
|
223
|
+
for (const entry of this.authByAgent.get(key) ?? []) {
|
|
224
|
+
if (!entries.includes(entry))
|
|
225
|
+
entries.push(entry);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return entries;
|
|
214
229
|
}
|
|
215
230
|
/**
|
|
216
231
|
* Check if an agent has any authorization for a publisher domain.
|
|
@@ -223,8 +238,27 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
223
238
|
* authorization after a missed revocation.
|
|
224
239
|
*/
|
|
225
240
|
isAuthorized(agentUrl, domain) {
|
|
226
|
-
const entries = this.authByDomain.get(domain);
|
|
227
|
-
return entries != null && entries.some(
|
|
241
|
+
const entries = this.authByDomain.get(this.authDomainKey(domain));
|
|
242
|
+
return entries != null && entries.some(entry => this.authorizationAgentMatches(entry, agentUrl));
|
|
243
|
+
}
|
|
244
|
+
// ====== Property Lookups ======
|
|
245
|
+
/** Get a property feed record by property_rid. Merged aliases resolve to their canonical RID. */
|
|
246
|
+
getProperty(rid) {
|
|
247
|
+
const property = this.propertiesByRid.get(this.resolvePropertyRid(rid));
|
|
248
|
+
return property ? this.cloneProperty(property) : undefined;
|
|
249
|
+
}
|
|
250
|
+
/** Get all property feed records for a publisher domain. */
|
|
251
|
+
getPropertiesForDomain(domain) {
|
|
252
|
+
const rids = this.propertyRidsByDomain.get(this.normalizeDomainKey(domain));
|
|
253
|
+
if (!rids)
|
|
254
|
+
return [];
|
|
255
|
+
const properties = [];
|
|
256
|
+
for (const rid of rids) {
|
|
257
|
+
const property = this.propertiesByRid.get(this.resolvePropertyRid(rid));
|
|
258
|
+
if (property)
|
|
259
|
+
properties.push(this.cloneProperty(property));
|
|
260
|
+
}
|
|
261
|
+
return properties;
|
|
228
262
|
}
|
|
229
263
|
// ====== Brand Hierarchy Lookups ======
|
|
230
264
|
/**
|
|
@@ -254,6 +288,7 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
254
288
|
return {
|
|
255
289
|
agents: this.agents.size,
|
|
256
290
|
authorizations: authCount,
|
|
291
|
+
properties: this.propertiesByRid.size,
|
|
257
292
|
brandHierarchies: this.brandHierarchyKeysByEntity.size,
|
|
258
293
|
};
|
|
259
294
|
}
|
|
@@ -694,37 +729,29 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
694
729
|
}
|
|
695
730
|
// ====== Private: Event Application ======
|
|
696
731
|
applyEvent(event) {
|
|
697
|
-
const payload = event.payload;
|
|
732
|
+
const payload = this.asRecord(event.payload);
|
|
698
733
|
switch (event.event_type) {
|
|
699
734
|
case 'agent.discovered':
|
|
700
735
|
case 'agent.profile_updated': {
|
|
701
736
|
if (!this.indexAgents)
|
|
702
737
|
break;
|
|
703
738
|
const existing = this.agents.get(event.entity_id);
|
|
704
|
-
|
|
739
|
+
const inventoryProfile = this.isAgentInventoryProfile(payload.inventory_profile)
|
|
740
|
+
? payload.inventory_profile
|
|
741
|
+
: undefined;
|
|
742
|
+
if (existing && inventoryProfile) {
|
|
705
743
|
this.agents.set(event.entity_id, {
|
|
706
744
|
...existing,
|
|
707
|
-
inventory_profile:
|
|
745
|
+
inventory_profile: inventoryProfile,
|
|
708
746
|
});
|
|
709
747
|
}
|
|
710
748
|
else if (!existing) {
|
|
711
749
|
// New agent: use payload data or create stub
|
|
712
750
|
this.agents.set(event.entity_id, {
|
|
713
751
|
url: event.entity_id,
|
|
714
|
-
name: payload.name
|
|
715
|
-
type: payload.type
|
|
716
|
-
inventory_profile:
|
|
717
|
-
channels: [],
|
|
718
|
-
property_types: [],
|
|
719
|
-
markets: [],
|
|
720
|
-
categories: [],
|
|
721
|
-
category_taxonomy: 'iab_content_3.0',
|
|
722
|
-
tags: [],
|
|
723
|
-
delivery_types: [],
|
|
724
|
-
property_count: 0,
|
|
725
|
-
publisher_count: 0,
|
|
726
|
-
has_tmp: false,
|
|
727
|
-
},
|
|
752
|
+
name: typeof payload.name === 'string' ? payload.name : event.entity_id,
|
|
753
|
+
type: typeof payload.type === 'string' ? payload.type : 'unknown',
|
|
754
|
+
inventory_profile: inventoryProfile ?? this.emptyInventoryProfile(),
|
|
728
755
|
match: { score: 0, matched_filters: [] },
|
|
729
756
|
});
|
|
730
757
|
}
|
|
@@ -734,62 +761,47 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
734
761
|
if (this.indexAgents)
|
|
735
762
|
this.agents.delete(event.entity_id);
|
|
736
763
|
if (this.indexAuthorizations)
|
|
737
|
-
this.
|
|
764
|
+
this.removeAuthorizationsForAgent(event.entity_id);
|
|
738
765
|
break;
|
|
739
766
|
}
|
|
740
767
|
case 'authorization.granted': {
|
|
741
768
|
if (!this.indexAuthorizations)
|
|
742
769
|
break;
|
|
743
|
-
|
|
744
|
-
if (!entry.agent_url || !entry.publisher_domain || !entry.authorization_type)
|
|
770
|
+
if (!this.isAuthorizationPayload(payload))
|
|
745
771
|
break;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (!
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
772
|
+
if (typeof payload.authorization_type !== 'string')
|
|
773
|
+
break;
|
|
774
|
+
this.upsertAuthorizationEntry(this.authorizationEntryFromEvent(payload));
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case 'authorization.modified': {
|
|
778
|
+
if (!this.indexAuthorizations)
|
|
779
|
+
break;
|
|
780
|
+
if (!this.isAuthorizationPayload(payload))
|
|
781
|
+
break;
|
|
782
|
+
const entry = this.authorizationEntryFromEvent(payload);
|
|
783
|
+
if (!entry.id && !entry.authorization_type)
|
|
784
|
+
break;
|
|
785
|
+
this.upsertAuthorizationEntry(entry, { fallbackToTupleOnMissingRowId: true });
|
|
756
786
|
break;
|
|
757
787
|
}
|
|
758
788
|
case 'authorization.revoked': {
|
|
759
789
|
if (!this.indexAuthorizations)
|
|
760
790
|
break;
|
|
761
|
-
|
|
762
|
-
const domain = payload.publisher_domain;
|
|
763
|
-
const authType = payload.authorization_type;
|
|
764
|
-
if (!agentUrl || !domain)
|
|
791
|
+
if (!this.isAuthorizationPayload(payload))
|
|
765
792
|
break;
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
else
|
|
772
|
-
this.authByDomain.delete(domain);
|
|
773
|
-
}
|
|
774
|
-
const agentEntries = this.authByAgent.get(agentUrl);
|
|
775
|
-
if (agentEntries) {
|
|
776
|
-
const filtered = agentEntries.filter(e => !(e.publisher_domain === domain && (!authType || e.authorization_type === authType)));
|
|
777
|
-
if (filtered.length > 0)
|
|
778
|
-
this.authByAgent.set(agentUrl, filtered);
|
|
779
|
-
else
|
|
780
|
-
this.authByAgent.delete(agentUrl);
|
|
781
|
-
}
|
|
793
|
+
const entry = this.authorizationEntryFromEvent(payload);
|
|
794
|
+
this.removeAuthorizationEntries(entry, {
|
|
795
|
+
matchByRowId: Boolean(entry.id || !entry.authorization_type),
|
|
796
|
+
fallbackToTupleOnMissingRowId: true,
|
|
797
|
+
});
|
|
782
798
|
break;
|
|
783
799
|
}
|
|
784
800
|
case 'agent.compliance_changed': {
|
|
785
801
|
if (this.indexAgents) {
|
|
786
802
|
const existing = this.agents.get(event.entity_id);
|
|
787
803
|
const summary = payload.compliance_summary;
|
|
788
|
-
if (existing &&
|
|
789
|
-
summary &&
|
|
790
|
-
typeof summary === 'object' &&
|
|
791
|
-
'status' in summary &&
|
|
792
|
-
typeof summary.status === 'string') {
|
|
804
|
+
if (existing && this.isAgentCompliance(summary)) {
|
|
793
805
|
this.agents.set(event.entity_id, {
|
|
794
806
|
...existing,
|
|
795
807
|
compliance_summary: summary,
|
|
@@ -807,6 +819,19 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
807
819
|
});
|
|
808
820
|
break;
|
|
809
821
|
}
|
|
822
|
+
case 'property.created':
|
|
823
|
+
case 'property.updated':
|
|
824
|
+
case 'property.reactivated':
|
|
825
|
+
case 'property.stale': {
|
|
826
|
+
if (this.indexProperties)
|
|
827
|
+
this.upsertPropertyEvent(payload);
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
case 'property.merged': {
|
|
831
|
+
if (this.indexProperties)
|
|
832
|
+
this.handlePropertyMerge(payload);
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
810
835
|
case 'brand.hierarchy_updated':
|
|
811
836
|
case 'brand.updated':
|
|
812
837
|
case 'brand.resolved': {
|
|
@@ -820,8 +845,20 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
820
845
|
this.deleteBrandHierarchy(event.entity_id, payload);
|
|
821
846
|
break;
|
|
822
847
|
}
|
|
823
|
-
|
|
848
|
+
case 'collection.created':
|
|
849
|
+
case 'collection.updated':
|
|
850
|
+
case 'collection.merged':
|
|
851
|
+
case 'collection.removed': {
|
|
852
|
+
this.ignoreEvent(event, 'collection.* indexing requires typed collection schemas and a reference consumer');
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
case 'publisher.adagents_changed':
|
|
856
|
+
case 'publisher.adagents_discovered': {
|
|
857
|
+
this.ignoreEvent(event, 'publisher.* indexing requires a reference consumer');
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
824
860
|
default:
|
|
861
|
+
this.ignoreEvent(event, 'unknown or unsupported registry feed event type');
|
|
825
862
|
break;
|
|
826
863
|
}
|
|
827
864
|
}
|
|
@@ -830,11 +867,301 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
830
867
|
this.agents.clear();
|
|
831
868
|
this.authByDomain.clear();
|
|
832
869
|
this.authByAgent.clear();
|
|
870
|
+
this.authLocationsById.clear();
|
|
871
|
+
this.propertiesByRid.clear();
|
|
872
|
+
this.propertyRidsByDomain.clear();
|
|
873
|
+
this.propertyAliasesByRid.clear();
|
|
833
874
|
this.brandAncestorsByDomain.clear();
|
|
834
875
|
this.brandHierarchyByDomain.clear();
|
|
835
876
|
this.brandHierarchyKeysByEntity.clear();
|
|
836
877
|
this.brandHierarchyEntityByKey.clear();
|
|
837
878
|
}
|
|
879
|
+
ignoreEvent(event, reason) {
|
|
880
|
+
this.ignoredEventHandler?.(event, reason);
|
|
881
|
+
this.emit('ignoredEvent', { event, reason });
|
|
882
|
+
}
|
|
883
|
+
upsertAuthorizationEntry(entry, options) {
|
|
884
|
+
const normalizedEntry = this.normalizeAuthorizationEntry(entry);
|
|
885
|
+
this.removeAuthorizationEntries(normalizedEntry, {
|
|
886
|
+
matchByRowId: Boolean(normalizedEntry.id),
|
|
887
|
+
fallbackToTupleOnMissingRowId: options?.fallbackToTupleOnMissingRowId,
|
|
888
|
+
});
|
|
889
|
+
const domainKey = this.authDomainKey(normalizedEntry.publisher_domain);
|
|
890
|
+
const domainEntries = this.authByDomain.get(domainKey) ?? [];
|
|
891
|
+
domainEntries.push(normalizedEntry);
|
|
892
|
+
this.authByDomain.set(domainKey, domainEntries);
|
|
893
|
+
const agentKey = this.authAgentKey(normalizedEntry);
|
|
894
|
+
for (const key of this.authAgentKeys(normalizedEntry)) {
|
|
895
|
+
const agentEntries = this.authByAgent.get(key) ?? [];
|
|
896
|
+
agentEntries.push(normalizedEntry);
|
|
897
|
+
this.authByAgent.set(key, agentEntries);
|
|
898
|
+
}
|
|
899
|
+
if (normalizedEntry.id) {
|
|
900
|
+
this.authLocationsById.set(normalizedEntry.id, {
|
|
901
|
+
agentKey,
|
|
902
|
+
domainKey,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
normalizeAuthorizationEntry(entry) {
|
|
907
|
+
if (entry.effective_until || !entry.effective_to)
|
|
908
|
+
return entry;
|
|
909
|
+
return { ...entry, effective_until: entry.effective_to };
|
|
910
|
+
}
|
|
911
|
+
authorizationEntryFromEvent(entry) {
|
|
912
|
+
return this.normalizeAuthorizationEntry(entry);
|
|
913
|
+
}
|
|
914
|
+
removeAuthorizationsForAgent(agentUrl) {
|
|
915
|
+
const agentKeys = this.authAgentKeysForUrl(agentUrl);
|
|
916
|
+
for (const [key, entries] of this.authByAgent) {
|
|
917
|
+
if (entries.some(entry => this.authorizationAgentMatches(entry, agentUrl)))
|
|
918
|
+
agentKeys.add(key);
|
|
919
|
+
}
|
|
920
|
+
for (const agentKey of agentKeys) {
|
|
921
|
+
const entries = [...(this.authByAgent.get(agentKey) ?? [])];
|
|
922
|
+
for (const entry of entries) {
|
|
923
|
+
this.removeAuthorizationEntries(entry, {
|
|
924
|
+
matchByRowId: Boolean(entry.id),
|
|
925
|
+
fallbackToTupleOnMissingRowId: true,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
this.authByAgent.delete(agentKey);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
removeAuthorizationEntries(target, options) {
|
|
932
|
+
const normalizedTarget = this.normalizeAuthorizationEntry(target);
|
|
933
|
+
const domainKeys = new Set([this.authDomainKey(normalizedTarget.publisher_domain)]);
|
|
934
|
+
const agentKeys = this.authAgentKeys(normalizedTarget);
|
|
935
|
+
let effectiveOptions = options;
|
|
936
|
+
if (normalizedTarget.id && options?.matchByRowId) {
|
|
937
|
+
const existingLocation = this.authLocationsById.get(normalizedTarget.id);
|
|
938
|
+
if (!existingLocation && options.fallbackToTupleOnMissingRowId) {
|
|
939
|
+
effectiveOptions = { ...options, matchByRowId: false };
|
|
940
|
+
}
|
|
941
|
+
if (existingLocation) {
|
|
942
|
+
domainKeys.add(existingLocation.domainKey);
|
|
943
|
+
agentKeys.add(existingLocation.agentKey);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
for (const domain of domainKeys) {
|
|
947
|
+
const entries = this.authByDomain.get(domain);
|
|
948
|
+
if (!entries)
|
|
949
|
+
continue;
|
|
950
|
+
for (const entry of entries) {
|
|
951
|
+
if (this.authorizationMatches(entry, normalizedTarget, effectiveOptions)) {
|
|
952
|
+
for (const key of this.authAgentKeys(entry))
|
|
953
|
+
agentKeys.add(key);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (const domain of domainKeys) {
|
|
958
|
+
this.removeAuthorizationEntriesFromBucket(this.authByDomain, domain, normalizedTarget, effectiveOptions);
|
|
959
|
+
}
|
|
960
|
+
for (const agentUrl of agentKeys) {
|
|
961
|
+
this.removeAuthorizationEntriesFromBucket(this.authByAgent, agentUrl, normalizedTarget, effectiveOptions);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
removeAuthorizationEntriesFromBucket(bucket, key, target, options) {
|
|
965
|
+
const entries = bucket.get(key);
|
|
966
|
+
if (!entries)
|
|
967
|
+
return;
|
|
968
|
+
const filtered = [];
|
|
969
|
+
for (const entry of entries) {
|
|
970
|
+
if (this.authorizationMatches(entry, target, options)) {
|
|
971
|
+
if (entry.id)
|
|
972
|
+
this.authLocationsById.delete(entry.id);
|
|
973
|
+
}
|
|
974
|
+
else {
|
|
975
|
+
filtered.push(entry);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (filtered.length > 0)
|
|
979
|
+
bucket.set(key, filtered);
|
|
980
|
+
else
|
|
981
|
+
bucket.delete(key);
|
|
982
|
+
}
|
|
983
|
+
authorizationMatches(entry, target, options) {
|
|
984
|
+
if (target.id && options?.matchByRowId)
|
|
985
|
+
return entry.id === target.id;
|
|
986
|
+
if (!this.authorizationEntriesShareAgent(entry, target))
|
|
987
|
+
return false;
|
|
988
|
+
if (this.authDomainKey(entry.publisher_domain) !== this.authDomainKey(target.publisher_domain))
|
|
989
|
+
return false;
|
|
990
|
+
if (target.authorization_type)
|
|
991
|
+
return entry.authorization_type === target.authorization_type;
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
authorizationAgentMatches(entry, agentUrl) {
|
|
995
|
+
return this.authAgentKeysOverlap(this.authAgentKeys(entry), this.authAgentKeysForUrl(agentUrl));
|
|
996
|
+
}
|
|
997
|
+
authorizationEntriesShareAgent(left, right) {
|
|
998
|
+
return this.authAgentKeysOverlap(this.authAgentKeys(left), this.authAgentKeys(right));
|
|
999
|
+
}
|
|
1000
|
+
authDomainKey(domain) {
|
|
1001
|
+
return this.normalizeDomainKey(domain);
|
|
1002
|
+
}
|
|
1003
|
+
authAgentKey(entry) {
|
|
1004
|
+
return this.authAgentKeyFromUrl(entry.agent_url);
|
|
1005
|
+
}
|
|
1006
|
+
authAgentKeys(entry) {
|
|
1007
|
+
const keys = this.authAgentKeysForUrl(entry.agent_url);
|
|
1008
|
+
if (entry.agent_url_canonical?.trim()) {
|
|
1009
|
+
for (const key of this.authAgentKeysForUrl(entry.agent_url_canonical))
|
|
1010
|
+
keys.add(key);
|
|
1011
|
+
}
|
|
1012
|
+
return keys;
|
|
1013
|
+
}
|
|
1014
|
+
authAgentKeysForUrl(agentUrl) {
|
|
1015
|
+
const trimmed = agentUrl.trim();
|
|
1016
|
+
const keys = new Set();
|
|
1017
|
+
if (trimmed)
|
|
1018
|
+
keys.add(trimmed);
|
|
1019
|
+
keys.add(this.authAgentKeyFromUrl(agentUrl));
|
|
1020
|
+
return keys;
|
|
1021
|
+
}
|
|
1022
|
+
authAgentKeysOverlap(left, right) {
|
|
1023
|
+
for (const key of left) {
|
|
1024
|
+
if (right.has(key))
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
authAgentKeyFromUrl(agentUrl) {
|
|
1030
|
+
const trimmed = agentUrl.trim();
|
|
1031
|
+
if (!trimmed)
|
|
1032
|
+
return trimmed;
|
|
1033
|
+
try {
|
|
1034
|
+
const parsed = new URL(trimmed);
|
|
1035
|
+
if (parsed.host)
|
|
1036
|
+
return `${parsed.protocol.toLowerCase()}//${parsed.host.toLowerCase()}`;
|
|
1037
|
+
}
|
|
1038
|
+
catch {
|
|
1039
|
+
// Fall through to best-effort normalization for malformed or scheme-less values.
|
|
1040
|
+
}
|
|
1041
|
+
const schemeEnd = trimmed.indexOf('://');
|
|
1042
|
+
const authorityStart = schemeEnd > 0 ? schemeEnd + 3 : 0;
|
|
1043
|
+
let authorityEnd = trimmed.length;
|
|
1044
|
+
for (const delimiter of ['/', '?', '#']) {
|
|
1045
|
+
const index = trimmed.indexOf(delimiter, authorityStart);
|
|
1046
|
+
if (index >= 0 && index < authorityEnd)
|
|
1047
|
+
authorityEnd = index;
|
|
1048
|
+
}
|
|
1049
|
+
if (authorityEnd === authorityStart)
|
|
1050
|
+
return trimmed.toLowerCase();
|
|
1051
|
+
const scheme = schemeEnd > 0 ? `${trimmed.slice(0, authorityStart).toLowerCase()}` : '';
|
|
1052
|
+
return `${scheme}${trimmed.slice(authorityStart, authorityEnd).toLowerCase()}`;
|
|
1053
|
+
}
|
|
1054
|
+
upsertPropertyEvent(payload) {
|
|
1055
|
+
const rid = this.propertyRidFromPayload(payload);
|
|
1056
|
+
if (!rid)
|
|
1057
|
+
return;
|
|
1058
|
+
const canonicalRid = this.resolvePropertyRid(rid);
|
|
1059
|
+
const existing = this.propertiesByRid.get(canonicalRid);
|
|
1060
|
+
const next = this.propertyFromPayload(canonicalRid, payload, existing);
|
|
1061
|
+
if (!next)
|
|
1062
|
+
return;
|
|
1063
|
+
this.setPropertyEntry(next, existing);
|
|
1064
|
+
}
|
|
1065
|
+
handlePropertyMerge(payload) {
|
|
1066
|
+
if (typeof payload.alias_rid !== 'string' || typeof payload.canonical_rid !== 'string')
|
|
1067
|
+
return;
|
|
1068
|
+
const aliasRid = this.resolvePropertyRid(payload.alias_rid);
|
|
1069
|
+
const canonicalRid = this.resolvePropertyRid(payload.canonical_rid);
|
|
1070
|
+
if (!aliasRid || !canonicalRid || aliasRid === canonicalRid)
|
|
1071
|
+
return;
|
|
1072
|
+
const aliasEntry = this.propertiesByRid.get(aliasRid);
|
|
1073
|
+
const canonicalEntry = this.propertiesByRid.get(canonicalRid);
|
|
1074
|
+
const payloadEntry = this.propertyFromPayload(canonicalRid, payload);
|
|
1075
|
+
this.propertyAliasesByRid.set(payload.alias_rid, canonicalRid);
|
|
1076
|
+
this.propertyAliasesByRid.set(aliasRid, canonicalRid);
|
|
1077
|
+
if (!aliasEntry && !canonicalEntry && !payloadEntry)
|
|
1078
|
+
return;
|
|
1079
|
+
const merged = {
|
|
1080
|
+
...(aliasEntry ? this.cloneProperty(aliasEntry) : {}),
|
|
1081
|
+
...(canonicalEntry ? this.cloneProperty(canonicalEntry) : {}),
|
|
1082
|
+
...(payloadEntry ? this.cloneProperty(payloadEntry) : {}),
|
|
1083
|
+
property_rid: canonicalRid,
|
|
1084
|
+
};
|
|
1085
|
+
if (!merged.publisher_domain) {
|
|
1086
|
+
merged.publisher_domain = canonicalEntry?.publisher_domain ?? aliasEntry?.publisher_domain;
|
|
1087
|
+
}
|
|
1088
|
+
this.deletePropertyEntry(aliasRid);
|
|
1089
|
+
this.setPropertyEntry(merged, canonicalEntry);
|
|
1090
|
+
}
|
|
1091
|
+
propertyFromPayload(rid, payload, existing) {
|
|
1092
|
+
const publisherDomain = this.propertyDomainFromPayload(payload) ?? existing?.publisher_domain;
|
|
1093
|
+
const next = existing ? this.cloneProperty(existing) : { property_rid: rid };
|
|
1094
|
+
let hasPayloadFields = false;
|
|
1095
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
1096
|
+
if (value === undefined || key === 'alias_rid' || key === 'canonical_rid')
|
|
1097
|
+
continue;
|
|
1098
|
+
hasPayloadFields = true;
|
|
1099
|
+
next[key] = this.cloneJsonValue(value);
|
|
1100
|
+
}
|
|
1101
|
+
next.property_rid = rid;
|
|
1102
|
+
if (publisherDomain)
|
|
1103
|
+
next.publisher_domain = publisherDomain;
|
|
1104
|
+
return hasPayloadFields || publisherDomain || existing ? next : null;
|
|
1105
|
+
}
|
|
1106
|
+
setPropertyEntry(entry, previous) {
|
|
1107
|
+
if (previous?.publisher_domain && previous.publisher_domain !== entry.publisher_domain) {
|
|
1108
|
+
this.removePropertyRidFromDomain(entry.property_rid, previous.publisher_domain);
|
|
1109
|
+
}
|
|
1110
|
+
this.propertiesByRid.set(entry.property_rid, this.cloneProperty(entry));
|
|
1111
|
+
if (entry.publisher_domain) {
|
|
1112
|
+
const key = this.normalizeDomainKey(entry.publisher_domain);
|
|
1113
|
+
const rids = this.propertyRidsByDomain.get(key) ?? new Set();
|
|
1114
|
+
rids.add(entry.property_rid);
|
|
1115
|
+
this.propertyRidsByDomain.set(key, rids);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
deletePropertyEntry(rid) {
|
|
1119
|
+
const entry = this.propertiesByRid.get(rid);
|
|
1120
|
+
if (entry?.publisher_domain)
|
|
1121
|
+
this.removePropertyRidFromDomain(rid, entry.publisher_domain);
|
|
1122
|
+
this.propertiesByRid.delete(rid);
|
|
1123
|
+
}
|
|
1124
|
+
removePropertyRidFromDomain(rid, domain) {
|
|
1125
|
+
const key = this.normalizeDomainKey(domain);
|
|
1126
|
+
const rids = this.propertyRidsByDomain.get(key);
|
|
1127
|
+
if (!rids)
|
|
1128
|
+
return;
|
|
1129
|
+
rids.delete(rid);
|
|
1130
|
+
if (rids.size === 0)
|
|
1131
|
+
this.propertyRidsByDomain.delete(key);
|
|
1132
|
+
}
|
|
1133
|
+
propertyRidFromPayload(payload) {
|
|
1134
|
+
if (typeof payload.property_rid === 'string' && payload.property_rid.trim())
|
|
1135
|
+
return payload.property_rid;
|
|
1136
|
+
if (typeof payload.rid === 'string' && payload.rid.trim())
|
|
1137
|
+
return payload.rid;
|
|
1138
|
+
if (typeof payload.id === 'string' && payload.id.trim())
|
|
1139
|
+
return payload.id;
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
propertyDomainFromPayload(payload) {
|
|
1143
|
+
if (typeof payload.publisher_domain === 'string' && payload.publisher_domain.trim())
|
|
1144
|
+
return payload.publisher_domain;
|
|
1145
|
+
const property = this.asRecord(payload.property);
|
|
1146
|
+
return typeof property.publisher_domain === 'string' && property.publisher_domain.trim()
|
|
1147
|
+
? property.publisher_domain
|
|
1148
|
+
: undefined;
|
|
1149
|
+
}
|
|
1150
|
+
stringValue(value) {
|
|
1151
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
1152
|
+
}
|
|
1153
|
+
resolvePropertyRid(rid) {
|
|
1154
|
+
let current = rid;
|
|
1155
|
+
const seen = new Set();
|
|
1156
|
+
while (this.propertyAliasesByRid.has(current) && !seen.has(current)) {
|
|
1157
|
+
seen.add(current);
|
|
1158
|
+
current = this.propertyAliasesByRid.get(current);
|
|
1159
|
+
}
|
|
1160
|
+
return current;
|
|
1161
|
+
}
|
|
1162
|
+
cloneProperty(property) {
|
|
1163
|
+
return this.cloneJsonValue(property);
|
|
1164
|
+
}
|
|
838
1165
|
applyBrandHierarchyEvent(entityId, payload) {
|
|
839
1166
|
const resolvedChain = this.extractResolvedBrandChain(payload.chain);
|
|
840
1167
|
const domainChain = resolvedChain.length > 0
|
|
@@ -906,12 +1233,101 @@ class RegistrySync extends node_events_1.EventEmitter {
|
|
|
906
1233
|
}
|
|
907
1234
|
this.brandHierarchyKeysByEntity.delete(entityKey);
|
|
908
1235
|
}
|
|
1236
|
+
asRecord(value) {
|
|
1237
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1238
|
+
}
|
|
1239
|
+
emptyInventoryProfile() {
|
|
1240
|
+
return {
|
|
1241
|
+
channels: [],
|
|
1242
|
+
property_types: [],
|
|
1243
|
+
markets: [],
|
|
1244
|
+
categories: [],
|
|
1245
|
+
category_taxonomy: 'iab_content_3.0',
|
|
1246
|
+
tags: [],
|
|
1247
|
+
delivery_types: [],
|
|
1248
|
+
property_count: 0,
|
|
1249
|
+
publisher_count: 0,
|
|
1250
|
+
has_tmp: false,
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
isStringArray(value) {
|
|
1254
|
+
return Array.isArray(value) && value.every(item => typeof item === 'string');
|
|
1255
|
+
}
|
|
1256
|
+
isAgentInventoryProfile(value) {
|
|
1257
|
+
const profile = this.asRecord(value);
|
|
1258
|
+
return (this.isStringArray(profile.channels) &&
|
|
1259
|
+
this.isStringArray(profile.property_types) &&
|
|
1260
|
+
this.isStringArray(profile.markets) &&
|
|
1261
|
+
this.isStringArray(profile.categories) &&
|
|
1262
|
+
(typeof profile.category_taxonomy === 'string' || profile.category_taxonomy === null) &&
|
|
1263
|
+
this.isStringArray(profile.tags) &&
|
|
1264
|
+
this.isStringArray(profile.delivery_types) &&
|
|
1265
|
+
(profile.format_ids === undefined || this.isStringArray(profile.format_ids)) &&
|
|
1266
|
+
typeof profile.property_count === 'number' &&
|
|
1267
|
+
typeof profile.publisher_count === 'number' &&
|
|
1268
|
+
typeof profile.has_tmp === 'boolean');
|
|
1269
|
+
}
|
|
1270
|
+
isAuthorizationPayload(value) {
|
|
1271
|
+
const payload = this.asRecord(value);
|
|
1272
|
+
return ((payload.id === undefined || this.stringValue(payload.id) !== undefined) &&
|
|
1273
|
+
this.stringValue(payload.agent_url) !== undefined &&
|
|
1274
|
+
this.stringValue(payload.publisher_domain) !== undefined &&
|
|
1275
|
+
(payload.authorization_type === undefined || this.stringValue(payload.authorization_type) !== undefined) &&
|
|
1276
|
+
(payload.property_ids === undefined || this.isStringArray(payload.property_ids)) &&
|
|
1277
|
+
(payload.property_tags === undefined || this.isStringArray(payload.property_tags)) &&
|
|
1278
|
+
(payload.placement_ids === undefined || this.isStringArray(payload.placement_ids)) &&
|
|
1279
|
+
(payload.placement_tags === undefined || this.isStringArray(payload.placement_tags)) &&
|
|
1280
|
+
(payload.countries === undefined || this.isStringArray(payload.countries)) &&
|
|
1281
|
+
(payload.effective_from === undefined || typeof payload.effective_from === 'string') &&
|
|
1282
|
+
(payload.effective_until === undefined || typeof payload.effective_until === 'string') &&
|
|
1283
|
+
(payload.effective_to === undefined || typeof payload.effective_to === 'string'));
|
|
1284
|
+
}
|
|
1285
|
+
isAgentCompliance(value) {
|
|
1286
|
+
const summary = this.asRecord(value);
|
|
1287
|
+
const statuses = new Set(['passing', 'degraded', 'failing', 'unknown', 'opted_out']);
|
|
1288
|
+
const lifecycleStages = new Set(['development', 'testing', 'production', 'deprecated']);
|
|
1289
|
+
if (summary.status === 'opted_out') {
|
|
1290
|
+
return ((summary.lifecycle_stage === undefined ||
|
|
1291
|
+
(typeof summary.lifecycle_stage === 'string' && lifecycleStages.has(summary.lifecycle_stage))) &&
|
|
1292
|
+
(summary.tracks === undefined ||
|
|
1293
|
+
(summary.tracks != null && typeof summary.tracks === 'object' && !Array.isArray(summary.tracks))) &&
|
|
1294
|
+
(summary.streak_days === undefined || typeof summary.streak_days === 'number') &&
|
|
1295
|
+
(summary.last_checked_at === undefined ||
|
|
1296
|
+
typeof summary.last_checked_at === 'string' ||
|
|
1297
|
+
summary.last_checked_at === null) &&
|
|
1298
|
+
(summary.headline === undefined || typeof summary.headline === 'string' || summary.headline === null));
|
|
1299
|
+
}
|
|
1300
|
+
return (typeof summary.status === 'string' &&
|
|
1301
|
+
statuses.has(summary.status) &&
|
|
1302
|
+
typeof summary.lifecycle_stage === 'string' &&
|
|
1303
|
+
lifecycleStages.has(summary.lifecycle_stage) &&
|
|
1304
|
+
summary.tracks != null &&
|
|
1305
|
+
typeof summary.tracks === 'object' &&
|
|
1306
|
+
!Array.isArray(summary.tracks) &&
|
|
1307
|
+
typeof summary.streak_days === 'number' &&
|
|
1308
|
+
(typeof summary.last_checked_at === 'string' || summary.last_checked_at === null) &&
|
|
1309
|
+
(typeof summary.headline === 'string' || summary.headline === null));
|
|
1310
|
+
}
|
|
1311
|
+
isResolvedBrand(value) {
|
|
1312
|
+
const brand = this.asRecord(value);
|
|
1313
|
+
return typeof brand.canonical_id === 'string' && typeof brand.canonical_domain === 'string';
|
|
1314
|
+
}
|
|
1315
|
+
cloneJsonValue(value) {
|
|
1316
|
+
if (Array.isArray(value))
|
|
1317
|
+
return value.map(item => this.cloneJsonValue(item));
|
|
1318
|
+
if (value && typeof value === 'object') {
|
|
1319
|
+
const copy = {};
|
|
1320
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
1321
|
+
copy[key] = this.cloneJsonValue(nested);
|
|
1322
|
+
}
|
|
1323
|
+
return copy;
|
|
1324
|
+
}
|
|
1325
|
+
return value;
|
|
1326
|
+
}
|
|
909
1327
|
extractResolvedBrandChain(value) {
|
|
910
1328
|
if (!Array.isArray(value))
|
|
911
1329
|
return [];
|
|
912
|
-
|
|
913
|
-
return [];
|
|
914
|
-
return value.filter(item => typeof item.canonical_domain === 'string');
|
|
1330
|
+
return value.filter(item => this.isResolvedBrand(item));
|
|
915
1331
|
}
|
|
916
1332
|
extractDomainChain(value) {
|
|
917
1333
|
if (!Array.isArray(value))
|