UNPKG

3.02 kBPlain TextView Raw
1import { Context } from './context'
2import { MaybePromise } from './composer'
3import { MiddlewareFn } from './middleware'
4
5export interface SessionStore<T> {
6 get: (name: string) => MaybePromise<T | undefined>
7 set: (name: string, value: T) => MaybePromise<void>
8 delete: (name: string) => MaybePromise<void>
9}
10
11interface SessionOptions<S extends object> {
12 getSessionKey?: (ctx: Context) => Promise<string | undefined>
13 store?: SessionStore<S>
14}
15
16export interface SessionContext<S extends object> extends Context {
17 session?: S
18}
19
20/**
21 * Returns middleware that adds `ctx.session` for storing arbitrary state per session key.
22 *
23 * The default `getSessionKey` is <code>\`${ctx.from.id}:${ctx.chat.id}\`</code>.
24 * If either `ctx.from` or `ctx.chat` is `undefined`, default session key and thus `ctx.session` are also `undefined`.
25 *
26 * Session data is kept only in memory by default,
27 * which means that all data will be lost when the process is terminated.
28 * If you want to store data across restarts, or share it among workers,
29 * you can [install persistent session middleware from npm](https://www.npmjs.com/search?q=telegraf-session),
30 * or pass custom `storage`.
31 *
32 * @example https://github.com/telegraf/telegraf/blob/develop/docs/examples/session-bot.ts
33 * @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499
34 */
35export function session<S extends object>(
36 options?: SessionOptions<S>
37): MiddlewareFn<SessionContext<S>> {
38 const getSessionKey = options?.getSessionKey ?? defaultGetSessionKey
39 const store = options?.store ?? new MemorySessionStore()
40 return async (ctx, next) => {
41 const key = await getSessionKey(ctx)
42 if (key == null) {
43 return await next()
44 }
45 ctx.session = await store.get(key)
46 await next()
47 if (ctx.session == null) {
48 await store.delete(key)
49 } else {
50 await store.set(key, ctx.session)
51 }
52 }
53}
54
55async function defaultGetSessionKey(ctx: Context): Promise<string | undefined> {
56 const fromId = ctx.from?.id
57 const chatId = ctx.chat?.id
58 if (fromId == null || chatId == null) {
59 return undefined
60 }
61 return `${fromId}:${chatId}`
62}
63
64/** @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499 */
65export class MemorySessionStore<T> implements SessionStore<T> {
66 private readonly store = new Map<string, { session: T; expires: number }>()
67
68 constructor(private readonly ttl = Infinity) {}
69
70 get(name: string): T | undefined {
71 const entry = this.store.get(name)
72 if (entry == null) {
73 return undefined
74 } else if (entry.expires < Date.now()) {
75 this.delete(name)
76 return undefined
77 }
78 return entry.session
79 }
80
81 set(name: string, value: T): void {
82 const now = Date.now()
83 this.store.set(name, { session: value, expires: now + this.ttl })
84 }
85
86 delete(name: string): void {
87 this.store.delete(name)
88 }
89}
90
91export function isSessionContext<S extends object>(
92 ctx: Context
93): ctx is SessionContext<S> {
94 return 'session' in ctx
95}