@bidkernel/analytics 0.5.0 → 0.7.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 +7 -7
- package/dist/analytics.global.js +1 -1
- package/dist/index.d.mts +103 -1
- package/dist/index.d.ts +103 -1
- package/dist/index.js +758 -49
- package/dist/index.mjs +758 -49
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -628,6 +628,11 @@ var MAX_QUEUE_SIZE = 200;
|
|
|
628
628
|
var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
|
|
629
629
|
var MAX_CONSECUTIVE_SEND_FAILURES = 10;
|
|
630
630
|
var MAX_PAYLOAD_BYTES = 32 * 1024;
|
|
631
|
+
var SAFE_CONTENT_TYPE = "text/plain";
|
|
632
|
+
var PENDING_BATCH_KEY_PREFIX = "_bidkernel_pending_";
|
|
633
|
+
var MAX_PENDING_BATCHES = 20;
|
|
634
|
+
var PENDING_BATCH_MAX_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
635
|
+
var PENDING_BATCH_CLEANUP_DELAY_MS = 1e4;
|
|
631
636
|
var EVENT_NAME_TO_TYPE = {
|
|
632
637
|
auctionStart: TraceEventType.AUCTION_START,
|
|
633
638
|
auctionEnd: TraceEventType.AUCTION_END,
|
|
@@ -643,6 +648,11 @@ var EVENT_NAME_TO_TYPE = {
|
|
|
643
648
|
timeInView: TraceEventType.TIME_IN_VIEW,
|
|
644
649
|
viewable: TraceEventType.VIEWABLE
|
|
645
650
|
};
|
|
651
|
+
var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
|
|
652
|
+
TraceEventType.IMPRESSION,
|
|
653
|
+
TraceEventType.BID_WIN,
|
|
654
|
+
TraceEventType.CLICK
|
|
655
|
+
]);
|
|
646
656
|
var SESSION_KEY = "_bidkernel_session";
|
|
647
657
|
var SESSION_TS_KEY = "_bidkernel_session_ts";
|
|
648
658
|
var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
|
|
@@ -658,6 +668,22 @@ function generateUUID() {
|
|
|
658
668
|
return v.toString(16);
|
|
659
669
|
});
|
|
660
670
|
}
|
|
671
|
+
function bytesToBase64(bytes) {
|
|
672
|
+
let binary = "";
|
|
673
|
+
const chunk = 8192;
|
|
674
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
675
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
676
|
+
}
|
|
677
|
+
return btoa(binary);
|
|
678
|
+
}
|
|
679
|
+
function base64ToBytes(b64) {
|
|
680
|
+
const binary = atob(b64);
|
|
681
|
+
const bytes = new Uint8Array(binary.length);
|
|
682
|
+
for (let i = 0; i < binary.length; i++) {
|
|
683
|
+
bytes[i] = binary.charCodeAt(i);
|
|
684
|
+
}
|
|
685
|
+
return bytes;
|
|
686
|
+
}
|
|
661
687
|
function extendSession() {
|
|
662
688
|
const now = Date.now();
|
|
663
689
|
inMemorySessionTs = now;
|
|
@@ -822,12 +848,25 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
822
848
|
consecutiveSendFailures = 0;
|
|
823
849
|
nextSendAllowedAt = 0;
|
|
824
850
|
replayedEventCount = 0;
|
|
851
|
+
immediateFlushScheduled = false;
|
|
852
|
+
// localStorage keys of exit batches this instance persisted, so a page that
|
|
853
|
+
// survives its own pagehide/hidden (bfcache restore, tab re-focus) can
|
|
854
|
+
// remove them instead of leaving them for a duplicate resend.
|
|
855
|
+
persistedBatchKeys = [];
|
|
856
|
+
persistedCleanupTimer = null;
|
|
825
857
|
// Viewability & refresh tracking
|
|
826
858
|
intersectionObserver = null;
|
|
827
859
|
slotViewabilityRecords = /* @__PURE__ */ new Map();
|
|
828
860
|
elementToSlotId = /* @__PURE__ */ new Map();
|
|
829
861
|
slotRefreshIndices = /* @__PURE__ */ new Map();
|
|
862
|
+
slotLastAuctionIds = /* @__PURE__ */ new Map();
|
|
830
863
|
pendingThresholdListeners = /* @__PURE__ */ new Map();
|
|
864
|
+
// Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
|
|
865
|
+
slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
|
|
866
|
+
// Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
|
|
867
|
+
cachedWinningBids = /* @__PURE__ */ new Map();
|
|
868
|
+
// Active video player attachment cleanup routines
|
|
869
|
+
videoDetachCleanups = /* @__PURE__ */ new Set();
|
|
831
870
|
constructor(config) {
|
|
832
871
|
this.config = {
|
|
833
872
|
endpoint: config.endpoint || "",
|
|
@@ -946,6 +985,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
946
985
|
document.addEventListener("visibilitychange", this.boundVisibilityChange);
|
|
947
986
|
}
|
|
948
987
|
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
988
|
+
this.resendPersistedBatches();
|
|
949
989
|
}
|
|
950
990
|
}
|
|
951
991
|
disable() {
|
|
@@ -972,10 +1012,24 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
972
1012
|
}
|
|
973
1013
|
this.slotViewabilityRecords.clear();
|
|
974
1014
|
this.elementToSlotId.clear();
|
|
1015
|
+
for (const cleanup of this.videoDetachCleanups) {
|
|
1016
|
+
try {
|
|
1017
|
+
cleanup();
|
|
1018
|
+
} catch {
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
this.videoDetachCleanups.clear();
|
|
1022
|
+
this.cachedWinningBids.clear();
|
|
1023
|
+
this.slotEmittedImpressionKeys.clear();
|
|
1024
|
+
this.slotLastAuctionIds.clear();
|
|
975
1025
|
if (this.flushTimer) {
|
|
976
1026
|
clearInterval(this.flushTimer);
|
|
977
1027
|
this.flushTimer = null;
|
|
978
1028
|
}
|
|
1029
|
+
if (this.persistedCleanupTimer) {
|
|
1030
|
+
clearTimeout(this.persistedCleanupTimer);
|
|
1031
|
+
this.persistedCleanupTimer = null;
|
|
1032
|
+
}
|
|
979
1033
|
if (typeof window !== "undefined") {
|
|
980
1034
|
window.removeEventListener("pagehide", this.boundFlushBeacon);
|
|
981
1035
|
if (typeof document !== "undefined") {
|
|
@@ -1290,6 +1344,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1290
1344
|
if (options?.bid !== void 0) existing.bidPayload = options.bid;
|
|
1291
1345
|
if (options?.metadata !== void 0) existing.metadata = options.metadata;
|
|
1292
1346
|
}
|
|
1347
|
+
if (options?.auctionId) {
|
|
1348
|
+
this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
|
|
1349
|
+
}
|
|
1293
1350
|
if (el) {
|
|
1294
1351
|
if (existing.element && existing.element !== el && this.intersectionObserver) {
|
|
1295
1352
|
this.intersectionObserver.unobserve(existing.element);
|
|
@@ -1302,6 +1359,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1302
1359
|
} else {
|
|
1303
1360
|
const initialRefreshIndex = options?.refreshIndex ?? this.slotRefreshIndices.get(resolvedSlotId) ?? 0;
|
|
1304
1361
|
this.slotRefreshIndices.set(resolvedSlotId, initialRefreshIndex);
|
|
1362
|
+
if (options?.auctionId) {
|
|
1363
|
+
this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
|
|
1364
|
+
}
|
|
1305
1365
|
const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
|
|
1306
1366
|
this.pendingThresholdListeners.delete(resolvedSlotId);
|
|
1307
1367
|
const record = {
|
|
@@ -1357,7 +1417,485 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1357
1417
|
this.unobserveSlot(slotId);
|
|
1358
1418
|
this.slotViewabilityRecords.delete(slotId);
|
|
1359
1419
|
this.slotRefreshIndices.delete(slotId);
|
|
1420
|
+
this.slotLastAuctionIds.delete(slotId);
|
|
1360
1421
|
this.pendingThresholdListeners.delete(slotId);
|
|
1422
|
+
const prefix = `${escapeKeyPart(slotId)}:`;
|
|
1423
|
+
for (const key of Array.from(this.slotEmittedImpressionKeys)) {
|
|
1424
|
+
if (key === slotId || key.startsWith(prefix)) {
|
|
1425
|
+
this.slotEmittedImpressionKeys.delete(key);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
this.cachedWinningBids.delete(slotId);
|
|
1429
|
+
}
|
|
1430
|
+
hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
|
|
1431
|
+
const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
|
|
1432
|
+
const baseSlotKey = `${escapeKeyPart(slotId)}:${rIndex}`;
|
|
1433
|
+
if (creativeId) {
|
|
1434
|
+
if (auctionId) {
|
|
1435
|
+
return this.slotEmittedImpressionKeys.has(
|
|
1436
|
+
`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
|
|
1437
|
+
) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1438
|
+
}
|
|
1439
|
+
return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1440
|
+
}
|
|
1441
|
+
if (auctionId) {
|
|
1442
|
+
return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
|
|
1443
|
+
}
|
|
1444
|
+
return this.slotEmittedImpressionKeys.has(baseSlotKey);
|
|
1445
|
+
}
|
|
1446
|
+
markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
|
|
1447
|
+
const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
|
|
1448
|
+
this.slotEmittedImpressionKeys.add(baseSlotKey);
|
|
1449
|
+
if (creativeId) {
|
|
1450
|
+
this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1451
|
+
}
|
|
1452
|
+
if (auctionId) {
|
|
1453
|
+
this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
|
|
1454
|
+
if (creativeId) {
|
|
1455
|
+
this.slotEmittedImpressionKeys.add(
|
|
1456
|
+
`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
recordImpression(slotId, options) {
|
|
1462
|
+
if (!this.isEnabled) return false;
|
|
1463
|
+
const resolvedSlotId = slotId || options?.adUnitCode || "";
|
|
1464
|
+
const adUnitCode = options?.adUnitCode || resolvedSlotId;
|
|
1465
|
+
const cached = (options?.auctionId && resolvedSlotId ? this.cachedWinningBids.get(`${options.auctionId}:${resolvedSlotId}`) : void 0) || (options?.auctionId && adUnitCode ? this.cachedWinningBids.get(`${options.auctionId}:${adUnitCode}`) : void 0) || (resolvedSlotId ? this.cachedWinningBids.get(resolvedSlotId) : void 0) || (adUnitCode ? this.cachedWinningBids.get(adUnitCode) : void 0);
|
|
1466
|
+
const rawBid = options?.bid || cached?.rawBid || {};
|
|
1467
|
+
const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
|
|
1468
|
+
const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
|
|
1469
|
+
const creativeId = rawBid.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || "";
|
|
1470
|
+
const mediaType = options?.mediaType || rawBid.mediaType || cached?.bidTrace?.mediaType || "banner";
|
|
1471
|
+
const targetSlot = resolvedSlotId || adUnitCode;
|
|
1472
|
+
const rec = (resolvedSlotId ? this.slotViewabilityRecords.get(resolvedSlotId) : void 0) || (adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0);
|
|
1473
|
+
const lastAuctionId = targetSlot ? this.slotLastAuctionIds.get(targetSlot) || (rec ? rec.auctionId : void 0) : void 0;
|
|
1474
|
+
const isDifferentAuction = Boolean(
|
|
1475
|
+
targetSlot && auctionId && lastAuctionId && lastAuctionId !== auctionId && options?.refreshIndex === void 0
|
|
1476
|
+
);
|
|
1477
|
+
const bidTrace = {
|
|
1478
|
+
bidder: rawBid.bidderCode || rawBid.bidder || cached?.bidTrace?.bidder || "",
|
|
1479
|
+
cpm: Number.isFinite(rawBid.originalCpm) ? rawBid.originalCpm : Number.isFinite(rawBid.cpm) ? rawBid.cpm : cached?.bidTrace?.cpm ?? 0,
|
|
1480
|
+
currency: rawBid.originalCurrency ?? rawBid.currency ?? cached?.bidTrace?.currency ?? "USD",
|
|
1481
|
+
...parseBidDimensions(rawBid).width ? parseBidDimensions(rawBid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(rawBid),
|
|
1482
|
+
dealId: rawBid.dealId || cached?.bidTrace?.dealId || "",
|
|
1483
|
+
mediaType,
|
|
1484
|
+
latencyMs: Number.isFinite(rawBid.timeToRespond) ? rawBid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
|
|
1485
|
+
advertiserDomain: rawBid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
|
|
1486
|
+
creativeId: rawBid.creativeId || cached?.bidTrace?.creativeId || ""
|
|
1487
|
+
};
|
|
1488
|
+
if (isDifferentAuction && targetSlot) {
|
|
1489
|
+
this.flushSlotTimeInView(targetSlot);
|
|
1490
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(targetSlot) ?? (rec ? rec.refreshIndex : 0)) + 1;
|
|
1491
|
+
this.slotRefreshIndices.set(targetSlot, nextRefreshIndex);
|
|
1492
|
+
this.slotLastAuctionIds.set(targetSlot, auctionId);
|
|
1493
|
+
if (rec) {
|
|
1494
|
+
rec.refreshIndex = nextRefreshIndex;
|
|
1495
|
+
rec.auctionId = auctionId;
|
|
1496
|
+
rec.transactionId = transactionId;
|
|
1497
|
+
rec.bidPayload = bidTrace;
|
|
1498
|
+
rec.viewableFired = false;
|
|
1499
|
+
rec.accumulatedTimeInViewMs = 0;
|
|
1500
|
+
}
|
|
1501
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1502
|
+
auctionId,
|
|
1503
|
+
transactionId,
|
|
1504
|
+
adUnitCode: targetSlot,
|
|
1505
|
+
bid: bidTrace,
|
|
1506
|
+
metadata: {
|
|
1507
|
+
...options?.metadata,
|
|
1508
|
+
refresh_index: String(nextRefreshIndex)
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
} else if (targetSlot) {
|
|
1512
|
+
if (auctionId) {
|
|
1513
|
+
this.slotLastAuctionIds.set(targetSlot, auctionId);
|
|
1514
|
+
}
|
|
1515
|
+
if (!this.slotRefreshIndices.has(targetSlot)) {
|
|
1516
|
+
this.slotRefreshIndices.set(targetSlot, 0);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
const currentRefreshIndex = options?.refreshIndex ?? (resolvedSlotId ? this.slotRefreshIndices.get(resolvedSlotId) ?? (adUnitCode ? this.slotRefreshIndices.get(adUnitCode) : void 0) ?? (rec ? rec.refreshIndex : 0) : 0);
|
|
1520
|
+
const alreadyEmitted = resolvedSlotId && this.hasImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId) || adUnitCode && adUnitCode !== resolvedSlotId && this.hasImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
|
|
1521
|
+
if (alreadyEmitted) {
|
|
1522
|
+
this.log(
|
|
1523
|
+
"DEBUG",
|
|
1524
|
+
`Impression already emitted for slot ${resolvedSlotId || adUnitCode} in cycle ${currentRefreshIndex}`
|
|
1525
|
+
);
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
if (resolvedSlotId)
|
|
1529
|
+
this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
|
|
1530
|
+
if (adUnitCode && adUnitCode !== resolvedSlotId)
|
|
1531
|
+
this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
|
|
1532
|
+
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1533
|
+
auctionId,
|
|
1534
|
+
transactionId,
|
|
1535
|
+
adUnitCode,
|
|
1536
|
+
bid: bidTrace,
|
|
1537
|
+
metadata: {
|
|
1538
|
+
...options?.metadata,
|
|
1539
|
+
refresh_index: String(currentRefreshIndex)
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
if (rec) {
|
|
1543
|
+
if (!rec.bidPayload && bidTrace) rec.bidPayload = bidTrace;
|
|
1544
|
+
if (!rec.auctionId && auctionId) rec.auctionId = auctionId;
|
|
1545
|
+
if (!rec.transactionId && transactionId) rec.transactionId = transactionId;
|
|
1546
|
+
}
|
|
1547
|
+
return true;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Bridges Google IMA SDK AdsManager events to Bidkernel analytics.
|
|
1551
|
+
* Defers IMPRESSION emission until AdEvent.STARTED (or IMPRESSION),
|
|
1552
|
+
* tracks milestones (FIRST_QUARTILE, MIDPOINT, THIRD_QUARTILE, COMPLETE),
|
|
1553
|
+
* and handles AD_ERROR.
|
|
1554
|
+
*/
|
|
1555
|
+
attachImaAdsManager(adsManager, options) {
|
|
1556
|
+
if (!adsManager || typeof adsManager.addEventListener !== "function") {
|
|
1557
|
+
this.log("WARN", "Invalid AdsManager passed to attachImaAdsManager");
|
|
1558
|
+
return () => {
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
const slotId = options?.slotId || options?.adUnitCode || "video";
|
|
1562
|
+
const adUnitCode = options?.adUnitCode || slotId;
|
|
1563
|
+
const auctionId = options?.auctionId || "";
|
|
1564
|
+
const transactionId = options?.transactionId || "";
|
|
1565
|
+
const getWinningBid = () => {
|
|
1566
|
+
return (auctionId ? this.cachedWinningBids.get(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.cachedWinningBids.get(`${auctionId}:${slotId}`) : void 0) || this.cachedWinningBids.get(adUnitCode) || (slotId !== adUnitCode ? this.cachedWinningBids.get(slotId) : void 0);
|
|
1567
|
+
};
|
|
1568
|
+
const onAdStartedOrImpression = (event) => {
|
|
1569
|
+
this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
|
|
1570
|
+
const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
|
|
1571
|
+
const adData = {};
|
|
1572
|
+
if (ad) {
|
|
1573
|
+
adData.creativeId = (typeof ad.getCreativeId === "function" ? ad.getCreativeId() : ad.creativeId) || "";
|
|
1574
|
+
adData.adId = (typeof ad.getAdId === "function" ? ad.getAdId() : ad.id) || "";
|
|
1575
|
+
adData.title = (typeof ad.getTitle === "function" ? ad.getTitle() : ad.title) || "";
|
|
1576
|
+
adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
|
|
1577
|
+
adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
|
|
1578
|
+
}
|
|
1579
|
+
const cached = getWinningBid();
|
|
1580
|
+
const mergedBid = {
|
|
1581
|
+
...cached?.rawBid || options?.bid,
|
|
1582
|
+
mediaType: "video",
|
|
1583
|
+
creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
|
|
1584
|
+
};
|
|
1585
|
+
const emitted = this.recordImpression(slotId, {
|
|
1586
|
+
adUnitCode,
|
|
1587
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1588
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1589
|
+
mediaType: "video",
|
|
1590
|
+
bid: mergedBid,
|
|
1591
|
+
metadata: {
|
|
1592
|
+
...options?.metadata,
|
|
1593
|
+
media_type: "video",
|
|
1594
|
+
...adData.title ? { ad_title: adData.title } : {},
|
|
1595
|
+
...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
if (emitted && options?.onImpression) {
|
|
1599
|
+
try {
|
|
1600
|
+
options.onImpression(slotId, { ad, bid: mergedBid });
|
|
1601
|
+
} catch (e) {
|
|
1602
|
+
this.log("ERROR", "Error in onImpression callback", e);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
const onMilestone = (milestone) => {
|
|
1607
|
+
this.log("DEBUG", `IMA AdEvent milestone: ${milestone}`);
|
|
1608
|
+
if (options?.onMilestone) {
|
|
1609
|
+
try {
|
|
1610
|
+
options.onMilestone(milestone, slotId);
|
|
1611
|
+
} catch (e) {
|
|
1612
|
+
this.log("ERROR", "Error in onMilestone callback", e);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
const onAdClick = (event) => {
|
|
1617
|
+
this.log("DEBUG", "IMA AdEvent.CLICK received", event);
|
|
1618
|
+
const cached = getWinningBid();
|
|
1619
|
+
this.enqueue(TraceEventType.CLICK, "click", {
|
|
1620
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1621
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1622
|
+
adUnitCode,
|
|
1623
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1624
|
+
metadata: {
|
|
1625
|
+
...options?.metadata,
|
|
1626
|
+
media_type: "video"
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
};
|
|
1630
|
+
const onAdError = (event) => {
|
|
1631
|
+
this.log("WARN", "IMA AdErrorEvent received", event);
|
|
1632
|
+
const err = typeof event?.getError === "function" ? event.getError() : event?.error || event;
|
|
1633
|
+
const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
|
|
1634
|
+
const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
|
|
1635
|
+
const cached = getWinningBid();
|
|
1636
|
+
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1637
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1638
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1639
|
+
adUnitCode,
|
|
1640
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1641
|
+
metadata: {
|
|
1642
|
+
...options?.metadata,
|
|
1643
|
+
reason: "ima_ad_error",
|
|
1644
|
+
message: msg,
|
|
1645
|
+
...code ? { ima_error_code: String(code) } : {}
|
|
1646
|
+
},
|
|
1647
|
+
error: typeof err === "object" ? err : new Error(msg)
|
|
1648
|
+
});
|
|
1649
|
+
if (options?.onError) {
|
|
1650
|
+
try {
|
|
1651
|
+
options.onError(err);
|
|
1652
|
+
} catch (e) {
|
|
1653
|
+
this.log("ERROR", "Error in onError callback", e);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
|
|
1658
|
+
const adEventType = googleIma?.AdEvent?.Type || {};
|
|
1659
|
+
const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
|
|
1660
|
+
const listeners = [
|
|
1661
|
+
{ type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
|
|
1662
|
+
{ type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
|
|
1663
|
+
{
|
|
1664
|
+
type: adEventType.FIRST_QUARTILE || "firstQuartile",
|
|
1665
|
+
handler: () => onMilestone("firstQuartile")
|
|
1666
|
+
},
|
|
1667
|
+
{ type: adEventType.MIDPOINT || "midpoint", handler: () => onMilestone("midpoint") },
|
|
1668
|
+
{
|
|
1669
|
+
type: adEventType.THIRD_QUARTILE || "thirdQuartile",
|
|
1670
|
+
handler: () => onMilestone("thirdQuartile")
|
|
1671
|
+
},
|
|
1672
|
+
{ type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
|
|
1673
|
+
{ type: adEventType.CLICK || "click", handler: onAdClick },
|
|
1674
|
+
{ type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
|
|
1675
|
+
];
|
|
1676
|
+
for (const { type, handler } of listeners) {
|
|
1677
|
+
try {
|
|
1678
|
+
adsManager.addEventListener(type, handler);
|
|
1679
|
+
} catch {
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const cleanup = () => {
|
|
1683
|
+
for (const { type, handler } of listeners) {
|
|
1684
|
+
try {
|
|
1685
|
+
adsManager.removeEventListener(type, handler);
|
|
1686
|
+
} catch {
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
this.videoDetachCleanups.delete(cleanup);
|
|
1690
|
+
};
|
|
1691
|
+
this.videoDetachCleanups.add(cleanup);
|
|
1692
|
+
return cleanup;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Attaches render hooks and viewability tracking to a video player.
|
|
1696
|
+
* Supports HTML5 <video> elements, container elements, or Google IMA AdsManager.
|
|
1697
|
+
*/
|
|
1698
|
+
attachVideoPlayer(target, options) {
|
|
1699
|
+
if (!target) {
|
|
1700
|
+
this.log("WARN", "attachVideoPlayer called with null/undefined target");
|
|
1701
|
+
return () => {
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
let el = null;
|
|
1705
|
+
if (typeof target === "string") {
|
|
1706
|
+
if (typeof document !== "undefined") {
|
|
1707
|
+
try {
|
|
1708
|
+
el = document.querySelector(target) || document.getElementById(target);
|
|
1709
|
+
} catch {
|
|
1710
|
+
el = document.getElementById(target);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
} else if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
|
|
1714
|
+
el = target;
|
|
1715
|
+
} else if (target && typeof target === "object" && target.nodeType === 1) {
|
|
1716
|
+
el = target;
|
|
1717
|
+
} else if (
|
|
1718
|
+
// Check if target is an IMA AdsManager
|
|
1719
|
+
typeof target.addEventListener === "function" && (typeof target.init === "function" || typeof target.start === "function" || typeof target.getCuePoints === "function" || typeof target.getVolume === "function" || target.__imaAdsManager)
|
|
1720
|
+
) {
|
|
1721
|
+
return this.attachImaAdsManager(target, options);
|
|
1722
|
+
} else if (target && typeof target.addEventListener === "function") {
|
|
1723
|
+
el = target;
|
|
1724
|
+
}
|
|
1725
|
+
const slotId = options?.slotId || options?.adUnitCode || (el ? el.id : "") || "video";
|
|
1726
|
+
const adUnitCode = options?.adUnitCode || slotId;
|
|
1727
|
+
const auctionId = options?.auctionId || "";
|
|
1728
|
+
const transactionId = options?.transactionId || "";
|
|
1729
|
+
const getWinningBid = () => {
|
|
1730
|
+
return (auctionId ? this.cachedWinningBids.get(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.cachedWinningBids.get(`${auctionId}:${slotId}`) : void 0) || this.cachedWinningBids.get(adUnitCode) || (slotId !== adUnitCode ? this.cachedWinningBids.get(slotId) : void 0);
|
|
1731
|
+
};
|
|
1732
|
+
let videoEl = null;
|
|
1733
|
+
if (el) {
|
|
1734
|
+
if (el.tagName && el.tagName.toLowerCase() === "video") {
|
|
1735
|
+
videoEl = el;
|
|
1736
|
+
} else {
|
|
1737
|
+
videoEl = el.querySelector("video");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const quartilesFired = {
|
|
1741
|
+
q1: false,
|
|
1742
|
+
q2: false,
|
|
1743
|
+
q3: false,
|
|
1744
|
+
q4: false
|
|
1745
|
+
};
|
|
1746
|
+
const triggerPlaybackImpression = (activeVideo) => {
|
|
1747
|
+
const vEl = activeVideo || videoEl || (el?.querySelector ? el.querySelector("video") : null);
|
|
1748
|
+
const cached = getWinningBid();
|
|
1749
|
+
const mergedBid = {
|
|
1750
|
+
...cached?.rawBid || options?.bid,
|
|
1751
|
+
mediaType: "video"
|
|
1752
|
+
};
|
|
1753
|
+
const emitted = this.recordImpression(slotId, {
|
|
1754
|
+
adUnitCode,
|
|
1755
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1756
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1757
|
+
mediaType: "video",
|
|
1758
|
+
bid: mergedBid,
|
|
1759
|
+
metadata: {
|
|
1760
|
+
...options?.metadata,
|
|
1761
|
+
media_type: "video",
|
|
1762
|
+
...vEl && Number.isFinite(vEl.duration) ? { video_duration_s: String(Math.round(vEl.duration)) } : {}
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
if (emitted && options?.onImpression) {
|
|
1766
|
+
try {
|
|
1767
|
+
options.onImpression(slotId, { element: vEl || el, bid: mergedBid });
|
|
1768
|
+
} catch (e) {
|
|
1769
|
+
this.log("ERROR", "Error in onImpression callback", e);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
};
|
|
1773
|
+
const handlePlaying = (e) => {
|
|
1774
|
+
this.log("DEBUG", "HTML5 video playing event received");
|
|
1775
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1776
|
+
triggerPlaybackImpression(targetVideo);
|
|
1777
|
+
};
|
|
1778
|
+
const handleTimeUpdate = (e) => {
|
|
1779
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1780
|
+
if (targetVideo && targetVideo.currentTime > 0) {
|
|
1781
|
+
triggerPlaybackImpression(targetVideo);
|
|
1782
|
+
if (Number.isFinite(targetVideo.duration) && targetVideo.duration > 0) {
|
|
1783
|
+
const progress = targetVideo.currentTime / targetVideo.duration;
|
|
1784
|
+
if (progress >= 0.25 && !quartilesFired.q1) {
|
|
1785
|
+
quartilesFired.q1 = true;
|
|
1786
|
+
options?.onMilestone?.("firstQuartile", slotId);
|
|
1787
|
+
}
|
|
1788
|
+
if (progress >= 0.5 && !quartilesFired.q2) {
|
|
1789
|
+
quartilesFired.q2 = true;
|
|
1790
|
+
options?.onMilestone?.("midpoint", slotId);
|
|
1791
|
+
}
|
|
1792
|
+
if (progress >= 0.75 && !quartilesFired.q3) {
|
|
1793
|
+
quartilesFired.q3 = true;
|
|
1794
|
+
options?.onMilestone?.("thirdQuartile", slotId);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
const handleEnded = () => {
|
|
1800
|
+
if (!quartilesFired.q4) {
|
|
1801
|
+
quartilesFired.q4 = true;
|
|
1802
|
+
options?.onMilestone?.("complete", slotId);
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
const handleError = (e) => {
|
|
1806
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1807
|
+
const err = targetVideo?.error || videoEl?.error;
|
|
1808
|
+
const cached = getWinningBid();
|
|
1809
|
+
const msg = err ? `HTML5 video error code ${err.code}: ${err.message}` : "Video playback error";
|
|
1810
|
+
this.log("WARN", msg);
|
|
1811
|
+
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1812
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1813
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1814
|
+
adUnitCode,
|
|
1815
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1816
|
+
metadata: {
|
|
1817
|
+
...options?.metadata,
|
|
1818
|
+
reason: "html5_video_error",
|
|
1819
|
+
message: msg,
|
|
1820
|
+
...err?.code ? { video_error_code: String(err.code) } : {}
|
|
1821
|
+
},
|
|
1822
|
+
error: err ? new Error(msg) : void 0
|
|
1823
|
+
});
|
|
1824
|
+
if (options?.onError) {
|
|
1825
|
+
try {
|
|
1826
|
+
options.onError(err);
|
|
1827
|
+
} catch (e2) {
|
|
1828
|
+
this.log("ERROR", "Error in onError callback", e2);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
const attachListenersToVideo = (targetVideo) => {
|
|
1833
|
+
targetVideo.addEventListener("playing", handlePlaying);
|
|
1834
|
+
targetVideo.addEventListener("play", handlePlaying);
|
|
1835
|
+
targetVideo.addEventListener("timeupdate", handleTimeUpdate);
|
|
1836
|
+
targetVideo.addEventListener("ended", handleEnded);
|
|
1837
|
+
targetVideo.addEventListener("error", handleError);
|
|
1838
|
+
};
|
|
1839
|
+
const removeListenersFromVideo = (targetVideo) => {
|
|
1840
|
+
targetVideo.removeEventListener("playing", handlePlaying);
|
|
1841
|
+
targetVideo.removeEventListener("play", handlePlaying);
|
|
1842
|
+
targetVideo.removeEventListener("timeupdate", handleTimeUpdate);
|
|
1843
|
+
targetVideo.removeEventListener("ended", handleEnded);
|
|
1844
|
+
targetVideo.removeEventListener("error", handleError);
|
|
1845
|
+
};
|
|
1846
|
+
let mutationObserver = null;
|
|
1847
|
+
if (el && el !== videoEl) {
|
|
1848
|
+
el.addEventListener("playing", handlePlaying, true);
|
|
1849
|
+
el.addEventListener("play", handlePlaying, true);
|
|
1850
|
+
el.addEventListener("timeupdate", handleTimeUpdate, true);
|
|
1851
|
+
el.addEventListener("ended", handleEnded, true);
|
|
1852
|
+
el.addEventListener("error", handleError, true);
|
|
1853
|
+
}
|
|
1854
|
+
if (videoEl) {
|
|
1855
|
+
attachListenersToVideo(videoEl);
|
|
1856
|
+
}
|
|
1857
|
+
if (el && el.tagName?.toLowerCase() !== "video" && typeof MutationObserver !== "undefined") {
|
|
1858
|
+
mutationObserver = new MutationObserver(() => {
|
|
1859
|
+
const found = el.querySelector("video");
|
|
1860
|
+
if (found && found !== videoEl) {
|
|
1861
|
+
if (videoEl) removeListenersFromVideo(videoEl);
|
|
1862
|
+
videoEl = found;
|
|
1863
|
+
if (videoEl) attachListenersToVideo(videoEl);
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
mutationObserver.observe(el, { childList: true, subtree: true });
|
|
1867
|
+
}
|
|
1868
|
+
const cachedInitial = getWinningBid();
|
|
1869
|
+
if (options?.trackViewability !== false && el) {
|
|
1870
|
+
this.observeSlot(el, slotId, {
|
|
1871
|
+
adUnitCode,
|
|
1872
|
+
auctionId: auctionId || cachedInitial?.auctionId,
|
|
1873
|
+
transactionId: transactionId || cachedInitial?.transactionId,
|
|
1874
|
+
mediaType: "video",
|
|
1875
|
+
bid: cachedInitial?.bidTrace || options?.bid,
|
|
1876
|
+
metadata: options?.metadata,
|
|
1877
|
+
emitRefreshEvent: false
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
const cleanup = () => {
|
|
1881
|
+
if (mutationObserver) {
|
|
1882
|
+
mutationObserver.disconnect();
|
|
1883
|
+
mutationObserver = null;
|
|
1884
|
+
}
|
|
1885
|
+
if (el && el !== videoEl) {
|
|
1886
|
+
el.removeEventListener("playing", handlePlaying, true);
|
|
1887
|
+
el.removeEventListener("play", handlePlaying, true);
|
|
1888
|
+
el.removeEventListener("timeupdate", handleTimeUpdate, true);
|
|
1889
|
+
el.removeEventListener("ended", handleEnded, true);
|
|
1890
|
+
el.removeEventListener("error", handleError, true);
|
|
1891
|
+
}
|
|
1892
|
+
if (videoEl) {
|
|
1893
|
+
removeListenersFromVideo(videoEl);
|
|
1894
|
+
}
|
|
1895
|
+
this.videoDetachCleanups.delete(cleanup);
|
|
1896
|
+
};
|
|
1897
|
+
this.videoDetachCleanups.add(cleanup);
|
|
1898
|
+
return cleanup;
|
|
1361
1899
|
}
|
|
1362
1900
|
onTimeInViewThreshold(slotId, thresholdMs, callback) {
|
|
1363
1901
|
const listener = {
|
|
@@ -1570,21 +2108,45 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1570
2108
|
handleBidWon(data) {
|
|
1571
2109
|
if (this.isDuplicate("bidWon", data)) return;
|
|
1572
2110
|
this.log("DEBUG", "bidWon", data);
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2111
|
+
const auctionId = data.auctionId || "";
|
|
2112
|
+
const transactionId = data.transactionId || "";
|
|
2113
|
+
const adUnitCode = data.adUnitCode || data.adId || "";
|
|
2114
|
+
const bidTrace = {
|
|
2115
|
+
bidder: data.bidderCode || data.bidder || "",
|
|
2116
|
+
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
2117
|
+
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
2118
|
+
...parseBidDimensions(data),
|
|
2119
|
+
dealId: data.dealId || "",
|
|
2120
|
+
mediaType: data.mediaType || "banner",
|
|
2121
|
+
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
2122
|
+
advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
|
|
2123
|
+
creativeId: data.creativeId || ""
|
|
2124
|
+
};
|
|
2125
|
+
if (adUnitCode) {
|
|
2126
|
+
const cachedEntry = {
|
|
2127
|
+
bidTrace,
|
|
2128
|
+
auctionId,
|
|
2129
|
+
transactionId,
|
|
2130
|
+
adUnitCode,
|
|
2131
|
+
rawBid: data,
|
|
2132
|
+
timestamp: Date.now()
|
|
2133
|
+
};
|
|
2134
|
+
this.cachedWinningBids.set(adUnitCode, cachedEntry);
|
|
2135
|
+
if (data.adId && data.adId !== adUnitCode) {
|
|
2136
|
+
this.cachedWinningBids.set(data.adId, cachedEntry);
|
|
2137
|
+
}
|
|
2138
|
+
if (auctionId) {
|
|
2139
|
+
this.cachedWinningBids.set(`${auctionId}:${adUnitCode}`, cachedEntry);
|
|
2140
|
+
if (data.adId && data.adId !== adUnitCode) {
|
|
2141
|
+
this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
|
|
2142
|
+
}
|
|
1587
2143
|
}
|
|
2144
|
+
}
|
|
2145
|
+
this.enqueue(TraceEventType.BID_WIN, "bidWon", {
|
|
2146
|
+
auctionId,
|
|
2147
|
+
transactionId,
|
|
2148
|
+
adUnitCode,
|
|
2149
|
+
bid: bidTrace
|
|
1588
2150
|
});
|
|
1589
2151
|
}
|
|
1590
2152
|
handleNoBid(data) {
|
|
@@ -1623,44 +2185,81 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1623
2185
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1624
2186
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1625
2187
|
const bid = data.bid || data || {};
|
|
1626
|
-
const adUnitCode = data.adUnitCode || bid.adUnitCode || "";
|
|
2188
|
+
const adUnitCode = data.adUnitCode || bid.adUnitCode || data.adId || bid.adId || "";
|
|
1627
2189
|
const auctionId = bid.auctionId || data.auctionId || "";
|
|
1628
2190
|
const transactionId = bid.transactionId || data.transactionId || "";
|
|
1629
2191
|
const mediaType = bid.mediaType || "banner";
|
|
2192
|
+
const slotKey = data.adUnitCode || bid.adUnitCode || "";
|
|
2193
|
+
const altKey = data.adId || bid.adId || "";
|
|
2194
|
+
const cached = (auctionId && slotKey ? this.cachedWinningBids.get(`${auctionId}:${slotKey}`) : void 0) || (auctionId && altKey ? this.cachedWinningBids.get(`${auctionId}:${altKey}`) : void 0) || (slotKey ? this.cachedWinningBids.get(slotKey) : void 0) || (altKey ? this.cachedWinningBids.get(altKey) : void 0);
|
|
1630
2195
|
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 || ""
|
|
2196
|
+
bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
|
|
2197
|
+
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
|
|
2198
|
+
currency: bid.originalCurrency ?? bid.currency ?? cached?.bidTrace?.currency ?? "USD",
|
|
2199
|
+
...parseBidDimensions(bid).width ? parseBidDimensions(bid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(bid),
|
|
2200
|
+
dealId: bid.dealId || cached?.bidTrace?.dealId || "",
|
|
2201
|
+
mediaType: mediaType || cached?.bidTrace?.mediaType || "banner",
|
|
2202
|
+
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
|
|
2203
|
+
advertiserDomain: bid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
|
|
2204
|
+
creativeId: bid.creativeId || cached?.bidTrace?.creativeId || ""
|
|
1640
2205
|
};
|
|
1641
|
-
const
|
|
2206
|
+
const resolvedAuctionId = auctionId || cached?.auctionId || "";
|
|
2207
|
+
const resolvedTransactionId = transactionId || cached?.transactionId || "";
|
|
2208
|
+
const record = adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0;
|
|
2209
|
+
const isDifferentAuction = record && resolvedAuctionId && record.auctionId && record.auctionId !== resolvedAuctionId;
|
|
2210
|
+
const isRefresh = Boolean(adUnitCode && record && isDifferentAuction);
|
|
1642
2211
|
if (isRefresh) {
|
|
1643
2212
|
this.flushSlotTimeInView(adUnitCode);
|
|
1644
|
-
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? 0) + 1;
|
|
2213
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0)) + 1;
|
|
1645
2214
|
this.slotRefreshIndices.set(adUnitCode, nextRefreshIndex);
|
|
2215
|
+
this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
|
|
2216
|
+
if (record) {
|
|
2217
|
+
record.refreshIndex = nextRefreshIndex;
|
|
2218
|
+
record.auctionId = resolvedAuctionId;
|
|
2219
|
+
record.transactionId = resolvedTransactionId;
|
|
2220
|
+
record.bidPayload = bidTrace;
|
|
2221
|
+
record.viewableFired = false;
|
|
2222
|
+
record.accumulatedTimeInViewMs = 0;
|
|
2223
|
+
}
|
|
1646
2224
|
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1647
|
-
auctionId,
|
|
1648
|
-
transactionId,
|
|
2225
|
+
auctionId: resolvedAuctionId,
|
|
2226
|
+
transactionId: resolvedTransactionId,
|
|
1649
2227
|
adUnitCode,
|
|
1650
2228
|
bid: bidTrace,
|
|
1651
2229
|
metadata: { refresh_index: String(nextRefreshIndex) }
|
|
1652
2230
|
});
|
|
1653
|
-
} else if (adUnitCode
|
|
1654
|
-
|
|
2231
|
+
} else if (adUnitCode) {
|
|
2232
|
+
if (resolvedAuctionId) {
|
|
2233
|
+
this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
|
|
2234
|
+
}
|
|
2235
|
+
if (!this.slotRefreshIndices.has(adUnitCode)) {
|
|
2236
|
+
this.slotRefreshIndices.set(adUnitCode, 0);
|
|
2237
|
+
}
|
|
1655
2238
|
}
|
|
1656
|
-
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? 0 : 0;
|
|
1657
|
-
this.
|
|
1658
|
-
auctionId,
|
|
1659
|
-
transactionId,
|
|
2239
|
+
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
|
|
2240
|
+
const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
|
|
1660
2241
|
adUnitCode,
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
2242
|
+
currentRefreshIndex,
|
|
2243
|
+
resolvedAuctionId,
|
|
2244
|
+
bidTrace.creativeId
|
|
2245
|
+
) : false;
|
|
2246
|
+
if (!alreadyEmitted) {
|
|
2247
|
+
if (adUnitCode) {
|
|
2248
|
+
this.markImpressionEmitted(
|
|
2249
|
+
adUnitCode,
|
|
2250
|
+
currentRefreshIndex,
|
|
2251
|
+
resolvedAuctionId,
|
|
2252
|
+
bidTrace.creativeId
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
2256
|
+
auctionId: resolvedAuctionId,
|
|
2257
|
+
transactionId: resolvedTransactionId,
|
|
2258
|
+
adUnitCode,
|
|
2259
|
+
bid: bidTrace,
|
|
2260
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
1664
2263
|
if (this.config.viewabilityEnabled && typeof document !== "undefined") {
|
|
1665
2264
|
let el = null;
|
|
1666
2265
|
const targetId = adUnitCode || data.adId || bid.adId;
|
|
@@ -1688,8 +2287,8 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1688
2287
|
if (el) {
|
|
1689
2288
|
this.observeSlot(el, adUnitCode || targetId, {
|
|
1690
2289
|
adUnitCode,
|
|
1691
|
-
auctionId,
|
|
1692
|
-
transactionId,
|
|
2290
|
+
auctionId: resolvedAuctionId,
|
|
2291
|
+
transactionId: resolvedTransactionId,
|
|
1693
2292
|
mediaType,
|
|
1694
2293
|
bid: bidTrace,
|
|
1695
2294
|
refreshIndex: currentRefreshIndex,
|
|
@@ -1754,6 +2353,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1754
2353
|
}
|
|
1755
2354
|
if (this.queue.length >= BATCH_SIZE) {
|
|
1756
2355
|
this.flush();
|
|
2356
|
+
} else if (IMMEDIATE_FLUSH_TYPES.has(type) && !this.immediateFlushScheduled) {
|
|
2357
|
+
this.immediateFlushScheduled = true;
|
|
2358
|
+
setTimeout(() => {
|
|
2359
|
+
this.immediateFlushScheduled = false;
|
|
2360
|
+
this.flush();
|
|
2361
|
+
}, 0);
|
|
1757
2362
|
}
|
|
1758
2363
|
}
|
|
1759
2364
|
shouldSample(type, level) {
|
|
@@ -1810,10 +2415,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1810
2415
|
events
|
|
1811
2416
|
};
|
|
1812
2417
|
}
|
|
1813
|
-
sendFetch(payload, useKeepalive = false) {
|
|
2418
|
+
sendFetch(payload, useKeepalive = false, onDelivered) {
|
|
1814
2419
|
const fetchOpts = {
|
|
1815
2420
|
method: "POST",
|
|
1816
|
-
|
|
2421
|
+
// Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
|
|
2422
|
+
headers: { "Content-Type": SAFE_CONTENT_TYPE },
|
|
1817
2423
|
body: payload.encoded
|
|
1818
2424
|
};
|
|
1819
2425
|
if (useKeepalive) {
|
|
@@ -1822,7 +2428,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1822
2428
|
fetch(payload.url, fetchOpts).then((res) => {
|
|
1823
2429
|
if (!res.ok) {
|
|
1824
2430
|
this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
|
|
1825
|
-
if (res.status >= 400 && res.status < 500) {
|
|
2431
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 408) {
|
|
1826
2432
|
this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
|
|
1827
2433
|
return;
|
|
1828
2434
|
}
|
|
@@ -1830,14 +2436,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1830
2436
|
} else {
|
|
1831
2437
|
this.consecutiveSendFailures = 0;
|
|
1832
2438
|
this.nextSendAllowedAt = 0;
|
|
2439
|
+
if (onDelivered) onDelivered();
|
|
1833
2440
|
}
|
|
1834
2441
|
}).catch((err) => {
|
|
1835
2442
|
this.log("ERROR", "Failed to send batch", err);
|
|
1836
2443
|
this.handleSendFailure(payload.events);
|
|
1837
2444
|
});
|
|
1838
|
-
|
|
2445
|
+
const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
|
|
2446
|
+
if (proc && typeof proc._tickCallback === "function") {
|
|
1839
2447
|
try {
|
|
1840
|
-
|
|
2448
|
+
proc._tickCallback();
|
|
1841
2449
|
} catch {
|
|
1842
2450
|
}
|
|
1843
2451
|
}
|
|
@@ -1873,19 +2481,99 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1873
2481
|
let sent = false;
|
|
1874
2482
|
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
1875
2483
|
try {
|
|
1876
|
-
const blob = new Blob([payload.encoded], {
|
|
1877
|
-
type: "application/x-protobuf"
|
|
1878
|
-
});
|
|
2484
|
+
const blob = new Blob([payload.encoded], { type: SAFE_CONTENT_TYPE });
|
|
1879
2485
|
sent = navigator.sendBeacon(payload.url, blob);
|
|
1880
2486
|
} catch {
|
|
1881
2487
|
sent = false;
|
|
1882
2488
|
}
|
|
1883
2489
|
}
|
|
1884
2490
|
if (!sent) {
|
|
1885
|
-
this.
|
|
2491
|
+
const persistKey = this.persistBatch(payload.encoded);
|
|
2492
|
+
this.sendFetch(payload, true, () => this.removePersistedBatch(persistKey));
|
|
1886
2493
|
}
|
|
1887
2494
|
payload = this.drainBatch();
|
|
1888
2495
|
}
|
|
2496
|
+
this.schedulePersistedCleanup();
|
|
2497
|
+
}
|
|
2498
|
+
// --- Exit-batch persistence -----------------------------------------------
|
|
2499
|
+
persistBatch(encoded) {
|
|
2500
|
+
try {
|
|
2501
|
+
if (typeof localStorage === "undefined") return null;
|
|
2502
|
+
let pendingCount = 0;
|
|
2503
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2504
|
+
const k = localStorage.key(i);
|
|
2505
|
+
if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
|
|
2506
|
+
}
|
|
2507
|
+
if (pendingCount >= MAX_PENDING_BATCHES) return null;
|
|
2508
|
+
const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
2509
|
+
localStorage.setItem(key, bytesToBase64(encoded));
|
|
2510
|
+
this.persistedBatchKeys.push(key);
|
|
2511
|
+
return key;
|
|
2512
|
+
} catch {
|
|
2513
|
+
return null;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
removePersistedBatch(key) {
|
|
2517
|
+
if (!key) return;
|
|
2518
|
+
try {
|
|
2519
|
+
if (typeof localStorage !== "undefined") localStorage.removeItem(key);
|
|
2520
|
+
} catch {
|
|
2521
|
+
}
|
|
2522
|
+
const idx = this.persistedBatchKeys.indexOf(key);
|
|
2523
|
+
if (idx !== -1) this.persistedBatchKeys.splice(idx, 1);
|
|
2524
|
+
}
|
|
2525
|
+
// A page that is still running PENDING_BATCH_CLEANUP_DELAY_MS after an exit
|
|
2526
|
+
// flush was never torn down, so its beacons have long since gone out; drop
|
|
2527
|
+
// the persisted copies rather than letting a later pageview resend them.
|
|
2528
|
+
schedulePersistedCleanup() {
|
|
2529
|
+
if (this.persistedBatchKeys.length === 0) return;
|
|
2530
|
+
if (this.persistedCleanupTimer) clearTimeout(this.persistedCleanupTimer);
|
|
2531
|
+
this.persistedCleanupTimer = setTimeout(() => {
|
|
2532
|
+
this.persistedCleanupTimer = null;
|
|
2533
|
+
for (const key of Array.from(this.persistedBatchKeys)) {
|
|
2534
|
+
this.removePersistedBatch(key);
|
|
2535
|
+
}
|
|
2536
|
+
}, PENDING_BATCH_CLEANUP_DELAY_MS);
|
|
2537
|
+
}
|
|
2538
|
+
// Resend batches a previous pageview persisted at exit but could not
|
|
2539
|
+
// confirm. Keys are claimed (removed) before sending so concurrent tabs
|
|
2540
|
+
// don't double-send; the batch bytes carry their original pageview, session,
|
|
2541
|
+
// and event timestamps, so late rows attribute correctly.
|
|
2542
|
+
resendPersistedBatches() {
|
|
2543
|
+
try {
|
|
2544
|
+
if (typeof localStorage === "undefined" || !this.config.endpoint) return;
|
|
2545
|
+
const keys = [];
|
|
2546
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2547
|
+
const k = localStorage.key(i);
|
|
2548
|
+
if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
|
|
2549
|
+
}
|
|
2550
|
+
for (const key of keys) {
|
|
2551
|
+
let value = null;
|
|
2552
|
+
try {
|
|
2553
|
+
value = localStorage.getItem(key);
|
|
2554
|
+
localStorage.removeItem(key);
|
|
2555
|
+
} catch {
|
|
2556
|
+
continue;
|
|
2557
|
+
}
|
|
2558
|
+
if (!value) continue;
|
|
2559
|
+
const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
|
|
2560
|
+
if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
|
|
2561
|
+
let encoded;
|
|
2562
|
+
try {
|
|
2563
|
+
encoded = base64ToBytes(value);
|
|
2564
|
+
} catch {
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
if (encoded.byteLength === 0) continue;
|
|
2568
|
+
this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
|
|
2569
|
+
this.sendFetch({
|
|
2570
|
+
url: `${this.config.endpoint}/${this.config.propertyId}`,
|
|
2571
|
+
encoded,
|
|
2572
|
+
events: []
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2575
|
+
} catch {
|
|
2576
|
+
}
|
|
1889
2577
|
}
|
|
1890
2578
|
log(level, msg, ...args) {
|
|
1891
2579
|
const levels = { DEBUG: 10, INFO: 20, WARN: 30, ERROR: 40 };
|
|
@@ -2012,7 +2700,28 @@ function getbidkernel(alias = "bidkernel") {
|
|
|
2012
2700
|
}
|
|
2013
2701
|
const win = window;
|
|
2014
2702
|
win[alias] = win[alias] || { q: [] };
|
|
2015
|
-
|
|
2703
|
+
const sdk = win[alias];
|
|
2704
|
+
if (!sdk.attachVideoPlayer) {
|
|
2705
|
+
sdk.attachVideoPlayer = (target, options) => {
|
|
2706
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2707
|
+
return inst?.attachVideoPlayer ? inst.attachVideoPlayer(target, options) : () => {
|
|
2708
|
+
};
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
if (!sdk.attachImaAdsManager) {
|
|
2712
|
+
sdk.attachImaAdsManager = (adsManager, options) => {
|
|
2713
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2714
|
+
return inst?.attachImaAdsManager ? inst.attachImaAdsManager(adsManager, options) : () => {
|
|
2715
|
+
};
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
if (!sdk.recordImpression) {
|
|
2719
|
+
sdk.recordImpression = (slotId, options) => {
|
|
2720
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2721
|
+
return inst?.recordImpression ? inst.recordImpression(slotId, options) : false;
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
return sdk;
|
|
2016
2725
|
}
|
|
2017
2726
|
export {
|
|
2018
2727
|
BidkernelPrebidAnalytics,
|