nodebb-plugin-ezoic-infinite 1.5.77 → 1.5.79

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +65 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.77",
3
+ "version": "1.5.79",
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
@@ -10,7 +10,10 @@
10
10
  const POOL_ID = 'nodebb-ezoic-placeholder-pool';
11
11
 
12
12
  // Smoothness caps
13
- const MAX_INSERTS_PER_RUN = 3;
13
+ // Limit how many placements we inject per scan pass.
14
+ // Too low = you end up with only a handful of placeholders after ajaxify.
15
+ // Too high = jank on very long pages.
16
+ const MAX_INSERTS_PER_RUN = 8;
14
17
 
15
18
  // Keep empty (unfilled) wraps alive for a while. Topics/messages can fill late (auction/CMP).
16
19
  // Pruning too early makes ads look like they "disappear" while scrolling.
@@ -19,9 +22,11 @@
19
22
 
20
23
  // Preload margins
21
24
  const PRELOAD_MARGIN_DESKTOP = '2600px 0px 2600px 0px';
22
- const PRELOAD_MARGIN_MOBILE = '1500px 0px 1500px 0px';
23
- const PRELOAD_MARGIN_DESKTOP_BOOST = '4200px 0px 4200px 0px';
24
- const PRELOAD_MARGIN_MOBILE_BOOST = '2500px 0px 2500px 0px';
25
+ // Mobile: larger preload window so ad fill requests start earlier and
26
+ // users don't scroll past empty placeholders.
27
+ const PRELOAD_MARGIN_MOBILE = '3200px 0px 3200px 0px';
28
+ const PRELOAD_MARGIN_DESKTOP_BOOST = '5200px 0px 5200px 0px';
29
+ const PRELOAD_MARGIN_MOBILE_BOOST = '5200px 0px 5200px 0px';
25
30
 
26
31
  const BOOST_DURATION_MS = 2500;
27
32
  const BOOST_SPEED_PX_PER_MS = 2.2; // ~2200px/s
@@ -586,12 +591,11 @@ function globalGapFixInit() {
586
591
  if (!state.allPosts.length) state.allPosts = parsePool(cfg.messagePlaceholderIds);
587
592
  if (!state.allCategories.length) state.allCategories = parsePool(cfg.categoryPlaceholderIds);
588
593
 
589
- // Create placeholders up-front in an offscreen pool.
590
- // Ezoic may attempt to define/show ids during load; if they don't exist yet,
591
- // it can spam errors and sometimes short-circuit. Pooling keeps ids existing without layout.
592
- primePlaceholderPool(state.allTopics);
593
- primePlaceholderPool(state.allPosts);
594
- primePlaceholderPool(state.allCategories);
594
+ // IMPORTANT:
595
+ // We do NOT prime a DOM pool anymore.
596
+ // Keeping placeholders connected (even offscreen) can lead Ezoic/GPT to
597
+ // pre-define slots, which then causes "Placeholder Id X has already been defined".
598
+ // Instead, we create the placeholder element only when we actually inject its wrapper.
595
599
  }
596
600
 
597
601
  // ---------------- insertion primitives ----------------
@@ -749,11 +753,24 @@ function globalGapFixInit() {
749
753
  if (prev.getAttribute && prev.getAttribute('data-ezoic-pin') === '1') break;
750
754
  } catch (e) {}
751
755
 
752
- // Don't decluster two "fresh" empty wraps it can reduce fill on slow auctions.
753
- // Only decluster when at least one is filled, or when the newer one is stale.
756
+ // Never remove a wrap that is already filled; otherwise it looks like
757
+ // ads "disappear" while scrolling. Only remove the empty neighbour.
754
758
  const prevFilled = isFilled(prev);
755
759
  const curFilled = isFilled(w);
756
- if (prevFilled || curFilled || !isFresh(w)) {
760
+
761
+ if (curFilled) {
762
+ // If the previous one is empty (and not fresh), drop the previous instead.
763
+ if (!prevFilled && !isFresh(prev)) {
764
+ withInternalDomChange(() => releaseWrapNode(prev));
765
+ removed++;
766
+ }
767
+ break;
768
+ }
769
+
770
+ // Current is empty.
771
+ // Don't decluster two "fresh" empty wraps — it can reduce fill on slow auctions.
772
+ // Only decluster when previous is filled, or when current is stale.
773
+ if (prevFilled || !isFresh(w)) {
757
774
  withInternalDomChange(() => releaseWrapNode(w));
758
775
  removed++;
759
776
  }
@@ -815,10 +832,16 @@ function globalGapFixInit() {
815
832
  try { io && io.observe(ph); } catch (e) {}
816
833
 
817
834
  // If already near viewport, fire immediately.
835
+ // Mobile tends to scroll faster + has slower auctions, so we fire earlier.
818
836
  try {
819
837
  const r = ph.getBoundingClientRect();
820
- const screens = isBoosted() ? 5.0 : 3.0;
821
- const minBottom = isBoosted() ? -1500 : -800;
838
+ const mobile = isMobile();
839
+ const screens = isBoosted()
840
+ ? (mobile ? 9.0 : 5.0)
841
+ : (mobile ? 6.0 : 3.0);
842
+ const minBottom = isBoosted()
843
+ ? (mobile ? -2600 : -1500)
844
+ : (mobile ? -1400 : -800);
822
845
  if (r.top < window.innerHeight * screens && r.bottom > minBottom) enqueueShow(id);
823
846
  } catch (e) {}
824
847
  }
@@ -908,6 +931,16 @@ function globalGapFixInit() {
908
931
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
909
932
  if (!ph || !ph.isConnected) return;
910
933
 
934
+ // If the placeholder already has creative, avoid re-showing.
935
+ // Re-showing is a common source of "Placeholder Id X has already been defined".
936
+ try {
937
+ if (ph.querySelector && ph.querySelector('iframe, ins, img, video, [data-google-container-id]')) {
938
+ clearTimeout(hardTimer);
939
+ release();
940
+ return;
941
+ }
942
+ } catch (e) {}
943
+
911
944
  const t = now();
912
945
  const last = state.lastShowById.get(id) || 0;
913
946
  if (t - last < 900) return;
@@ -1245,13 +1278,29 @@ function globalGapFixInit() {
1245
1278
  requestBurst();
1246
1279
  });
1247
1280
 
1281
+ // Some setups populate content in multiple phases; ensure we re-scan.
1282
+ $(window).on('action:ajaxify.contentLoaded.ezoicInfinite', () => {
1283
+ if (isBlocked()) return;
1284
+ requestBurst();
1285
+ });
1286
+
1248
1287
  $(window).on(
1249
- 'action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite',
1288
+ 'action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:categories.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite',
1250
1289
  () => {
1251
1290
  if (isBlocked()) return;
1252
1291
  requestBurst();
1253
1292
  }
1254
1293
  );
1294
+
1295
+ // Also listen through NodeBB's AMD hooks module when available.
1296
+ try {
1297
+ require(['hooks'], (hooks) => {
1298
+ if (!hooks || typeof hooks.on !== 'function') return;
1299
+ ['action:ajaxify.end', 'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded', 'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded'].forEach((ev) => {
1300
+ try { hooks.on(ev, () => { if (!isBlocked()) requestBurst(); }); } catch (e) {}
1301
+ });
1302
+ });
1303
+ } catch (e) {}
1255
1304
  }
1256
1305
 
1257
1306
  function bindScroll() {