import BaseScene from './base' import Composer from '../composer' import Context from '../context' import d from 'debug' import { SessionContext } from '../session' const debug = d('telegraf:scenes:context') const noop = () => Promise.resolve() const now = () => Math.floor(Date.now() / 1000) export interface SceneContext extends Context { session: SceneSession scene: SceneContextScene, D> } export interface SceneSessionData { current?: string expires?: number state?: object } export interface SceneSession { __scenes: S } export interface SceneContextSceneOptions { ttl?: number default?: string defaultSession: D } export default class SceneContextScene< C extends SessionContext>, D extends SceneSessionData = SceneSessionData > { private readonly options: SceneContextSceneOptions constructor( private readonly ctx: C, private readonly scenes: Map>, options: Partial> ) { // @ts-expect-error {} might not be assignable to D const fallbackSessionDefault: D = {} this.options = { defaultSession: fallbackSessionDefault, ...options } } get session(): D { const defaultSession = this.options.defaultSession let session = this.ctx.session?.__scenes ?? defaultSession if (session.expires !== undefined && session.expires < now()) { session = defaultSession } if (this.ctx.session === undefined) { this.ctx.session = { __scenes: session } } else { this.ctx.session.__scenes = session } return session } get state() { return (this.session.state ??= {}) } set state(value) { this.session.state = { ...value } } get current() { const sceneId = this.session.current ?? this.options.default return sceneId === undefined || !this.scenes.has(sceneId) ? undefined : this.scenes.get(sceneId) } reset() { if (this.ctx.session !== undefined) this.ctx.session.__scenes = this.options.defaultSession } async enter( sceneId: string, initialState: object = {}, silent: boolean = false ) { if (!this.scenes.has(sceneId)) { throw new Error(`Can't find scene: ${sceneId}`) } if (!silent) { await this.leave() } debug('Entering scene', sceneId, initialState, silent) this.session.current = sceneId this.state = initialState const ttl = this.current?.ttl ?? this.options.ttl if (ttl !== undefined) { this.session.expires = now() + ttl } if (this.current === undefined || silent) { return } const handler = 'enterMiddleware' in this.current && typeof this.current.enterMiddleware === 'function' ? this.current.enterMiddleware() : this.current.middleware() return await handler(this.ctx, noop) } reenter() { return this.session.current === undefined ? undefined : this.enter(this.session.current, this.state) } private leaving = false async leave() { if (this.leaving) return debug('Leaving scene') try { this.leaving = true if (this.current === undefined) { return } const handler = 'leaveMiddleware' in this.current && typeof this.current.leaveMiddleware === 'function' ? this.current.leaveMiddleware() : Composer.passThru() await handler(this.ctx, noop) return this.reset() } finally { this.leaving = false } } }