scenes_stage.js

const SceneContext = require('../scenes/context')
const Composer = require('../composer')
const { compose, optional, lazy, safePassThru } = Composer

/**
 * The default scene is the scene that is entered when the user is not in another scene.
 *
 * When you use custom name of session property or multiple sessions, you should configure `sessionName`
 *
 * After TTL expired, all scene data stored in local session of scene be permanently removed
 *
 * @typedef {object} StageOptions
 * @property {string} [sessionName='session'] Name of session property used for scenes, by default - `session`
 * @property {string} [default] Name of scene by default
 * @property {number} [ttl] Time of life for scenes in seconds
 * @memberOf Scenes
 */

/**
 * The Stage class extends Composer, so you can use all of its methods such as `hears`, `use`, `command` Catch and
 * more.
 *
 * These handlers have a higher priority than scene handlers, but they are always executed,
 * not only then the user is in the scene.
 *
 * <b style="color: #139d9d" >Be attentive, scenes are only available in handlers and middleware registered after
 * Stage</b>
 *
 * For example:
 *
 * ```js
 * const { Stage, Opengram } = require('opengram')
 * const bot = new Opengram(process.env.BOT_TOKEN)
 * const stage = new Stage([...]) // Create Stage instance and register scenes
 *
 * bot.use(stage)
 * bot.start(async(ctx) => {
 *   await ctx.reply('Hello!')
 *   await ctx.scene.enter('name')
 * })
 *
 * // This handler has more priority than scenes handlers and `bot.start` (because Stage registered before bot.start)
 * stage.on('message', async(ctx, next) => {
 *   console.log(ctx.message.text)
 *   await next()
 * })
 *
 * bot.launch()
 * ```
 *
 * @memberof Scenes
 * @extends Composer
 */
class Stage extends Composer {
  /**
   * @constructor
   * @param {Array<Scenes.BaseScene|Scenes.WizardScene>} [scenes] Array of scenes objects
   * @param {StageOptions} [options] Options
   * @throws {TypeError}
   */
  constructor (scenes = [], options) {
    super()
    this.options = {
      sessionName: 'session',
      ...options
    }
    this.scenes = new Map()
    scenes.forEach((scene) => this.register(scene))
  }

  /**
   * Register new scene object in scenes repository
   *
   * @param {Scenes.BaseScene|Scenes.WizardScene} [scenes] Scenes objects
   * @throws {TypeError}
   * @return {Stage}
   */
  register (...scenes) {
    scenes.forEach((scene) => {
      if (!scene || !scene.id || typeof scene.middleware !== 'function') {
        throw new TypeError('opengram: Unsupported scene')
      }
      this.scenes.set(scene.id, scene)
    })
    return this
  }

  /**
   * Generates and returns stage middleware for embedding
   *
   * @return {Middleware}
   */
  middleware () {
    const handler = compose([
      (ctx, next) => {
        ctx.scene = new SceneContext(ctx, this.scenes, this.options)
        return next()
      },
      super.middleware(),
      lazy((ctx) => ctx.scene.current || safePassThru())
    ])
    return optional((ctx) => ctx[this.options.sessionName], handler)
  }

  /**
   * Generates middleware which call `ctx.scene.enter` with given arguments
   *
   * @param {string} sceneId Scene name
   * @param {object} [initialState] Scene initial state
   * @param {boolean} [silent] ???
   * @throws {Error}
   * @return {Promise}
   * @return {Middleware}
  */
  static enter (sceneId, initialState, silent) {
    return (ctx) => ctx.scene.enter(sceneId, initialState, silent)
  }

  /**
   * Generates middleware which call `ctx.scene.reenter` with given arguments
   *
   * @return {Middleware}
   */
  static reenter () {
    return (ctx) => ctx.scene.reenter()
  }

  /**
   * Generates middleware which call `ctx.scene.leave` with given arguments
   *
   * @return {Middleware}
   */
  static leave () {
    return (ctx) => ctx.scene.leave()
  }
}

module.exports = Stage