README.md 20.4 KB
Newer Older
1
[asteroid]: https://www.npmjs.com/package/asteroid
2
[lru]: https://www.npmjs.com/package/lru
3
[rest]: https://rocket.chat/docs/developer-guides/rest-api/
4
[start]: https://github.com/RocketChat/Rocket.Chat.js.SDK/blob/master/src/utils/start.ts
5

6
# Rocket.Chat Node.js SDK
7

8
Application interface for server methods and message stream subscriptions.
9

SingLi's avatar
SingLi committed
10 11 12 13
## Super Quick Start (30 seconds)

Create your own working BOT for Rocket.Chat, in seconds, at [glitch.com](https://glitch.com/~rocketchat-bot).

14 15 16 17 18
## Quick Start

Add your own Rocket.Chat BOT, running on your favorite Linux, MacOS or Windows system.

First, make sure you have the latest version of [nodeJS](https://nodejs.org/) (nodeJS 8.x or higher).   
19

20 21 22 23
```
node -v
v8.9.3
```
24

25 26 27 28 29 30 31
In a project directory, add Rocket.Chat.js.SDK as dependency:

```
npm install @rocket.chat/sdk --save
```

Next, create _easybot.js_ with the following:
32 33

```js
34
const { driver } = require('@rocket.chat/sdk');
35 36 37 38 39 40 41 42 43
// customize the following with your server and BOT account information
const HOST = 'myserver.com';
const USER = 'mysuer';
const PASS = 'mypassword';
const BOTNAME = 'easybot';  // name  bot response to
const SSL = true;  // server uses https ?
const ROOMS = ['GENERAL', 'myroom1'];

var myuserid;
44
// this simple bot does not handle errors, different message types, server resets 
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
// and other production situations 

const runbot = async () => {
    const conn = await driver.connect( { host: HOST, useSsl: SSL})
    myuserid = await driver.login({username: USER, password: PASS});
    const roomsJoined = await driver.joinRooms(ROOMS);
    console.log('joined rooms');

    // set up subscriptions - rooms we are interested in listening to
    const subscribed = await driver.subscribeToMessages();
    console.log('subscribed');

    // connect the processMessages callback
    const msgloop = await driver.reactToMessages( processMessages );
    console.log('connected and waiting for messages');

    // when a message is created in one of the ROOMS, we 
    // receive it in the processMesssages callback

    // greets from the first room in ROOMS 
SingLi's avatar
SingLi committed
65
    const sent = await driver.sendToRoom( BOTNAME + ' is listening ...',ROOMS[0]);
66 67 68 69 70 71 72 73 74 75 76 77 78 79
    console.log('Greeting message sent');
}

// callback for incoming messages filter and processing
const processMessages = async(err, message, messageOptions) => {
  if (!err) {
    // filter our own message
    if (message.u._id === myuserid) return;
    // can filter further based on message.rid
    const roomname = await driver.getRoomName(message.rid);
    if (message.msg.toLowerCase().startsWith(BOTNAME)) {
      const response = message.u.username + 
            ', how can ' + BOTNAME + ' help you with ' +
            message.msg.substr(BOTNAME.length + 1);
SingLi's avatar
SingLi committed
80
      const sentmsg = await driver.sendToRoom(response, roomname);
81 82 83 84 85 86
    }
  }
}

runbot()
```
87 88 89 90 91

The above code uses async calls to login, join rooms, subscribe to 
message streams and respond to messages (with a callback) using provided
options to filter the types of messages to respond to.

92 93 94 95 96 97 98 99
Make sure you customize the constants to your Rocket.Chat server account.  

Finally, run the bot:

```
node easybot.js
```

100
_TBD:  insert screenshot of bot working on a server_
101

102 103 104 105 106 107 108 109 110 111 112 113
### Demo

There's a simple listener script provided to demonstrate functionality locally.
[See the source here][start] and/or run it with `yarn start`.

The start script will log to console any message events that appear in its
stream. It will respond to a couple specific commands demonstrating usage of
the API helpers. Try messaging the bot directly one of the following:

- `tell everyone <something>` - It will send that "something" to everyone
- `who's online` - It will tell you who's online

114 115
## Overview

116 117 118
Using this package third party apps can control and query a Rocket.Chat server
instance, via Asteroid login and method calls as well as DDP for subscribing
to stream events.
119

120 121 122
Designed especially for chat automation, this SDK makes it easy for bot and
integration developers to provide the best solutions and experience for their
community.
123

124 125
For example, the Hubot Rocketchat adapter uses this package to enable chat-ops
workflows and multi-channel, multi-user, public and private interactions.
126
We have more bot features and adapters on the roadmap and encourage the
127
community to implement this SDK to provide adapters for their bot framework
128 129
or platform of choice.

130
## Docs
131

132
Full documentation can be generated locally using `yarn docs`.
133 134 135
This isn't in a format we can publish yet, but can be useful for development.

Below is just a summary:
136 137

---
138

139
The following modules are exported by the SDK:
140 141
- `driver` - Handles connection, method calls, room subscriptions (via Asteroid)
- `methodCache` - Manages results cache for calls to server (via LRU cache)
142
- `api` - Provides a client for making requests with Rocket.Chat's REST API
143

144 145
Access these modules by importing them from SDK, e.g:

146 147
For Node 8 / ES5

148 149 150
```js
const { driver, methodCache, api } = require('@rocket.chat/sdk')
```
151

152 153
For ES6 supporting platforms

154 155 156
```js
import { driver, methodCache, api } from '@rocket.chat/sdk'
```
157 158 159 160 161

Any Rocket.Chat server method can be called via `driver.callMethod`,
`driver.cacheCall` or `driver.asyncCall`. Server methods are not fully
documented, most require searching the Rocket.Chat codebase.

162 163 164 165 166
Driver methods use an [Asteroid][asteroid] DDP connection. See its own docs for
more advanced methods that can be called from the `driver.asteroid` interface.

Rocket.Chat REST API calls can be made via `api.get` or `api.post`, with
parameters defining the endpoint, payload and if authorization is required
167 168 169
(respectively). See the [REST API docs][rest] for details.

Some common requests for user queries are made available as simple helpers under
170
`api.users`, such as `api.users.onlineIds()` which returns the user IDs of all
171
online users. Run `ts-node src/utils/users.ts` for a demo of user query outputs.
172

173
## MESSAGE OBJECTS
174

175 176
---

177 178 179 180 181 182 183 184 185 186
The Rocket.Chat message schema can be found here:
https://rocket.chat/docs/developer-guides/schema-definition/

The structure for messages in this package matches that schema, with a
TypeScript interface defined here: https://github.com/RocketChat/Rocket.Chat.js.SDK/blob/master/src/config/messageInterfaces.ts

The `driver.prepareMessage` method (documented below) provides a helper for
simple message creation and the `message` module can also be imported to create
new `Message` class instances directly if detailed attributes are required.

187
## DRIVER METHODS
188 189 190

---

191
### `driver.connect(options[, cb])`
192 193

Connects to a Rocket.Chat server
194
- Options accepts `host` and `timeout` attributes
195
- Can return a promise, or use error-first callback pattern
196
- Resolves with an [Asteroid][asteroid] instance
197

198 199 200 201 202
### `driver.disconnect()`

Unsubscribe, logout, disconnect from Rocket.Chat
- Returns promise

203
### `driver.login([credentials])`
204 205 206

Login to Rocket.Chat via Asteroid
- Accepts object with `username` and/or `email` and `password`
207
- Uses defaults from env `ROCKETCHAT_USER` and `ROCKETCHAT_PASSWORD`
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
- Returns promise
- Resolves with logged in user ID

### `driver.logout()`

Logout current user via Asteroid
- Returns promise

### `driver.subscribe(topic, roomId)`

Subscribe to Meteor subscription
- Accepts parameters for Rocket.Chat streamer
- Returns promise
- Resolves with subscription instance (with ID)

### `driver.unsubscribe(subscription)`

Cancel a subscription
- Accepts a subscription instance
- Returns promise

### `driver.unsubscribeAll()`

Cancel all current subscriptions
- Returns promise

### `driver.subscribeToMessages()`

Shortcut to subscribe to user's message stream
- Uses `.subscribe` arguments with defaults
  - topic: `stream-room-messages`
  - roomId: `__my_messages__`
- Returns a subscription instance

242
### `driver.reactToMessages(callback)`
243

244 245 246
Once a subscription is created, using `driver.subscribeToMessages()` this method
can be used to attach a callback to changes in the message stream.

247
Fires callback with every change in subscriptions.
248 249 250 251
- Uses error-first callback pattern
- Second argument is the changed item
- Third argument is additional attributes, such as `roomType`

252 253 254 255
For example usage, see the Rocket.Chat Hubot adapter's receive function, which
is bound as a callback to this method:
https://github.com/RocketChat/hubot-rocketchat/blob/convert-es6/index.js#L97-L193

256 257 258 259 260 261 262 263 264 265 266
### `driver.respondToMessages(callback, options)`

Proxy for `reactToMessages` with some filtering of messages based on config.
This is a more user-friendly method for bots to subscribe to a message stream.

Fires callback after filters run on subscription events.
- Uses error-first callback pattern
- Second argument is the changed item
- Third argument is additional attributes, such as `roomType`

Accepts options object, that parallels respond filter env variables:
267 268
- options.rooms : respond to messages in joined rooms
- options.allPublic : respond to messages on all channels
269 270 271 272
- options.dm : respond to messages in DMs with the SDK user
- options.livechat : respond to messages in Livechat rooms
- options.edited : respond to edited messages

273 274 275 276 277
If rooms are given as option or set in the environment with `ROCKETCHAT_ROOM`
but have not been joined yet this method will join to those rooms automatically.

If `allPublic` is true, the `rooms` option will be ignored.

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
### `driver.asyncCall(method, params)`

Wraps server method calls to always be async
- Accepts a method name and params (array or single param)
- Returns a Promise

### `driver.cacheCall(method, key)`

Call server method with `methodCache`
- Accepts a method name and single param (used as cache key)
- Returns a promise
- Resolves with server results or cached if still valid

### `driver.callMethod(method, params)`

Implements either `asyncCall` or `cacheCall` if cache exists
- Accepts a method name and params (array or single param)
- Outcome depends on if `methodCache.create` was done for the method

### `driver.useLog(logger)`

Replace the default log, e.g. with one from a bot framework
- Accepts class or object with `debug`, `info`, `warn`, `error` methods.
- Returns nothing

### `driver.getRoomId(name)`

Get ID for a room by name
- Accepts name or ID string
- Is cached
- Returns a promise
- Resolves with room ID

### `driver.getRoomName(id)`

Get name for a room by ID
- Accepts ID string
- Is cached
- Returns a promise
- Resolves with room name

### `driver.getDirectMessageRoomId(username)`

Get ID for a DM room by its recipient's name
- Accepts string username
- Returns a promise
- Resolves with room ID

### `driver.joinRoom(room)`

Join the logged in user into a room
- Accepts room name or ID string
- Returns a promise

### `driver.joinRooms(rooms)`

As above, with array of room names/IDs

336
### `driver.prepareMessage(content[, roomId])`
337 338 339 340 341 342

Structure message content for sending
- Accepts a message object or message text string
- Optionally addressing to room ID with second param
- Returns a message object

343
### `driver.sendMessage(message)`
344

345 346 347 348 349
Send a prepared message object (with pre-defined room ID)
- Accepts a message object
- Returns a promise that resolves to sent message object

### `driver.sendToRoomId(content, roomId)`
350

351 352 353 354 355
Prepare and send string/s to specified room ID
- Accepts message text string or array of strings
- Returns a promise or array of promises that resolve to sent message object/s

### `driver.sendToRoom(content, room)`
356 357 358 359 360 361

As above, with room name instead of ID

### `driver.sendDirectToUser(content, username)`

As above, with username for DM instead of ID
362
- Creates DM room if it doesn't exist
363 364 365

---

366
## METHOD CACHE
367

368 369 370 371
[LRU][lru] is used to cache results from the server, to reduce unnecessary calls
for data that is unlikely to change, such as room IDs. Utility methods and env
vars allow configuring, creating and resetting caches for specific methods.

372 373 374 375 376
---

### `methodCache.use(instance)`

Set the instance to call methods on, with cached results
377
- Accepts an Asteroid instance (or possibly other classes)
378 379
- Returns nothing

380
### `methodCache.create(method[, options])`
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405

Setup a cache for a method call
- Accepts method name and cache options object, such as:
  - `max` Maximum size of cache
  - `maxAge` Maximum age of cache

### `methodCache.call(method, key)`

Get results of a prior method call or call and cache
- Accepts method name to call and key as single param
- Only methods with a single string argument can be cached (currently) due to 
the usage of this argument as the index for the cached results.

### `methodCache.has(method)`

Checking if method has been cached
- Accepts method name
- Returns bool

### `methodCache.get(method, key)`

Get results of a prior method call
- Accepts method name and key (argument method called with)
- Returns results at key

406
### `methodCache.reset(method[, key])`
407 408 409 410 411 412 413 414 415 416

Reset a cached method call's results
- Accepts a method name, optional key
- If key given, clears only that result set
- Returns bool

### `methodCache.resetAll()`

 Reset cached results for all methods
 - Returns nothing
417

418 419
---

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
### API CLIENT

[node-rest]: https://www.npmjs.com/package/node-rest-client
[rest-api]: https://rocket.chat/docs/developer-guides/rest-api/
We've included an [API client][node-rest] to make it super simple for bots and
apps consuming the SDK to call the [Rocket.Chat REST API][rest-api] endpoints.

By default, it will attempt to login with the same defaults or env config as
the driver, but the `.login` method could be used manually prior to requests to
use different credentials.

If a request is made to an endpoint requiring authentication, before login is
called, it will attempt to login first and keep the response token for later.

Bots and apps should manually call the API `.logout` method on shutdown if they
have used the API.

437 438
---

439 440 441 442
### `api.loggedIn()`

Returns boolean status of existing login

443
### `api.post(endpoint, data[, auth, ignore])`
444 445 446 447 448 449 450

Make a POST request to the REST API
- `endpoint` - The API resource ID, e.g. `channels.info`
- `data` - Request payload object to send, e.g. { roomName: 'general' }
- `auth` - If authorisation is required (defaults to true)
- Returns promise

451
### `api.get(endpoint, data[, auth, ignore])`
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476

Make a GET request to the REST API
- `endpoint` - The API endpoint resource ID, e.g. `users.list`
- `data` - Params (converted to query string), e.g. { fields: { 'username': 1 } }
- `auth` - If authorisation is required (defaults to true)
- Returns promise

### `api.login([user])`

Perform login with default or given credentials
- `user` object with `.username` and `.password` properties.
- Returns promise, resolves with login result

### `api.logout()`

Logout the current user. Returns promise

### `api.currentLogin`

Exported property with details of the current API session
- `.result` - The login request result
- `.username` - The logged in user's username
- `.userId` - The logged in user's ID
- `.authToken` - The current auth token

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
### `api.userFields`

Exported property for user query helper default fields
- Defaults to `{ name: 1, username: 1, status: 1, type: 1 }`
- See https://rocket.chat/docs/developer-guides/rest-api/query-and-fields-info/

### `api.users.all([fields])`

Helper for querying all users
- Optional fields object (see fields docs link above)
- Returns promise, resolves with array of user objects

### `api.users.allNames()`

Helper for querying all usernames
- Returns promise, resolves with array of usernames

### `api.users.allIDs()`

Helper for querying all user IDs
- Returns promise, resolves with array of IDs

### `api.users.online([fields])`

Helper for querying online users
- Optional fields object (see fields docs link above)
- Returns promise, resolves with array of user objects

### `api.users.onlineNames()`

Helper for querying online usernames
- Returns promise, resolves with array of usernames

510
### `api.users.onlineIds()`
511 512 513 514

Helper for querying online user IDs
- Returns promise, resolves with array of IDs

515 516 517
---

## Development
518 519

A local instance of Rocket.Chat is required for unit tests to confirm connection
520 521
and subscription methods are functional. And it helps to manually run your SDK
interactions (i.e. bots) locally while in development.
522 523 524

## Use as Dependency

525 526 527 528 529 530 531 532
```
yarn add @rocket.chat/sdk
```
or 

```
npm install --save @rocket.chat/sdk
```
533 534 535

ES6 module, using async

536
```js
537
import * as rocketchat from '@rocket.chat/sdk'
538

539
const asteroid = await rocketchat.driver.connect({ host: 'localhost:3000' })
540
console.log('connected', asteroid)
541 542
```

543 544
ES5 module, using callback

545
```js
546
const rocketchat = require('@rocket.chat/sdk')
547

548
rocketchat.driver.connect({ host: 'localhost:3000' }, function (err, asteroid) {
549 550 551 552
  if (err) console.error(err)
  else console.log('connected', asteroid)
})
```
553 554 555

### Settings

556 557
| Env var                | Description                                           |
| ---------------------- | ----------------------------------------------------- |
558 559 560
| `ROCKETCHAT_URL`*      | URL of the Rocket.Chat to connect to                  |
| `ROCKETCHAT_USER`*     | Username for bot account login                        |
| `ROCKETCHAT_PASSWORD`* | Password for bot account login                        |
561
| `ROCKETCHAT_AUTH`      | Set to 'ldap' to enable LDAP login                    |
562 563 564 565 566 567
| `ROCKETCHAT_USE_SSL`   | Force bot to connect with SSL                         |
| `ROCKETCHAT_ROOM`      | Respond listens in the named channel/s (can be csv)   |
| `LISTEN_ON_ALL_PUBLIC` | true/false, respond listens in all public channels    |
| `RESPOND_TO_LIVECHAT`  | true/false, respond listens in livechat               |
| `RESPOND_TO_DM`        | true/false, respond listens to DMs with bot           |
| `RESPOND_TO_EDITED`    | true/false, respond listens to edited messages        |
568
| `INTEGRATION_ID`       | ID applied to message object to integration source    |
569
| **Advanced configs**   |                                                       |
570 571 572 573
| `ROOM_CACHE_SIZE`      | Size of cache (LRU) for room (ID or name) lookups     |
| `ROOM_CACHE_MAX_AGE`   | Max age of cache for room lookups                     |
| `DM_ROOM_CACHE_SIZE`   | Size of cache for Direct Message room lookups         |
| `DM_ROOM_CACHE_MAX_AGE`| Max age of cache for DM lookups                       |
574 575 576
| **Test configs**       |                                                       |
| `ADMIN_USERNAME`       | Admin user password for API                           |
| `ADMIN_PASS`           | Admin user password for API                           |
577 578 579 580

These are only required in test and development, assuming in production they
will be passed from the adapter implementing this package.

581 582 583
`ROCKETCHAT_ROOM` is ignored when using `LISTEN_ON_ALL_PUBLIC`. This option also
allows the bot to listen and respond to messages _from all private groups_ where
the bot's user has been added as a member.
584

585 586 587
### Installing Rocket.Chat

Clone and run a new instance of Rocket.Chat locally, using either the internal
588
mongo or a dedicated local mongo for testing, so you won't affect any other
589 590 591
Rocket.Chat development you might do locally.

The following will provision a default admin user on build, so it can be used to
592
access the API, allowing SDK utils to prepare for and clean up tests.
593

594 595 596 597 598 599
```
git clone https://github.com/RocketChat/Rocket.Chat.git rc-sdk-test
cd rc-sdk-test
meteor npm install
export ADMIN_PASS=pass; export ADMIN_USERNAME=sdk; export MONGO_URL='mongodb://localhost:27017/rc-sdk-test'; meteor
```
600 601 602 603 604

Using `yarn` to run local tests and build scripts is recommended.

Do `npm install -g yarn` if you don't have it. Then setup the project:

605 606 607 608 609
```
git clone https://github.com/RocketChat/Rocket.Chat.js.SDK.git
cd Rocket.Chat.js.SDK
yarn
```
610

611
### Test and Build Scripts
612

613
- `yarn test` runs tests and coverage locally (pretest does lint)
614
- `yarn test:debug` runs tests without coverage, breaking for debug attach
615 616 617
- `yarn start` run locally from source, to allow manual testing of streams
- `yarn docs` generates API docs locally, then `open docs/index.html`
- `yarn build` runs tests, coverage, compiles, and tests package for publishing
618 619 620
- `yarn test:package` uses package-preview to make sure the published node
package can be required and run only with defined dependencies, to avoid errors
that might pass locally due to existing global dependencies or symlinks.
621

622 623
`yarn:hook` is run on git push hooks to prevent publishing with failing tests,
but won't change coverage to avoid making any working copy changes after commit.
624 625 626 627 628

### Integration Tests

The node scripts in `utils` are used to prepare for and clean up after test
interactions. They use the Rocket.Chat API to create a bot user and a mock human
629 630
user for the bot to interact with. It is always advised to only run tests with
a connection to a clean local or re-usable container instance of Rocket.Chat.
631 632 633 634

### Debugging

Configs are included in source for VS Code using Wallaby or Mocha Sidebar.