1 | import BaseScene from './base'
|
2 | import Composer from '../composer'
|
3 | import Context from '../context'
|
4 | import d from 'debug'
|
5 | import { SessionContext } from '../session'
|
6 | const debug = d('telegraf:scenes:context')
|
7 |
|
8 | const noop = () => Promise.resolve()
|
9 | const now = () => Math.floor(Date.now() / 1000)
|
10 |
|
11 | export interface SceneContext<D extends SceneSessionData = SceneSessionData>
|
12 | extends Context {
|
13 | session: SceneSession<D>
|
14 | scene: SceneContextScene<SceneContext<D>, D>
|
15 | }
|
16 |
|
17 | export interface SceneSessionData {
|
18 | current?: string
|
19 | expires?: number
|
20 | state?: object
|
21 | }
|
22 |
|
23 | export interface SceneSession<S extends SceneSessionData = SceneSessionData> {
|
24 | __scenes: S
|
25 | }
|
26 |
|
27 | export interface SceneContextSceneOptions<D extends SceneSessionData> {
|
28 | ttl?: number
|
29 | default?: string
|
30 | defaultSession: D
|
31 | }
|
32 |
|
33 | export default class SceneContextScene<
|
34 | C extends SessionContext<SceneSession<D>>,
|
35 | D extends SceneSessionData = SceneSessionData
|
36 | > {
|
37 | private readonly options: SceneContextSceneOptions<D>
|
38 |
|
39 | constructor(
|
40 | private readonly ctx: C,
|
41 | private readonly scenes: Map<string, BaseScene<C>>,
|
42 | options: Partial<SceneContextSceneOptions<D>>
|
43 | ) {
|
44 |
|
45 | const fallbackSessionDefault: D = {}
|
46 |
|
47 | this.options = { defaultSession: fallbackSessionDefault, ...options }
|
48 | }
|
49 |
|
50 | get session(): D {
|
51 | const defaultSession = this.options.defaultSession
|
52 |
|
53 | let session = this.ctx.session?.__scenes ?? defaultSession
|
54 | if (session.expires !== undefined && session.expires < now()) {
|
55 | session = defaultSession
|
56 | }
|
57 | if (this.ctx.session === undefined) {
|
58 | this.ctx.session = { __scenes: session }
|
59 | } else {
|
60 | this.ctx.session.__scenes = session
|
61 | }
|
62 | return session
|
63 | }
|
64 |
|
65 | get state() {
|
66 | return (this.session.state ??= {})
|
67 | }
|
68 |
|
69 | set state(value) {
|
70 | this.session.state = { ...value }
|
71 | }
|
72 |
|
73 | get current() {
|
74 | const sceneId = this.session.current ?? this.options.default
|
75 | return sceneId === undefined || !this.scenes.has(sceneId)
|
76 | ? undefined
|
77 | : this.scenes.get(sceneId)
|
78 | }
|
79 |
|
80 | reset() {
|
81 | if (this.ctx.session !== undefined)
|
82 | this.ctx.session.__scenes = this.options.defaultSession
|
83 | }
|
84 |
|
85 | async enter(
|
86 | sceneId: string,
|
87 | initialState: object = {},
|
88 | silent: boolean = false
|
89 | ) {
|
90 | if (!this.scenes.has(sceneId)) {
|
91 | throw new Error(`Can't find scene: ${sceneId}`)
|
92 | }
|
93 | if (!silent) {
|
94 | await this.leave()
|
95 | }
|
96 | debug('Entering scene', sceneId, initialState, silent)
|
97 | this.session.current = sceneId
|
98 | this.state = initialState
|
99 | const ttl = this.current?.ttl ?? this.options.ttl
|
100 | if (ttl !== undefined) {
|
101 | this.session.expires = now() + ttl
|
102 | }
|
103 | if (this.current === undefined || silent) {
|
104 | return
|
105 | }
|
106 | const handler =
|
107 | 'enterMiddleware' in this.current &&
|
108 | typeof this.current.enterMiddleware === 'function'
|
109 | ? this.current.enterMiddleware()
|
110 | : this.current.middleware()
|
111 | return await handler(this.ctx, noop)
|
112 | }
|
113 |
|
114 | reenter() {
|
115 | return this.session.current === undefined
|
116 | ? undefined
|
117 | : this.enter(this.session.current, this.state)
|
118 | }
|
119 |
|
120 | private leaving = false
|
121 | async leave() {
|
122 | if (this.leaving) return
|
123 | debug('Leaving scene')
|
124 | try {
|
125 | this.leaving = true
|
126 | if (this.current === undefined) {
|
127 | return
|
128 | }
|
129 | const handler =
|
130 | 'leaveMiddleware' in this.current &&
|
131 | typeof this.current.leaveMiddleware === 'function'
|
132 | ? this.current.leaveMiddleware()
|
133 | : Composer.passThru()
|
134 | await handler(this.ctx, noop)
|
135 | return this.reset()
|
136 | } finally {
|
137 | this.leaving = false
|
138 | }
|
139 | }
|
140 | }
|