nodebb-plugin-ezoic-infinite 1.7.96 → 1.7.97

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 +74 -72
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.96",
3
+ "version": "1.7.97",
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
@@ -79,15 +79,14 @@
79
79
 
80
80
  const EMPTY_CHECK_MS = 20_000; // délai avant collapse d'un wrap vide post-show
81
81
  const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
82
- const MAX_INSERTS_RUN = 10; // max insertions par appel runCore (plus réactif)
82
+ const MAX_INSERTS_RUN = 6; // max insertions par appel runCore
83
83
  const MAX_INFLIGHT = 4; // max showAds() simultanés
84
- const SHOW_THROTTLE_MS = 600; // anti-spam showAds() par id (plus réactif)
85
- const BURST_COOLDOWN_MS = 120; // délai min entre deux déclenchements de burst
84
+ const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
85
+ const BURST_COOLDOWN_MS = 200; // délai min entre deux déclenchements de burst
86
86
 
87
87
  // Marges IO larges et fixes — observer créé une seule fois au boot
88
88
  const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
89
89
  const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
90
- const FAST_SHOW_MARGIN_PX = 900; // show immédiat si slot déjà proche viewport
91
90
 
92
91
  const SEL = {
93
92
  post: '[component="post"][data-pid]',
@@ -133,8 +132,6 @@
133
132
  burstDeadline: 0,
134
133
  burstCount: 0,
135
134
  lastBurstTs: 0,
136
- scrollDir: 1,
137
- lastScrollY: 0,
138
135
  };
139
136
 
140
137
  let blockedUntil = 0;
@@ -145,6 +142,34 @@
145
142
  const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
146
143
  const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
147
144
 
145
+ function ezCmd(fn) {
146
+ try {
147
+ window.ezstandalone = window.ezstandalone || {};
148
+ const ez = window.ezstandalone;
149
+ if (Array.isArray(ez.cmd)) ez.cmd.push(fn);
150
+ else fn();
151
+ } catch (_) {}
152
+ }
153
+
154
+ function destroyPlaceholderIds(ids) {
155
+ try {
156
+ const uniq = Array.from(new Set((ids || [])
157
+ .map(v => parseInt(v, 10))
158
+ .filter(v => Number.isFinite(v) && v > 0)));
159
+ if (!uniq.length) return;
160
+ ezCmd(() => {
161
+ try {
162
+ const ez = window.ezstandalone;
163
+ if (typeof ez?.destroyPlaceholders === 'function') ez.destroyPlaceholders(uniq);
164
+ } catch (_) {}
165
+ });
166
+ } catch (_) {}
167
+ }
168
+
169
+ function placeholderCount(id) {
170
+ try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length; } catch (_) { return 0; }
171
+ }
172
+
148
173
  function mutate(fn) {
149
174
  S.mutGuard++;
150
175
  try { fn(); } finally { S.mutGuard--; }
@@ -316,58 +341,33 @@
316
341
  typeof ez?.define !== 'function' ||
317
342
  typeof ez?.displayMore !== 'function') return null;
318
343
 
319
- const vh = window.innerHeight || 800;
320
- const dir = S.scrollDir >= 0 ? 1 : -1;
321
- const farTopThreshold = -vh; // hors écran au-dessus
322
- const farBottomThreshold = vh * 2; // loin sous le viewport (scroll-up)
323
-
324
- let prefEmpty = null, prefEmptyScore = -Infinity;
325
- let prefFilled = null, prefFilledScore = -Infinity;
326
- let altEmpty = null, altEmptyScore = -Infinity;
327
- let altFilled = null, altFilledScore = -Infinity;
344
+ const vh = window.innerHeight || 800;
345
+ // Seuil : -1vh (hors viewport visible). On appelle unobserve(ph) juste
346
+ // après pour neutraliser l'IO — plus de showAds parasite possible.
347
+ const threshold = -vh;
348
+ let bestEmpty = null, bestEmptyBottom = Infinity;
349
+ let bestFilled = null, bestFilledBottom = Infinity;
328
350
 
329
- for (const wrap of S.wrapByKey.values()) {
330
- if (!wrap?.isConnected) continue;
331
- if (!wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
351
+ document.querySelectorAll(`.${WRAP_CLASS}.${klass}`).forEach(wrap => {
332
352
  try {
333
353
  const rect = wrap.getBoundingClientRect();
334
- const farAbove = rect.bottom <= farTopThreshold;
335
- const farBelow = rect.top >= farBottomThreshold;
336
- let preferred = false;
337
- let score = -Infinity;
338
-
339
- if (dir >= 0) {
340
- if (farAbove) { preferred = true; score = Math.abs(rect.bottom); }
341
- else if (farBelow) { score = Math.abs(rect.top); }
354
+ if (rect.bottom > threshold) return;
355
+ if (!isFilled(wrap)) {
356
+ if (rect.bottom < bestEmptyBottom) { bestEmptyBottom = rect.bottom; bestEmpty = wrap; }
342
357
  } else {
343
- if (farBelow) { preferred = true; score = Math.abs(rect.top); }
344
- else if (farAbove) { score = Math.abs(rect.bottom); }
345
- }
346
- if (score === -Infinity) continue;
347
-
348
- const filled = isFilled(wrap);
349
- if (preferred) {
350
- if (!filled) {
351
- if (score > prefEmptyScore) { prefEmptyScore = score; prefEmpty = wrap; }
352
- } else {
353
- if (score > prefFilledScore) { prefFilledScore = score; prefFilled = wrap; }
354
- }
355
- } else {
356
- if (!filled) {
357
- if (score > altEmptyScore) { altEmptyScore = score; altEmpty = wrap; }
358
- } else {
359
- if (score > altFilledScore) { altFilledScore = score; altFilled = wrap; }
360
- }
358
+ if (rect.bottom < bestFilledBottom) { bestFilledBottom = rect.bottom; bestFilled = wrap; }
361
359
  }
362
360
  } catch (_) {}
363
- }
361
+ });
364
362
 
365
- const best = prefEmpty ?? prefFilled ?? altEmpty ?? altFilled;
363
+ const best = bestEmpty ?? bestFilled;
366
364
  if (!best) return null;
367
365
  const id = parseInt(best.getAttribute(A_WRAPID), 10);
368
366
  if (!Number.isFinite(id)) return null;
369
367
 
370
368
  const oldKey = best.getAttribute(A_ANCHOR);
369
+ // Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
370
+ // parasite si le nœud était encore dans la zone IO_MARGIN.
371
371
  try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
372
372
  mutate(() => {
373
373
  best.setAttribute(A_ANCHOR, newKey);
@@ -381,8 +381,9 @@
381
381
  if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
382
382
  S.wrapByKey.set(newKey, best);
383
383
 
384
- const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 220); };
385
- const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay,220); };
384
+ // Délais requis : destroyPlaceholders est asynchrone en interne
385
+ const doDestroy = () => { try { ez.destroyPlaceholders([id]); } catch (_) {} setTimeout(doDefine, 300); };
386
+ const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
386
387
  const doDisplay = () => { try { ez.displayMore([id]); } catch (_) {} };
387
388
  try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
388
389
 
@@ -423,7 +424,10 @@
423
424
  const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
424
425
  if (ph instanceof Element) S.io?.unobserve(ph);
425
426
  const id = parseInt(w.getAttribute(A_WRAPID), 10);
426
- if (Number.isFinite(id)) S.mountedIds.delete(id);
427
+ if (Number.isFinite(id)) {
428
+ destroyPlaceholderIds([id]);
429
+ S.mountedIds.delete(id);
430
+ }
427
431
  const key = w.getAttribute(A_ANCHOR);
428
432
  if (key && S.wrapByKey.get(key) === w) S.wrapByKey.delete(key);
429
433
  w.remove();
@@ -530,16 +534,7 @@
530
534
 
531
535
  function observePh(id) {
532
536
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
533
- if (!ph?.isConnected) return;
534
- try { getIO()?.observe(ph); } catch (_) {}
535
- try {
536
- const r = ph.getBoundingClientRect();
537
- const vh = window.innerHeight || 800;
538
- if (r.bottom >= -FAST_SHOW_MARGIN_PX && r.top <= vh + FAST_SHOW_MARGIN_PX) {
539
- const pid = parseInt(ph.getAttribute('data-ezoic-id'), 10);
540
- if (Number.isFinite(pid) && pid > 0) enqueueShow(pid);
541
- }
542
- } catch (_) {}
537
+ if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
543
538
  }
544
539
 
545
540
  function enqueueShow(id) {
@@ -578,6 +573,7 @@
578
573
  if (isBlocked()) { clearTimeout(timer); return release(); }
579
574
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
580
575
  if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
576
+ if (placeholderCount(id) !== 1) { clearTimeout(timer); return release(); }
581
577
 
582
578
  const t = ts();
583
579
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
@@ -590,7 +586,7 @@
590
586
  const doShow = () => {
591
587
  try { ez.showAds(id); } catch (_) {}
592
588
  scheduleEmptyCheck(id, t);
593
- setTimeout(() => { clearTimeout(timer); release(); }, 350);
589
+ setTimeout(() => { clearTimeout(timer); release(); }, 700);
594
590
  };
595
591
  Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
596
592
  } catch (_) { clearTimeout(timer); release(); }
@@ -631,6 +627,7 @@
631
627
  const id = parseInt(v, 10);
632
628
  if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
633
629
  if (!document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) continue;
630
+ if (placeholderCount(id) !== 1) continue;
634
631
  seen.add(id);
635
632
  try { orig(id); } catch (_) {}
636
633
  }
@@ -715,7 +712,7 @@
715
712
  S.burstCount++;
716
713
  scheduleRun(n => {
717
714
  if (!n && !S.pending.length) { S.burstActive = false; return; }
718
- setTimeout(step, n > 0 ? 80 : 180);
715
+ setTimeout(step, n > 0 ? 150 : 300);
719
716
  });
720
717
  };
721
718
  step();
@@ -725,6 +722,18 @@
725
722
 
726
723
  function cleanup() {
727
724
  blockedUntil = ts() + 1500;
725
+ try {
726
+ const ids = Array.from(document.querySelectorAll(`.${WRAP_CLASS}[${A_WRAPID}]`))
727
+ .map(w => parseInt(w.getAttribute(A_WRAPID), 10))
728
+ .filter(v => Number.isFinite(v) && v > 0);
729
+ destroyPlaceholderIds(ids);
730
+ ezCmd(() => {
731
+ try {
732
+ const ez = window.ezstandalone;
733
+ if (typeof ez?.destroyAll === 'function') ez.destroyAll();
734
+ } catch (_) {}
735
+ });
736
+ } catch (_) {}
728
737
  mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
729
738
  S.cfg = null;
730
739
  S.poolsReady = false;
@@ -835,7 +844,9 @@
835
844
  S.pageKey = pageKey();
836
845
  blockedUntil = 0;
837
846
  muteConsole(); ensureTcfLocator(); warmNetwork();
838
- patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
847
+ patchShowAds(); getIO(); ensureDomObserver();
848
+ ezCmd(() => { try { window.ezstandalone?.showAds?.(); } catch (_) {} });
849
+ requestBurst();
839
850
  });
840
851
 
841
852
  const burstEvts = [
@@ -860,22 +871,13 @@
860
871
  window.addEventListener('scroll', () => {
861
872
  if (ticking) return;
862
873
  ticking = true;
863
- requestAnimationFrame(() => {
864
- ticking = false;
865
- try {
866
- const y = window.scrollY || window.pageYOffset || 0;
867
- S.scrollDir = (y < (S.lastScrollY || 0)) ? -1 : 1;
868
- S.lastScrollY = y;
869
- } catch (_) {}
870
- requestBurst();
871
- });
874
+ requestAnimationFrame(() => { ticking = false; requestBurst(); });
872
875
  }, { passive: true });
873
876
  }
874
877
 
875
878
  // ── Boot ───────────────────────────────────────────────────────────────────
876
879
 
877
880
  S.pageKey = pageKey();
878
- try { S.lastScrollY = window.scrollY || window.pageYOffset || 0; } catch (_) {}
879
881
  muteConsole();
880
882
  ensureTcfLocator();
881
883
  warmNetwork();