nodebb-plugin-ezoic-infinite 1.5.38 → 1.5.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.38",
3
+ "version": "1.5.39",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -8,24 +8,14 @@
8
8
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
9
9
 
10
10
  // Insert at most N ads per run to keep the UI smooth on infinite scroll
11
- const MAX_INSERTS_PER_RUN = 6;
11
+ const MAX_INSERTS_PER_RUN = 3;
12
12
 
13
13
  // Preload before viewport (earlier load for smoother scroll)
14
- // Preload far enough ahead that fast scroll doesn't outrun ad loading.
15
- // Slightly more aggressive margins to reduce “I scrolled past it before it loaded”.
16
- const PRELOAD_MARGIN_DESKTOP = '6000px 0px 6000px 0px';
17
- const PRELOAD_MARGIN_MOBILE = '3500px 0px 3500px 0px';
18
-
19
- // When the user scrolls very fast, temporarily preload more aggressively.
20
- // This helps ensure ads are already in-flight before the user reaches them.
21
- const PRELOAD_MARGIN_DESKTOP_BOOST = '8500px 0px 8500px 0px';
22
- const PRELOAD_MARGIN_MOBILE_BOOST = '5500px 0px 5500px 0px';
23
- const BOOST_DURATION_MS = 2500;
24
- const BOOST_SPEED_PX_PER_MS = 2.2; // ~2200px/s
25
-
26
- // Allow a bit more parallelism; the perf profile can still dial this down on low-end devices.
27
- const MAX_INFLIGHT_DESKTOP = 8;
28
- const MAX_INFLIGHT_MOBILE = 6;
14
+ const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
15
+ const PRELOAD_MARGIN_MOBILE = '1500px 0px 1500px 0px';
16
+
17
+ const MAX_INFLIGHT_DESKTOP = 4;
18
+ const MAX_INFLIGHT_MOBILE = 3;
29
19
 
30
20
 
31
21
  // Adaptive performance profile (device/network aware)
@@ -73,23 +63,17 @@ function getPerfProfile() {
73
63
  return p;
74
64
  }
75
65
 
76
- function isBoosted() {
77
- try { return Date.now() < (state.scrollBoostUntil || 0); } catch (e) { return false; }
78
- }
79
-
80
66
  function isMobile() {
81
67
  try { return window && window.innerWidth && window.innerWidth < 768; } catch (e) { return false; }
82
68
  }
83
69
 
84
70
  function getPreloadRootMargin() {
85
- if (isMobile()) return isBoosted() ? PRELOAD_MARGIN_MOBILE_BOOST : PRELOAD_MARGIN_MOBILE;
86
- return isBoosted() ? PRELOAD_MARGIN_DESKTOP_BOOST : PRELOAD_MARGIN_DESKTOP;
71
+ return isMobile() ? PRELOAD_MARGIN_MOBILE : PRELOAD_MARGIN_DESKTOP;
87
72
  }
88
73
 
89
74
  function getMaxInflight() {
90
75
  const perf = getPerfProfile();
91
- const base = isMobile() ? perf.maxInflightMobile : perf.maxInflightDesktop;
92
- return base + (isBoosted() ? 1 : 0);
76
+ return isMobile() ? perf.maxInflightMobile : perf.maxInflightDesktop;
93
77
  }
94
78
 
95
79
  const SELECTORS = {
@@ -158,11 +142,6 @@ function mutationHasRelevantAddedNodes(mutations) {
158
142
  inflight: 0,
159
143
  pending: [],
160
144
  pendingSet: new Set(),
161
-
162
- // fast scroll boosting
163
- scrollBoostUntil: 0,
164
- lastScrollY: 0,
165
- lastScrollTs: 0,
166
145
  ioMargin: null,
167
146
 
168
147
  // hero)
@@ -575,155 +554,45 @@ function startShow(id) {
575
554
  drainQueue();
576
555
  };
577
556
 
578
- // Safety release even if the ad pipeline stalls
579
557
  const hardTimer = setTimeout(release, 6500);
580
558
 
581
- try {
582
- if (isBlocked()) return;
583
-
584
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
585
- if (!ph || !ph.isConnected) return;
586
-
587
- // Guard against rapid duplicate calls for the same id
588
- const now2 = Date.now();
589
- const last2 = state.lastShowById.get(id) || 0;
590
- if (now2 - last2 < 250) return;
591
- state.lastShowById.set(id, now2);
592
-
593
- window.ezstandalone = window.ezstandalone || {};
594
- const ez = window.ezstandalone;
595
-
596
- const doShow = () => {
597
- try {
598
- if (state.usedOnce && state.usedOnce.has(id)) {
599
- safeDestroyById(id);
600
- }
601
- } catch (e) {}
602
-
603
- try { ez.showAds(id); } catch (e) {}
604
- try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
605
-
606
- // Tighten any oversized reserved height once the creative is in the DOM.
607
- try { scheduleTighten(id); } catch (e) {}
608
-
609
- // Release budget quickly; the creative can keep loading independently.
610
- setTimeout(() => { clearTimeout(hardTimer); release(); }, 300);
611
- };
612
-
613
- // Use cmd queue if present, but don't delay to next frame.
614
- if (Array.isArray(ez.cmd)) {
615
- try { ez.cmd.push(doShow); } catch (e) { doShow(); }
616
- } else {
617
- doShow();
618
- }
619
- } finally {
620
- // If we returned early, hardTimer will release.
621
- }
622
- }
559
+ requestAnimationFrame(() => {
560
+ try {
561
+ if (isBlocked()) return;
623
562
 
563
+ const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
564
+ if (!ph || !ph.isConnected) return;
624
565
 
625
- // ---------- height normalization (reduce empty space) ----------
626
- // Some Ezoic / GPT wrappers reserve a larger min-height (e.g., 400px) than the rendered creative (e.g., 250px),
627
- // leaving visible empty space. We “tighten” the reserved min-height to the actual iframe/container height after load.
628
- const _heightObsByPlaceholder = new Map();
566
+ const now2 = Date.now();
567
+ const last2 = state.lastShowById.get(id) || 0;
568
+ if (now2 - last2 < 900) return;
569
+ state.lastShowById.set(id, now2);
629
570
 
630
- function tightenEzoicHeightFor(id) {
631
- try {
632
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
633
- if (!ph || !ph.isConnected) return;
634
-
635
- const ad = ph.querySelector('span.ezoic-ad');
636
- if (!ad) return;
637
-
638
- // Prefer the safeframe/container size if present.
639
- let h = 0;
640
- const container = ad.querySelector('div[id$="__container__"]');
641
- if (container) {
642
- const r = container.getBoundingClientRect();
643
- if (r && r.height) h = Math.max(h, r.height);
644
- }
645
- const iframe = ad.querySelector('iframe');
646
- if (iframe) {
647
- const r2 = iframe.getBoundingClientRect();
648
- if (r2 && r2.height) h = Math.max(h, r2.height);
649
- const attrH = parseInt(iframe.getAttribute('height') || '', 10);
650
- if (Number.isFinite(attrH) && attrH > 0) h = Math.max(h, attrH);
651
- }
571
+ window.ezstandalone = window.ezstandalone || {};
572
+ const ez = window.ezstandalone;
652
573
 
653
- // Fall back to inner wrapper height.
654
- if (!h) {
655
- const inner = ad.firstElementChild;
656
- if (inner) {
657
- const r3 = inner.getBoundingClientRect();
658
- if (r3 && r3.height) h = Math.max(h, r3.height);
659
- }
660
- }
574
+ const doShow = () => {
575
+ try {
576
+ if (state.usedOnce && state.usedOnce.has(id)) {
577
+ safeDestroyById(id);
578
+ }
579
+ } catch (e) {}
661
580
 
662
- if (!h || h < 10) return;
663
- const px = `${Math.ceil(h)}px`;
581
+ try { ez.showAds(id); } catch (e) {}
582
+ try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
664
583
 
665
- // Override inline min-height with an inline !important value.
666
- try { ad.style.setProperty('min-height', px, 'important'); } catch (e) {}
667
- // Keep height auto so responsive creatives can expand if needed.
668
- try { ad.style.setProperty('height', 'auto', 'important'); } catch (e) {}
669
- } catch (e) {}
670
- }
584
+ setTimeout(() => { clearTimeout(hardTimer); release(); }, 650);
585
+ };
671
586
 
672
- function ensureHeightObserver(id) {
673
- try {
674
- if (_heightObsByPlaceholder.has(id)) return;
675
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
676
- if (!ph || !ph.isConnected) return;
677
-
678
- const ro = (typeof ResizeObserver === 'function') ? new ResizeObserver(() => {
679
- tightenEzoicHeightFor(id);
680
- }) : null;
681
-
682
- // Also watch for Ezoic re-applying an oversized inline min-height (style attribute changes).
683
- const mo = (typeof MutationObserver === 'function') ? new MutationObserver(() => {
684
- tightenEzoicHeightFor(id);
685
- }) : null;
686
-
687
- const attach = () => {
688
- const ad = ph.querySelector('span.ezoic-ad[data-ez-name], span.ezoic-ad');
689
- if (!ad) return false;
690
-
691
- if (ro) {
692
- const container = ad.querySelector && ad.querySelector('div[id$="__container__"]');
693
- const iframe = ad.querySelector && ad.querySelector('iframe');
694
- try { if (container) ro.observe(container); } catch (e) {}
695
- try { if (iframe) ro.observe(iframe); } catch (e) {}
696
- try { ro.observe(ad); } catch (e) {}
697
- }
698
- if (mo) {
699
- try { mo.observe(ad, { attributes: true, attributeFilter: ['style', 'class'] }); } catch (e) {}
587
+ if (Array.isArray(ez.cmd)) {
588
+ try { ez.cmd.push(doShow); } catch (e) { doShow(); }
589
+ } else {
590
+ doShow();
700
591
  }
701
- return true;
702
- };
703
-
704
- // If the ad wrapper is injected slightly later, observe the placeholder briefly for children.
705
- if (!attach() && typeof MutationObserver === 'function') {
706
- try {
707
- const tmp = new MutationObserver(() => {
708
- if (attach()) {
709
- try { tmp.disconnect(); } catch (e) {}
710
- tightenEzoicHeightFor(id);
711
- }
712
- });
713
- tmp.observe(ph, { childList: true, subtree: true });
714
- setTimeout(() => { try { tmp.disconnect(); } catch (e) {} }, 3000);
715
- } catch (e) {}
592
+ } finally {
593
+ // If we returned early, hardTimer will release.
716
594
  }
717
-
718
- _heightObsByPlaceholder.set(id, { ro: ro || null, mo: mo || null });
719
- } catch (e) {}
720
- }
721
-
722
- function scheduleTighten(id) {
723
- // Run a couple of times (creatives may resize after initial load)
724
- try { setTimeout(() => tightenEzoicHeightFor(id), 250); } catch (e) {}
725
- try { setTimeout(() => tightenEzoicHeightFor(id), 1100); } catch (e) {}
726
- try { ensureHeightObserver(id); } catch (e) {}
595
+ });
727
596
  }
728
597
 
729
598
 
@@ -778,9 +647,7 @@ function scheduleTighten(id) {
778
647
  // If already near the fold, arm & fire immediately
779
648
  try {
780
649
  const r = wrap.getBoundingClientRect();
781
- // Fire early enough that the ad is likely ready when the user reaches it.
782
- // During boost (fast scroll), preload even farther.
783
- const screens = isBoosted() ? 6.0 : 4.0;
650
+ const screens = 3.0;
784
651
  const h = (window.innerHeight || 800);
785
652
  if (r.top < screens * h && r.bottom > -screens * h) {
786
653
  armPlaceholder(wrap, id);
@@ -807,7 +674,7 @@ function scheduleTighten(id) {
807
674
  const targets = computeTargets(items.length, interval, showFirst);
808
675
  let inserted = 0;
809
676
  const perf = getPerfProfile();
810
- const maxInserts = perf.maxInsertsPerRun + (isBoosted() ? 2 : 0);
677
+ const maxInserts = perf.maxInsertsPerRun;
811
678
 
812
679
  for (const afterPos of targets) {
813
680
  if (inserted >= maxInserts) break;
@@ -986,16 +853,6 @@ function scheduleTighten(id) {
986
853
  // reset perf profile cache
987
854
  state.perfProfile = null;
988
855
 
989
- // disconnect any ResizeObservers used for height tightening
990
- try {
991
- for (const v of _heightObsByPlaceholder.values()) {
992
- try { if (v && v.ro && v.ro.disconnect) v.ro.disconnect(); } catch (e) {}
993
- try { if (v && v.mo && v.mo.disconnect) v.mo.disconnect(); } catch (e) {}
994
- try { if (v && v.disconnect) v.disconnect(); } catch (e) {} // backward compat
995
- }
996
- _heightObsByPlaceholder.clear();
997
- } catch (e) {}
998
-
999
856
  // reset state
1000
857
  state.cfg = null;
1001
858
  state.allTopics = [];
@@ -1059,41 +916,6 @@ function scheduleTighten(id) {
1059
916
  });
1060
917
  }
1061
918
 
1062
- function bindScroll() {
1063
- let ticking = false;
1064
- window.addEventListener('scroll', () => {
1065
- // Detect very fast scrolling and temporarily boost preload/parallelism.
1066
- try {
1067
- const now = Date.now();
1068
- const y = window.scrollY || window.pageYOffset || 0;
1069
- if (state.lastScrollTs) {
1070
- const dt = now - state.lastScrollTs;
1071
- const dy = Math.abs(y - (state.lastScrollY || 0));
1072
- if (dt > 0) {
1073
- const speed = dy / dt; // px/ms
1074
- if (speed >= BOOST_SPEED_PX_PER_MS) {
1075
- const wasBoosted = isBoosted();
1076
- state.scrollBoostUntil = Math.max(state.scrollBoostUntil || 0, now + BOOST_DURATION_MS);
1077
- if (!wasBoosted) {
1078
- // margin changed -> rebuild IO so existing placeholders get earlier preload
1079
- ensurePreloadObserver();
1080
- }
1081
- }
1082
- }
1083
- }
1084
- state.lastScrollY = y;
1085
- state.lastScrollTs = now;
1086
- } catch (e) {}
1087
-
1088
- if (ticking) return;
1089
- ticking = true;
1090
- window.requestAnimationFrame(() => {
1091
- ticking = false;
1092
- if (!isBlocked()) scheduleRun();
1093
- });
1094
- }, { passive: true });
1095
- }
1096
-
1097
919
  // ---------- boot ----------
1098
920
 
1099
921
  state.pageKey = getPageKey();
@@ -1103,7 +925,6 @@ function scheduleTighten(id) {
1103
925
  ensureDomObserver();
1104
926
 
1105
927
  bindNodeBB();
1106
- bindScroll();
1107
928
 
1108
929
  // First paint: try hero + run
1109
930
  blockedUntil = 0;
package/public/style.css CHANGED
@@ -1,26 +1,9 @@
1
1
  /* Minimal styling for injected Ezoic wrappers.
2
2
  Spacing (margins/padding) is intentionally NOT forced here, because it can be
3
3
  configured inside Ezoic and may vary by placement/device.
4
-
5
- Goals:
6
- - prevent flex/centering styles from parent lists from affecting ad internals
7
- - reduce layout jank when iframes resize during scroll
8
4
  */
9
5
 
10
6
  .ezoic-ad {
11
- display: block !important;
12
- position: relative;
7
+ display: block;
13
8
  width: 100%;
14
- overflow: hidden;
15
-
16
- /* limit how far layout/paint changes propagate */
17
- contain: layout paint style;
18
- }
19
-
20
- /* Keep ad content aligned to the top (avoids the "slides to bottom" look) */
21
- .ezoic-ad iframe,
22
- .ezoic-ad ins,
23
- .ezoic-ad > div {
24
- display: block !important;
25
- vertical-align: top !important;
26
9
  }