nodebb-plugin-ezoic-infinite 1.9.3 → 1.9.5

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
@@ -83,27 +83,30 @@ async function getSettings() {
83
83
  if (_settingsInflight) return _settingsInflight;
84
84
  const gen = _settingsGen;
85
85
  _settingsInflight = (async () => {
86
+ let data = null;
86
87
  try {
87
88
  const s = await meta.settings.get(SETTINGS_KEY);
89
+ data = {
90
+ enableBetweenAds: parseBool(s.enableBetweenAds, true),
91
+ showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
92
+ placeholderIds: (s.placeholderIds || '').trim(),
93
+ intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
94
+ enableCategoryAds: parseBool(s.enableCategoryAds, false),
95
+ showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
96
+ categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
97
+ intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
98
+ enableMessageAds: parseBool(s.enableMessageAds, false),
99
+ showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
100
+ messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
101
+ messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
102
+ excludedGroups: normalizeExcludedGroups(s.excludedGroups),
103
+ };
104
+ data.excludedSet = new Set(data.excludedGroups);
88
105
  if (_settingsGen === gen) {
89
106
  _settingsCacheAt = Date.now();
90
- _settingsCache = {
91
- enableBetweenAds: parseBool(s.enableBetweenAds, true),
92
- showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
93
- placeholderIds: (s.placeholderIds || '').trim(),
94
- intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
95
- enableCategoryAds: parseBool(s.enableCategoryAds, false),
96
- showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
97
- categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
98
- intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
99
- enableMessageAds: parseBool(s.enableMessageAds, false),
100
- showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
101
- messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
102
- messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
103
- excludedGroups: normalizeExcludedGroups(s.excludedGroups),
104
- };
107
+ _settingsCache = data;
105
108
  }
106
- return _settingsCache;
109
+ return _settingsCache || data;
107
110
  } finally {
108
111
  if (_settingsGen === gen) _settingsInflight = null;
109
112
  }
@@ -111,7 +114,7 @@ async function getSettings() {
111
114
  return _settingsInflight;
112
115
  }
113
116
 
114
- async function isUserExcluded(uid, excludedGroups) {
117
+ async function isUserExcluded(uid, excludedGroups, excludedSet) {
115
118
  if (!uid || !excludedGroups.length) return false;
116
119
 
117
120
  const key = `${uid}|${excludedGroups.join('|')}`;
@@ -119,16 +122,21 @@ async function isUserExcluded(uid, excludedGroups) {
119
122
  const hit = _excludeCache.get(key);
120
123
  if (hit && (t - hit.at) < EXCLUDE_TTL) return hit.value;
121
124
 
122
- const excludedSet = new Set(excludedGroups);
123
125
  const userGroups = await groups.getUserGroups([uid]);
124
126
  const value = (userGroups[0] || []).some(g => excludedSet.has(g.name));
125
127
 
126
128
  _excludeCache.set(key, { value, at: Date.now() });
127
129
  if (_excludeCache.size > 1000) {
128
- let n = 100;
129
- for (const k of _excludeCache.keys()) {
130
- if (!n--) break;
131
- _excludeCache.delete(k);
130
+ const expire = Date.now() - EXCLUDE_TTL;
131
+ for (const [k, v] of _excludeCache) {
132
+ if (v.at < expire) _excludeCache.delete(k);
133
+ }
134
+ if (_excludeCache.size > 900) {
135
+ let n = 100;
136
+ for (const k of _excludeCache.keys()) {
137
+ if (!n--) break;
138
+ _excludeCache.delete(k);
139
+ }
132
140
  }
133
141
  }
134
142
 
@@ -146,16 +154,13 @@ function clearCaches() {
146
154
  _excludeCache.clear();
147
155
  _inlineCfgNormal = null;
148
156
  _inlineCfgExcluded = null;
149
- _allGroupsGen++;
150
- _allGroupsCache = null;
151
- _allGroupsCacheAt = 0;
152
- _allGroupsInflight = null;
157
+ // _allGroupsCache is NOT cleared: the group list is independent of plugin settings.
153
158
  }
154
159
 
155
160
  // ── Client config ────────────────────────────────────────────────────────────
156
161
 
157
162
  function buildClientConfig(settings, excluded) {
158
- const { excludedGroups, ...rest } = settings;
163
+ const { excludedGroups, excludedSet, ...rest } = settings;
159
164
  return { excluded, ...rest };
160
165
  }
161
166
 
@@ -166,12 +171,20 @@ function serializeInlineConfig(cfg) {
166
171
  // ── Head injection ───────────────────────────────────────────────────────────
167
172
 
168
173
  const HEAD_PRECONNECTS = [
174
+ // Ezoic ad serving
169
175
  '<link rel="preconnect" href="https://g.ezoic.net" crossorigin>',
170
176
  '<link rel="preconnect" href="https://go.ezoic.net" crossorigin>',
171
177
  '<link rel="preconnect" href="https://securepubads.g.doubleclick.net" crossorigin>',
172
178
  '<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin>',
179
+ // Synchronous blocking scripts — preconnect to establish TCP+TLS before the parser reaches them
180
+ '<link rel="preconnect" href="https://cmp.gatekeeperconsent.com" crossorigin>',
181
+ '<link rel="preconnect" href="https://the.gatekeeperconsent.com" crossorigin>',
182
+ '<link rel="preconnect" href="https://www.ezojs.com" crossorigin>',
173
183
  '<link rel="dns-prefetch" href="https://g.ezoic.net">',
174
184
  '<link rel="dns-prefetch" href="https://securepubads.g.doubleclick.net">',
185
+ '<link rel="dns-prefetch" href="https://cmp.gatekeeperconsent.com">',
186
+ '<link rel="dns-prefetch" href="https://the.gatekeeperconsent.com">',
187
+ '<link rel="dns-prefetch" href="https://www.ezojs.com">',
175
188
  ].join('\n');
176
189
 
177
190
  // Working config: sa.min.js loaded synchronously (no async).
@@ -189,7 +202,7 @@ const EZOIC_SCRIPTS = [
189
202
  '<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
190
203
  '<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
191
204
  '<script data-cfasync="false" src="//www.ezojs.com/ezoic/sa.min.js"></script>',
192
- '<script src="//ezoicanalytics.com/analytics.js"></script>',
205
+ '<script async src="//ezoicanalytics.com/analytics.js"></script>',
193
206
  ].join('\n');
194
207
 
195
208
  // ── Hooks ────────────────────────────────────────────────────────────────────
@@ -217,7 +230,7 @@ plugin.injectEzoicHead = async (data) => {
217
230
 
218
231
  const settings = await getSettings();
219
232
  const uid = data.req?.uid ?? 0;
220
- const excluded = await isUserExcluded(uid, settings.excludedGroups);
233
+ const excluded = await isUserExcluded(uid, settings.excludedGroups, settings.excludedSet);
221
234
 
222
235
  if (!_inlineCfgNormal) {
223
236
  _inlineCfgNormal = serializeInlineConfig(buildClientConfig(settings, false));
@@ -272,7 +285,7 @@ plugin.init = async ({ router, middleware }) => {
272
285
  router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
273
286
  try {
274
287
  const settings = await getSettings();
275
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
288
+ const excluded = await isUserExcluded(req.uid, settings.excludedGroups, settings.excludedSet);
276
289
  res.json(buildClientConfig(settings, excluded));
277
290
  } catch (err) {
278
291
  console.error('[ezoic-infinite] config API error:', err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
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
@@ -32,7 +32,7 @@
32
32
  BURST_COOLDOWN_MS: 200,
33
33
  BLOCK_DURATION_MS: 1_000,
34
34
  SHOW_TIMEOUT_MS: 4_500,
35
- SHOW_RELEASE_MS: 150,
35
+ SHOW_RELEASE_MS: 50,
36
36
  RECYCLE_DELAY_MS: 50,
37
37
  UNCOLLAPSE_CHECK_MS: [500, 5_000],
38
38
  };
@@ -580,7 +580,8 @@
580
580
  scheduleEmptyCheck(id, t);
581
581
  setTimeout(() => { clearTimeout(timer); release(); }, TIMING.SHOW_RELEASE_MS);
582
582
  };
583
- typeof ez.cmd?.push === 'function' ? ez.cmd.push(doShow) : doShow();
583
+ const ezReady = ez.loadingStatus === 'complete';
584
+ typeof ez.cmd?.push === 'function' && !ezReady ? ez.cmd.push(doShow) : doShow();
584
585
  } catch (_) { clearTimeout(timer); release(); }
585
586
  }
586
587
 
@@ -7,10 +7,10 @@
7
7
  <div class="form-check mb-3">
8
8
  <input class="form-check-input" type="checkbox" id="enableBetweenAds" name="enableBetweenAds" {enableBetweenAds_checked}>
9
9
  <label class="form-check-label" for="enableBetweenAds">Activer les pubs entre les posts</label>
10
- <div class="form-check mt-2">
11
- <input class="form-check-input" type="checkbox" name="showFirstTopicAd" {showFirstTopicAd_checked} />
12
- <label class="form-check-label">Afficher une pub après le 1er sujet</label>
13
- </div>
10
+ <div class="form-check mt-2">
11
+ <input class="form-check-input" type="checkbox" name="showFirstTopicAd" {showFirstTopicAd_checked} />
12
+ <label class="form-check-label">Afficher une pub après le 1er sujet</label>
13
+ </div>
14
14
  </div>
15
15
 
16
16
  <div class="mb-3">
@@ -52,13 +52,13 @@
52
52
  <h4 class="mt-3">Pubs “message” entre les réponses</h4>
53
53
  <p class="form-text">Insère un bloc qui ressemble à un post, toutes les N réponses (dans une page topic).</p>
54
54
 
55
- <div class="form-check mb-3">
56
- <input class="form-check-input" type="checkbox" id="enableMessageAds" name="enableMessageAds" {enableMessageAds_checked}>
57
- <label class="form-check-label" for="enableMessageAds">Activer les pubs “message”</label>
58
- <div class="form-check mt-2">
59
- <input class="form-check-input" type="checkbox" name="showFirstMessageAd" {showFirstMessageAd_checked} />
60
- <label class="form-check-label">Afficher une pub après le 1er message</label>
61
- </div>
55
+ <div class=”form-check mb-3”>
56
+ <input class=”form-check-input type=”checkbox id=”enableMessageAds name=”enableMessageAds {enableMessageAds_checked}>
57
+ <label class=”form-check-label for=”enableMessageAds”>Activer les pubs “message”</label>
58
+ <div class=”form-check mt-2”>
59
+ <input class=”form-check-input type=”checkbox name=”showFirstMessageAd {showFirstMessageAd_checked} />
60
+ <label class=”form-check-label”>Afficher une pub après le 1er message</label>
61
+ </div>
62
62
  </div>
63
63
 
64
64
  <div class="mb-3">