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 +4 -0
- package/lib/group-helpers.js +31 -0
- package/lib/helloassoWebhook.js +1 -0
- package/lib/realtime.js +0 -4
- package/lib/widgets.js +37 -19
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/admin.js +17 -33
- package/public/client.js +27 -31
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
|
+
}
|
package/lib/helloassoWebhook.js
CHANGED
|
@@ -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({
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
tRefetch =
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
package/plugin.json
CHANGED
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
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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, ''');
|
|
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
|
|