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.isSessionContext = exports.MemorySessionStore = exports.session = void 0;
|
7 | const debug_1 = __importDefault(require("debug"));
|
8 | const debug = (0, debug_1.default)('telegraf:session');
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | function session(options) {
|
24 | var _a, _b, _c;
|
25 | const prop = (_a = options === null || options === void 0 ? void 0 : options.property) !== null && _a !== void 0 ? _a : 'session';
|
26 | const getSessionKey = (_b = options === null || options === void 0 ? void 0 : options.getSessionKey) !== null && _b !== void 0 ? _b : defaultGetSessionKey;
|
27 | const store = (_c = options === null || options === void 0 ? void 0 : options.store) !== null && _c !== void 0 ? _c : new MemorySessionStore();
|
28 |
|
29 |
|
30 | const cache = new Map();
|
31 |
|
32 | const concurrents = new Map();
|
33 |
|
34 |
|
35 |
|
36 | return async (ctx, next) => {
|
37 | var _a;
|
38 | const updId = ctx.update.update_id;
|
39 |
|
40 |
|
41 | const key = await getSessionKey(ctx);
|
42 | if (!key) {
|
43 |
|
44 | ctx[prop] = undefined;
|
45 | return await next();
|
46 | }
|
47 | let cached = cache.get(key);
|
48 | if (cached) {
|
49 | debug(`(${updId}) found cached session, reusing from cache`);
|
50 | ++cached.counter;
|
51 | }
|
52 | else {
|
53 | debug(`(${updId}) did not find cached session`);
|
54 |
|
55 | let promise = concurrents.get(key);
|
56 | if (promise)
|
57 | debug(`(${updId}) found a concurrent request, reusing promise`);
|
58 | else {
|
59 | debug(`(${updId}) fetching from upstream store`);
|
60 | promise = store.get(key);
|
61 | }
|
62 |
|
63 | concurrents.set(key, promise);
|
64 | const upstream = await promise;
|
65 |
|
66 | concurrents.delete(key);
|
67 | debug(`(${updId}) updating cache`);
|
68 |
|
69 | const c = cache.get(key);
|
70 | if (c) {
|
71 |
|
72 | c.counter++;
|
73 |
|
74 | cached = c;
|
75 | }
|
76 | else {
|
77 |
|
78 | cached = { ref: upstream !== null && upstream !== void 0 ? upstream : (_a = options === null || options === void 0 ? void 0 : options.defaultSession) === null || _a === void 0 ? void 0 : _a.call(options, ctx), counter: 1 };
|
79 | cache.set(key, cached);
|
80 | }
|
81 | }
|
82 |
|
83 |
|
84 | const c = cached;
|
85 | let touched = false;
|
86 | Object.defineProperty(ctx, prop, {
|
87 | get() {
|
88 | touched = true;
|
89 | return c.ref;
|
90 | },
|
91 | set(value) {
|
92 | touched = true;
|
93 | c.ref = value;
|
94 | },
|
95 | });
|
96 | try {
|
97 | await next();
|
98 | }
|
99 | finally {
|
100 | if (--c.counter === 0) {
|
101 |
|
102 | debug(`(${updId}) refcounter reached 0, removing cached`);
|
103 | cache.delete(key);
|
104 | }
|
105 | debug(`(${updId}) middlewares completed, checking session`);
|
106 |
|
107 | if (touched)
|
108 | if (ctx[prop] == null) {
|
109 | debug(`(${updId}) ctx.${prop} missing, removing from store`);
|
110 | await store.delete(key);
|
111 | }
|
112 | else {
|
113 | debug(`(${updId}) ctx.${prop} found, updating store`);
|
114 | await store.set(key, ctx[prop]);
|
115 | }
|
116 | }
|
117 | };
|
118 | }
|
119 | exports.session = session;
|
120 | async function defaultGetSessionKey(ctx) {
|
121 | var _a, _b;
|
122 | const fromId = (_a = ctx.from) === null || _a === void 0 ? void 0 : _a.id;
|
123 | const chatId = (_b = ctx.chat) === null || _b === void 0 ? void 0 : _b.id;
|
124 | if (fromId == null || chatId == null) {
|
125 | return undefined;
|
126 | }
|
127 | return `${fromId}:${chatId}`;
|
128 | }
|
129 |
|
130 | class MemorySessionStore {
|
131 | constructor(ttl = Infinity) {
|
132 | this.ttl = ttl;
|
133 | this.store = new Map();
|
134 | }
|
135 | get(name) {
|
136 | const entry = this.store.get(name);
|
137 | if (entry == null) {
|
138 | return undefined;
|
139 | }
|
140 | else if (entry.expires < Date.now()) {
|
141 | this.delete(name);
|
142 | return undefined;
|
143 | }
|
144 | return entry.session;
|
145 | }
|
146 | set(name, value) {
|
147 | const now = Date.now();
|
148 | this.store.set(name, { session: value, expires: now + this.ttl });
|
149 | }
|
150 | delete(name) {
|
151 | this.store.delete(name);
|
152 | }
|
153 | }
|
154 | exports.MemorySessionStore = MemorySessionStore;
|
155 |
|
156 | function isSessionContext(ctx) {
|
157 | return 'session' in ctx;
|
158 | }
|
159 | exports.isSessionContext = isSessionContext;
|