1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const Promise = require("bluebird");
|
4 | const botbuilder_1 = require("botbuilder");
|
5 | /**
|
6 | * Service that provides session for addresses. Injects code into a Universal bot to allow session save and load listeners to work
|
7 | */
|
8 | class SessionService {
|
9 | //tslint:enable
|
10 | constructor(bot) {
|
11 | this.bot = bot;
|
12 | this.applySessionSaveListener();
|
13 | this.applySessionLoadListener();
|
14 | this.savePerformed = Promise.resolve();
|
15 | }
|
16 | /**
|
17 | * fetches a session for a particular address
|
18 | *
|
19 | * @param addr address of session to load
|
20 | */
|
21 | getSession(addr) {
|
22 | return new Promise((res, rej) => {
|
23 | this.createSessionWrapperWithLoadMessageOverride(addr);
|
24 | this.currentSessionLoadResolver = res;
|
25 | this.loadSession(addr);
|
26 | }).bind(this);
|
27 | }
|
28 | /**
|
29 | * a message internal to the BotTester framework. Due limitations in the botbuilder framework, this message is sent to the bot whenever
|
30 | * session.save is called. This allows the framework to know when session.save is called and resolve accordingly. Without this, the
|
31 | * session state may not be persisted before the next test step is run
|
32 | * @param address address that the save message should come from
|
33 | */
|
34 | getInternalSaveMessage(address) {
|
35 | const saveEvent = new botbuilder_1.Message()
|
36 | .address(address)
|
37 | .toMessage();
|
38 | saveEvent.type = '__save__';
|
39 | return saveEvent;
|
40 | }
|
41 | /**
|
42 | * This is a delicate hack that relies on accessing and modifying the private field createSession on UniversalBot.
|
43 | * This makes the bot's next createSession call result in a session with a message of type 'load'. This allows the sessionLoadListener
|
44 | * to know if a message that the bot thinks it received was actually the result of a call to bot.createSession
|
45 | * @param addr address of the message that is being loaded
|
46 | */
|
47 | createSessionWrapperWithLoadMessageOverride(addr) {
|
48 | // tslint:disable
|
49 | const createSessionOriginal = this.bot['createSession'];
|
50 | this.bot['createSession'] = function () {
|
51 | //tslint:enable
|
52 | const createSessionArgs = Array.prototype.slice.call(arguments);
|
53 | const loadMsg = new botbuilder_1.Message()
|
54 | .address(addr)
|
55 | .toMessage();
|
56 | loadMsg.type = 'load';
|
57 | createSessionArgs[1] = loadMsg;
|
58 | //tslint:disable
|
59 | this.bot['createSession'] = createSessionOriginal;
|
60 | createSessionOriginal.apply(this.bot, createSessionArgs);
|
61 | }.bind(this);
|
62 | }
|
63 | /**
|
64 | * Loads a session associated with an address.
|
65 | * @param address address to be loaded
|
66 | */
|
67 | loadSession(address) {
|
68 | this.savePerformed
|
69 | .then(() => {
|
70 | //tslint:disable
|
71 | // this callback will never actually get called, but it sets off the events allowing
|
72 | // for the encapsulating promise to resolve
|
73 | this.bot.loadSession(address, (a) => { });
|
74 | //tslint:enable
|
75 | });
|
76 | }
|
77 | /**
|
78 | * adds middleware to the bot that checks for incoming load messages sent by createSessionWrapperWithLoadMessageOverride's
|
79 | * bot.createSession wrapper. This lets us know that the message was never meant to go through the bot's middelware and ignore it.
|
80 | * The session loaded into this message is then used as the value that the Promise returned from getSession resolves to
|
81 | */
|
82 | applySessionLoadListener() {
|
83 | this.bot.use({
|
84 | botbuilder: (session, next) => {
|
85 | // TODO add in address comparison with address encoded in createSessionWrapperWithLoadMessageOverride's wrapper to
|
86 | // createSession
|
87 | if (session.message.type === 'load') {
|
88 | //tslint:disable
|
89 | // its not actually supposed to be in the middleware, so unset this
|
90 | session['inMiddleware'] = false;
|
91 | //tslint:enable
|
92 | this.currentSessionLoadResolver(session);
|
93 | }
|
94 | else {
|
95 | next();
|
96 | }
|
97 | }
|
98 | });
|
99 | }
|
100 | /**
|
101 | * Adds a routing event listner which is the first execution path called after a session has been successfully loaded. If this session
|
102 | * has not already gone through this listener, then it wraps session.save in a function that sends the internal save message that is
|
103 | * mocked to be sent to the user, and thereby intercepted by the MessageService. This ensures that the test runner does not continue
|
104 | * preemptively. When the session's save method is called, the message is actually sent and alerts the BotTester framework
|
105 | */
|
106 | applySessionSaveListener() {
|
107 | // this is a critical hack that attaches an extra field onto session. Gonna just ignore this lint issue for now
|
108 | this.bot.on('routing', (session) => {
|
109 | // tslint:enable
|
110 | // if session.saveUpdated === true, then we should ignore this session being routed because it has already had its save method
|
111 | // hijacked
|
112 | if (!session.saveUpdated) {
|
113 | session.saveUpdated = true;
|
114 | const saveEvent = this.getInternalSaveMessage(session.message.address);
|
115 | const save = session.save.bind(session);
|
116 | session.save = function () {
|
117 | save();
|
118 | session.send(saveEvent);
|
119 | // ensure that the state is saved before any session loading occurs (if use calls getSession, we want it to
|
120 | // load only after the session state is saved)
|
121 | this.savePerformed = Promise.promisify(session.sendBatch).call(session);
|
122 | return session;
|
123 | };
|
124 | }
|
125 | });
|
126 | }
|
127 | }
|
128 | exports.SessionService = SessionService;
|
129 | //# sourceMappingURL=SessionService.js.map |
\ | No newline at end of file |