import * as Promise from 'bluebird' ; import { IAddress , IMessage , Message , Session , UniversalBot } from 'botbuilder' ; interface ISaveUpdatableSession extends Session { saveUpdated : boolean ; } /**
 * Service that provides session for addresses. Injects code into a Universal bot to allow session save and load listeners to work
 */ export class SessionService { private bot : UniversalBot ; private currentSessionLoadResolver : ( s : Session ) => void ; /**
     * This is a crucial field to proper serial execution of the BotTester framework. session.save occurs asynchronously in UniversalBot
     * batch executions. We do not want to be able to load a session if session.save has been called, but the bot's batch has yet to
     * execute. This promise will be set to resolved when the bot's latest batch has finished, should the framework be watching for a save
     * call. This value is defaulted to a resolved promise.
     */ private savePerformed : Promise < any > ; constructor ( bot ) { this . bot = bot ; this . applySessionSaveListener ( ) ; this . applySessionLoadListener ( ) ; this . savePerformed = Promise . resolve ( ) ; } param addr public getSession ( addr ) { return new Promise < Session > ( ( res , rej ) => { this . createSessionWrapperWithLoadMessageOverride ( addr ) ; this . currentSessionLoadResolver = res ; this . loadSession ( addr ) ; } ) . bind ( this ) ; } param address public getInternalSaveMessage ( address ) { const saveEvent = new Message ( ) . address ( address ) . toMessage ( ) ; saveEvent . type = '__save__' ; return saveEvent ; } param addr private createSessionWrapperWithLoadMessageOverride ( addr ) { const createSessionOriginal = this . bot [ 'createSession' ] ; this . bot [ 'createSession' ] = function ( ) { const createSessionArgs = Array . prototype . slice . call ( arguments ) ; const loadMsg = new Message ( ) . address ( addr ) . toMessage ( ) ; loadMsg . type = 'load' ; createSessionArgs [ 1 ] = loadMsg ; this . bot [ 'createSession' ] = createSessionOriginal ; createSessionOriginal . apply ( this . bot , createSessionArgs ) ; } . bind ( this ) ; } param address private loadSession ( address ) { this . savePerformed . then ( ( ) => { this . bot . loadSession ( address , ( a ) => { } ) ; } ) ; } /**
     * adds middleware to the bot that checks for incoming load messages sent by createSessionWrapperWithLoadMessageOverride's
     * bot.createSession wrapper. This lets us know that the message was never meant to go through the bot's middelware and ignore it.
     * The session loaded into this message is then used as the value that the Promise returned from getSession resolves to
     */ private applySessionLoadListener ( ) { this . bot . use ( { botbuilder : ( session , next ) => { if ( session . message . type === 'load' ) { session [ 'inMiddleware' ] = false ; this . currentSessionLoadResolver ( session ) ; } else { next ( ) ; } } } ) ; } /**
     * Adds a routing event listner which is the first execution path called after a session has been successfully loaded. If this session
     * has not already gone through this listener, then it wraps session.save in a function that sends the internal save message that is
     * mocked to be sent to the user, and thereby intercepted by the MessageService. This ensures that the test runner does not continue
     * preemptively. When the session's save method is called, the message is actually sent and alerts the BotTester framework
     */ private applySessionSaveListener ( ) { this . bot . on ( 'routing' , ( session ) => { if ( ! session . saveUpdated ) { session . saveUpdated = true ; const saveEvent = this . getInternalSaveMessage ( session . message . address ) ; const save = session . save . bind ( session ) ; session . save = function ( ) : Session { save ( ) ; session . send ( saveEvent ) ; this . savePerformed = Promise . promisify ( session . sendBatch ) . call ( session ) ; return session ; } ; } } ) ; } }