nodebb-plugin-ezoic-infinite 1.4.64 → 1.4.66

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.4.64",
3
+ "version": "1.4.66",
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
@@ -8,7 +8,6 @@
8
8
  }, WRAP_CLASS = 'ezoic-ad';
9
9
  const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-', MAX_INSERTS_PER_RUN = 3;
10
10
 
11
- // Nécessaire pour savoir si on doit appeler destroyPlaceholders avant recyclage.
12
11
  const sessionDefinedIds = new Set();
13
12
 
14
13
  const insertingIds = new Set(), state = {
@@ -106,9 +105,7 @@
106
105
 
107
106
  function destroyPlaceholderIds(ids) {
108
107
  if (!ids || !ids.length) return;
109
- // Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
110
108
  const filtered = ids.filter((id) => {
111
- // Utiliser sessionDefinedIds (survit aux navigations) plutôt que state.definedIds
112
109
  try { return sessionDefinedIds.has(id); } catch (e) { return true; }
113
110
  });
114
111
  if (!filtered.length) return;
@@ -128,6 +125,22 @@
128
125
  } catch (e) {}
129
126
  }
130
127
 
128
+ function cleanupEmptyWrappers() {
129
+ try {
130
+ document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
131
+ const ph = wrapper.querySelector('[id^="ezoic-pub-ad-placeholder-"]');
132
+ if (ph && ph.children.length === 0) {
133
+ // Placeholder vide après 3s = pub non chargée
134
+ setTimeout(() => {
135
+ if (ph.children.length === 0) {
136
+ wrapper.remove();
137
+ }
138
+ }, 3000);
139
+ }
140
+ });
141
+ } catch (e) {}
142
+ }
143
+
131
144
  function getRecyclable(liveArr) {
132
145
  const margin = 600;
133
146
  for (let i = 0; i < liveArr.length; i++) {
@@ -199,10 +212,8 @@
199
212
  if (findWrap(kindClass, afterPos)) return null;
200
213
 
201
214
  // CRITICAL: Double-lock pour éviter race conditions sur les doublons
202
- // 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
203
215
  if (insertingIds.has(id)) return null;
204
216
 
205
- // 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
206
217
  const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
207
218
  if (existingPh && existingPh.isConnected) return null;
208
219
 
@@ -215,7 +226,6 @@
215
226
  attachFillObserver(wrap, id);
216
227
  return wrap;
217
228
  } finally {
218
- // Libérer le lock après 100ms (le temps que le DOM soit stable)
219
229
  setTimeout(() => insertingIds.delete(id), 50);
220
230
  }
221
231
  }
@@ -226,8 +236,6 @@
226
236
  }
227
237
 
228
238
  function patchShowAds() {
229
- // Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
230
- // Also ensures the patch is applied even if Ezoic loads after our script.
231
239
  const applyPatch = () => {
232
240
  try {
233
241
  window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
@@ -254,7 +262,6 @@
254
262
  };
255
263
 
256
264
  applyPatch();
257
- // Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
258
265
  if (!window.__nodebbEzoicPatched) {
259
266
  try {
260
267
  window.ezstandalone = window.ezstandalone || {};
@@ -267,7 +274,6 @@
267
274
  function markFilled(wrap) {
268
275
  try {
269
276
  if (!wrap) return;
270
- // Disconnect the fill observer first (no need to remove+re-add the attribute)
271
277
  if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
272
278
  wrap.setAttribute('data-ezoic-filled', '1');
273
279
  } catch (e) {}
@@ -295,7 +301,6 @@
295
301
  }
296
302
  });
297
303
  obs.observe(ph, { childList: true, subtree: true });
298
- // Keep a weak reference on the wrapper so we can disconnect on recycle/remove
299
304
  wrap.__ezoicFillObs = obs;
300
305
  } catch (e) {}
301
306
  }
@@ -315,15 +320,12 @@
315
320
  return filled;
316
321
  }
317
322
 
318
- // Appeler showAds() en batch selon recommandations Ezoic
319
- // Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
320
323
  let batchShowAdsTimer = null;
321
324
  const pendingShowAdsIds = new Set();
322
325
 
323
326
  function scheduleShowAdsBatch(id) {
324
327
  if (!id) return;
325
328
 
326
- // CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
327
329
  if (sessionDefinedIds.has(id)) {
328
330
  try {
329
331
  destroyPlaceholderIds([id]);
@@ -338,7 +340,6 @@
338
340
  // Ajouter à la batch
339
341
  pendingShowAdsIds.add(id);
340
342
 
341
- // Debounce: attendre 100ms pour collecter tous les IDs
342
343
  clearTimeout(batchShowAdsTimer);
343
344
  batchShowAdsTimer = setTimeout(() => {
344
345
  if (pendingShowAdsIds.size === 0) return;
@@ -388,19 +389,15 @@
388
389
  const startPageKey = state.pageKey;
389
390
  let attempts = 0;
390
391
  (function waitForPh() {
391
- // Abort if the user navigated away since this showAds was scheduled
392
392
  if (state.pageKey !== startPageKey) return;
393
- // Abort if another concurrent call is already handling this id
394
393
  if (state.pendingById.has(id)) return;
395
394
 
396
395
  attempts += 1;
397
396
  const el = document.getElementById(phId);
398
397
  if (el && el.isConnected) {
399
- // CRITIQUE: Vérifier que le placeholder est VISIBLE
400
398
 
401
399
  // Si on arrive ici, soit visible, soit timeout
402
400
 
403
- // Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
404
401
  if (doCall()) {
405
402
  state.pendingById.delete(id);
406
403
  return;
@@ -466,7 +463,6 @@
466
463
  const el = items[afterPos - 1];
467
464
  if (!el || !el.isConnected) continue;
468
465
 
469
- // Prevent adjacent ads (DOM-based, robust against virtualization)
470
466
  if (isAdjacentAd(el) || isPrevAd(el)) {
471
467
  continue;
472
468
  }
@@ -483,17 +479,14 @@
483
479
 
484
480
  let wrap = null;
485
481
  if (pick.recycled && pick.recycled.wrap) {
486
- // Only destroy if Ezoic has actually defined this placeholder before
487
482
  if (sessionDefinedIds.has(id)) {
488
483
  destroyPlaceholderIds([id]);
489
484
  }
490
- // Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
491
485
  const oldWrap = pick.recycled.wrap;
492
486
  try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
493
487
  try { oldWrap && oldWrap.remove(); } catch (e) {}
494
488
  wrap = insertAfter(el, id, kindClass, afterPos);
495
489
  if (!wrap) continue;
496
- // Attendre que le wrapper soit dans le DOM puis appeler showAds
497
490
  setTimeout(() => {
498
491
  callShowAdsWhenReady(id);
499
492
  }, 50);
@@ -507,12 +500,10 @@
507
500
  }
508
501
 
509
502
  liveArr.push({ id, wrap });
510
- // If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
511
503
  if (wrap && (
512
504
  (wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
513
505
  )) {
514
506
  try { wrap.remove(); } catch (e) {}
515
- // Put id back if it was newly consumed (not recycled)
516
507
  if (!(pick.recycled && pick.recycled.wrap)) {
517
508
  try { kindPool.unshift(id); } catch (e) {}
518
509
  usedSet.delete(id);
@@ -529,7 +520,6 @@
529
520
  for (let i = 0; i < ads.length; i++) {
530
521
  const ad = ads[i], prev = ad.previousElementSibling;
531
522
  if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
532
- // Supprimer le wrapper adjacent au lieu de le cacher
533
523
  try {
534
524
  const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
535
525
  if (ph) {
@@ -550,8 +540,6 @@
550
540
  function cleanup() {
551
541
  destroyUsedPlaceholders();
552
542
 
553
- // CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
554
- // Sinon ils restent et deviennent "unused" sur la nouvelle page
555
543
  document.querySelectorAll('.ezoic-ad').forEach(el => {
556
544
  try { el.remove(); } catch (e) {}
557
545
  });
@@ -567,23 +555,14 @@
567
555
  state.usedPosts.clear();
568
556
  state.usedCategories.clear();
569
557
  state.lastShowById.clear();
570
- // CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
571
- // Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
572
558
  state.pendingById.clear();
573
559
  state.definedIds.clear();
574
560
 
575
- // NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
576
- // quand NodeBB vide le DOM lors de la navigation ajaxify
577
- // Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
578
-
579
- // CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
580
- // showAds() continuent à s'exécuter après la navigation
581
561
  state.activeTimeouts.forEach(id => {
582
562
  try { clearTimeout(id); } catch (e) {}
583
563
  });
584
564
  state.activeTimeouts.clear();
585
565
 
586
- // Vider aussi pendingById pour annuler les showAds en attente
587
566
  state.pendingById.clear();
588
567
 
589
568
  if (state.obs) { state.obs.disconnect(); state.obs = null; }
@@ -600,7 +579,6 @@
600
579
  }
601
580
 
602
581
  async function runCore() {
603
- // Attendre que canInsert soit true (protection race condition navigation)
604
582
  if (!state.canShowAds) {
605
583
  return;
606
584
  }
@@ -643,7 +621,6 @@
643
621
 
644
622
  enforceNoAdjacentAds();
645
623
 
646
- // If nothing inserted and list isn't in DOM yet (first click), retry a bit
647
624
  let count = 0;
648
625
  if (kind === 'topic') count = getPostContainers().length;
649
626
  else if (kind === 'categoryTopics') count = getTopicItems().length;
@@ -655,12 +632,9 @@
655
632
  }
656
633
 
657
634
  if (inserted >= MAX_INSERTS_PER_RUN) {
658
- // Plus d'insertions possibles ce cycle, continuer immédiatement
659
635
  setTimeout(arguments[0], 50);
660
636
  } else if (inserted === 0 && count > 0) {
661
637
  // Pool épuisé ou recyclage pas encore disponible.
662
- // Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
663
- // le temps de défiler hors écran et devenir recyclables.
664
638
  if (state.poolWaitAttempts < 8) {
665
639
  state.poolWaitAttempts += 1;
666
640
  setTimeout(arguments[0], 50);
@@ -694,15 +668,11 @@
694
668
  state.pageKey = getPageKey();
695
669
  ensureObserver();
696
670
 
697
- // CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
698
- // pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
699
- // Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
700
671
  state.canShowAds = true;
701
672
  });
702
673
 
703
674
  $(window).on('action:category.loaded.ezoicInfinite', () => {
704
675
  ensureObserver();
705
- // category.loaded = infinite scroll, Ezoic déjà chargé normalement
706
676
  waitForContentThenRun();
707
677
  });
708
678
  $(window).on('action:topics.loaded.ezoicInfinite', () => {
@@ -732,7 +702,6 @@
732
702
  window.requestAnimationFrame(() => {
733
703
  ticking = false;
734
704
  enforceNoAdjacentAds();
735
- // Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
736
705
  const now = Date.now();
737
706
  if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
738
707
  state.lastScrollRun = now;
@@ -742,7 +711,6 @@
742
711
  }, { passive: true });
743
712
  }
744
713
 
745
- // Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
746
714
  function waitForContentThenRun() {
747
715
  const MIN_WORDS = 250;
748
716
  let attempts = 0;
@@ -773,7 +741,6 @@
773
741
  })();
774
742
  }
775
743
 
776
- // Fonction qui attend que Ezoic soit vraiment chargé
777
744
  function waitForEzoicThenRun() {
778
745
  let attempts = 0;
779
746
  const maxAttempts = 50; // 50 × 200ms = 10s max
package/public/style.css CHANGED
@@ -1,3 +1,18 @@
1
- .ezoic-ad{height:auto !important; min-height:0 !important; padding:0 !important; margin:0.5rem 0;}
1
+ .ezoic-ad{height:auto !important; padding:0 !important; margin: 0.25rem 0;}
2
2
  .ezoic-ad .ezoic-ad-inner{padding:0;margin:0;}
3
3
  .ezoic-ad .ezoic-ad-inner > div{padding:0;margin:0;}
4
+
5
+ /* Cacher placeholder vide pour éviter espace réservé */
6
+ .ezoic-ad [id^="ezoic-pub-ad-placeholder-"]:empty {
7
+ display: none !important;
8
+ height: 0 !important;
9
+ margin: 0 !important;
10
+ padding: 0 !important;
11
+ }
12
+
13
+ /* Wrapper vide aussi caché */
14
+ .ezoic-ad:has([id^="ezoic-pub-ad-placeholder-"]:empty) {
15
+ display: none !important;
16
+ height: 0 !important;
17
+ margin: 0 !important;
18
+ }