nodebb-plugin-ezoic-infinite 1.8.65 → 1.8.67
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 +14 -25
- package/package.json +1 -1
- package/public/client.js +104 -194
- package/public/style.css +1 -6
package/library.js
CHANGED
|
@@ -87,11 +87,8 @@ async function isUserExcluded(uid, excludedGroups) {
|
|
|
87
87
|
const value = (userGroups[0] || []).some(g => excludedSet.has(g.name));
|
|
88
88
|
|
|
89
89
|
_excludeCache.set(key, { value, at: Date.now() });
|
|
90
|
-
|
|
91
|
-
// Limit cache size to prevent unbounded growth
|
|
92
90
|
if (_excludeCache.size > 1000) {
|
|
93
|
-
|
|
94
|
-
_excludeCache.delete(oldest);
|
|
91
|
+
_excludeCache.delete(_excludeCache.keys().next().value);
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
return value;
|
|
@@ -124,7 +121,7 @@ function buildClientConfig(settings, excluded) {
|
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
function serializeInlineConfig(cfg) {
|
|
127
|
-
return `<script>window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
124
|
+
return `<script data-cfasync="false">window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
128
125
|
}
|
|
129
126
|
|
|
130
127
|
// ── Head injection ───────────────────────────────────────────────────────────
|
|
@@ -138,14 +135,13 @@ const HEAD_PRECONNECTS = [
|
|
|
138
135
|
'<link rel="dns-prefetch" href="https://securepubads.g.doubleclick.net">',
|
|
139
136
|
].join('\n');
|
|
140
137
|
|
|
138
|
+
// Match v50 exactly: CMP → sa.min.js, no stubs.
|
|
139
|
+
// sa.min.js defines _ezaq and ezstandalone itself.
|
|
140
|
+
// Placing stubs before or alongside sa.min.js changes Ezoic's init path
|
|
141
|
+
// and causes "Timed out waiting for loadingStatus" errors.
|
|
141
142
|
const EZOIC_SCRIPTS = [
|
|
142
143
|
'<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
|
|
143
144
|
'<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
|
|
144
|
-
'<script>',
|
|
145
|
-
'window._ezaq = window._ezaq || {};',
|
|
146
|
-
'window.ezstandalone = window.ezstandalone || {};',
|
|
147
|
-
'ezstandalone.cmd = ezstandalone.cmd || [];',
|
|
148
|
-
'</script>',
|
|
149
145
|
'<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
|
|
150
146
|
'<script src="//ezoicanalytics.com/analytics.js"></script>',
|
|
151
147
|
].join('\n');
|
|
@@ -166,10 +162,6 @@ plugin.addAdminNavigation = async (header) => {
|
|
|
166
162
|
return header;
|
|
167
163
|
};
|
|
168
164
|
|
|
169
|
-
/**
|
|
170
|
-
* Inject Ezoic scripts into <head> via templateData.customHTML.
|
|
171
|
-
* NodeBB v4/Harmony: header.tpl has {{customHTML}} in <head>.
|
|
172
|
-
*/
|
|
173
165
|
plugin.injectEzoicHead = async (data) => {
|
|
174
166
|
try {
|
|
175
167
|
const settings = await getSettings();
|
|
@@ -177,17 +169,14 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
177
169
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
178
170
|
|
|
179
171
|
if (excluded) {
|
|
180
|
-
// Even for excluded users, inject a minimal stub so that any script
|
|
181
|
-
// referencing ezstandalone doesn't throw a ReferenceError, plus
|
|
182
|
-
// the config with excluded=true so client.js bails early.
|
|
183
172
|
const cfg = buildClientConfig(settings, true);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
'window.
|
|
188
|
-
'ezstandalone.
|
|
189
|
-
'
|
|
190
|
-
|
|
173
|
+
// Minimal stub for excluded users — prevents ReferenceError if other
|
|
174
|
+
// scripts reference _ezaq or ezstandalone
|
|
175
|
+
const stub = '<script>'
|
|
176
|
+
+ 'window._ezaq=window._ezaq||{};'
|
|
177
|
+
+ 'window.ezstandalone=window.ezstandalone||{};'
|
|
178
|
+
+ 'window.ezstandalone.cmd=window.ezstandalone.cmd||[];'
|
|
179
|
+
+ '</script>';
|
|
191
180
|
data.templateData.customHTML =
|
|
192
181
|
stub + '\n' +
|
|
193
182
|
serializeInlineConfig(cfg) +
|
|
@@ -235,4 +224,4 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
235
224
|
});
|
|
236
225
|
};
|
|
237
226
|
|
|
238
|
-
module.exports = plugin;
|
|
227
|
+
module.exports = plugin;
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js v2.
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v2.4.0
|
|
3
3
|
*
|
|
4
|
-
* Architecture
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
* - aria-hidden + TCF locator protection
|
|
4
|
+
* Architecture: proven v50 core + targeted improvements.
|
|
5
|
+
* Ezoic API: showAds() + destroyPlaceholders() per official docs.
|
|
6
|
+
* Key features: O(1) recycle via wrapsByClass, MutationObserver fill detect,
|
|
7
|
+
* conservative empty check, aria-hidden + TCF protection, retry boot for
|
|
8
|
+
* Cloudflare/async timing.
|
|
10
9
|
*/
|
|
11
10
|
(function nbbEzoicInfinite() {
|
|
12
11
|
'use strict';
|
|
@@ -77,35 +76,28 @@
|
|
|
77
76
|
// ── State ──────────────────────────────────────────────────────────────────
|
|
78
77
|
|
|
79
78
|
const S = {
|
|
80
|
-
pageKey:
|
|
81
|
-
kind:
|
|
82
|
-
cfg:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
87
|
-
|
|
79
|
+
pageKey: null,
|
|
80
|
+
kind: null,
|
|
81
|
+
cfg: null,
|
|
82
|
+
poolsReady: false,
|
|
83
|
+
pools: { topics: [], posts: [], categories: [] },
|
|
84
|
+
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
88
85
|
mountedIds: new Set(),
|
|
89
86
|
lastShow: new Map(),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
pending: [],
|
|
102
|
-
pendingSet: new Set(),
|
|
103
|
-
|
|
104
|
-
runQueued: false,
|
|
105
|
-
burstActive: false,
|
|
87
|
+
wrapByKey: new Map(),
|
|
88
|
+
wrapsByClass: new Map(),
|
|
89
|
+
io: null,
|
|
90
|
+
domObs: null,
|
|
91
|
+
mutGuard: 0,
|
|
92
|
+
blockedUntil: 0,
|
|
93
|
+
inflight: 0,
|
|
94
|
+
pending: [],
|
|
95
|
+
pendingSet: new Set(),
|
|
96
|
+
runQueued: false,
|
|
97
|
+
burstActive: false,
|
|
106
98
|
burstDeadline: 0,
|
|
107
|
-
burstCount:
|
|
108
|
-
lastBurstTs:
|
|
99
|
+
burstCount: 0,
|
|
100
|
+
lastBurstTs: 0,
|
|
109
101
|
};
|
|
110
102
|
|
|
111
103
|
const isBlocked = () => now() < S.blockedUntil;
|
|
@@ -214,15 +206,13 @@
|
|
|
214
206
|
}
|
|
215
207
|
|
|
216
208
|
// ── GC disconnected wraps ──────────────────────────────────────────────────
|
|
217
|
-
// NodeBB virtualizes posts off-viewport. The MutationObserver catches most
|
|
218
|
-
// removals, but this is a safety net for edge cases.
|
|
219
209
|
|
|
220
210
|
function gcDisconnectedWraps() {
|
|
221
|
-
for (const [key, w] of S.wrapByKey) {
|
|
211
|
+
for (const [key, w] of Array.from(S.wrapByKey)) {
|
|
222
212
|
if (!w?.isConnected) S.wrapByKey.delete(key);
|
|
223
213
|
}
|
|
224
|
-
for (const [klass, set] of S.wrapsByClass) {
|
|
225
|
-
for (const w of set) {
|
|
214
|
+
for (const [klass, set] of Array.from(S.wrapsByClass)) {
|
|
215
|
+
for (const w of Array.from(set)) {
|
|
226
216
|
if (w?.isConnected) continue;
|
|
227
217
|
set.delete(w);
|
|
228
218
|
const id = parseInt(w.getAttribute?.(ATTR.WRAPID), 10);
|
|
@@ -261,9 +251,7 @@
|
|
|
261
251
|
} catch (_) { return false; }
|
|
262
252
|
}
|
|
263
253
|
|
|
264
|
-
|
|
265
|
-
return wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
266
|
-
}
|
|
254
|
+
const adjacentWrap = el => wrapIsLive(el.nextElementSibling) || wrapIsLive(el.previousElementSibling);
|
|
267
255
|
|
|
268
256
|
// ── Fill detection ─────────────────────────────────────────────────────────
|
|
269
257
|
|
|
@@ -297,15 +285,13 @@
|
|
|
297
285
|
}
|
|
298
286
|
|
|
299
287
|
// ── Recycling ──────────────────────────────────────────────────────────────
|
|
300
|
-
// Per Ezoic docs: destroyPlaceholders(id) → remove HTML → fresh placeholder → showAds(id)
|
|
301
288
|
|
|
302
289
|
function recycleWrap(klass, targetEl, newKey) {
|
|
303
290
|
const ez = window.ezstandalone;
|
|
304
291
|
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
305
292
|
typeof ez?.showAds !== 'function') return null;
|
|
306
293
|
|
|
307
|
-
const
|
|
308
|
-
const threshold = -(3 * vh);
|
|
294
|
+
const threshold = -(3 * (window.innerHeight || 800));
|
|
309
295
|
const t = now();
|
|
310
296
|
let bestEmpty = null, bestEmptyY = Infinity;
|
|
311
297
|
let bestFull = null, bestFullY = Infinity;
|
|
@@ -329,21 +315,17 @@
|
|
|
329
315
|
|
|
330
316
|
const best = bestEmpty ?? bestFull;
|
|
331
317
|
if (!best) return null;
|
|
332
|
-
|
|
333
318
|
const id = parseInt(best.getAttribute(ATTR.WRAPID), 10);
|
|
334
319
|
if (!Number.isFinite(id)) return null;
|
|
335
320
|
const oldKey = best.getAttribute(ATTR.ANCHOR);
|
|
336
321
|
|
|
337
|
-
// Unobserve before moving
|
|
338
322
|
try {
|
|
339
323
|
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
340
324
|
if (ph) S.io?.unobserve(ph);
|
|
341
325
|
} catch (_) {}
|
|
342
326
|
|
|
343
|
-
// Ezoic recycle: destroy → fresh DOM → showAds
|
|
344
327
|
const doRecycle = () => {
|
|
345
328
|
try { ez.destroyPlaceholders(id); } catch (_) {}
|
|
346
|
-
|
|
347
329
|
setTimeout(() => {
|
|
348
330
|
mutate(() => {
|
|
349
331
|
best.setAttribute(ATTR.ANCHOR, newKey);
|
|
@@ -351,21 +333,15 @@
|
|
|
351
333
|
best.setAttribute(ATTR.SHOWN, '0');
|
|
352
334
|
best.classList.remove('is-empty');
|
|
353
335
|
best.replaceChildren();
|
|
354
|
-
|
|
355
336
|
const fresh = document.createElement('div');
|
|
356
337
|
fresh.id = `${PH_PREFIX}${id}`;
|
|
357
338
|
fresh.setAttribute('data-ezoic-id', String(id));
|
|
358
339
|
best.appendChild(fresh);
|
|
359
340
|
targetEl.insertAdjacentElement('afterend', best);
|
|
360
341
|
});
|
|
361
|
-
|
|
362
342
|
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
363
343
|
S.wrapByKey.set(newKey, best);
|
|
364
|
-
|
|
365
|
-
setTimeout(() => {
|
|
366
|
-
observePh(id);
|
|
367
|
-
enqueueShow(id);
|
|
368
|
-
}, TIMING.RECYCLE_DELAY_MS);
|
|
344
|
+
setTimeout(() => { observePh(id); enqueueShow(id); }, TIMING.RECYCLE_DELAY_MS);
|
|
369
345
|
}, TIMING.RECYCLE_DELAY_MS);
|
|
370
346
|
};
|
|
371
347
|
|
|
@@ -431,14 +407,13 @@
|
|
|
431
407
|
const cfg = KIND[klass];
|
|
432
408
|
const wraps = S.wrapsByClass.get(klass);
|
|
433
409
|
if (!wraps?.size) return;
|
|
434
|
-
|
|
435
410
|
const liveAnchors = new Set();
|
|
436
411
|
for (const el of document.querySelectorAll(`${cfg.baseTag}[${cfg.anchorAttr}]`)) {
|
|
437
412
|
const v = el.getAttribute(cfg.anchorAttr);
|
|
438
413
|
if (v) liveAnchors.add(v);
|
|
439
414
|
}
|
|
440
415
|
const t = now();
|
|
441
|
-
for (const w of wraps) {
|
|
416
|
+
for (const w of Array.from(wraps)) {
|
|
442
417
|
const created = parseInt(w.getAttribute(ATTR.CREATED) || '0', 10);
|
|
443
418
|
if (t - created < TIMING.MIN_PRUNE_AGE_MS) continue;
|
|
444
419
|
const key = w.getAttribute(ATTR.ANCHOR) ?? '';
|
|
@@ -480,8 +455,7 @@
|
|
|
480
455
|
const w = insertAfter(el, id, klass, key);
|
|
481
456
|
if (w) { observePh(id); inserted++; }
|
|
482
457
|
} else {
|
|
483
|
-
|
|
484
|
-
if (!recycled) break;
|
|
458
|
+
if (!recycleWrap(klass, el, key)) break;
|
|
485
459
|
inserted++;
|
|
486
460
|
}
|
|
487
461
|
}
|
|
@@ -552,11 +526,9 @@
|
|
|
552
526
|
if (isBlocked()) { clearTimeout(timer); return release(); }
|
|
553
527
|
const ph = document.getElementById(`${PH_PREFIX}${id}`);
|
|
554
528
|
if (!ph?.isConnected || isFilled(ph)) { clearTimeout(timer); return release(); }
|
|
555
|
-
|
|
556
529
|
const t = now();
|
|
557
530
|
if (t - (S.lastShow.get(id) ?? 0) < TIMING.SHOW_THROTTLE_MS) { clearTimeout(timer); return release(); }
|
|
558
531
|
S.lastShow.set(id, t);
|
|
559
|
-
|
|
560
532
|
const wrap = ph.closest(`.${WRAP_CLASS}`);
|
|
561
533
|
try { wrap?.setAttribute(ATTR.SHOWN, String(t)); } catch (_) {}
|
|
562
534
|
|
|
@@ -582,7 +554,6 @@
|
|
|
582
554
|
if (!wrap || !ph?.isConnected) return;
|
|
583
555
|
if (parseInt(wrap.getAttribute(ATTR.SHOWN) || '0', 10) > showTs) return;
|
|
584
556
|
if (clearEmptyIfFilled(wrap)) return;
|
|
585
|
-
// Don't collapse if GPT slot exists (still loading)
|
|
586
557
|
if (ph.querySelector('[id^="div-gpt-ad"]')) return;
|
|
587
558
|
if (ph.offsetHeight > 10) return;
|
|
588
559
|
wrap.classList.add('is-empty');
|
|
@@ -592,7 +563,6 @@
|
|
|
592
563
|
}
|
|
593
564
|
|
|
594
565
|
// ── Patch Ezoic showAds ────────────────────────────────────────────────────
|
|
595
|
-
// Matches v50: individual calls, no batching, pass-through no-arg calls.
|
|
596
566
|
|
|
597
567
|
function patchShowAds() {
|
|
598
568
|
const apply = () => {
|
|
@@ -603,7 +573,7 @@
|
|
|
603
573
|
window.__nbbEzPatched = true;
|
|
604
574
|
const orig = ez.showAds.bind(ez);
|
|
605
575
|
ez.showAds = function (...args) {
|
|
606
|
-
if (args.length
|
|
576
|
+
if (!args.length) return orig();
|
|
607
577
|
if (isBlocked()) return;
|
|
608
578
|
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
609
579
|
const seen = new Set();
|
|
@@ -640,24 +610,19 @@
|
|
|
640
610
|
|
|
641
611
|
const exec = (klass, getItems, cfgEnable, cfgInterval, cfgShowFirst, poolKey) => {
|
|
642
612
|
if (!normBool(cfgEnable)) return 0;
|
|
643
|
-
|
|
644
|
-
return injectBetween(klass, getItems(), interval, normBool(cfgShowFirst), poolKey);
|
|
613
|
+
return injectBetween(klass, getItems(), Math.max(1, parseInt(cfgInterval, 10) || 3), normBool(cfgShowFirst), poolKey);
|
|
645
614
|
};
|
|
646
615
|
|
|
647
|
-
if (kind === 'topic')
|
|
648
|
-
return exec('ezoic-ad-message', getPosts,
|
|
649
|
-
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts');
|
|
650
|
-
}
|
|
616
|
+
if (kind === 'topic')
|
|
617
|
+
return exec('ezoic-ad-message', getPosts, cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts');
|
|
651
618
|
if (kind === 'categoryTopics') {
|
|
652
619
|
pruneOrphansBetween();
|
|
653
|
-
return exec('ezoic-ad-between', getTopics,
|
|
654
|
-
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics');
|
|
620
|
+
return exec('ezoic-ad-between', getTopics, cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics');
|
|
655
621
|
}
|
|
656
|
-
return exec('ezoic-ad-categories', getCategories,
|
|
657
|
-
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories');
|
|
622
|
+
return exec('ezoic-ad-categories', getCategories, cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories');
|
|
658
623
|
}
|
|
659
624
|
|
|
660
|
-
// ── Scheduler
|
|
625
|
+
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
661
626
|
|
|
662
627
|
function scheduleRun(cb) {
|
|
663
628
|
if (S.runQueued) return;
|
|
@@ -694,7 +659,7 @@
|
|
|
694
659
|
step();
|
|
695
660
|
}
|
|
696
661
|
|
|
697
|
-
// ── Cleanup
|
|
662
|
+
// ── Cleanup ────────────────────────────────────────────────────────────────
|
|
698
663
|
|
|
699
664
|
function cleanup() {
|
|
700
665
|
S.blockedUntil = now() + TIMING.BLOCK_DURATION_MS;
|
|
@@ -730,53 +695,37 @@
|
|
|
730
695
|
kind === 'categoryTopics' ? [SEL.topic] :
|
|
731
696
|
kind === 'categories' ? [SEL.category] :
|
|
732
697
|
[SEL.post, SEL.topic, SEL.category];
|
|
698
|
+
outer:
|
|
733
699
|
for (const m of muts) {
|
|
734
700
|
if (m.type !== 'childList') continue;
|
|
735
|
-
// Free IDs from wraps removed by NodeBB virtualization
|
|
736
701
|
for (const node of m.removedNodes) {
|
|
737
702
|
if (!(node instanceof Element)) continue;
|
|
738
703
|
try {
|
|
739
|
-
if (node.classList?.contains(WRAP_CLASS))
|
|
740
|
-
|
|
741
|
-
} else {
|
|
742
|
-
const wraps = node.querySelectorAll?.(`.${WRAP_CLASS}`);
|
|
743
|
-
if (wraps?.length) for (const w of wraps) dropWrap(w);
|
|
744
|
-
}
|
|
704
|
+
if (node.classList?.contains(WRAP_CLASS)) dropWrap(node);
|
|
705
|
+
else { const ws = node.querySelectorAll?.(`.${WRAP_CLASS}`); if (ws?.length) for (const w of ws) dropWrap(w); }
|
|
745
706
|
} catch (_) {}
|
|
746
707
|
}
|
|
747
708
|
for (const node of m.addedNodes) {
|
|
748
709
|
if (!(node instanceof Element)) continue;
|
|
749
|
-
// Ad fill detection → uncollapse
|
|
750
710
|
try {
|
|
751
711
|
if (node.matches?.(FILL_SEL) || node.querySelector?.(FILL_SEL)) {
|
|
752
|
-
const wrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) ||
|
|
753
|
-
m.target?.closest?.(`.${WRAP_CLASS}.is-empty`);
|
|
712
|
+
const wrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) || m.target?.closest?.(`.${WRAP_CLASS}.is-empty`);
|
|
754
713
|
if (wrap) clearEmptyIfFilled(wrap);
|
|
755
714
|
}
|
|
756
715
|
} catch (_) {}
|
|
757
|
-
// Re-observe wraps re-inserted by NodeBB virtualization
|
|
758
716
|
try {
|
|
759
|
-
const reinserted = node.classList?.contains(WRAP_CLASS)
|
|
760
|
-
? [node]
|
|
761
|
-
: Array.from(node.querySelectorAll?.(`.${WRAP_CLASS}`) || []);
|
|
717
|
+
const reinserted = node.classList?.contains(WRAP_CLASS) ? [node] : Array.from(node.querySelectorAll?.(`.${WRAP_CLASS}`) || []);
|
|
762
718
|
for (const wrap of reinserted) {
|
|
763
719
|
const id = parseInt(wrap.getAttribute(ATTR.WRAPID), 10);
|
|
764
|
-
if (id > 0) {
|
|
765
|
-
const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
766
|
-
if (ph) try { getIO()?.observe(ph); } catch (_) {}
|
|
767
|
-
}
|
|
720
|
+
if (id > 0) { const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`); if (ph) try { getIO()?.observe(ph); } catch (_) {} }
|
|
768
721
|
}
|
|
769
722
|
} catch (_) {}
|
|
770
|
-
// New content detection
|
|
771
723
|
if (!needsBurst) {
|
|
772
724
|
for (const sel of relevantSels) {
|
|
773
|
-
try {
|
|
774
|
-
if (node.matches(sel) || node.querySelector(sel)) { needsBurst = true; break; }
|
|
775
|
-
} catch (_) {}
|
|
725
|
+
try { if (node.matches(sel) || node.querySelector(sel)) { needsBurst = true; break outer; } } catch (_) {}
|
|
776
726
|
}
|
|
777
727
|
}
|
|
778
728
|
}
|
|
779
|
-
if (needsBurst) break;
|
|
780
729
|
}
|
|
781
730
|
if (needsBurst) requestBurst();
|
|
782
731
|
});
|
|
@@ -789,65 +738,34 @@
|
|
|
789
738
|
if (!window.__tcfapi && !window.__cmp) return;
|
|
790
739
|
const LOCATOR_ID = '__tcfapiLocator';
|
|
791
740
|
const ensureInHead = () => {
|
|
792
|
-
let
|
|
793
|
-
if (
|
|
794
|
-
if (existing.parentElement !== document.head) {
|
|
795
|
-
try { document.head.appendChild(existing); } catch (_) {}
|
|
796
|
-
}
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
741
|
+
let el = document.getElementById(LOCATOR_ID);
|
|
742
|
+
if (el) { if (el.parentElement !== document.head) try { document.head.appendChild(el); } catch (_) {} return; }
|
|
799
743
|
const f = document.createElement('iframe');
|
|
800
|
-
f.style.display = 'none';
|
|
801
|
-
f
|
|
802
|
-
try { document.head.appendChild(f); } catch (_) {
|
|
803
|
-
(document.body || document.documentElement).appendChild(f);
|
|
804
|
-
}
|
|
744
|
+
f.style.display = 'none'; f.id = f.name = LOCATOR_ID;
|
|
745
|
+
try { document.head.appendChild(f); } catch (_) { (document.body || document.documentElement).appendChild(f); }
|
|
805
746
|
};
|
|
806
747
|
ensureInHead();
|
|
807
|
-
|
|
808
748
|
if (!window.__nbbCmpGuarded) {
|
|
809
749
|
window.__nbbCmpGuarded = true;
|
|
810
750
|
if (typeof window.__tcfapi === 'function') {
|
|
811
|
-
const
|
|
812
|
-
window.__tcfapi = function (cmd,
|
|
813
|
-
try {
|
|
814
|
-
|
|
815
|
-
try { cb?.(...args); } catch (_) {}
|
|
816
|
-
}, param);
|
|
817
|
-
} catch (e) {
|
|
818
|
-
if (e?.message?.includes('null')) {
|
|
819
|
-
ensureInHead();
|
|
820
|
-
try { return origTcf.call(this, cmd, version, cb, param); } catch (_) {}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
751
|
+
const orig = window.__tcfapi;
|
|
752
|
+
window.__tcfapi = function (cmd, ver, cb, param) {
|
|
753
|
+
try { return orig.call(this, cmd, ver, function (...a) { try { cb?.(...a); } catch (_) {} }, param); }
|
|
754
|
+
catch (e) { if (e?.message?.includes('null')) { ensureInHead(); try { return orig.call(this, cmd, ver, cb, param); } catch (_) {} } }
|
|
823
755
|
};
|
|
824
756
|
}
|
|
825
757
|
if (typeof window.__cmp === 'function') {
|
|
826
|
-
const
|
|
827
|
-
window.__cmp = function (...
|
|
828
|
-
try { return
|
|
829
|
-
catch (e) {
|
|
830
|
-
if (e?.message?.includes('null')) {
|
|
831
|
-
ensureInHead();
|
|
832
|
-
try { return origCmp.apply(this, args); } catch (_) {}
|
|
833
|
-
}
|
|
834
|
-
}
|
|
758
|
+
const orig = window.__cmp;
|
|
759
|
+
window.__cmp = function (...a) {
|
|
760
|
+
try { return orig.apply(this, a); }
|
|
761
|
+
catch (e) { if (e?.message?.includes('null')) { ensureInHead(); try { return orig.apply(this, a); } catch (_) {} } }
|
|
835
762
|
};
|
|
836
763
|
}
|
|
837
764
|
}
|
|
838
|
-
|
|
839
765
|
if (!window.__nbbTcfObs) {
|
|
840
|
-
window.__nbbTcfObs = new MutationObserver(() => {
|
|
841
|
-
|
|
842
|
-
});
|
|
843
|
-
try {
|
|
844
|
-
window.__nbbTcfObs.observe(document.body || document.documentElement, {
|
|
845
|
-
childList: true, subtree: false,
|
|
846
|
-
});
|
|
847
|
-
if (document.head) {
|
|
848
|
-
window.__nbbTcfObs.observe(document.head, { childList: true, subtree: false });
|
|
849
|
-
}
|
|
850
|
-
} catch (_) {}
|
|
766
|
+
window.__nbbTcfObs = new MutationObserver(() => { if (!document.getElementById(LOCATOR_ID)) ensureInHead(); });
|
|
767
|
+
try { window.__nbbTcfObs.observe(document.body || document.documentElement, { childList: true, subtree: false }); } catch (_) {}
|
|
768
|
+
try { if (document.head) window.__nbbTcfObs.observe(document.head, { childList: true, subtree: false }); } catch (_) {}
|
|
851
769
|
}
|
|
852
770
|
}
|
|
853
771
|
|
|
@@ -855,20 +773,10 @@
|
|
|
855
773
|
|
|
856
774
|
function protectAriaHidden() {
|
|
857
775
|
if (window.__nbbAriaObs) return;
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
} catch (_) {}
|
|
864
|
-
};
|
|
865
|
-
remove();
|
|
866
|
-
window.__nbbAriaObs = new MutationObserver(remove);
|
|
867
|
-
try {
|
|
868
|
-
window.__nbbAriaObs.observe(document.body, {
|
|
869
|
-
attributes: true, attributeFilter: ['aria-hidden'],
|
|
870
|
-
});
|
|
871
|
-
} catch (_) {}
|
|
776
|
+
const fix = () => { try { if (document.body.getAttribute('aria-hidden') === 'true') document.body.removeAttribute('aria-hidden'); } catch (_) {} };
|
|
777
|
+
fix();
|
|
778
|
+
window.__nbbAriaObs = new MutationObserver(fix);
|
|
779
|
+
try { window.__nbbAriaObs.observe(document.body, { attributes: true, attributeFilter: ['aria-hidden'] }); } catch (_) {}
|
|
872
780
|
}
|
|
873
781
|
|
|
874
782
|
// ── Console muting ─────────────────────────────────────────────────────────
|
|
@@ -877,31 +785,26 @@
|
|
|
877
785
|
if (window.__nbbEzMuted) return;
|
|
878
786
|
window.__nbbEzMuted = true;
|
|
879
787
|
const MUTED = [
|
|
880
|
-
'[EzoicAds JS]: Placeholder Id',
|
|
881
|
-
'No valid placeholders for loadMore',
|
|
882
|
-
'cannot call refresh on the same page',
|
|
883
|
-
'no placeholders are currently defined in Refresh',
|
|
884
|
-
'Debugger iframe already exists',
|
|
885
|
-
'[CMP] Error in custom getTCData',
|
|
886
|
-
'vignette: no interstitial API',
|
|
887
|
-
'Ezoic JS-Enable should only ever',
|
|
888
|
-
'### sending slotDestroyed',
|
|
889
|
-
'Error loading identity bridging',
|
|
890
788
|
`with id ${PH_PREFIX}`,
|
|
891
|
-
'adsbygoogle.push() error
|
|
892
|
-
'
|
|
789
|
+
'adsbygoogle.push() error',
|
|
790
|
+
'already been defined',
|
|
893
791
|
'bad response. Status',
|
|
894
|
-
'slotDestroyed
|
|
792
|
+
'slotDestroyed',
|
|
895
793
|
'identity bridging',
|
|
794
|
+
'[EzoicAds JS]: Placeholder',
|
|
795
|
+
'No valid placeholders',
|
|
796
|
+
'cannot call refresh',
|
|
797
|
+
'no placeholders are currently defined',
|
|
798
|
+
'Debugger iframe already',
|
|
799
|
+
'Error in custom getTCData',
|
|
800
|
+
'no interstitial API',
|
|
801
|
+
'JS-Enable should only',
|
|
896
802
|
];
|
|
897
803
|
for (const method of ['log', 'info', 'warn', 'error']) {
|
|
898
804
|
const orig = console[method];
|
|
899
805
|
if (typeof orig !== 'function') continue;
|
|
900
806
|
console[method] = function (...args) {
|
|
901
|
-
if (typeof args[0] === 'string') {
|
|
902
|
-
const msg = args[0];
|
|
903
|
-
for (const p of MUTED) { if (msg.includes(p)) return; }
|
|
904
|
-
}
|
|
807
|
+
if (typeof args[0] === 'string') { for (const p of MUTED) if (args[0].includes(p)) return; }
|
|
905
808
|
return orig.apply(console, args);
|
|
906
809
|
};
|
|
907
810
|
}
|
|
@@ -909,8 +812,10 @@
|
|
|
909
812
|
|
|
910
813
|
// ── Network warmup ─────────────────────────────────────────────────────────
|
|
911
814
|
|
|
912
|
-
|
|
815
|
+
let _warmed = false;
|
|
913
816
|
function warmNetwork() {
|
|
817
|
+
if (_warmed) return;
|
|
818
|
+
_warmed = true;
|
|
914
819
|
const head = document.head;
|
|
915
820
|
if (!head) return;
|
|
916
821
|
for (const [rel, href, cors] of [
|
|
@@ -921,9 +826,7 @@
|
|
|
921
826
|
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
922
827
|
['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
|
|
923
828
|
]) {
|
|
924
|
-
|
|
925
|
-
if (_warmed.has(k)) continue;
|
|
926
|
-
_warmed.add(k);
|
|
829
|
+
if (head.querySelector(`link[rel="${rel}"][href="${href}"]`)) continue;
|
|
927
830
|
const l = document.createElement('link');
|
|
928
831
|
l.rel = rel; l.href = href;
|
|
929
832
|
if (cors) l.crossOrigin = 'anonymous';
|
|
@@ -939,30 +842,22 @@
|
|
|
939
842
|
$(window).off('.nbbEzoic');
|
|
940
843
|
$(window).on('action:ajaxify.start.nbbEzoic', cleanup);
|
|
941
844
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
942
|
-
S.pageKey
|
|
943
|
-
S.kind = null;
|
|
944
|
-
S.blockedUntil = 0;
|
|
845
|
+
S.pageKey = pageKey(); S.kind = null; S.blockedUntil = 0;
|
|
945
846
|
muteConsole(); ensureTcfLocator(); protectAriaHidden();
|
|
946
847
|
warmNetwork(); patchShowAds(); getIO(); ensureDomObserver();
|
|
947
848
|
requestBurst();
|
|
948
849
|
});
|
|
949
|
-
|
|
950
|
-
const burstEvents = [
|
|
850
|
+
const burstEvts = [
|
|
951
851
|
'action:ajaxify.contentLoaded', 'action:posts.loaded',
|
|
952
852
|
'action:topics.loaded', 'action:categories.loaded',
|
|
953
853
|
'action:category.loaded', 'action:topic.loaded',
|
|
954
854
|
].map(e => `${e}.nbbEzoic`).join(' ');
|
|
955
|
-
$(window).on(
|
|
956
|
-
|
|
855
|
+
$(window).on(burstEvts, () => { if (!isBlocked()) requestBurst(); });
|
|
957
856
|
try {
|
|
958
857
|
require(['hooks'], hooks => {
|
|
959
858
|
if (typeof hooks?.on !== 'function') return;
|
|
960
|
-
for (const ev of [
|
|
961
|
-
'action:ajaxify.end', 'action:posts.loaded', 'action:topics.loaded',
|
|
962
|
-
'action:categories.loaded', 'action:topic.loaded',
|
|
963
|
-
]) {
|
|
859
|
+
for (const ev of ['action:ajaxify.end', 'action:posts.loaded', 'action:topics.loaded', 'action:categories.loaded', 'action:topic.loaded'])
|
|
964
860
|
try { hooks.on(ev, () => { if (!isBlocked()) requestBurst(); }); } catch (_) {}
|
|
965
|
-
}
|
|
966
861
|
});
|
|
967
862
|
} catch (_) {}
|
|
968
863
|
}
|
|
@@ -970,8 +865,7 @@
|
|
|
970
865
|
function bindScroll() {
|
|
971
866
|
let ticking = false;
|
|
972
867
|
window.addEventListener('scroll', () => {
|
|
973
|
-
if (ticking) return;
|
|
974
|
-
ticking = true;
|
|
868
|
+
if (ticking) return; ticking = true;
|
|
975
869
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
976
870
|
}, { passive: true });
|
|
977
871
|
}
|
|
@@ -991,4 +885,20 @@
|
|
|
991
885
|
S.blockedUntil = 0;
|
|
992
886
|
requestBurst();
|
|
993
887
|
|
|
888
|
+
// Retry boot: sa.min.js async + Cloudflare Rocket Loader + NodeBB SPA
|
|
889
|
+
// can cause client.js to boot before DOM/Ezoic are ready.
|
|
890
|
+
// Retries stop once ads are mounted or after ~10s.
|
|
891
|
+
let _retries = 0;
|
|
892
|
+
function retryBoot() {
|
|
893
|
+
if (_retries >= 12 || S.mountedIds.size > 0) return;
|
|
894
|
+
_retries++;
|
|
895
|
+
patchShowAds();
|
|
896
|
+
if (!isBlocked() && !S.burstActive) {
|
|
897
|
+
S.lastBurstTs = 0;
|
|
898
|
+
requestBurst();
|
|
899
|
+
}
|
|
900
|
+
setTimeout(retryBoot, _retries <= 4 ? 300 : 1000);
|
|
901
|
+
}
|
|
902
|
+
setTimeout(retryBoot, 250);
|
|
903
|
+
|
|
994
904
|
})();
|
package/public/style.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — style.css v2.
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — style.css v2.4
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* ── Wrapper ──────────────────────────────────────────────────────────────── */
|
|
@@ -112,10 +112,6 @@
|
|
|
112
112
|
|
|
113
113
|
/* ── Mobile ───────────────────────────────────────────────────────────────── */
|
|
114
114
|
@media (max-width: 767px) {
|
|
115
|
-
html, body {
|
|
116
|
-
overflow-x: hidden !important;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
115
|
.nodebb-ezoic-wrap,
|
|
120
116
|
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"],
|
|
121
117
|
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] > div {
|
|
@@ -149,7 +145,6 @@
|
|
|
149
145
|
|
|
150
146
|
.nodebb-ezoic-wrap > [id^="ezoic-pub-ad-placeholder-"] {
|
|
151
147
|
overflow-x: visible !important;
|
|
152
|
-
-webkit-overflow-scrolling: auto;
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
|