@bidkernel/analytics 0.4.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/dist/analytics.global.js +1 -1
- package/dist/index.d.mts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +567 -18
- package/dist/index.mjs +567 -18
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -805,7 +805,8 @@ function parseBidDimensions(bid) {
|
|
|
805
805
|
height: Number.isFinite(bid?.height) ? bid.height : 0
|
|
806
806
|
};
|
|
807
807
|
}
|
|
808
|
-
var BidkernelPrebidAnalytics = class {
|
|
808
|
+
var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
809
|
+
static activeInstances = /* @__PURE__ */ new Set();
|
|
809
810
|
config;
|
|
810
811
|
queue = [];
|
|
811
812
|
errorCount = 0;
|
|
@@ -821,6 +822,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
821
822
|
consecutiveSendFailures = 0;
|
|
822
823
|
nextSendAllowedAt = 0;
|
|
823
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();
|
|
824
831
|
constructor(config) {
|
|
825
832
|
this.config = {
|
|
826
833
|
endpoint: config.endpoint || "",
|
|
@@ -834,19 +841,36 @@ var BidkernelPrebidAnalytics = class {
|
|
|
834
841
|
auctionEnabled: config.auctionEnabled ?? true,
|
|
835
842
|
warningsEnabled: config.warningsEnabled ?? true,
|
|
836
843
|
errorsEnabled: config.errorsEnabled ?? true,
|
|
844
|
+
viewabilityEnabled: config.viewabilityEnabled ?? true,
|
|
837
845
|
logLevel: config.logLevel || "INFO",
|
|
838
846
|
pbjsGlobalName: config.pbjsGlobalName || "pbjs",
|
|
839
847
|
attachPbjsListeners: config.attachPbjsListeners ?? true
|
|
840
848
|
};
|
|
841
|
-
this.boundFlushBeacon = () =>
|
|
849
|
+
this.boundFlushBeacon = () => {
|
|
850
|
+
this.flushAllSlotsTimeInView();
|
|
851
|
+
this.flushBeacon();
|
|
852
|
+
};
|
|
842
853
|
this.boundVisibilityChange = () => this.handleVisibilityChange();
|
|
843
854
|
}
|
|
844
855
|
enable() {
|
|
845
856
|
if (this.isEnabled) return;
|
|
846
857
|
this.isEnabled = true;
|
|
858
|
+
_BidkernelPrebidAnalytics.activeInstances.add(this);
|
|
847
859
|
if (!this.config.endpoint) {
|
|
848
860
|
this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
|
|
849
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
|
+
}
|
|
850
874
|
if (this.config.attachPbjsListeners) {
|
|
851
875
|
const win = typeof window !== "undefined" ? window : {};
|
|
852
876
|
const pbjs = win[this.config.pbjsGlobalName] || {};
|
|
@@ -926,7 +950,28 @@ var BidkernelPrebidAnalytics = class {
|
|
|
926
950
|
}
|
|
927
951
|
disable() {
|
|
928
952
|
if (!this.isEnabled) return;
|
|
953
|
+
this.flushAllSlotsTimeInView();
|
|
929
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();
|
|
930
975
|
if (this.flushTimer) {
|
|
931
976
|
clearInterval(this.flushTimer);
|
|
932
977
|
this.flushTimer = null;
|
|
@@ -950,8 +995,443 @@ var BidkernelPrebidAnalytics = class {
|
|
|
950
995
|
this.flush();
|
|
951
996
|
}
|
|
952
997
|
handleVisibilityChange() {
|
|
953
|
-
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
|
+
}
|
|
954
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);
|
|
955
1435
|
}
|
|
956
1436
|
}
|
|
957
1437
|
trackRawEvent(level, eventName, data) {
|
|
@@ -966,6 +1446,8 @@ var BidkernelPrebidAnalytics = class {
|
|
|
966
1446
|
}
|
|
967
1447
|
navigate(pageviewId, pageUrl) {
|
|
968
1448
|
this.disable();
|
|
1449
|
+
this.slotRefreshIndices.clear();
|
|
1450
|
+
this.pendingThresholdListeners.clear();
|
|
969
1451
|
this.config.pageviewId = pageviewId || generateUUID();
|
|
970
1452
|
if (pageUrl) {
|
|
971
1453
|
try {
|
|
@@ -1140,23 +1622,81 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1140
1622
|
handleAdRenderSucceeded(data) {
|
|
1141
1623
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1142
1624
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1143
|
-
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;
|
|
1144
1657
|
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1145
|
-
auctionId
|
|
1146
|
-
transactionId
|
|
1147
|
-
adUnitCode
|
|
1148
|
-
bid:
|
|
1149
|
-
|
|
1150
|
-
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : 0,
|
|
1151
|
-
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1152
|
-
...parseBidDimensions(bid),
|
|
1153
|
-
dealId: bid.dealId || "",
|
|
1154
|
-
mediaType: bid.mediaType || "banner",
|
|
1155
|
-
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1156
|
-
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1157
|
-
creativeId: bid.creativeId || ""
|
|
1158
|
-
}
|
|
1658
|
+
auctionId,
|
|
1659
|
+
transactionId,
|
|
1660
|
+
adUnitCode,
|
|
1661
|
+
bid: bidTrace,
|
|
1662
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
1159
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
|
+
}
|
|
1160
1700
|
}
|
|
1161
1701
|
isDuplicate(eventName, data) {
|
|
1162
1702
|
return this.deduper.isDuplicate(eventName, data);
|
|
@@ -1202,6 +1742,9 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1202
1742
|
if (data.gpid) {
|
|
1203
1743
|
metadata.gpid = String(data.gpid);
|
|
1204
1744
|
}
|
|
1745
|
+
if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
|
|
1746
|
+
metadata.refresh_index = String(data.refreshIndex);
|
|
1747
|
+
}
|
|
1205
1748
|
if (Object.keys(metadata).length > 0) {
|
|
1206
1749
|
protoEvent.metadata = metadata;
|
|
1207
1750
|
}
|
|
@@ -1292,6 +1835,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1292
1835
|
this.log("ERROR", "Failed to send batch", err);
|
|
1293
1836
|
this.handleSendFailure(payload.events);
|
|
1294
1837
|
});
|
|
1838
|
+
if (typeof process !== "undefined" && typeof process._tickCallback === "function") {
|
|
1839
|
+
try {
|
|
1840
|
+
process._tickCallback();
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1295
1844
|
}
|
|
1296
1845
|
handleSendFailure(events) {
|
|
1297
1846
|
this.consecutiveSendFailures++;
|
package/package.json
CHANGED