@botfabrik/engine-webclient 4.96.6 → 4.96.10
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/README.md +5 -0
- package/dist/auth/auth-pages.d.ts +6 -0
- package/dist/auth/auth-pages.js +102 -0
- package/dist/auth/index.d.ts +11 -0
- package/dist/auth/index.js +145 -0
- package/dist/auth/relay-state.d.ts +2 -0
- package/dist/auth/relay-state.js +39 -0
- package/dist/auth/ttl-cache.d.ts +14 -0
- package/dist/auth/ttl-cache.js +52 -0
- package/dist/client/assets/index-BKoo63hO.js +124 -0
- package/dist/client/assets/{index-DyL1BpAK.js.map → index-BKoo63hO.js.map} +1 -1
- package/dist/client/assets/{index-CEalwQZJ.css → index-BdXpbt-o.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +4 -0
- package/dist/createSessionInfo.d.ts +3 -3
- package/dist/createSessionInfo.js +35 -9
- package/dist/createSessionInfo.test.js +27 -3
- package/dist/index.d.ts +3 -140
- package/dist/index.js +90 -79
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +2 -2
- package/dist/requestSessionData.d.ts +1 -2
- package/dist/requestSessionData.js +2 -2
- package/dist/speechToText.d.ts +1 -5
- package/dist/types.d.ts +170 -0
- package/dist/types.js +8 -0
- package/package.json +9 -5
- package/dist/client/assets/index-DyL1BpAK.js +0 -124
package/README.md
CHANGED
|
@@ -287,6 +287,10 @@ Durch das Bewerten des Chats wird die Action `core.conversation.rating.from.gues
|
|
|
287
287
|
Ermöglicht die Option, den Chat-Client in einem eigenständigen Tab oder Fenster zu öffnen.
|
|
288
288
|
Wenn diese Option auf `true` gesetzt ist, wird eine Button angezeigt, die es den Benutzern ermöglicht, den Chat ausserhalb eines eingebetteten Iframe in einem neuen Browser-Tab öffnen können.
|
|
289
289
|
|
|
290
|
+
### auth (optional)
|
|
291
|
+
|
|
292
|
+
Diese Konfiguration stellt sicher, dass sich Benutzer vor der Nutzung des Chats über SAML authentifizieren. Ist die Funktion aktiviert, erscheint automatisch der Startbildschirm. Beim Klick auf „Chat starten“ öffnet sich das Login-Fenster des Identity Providers in einem neuen Fenster. Nach erfolgreicher Anmeldung schliesst sich dieses automatisch und der Chat wird gestartet. Im Rahmen des Logins werden die E-Mail-Adresse sowie der Vor- und Nachname des Benutzers in der Session gespeichert.
|
|
293
|
+
|
|
290
294
|
### fabVisible (optional, true [default] | false)
|
|
291
295
|
|
|
292
296
|
Falls dieses Property auf `false` ist, erscheint z.B. der FAB (Floating Action Button) auf der Webseite nicht. Der Chatbot kann via FAB nur mittels Bookmarklet verwendet werden.
|
|
@@ -568,4 +572,5 @@ Erlaub das Registrieren von Event-Handlers.
|
|
|
568
572
|
chatbot.subscribe('ON_CHAT_WINDOW_STATE_CHANGE', function onChange(open) {
|
|
569
573
|
console.log('chat window is ' + open ? 'open' : 'close')
|
|
570
574
|
})
|
|
575
|
+
|
|
571
576
|
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Response } from 'express';
|
|
2
|
+
type Lang = 'de' | 'en' | 'fr' | 'it';
|
|
3
|
+
export declare function setAuthPageHeaders(res: Response): void;
|
|
4
|
+
export declare function renderAuthSuccessPage(lang: Lang): string;
|
|
5
|
+
export declare function renderAuthErrorPage(lang: Lang): string;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setAuthPageHeaders = setAuthPageHeaders;
|
|
4
|
+
exports.renderAuthSuccessPage = renderAuthSuccessPage;
|
|
5
|
+
exports.renderAuthErrorPage = renderAuthErrorPage;
|
|
6
|
+
const STRINGS = {
|
|
7
|
+
de: {
|
|
8
|
+
successTitle: 'Login erfolgreich',
|
|
9
|
+
successText: 'Dieses Fenster wird automatisch geschlossen.',
|
|
10
|
+
closeNow: 'Jetzt schliessen',
|
|
11
|
+
errorTitle: 'Login fehlgeschlagen',
|
|
12
|
+
errorText: 'Beim Anmelden ist ein Fehler aufgetreten. Bitte versuche es erneut.',
|
|
13
|
+
errorHint: 'Du kannst dieses Fenster schliessen und es erneut versuchen.',
|
|
14
|
+
closeWindow: 'Fenster schliessen',
|
|
15
|
+
},
|
|
16
|
+
en: {
|
|
17
|
+
successTitle: 'Login successful',
|
|
18
|
+
successText: 'This window will close automatically.',
|
|
19
|
+
closeNow: 'Close now',
|
|
20
|
+
errorTitle: 'Login failed',
|
|
21
|
+
errorText: 'An error occurred during sign-in. Please try again.',
|
|
22
|
+
errorHint: 'You can close this window and try again.',
|
|
23
|
+
closeWindow: 'Close window',
|
|
24
|
+
},
|
|
25
|
+
fr: {
|
|
26
|
+
successTitle: 'Connexion reussie',
|
|
27
|
+
successText: 'Cette fenetre se fermera automatiquement.',
|
|
28
|
+
closeNow: 'Fermer maintenant',
|
|
29
|
+
errorTitle: 'Echec de la connexion',
|
|
30
|
+
errorText: 'Une erreur s’est produite lors de la connexion. Veuillez reessayer.',
|
|
31
|
+
errorHint: 'Vous pouvez fermer cette fenetre et reessayer.',
|
|
32
|
+
closeWindow: 'Fermer la fenetre',
|
|
33
|
+
},
|
|
34
|
+
it: {
|
|
35
|
+
successTitle: 'Accesso riuscito',
|
|
36
|
+
successText: 'Questa finestra si chiudera automaticamente.',
|
|
37
|
+
closeNow: 'Chiudi ora',
|
|
38
|
+
errorTitle: 'Accesso non riuscito',
|
|
39
|
+
errorText: 'Si è verificato un errore durante l’accesso. Riprova.',
|
|
40
|
+
errorHint: 'Puoi chiudere questa finestra e riprovare.',
|
|
41
|
+
closeWindow: 'Chiudi finestra',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function setAuthPageHeaders(res) {
|
|
45
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
46
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'; img-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'");
|
|
47
|
+
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
48
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
49
|
+
}
|
|
50
|
+
function renderAuthSuccessPage(lang) {
|
|
51
|
+
const t = STRINGS[lang];
|
|
52
|
+
return `<!doctype html>
|
|
53
|
+
<html lang="${lang}">
|
|
54
|
+
<meta charset="utf-8">
|
|
55
|
+
<title>${t.successTitle}</title>
|
|
56
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
57
|
+
<style>
|
|
58
|
+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 0; padding: 2rem; }
|
|
59
|
+
.box { max-width: 520px; margin: 15vh auto 0; text-align: center; }
|
|
60
|
+
h1 { font-size: 1.25rem; margin: 0 0 .75rem; }
|
|
61
|
+
p { color: #444; margin: 0 0 1rem; }
|
|
62
|
+
button { border: 0; padding: .7rem 1rem; border-radius: .5rem; cursor: pointer; }
|
|
63
|
+
</style>
|
|
64
|
+
<body>
|
|
65
|
+
<div class="box" role="status" aria-live="polite">
|
|
66
|
+
<h1>${t.successTitle}</h1>
|
|
67
|
+
<p>${t.successText}</p>
|
|
68
|
+
<button onclick="window.close()">${t.closeNow}</button>
|
|
69
|
+
</div>
|
|
70
|
+
<script>
|
|
71
|
+
try { window.close(); } catch (e) {}
|
|
72
|
+
setTimeout(() => { try { window.close(); } catch (e) {} }, 2000);
|
|
73
|
+
</script>
|
|
74
|
+
</body>
|
|
75
|
+
</html>`;
|
|
76
|
+
}
|
|
77
|
+
function renderAuthErrorPage(lang) {
|
|
78
|
+
const t = STRINGS[lang];
|
|
79
|
+
return `<!doctype html>
|
|
80
|
+
<html lang="${lang}">
|
|
81
|
+
<meta charset="utf-8">
|
|
82
|
+
<title>${t.errorTitle}</title>
|
|
83
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
84
|
+
<style>
|
|
85
|
+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 0; padding: 2rem; background: #fff; }
|
|
86
|
+
.box { max-width: 560px; margin: 15vh auto 0; text-align: center; }
|
|
87
|
+
h1 { color: #b00020; font-size: 1.25rem; margin: 0 0 .75rem; }
|
|
88
|
+
p { color: #444; margin: 0 0 1rem; }
|
|
89
|
+
.hint { color: #666; font-size: .95rem; }
|
|
90
|
+
button { background: #111; color: #fff; border: 0; padding: .75rem 1rem; border-radius: .5rem; cursor: pointer; }
|
|
91
|
+
button:focus { outline: 2px solid #000; outline-offset: 2px; }
|
|
92
|
+
</style>
|
|
93
|
+
<body>
|
|
94
|
+
<div class="box" role="alert" aria-live="assertive">
|
|
95
|
+
<h1>${t.errorTitle}</h1>
|
|
96
|
+
<p>${t.errorText}</p>
|
|
97
|
+
<p class="hint">${t.errorHint}</p>
|
|
98
|
+
<button type="button" onclick="window.close()" aria-label="${t.closeWindow}">${t.closeWindow}</button>
|
|
99
|
+
</div>
|
|
100
|
+
</body>
|
|
101
|
+
</html>`;
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BotInstance, Logger } from '@botfabrik/engine-domain';
|
|
2
|
+
import type { Namespace } from 'socket.io';
|
|
3
|
+
import type { Auth } from '../types';
|
|
4
|
+
export type AuthenticatedUser = {
|
|
5
|
+
email: string;
|
|
6
|
+
firstName: string | undefined;
|
|
7
|
+
lastName: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
export declare function setUpSamlAuth(bot: BotInstance, auth: Auth, clientName: string, nsp: Namespace): void;
|
|
10
|
+
export declare function storeLoginRequestToken(loginRequestToken: string, socketId: string): void;
|
|
11
|
+
export declare function verifyLoginToken(token: string | undefined, auth: Auth | undefined, logger: Logger): AuthenticatedUser | undefined;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setUpSamlAuth = setUpSamlAuth;
|
|
7
|
+
exports.storeLoginRequestToken = storeLoginRequestToken;
|
|
8
|
+
exports.verifyLoginToken = verifyLoginToken;
|
|
9
|
+
const passport_saml_1 = require("@node-saml/passport-saml");
|
|
10
|
+
const express_1 = __importDefault(require("express"));
|
|
11
|
+
const jsonwebtoken_1 = require("jsonwebtoken");
|
|
12
|
+
const passport_1 = __importDefault(require("passport"));
|
|
13
|
+
const auth_pages_1 = require("./auth-pages");
|
|
14
|
+
const relay_state_1 = require("./relay-state");
|
|
15
|
+
const ttl_cache_1 = require("./ttl-cache");
|
|
16
|
+
const E_MAIL_CLAIM = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress';
|
|
17
|
+
const FIRST_NAME_CLAIM = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname';
|
|
18
|
+
const LAST_NAME_CLAIM = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname';
|
|
19
|
+
const loginTokenCache = new ttl_cache_1.TtlCache({
|
|
20
|
+
defaultTtlMs: 5 * 60 * 1000, // 5 minutes
|
|
21
|
+
});
|
|
22
|
+
function setUpSamlAuth(bot, auth, clientName, nsp) {
|
|
23
|
+
bot.logger.info(`Setting up SAML authentication for ${clientName}`);
|
|
24
|
+
const strategyName = `${clientName}-saml`;
|
|
25
|
+
const callbackUrl = `/${clientName}/auth/saml/login/callback`;
|
|
26
|
+
const samlStrategy = new passport_saml_1.Strategy({
|
|
27
|
+
callbackUrl: `${bot.webserver.baseUrl}${callbackUrl}`,
|
|
28
|
+
entryPoint: auth.saml.entryPoint,
|
|
29
|
+
issuer: auth.saml.issuer,
|
|
30
|
+
idpCert: fixUnwantedEscapeCharacters(auth.saml.idpCert),
|
|
31
|
+
...(auth.saml.options || {}),
|
|
32
|
+
}, signonVerify, logoutVerify);
|
|
33
|
+
passport_1.default.use(strategyName, samlStrategy);
|
|
34
|
+
bot.webserver.express.get(`/${clientName}/auth/login`, (req, res, next) => {
|
|
35
|
+
const { loginRequestToken } = req.query;
|
|
36
|
+
const relayState = (0, relay_state_1.signRelayState)(String(loginRequestToken), auth.jwtSecret);
|
|
37
|
+
const options = {
|
|
38
|
+
session: false,
|
|
39
|
+
additionalParams: { RelayState: relayState },
|
|
40
|
+
};
|
|
41
|
+
const authenticateFn = passport_1.default.authenticate(strategyName, options);
|
|
42
|
+
authenticateFn(req, res, next);
|
|
43
|
+
});
|
|
44
|
+
bot.webserver.express.post(callbackUrl, express_1.default.urlencoded({ extended: false }), (req, res, next) => {
|
|
45
|
+
const authenticatorFn = passport_1.default.authenticate(strategyName, { session: false }, (err, user) => {
|
|
46
|
+
const lang = getLang(req);
|
|
47
|
+
(0, auth_pages_1.setAuthPageHeaders)(res);
|
|
48
|
+
try {
|
|
49
|
+
if (err) {
|
|
50
|
+
bot.logger.error('SAML callback error:', err);
|
|
51
|
+
return res.status(500).send((0, auth_pages_1.renderAuthErrorPage)(lang));
|
|
52
|
+
}
|
|
53
|
+
const relayState = req.body?.RelayState;
|
|
54
|
+
if (!relayState) {
|
|
55
|
+
bot.logger.warn('Missing RelayState');
|
|
56
|
+
return res.status(400).send((0, auth_pages_1.renderAuthErrorPage)(lang));
|
|
57
|
+
}
|
|
58
|
+
const loginRequestToken = (0, relay_state_1.verifyRelayState)(relayState, auth.jwtSecret);
|
|
59
|
+
const { socketId } = consumeLoginRequestToken(loginRequestToken);
|
|
60
|
+
if (!user) {
|
|
61
|
+
return res.status(401).send((0, auth_pages_1.renderAuthErrorPage)(lang));
|
|
62
|
+
}
|
|
63
|
+
const loginToken = (0, jsonwebtoken_1.sign)(user, auth.jwtSecret, {
|
|
64
|
+
expiresIn: '1m',
|
|
65
|
+
});
|
|
66
|
+
const socket = nsp.sockets.get(socketId);
|
|
67
|
+
if (socket) {
|
|
68
|
+
socket.emit('login-success', { loginToken });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
bot.logger.warn('Target socket not found', { socketId });
|
|
72
|
+
}
|
|
73
|
+
return res.send((0, auth_pages_1.renderAuthSuccessPage)(lang));
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
bot.logger.error('Callback handling failure:', e);
|
|
77
|
+
return res.status(500).send((0, auth_pages_1.renderAuthErrorPage)(lang));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
authenticatorFn(req, res, next);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function storeLoginRequestToken(loginRequestToken, socketId) {
|
|
84
|
+
// associate socketId with loginRequestToken and store it in memory cache
|
|
85
|
+
loginTokenCache.set(loginRequestToken, { loginRequestToken, socketId });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Retrieve the socketId associated with a loginRequestToken.
|
|
89
|
+
* @param loginRequestToken
|
|
90
|
+
* @returns socketId
|
|
91
|
+
*/
|
|
92
|
+
function consumeLoginRequestToken(loginRequestToken) {
|
|
93
|
+
const cachedLoginRequest = loginTokenCache.get(loginRequestToken);
|
|
94
|
+
if (!cachedLoginRequest) {
|
|
95
|
+
throw new Error('Invalid login request token');
|
|
96
|
+
}
|
|
97
|
+
loginTokenCache.delete(loginRequestToken);
|
|
98
|
+
return { socketId: cachedLoginRequest.socketId };
|
|
99
|
+
}
|
|
100
|
+
function verifyLoginToken(token, auth, logger) {
|
|
101
|
+
try {
|
|
102
|
+
if (auth) {
|
|
103
|
+
const verified = (0, jsonwebtoken_1.verify)(token || '', auth.jwtSecret);
|
|
104
|
+
return {
|
|
105
|
+
email: verified['email'],
|
|
106
|
+
firstName: verified['firstName'],
|
|
107
|
+
lastName: verified['lastName'],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error('Error verifying login token:', error);
|
|
116
|
+
throw new Error('Invalid login token');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const fixUnwantedEscapeCharacters = (idpCert) => {
|
|
120
|
+
const certs = Array.isArray(idpCert) ? idpCert : [idpCert];
|
|
121
|
+
return certs.map((cert) => cert.replace(/\\n/g, '\n')); // process env adds an unwanted escape character
|
|
122
|
+
};
|
|
123
|
+
const extractEmail = (profile) => {
|
|
124
|
+
const email = profile[E_MAIL_CLAIM] || profile.email;
|
|
125
|
+
if (!email) {
|
|
126
|
+
throw new Error('No email provided in SAML profile');
|
|
127
|
+
}
|
|
128
|
+
return email;
|
|
129
|
+
};
|
|
130
|
+
const signonVerify = async (profile, done) => {
|
|
131
|
+
if (!profile) {
|
|
132
|
+
return done(new Error('No profile provided'), undefined);
|
|
133
|
+
}
|
|
134
|
+
const firstName = profile[FIRST_NAME_CLAIM];
|
|
135
|
+
const lastName = profile[LAST_NAME_CLAIM];
|
|
136
|
+
const email = extractEmail(profile);
|
|
137
|
+
const user = { email, firstName, lastName };
|
|
138
|
+
return done(null, user);
|
|
139
|
+
};
|
|
140
|
+
const logoutVerify = async (profile, done) => {
|
|
141
|
+
return done(null, profile || {});
|
|
142
|
+
};
|
|
143
|
+
const getLang = (req) => {
|
|
144
|
+
return (req.acceptsLanguages('de', 'en', 'fr', 'it') || 'de');
|
|
145
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.signRelayState = signRelayState;
|
|
7
|
+
exports.verifyRelayState = verifyRelayState;
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const STATE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
function signRelayState(rawToken, secret) {
|
|
11
|
+
if (!/^[\w-]{16,128}$/.test(rawToken))
|
|
12
|
+
throw new Error('bad token format');
|
|
13
|
+
const exp = Date.now() + STATE_TTL_MS;
|
|
14
|
+
const payload = `${rawToken}.${exp}`;
|
|
15
|
+
const mac = crypto_1.default
|
|
16
|
+
.createHmac('sha256', secret)
|
|
17
|
+
.update(payload)
|
|
18
|
+
.digest('base64url');
|
|
19
|
+
return Buffer.from(`${payload}.${mac}`, 'utf8').toString('base64url');
|
|
20
|
+
}
|
|
21
|
+
function verifyRelayState(stateB64, secret) {
|
|
22
|
+
const raw = Buffer.from(stateB64, 'base64url').toString('utf8');
|
|
23
|
+
const [token, expStr, mac] = raw.split('.');
|
|
24
|
+
if (!token || !expStr || !mac) {
|
|
25
|
+
throw new Error('bad state');
|
|
26
|
+
}
|
|
27
|
+
const exp = Number(expStr);
|
|
28
|
+
if (!Number.isFinite(exp) || exp < Date.now()) {
|
|
29
|
+
throw new Error('state expired');
|
|
30
|
+
}
|
|
31
|
+
const expected = crypto_1.default
|
|
32
|
+
.createHmac('sha256', secret)
|
|
33
|
+
.update(`${token}.${exp}`)
|
|
34
|
+
.digest('base64url');
|
|
35
|
+
if (!crypto_1.default.timingSafeEqual(Buffer.from(mac), Buffer.from(expected))) {
|
|
36
|
+
throw new Error('state tampered');
|
|
37
|
+
}
|
|
38
|
+
return token;
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class TtlCache<K, V> {
|
|
2
|
+
private readonly data;
|
|
3
|
+
private readonly timers;
|
|
4
|
+
private readonly defaultTtlMs?;
|
|
5
|
+
constructor(opts?: {
|
|
6
|
+
defaultTtlMs?: number;
|
|
7
|
+
});
|
|
8
|
+
get size(): number;
|
|
9
|
+
set(key: K, value: V, ttlMs?: number): void;
|
|
10
|
+
get(key: K): V | undefined;
|
|
11
|
+
has(key: K): boolean;
|
|
12
|
+
delete(key: K): boolean;
|
|
13
|
+
clear(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TtlCache = void 0;
|
|
4
|
+
class TtlCache {
|
|
5
|
+
data = new Map();
|
|
6
|
+
timers = new Map();
|
|
7
|
+
defaultTtlMs;
|
|
8
|
+
constructor(opts) {
|
|
9
|
+
this.defaultTtlMs = opts?.defaultTtlMs ?? 3000;
|
|
10
|
+
}
|
|
11
|
+
get size() {
|
|
12
|
+
return this.data.size;
|
|
13
|
+
}
|
|
14
|
+
set(key, value, ttlMs) {
|
|
15
|
+
const t = this.timers.get(key);
|
|
16
|
+
if (t) {
|
|
17
|
+
clearTimeout(t);
|
|
18
|
+
this.timers.delete(key);
|
|
19
|
+
}
|
|
20
|
+
this.data.set(key, value);
|
|
21
|
+
const ttl = ttlMs ?? this.defaultTtlMs;
|
|
22
|
+
if (ttl != null && ttl > 0) {
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
this.delete(key);
|
|
25
|
+
}, ttl);
|
|
26
|
+
timer.unref();
|
|
27
|
+
this.timers.set(key, timer);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
get(key) {
|
|
31
|
+
return this.data.get(key);
|
|
32
|
+
}
|
|
33
|
+
has(key) {
|
|
34
|
+
return this.data.has(key);
|
|
35
|
+
}
|
|
36
|
+
delete(key) {
|
|
37
|
+
const t = this.timers.get(key);
|
|
38
|
+
if (t) {
|
|
39
|
+
clearTimeout(t);
|
|
40
|
+
this.timers.delete(key);
|
|
41
|
+
}
|
|
42
|
+
return this.data.delete(key);
|
|
43
|
+
}
|
|
44
|
+
clear() {
|
|
45
|
+
for (const t of this.timers.values()) {
|
|
46
|
+
clearTimeout(t);
|
|
47
|
+
}
|
|
48
|
+
this.timers.clear();
|
|
49
|
+
this.data.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.TtlCache = TtlCache;
|