@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.mjs
CHANGED
|
@@ -627,6 +627,7 @@ var MAX_ERRORS_PER_SESSION = 50;
|
|
|
627
627
|
var MAX_QUEUE_SIZE = 200;
|
|
628
628
|
var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
|
|
629
629
|
var MAX_CONSECUTIVE_SEND_FAILURES = 10;
|
|
630
|
+
var MAX_PAYLOAD_BYTES = 32 * 1024;
|
|
630
631
|
var EVENT_NAME_TO_TYPE = {
|
|
631
632
|
auctionStart: TraceEventType.AUCTION_START,
|
|
632
633
|
auctionEnd: TraceEventType.AUCTION_END,
|
|
@@ -693,47 +694,73 @@ function getOrCreateSessionId() {
|
|
|
693
694
|
inMemorySessionTs = now;
|
|
694
695
|
return inMemorySessionId;
|
|
695
696
|
}
|
|
697
|
+
function escapeKeyPart(str) {
|
|
698
|
+
return String(str ?? "").replace(/%/g, "%25").replace(/:/g, "%3A");
|
|
699
|
+
}
|
|
696
700
|
function getPrebidEventKey(eventName, data) {
|
|
697
701
|
switch (eventName) {
|
|
698
702
|
case "auctionInit":
|
|
699
|
-
return `auctionInit:${data?.auctionId
|
|
703
|
+
return `auctionInit:${escapeKeyPart(data?.auctionId)}`;
|
|
700
704
|
case "auctionEnd":
|
|
701
|
-
return `auctionEnd:${data?.auctionId
|
|
702
|
-
case "bidRequested":
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
case "
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
return
|
|
705
|
+
return `auctionEnd:${escapeKeyPart(data?.auctionId)}`;
|
|
706
|
+
case "bidRequested": {
|
|
707
|
+
const bids = Array.isArray(data?.bids) ? data.bids.map((b) => escapeKeyPart(b?.bidId || b?.transactionId || b?.adUnitCode)).join(",") : "";
|
|
708
|
+
return `bidRequested:${escapeKeyPart(data?.auctionId)}:${escapeKeyPart(data?.bidderCode || data?.bidder)}:${bids}`;
|
|
709
|
+
}
|
|
710
|
+
case "bidResponse": {
|
|
711
|
+
const bid = data || {};
|
|
712
|
+
const uniqueBidId = bid.creativeId || bid.adId || bid.requestId || bid.bidId || "";
|
|
713
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
714
|
+
return `bidResponse:${escapeKeyPart(bid.auctionId)}:${escapeKeyPart(bid.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueBidId)}:${escapeKeyPart(price)}`;
|
|
715
|
+
}
|
|
716
|
+
case "bidTimeout": {
|
|
717
|
+
const items = Array.isArray(data) ? data : data ? [data] : [];
|
|
718
|
+
return "bidTimeout:" + items.map(
|
|
719
|
+
(t) => `${escapeKeyPart(t?.auctionId)}:${escapeKeyPart(t?.bidderCode || t?.bidder)}:${escapeKeyPart(t?.bidId || t?.transactionId || t?.adUnitCode)}`
|
|
720
|
+
).join(",");
|
|
721
|
+
}
|
|
722
|
+
case "bidWon": {
|
|
723
|
+
const bid = data || {};
|
|
724
|
+
const uniqueBidId = bid.creativeId || bid.adId || bid.requestId || bid.bidId || "";
|
|
725
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
726
|
+
return `bidWon:${escapeKeyPart(bid.auctionId)}:${escapeKeyPart(bid.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueBidId)}:${escapeKeyPart(price)}`;
|
|
727
|
+
}
|
|
715
728
|
case "noBid":
|
|
716
|
-
return `noBid:${data?.auctionId
|
|
717
|
-
case "adRenderFailed":
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
729
|
+
return `noBid:${escapeKeyPart(data?.auctionId)}:${escapeKeyPart(data?.adUnitCode)}:${escapeKeyPart(data?.bidderCode || data?.bidder)}:${escapeKeyPart(data?.bidId || data?.transactionId)}`;
|
|
730
|
+
case "adRenderFailed": {
|
|
731
|
+
const bid = data?.bid || {};
|
|
732
|
+
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)}`;
|
|
733
|
+
}
|
|
734
|
+
case "adRenderSucceeded": {
|
|
735
|
+
const bid = data?.bid || data || {};
|
|
736
|
+
const uniqueId = bid.creativeId || bid.adId || data?.adId || "";
|
|
737
|
+
const price = bid.originalCpm ?? bid.cpm ?? "";
|
|
738
|
+
return `adRenderSucceeded:${escapeKeyPart(bid.auctionId || data?.auctionId)}:${escapeKeyPart(bid.adUnitCode || data?.adUnitCode)}:${escapeKeyPart(bid.bidderCode || bid.bidder)}:${escapeKeyPart(uniqueId)}:${escapeKeyPart(price)}`;
|
|
739
|
+
}
|
|
721
740
|
case "setTargeting":
|
|
722
|
-
return `setTargeting:${Object.keys(data || {}).sort().join(",")}`;
|
|
741
|
+
return `setTargeting:${Object.keys(data || {}).sort().map(escapeKeyPart).join(",")}`;
|
|
723
742
|
case "auctionDebug":
|
|
724
|
-
return `auctionDebug:${data?.type
|
|
743
|
+
return `auctionDebug:${escapeKeyPart(data?.type)}:${escapeKeyPart(String(data?.arguments?.[0] ?? "").slice(0, 50))}`;
|
|
725
744
|
default:
|
|
726
745
|
return "";
|
|
727
746
|
}
|
|
728
747
|
}
|
|
729
748
|
var PrebidEventDeduper = class {
|
|
730
|
-
|
|
749
|
+
// Object identity is tracked per event name: Prebid passes the SAME bid
|
|
750
|
+
// object to bidResponse and later to bidWon, so a bare WeakSet would drop
|
|
751
|
+
// every bidWon as a duplicate of its own bidResponse.
|
|
752
|
+
seenObjects = /* @__PURE__ */ new WeakMap();
|
|
731
753
|
seenKeys = /* @__PURE__ */ new Set();
|
|
732
754
|
isDuplicate(eventName, data) {
|
|
733
755
|
if (!data) return false;
|
|
734
756
|
if (typeof data === "object") {
|
|
735
|
-
|
|
736
|
-
|
|
757
|
+
const seenEvents = this.seenObjects.get(data);
|
|
758
|
+
if (seenEvents) {
|
|
759
|
+
if (seenEvents.has(eventName)) return true;
|
|
760
|
+
seenEvents.add(eventName);
|
|
761
|
+
} else {
|
|
762
|
+
this.seenObjects.set(data, /* @__PURE__ */ new Set([eventName]));
|
|
763
|
+
}
|
|
737
764
|
}
|
|
738
765
|
const key = getPrebidEventKey(eventName, data);
|
|
739
766
|
if (key) {
|
|
@@ -763,7 +790,23 @@ function parseSize(raw) {
|
|
|
763
790
|
}
|
|
764
791
|
return { width: 0, height: 0 };
|
|
765
792
|
}
|
|
766
|
-
|
|
793
|
+
function parseBidDimensions(bid) {
|
|
794
|
+
if (Number.isFinite(bid?.width) && Number.isFinite(bid?.height)) {
|
|
795
|
+
return { width: bid.width, height: bid.height };
|
|
796
|
+
}
|
|
797
|
+
if (typeof bid?.size === "string") {
|
|
798
|
+
const match = bid.size.match(/^(\d+)x(\d+)$/);
|
|
799
|
+
if (match) {
|
|
800
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
width: Number.isFinite(bid?.width) ? bid.width : 0,
|
|
805
|
+
height: Number.isFinite(bid?.height) ? bid.height : 0
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
809
|
+
static activeInstances = /* @__PURE__ */ new Set();
|
|
767
810
|
config;
|
|
768
811
|
queue = [];
|
|
769
812
|
errorCount = 0;
|
|
@@ -778,6 +821,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
778
821
|
deduper = new PrebidEventDeduper();
|
|
779
822
|
consecutiveSendFailures = 0;
|
|
780
823
|
nextSendAllowedAt = 0;
|
|
824
|
+
replayedEventCount = 0;
|
|
825
|
+
// Viewability & refresh tracking
|
|
826
|
+
intersectionObserver = null;
|
|
827
|
+
slotViewabilityRecords = /* @__PURE__ */ new Map();
|
|
828
|
+
elementToSlotId = /* @__PURE__ */ new Map();
|
|
829
|
+
slotRefreshIndices = /* @__PURE__ */ new Map();
|
|
830
|
+
pendingThresholdListeners = /* @__PURE__ */ new Map();
|
|
781
831
|
constructor(config) {
|
|
782
832
|
this.config = {
|
|
783
833
|
endpoint: config.endpoint || "",
|
|
@@ -791,19 +841,36 @@ var BidkernelPrebidAnalytics = class {
|
|
|
791
841
|
auctionEnabled: config.auctionEnabled ?? true,
|
|
792
842
|
warningsEnabled: config.warningsEnabled ?? true,
|
|
793
843
|
errorsEnabled: config.errorsEnabled ?? true,
|
|
844
|
+
viewabilityEnabled: config.viewabilityEnabled ?? true,
|
|
794
845
|
logLevel: config.logLevel || "INFO",
|
|
795
846
|
pbjsGlobalName: config.pbjsGlobalName || "pbjs",
|
|
796
847
|
attachPbjsListeners: config.attachPbjsListeners ?? true
|
|
797
848
|
};
|
|
798
|
-
this.boundFlushBeacon = () =>
|
|
849
|
+
this.boundFlushBeacon = () => {
|
|
850
|
+
this.flushAllSlotsTimeInView();
|
|
851
|
+
this.flushBeacon();
|
|
852
|
+
};
|
|
799
853
|
this.boundVisibilityChange = () => this.handleVisibilityChange();
|
|
800
854
|
}
|
|
801
855
|
enable() {
|
|
802
856
|
if (this.isEnabled) return;
|
|
803
857
|
this.isEnabled = true;
|
|
858
|
+
_BidkernelPrebidAnalytics.activeInstances.add(this);
|
|
804
859
|
if (!this.config.endpoint) {
|
|
805
860
|
this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
|
|
806
861
|
}
|
|
862
|
+
if (this.config.viewabilityEnabled) {
|
|
863
|
+
if (typeof IntersectionObserver !== "undefined") {
|
|
864
|
+
this.intersectionObserver = new IntersectionObserver(this.handleIntersection.bind(this), {
|
|
865
|
+
threshold: [0.5]
|
|
866
|
+
});
|
|
867
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
868
|
+
if (record.element) {
|
|
869
|
+
this.intersectionObserver.observe(record.element);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
807
874
|
if (this.config.attachPbjsListeners) {
|
|
808
875
|
const win = typeof window !== "undefined" ? window : {};
|
|
809
876
|
const pbjs = win[this.config.pbjsGlobalName] || {};
|
|
@@ -831,28 +898,32 @@ var BidkernelPrebidAnalytics = class {
|
|
|
831
898
|
try {
|
|
832
899
|
const pastEvents = pbjs.getEvents();
|
|
833
900
|
if (Array.isArray(pastEvents)) {
|
|
834
|
-
this.
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
901
|
+
const newPastEvents = pastEvents.slice(this.replayedEventCount);
|
|
902
|
+
this.replayedEventCount = pastEvents.length;
|
|
903
|
+
if (newPastEvents.length > 0) {
|
|
904
|
+
this.log(
|
|
905
|
+
"DEBUG",
|
|
906
|
+
`Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
|
|
907
|
+
);
|
|
908
|
+
const handlerMap = {
|
|
909
|
+
auctionInit: this.handleAuctionInit.bind(this),
|
|
910
|
+
auctionEnd: this.handleAuctionEnd.bind(this),
|
|
911
|
+
bidRequested: this.handleBidRequested.bind(this),
|
|
912
|
+
bidResponse: this.handleBidResponse.bind(this),
|
|
913
|
+
bidTimeout: this.handleBidTimeout.bind(this),
|
|
914
|
+
bidWon: this.handleBidWon.bind(this),
|
|
915
|
+
noBid: this.handleNoBid.bind(this),
|
|
916
|
+
adRenderFailed: this.handleAdRenderFailed.bind(this),
|
|
917
|
+
adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
|
|
918
|
+
};
|
|
919
|
+
for (const ev of newPastEvents) {
|
|
920
|
+
if (!ev) continue;
|
|
921
|
+
const eventType = ev.eventType || ev.event || ev.name;
|
|
922
|
+
const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
|
|
923
|
+
const handler = handlerMap[eventType];
|
|
924
|
+
if (handler) {
|
|
925
|
+
handler(args);
|
|
926
|
+
}
|
|
856
927
|
}
|
|
857
928
|
}
|
|
858
929
|
}
|
|
@@ -879,7 +950,28 @@ var BidkernelPrebidAnalytics = class {
|
|
|
879
950
|
}
|
|
880
951
|
disable() {
|
|
881
952
|
if (!this.isEnabled) return;
|
|
953
|
+
this.flushAllSlotsTimeInView();
|
|
882
954
|
this.isEnabled = false;
|
|
955
|
+
_BidkernelPrebidAnalytics.activeInstances.delete(this);
|
|
956
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
957
|
+
if (record.dwellTimer) {
|
|
958
|
+
clearTimeout(record.dwellTimer);
|
|
959
|
+
record.dwellTimer = null;
|
|
960
|
+
record.dwellStartedAt = null;
|
|
961
|
+
}
|
|
962
|
+
for (const l of record.thresholdListeners) {
|
|
963
|
+
if (l.timer) {
|
|
964
|
+
clearTimeout(l.timer);
|
|
965
|
+
l.timer = null;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (this.intersectionObserver) {
|
|
970
|
+
this.intersectionObserver.disconnect();
|
|
971
|
+
this.intersectionObserver = null;
|
|
972
|
+
}
|
|
973
|
+
this.slotViewabilityRecords.clear();
|
|
974
|
+
this.elementToSlotId.clear();
|
|
883
975
|
if (this.flushTimer) {
|
|
884
976
|
clearInterval(this.flushTimer);
|
|
885
977
|
this.flushTimer = null;
|
|
@@ -903,8 +995,443 @@ var BidkernelPrebidAnalytics = class {
|
|
|
903
995
|
this.flush();
|
|
904
996
|
}
|
|
905
997
|
handleVisibilityChange() {
|
|
906
|
-
if (typeof document
|
|
998
|
+
if (typeof document === "undefined") return;
|
|
999
|
+
if (document.visibilityState === "hidden") {
|
|
1000
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1001
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
1002
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1003
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1004
|
+
record.lastEnteredViewAt = null;
|
|
1005
|
+
this.checkThresholdListeners(record);
|
|
1006
|
+
}
|
|
1007
|
+
if (record.dwellTimer) {
|
|
1008
|
+
clearTimeout(record.dwellTimer);
|
|
1009
|
+
record.dwellTimer = null;
|
|
1010
|
+
record.dwellStartedAt = null;
|
|
1011
|
+
}
|
|
1012
|
+
for (const l of record.thresholdListeners) {
|
|
1013
|
+
if (l.timer) {
|
|
1014
|
+
clearTimeout(l.timer);
|
|
1015
|
+
l.timer = null;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const durationMs = Math.round(record.accumulatedTimeInViewMs);
|
|
1019
|
+
if (durationMs > 0) {
|
|
1020
|
+
this.enqueue(TraceEventType.TIME_IN_VIEW, "timeInView", {
|
|
1021
|
+
auctionId: record.auctionId,
|
|
1022
|
+
transactionId: record.transactionId,
|
|
1023
|
+
adUnitCode: record.adUnitCode,
|
|
1024
|
+
viewableDurationMs: durationMs,
|
|
1025
|
+
bid: record.bidPayload,
|
|
1026
|
+
metadata: {
|
|
1027
|
+
...record.metadata,
|
|
1028
|
+
refresh_index: String(record.refreshIndex)
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
907
1033
|
this.flushBeacon();
|
|
1034
|
+
} else if (document.visibilityState === "visible") {
|
|
1035
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1036
|
+
for (const record of this.slotViewabilityRecords.values()) {
|
|
1037
|
+
if (record.inView) {
|
|
1038
|
+
record.lastEnteredViewAt = now;
|
|
1039
|
+
if (!record.viewableFired && record.dwellTimer === null) {
|
|
1040
|
+
const dwellTarget = record.mediaType === "video" ? 2e3 : 1e3;
|
|
1041
|
+
record.dwellStartedAt = now;
|
|
1042
|
+
record.dwellTimer = setTimeout(() => {
|
|
1043
|
+
this.handleDwellComplete(record);
|
|
1044
|
+
}, dwellTarget);
|
|
1045
|
+
}
|
|
1046
|
+
this.scheduleThresholdTimers(record);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
handleIntersection(entries) {
|
|
1052
|
+
for (const entry of entries) {
|
|
1053
|
+
let slotId = this.elementToSlotId.get(entry.target);
|
|
1054
|
+
if (!slotId) {
|
|
1055
|
+
for (const instance of _BidkernelPrebidAnalytics.activeInstances) {
|
|
1056
|
+
if (instance !== this && instance.elementToSlotId.has(entry.target)) {
|
|
1057
|
+
instance.handleIntersection([entry]);
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1064
|
+
if (!record) continue;
|
|
1065
|
+
const isViewableRatio = entry.isIntersecting && (entry.intersectionRatio === void 0 || entry.intersectionRatio >= 0.5);
|
|
1066
|
+
const isDocVisible = typeof document === "undefined" || document.visibilityState === "visible";
|
|
1067
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1068
|
+
if (isViewableRatio) {
|
|
1069
|
+
if (!record.inView) {
|
|
1070
|
+
record.inView = true;
|
|
1071
|
+
if (isDocVisible) {
|
|
1072
|
+
record.lastEnteredViewAt = now;
|
|
1073
|
+
if (!record.viewableFired && record.dwellTimer === null) {
|
|
1074
|
+
const dwellTarget = record.mediaType === "video" ? 2e3 : 1e3;
|
|
1075
|
+
record.dwellStartedAt = now;
|
|
1076
|
+
record.dwellTimer = setTimeout(() => {
|
|
1077
|
+
this.handleDwellComplete(record);
|
|
1078
|
+
}, dwellTarget);
|
|
1079
|
+
}
|
|
1080
|
+
this.scheduleThresholdTimers(record);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
if (record.inView) {
|
|
1085
|
+
if (record.lastEnteredViewAt !== null) {
|
|
1086
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1087
|
+
record.lastEnteredViewAt = null;
|
|
1088
|
+
}
|
|
1089
|
+
record.inView = false;
|
|
1090
|
+
this.checkThresholdListeners(record);
|
|
1091
|
+
}
|
|
1092
|
+
if (record.dwellTimer !== null) {
|
|
1093
|
+
clearTimeout(record.dwellTimer);
|
|
1094
|
+
record.dwellTimer = null;
|
|
1095
|
+
record.dwellStartedAt = null;
|
|
1096
|
+
}
|
|
1097
|
+
for (const l of record.thresholdListeners) {
|
|
1098
|
+
if (l.timer) {
|
|
1099
|
+
clearTimeout(l.timer);
|
|
1100
|
+
l.timer = null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
handleDwellComplete(record) {
|
|
1107
|
+
record.dwellTimer = null;
|
|
1108
|
+
record.dwellStartedAt = null;
|
|
1109
|
+
if (!record.inView) return;
|
|
1110
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
1111
|
+
if (!record.viewableFired) {
|
|
1112
|
+
record.viewableFired = true;
|
|
1113
|
+
this.enqueue(TraceEventType.VIEWABLE, "viewable", {
|
|
1114
|
+
auctionId: record.auctionId,
|
|
1115
|
+
transactionId: record.transactionId,
|
|
1116
|
+
adUnitCode: record.adUnitCode,
|
|
1117
|
+
bid: record.bidPayload,
|
|
1118
|
+
metadata: {
|
|
1119
|
+
...record.metadata,
|
|
1120
|
+
refresh_index: String(record.refreshIndex)
|
|
1121
|
+
}
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
scheduleThresholdTimers(record) {
|
|
1126
|
+
if (!record.inView || typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1130
|
+
for (const listener of record.thresholdListeners) {
|
|
1131
|
+
if (listener.fired) continue;
|
|
1132
|
+
if (currentTotal >= listener.thresholdMs) {
|
|
1133
|
+
listener.fired = true;
|
|
1134
|
+
if (listener.timer) {
|
|
1135
|
+
clearTimeout(listener.timer);
|
|
1136
|
+
listener.timer = null;
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
listener.callback(record.slotId, currentTotal);
|
|
1140
|
+
} catch (e) {
|
|
1141
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1142
|
+
}
|
|
1143
|
+
} else {
|
|
1144
|
+
if (listener.timer) {
|
|
1145
|
+
clearTimeout(listener.timer);
|
|
1146
|
+
}
|
|
1147
|
+
const remaining = listener.thresholdMs - currentTotal;
|
|
1148
|
+
listener.timer = setTimeout(() => {
|
|
1149
|
+
listener.timer = null;
|
|
1150
|
+
if (!listener.fired && record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1151
|
+
const nowTotal = this.getCurrentTimeInView(record);
|
|
1152
|
+
if (nowTotal >= listener.thresholdMs) {
|
|
1153
|
+
listener.fired = true;
|
|
1154
|
+
try {
|
|
1155
|
+
listener.callback(record.slotId, nowTotal);
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}, remaining);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
checkThresholdListeners(record) {
|
|
1166
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1167
|
+
for (const listener of record.thresholdListeners) {
|
|
1168
|
+
if (!listener.fired && currentTotal >= listener.thresholdMs) {
|
|
1169
|
+
listener.fired = true;
|
|
1170
|
+
if (listener.timer) {
|
|
1171
|
+
clearTimeout(listener.timer);
|
|
1172
|
+
listener.timer = null;
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
listener.callback(record.slotId, currentTotal);
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
this.log("ERROR", "Error in threshold listener callback", e);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
getCurrentTimeInView(record) {
|
|
1183
|
+
let total = record.accumulatedTimeInViewMs;
|
|
1184
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1185
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1186
|
+
total += Math.max(0, now - record.lastEnteredViewAt);
|
|
1187
|
+
}
|
|
1188
|
+
return total;
|
|
1189
|
+
}
|
|
1190
|
+
flushSlotTimeInView(slotId) {
|
|
1191
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1192
|
+
if (!record) return;
|
|
1193
|
+
if (record.inView && record.lastEnteredViewAt !== null) {
|
|
1194
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1195
|
+
record.accumulatedTimeInViewMs += Math.max(0, now - record.lastEnteredViewAt);
|
|
1196
|
+
record.lastEnteredViewAt = null;
|
|
1197
|
+
}
|
|
1198
|
+
const durationMs = Math.round(record.accumulatedTimeInViewMs);
|
|
1199
|
+
if (durationMs > 0) {
|
|
1200
|
+
this.enqueue(TraceEventType.TIME_IN_VIEW, "timeInView", {
|
|
1201
|
+
auctionId: record.auctionId,
|
|
1202
|
+
transactionId: record.transactionId,
|
|
1203
|
+
adUnitCode: record.adUnitCode,
|
|
1204
|
+
viewableDurationMs: durationMs,
|
|
1205
|
+
bid: record.bidPayload,
|
|
1206
|
+
metadata: {
|
|
1207
|
+
...record.metadata,
|
|
1208
|
+
refresh_index: String(record.refreshIndex)
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
record.accumulatedTimeInViewMs = 0;
|
|
1213
|
+
}
|
|
1214
|
+
flushAllSlotsTimeInView() {
|
|
1215
|
+
for (const slotId of Array.from(this.slotViewabilityRecords.keys())) {
|
|
1216
|
+
this.flushSlotTimeInView(slotId);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
observeSlot(element, slotId, options) {
|
|
1220
|
+
if (!this.config.viewabilityEnabled) return;
|
|
1221
|
+
let el = null;
|
|
1222
|
+
if (typeof element === "string") {
|
|
1223
|
+
if (typeof document !== "undefined") {
|
|
1224
|
+
try {
|
|
1225
|
+
el = document.querySelector(element) || document.getElementById(element);
|
|
1226
|
+
} catch {
|
|
1227
|
+
el = document.getElementById(element);
|
|
1228
|
+
}
|
|
1229
|
+
if (!el) {
|
|
1230
|
+
el = document.getElementById(element);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
el = element;
|
|
1235
|
+
}
|
|
1236
|
+
const resolvedSlotId = slotId || options?.adUnitCode || (el ? el.id : "") || generateUUID();
|
|
1237
|
+
const existing = this.slotViewabilityRecords.get(resolvedSlotId);
|
|
1238
|
+
if (existing) {
|
|
1239
|
+
const isNewRefreshCycle = options?.refreshIndex === void 0 || options.refreshIndex > existing.refreshIndex;
|
|
1240
|
+
if (isNewRefreshCycle) {
|
|
1241
|
+
this.flushSlotTimeInView(resolvedSlotId);
|
|
1242
|
+
const nextRefreshIndex = options?.refreshIndex !== void 0 ? options.refreshIndex : (this.slotRefreshIndices.get(resolvedSlotId) ?? existing.refreshIndex) + 1;
|
|
1243
|
+
this.slotRefreshIndices.set(resolvedSlotId, nextRefreshIndex);
|
|
1244
|
+
if (options?.emitRefreshEvent !== false) {
|
|
1245
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1246
|
+
auctionId: options?.auctionId || existing.auctionId,
|
|
1247
|
+
transactionId: options?.transactionId || existing.transactionId,
|
|
1248
|
+
adUnitCode: resolvedSlotId,
|
|
1249
|
+
bid: options?.bid ?? existing.bidPayload,
|
|
1250
|
+
metadata: {
|
|
1251
|
+
...options?.metadata,
|
|
1252
|
+
refresh_index: String(nextRefreshIndex)
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
if (existing.dwellTimer) {
|
|
1257
|
+
clearTimeout(existing.dwellTimer);
|
|
1258
|
+
existing.dwellTimer = null;
|
|
1259
|
+
existing.dwellStartedAt = null;
|
|
1260
|
+
}
|
|
1261
|
+
for (const l of existing.thresholdListeners) {
|
|
1262
|
+
l.fired = false;
|
|
1263
|
+
if (l.timer) {
|
|
1264
|
+
clearTimeout(l.timer);
|
|
1265
|
+
l.timer = null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
existing.viewableFired = false;
|
|
1269
|
+
existing.accumulatedTimeInViewMs = 0;
|
|
1270
|
+
const now = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
1271
|
+
existing.lastEnteredViewAt = existing.inView ? now : null;
|
|
1272
|
+
existing.refreshIndex = nextRefreshIndex;
|
|
1273
|
+
existing.auctionId = options?.auctionId || existing.auctionId;
|
|
1274
|
+
existing.transactionId = options?.transactionId || existing.transactionId;
|
|
1275
|
+
existing.mediaType = options?.mediaType || existing.mediaType || "banner";
|
|
1276
|
+
existing.bidPayload = options?.bid !== void 0 ? options.bid : existing.bidPayload;
|
|
1277
|
+
existing.metadata = options?.metadata ?? existing.metadata;
|
|
1278
|
+
if (existing.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1279
|
+
const dwellTarget = existing.mediaType === "video" ? 2e3 : 1e3;
|
|
1280
|
+
existing.dwellStartedAt = now;
|
|
1281
|
+
existing.dwellTimer = setTimeout(() => {
|
|
1282
|
+
this.handleDwellComplete(existing);
|
|
1283
|
+
}, dwellTarget);
|
|
1284
|
+
this.scheduleThresholdTimers(existing);
|
|
1285
|
+
}
|
|
1286
|
+
} else {
|
|
1287
|
+
existing.auctionId = options?.auctionId || existing.auctionId;
|
|
1288
|
+
existing.transactionId = options?.transactionId || existing.transactionId;
|
|
1289
|
+
existing.mediaType = options?.mediaType || existing.mediaType;
|
|
1290
|
+
if (options?.bid !== void 0) existing.bidPayload = options.bid;
|
|
1291
|
+
if (options?.metadata !== void 0) existing.metadata = options.metadata;
|
|
1292
|
+
}
|
|
1293
|
+
if (el) {
|
|
1294
|
+
if (existing.element && existing.element !== el && this.intersectionObserver) {
|
|
1295
|
+
this.intersectionObserver.unobserve(existing.element);
|
|
1296
|
+
this.elementToSlotId.delete(existing.element);
|
|
1297
|
+
}
|
|
1298
|
+
existing.element = el;
|
|
1299
|
+
this.elementToSlotId.set(el, resolvedSlotId);
|
|
1300
|
+
this.intersectionObserver?.observe(el);
|
|
1301
|
+
}
|
|
1302
|
+
} else {
|
|
1303
|
+
const initialRefreshIndex = options?.refreshIndex ?? this.slotRefreshIndices.get(resolvedSlotId) ?? 0;
|
|
1304
|
+
this.slotRefreshIndices.set(resolvedSlotId, initialRefreshIndex);
|
|
1305
|
+
const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
|
|
1306
|
+
this.pendingThresholdListeners.delete(resolvedSlotId);
|
|
1307
|
+
const record = {
|
|
1308
|
+
slotId: resolvedSlotId,
|
|
1309
|
+
element: el,
|
|
1310
|
+
adUnitCode: options?.adUnitCode || resolvedSlotId,
|
|
1311
|
+
auctionId: options?.auctionId || "",
|
|
1312
|
+
transactionId: options?.transactionId || "",
|
|
1313
|
+
mediaType: options?.mediaType || "banner",
|
|
1314
|
+
bidPayload: options?.bid,
|
|
1315
|
+
metadata: options?.metadata,
|
|
1316
|
+
refreshIndex: initialRefreshIndex,
|
|
1317
|
+
inView: false,
|
|
1318
|
+
lastEnteredViewAt: null,
|
|
1319
|
+
accumulatedTimeInViewMs: 0,
|
|
1320
|
+
viewableFired: false,
|
|
1321
|
+
dwellTimer: null,
|
|
1322
|
+
dwellStartedAt: null,
|
|
1323
|
+
thresholdListeners: listeners
|
|
1324
|
+
};
|
|
1325
|
+
this.slotViewabilityRecords.set(resolvedSlotId, record);
|
|
1326
|
+
if (el) {
|
|
1327
|
+
this.elementToSlotId.set(el, resolvedSlotId);
|
|
1328
|
+
this.intersectionObserver?.observe(el);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
unobserveSlot(slotId) {
|
|
1333
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1334
|
+
if (!record) return;
|
|
1335
|
+
this.flushSlotTimeInView(slotId);
|
|
1336
|
+
if (record.dwellTimer) {
|
|
1337
|
+
clearTimeout(record.dwellTimer);
|
|
1338
|
+
record.dwellTimer = null;
|
|
1339
|
+
record.dwellStartedAt = null;
|
|
1340
|
+
}
|
|
1341
|
+
for (const l of record.thresholdListeners) {
|
|
1342
|
+
if (l.timer) {
|
|
1343
|
+
clearTimeout(l.timer);
|
|
1344
|
+
l.timer = null;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
record.thresholdListeners = /* @__PURE__ */ new Set();
|
|
1348
|
+
if (record.element && this.intersectionObserver) {
|
|
1349
|
+
this.intersectionObserver.unobserve(record.element);
|
|
1350
|
+
this.elementToSlotId.delete(record.element);
|
|
1351
|
+
record.element = null;
|
|
1352
|
+
}
|
|
1353
|
+
record.inView = false;
|
|
1354
|
+
record.lastEnteredViewAt = null;
|
|
1355
|
+
}
|
|
1356
|
+
destroySlot(slotId) {
|
|
1357
|
+
this.unobserveSlot(slotId);
|
|
1358
|
+
this.slotViewabilityRecords.delete(slotId);
|
|
1359
|
+
this.slotRefreshIndices.delete(slotId);
|
|
1360
|
+
this.pendingThresholdListeners.delete(slotId);
|
|
1361
|
+
}
|
|
1362
|
+
onTimeInViewThreshold(slotId, thresholdMs, callback) {
|
|
1363
|
+
const listener = {
|
|
1364
|
+
thresholdMs,
|
|
1365
|
+
callback,
|
|
1366
|
+
timer: null,
|
|
1367
|
+
fired: false
|
|
1368
|
+
};
|
|
1369
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1370
|
+
if (record) {
|
|
1371
|
+
record.thresholdListeners.add(listener);
|
|
1372
|
+
const currentTotal = this.getCurrentTimeInView(record);
|
|
1373
|
+
if (currentTotal >= thresholdMs) {
|
|
1374
|
+
listener.fired = true;
|
|
1375
|
+
try {
|
|
1376
|
+
callback(slotId, currentTotal);
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
this.log("ERROR", "Error in threshold callback", e);
|
|
1379
|
+
}
|
|
1380
|
+
} else if (record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1381
|
+
const remaining = thresholdMs - currentTotal;
|
|
1382
|
+
listener.timer = setTimeout(() => {
|
|
1383
|
+
listener.timer = null;
|
|
1384
|
+
if (!listener.fired && record.inView && (typeof document === "undefined" || document.visibilityState === "visible")) {
|
|
1385
|
+
const nowTotal = this.getCurrentTimeInView(record);
|
|
1386
|
+
if (nowTotal >= thresholdMs) {
|
|
1387
|
+
listener.fired = true;
|
|
1388
|
+
try {
|
|
1389
|
+
callback(slotId, nowTotal);
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
this.log("ERROR", "Error in threshold callback", e);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}, remaining);
|
|
1396
|
+
}
|
|
1397
|
+
} else {
|
|
1398
|
+
if (!this.pendingThresholdListeners.has(slotId)) {
|
|
1399
|
+
this.pendingThresholdListeners.set(slotId, /* @__PURE__ */ new Set());
|
|
1400
|
+
}
|
|
1401
|
+
this.pendingThresholdListeners.get(slotId).add(listener);
|
|
1402
|
+
}
|
|
1403
|
+
return () => {
|
|
1404
|
+
if (listener.timer) {
|
|
1405
|
+
clearTimeout(listener.timer);
|
|
1406
|
+
listener.timer = null;
|
|
1407
|
+
}
|
|
1408
|
+
if (record) {
|
|
1409
|
+
record.thresholdListeners.delete(listener);
|
|
1410
|
+
}
|
|
1411
|
+
const pending = this.pendingThresholdListeners.get(slotId);
|
|
1412
|
+
if (pending) {
|
|
1413
|
+
pending.delete(listener);
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
getViewabilityState(slotId) {
|
|
1418
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1419
|
+
if (!record) return void 0;
|
|
1420
|
+
const timeInViewMs = Math.round(this.getCurrentTimeInView(record));
|
|
1421
|
+
return {
|
|
1422
|
+
inView: record.inView,
|
|
1423
|
+
viewable: record.viewableFired,
|
|
1424
|
+
timeInViewMs,
|
|
1425
|
+
refreshIndex: record.refreshIndex
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
getRefreshIndex(slotId) {
|
|
1429
|
+
return this.slotRefreshIndices.get(slotId) ?? 0;
|
|
1430
|
+
}
|
|
1431
|
+
triggerSlotRefresh(slotId, options) {
|
|
1432
|
+
const record = this.slotViewabilityRecords.get(slotId);
|
|
1433
|
+
if (record && record.element) {
|
|
1434
|
+
this.observeSlot(record.element, slotId, options);
|
|
908
1435
|
}
|
|
909
1436
|
}
|
|
910
1437
|
trackRawEvent(level, eventName, data) {
|
|
@@ -919,6 +1446,8 @@ var BidkernelPrebidAnalytics = class {
|
|
|
919
1446
|
}
|
|
920
1447
|
navigate(pageviewId, pageUrl) {
|
|
921
1448
|
this.disable();
|
|
1449
|
+
this.slotRefreshIndices.clear();
|
|
1450
|
+
this.pendingThresholdListeners.clear();
|
|
922
1451
|
this.config.pageviewId = pageviewId || generateUUID();
|
|
923
1452
|
if (pageUrl) {
|
|
924
1453
|
try {
|
|
@@ -937,30 +1466,34 @@ var BidkernelPrebidAnalytics = class {
|
|
|
937
1466
|
if (this.isDuplicate("auctionInit", data)) return;
|
|
938
1467
|
this.log("DEBUG", "auctionInit", data);
|
|
939
1468
|
this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
|
|
940
|
-
auctionId: data.auctionId || ""
|
|
1469
|
+
auctionId: data.auctionId || "",
|
|
1470
|
+
transactionId: data.transactionId || ""
|
|
941
1471
|
});
|
|
942
1472
|
}
|
|
943
1473
|
handleAuctionEnd(data) {
|
|
944
1474
|
if (this.isDuplicate("auctionEnd", data)) return;
|
|
945
1475
|
this.log("DEBUG", "auctionEnd", data);
|
|
946
1476
|
this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
|
|
947
|
-
auctionId: data.auctionId || ""
|
|
1477
|
+
auctionId: data.auctionId || "",
|
|
1478
|
+
transactionId: data.transactionId || ""
|
|
948
1479
|
});
|
|
949
1480
|
}
|
|
950
1481
|
handleBidRequested(data) {
|
|
951
1482
|
if (this.isDuplicate("bidRequested", data)) return;
|
|
952
1483
|
this.log("DEBUG", "bidRequested", data);
|
|
953
1484
|
const auctionId = data.auctionId || "";
|
|
954
|
-
const bidder = data.bidderCode || "";
|
|
1485
|
+
const bidder = data.bidderCode || data.bidder || "";
|
|
955
1486
|
if (Array.isArray(data.bids)) {
|
|
956
1487
|
data.bids.forEach((bid) => {
|
|
957
1488
|
const mediaTypes = Object.keys(bid.mediaTypes || {});
|
|
958
1489
|
const gpid = bid.ortb2Imp?.ext?.gpid || bid.gpid || data.gpid || "";
|
|
1490
|
+
const transactionId = bid.transactionId || bid.ortb2Imp?.id || data.transactionId || "";
|
|
959
1491
|
if (mediaTypes.length === 0) {
|
|
960
1492
|
const mediaType = bid.mediaType || "banner";
|
|
961
1493
|
const { width, height } = parseSize(bid.sizes || bid.playerSize);
|
|
962
1494
|
this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
|
|
963
1495
|
auctionId,
|
|
1496
|
+
transactionId,
|
|
964
1497
|
adUnitCode: bid.adUnitCode || "",
|
|
965
1498
|
gpid,
|
|
966
1499
|
bid: {
|
|
@@ -984,6 +1517,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
984
1517
|
const { width, height } = parseSize(rawSize);
|
|
985
1518
|
this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
|
|
986
1519
|
auctionId,
|
|
1520
|
+
transactionId,
|
|
987
1521
|
adUnitCode: bid.adUnitCode || "",
|
|
988
1522
|
gpid,
|
|
989
1523
|
bid: {
|
|
@@ -1002,13 +1536,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1002
1536
|
this.log("DEBUG", "bidResponse", data);
|
|
1003
1537
|
this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
|
|
1004
1538
|
auctionId: data.auctionId || "",
|
|
1539
|
+
transactionId: data.transactionId || "",
|
|
1005
1540
|
adUnitCode: data.adUnitCode || "",
|
|
1006
1541
|
bid: {
|
|
1007
1542
|
bidder: data.bidderCode || data.bidder || "",
|
|
1008
1543
|
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
1009
1544
|
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
1010
|
-
|
|
1011
|
-
height: Number.isFinite(data.height) ? data.height : 0,
|
|
1545
|
+
...parseBidDimensions(data),
|
|
1012
1546
|
dealId: data.dealId || "",
|
|
1013
1547
|
mediaType: data.mediaType || "banner",
|
|
1014
1548
|
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
@@ -1020,15 +1554,16 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1020
1554
|
handleBidTimeout(data) {
|
|
1021
1555
|
if (this.isDuplicate("bidTimeout", data)) return;
|
|
1022
1556
|
this.log("DEBUG", "bidTimeout", data);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1557
|
+
const items = Array.isArray(data) ? data : data ? [data] : [];
|
|
1558
|
+
for (const t of items) {
|
|
1559
|
+
this.enqueue(TraceEventType.BID_TIMEOUT, "bidTimeout", {
|
|
1560
|
+
auctionId: t.auctionId || "",
|
|
1561
|
+
transactionId: t.transactionId || "",
|
|
1562
|
+
adUnitCode: t.adUnitCode || "",
|
|
1563
|
+
bid: {
|
|
1564
|
+
bidder: t.bidderCode || t.bidder || "",
|
|
1565
|
+
latencyMs: Number.isFinite(t.timeout) ? t.timeout : 0
|
|
1566
|
+
}
|
|
1032
1567
|
});
|
|
1033
1568
|
}
|
|
1034
1569
|
}
|
|
@@ -1037,13 +1572,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1037
1572
|
this.log("DEBUG", "bidWon", data);
|
|
1038
1573
|
this.enqueue(TraceEventType.BID_WIN, "bidWon", {
|
|
1039
1574
|
auctionId: data.auctionId || "",
|
|
1575
|
+
transactionId: data.transactionId || "",
|
|
1040
1576
|
adUnitCode: data.adUnitCode || "",
|
|
1041
1577
|
bid: {
|
|
1042
1578
|
bidder: data.bidderCode || data.bidder || "",
|
|
1043
1579
|
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
1044
1580
|
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
1045
|
-
|
|
1046
|
-
height: Number.isFinite(data.height) ? data.height : 0,
|
|
1581
|
+
...parseBidDimensions(data),
|
|
1047
1582
|
dealId: data.dealId || "",
|
|
1048
1583
|
mediaType: data.mediaType || "banner",
|
|
1049
1584
|
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
@@ -1057,6 +1592,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1057
1592
|
this.log("DEBUG", "noBid", data);
|
|
1058
1593
|
this.enqueue(TraceEventType.NO_BID, "noBid", {
|
|
1059
1594
|
auctionId: data.auctionId || "",
|
|
1595
|
+
transactionId: data.transactionId || "",
|
|
1060
1596
|
adUnitCode: data.adUnitCode || "",
|
|
1061
1597
|
bid: {
|
|
1062
1598
|
bidder: data.bidderCode || data.bidder || ""
|
|
@@ -1066,16 +1602,19 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1066
1602
|
handleAdRenderFailed(data) {
|
|
1067
1603
|
if (this.isDuplicate("adRenderFailed", data)) return;
|
|
1068
1604
|
this.log("DEBUG", "adRenderFailed", data);
|
|
1605
|
+
const bid = data.bid || {};
|
|
1069
1606
|
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1070
|
-
auctionId: data.
|
|
1071
|
-
|
|
1607
|
+
auctionId: bid.auctionId || data.auctionId || "",
|
|
1608
|
+
transactionId: bid.transactionId || data.transactionId || "",
|
|
1609
|
+
adUnitCode: bid.adUnitCode || data.adUnitCode || "",
|
|
1072
1610
|
bid: {
|
|
1073
|
-
bidder: data.
|
|
1611
|
+
bidder: bid.bidderCode || bid.bidder || data.bidderCode || data.bidder || ""
|
|
1074
1612
|
},
|
|
1075
1613
|
metadata: {
|
|
1076
1614
|
reason: String(data.reason || ""),
|
|
1077
|
-
message: String(data.message || "")
|
|
1078
|
-
}
|
|
1615
|
+
message: String(data.message || data.error?.message || "")
|
|
1616
|
+
},
|
|
1617
|
+
error: data.error || (data.message ? new Error(String(data.message)) : void 0)
|
|
1079
1618
|
});
|
|
1080
1619
|
}
|
|
1081
1620
|
// Prebid's adRenderSucceeded payload is { doc, bid, adId }: the winning bid
|
|
@@ -1083,23 +1622,81 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1083
1622
|
handleAdRenderSucceeded(data) {
|
|
1084
1623
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1085
1624
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1086
|
-
const bid = data.bid || {};
|
|
1625
|
+
const bid = data.bid || data || {};
|
|
1626
|
+
const adUnitCode = data.adUnitCode || bid.adUnitCode || "";
|
|
1627
|
+
const auctionId = bid.auctionId || data.auctionId || "";
|
|
1628
|
+
const transactionId = bid.transactionId || data.transactionId || "";
|
|
1629
|
+
const mediaType = bid.mediaType || "banner";
|
|
1630
|
+
const bidTrace = {
|
|
1631
|
+
bidder: bid.bidderCode || bid.bidder || "",
|
|
1632
|
+
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : 0,
|
|
1633
|
+
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1634
|
+
...parseBidDimensions(bid),
|
|
1635
|
+
dealId: bid.dealId || "",
|
|
1636
|
+
mediaType,
|
|
1637
|
+
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1638
|
+
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1639
|
+
creativeId: bid.creativeId || ""
|
|
1640
|
+
};
|
|
1641
|
+
const isRefresh = adUnitCode && (this.slotViewabilityRecords.has(adUnitCode) || this.slotRefreshIndices.has(adUnitCode) && this.slotRefreshIndices.get(adUnitCode) > 0);
|
|
1642
|
+
if (isRefresh) {
|
|
1643
|
+
this.flushSlotTimeInView(adUnitCode);
|
|
1644
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? 0) + 1;
|
|
1645
|
+
this.slotRefreshIndices.set(adUnitCode, nextRefreshIndex);
|
|
1646
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1647
|
+
auctionId,
|
|
1648
|
+
transactionId,
|
|
1649
|
+
adUnitCode,
|
|
1650
|
+
bid: bidTrace,
|
|
1651
|
+
metadata: { refresh_index: String(nextRefreshIndex) }
|
|
1652
|
+
});
|
|
1653
|
+
} else if (adUnitCode && !this.slotRefreshIndices.has(adUnitCode)) {
|
|
1654
|
+
this.slotRefreshIndices.set(adUnitCode, 0);
|
|
1655
|
+
}
|
|
1656
|
+
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? 0 : 0;
|
|
1087
1657
|
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1088
|
-
auctionId
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1094
|
-
width: Number.isFinite(bid.width) ? bid.width : 0,
|
|
1095
|
-
height: Number.isFinite(bid.height) ? bid.height : 0,
|
|
1096
|
-
dealId: bid.dealId || "",
|
|
1097
|
-
mediaType: bid.mediaType || "banner",
|
|
1098
|
-
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1099
|
-
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1100
|
-
creativeId: bid.creativeId || ""
|
|
1101
|
-
}
|
|
1658
|
+
auctionId,
|
|
1659
|
+
transactionId,
|
|
1660
|
+
adUnitCode,
|
|
1661
|
+
bid: bidTrace,
|
|
1662
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
1102
1663
|
});
|
|
1664
|
+
if (this.config.viewabilityEnabled && typeof document !== "undefined") {
|
|
1665
|
+
let el = null;
|
|
1666
|
+
const targetId = adUnitCode || data.adId || bid.adId;
|
|
1667
|
+
if (targetId) {
|
|
1668
|
+
el = document.getElementById(targetId);
|
|
1669
|
+
}
|
|
1670
|
+
if (!el && typeof window !== "undefined" && window.googletag?.pubads) {
|
|
1671
|
+
try {
|
|
1672
|
+
const slots = window.googletag.pubads().getSlots();
|
|
1673
|
+
for (const slot of slots) {
|
|
1674
|
+
if (slot.getAdUnitPath?.() === adUnitCode || slot.getSlotElementId?.() === adUnitCode) {
|
|
1675
|
+
el = document.getElementById(slot.getSlotElementId());
|
|
1676
|
+
if (el) break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
} catch {
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (!el && targetId) {
|
|
1683
|
+
try {
|
|
1684
|
+
el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (el) {
|
|
1689
|
+
this.observeSlot(el, adUnitCode || targetId, {
|
|
1690
|
+
adUnitCode,
|
|
1691
|
+
auctionId,
|
|
1692
|
+
transactionId,
|
|
1693
|
+
mediaType,
|
|
1694
|
+
bid: bidTrace,
|
|
1695
|
+
refreshIndex: currentRefreshIndex,
|
|
1696
|
+
emitRefreshEvent: false
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1103
1700
|
}
|
|
1104
1701
|
isDuplicate(eventName, data) {
|
|
1105
1702
|
return this.deduper.isDuplicate(eventName, data);
|
|
@@ -1107,6 +1704,7 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1107
1704
|
enqueue(type, eventName, data, level) {
|
|
1108
1705
|
if (!this.isEnabled) return;
|
|
1109
1706
|
if (!this.shouldSample(type, level)) return;
|
|
1707
|
+
extendSession();
|
|
1110
1708
|
const protoEvent = {
|
|
1111
1709
|
eventId: generateUUID(),
|
|
1112
1710
|
timestampMs: Date.now(),
|
|
@@ -1121,11 +1719,14 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1121
1719
|
protoEvent.bid = {
|
|
1122
1720
|
bidder: data.bid.bidder || "",
|
|
1123
1721
|
cpm: Number.isFinite(data.bid.cpm) ? data.bid.cpm : 0,
|
|
1124
|
-
|
|
1722
|
+
// No defaults here: handlers with a priced bid (bidResponse, bidWon,
|
|
1723
|
+
// impression) set currency/mediaType themselves. Defaulting for the
|
|
1724
|
+
// rest would stamp fake "USD"/"banner" on noBid and bidRequest rows.
|
|
1725
|
+
currency: data.bid.currency || "",
|
|
1125
1726
|
width: Number.isFinite(data.bid.width) ? data.bid.width : 0,
|
|
1126
1727
|
height: Number.isFinite(data.bid.height) ? data.bid.height : 0,
|
|
1127
1728
|
dealId: data.bid.dealId || "",
|
|
1128
|
-
mediaType: data.bid.mediaType || "
|
|
1729
|
+
mediaType: data.bid.mediaType || "",
|
|
1129
1730
|
latencyMs: Number.isFinite(data.bid.latencyMs) ? data.bid.latencyMs : 0,
|
|
1130
1731
|
advertiserDomain: data.bid.advertiserDomain || "",
|
|
1131
1732
|
creativeId: data.bid.creativeId || ""
|
|
@@ -1141,6 +1742,9 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1141
1742
|
if (data.gpid) {
|
|
1142
1743
|
metadata.gpid = String(data.gpid);
|
|
1143
1744
|
}
|
|
1745
|
+
if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
|
|
1746
|
+
metadata.refresh_index = String(data.refreshIndex);
|
|
1747
|
+
}
|
|
1144
1748
|
if (Object.keys(metadata).length > 0) {
|
|
1145
1749
|
protoEvent.metadata = metadata;
|
|
1146
1750
|
}
|
|
@@ -1170,14 +1774,13 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1170
1774
|
this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
|
|
1171
1775
|
}
|
|
1172
1776
|
}
|
|
1173
|
-
// Drains
|
|
1174
|
-
//
|
|
1777
|
+
// Drains a batch (up to MAX_PAYLOAD_BYTES) from the queue.
|
|
1778
|
+
// Shared by flush() and flushBeacon().
|
|
1175
1779
|
drainBatch() {
|
|
1176
1780
|
if (this.queue.length === 0 || !this.config.endpoint) return null;
|
|
1177
|
-
|
|
1178
|
-
this.queue = [];
|
|
1781
|
+
let events = this.queue;
|
|
1179
1782
|
const domain = typeof window !== "undefined" ? window.location.hostname : "";
|
|
1180
|
-
const
|
|
1783
|
+
const buildBatch = (evts) => ({
|
|
1181
1784
|
propertyId: this.config.propertyId,
|
|
1182
1785
|
pageviewId: this.config.pageviewId,
|
|
1183
1786
|
sessionId: this.config.sessionId,
|
|
@@ -1187,25 +1790,42 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1187
1790
|
deviceType: this.config.deviceType,
|
|
1188
1791
|
userId: this.config.userId,
|
|
1189
1792
|
domain,
|
|
1190
|
-
events
|
|
1191
|
-
};
|
|
1793
|
+
events: evts
|
|
1794
|
+
});
|
|
1795
|
+
let encoded = TraceEventBatch.encode(
|
|
1796
|
+
buildBatch(events)
|
|
1797
|
+
).finish();
|
|
1798
|
+
while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
|
|
1799
|
+
events = events.slice(0, Math.floor(events.length / 2));
|
|
1800
|
+
encoded = TraceEventBatch.encode(
|
|
1801
|
+
buildBatch(events)
|
|
1802
|
+
).finish();
|
|
1803
|
+
}
|
|
1804
|
+
this.queue = this.queue.slice(events.length);
|
|
1192
1805
|
return {
|
|
1193
1806
|
url: `${this.config.endpoint}/${this.config.propertyId}`,
|
|
1194
1807
|
// protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
|
|
1195
1808
|
// always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
|
|
1196
|
-
encoded
|
|
1809
|
+
encoded,
|
|
1197
1810
|
events
|
|
1198
1811
|
};
|
|
1199
1812
|
}
|
|
1200
|
-
sendFetch(payload) {
|
|
1201
|
-
|
|
1813
|
+
sendFetch(payload, useKeepalive = false) {
|
|
1814
|
+
const fetchOpts = {
|
|
1202
1815
|
method: "POST",
|
|
1203
1816
|
headers: { "Content-Type": "application/x-protobuf" },
|
|
1204
|
-
body: payload.encoded
|
|
1205
|
-
|
|
1206
|
-
|
|
1817
|
+
body: payload.encoded
|
|
1818
|
+
};
|
|
1819
|
+
if (useKeepalive) {
|
|
1820
|
+
fetchOpts.keepalive = true;
|
|
1821
|
+
}
|
|
1822
|
+
fetch(payload.url, fetchOpts).then((res) => {
|
|
1207
1823
|
if (!res.ok) {
|
|
1208
1824
|
this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
|
|
1825
|
+
if (res.status >= 400 && res.status < 500) {
|
|
1826
|
+
this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1209
1829
|
this.handleSendFailure(payload.events);
|
|
1210
1830
|
} else {
|
|
1211
1831
|
this.consecutiveSendFailures = 0;
|
|
@@ -1215,6 +1835,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1215
1835
|
this.log("ERROR", "Failed to send batch", err);
|
|
1216
1836
|
this.handleSendFailure(payload.events);
|
|
1217
1837
|
});
|
|
1838
|
+
if (typeof process !== "undefined" && typeof process._tickCallback === "function") {
|
|
1839
|
+
try {
|
|
1840
|
+
process._tickCallback();
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1218
1844
|
}
|
|
1219
1845
|
handleSendFailure(events) {
|
|
1220
1846
|
this.consecutiveSendFailures++;
|
|
@@ -1234,24 +1860,31 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1234
1860
|
}
|
|
1235
1861
|
flush() {
|
|
1236
1862
|
if (Date.now() < this.nextSendAllowedAt) return;
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1863
|
+
let payload = this.drainBatch();
|
|
1864
|
+
while (payload) {
|
|
1865
|
+
this.sendFetch(payload, false);
|
|
1866
|
+
if (Date.now() < this.nextSendAllowedAt) break;
|
|
1867
|
+
payload = this.drainBatch();
|
|
1868
|
+
}
|
|
1240
1869
|
}
|
|
1241
1870
|
flushBeacon() {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1871
|
+
let payload = this.drainBatch();
|
|
1872
|
+
while (payload) {
|
|
1873
|
+
let sent = false;
|
|
1874
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
1875
|
+
try {
|
|
1876
|
+
const blob = new Blob([payload.encoded], {
|
|
1877
|
+
type: "application/x-protobuf"
|
|
1878
|
+
});
|
|
1879
|
+
sent = navigator.sendBeacon(payload.url, blob);
|
|
1880
|
+
} catch {
|
|
1881
|
+
sent = false;
|
|
1882
|
+
}
|
|
1251
1883
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1884
|
+
if (!sent) {
|
|
1885
|
+
this.sendFetch(payload, true);
|
|
1886
|
+
}
|
|
1887
|
+
payload = this.drainBatch();
|
|
1255
1888
|
}
|
|
1256
1889
|
}
|
|
1257
1890
|
log(level, msg, ...args) {
|