nodebb-plugin-ezoic-infinite 1.7.63 → 1.7.65

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 +57 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.7.63",
3
+ "version": "1.7.65",
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * NodeBB Ezoic Infinite Ads — client.js v62
2
+ * NodeBB Ezoic Infinite Ads — client.js v69
3
3
  *
4
4
  * Historique
5
5
  * ──────────
@@ -7,28 +7,19 @@
7
7
  * v20 Table KIND. IO fixe. Fix TCF locator.
8
8
  * v25 Fix scroll-up / virtualisation NodeBB.
9
9
  * v28 Wraps persistants pendant la session.
10
- * v35 S.poolsReady. muteConsole élargi.
11
10
  * v36 S.wrapByKey Map O(1). MutationObserver optimisé.
12
- * v38 ez.refresh() supprimé. Pool épuisé → break propre.
13
- * v40 Recyclage destroyPlaceholders+define+displayMore (délais 300ms).
14
- * v43 Seuil recyclage -1vh + unobserve avant déplacement.
11
+ * v38 Pool épuisé → break propre (ez.refresh supprimé).
15
12
  * v49 Fix normalizeExcludedGroups (JSON.parse tableau NodeBB).
16
- * v51 fetchConfig backoff 10s. IO recrée au resize. tcfObs dans S.
17
- * v52 pruneOrphansBetween supprimé (NodeBB virtualise aussi les topics).
18
- * v53 S.recycling garde double-recyclage. pickId early-exit. cleanup complet.
19
- * v54 ensureTcfLocator rappelé à chaque ajaxify.end.
20
- * v56 scheduleEmptyCheck / is-empty supprimés (collapse prématuré).
21
- * v62 is-empty réintroduit : collapse 60s après insertion du wrap (pas après
22
- * showAds) si isFilled est toujours false. Évite les trous permanents.
23
- * v61 recycleAndMove : ne pas recycler un wrap rempli depuis moins de 30s.
24
- * Empêche qu'une pub qui vient de charger soit déplacée immédiatement.
25
- * v59 CSS : min-height 90px sur ezoic-ad-between (anti-CLS AMP ads).
26
- * v58 tcfObs survit aux navigations : ne plus déconnecter dans cleanup().
27
- * L'iframe __tcfapiLocator doit exister en permanence pour le CMP —
28
- * la fenêtre entre cleanup() et ajaxify.end causait des erreurs
29
- * "Cannot read properties of null (postMessage)" et disparition des pubs.
30
- * v57 Nettoyage prod : A_SHOWN/A_CREATED supprimés, normBool Set O(1),
31
- * muteConsole étend aux erreurs CMP getTCData, code nettoyé.
13
+ * v51 fetchConfig backoff 10s. IO recrée au resize.
14
+ * v52 pruneOrphansBetween supprimé (NodeBB virtualise les topics).
15
+ * v56 scheduleEmptyCheck supprimé (collapse prématuré).
16
+ * v58 tcfObs survit aux navigations (iframe CMP permanente).
17
+ * v62 is-empty réintroduit, déclenché à l'insertion du wrap.
18
+ * v64 recycleAndMove supprimé (slots restaient en 'unused' après destroy).
19
+ * v67 define(allIds) au boot Ezoic enregistre tous les slots en interne.
20
+ * v68 setIsSinglePageApplication(true) + newPage() à chaque navigation.
21
+ * v69 Nettoyage prod final : S.recycling orphelin supprimé, helpers Ezoic
22
+ * SPA extraits en fonctions dédiées, commentaires legacy retirés.
32
23
  */
33
24
  (function nbbEzoicInfinite() {
34
25
  'use strict';
@@ -40,11 +31,11 @@
40
31
  const A_ANCHOR = 'data-ezoic-anchor'; // "kindClass:stableId"
41
32
  const A_WRAPID = 'data-ezoic-wrapid'; // id Ezoic
42
33
 
43
- const EMPTY_CHECK_MS = 5_000; // 5s après showAds séquence define+display+show prend ~1.4s
44
- const MAX_INSERTS_RUN = 6; // insertions max par appel runCore
45
- const MAX_INFLIGHT = 4; // showAds() simultanés max
46
- const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
47
- const BURST_COOLDOWN_MS = 200; // délai min entre deux bursts
34
+ const EMPTY_CHECK_MS = 5_000; // collapse wrap vide 5s après insertion
35
+ const MAX_INSERTS_RUN = 6; // insertions max par appel runCore
36
+ const MAX_INFLIGHT = 4; // showAds() simultanés max
37
+ const SHOW_THROTTLE_MS = 900; // anti-spam showAds() par id
38
+ const BURST_COOLDOWN_MS = 200; // délai min entre deux bursts
48
39
 
49
40
  const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
50
41
  const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
@@ -60,7 +51,7 @@
60
51
  * sel sélecteur CSS des éléments cibles
61
52
  * baseTag préfixe tag pour querySelector d'ancre
62
53
  * anchorAttr attribut DOM stable → clé unique du wrap
63
- * ordinalAttr attribut 0-based pour le calcul de l'intervalle (null = fallback positionnel)
54
+ * ordinalAttr attribut 0-based pour calcul de l'intervalle (null = fallback positionnel)
64
55
  */
65
56
  const KIND = {
66
57
  'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
@@ -80,13 +71,12 @@
80
71
  lastShow: new Map(),
81
72
  io: null,
82
73
  domObs: null,
83
- tcfObs: null,
74
+ tcfObs: null, // survit aux navigations — ne jamais déconnecter
84
75
  mutGuard: 0,
85
76
  inflight: 0,
86
77
  pending: [],
87
78
  pendingSet: new Set(),
88
79
  wrapByKey: new Map(), // anchorKey → wrap DOM node
89
- recycling: new Set(), // ids en cours de séquence destroy→define→displayMore
90
80
  runQueued: false,
91
81
  burstActive: false,
92
82
  burstDeadline: 0,
@@ -138,16 +128,26 @@
138
128
  S.pools.posts = parseIds(cfg.messagePlaceholderIds);
139
129
  S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
140
130
  S.poolsReady = true;
141
- // Déclarer tous les ids du pool une seule fois — Ezoic les enregistre en interne
131
+ // Déclarer tous les ids en une seule fois — requis pour que showAds()
132
+ // fonctionne sur des slots insérés dynamiquement (infinite scroll).
142
133
  const allIds = [...S.pools.topics, ...S.pools.posts, ...S.pools.categories];
143
- if (allIds.length) {
134
+ if (allIds.length) ezCmd(ez => ez.define(allIds));
135
+ }
136
+
137
+ // ── Helpers Ezoic ──────────────────────────────────────────────────────────
138
+
139
+ function ezCmd(fn) {
140
+ try {
144
141
  window.ezstandalone = window.ezstandalone || {};
145
142
  const ez = window.ezstandalone;
146
- const doDef = () => { try { ez.define(allIds); } catch (_) {} };
147
- typeof ez.cmd?.push === 'function' ? ez.cmd.push(doDef) : doDef();
148
- }
143
+ const exec = () => { try { fn(ez); } catch (_) {} };
144
+ typeof ez.cmd?.push === 'function' ? ez.cmd.push(exec) : exec();
145
+ } catch (_) {}
149
146
  }
150
147
 
148
+ function notifyEzoicSpa() { ezCmd(ez => ez.setIsSinglePageApplication?.(true)); }
149
+ function notifyEzoicNewPage() { ezCmd(ez => ez.newPage?.()); }
150
+
151
151
  // ── Page identity ──────────────────────────────────────────────────────────
152
152
 
153
153
  function pageKey() {
@@ -355,28 +355,27 @@
355
355
  if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
356
356
  }
357
357
 
358
- function enqueueShow(id) {
359
- if (!id || isBlocked()) return;
360
- if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) return;
361
- if (S.inflight >= MAX_INFLIGHT) {
362
- if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
363
- return;
364
- }
365
- startShow(id);
366
- }
367
-
368
358
  function scheduleEmptyCheck(id) {
369
359
  setTimeout(() => {
370
360
  try {
371
361
  const ph = document.getElementById(`${PH_PREFIX}${id}`);
372
362
  const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
373
363
  if (!wrap || !ph?.isConnected) return;
374
- // Collapse uniquement si vraiment vide après 60s
375
364
  if (!isFilled(wrap)) wrap.classList.add('is-empty');
376
365
  } catch (_) {}
377
366
  }, EMPTY_CHECK_MS);
378
367
  }
379
368
 
369
+ function enqueueShow(id) {
370
+ if (!id || isBlocked()) return;
371
+ if (ts() - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) return;
372
+ if (S.inflight >= MAX_INFLIGHT) {
373
+ if (!S.pendingSet.has(id)) { S.pending.push(id); S.pendingSet.add(id); }
374
+ return;
375
+ }
376
+ startShow(id);
377
+ }
378
+
380
379
  function drainQueue() {
381
380
  if (isBlocked()) return;
382
381
  while (S.inflight < MAX_INFLIGHT && S.pending.length) {
@@ -405,16 +404,11 @@
405
404
  const t = ts();
406
405
  if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
407
406
  S.lastShow.set(id, t);
408
- // Marquer le wrap avec le timestamp de fill pour bloquer le recyclage
409
- // Si la pub charge après is-empty, retirer le collapse
410
- try { document.getElementById(`${PH_PREFIX}${id}`)?.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
411
- window.ezstandalone = window.ezstandalone || {};
412
- const ez = window.ezstandalone;
413
- const doShow = () => {
407
+ try { ph.closest?.(`.${WRAP_CLASS}`)?.classList.remove('is-empty'); } catch (_) {}
408
+ ezCmd(ez => {
414
409
  try { ez.showAds(id); } catch (_) {}
415
410
  setTimeout(() => { clearTimeout(timer); release(); }, 700);
416
- };
417
- Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
411
+ });
418
412
  } catch (_) { clearTimeout(timer); release(); }
419
413
  });
420
414
  }
@@ -515,11 +509,11 @@
515
509
  function cleanup() {
516
510
  blockedUntil = ts() + 1500;
517
511
  mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
518
- S.cfg = null;
512
+ S.cfg = null;
519
513
  _cfgErrorUntil = 0;
520
- S.poolsReady = false;
521
- S.pools = { topics: [], posts: [], categories: [] };
522
- S.cursors = { topics: 0, posts: 0, categories: 0 };
514
+ S.poolsReady = false;
515
+ S.pools = { topics: [], posts: [], categories: [] };
516
+ S.cursors = { topics: 0, posts: 0, categories: 0 };
523
517
  S.mountedIds.clear();
524
518
  S.lastShow.clear();
525
519
  S.wrapByKey.clear();
@@ -530,8 +524,8 @@
530
524
  S.runQueued = false;
531
525
  if (S.domObs) { S.domObs.disconnect(); S.domObs = null; }
532
526
  // tcfObs intentionnellement préservé : l'iframe __tcfapiLocator doit
533
- // exister en permanence la déconnecter pendant la navigation cause
534
- // des erreurs CMP postMessage et la disparition des pubs.
527
+ // rester en vie pendant toute la session la déconnecter entre deux
528
+ // navigations cause des erreurs CMP postMessage.
535
529
  }
536
530
 
537
531
  // ── MutationObserver DOM ───────────────────────────────────────────────────
@@ -627,7 +621,9 @@
627
621
  $(window).on('action:ajaxify.end.nbbEzoic', () => {
628
622
  S.pageKey = pageKey();
629
623
  blockedUntil = 0;
630
- muteConsole(); ensureTcfLocator();
624
+ muteConsole();
625
+ ensureTcfLocator();
626
+ notifyEzoicNewPage();
631
627
  patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
632
628
  });
633
629
  const burstEvts = [
@@ -666,6 +662,7 @@
666
662
  muteConsole();
667
663
  ensureTcfLocator();
668
664
  warmNetwork();
665
+ notifyEzoicSpa(); // NodeBB est une SPA — Ezoic ajuste son cycle interne
669
666
  patchShowAds();
670
667
  getIO();
671
668
  ensureDomObserver();