@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.js
CHANGED
|
@@ -658,6 +658,11 @@ var MAX_QUEUE_SIZE = 200;
|
|
|
658
658
|
var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
|
|
659
659
|
var MAX_CONSECUTIVE_SEND_FAILURES = 10;
|
|
660
660
|
var MAX_PAYLOAD_BYTES = 32 * 1024;
|
|
661
|
+
var SAFE_CONTENT_TYPE = "text/plain";
|
|
662
|
+
var PENDING_BATCH_KEY_PREFIX = "_bidkernel_pending_";
|
|
663
|
+
var MAX_PENDING_BATCHES = 20;
|
|
664
|
+
var PENDING_BATCH_MAX_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
665
|
+
var PENDING_BATCH_CLEANUP_DELAY_MS = 1e4;
|
|
661
666
|
var EVENT_NAME_TO_TYPE = {
|
|
662
667
|
auctionStart: TraceEventType.AUCTION_START,
|
|
663
668
|
auctionEnd: TraceEventType.AUCTION_END,
|
|
@@ -673,6 +678,11 @@ var EVENT_NAME_TO_TYPE = {
|
|
|
673
678
|
timeInView: TraceEventType.TIME_IN_VIEW,
|
|
674
679
|
viewable: TraceEventType.VIEWABLE
|
|
675
680
|
};
|
|
681
|
+
var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
|
|
682
|
+
TraceEventType.IMPRESSION,
|
|
683
|
+
TraceEventType.BID_WIN,
|
|
684
|
+
TraceEventType.CLICK
|
|
685
|
+
]);
|
|
676
686
|
var SESSION_KEY = "_bidkernel_session";
|
|
677
687
|
var SESSION_TS_KEY = "_bidkernel_session_ts";
|
|
678
688
|
var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
|
|
@@ -688,6 +698,22 @@ function generateUUID() {
|
|
|
688
698
|
return v.toString(16);
|
|
689
699
|
});
|
|
690
700
|
}
|
|
701
|
+
function bytesToBase64(bytes) {
|
|
702
|
+
let binary = "";
|
|
703
|
+
const chunk = 8192;
|
|
704
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
705
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
706
|
+
}
|
|
707
|
+
return btoa(binary);
|
|
708
|
+
}
|
|
709
|
+
function base64ToBytes(b64) {
|
|
710
|
+
const binary = atob(b64);
|
|
711
|
+
const bytes = new Uint8Array(binary.length);
|
|
712
|
+
for (let i = 0; i < binary.length; i++) {
|
|
713
|
+
bytes[i] = binary.charCodeAt(i);
|
|
714
|
+
}
|
|
715
|
+
return bytes;
|
|
716
|
+
}
|
|
691
717
|
function extendSession() {
|
|
692
718
|
const now = Date.now();
|
|
693
719
|
inMemorySessionTs = now;
|
|
@@ -852,12 +878,25 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
852
878
|
consecutiveSendFailures = 0;
|
|
853
879
|
nextSendAllowedAt = 0;
|
|
854
880
|
replayedEventCount = 0;
|
|
881
|
+
immediateFlushScheduled = false;
|
|
882
|
+
// localStorage keys of exit batches this instance persisted, so a page that
|
|
883
|
+
// survives its own pagehide/hidden (bfcache restore, tab re-focus) can
|
|
884
|
+
// remove them instead of leaving them for a duplicate resend.
|
|
885
|
+
persistedBatchKeys = [];
|
|
886
|
+
persistedCleanupTimer = null;
|
|
855
887
|
// Viewability & refresh tracking
|
|
856
888
|
intersectionObserver = null;
|
|
857
889
|
slotViewabilityRecords = /* @__PURE__ */ new Map();
|
|
858
890
|
elementToSlotId = /* @__PURE__ */ new Map();
|
|
859
891
|
slotRefreshIndices = /* @__PURE__ */ new Map();
|
|
892
|
+
slotLastAuctionIds = /* @__PURE__ */ new Map();
|
|
860
893
|
pendingThresholdListeners = /* @__PURE__ */ new Map();
|
|
894
|
+
// Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
|
|
895
|
+
slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
|
|
896
|
+
// Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
|
|
897
|
+
cachedWinningBids = /* @__PURE__ */ new Map();
|
|
898
|
+
// Active video player attachment cleanup routines
|
|
899
|
+
videoDetachCleanups = /* @__PURE__ */ new Set();
|
|
861
900
|
constructor(config) {
|
|
862
901
|
this.config = {
|
|
863
902
|
endpoint: config.endpoint || "",
|
|
@@ -976,6 +1015,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
976
1015
|
document.addEventListener("visibilitychange", this.boundVisibilityChange);
|
|
977
1016
|
}
|
|
978
1017
|
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
1018
|
+
this.resendPersistedBatches();
|
|
979
1019
|
}
|
|
980
1020
|
}
|
|
981
1021
|
disable() {
|
|
@@ -1002,10 +1042,24 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1002
1042
|
}
|
|
1003
1043
|
this.slotViewabilityRecords.clear();
|
|
1004
1044
|
this.elementToSlotId.clear();
|
|
1045
|
+
for (const cleanup of this.videoDetachCleanups) {
|
|
1046
|
+
try {
|
|
1047
|
+
cleanup();
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
this.videoDetachCleanups.clear();
|
|
1052
|
+
this.cachedWinningBids.clear();
|
|
1053
|
+
this.slotEmittedImpressionKeys.clear();
|
|
1054
|
+
this.slotLastAuctionIds.clear();
|
|
1005
1055
|
if (this.flushTimer) {
|
|
1006
1056
|
clearInterval(this.flushTimer);
|
|
1007
1057
|
this.flushTimer = null;
|
|
1008
1058
|
}
|
|
1059
|
+
if (this.persistedCleanupTimer) {
|
|
1060
|
+
clearTimeout(this.persistedCleanupTimer);
|
|
1061
|
+
this.persistedCleanupTimer = null;
|
|
1062
|
+
}
|
|
1009
1063
|
if (typeof window !== "undefined") {
|
|
1010
1064
|
window.removeEventListener("pagehide", this.boundFlushBeacon);
|
|
1011
1065
|
if (typeof document !== "undefined") {
|
|
@@ -1320,6 +1374,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1320
1374
|
if (options?.bid !== void 0) existing.bidPayload = options.bid;
|
|
1321
1375
|
if (options?.metadata !== void 0) existing.metadata = options.metadata;
|
|
1322
1376
|
}
|
|
1377
|
+
if (options?.auctionId) {
|
|
1378
|
+
this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
|
|
1379
|
+
}
|
|
1323
1380
|
if (el) {
|
|
1324
1381
|
if (existing.element && existing.element !== el && this.intersectionObserver) {
|
|
1325
1382
|
this.intersectionObserver.unobserve(existing.element);
|
|
@@ -1332,6 +1389,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1332
1389
|
} else {
|
|
1333
1390
|
const initialRefreshIndex = options?.refreshIndex ?? this.slotRefreshIndices.get(resolvedSlotId) ?? 0;
|
|
1334
1391
|
this.slotRefreshIndices.set(resolvedSlotId, initialRefreshIndex);
|
|
1392
|
+
if (options?.auctionId) {
|
|
1393
|
+
this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
|
|
1394
|
+
}
|
|
1335
1395
|
const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
|
|
1336
1396
|
this.pendingThresholdListeners.delete(resolvedSlotId);
|
|
1337
1397
|
const record = {
|
|
@@ -1387,7 +1447,485 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1387
1447
|
this.unobserveSlot(slotId);
|
|
1388
1448
|
this.slotViewabilityRecords.delete(slotId);
|
|
1389
1449
|
this.slotRefreshIndices.delete(slotId);
|
|
1450
|
+
this.slotLastAuctionIds.delete(slotId);
|
|
1390
1451
|
this.pendingThresholdListeners.delete(slotId);
|
|
1452
|
+
const prefix = `${escapeKeyPart(slotId)}:`;
|
|
1453
|
+
for (const key of Array.from(this.slotEmittedImpressionKeys)) {
|
|
1454
|
+
if (key === slotId || key.startsWith(prefix)) {
|
|
1455
|
+
this.slotEmittedImpressionKeys.delete(key);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
this.cachedWinningBids.delete(slotId);
|
|
1459
|
+
}
|
|
1460
|
+
hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
|
|
1461
|
+
const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
|
|
1462
|
+
const baseSlotKey = `${escapeKeyPart(slotId)}:${rIndex}`;
|
|
1463
|
+
if (creativeId) {
|
|
1464
|
+
if (auctionId) {
|
|
1465
|
+
return this.slotEmittedImpressionKeys.has(
|
|
1466
|
+
`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
|
|
1467
|
+
) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1468
|
+
}
|
|
1469
|
+
return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1470
|
+
}
|
|
1471
|
+
if (auctionId) {
|
|
1472
|
+
return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
|
|
1473
|
+
}
|
|
1474
|
+
return this.slotEmittedImpressionKeys.has(baseSlotKey);
|
|
1475
|
+
}
|
|
1476
|
+
markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
|
|
1477
|
+
const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
|
|
1478
|
+
this.slotEmittedImpressionKeys.add(baseSlotKey);
|
|
1479
|
+
if (creativeId) {
|
|
1480
|
+
this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
|
|
1481
|
+
}
|
|
1482
|
+
if (auctionId) {
|
|
1483
|
+
this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
|
|
1484
|
+
if (creativeId) {
|
|
1485
|
+
this.slotEmittedImpressionKeys.add(
|
|
1486
|
+
`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
recordImpression(slotId, options) {
|
|
1492
|
+
if (!this.isEnabled) return false;
|
|
1493
|
+
const resolvedSlotId = slotId || options?.adUnitCode || "";
|
|
1494
|
+
const adUnitCode = options?.adUnitCode || resolvedSlotId;
|
|
1495
|
+
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);
|
|
1496
|
+
const rawBid = options?.bid || cached?.rawBid || {};
|
|
1497
|
+
const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
|
|
1498
|
+
const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
|
|
1499
|
+
const creativeId = rawBid.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || "";
|
|
1500
|
+
const mediaType = options?.mediaType || rawBid.mediaType || cached?.bidTrace?.mediaType || "banner";
|
|
1501
|
+
const targetSlot = resolvedSlotId || adUnitCode;
|
|
1502
|
+
const rec = (resolvedSlotId ? this.slotViewabilityRecords.get(resolvedSlotId) : void 0) || (adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0);
|
|
1503
|
+
const lastAuctionId = targetSlot ? this.slotLastAuctionIds.get(targetSlot) || (rec ? rec.auctionId : void 0) : void 0;
|
|
1504
|
+
const isDifferentAuction = Boolean(
|
|
1505
|
+
targetSlot && auctionId && lastAuctionId && lastAuctionId !== auctionId && options?.refreshIndex === void 0
|
|
1506
|
+
);
|
|
1507
|
+
const bidTrace = {
|
|
1508
|
+
bidder: rawBid.bidderCode || rawBid.bidder || cached?.bidTrace?.bidder || "",
|
|
1509
|
+
cpm: Number.isFinite(rawBid.originalCpm) ? rawBid.originalCpm : Number.isFinite(rawBid.cpm) ? rawBid.cpm : cached?.bidTrace?.cpm ?? 0,
|
|
1510
|
+
currency: rawBid.originalCurrency ?? rawBid.currency ?? cached?.bidTrace?.currency ?? "USD",
|
|
1511
|
+
...parseBidDimensions(rawBid).width ? parseBidDimensions(rawBid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(rawBid),
|
|
1512
|
+
dealId: rawBid.dealId || cached?.bidTrace?.dealId || "",
|
|
1513
|
+
mediaType,
|
|
1514
|
+
latencyMs: Number.isFinite(rawBid.timeToRespond) ? rawBid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
|
|
1515
|
+
advertiserDomain: rawBid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
|
|
1516
|
+
creativeId: rawBid.creativeId || cached?.bidTrace?.creativeId || ""
|
|
1517
|
+
};
|
|
1518
|
+
if (isDifferentAuction && targetSlot) {
|
|
1519
|
+
this.flushSlotTimeInView(targetSlot);
|
|
1520
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(targetSlot) ?? (rec ? rec.refreshIndex : 0)) + 1;
|
|
1521
|
+
this.slotRefreshIndices.set(targetSlot, nextRefreshIndex);
|
|
1522
|
+
this.slotLastAuctionIds.set(targetSlot, auctionId);
|
|
1523
|
+
if (rec) {
|
|
1524
|
+
rec.refreshIndex = nextRefreshIndex;
|
|
1525
|
+
rec.auctionId = auctionId;
|
|
1526
|
+
rec.transactionId = transactionId;
|
|
1527
|
+
rec.bidPayload = bidTrace;
|
|
1528
|
+
rec.viewableFired = false;
|
|
1529
|
+
rec.accumulatedTimeInViewMs = 0;
|
|
1530
|
+
}
|
|
1531
|
+
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1532
|
+
auctionId,
|
|
1533
|
+
transactionId,
|
|
1534
|
+
adUnitCode: targetSlot,
|
|
1535
|
+
bid: bidTrace,
|
|
1536
|
+
metadata: {
|
|
1537
|
+
...options?.metadata,
|
|
1538
|
+
refresh_index: String(nextRefreshIndex)
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
} else if (targetSlot) {
|
|
1542
|
+
if (auctionId) {
|
|
1543
|
+
this.slotLastAuctionIds.set(targetSlot, auctionId);
|
|
1544
|
+
}
|
|
1545
|
+
if (!this.slotRefreshIndices.has(targetSlot)) {
|
|
1546
|
+
this.slotRefreshIndices.set(targetSlot, 0);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
const currentRefreshIndex = options?.refreshIndex ?? (resolvedSlotId ? this.slotRefreshIndices.get(resolvedSlotId) ?? (adUnitCode ? this.slotRefreshIndices.get(adUnitCode) : void 0) ?? (rec ? rec.refreshIndex : 0) : 0);
|
|
1550
|
+
const alreadyEmitted = resolvedSlotId && this.hasImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId) || adUnitCode && adUnitCode !== resolvedSlotId && this.hasImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
|
|
1551
|
+
if (alreadyEmitted) {
|
|
1552
|
+
this.log(
|
|
1553
|
+
"DEBUG",
|
|
1554
|
+
`Impression already emitted for slot ${resolvedSlotId || adUnitCode} in cycle ${currentRefreshIndex}`
|
|
1555
|
+
);
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
if (resolvedSlotId)
|
|
1559
|
+
this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
|
|
1560
|
+
if (adUnitCode && adUnitCode !== resolvedSlotId)
|
|
1561
|
+
this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
|
|
1562
|
+
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
1563
|
+
auctionId,
|
|
1564
|
+
transactionId,
|
|
1565
|
+
adUnitCode,
|
|
1566
|
+
bid: bidTrace,
|
|
1567
|
+
metadata: {
|
|
1568
|
+
...options?.metadata,
|
|
1569
|
+
refresh_index: String(currentRefreshIndex)
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
if (rec) {
|
|
1573
|
+
if (!rec.bidPayload && bidTrace) rec.bidPayload = bidTrace;
|
|
1574
|
+
if (!rec.auctionId && auctionId) rec.auctionId = auctionId;
|
|
1575
|
+
if (!rec.transactionId && transactionId) rec.transactionId = transactionId;
|
|
1576
|
+
}
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Bridges Google IMA SDK AdsManager events to Bidkernel analytics.
|
|
1581
|
+
* Defers IMPRESSION emission until AdEvent.STARTED (or IMPRESSION),
|
|
1582
|
+
* tracks milestones (FIRST_QUARTILE, MIDPOINT, THIRD_QUARTILE, COMPLETE),
|
|
1583
|
+
* and handles AD_ERROR.
|
|
1584
|
+
*/
|
|
1585
|
+
attachImaAdsManager(adsManager, options) {
|
|
1586
|
+
if (!adsManager || typeof adsManager.addEventListener !== "function") {
|
|
1587
|
+
this.log("WARN", "Invalid AdsManager passed to attachImaAdsManager");
|
|
1588
|
+
return () => {
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
const slotId = options?.slotId || options?.adUnitCode || "video";
|
|
1592
|
+
const adUnitCode = options?.adUnitCode || slotId;
|
|
1593
|
+
const auctionId = options?.auctionId || "";
|
|
1594
|
+
const transactionId = options?.transactionId || "";
|
|
1595
|
+
const getWinningBid = () => {
|
|
1596
|
+
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);
|
|
1597
|
+
};
|
|
1598
|
+
const onAdStartedOrImpression = (event) => {
|
|
1599
|
+
this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
|
|
1600
|
+
const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
|
|
1601
|
+
const adData = {};
|
|
1602
|
+
if (ad) {
|
|
1603
|
+
adData.creativeId = (typeof ad.getCreativeId === "function" ? ad.getCreativeId() : ad.creativeId) || "";
|
|
1604
|
+
adData.adId = (typeof ad.getAdId === "function" ? ad.getAdId() : ad.id) || "";
|
|
1605
|
+
adData.title = (typeof ad.getTitle === "function" ? ad.getTitle() : ad.title) || "";
|
|
1606
|
+
adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
|
|
1607
|
+
adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
|
|
1608
|
+
}
|
|
1609
|
+
const cached = getWinningBid();
|
|
1610
|
+
const mergedBid = {
|
|
1611
|
+
...cached?.rawBid || options?.bid,
|
|
1612
|
+
mediaType: "video",
|
|
1613
|
+
creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
|
|
1614
|
+
};
|
|
1615
|
+
const emitted = this.recordImpression(slotId, {
|
|
1616
|
+
adUnitCode,
|
|
1617
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1618
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1619
|
+
mediaType: "video",
|
|
1620
|
+
bid: mergedBid,
|
|
1621
|
+
metadata: {
|
|
1622
|
+
...options?.metadata,
|
|
1623
|
+
media_type: "video",
|
|
1624
|
+
...adData.title ? { ad_title: adData.title } : {},
|
|
1625
|
+
...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
if (emitted && options?.onImpression) {
|
|
1629
|
+
try {
|
|
1630
|
+
options.onImpression(slotId, { ad, bid: mergedBid });
|
|
1631
|
+
} catch (e) {
|
|
1632
|
+
this.log("ERROR", "Error in onImpression callback", e);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
const onMilestone = (milestone) => {
|
|
1637
|
+
this.log("DEBUG", `IMA AdEvent milestone: ${milestone}`);
|
|
1638
|
+
if (options?.onMilestone) {
|
|
1639
|
+
try {
|
|
1640
|
+
options.onMilestone(milestone, slotId);
|
|
1641
|
+
} catch (e) {
|
|
1642
|
+
this.log("ERROR", "Error in onMilestone callback", e);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
const onAdClick = (event) => {
|
|
1647
|
+
this.log("DEBUG", "IMA AdEvent.CLICK received", event);
|
|
1648
|
+
const cached = getWinningBid();
|
|
1649
|
+
this.enqueue(TraceEventType.CLICK, "click", {
|
|
1650
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1651
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1652
|
+
adUnitCode,
|
|
1653
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1654
|
+
metadata: {
|
|
1655
|
+
...options?.metadata,
|
|
1656
|
+
media_type: "video"
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
};
|
|
1660
|
+
const onAdError = (event) => {
|
|
1661
|
+
this.log("WARN", "IMA AdErrorEvent received", event);
|
|
1662
|
+
const err = typeof event?.getError === "function" ? event.getError() : event?.error || event;
|
|
1663
|
+
const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
|
|
1664
|
+
const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
|
|
1665
|
+
const cached = getWinningBid();
|
|
1666
|
+
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1667
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1668
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1669
|
+
adUnitCode,
|
|
1670
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1671
|
+
metadata: {
|
|
1672
|
+
...options?.metadata,
|
|
1673
|
+
reason: "ima_ad_error",
|
|
1674
|
+
message: msg,
|
|
1675
|
+
...code ? { ima_error_code: String(code) } : {}
|
|
1676
|
+
},
|
|
1677
|
+
error: typeof err === "object" ? err : new Error(msg)
|
|
1678
|
+
});
|
|
1679
|
+
if (options?.onError) {
|
|
1680
|
+
try {
|
|
1681
|
+
options.onError(err);
|
|
1682
|
+
} catch (e) {
|
|
1683
|
+
this.log("ERROR", "Error in onError callback", e);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
|
|
1688
|
+
const adEventType = googleIma?.AdEvent?.Type || {};
|
|
1689
|
+
const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
|
|
1690
|
+
const listeners = [
|
|
1691
|
+
{ type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
|
|
1692
|
+
{ type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
|
|
1693
|
+
{
|
|
1694
|
+
type: adEventType.FIRST_QUARTILE || "firstQuartile",
|
|
1695
|
+
handler: () => onMilestone("firstQuartile")
|
|
1696
|
+
},
|
|
1697
|
+
{ type: adEventType.MIDPOINT || "midpoint", handler: () => onMilestone("midpoint") },
|
|
1698
|
+
{
|
|
1699
|
+
type: adEventType.THIRD_QUARTILE || "thirdQuartile",
|
|
1700
|
+
handler: () => onMilestone("thirdQuartile")
|
|
1701
|
+
},
|
|
1702
|
+
{ type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
|
|
1703
|
+
{ type: adEventType.CLICK || "click", handler: onAdClick },
|
|
1704
|
+
{ type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
|
|
1705
|
+
];
|
|
1706
|
+
for (const { type, handler } of listeners) {
|
|
1707
|
+
try {
|
|
1708
|
+
adsManager.addEventListener(type, handler);
|
|
1709
|
+
} catch {
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
const cleanup = () => {
|
|
1713
|
+
for (const { type, handler } of listeners) {
|
|
1714
|
+
try {
|
|
1715
|
+
adsManager.removeEventListener(type, handler);
|
|
1716
|
+
} catch {
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
this.videoDetachCleanups.delete(cleanup);
|
|
1720
|
+
};
|
|
1721
|
+
this.videoDetachCleanups.add(cleanup);
|
|
1722
|
+
return cleanup;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Attaches render hooks and viewability tracking to a video player.
|
|
1726
|
+
* Supports HTML5 <video> elements, container elements, or Google IMA AdsManager.
|
|
1727
|
+
*/
|
|
1728
|
+
attachVideoPlayer(target, options) {
|
|
1729
|
+
if (!target) {
|
|
1730
|
+
this.log("WARN", "attachVideoPlayer called with null/undefined target");
|
|
1731
|
+
return () => {
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
let el = null;
|
|
1735
|
+
if (typeof target === "string") {
|
|
1736
|
+
if (typeof document !== "undefined") {
|
|
1737
|
+
try {
|
|
1738
|
+
el = document.querySelector(target) || document.getElementById(target);
|
|
1739
|
+
} catch {
|
|
1740
|
+
el = document.getElementById(target);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
} else if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
|
|
1744
|
+
el = target;
|
|
1745
|
+
} else if (target && typeof target === "object" && target.nodeType === 1) {
|
|
1746
|
+
el = target;
|
|
1747
|
+
} else if (
|
|
1748
|
+
// Check if target is an IMA AdsManager
|
|
1749
|
+
typeof target.addEventListener === "function" && (typeof target.init === "function" || typeof target.start === "function" || typeof target.getCuePoints === "function" || typeof target.getVolume === "function" || target.__imaAdsManager)
|
|
1750
|
+
) {
|
|
1751
|
+
return this.attachImaAdsManager(target, options);
|
|
1752
|
+
} else if (target && typeof target.addEventListener === "function") {
|
|
1753
|
+
el = target;
|
|
1754
|
+
}
|
|
1755
|
+
const slotId = options?.slotId || options?.adUnitCode || (el ? el.id : "") || "video";
|
|
1756
|
+
const adUnitCode = options?.adUnitCode || slotId;
|
|
1757
|
+
const auctionId = options?.auctionId || "";
|
|
1758
|
+
const transactionId = options?.transactionId || "";
|
|
1759
|
+
const getWinningBid = () => {
|
|
1760
|
+
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);
|
|
1761
|
+
};
|
|
1762
|
+
let videoEl = null;
|
|
1763
|
+
if (el) {
|
|
1764
|
+
if (el.tagName && el.tagName.toLowerCase() === "video") {
|
|
1765
|
+
videoEl = el;
|
|
1766
|
+
} else {
|
|
1767
|
+
videoEl = el.querySelector("video");
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
const quartilesFired = {
|
|
1771
|
+
q1: false,
|
|
1772
|
+
q2: false,
|
|
1773
|
+
q3: false,
|
|
1774
|
+
q4: false
|
|
1775
|
+
};
|
|
1776
|
+
const triggerPlaybackImpression = (activeVideo) => {
|
|
1777
|
+
const vEl = activeVideo || videoEl || (el?.querySelector ? el.querySelector("video") : null);
|
|
1778
|
+
const cached = getWinningBid();
|
|
1779
|
+
const mergedBid = {
|
|
1780
|
+
...cached?.rawBid || options?.bid,
|
|
1781
|
+
mediaType: "video"
|
|
1782
|
+
};
|
|
1783
|
+
const emitted = this.recordImpression(slotId, {
|
|
1784
|
+
adUnitCode,
|
|
1785
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1786
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1787
|
+
mediaType: "video",
|
|
1788
|
+
bid: mergedBid,
|
|
1789
|
+
metadata: {
|
|
1790
|
+
...options?.metadata,
|
|
1791
|
+
media_type: "video",
|
|
1792
|
+
...vEl && Number.isFinite(vEl.duration) ? { video_duration_s: String(Math.round(vEl.duration)) } : {}
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
if (emitted && options?.onImpression) {
|
|
1796
|
+
try {
|
|
1797
|
+
options.onImpression(slotId, { element: vEl || el, bid: mergedBid });
|
|
1798
|
+
} catch (e) {
|
|
1799
|
+
this.log("ERROR", "Error in onImpression callback", e);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
const handlePlaying = (e) => {
|
|
1804
|
+
this.log("DEBUG", "HTML5 video playing event received");
|
|
1805
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1806
|
+
triggerPlaybackImpression(targetVideo);
|
|
1807
|
+
};
|
|
1808
|
+
const handleTimeUpdate = (e) => {
|
|
1809
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1810
|
+
if (targetVideo && targetVideo.currentTime > 0) {
|
|
1811
|
+
triggerPlaybackImpression(targetVideo);
|
|
1812
|
+
if (Number.isFinite(targetVideo.duration) && targetVideo.duration > 0) {
|
|
1813
|
+
const progress = targetVideo.currentTime / targetVideo.duration;
|
|
1814
|
+
if (progress >= 0.25 && !quartilesFired.q1) {
|
|
1815
|
+
quartilesFired.q1 = true;
|
|
1816
|
+
options?.onMilestone?.("firstQuartile", slotId);
|
|
1817
|
+
}
|
|
1818
|
+
if (progress >= 0.5 && !quartilesFired.q2) {
|
|
1819
|
+
quartilesFired.q2 = true;
|
|
1820
|
+
options?.onMilestone?.("midpoint", slotId);
|
|
1821
|
+
}
|
|
1822
|
+
if (progress >= 0.75 && !quartilesFired.q3) {
|
|
1823
|
+
quartilesFired.q3 = true;
|
|
1824
|
+
options?.onMilestone?.("thirdQuartile", slotId);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
};
|
|
1829
|
+
const handleEnded = () => {
|
|
1830
|
+
if (!quartilesFired.q4) {
|
|
1831
|
+
quartilesFired.q4 = true;
|
|
1832
|
+
options?.onMilestone?.("complete", slotId);
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
const handleError = (e) => {
|
|
1836
|
+
const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
|
|
1837
|
+
const err = targetVideo?.error || videoEl?.error;
|
|
1838
|
+
const cached = getWinningBid();
|
|
1839
|
+
const msg = err ? `HTML5 video error code ${err.code}: ${err.message}` : "Video playback error";
|
|
1840
|
+
this.log("WARN", msg);
|
|
1841
|
+
this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
|
|
1842
|
+
auctionId: auctionId || cached?.auctionId,
|
|
1843
|
+
transactionId: transactionId || cached?.transactionId,
|
|
1844
|
+
adUnitCode,
|
|
1845
|
+
bid: cached?.bidTrace || options?.bid,
|
|
1846
|
+
metadata: {
|
|
1847
|
+
...options?.metadata,
|
|
1848
|
+
reason: "html5_video_error",
|
|
1849
|
+
message: msg,
|
|
1850
|
+
...err?.code ? { video_error_code: String(err.code) } : {}
|
|
1851
|
+
},
|
|
1852
|
+
error: err ? new Error(msg) : void 0
|
|
1853
|
+
});
|
|
1854
|
+
if (options?.onError) {
|
|
1855
|
+
try {
|
|
1856
|
+
options.onError(err);
|
|
1857
|
+
} catch (e2) {
|
|
1858
|
+
this.log("ERROR", "Error in onError callback", e2);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
const attachListenersToVideo = (targetVideo) => {
|
|
1863
|
+
targetVideo.addEventListener("playing", handlePlaying);
|
|
1864
|
+
targetVideo.addEventListener("play", handlePlaying);
|
|
1865
|
+
targetVideo.addEventListener("timeupdate", handleTimeUpdate);
|
|
1866
|
+
targetVideo.addEventListener("ended", handleEnded);
|
|
1867
|
+
targetVideo.addEventListener("error", handleError);
|
|
1868
|
+
};
|
|
1869
|
+
const removeListenersFromVideo = (targetVideo) => {
|
|
1870
|
+
targetVideo.removeEventListener("playing", handlePlaying);
|
|
1871
|
+
targetVideo.removeEventListener("play", handlePlaying);
|
|
1872
|
+
targetVideo.removeEventListener("timeupdate", handleTimeUpdate);
|
|
1873
|
+
targetVideo.removeEventListener("ended", handleEnded);
|
|
1874
|
+
targetVideo.removeEventListener("error", handleError);
|
|
1875
|
+
};
|
|
1876
|
+
let mutationObserver = null;
|
|
1877
|
+
if (el && el !== videoEl) {
|
|
1878
|
+
el.addEventListener("playing", handlePlaying, true);
|
|
1879
|
+
el.addEventListener("play", handlePlaying, true);
|
|
1880
|
+
el.addEventListener("timeupdate", handleTimeUpdate, true);
|
|
1881
|
+
el.addEventListener("ended", handleEnded, true);
|
|
1882
|
+
el.addEventListener("error", handleError, true);
|
|
1883
|
+
}
|
|
1884
|
+
if (videoEl) {
|
|
1885
|
+
attachListenersToVideo(videoEl);
|
|
1886
|
+
}
|
|
1887
|
+
if (el && el.tagName?.toLowerCase() !== "video" && typeof MutationObserver !== "undefined") {
|
|
1888
|
+
mutationObserver = new MutationObserver(() => {
|
|
1889
|
+
const found = el.querySelector("video");
|
|
1890
|
+
if (found && found !== videoEl) {
|
|
1891
|
+
if (videoEl) removeListenersFromVideo(videoEl);
|
|
1892
|
+
videoEl = found;
|
|
1893
|
+
if (videoEl) attachListenersToVideo(videoEl);
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
mutationObserver.observe(el, { childList: true, subtree: true });
|
|
1897
|
+
}
|
|
1898
|
+
const cachedInitial = getWinningBid();
|
|
1899
|
+
if (options?.trackViewability !== false && el) {
|
|
1900
|
+
this.observeSlot(el, slotId, {
|
|
1901
|
+
adUnitCode,
|
|
1902
|
+
auctionId: auctionId || cachedInitial?.auctionId,
|
|
1903
|
+
transactionId: transactionId || cachedInitial?.transactionId,
|
|
1904
|
+
mediaType: "video",
|
|
1905
|
+
bid: cachedInitial?.bidTrace || options?.bid,
|
|
1906
|
+
metadata: options?.metadata,
|
|
1907
|
+
emitRefreshEvent: false
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
const cleanup = () => {
|
|
1911
|
+
if (mutationObserver) {
|
|
1912
|
+
mutationObserver.disconnect();
|
|
1913
|
+
mutationObserver = null;
|
|
1914
|
+
}
|
|
1915
|
+
if (el && el !== videoEl) {
|
|
1916
|
+
el.removeEventListener("playing", handlePlaying, true);
|
|
1917
|
+
el.removeEventListener("play", handlePlaying, true);
|
|
1918
|
+
el.removeEventListener("timeupdate", handleTimeUpdate, true);
|
|
1919
|
+
el.removeEventListener("ended", handleEnded, true);
|
|
1920
|
+
el.removeEventListener("error", handleError, true);
|
|
1921
|
+
}
|
|
1922
|
+
if (videoEl) {
|
|
1923
|
+
removeListenersFromVideo(videoEl);
|
|
1924
|
+
}
|
|
1925
|
+
this.videoDetachCleanups.delete(cleanup);
|
|
1926
|
+
};
|
|
1927
|
+
this.videoDetachCleanups.add(cleanup);
|
|
1928
|
+
return cleanup;
|
|
1391
1929
|
}
|
|
1392
1930
|
onTimeInViewThreshold(slotId, thresholdMs, callback) {
|
|
1393
1931
|
const listener = {
|
|
@@ -1600,21 +2138,45 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1600
2138
|
handleBidWon(data) {
|
|
1601
2139
|
if (this.isDuplicate("bidWon", data)) return;
|
|
1602
2140
|
this.log("DEBUG", "bidWon", data);
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2141
|
+
const auctionId = data.auctionId || "";
|
|
2142
|
+
const transactionId = data.transactionId || "";
|
|
2143
|
+
const adUnitCode = data.adUnitCode || data.adId || "";
|
|
2144
|
+
const bidTrace = {
|
|
2145
|
+
bidder: data.bidderCode || data.bidder || "",
|
|
2146
|
+
cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
|
|
2147
|
+
currency: data.originalCurrency ?? data.currency ?? "USD",
|
|
2148
|
+
...parseBidDimensions(data),
|
|
2149
|
+
dealId: data.dealId || "",
|
|
2150
|
+
mediaType: data.mediaType || "banner",
|
|
2151
|
+
latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
|
|
2152
|
+
advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
|
|
2153
|
+
creativeId: data.creativeId || ""
|
|
2154
|
+
};
|
|
2155
|
+
if (adUnitCode) {
|
|
2156
|
+
const cachedEntry = {
|
|
2157
|
+
bidTrace,
|
|
2158
|
+
auctionId,
|
|
2159
|
+
transactionId,
|
|
2160
|
+
adUnitCode,
|
|
2161
|
+
rawBid: data,
|
|
2162
|
+
timestamp: Date.now()
|
|
2163
|
+
};
|
|
2164
|
+
this.cachedWinningBids.set(adUnitCode, cachedEntry);
|
|
2165
|
+
if (data.adId && data.adId !== adUnitCode) {
|
|
2166
|
+
this.cachedWinningBids.set(data.adId, cachedEntry);
|
|
2167
|
+
}
|
|
2168
|
+
if (auctionId) {
|
|
2169
|
+
this.cachedWinningBids.set(`${auctionId}:${adUnitCode}`, cachedEntry);
|
|
2170
|
+
if (data.adId && data.adId !== adUnitCode) {
|
|
2171
|
+
this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
|
|
2172
|
+
}
|
|
1617
2173
|
}
|
|
2174
|
+
}
|
|
2175
|
+
this.enqueue(TraceEventType.BID_WIN, "bidWon", {
|
|
2176
|
+
auctionId,
|
|
2177
|
+
transactionId,
|
|
2178
|
+
adUnitCode,
|
|
2179
|
+
bid: bidTrace
|
|
1618
2180
|
});
|
|
1619
2181
|
}
|
|
1620
2182
|
handleNoBid(data) {
|
|
@@ -1653,44 +2215,81 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1653
2215
|
if (this.isDuplicate("adRenderSucceeded", data)) return;
|
|
1654
2216
|
this.log("DEBUG", "adRenderSucceeded", data);
|
|
1655
2217
|
const bid = data.bid || data || {};
|
|
1656
|
-
const adUnitCode = data.adUnitCode || bid.adUnitCode || "";
|
|
2218
|
+
const adUnitCode = data.adUnitCode || bid.adUnitCode || data.adId || bid.adId || "";
|
|
1657
2219
|
const auctionId = bid.auctionId || data.auctionId || "";
|
|
1658
2220
|
const transactionId = bid.transactionId || data.transactionId || "";
|
|
1659
2221
|
const mediaType = bid.mediaType || "banner";
|
|
2222
|
+
const slotKey = data.adUnitCode || bid.adUnitCode || "";
|
|
2223
|
+
const altKey = data.adId || bid.adId || "";
|
|
2224
|
+
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);
|
|
1660
2225
|
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 || ""
|
|
2226
|
+
bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
|
|
2227
|
+
cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
|
|
2228
|
+
currency: bid.originalCurrency ?? bid.currency ?? cached?.bidTrace?.currency ?? "USD",
|
|
2229
|
+
...parseBidDimensions(bid).width ? parseBidDimensions(bid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(bid),
|
|
2230
|
+
dealId: bid.dealId || cached?.bidTrace?.dealId || "",
|
|
2231
|
+
mediaType: mediaType || cached?.bidTrace?.mediaType || "banner",
|
|
2232
|
+
latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
|
|
2233
|
+
advertiserDomain: bid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
|
|
2234
|
+
creativeId: bid.creativeId || cached?.bidTrace?.creativeId || ""
|
|
1670
2235
|
};
|
|
1671
|
-
const
|
|
2236
|
+
const resolvedAuctionId = auctionId || cached?.auctionId || "";
|
|
2237
|
+
const resolvedTransactionId = transactionId || cached?.transactionId || "";
|
|
2238
|
+
const record = adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0;
|
|
2239
|
+
const isDifferentAuction = record && resolvedAuctionId && record.auctionId && record.auctionId !== resolvedAuctionId;
|
|
2240
|
+
const isRefresh = Boolean(adUnitCode && record && isDifferentAuction);
|
|
1672
2241
|
if (isRefresh) {
|
|
1673
2242
|
this.flushSlotTimeInView(adUnitCode);
|
|
1674
|
-
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? 0) + 1;
|
|
2243
|
+
const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0)) + 1;
|
|
1675
2244
|
this.slotRefreshIndices.set(adUnitCode, nextRefreshIndex);
|
|
2245
|
+
this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
|
|
2246
|
+
if (record) {
|
|
2247
|
+
record.refreshIndex = nextRefreshIndex;
|
|
2248
|
+
record.auctionId = resolvedAuctionId;
|
|
2249
|
+
record.transactionId = resolvedTransactionId;
|
|
2250
|
+
record.bidPayload = bidTrace;
|
|
2251
|
+
record.viewableFired = false;
|
|
2252
|
+
record.accumulatedTimeInViewMs = 0;
|
|
2253
|
+
}
|
|
1676
2254
|
this.enqueue(TraceEventType.REFRESH, "refresh", {
|
|
1677
|
-
auctionId,
|
|
1678
|
-
transactionId,
|
|
2255
|
+
auctionId: resolvedAuctionId,
|
|
2256
|
+
transactionId: resolvedTransactionId,
|
|
1679
2257
|
adUnitCode,
|
|
1680
2258
|
bid: bidTrace,
|
|
1681
2259
|
metadata: { refresh_index: String(nextRefreshIndex) }
|
|
1682
2260
|
});
|
|
1683
|
-
} else if (adUnitCode
|
|
1684
|
-
|
|
2261
|
+
} else if (adUnitCode) {
|
|
2262
|
+
if (resolvedAuctionId) {
|
|
2263
|
+
this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
|
|
2264
|
+
}
|
|
2265
|
+
if (!this.slotRefreshIndices.has(adUnitCode)) {
|
|
2266
|
+
this.slotRefreshIndices.set(adUnitCode, 0);
|
|
2267
|
+
}
|
|
1685
2268
|
}
|
|
1686
|
-
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? 0 : 0;
|
|
1687
|
-
this.
|
|
1688
|
-
auctionId,
|
|
1689
|
-
transactionId,
|
|
2269
|
+
const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
|
|
2270
|
+
const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
|
|
1690
2271
|
adUnitCode,
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
2272
|
+
currentRefreshIndex,
|
|
2273
|
+
resolvedAuctionId,
|
|
2274
|
+
bidTrace.creativeId
|
|
2275
|
+
) : false;
|
|
2276
|
+
if (!alreadyEmitted) {
|
|
2277
|
+
if (adUnitCode) {
|
|
2278
|
+
this.markImpressionEmitted(
|
|
2279
|
+
adUnitCode,
|
|
2280
|
+
currentRefreshIndex,
|
|
2281
|
+
resolvedAuctionId,
|
|
2282
|
+
bidTrace.creativeId
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
this.enqueue(TraceEventType.IMPRESSION, "impression", {
|
|
2286
|
+
auctionId: resolvedAuctionId,
|
|
2287
|
+
transactionId: resolvedTransactionId,
|
|
2288
|
+
adUnitCode,
|
|
2289
|
+
bid: bidTrace,
|
|
2290
|
+
metadata: { refresh_index: String(currentRefreshIndex) }
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
1694
2293
|
if (this.config.viewabilityEnabled && typeof document !== "undefined") {
|
|
1695
2294
|
let el = null;
|
|
1696
2295
|
const targetId = adUnitCode || data.adId || bid.adId;
|
|
@@ -1718,8 +2317,8 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1718
2317
|
if (el) {
|
|
1719
2318
|
this.observeSlot(el, adUnitCode || targetId, {
|
|
1720
2319
|
adUnitCode,
|
|
1721
|
-
auctionId,
|
|
1722
|
-
transactionId,
|
|
2320
|
+
auctionId: resolvedAuctionId,
|
|
2321
|
+
transactionId: resolvedTransactionId,
|
|
1723
2322
|
mediaType,
|
|
1724
2323
|
bid: bidTrace,
|
|
1725
2324
|
refreshIndex: currentRefreshIndex,
|
|
@@ -1784,6 +2383,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1784
2383
|
}
|
|
1785
2384
|
if (this.queue.length >= BATCH_SIZE) {
|
|
1786
2385
|
this.flush();
|
|
2386
|
+
} else if (IMMEDIATE_FLUSH_TYPES.has(type) && !this.immediateFlushScheduled) {
|
|
2387
|
+
this.immediateFlushScheduled = true;
|
|
2388
|
+
setTimeout(() => {
|
|
2389
|
+
this.immediateFlushScheduled = false;
|
|
2390
|
+
this.flush();
|
|
2391
|
+
}, 0);
|
|
1787
2392
|
}
|
|
1788
2393
|
}
|
|
1789
2394
|
shouldSample(type, level) {
|
|
@@ -1840,10 +2445,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1840
2445
|
events
|
|
1841
2446
|
};
|
|
1842
2447
|
}
|
|
1843
|
-
sendFetch(payload, useKeepalive = false) {
|
|
2448
|
+
sendFetch(payload, useKeepalive = false, onDelivered) {
|
|
1844
2449
|
const fetchOpts = {
|
|
1845
2450
|
method: "POST",
|
|
1846
|
-
|
|
2451
|
+
// Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
|
|
2452
|
+
headers: { "Content-Type": SAFE_CONTENT_TYPE },
|
|
1847
2453
|
body: payload.encoded
|
|
1848
2454
|
};
|
|
1849
2455
|
if (useKeepalive) {
|
|
@@ -1852,7 +2458,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1852
2458
|
fetch(payload.url, fetchOpts).then((res) => {
|
|
1853
2459
|
if (!res.ok) {
|
|
1854
2460
|
this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
|
|
1855
|
-
if (res.status >= 400 && res.status < 500) {
|
|
2461
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 408) {
|
|
1856
2462
|
this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
|
|
1857
2463
|
return;
|
|
1858
2464
|
}
|
|
@@ -1860,14 +2466,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1860
2466
|
} else {
|
|
1861
2467
|
this.consecutiveSendFailures = 0;
|
|
1862
2468
|
this.nextSendAllowedAt = 0;
|
|
2469
|
+
if (onDelivered) onDelivered();
|
|
1863
2470
|
}
|
|
1864
2471
|
}).catch((err) => {
|
|
1865
2472
|
this.log("ERROR", "Failed to send batch", err);
|
|
1866
2473
|
this.handleSendFailure(payload.events);
|
|
1867
2474
|
});
|
|
1868
|
-
|
|
2475
|
+
const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
|
|
2476
|
+
if (proc && typeof proc._tickCallback === "function") {
|
|
1869
2477
|
try {
|
|
1870
|
-
|
|
2478
|
+
proc._tickCallback();
|
|
1871
2479
|
} catch {
|
|
1872
2480
|
}
|
|
1873
2481
|
}
|
|
@@ -1903,19 +2511,99 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
|
|
|
1903
2511
|
let sent = false;
|
|
1904
2512
|
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
|
|
1905
2513
|
try {
|
|
1906
|
-
const blob = new Blob([payload.encoded], {
|
|
1907
|
-
type: "application/x-protobuf"
|
|
1908
|
-
});
|
|
2514
|
+
const blob = new Blob([payload.encoded], { type: SAFE_CONTENT_TYPE });
|
|
1909
2515
|
sent = navigator.sendBeacon(payload.url, blob);
|
|
1910
2516
|
} catch {
|
|
1911
2517
|
sent = false;
|
|
1912
2518
|
}
|
|
1913
2519
|
}
|
|
1914
2520
|
if (!sent) {
|
|
1915
|
-
this.
|
|
2521
|
+
const persistKey = this.persistBatch(payload.encoded);
|
|
2522
|
+
this.sendFetch(payload, true, () => this.removePersistedBatch(persistKey));
|
|
1916
2523
|
}
|
|
1917
2524
|
payload = this.drainBatch();
|
|
1918
2525
|
}
|
|
2526
|
+
this.schedulePersistedCleanup();
|
|
2527
|
+
}
|
|
2528
|
+
// --- Exit-batch persistence -----------------------------------------------
|
|
2529
|
+
persistBatch(encoded) {
|
|
2530
|
+
try {
|
|
2531
|
+
if (typeof localStorage === "undefined") return null;
|
|
2532
|
+
let pendingCount = 0;
|
|
2533
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2534
|
+
const k = localStorage.key(i);
|
|
2535
|
+
if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
|
|
2536
|
+
}
|
|
2537
|
+
if (pendingCount >= MAX_PENDING_BATCHES) return null;
|
|
2538
|
+
const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
2539
|
+
localStorage.setItem(key, bytesToBase64(encoded));
|
|
2540
|
+
this.persistedBatchKeys.push(key);
|
|
2541
|
+
return key;
|
|
2542
|
+
} catch {
|
|
2543
|
+
return null;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
removePersistedBatch(key) {
|
|
2547
|
+
if (!key) return;
|
|
2548
|
+
try {
|
|
2549
|
+
if (typeof localStorage !== "undefined") localStorage.removeItem(key);
|
|
2550
|
+
} catch {
|
|
2551
|
+
}
|
|
2552
|
+
const idx = this.persistedBatchKeys.indexOf(key);
|
|
2553
|
+
if (idx !== -1) this.persistedBatchKeys.splice(idx, 1);
|
|
2554
|
+
}
|
|
2555
|
+
// A page that is still running PENDING_BATCH_CLEANUP_DELAY_MS after an exit
|
|
2556
|
+
// flush was never torn down, so its beacons have long since gone out; drop
|
|
2557
|
+
// the persisted copies rather than letting a later pageview resend them.
|
|
2558
|
+
schedulePersistedCleanup() {
|
|
2559
|
+
if (this.persistedBatchKeys.length === 0) return;
|
|
2560
|
+
if (this.persistedCleanupTimer) clearTimeout(this.persistedCleanupTimer);
|
|
2561
|
+
this.persistedCleanupTimer = setTimeout(() => {
|
|
2562
|
+
this.persistedCleanupTimer = null;
|
|
2563
|
+
for (const key of Array.from(this.persistedBatchKeys)) {
|
|
2564
|
+
this.removePersistedBatch(key);
|
|
2565
|
+
}
|
|
2566
|
+
}, PENDING_BATCH_CLEANUP_DELAY_MS);
|
|
2567
|
+
}
|
|
2568
|
+
// Resend batches a previous pageview persisted at exit but could not
|
|
2569
|
+
// confirm. Keys are claimed (removed) before sending so concurrent tabs
|
|
2570
|
+
// don't double-send; the batch bytes carry their original pageview, session,
|
|
2571
|
+
// and event timestamps, so late rows attribute correctly.
|
|
2572
|
+
resendPersistedBatches() {
|
|
2573
|
+
try {
|
|
2574
|
+
if (typeof localStorage === "undefined" || !this.config.endpoint) return;
|
|
2575
|
+
const keys = [];
|
|
2576
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2577
|
+
const k = localStorage.key(i);
|
|
2578
|
+
if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
|
|
2579
|
+
}
|
|
2580
|
+
for (const key of keys) {
|
|
2581
|
+
let value = null;
|
|
2582
|
+
try {
|
|
2583
|
+
value = localStorage.getItem(key);
|
|
2584
|
+
localStorage.removeItem(key);
|
|
2585
|
+
} catch {
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
if (!value) continue;
|
|
2589
|
+
const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
|
|
2590
|
+
if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
|
|
2591
|
+
let encoded;
|
|
2592
|
+
try {
|
|
2593
|
+
encoded = base64ToBytes(value);
|
|
2594
|
+
} catch {
|
|
2595
|
+
continue;
|
|
2596
|
+
}
|
|
2597
|
+
if (encoded.byteLength === 0) continue;
|
|
2598
|
+
this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
|
|
2599
|
+
this.sendFetch({
|
|
2600
|
+
url: `${this.config.endpoint}/${this.config.propertyId}`,
|
|
2601
|
+
encoded,
|
|
2602
|
+
events: []
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
} catch {
|
|
2606
|
+
}
|
|
1919
2607
|
}
|
|
1920
2608
|
log(level, msg, ...args) {
|
|
1921
2609
|
const levels = { DEBUG: 10, INFO: 20, WARN: 30, ERROR: 40 };
|
|
@@ -2042,7 +2730,28 @@ function getbidkernel(alias = "bidkernel") {
|
|
|
2042
2730
|
}
|
|
2043
2731
|
const win = window;
|
|
2044
2732
|
win[alias] = win[alias] || { q: [] };
|
|
2045
|
-
|
|
2733
|
+
const sdk = win[alias];
|
|
2734
|
+
if (!sdk.attachVideoPlayer) {
|
|
2735
|
+
sdk.attachVideoPlayer = (target, options) => {
|
|
2736
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2737
|
+
return inst?.attachVideoPlayer ? inst.attachVideoPlayer(target, options) : () => {
|
|
2738
|
+
};
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
if (!sdk.attachImaAdsManager) {
|
|
2742
|
+
sdk.attachImaAdsManager = (adsManager, options) => {
|
|
2743
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2744
|
+
return inst?.attachImaAdsManager ? inst.attachImaAdsManager(adsManager, options) : () => {
|
|
2745
|
+
};
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
if (!sdk.recordImpression) {
|
|
2749
|
+
sdk.recordImpression = (slotId, options) => {
|
|
2750
|
+
const inst = win._bidkernelPrebidAnalytics;
|
|
2751
|
+
return inst?.recordImpression ? inst.recordImpression(slotId, options) : false;
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
return sdk;
|
|
2046
2755
|
}
|
|
2047
2756
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2048
2757
|
0 && (module.exports = {
|