nodebb-plugin-ezoic-infinite 1.8.0 → 1.8.2
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 +1 -1
- package/public/client.js +147 -32
- package/public/style.css +0 -6
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -77,12 +77,18 @@
|
|
|
77
77
|
const A_CREATED = 'data-ezoic-created'; // timestamp création ms
|
|
78
78
|
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
79
79
|
|
|
80
|
+
// Tunables (stables en prod)
|
|
80
81
|
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
81
|
-
const MAX_INSERTS_RUN =
|
|
82
|
-
const MAX_INFLIGHT = 4; // max showAds() simultanés
|
|
82
|
+
const MAX_INSERTS_RUN = 10; // plus réactif si NodeBB injecte en rafale
|
|
83
|
+
const MAX_INFLIGHT = 4; // max showAds() simultanés (garde-fou)
|
|
83
84
|
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
84
|
-
const SHOW_THROTTLE_MS =
|
|
85
|
-
const
|
|
85
|
+
const SHOW_THROTTLE_MS = 600; // anti-spam showAds() par id (plus réactif)
|
|
86
|
+
const SHOW_RELEASE_MS = 700; // relâche inflight après showAds() batché
|
|
87
|
+
const SHOW_FAILSAFE_MS = 7000; // relâche forcée si stack pub lente
|
|
88
|
+
const BATCH_FLUSH_MS = 40; // micro-buffer pour regrouper les ids proches
|
|
89
|
+
const MAX_DESTROY_BATCH = 4; // ids max par destroyPlaceholders(...ids)
|
|
90
|
+
const DESTROY_FLUSH_MS = 30; // micro-buffer destroy pour lisser les rafales
|
|
91
|
+
const BURST_COOLDOWN_MS = 120; // délai min entre deux déclenchements de burst
|
|
86
92
|
|
|
87
93
|
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
88
94
|
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
@@ -126,6 +132,11 @@
|
|
|
126
132
|
inflight: 0, // showAds() en cours
|
|
127
133
|
pending: [], // ids en attente de slot inflight
|
|
128
134
|
pendingSet: new Set(),
|
|
135
|
+
showBatchTimer: 0,
|
|
136
|
+
destroyBatchTimer: 0,
|
|
137
|
+
destroyPending: [],
|
|
138
|
+
destroyPendingSet: new Set(),
|
|
139
|
+
sweepQueued: false,
|
|
129
140
|
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
130
141
|
ezActiveIds: new Set(), // ids déjà passés à showAds/displayMore
|
|
131
142
|
scrollDir: 1, // 1=bas, -1=haut
|
|
@@ -147,6 +158,33 @@
|
|
|
147
158
|
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
148
159
|
const isFilled = n => !!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
149
160
|
|
|
161
|
+
function phEl(id) {
|
|
162
|
+
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function hasSinglePlaceholder(id) {
|
|
166
|
+
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function canShowPlaceholderId(id, now = ts()) {
|
|
170
|
+
const n = parseInt(id, 10);
|
|
171
|
+
if (!Number.isFinite(n) || n <= 0) return false;
|
|
172
|
+
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
173
|
+
const ph = phEl(n);
|
|
174
|
+
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
175
|
+
if (!hasSinglePlaceholder(n)) return false;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function queueSweepDeadWraps() {
|
|
180
|
+
if (S.sweepQueued) return;
|
|
181
|
+
S.sweepQueued = true;
|
|
182
|
+
requestAnimationFrame(() => {
|
|
183
|
+
S.sweepQueued = false;
|
|
184
|
+
sweepDeadWraps();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
150
188
|
function getDynamicShowBatchMax() {
|
|
151
189
|
const speed = S.scrollSpeed || 0;
|
|
152
190
|
const pend = S.pending.length;
|
|
@@ -164,15 +202,42 @@
|
|
|
164
202
|
S.mutGuard++;
|
|
165
203
|
try { fn(); } finally { S.mutGuard--; }
|
|
166
204
|
}
|
|
205
|
+
function scheduleDestroyFlush() {
|
|
206
|
+
if (S.destroyBatchTimer) return;
|
|
207
|
+
S.destroyBatchTimer = setTimeout(() => {
|
|
208
|
+
S.destroyBatchTimer = 0;
|
|
209
|
+
flushDestroyBatch();
|
|
210
|
+
}, DESTROY_FLUSH_MS);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function flushDestroyBatch() {
|
|
214
|
+
if (!S.destroyPending.length) return;
|
|
215
|
+
const ids = [];
|
|
216
|
+
while (S.destroyPending.length && ids.length < MAX_DESTROY_BATCH) {
|
|
217
|
+
const id = S.destroyPending.shift();
|
|
218
|
+
S.destroyPendingSet.delete(id);
|
|
219
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
220
|
+
ids.push(id);
|
|
221
|
+
}
|
|
222
|
+
if (ids.length) {
|
|
223
|
+
try {
|
|
224
|
+
const ez = window.ezstandalone;
|
|
225
|
+
const run = () => { try { ez?.destroyPlaceholders?.(ids); } catch (_) {} };
|
|
226
|
+
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
227
|
+
} catch (_) {}
|
|
228
|
+
}
|
|
229
|
+
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
230
|
+
}
|
|
231
|
+
|
|
167
232
|
function destroyEzoicId(id) {
|
|
168
233
|
if (!Number.isFinite(id) || id <= 0) return;
|
|
169
234
|
if (!S.ezActiveIds.has(id)) return;
|
|
170
|
-
try {
|
|
171
|
-
const ez = window.ezstandalone;
|
|
172
|
-
const run = () => { try { ez?.destroyPlaceholders?.([id]); } catch (_) {} };
|
|
173
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
174
|
-
} catch (_) {}
|
|
175
235
|
S.ezActiveIds.delete(id);
|
|
236
|
+
if (!S.destroyPendingSet.has(id)) {
|
|
237
|
+
S.destroyPending.push(id);
|
|
238
|
+
S.destroyPendingSet.add(id);
|
|
239
|
+
}
|
|
240
|
+
scheduleDestroyFlush();
|
|
176
241
|
}
|
|
177
242
|
|
|
178
243
|
|
|
@@ -329,6 +394,25 @@ function destroyEzoicId(id) {
|
|
|
329
394
|
return null;
|
|
330
395
|
}
|
|
331
396
|
|
|
397
|
+
function sweepDeadWraps() {
|
|
398
|
+
// NodeBB peut retirer des nœuds sans passer par dropWrap() (virtualisation / rerender).
|
|
399
|
+
// On libère alors les IDs/keys fantômes pour éviter l'épuisement du pool.
|
|
400
|
+
for (const [key, wrap] of Array.from(S.wrapByKey.entries())) {
|
|
401
|
+
if (wrap?.isConnected) continue;
|
|
402
|
+
S.wrapByKey.delete(key);
|
|
403
|
+
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
404
|
+
if (Number.isFinite(id)) {
|
|
405
|
+
S.mountedIds.delete(id);
|
|
406
|
+
S.pendingSet.delete(id);
|
|
407
|
+
S.lastShow.delete(id);
|
|
408
|
+
S.ezActiveIds.delete(id);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (S.pending.length) {
|
|
412
|
+
S.pending = S.pending.filter(id => S.pendingSet.has(id));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
332
416
|
/**
|
|
333
417
|
* Pool épuisé : recycle un wrap loin au-dessus du viewport.
|
|
334
418
|
* Séquence avec délais (destroyPlaceholders est asynchrone) :
|
|
@@ -352,13 +436,14 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
352
436
|
let bestAnyEmpty = null, bestAnyMetric = Infinity;
|
|
353
437
|
let bestAnyFilled = null, bestAnyFilledMetric = Infinity;
|
|
354
438
|
|
|
355
|
-
|
|
439
|
+
for (const wrap of S.wrapByKey.values()) {
|
|
440
|
+
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
356
441
|
try {
|
|
357
442
|
const rect = wrap.getBoundingClientRect();
|
|
358
443
|
const isAbove = rect.bottom <= farAbove;
|
|
359
444
|
const isBelow = rect.top >= farBelow;
|
|
360
445
|
const anyFar = isAbove || isBelow;
|
|
361
|
-
if (!anyFar)
|
|
446
|
+
if (!anyFar) continue;
|
|
362
447
|
|
|
363
448
|
const qualifies = preferAbove ? isAbove : isBelow;
|
|
364
449
|
const metric = preferAbove ? Math.abs(rect.bottom) : Math.abs(rect.top - vh);
|
|
@@ -377,7 +462,7 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
377
462
|
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
378
463
|
}
|
|
379
464
|
} catch (_) {}
|
|
380
|
-
}
|
|
465
|
+
}
|
|
381
466
|
|
|
382
467
|
const best = bestPrefEmpty ?? bestPrefFilled ?? bestAnyEmpty ?? bestAnyFilled;
|
|
383
468
|
if (!best) return null;
|
|
@@ -399,11 +484,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
399
484
|
S.wrapByKey.set(newKey, best);
|
|
400
485
|
|
|
401
486
|
const doDestroy = () => {
|
|
402
|
-
if (S.ezActiveIds.has(id))
|
|
403
|
-
|
|
404
|
-
S.ezActiveIds.delete(id);
|
|
405
|
-
}
|
|
406
|
-
setTimeout(doDefine, 300);
|
|
487
|
+
if (S.ezActiveIds.has(id)) destroyEzoicId(id);
|
|
488
|
+
setTimeout(doDefine, 330);
|
|
407
489
|
};
|
|
408
490
|
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
409
491
|
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); } catch (_) {} };
|
|
@@ -522,7 +604,8 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
522
604
|
const key = anchorKey(klass, el);
|
|
523
605
|
if (findWrap(key)) continue;
|
|
524
606
|
|
|
525
|
-
|
|
607
|
+
let id = pickId(poolKey);
|
|
608
|
+
if (!id) { sweepDeadWraps(); id = pickId(poolKey); }
|
|
526
609
|
if (id) {
|
|
527
610
|
const w = insertAfter(el, id, klass, key);
|
|
528
611
|
if (w) { observePh(id); inserted++; }
|
|
@@ -553,15 +636,34 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
553
636
|
}
|
|
554
637
|
|
|
555
638
|
function observePh(id) {
|
|
556
|
-
const ph =
|
|
639
|
+
const ph = phEl(id);
|
|
557
640
|
if (ph?.isConnected) try { getIO()?.observe(ph); } catch (_) {}
|
|
641
|
+
// Fast-path : si déjà proche viewport, ne pas attendre un callback IO complet
|
|
642
|
+
try {
|
|
643
|
+
if (!ph?.isConnected) return;
|
|
644
|
+
const rect = ph.getBoundingClientRect();
|
|
645
|
+
const vh = window.innerHeight || 800;
|
|
646
|
+
const preload = isMobile() ? 1400 : 1000;
|
|
647
|
+
if (rect.top <= vh + preload && rect.bottom >= -preload) enqueueShow(id);
|
|
648
|
+
} catch (_) {}
|
|
558
649
|
}
|
|
559
650
|
|
|
560
651
|
function enqueueShow(id) {
|
|
561
652
|
if (!id || isBlocked()) return;
|
|
562
|
-
|
|
563
|
-
if (!
|
|
564
|
-
|
|
653
|
+
const n = parseInt(id, 10);
|
|
654
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
655
|
+
if (ts() - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return;
|
|
656
|
+
if (!S.pendingSet.has(n)) { S.pending.push(n); S.pendingSet.add(n); }
|
|
657
|
+
scheduleDrainQueue();
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function scheduleDrainQueue() {
|
|
661
|
+
if (isBlocked()) return;
|
|
662
|
+
if (S.showBatchTimer) return;
|
|
663
|
+
S.showBatchTimer = setTimeout(() => {
|
|
664
|
+
S.showBatchTimer = 0;
|
|
665
|
+
drainQueue();
|
|
666
|
+
}, BATCH_FLUSH_MS);
|
|
565
667
|
}
|
|
566
668
|
|
|
567
669
|
function drainQueue() {
|
|
@@ -576,10 +678,12 @@ function drainQueue() {
|
|
|
576
678
|
const id = S.pending.shift();
|
|
577
679
|
S.pendingSet.delete(id);
|
|
578
680
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
681
|
+
if (!phEl(id)?.isConnected) continue;
|
|
579
682
|
seen.add(id);
|
|
580
683
|
picked.push(id);
|
|
581
684
|
}
|
|
582
685
|
if (picked.length) startShowBatch(picked);
|
|
686
|
+
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
583
687
|
}
|
|
584
688
|
|
|
585
689
|
function startShowBatch(ids) {
|
|
@@ -594,7 +698,7 @@ function startShowBatch(ids) {
|
|
|
594
698
|
S.inflight = Math.max(0, S.inflight - reserve);
|
|
595
699
|
drainQueue();
|
|
596
700
|
};
|
|
597
|
-
const timer = setTimeout(release,
|
|
701
|
+
const timer = setTimeout(release, SHOW_FAILSAFE_MS);
|
|
598
702
|
|
|
599
703
|
requestAnimationFrame(() => {
|
|
600
704
|
try {
|
|
@@ -606,10 +710,8 @@ function startShowBatch(ids) {
|
|
|
606
710
|
for (const raw of ids) {
|
|
607
711
|
const id = parseInt(raw, 10);
|
|
608
712
|
if (!Number.isFinite(id) || id <= 0) continue;
|
|
609
|
-
const ph =
|
|
610
|
-
if (!
|
|
611
|
-
if (t - (S.lastShow.get(id) ?? 0) < SHOW_THROTTLE_MS) continue;
|
|
612
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
713
|
+
const ph = phEl(id);
|
|
714
|
+
if (!canShowPlaceholderId(id, t)) continue;
|
|
613
715
|
|
|
614
716
|
S.lastShow.set(id, t);
|
|
615
717
|
try { ph.closest?.(`.${WRAP_CLASS}`)?.setAttribute(A_SHOWN, String(t)); } catch (_) {}
|
|
@@ -625,7 +727,7 @@ function startShowBatch(ids) {
|
|
|
625
727
|
for (const id of valid) {
|
|
626
728
|
S.ezActiveIds.add(id);
|
|
627
729
|
}
|
|
628
|
-
setTimeout(() => { clearTimeout(timer); release(); },
|
|
730
|
+
setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS);
|
|
629
731
|
};
|
|
630
732
|
Array.isArray(ez.cmd) ? ez.cmd.push(doShow) : doShow();
|
|
631
733
|
} catch (_) { clearTimeout(timer); release(); }
|
|
@@ -655,8 +757,7 @@ function startShowBatch(ids) {
|
|
|
655
757
|
for (const v of ids) {
|
|
656
758
|
const id = parseInt(v, 10);
|
|
657
759
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
658
|
-
if (!
|
|
659
|
-
if (document.querySelectorAll(`#${PH_PREFIX}${id}`).length !== 1) continue;
|
|
760
|
+
if (!phEl(id)?.isConnected || !hasSinglePlaceholder(id)) continue;
|
|
660
761
|
seen.add(id);
|
|
661
762
|
valid.push(id);
|
|
662
763
|
}
|
|
@@ -681,6 +782,7 @@ function startShowBatch(ids) {
|
|
|
681
782
|
async function runCore() {
|
|
682
783
|
if (isBlocked()) return 0;
|
|
683
784
|
patchShowAds();
|
|
785
|
+
sweepDeadWraps();
|
|
684
786
|
|
|
685
787
|
const cfg = await fetchConfig();
|
|
686
788
|
if (!cfg || cfg.excluded) return 0;
|
|
@@ -747,7 +849,7 @@ function startShowBatch(ids) {
|
|
|
747
849
|
S.burstCount++;
|
|
748
850
|
scheduleRun(n => {
|
|
749
851
|
if (!n && !S.pending.length) { S.burstActive = false; return; }
|
|
750
|
-
setTimeout(step, n > 0 ?
|
|
852
|
+
setTimeout(step, n > 0 ? 80 : 180);
|
|
751
853
|
});
|
|
752
854
|
};
|
|
753
855
|
step();
|
|
@@ -769,8 +871,13 @@ function startShowBatch(ids) {
|
|
|
769
871
|
S.inflight = 0;
|
|
770
872
|
S.pending = [];
|
|
771
873
|
S.pendingSet.clear();
|
|
874
|
+
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
875
|
+
if (S.destroyBatchTimer) { clearTimeout(S.destroyBatchTimer); S.destroyBatchTimer = 0; }
|
|
876
|
+
S.destroyPending = [];
|
|
877
|
+
S.destroyPendingSet.clear();
|
|
772
878
|
S.burstActive = false;
|
|
773
879
|
S.runQueued = false;
|
|
880
|
+
S.sweepQueued = false;
|
|
774
881
|
S.scrollSpeed = 0;
|
|
775
882
|
S.lastScrollY = 0;
|
|
776
883
|
S.lastScrollTs = 0;
|
|
@@ -784,6 +891,14 @@ function startShowBatch(ids) {
|
|
|
784
891
|
S.domObs = new MutationObserver(muts => {
|
|
785
892
|
if (S.mutGuard > 0 || isBlocked()) return;
|
|
786
893
|
for (const m of muts) {
|
|
894
|
+
let sawWrapRemoval = false;
|
|
895
|
+
for (const n of m.removedNodes) {
|
|
896
|
+
if (n.nodeType !== 1) continue;
|
|
897
|
+
if ((n.matches && n.matches(`.${WRAP_CLASS}`)) || (n.querySelector && n.querySelector(`.${WRAP_CLASS}`))) {
|
|
898
|
+
sawWrapRemoval = true;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (sawWrapRemoval) queueSweepDeadWraps();
|
|
787
902
|
for (const n of m.addedNodes) {
|
|
788
903
|
if (n.nodeType !== 1) continue;
|
|
789
904
|
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
@@ -871,7 +986,7 @@ function startShowBatch(ids) {
|
|
|
871
986
|
S.pageKey = pageKey();
|
|
872
987
|
blockedUntil = 0;
|
|
873
988
|
muteConsole(); ensureTcfLocator(); warmNetwork();
|
|
874
|
-
patchShowAds(); getIO(); ensureDomObserver(); requestBurst();
|
|
989
|
+
patchShowAds(); getIO(); ensureDomObserver(); sweepDeadWraps(); requestBurst();
|
|
875
990
|
});
|
|
876
991
|
|
|
877
992
|
const burstEvts = [
|
package/public/style.css
CHANGED
|
@@ -56,12 +56,6 @@
|
|
|
56
56
|
top: auto !important;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/* ── État vide ────────────────────────────────────────────────────────────── */
|
|
60
|
-
/*
|
|
61
|
-
Ajouté 20s après showAds si aucun fill détecté.
|
|
62
|
-
Collapse à 1px (pas 0) : reste observable par l'IO si le fill arrive tard.
|
|
63
|
-
*/
|
|
64
|
-
|
|
65
59
|
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
66
60
|
.ezoic-ad {
|
|
67
61
|
margin: 0 !important;
|