@bidkernel/analytics 0.3.0 → 0.5.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/README.md +45 -12
- package/dist/analytics.global.js +1 -1
- package/dist/index.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +749 -116
- package/dist/index.mjs +749 -116
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -657,6 +657,7 @@ var MAX_ERRORS_PER_SESSION = 50;
|
|
|
657
657
|
var MAX_QUEUE_SIZE = 200;
|
|
658
658
|
var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
|
|
659
659
|
var MAX_CONSECUTIVE_SEND_FAILURES = 10;
|
|
660
|
+
var MAX_PAYLOAD_BYTES = 32 * 1024;
|
|
660
661
|
var EVENT_NAME_TO_TYPE = {
|
|
661
662
|
auctionStart: TraceEventType.AUCTION_START,
|
|
662
663
|
auctionEnd: TraceEventType.AUCTION_END,
|
|
@@ -723,47 +724,73 @@ function getOrCreateSessionId() {
|
|
|
723
724
|
inMemorySessionTs = now;
|
|
724
725
|
return inMemorySessionId;
|
|
725
726
|
}
|
|
727
|
+
function escapeKeyPart(str) {
|
|
728
|
+
return String(str ?? "").replace(/%/g, "%25").replace(/:/g, "%3A");
|
|
729
|
+
}
|
|
726
730
|
function getPrebidEventKey(eventName, data) {
|
|
727
731
|
switch (eventName) {
|
|
728
732
|
case "auctionInit":
|
|
729
|
-
return `auctionInit:${data?.auctionId
|
|
733
|
+
return `auctionInit:${escapeKeyPart(data?.auctionId)}`;
|
|
730
734
|
case "auctionEnd":
|
|
731
|
-
return `auctionEnd:${data?.auctionId
|
|
732
|
-
case "bidRequested":
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
case "
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
return
|
|
735
|
+
return `auctionEnd:${escapeKeyPart(data?.auctionId)}`;
|
|
736
|
+
case "bidRequested": {
|
|
737
|
+
const bids = Array.isArray(data?.bids) ? data.bids.map((b) => escapeKeyPart(b?.bidId || b?.transactionId || b?.adUnitCode)).join(",") : "";
|
|
738
|
+
return `bidRequested:${escapeKeyPart(data?.auctionId)}:${escapeKeyPart(data?.bidderCode || data?.bidder)}:${bids}`;
|
|
739
|
+
}
|
|
740
|
+
case "bidResponse": {
|
|
741
|
+
const bid = data || {};
|
|
742
|
+
const uniqueBidId = bid.creativeId || bid.adId || bid.requestId || bid.bidId || "";
|
|
743
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
744
|
+
return `bidResponse:${escapeKeyPart(bid.auctionId)}:${escapeKeyPart(bid.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueBidId)}:${escapeKeyPart(price)}`;
|
|
745
|
+
}
|
|
746
|
+
case "bidTimeout": {
|
|
747
|
+
const items = Array.isArray(data) ? data : data ? [data] : [];
|
|
748
|
+
return "bidTimeout:" + items.map(
|
|
749
|
+
(t) => `${escapeKeyPart(t?.auctionId)}:${escapeKeyPart(t?.bidderCode || t?.bidder)}:${escapeKeyPart(t?.bidId || t?.transactionId || t?.adUnitCode)}`
|
|
750
|
+
).join(",");
|
|
751
|
+
}
|
|
752
|
+
case "bidWon": {
|
|
753
|
+
const bid = data || {};
|
|
754
|
+
const uniqueBidId = bid.creativeId || bid.adId || bid.requestId || bid.bidId || "";
|
|
755
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
756
|
+
return `bidWon:${escapeKeyPart(bid.auctionId)}:${escapeKeyPart(bid.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueBidId)}:${escapeKeyPart(price)}`;
|
|
757
|
+
}
|
|
745
758
|
case "noBid":
|
|
746
|
-
return `noBid:${data?.auctionId
|
|
747
|
-
case "adRenderFailed":
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
759
|
+
return `noBid:${escapeKeyPart(data?.auctionId)}:${escapeKeyPart(data?.adUnitCode)}:${escapeKeyPart(data?.bidderCode || data?.bidder)}:${escapeKeyPart(data?.bidId || data?.transactionId)}`;
|
|
760
|
+
case "adRenderFailed": {
|
|
761
|
+
const bid = data?.bid || {};
|
|
762
|
+
return `adRenderFailed:${escapeKeyPart(bid.auctionId || data?.auctionId)}:${escapeKeyPart(bid.adUnitCode || data?.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder || data?.bidderCode || data?.bidder)}:${escapeKeyPart(data?.reason)}:${escapeKeyPart(data?.message)}`;
|
|
763
|
+
}
|
|
764
|
+
case "adRenderSucceeded": {
|
|
765
|
+
const bid = data?.bid || data || {};
|
|
766
|
+
const uniqueId = bid.creativeId || bid.adId || data?.adId || "";
|
|
767
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
768
|
+
return `adRenderSucceeded:${escapeKeyPart(bid.auctionId || data?.auctionId)}:${escapeKeyPart(bid.adUnitCode || data?.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueId)}:${escapeKeyPart(price)}`;
|
|
769
|
+
}
|
|
751
770
|
case "setTargeting":
|
|
752
|
-
return `setTargeting:${Object.keys(data || {}).sort().join(",")}`;
|
|
771
|
+
return `setTargeting:${Object.keys(data || {}).sort().map(escapeKeyPart).join(",")}`;
|
|
753
772
|
case "auctionDebug":
|
|
754
|
-
return `auctionDebug:${data?.type
|
|
773
|
+
return `auctionDebug:${escapeKeyPart(data?.type)}:${escapeKeyPart(String(data?.arguments?.[0] ?? "").slice(0, 50))}`;
|
|
755
774
|
default:
|
|
756
775
|
return "";
|
|
757
776
|
}
|
|
758
777
|
}
|
|
759
778
|
var PrebidEventDeduper = class {
|
|
760
|
-
|
|
779
|
+
// Object identity is tracked per event name: Prebid passes the SAME bid
|
|
780
|
+
// object to bidResponse and later to bidWon, so a bare WeakSet would drop
|
|
781
|
+
// every bidWon as a duplicate of its own bidResponse.
|
|
782
|
+
seenObjects = /* @__PURE__ */ new WeakMap();
|
|
761
783
|
seenKeys = /* @__PURE__ */ new Set();
|
|
762
784
|
isDuplicate(eventName, data) {
|
|
763
785
|
if (!data) return false;
|
|
764
786
|
if (typeof data === "object") {
|
|
765
|
-
|
|
766
|
-
|
|
787
|
+
const seenEvents = this.seenObjects.get(data);
|
|
788
|
+
if (seenEvents) {
|
|
789
|
+
if (seenEvents.has(eventName)) return true;
|
|
790
|
+
seenEvents.add(eventName);
|
|
791
|
+
} else {
|
|
792
|
+
this.seenObjects.set(data, /* @__PURE__ */ new Set([eventName]));
|
|
793
|
+
}
|
|
767
794
|
}
|
|
768
795
|
const key = getPrebidEventKey(eventName, data);
|
|
769
796
|
if (key) {
|
|
@@ -793,7 +820,23 @@ function parseSize(raw) {
|
|
|
793
820
|
}
|
|
794
821
|
return { width: 0, height: 0 };
|
|
795
822
|
}
|
|
796
|
-
|
|
823
|
+
function parseBidDimensions(bid) {
|
|
824
|
+
if (Number.isFinite(bid?.width) && Number.isFinite(bid?.height)) {
|
|
825
|
+
return { width: bid.width, height: bid.height };
|
|
826
|
+
}
|
|
827
|
+
if (typeof bid?.size === "string") {
|
|
828
|
+
const match = bid.size.match(/^(\d+)x(\d+)$/);
|
|
829
|
+
if (match) {
|
|
830
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
width: Number.isFinite(bid?.width) ? bid.width : 0,
|
|
835
|
+
height: Number.isFinite(bid?.height) ? bid.height : 0
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
839
|
+
static activeInstances = /* @__PURE__ */ new Set();
|
|
797
840
|
config;
|
|
798
841
|
queue = [];
|
|
799
842
|
errorCount = 0;
|
|
@@ -808,6 +851,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
808
851
|
deduper = new PrebidEventDeduper();
|
|
809
852
|
consecutiveSendFailures = 0;
|
|
810
853
|
nextSendAllowedAt = 0;
|
|
854
|
+
replayedEventCount = 0;
|
|
855
|
+
// Viewability & refresh tracking
|
|
856
|
+
intersectionObserver = null;
|
|
857
|
+
slotViewabilityRecords = /* @__PURE__ */ new Map();
|
|
858
|
+
elementToSlotId = /* @__PURE__ */ new Map();
|
|
859
|
+
slotRefreshIndices = /* @__PURE__ */ new Map();
|
|
860
|
+
pendingThresholdListeners = /* @__PURE__ */ new Map();
|
|
811
861
|
constructor(config) {
|
|
812
862
|
this.config = {
|
|
813
863
|
endpoint: config.endpoint || "",
|
|
@@ -821,19 +871,36 @@ var BidkernelPrebidAnalytics = class {
|
|
|
821
871
|
auctionEnabled: config.auctionEnabled ?? true,
|
|
822
872
|
warningsEnabled: config.warningsEnabled ?? true,
|
|
823
873
|
errorsEnabled: config.errorsEnabled ?? true,
|
|
874
|
+
viewabilityEnabled: config.viewabilityEnabled ?? true,
|
|
824
875
|
logLevel: config.logLevel || "INFO",
|
|
825
876
|
pbjsGlobalName: config.pbjsGlobalName || "pbjs",
|
|
826
877
|
attachPbjsListeners: config.attachPbjsListeners ?? true
|
|
827
878
|
};
|
|
828
|
-
this.boundFlushBeacon = () =>
|
|
879
|
+
this.boundFlushBeacon = () => {
|
|
880
|
+
this.flushAllSlotsTimeInView();
|
|
881
|
+
this.flushBeacon();
|
|
882
|
+
};
|
|
829
883
|
this.boundVisibilityChange = () => this.handleVisibilityChange();
|
|
830
884
|
}
|
|
831
885
|
enable() {
|
|
832
886
|
if (this.isEnabled) return;
|
|
833
887
|
this.isEnabled = true;
|
|
888
|
+
_BidkernelPrebidAnalytics.activeInstances.add(this);
|
|
834
889
|
if (!this.config.endpoint) {
|
|
835
890
|
this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
|
|
836
891
|
}
|
|
892
|
+
if (this.config.viewabilityEnabled) {
|
|
893
|
+
if (typeof IntersectionObserver !== "undefined") {
|
|
894
|
+
this.intersectionObserver = new IntersectionObserver(this.handleIntersection.bind(this), {
|
|
895
|
+
threshold: [0.5]
|
|
896
|
+
});
|
|
897
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
898
|
+
if (record.element) {
|
|
899
|
+
this.intersectionObserver.observe(record.element);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
837
904
|
if (this.config.attachPbjsListeners) {
|
|
838
905
|
const win = typeof window !== "undefined" ? window : {};
|
|
839
906
|
const pbjs = win[this.config.pbjsGlobalName] || {};
|
|
@@ -861,28 +928,32 @@ var BidkernelPrebidAnalytics = class {
|
|
|
861
928
|
try {
|
|
862
929
|
const pastEvents = pbjs.getEvents();
|
|
863
930
|
if (Array.isArray(pastEvents)) {
|
|
864
|
-
this.
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
931
|
+
const newPastEvents = pastEvents.slice(this.replayedEventCount);
|
|
932
|
+
this.replayedEventCount = pastEvents.length;
|
|
933
|
+
if (newPastEvents.length > 0) {
|
|
934
|
+
this.log(
|
|
935
|
+
"DEBUG",
|
|
936
|
+
`Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
|
|
937
|
+
);
|
|
938
|
+
const handlerMap = {
|
|
939
|
+
auctionInit: this.handleAuctionInit.bind(this),
|
|
940
|
+
auctionEnd: this.handleAuctionEnd.bind(this),
|
|
941
|
+
bidRequested: this.handleBidRequested.bind(this),
|
|
942
|
+
bidResponse: this.handleBidResponse.bind(this),
|
|
943
|
+
bidTimeout: this.handleBidTimeout.bind(this),
|
|
944
|
+
bidWon: this.handleBidWon.bind(this),
|
|
945
|
+
noBid: this.handleNoBid.bind(this),
|
|
946
|
+
adRenderFailed: this.handleAdRenderFailed.bind(this),
|
|
947
|
+
adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
|
|
948
|
+
};
|
|
949
|
+
for (const ev of newPastEvents) {
|
|
950
|
+
if (!ev) continue;
|
|
951
|
+
const eventType = ev.eventType || ev.event || ev.name;
|
|
952
|
+
const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
|
|
953
|
+
const handler = handlerMap[eventType];
|
|
954
|
+
if (handler) {
|
|
955
|
+
handler(args);
|
|
956
|
+
}
|
|
886
957
|
}
|
|
887
958
|
}
|
|
888
959
|
}
|
|
@@ -909,7 +980,28 @@ var BidkernelPrebidAnalytics = class {
|
|
|
909
980
|
}
|
|
910
981
|
disable() {
|
|
911
982
|
if (!this.isEnabled) return;
|
|
983
|
+
this.flushAllSlotsTimeInView();
|
|
912
984
|
this.isEnabled = false;
|
|
985
|
+
_BidkernelPrebidAnalytics.activeInstances.delete(this);
|
|
986
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
987
|
+
if (record.dwellTimer) {
|
|
988
|
+
clearTimeout(record.dwellTimer);
|
|
989
|
+
record.dwellTimer = null;
|
|
990
|
+
record.dwellStartedAt = null;
|
|
991
|
+
}
|
|
992
|
+
for (const l of record.thresholdListeners) {
|
|
993
|
+
if (l.timer) {
|
|
994
|
+
clearTimeout(l.timer);
|
|
995
|
+
l.timer = null;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (this.intersectionObserver) {
|
|
1000
|
+
this.intersectionObserver.disconnect();
|
|
1001
|
+
this.intersectionObserver = null;
|
|
1002
|
+
}
|
|
1003
|
+
this.slotViewabilityRecords.clear();
|
|
1004
|
+
this.elementToSlotId.clear();
|
|
913
1005
|
if (this.flushTimer) {
|
|
914
1006
|
clearInterval(this.flushTimer);
|
|
915
1007
|
this.flushTimer = null;
|
|
@@ -933,8 +1025,443 @@ var BidkernelPrebidAnalytics = class {
|
|
|
933
1025
|
this.flush();
|
|
934
1026
|
}
|
|
935
1027
|
handleVisibilityChange() {
|
|
936
|
-
if (typeof document
|
|
1028
|
+
if (typeof document === "undefined") return;
|
|
1029
|
+
if (document.visibilityState === "hidden") {
|
|
1030
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1031
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
1032
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1033
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1034
|
+
record.lastEnteredViewAt = null;
|
|
1035
|
+
this.checkThresholdListeners(record);
|
|
1036
|
+
}
|
|
1037
|
+
if (record.dwellTimer) {
|
|
1038
|
+
clearTimeout(record.dwellTimer);
|
|
1039
|
+
record.dwellTimer = null;
|
|
1040
|
+
record.dwellStartedAt = null;
|
|
1041
|
+
}
|
|
1042
|
+
for (const l of record.thresholdListeners) {
|
|
1043
|
+
if (l.timer) {
|
|
1044
|
+
clearTimeout(l.timer);
|
|
1045
|
+
l.timer = null;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
const durationMs = Math.round(record.accumulatedTimeInViewMs);
|
|
1049
|
+
if (durationMs > 0) {
|
|
1050
|
+
this.enqueue(TraceEventType.TIME_IN_VIEW, "timeInView", {
|
|
1051
|
+
auctionId: record.auctionId,
|
|
1052
|
+
transactionId: record.transactionId,
|
|
1053
|
+
adUnitCode: record.adUnitCode,
|
|
1054
|
+
viewableDurationMs: durationMs,
|
|
1055
|
+
bid: record.bidPayload,
|
|
1056
|
+
metadata: {
|
|
1057
|
+
...record.metadata,
|
|
1058
|
+
refresh_index: String(record.refreshIndex)
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
937
1063
|
this.flushBeacon();
|
|
1064
|
+
} else if (document.visibilityState === "visible") {
|
|
1065
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1066
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
1067
|
+
if (record.inView) {
|
|
1068
|
+
record.lastEnteredViewAt = now;
|
|
1069
|
+
if (!record.viewableFired && record.dwellTimer === null) {
|
|
1070
|
+
const dwellTarget = record.mediaType === "video" ? 2e3 : 1e3;
|
|
1071
|
+
record.dwellStartedAt = now;
|
|
1072
|
+
record.dwellTimer = setTimeout(() => {
|
|
1073
|
+
this.handleDwellComplete(record);
|
|
1074
|
+
}, dwellTarget);
|
|
1075
|
+
}
|
|
1076
|
+
this.scheduleThresholdTimers(record);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
handleIntersection(entries) {
|
|
1082
|
+
for (const entry of entries) {
|
|
1083
|
+
let slotId = this.elementToSlotId.get(entry.target);
|
|
1084
|
+
if (!slotId) {
|
|
1085
|
+
for (const instance of _BidkernelPrebidAnalytics.activeInstances) {
|
|
1086
|
+
if (instance !== this && instance.elementToSlotId.has(entry.target)) {
|
|
1087
|
+
instance.handleIntersection([entry]);
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1094
|
+
if (!record) continue;
|
|
1095
|
+
const isViewableRatio = entry.isIntersecting && (entry.intersectionRatio === void 0 || entry.intersectionRatio >= 0.5);
|
|
1096
|
+
const isDocVisible = typeof document === "undefined" || document.visibilityState === "visible";
|
|
1097
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1098
|
+
if (isViewableRatio) {
|
|
1099
|
+
if (!record.inView) {
|
|
1100
|
+
record.inView = true;
|
|
1101
|
+
if (isDocVisible) {
|
|
1102
|
+
record.lastEnteredViewAt = now;
|
|
1103
|
+
if (!record.viewableFired && record.dwellTimer === null) {
|
|
1104
|
+
const dwellTarget = record.mediaType === "video" ? 2e3 : 1e3;
|
|
1105
|
+
record.dwellStartedAt = now;
|
|
1106
|
+
record.dwellTimer = setTimeout(() => {
|
|
1107
|
+
this.handleDwellComplete(record);
|
|
1108
|
+
}, dwellTarget);
|
|
1109
|
+
}
|
|
1110
|
+
this.scheduleThresholdTimers(record);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
} else {
|
|
1114
|
+
if (record.inView) {
|
|
1115
|
+
if (record.lastEnteredViewAt !== null) {
|
|
1116
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1117
|
+
record.lastEnteredViewAt = null;
|
|
1118
|
+
}
|
|
1119
|
+
record.inView = false;
|
|
1120
|
+
this.checkThresholdListeners(record);
|
|
1121
|
+
}
|
|
1122
|
+
if (record.dwellTimer !== null) {
|
|
1123
|
+
clearTimeout(record.dwellTimer);
|
|
1124
|
+
record.dwellTimer = null;
|
|
1125
|
+
record.dwellStartedAt = null;
|
|
1126
|
+
}
|
|
1127
|
+
for (const l of record.thresholdListeners) {
|
|
1128
|
+
if (l.timer) {
|
|
1129
|
+
clearTimeout(l.timer);
|
|
1130
|
+
l.timer = null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
handleDwellComplete(record) {
|
|
1137
|
+
record.dwellTimer = null;
|
|
1138
|
+
record.dwellStartedAt = null;
|
|
1139
|
+
if (!record.inView) return;
|
|
1140
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
1141
|
+
if (!record.viewableFired) {
|
|
1142
|
+
record.viewableFired = true;
|
|
1143
|
+
this.enqueue(TraceEventType.VIEWABLE, "viewable", {
|
|
1144
|
+
auctionId: record.auctionId,
|
|
1145
|
+
transactionId: record.transactionId,
|
|
1146
|
+
adUnitCode: record.adUnitCode,
|
|
1147
|
+
bid: record.bidPayload,
|
|
1148
|
+
metadata: {
|
|
1149
|
+
...record.metadata,
|
|
1150
|
+
refresh_index: String(record.refreshIndex)
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
scheduleThresholdTimers(record) {
|
|
1156
|
+
if (!record.inView || typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1160
|
+
for (const listener of record.thresholdListeners) {
|
|
1161
|
+
if (listener.fired) continue;
|
|
1162
|
+
if (currentTotal >= listener.thresholdMs) {
|
|
1163
|
+
listener.fired = true;
|
|
1164
|
+
if (listener.timer) {
|
|
1165
|
+
clearTimeout(listener.timer);
|
|
1166
|
+
listener.timer = null;
|
|
1167
|
+
}
|
|
1168
|
+
try {
|
|
1169
|
+
listener.callback(record.slotId, currentTotal);
|
|
1170
|
+
} catch (e) {
|
|
1171
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1172
|
+
}
|
|
1173
|
+
} else {
|
|
1174
|
+
if (listener.timer) {
|
|
1175
|
+
clearTimeout(listener.timer);
|
|
1176
|
+
}
|
|
1177
|
+
const remaining = listener.thresholdMs - currentTotal;
|
|
1178
|
+
listener.timer = setTimeout(() => {
|
|
1179
|
+
listener.timer = null;
|
|
1180
|
+
if (!listener.fired && record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1181
|
+
const nowTotal = this.getCurrentTimeInView(record);
|
|
1182
|
+
if (nowTotal >= listener.thresholdMs) {
|
|
1183
|
+
listener.fired = true;
|
|
1184
|
+
try {
|
|
1185
|
+
listener.callback(record.slotId, nowTotal);
|
|
1186
|
+
} catch (e) {
|
|
1187
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}, remaining);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
checkThresholdListeners(record) {
|
|
1196
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1197
|
+
for (const listener of record.thresholdListeners) {
|
|
1198
|
+
if (!listener.fired && currentTotal >= listener.thresholdMs) {
|
|
1199
|
+
listener.fired = true;
|
|
1200
|
+
if (listener.timer) {
|
|
1201
|
+
clearTimeout(listener.timer);
|
|
1202
|
+
listener.timer = null;
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
listener.callback(record.slotId, currentTotal);
|
|
1206
|
+
} catch (e) {
|
|
1207
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
getCurrentTimeInView(record) {
|
|
1213
|
+
let total = record.accumulatedTimeInViewMs;
|
|
1214
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1215
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1216
|
+
total += Math.max(0, now - record.lastEnteredViewAt);
|
|
1217
|
+
}
|
|
1218
|
+
return total;
|
|
1219
|
+
}
|
|
1220
|
+
flushSlotTimeInView(slotId) {
|
|
1221
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1222
|
+
if (!record) return;
|
|
1223
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1224
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1225
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1226
|
+
record.lastEnteredViewAt = null;
|
|
1227
|
+
}
|
|
1228
|
+
const durationMs = Math.round(record.accumulatedTimeInViewMs);
|
|
1229
|
+
if (durationMs > 0) {
|
|
1230
|
+
this.enqueue(TraceEventType.TIME_IN_VIEW, "timeInView", {
|
|
1231
|
+
auctionId: record.auctionId,
|
|
1232
|
+
transactionId: record.transactionId,
|
|
1233
|
+
adUnitCode: record.adUnitCode,
|
|
1234
|
+
viewableDurationMs: durationMs,
|
|
1235
|
+
bid: record.bidPayload,
|
|
1236
|
+
metadata: {
|
|
1237
|
+
...record.metadata,
|
|
1238
|
+
refresh_index: String(record.refreshIndex)
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
record.accumulatedTimeInViewMs = 0;
|
|
1243
|
+
}
|
|
1244
|
+
flushAllSlotsTimeInView() {
|
|
1245
|
+
for (const slotId of Array.from(this.slotViewabilityRecords.keys())) {
|
|
1246
|
+
this.flushSlotTimeInView(slotId);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
observeSlot(element, slotId, options) {
|
|
1250
|
+
if (!this.config.viewabilityEnabled) return;
|
|
1251
|
+
let el = null;
|
|
1252
|
+
if (typeof element === "string") {
|
|
1253
|
+
if (typeof document !== "undefined") {
|
|
1254
|
+
try {
|
|
1255
|
+
el = document.querySelector(element) || document.getElementById(element);
|
|
1256
|
+
} catch {
|
|
1257
|
+
el = document.getElementById(element);
|
|
1258
|
+
}
|
|
1259
|
+
if (!el) {
|
|
1260
|
+
el = document.getElementById(element);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
} else {
|
|
1264
|
+
el = element;
|
|
1265
|
+
}
|
|
1266
|
+
const resolvedSlotId = slotId || options?.adUnitCode || (el ? el.id : "") || generateUUID();
|
|
1267
|
+
const existing = this.slotViewabilityRecords.get(resolvedSlotId);
|
|
1268
|
+
if (existing) {
|
|
1269
|
+
const isNewRefreshCycle = options?.refreshIndex === void 0 || options.refreshIndex > existing.refreshIndex;
|
|
1270
|
+
if (isNewRefreshCycle) {
|
|
1271
|
+
this.flushSlotTimeInView(resolvedSlotId);
|
|
1272
|
+
const nextRefreshIndex = options?.refreshIndex !== void 0 ? options.refreshIndex : (this.slotRefreshIndices.get(resolvedSlotId) ?? existing.refreshIndex) + 1;
|
|
1273
|
+
this.slotRefreshIndices.set(resolvedSlotId, nextRefreshIndex);
|
|
1274
|
+
if (options?.emitRefreshEvent !== false) {
|
|
1275
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1276
|
+
auctionId: options?.auctionId || existing.auctionId,
|
|
1277
|
+
transactionId: options?.transactionId || existing.transactionId,
|
|
1278
|
+
adUnitCode: resolvedSlotId,
|
|
1279
|
+
bid: options?.bid ?? existing.bidPayload,
|
|
1280
|
+
metadata: {
|
|
1281
|
+
...options?.metadata,
|
|
1282
|
+
refresh_index: String(nextRefreshIndex)
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
if (existing.dwellTimer) {
|
|
1287
|
+
clearTimeout(existing.dwellTimer);
|
|
1288
|
+
existing.dwellTimer = null;
|
|
1289
|
+
existing.dwellStartedAt = null;
|
|
1290
|
+
}
|
|
1291
|
+
for (const l of existing.thresholdListeners) {
|
|
1292
|
+
l.fired = false;
|
|
1293
|
+
if (l.timer) {
|
|
1294
|
+
clearTimeout(l.timer);
|
|
1295
|
+
l.timer = null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
existing.viewableFired = false;
|
|
1299
|
+
existing.accumulatedTimeInViewMs = 0;
|
|
1300
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1301
|
+
existing.lastEnteredViewAt = existing.inView ? now : null;
|
|
1302
|
+
existing.refreshIndex = nextRefreshIndex;
|
|
1303
|
+
existing.auctionId = options?.auctionId || existing.auctionId;
|
|
1304
|
+
existing.transactionId = options?.transactionId || existing.transactionId;
|
|
1305
|
+
existing.mediaType = options?.mediaType || existing.mediaType || "banner";
|
|
1306
|
+
existing.bidPayload = options?.bid !== void 0 ? options.bid : existing.bidPayload;
|
|
1307
|
+
existing.metadata = options?.metadata ?? existing.metadata;
|
|
1308
|
+
if (existing.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1309
|
+
const dwellTarget = existing.mediaType === "video" ? 2e3 : 1e3;
|
|
1310
|
+
existing.dwellStartedAt = now;
|
|
1311
|
+
existing.dwellTimer = setTimeout(() => {
|
|
1312
|
+
this.handleDwellComplete(existing);
|
|
1313
|
+
}, dwellTarget);
|
|
1314
|
+
this.scheduleThresholdTimers(existing);
|
|
1315
|
+
}
|
|
1316
|
+
} else {
|
|
1317
|
+
existing.auctionId = options?.auctionId || existing.auctionId;
|
|
1318
|
+
existing.transactionId = options?.transactionId || existing.transactionId;
|
|
1319
|
+
existing.mediaType = options?.mediaType || existing.mediaType;
|
|
1320
|
+
if (options?.bid !== void 0) existing.bidPayload = options.bid;
|
|
1321
|
+
if (options?.metadata !== void 0) existing.metadata = options.metadata;
|
|
1322
|
+
}
|
|
1323
|
+
if (el) {
|
|
1324
|
+
if (existing.element && existing.element !== el && this.intersectionObserver) {
|
|
1325
|
+
this.intersectionObserver.unobserve(existing.element);
|
|
1326
|
+
this.elementToSlotId.delete(existing.element);
|
|
1327
|
+
}
|
|
1328
|
+
existing.element = el;
|
|
1329
|
+
this.elementToSlotId.set(el, resolvedSlotId);
|
|
1330
|
+
this.intersectionObserver?.observe(el);
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
const initialRefreshIndex = options?.refreshIndex ?? this.slotRefreshIndices.get(resolvedSlotId) ?? 0;
|
|
1334
|
+
this.slotRefreshIndices.set(resolvedSlotId, initialRefreshIndex);
|
|
1335
|
+
const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
|
|
1336
|
+
this.pendingThresholdListeners.delete(resolvedSlotId);
|
|
1337
|
+
const record = {
|
|
1338
|
+
slotId: resolvedSlotId,
|
|
1339
|
+
element: el,
|
|
1340
|
+
adUnitCode: options?.adUnitCode || resolvedSlotId,
|
|
1341
|
+
auctionId: options?.auctionId || "",
|
|
1342
|
+
transactionId: options?.transactionId || "",
|
|
1343
|
+
mediaType: options?.mediaType || "banner",
|
|
1344
|
+
bidPayload: options?.bid,
|
|
1345
|
+
metadata: options?.metadata,
|
|
1346
|
+
refreshIndex: initialRefreshIndex,
|
|
1347
|
+
inView: false,
|
|
1348
|
+
lastEnteredViewAt: null,
|
|
1349
|
+
accumulatedTimeInViewMs: 0,
|
|
1350
|
+
viewableFired: false,
|
|
1351
|
+
dwellTimer: null,
|
|
1352
|
+
dwellStartedAt: null,
|
|
1353
|
+
thresholdListeners: listeners
|
|
1354
|
+
};
|
|
1355
|
+
this.slotViewabilityRecords.set(resolvedSlotId, record);
|
|
1356
|
+
if (el) {
|
|
1357
|
+
this.elementToSlotId.set(el, resolvedSlotId);
|
|
1358
|
+
this.intersectionObserver?.observe(el);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
unobserveSlot(slotId) {
|
|
1363
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1364
|
+
if (!record) return;
|
|
1365
|
+
this.flushSlotTimeInView(slotId);
|
|
1366
|
+
if (record.dwellTimer) {
|
|
1367
|
+
clearTimeout(record.dwellTimer);
|
|
1368
|
+
record.dwellTimer = null;
|
|
1369
|
+
record.dwellStartedAt = null;
|
|
1370
|
+
}
|
|
1371
|
+
for (const l of record.thresholdListeners) {
|
|
1372
|
+
if (l.timer) {
|
|
1373
|
+
clearTimeout(l.timer);
|
|
1374
|
+
l.timer = null;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
record.thresholdListeners = /* @__PURE__ */ new Set();
|
|
1378
|
+
if (record.element && this.intersectionObserver) {
|
|
1379
|
+
this.intersectionObserver.unobserve(record.element);
|
|
1380
|
+
this.elementToSlotId.delete(record.element);
|
|
1381
|
+
record.element = null;
|
|
1382
|
+
}
|
|
1383
|
+
record.inView = false;
|
|
1384
|
+
record.lastEnteredViewAt = null;
|
|
1385
|
+
}
|
|
1386
|
+
destroySlot(slotId) {
|
|
1387
|
+
this.unobserveSlot(slotId);
|
|
1388
|
+
this.slotViewabilityRecords.delete(slotId);
|
|
1389
|
+
this.slotRefreshIndices.delete(slotId);
|
|
1390
|
+
this.pendingThresholdListeners.delete(slotId);
|
|
1391
|
+
}
|
|
1392
|
+
onTimeInViewThreshold(slotId, thresholdMs, callback) {
|
|
1393
|
+
const listener = {
|
|
1394
|
+
thresholdMs,
|
|
1395
|
+
callback,
|
|
1396
|
+
timer: null,
|
|
1397
|
+
fired: false
|
|
1398
|
+
};
|
|
1399
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1400
|
+
if (record) {
|
|
1401
|
+
record.thresholdListeners.add(listener);
|
|
1402
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1403
|
+
if (currentTotal >= thresholdMs) {
|
|
1404
|
+
listener.fired = true;
|
|
1405
|
+
try {
|
|
1406
|
+
callback(slotId, currentTotal);
|
|
1407
|
+
} catch (e) {
|
|
1408
|
+
this.log("ERROR", "Error in threshold callback", e);
|
|
1409
|
+
}
|
|
1410
|
+
} else if (record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1411
|
+
const remaining = thresholdMs - currentTotal;
|
|
1412
|
+
listener.timer = setTimeout(() => {
|
|
1413
|
+
listener.timer = null;
|
|
1414
|
+
if (!listener.fired && record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1415
|
+
const nowTotal = this.getCurrentTimeInView(record);
|
|
1416
|
+
if (nowTotal >= thresholdMs) {
|
|
1417
|
+
listener.fired = true;
|
|
1418
|
+
try {
|
|
1419
|
+
callback(slotId, nowTotal);
|
|
1420
|
+
} catch (e) {
|
|
1421
|
+
this.log("ERROR", "Error in threshold callback", e);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}, remaining);
|
|
1426
|
+
}
|
|
1427
|
+
} else {
|
|
1428
|
+
if (!this.pendingThresholdListeners.has(slotId)) {
|
|
1429
|
+
this.pendingThresholdListeners.set(slotId, /* @__PURE__ */ new Set());
|
|
1430
|
+
}
|
|
1431
|
+
this.pendingThresholdListeners.get(slotId).add(listener);
|
|
1432
|
+
}
|
|
1433
|
+
return () => {
|
|
1434
|
+
if (listener.timer) {
|
|
1435
|
+
clearTimeout(listener.timer);
|
|
1436
|
+
listener.timer = null;
|
|
1437
|
+
}
|
|
1438
|
+
if (record) {
|
|
1439
|
+
record.thresholdListeners.delete(listener);
|
|
1440
|
+
}
|
|
1441
|
+
const pending = this.pendingThresholdListeners.get(slotId);
|
|
1442
|
+
if (pending) {
|
|
1443
|
+
pending.delete(listener);
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
getViewabilityState(slotId) {
|
|
1448
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1449
|
+
if (!record) return void 0;
|
|
1450
|
+
const timeInViewMs = Math.round(this.getCurrentTimeInView(record));
|
|
1451
|
+
return {
|
|
1452
|
+
inView: record.inView,
|
|
1453
|
+
viewable: record.viewableFired,
|
|
1454
|
+
timeInViewMs,
|
|
1455
|
+
refreshIndex: record.refreshIndex
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
getRefreshIndex(slotId) {
|
|
1459
|
+
return this.slotRefreshIndices.get(slotId) ?? 0;
|
|
1460
|
+
}
|
|
1461
|
+
triggerSlotRefresh(slotId, options) {
|
|
1462
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1463
|
+
if (record && record.element) {
|
|
1464
|
+
this.observeSlot(record.element, slotId, options);
|
|
938
1465
|
}
|
|
939
1466
|
}
|
|
940
1467
|
trackRawEvent(level, eventName, data) {
|
|
@@ -949,6 +1476,8 @@ var BidkernelPrebidAnalytics = class {
|
|
|
949
1476
|
}
|
|
950
1477
|
navigate(pageviewId, pageUrl) {
|
|
951
1478
|
this.disable();
|
|
1479
|
+
this.slotRefreshIndices.clear();
|
|
1480
|
+
this.pendingThresholdListeners.clear();
|
|
952
1481
|
this.config.pageviewId = pageviewId || generateUUID();
|
|
953
1482
|
if (pageUrl) {
|
|
954
1483
|
try {
|
|
@@ -967,30 +1496,34 @@ var BidkernelPrebidAnalytics = class {
|
|
|
967
1496
|
if (this.isDuplicate("auctionInit", data)) return;
|
|
968
1497
|
this.log("DEBUG", "auctionInit", data);
|
|
969
1498
|
this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
|
|
970
|
-
auctionId: data.auctionId || ""
|
|
1499
|
+
auctionId: data.auctionId || "",
|
|
1500
|
+
transactionId: data.transactionId || ""
|
|
971
1501
|
});
|
|
972
1502
|
}
|
|
973
1503
|
handleAuctionEnd(data) {
|
|
974
1504
|
if (this.isDuplicate("auctionEnd", data)) return;
|
|
975
1505
|
this.log("DEBUG", "auctionEnd", data);
|
|
976
1506
|
this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
|
|
977
|
-
auctionId: data.auctionId || ""
|
|
1507
|
+
auctionId: data.auctionId || "",
|
|
1508
|
+
transactionId: data.transactionId || ""
|
|
978
1509
|
});
|
|
979
1510
|
}
|
|
980
1511
|
handleBidRequested(data) {
|
|
981
1512
|
if (this.isDuplicate("bidRequested", data)) return;
|
|
982
1513
|
this.log("DEBUG", "bidRequested", data);
|
|
983
1514
|
const auctionId = data.auctionId || "";
|
|
984
|
-
const bidder = data.bidderCode || "";
|
|
1515
|
+
const bidder = data.bidderCode || data.bidder || "";
|
|
985
1516
|
if (Array.isArray(data.bids)) {
|
|
986
1517
|
data.bids.forEach((bid) => {
|
|
987
1518
|
const mediaTypes = Object.keys(bid.mediaTypes || {});
|
|
988
1519
|
const gpid = bid.ortb2Imp?.ext?.gpid || bid.gpid || data.gpid || "";
|
|
1520
|
+
const transactionId = bid.transactionId || bid.ortb2Imp?.id || data.transactionId || "";
|
|
989
1521
|
if (mediaTypes.length === 0) {
|
|
990
1522
|
const mediaType = bid.mediaType || "banner";
|
|
991
1523
|
const { width, height } = parseSize(bid.sizes || bid.playerSize);
|
|
992
1524
|
this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
|
|
993
1525
|
auctionId,
|
|
1526
|
+
transactionId,
|
|
994
1527
|
adUnitCode: bid.adUnitCode || "",
|
|
995
1528
|
gpid,
|
|
996
1529
|
bid: {
|
|
@@ -1014,6 +1547,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1014
1547
|
const { width, height } = parseSize(rawSize);
|
|
1015
1548
|
this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
|
|
1016
1549
|
auctionId,
|
|
1550
|
+
transactionId,
|
|
1017
1551
|
adUnitCode: bid.adUnitCode || "",
|
|
1018
1552
|
gpid,
|
|
1019
1553
|
bid: {
|
|
@@ -1032,13 +1566,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1032
1566
|
this.log("DEBUG", "bidResponse", data);
|
|
1033
1567
|
this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
|
|
1034
1568
|
auctionId: data.auctionId || "",
|
|
1569
|
+
transactionId: data.transactionId || "",
|
|
1035
1570
|
adUnitCode: data.adUnitCode || "",
|
|
1036
1571
|
bid: {
|
|
1037
1572
|
bidder: data.bidderCode || data.bidder || "",
|
|
1038
1573
|
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
1039
1574
|
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
1040
|
-
|
|
1041
|
-
height: Number.isFinite(data.height) ? data.height : 0,
|
|
1575
|
+
...parseBidDimensions(data),
|
|
1042
1576
|
dealId: data.dealId || "",
|
|
1043
1577
|
mediaType: data.mediaType || "banner",
|
|
1044
1578
|
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
@@ -1050,15 +1584,16 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1050
1584
|
handleBidTimeout(data) {
|
|
1051
1585
|
if (this.isDuplicate("bidTimeout", data)) return;
|
|
1052
1586
|
this.log("DEBUG", "bidTimeout", data);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1587
|
+
const items = Array.isArray(data) ? data : data ? [data] : [];
|
|
1588
|
+
for (const t of items) {
|
|
1589
|
+
this.enqueue(TraceEventType.BID_TIMEOUT, "bidTimeout", {
|
|
1590
|
+
auctionId: t.auctionId || "",
|
|
1591
|
+
transactionId: t.transactionId || "",
|
|
1592
|
+
adUnitCode: t.adUnitCode || "",
|
|
1593
|
+
bid: {
|
|
1594
|
+
bidder: t.bidderCode || t.bidder || "",
|
|
1595
|
+
latencyMs: Number.isFinite(t.timeout) ? t.timeout : 0
|
|
1596
|
+
}
|
|
1062
1597
|
});
|
|
1063
1598
|
}
|
|
1064
1599
|
}
|
|
@@ -1067,13 +1602,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1067
1602
|
this.log("DEBUG", "bidWon", data);
|
|
1068
1603
|
this.enqueue(TraceEventType.BID_WIN, "bidWon", {
|
|
1069
1604
|
auctionId: data.auctionId || "",
|
|
1605
|
+
transactionId: data.transactionId || "",
|
|
1070
1606
|
adUnitCode: data.adUnitCode || "",
|
|
1071
1607
|
bid: {
|
|
1072
1608
|
bidder: data.bidderCode || data.bidder || "",
|
|
1073
1609
|
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
1074
1610
|
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
1075
|
-
|
|
1076
|
-
height: Number.isFinite(data.height) ? data.height : 0,
|
|
1611
|
+
...parseBidDimensions(data),
|
|
1077
1612
|
dealId: data.dealId || "",
|
|
1078
1613
|
mediaType: data.mediaType || "banner",
|
|
1079
1614
|
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
@@ -1087,6 +1622,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1087
1622
|
this.log("DEBUG", "noBid", data);
|
|
1088
1623
|
this.enqueue(TraceEventType.NO_BID, "noBid", {
|
|
1089
1624
|
auctionId: data.auctionId || "",
|
|
1625
|
+
transactionId: data.transactionId || "",
|
|
1090
1626
|
adUnitCode: data.adUnitCode || "",
|
|
1091
1627
|
bid: {
|
|
1092
1628
|
bidder: data.bidderCode || data.bidder || ""
|
|
@@ -1096,16 +1632,19 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1096
1632
|
handleAdRenderFailed(data) {
|
|
1097
1633
|
if (this.isDuplicate("adRenderFailed", data)) return;
|
|
1098
1634
|
this.log("DEBUG", "adRenderFailed", data);
|
|
1635
|
+
const bid = data.bid || {};
|
|
1099
1636
|
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1100
|
-
auctionId: data.
|
|
1101
|
-
|
|
1637
|
+
auctionId: bid.auctionId || data.auctionId || "",
|
|
1638
|
+
transactionId: bid.transactionId || data.transactionId || "",
|
|
1639
|
+
adUnitCode: bid.adUnitCode || data.adUnitCode || "",
|
|
1102
1640
|
bid: {
|
|
1103
|
-
bidder: data.
|
|
1641
|
+
bidder: bid.bidderCode || bid.bidder || data.bidderCode || data.bidder || ""
|
|
1104
1642
|
},
|
|
1105
1643
|
metadata: {
|
|
1106
1644
|
reason: String(data.reason || ""),
|
|
1107
|
-
message: String(data.message || "")
|
|
1108
|
-
}
|
|
1645
|
+
message: String(data.message || data.error?.message || "")
|
|
1646
|
+
},
|
|
1647
|
+
error: data.error || (data.message ? new Error(String(data.message)) : void 0)
|
|
1109
1648
|
});
|
|
1110
1649
|
}
|
|
1111
1650
|
// Prebid's adRenderSucceeded payload is { doc, bid, adId }: the winning bid
|
|
@@ -1113,23 +1652,81 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1113
1652
|
handleAdRenderSucceeded(data) {
|
|
1114
1653
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1115
1654
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1116
|
-
const bid = data.bid || {};
|
|
1655
|
+
const bid = data.bid || data || {};
|
|
1656
|
+
const adUnitCode = data.adUnitCode || bid.adUnitCode || "";
|
|
1657
|
+
const auctionId = bid.auctionId || data.auctionId || "";
|
|
1658
|
+
const transactionId = bid.transactionId || data.transactionId || "";
|
|
1659
|
+
const mediaType = bid.mediaType || "banner";
|
|
1660
|
+
const bidTrace = {
|
|
1661
|
+
bidder: bid.bidderCode || bid.bidder || "",
|
|
1662
|
+
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : 0,
|
|
1663
|
+
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1664
|
+
...parseBidDimensions(bid),
|
|
1665
|
+
dealId: bid.dealId || "",
|
|
1666
|
+
mediaType,
|
|
1667
|
+
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1668
|
+
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1669
|
+
creativeId: bid.creativeId || ""
|
|
1670
|
+
};
|
|
1671
|
+
const isRefresh = adUnitCode && (this.slotViewabilityRecords.has(adUnitCode) || this.slotRefreshIndices.has(adUnitCode) && this.slotRefreshIndices.get(adUnitCode) > 0);
|
|
1672
|
+
if (isRefresh) {
|
|
1673
|
+
this.flushSlotTimeInView(adUnitCode);
|
|
1674
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? 0) + 1;
|
|
1675
|
+
this.slotRefreshIndices.set(adUnitCode, nextRefreshIndex);
|
|
1676
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1677
|
+
auctionId,
|
|
1678
|
+
transactionId,
|
|
1679
|
+
adUnitCode,
|
|
1680
|
+
bid: bidTrace,
|
|
1681
|
+
metadata: { refresh_index: String(nextRefreshIndex) }
|
|
1682
|
+
});
|
|
1683
|
+
} else if (adUnitCode && !this.slotRefreshIndices.has(adUnitCode)) {
|
|
1684
|
+
this.slotRefreshIndices.set(adUnitCode, 0);
|
|
1685
|
+
}
|
|
1686
|
+
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? 0 : 0;
|
|
1117
1687
|
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1118
|
-
auctionId
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1124
|
-
width: Number.isFinite(bid.width) ? bid.width : 0,
|
|
1125
|
-
height: Number.isFinite(bid.height) ? bid.height : 0,
|
|
1126
|
-
dealId: bid.dealId || "",
|
|
1127
|
-
mediaType: bid.mediaType || "banner",
|
|
1128
|
-
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1129
|
-
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1130
|
-
creativeId: bid.creativeId || ""
|
|
1131
|
-
}
|
|
1688
|
+
auctionId,
|
|
1689
|
+
transactionId,
|
|
1690
|
+
adUnitCode,
|
|
1691
|
+
bid: bidTrace,
|
|
1692
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
1132
1693
|
});
|
|
1694
|
+
if (this.config.viewabilityEnabled && typeof document !== "undefined") {
|
|
1695
|
+
let el = null;
|
|
1696
|
+
const targetId = adUnitCode || data.adId || bid.adId;
|
|
1697
|
+
if (targetId) {
|
|
1698
|
+
el = document.getElementById(targetId);
|
|
1699
|
+
}
|
|
1700
|
+
if (!el && typeof window !== "undefined" && window.googletag?.pubads) {
|
|
1701
|
+
try {
|
|
1702
|
+
const slots = window.googletag.pubads().getSlots();
|
|
1703
|
+
for (const slot of slots) {
|
|
1704
|
+
if (slot.getAdUnitPath?.() === adUnitCode || slot.getSlotElementId?.() === adUnitCode) {
|
|
1705
|
+
el = document.getElementById(slot.getSlotElementId());
|
|
1706
|
+
if (el) break;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
} catch {
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
if (!el && targetId) {
|
|
1713
|
+
try {
|
|
1714
|
+
el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (el) {
|
|
1719
|
+
this.observeSlot(el, adUnitCode || targetId, {
|
|
1720
|
+
adUnitCode,
|
|
1721
|
+
auctionId,
|
|
1722
|
+
transactionId,
|
|
1723
|
+
mediaType,
|
|
1724
|
+
bid: bidTrace,
|
|
1725
|
+
refreshIndex: currentRefreshIndex,
|
|
1726
|
+
emitRefreshEvent: false
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1133
1730
|
}
|
|
1134
1731
|
isDuplicate(eventName, data) {
|
|
1135
1732
|
return this.deduper.isDuplicate(eventName, data);
|
|
@@ -1137,6 +1734,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1137
1734
|
enqueue(type, eventName, data, level) {
|
|
1138
1735
|
if (!this.isEnabled) return;
|
|
1139
1736
|
if (!this.shouldSample(type, level)) return;
|
|
1737
|
+
extendSession();
|
|
1140
1738
|
const protoEvent = {
|
|
1141
1739
|
eventId: generateUUID(),
|
|
1142
1740
|
timestampMs: Date.now(),
|
|
@@ -1151,11 +1749,14 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1151
1749
|
protoEvent.bid = {
|
|
1152
1750
|
bidder: data.bid.bidder || "",
|
|
1153
1751
|
cpm: Number.isFinite(data.bid.cpm) ? data.bid.cpm : 0,
|
|
1154
|
-
|
|
1752
|
+
// No defaults here: handlers with a priced bid (bidResponse, bidWon,
|
|
1753
|
+
// impression) set currency/mediaType themselves. Defaulting for the
|
|
1754
|
+
// rest would stamp fake "USD"/"banner" on noBid and bidRequest rows.
|
|
1755
|
+
currency: data.bid.currency || "",
|
|
1155
1756
|
width: Number.isFinite(data.bid.width) ? data.bid.width : 0,
|
|
1156
1757
|
height: Number.isFinite(data.bid.height) ? data.bid.height : 0,
|
|
1157
1758
|
dealId: data.bid.dealId || "",
|
|
1158
|
-
mediaType: data.bid.mediaType || "
|
|
1759
|
+
mediaType: data.bid.mediaType || "",
|
|
1159
1760
|
latencyMs: Number.isFinite(data.bid.latencyMs) ? data.bid.latencyMs : 0,
|
|
1160
1761
|
advertiserDomain: data.bid.advertiserDomain || "",
|
|
1161
1762
|
creativeId: data.bid.creativeId || ""
|
|
@@ -1171,6 +1772,9 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1171
1772
|
if (data.gpid) {
|
|
1172
1773
|
metadata.gpid = String(data.gpid);
|
|
1173
1774
|
}
|
|
1775
|
+
if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
|
|
1776
|
+
metadata.refresh_index = String(data.refreshIndex);
|
|
1777
|
+
}
|
|
1174
1778
|
if (Object.keys(metadata).length > 0) {
|
|
1175
1779
|
protoEvent.metadata = metadata;
|
|
1176
1780
|
}
|
|
@@ -1200,14 +1804,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1200
1804
|
this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
|
|
1201
1805
|
}
|
|
1202
1806
|
}
|
|
1203
|
-
// Drains
|
|
1204
|
-
//
|
|
1807
|
+
// Drains a batch (up to MAX_PAYLOAD_BYTES) from the queue.
|
|
1808
|
+
// Shared by flush() and flushBeacon().
|
|
1205
1809
|
drainBatch() {
|
|
1206
1810
|
if (this.queue.length === 0 || !this.config.endpoint) return null;
|
|
1207
|
-
|
|
1208
|
-
this.queue = [];
|
|
1811
|
+
let events = this.queue;
|
|
1209
1812
|
const domain = typeof window !== "undefined" ? window.location.hostname : "";
|
|
1210
|
-
const
|
|
1813
|
+
const buildBatch = (evts) => ({
|
|
1211
1814
|
propertyId: this.config.propertyId,
|
|
1212
1815
|
pageviewId: this.config.pageviewId,
|
|
1213
1816
|
sessionId: this.config.sessionId,
|
|
@@ -1217,25 +1820,42 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1217
1820
|
deviceType: this.config.deviceType,
|
|
1218
1821
|
userId: this.config.userId,
|
|
1219
1822
|
domain,
|
|
1220
|
-
events
|
|
1221
|
-
};
|
|
1823
|
+
events: evts
|
|
1824
|
+
});
|
|
1825
|
+
let encoded = TraceEventBatch.encode(
|
|
1826
|
+
buildBatch(events)
|
|
1827
|
+
).finish();
|
|
1828
|
+
while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
|
|
1829
|
+
events = events.slice(0, Math.floor(events.length / 2));
|
|
1830
|
+
encoded = TraceEventBatch.encode(
|
|
1831
|
+
buildBatch(events)
|
|
1832
|
+
).finish();
|
|
1833
|
+
}
|
|
1834
|
+
this.queue = this.queue.slice(events.length);
|
|
1222
1835
|
return {
|
|
1223
1836
|
url: `${this.config.endpoint}/${this.config.propertyId}`,
|
|
1224
1837
|
// protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
|
|
1225
1838
|
// always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
|
|
1226
|
-
encoded
|
|
1839
|
+
encoded,
|
|
1227
1840
|
events
|
|
1228
1841
|
};
|
|
1229
1842
|
}
|
|
1230
|
-
sendFetch(payload) {
|
|
1231
|
-
|
|
1843
|
+
sendFetch(payload, useKeepalive = false) {
|
|
1844
|
+
const fetchOpts = {
|
|
1232
1845
|
method: "POST",
|
|
1233
1846
|
headers: { "Content-Type": "application/x-protobuf" },
|
|
1234
|
-
body: payload.encoded
|
|
1235
|
-
|
|
1236
|
-
|
|
1847
|
+
body: payload.encoded
|
|
1848
|
+
};
|
|
1849
|
+
if (useKeepalive) {
|
|
1850
|
+
fetchOpts.keepalive = true;
|
|
1851
|
+
}
|
|
1852
|
+
fetch(payload.url, fetchOpts).then((res) => {
|
|
1237
1853
|
if (!res.ok) {
|
|
1238
1854
|
this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
|
|
1855
|
+
if (res.status >= 400 && res.status < 500) {
|
|
1856
|
+
this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1239
1859
|
this.handleSendFailure(payload.events);
|
|
1240
1860
|
} else {
|
|
1241
1861
|
this.consecutiveSendFailures = 0;
|
|
@@ -1245,6 +1865,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1245
1865
|
this.log("ERROR", "Failed to send batch", err);
|
|
1246
1866
|
this.handleSendFailure(payload.events);
|
|
1247
1867
|
});
|
|
1868
|
+
if (typeof process !== "undefined" && typeof process._tickCallback === "function") {
|
|
1869
|
+
try {
|
|
1870
|
+
process._tickCallback();
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1248
1874
|
}
|
|
1249
1875
|
handleSendFailure(events) {
|
|
1250
1876
|
this.consecutiveSendFailures++;
|
|
@@ -1264,24 +1890,31 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1264
1890
|
}
|
|
1265
1891
|
flush() {
|
|
1266
1892
|
if (Date.now() < this.nextSendAllowedAt) return;
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1893
|
+
let payload = this.drainBatch();
|
|
1894
|
+
while (payload) {
|
|
1895
|
+
this.sendFetch(payload, false);
|
|
1896
|
+
if (Date.now() < this.nextSendAllowedAt) break;
|
|
1897
|
+
payload = this.drainBatch();
|
|
1898
|
+
}
|
|
1270
1899
|
}
|
|
1271
1900
|
flushBeacon() {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1901
|
+
let payload = this.drainBatch();
|
|
1902
|
+
while (payload) {
|
|
1903
|
+
let sent = false;
|
|
1904
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
1905
|
+
try {
|
|
1906
|
+
const blob = new Blob([payload.encoded], {
|
|
1907
|
+
type: "application/x-protobuf"
|
|
1908
|
+
});
|
|
1909
|
+
sent = navigator.sendBeacon(payload.url, blob);
|
|
1910
|
+
} catch {
|
|
1911
|
+
sent = false;
|
|
1912
|
+
}
|
|
1281
1913
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1914
|
+
if (!sent) {
|
|
1915
|
+
this.sendFetch(payload, true);
|
|
1916
|
+
}
|
|
1917
|
+
payload = this.drainBatch();
|
|
1285
1918
|
}
|
|
1286
1919
|
}
|
|
1287
1920
|
log(level, msg, ...args) {
|