nodebb-plugin-onekite-calendar 2.0.38 → 2.0.40

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.0.38
2
+ - Refactor : factorisation du code client (helpers headers/CSRF) et simplification du widget.
3
+ - Widget : suppression des fallbacks de polling/visibilitychange (mise à jour via sockets uniquement) + conservation du no-cache pour éviter les 304.
4
+
1
5
  ## 2.0.37
2
6
  - Les rappels validateurs et notifications d’expiration ne sont envoyés qu’aux membres du/des groupe(s) "personnes notifiées" (notifyGroups).
3
7
 
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const groups = require.main.require('./src/groups');
4
+
5
+ async function getGroupNameBySlug(slug) {
6
+ const fn = groups && groups.getGroupNameByGroupSlug;
7
+ if (typeof fn !== 'function') return null;
8
+
9
+ const id = String(slug || '').trim();
10
+ if (!id) return null;
11
+
12
+ try {
13
+ const maybe = fn(id);
14
+ if (maybe && typeof maybe.then === 'function') {
15
+ return await maybe;
16
+ }
17
+ if (typeof maybe === 'string') {
18
+ return maybe;
19
+ }
20
+ } catch (e) {
21
+ // fall through to callback form
22
+ }
23
+
24
+ return await new Promise((resolve) => {
25
+ try {
26
+ fn(id, (err, name) => resolve(err ? null : name));
27
+ } catch (e) {
28
+ resolve(null);
29
+ }
30
+ });
31
+ }
@@ -326,6 +326,7 @@ async function handler(req, res, next) {
326
326
  itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
327
327
  dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
328
328
  paymentReceiptUrl: r.paymentReceiptUrl || '',
329
+ pickupTime: r.pickupTime || '',
329
330
  pickupAddress: r.pickupAddress || '',
330
331
  mapUrl,
331
332
  });
package/lib/realtime.js CHANGED
@@ -24,10 +24,6 @@ function emitCalendarUpdated(payload) {
24
24
 
25
25
  // New event name (generic).
26
26
  server.sockets.emit('event:calendar-onekite.calendarUpdated', payload || {});
27
-
28
- // Backwards compatible event name (older clients only listened to this).
29
- // Keep it emitted for *any* calendar mutation, not just reservation status.
30
- server.sockets.emit('event:calendar-onekite.reservationUpdated', payload || {});
31
27
  } catch (e) {}
32
28
  }
33
29
 
package/lib/widgets.js CHANGED
@@ -271,10 +271,31 @@ widgets.renderTwoWeeksWidget = async function (data) {
271
271
  wrap.appendChild(dot);
272
272
  return { domNodes: [wrap] };
273
273
  },
274
+ // IMPORTANT: disable HTTP caching here.
275
+ // Some NodeBB setups add ETag/304 on API routes; fetch(...).json() will
276
+ // then fail because 304 responses have no body, which prevents the widget
277
+ // from updating on refetchEvents(). We add a cache-buster and request
278
+ // no-store to guarantee a fresh JSON payload.
274
279
  events: function(info, successCallback, failureCallback) {
275
- const qs = new URLSearchParams({ start: info.startStr, end: info.endStr, widget: '1' });
276
- fetch(eventsEndpoint + '?' + qs.toString(), { credentials: 'same-origin' })
277
- .then((r) => r.json())
280
+ const qs = new URLSearchParams({
281
+ start: info.startStr,
282
+ end: info.endStr,
283
+ widget: '1',
284
+ _: String(Date.now()),
285
+ });
286
+ fetch(eventsEndpoint + '?' + qs.toString(), {
287
+ credentials: 'same-origin',
288
+ cache: 'no-store',
289
+ headers: { 'Cache-Control': 'no-cache' },
290
+ })
291
+ .then((r) => {
292
+ if (!r.ok) {
293
+ const err = new Error(String(r.status || 'fetch_failed'));
294
+ err.status = r.status;
295
+ throw err;
296
+ }
297
+ return r.json();
298
+ })
278
299
  .then((json) => successCallback(json || []))
279
300
  .catch((e) => failureCallback(e));
280
301
  },
@@ -371,19 +392,19 @@ dateClick: function() { window.location.href = calUrl; },
371
392
  calendar.render();
372
393
 
373
394
  // Real-time refresh for the widget (same server events as the main calendar)
395
+ // We intentionally rely on sockets only (no periodic polling fallback):
396
+ // - keeps the widget lightweight
397
+ // - avoids pointless API traffic
398
+ // - updates are already broadcast server-side across NodeBB instances
374
399
  try {
375
400
  // Debounce per widget instance
376
401
  let tRefetch = null;
377
402
  const refetch = function () {
378
- try {
379
- if (tRefetch) return;
380
- tRefetch = setTimeout(() => {
381
- tRefetch = null;
382
- try {
383
- calendar.refetchEvents();
384
- } catch (e) {}
385
- }, 200);
386
- } catch (e) {}
403
+ if (tRefetch) return;
404
+ tRefetch = setTimeout(() => {
405
+ tRefetch = null;
406
+ try { calendar.refetchEvents(); } catch (e) {}
407
+ }, 200);
387
408
  };
388
409
 
389
410
  // Register refetcher and bind socket listeners once per page
@@ -393,15 +414,12 @@ dateClick: function() { window.location.href = calUrl; },
393
414
  if (!window.__oneKiteWidgetSocketBound && typeof socket !== 'undefined' && socket && typeof socket.on === 'function') {
394
415
  window.__oneKiteWidgetSocketBound = true;
395
416
  const triggerAll = function () {
396
- try {
397
- const list = window.__oneKiteWidgetRefetchers || [];
398
- for (let i = 0; i < list.length; i += 1) {
399
- try { list[i](); } catch (e) {}
400
- }
401
- } catch (e) {}
417
+ const list = window.__oneKiteWidgetRefetchers || [];
418
+ for (let i = 0; i < list.length; i += 1) {
419
+ try { list[i](); } catch (e) {}
420
+ }
402
421
  };
403
422
  socket.on('event:calendar-onekite.calendarUpdated', triggerAll);
404
- socket.on('event:calendar-onekite.reservationUpdated', triggerAll);
405
423
  }
406
424
  } catch (e) {}
407
425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.38",
3
+ "version": "2.0.40",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -39,5 +39,5 @@
39
39
  "acpScripts": [
40
40
  "public/admin.js"
41
41
  ],
42
- "version": "2.0.38"
42
+ "version": "2.0.40"
43
43
  }
package/public/admin.js CHANGED
@@ -109,48 +109,32 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
109
109
  }
110
110
 
111
111
  async function fetchJson(url, opts) {
112
- const res = await fetch(url, {
113
- credentials: 'same-origin',
114
- headers: (() => {
115
- const headers = { 'Content-Type': 'application/json' };
116
- const token =
112
+ const getCsrfToken = () => {
113
+ try {
114
+ return (
117
115
  (window.config && (window.config.csrf_token || window.config.csrfToken)) ||
118
116
  (window.ajaxify && window.ajaxify.data && window.ajaxify.data.csrf_token) ||
119
117
  (document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
120
118
  (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
121
119
  (typeof app !== 'undefined' && app && app.csrfToken) ||
122
- null;
123
- if (token) headers['x-csrf-token'] = token;
124
- return headers;
125
- })(),
120
+ null
121
+ );
122
+ } catch (e) {
123
+ return null;
124
+ }
125
+ };
126
+
127
+ const headers = { 'Content-Type': 'application/json' };
128
+ const token = getCsrfToken();
129
+ if (token) headers['x-csrf-token'] = token;
130
+
131
+ const res = await fetch(url, {
132
+ credentials: 'same-origin',
133
+ headers,
126
134
  ...opts,
127
135
  });
128
136
 
129
-
130
137
  if (!res.ok) {
131
- // NodeBB versions differ: some expose admin APIs under /api/admin instead of /api/v3/admin
132
- if (res.status === 404 && typeof url === 'string' && url.includes('/api/v3/admin/')) {
133
- const altUrl = url.replace('/api/v3/admin/', '/api/admin/');
134
- const res2 = await fetch(altUrl, {
135
- credentials: 'same-origin',
136
- headers: (() => {
137
- const headers = { 'Content-Type': 'application/json' };
138
- const token =
139
- (window.config && (window.config.csrf_token || window.config.csrfToken)) ||
140
- (window.ajaxify && window.ajaxify.data && window.ajaxify.data.csrf_token) ||
141
- (document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
142
- (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
143
- (typeof app !== 'undefined' && app && app.csrfToken) ||
144
- null;
145
- if (token) headers['x-csrf-token'] = token;
146
- return headers;
147
- })(),
148
- ...opts,
149
- });
150
- if (res2.ok) {
151
- return await res2.json();
152
- }
153
- }
154
138
  const text = await res.text().catch(() => '');
155
139
  throw new Error(`${res.status} ${text}`);
156
140
  }
package/public/client.js CHANGED
@@ -138,6 +138,28 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
138
138
  .replace(/'/g, '&#39;');
139
139
  }
140
140
 
141
+ function getCsrfToken() {
142
+ try {
143
+ return (
144
+ (window.config && (window.config.csrf_token || window.config.csrfToken)) ||
145
+ (window.ajaxify && window.ajaxify.data && window.ajaxify.data.csrf_token) ||
146
+ (document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
147
+ (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
148
+ (typeof app !== 'undefined' && app && app.csrfToken) ||
149
+ null
150
+ );
151
+ } catch (e) {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ function jsonHeaders(extra) {
157
+ const headers = Object.assign({ 'Content-Type': 'application/json' }, extra || {});
158
+ const token = getCsrfToken();
159
+ if (token) headers['x-csrf-token'] = token;
160
+ return headers;
161
+ }
162
+
141
163
  function pad2(n) { return String(n).padStart(2, '0'); }
142
164
 
143
165
  function toDateInputValue(d) {
@@ -502,18 +524,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
502
524
  async function fetchJson(url, opts) {
503
525
  const res = await fetch(url, {
504
526
  credentials: 'same-origin',
505
- headers: (() => {
506
- const headers = { 'Content-Type': 'application/json' };
507
- const token =
508
- (window.config && (window.config.csrf_token || window.config.csrfToken)) ||
509
- (window.ajaxify && window.ajaxify.data && window.ajaxify.data.csrf_token) ||
510
- (document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
511
- (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
512
- (typeof app !== 'undefined' && app && app.csrfToken) ||
513
- null;
514
- if (token) headers['x-csrf-token'] = token;
515
- return headers;
516
- })(),
527
+ headers: jsonHeaders((opts && opts.headers) || {}),
517
528
  ...opts,
518
529
  });
519
530
  if (!res.ok) {
@@ -545,6 +556,9 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
545
556
  if (!cal || typeof cal.refetchEvents !== 'function') return;
546
557
  clearTimeout(window.__onekiteRefetchTimer);
547
558
  window.__onekiteRefetchTimer = setTimeout(() => {
559
+ // Clear the in-memory JSON cache so status/color changes are fetched
560
+ // immediately (otherwise an unchanged ETag may keep stale colors).
561
+ try { invalidateEventsCache(); } catch (e) {}
548
562
  try { cal.refetchEvents(); } catch (e) {}
549
563
  }, 150);
550
564
  } catch (e) {}
@@ -560,19 +574,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
560
574
  try {
561
575
  res = await fetch(url, {
562
576
  credentials: 'same-origin',
563
- headers: (() => {
564
- // reuse csrf header builder (fetchJson) by calling it indirectly
565
- const base = { 'Content-Type': 'application/json' };
566
- const token =
567
- (window.config && (window.config.csrf_token || window.config.csrfToken)) ||
568
- (window.ajaxify && window.ajaxify.data && window.ajaxify.data.csrf_token) ||
569
- (document.querySelector('meta[name="csrf-token"]') && document.querySelector('meta[name="csrf-token"]').getAttribute('content')) ||
570
- (document.querySelector('meta[name="csrf_token"]') && document.querySelector('meta[name="csrf_token"]').getAttribute('content')) ||
571
- (typeof app !== 'undefined' && app && app.csrfToken) ||
572
- null;
573
- if (token) base['x-csrf-token'] = token;
574
- return Object.assign(base, headers);
575
- })(),
577
+ headers: jsonHeaders(headers),
576
578
  ...opts,
577
579
  });
578
580
  } catch (e) {
@@ -2380,12 +2382,6 @@ try {
2380
2382
  scheduleRefetch(cal);
2381
2383
  } catch (e) {}
2382
2384
  });
2383
- socket.on('event:calendar-onekite.reservationUpdated', function () {
2384
- try {
2385
- const cal = window.oneKiteCalendar;
2386
- scheduleRefetch(cal);
2387
- } catch (e) {}
2388
- });
2389
2385
  }
2390
2386
  } catch (e) {}
2391
2387