nodebb-plugin-onekite-calendar 2.0.31 → 2.0.32
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 +0 -5
- package/lib/api.js +0 -60
- package/lib/scheduler.js +6 -41
- package/lib/widgets.js +1 -23
- package/package.json +1 -1
- package/pkg/package/public/client.js +1 -28
- package/plugin.json +1 -1
- package/public/client.js +1 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# Changelog – calendar-onekite
|
|
2
2
|
|
|
3
|
-
## 1.3.28
|
|
4
|
-
- Perf (temps réel) : debounce global des `refetchEvents()` (calendrier + widget) et **skip** quand l’onglet est masqué (refetch unique au retour de visibilité).
|
|
5
|
-
- Perf/Robustesse (multi-instance) : ajout d’un **lock distribué Redis** sur le tick scheduler (un seul runner exécute le cycle à un instant donné).
|
|
6
|
-
- Perf (API events) : cache Redis très court (**TTL 2s**) par utilisateur/permissions sur l’endpoint events pour absorber les rafales.
|
|
7
|
-
|
|
8
3
|
## 1.3.27
|
|
9
4
|
- Refactor : ajout d'un module `lib/utils.js` (format dates FR, lecture settings ACP, helpers listes/UIDs) pour supprimer les duplications.
|
|
10
5
|
- Cleanup : suppression d'un doublon de fonctions dans `helloassoWebhook.js` (formatFR + getReservationIdFromPayload).
|
package/lib/api.js
CHANGED
|
@@ -9,8 +9,6 @@ const user = require.main.require('./src/user');
|
|
|
9
9
|
const groups = require.main.require('./src/groups');
|
|
10
10
|
const db = require.main.require('./src/database');
|
|
11
11
|
const logger = require.main.require('./src/logger');
|
|
12
|
-
let cache = null;
|
|
13
|
-
try { cache = require.main.require('./src/cache'); } catch (e) { cache = null; }
|
|
14
12
|
|
|
15
13
|
const dbLayer = require('./db');
|
|
16
14
|
const { formatFR } = require('./utils');
|
|
@@ -479,42 +477,6 @@ function computeEtag(payload) {
|
|
|
479
477
|
return `W/"${hash}"`;
|
|
480
478
|
}
|
|
481
479
|
|
|
482
|
-
async function cacheGet(key) {
|
|
483
|
-
try {
|
|
484
|
-
if (!cache) return null;
|
|
485
|
-
if (typeof cache.get === 'function') {
|
|
486
|
-
if (cache.get.length >= 2) {
|
|
487
|
-
return await new Promise((resolve) => {
|
|
488
|
-
cache.get(key, (err, data) => resolve(err ? null : data));
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
return await cache.get(key);
|
|
492
|
-
}
|
|
493
|
-
} catch (e) {}
|
|
494
|
-
return null;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async function cacheSet(key, value, ttlSeconds) {
|
|
498
|
-
try {
|
|
499
|
-
if (!cache) return;
|
|
500
|
-
const ttl = Math.max(1, parseInt(ttlSeconds, 10) || 2);
|
|
501
|
-
if (typeof cache.set === 'function') {
|
|
502
|
-
if (cache.set.length >= 4) {
|
|
503
|
-
await new Promise((resolve) => {
|
|
504
|
-
cache.set(key, value, ttl, (err) => resolve(!err));
|
|
505
|
-
});
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
// Some NodeBB versions accept an options object.
|
|
509
|
-
try {
|
|
510
|
-
await cache.set(key, value, { ttl });
|
|
511
|
-
return;
|
|
512
|
-
} catch (e) {}
|
|
513
|
-
await cache.set(key, value, ttl);
|
|
514
|
-
}
|
|
515
|
-
} catch (e) {}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
480
|
api.getEvents = async function (req, res) {
|
|
519
481
|
const qStartRaw = (req && req.query && req.query.start !== undefined) ? String(req.query.start).trim() : '';
|
|
520
482
|
const qEndRaw = (req && req.query && req.query.end !== undefined) ? String(req.query.end).trim() : '';
|
|
@@ -534,22 +496,6 @@ api.getEvents = async function (req, res) {
|
|
|
534
496
|
const canSpecialCreate = req.uid ? await canCreateSpecial(req.uid, settings) : false;
|
|
535
497
|
const canSpecialDelete = req.uid ? await canDeleteSpecial(req.uid, settings) : false;
|
|
536
498
|
|
|
537
|
-
// Ultra-short Redis-backed cache (2s) to absorb bursts and reduce redundant renders.
|
|
538
|
-
// Keyed per uid + permissions because payload can include private fields.
|
|
539
|
-
const uidKey = req.uid ? String(req.uid) : '0';
|
|
540
|
-
const cacheKey = `calendar-onekite:events:${uidKey}:${canMod ? 'm' : 'u'}:${widgetMode ? 'w' : 'p'}:${qStartYmd || startTs}:${qEndYmd || endTs}`;
|
|
541
|
-
try {
|
|
542
|
-
const cached = await cacheGet(cacheKey);
|
|
543
|
-
if (cached && cached.payload && cached.etag) {
|
|
544
|
-
res.setHeader('ETag', cached.etag);
|
|
545
|
-
res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
|
|
546
|
-
if (String(req.headers['if-none-match'] || '') === String(cached.etag)) {
|
|
547
|
-
return res.status(304).end();
|
|
548
|
-
}
|
|
549
|
-
return res.json(cached.payload);
|
|
550
|
-
}
|
|
551
|
-
} catch (e) {}
|
|
552
|
-
|
|
553
499
|
// Fetch a wider window because an event can start before the query range
|
|
554
500
|
// and still overlap.
|
|
555
501
|
const wideStart = Math.max(0, startTs - 366 * 24 * 3600 * 1000);
|
|
@@ -688,12 +634,6 @@ api.getEvents = async function (req, res) {
|
|
|
688
634
|
});
|
|
689
635
|
|
|
690
636
|
const etag = computeEtag(out);
|
|
691
|
-
|
|
692
|
-
// Store in cache for a very short period (burst absorption).
|
|
693
|
-
try {
|
|
694
|
-
await cacheSet(cacheKey, { etag, payload: out }, 2);
|
|
695
|
-
} catch (e) {}
|
|
696
|
-
|
|
697
637
|
res.setHeader('ETag', etag);
|
|
698
638
|
res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
|
|
699
639
|
if (String(req.headers['if-none-match'] || '') === etag) {
|
package/lib/scheduler.js
CHANGED
|
@@ -8,43 +8,9 @@ const realtime = require('./realtime');
|
|
|
8
8
|
const nconf = require.main.require('nconf');
|
|
9
9
|
const groups = require.main.require('./src/groups');
|
|
10
10
|
const utils = require('./utils');
|
|
11
|
-
let redis = null;
|
|
12
|
-
try { redis = require.main.require('./src/redis'); } catch (e) { redis = null; }
|
|
13
11
|
|
|
14
12
|
let timer = null;
|
|
15
13
|
|
|
16
|
-
// Distributed lock (Redis) to avoid running the scheduler on multiple NodeBB instances.
|
|
17
|
-
// Safe no-op if Redis is not available.
|
|
18
|
-
async function acquireSchedulerLock(ttlMs) {
|
|
19
|
-
const key = 'calendar-onekite:scheduler:lock';
|
|
20
|
-
const value = `${process.pid}:${Date.now()}`;
|
|
21
|
-
const ttl = Math.max(1000, parseInt(ttlMs, 10) || 55000);
|
|
22
|
-
|
|
23
|
-
// Prefer Redis SET NX PX.
|
|
24
|
-
try {
|
|
25
|
-
const client = redis && (redis.client || redis);
|
|
26
|
-
if (client && typeof client.set === 'function') {
|
|
27
|
-
const res = await new Promise((resolve, reject) => {
|
|
28
|
-
// node_redis style
|
|
29
|
-
client.set(key, value, 'NX', 'PX', ttl, (err, out) => {
|
|
30
|
-
if (err) return reject(err);
|
|
31
|
-
resolve(out);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
return String(res || '').toUpperCase() === 'OK';
|
|
35
|
-
}
|
|
36
|
-
} catch (e) {
|
|
37
|
-
// ignore and fall back
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Fallback: in-process lock only.
|
|
41
|
-
if (global.__onekiteSchedulerInProcessLockUntil && Date.now() < global.__onekiteSchedulerInProcessLockUntil) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
global.__onekiteSchedulerInProcessLockUntil = Date.now() + ttl;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
14
|
// Some NodeBB database adapters don't expose setAdd/setRemove helpers.
|
|
49
15
|
// Use a safe "add once" guard to avoid crashing the scheduler.
|
|
50
16
|
async function addOnce(key, value) {
|
|
@@ -434,14 +400,13 @@ function start() {
|
|
|
434
400
|
// eslint-disable-next-line no-console
|
|
435
401
|
console.info('[calendar-onekite] Scheduler enabled');
|
|
436
402
|
timer = setInterval(() => {
|
|
437
|
-
(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
})().catch((err) => {
|
|
403
|
+
expirePending().catch((err) => {
|
|
404
|
+
// eslint-disable-next-line no-console
|
|
405
|
+
console.warn('[calendar-onekite] Scheduler error in expirePending', err && err.message ? err.message : err);
|
|
406
|
+
});
|
|
407
|
+
processAwaitingPayment().catch((err) => {
|
|
443
408
|
// eslint-disable-next-line no-console
|
|
444
|
-
console.warn('[calendar-onekite] Scheduler
|
|
409
|
+
console.warn('[calendar-onekite] Scheduler error in processAwaitingPayment', err && err.message ? err.message : err);
|
|
445
410
|
});
|
|
446
411
|
}, 60 * 1000);
|
|
447
412
|
}
|
package/lib/widgets.js
CHANGED
|
@@ -376,20 +376,13 @@ dateClick: function() { window.location.href = calUrl; },
|
|
|
376
376
|
let tRefetch = null;
|
|
377
377
|
const refetch = function () {
|
|
378
378
|
try {
|
|
379
|
-
// Skip work while tab is hidden; refetch once when visible again.
|
|
380
|
-
try {
|
|
381
|
-
if (typeof document !== 'undefined' && document && document.hidden) {
|
|
382
|
-
window.__oneKiteWidgetNeedsRefetch = true;
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
} catch (e) {}
|
|
386
379
|
if (tRefetch) return;
|
|
387
380
|
tRefetch = setTimeout(() => {
|
|
388
381
|
tRefetch = null;
|
|
389
382
|
try {
|
|
390
383
|
calendar.refetchEvents();
|
|
391
384
|
} catch (e) {}
|
|
392
|
-
},
|
|
385
|
+
}, 200);
|
|
393
386
|
} catch (e) {}
|
|
394
387
|
};
|
|
395
388
|
|
|
@@ -409,21 +402,6 @@ dateClick: function() { window.location.href = calUrl; },
|
|
|
409
402
|
};
|
|
410
403
|
socket.on('event:calendar-onekite.calendarUpdated', triggerAll);
|
|
411
404
|
socket.on('event:calendar-onekite.reservationUpdated', triggerAll);
|
|
412
|
-
|
|
413
|
-
// If updates occurred while hidden, refetch once when visible again.
|
|
414
|
-
try {
|
|
415
|
-
if (!window.__oneKiteWidgetVisibilityBound && typeof document !== 'undefined' && document && typeof document.addEventListener === 'function') {
|
|
416
|
-
window.__oneKiteWidgetVisibilityBound = true;
|
|
417
|
-
document.addEventListener('visibilitychange', () => {
|
|
418
|
-
try {
|
|
419
|
-
if (!document.hidden && window.__oneKiteWidgetNeedsRefetch) {
|
|
420
|
-
window.__oneKiteWidgetNeedsRefetch = false;
|
|
421
|
-
triggerAll();
|
|
422
|
-
}
|
|
423
|
-
} catch (e) {}
|
|
424
|
-
}, { passive: true });
|
|
425
|
-
}
|
|
426
|
-
} catch (e) {}
|
|
427
405
|
}
|
|
428
406
|
} catch (e) {}
|
|
429
407
|
|
package/package.json
CHANGED
|
@@ -533,40 +533,13 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
533
533
|
function scheduleRefetch(cal) {
|
|
534
534
|
try {
|
|
535
535
|
if (!cal || typeof cal.refetchEvents !== 'function') return;
|
|
536
|
-
|
|
537
|
-
// Avoid refetching while the tab is hidden; do it once when visible again.
|
|
538
|
-
try {
|
|
539
|
-
if (typeof document !== 'undefined' && document && document.hidden) {
|
|
540
|
-
window.__onekiteNeedsRefetch = true;
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
} catch (e) {}
|
|
544
|
-
|
|
545
536
|
clearTimeout(window.__onekiteRefetchTimer);
|
|
546
537
|
window.__onekiteRefetchTimer = setTimeout(() => {
|
|
547
538
|
try { cal.refetchEvents(); } catch (e) {}
|
|
548
|
-
},
|
|
539
|
+
}, 150);
|
|
549
540
|
} catch (e) {}
|
|
550
541
|
}
|
|
551
542
|
|
|
552
|
-
// If updates occurred while hidden, refetch once when visible again.
|
|
553
|
-
try {
|
|
554
|
-
if (!window.__onekiteVisibilityBound && typeof document !== 'undefined' && document && typeof document.addEventListener === 'function') {
|
|
555
|
-
window.__onekiteVisibilityBound = true;
|
|
556
|
-
document.addEventListener('visibilitychange', () => {
|
|
557
|
-
try {
|
|
558
|
-
if (!document.hidden && window.__onekiteNeedsRefetch) {
|
|
559
|
-
window.__onekiteNeedsRefetch = false;
|
|
560
|
-
const cal = window.oneKiteCalendar;
|
|
561
|
-
if (cal && typeof cal.refetchEvents === 'function') {
|
|
562
|
-
scheduleRefetch(cal);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
} catch (e) {}
|
|
566
|
-
}, { passive: true });
|
|
567
|
-
}
|
|
568
|
-
} catch (e) {}
|
|
569
|
-
|
|
570
543
|
async function fetchJsonCached(url, opts) {
|
|
571
544
|
const cached = jsonCache.get(url);
|
|
572
545
|
const headers = Object.assign({}, (opts && opts.headers) || {});
|
package/plugin.json
CHANGED
package/public/client.js
CHANGED
|
@@ -543,40 +543,13 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
543
543
|
function scheduleRefetch(cal) {
|
|
544
544
|
try {
|
|
545
545
|
if (!cal || typeof cal.refetchEvents !== 'function') return;
|
|
546
|
-
|
|
547
|
-
// Avoid refetching while the tab is hidden; do it once when visible again.
|
|
548
|
-
try {
|
|
549
|
-
if (typeof document !== 'undefined' && document && document.hidden) {
|
|
550
|
-
window.__onekiteNeedsRefetch = true;
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
} catch (e) {}
|
|
554
|
-
|
|
555
546
|
clearTimeout(window.__onekiteRefetchTimer);
|
|
556
547
|
window.__onekiteRefetchTimer = setTimeout(() => {
|
|
557
548
|
try { cal.refetchEvents(); } catch (e) {}
|
|
558
|
-
},
|
|
549
|
+
}, 150);
|
|
559
550
|
} catch (e) {}
|
|
560
551
|
}
|
|
561
552
|
|
|
562
|
-
// If updates occurred while hidden, refetch once when visible again.
|
|
563
|
-
try {
|
|
564
|
-
if (!window.__onekiteVisibilityBound && typeof document !== 'undefined' && document && typeof document.addEventListener === 'function') {
|
|
565
|
-
window.__onekiteVisibilityBound = true;
|
|
566
|
-
document.addEventListener('visibilitychange', () => {
|
|
567
|
-
try {
|
|
568
|
-
if (!document.hidden && window.__onekiteNeedsRefetch) {
|
|
569
|
-
window.__onekiteNeedsRefetch = false;
|
|
570
|
-
const cal = window.oneKiteCalendar;
|
|
571
|
-
if (cal && typeof cal.refetchEvents === 'function') {
|
|
572
|
-
scheduleRefetch(cal);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
} catch (e) {}
|
|
576
|
-
}, { passive: true });
|
|
577
|
-
}
|
|
578
|
-
} catch (e) {}
|
|
579
|
-
|
|
580
553
|
async function fetchJsonCached(url, opts) {
|
|
581
554
|
const cached = jsonCache.get(url);
|
|
582
555
|
const headers = Object.assign({}, (opts && opts.headers) || {});
|