Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1235 from howdyai/069
Browse files Browse the repository at this point in the history
0.6.9
  • Loading branch information
Ben Brown authored Jan 29, 2018
2 parents 040ced1 + 8a9b143 commit dc0e780
Show file tree
Hide file tree
Showing 9 changed files with 474 additions and 175 deletions.
63 changes: 63 additions & 0 deletions __test__/lib/Teams.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';
let teamsBot;
let teamsApi;
let config = {
clientId: 'client',
clientSecret: 'secret',
debug: false
};

describe('authentication', () => {
beforeEach(() => {
jest.doMock('../../lib/TeamsAPI', () => {
return jest.fn((configuration) => {
return {
getToken: jest.fn((cb) => {
configuration.token = 'token';
configuration.token_expires_in = '3600';
cb(null);
})
};
});
});

jest.useFakeTimers();
teamsApi = require('../../lib/TeamsAPI');
});

afterEach(() => {
jest.resetModules();
jest.clearAllTimers();
});

test('get token', () => {
let bot = require('../../lib/Teams')(config);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
});

test('refresh token before expiry', () => {
let bot = require('../../lib/Teams')(config);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runOnlyPendingTimers();
expect(bot.api.getToken).toHaveBeenCalledTimes(2);
});

test('token valid for 20 mins should refresh after 10 mins', () => {
teamsApi.mockImplementation(jest.fn((configuration) => {
return {
getToken: jest.fn((cb) => {
configuration.token = 'token';
configuration.token_expires_in = '1200';
cb(null);
})
};
}));
let bot = require('../../lib/Teams')(config);
expect(bot.config.token_expires_in).toBe('1200');
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runTimersToTime(1000 * 60 * 9);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runTimersToTime(1000 * 60 * 1.1);
expect(bot.api.getToken).toHaveBeenCalledTimes(2);
});
});
15 changes: 15 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

[Want to contribute? Read our guide!](https://github.com/howdyai/botkit/blob/master/CONTRIBUTING.md)

# 0.6.9

* Add 2 new middleware endpoints that occur during conversations - `conversationStart(bot, convo, next)` and `conversationEnd(bot, convo, next)`. [Some new documentation](docs/readme.md#conversation-events-and-middleware-endpoints)
* Conversations powered by Botkit Studio will now include `convo.context.script_name` and `convo.context.script_id` which point back to the script loaded from the Botkit Studio API
* When using Botkit Studio's execute script action, the resulting conversation object will have 2 additional context fields: `convo.context.transition_from` and `convo.context.transition_from_id` which will point to the script from which the user transitioned
* When using Botkit Studio's execute script action, the original conversation from which the user is transitioning will have 2 additional context fields: `convo.context.transition_to` and `convo.context.transition_to_id` which will point to the script to which the user transitioned
* Fix for Botkit Studio scripts which used "end and mark successful" action from a condition. Previously this would end, but not mark successful.

Merged Pull Requests:
* Make sure Facebook API errors are passed to callback if specified [PR #1225](https://github.com/howdyai/botkit/pull/1225)
* Refresh Microsoft Teams token when it has expired. [PR #1230](https://github.com/howdyai/botkit/pull/1230)
* Update TypeScript definition for Web bots [PR #1231](https://github.com/howdyai/botkit/pull/1231)
* Update TypeScript definition for bot.replyAndUpdate [PR #1232](https://github.com/howdyai/botkit/pull/1232)
* Fix for Microsoft teams button builder function [PR #1233](https://github.com/howdyai/botkit/pull/1233)

# 0.6.8

BIG UPDATE:
Expand Down
57 changes: 52 additions & 5 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ Only the user who sent the original incoming message will be able to respond to
| message | message object containing {user: userId} of the user you would like to start a conversation with
| callback | a callback function in the form of function(err,conversation) { ... }

`startPrivateConversation()` is a function that initiates a conversation with a specific user. Note function is currently *Slack-only!*
`startPrivateConversation()` is a function that initiates a conversation with a specific user. Note that this function only works on platforms with multiple channels where there are public and private channels, like Slack, Microsoft Teams and Cisco Spark.

#### bot.createConversation()
| Argument | Description
Expand Down Expand Up @@ -712,7 +712,7 @@ Set the action field of a message to `stop` end immediately, but mark as failed.

Set the action field of a message to `timeout` to end immediately and indicate that the conversation has timed out.

After the conversation ends, these values will be available in the `convo.status` field. This field can then be used to check the final outcome of a conversation. See [handling the end of conversations](#handling-end-of-conversation).
After the conversation ends, these values will be available in the `convo.status` field. This field can then be used to check the final outcome of a conversation. See [handling the end of conversations](#conversation-events-and-middleware-endpoints).

### Using Variable Tokens and Templates in Conversation Threads

Expand Down Expand Up @@ -796,10 +796,57 @@ so that it is sent immediately, before any other queued messages.

`convo.setTimeout(timeout)` times out conversation if no response from user after specified time period (in milliseconds).

### Handling End of Conversation
### Conversation Events and Middleware Endpoints

As conversations are conducted, Botkit will emit several types of events, and fire any developer-specified middleware functions that allow the conversation object to be observed and modified.

**Conversation Events**

| Event Name | Description
|--- |---
| conversationStarted | A conversation has begun
| conversationEnded | A conversation has ended

These events are emitted by the main Botkit controller, not the conversation object. They will fire for all conversations. This should not be confused with the `end` event emitted by an individual conversation object, [detailed here](#handling-end-of-conversation).

Note that the signature for handler functions for these events is slightly different than other Botkit events.
The second parameter for these events is the conversation object, not an individual message event.

```js
controller.on('conversationStarted', function(bot, convo) {
// do something with this convo object
});

controller.on('conversationEnded', function(bot, convo) {
// do something with this convo object
});
```

Conversations trigger events during the course of their life. Currently,
only two events are fired, and only one is very useful: end.
**Conversation Middleware Endpoints**

| Endpoint | Description
|--- |---
| conversationStart | Fires when a conversation is activated, but before the first message is sent
| conversationEnd | Fires after a conversation is over, but the conversation object is marked completed

If you need to not only inspect but also modify the content of the conversation as it begins or ends, use middleware instead of event handlers. Middleware functions registered to these endpoints fire as the conversation transitions from one state to another, and fire synchronously, in the order they are added. They can be used to do things like initialize conversations with variable values, or capture responses to a database on a global basis.

```js
controller.middleware.conversationStart.use(function(bot, convo, next) {
console.log('CONVERSATION START MIDDLEWARE!!!!');

// this variable will be available in EVERY SINGLE conversation!
convo.setVar('foo','bar');
next();
});

controller.middleware.conversationEnd.use(function(bot, convo, next) {
console.log('CONVERSATION END MIDDLEWARE!!!!');
next();
});
```

### Handling End of Conversation

Conversations end naturally when the last message has been sent and no messages remain in the queue.
In this case, the value of `convo.status` will be `completed`. Other values for this field include `active`, `stopped`, and `timeout`.
Expand Down
24 changes: 15 additions & 9 deletions lib/Botkit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ declare namespace botkit {
function sparkbot(configuration: CiscoSparkConfiguration): CiscoSparkController;
function twilioipmbot(configuration: TwilioIPMConfiguration): TwilioIPMController;
function twiliosmsbot(configuration: TwilioSMSConfiguration): TwilioSMSController;
function socketbot(configuration: SocketConfiguration): SocketController;
function socketbot(configuration: WebConfiguration): WebController;
function anywhere(configuration: WebConfiguration): WebController;

interface Bot<S, M extends Message> {
readonly botkit: Controller<S, M, this>;
readonly identity: Identity;
Expand Down Expand Up @@ -244,6 +246,9 @@ declare namespace botkit {
title_link?: string;
ts?: string;
}
interface SlackUpdateMessageCallback {
(newResponse: string | SlackMessage, cb?: (err: Error) => void): void
}
interface SlackBot extends Bot<SlackSpawnConfiguration, SlackMessage> {
readonly api: SlackWebAPI;
configureIncomingWebhook(config: { url: string; }): this;
Expand All @@ -254,7 +259,7 @@ declare namespace botkit {
identifyTeam(): string;
identifyBot(): { id: string; name: string; team_id: string; };
replyAcknowledge(cb?: (err: Error) => void): void;
replyAndUpdate(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string) => void): void;
replyAndUpdate(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string, updateResponse: SlackUpdateMessageCallback) => void): void;
replyInThread(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string) => void): void;
replyPrivate(src: SlackMessage, resp: string | SlackMessage, cb?: (err: Error) => void): void;
replyPrivateDelayed(src: SlackMessage, resp: string | SlackMessage, cb?: (err: Error) => void): void;
Expand Down Expand Up @@ -492,21 +497,22 @@ declare namespace botkit {
}
interface TwilioSMSSpawnConfiguration {
}
interface SocketBot extends Bot<SocketSpawnConfiguration, SocketMessage> {
send(src: SocketMessage, cb?: (err: Error, res: any) => void): void;
findConversation(message: SocketMessage, cb: (convo?: Conversation<SocketMessage>) => void): void;
interface WebBot extends Bot<WebSpawnConfiguration, WebMessage> {
connected: boolean;
send(src: WebMessage, cb?: (err: Error, res: any) => void): void;
findConversation(message: WebMessage, cb: (convo?: Conversation<WebMessage>) => void): void;
}
interface SocketConfiguration extends Configuration {
interface WebConfiguration extends Configuration {
replyWithTyping?: boolean;
}
interface SocketController extends Controller<SocketSpawnConfiguration, SocketMessage, SocketBot> {
interface WebController extends Controller<WebSpawnConfiguration, WebMessage, WebBot> {
httpserver: http.Server;
webserver: express.Express;
openSocketServer(server: http.Server): void;
}
export interface SocketMessage extends Message {
export interface WebMessage extends Message {
}
interface SocketSpawnConfiguration {
interface WebSpawnConfiguration {
}
interface User {
id: string;
Expand Down
61 changes: 42 additions & 19 deletions lib/CoreBot.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function Botkit(configuration) {
capture: ware(),
format: ware(),
send: ware(),
conversationStart: ware(),
conversationEnd: ware(),
};

// list of events to exclude from conversations
Expand Down Expand Up @@ -152,6 +154,8 @@ function Botkit(configuration) {
user: message.user,
channel: message.channel,
bot: task.bot,
script_name: message.script_name || null,
script_id: message.script_id || null,
};

this.events = {};
Expand Down Expand Up @@ -270,9 +274,14 @@ function Botkit(configuration) {
};

this.activate = function() {
this.task.trigger('conversationStarted', [this]);
this.task.botkit.trigger('conversationStarted', [this.task.bot, this]);
this.status = 'active';

botkit.middleware.conversationStart.run(this.task.bot, this, function(err, bot, convo) {

convo.status = 'active';
convo.task.trigger('conversationStarted', [convo]);
convo.task.botkit.trigger('conversationStarted', [bot, convo]);

});
};

/**
Expand Down Expand Up @@ -344,10 +353,17 @@ function Botkit(configuration) {
if (condition.execute) {
var script = condition.execute.script;
var thread = condition.execute.thread;
that.stop('transitioning to ' + script);
botkit.studio.get(that.context.bot, script, that.source_message.user, that.source_message.channel, that.source_message).then(function(new_convo) {

that.context.transition_to = new_convo.context.script_name || null;
that.context.transition_to_id = new_convo.context.script_id || null;
that.stop('transitioning to ' + script);

new_convo.responses = that.responses;
new_convo.vars = that.vars;
new_convo.context.transition_from = that.context.script_name || null;
new_convo.context.transition_from_id = that.context.script_id || null;

new_convo.gotoThread(thread);
new_convo.activate();
});
Expand Down Expand Up @@ -909,6 +925,8 @@ function Botkit(configuration) {
this.silentRepeat();
} else if (message.action == 'stop') {
this.stop();
} else if (message.action == 'complete') {
this.stop('completed');
} else if (message.action == 'timeout') {
this.stop('timeout');
} else if (this.threads[message.action]) {
Expand Down Expand Up @@ -958,34 +976,39 @@ function Botkit(configuration) {
var convo = new Conversation(this, message);
convo.id = botkit.convoCount++;
this.convos.push(convo);

return convo;
};

this.startConversation = function(message) {

var convo = this.createConversation(message);
botkit.log('> [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);
botkit.debug('> [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);

convo.activate();
return convo;
};

this.conversationEnded = function(convo) {
botkit.log('> [End] ', convo.id, ' Conversation with ',
convo.source_message.user, 'in', convo.source_message.channel);
this.trigger('conversationEnded', [convo]);
this.botkit.trigger('conversationEnded', [bot, convo]);
convo.trigger('end', [convo]);
var actives = 0;
for (var c = 0; c < this.convos.length; c++) {
if (this.convos[c].isActive()) {
actives++;
}
}
if (actives == 0) {
this.taskEnded();
}

var that = this;
botkit.middleware.conversationEnd.run(this.bot, convo, function(err, bot, convo) {

botkit.debug('> [End] ', convo.id, ' Conversation with ',
convo.source_message.user, 'in', convo.source_message.channel);
that.trigger('conversationEnded', [convo]);
that.botkit.trigger('conversationEnded', [bot, convo]);
convo.trigger('end', [convo]);
var actives = 0;
for (var c = 0; c < that.convos.length; c++) {
if (that.convos[c].isActive()) {
actives++;
}
}
if (actives == 0) {
that.taskEnded();
}
});
};

this.endImmediately = function(reason) {
Expand Down
Loading

0 comments on commit dc0e780

Please sign in to comment.