nodebb-plugin-ezoic-infinite 1.4.65 → 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.65",
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,8 +125,6 @@
128
125
  } catch (e) {}
129
126
  }
130
127
 
131
-
132
- // Nettoyer les wrappers vides (sans pub) pour éviter espaces verticaux
133
128
  function cleanupEmptyWrappers() {
134
129
  try {
135
130
  document.querySelectorAll('.ezoic-ad').forEach(wrapper => {
@@ -217,10 +212,8 @@
217
212
  if (findWrap(kindClass, afterPos)) return null;
218
213
 
219
214
  // CRITICAL: Double-lock pour éviter race conditions sur les doublons
220
- // 1. Vérifier qu'aucun autre thread n'est en train d'insérer cet ID
221
215
  if (insertingIds.has(id)) return null;
222
216
 
223
- // 2. Vérifier qu'aucun placeholder avec cet ID n'existe déjà dans le DOM
224
217
  const existingPh = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
225
218
  if (existingPh && existingPh.isConnected) return null;
226
219
 
@@ -233,7 +226,6 @@
233
226
  attachFillObserver(wrap, id);
234
227
  return wrap;
235
228
  } finally {
236
- // Libérer le lock après 100ms (le temps que le DOM soit stable)
237
229
  setTimeout(() => insertingIds.delete(id), 50);
238
230
  }
239
231
  }
@@ -244,8 +236,6 @@
244
236
  }
245
237
 
246
238
  function patchShowAds() {
247
- // Minimal safety net: batch showAds can be triggered by other scripts; split into individual calls.
248
- // Also ensures the patch is applied even if Ezoic loads after our script.
249
239
  const applyPatch = () => {
250
240
  try {
251
241
  window.ezstandalone = window.ezstandalone || {}, ez = window.ezstandalone;
@@ -272,7 +262,6 @@
272
262
  };
273
263
 
274
264
  applyPatch();
275
- // Si Ezoic n'est pas encore chargé, appliquer le patch via sa cmd queue
276
265
  if (!window.__nodebbEzoicPatched) {
277
266
  try {
278
267
  window.ezstandalone = window.ezstandalone || {};
@@ -285,7 +274,6 @@
285
274
  function markFilled(wrap) {
286
275
  try {
287
276
  if (!wrap) return;
288
- // Disconnect the fill observer first (no need to remove+re-add the attribute)
289
277
  if (wrap.__ezoicFillObs) { wrap.__ezoicFillObs.disconnect(); wrap.__ezoicFillObs = null; }
290
278
  wrap.setAttribute('data-ezoic-filled', '1');
291
279
  } catch (e) {}
@@ -313,7 +301,6 @@
313
301
  }
314
302
  });
315
303
  obs.observe(ph, { childList: true, subtree: true });
316
- // Keep a weak reference on the wrapper so we can disconnect on recycle/remove
317
304
  wrap.__ezoicFillObs = obs;
318
305
  } catch (e) {}
319
306
  }
@@ -333,15 +320,12 @@
333
320
  return filled;
334
321
  }
335
322
 
336
- // Appeler showAds() en batch selon recommandations Ezoic
337
- // Au lieu de showAds(id1), showAds(id2)... faire showAds(id1, id2, id3...)
338
323
  let batchShowAdsTimer = null;
339
324
  const pendingShowAdsIds = new Set();
340
325
 
341
326
  function scheduleShowAdsBatch(id) {
342
327
  if (!id) return;
343
328
 
344
- // CRITIQUE: Si cet ID a déjà été défini (sessionDefinedIds), le détruire d'abord
345
329
  if (sessionDefinedIds.has(id)) {
346
330
  try {
347
331
  destroyPlaceholderIds([id]);
@@ -356,7 +340,6 @@
356
340
  // Ajouter à la batch
357
341
  pendingShowAdsIds.add(id);
358
342
 
359
- // Debounce: attendre 100ms pour collecter tous les IDs
360
343
  clearTimeout(batchShowAdsTimer);
361
344
  batchShowAdsTimer = setTimeout(() => {
362
345
  if (pendingShowAdsIds.size === 0) return;
@@ -406,19 +389,15 @@
406
389
  const startPageKey = state.pageKey;
407
390
  let attempts = 0;
408
391
  (function waitForPh() {
409
- // Abort if the user navigated away since this showAds was scheduled
410
392
  if (state.pageKey !== startPageKey) return;
411
- // Abort if another concurrent call is already handling this id
412
393
  if (state.pendingById.has(id)) return;
413
394
 
414
395
  attempts += 1;
415
396
  const el = document.getElementById(phId);
416
397
  if (el && el.isConnected) {
417
- // CRITIQUE: Vérifier que le placeholder est VISIBLE
418
398
 
419
399
  // Si on arrive ici, soit visible, soit timeout
420
400
 
421
- // Si doCall() réussit, Ezoic est chargé et showAds a été appelé → sortir
422
401
  if (doCall()) {
423
402
  state.pendingById.delete(id);
424
403
  return;
@@ -484,7 +463,6 @@
484
463
  const el = items[afterPos - 1];
485
464
  if (!el || !el.isConnected) continue;
486
465
 
487
- // Prevent adjacent ads (DOM-based, robust against virtualization)
488
466
  if (isAdjacentAd(el) || isPrevAd(el)) {
489
467
  continue;
490
468
  }
@@ -501,17 +479,14 @@
501
479
 
502
480
  let wrap = null;
503
481
  if (pick.recycled && pick.recycled.wrap) {
504
- // Only destroy if Ezoic has actually defined this placeholder before
505
482
  if (sessionDefinedIds.has(id)) {
506
483
  destroyPlaceholderIds([id]);
507
484
  }
508
- // Remove the old wrapper entirely, then create a fresh wrapper at the new position (same id)
509
485
  const oldWrap = pick.recycled.wrap;
510
486
  try { if (oldWrap && oldWrap.__ezoicFillObs) { oldWrap.__ezoicFillObs.disconnect(); } } catch (e) {}
511
487
  try { oldWrap && oldWrap.remove(); } catch (e) {}
512
488
  wrap = insertAfter(el, id, kindClass, afterPos);
513
489
  if (!wrap) continue;
514
- // Attendre que le wrapper soit dans le DOM puis appeler showAds
515
490
  setTimeout(() => {
516
491
  callShowAdsWhenReady(id);
517
492
  }, 50);
@@ -525,12 +500,10 @@
525
500
  }
526
501
 
527
502
  liveArr.push({ id, wrap });
528
- // If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
529
503
  if (wrap && (
530
504
  (wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))
531
505
  )) {
532
506
  try { wrap.remove(); } catch (e) {}
533
- // Put id back if it was newly consumed (not recycled)
534
507
  if (!(pick.recycled && pick.recycled.wrap)) {
535
508
  try { kindPool.unshift(id); } catch (e) {}
536
509
  usedSet.delete(id);
@@ -547,7 +520,6 @@
547
520
  for (let i = 0; i < ads.length; i++) {
548
521
  const ad = ads[i], prev = ad.previousElementSibling;
549
522
  if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) {
550
- // Supprimer le wrapper adjacent au lieu de le cacher
551
523
  try {
552
524
  const ph = ad.querySelector && ad.querySelector(`[id^="${PLACEHOLDER_PREFIX}"]`);
553
525
  if (ph) {
@@ -568,8 +540,6 @@
568
540
  function cleanup() {
569
541
  destroyUsedPlaceholders();
570
542
 
571
- // CRITIQUE: Supprimer TOUS les wrappers .ezoic-ad du DOM
572
- // Sinon ils restent et deviennent "unused" sur la nouvelle page
573
543
  document.querySelectorAll('.ezoic-ad').forEach(el => {
574
544
  try { el.remove(); } catch (e) {}
575
545
  });
@@ -585,23 +555,14 @@
585
555
  state.usedPosts.clear();
586
556
  state.usedCategories.clear();
587
557
  state.lastShowById.clear();
588
- // CRITIQUE: Vider pendingById pour annuler tous les showAds en cours
589
- // Sinon Ezoic essaie d'accéder aux placeholders pendant que NodeBB vide le DOM
590
558
  state.pendingById.clear();
591
559
  state.definedIds.clear();
592
560
 
593
- // NE PAS supprimer les wrappers Ezoic ici - ils seront supprimés naturellement
594
- // quand NodeBB vide le DOM lors de la navigation ajaxify
595
- // Les supprimer manuellement cause des problèmes avec l'état interne d'Ezoic
596
-
597
- // CRITIQUE: Annuler TOUS les timeouts en cours pour éviter que les anciens
598
- // showAds() continuent à s'exécuter après la navigation
599
561
  state.activeTimeouts.forEach(id => {
600
562
  try { clearTimeout(id); } catch (e) {}
601
563
  });
602
564
  state.activeTimeouts.clear();
603
565
 
604
- // Vider aussi pendingById pour annuler les showAds en attente
605
566
  state.pendingById.clear();
606
567
 
607
568
  if (state.obs) { state.obs.disconnect(); state.obs = null; }
@@ -618,7 +579,6 @@
618
579
  }
619
580
 
620
581
  async function runCore() {
621
- // Attendre que canInsert soit true (protection race condition navigation)
622
582
  if (!state.canShowAds) {
623
583
  return;
624
584
  }
@@ -661,7 +621,6 @@
661
621
 
662
622
  enforceNoAdjacentAds();
663
623
 
664
- // If nothing inserted and list isn't in DOM yet (first click), retry a bit
665
624
  let count = 0;
666
625
  if (kind === 'topic') count = getPostContainers().length;
667
626
  else if (kind === 'categoryTopics') count = getTopicItems().length;
@@ -673,12 +632,9 @@
673
632
  }
674
633
 
675
634
  if (inserted >= MAX_INSERTS_PER_RUN) {
676
- // Plus d'insertions possibles ce cycle, continuer immédiatement
677
635
  setTimeout(arguments[0], 50);
678
636
  } else if (inserted === 0 && count > 0) {
679
637
  // Pool épuisé ou recyclage pas encore disponible.
680
- // Réessayer jusqu'à 8 fois (toutes les 400ms) pour laisser aux anciens wrappers
681
- // le temps de défiler hors écran et devenir recyclables.
682
638
  if (state.poolWaitAttempts < 8) {
683
639
  state.poolWaitAttempts += 1;
684
640
  setTimeout(arguments[0], 50);
@@ -712,15 +668,11 @@
712
668
  state.pageKey = getPageKey();
713
669
  ensureObserver();
714
670
 
715
- // CRITIQUE: Attendre 300ms avant de permettre l'insertion de nouveaux placeholders
716
- // pour laisser les anciens showAds() (en cours) se terminer ou échouer proprement
717
- // Sinon race condition: NodeBB vide le DOM pendant que Ezoic essaie d'accéder aux placeholders
718
671
  state.canShowAds = true;
719
672
  });
720
673
 
721
674
  $(window).on('action:category.loaded.ezoicInfinite', () => {
722
675
  ensureObserver();
723
- // category.loaded = infinite scroll, Ezoic déjà chargé normalement
724
676
  waitForContentThenRun();
725
677
  });
726
678
  $(window).on('action:topics.loaded.ezoicInfinite', () => {
@@ -750,7 +702,6 @@
750
702
  window.requestAnimationFrame(() => {
751
703
  ticking = false;
752
704
  enforceNoAdjacentAds();
753
- // Debounce scheduleRun - une fois toutes les 2 secondes max au scroll
754
705
  const now = Date.now();
755
706
  if (!state.lastScrollRun || now - state.lastScrollRun > 2000) {
756
707
  state.lastScrollRun = now;
@@ -760,7 +711,6 @@
760
711
  }, { passive: true });
761
712
  }
762
713
 
763
- // Fonction qui attend que la page ait assez de contenu avant d'insérer les pubs
764
714
  function waitForContentThenRun() {
765
715
  const MIN_WORDS = 250;
766
716
  let attempts = 0;
@@ -791,7 +741,6 @@
791
741
  })();
792
742
  }
793
743
 
794
- // Fonction qui attend que Ezoic soit vraiment chargé
795
744
  function waitForEzoicThenRun() {
796
745
  let attempts = 0;
797
746
  const maxAttempts = 50; // 50 × 200ms = 10s max
package/public/style.css CHANGED
@@ -1,3 +1,18 @@
1
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
+ }