nodebb-plugin-ezoic-infinite 1.8.93 → 1.8.95

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/library.js CHANGED
@@ -93,47 +93,37 @@ async function isUserExcluded(uid, excludedGroups) {
93
93
 
94
94
  _excludeCache.set(key, { value, at: Date.now() });
95
95
  if (_excludeCache.size > 1000) {
96
- const now = Date.now();
97
96
  let toDel = 100;
98
- // Supprimer en priorité les entrées expirées, puis les plus anciennes
97
+ const t = Date.now();
99
98
  for (const [k, v] of _excludeCache) {
100
- if (now - v.at >= EXCLUDE_TTL) { _excludeCache.delete(k); if (--toDel <= 0) break; }
99
+ if (!toDel) break;
100
+ if (t - v.at >= EXCLUDE_TTL) { _excludeCache.delete(k); toDel--; }
101
101
  }
102
- if (toDel > 0) {
103
- for (const k of _excludeCache.keys()) {
104
- _excludeCache.delete(k);
105
- if (--toDel <= 0) break;
106
- }
102
+ for (const k of _excludeCache.keys()) {
103
+ if (!toDel) break;
104
+ _excludeCache.delete(k); toDel--;
107
105
  }
108
106
  }
109
107
 
110
108
  return value;
111
109
  }
112
110
 
111
+ let _inlineCfgNormal = null;
112
+ let _inlineCfgExcluded = null;
113
+
113
114
  function clearCaches() {
114
115
  _settingsCache = null;
115
116
  _settingsCacheAt = 0;
116
117
  _excludeCache.clear();
118
+ _inlineCfgNormal = null;
119
+ _inlineCfgExcluded = null;
117
120
  }
118
121
 
119
122
  // ── Client config ────────────────────────────────────────────────────────────
120
123
 
121
124
  function buildClientConfig(settings, excluded) {
122
- return {
123
- excluded,
124
- enableBetweenAds: settings.enableBetweenAds,
125
- showFirstTopicAd: settings.showFirstTopicAd,
126
- placeholderIds: settings.placeholderIds,
127
- intervalPosts: settings.intervalPosts,
128
- enableCategoryAds: settings.enableCategoryAds,
129
- showFirstCategoryAd: settings.showFirstCategoryAd,
130
- categoryPlaceholderIds: settings.categoryPlaceholderIds,
131
- intervalCategories: settings.intervalCategories,
132
- enableMessageAds: settings.enableMessageAds,
133
- showFirstMessageAd: settings.showFirstMessageAd,
134
- messagePlaceholderIds: settings.messagePlaceholderIds,
135
- messageIntervalPosts: settings.messageIntervalPosts,
136
- };
125
+ const { excludedGroups, ...rest } = settings;
126
+ return { excluded, ...rest };
137
127
  }
138
128
 
139
129
  function serializeInlineConfig(cfg) {
@@ -191,8 +181,12 @@ plugin.injectEzoicHead = async (data) => {
191
181
  const uid = data.req?.uid ?? 0;
192
182
  const excluded = await isUserExcluded(uid, settings.excludedGroups);
193
183
 
184
+ if (!_inlineCfgNormal) {
185
+ _inlineCfgNormal = serializeInlineConfig(buildClientConfig(settings, false));
186
+ _inlineCfgExcluded = serializeInlineConfig(buildClientConfig(settings, true));
187
+ }
188
+
194
189
  if (excluded) {
195
- const cfg = buildClientConfig(settings, true);
196
190
  const stub = '<script data-cfasync="false">'
197
191
  + 'window._ezaq=window._ezaq||{};'
198
192
  + 'window.ezstandalone=window.ezstandalone||{};'
@@ -200,14 +194,13 @@ plugin.injectEzoicHead = async (data) => {
200
194
  + '</script>';
201
195
  data.templateData.customHTML =
202
196
  stub + '\n' +
203
- serializeInlineConfig(cfg) +
197
+ _inlineCfgExcluded +
204
198
  (data.templateData.customHTML || '');
205
199
  } else {
206
- const cfg = buildClientConfig(settings, false);
207
200
  data.templateData.customHTML =
208
201
  HEAD_PRECONNECTS + '\n' +
209
202
  EZOIC_SCRIPTS + '\n' +
210
- serializeInlineConfig(cfg) +
203
+ _inlineCfgNormal +
211
204
  (data.templateData.customHTML || '');
212
205
  }
213
206
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.8.93",
3
+ "version": "1.8.95",
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,11 +1,12 @@
1
1
  /**
2
- * NodeBB Ezoic Infinite Ads — client.js v2.5.1
2
+ * NodeBB Ezoic Infinite Ads — client.js v2.5.2
3
3
  *
4
4
  * Architecture: proven v50 core + targeted improvements.
5
5
  * Ezoic API: showAds() + destroyPlaceholders() per official docs.
6
6
  * Key features: O(1) recycle via wrapsByClass, MutationObserver fill detect,
7
7
  * conservative empty check, aria-hidden + TCF protection, retry boot for
8
8
  * Cloudflare/async timing.
9
+ * v2.5.2: removed redundant warmNetwork() — preconnects are server-injected.
9
10
  */
10
11
  (function nbbEzoicInfinite() {
11
12
  'use strict';
@@ -177,8 +178,8 @@
177
178
  return out;
178
179
  }
179
180
 
180
- const getTopics = () => Array.from(document.querySelectorAll(SEL.topic));
181
- const getCategories = () => Array.from(document.querySelectorAll(SEL.category));
181
+ const getTopics = () => document.querySelectorAll(SEL.topic);
182
+ const getCategories = () => document.querySelectorAll(SEL.category);
182
183
 
183
184
  // ── Anchor keys & wrap registry ────────────────────────────────────────────
184
185
 
@@ -268,9 +269,8 @@
268
269
  }
269
270
 
270
271
  function addWrapTimer(id, ms, fn) {
271
- const timers = S.wrapTimers.get(id) || [];
272
- timers.push(setTimeout(() => { try { fn(); } catch (_) {} }, ms));
273
- S.wrapTimers.set(id, timers);
272
+ if (!S.wrapTimers.has(id)) S.wrapTimers.set(id, []);
273
+ S.wrapTimers.get(id).push(setTimeout(() => { try { fn(); } catch (_) {} }, ms));
274
274
  }
275
275
 
276
276
  function scheduleUncollapseChecks(wrap, id) {
@@ -434,12 +434,13 @@
434
434
 
435
435
  // ── Injection ──────────────────────────────────────────────────────────────
436
436
 
437
- function ordinal(klass, el) {
437
+ function ordinal(klass, el, hint) {
438
438
  const attr = KIND[klass]?.ordinalAttr;
439
439
  if (attr) {
440
440
  const v = el.getAttribute(attr);
441
441
  if (v != null && v !== '' && !isNaN(v)) return parseInt(v, 10);
442
442
  }
443
+ if (hint !== undefined) return hint;
443
444
  const fullSel = KIND[klass]?.sel ?? '';
444
445
  let i = 0;
445
446
  for (const s of el.parentElement?.children ?? []) {
@@ -452,10 +453,11 @@
452
453
  function injectBetween(klass, items, interval, showFirst, poolKey) {
453
454
  if (!items.length) return 0;
454
455
  let inserted = 0;
456
+ let pos = 0;
455
457
  for (const el of items) {
456
458
  if (inserted >= MAX_INSERTS_RUN) break;
457
459
  if (!el?.isConnected) continue;
458
- const ord = ordinal(klass, el);
460
+ const ord = ordinal(klass, el, pos++);
459
461
  if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
460
462
  if (adjacentWrap(el)) continue;
461
463
  const key = anchorKey(klass, el);
@@ -608,7 +610,6 @@
608
610
 
609
611
  async function runCore() {
610
612
  if (isBlocked()) return 0;
611
- patchShowAds();
612
613
 
613
614
  const t = now();
614
615
  if (t - _lastGc > 30_000) { _lastGc = t; gcDisconnectedWraps(); }
@@ -798,30 +799,6 @@
798
799
  try { window.__nbbAriaObs.observe(document.body, { attributes: true, attributeFilter: ['aria-hidden'] }); } catch (_) {}
799
800
  }
800
801
 
801
- // ── Network warmup ─────────────────────────────────────────────────────────
802
-
803
- let _warmed = false;
804
- function warmNetwork() {
805
- if (_warmed) return;
806
- _warmed = true;
807
- const head = document.head;
808
- if (!head) return;
809
- for (const [rel, href, cors] of [
810
- ['preconnect', 'https://g.ezoic.net', true ],
811
- ['preconnect', 'https://go.ezoic.net', true ],
812
- ['preconnect', 'https://securepubads.g.doubleclick.net', true ],
813
- ['preconnect', 'https://pagead2.googlesyndication.com', true ],
814
- ['dns-prefetch', 'https://g.ezoic.net', false],
815
- ['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
816
- ]) {
817
- if (head.querySelector(`link[rel="${rel}"][href="${href}"]`)) continue;
818
- const l = document.createElement('link');
819
- l.rel = rel; l.href = href;
820
- if (cors) l.crossOrigin = 'anonymous';
821
- head.appendChild(l);
822
- }
823
- }
824
-
825
802
  // ── Bindings ───────────────────────────────────────────────────────────────
826
803
 
827
804
  function bindNodeBB() {
@@ -832,7 +809,7 @@
832
809
  $(window).on('action:ajaxify.end.nbbEzoic', () => {
833
810
  S.pageKey = pageKey(); S.kind = null; S.blockedUntil = 0;
834
811
  ensureTcfLocator(); protectAriaHidden();
835
- warmNetwork(); patchShowAds(); getIO(); ensureDomObserver();
812
+ patchShowAds(); getIO(); ensureDomObserver();
836
813
  requestBurst();
837
814
  });
838
815
  // action:ajaxify.contentLoaded et action:category.loaded ne passent pas par hooks → jQuery uniquement
@@ -863,7 +840,6 @@
863
840
  S.pageKey = pageKey();
864
841
  ensureTcfLocator();
865
842
  protectAriaHidden();
866
- warmNetwork();
867
843
  patchShowAds();
868
844
  getIO();
869
845
  ensureDomObserver();
@@ -886,20 +862,18 @@
886
862
  // in ez-standalone.js → onStandaloneLoadEvent crash). After ~6s, remove and reload
887
863
  // sa.min.js; Ezoic's partial first-run state may let the second run succeed.
888
864
  // Once recovered, re-enqueue all mounted placeholders so showAds() fires.
889
- let _retries = 0;
890
- let _scriptReloaded = false;
891
- let _postReloadShown = false;
865
+ const RETRY = { count: 0, scriptReloaded: false, postReloadShown: false };
892
866
  function retryBoot() {
893
- if (_retries >= 12) return;
894
- _retries++;
867
+ if (RETRY.count >= 12) return;
868
+ RETRY.count++;
895
869
  patchShowAds();
896
870
 
897
871
  const ez = window.ezstandalone;
898
872
  const status = ez?.loadingStatus;
899
873
 
900
874
  // After reload: once Ezoic reaches 'complete', re-call showAds for all mounted placeholders
901
- if (_scriptReloaded && !_postReloadShown && status === 'complete') {
902
- _postReloadShown = true;
875
+ if (RETRY.scriptReloaded && !RETRY.postReloadShown && status === 'complete') {
876
+ RETRY.postReloadShown = true;
903
877
  for (const id of S.mountedIds) { try { enqueueShow(id); } catch (_) {} }
904
878
  return;
905
879
  }
@@ -907,19 +881,19 @@
907
881
  // Normal exit: placeholders mounted, no reload triggered (healthy load).
908
882
  // Exception: if Ezoic status is stuck (not absent and not 'complete'), continue
909
883
  // so crash detection below can trigger the sa.min.js reload.
910
- if (S.mountedIds.size > 0 && !_scriptReloaded && (!status || status === 'complete')) return;
884
+ if (S.mountedIds.size > 0 && !RETRY.scriptReloaded && (!status || status === 'complete')) return;
911
885
  // Exit once reload is done and re-show has been triggered
912
- if (_scriptReloaded && _postReloadShown) return;
886
+ if (RETRY.scriptReloaded && RETRY.postReloadShown) return;
913
887
 
914
- if (!_scriptReloaded) {
888
+ if (!RETRY.scriptReloaded) {
915
889
  // Case 1: sa.min.js never injected (Rocket Loader stripped it, etc.)
916
- const neverInjected = !status && !document.querySelector('script[src*="sa.min.js"]') && _retries <= 3;
890
+ const neverInjected = !status && !document.querySelector('script[src*="sa.min.js"]') && RETRY.count <= 3;
917
891
  // Case 2: cold-start crash — loadingStatus stuck before 'complete' after enough wait.
918
892
  // Do NOT reload when status==='complete' (post-defineScript-failure would re-trigger).
919
893
  const anyFilled = !!document.querySelector(`.${WRAP_CLASS} ${FILL_SEL}`);
920
- const crashed = _retries >= 8 && !anyFilled && status && status !== 'complete';
894
+ const crashed = RETRY.count >= 8 && !anyFilled && status && status !== 'complete';
921
895
  if (neverInjected || crashed) {
922
- _scriptReloaded = true;
896
+ RETRY.scriptReloaded = true;
923
897
  try {
924
898
  if (crashed) {
925
899
  // Remove the broken script before re-adding to avoid a duplicate
@@ -942,7 +916,7 @@
942
916
  S.lastBurstTs = now() - TIMING.BURST_COOLDOWN_MS;
943
917
  requestBurst();
944
918
  }
945
- setTimeout(retryBoot, _retries <= 4 ? 300 : 1000);
919
+ setTimeout(retryBoot, RETRY.count <= 4 ? 300 : 1000);
946
920
  }
947
921
  setTimeout(retryBoot, 250);
948
922