@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.js
CHANGED
|
@@ -835,7 +835,8 @@ function parseBidDimensions(bid) {
|
|
|
835
835
|
height: Number.isFinite(bid?.height) ? bid.height : 0
|
|
836
836
|
};
|
|
837
837
|
}
|
|
838
|
-
var BidkernelPrebidAnalytics = class {
|
|
838
|
+
var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
839
|
+
static activeInstances = /* @__PURE__ */ new Set();
|
|
839
840
|
config;
|
|
840
841
|
queue = [];
|
|
841
842
|
errorCount = 0;
|
|
@@ -851,6 +852,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
851
852
|
consecutiveSendFailures = 0;
|
|
852
853
|
nextSendAllowedAt = 0;
|
|
853
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();
|
|
854
861
|
constructor(config) {
|
|
855
862
|
this.config = {
|
|
856
863
|
endpoint: config.endpoint || "",
|
|
@@ -864,19 +871,36 @@ var BidkernelPrebidAnalytics = class {
|
|
|
864
871
|
auctionEnabled: config.auctionEnabled ?? true,
|
|
865
872
|
warningsEnabled: config.warningsEnabled ?? true,
|
|
866
873
|
errorsEnabled: config.errorsEnabled ?? true,
|
|
874
|
+
viewabilityEnabled: config.viewabilityEnabled ?? true,
|
|
867
875
|
logLevel: config.logLevel || "INFO",
|
|
868
876
|
pbjsGlobalName: config.pbjsGlobalName || "pbjs",
|
|
869
877
|
attachPbjsListeners: config.attachPbjsListeners ?? true
|
|
870
878
|
};
|
|
871
|
-
this.boundFlushBeacon = () =>
|
|
879
|
+
this.boundFlushBeacon = () => {
|
|
880
|
+
this.flushAllSlotsTimeInView();
|
|
881
|
+
this.flushBeacon();
|
|
882
|
+
};
|
|
872
883
|
this.boundVisibilityChange = () => this.handleVisibilityChange();
|
|
873
884
|
}
|
|
874
885
|
enable() {
|
|
875
886
|
if (this.isEnabled) return;
|
|
876
887
|
this.isEnabled = true;
|
|
888
|
+
_BidkernelPrebidAnalytics.activeInstances.add(this);
|
|
877
889
|
if (!this.config.endpoint) {
|
|
878
890
|
this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
|
|
879
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
|
+
}
|
|
880
904
|
if (this.config.attachPbjsListeners) {
|
|
881
905
|
const win = typeof window !== "undefined" ? window : {};
|
|
882
906
|
const pbjs = win[this.config.pbjsGlobalName] || {};
|
|
@@ -956,7 +980,28 @@ var BidkernelPrebidAnalytics = class {
|
|
|
956
980
|
}
|
|
957
981
|
disable() {
|
|
958
982
|
if (!this.isEnabled) return;
|
|
983
|
+
this.flushAllSlotsTimeInView();
|
|
959
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();
|
|
960
1005
|
if (this.flushTimer) {
|
|
961
1006
|
clearInterval(this.flushTimer);
|
|
962
1007
|
this.flushTimer = null;
|
|
@@ -980,8 +1025,443 @@ var BidkernelPrebidAnalytics = class {
|
|
|
980
1025
|
this.flush();
|
|
981
1026
|
}
|
|
982
1027
|
handleVisibilityChange() {
|
|
983
|
-
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
|
+
}
|
|
984
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);
|
|
985
1465
|
}
|
|
986
1466
|
}
|
|
987
1467
|
trackRawEvent(level, eventName, data) {
|
|
@@ -996,6 +1476,8 @@ var BidkernelPrebidAnalytics = class {
|
|
|
996
1476
|
}
|
|
997
1477
|
navigate(pageviewId, pageUrl) {
|
|
998
1478
|
this.disable();
|
|
1479
|
+
this.slotRefreshIndices.clear();
|
|
1480
|
+
this.pendingThresholdListeners.clear();
|
|
999
1481
|
this.config.pageviewId = pageviewId || generateUUID();
|
|
1000
1482
|
if (pageUrl) {
|
|
1001
1483
|
try {
|
|
@@ -1170,23 +1652,81 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1170
1652
|
handleAdRenderSucceeded(data) {
|
|
1171
1653
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1172
1654
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1173
|
-
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;
|
|
1174
1687
|
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1175
|
-
auctionId
|
|
1176
|
-
transactionId
|
|
1177
|
-
adUnitCode
|
|
1178
|
-
bid:
|
|
1179
|
-
|
|
1180
|
-
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : 0,
|
|
1181
|
-
currency: bid.originalCurrency ?? bid.currency ?? "USD",
|
|
1182
|
-
...parseBidDimensions(bid),
|
|
1183
|
-
dealId: bid.dealId || "",
|
|
1184
|
-
mediaType: bid.mediaType || "banner",
|
|
1185
|
-
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
|
|
1186
|
-
advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
|
|
1187
|
-
creativeId: bid.creativeId || ""
|
|
1188
|
-
}
|
|
1688
|
+
auctionId,
|
|
1689
|
+
transactionId,
|
|
1690
|
+
adUnitCode,
|
|
1691
|
+
bid: bidTrace,
|
|
1692
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
1189
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
|
+
}
|
|
1190
1730
|
}
|
|
1191
1731
|
isDuplicate(eventName, data) {
|
|
1192
1732
|
return this.deduper.isDuplicate(eventName, data);
|
|
@@ -1232,6 +1772,9 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1232
1772
|
if (data.gpid) {
|
|
1233
1773
|
metadata.gpid = String(data.gpid);
|
|
1234
1774
|
}
|
|
1775
|
+
if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
|
|
1776
|
+
metadata.refresh_index = String(data.refreshIndex);
|
|
1777
|
+
}
|
|
1235
1778
|
if (Object.keys(metadata).length > 0) {
|
|
1236
1779
|
protoEvent.metadata = metadata;
|
|
1237
1780
|
}
|
|
@@ -1322,6 +1865,12 @@ var BidkernelPrebidAnalytics = class {
|
|
|
1322
1865
|
this.log("ERROR", "Failed to send batch", err);
|
|
1323
1866
|
this.handleSendFailure(payload.events);
|
|
1324
1867
|
});
|
|
1868
|
+
if (typeof process !== "undefined" && typeof process._tickCallback === "function") {
|
|
1869
|
+
try {
|
|
1870
|
+
process._tickCallback();
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1325
1874
|
}
|
|
1326
1875
|
handleSendFailure(events) {
|
|
1327
1876
|
this.consecutiveSendFailures++;
|