nodebb-plugin-ezoic-infinite 1.4.4 → 1.4.6

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 +86 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
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
@@ -40,6 +40,8 @@
40
40
  retryQueue: [],
41
41
  retryQueueSet: new Set(),
42
42
  retryQueueRunning: false,
43
+ badIds: new Set(),
44
+ definedIds: new Set(),
43
45
 
44
46
  scheduled: false,
45
47
  timer: null,
@@ -121,10 +123,16 @@
121
123
 
122
124
  function destroyPlaceholderIds(ids) {
123
125
  if (!ids || !ids.length) return;
126
+ // Only destroy ids that were actually "defined" (filled at least once) to avoid Ezoic warnings.
127
+ const filtered = ids.filter((id) => {
128
+ try { return state.definedIds && state.definedIds.has(id); } catch (e) { return true; }
129
+ });
130
+ if (!filtered.length) return;
131
+
124
132
  const call = () => {
125
133
  try {
126
134
  if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') {
127
- window.ezstandalone.destroyPlaceholders(ids);
135
+ window.ezstandalone.destroyPlaceholders(filtered);
128
136
  }
129
137
  } catch (e) {}
130
138
  };
@@ -135,6 +143,15 @@
135
143
  else window.ezstandalone.cmd.push(call);
136
144
  } catch (e) {}
137
145
  }
146
+ } catch (e) {}
147
+ };
148
+ try {
149
+ window.ezstandalone = window.ezstandalone || {};
150
+ window.ezstandalone.cmd = window.ezstandalone.cmd || [];
151
+ if (window.ezstandalone && typeof window.ezstandalone.destroyPlaceholders === 'function') call();
152
+ else window.ezstandalone.cmd.push(call);
153
+ } catch (e) {}
154
+ }
138
155
 
139
156
  function getRecyclable(liveArr) {
140
157
  const margin = 1800;
@@ -168,6 +185,34 @@
168
185
  return { id: null, recycled: null };
169
186
  }
170
187
 
188
+
189
+ function resetPlaceholderInWrap(wrap, id) {
190
+ try {
191
+ if (!wrap) return;
192
+ const old = wrap.querySelector && wrap.querySelector(`#${PLACEHOLDER_PREFIX}${id}`);
193
+ if (old) old.remove();
194
+ // Remove any leftover markup inside wrapper
195
+ wrap.querySelectorAll && wrap.querySelectorAll('iframe, ins').forEach(n => n.remove());
196
+ const ph = document.createElement('div');
197
+ ph.id = `${PLACEHOLDER_PREFIX}${id}`;
198
+ wrap.appendChild(ph);
199
+ } catch (e) {}
200
+ }
201
+
202
+ function isAdjacentAd(target) {
203
+ if (!target || !target.nextElementSibling) return false;
204
+ const next = target.nextElementSibling;
205
+ if (next && next.classList && next.classList.contains(WRAP_CLASS)) return true;
206
+ return false;
207
+ }
208
+
209
+ function isPrevAd(target) {
210
+ if (!target || !target.previousElementSibling) return false;
211
+ const prev = target.previousElementSibling;
212
+ if (prev && prev.classList && prev.classList.contains(WRAP_CLASS)) return true;
213
+ return false;
214
+ }
215
+
171
216
  function buildWrap(id, kindClass, afterPos) {
172
217
  const wrap = document.createElement('div');
173
218
  wrap.className = `${WRAP_CLASS} ${kindClass}`;
@@ -243,10 +288,13 @@
243
288
  function isPlaceholderFilled(id) {
244
289
  const ph = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
245
290
  if (!ph || !ph.isConnected) return false;
246
- if (ph.childNodes && ph.childNodes.length > 0) return true;
247
- const wrap = ph.parentElement;
248
- if (wrap && wrap.querySelector && wrap.querySelector('iframe, ins, [id^="ezslot_"], [class*="ez"]')) return true;
249
- return false;
291
+
292
+ // Ezoic typically injects content inside the placeholder node.
293
+ const filled = !!(ph.childNodes && ph.childNodes.length > 0);
294
+ if (filled) {
295
+ try { state.definedIds && state.definedIds.add(id); } catch (e) {}
296
+ }
297
+ return filled;
250
298
  }
251
299
 
252
300
  function scheduleRefill(delay = 350) {
@@ -256,6 +304,7 @@
256
304
 
257
305
  function enqueueRetry(id) {
258
306
  if (!id) return;
307
+ if (state.badIds && state.badIds.has(id)) return;
259
308
  if (state.retryQueueSet.has(id)) return;
260
309
  state.retryQueueSet.add(id);
261
310
  state.retryQueue.push(id);
@@ -270,11 +319,23 @@
270
319
  const id = state.retryQueue.shift();
271
320
  if (!id) {
272
321
  state.retryQueueRunning = false;
322
+ state.badIds = new Set();
323
+ state.definedIds = new Set();
273
324
  return;
274
325
  }
275
326
  state.retryQueueSet.delete(id);
327
+ // If this id was previously attempted and still empty, force a full reset before re-requesting.
328
+ const attempts = (state.retryById.get(id) || 0);
329
+ const phNow = document.getElementById(`${PLACEHOLDER_PREFIX}${id}`);
330
+ const wrapNow = phNow && phNow.parentElement;
331
+ if (attempts > 0 && wrapNow && wrapNow.isConnected && !isPlaceholderFilled(id)) {
332
+ if (state.definedIds && state.definedIds.has(id)) {
333
+ destroyPlaceholderIds([id]);
334
+ }
335
+ resetPlaceholderInWrap(wrapNow, id);
336
+ }
276
337
  callShowAdsWhenReady(id);
277
- setTimeout(step, 950);
338
+ setTimeout(step, 1100);
278
339
  };
279
340
 
280
341
  step();
@@ -297,7 +358,7 @@
297
358
  }
298
359
 
299
360
  const tries = (state.retryById.get(id) || 0);
300
- if (tries >= 8) continue;
361
+ if (tries >= 8) { state.badIds && state.badIds.add(id); continue; }
301
362
 
302
363
  const r = safeRect(wrap);
303
364
  if (r && (r.top > window.innerHeight + 1200 || r.bottom < -1200)) continue;
@@ -423,8 +484,7 @@
423
484
  if (!el || !el.isConnected) continue;
424
485
 
425
486
  // Prevent adjacent ads (DOM-based, robust against virtualization)
426
- const nextSibling = el.nextElementSibling;
427
- if (nextSibling && nextSibling.classList && nextSibling.classList.contains(WRAP_CLASS)) {
487
+ if (isAdjacentAd(el) || isPrevAd(el)) {
428
488
  continue;
429
489
  }
430
490
 
@@ -444,10 +504,14 @@
444
504
 
445
505
  let wrap = null;
446
506
  if (pick.recycled && pick.recycled.wrap) {
447
- destroyPlaceholderIds([id]);
507
+ // Only destroy if Ezoic has actually defined this placeholder before
508
+ if (state.definedIds && state.definedIds.has(id)) {
509
+ destroyPlaceholderIds([id]);
510
+ }
448
511
  wrap = pick.recycled.wrap;
449
512
  if (!moveWrapAfter(wrap, el, kindClass, afterPos)) continue;
450
- setTimeout(() => { enqueueRetry(id); }, 250);
513
+ resetPlaceholderInWrap(wrap, id);
514
+ setTimeout(() => { enqueueRetry(id); }, 450);
451
515
  } else {
452
516
  usedSet.add(id);
453
517
  wrap = insertAfter(el, id, kindClass, afterPos);
@@ -455,6 +519,17 @@
455
519
  }
456
520
 
457
521
  liveArr.push({ id, wrap });
522
+ // If adjacency ended up happening (e.g. DOM shifts), rollback this placement.
523
+ if (wrap && (wrap.previousElementSibling && wrap.previousElementSibling.classList && wrap.previousElementSibling.classList.contains(WRAP_CLASS)) || (wrap.nextElementSibling && wrap.nextElementSibling.classList && wrap.nextElementSibling.classList.contains(WRAP_CLASS))) {
524
+ try { wrap.remove(); } catch (e) {}
525
+ // Put id back if it was newly consumed (not recycled)
526
+ if (!(pick.recycled && pick.recycled.wrap)) {
527
+ try { kindPool.unshift(id); } catch (e) {}
528
+ try { usedSet.delete(id); } catch (e) {}
529
+ }
530
+ inserted -= 0; // no-op
531
+ continue;
532
+ }
458
533
  if (!(pick.recycled && pick.recycled.wrap)) {
459
534
  callShowAdsWhenReady(id);
460
535
  }