nodebb-plugin-ezoic-infinite 1.5.51 → 1.5.52

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.51",
3
+ "version": "1.5.52",
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
@@ -100,7 +100,7 @@
100
100
  function markEmptyWrapper(id) {
101
101
  try {
102
102
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
103
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
103
+ if (!ph || !ph.isConnected) return;
104
104
  const wrap = ph.closest ? ph.closest(`.${WRAP_CLASS}`) : null;
105
105
  if (!wrap) return;
106
106
  // If still empty after a delay, collapse it.
@@ -205,13 +205,6 @@
205
205
  ['dns-prefetch', 'https://g.ezoic.net', false],
206
206
  ['preconnect', 'https://go.ezoic.net', true],
207
207
  ['dns-prefetch', 'https://go.ezoic.net', false],
208
- // Google Ads / Safeframe warm-up (helps cold-start latency)
209
- ['preconnect', 'https://pagead2.googlesyndication.com', true],
210
- ['dns-prefetch', 'https://pagead2.googlesyndication.com', false],
211
- ['preconnect', 'https://securepubads.g.doubleclick.net', true],
212
- ['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
213
- ['preconnect', 'https://tpc.googlesyndication.com', true],
214
- ['dns-prefetch', 'https://tpc.googlesyndication.com', false],
215
208
  ];
216
209
  for (const [rel, href, cors] of links) {
217
210
  const key = `${rel}|${href}`;
@@ -227,46 +220,76 @@
227
220
  }
228
221
 
229
222
  // Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
230
- function patchShowAds() {
231
- const applyPatch = () => {
232
- try {
233
- window.ezstandalone = window.ezstandalone || {};
234
- const ez = window.ezstandalone;
235
- if (window.__nodebbEzoicPatched) return;
236
- if (typeof ez.showAds !== 'function') return;
237
-
238
- window.__nodebbEzoicPatched = true;
239
- const orig = ez.showAds;
240
-
241
- ez.showAds = function (...args) {
242
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
243
-
244
- let ids = [];
245
- if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
246
- else ids = args;
247
-
248
- const seen = new Set();
249
- for (const v of ids) {
250
- const id = parseInt(v, 10);
251
- if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
252
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
253
- if (!ph || !ph.isConnected) continue;
254
- seen.add(id);
255
- try { orig.call(ez, id); } catch (e) {}
256
- }
257
- };
258
- } catch (e) {}
223
+
224
+ function patchShowAds() {
225
+ // Robustly filter ez.showAds calls so Ezoic won't spam "placeholder does not exist"
226
+ // during ajaxify navigation or when some placeholders are not present on the current view.
227
+ try {
228
+ window.ezstandalone = window.ezstandalone || {};
229
+ const ez = window.ezstandalone;
230
+ ez.cmd = ez.cmd || [];
231
+
232
+ const wrap = (orig) => {
233
+ if (typeof orig !== 'function') return orig;
234
+ if (orig.__nodebbWrapped) return orig;
235
+
236
+ const wrapped = function (...args) {
237
+ if (isBlocked()) return;
238
+
239
+ let ids = [];
240
+ if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
241
+ else ids = args;
242
+
243
+ const filtered = [];
244
+ const seen = new Set();
245
+ for (const v of ids) {
246
+ const id = parseInt(v, 10);
247
+ if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
248
+ const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
249
+ if (!ph || !ph.isConnected) continue;
250
+ seen.add(id);
251
+ filtered.push(id);
252
+ }
253
+ if (!filtered.length) return;
254
+
255
+ // Call original with the same shape most implementations accept
256
+ try { return orig.call(ez, filtered.length === 1 ? filtered[0] : filtered); } catch (e) {}
257
+ };
258
+ wrapped.__nodebbWrapped = true;
259
+ wrapped.__nodebbOrig = orig;
260
+ return wrapped;
259
261
  };
260
262
 
261
- applyPatch();
262
- if (!window.__nodebbEzoicPatched) {
263
- try {
264
- window.ezstandalone = window.ezstandalone || {};
265
- window.ezstandalone.cmd = window.ezstandalone.cmd || [];
266
- window.ezstandalone.cmd.push(applyPatch);
267
- } catch (e) {}
263
+ // If showAds already exists, wrap it now.
264
+ if (typeof ez.showAds === 'function') {
265
+ ez.showAds = wrap(ez.showAds);
266
+ return;
268
267
  }
269
- }
268
+
269
+ // Otherwise, define a setter hook so whenever Ezoic defines showAds, we wrap it.
270
+ if (!ez.__nodebbShowAdsHooked) {
271
+ ez.__nodebbShowAdsHooked = true;
272
+ let _showAds = null;
273
+
274
+ Object.defineProperty(ez, 'showAds', {
275
+ configurable: true,
276
+ enumerable: true,
277
+ get() { return _showAds; },
278
+ set(fn) { _showAds = wrap(fn); }
279
+ });
280
+
281
+ // In case Ezoic sets showAds from cmd queue
282
+ ez.cmd.push(() => {
283
+ try {
284
+ if (typeof ez.showAds === 'function' && !ez.showAds.__nodebbWrapped) {
285
+ ez.showAds = wrap(ez.showAds);
286
+ }
287
+ } catch (e) {}
288
+ });
289
+ }
290
+ } catch (e) {}
291
+ }
292
+
270
293
 
271
294
  const RECYCLE_COOLDOWN_MS = 1500;
272
295
 
@@ -377,7 +400,7 @@ function withInternalDomChange(fn) {
377
400
  window.setTimeout(() => {
378
401
  try {
379
402
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
380
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
403
+ if (!ph || !ph.isConnected) return;
381
404
  const wrap = ph.closest(`.${WRAP_CLASS}`);
382
405
  if (!wrap) return;
383
406
  const hasContent = ph.childElementCount > 0 || ph.offsetHeight > 0;
@@ -490,7 +513,7 @@ function buildWrap(id, kindClass, afterPos) {
490
513
  }
491
514
 
492
515
  function drainQueue() {
493
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
516
+ if (isBlocked()) return;
494
517
  const max = getMaxInflight();
495
518
  while (state.inflight < max && state.pending.length) {
496
519
  const id = state.pending.shift();
@@ -502,6 +525,7 @@ function drainQueue() {
502
525
  function startShow(id) {
503
526
  if (!id || isBlocked()) return;
504
527
 
528
+ // Reserve one inflight slot for this request
505
529
  state.inflight++;
506
530
  let released = false;
507
531
  const release = () => {
@@ -513,16 +537,22 @@ function startShow(id) {
513
537
 
514
538
  const hardTimer = setTimeout(release, 6500);
515
539
 
540
+ const earlyExit = () => {
541
+ try { clearTimeout(hardTimer); } catch (e) {}
542
+ release();
543
+ };
544
+
516
545
  requestAnimationFrame(() => {
517
546
  try {
518
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
547
+ if (isBlocked()) return earlyExit();
519
548
 
520
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
521
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
549
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
550
+ const ph = document.getElementById(domId);
551
+ if (!ph || !ph.isConnected) return earlyExit();
522
552
 
523
553
  const now2 = Date.now();
524
554
  const last2 = state.lastShowById.get(id) || 0;
525
- if (now2 - last2 < 900) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
555
+ if (now2 - last2 < 900) return earlyExit();
526
556
  state.lastShowById.set(id, now2);
527
557
 
528
558
  window.ezstandalone = window.ezstandalone || {};
@@ -530,35 +560,31 @@ function startShow(id) {
530
560
 
531
561
  const doShow = () => {
532
562
  try {
533
- // Re-check at execution time: ajaxify/infinite-scroll may have removed the placeholder.
534
- const phNow = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
535
- if (!phNow || !phNow.isConnected) {
536
- try { clearTimeout(hardTimer); } catch (e) {}
537
- release();
538
- return;
539
- }
540
- } catch (e) {}
563
+ if (isBlocked()) return earlyExit();
541
564
 
542
- try {
543
- // If we reused an id in this pageview, destroy first (use DOM id string form).
565
+ // Re-check at execution time (cmd queue may delay)
566
+ const ph2 = document.getElementById(domId);
567
+ if (!ph2 || !ph2.isConnected) return earlyExit();
568
+
569
+ // If this id was used before in this pageview, destroy first (Ezoic best practice on recycle)
544
570
  if (state.usedOnce && state.usedOnce.has(id) && typeof ez.destroyPlaceholders === 'function') {
545
- try { ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]); } catch (e) {}
571
+ try { ez.destroyPlaceholders([domId]); } catch (e) {}
546
572
  }
547
- } catch (e) {}
548
573
 
549
- try {
550
- // Call Ezoic. Our showAds patch will also filter if DOM disappeared.
551
- ez.showAds(id);
552
- } catch (e) {}
574
+ // Call showAds (patched to filter missing placeholders)
575
+ try { ez.showAds(id); } catch (e) {}
553
576
 
554
- try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
555
- try { markEmptyWrapper(id); } catch (e) {}
577
+ try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
578
+ try { markEmptyWrapper(id); } catch (e) {} // harmless; if removed later, it no-ops
556
579
 
557
- // Release budget quickly; rendering continues async in ad stack.
558
- setTimeout(() => {
559
- try { clearTimeout(hardTimer); } catch (e) {}
560
- release();
561
- }, 300);
580
+ // Release shortly after triggering, so we can pipeline the next ones
581
+ setTimeout(() => {
582
+ try { clearTimeout(hardTimer); } catch (e) {}
583
+ release();
584
+ }, 650);
585
+ } catch (e) {
586
+ earlyExit();
587
+ }
562
588
  };
563
589
 
564
590
  if (Array.isArray(ez.cmd)) {
@@ -566,8 +592,8 @@ function startShow(id) {
566
592
  } else {
567
593
  doShow();
568
594
  }
569
- } finally {
570
- // If we returned early, hardTimer will release.
595
+ } catch (e) {
596
+ earlyExit();
571
597
  }
572
598
  });
573
599
  }
@@ -614,7 +640,7 @@ function startShow(id) {
614
640
 
615
641
  function observePlaceholder(id) {
616
642
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
617
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
643
+ if (!ph || !ph.isConnected) return;
618
644
  const io = ensurePreloadObserver();
619
645
  try { io && io.observe(ph); } catch (e) {}
620
646
 
@@ -874,7 +900,7 @@ function startShow(id) {
874
900
 
875
901
  // Infinite scroll / partial updates
876
902
  $(window).on('action:posts.loaded.ezoicInfinite action:topics.loaded.ezoicInfinite action:category.loaded.ezoicInfinite action:topic.loaded.ezoicInfinite', () => {
877
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
903
+ if (isBlocked()) return;
878
904
  scheduleRun();
879
905
  });
880
906
  }
package/public/style.css CHANGED
@@ -38,16 +38,3 @@
38
38
  .ezoic-ad > [id^="ezoic-pub-ad-placeholder-"] {
39
39
  min-height: 0 !important;
40
40
  }
41
-
42
-
43
- /* Smooth fill: avoid harsh flash when safeframe/iframe arrives */
44
- .ezoic-ad iframe {
45
- opacity: 0;
46
- transition: opacity 140ms ease;
47
- display: block !important;
48
- vertical-align: top !important;
49
- }
50
- .ezoic-ad iframe[data-load-complete="true"],
51
- .ezoic-ad iframe[src] {
52
- opacity: 1;
53
- }