nodebb-plugin-ezoic-infinite 1.5.52 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +103 -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.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
@@ -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,15 @@ 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
+ if (ph && ph.dataset) {
390
+ delete ph.dataset.ezDefined;
391
+ delete ph.dataset.ezRequested;
392
+ }
360
393
  const ez = window.ezstandalone;
361
394
  if (ez && typeof ez.destroyPlaceholders === 'function') {
362
- ez.destroyPlaceholders([`${PLACEHOLDER_PREFIX}${id}`]);
395
+ ez.destroyPlaceholders([domId]);
363
396
  }
364
397
  } catch (e) {}
365
398
  }
@@ -522,10 +555,11 @@ function drainQueue() {
522
555
  }
523
556
  }
524
557
 
558
+
525
559
  function startShow(id) {
526
- if (!id || isBlocked()) return;
560
+ if (!id) return;
527
561
 
528
- // Reserve one inflight slot for this request
562
+ // Reserve an inflight slot, but ALWAYS release on any early exit.
529
563
  state.inflight++;
530
564
  let released = false;
531
565
  const release = () => {
@@ -537,22 +571,17 @@ function startShow(id) {
537
571
 
538
572
  const hardTimer = setTimeout(release, 6500);
539
573
 
540
- const earlyExit = () => {
541
- try { clearTimeout(hardTimer); } catch (e) {}
542
- release();
543
- };
544
-
545
574
  requestAnimationFrame(() => {
546
575
  try {
547
- if (isBlocked()) return earlyExit();
576
+ if (isBlocked()) { clearTimeout(hardTimer); release(); return; }
548
577
 
549
578
  const domId = `${PLACEHOLDER_PREFIX}${id}`;
550
- const ph = document.getElementById(domId);
551
- if (!ph || !ph.isConnected) return earlyExit();
579
+ const phNow = document.getElementById(domId);
580
+ if (!phNow || !phNow.isConnected) { clearTimeout(hardTimer); release(); return; }
552
581
 
553
582
  const now2 = Date.now();
554
583
  const last2 = state.lastShowById.get(id) || 0;
555
- if (now2 - last2 < 900) return earlyExit();
584
+ if (now2 - last2 < 900) { clearTimeout(hardTimer); release(); return; }
556
585
  state.lastShowById.set(id, now2);
557
586
 
558
587
  window.ezstandalone = window.ezstandalone || {};
@@ -560,30 +589,24 @@ function startShow(id) {
560
589
 
561
590
  const doShow = () => {
562
591
  try {
563
- if (isBlocked()) return earlyExit();
592
+ if (isBlocked()) return;
564
593
 
565
- // Re-check at execution time (cmd queue may delay)
566
- const ph2 = document.getElementById(domId);
567
- if (!ph2 || !ph2.isConnected) return earlyExit();
594
+ // Re-check at execution time (ez.cmd can run later).
595
+ const ph = document.getElementById(domId);
596
+ if (!ph || !ph.isConnected) return;
568
597
 
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) {}
598
+ // If this id was used before, destroy safely using full DOM id.
599
+ if (state.usedOnce && state.usedOnce.has(id)) {
600
+ safeDestroyById(id);
572
601
  }
573
602
 
574
- // Call showAds (patched to filter missing placeholders)
575
- try { ez.showAds(id); } catch (e) {}
603
+ // showAds is patched to ignore missing placeholders and repeated defines.
604
+ ez.showAds(id);
576
605
 
577
606
  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();
607
+ } catch (e) {}
608
+ finally {
609
+ setTimeout(() => { try { clearTimeout(hardTimer); } catch (_) {} release(); }, 650);
587
610
  }
588
611
  };
589
612
 
@@ -593,12 +616,14 @@ function startShow(id) {
593
616
  doShow();
594
617
  }
595
618
  } catch (e) {
596
- earlyExit();
619
+ try { clearTimeout(hardTimer); } catch (_) {}
620
+ release();
597
621
  }
598
622
  });
599
623
  }
600
624
 
601
625
 
626
+
602
627
  // ---------- preload / above-the-fold ----------
603
628
 
604
629
  function ensurePreloadObserver() {