nodebb-plugin-ezoic-infinite 1.5.51 → 1.5.53

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.53",
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}`;
@@ -226,34 +219,91 @@
226
219
  } catch (e) {}
227
220
  }
228
221
 
222
+
229
223
  // Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
224
+ // Ezoic can (re)define ezstandalone.showAds over time; we hook assignments to keep the guard.
230
225
  function patchShowAds() {
231
- const applyPatch = () => {
226
+ function wrapShowAds(ez, orig) {
227
+ if (orig && orig.__nodebbWrapped) return orig;
228
+
229
+ const wrapped = function (...args) {
230
+ if (isBlocked()) return;
231
+
232
+ let ids = [];
233
+ if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
234
+ else ids = args;
235
+
236
+ const seen = new Set();
237
+ for (const v of ids) {
238
+ const id = parseInt(v, 10);
239
+ if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
240
+
241
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
242
+ const ph = document.getElementById(domId);
243
+ if (!ph || !ph.isConnected) continue;
244
+
245
+ // Prevent repeated "define" attempts on the same placeholder while it remains in DOM.
246
+ if (ph.dataset && (ph.dataset.ezRequested === '1' || ph.dataset.ezDefined === '1')) continue;
247
+
248
+ seen.add(id);
249
+ try {
250
+ if (ph.dataset) ph.dataset.ezRequested = '1';
251
+ orig.call(ez, id);
252
+ if (ph.dataset) {
253
+ ph.dataset.ezDefined = '1';
254
+ ph.dataset.ezRequested = '0';
255
+ }
256
+ } catch (e) {
257
+ try { if (ph.dataset) ph.dataset.ezRequested = '0'; } catch (_) {}
258
+ }
259
+ }
260
+ };
261
+ try { wrapped.__nodebbWrapped = true; } catch (e) {}
262
+ return wrapped;
263
+ }
264
+
265
+ function ensureHook() {
232
266
  try {
233
267
  window.ezstandalone = window.ezstandalone || {};
234
268
  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) {}
269
+
270
+ // Hook future assignments to showAds so we keep our wrapper even if Ezoic overwrites it.
271
+ if (!ez.__nodebbShowAdsHooked) {
272
+ ez.__nodebbShowAdsHooked = true;
273
+ let _showAds = ez.showAds;
274
+
275
+ Object.defineProperty(ez, 'showAds', {
276
+ configurable: true,
277
+ enumerable: true,
278
+ get() {
279
+ return _showAds;
280
+ },
281
+ set(fn) {
282
+ _showAds = (typeof fn === 'function') ? wrapShowAds(ez, fn) : fn;
283
+ },
284
+ });
285
+
286
+ // If showAds already exists, wrap it immediately
287
+ if (typeof _showAds === 'function') {
288
+ _showAds = wrapShowAds(ez, _showAds);
256
289
  }
290
+ } else if (typeof ez.showAds === 'function' && !ez.showAds.__nodebbWrapped) {
291
+ // In case defineProperty wasn't possible (rare), re-wrap
292
+ ez.showAds = wrapShowAds(ez, ez.showAds);
293
+ }
294
+ } catch (e) {}
295
+ }
296
+
297
+ ensureHook();
298
+
299
+ // Also attempt via cmd queue
300
+ try {
301
+ window.ezstandalone = window.ezstandalone || {};
302
+ window.ezstandalone.cmd = window.ezstandalone.cmd || [];
303
+ window.ezstandalone.cmd.push(ensureHook);
304
+ } catch (e) {}
305
+ }
306
+
257
307
  };
258
308
  } catch (e) {}
259
309
  };
@@ -334,9 +384,15 @@ function withInternalDomChange(fn) {
334
384
 
335
385
  function safeDestroyById(id) {
336
386
  try {
387
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
388
+ const ph = document.getElementById(domId);
389
+ if (ph && ph.dataset) {
390
+ delete ph.dataset.ezDefined;
391
+ delete ph.dataset.ezRequested;
392
+ }
337
393
  const ez = window.ezstandalone;
338
394
  if (ez && typeof ez.destroyPlaceholders === 'function') {
339
- ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]);
395
+ ez.destroyPlaceholders([domId]);
340
396
  }
341
397
  } catch (e) {}
342
398
  }
@@ -377,7 +433,7 @@ function withInternalDomChange(fn) {
377
433
  window.setTimeout(() => {
378
434
  try {
379
435
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
380
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
436
+ if (!ph || !ph.isConnected) return;
381
437
  const wrap = ph.closest(`.${WRAP_CLASS}`);
382
438
  if (!wrap) return;
383
439
  const hasContent = ph.childElementCount > 0 || ph.offsetHeight > 0;
@@ -490,7 +546,7 @@ function buildWrap(id, kindClass, afterPos) {
490
546
  }
491
547
 
492
548
  function drainQueue() {
493
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
549
+ if (isBlocked()) return;
494
550
  const max = getMaxInflight();
495
551
  while (state.inflight < max && state.pending.length) {
496
552
  const id = state.pending.shift();
@@ -499,9 +555,11 @@ function drainQueue() {
499
555
  }
500
556
  }
501
557
 
558
+
502
559
  function startShow(id) {
503
- if (!id || isBlocked()) return;
560
+ if (!id) return;
504
561
 
562
+ // Reserve an inflight slot, but ALWAYS release on any early exit.
505
563
  state.inflight++;
506
564
  let released = false;
507
565
  const release = () => {
@@ -515,14 +573,15 @@ function startShow(id) {
515
573
 
516
574
  requestAnimationFrame(() => {
517
575
  try {
518
- if (isBlocked()) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
576
+ if (isBlocked()) { clearTimeout(hardTimer); release(); return; }
519
577
 
520
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
521
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
578
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
579
+ const phNow = document.getElementById(domId);
580
+ if (!phNow || !phNow.isConnected) { clearTimeout(hardTimer); release(); return; }
522
581
 
523
582
  const now2 = Date.now();
524
583
  const last2 = state.lastShowById.get(id) || 0;
525
- if (now2 - last2 < 900) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
584
+ if (now2 - last2 < 900) { clearTimeout(hardTimer); release(); return; }
526
585
  state.lastShowById.set(id, now2);
527
586
 
528
587
  window.ezstandalone = window.ezstandalone || {};
@@ -530,35 +589,25 @@ function startShow(id) {
530
589
 
531
590
  const doShow = () => {
532
591
  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) {}
592
+ if (isBlocked()) return;
541
593
 
542
- try {
543
- // If we reused an id in this pageview, destroy first (use DOM id string form).
544
- if (state.usedOnce && state.usedOnce.has(id) && typeof ez.destroyPlaceholders === 'function') {
545
- try { ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]); } catch (e) {}
594
+ // Re-check at execution time (ez.cmd can run later).
595
+ const ph = document.getElementById(domId);
596
+ if (!ph || !ph.isConnected) return;
597
+
598
+ // If this id was used before, destroy safely using full DOM id.
599
+ if (state.usedOnce && state.usedOnce.has(id)) {
600
+ safeDestroyById(id);
546
601
  }
547
- } catch (e) {}
548
602
 
549
- try {
550
- // Call Ezoic. Our showAds patch will also filter if DOM disappeared.
603
+ // showAds is patched to ignore missing placeholders and repeated defines.
551
604
  ez.showAds(id);
552
- } catch (e) {}
553
-
554
- try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
555
- try { markEmptyWrapper(id); } catch (e) {}
556
605
 
557
- // Release budget quickly; rendering continues async in ad stack.
558
- setTimeout(() => {
559
- try { clearTimeout(hardTimer); } catch (e) {}
560
- release();
561
- }, 300);
606
+ try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
607
+ } catch (e) {}
608
+ finally {
609
+ setTimeout(() => { try { clearTimeout(hardTimer); } catch (_) {} release(); }, 650);
610
+ }
562
611
  };
563
612
 
564
613
  if (Array.isArray(ez.cmd)) {
@@ -566,13 +615,15 @@ function startShow(id) {
566
615
  } else {
567
616
  doShow();
568
617
  }
569
- } finally {
570
- // If we returned early, hardTimer will release.
618
+ } catch (e) {
619
+ try { clearTimeout(hardTimer); } catch (_) {}
620
+ release();
571
621
  }
572
622
  });
573
623
  }
574
624
 
575
625
 
626
+
576
627
  // ---------- preload / above-the-fold ----------
577
628
 
578
629
  function ensurePreloadObserver() {
@@ -614,7 +665,7 @@ function startShow(id) {
614
665
 
615
666
  function observePlaceholder(id) {
616
667
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
617
- if (!ph || !ph.isConnected) { try { clearTimeout(hardTimer); } catch (e) {} release(); return; }
668
+ if (!ph || !ph.isConnected) return;
618
669
  const io = ensurePreloadObserver();
619
670
  try { io && io.observe(ph); } catch (e) {}
620
671
 
@@ -874,7 +925,7 @@ function startShow(id) {
874
925
 
875
926
  // Infinite scroll / partial updates
876
927
  $(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; }
928
+ if (isBlocked()) return;
878
929
  scheduleRun();
879
930
  });
880
931
  }
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
- }