nodebb-plugin-ezoic-infinite 1.5.52 → 1.5.54

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 +108 -78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.5.52",
3
+ "version": "1.5.54",
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
@@ -219,19 +219,12 @@
219
219
  } catch (e) {}
220
220
  }
221
221
 
222
- // Patch showAds to avoid warnings when a placeholder disappears (infinite scroll, ajaxify)
223
222
 
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;
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.
225
+ function patchShowAds() {
226
+ function wrapShowAds(ez, orig) {
227
+ if (orig && orig.__nodebbWrapped) return orig;
235
228
 
236
229
  const wrapped = function (...args) {
237
230
  if (isBlocked()) return;
@@ -240,56 +233,90 @@ function patchShowAds() {
240
233
  if (args.length === 1 && Array.isArray(args[0])) ids = args[0];
241
234
  else ids = args;
242
235
 
243
- const filtered = [];
244
236
  const seen = new Set();
245
237
  for (const v of ids) {
246
238
  const id = parseInt(v, 10);
247
239
  if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
248
- const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
240
+
241
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
242
+ const ph = document.getElementById(domId);
249
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
+
250
248
  seen.add(id);
251
- filtered.push(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
+ }
252
259
  }
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
260
  };
258
- wrapped.__nodebbWrapped = true;
259
- wrapped.__nodebbOrig = orig;
261
+ try { wrapped.__nodebbWrapped = true; } catch (e) {}
260
262
  return wrapped;
261
- };
263
+ }
262
264
 
263
- // If showAds already exists, wrap it now.
264
- if (typeof ez.showAds === 'function') {
265
- ez.showAds = wrap(ez.showAds);
266
- return;
265
+ function ensureHook() {
266
+ try {
267
+ window.ezstandalone = window.ezstandalone || {};
268
+ const ez = window.ezstandalone;
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);
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) {}
267
295
  }
268
296
 
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;
297
+ ensureHook();
273
298
 
274
- Object.defineProperty(ez, 'showAds', {
275
- configurable: true,
276
- enumerable: true,
277
- get() { return _showAds; },
278
- set(fn) { _showAds = wrap(fn); }
279
- });
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
+ }
280
306
 
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
- }
307
+ };
308
+ } catch (e) {}
309
+ };
292
310
 
311
+ applyPatch();
312
+ if (!window.__nodebbEzoicPatched) {
313
+ try {
314
+ window.ezstandalone = window.ezstandalone || {};
315
+ window.ezstandalone.cmd = window.ezstandalone.cmd || [];
316
+ window.ezstandalone.cmd.push(applyPatch);
317
+ } catch (e) {}
318
+ }
319
+ }
293
320
 
294
321
  const RECYCLE_COOLDOWN_MS = 1500;
295
322
 
@@ -357,9 +384,20 @@ function withInternalDomChange(fn) {
357
384
 
358
385
  function safeDestroyById(id) {
359
386
  try {
387
+ const domId = `${PLACEHOLDER_PREFIX}${id}`;
388
+ const ph = document.getElementById(domId);
389
+
390
+ // If the element is already gone, do NOT call destroyPlaceholders (Ezoic will log "does not exist").
391
+ if (!ph || !ph.isConnected) return;
392
+
393
+ if (ph.dataset) {
394
+ delete ph.dataset.ezDefined;
395
+ delete ph.dataset.ezRequested;
396
+ }
397
+
360
398
  const ez = window.ezstandalone;
361
399
  if (ez && typeof ez.destroyPlaceholders === 'function') {
362
- ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]);
400
+ ez.destroyPlaceholders([domId]);
363
401
  }
364
402
  } catch (e) {}
365
403
  }
@@ -522,10 +560,11 @@ function drainQueue() {
522
560
  }
523
561
  }
524
562
 
563
+
525
564
  function startShow(id) {
526
- if (!id || isBlocked()) return;
565
+ if (!id) return;
527
566
 
528
- // Reserve one inflight slot for this request
567
+ // Reserve an inflight slot, but ALWAYS release on any early exit.
529
568
  state.inflight++;
530
569
  let released = false;
531
570
  const release = () => {
@@ -537,22 +576,17 @@ function startShow(id) {
537
576
 
538
577
  const hardTimer = setTimeout(release, 6500);
539
578
 
540
- const earlyExit = () => {
541
- try { clearTimeout(hardTimer); } catch (e) {}
542
- release();
543
- };
544
-
545
579
  requestAnimationFrame(() => {
546
580
  try {
547
- if (isBlocked()) return earlyExit();
581
+ if (isBlocked()) { clearTimeout(hardTimer); release(); return; }
548
582
 
549
583
  const domId = `${PLACEHOLDER_PREFIX}${id}`;
550
- const ph = document.getElementById(domId);
551
- if (!ph || !ph.isConnected) return earlyExit();
584
+ const phNow = document.getElementById(domId);
585
+ if (!phNow || !phNow.isConnected) { clearTimeout(hardTimer); release(); return; }
552
586
 
553
587
  const now2 = Date.now();
554
588
  const last2 = state.lastShowById.get(id) || 0;
555
- if (now2 - last2 < 900) return earlyExit();
589
+ if (now2 - last2 < 900) { clearTimeout(hardTimer); release(); return; }
556
590
  state.lastShowById.set(id, now2);
557
591
 
558
592
  window.ezstandalone = window.ezstandalone || {};
@@ -560,30 +594,24 @@ function startShow(id) {
560
594
 
561
595
  const doShow = () => {
562
596
  try {
563
- if (isBlocked()) return earlyExit();
597
+ if (isBlocked()) return;
564
598
 
565
- // Re-check at execution time (cmd queue may delay)
566
- const ph2 = document.getElementById(domId);
567
- if (!ph2 || !ph2.isConnected) return earlyExit();
599
+ // Re-check at execution time (ez.cmd can run later).
600
+ const ph = document.getElementById(domId);
601
+ if (!ph || !ph.isConnected) return;
568
602
 
569
- // If this id was used before in this pageview, destroy first (Ezoic best practice on recycle)
570
- if (state.usedOnce && state.usedOnce.has(id) && typeof ez.destroyPlaceholders === 'function') {
571
- try { ez.destroyPlaceholders([domId]); } catch (e) {}
603
+ // If this id was used before, destroy safely using full DOM id.
604
+ if (state.usedOnce && state.usedOnce.has(id)) {
605
+ safeDestroyById(id);
572
606
  }
573
607
 
574
- // Call showAds (patched to filter missing placeholders)
575
- try { ez.showAds(id); } catch (e) {}
608
+ // showAds is patched to ignore missing placeholders and repeated defines.
609
+ ez.showAds(id);
576
610
 
577
611
  try { state.usedOnce && state.usedOnce.add(id); } catch (e) {}
578
- try { markEmptyWrapper(id); } catch (e) {} // harmless; if removed later, it no-ops
579
-
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();
612
+ } catch (e) {}
613
+ finally {
614
+ setTimeout(() => { try { clearTimeout(hardTimer); } catch (_) {} release(); }, 650);
587
615
  }
588
616
  };
589
617
 
@@ -593,12 +621,14 @@ function startShow(id) {
593
621
  doShow();
594
622
  }
595
623
  } catch (e) {
596
- earlyExit();
624
+ try { clearTimeout(hardTimer); } catch (_) {}
625
+ release();
597
626
  }
598
627
  });
599
628
  }
600
629
 
601
630
 
631
+
602
632
  // ---------- preload / above-the-fold ----------
603
633
 
604
634
  function ensurePreloadObserver() {