init
commit
5c3c0e66d4
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-const-assign": "warn",
|
||||||
|
"no-this-before-super": "warn",
|
||||||
|
"no-undef": "warn",
|
||||||
|
"no-unreachable": "warn",
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"constructor-super": "warn",
|
||||||
|
"valid-typeof": "warn"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
/node_modules
|
||||||
|
/yarn-error.log
|
||||||
|
/.nyc_output
|
||||||
|
/.env
|
||||||
|
/shrinkwrap.yaml
|
||||||
|
/package-lock.json
|
||||||
|
/docs/
|
||||||
|
.DS_Store
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extension": [
|
||||||
|
".ts"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/lib/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.d.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"reporter": [
|
||||||
|
"lcovonly",
|
||||||
|
"text"
|
||||||
|
],
|
||||||
|
"all": true
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"name": "Attach",
|
||||||
|
"port": 9229
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"tslint.autoFixOnSave": true,
|
||||||
|
"tslint.jsEnable": false,
|
||||||
|
"mocha-snippets.semicolon": false,
|
||||||
|
"mocha-snippets.glob": "src/**/*.spec.ts",
|
||||||
|
"mocha.files.glob": "src/**/*.spec.ts",
|
||||||
|
"mocha.requires": [
|
||||||
|
"dotenv/config",
|
||||||
|
"ts-node/register"
|
||||||
|
],
|
||||||
|
"mocha.options": {
|
||||||
|
"reporter": "list"
|
||||||
|
},
|
||||||
|
"coverage-gutters.showGutterCoverage": true,
|
||||||
|
"eslint.packageManager": "yarn",
|
||||||
|
"search.exclude": {
|
||||||
|
"**/dist": true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Rocket.Chat
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,634 @@
|
|||||||
|
[asteroid]: https://www.npmjs.com/package/asteroid
|
||||||
|
[lru]: https://www.npmjs.com/package/lru
|
||||||
|
[rest]: https://rocket.chat/docs/developer-guides/rest-api/
|
||||||
|
[start]: https://github.com/RocketChat/Rocket.Chat.js.SDK/blob/master/src/utils/start.ts
|
||||||
|
|
||||||
|
# Rocket.Chat Node.js SDK
|
||||||
|
|
||||||
|
Application interface for server methods and message stream subscriptions.
|
||||||
|
|
||||||
|
## Super Quick Start (30 seconds)
|
||||||
|
|
||||||
|
Create your own working BOT for Rocket.Chat, in seconds, at [glitch.com](https://glitch.com/~rocketchat-bot).
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
```
|
||||||
|
node -v
|
||||||
|
v8.9.3
|
||||||
|
```
|
||||||
|
|
||||||
|
In a project directory, add Rocket.Chat.js.SDK as dependency:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @rocket.chat/sdk --save
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, create _easybot.js_ with the following:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { driver } = require('@rocket.chat/sdk');
|
||||||
|
// 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;
|
||||||
|
// this simple bot does not handle errors, different message types, server resets
|
||||||
|
// 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
|
||||||
|
const sent = await driver.sendToRoom( BOTNAME + ' is listening ...',ROOMS[0]);
|
||||||
|
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);
|
||||||
|
const sentmsg = await driver.sendToRoom(response, roomname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runbot()
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Make sure you customize the constants to your Rocket.Chat server account.
|
||||||
|
|
||||||
|
Finally, run the bot:
|
||||||
|
|
||||||
|
```
|
||||||
|
node easybot.js
|
||||||
|
```
|
||||||
|
|
||||||
|
_TBD: insert screenshot of bot working on a server_
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For example, the Hubot Rocketchat adapter uses this package to enable chat-ops
|
||||||
|
workflows and multi-channel, multi-user, public and private interactions.
|
||||||
|
We have more bot features and adapters on the roadmap and encourage the
|
||||||
|
community to implement this SDK to provide adapters for their bot framework
|
||||||
|
or platform of choice.
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
Full documentation can be generated locally using `yarn docs`.
|
||||||
|
This isn't in a format we can publish yet, but can be useful for development.
|
||||||
|
|
||||||
|
Below is just a summary:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The following modules are exported by the SDK:
|
||||||
|
- `driver` - Handles connection, method calls, room subscriptions (via Asteroid)
|
||||||
|
- `methodCache` - Manages results cache for calls to server (via LRU cache)
|
||||||
|
- `api` - Provides a client for making requests with Rocket.Chat's REST API
|
||||||
|
|
||||||
|
Access these modules by importing them from SDK, e.g:
|
||||||
|
|
||||||
|
For Node 8 / ES5
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { driver, methodCache, api } = require('@rocket.chat/sdk')
|
||||||
|
```
|
||||||
|
|
||||||
|
For ES6 supporting platforms
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { driver, methodCache, api } from '@rocket.chat/sdk'
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
(respectively). See the [REST API docs][rest] for details.
|
||||||
|
|
||||||
|
Some common requests for user queries are made available as simple helpers under
|
||||||
|
`api.users`, such as `api.users.onlineIds()` which returns the user IDs of all
|
||||||
|
online users. Run `ts-node src/utils/users.ts` for a demo of user query outputs.
|
||||||
|
|
||||||
|
## MESSAGE OBJECTS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## DRIVER METHODS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `driver.connect(options[, cb])`
|
||||||
|
|
||||||
|
Connects to a Rocket.Chat server
|
||||||
|
- Options accepts `host` and `timeout` attributes
|
||||||
|
- Can return a promise, or use error-first callback pattern
|
||||||
|
- Resolves with an [Asteroid][asteroid] instance
|
||||||
|
|
||||||
|
### `driver.disconnect()`
|
||||||
|
|
||||||
|
Unsubscribe, logout, disconnect from Rocket.Chat
|
||||||
|
- Returns promise
|
||||||
|
|
||||||
|
### `driver.login([credentials])`
|
||||||
|
|
||||||
|
Login to Rocket.Chat via Asteroid
|
||||||
|
- Accepts object with `username` and/or `email` and `password`
|
||||||
|
- Uses defaults from env `ROCKETCHAT_USER` and `ROCKETCHAT_PASSWORD`
|
||||||
|
- 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
|
||||||
|
|
||||||
|
### `driver.reactToMessages(callback)`
|
||||||
|
|
||||||
|
Once a subscription is created, using `driver.subscribeToMessages()` this method
|
||||||
|
can be used to attach a callback to changes in the message stream.
|
||||||
|
|
||||||
|
Fires callback with every change in subscriptions.
|
||||||
|
- Uses error-first callback pattern
|
||||||
|
- Second argument is the changed item
|
||||||
|
- Third argument is additional attributes, such as `roomType`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `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:
|
||||||
|
- options.rooms : respond to messages in joined rooms
|
||||||
|
- options.allPublic : respond to messages on all channels
|
||||||
|
- 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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### `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
|
||||||
|
|
||||||
|
### `driver.prepareMessage(content[, roomId])`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `driver.sendMessage(message)`
|
||||||
|
|
||||||
|
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)`
|
||||||
|
|
||||||
|
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)`
|
||||||
|
|
||||||
|
As above, with room name instead of ID
|
||||||
|
|
||||||
|
### `driver.sendDirectToUser(content, username)`
|
||||||
|
|
||||||
|
As above, with username for DM instead of ID
|
||||||
|
- Creates DM room if it doesn't exist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## METHOD CACHE
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `methodCache.use(instance)`
|
||||||
|
|
||||||
|
Set the instance to call methods on, with cached results
|
||||||
|
- Accepts an Asteroid instance (or possibly other classes)
|
||||||
|
- Returns nothing
|
||||||
|
|
||||||
|
### `methodCache.create(method[, options])`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `methodCache.reset(method[, key])`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `api.loggedIn()`
|
||||||
|
|
||||||
|
Returns boolean status of existing login
|
||||||
|
|
||||||
|
### `api.post(endpoint, data[, auth, ignore])`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `api.get(endpoint, data[, auth, ignore])`
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
### `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
|
||||||
|
|
||||||
|
### `api.users.onlineIds()`
|
||||||
|
|
||||||
|
Helper for querying online user IDs
|
||||||
|
- Returns promise, resolves with array of IDs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
A local instance of Rocket.Chat is required for unit tests to confirm connection
|
||||||
|
and subscription methods are functional. And it helps to manually run your SDK
|
||||||
|
interactions (i.e. bots) locally while in development.
|
||||||
|
|
||||||
|
## Use as Dependency
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add @rocket.chat/sdk
|
||||||
|
```
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save @rocket.chat/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
ES6 module, using async
|
||||||
|
|
||||||
|
```js
|
||||||
|
import * as rocketchat from '@rocket.chat/sdk'
|
||||||
|
|
||||||
|
const asteroid = await rocketchat.driver.connect({ host: 'localhost:3000' })
|
||||||
|
console.log('connected', asteroid)
|
||||||
|
```
|
||||||
|
|
||||||
|
ES5 module, using callback
|
||||||
|
|
||||||
|
```js
|
||||||
|
const rocketchat = require('@rocket.chat/sdk')
|
||||||
|
|
||||||
|
rocketchat.driver.connect({ host: 'localhost:3000' }, function (err, asteroid) {
|
||||||
|
if (err) console.error(err)
|
||||||
|
else console.log('connected', asteroid)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
| Env var | Description |
|
||||||
|
| ---------------------- | ----------------------------------------------------- |
|
||||||
|
| `ROCKETCHAT_URL`* | URL of the Rocket.Chat to connect to |
|
||||||
|
| `ROCKETCHAT_USER`* | Username for bot account login |
|
||||||
|
| `ROCKETCHAT_PASSWORD`* | Password for bot account login |
|
||||||
|
| `ROCKETCHAT_AUTH` | Set to 'ldap' to enable LDAP login |
|
||||||
|
| `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 |
|
||||||
|
| `INTEGRATION_ID` | ID applied to message object to integration source |
|
||||||
|
| **Advanced configs** | |
|
||||||
|
| `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 |
|
||||||
|
| **Test configs** | |
|
||||||
|
| `ADMIN_USERNAME` | Admin user password for API |
|
||||||
|
| `ADMIN_PASS` | Admin user password for API |
|
||||||
|
|
||||||
|
These are only required in test and development, assuming in production they
|
||||||
|
will be passed from the adapter implementing this package.
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
### Installing Rocket.Chat
|
||||||
|
|
||||||
|
Clone and run a new instance of Rocket.Chat locally, using either the internal
|
||||||
|
mongo or a dedicated local mongo for testing, so you won't affect any other
|
||||||
|
Rocket.Chat development you might do locally.
|
||||||
|
|
||||||
|
The following will provision a default admin user on build, so it can be used to
|
||||||
|
access the API, allowing SDK utils to prepare for and clean up tests.
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/RocketChat/Rocket.Chat.js.SDK.git
|
||||||
|
cd Rocket.Chat.js.SDK
|
||||||
|
yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test and Build Scripts
|
||||||
|
|
||||||
|
- `yarn test` runs tests and coverage locally (pretest does lint)
|
||||||
|
- `yarn test:debug` runs tests without coverage, breaking for debug attach
|
||||||
|
- `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
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
Configs are included in source for VS Code using Wallaby or Mocha Sidebar.
|
@ -0,0 +1,787 @@
|
|||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/api.ts
|
||||||
|
FN:25,loggedIn
|
||||||
|
FN:42,getQueryString
|
||||||
|
FN:44,(anonymous_9)
|
||||||
|
FN:57,setAuth
|
||||||
|
FN:63,getHeaders
|
||||||
|
FN:76,clearHeaders
|
||||||
|
FN:82,success
|
||||||
|
FN:104,post
|
||||||
|
FN:114,(anonymous_16)
|
||||||
|
FN:115,(anonymous_17)
|
||||||
|
FN:119,(anonymous_18)
|
||||||
|
FN:136,get
|
||||||
|
FN:147,(anonymous_21)
|
||||||
|
FN:148,(anonymous_22)
|
||||||
|
FN:152,(anonymous_23)
|
||||||
|
FN:166,login
|
||||||
|
FN:196,logout
|
||||||
|
FN:202,(anonymous_27)
|
||||||
|
FN:213,(anonymous_28)
|
||||||
|
FN:213,(anonymous_29)
|
||||||
|
FN:214,(anonymous_30)
|
||||||
|
FN:214,(anonymous_31)
|
||||||
|
FN:214,(anonymous_32)
|
||||||
|
FN:215,(anonymous_33)
|
||||||
|
FN:215,(anonymous_34)
|
||||||
|
FN:215,(anonymous_35)
|
||||||
|
FN:216,(anonymous_36)
|
||||||
|
FN:216,(anonymous_37)
|
||||||
|
FN:217,(anonymous_38)
|
||||||
|
FN:217,(anonymous_39)
|
||||||
|
FN:217,(anonymous_40)
|
||||||
|
FN:218,(anonymous_41)
|
||||||
|
FN:218,(anonymous_42)
|
||||||
|
FN:218,(anonymous_43)
|
||||||
|
FNF:34
|
||||||
|
FNH:16
|
||||||
|
FNDA:65,loggedIn
|
||||||
|
FNDA:50,getQueryString
|
||||||
|
FNDA:66,(anonymous_9)
|
||||||
|
FNDA:12,setAuth
|
||||||
|
FNDA:81,getHeaders
|
||||||
|
FNDA:15,clearHeaders
|
||||||
|
FNDA:83,success
|
||||||
|
FNDA:31,post
|
||||||
|
FNDA:31,(anonymous_16)
|
||||||
|
FNDA:31,(anonymous_17)
|
||||||
|
FNDA:0,(anonymous_18)
|
||||||
|
FNDA:47,get
|
||||||
|
FNDA:47,(anonymous_21)
|
||||||
|
FNDA:47,(anonymous_22)
|
||||||
|
FNDA:0,(anonymous_23)
|
||||||
|
FNDA:26,login
|
||||||
|
FNDA:25,logout
|
||||||
|
FNDA:12,(anonymous_27)
|
||||||
|
FNDA:0,(anonymous_28)
|
||||||
|
FNDA:0,(anonymous_29)
|
||||||
|
FNDA:0,(anonymous_30)
|
||||||
|
FNDA:0,(anonymous_31)
|
||||||
|
FNDA:0,(anonymous_32)
|
||||||
|
FNDA:0,(anonymous_33)
|
||||||
|
FNDA:0,(anonymous_34)
|
||||||
|
FNDA:0,(anonymous_35)
|
||||||
|
FNDA:0,(anonymous_36)
|
||||||
|
FNDA:0,(anonymous_37)
|
||||||
|
FNDA:0,(anonymous_38)
|
||||||
|
FNDA:0,(anonymous_39)
|
||||||
|
FNDA:0,(anonymous_40)
|
||||||
|
FNDA:0,(anonymous_41)
|
||||||
|
FNDA:0,(anonymous_42)
|
||||||
|
FNDA:0,(anonymous_43)
|
||||||
|
DA:1,1
|
||||||
|
DA:2,1
|
||||||
|
DA:3,1
|
||||||
|
DA:17,1
|
||||||
|
DA:25,1
|
||||||
|
DA:26,65
|
||||||
|
DA:30,1
|
||||||
|
DA:31,1
|
||||||
|
DA:37,1
|
||||||
|
DA:42,1
|
||||||
|
DA:43,50
|
||||||
|
DA:44,36
|
||||||
|
DA:45,66
|
||||||
|
DA:48,66
|
||||||
|
DA:53,1
|
||||||
|
DA:54,1
|
||||||
|
DA:57,1
|
||||||
|
DA:58,12
|
||||||
|
DA:59,12
|
||||||
|
DA:63,1
|
||||||
|
DA:64,81
|
||||||
|
DA:65,67
|
||||||
|
DA:70,1
|
||||||
|
DA:72,66
|
||||||
|
DA:76,1
|
||||||
|
DA:77,15
|
||||||
|
DA:78,15
|
||||||
|
DA:82,1
|
||||||
|
DA:83,83
|
||||||
|
DA:104,1
|
||||||
|
DA:110,31
|
||||||
|
DA:111,31
|
||||||
|
DA:112,31
|
||||||
|
DA:113,31
|
||||||
|
DA:114,31
|
||||||
|
DA:115,31
|
||||||
|
DA:116,31
|
||||||
|
DA:117,31
|
||||||
|
DA:118,31
|
||||||
|
DA:119,0
|
||||||
|
DA:121,31
|
||||||
|
DA:122,31
|
||||||
|
DA:124,0
|
||||||
|
DA:125,0
|
||||||
|
DA:136,1
|
||||||
|
DA:142,47
|
||||||
|
DA:143,47
|
||||||
|
DA:144,47
|
||||||
|
DA:145,47
|
||||||
|
DA:146,47
|
||||||
|
DA:147,47
|
||||||
|
DA:148,47
|
||||||
|
DA:149,47
|
||||||
|
DA:150,47
|
||||||
|
DA:151,47
|
||||||
|
DA:152,0
|
||||||
|
DA:154,47
|
||||||
|
DA:155,47
|
||||||
|
DA:157,0
|
||||||
|
DA:166,1
|
||||||
|
DA:170,26
|
||||||
|
DA:171,26
|
||||||
|
DA:172,19
|
||||||
|
DA:173,19
|
||||||
|
DA:174,14
|
||||||
|
DA:176,5
|
||||||
|
DA:179,12
|
||||||
|
DA:180,12
|
||||||
|
DA:181,12
|
||||||
|
DA:187,12
|
||||||
|
DA:188,12
|
||||||
|
DA:189,12
|
||||||
|
DA:191,0
|
||||||
|
DA:196,1
|
||||||
|
DA:197,25
|
||||||
|
DA:198,13
|
||||||
|
DA:199,13
|
||||||
|
DA:201,12
|
||||||
|
DA:202,12
|
||||||
|
DA:203,12
|
||||||
|
DA:204,12
|
||||||
|
DA:209,1
|
||||||
|
DA:212,1
|
||||||
|
DA:213,0
|
||||||
|
DA:214,0
|
||||||
|
DA:215,0
|
||||||
|
DA:216,0
|
||||||
|
DA:217,0
|
||||||
|
DA:218,0
|
||||||
|
LF:89
|
||||||
|
LH:77
|
||||||
|
BRDA:38,0,0,1
|
||||||
|
BRDA:38,0,1,0
|
||||||
|
BRDA:43,1,0,14
|
||||||
|
BRDA:43,1,1,36
|
||||||
|
BRDA:43,2,0,50
|
||||||
|
BRDA:43,2,1,38
|
||||||
|
BRDA:43,2,2,38
|
||||||
|
BRDA:46,3,0,3
|
||||||
|
BRDA:46,3,1,63
|
||||||
|
BRDA:63,4,0,1
|
||||||
|
BRDA:64,5,0,14
|
||||||
|
BRDA:64,5,1,67
|
||||||
|
BRDA:65,6,0,1
|
||||||
|
BRDA:65,6,1,66
|
||||||
|
BRDA:66,7,0,67
|
||||||
|
BRDA:66,7,1,66
|
||||||
|
BRDA:66,7,2,66
|
||||||
|
BRDA:66,7,3,66
|
||||||
|
BRDA:92,8,0,81
|
||||||
|
BRDA:92,8,1,2
|
||||||
|
BRDA:85,9,0,83
|
||||||
|
BRDA:85,9,1,83
|
||||||
|
BRDA:85,9,2,57
|
||||||
|
BRDA:85,9,3,82
|
||||||
|
BRDA:85,9,4,26
|
||||||
|
BRDA:85,9,5,57
|
||||||
|
BRDA:85,9,6,55
|
||||||
|
BRDA:85,9,7,2
|
||||||
|
BRDA:85,9,8,0
|
||||||
|
BRDA:85,9,9,0
|
||||||
|
BRDA:107,10,0,4
|
||||||
|
BRDA:112,11,0,0
|
||||||
|
BRDA:112,11,1,31
|
||||||
|
BRDA:112,12,0,31
|
||||||
|
BRDA:112,12,1,19
|
||||||
|
BRDA:116,13,0,0
|
||||||
|
BRDA:116,13,1,31
|
||||||
|
BRDA:117,14,0,0
|
||||||
|
BRDA:117,14,1,31
|
||||||
|
BRDA:139,15,0,17
|
||||||
|
BRDA:144,16,0,1
|
||||||
|
BRDA:144,16,1,46
|
||||||
|
BRDA:144,17,0,47
|
||||||
|
BRDA:144,17,1,46
|
||||||
|
BRDA:149,18,0,0
|
||||||
|
BRDA:149,18,1,47
|
||||||
|
BRDA:150,19,0,0
|
||||||
|
BRDA:150,19,1,47
|
||||||
|
BRDA:166,20,0,6
|
||||||
|
BRDA:171,21,0,19
|
||||||
|
BRDA:171,21,1,7
|
||||||
|
BRDA:173,22,0,14
|
||||||
|
BRDA:173,22,1,5
|
||||||
|
BRDA:180,23,0,12
|
||||||
|
BRDA:180,23,1,0
|
||||||
|
BRDA:180,24,0,12
|
||||||
|
BRDA:180,24,1,12
|
||||||
|
BRDA:180,24,2,12
|
||||||
|
BRDA:197,25,0,13
|
||||||
|
BRDA:197,25,1,12
|
||||||
|
BRDA:213,26,0,0
|
||||||
|
BRDA:216,27,0,0
|
||||||
|
BRF:62
|
||||||
|
BRH:51
|
||||||
|
end_of_record
|
||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/driver.ts
|
||||||
|
FN:78,useLog
|
||||||
|
FN:99,connect
|
||||||
|
FN:103,(anonymous_10)
|
||||||
|
FN:110,(anonymous_11)
|
||||||
|
FN:111,(anonymous_12)
|
||||||
|
FN:116,(anonymous_13)
|
||||||
|
FN:118,(anonymous_14)
|
||||||
|
FN:129,(anonymous_15)
|
||||||
|
FN:141,disconnect
|
||||||
|
FN:145,(anonymous_17)
|
||||||
|
FN:155,setupMethodCache
|
||||||
|
FN:176,asyncCall
|
||||||
|
FN:180,(anonymous_20)
|
||||||
|
FN:184,(anonymous_21)
|
||||||
|
FN:199,callMethod
|
||||||
|
FN:210,cacheCall
|
||||||
|
FN:212,(anonymous_24)
|
||||||
|
FN:216,(anonymous_25)
|
||||||
|
FN:228,login
|
||||||
|
FN:249,(anonymous_27)
|
||||||
|
FN:253,(anonymous_28)
|
||||||
|
FN:260,logout
|
||||||
|
FN:262,(anonymous_30)
|
||||||
|
FN:273,subscribe
|
||||||
|
FN:277,(anonymous_32)
|
||||||
|
FN:282,(anonymous_33)
|
||||||
|
FN:290,unsubscribe
|
||||||
|
FN:300,unsubscribeAll
|
||||||
|
FN:301,(anonymous_36)
|
||||||
|
FN:308,subscribeToMessages
|
||||||
|
FN:310,(anonymous_38)
|
||||||
|
FN:339,reactToMessages
|
||||||
|
FN:342,(anonymous_40)
|
||||||
|
FN:367,respondToMessages
|
||||||
|
FN:384,(anonymous_42)
|
||||||
|
FN:390,(anonymous_43)
|
||||||
|
FN:390,(anonymous_44)
|
||||||
|
FN:436,getRoomId
|
||||||
|
FN:441,getRoomName
|
||||||
|
FN:450,getDirectMessageRoomId
|
||||||
|
FN:452,(anonymous_48)
|
||||||
|
FN:456,joinRoom
|
||||||
|
FN:468,leaveRoom
|
||||||
|
FN:480,joinRooms
|
||||||
|
FN:481,(anonymous_54)
|
||||||
|
FN:488,prepareMessage
|
||||||
|
FN:501,sendMessage
|
||||||
|
FN:514,sendToRoomId
|
||||||
|
FN:521,(anonymous_58)
|
||||||
|
FN:532,sendToRoom
|
||||||
|
FN:537,(anonymous_60)
|
||||||
|
FN:545,sendDirectToUser
|
||||||
|
FN:550,(anonymous_62)
|
||||||
|
FN:557,editMessage
|
||||||
|
FN:566,setReaction
|
||||||
|
FNF:55
|
||||||
|
FNH:47
|
||||||
|
FNDA:0,useLog
|
||||||
|
FNDA:37,connect
|
||||||
|
FNDA:37,(anonymous_10)
|
||||||
|
FNDA:33,(anonymous_11)
|
||||||
|
FNDA:1,(anonymous_12)
|
||||||
|
FNDA:0,(anonymous_13)
|
||||||
|
FNDA:4,(anonymous_14)
|
||||||
|
FNDA:33,(anonymous_15)
|
||||||
|
FNDA:1,disconnect
|
||||||
|
FNDA:1,(anonymous_17)
|
||||||
|
FNDA:37,setupMethodCache
|
||||||
|
FNDA:23,asyncCall
|
||||||
|
FNDA:0,(anonymous_20)
|
||||||
|
FNDA:23,(anonymous_21)
|
||||||
|
FNDA:1,callMethod
|
||||||
|
FNDA:13,cacheCall
|
||||||
|
FNDA:0,(anonymous_24)
|
||||||
|
FNDA:13,(anonymous_25)
|
||||||
|
FNDA:28,login
|
||||||
|
FNDA:28,(anonymous_27)
|
||||||
|
FNDA:0,(anonymous_28)
|
||||||
|
FNDA:2,logout
|
||||||
|
FNDA:0,(anonymous_30)
|
||||||
|
FNDA:15,subscribe
|
||||||
|
FNDA:15,(anonymous_32)
|
||||||
|
FNDA:15,(anonymous_33)
|
||||||
|
FNDA:8,unsubscribe
|
||||||
|
FNDA:1,unsubscribeAll
|
||||||
|
FNDA:8,(anonymous_36)
|
||||||
|
FNDA:15,subscribeToMessages
|
||||||
|
FNDA:15,(anonymous_38)
|
||||||
|
FNDA:12,reactToMessages
|
||||||
|
FNDA:151,(anonymous_40)
|
||||||
|
FNDA:10,respondToMessages
|
||||||
|
FNDA:0,(anonymous_42)
|
||||||
|
FNDA:40,(anonymous_43)
|
||||||
|
FNDA:40,(anonymous_44)
|
||||||
|
FNDA:8,getRoomId
|
||||||
|
FNDA:3,getRoomName
|
||||||
|
FNDA:2,getDirectMessageRoomId
|
||||||
|
FNDA:2,(anonymous_48)
|
||||||
|
FNDA:4,joinRoom
|
||||||
|
FNDA:0,leaveRoom
|
||||||
|
FNDA:2,joinRooms
|
||||||
|
FNDA:4,(anonymous_54)
|
||||||
|
FNDA:14,prepareMessage
|
||||||
|
FNDA:15,sendMessage
|
||||||
|
FNDA:8,sendToRoomId
|
||||||
|
FNDA:6,(anonymous_58)
|
||||||
|
FNDA:2,sendToRoom
|
||||||
|
FNDA:2,(anonymous_60)
|
||||||
|
FNDA:2,sendDirectToUser
|
||||||
|
FNDA:2,(anonymous_62)
|
||||||
|
FNDA:1,editMessage
|
||||||
|
FNDA:2,setReaction
|
||||||
|
DA:1,1
|
||||||
|
DA:2,1
|
||||||
|
DA:3,1
|
||||||
|
DA:4,1
|
||||||
|
DA:5,1
|
||||||
|
DA:19,1
|
||||||
|
DA:23,1
|
||||||
|
DA:24,1
|
||||||
|
DA:37,1
|
||||||
|
DA:46,1
|
||||||
|
DA:58,1
|
||||||
|
DA:68,1
|
||||||
|
DA:78,1
|
||||||
|
DA:79,0
|
||||||
|
DA:99,1
|
||||||
|
DA:103,37
|
||||||
|
DA:104,37
|
||||||
|
DA:105,37
|
||||||
|
DA:106,37
|
||||||
|
DA:107,37
|
||||||
|
DA:109,37
|
||||||
|
DA:110,37
|
||||||
|
DA:111,33
|
||||||
|
DA:114,33
|
||||||
|
DA:116,37
|
||||||
|
DA:117,37
|
||||||
|
DA:118,37
|
||||||
|
DA:119,4
|
||||||
|
DA:120,4
|
||||||
|
DA:121,4
|
||||||
|
DA:122,4
|
||||||
|
DA:123,4
|
||||||
|
DA:128,37
|
||||||
|
DA:129,37
|
||||||
|
DA:130,33
|
||||||
|
DA:132,33
|
||||||
|
DA:133,33
|
||||||
|
DA:134,33
|
||||||
|
DA:141,1
|
||||||
|
DA:142,1
|
||||||
|
DA:143,1
|
||||||
|
DA:144,1
|
||||||
|
DA:145,1
|
||||||
|
DA:156,37
|
||||||
|
DA:157,37
|
||||||
|
DA:165,37
|
||||||
|
DA:176,1
|
||||||
|
DA:177,23
|
||||||
|
DA:178,23
|
||||||
|
DA:179,23
|
||||||
|
DA:181,0
|
||||||
|
DA:182,0
|
||||||
|
DA:185,23
|
||||||
|
DA:188,23
|
||||||
|
DA:199,1
|
||||||
|
DA:200,1
|
||||||
|
DA:210,1
|
||||||
|
DA:211,13
|
||||||
|
DA:213,0
|
||||||
|
DA:214,0
|
||||||
|
DA:217,13
|
||||||
|
DA:220,13
|
||||||
|
DA:228,1
|
||||||
|
DA:242,28
|
||||||
|
DA:243,28
|
||||||
|
DA:248,28
|
||||||
|
DA:250,28
|
||||||
|
DA:251,28
|
||||||
|
DA:254,0
|
||||||
|
DA:255,0
|
||||||
|
DA:260,1
|
||||||
|
DA:261,2
|
||||||
|
DA:263,0
|
||||||
|
DA:264,0
|
||||||
|
DA:273,1
|
||||||
|
DA:277,15
|
||||||
|
DA:278,15
|
||||||
|
DA:279,15
|
||||||
|
DA:280,15
|
||||||
|
DA:281,15
|
||||||
|
DA:283,15
|
||||||
|
DA:284,15
|
||||||
|
DA:290,1
|
||||||
|
DA:291,8
|
||||||
|
DA:292,8
|
||||||
|
DA:293,8
|
||||||
|
DA:295,8
|
||||||
|
DA:296,8
|
||||||
|
DA:300,1
|
||||||
|
DA:301,8
|
||||||
|
DA:308,1
|
||||||
|
DA:309,15
|
||||||
|
DA:311,15
|
||||||
|
DA:312,15
|
||||||
|
DA:339,1
|
||||||
|
DA:340,12
|
||||||
|
DA:342,12
|
||||||
|
DA:343,151
|
||||||
|
DA:344,151
|
||||||
|
DA:345,151
|
||||||
|
DA:346,151
|
||||||
|
DA:347,81
|
||||||
|
DA:348,81
|
||||||
|
DA:350,70
|
||||||
|
DA:353,0
|
||||||
|
DA:367,1
|
||||||
|
DA:371,10
|
||||||
|
DA:373,10
|
||||||
|
DA:377,10
|
||||||
|
DA:383,1
|
||||||
|
DA:385,0
|
||||||
|
DA:389,10
|
||||||
|
DA:390,40
|
||||||
|
DA:391,40
|
||||||
|
DA:392,0
|
||||||
|
DA:393,0
|
||||||
|
DA:397,40
|
||||||
|
DA:400,39
|
||||||
|
DA:401,39
|
||||||
|
DA:404,38
|
||||||
|
DA:405,38
|
||||||
|
DA:408,38
|
||||||
|
DA:411,38
|
||||||
|
DA:414,38
|
||||||
|
DA:417,36
|
||||||
|
DA:420,36
|
||||||
|
DA:423,7
|
||||||
|
DA:424,7
|
||||||
|
DA:427,7
|
||||||
|
DA:429,10
|
||||||
|
DA:436,1
|
||||||
|
DA:437,8
|
||||||
|
DA:441,1
|
||||||
|
DA:442,3
|
||||||
|
DA:450,1
|
||||||
|
DA:451,2
|
||||||
|
DA:452,2
|
||||||
|
DA:456,1
|
||||||
|
DA:457,4
|
||||||
|
DA:458,4
|
||||||
|
DA:459,4
|
||||||
|
DA:460,0
|
||||||
|
DA:462,4
|
||||||
|
DA:463,4
|
||||||
|
DA:468,1
|
||||||
|
DA:469,0
|
||||||
|
DA:470,0
|
||||||
|
DA:471,0
|
||||||
|
DA:472,0
|
||||||
|
DA:474,0
|
||||||
|
DA:475,0
|
||||||
|
DA:480,1
|
||||||
|
DA:481,4
|
||||||
|
DA:488,1
|
||||||
|
DA:492,14
|
||||||
|
DA:493,14
|
||||||
|
DA:494,14
|
||||||
|
DA:501,1
|
||||||
|
DA:502,15
|
||||||
|
DA:514,1
|
||||||
|
DA:518,8
|
||||||
|
DA:519,5
|
||||||
|
DA:521,3
|
||||||
|
DA:522,6
|
||||||
|
DA:532,1
|
||||||
|
DA:536,2
|
||||||
|
DA:537,2
|
||||||
|
DA:545,1
|
||||||
|
DA:549,2
|
||||||
|
DA:550,2
|
||||||
|
DA:557,1
|
||||||
|
DA:558,1
|
||||||
|
DA:566,1
|
||||||
|
DA:567,2
|
||||||
|
LF:174
|
||||||
|
LH:154
|
||||||
|
BRDA:100,0,0,30
|
||||||
|
BRDA:123,1,0,3
|
||||||
|
BRDA:123,1,1,1
|
||||||
|
BRDA:128,2,0,37
|
||||||
|
BRDA:128,2,1,0
|
||||||
|
BRDA:133,3,0,2
|
||||||
|
BRDA:133,3,1,31
|
||||||
|
BRDA:177,4,0,21
|
||||||
|
BRDA:177,4,1,2
|
||||||
|
BRDA:186,5,0,16
|
||||||
|
BRDA:186,5,1,7
|
||||||
|
BRDA:201,6,0,1
|
||||||
|
BRDA:201,6,1,0
|
||||||
|
BRDA:200,7,0,1
|
||||||
|
BRDA:200,7,1,1
|
||||||
|
BRDA:218,8,0,12
|
||||||
|
BRDA:218,8,1,1
|
||||||
|
BRDA:228,9,0,28
|
||||||
|
BRDA:244,10,0,28
|
||||||
|
BRDA:244,10,1,28
|
||||||
|
BRDA:292,11,0,0
|
||||||
|
BRDA:292,11,1,8
|
||||||
|
BRDA:344,12,0,151
|
||||||
|
BRDA:344,12,1,0
|
||||||
|
BRDA:344,13,0,151
|
||||||
|
BRDA:344,13,1,151
|
||||||
|
BRDA:346,14,0,81
|
||||||
|
BRDA:346,14,1,70
|
||||||
|
BRDA:369,15,0,1
|
||||||
|
BRDA:377,16,0,1
|
||||||
|
BRDA:377,16,1,9
|
||||||
|
BRDA:378,17,0,10
|
||||||
|
BRDA:378,17,1,10
|
||||||
|
BRDA:378,17,2,1
|
||||||
|
BRDA:378,17,3,1
|
||||||
|
BRDA:391,18,0,0
|
||||||
|
BRDA:391,18,1,40
|
||||||
|
BRDA:397,19,0,1
|
||||||
|
BRDA:397,19,1,39
|
||||||
|
BRDA:401,20,0,1
|
||||||
|
BRDA:401,20,1,38
|
||||||
|
BRDA:401,21,0,39
|
||||||
|
BRDA:401,21,1,2
|
||||||
|
BRDA:405,22,0,0
|
||||||
|
BRDA:405,22,1,38
|
||||||
|
BRDA:405,23,0,38
|
||||||
|
BRDA:405,23,1,0
|
||||||
|
BRDA:408,24,0,0
|
||||||
|
BRDA:408,24,1,38
|
||||||
|
BRDA:408,25,0,38
|
||||||
|
BRDA:408,25,1,38
|
||||||
|
BRDA:408,25,2,37
|
||||||
|
BRDA:414,26,0,2
|
||||||
|
BRDA:414,26,1,36
|
||||||
|
BRDA:414,27,0,38
|
||||||
|
BRDA:414,27,1,35
|
||||||
|
BRDA:417,28,0,1
|
||||||
|
BRDA:417,28,1,35
|
||||||
|
BRDA:420,29,0,29
|
||||||
|
BRDA:420,29,1,7
|
||||||
|
BRDA:459,30,0,0
|
||||||
|
BRDA:459,30,1,4
|
||||||
|
BRDA:471,31,0,0
|
||||||
|
BRDA:471,31,1,0
|
||||||
|
BRDA:493,32,0,11
|
||||||
|
BRDA:493,32,1,3
|
||||||
|
BRDA:518,33,0,5
|
||||||
|
BRDA:518,33,1,3
|
||||||
|
BRF:68
|
||||||
|
BRH:57
|
||||||
|
end_of_record
|
||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/log.ts
|
||||||
|
FN:5,(anonymous_0)
|
||||||
|
FN:8,(anonymous_1)
|
||||||
|
FN:11,(anonymous_2)
|
||||||
|
FN:14,(anonymous_3)
|
||||||
|
FN:17,(anonymous_4)
|
||||||
|
FN:24,replaceLog
|
||||||
|
FN:28,silence
|
||||||
|
FN:30,(anonymous_7)
|
||||||
|
FN:31,(anonymous_8)
|
||||||
|
FN:32,(anonymous_9)
|
||||||
|
FN:33,(anonymous_10)
|
||||||
|
FN:34,(anonymous_11)
|
||||||
|
FNF:12
|
||||||
|
FNH:4
|
||||||
|
FNDA:0,(anonymous_0)
|
||||||
|
FNDA:0,(anonymous_1)
|
||||||
|
FNDA:0,(anonymous_2)
|
||||||
|
FNDA:0,(anonymous_3)
|
||||||
|
FNDA:0,(anonymous_4)
|
||||||
|
FNDA:2,replaceLog
|
||||||
|
FNDA:2,silence
|
||||||
|
FNDA:330,(anonymous_7)
|
||||||
|
FNDA:314,(anonymous_8)
|
||||||
|
FNDA:0,(anonymous_9)
|
||||||
|
FNDA:0,(anonymous_10)
|
||||||
|
FNDA:0,(anonymous_11)
|
||||||
|
DA:6,0
|
||||||
|
DA:9,0
|
||||||
|
DA:12,0
|
||||||
|
DA:15,0
|
||||||
|
DA:18,0
|
||||||
|
DA:22,1
|
||||||
|
DA:25,2
|
||||||
|
DA:29,2
|
||||||
|
DA:30,330
|
||||||
|
DA:31,314
|
||||||
|
DA:32,0
|
||||||
|
DA:33,0
|
||||||
|
DA:34,0
|
||||||
|
DA:39,1
|
||||||
|
DA:40,1
|
||||||
|
DA:41,1
|
||||||
|
LF:16
|
||||||
|
LH:8
|
||||||
|
BRF:0
|
||||||
|
BRH:0
|
||||||
|
end_of_record
|
||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/message.ts
|
||||||
|
FN:14,(anonymous_0)
|
||||||
|
FN:19,(anonymous_1)
|
||||||
|
FNF:2
|
||||||
|
FNH:2
|
||||||
|
FNDA:20,(anonymous_0)
|
||||||
|
FNDA:14,(anonymous_1)
|
||||||
|
DA:13,1
|
||||||
|
DA:15,20
|
||||||
|
DA:16,5
|
||||||
|
DA:17,20
|
||||||
|
DA:20,14
|
||||||
|
DA:21,14
|
||||||
|
LF:6
|
||||||
|
LH:6
|
||||||
|
BRDA:15,0,0,15
|
||||||
|
BRDA:15,0,1,5
|
||||||
|
BRF:2
|
||||||
|
BRH:2
|
||||||
|
end_of_record
|
||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/methodCache.ts
|
||||||
|
FN:16,use
|
||||||
|
FN:26,create
|
||||||
|
FN:37,call
|
||||||
|
FN:60,has
|
||||||
|
FN:69,get
|
||||||
|
FN:78,reset
|
||||||
|
FN:88,resetAll
|
||||||
|
FN:89,(anonymous_8)
|
||||||
|
FNF:8
|
||||||
|
FNH:8
|
||||||
|
FNDA:53,use
|
||||||
|
FNDA:118,create
|
||||||
|
FNDA:36,call
|
||||||
|
FNDA:4,has
|
||||||
|
FNDA:4,get
|
||||||
|
FNDA:3,reset
|
||||||
|
FNDA:20,resetAll
|
||||||
|
FNDA:126,(anonymous_8)
|
||||||
|
DA:1,1
|
||||||
|
DA:2,1
|
||||||
|
DA:6,1
|
||||||
|
DA:7,1
|
||||||
|
DA:16,1
|
||||||
|
DA:17,53
|
||||||
|
DA:26,1
|
||||||
|
DA:27,118
|
||||||
|
DA:28,118
|
||||||
|
DA:29,118
|
||||||
|
DA:37,1
|
||||||
|
DA:38,36
|
||||||
|
DA:39,36
|
||||||
|
DA:42,36
|
||||||
|
DA:43,3
|
||||||
|
DA:45,3
|
||||||
|
DA:48,33
|
||||||
|
DA:49,33
|
||||||
|
DA:50,31
|
||||||
|
DA:52,34
|
||||||
|
DA:60,1
|
||||||
|
DA:61,4
|
||||||
|
DA:69,1
|
||||||
|
DA:70,4
|
||||||
|
DA:78,1
|
||||||
|
DA:79,3
|
||||||
|
DA:80,3
|
||||||
|
DA:81,1
|
||||||
|
DA:88,1
|
||||||
|
DA:89,126
|
||||||
|
LF:30
|
||||||
|
LH:30
|
||||||
|
BRDA:26,0,0,5
|
||||||
|
BRDA:38,1,0,3
|
||||||
|
BRDA:38,1,1,33
|
||||||
|
BRDA:42,2,0,3
|
||||||
|
BRDA:42,2,1,33
|
||||||
|
BRDA:70,3,0,4
|
||||||
|
BRDA:70,3,1,0
|
||||||
|
BRDA:79,4,0,3
|
||||||
|
BRDA:79,4,1,0
|
||||||
|
BRDA:80,5,0,2
|
||||||
|
BRDA:80,5,1,1
|
||||||
|
BRF:11
|
||||||
|
BRH:9
|
||||||
|
end_of_record
|
||||||
|
TN:
|
||||||
|
SF:/Volumes/x/code/rocketchat/Rocket.Chat.js.SDK/src/lib/settings.ts
|
||||||
|
FN:16,(anonymous_0)
|
||||||
|
FNF:1
|
||||||
|
FNH:1
|
||||||
|
FNDA:3,(anonymous_0)
|
||||||
|
DA:3,9
|
||||||
|
DA:4,9
|
||||||
|
DA:5,9
|
||||||
|
DA:8,9
|
||||||
|
DA:9,9
|
||||||
|
DA:12,9
|
||||||
|
DA:15,9
|
||||||
|
DA:16,3
|
||||||
|
DA:18,9
|
||||||
|
DA:19,9
|
||||||
|
DA:20,9
|
||||||
|
DA:21,9
|
||||||
|
DA:24,9
|
||||||
|
DA:27,9
|
||||||
|
DA:28,9
|
||||||
|
DA:29,9
|
||||||
|
DA:30,9
|
||||||
|
LF:17
|
||||||
|
LH:17
|
||||||
|
BRDA:3,0,0,9
|
||||||
|
BRDA:3,0,1,9
|
||||||
|
BRDA:4,1,0,9
|
||||||
|
BRDA:4,1,1,9
|
||||||
|
BRDA:8,2,0,9
|
||||||
|
BRDA:8,2,1,6
|
||||||
|
BRDA:10,3,0,2
|
||||||
|
BRDA:10,3,1,7
|
||||||
|
BRDA:10,4,0,2
|
||||||
|
BRDA:10,4,1,0
|
||||||
|
BRDA:11,5,0,7
|
||||||
|
BRDA:11,5,1,5
|
||||||
|
BRDA:16,6,0,2
|
||||||
|
BRDA:16,6,1,7
|
||||||
|
BRDA:16,7,0,2
|
||||||
|
BRDA:16,7,1,0
|
||||||
|
BRDA:18,8,0,9
|
||||||
|
BRDA:18,8,1,8
|
||||||
|
BRDA:19,9,0,9
|
||||||
|
BRDA:19,9,1,8
|
||||||
|
BRDA:20,10,0,9
|
||||||
|
BRDA:20,10,1,8
|
||||||
|
BRDA:21,11,0,9
|
||||||
|
BRDA:21,11,1,8
|
||||||
|
BRDA:24,12,0,9
|
||||||
|
BRDA:24,12,1,9
|
||||||
|
BRDA:27,13,0,9
|
||||||
|
BRDA:27,13,1,9
|
||||||
|
BRDA:28,14,0,9
|
||||||
|
BRDA:28,14,1,9
|
||||||
|
BRDA:29,15,0,9
|
||||||
|
BRDA:29,15,1,9
|
||||||
|
BRDA:30,16,0,9
|
||||||
|
BRDA:30,16,1,9
|
||||||
|
BRF:34
|
||||||
|
BRH:32
|
||||||
|
end_of_record
|
@ -0,0 +1,110 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
/**
|
||||||
|
* Asteroid DDP - add known properties to avoid TS lint errors
|
||||||
|
*/
|
||||||
|
export interface IAsteroidDDP extends EventEmitter {
|
||||||
|
readyState: 1 | 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Asteroid type
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface IAsteroid extends EventEmitter {
|
||||||
|
connect: () => Promise<void>;
|
||||||
|
disconnect: () => Promise<void>;
|
||||||
|
createUser: (usernameOrEmail: string, password: string, profile: IUserOptions) => Promise<any>;
|
||||||
|
loginWithLDAP: (...params: any[]) => Promise<any>;
|
||||||
|
loginWithFacebook: (...params: any[]) => Promise<any>;
|
||||||
|
loginWithGoogle: (...params: any[]) => Promise<any>;
|
||||||
|
loginWithTwitter: (...params: any[]) => Promise<any>;
|
||||||
|
loginWithGithub: (...params: any[]) => Promise<any>;
|
||||||
|
loginWithPassword: (usernameOrEmail: string, password: string) => Promise<any>;
|
||||||
|
logout: () => Promise<null>;
|
||||||
|
subscribe: (name: string, ...params: any[]) => ISubscription;
|
||||||
|
subscriptions: ISubscription[];
|
||||||
|
call: (method: string, ...params: any[]) => IMethodResult;
|
||||||
|
apply: (method: string, params: any[]) => IMethodResult;
|
||||||
|
getCollection: (name: string) => ICollection;
|
||||||
|
resumeLoginPromise: Promise<string>;
|
||||||
|
ddp: IAsteroidDDP;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Asteroid user options type
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface IUserOptions {
|
||||||
|
username?: string;
|
||||||
|
email?: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Asteroid subscription type.
|
||||||
|
* ID is populated when ready promise resolves.
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface ISubscription {
|
||||||
|
stop: () => void;
|
||||||
|
ready: Promise<IReady>;
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
export interface IReady {
|
||||||
|
state: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* If the method is successful, the `result` promise will be resolved with the
|
||||||
|
* return value passed by the server. The `updated` promise will be resolved
|
||||||
|
* with nothing once the server emits the updated message, that tells the client
|
||||||
|
* that any side-effect that the method execution caused on the database has
|
||||||
|
* been reflected on the client (for example, if the method caused the insertion
|
||||||
|
* of an item into a collection, the client has been notified of said
|
||||||
|
* insertion).
|
||||||
|
*
|
||||||
|
* If the method fails, the `result` promise will be rejected with the error
|
||||||
|
* returned by the server. The `updated` promise will be rejected as well
|
||||||
|
* (with nothing).
|
||||||
|
*/
|
||||||
|
export interface IMethodResult {
|
||||||
|
result: Promise<any>;
|
||||||
|
updated: Promise<any>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface ICollection {
|
||||||
|
name: string;
|
||||||
|
insert: (item: any) => ICollectionResult;
|
||||||
|
update: (id: string, item: any) => ICollectionResult;
|
||||||
|
remove: (id: string) => ICollectionResult;
|
||||||
|
reactiveQuery: (selector: object | Function) => IReactiveQuery;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The `local` promise is immediately resolved with the `_id` of the updated
|
||||||
|
* item. That is, unless an error occurred. In that case, an exception will be
|
||||||
|
* raised.
|
||||||
|
* The `remote` promise is resolved with the `_id` of the updated item if the
|
||||||
|
* remote update is successful. Otherwise it's rejected with the reason of the
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
export interface ICollectionResult {
|
||||||
|
local: Promise<any>;
|
||||||
|
remote: Promise<any>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A reactive subset of a collection. Possible events are:
|
||||||
|
* `change`: emitted whenever the result of the query changes. The id of the
|
||||||
|
* item that changed is passed to the handler.
|
||||||
|
*/
|
||||||
|
export interface IReactiveQuery {
|
||||||
|
on: (event: string, handler: Function) => void;
|
||||||
|
result: any[];
|
||||||
|
}
|
||||||
|
/** Credentials for Asteroid login method */
|
||||||
|
export interface ICredentials {
|
||||||
|
password: string;
|
||||||
|
username?: string;
|
||||||
|
email?: string;
|
||||||
|
ldap?: boolean;
|
||||||
|
ldapOptions?: object;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=asteroidInterfaces.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"asteroidInterfaces.js","sourceRoot":"","sources":["../../src/config/asteroidInterfaces.ts"],"names":[],"mappings":"","sourcesContent":["import { EventEmitter } from 'events'\n// import { Map } from 'immutable'\n\n/**\n * Asteroid DDP - add known properties to avoid TS lint errors\n */\nexport interface IAsteroidDDP extends EventEmitter {\n readyState: 1 | 0\n}\n\n/**\n * Asteroid type\n * @todo Update with typing from definitely typed (when available)\n */\nexport interface IAsteroid extends EventEmitter {\n connect: () => Promise<void>,\n disconnect: () => Promise<void>,\n createUser: (usernameOrEmail: string, password: string, profile: IUserOptions) => Promise<any>\n loginWithLDAP: (...params: any[]) => Promise<any>\n loginWithFacebook: (...params: any[]) => Promise<any>\n loginWithGoogle: (...params: any[]) => Promise<any>\n loginWithTwitter: (...params: any[]) => Promise<any>\n loginWithGithub: (...params: any[]) => Promise<any>\n loginWithPassword: (usernameOrEmail: string, password: string) => Promise<any>\n logout: () => Promise<null>\n subscribe: (name: string, ...params: any[]) => ISubscription\n subscriptions: ISubscription[],\n call: (method: string, ...params: any[]) => IMethodResult\n apply: (method: string, params: any[]) => IMethodResult\n getCollection: (name: string) => ICollection\n resumeLoginPromise: Promise<string>\n ddp: IAsteroidDDP\n}\n\n/**\n * Asteroid user options type\n * @todo Update with typing from definitely typed (when available)\n */\nexport interface IUserOptions {\n username?: string,\n email?: string,\n password: string\n}\n\n/**\n * Asteroid subscription type.\n * ID is populated when ready promise resolves.\n * @todo Update with typing from definitely typed (when available)\n */\nexport interface ISubscription {\n stop: () => void,\n ready: Promise<IReady>,\n id?: string\n}\n\n// Asteroid v1 only\nexport interface IReady { state: string, value: string }\n\n/* // v2\nexport interface ISubscription extends EventEmitter {\n id: string\n}\n*/\n\n/**\n * If the method is successful, the `result` promise will be resolved with the\n * return value passed by the server. The `updated` promise will be resolved\n * with nothing once the server emits the updated message, that tells the client\n * that any side-effect that the method execution caused on the database has\n * been reflected on the client (for example, if the method caused the insertion\n * of an item into a collection, the client has been notified of said\n * insertion).\n *\n * If the method fails, the `result` promise will be rejected with the error\n * returned by the server. The `updated` promise will be rejected as well\n * (with nothing).\n */\nexport interface IMethodResult {\n result: Promise<any>,\n updated: Promise<any>\n}\n\n/**\n *\n */\nexport interface ICollection {\n name: string,\n insert: (item: any) => ICollectionResult,\n update: (id: string, item: any) => ICollectionResult,\n remove: (id: string) => ICollectionResult,\n reactiveQuery: (selector: object | Function) => IReactiveQuery\n}\n\n/**\n * The `local` promise is immediately resolved with the `_id` of the updated\n * item. That is, unless an error occurred. In that case, an exception will be\n * raised.\n * The `remote` promise is resolved with the `_id` of the updated item if the\n * remote update is successful. Otherwise it's rejected with the reason of the\n * failure.\n */\nexport interface ICollectionResult {\n local: Promise<any>,\n remote: Promise<any>\n}\n\n/**\n * A reactive subset of a collection. Possible events are:\n * `change`: emitted whenever the result of the query changes. The id of the\n * item that changed is passed to the handler.\n */\nexport interface IReactiveQuery {\n on: (event: string, handler: Function) => void,\n result: any[]\n}\n\n/** Credentials for Asteroid login method */\nexport interface ICredentials {\n password: string,\n username?: string,\n email?: string,\n ldap?: boolean,\n ldapOptions?: object\n}\n"]}
|
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Connection options type
|
||||||
|
* @param host Rocket.Chat instance Host URL:PORT (without protocol)
|
||||||
|
* @param timeout How long to wait (ms) before abandoning connection
|
||||||
|
*/
|
||||||
|
export interface IConnectOptions {
|
||||||
|
host?: string;
|
||||||
|
useSsl?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
integration?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Message respond options
|
||||||
|
* @param rooms Respond to only selected room/s (names or IDs)
|
||||||
|
* @param allPublic Respond on all public channels (ignores rooms if true)
|
||||||
|
* @param dm Respond to messages in DM / private chats
|
||||||
|
* @param livechat Respond to messages in livechat
|
||||||
|
* @param edited Respond to edited messages
|
||||||
|
*/
|
||||||
|
export interface IRespondOptions {
|
||||||
|
rooms?: string[];
|
||||||
|
allPublic?: boolean;
|
||||||
|
dm?: boolean;
|
||||||
|
livechat?: boolean;
|
||||||
|
edited?: boolean;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Loggers need to provide the same set of methods
|
||||||
|
*/
|
||||||
|
export interface ILogger {
|
||||||
|
debug: (...args: any[]) => void;
|
||||||
|
info: (...args: any[]) => void;
|
||||||
|
warning: (...args: any[]) => void;
|
||||||
|
warn: (...args: any[]) => void;
|
||||||
|
error: (...args: any[]) => void;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Error-first callback param type
|
||||||
|
*/
|
||||||
|
export interface ICallback {
|
||||||
|
(error: Error | null, ...args: any[]): void;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=driverInterfaces.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"driverInterfaces.js","sourceRoot":"","sources":["../../src/config/driverInterfaces.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Connection options type\n * @param host Rocket.Chat instance Host URL:PORT (without protocol)\n * @param timeout How long to wait (ms) before abandoning connection\n */\nexport interface IConnectOptions {\n host?: string,\n useSsl?: boolean,\n timeout?: number,\n integration?: string\n}\n\n/**\n * Message respond options\n * @param rooms Respond to only selected room/s (names or IDs)\n * @param allPublic Respond on all public channels (ignores rooms if true)\n * @param dm Respond to messages in DM / private chats\n * @param livechat Respond to messages in livechat\n * @param edited Respond to edited messages\n */\nexport interface IRespondOptions {\n rooms?: string[],\n allPublic?: boolean,\n dm?: boolean,\n livechat?: boolean,\n edited?: boolean\n}\n\n/**\n * Loggers need to provide the same set of methods\n */\nexport interface ILogger {\n debug: (...args: any[]) => void\n info: (...args: any[]) => void\n warning: (...args: any[]) => void\n warn: (...args: any[]) => void\n error: (...args: any[]) => void\n}\n\n/**\n * Error-first callback param type\n */\nexport interface ICallback {\n (error: Error | null, ...args: any[]): void\n}\n"]}
|
@ -0,0 +1,70 @@
|
|||||||
|
/** @todo contribute these to @types/rocketchat and require */
|
||||||
|
export interface IMessage {
|
||||||
|
rid: string | null;
|
||||||
|
_id?: string;
|
||||||
|
t?: string;
|
||||||
|
msg?: string;
|
||||||
|
alias?: string;
|
||||||
|
emoji?: string;
|
||||||
|
avatar?: string;
|
||||||
|
groupable?: boolean;
|
||||||
|
bot?: any;
|
||||||
|
urls?: string[];
|
||||||
|
mentions?: string[];
|
||||||
|
attachments?: IMessageAttachment[];
|
||||||
|
reactions?: IMessageReaction;
|
||||||
|
location?: IMessageLocation;
|
||||||
|
u?: IUser;
|
||||||
|
editedBy?: IUser;
|
||||||
|
editedAt?: Date;
|
||||||
|
}
|
||||||
|
export interface IUser {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
export interface IMessageAttachment {
|
||||||
|
fields?: IAttachmentField[];
|
||||||
|
actions?: IMessageAction[];
|
||||||
|
color?: string;
|
||||||
|
text?: string;
|
||||||
|
ts?: string;
|
||||||
|
thumb_url?: string;
|
||||||
|
message_link?: string;
|
||||||
|
collapsed?: boolean;
|
||||||
|
author_name?: string;
|
||||||
|
author_link?: string;
|
||||||
|
author_icon?: string;
|
||||||
|
title?: string;
|
||||||
|
title_link?: string;
|
||||||
|
title_link_download?: string;
|
||||||
|
image_url?: string;
|
||||||
|
audio_url?: string;
|
||||||
|
video_url?: string;
|
||||||
|
}
|
||||||
|
export interface IAttachmentField {
|
||||||
|
short?: boolean;
|
||||||
|
title?: string;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
export interface IMessageAction {
|
||||||
|
type?: string;
|
||||||
|
text?: string;
|
||||||
|
url?: string;
|
||||||
|
image_url?: string;
|
||||||
|
is_webview?: boolean;
|
||||||
|
webview_height_ratio?: 'compact' | 'tall' | 'full';
|
||||||
|
msg?: string;
|
||||||
|
msg_in_chat_window?: boolean;
|
||||||
|
button_alignment?: 'vertical' | 'horizontal';
|
||||||
|
temporary_buttons?: boolean;
|
||||||
|
}
|
||||||
|
export interface IMessageLocation {
|
||||||
|
type: string;
|
||||||
|
coordinates: string[];
|
||||||
|
}
|
||||||
|
export interface IMessageReaction {
|
||||||
|
[emoji: string]: {
|
||||||
|
usernames: string[];
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
"use strict";
|
||||||
|
/** @todo contribute these to @types/rocketchat and require */
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=messageInterfaces.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"messageInterfaces.js","sourceRoot":"","sources":["../../src/config/messageInterfaces.ts"],"names":[],"mappings":";AAAA,8DAA8D","sourcesContent":["/** @todo contribute these to @types/rocketchat and require */\n\nexport interface IMessage {\n rid: string | null // room ID\n _id?: string // generated by Random.id()\n t?: string // type e.g. rm\n msg?: string // text content\n alias?: string // ??\n emoji?: string // emoji to use as avatar\n avatar?: string // url\n groupable?: boolean // ?\n bot?: any // integration details\n urls?: string[] // ?\n mentions?: string[] // ?\n attachments?: IMessageAttachment[]\n reactions?: IMessageReaction\n location ?: IMessageLocation\n u?: IUser // User that sent the message\n editedBy?: IUser // User that edited the message\n editedAt?: Date // When the message was edited\n}\n\nexport interface IUser {\n _id: string\n username: string\n name?: string\n}\n\nexport interface IMessageAttachment {\n fields?: IAttachmentField[]\n actions?: IMessageAction[]\n color?: string\n text?: string\n ts?: string\n thumb_url?: string\n message_link?: string\n collapsed?: boolean\n author_name?: string\n author_link?: string\n author_icon?: string\n title?: string\n title_link?: string\n title_link_download?: string\n image_url?: string\n audio_url?: string\n video_url?: string\n}\n\nexport interface IAttachmentField {\n short?: boolean\n title?: string\n value?: string\n}\n\nexport interface IMessageAction {\n type?: string\n text?: string\n url?: string\n image_url?: string\n is_webview?: boolean\n webview_height_ratio?: 'compact' | 'tall' | 'full'\n msg?: string\n msg_in_chat_window?: boolean\n button_alignment?: 'vertical' | 'horizontal'\n temporary_buttons?: boolean\n}\n\nexport interface IMessageLocation {\n type: string // e.g. Point\n coordinates: string[] // longitude latitude\n}\n\nexport interface IMessageReaction {\n [emoji: string]: { usernames: string[] } // emoji: [usernames that reacted]\n}\n"]}
|
@ -0,0 +1,5 @@
|
|||||||
|
import * as driver from './lib/driver';
|
||||||
|
import * as methodCache from './lib/methodCache';
|
||||||
|
import * as api from './lib/api';
|
||||||
|
import * as settings from './lib/settings';
|
||||||
|
export { driver, methodCache, api, settings };
|
@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||||
|
result["default"] = mod;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const driver = __importStar(require("./lib/driver"));
|
||||||
|
exports.driver = driver;
|
||||||
|
const methodCache = __importStar(require("./lib/methodCache"));
|
||||||
|
exports.methodCache = methodCache;
|
||||||
|
const api = __importStar(require("./lib/api"));
|
||||||
|
exports.api = api;
|
||||||
|
const settings = __importStar(require("./lib/settings"));
|
||||||
|
exports.settings = settings;
|
||||||
|
//# sourceMappingURL=index.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;AAAA,qDAAsC;AAKpC,wBAAM;AAJR,+DAAgD;AAK9C,kCAAW;AAJb,+CAAgC;AAK9B,kBAAG;AAJL,yDAA0C;AAKxC,4BAAQ","sourcesContent":["import * as driver from './lib/driver'\nimport * as methodCache from './lib/methodCache'\nimport * as api from './lib/api'\nimport * as settings from './lib/settings'\nexport {\n driver,\n methodCache,\n api,\n settings\n}\n"]}
|
@ -0,0 +1,87 @@
|
|||||||
|
/** Result object from an API login */
|
||||||
|
export interface ILoginResultAPI {
|
||||||
|
status: string;
|
||||||
|
data: {
|
||||||
|
authToken: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/** Structure for passing and keeping login credentials */
|
||||||
|
export interface ILoginCredentials {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
export declare let currentLogin: {
|
||||||
|
username: string;
|
||||||
|
userId: string;
|
||||||
|
authToken: string;
|
||||||
|
result: ILoginResultAPI;
|
||||||
|
} | null;
|
||||||
|
/** Check for existing login */
|
||||||
|
export declare function loggedIn(): boolean;
|
||||||
|
/** Initialise client and configs */
|
||||||
|
export declare const client: any;
|
||||||
|
export declare const host: string;
|
||||||
|
/**
|
||||||
|
* Prepend protocol (or put back if removed from env settings for driver)
|
||||||
|
* Hard code endpoint prefix, because all syntax depends on this version
|
||||||
|
*/
|
||||||
|
export declare const url: string;
|
||||||
|
/** Convert payload data to query string for GET requests */
|
||||||
|
export declare function getQueryString(data: any): string;
|
||||||
|
/** Setup default headers with empty auth for now */
|
||||||
|
export declare const basicHeaders: {
|
||||||
|
'Content-Type': string;
|
||||||
|
};
|
||||||
|
export declare const authHeaders: {
|
||||||
|
'X-Auth-Token': string;
|
||||||
|
'X-User-Id': string;
|
||||||
|
};
|
||||||
|
/** Populate auth headers (from response data on login) */
|
||||||
|
export declare function setAuth(authData: {
|
||||||
|
authToken: string;
|
||||||
|
userId: string;
|
||||||
|
}): void;
|
||||||
|
/** Join basic headers with auth headers if required */
|
||||||
|
export declare function getHeaders(authRequired?: boolean): {
|
||||||
|
'Content-Type': string;
|
||||||
|
};
|
||||||
|
/** Clear headers so they can't be used without logging in again */
|
||||||
|
export declare function clearHeaders(): void;
|
||||||
|
/** Check result data for success, allowing override to ignore some errors */
|
||||||
|
export declare function success(result: any, ignore?: RegExp): boolean;
|
||||||
|
/**
|
||||||
|
* Do a POST request to an API endpoint.
|
||||||
|
* If it needs a token, login first (with defaults) to set auth headers.
|
||||||
|
* @todo Look at why some errors return HTML (caught as buffer) instead of JSON
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `chat.update`
|
||||||
|
* @param data Payload for POST request to endpoint
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
export declare function post(endpoint: string, data: any, auth?: boolean, ignore?: RegExp): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Do a GET request to an API endpoint
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `users.info`
|
||||||
|
* @param data Object to serialise for GET request query string
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
export declare function get(endpoint: string, data?: any, auth?: boolean, ignore?: RegExp): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Login a user for further API calls
|
||||||
|
* Result should come back with a token, to authorise following requests.
|
||||||
|
* Use env default credentials, unless overridden by login arguments.
|
||||||
|
*/
|
||||||
|
export declare function login(user?: ILoginCredentials): Promise<ILoginResultAPI>;
|
||||||
|
/** Logout a user at end of API calls */
|
||||||
|
export declare function logout(): Promise<void>;
|
||||||
|
/** Defaults for user queries */
|
||||||
|
export declare const userFields: {
|
||||||
|
name: number;
|
||||||
|
username: number;
|
||||||
|
status: number;
|
||||||
|
type: number;
|
||||||
|
};
|
||||||
|
/** Query helpers for user collection requests */
|
||||||
|
export declare const users: any;
|
@ -0,0 +1,218 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||||
|
result["default"] = mod;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const node_rest_client_1 = require("node-rest-client");
|
||||||
|
const settings = __importStar(require("./settings"));
|
||||||
|
const log_1 = require("./log");
|
||||||
|
exports.currentLogin = null;
|
||||||
|
/** Check for existing login */
|
||||||
|
function loggedIn() {
|
||||||
|
return (exports.currentLogin !== null);
|
||||||
|
}
|
||||||
|
exports.loggedIn = loggedIn;
|
||||||
|
/** Initialise client and configs */
|
||||||
|
exports.client = new node_rest_client_1.Client();
|
||||||
|
exports.host = settings.host;
|
||||||
|
/**
|
||||||
|
* Prepend protocol (or put back if removed from env settings for driver)
|
||||||
|
* Hard code endpoint prefix, because all syntax depends on this version
|
||||||
|
*/
|
||||||
|
exports.url = ((exports.host.indexOf('http') === -1)
|
||||||
|
? exports.host.replace(/^(\/\/)?/, 'http://')
|
||||||
|
: exports.host) + '/api/v1/';
|
||||||
|
/** Convert payload data to query string for GET requests */
|
||||||
|
function getQueryString(data) {
|
||||||
|
if (!data || typeof data !== 'object' || !Object.keys(data).length)
|
||||||
|
return '';
|
||||||
|
return '?' + Object.keys(data).map((k) => {
|
||||||
|
const value = (typeof data[k] === 'object')
|
||||||
|
? JSON.stringify(data[k])
|
||||||
|
: encodeURIComponent(data[k]);
|
||||||
|
return `${encodeURIComponent(k)}=${value}`;
|
||||||
|
}).join('&');
|
||||||
|
}
|
||||||
|
exports.getQueryString = getQueryString;
|
||||||
|
/** Setup default headers with empty auth for now */
|
||||||
|
exports.basicHeaders = { 'Content-Type': 'application/json' };
|
||||||
|
exports.authHeaders = { 'X-Auth-Token': '', 'X-User-Id': '' };
|
||||||
|
/** Populate auth headers (from response data on login) */
|
||||||
|
function setAuth(authData) {
|
||||||
|
exports.authHeaders['X-Auth-Token'] = authData.authToken;
|
||||||
|
exports.authHeaders['X-User-Id'] = authData.userId;
|
||||||
|
}
|
||||||
|
exports.setAuth = setAuth;
|
||||||
|
/** Join basic headers with auth headers if required */
|
||||||
|
function getHeaders(authRequired = false) {
|
||||||
|
if (!authRequired)
|
||||||
|
return exports.basicHeaders;
|
||||||
|
if ((!('X-Auth-Token' in exports.authHeaders) || !('X-User-Id' in exports.authHeaders)) ||
|
||||||
|
exports.authHeaders['X-Auth-Token'] === '' ||
|
||||||
|
exports.authHeaders['X-User-Id'] === '') {
|
||||||
|
throw new Error('Auth required endpoint cannot be called before login');
|
||||||
|
}
|
||||||
|
return Object.assign({}, exports.basicHeaders, exports.authHeaders);
|
||||||
|
}
|
||||||
|
exports.getHeaders = getHeaders;
|
||||||
|
/** Clear headers so they can't be used without logging in again */
|
||||||
|
function clearHeaders() {
|
||||||
|
delete exports.authHeaders['X-Auth-Token'];
|
||||||
|
delete exports.authHeaders['X-User-Id'];
|
||||||
|
}
|
||||||
|
exports.clearHeaders = clearHeaders;
|
||||||
|
/** Check result data for success, allowing override to ignore some errors */
|
||||||
|
function success(result, ignore) {
|
||||||
|
return ((typeof result.error === 'undefined' &&
|
||||||
|
typeof result.status === 'undefined' &&
|
||||||
|
typeof result.success === 'undefined') ||
|
||||||
|
(result.status && result.status === 'success') ||
|
||||||
|
(result.success && result.success === true) ||
|
||||||
|
(ignore && result.error && !ignore.test(result.error))) ? true : false;
|
||||||
|
}
|
||||||
|
exports.success = success;
|
||||||
|
/**
|
||||||
|
* Do a POST request to an API endpoint.
|
||||||
|
* If it needs a token, login first (with defaults) to set auth headers.
|
||||||
|
* @todo Look at why some errors return HTML (caught as buffer) instead of JSON
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `chat.update`
|
||||||
|
* @param data Payload for POST request to endpoint
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
function post(endpoint, data, auth = true, ignore) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
log_1.logger.debug(`[API] POST: ${endpoint}`, JSON.stringify(data));
|
||||||
|
if (auth && !loggedIn())
|
||||||
|
yield login();
|
||||||
|
let headers = getHeaders(auth);
|
||||||
|
const result = yield new Promise((resolve, reject) => {
|
||||||
|
exports.client.post(exports.url + endpoint, { headers, data }, (result) => {
|
||||||
|
if (Buffer.isBuffer(result))
|
||||||
|
reject('Result was buffer (HTML, not JSON)');
|
||||||
|
else if (!success(result, ignore))
|
||||||
|
reject(result);
|
||||||
|
else
|
||||||
|
resolve(result);
|
||||||
|
}).on('error', (err) => reject(err));
|
||||||
|
});
|
||||||
|
log_1.logger.debug('[API] POST result:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
log_1.logger.error(`[API] POST error (${endpoint}):`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.post = post;
|
||||||
|
/**
|
||||||
|
* Do a GET request to an API endpoint
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `users.info`
|
||||||
|
* @param data Object to serialise for GET request query string
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
function get(endpoint, data, auth = true, ignore) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
log_1.logger.debug(`[API] GET: ${endpoint}`, data);
|
||||||
|
if (auth && !loggedIn())
|
||||||
|
yield login();
|
||||||
|
let headers = getHeaders(auth);
|
||||||
|
const query = getQueryString(data);
|
||||||
|
const result = yield new Promise((resolve, reject) => {
|
||||||
|
exports.client.get(exports.url + endpoint + query, { headers }, (result) => {
|
||||||
|
if (Buffer.isBuffer(result))
|
||||||
|
reject('Result was buffer (HTML, not JSON)');
|
||||||
|
else if (!success(result, ignore))
|
||||||
|
reject(result);
|
||||||
|
else
|
||||||
|
resolve(result);
|
||||||
|
}).on('error', (err) => reject(err));
|
||||||
|
});
|
||||||
|
log_1.logger.debug('[API] GET result:', result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log_1.logger.error(`[API] GET error (${endpoint}):`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.get = get;
|
||||||
|
/**
|
||||||
|
* Login a user for further API calls
|
||||||
|
* Result should come back with a token, to authorise following requests.
|
||||||
|
* Use env default credentials, unless overridden by login arguments.
|
||||||
|
*/
|
||||||
|
function login(user = {
|
||||||
|
username: settings.username,
|
||||||
|
password: settings.password
|
||||||
|
}) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
log_1.logger.info(`[API] Logging in ${user.username}`);
|
||||||
|
if (exports.currentLogin !== null) {
|
||||||
|
log_1.logger.debug(`[API] Already logged in`);
|
||||||
|
if (exports.currentLogin.username === user.username) {
|
||||||
|
return exports.currentLogin.result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yield logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = yield post('login', user, false);
|
||||||
|
if (result && result.data && result.data.authToken) {
|
||||||
|
exports.currentLogin = {
|
||||||
|
result: result,
|
||||||
|
username: user.username,
|
||||||
|
authToken: result.data.authToken,
|
||||||
|
userId: result.data.userId
|
||||||
|
};
|
||||||
|
setAuth(exports.currentLogin);
|
||||||
|
log_1.logger.info(`[API] Logged in ID ${exports.currentLogin.userId}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`[API] Login failed for ${user.username}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.login = login;
|
||||||
|
/** Logout a user at end of API calls */
|
||||||
|
function logout() {
|
||||||
|
if (exports.currentLogin === null) {
|
||||||
|
log_1.logger.debug(`[API] Already logged out`);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
log_1.logger.info(`[API] Logging out ${exports.currentLogin.username}`);
|
||||||
|
return get('logout', null, true).then(() => {
|
||||||
|
clearHeaders();
|
||||||
|
exports.currentLogin = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.logout = logout;
|
||||||
|
/** Defaults for user queries */
|
||||||
|
exports.userFields = { name: 1, username: 1, status: 1, type: 1 };
|
||||||
|
/** Query helpers for user collection requests */
|
||||||
|
exports.users = {
|
||||||
|
all: (fields = exports.userFields) => get('users.list', { fields }).then((r) => r.users),
|
||||||
|
allNames: () => get('users.list', { fields: { 'username': 1 } }).then((r) => r.users.map((u) => u.username)),
|
||||||
|
allIDs: () => get('users.list', { fields: { '_id': 1 } }).then((r) => r.users.map((u) => u._id)),
|
||||||
|
online: (fields = exports.userFields) => get('users.list', { fields, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users),
|
||||||
|
onlineNames: () => get('users.list', { fields: { 'username': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users.map((u) => u.username)),
|
||||||
|
onlineIds: () => get('users.list', { fields: { '_id': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users.map((u) => u._id))
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=api.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,201 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { Message } from './message';
|
||||||
|
import { IConnectOptions, IRespondOptions, ICallback, ILogger } from '../config/driverInterfaces';
|
||||||
|
import { IAsteroid, ICredentials, ISubscription, ICollection } from '../config/asteroidInterfaces';
|
||||||
|
import { IMessage } from '../config/messageInterfaces';
|
||||||
|
import { IMessageReceiptAPI } from '../utils/interfaces';
|
||||||
|
/** Internal for comparing message update timestamps */
|
||||||
|
export declare let lastReadTime: Date;
|
||||||
|
/**
|
||||||
|
* The integration property is applied as an ID on sent messages `bot.i` param
|
||||||
|
* Should be replaced when connection is invoked by a package using the SDK
|
||||||
|
* e.g. The Hubot adapter would pass its integration ID with credentials, like:
|
||||||
|
*/
|
||||||
|
export declare const integrationId: string;
|
||||||
|
/**
|
||||||
|
* Event Emitter for listening to connection.
|
||||||
|
* @example
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* driver.events.on('connected', () => console.log('driver connected'))
|
||||||
|
*/
|
||||||
|
export declare const events: EventEmitter;
|
||||||
|
/**
|
||||||
|
* An Asteroid instance for interacting with Rocket.Chat.
|
||||||
|
* Variable not initialised until `connect` called.
|
||||||
|
*/
|
||||||
|
export declare let asteroid: IAsteroid;
|
||||||
|
/**
|
||||||
|
* Asteroid subscriptions, exported for direct polling by adapters
|
||||||
|
* Variable not initialised until `prepMeteorSubscriptions` called.
|
||||||
|
*/
|
||||||
|
export declare let subscriptions: ISubscription[];
|
||||||
|
/**
|
||||||
|
* Current user object populated from resolved login
|
||||||
|
*/
|
||||||
|
export declare let userId: string;
|
||||||
|
/**
|
||||||
|
* Array of joined room IDs (for reactive queries)
|
||||||
|
*/
|
||||||
|
export declare let joinedIds: string[];
|
||||||
|
/**
|
||||||
|
* Array of messages received from reactive collection
|
||||||
|
*/
|
||||||
|
export declare let messages: ICollection;
|
||||||
|
/**
|
||||||
|
* Allow override of default logging with adapter's log instance
|
||||||
|
*/
|
||||||
|
export declare function useLog(externalLog: ILogger): void;
|
||||||
|
/**
|
||||||
|
* Initialise asteroid instance with given options or defaults.
|
||||||
|
* Returns promise, resolved with Asteroid instance. Callback follows
|
||||||
|
* error-first-pattern. Error returned or promise rejected on timeout.
|
||||||
|
* Removes http/s protocol to get connection hostname if taken from URL.
|
||||||
|
* @example <caption>Use with callback</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect({}, (err) => {
|
||||||
|
* if (err) throw err
|
||||||
|
* else console.log('connected')
|
||||||
|
* })
|
||||||
|
* @example <caption>Using promise</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* .then(() => console.log('connected'))
|
||||||
|
* .catch((err) => console.error(err))
|
||||||
|
*/
|
||||||
|
export declare function connect(options?: IConnectOptions, callback?: ICallback): Promise<IAsteroid>;
|
||||||
|
/** Remove all active subscriptions, logout and disconnect from Rocket.Chat */
|
||||||
|
export declare function disconnect(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Wraps method calls to ensure they return a Promise with caught exceptions.
|
||||||
|
* @param method The Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param params Single or array of parameters of the method to call
|
||||||
|
*/
|
||||||
|
export declare function asyncCall(method: string, params: any | any[]): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Call a method as async via Asteroid, or through cache if one is created.
|
||||||
|
* If the method doesn't have or need parameters, it can't use them for caching
|
||||||
|
* so it will always call asynchronously.
|
||||||
|
* @param name The Rocket.Chat server method to call
|
||||||
|
* @param params Single or array of parameters of the method to call
|
||||||
|
*/
|
||||||
|
export declare function callMethod(name: string, params?: any | any[]): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Wraps Asteroid method calls, passed through method cache if cache is valid.
|
||||||
|
* @param method The Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param key Single string parameters only, required to use as cache key
|
||||||
|
*/
|
||||||
|
export declare function cacheCall(method: string, key: string): Promise<any>;
|
||||||
|
/** Login to Rocket.Chat via Asteroid */
|
||||||
|
export declare function login(credentials?: ICredentials): Promise<any>;
|
||||||
|
/** Logout of Rocket.Chat via Asteroid */
|
||||||
|
export declare function logout(): Promise<void | null>;
|
||||||
|
/**
|
||||||
|
* Subscribe to Meteor subscription
|
||||||
|
* Resolves with subscription (added to array), with ID property
|
||||||
|
* @todo - 3rd param of asteroid.subscribe is deprecated in Rocket.Chat?
|
||||||
|
*/
|
||||||
|
export declare function subscribe(topic: string, roomId: string): Promise<ISubscription>;
|
||||||
|
/** Unsubscribe from Meteor subscription */
|
||||||
|
export declare function unsubscribe(subscription: ISubscription): void;
|
||||||
|
/** Unsubscribe from all subscriptions in collection */
|
||||||
|
export declare function unsubscribeAll(): void;
|
||||||
|
/**
|
||||||
|
* Begin subscription to room events for user.
|
||||||
|
* Older adapters used an option for this method but it was always the default.
|
||||||
|
*/
|
||||||
|
export declare function subscribeToMessages(): Promise<ISubscription>;
|
||||||
|
/**
|
||||||
|
* Once a subscription is created, using `subscribeToMessages` this method
|
||||||
|
* can be used to attach a callback to changes in the message stream.
|
||||||
|
* This can be called directly for custom extensions, but for most usage (e.g.
|
||||||
|
* for bots) the respondToMessages is more useful to only receive messages
|
||||||
|
* matching configuration.
|
||||||
|
*
|
||||||
|
* If the bot hasn't been joined to any rooms at this point, it will attempt to
|
||||||
|
* join now based on environment config, otherwise it might not receive any
|
||||||
|
* messages. It doesn't matter that this happens asynchronously because the
|
||||||
|
* bot's joined rooms can change after the reactive query is set up.
|
||||||
|
*
|
||||||
|
* @todo `reactToMessages` should call `subscribeToMessages` if not already
|
||||||
|
* done, so it's not required as an arbitrary step for simpler adapters.
|
||||||
|
* Also make `login` call `connect` for the same reason, the way
|
||||||
|
* `respondToMessages` calls `respondToMessages`, so all that's really
|
||||||
|
* required is:
|
||||||
|
* `driver.login(credentials).then(() => driver.respondToMessages(callback))`
|
||||||
|
* @param callback Function called with every change in subscriptions.
|
||||||
|
* - Uses error-first callback pattern
|
||||||
|
* - Second argument is the changed item
|
||||||
|
* - Third argument is additional attributes, such as `roomType`
|
||||||
|
*/
|
||||||
|
export declare function reactToMessages(callback: ICallback): void;
|
||||||
|
/**
|
||||||
|
* Proxy for `reactToMessages` with some filtering of messages based on config.
|
||||||
|
*
|
||||||
|
* @param callback Function called 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`
|
||||||
|
* @param options Sets filters for different event/message types.
|
||||||
|
*/
|
||||||
|
export declare function respondToMessages(callback: ICallback, options?: IRespondOptions): Promise<void | void[]>;
|
||||||
|
/** Get ID for a room by name (or ID). */
|
||||||
|
export declare function getRoomId(name: string): Promise<string>;
|
||||||
|
/** Get name for a room by ID. */
|
||||||
|
export declare function getRoomName(id: string): Promise<string>;
|
||||||
|
/**
|
||||||
|
* Get ID for a DM room by its recipient's name.
|
||||||
|
* Will create a DM (with the bot) if it doesn't exist already.
|
||||||
|
* @todo test why create resolves with object instead of simply ID
|
||||||
|
*/
|
||||||
|
export declare function getDirectMessageRoomId(username: string): Promise<string>;
|
||||||
|
/** Join the bot into a room by its name or ID */
|
||||||
|
export declare function joinRoom(room: string): Promise<void>;
|
||||||
|
/** Exit a room the bot has joined */
|
||||||
|
export declare function leaveRoom(room: string): Promise<void>;
|
||||||
|
/** Join a set of rooms by array of names or IDs */
|
||||||
|
export declare function joinRooms(rooms: string[]): Promise<void[]>;
|
||||||
|
/**
|
||||||
|
* Structure message content, optionally addressing to room ID.
|
||||||
|
* Accepts message text string or a structured message object.
|
||||||
|
*/
|
||||||
|
export declare function prepareMessage(content: string | IMessage, roomId?: string): Message;
|
||||||
|
/**
|
||||||
|
* Send a prepared message object (with pre-defined room ID).
|
||||||
|
* Usually prepared and called by sendMessageByRoomId or sendMessageByRoom.
|
||||||
|
*/
|
||||||
|
export declare function sendMessage(message: IMessage): Promise<IMessageReceiptAPI>;
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to specified room ID.
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param roomId ID of the target room to use in send.
|
||||||
|
* @todo Returning one or many gets complicated with type checking not allowing
|
||||||
|
* use of a property because result may be array, when you know it's not.
|
||||||
|
* Solution would probably be to always return an array, even for single
|
||||||
|
* send. This would be a breaking change, should hold until major version.
|
||||||
|
*/
|
||||||
|
export declare function sendToRoomId(content: string | string[] | IMessage, roomId: string): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI>;
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to specified room name (or ID).
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param room A name (or ID) to resolve as ID to use in send.
|
||||||
|
*/
|
||||||
|
export declare function sendToRoom(content: string | string[] | IMessage, room: string): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI>;
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to a user in a DM.
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param username Name to create (or get) DM for room ID to use in send.
|
||||||
|
*/
|
||||||
|
export declare function sendDirectToUser(content: string | string[] | IMessage, username: string): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI>;
|
||||||
|
/**
|
||||||
|
* Edit an existing message, replacing any attributes with those provided.
|
||||||
|
* The given message object should have the ID of an existing message.
|
||||||
|
*/
|
||||||
|
export declare function editMessage(message: IMessage): Promise<IMessage>;
|
||||||
|
/**
|
||||||
|
* Send a reaction to an existing message. Simple proxy for method call.
|
||||||
|
* @param emoji Accepts string like `:thumbsup:` to add 👍 reaction
|
||||||
|
* @param messageId ID for a previously sent message
|
||||||
|
*/
|
||||||
|
export declare function setReaction(emoji: string, messageId: string): Promise<any>;
|
@ -0,0 +1,666 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
}
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||||
|
result["default"] = mod;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events_1 = require("events");
|
||||||
|
const asteroid_1 = __importDefault(require("asteroid"));
|
||||||
|
const settings = __importStar(require("./settings"));
|
||||||
|
const methodCache = __importStar(require("./methodCache"));
|
||||||
|
const message_1 = require("./message");
|
||||||
|
const log_1 = require("./log");
|
||||||
|
|
||||||
|
const _messageCollectionName = 'stream-room-messages';
|
||||||
|
const _messageStreamName = '__my_messages__';
|
||||||
|
/**
|
||||||
|
* The integration property is applied as an ID on sent messages `bot.i` param
|
||||||
|
* Should be replaced when connection is invoked by a package using the SDK
|
||||||
|
* e.g. The Hubot adapter would pass its integration ID with credentials, like:
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
exports.integrationId = settings.integrationId;
|
||||||
|
/**
|
||||||
|
* Event Emitter for listening to connection.
|
||||||
|
* @example
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* driver.events.on('connected', () => console.log('driver connected'))
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
exports.events = new events_1.EventEmitter();
|
||||||
|
/**
|
||||||
|
* Asteroid subscriptions, exported for direct polling by adapters
|
||||||
|
* Variable not initialised until `prepMeteorSubscriptions` called.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
exports.subscriptions = [];
|
||||||
|
/**
|
||||||
|
* Array of joined room IDs (for reactive queries)
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
exports.joinedIds = [];
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Replaces the default logger with one from a bot framework
|
||||||
|
* @param {Class|Object} externalLog - Class or object with `debug`, `info`, `warn`, `error` methods
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function useLog(externalLog) {
|
||||||
|
log_1.replaceLog(externalLog);
|
||||||
|
}
|
||||||
|
exports.useLog = useLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Initialize asteroid instance with given options or defaults.
|
||||||
|
* Callback follows error-first-pattern.
|
||||||
|
* Error returned or promise rejected on timeout.
|
||||||
|
* @param {Object} [options={}] - an object containing options to initialize Asteroid instance with
|
||||||
|
* @param {string} [options.host=''] - hostname to connect to. Removes http/s protocol if taken from URL
|
||||||
|
* @param {boolean} [options.useSsl=false] - whether the SSL is enabled for the host
|
||||||
|
* @param {number} [options.timeout] - timeframe after which the connection attempt will be considered as failed
|
||||||
|
* @returns {Promise<Asteroid>}
|
||||||
|
* @throws {Error} Asteroid connection timeout
|
||||||
|
* @example <caption>Usage with callback</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect({}, (err) => {
|
||||||
|
* if (err) throw err
|
||||||
|
* else console.log('connected')
|
||||||
|
* })
|
||||||
|
* @example <caption>Usage with promise</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* .then(() => console.log('connected'))
|
||||||
|
* .catch((err) => console.error(err))
|
||||||
|
*/
|
||||||
|
function connect(options = {}, callback) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const config = Object.assign({}, settings, options); // override defaults
|
||||||
|
config.host = config.host.replace(/(^\w+:|^)\/\//, '');
|
||||||
|
log_1.logger.info('[connect] Connecting', config);
|
||||||
|
exports.asteroid = new asteroid_1.default(config.host, config.useSsl);
|
||||||
|
setupMethodCache(exports.asteroid); // init instance for later caching method calls
|
||||||
|
exports.asteroid.on('connected', () => {
|
||||||
|
exports.asteroid.resumeLoginPromise.catch(function () {
|
||||||
|
// pass
|
||||||
|
});
|
||||||
|
exports.events.emit('connected');
|
||||||
|
});
|
||||||
|
exports.asteroid.on('reconnected', () => exports.events.emit('reconnected'));
|
||||||
|
let cancelled = false;
|
||||||
|
const rejectionTimeout = setTimeout(function () {
|
||||||
|
log_1.logger.info(`[connect] Timeout (${config.timeout})`);
|
||||||
|
const err = new Error('Asteroid connection timeout');
|
||||||
|
cancelled = true;
|
||||||
|
exports.events.removeAllListeners('connected');
|
||||||
|
callback ? callback(err, exports.asteroid) : reject(err);
|
||||||
|
}, config.timeout);
|
||||||
|
// if to avoid condition where timeout happens before listener to 'connected' is added
|
||||||
|
// and this listener is not removed (because it was added after the removal)
|
||||||
|
if (!cancelled) {
|
||||||
|
exports.events.once('connected', () => {
|
||||||
|
log_1.logger.info('[connect] Connected');
|
||||||
|
// if (cancelled) return asteroid.ddp.disconnect() // cancel if already rejected
|
||||||
|
clearTimeout(rejectionTimeout);
|
||||||
|
if (callback)
|
||||||
|
callback(null, exports.asteroid);
|
||||||
|
resolve(exports.asteroid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.connect = connect;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Remove all active subscriptions, logout and disconnect from Rocket.Chat
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function disconnect() {
|
||||||
|
log_1.logger.info('Unsubscribing, logging out, disconnecting');
|
||||||
|
unsubscribeAll();
|
||||||
|
return logout()
|
||||||
|
.then(() => Promise.resolve());
|
||||||
|
}
|
||||||
|
exports.disconnect = disconnect;
|
||||||
|
// ASYNC AND CACHE METHOD UTILS
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Setup method cache configs from env or defaults, before they are called.
|
||||||
|
* @param asteroid The asteroid instance to cache method calls
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
function setupMethodCache(asteroid) {
|
||||||
|
methodCache.use(asteroid);
|
||||||
|
methodCache.create('getRoomIdByNameOrId', {
|
||||||
|
max: settings.roomCacheMaxSize,
|
||||||
|
maxAge: settings.roomCacheMaxAge
|
||||||
|
}),
|
||||||
|
methodCache.create('getRoomNameById', {
|
||||||
|
max: settings.roomCacheMaxSize,
|
||||||
|
maxAge: settings.roomCacheMaxAge
|
||||||
|
});
|
||||||
|
methodCache.create('createDirectMessage', {
|
||||||
|
max: settings.dmCacheMaxSize,
|
||||||
|
maxAge: settings.dmCacheMaxAge
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Wrap server method calls to always be async (return a Promise with caught exceptions)
|
||||||
|
* @param {any} method - the Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param {string|string[]} params - single or array of parameters of the method to call
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function asyncCall(method, params) {
|
||||||
|
if (!Array.isArray(params))
|
||||||
|
params = [params]; // cast to array for apply
|
||||||
|
log_1.logger.info(`[${method}] Calling (async): ${JSON.stringify(params)}`);
|
||||||
|
return Promise.resolve(exports.asteroid.apply(method, params).result)
|
||||||
|
.catch((err) => {
|
||||||
|
log_1.logger.error(`[${method}] Error:`, err);
|
||||||
|
throw err; // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
(result)
|
||||||
|
? log_1.logger.debug(`[${method}] Success: ${JSON.stringify(result)}`)
|
||||||
|
: log_1.logger.debug(`[${method}] Success`);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.asyncCall = asyncCall;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Call a method as async ({@link module:driver#asyncCall|asyncCall}) via Asteroid,
|
||||||
|
* or through cache ({@link module:driver#cacheCall|cacheCall}) if one is created.
|
||||||
|
*
|
||||||
|
* If the method was called without parameters, they cannot be cached.
|
||||||
|
* As the result, the method will always be called asynchronously.
|
||||||
|
* @param {any} name - the Rocket.Chat server method to call through Asteroid
|
||||||
|
* @param {string|string[]} params - single or array of parameters of the method to call
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function callMethod(name, params) {
|
||||||
|
return (methodCache.has(name) || typeof params === 'undefined')
|
||||||
|
? asyncCall(name, params)
|
||||||
|
: cacheCall(name, params);
|
||||||
|
}
|
||||||
|
exports.callMethod = callMethod;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Call Asteroid method calls with `methodCache`, if cache is valid
|
||||||
|
* @param {any} method - the Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param {string} key - single string parameters only, used as a cache key
|
||||||
|
* @returns {Promise} - Server results or cached results, if valid
|
||||||
|
*/
|
||||||
|
function cacheCall(method, key) {
|
||||||
|
return methodCache.call(method, key)
|
||||||
|
.catch((err) => {
|
||||||
|
log_1.logger.error(`[${method}] Error:`, err);
|
||||||
|
throw err; // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
(result)
|
||||||
|
? log_1.logger.debug(`[${method}] Success: ${JSON.stringify(result)}`)
|
||||||
|
: log_1.logger.debug(`[${method}] Success`);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.cacheCall = cacheCall;
|
||||||
|
// LOGIN AND SUBSCRIBE TO ROOMS
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Login to Rocket.Chat via Asteroid
|
||||||
|
* @param {Object} [credentials={}] - an object containing credentials to log in to Rocket.Chat
|
||||||
|
* @param {string} [credentials.username = ROCKETCHAT_USER] - username of the Rocket.Chat user
|
||||||
|
* @param {string} [credentials.email = ROCKETCHAT_USER] - email of the Rocket.Chat user.
|
||||||
|
* @param {string} [credentials.password=ROCKETCHAT_PASSWORD] - password of the Rocket.Chat user
|
||||||
|
* @param {boolean} [credentials.ldap=false] - whether LDAP is enabled for login
|
||||||
|
* @returns {Promise<UserID>}
|
||||||
|
*/
|
||||||
|
function login(credentials = {
|
||||||
|
username: settings.username,
|
||||||
|
password: settings.password,
|
||||||
|
ldap: settings.ldap
|
||||||
|
}) {
|
||||||
|
let login;
|
||||||
|
// if (credentials.ldap) {
|
||||||
|
// logger.info(`[login] Logging in ${credentials.username} with LDAP`)
|
||||||
|
// login = asteroid.loginWithLDAP(
|
||||||
|
// credentials.email || credentials.username,
|
||||||
|
// credentials.password,
|
||||||
|
// { ldap: true, ldapOptions: credentials.ldapOptions || {} }
|
||||||
|
// )
|
||||||
|
// } else {
|
||||||
|
log_1.logger.info(`[login] Logging in ${credentials.username}`);
|
||||||
|
login = exports.asteroid.loginWithPassword(credentials.email || credentials.username, credentials.password);
|
||||||
|
// }
|
||||||
|
return login
|
||||||
|
.then((loggedInUserId) => {
|
||||||
|
exports.userId = loggedInUserId;
|
||||||
|
return loggedInUserId;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log_1.logger.info('[login] Error:', err);
|
||||||
|
throw err; // throw after log to stop async chain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.login = login;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Logout Rocket.Chat via Asteroid
|
||||||
|
* @returns {Promise}
|
||||||
|
* */
|
||||||
|
function logout() {
|
||||||
|
return exports.asteroid.logout()
|
||||||
|
.catch((err) => {
|
||||||
|
log_1.logger.error('[Logout] Error:', err);
|
||||||
|
throw err; // throw after log to stop async chain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.logout = logout;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Subscribe to Meteor subscription
|
||||||
|
* @param {string} topic - subscription topic
|
||||||
|
* @param {number} roomId - unique ID of the room to subscribe to
|
||||||
|
* @todo - 3rd param of asteroid.subscribe is deprecated in Rocket.Chat?
|
||||||
|
* @returns {Promise<Subscription>} - Subscription instance (added to array), with ID property
|
||||||
|
*/
|
||||||
|
function subscribe(topic, roomId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
log_1.logger.info(`[subscribe] Preparing subscription: ${topic}: ${roomId}`);
|
||||||
|
const subscription = exports.asteroid.subscribe(topic, roomId, true);
|
||||||
|
exports.subscriptions.push(subscription);
|
||||||
|
return subscription.ready
|
||||||
|
.then((id) => {
|
||||||
|
log_1.logger.info(`[subscribe] Stream ready: ${id}`);
|
||||||
|
resolve(subscription);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.subscribe = subscribe;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Unsubscribe from Meteor subscription
|
||||||
|
* @param {any} subscription - Subscription instance to unsbscribe from
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function unsubscribe(subscription) {
|
||||||
|
const index = exports.subscriptions.indexOf(subscription);
|
||||||
|
if (index === -1)
|
||||||
|
return;
|
||||||
|
subscription.stop();
|
||||||
|
// asteroid.unsubscribe(subscription.id) // v2
|
||||||
|
exports.subscriptions.splice(index, 1); // remove from collection
|
||||||
|
log_1.logger.info(`[${subscription.id}] Unsubscribed`);
|
||||||
|
}
|
||||||
|
exports.unsubscribe = unsubscribe;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Unsubscribe from all subscriptions in collection
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
function unsubscribeAll() {
|
||||||
|
exports.subscriptions.map((s) => unsubscribe(s));
|
||||||
|
}
|
||||||
|
exports.unsubscribeAll = unsubscribeAll;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Begin subscription to room events for user
|
||||||
|
*
|
||||||
|
* > NOTE: Older adapters used an option for this method but it was always the default.
|
||||||
|
* @param {string} [topic=stream-room-messages] - subscription topic
|
||||||
|
* @param {number} [roomId=__my_messages__] - unique ID of the room to subscribe to
|
||||||
|
* @returns {Promise<Subscription>} - Subscription instance
|
||||||
|
*/
|
||||||
|
function subscribeToMessages() {
|
||||||
|
return subscribe(_messageCollectionName, _messageStreamName)
|
||||||
|
.then((subscription) => {
|
||||||
|
exports.messages = exports.asteroid.getCollection(_messageCollectionName);
|
||||||
|
return subscription;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.subscribeToMessages = subscribeToMessages;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Attach a callback to changes in the message stream.
|
||||||
|
*
|
||||||
|
* This method should be used only after a subscription was created using
|
||||||
|
* {@link module:driver#subscribeToMessages|subscribeToMessages}.
|
||||||
|
* Fires callback with every change in subscriptions.
|
||||||
|
*
|
||||||
|
* > NOTE: This method can be called directly for custom extensions, but for most usage
|
||||||
|
* (e.g. for bots) the
|
||||||
|
* {@link module:driver#respondToMessages|respondToMessages} is more useful to only receive messages
|
||||||
|
* matching configuration.
|
||||||
|
*
|
||||||
|
* If the bot hasn't been joined to any rooms at this point, it will attempt to
|
||||||
|
* join now based on environment config, otherwise it might not receive any
|
||||||
|
* messages. It doesn't matter that this happens asynchronously because the rooms
|
||||||
|
* the bot joined to can change after the reactive query is set up.
|
||||||
|
*
|
||||||
|
* @todo `reactToMessages` should call `subscribeToMessages` if not already
|
||||||
|
* done, so it's not required as an arbitrary step for simpler adapters.
|
||||||
|
* Also make `login` call `connect` for the same reason, the way
|
||||||
|
* `respondToMessages` calls `respondToMessages`, so all that's really
|
||||||
|
* required is:
|
||||||
|
* `driver.login(credentials).then(() => driver.respondToMessages(callback))`
|
||||||
|
* @param {Function} callback - function called with every change in subscriptions.
|
||||||
|
* - It uses error-first callback pattern
|
||||||
|
* - The second argument is the changed item
|
||||||
|
* - The third argument is additional attributes, such as `roomType`
|
||||||
|
*/
|
||||||
|
function reactToMessages(callback) {
|
||||||
|
log_1.logger.info(`[reactive] Listening for change events in collection ${exports.messages.name}`);
|
||||||
|
exports.messages.reactiveQuery({}).on('change', (_id) => {
|
||||||
|
const changedMessageQuery = exports.messages.reactiveQuery({ _id });
|
||||||
|
if (changedMessageQuery.result && changedMessageQuery.result.length > 0) {
|
||||||
|
const changedMessage = changedMessageQuery.result[0];
|
||||||
|
if (Array.isArray(changedMessage.args)) {
|
||||||
|
log_1.logger.info(`[received] Message in room ${changedMessage.args[0].rid}`);
|
||||||
|
callback(null, changedMessage.args[0], changedMessage.args[1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log_1.logger.debug('[received] Update without message args');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log_1.logger.debug('[received] Reactive query at ID ${ _id } without results');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.reactToMessages = reactToMessages;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Proxy for {@link module:driver#reactToMessages|reactToMessages}
|
||||||
|
* with some filtering of messages based on config. This is a more user-friendly method
|
||||||
|
* for bots to subscribe to a message stream.
|
||||||
|
* @param {Function} callback - function called after filters run on subscription events.
|
||||||
|
* - It uses error-first callback pattern
|
||||||
|
* - The second argument is the changed item
|
||||||
|
* - The third argument is additional attributes, such as `roomType`
|
||||||
|
* @param {Object} options - an object that sets filters for different event/message types
|
||||||
|
* @param {string[]} options.rooms - respond to only selected room/s (names or IDs). Ignored if `options.allPublic=true`
|
||||||
|
* 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.
|
||||||
|
* @param {boolean} options.allPublic - respond on all public channels. Ignored if `options.rooms=true`
|
||||||
|
* @param {boolean} options.dm - respond to messages in direct messages / private chats with the SDK user
|
||||||
|
* @param {boolean} options.livechat - respond to messages in Livechat rooms
|
||||||
|
* @param {boolean} options.edited - respond to edited messages
|
||||||
|
*/
|
||||||
|
function respondToMessages(callback, options = {}) {
|
||||||
|
const config = Object.assign({}, settings, options);
|
||||||
|
// return value, may be replaced by async ops
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
// Join configured rooms if they haven't been already, unless listening to all
|
||||||
|
// public rooms, in which case it doesn't matter
|
||||||
|
if (!config.allPublic &&
|
||||||
|
exports.joinedIds.length === 0 &&
|
||||||
|
config.rooms &&
|
||||||
|
config.rooms.length > 0) {
|
||||||
|
promise = joinRooms(config.rooms)
|
||||||
|
.catch((err) => {
|
||||||
|
log_1.logger.error(`[joinRooms] Failed to join configured rooms (${config.rooms.join(', ')}): ${err.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.lastReadTime = new Date(); // init before any message read
|
||||||
|
reactToMessages((err, message, meta) => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
if (err) {
|
||||||
|
log_1.logger.error(`[received] Unable to receive: ${err.message}`);
|
||||||
|
callback(err); // bubble errors back to adapter
|
||||||
|
}
|
||||||
|
// Ignore bot's own messages
|
||||||
|
if (message.u._id === exports.userId)
|
||||||
|
return;
|
||||||
|
// Ignore DMs unless configured not to
|
||||||
|
const isDM = meta.roomType === 'd';
|
||||||
|
if (isDM && !config.dm)
|
||||||
|
return;
|
||||||
|
// Ignore Livechat unless configured not to
|
||||||
|
const isLC = meta.roomType === 'l';
|
||||||
|
if (isLC && !config.livechat)
|
||||||
|
return;
|
||||||
|
// Ignore messages in un-joined public rooms unless configured not to
|
||||||
|
if (!config.allPublic && !isDM && !meta.roomParticipant)
|
||||||
|
return;
|
||||||
|
// Set current time for comparison to incoming
|
||||||
|
let currentReadTime = new Date(message.ts.$date);
|
||||||
|
// Ignore edited messages if configured to
|
||||||
|
if (!config.edited && message.editedAt)
|
||||||
|
return;
|
||||||
|
// Set read time as time of edit, if message is edited
|
||||||
|
if (message.editedAt)
|
||||||
|
currentReadTime = new Date(message.editedAt.$date);
|
||||||
|
// Ignore messages in stream that aren't new
|
||||||
|
if (currentReadTime <= exports.lastReadTime)
|
||||||
|
return;
|
||||||
|
// At this point, message has passed checks and can be responded to
|
||||||
|
log_1.logger.info(`[received] Message ${message._id} from ${message.u.username}`);
|
||||||
|
exports.lastReadTime = currentReadTime;
|
||||||
|
// Processing completed, call callback to respond to message
|
||||||
|
callback(null, message, meta);
|
||||||
|
}));
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
exports.respondToMessages = respondToMessages;
|
||||||
|
// PREPARE AND SEND MESSAGES
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Get room's ID by its name
|
||||||
|
* @param {string} name - room's name or ID
|
||||||
|
* @returns {Promise<roomId>}
|
||||||
|
*/
|
||||||
|
function getRoomId(name) {
|
||||||
|
return cacheCall('getRoomIdByNameOrId', name);
|
||||||
|
}
|
||||||
|
exports.getRoomId = getRoomId;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Get room's name by its ID
|
||||||
|
* @param {string} id - room's ID
|
||||||
|
* @returns {Promise<roomName>}
|
||||||
|
*/
|
||||||
|
function getRoomName(id) {
|
||||||
|
return cacheCall('getRoomNameById', id);
|
||||||
|
}
|
||||||
|
exports.getRoomName = getRoomName;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Get ID for a DM room by its recipient's name.
|
||||||
|
*
|
||||||
|
* The call will create a DM (with the bot) if it doesn't exist yet.
|
||||||
|
* @param {string} username - recipient's username
|
||||||
|
* @returns {Promise<roomId>}
|
||||||
|
* @todo test why create resolves with object instead of simply ID
|
||||||
|
*/
|
||||||
|
function getDirectMessageRoomId(username) {
|
||||||
|
return cacheCall('createDirectMessage', username)
|
||||||
|
.then((DM) => DM.rid);
|
||||||
|
}
|
||||||
|
exports.getDirectMessageRoomId = getDirectMessageRoomId;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Join the bot into a room using room's name or ID
|
||||||
|
* @param {string} room - room's name or ID
|
||||||
|
* @returns {Promise}
|
||||||
|
* */
|
||||||
|
function joinRoom(room) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let roomId = yield getRoomId(room);
|
||||||
|
let joinedIndex = exports.joinedIds.indexOf(room);
|
||||||
|
if (joinedIndex !== -1) {
|
||||||
|
log_1.logger.error(`[joinRoom] room was already joined`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yield asyncCall('joinRoom', roomId);
|
||||||
|
exports.joinedIds.push(roomId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.joinRoom = joinRoom;
|
||||||
|
/**
|
||||||
|
* Exit a room the bot has joined
|
||||||
|
* @ignore
|
||||||
|
* */
|
||||||
|
function leaveRoom(room) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let roomId = yield getRoomId(room);
|
||||||
|
let joinedIndex = exports.joinedIds.indexOf(room);
|
||||||
|
if (joinedIndex === -1) {
|
||||||
|
log_1.logger.error(`[leaveRoom] failed because bot has not joined ${room}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yield asyncCall('leaveRoom', roomId);
|
||||||
|
delete exports.joinedIds[joinedIndex];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.leaveRoom = leaveRoom;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Join a set of rooms by array of room names or IDs
|
||||||
|
* @param {string[]} rooms - array of room names or IDs
|
||||||
|
* @returns {Promise}
|
||||||
|
* */
|
||||||
|
function joinRooms(rooms) {
|
||||||
|
return Promise.all(rooms.map((room) => joinRoom(room)));
|
||||||
|
}
|
||||||
|
exports.joinRooms = joinRooms;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Structure message content, optionally sending it to a specific room ID
|
||||||
|
* @param {string|Object} content - message text string or a structured message object
|
||||||
|
* @param {string} [roomId] - room's ID to send message content to
|
||||||
|
* @returns {Object<message>}
|
||||||
|
*/
|
||||||
|
function prepareMessage(content, roomId) {
|
||||||
|
const message = new message_1.Message(content, exports.integrationId);
|
||||||
|
if (roomId)
|
||||||
|
message.setRoomId(roomId);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
exports.prepareMessage = prepareMessage;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Send a prepared message object (with a pre-defined room ID).
|
||||||
|
* Usually prepared and called by `sendMessageByRoomId` or `sendMessageByRoom`.
|
||||||
|
* @param {Object} message - structured message object
|
||||||
|
* @returns {Promise<messageObject>}
|
||||||
|
*/
|
||||||
|
function sendMessage(message) {
|
||||||
|
return asyncCall('sendMessage', message);
|
||||||
|
}
|
||||||
|
exports.sendMessage = sendMessage;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Prepare and send string(s) to the specified room ID
|
||||||
|
* @param {string|string[]} content - message text string or array of strings
|
||||||
|
* @param {string} roomId - ID of the target room to use in send
|
||||||
|
* @returns {Promise<messageObject>|Promise[]}
|
||||||
|
* @todo Returning one or many gets complicated with type checking not allowing
|
||||||
|
* use of a property because result may be array, when you know it's not.
|
||||||
|
* Solution would probably be to always return an array, even for single
|
||||||
|
* send. This would be a breaking change, should hold until major version.
|
||||||
|
*/
|
||||||
|
function sendToRoomId(content, roomId) {
|
||||||
|
if (!Array.isArray(content)) {
|
||||||
|
return sendMessage(prepareMessage(content, roomId));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Promise.all(content.map((text) => {
|
||||||
|
return sendMessage(prepareMessage(text, roomId));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.sendToRoomId = sendToRoomId;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Prepare and send string(s) to the specified room name (or ID).
|
||||||
|
* @param {string|string[]} content - message text string or array of strings
|
||||||
|
* @param {string} room - name or ID of the target room to use in send
|
||||||
|
* @returns {Promise<messageObject>}
|
||||||
|
*/
|
||||||
|
function sendToRoom(content, room) {
|
||||||
|
return getRoomId(room)
|
||||||
|
.then((roomId) => sendToRoomId(content, roomId));
|
||||||
|
}
|
||||||
|
exports.sendToRoom = sendToRoom;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Prepare and send string(s) to a user in a DM
|
||||||
|
* @param {string|string[]} content - message text string or array of strings
|
||||||
|
* @param {string} username - name or ID of the target room to use in send.
|
||||||
|
* Creates DM room if it does not exist
|
||||||
|
* @returns {Promise<messageObject>}
|
||||||
|
*/
|
||||||
|
function sendDirectToUser(content, username) {
|
||||||
|
return getDirectMessageRoomId(username)
|
||||||
|
.then((rid) => sendToRoomId(content, rid));
|
||||||
|
}
|
||||||
|
exports.sendDirectToUser = sendDirectToUser;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Edit an existing message, replacing any attributes with the provided ones
|
||||||
|
* @param {Object} message - structured message object.
|
||||||
|
* The given message object should have the ID of an existing message.
|
||||||
|
* @returns {Object<message>}
|
||||||
|
*/
|
||||||
|
function editMessage(message) {
|
||||||
|
return asyncCall('updateMessage', message);
|
||||||
|
}
|
||||||
|
exports.editMessage = editMessage;
|
||||||
|
/**
|
||||||
|
* @memberof module:driver
|
||||||
|
* @instance
|
||||||
|
* @description Send a reaction to an existing message. Simple proxy for method call.
|
||||||
|
* @param {string} emoji - reaction emoji to add. For example, `:thumbsup:` to add 👍.
|
||||||
|
* @param {string} messageId - ID of the previously sent message
|
||||||
|
* @returns {Object<message>}
|
||||||
|
*/
|
||||||
|
function setReaction(emoji, messageId) {
|
||||||
|
return asyncCall('setReaction', [emoji, messageId]);
|
||||||
|
}
|
||||||
|
exports.setReaction = setReaction;
|
||||||
|
//# sourceMappingURL=driver.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,5 @@
|
|||||||
|
import { ILogger } from '../config/driverInterfaces';
|
||||||
|
declare let logger: ILogger;
|
||||||
|
declare function replaceLog(externalLog: ILogger): void;
|
||||||
|
declare function silence(): void;
|
||||||
|
export { logger, replaceLog, silence };
|
@ -0,0 +1,37 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/** Temp logging, should override form adapter's log */
|
||||||
|
class InternalLog {
|
||||||
|
debug(...args) {
|
||||||
|
console.log(...args);
|
||||||
|
}
|
||||||
|
info(...args) {
|
||||||
|
console.log(...args);
|
||||||
|
}
|
||||||
|
warning(...args) {
|
||||||
|
console.warn(...args);
|
||||||
|
}
|
||||||
|
warn(...args) {
|
||||||
|
return this.warning(...args);
|
||||||
|
}
|
||||||
|
error(...args) {
|
||||||
|
console.error(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let logger = new InternalLog();
|
||||||
|
exports.logger = logger;
|
||||||
|
function replaceLog(externalLog) {
|
||||||
|
exports.logger = logger = externalLog;
|
||||||
|
}
|
||||||
|
exports.replaceLog = replaceLog;
|
||||||
|
function silence() {
|
||||||
|
replaceLog({
|
||||||
|
debug: () => null,
|
||||||
|
info: () => null,
|
||||||
|
warn: () => null,
|
||||||
|
warning: () => null,
|
||||||
|
error: () => null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.silence = silence;
|
||||||
|
//# sourceMappingURL=log.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/lib/log.ts"],"names":[],"mappings":";;AAEA,uDAAuD;AACvD;IACE,KAAK,CAAE,GAAG,IAAW;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACtB,CAAC;IACD,IAAI,CAAE,GAAG,IAAW;QAClB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACtB,CAAC;IACD,OAAO,CAAE,GAAG,IAAW;QACrB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACvB,CAAC;IACD,IAAI,CAAE,GAAG,IAAW;QAClB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;IAC9B,CAAC;IACD,KAAK,CAAE,GAAG,IAAW;QACnB,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAA;IACxB,CAAC;CACF;AAED,IAAI,MAAM,GAAY,IAAI,WAAW,EAAE,CAAA;AAiBrC,wBAAM;AAfR,oBAAqB,WAAoB;IACvC,iBAAA,MAAM,GAAG,WAAW,CAAA;AACtB,CAAC;AAcC,gCAAU;AAZZ;IACE,UAAU,CAAC;QACT,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;QACjB,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI;QAChB,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI;QAChB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;QACnB,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;KAClB,CAAC,CAAA;AACJ,CAAC;AAKC,0BAAO","sourcesContent":["import { ILogger } from '../config/driverInterfaces'\n\n/** Temp logging, should override form adapter's log */\nclass InternalLog implements ILogger {\n debug (...args: any[]) {\n console.log(...args)\n }\n info (...args: any[]) {\n console.log(...args)\n }\n warning (...args: any[]) {\n console.warn(...args)\n }\n warn (...args: any[]) { // legacy method\n return this.warning(...args)\n }\n error (...args: any[]) {\n console.error(...args)\n }\n}\n\nlet logger: ILogger = new InternalLog()\n\nfunction replaceLog (externalLog: ILogger) {\n logger = externalLog\n}\n\nfunction silence () {\n replaceLog({\n debug: () => null,\n info: () => null,\n warn: () => null,\n warning: () => null,\n error: () => null\n })\n}\n\nexport {\n logger,\n replaceLog,\n silence\n}\n"]}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { IMessage } from '../config/messageInterfaces';
|
||||||
|
export interface Message extends IMessage {
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Rocket.Chat message class.
|
||||||
|
* Sets integration param to allow tracing source of automated sends.
|
||||||
|
* @param content Accepts message text or a preformed message object
|
||||||
|
* @todo Potential for SDK usage that isn't bots, bot prop should be optional?
|
||||||
|
*/
|
||||||
|
export declare class Message {
|
||||||
|
constructor(content: string | IMessage, integrationId: string);
|
||||||
|
setRoomId(roomId: string): Message;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/**
|
||||||
|
* Rocket.Chat message class.
|
||||||
|
* Sets integration param to allow tracing source of automated sends.
|
||||||
|
* @param content Accepts message text or a preformed message object
|
||||||
|
* @todo Potential for SDK usage that isn't bots, bot prop should be optional?
|
||||||
|
*/
|
||||||
|
class Message {
|
||||||
|
constructor(content, integrationId) {
|
||||||
|
if (typeof content === 'string')
|
||||||
|
this.msg = content;
|
||||||
|
else
|
||||||
|
Object.assign(this, content);
|
||||||
|
this.bot = { i: integrationId };
|
||||||
|
}
|
||||||
|
setRoomId(roomId) {
|
||||||
|
this.rid = roomId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Message = Message;
|
||||||
|
//# sourceMappingURL=message.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../src/lib/message.ts"],"names":[],"mappings":";;AAMA;;;;;GAKG;AACH;IACE,YAAa,OAA0B,EAAE,aAAqB;QAC5D,EAAE,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC;YAAC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAA;QACnD,IAAI;YAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACjC,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,aAAa,EAAE,CAAA;IACjC,CAAC;IACD,SAAS,CAAE,MAAc;QACvB,IAAI,CAAC,GAAG,GAAG,MAAM,CAAA;QACjB,MAAM,CAAC,IAAI,CAAA;IACb,CAAC;CACF;AAVD,0BAUC","sourcesContent":["import { IMessage } from '../config/messageInterfaces'\n\n// Message class declaration implicitly implements interface\n// https://github.com/Microsoft/TypeScript/issues/340\nexport interface Message extends IMessage {}\n\n/**\n * Rocket.Chat message class.\n * Sets integration param to allow tracing source of automated sends.\n * @param content Accepts message text or a preformed message object\n * @todo Potential for SDK usage that isn't bots, bot prop should be optional?\n */\nexport class Message {\n constructor (content: string | IMessage, integrationId: string) {\n if (typeof content === 'string') this.msg = content\n else Object.assign(this, content)\n this.bot = { i: integrationId }\n }\n setRoomId (roomId: string): Message {\n this.rid = roomId\n return this\n }\n}\n"]}
|
@ -0,0 +1,46 @@
|
|||||||
|
/// <reference types="lru-cache" />
|
||||||
|
import LRU from 'lru-cache';
|
||||||
|
/** @TODO: Remove ! post-fix expression when TypeScript #9619 resolved */
|
||||||
|
export declare let instance: any;
|
||||||
|
export declare const results: Map<string, LRU.Cache<string, any>>;
|
||||||
|
export declare const defaults: LRU.Options;
|
||||||
|
/**
|
||||||
|
* Set the instance to call methods on, with cached results.
|
||||||
|
* @param instanceToUse Instance of a class
|
||||||
|
*/
|
||||||
|
export declare function use(instanceToUse: object): void;
|
||||||
|
/**
|
||||||
|
* Setup a cache for a method call.
|
||||||
|
* @param method Method name, for index of cached results
|
||||||
|
* @param options.max Maximum size of cache
|
||||||
|
* @param options.maxAge Maximum age of cache
|
||||||
|
*/
|
||||||
|
export declare function create(method: string, options?: LRU.Options): LRU.Cache<string, any> | undefined;
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call or call and cache.
|
||||||
|
* @param method Method name, to call on instance in use
|
||||||
|
* @param key Key to pass to method call and save results against
|
||||||
|
*/
|
||||||
|
export declare function call(method: string, key: string): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Proxy for checking if method has been cached.
|
||||||
|
* Cache may exist from manual creation, or prior call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
*/
|
||||||
|
export declare function has(method: string): boolean;
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
* @param key Key for method result set to return
|
||||||
|
*/
|
||||||
|
export declare function get(method: string, key: string): LRU.Cache<string, any> | undefined;
|
||||||
|
/**
|
||||||
|
* Reset a cached method call's results (all or only for given key).
|
||||||
|
* @param method Method name for cache to clear
|
||||||
|
* @param key Key for method result set to clear
|
||||||
|
*/
|
||||||
|
export declare function reset(method: string, key?: string): void;
|
||||||
|
/**
|
||||||
|
* Reset cached results for all methods.
|
||||||
|
*/
|
||||||
|
export declare function resetAll(): void;
|
@ -0,0 +1,97 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const lru_cache_1 = __importDefault(require("lru-cache"));
|
||||||
|
const log_1 = require("./log");
|
||||||
|
exports.results = new Map();
|
||||||
|
exports.defaults = {
|
||||||
|
max: 100,
|
||||||
|
maxAge: 300 * 1000
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Set the instance to call methods on, with cached results.
|
||||||
|
* @param instanceToUse Instance of a class
|
||||||
|
*/
|
||||||
|
function use(instanceToUse) {
|
||||||
|
exports.instance = instanceToUse;
|
||||||
|
}
|
||||||
|
exports.use = use;
|
||||||
|
/**
|
||||||
|
* Setup a cache for a method call.
|
||||||
|
* @param method Method name, for index of cached results
|
||||||
|
* @param options.max Maximum size of cache
|
||||||
|
* @param options.maxAge Maximum age of cache
|
||||||
|
*/
|
||||||
|
function create(method, options = {}) {
|
||||||
|
options = Object.assign(exports.defaults, options);
|
||||||
|
exports.results.set(method, new lru_cache_1.default(options));
|
||||||
|
return exports.results.get(method);
|
||||||
|
}
|
||||||
|
exports.create = create;
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call or call and cache.
|
||||||
|
* @param method Method name, to call on instance in use
|
||||||
|
* @param key Key to pass to method call and save results against
|
||||||
|
*/
|
||||||
|
function call(method, key) {
|
||||||
|
if (!exports.results.has(method))
|
||||||
|
create(method); // create as needed
|
||||||
|
const methodCache = exports.results.get(method);
|
||||||
|
let callResults;
|
||||||
|
if (methodCache.has(key)) {
|
||||||
|
log_1.logger.debug(`[${method}] Calling (cached): ${key}`);
|
||||||
|
// return from cache if key has been used on method before
|
||||||
|
callResults = methodCache.get(key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// call and cache for next time, returning results
|
||||||
|
log_1.logger.debug(`[${method}] Calling (caching): ${key}`);
|
||||||
|
callResults = exports.instance.call(method, key).result;
|
||||||
|
methodCache.set(key, callResults);
|
||||||
|
}
|
||||||
|
return Promise.resolve(callResults);
|
||||||
|
}
|
||||||
|
exports.call = call;
|
||||||
|
/**
|
||||||
|
* Proxy for checking if method has been cached.
|
||||||
|
* Cache may exist from manual creation, or prior call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
*/
|
||||||
|
function has(method) {
|
||||||
|
return exports.results.has(method);
|
||||||
|
}
|
||||||
|
exports.has = has;
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
* @param key Key for method result set to return
|
||||||
|
*/
|
||||||
|
function get(method, key) {
|
||||||
|
if (exports.results.has(method))
|
||||||
|
return exports.results.get(method).get(key);
|
||||||
|
}
|
||||||
|
exports.get = get;
|
||||||
|
/**
|
||||||
|
* Reset a cached method call's results (all or only for given key).
|
||||||
|
* @param method Method name for cache to clear
|
||||||
|
* @param key Key for method result set to clear
|
||||||
|
*/
|
||||||
|
function reset(method, key) {
|
||||||
|
if (exports.results.has(method)) {
|
||||||
|
if (key)
|
||||||
|
return exports.results.get(method).del(key);
|
||||||
|
else
|
||||||
|
return exports.results.get(method).reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.reset = reset;
|
||||||
|
/**
|
||||||
|
* Reset cached results for all methods.
|
||||||
|
*/
|
||||||
|
function resetAll() {
|
||||||
|
exports.results.forEach((cache) => cache.reset());
|
||||||
|
}
|
||||||
|
exports.resetAll = resetAll;
|
||||||
|
//# sourceMappingURL=methodCache.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"methodCache.js","sourceRoot":"","sources":["../../src/lib/methodCache.ts"],"names":[],"mappings":";;;;;AAAA,0DAA2B;AAC3B,+BAA8B;AAIjB,QAAA,OAAO,GAAwC,IAAI,GAAG,EAAE,CAAA;AACxD,QAAA,QAAQ,GAAgB;IACnC,GAAG,EAAE,GAAG;IACR,MAAM,EAAE,GAAG,GAAG,IAAI;CACnB,CAAA;AAED;;;GAGG;AACH,aAAqB,aAAqB;IACxC,gBAAQ,GAAG,aAAa,CAAA;AAC1B,CAAC;AAFD,kBAEC;AAED;;;;;GAKG;AACH,gBAAwB,MAAc,EAAE,UAAuB,EAAE;IAC/D,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAQ,EAAE,OAAO,CAAC,CAAA;IAC1C,eAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,mBAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IACrC,MAAM,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAC5B,CAAC;AAJD,wBAIC;AAED;;;;GAIG;AACH,cAAsB,MAAc,EAAE,GAAW;IAC/C,EAAE,CAAC,CAAC,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,MAAM,CAAC,CAAA,CAAC,mBAAmB;IAC5D,MAAM,WAAW,GAAG,eAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAA;IACxC,IAAI,WAAW,CAAA;IAEf,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACzB,YAAM,CAAC,KAAK,CAAC,IAAI,MAAM,uBAAuB,GAAG,EAAE,CAAC,CAAA;QACpD,0DAA0D;QAC1D,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAAC,IAAI,CAAC,CAAC;QACN,kDAAkD;QAClD,YAAM,CAAC,KAAK,CAAC,IAAI,MAAM,wBAAwB,GAAG,EAAE,CAAC,CAAA;QACrD,WAAW,GAAG,gBAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAA;QAC/C,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;IACnC,CAAC;IACD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;AACrC,CAAC;AAhBD,oBAgBC;AAED;;;;GAIG;AACH,aAAqB,MAAc;IACjC,MAAM,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AAC5B,CAAC;AAFD,kBAEC;AAED;;;;GAIG;AACH,aAAqB,MAAc,EAAE,GAAW;IAC9C,EAAE,CAAC,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAC/D,CAAC;AAFD,kBAEC;AAED;;;;GAIG;AACH,eAAuB,MAAc,EAAE,GAAY;IACjD,EAAE,CAAC,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,EAAE,CAAC,CAAC,GAAG,CAAC;YAAC,MAAM,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7C,IAAI;YAAC,MAAM,CAAC,eAAO,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,KAAK,EAAE,CAAA;IAC1C,CAAC;AACH,CAAC;AALD,sBAKC;AAED;;GAEG;AACH;IACE,eAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;AAC3C,CAAC;AAFD,4BAEC","sourcesContent":["import LRU from 'lru-cache'\nimport { logger } from './log'\n\n/** @TODO: Remove ! post-fix expression when TypeScript #9619 resolved */\nexport let instance: any\nexport const results: Map<string, LRU.Cache<string, any>> = new Map()\nexport const defaults: LRU.Options = {\n max: 100,\n maxAge: 300 * 1000\n}\n\n/**\n * Set the instance to call methods on, with cached results.\n * @param instanceToUse Instance of a class\n */\nexport function use (instanceToUse: object): void {\n instance = instanceToUse\n}\n\n/**\n * Setup a cache for a method call.\n * @param method Method name, for index of cached results\n * @param options.max Maximum size of cache\n * @param options.maxAge Maximum age of cache\n */\nexport function create (method: string, options: LRU.Options = {}): LRU.Cache<string, any> | undefined {\n options = Object.assign(defaults, options)\n results.set(method, new LRU(options))\n return results.get(method)\n}\n\n/**\n * Get results of a prior method call or call and cache.\n * @param method Method name, to call on instance in use\n * @param key Key to pass to method call and save results against\n */\nexport function call (method: string, key: string): Promise<any> {\n if (!results.has(method)) create(method) // create as needed\n const methodCache = results.get(method)!\n let callResults\n\n if (methodCache.has(key)) {\n logger.debug(`[${method}] Calling (cached): ${key}`)\n // return from cache if key has been used on method before\n callResults = methodCache.get(key)\n } else {\n // call and cache for next time, returning results\n logger.debug(`[${method}] Calling (caching): ${key}`)\n callResults = instance.call(method, key).result\n methodCache.set(key, callResults)\n }\n return Promise.resolve(callResults)\n}\n\n/**\n * Proxy for checking if method has been cached.\n * Cache may exist from manual creation, or prior call.\n * @param method Method name for cache to get\n */\nexport function has (method: string): boolean {\n return results.has(method)\n}\n\n/**\n * Get results of a prior method call.\n * @param method Method name for cache to get\n * @param key Key for method result set to return\n */\nexport function get (method: string, key: string): LRU.Cache<string, any> | undefined {\n if (results.has(method)) return results.get(method)!.get(key)\n}\n\n/**\n * Reset a cached method call's results (all or only for given key).\n * @param method Method name for cache to clear\n * @param key Key for method result set to clear\n */\nexport function reset (method: string, key?: string): void {\n if (results.has(method)) {\n if (key) return results.get(method)!.del(key)\n else return results.get(method)!.reset()\n }\n}\n\n/**\n * Reset cached results for all methods.\n */\nexport function resetAll (): void {\n results.forEach((cache) => cache.reset())\n}\n"]}
|
@ -0,0 +1,16 @@
|
|||||||
|
export declare let username: string;
|
||||||
|
export declare let password: string;
|
||||||
|
export declare let ldap: boolean;
|
||||||
|
export declare let host: string;
|
||||||
|
export declare let useSsl: boolean;
|
||||||
|
export declare let timeout: number;
|
||||||
|
export declare let rooms: string[];
|
||||||
|
export declare let allPublic: boolean;
|
||||||
|
export declare let dm: boolean;
|
||||||
|
export declare let livechat: boolean;
|
||||||
|
export declare let edited: boolean;
|
||||||
|
export declare let integrationId: string;
|
||||||
|
export declare let roomCacheMaxSize: number;
|
||||||
|
export declare let roomCacheMaxAge: number;
|
||||||
|
export declare let dmCacheMaxSize: number;
|
||||||
|
export declare let dmCacheMaxAge: number;
|
@ -0,0 +1,28 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
// Login settings - LDAP needs to be explicitly enabled
|
||||||
|
exports.username = process.env.ROCKETCHAT_USER || 'bot';
|
||||||
|
exports.password = process.env.ROCKETCHAT_PASSWORD || 'pass';
|
||||||
|
exports.ldap = (process.env.ROCKETCHAT_AUTH === 'ldap');
|
||||||
|
// Connection settings - Enable SSL by default if Rocket.Chat URL contains https
|
||||||
|
exports.host = process.env.ROCKETCHAT_URL || 'localhost:3000';
|
||||||
|
exports.useSsl = (process.env.ROCKETCHAT_USE_SSL)
|
||||||
|
? ((process.env.ROCKETCHAT_USE_SSL || '').toString().toLowerCase() === 'true')
|
||||||
|
: ((process.env.ROCKETCHAT_URL || '').toString().toLowerCase().startsWith('https'));
|
||||||
|
exports.timeout = 20 * 1000; // 20 seconds
|
||||||
|
// Respond settings - reactive callback filters for .respondToMessages
|
||||||
|
exports.rooms = (process.env.ROCKETCHAT_ROOM)
|
||||||
|
? (process.env.ROCKETCHAT_ROOM || '').split(',').map((room) => room.trim())
|
||||||
|
: [];
|
||||||
|
exports.allPublic = (process.env.LISTEN_ON_ALL_PUBLIC || 'false').toLowerCase() === 'true';
|
||||||
|
exports.dm = (process.env.RESPOND_TO_DM || 'false').toLowerCase() === 'true';
|
||||||
|
exports.livechat = (process.env.RESPOND_TO_LIVECHAT || 'false').toLowerCase() === 'true';
|
||||||
|
exports.edited = (process.env.RESPOND_TO_EDITED || 'false').toLowerCase() === 'true';
|
||||||
|
// Message attribute settings
|
||||||
|
exports.integrationId = process.env.INTEGRATION_ID || 'js.SDK';
|
||||||
|
// Cache settings
|
||||||
|
exports.roomCacheMaxSize = parseInt(process.env.ROOM_CACHE_SIZE || '10', 10);
|
||||||
|
exports.roomCacheMaxAge = 1000 * parseInt(process.env.ROOM_CACHE_MAX_AGE || '300', 10);
|
||||||
|
exports.dmCacheMaxSize = parseInt(process.env.DM_ROOM_CACHE_SIZE || '10', 10);
|
||||||
|
exports.dmCacheMaxAge = 1000 * parseInt(process.env.DM_ROOM_CACHE_MAX_AGE || '100', 10);
|
||||||
|
//# sourceMappingURL=settings.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/lib/settings.ts"],"names":[],"mappings":";;AACA,uDAAuD;AAC5C,QAAA,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,KAAK,CAAA;AAC/C,QAAA,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM,CAAA;AACpD,QAAA,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,MAAM,CAAC,CAAA;AAE1D,gFAAgF;AACrE,QAAA,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAA;AACrD,QAAA,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAClD,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;IAC9E,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;AAC1E,QAAA,OAAO,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,aAAa;AAE5C,sEAAsE;AAC3D,QAAA,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC9C,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC,CAAC,EAAE,CAAA;AACK,QAAA,SAAS,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;AAClF,QAAA,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;AACpE,QAAA,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;AAChF,QAAA,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;AAEvF,6BAA6B;AAClB,QAAA,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,QAAQ,CAAA;AAEjE,iBAAiB;AACN,QAAA,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,EAAE,EAAE,CAAC,CAAA;AACpE,QAAA,eAAe,GAAG,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,EAAE,EAAE,CAAC,CAAA;AAC9E,QAAA,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,EAAE,EAAE,CAAC,CAAA;AACrE,QAAA,aAAa,GAAG,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,KAAK,EAAE,EAAE,CAAC,CAAA","sourcesContent":["\n// Login settings - LDAP needs to be explicitly enabled\nexport let username = process.env.ROCKETCHAT_USER || 'bot'\nexport let password = process.env.ROCKETCHAT_PASSWORD || 'pass'\nexport let ldap = (process.env.ROCKETCHAT_AUTH === 'ldap')\n\n// Connection settings - Enable SSL by default if Rocket.Chat URL contains https\nexport let host = process.env.ROCKETCHAT_URL || 'localhost:3000'\nexport let useSsl = (process.env.ROCKETCHAT_USE_SSL)\n ? ((process.env.ROCKETCHAT_USE_SSL || '').toString().toLowerCase() === 'true')\n : ((process.env.ROCKETCHAT_URL || '').toString().toLowerCase().startsWith('https'))\nexport let timeout = 20 * 1000 // 20 seconds\n\n// Respond settings - reactive callback filters for .respondToMessages\nexport let rooms = (process.env.ROCKETCHAT_ROOM)\n ? (process.env.ROCKETCHAT_ROOM || '').split(',').map((room) => room.trim())\n : []\nexport let allPublic = (process.env.LISTEN_ON_ALL_PUBLIC || 'false').toLowerCase() === 'true'\nexport let dm = (process.env.RESPOND_TO_DM || 'false').toLowerCase() === 'true'\nexport let livechat = (process.env.RESPOND_TO_LIVECHAT || 'false').toLowerCase() === 'true'\nexport let edited = (process.env.RESPOND_TO_EDITED || 'false').toLowerCase() === 'true'\n\n// Message attribute settings\nexport let integrationId = process.env.INTEGRATION_ID || 'js.SDK'\n\n// Cache settings\nexport let roomCacheMaxSize = parseInt(process.env.ROOM_CACHE_SIZE || '10', 10)\nexport let roomCacheMaxAge = 1000 * parseInt(process.env.ROOM_CACHE_MAX_AGE || '300', 10)\nexport let dmCacheMaxSize = parseInt(process.env.DM_ROOM_CACHE_SIZE || '10', 10)\nexport let dmCacheMaxAge = 1000 * parseInt(process.env.DM_ROOM_CACHE_MAX_AGE || '100', 10)\n"]}
|
@ -0,0 +1,4 @@
|
|||||||
|
import { INewUserAPI } from './interfaces';
|
||||||
|
export declare const apiUser: INewUserAPI;
|
||||||
|
export declare const botUser: INewUserAPI;
|
||||||
|
export declare const mockUser: INewUserAPI;
|
@ -0,0 +1,34 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
// The API user, should be provisioned on build with local Rocket.Chat
|
||||||
|
exports.apiUser = {
|
||||||
|
username: process.env.ADMIN_USERNAME || 'admin',
|
||||||
|
password: process.env.ADMIN_PASS || 'pass'
|
||||||
|
};
|
||||||
|
// The Bot user, will attempt to login and run methods in tests
|
||||||
|
exports.botUser = {
|
||||||
|
email: 'bot@localhost',
|
||||||
|
name: 'Bot',
|
||||||
|
password: process.env.ROCKETCHAT_PASSWORD || 'pass',
|
||||||
|
username: process.env.ROCKETCHAT_USER || 'bot',
|
||||||
|
active: true,
|
||||||
|
roles: ['bot'],
|
||||||
|
joinDefaultChannels: true,
|
||||||
|
requirePasswordChange: false,
|
||||||
|
sendWelcomeEmail: false,
|
||||||
|
verified: true
|
||||||
|
};
|
||||||
|
// The Mock user, will send messages via API for the bot to respond to
|
||||||
|
exports.mockUser = {
|
||||||
|
email: 'mock@localhost',
|
||||||
|
name: 'Mock User',
|
||||||
|
password: 'mock',
|
||||||
|
username: 'mock',
|
||||||
|
active: true,
|
||||||
|
roles: ['user'],
|
||||||
|
joinDefaultChannels: true,
|
||||||
|
requirePasswordChange: false,
|
||||||
|
sendWelcomeEmail: false,
|
||||||
|
verified: true
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=config.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":";;AAEA,sEAAsE;AACzD,QAAA,OAAO,GAAgB;IAClC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO;IAC/C,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,MAAM;CAC3C,CAAA;AAED,+DAA+D;AAClD,QAAA,OAAO,GAAgB;IAClC,KAAK,EAAE,eAAe;IACtB,IAAI,EAAE,KAAK;IACX,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM;IACnD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,KAAK;IAC9C,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,CAAC,KAAK,CAAC;IACd,mBAAmB,EAAE,IAAI;IACzB,qBAAqB,EAAE,KAAK;IAC5B,gBAAgB,EAAE,KAAK;IACvB,QAAQ,EAAE,IAAI;CACf,CAAA;AAED,sEAAsE;AACzD,QAAA,QAAQ,GAAgB;IACnC,KAAK,EAAE,gBAAgB;IACvB,IAAI,EAAE,WAAW;IACjB,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,mBAAmB,EAAE,IAAI;IACzB,qBAAqB,EAAE,KAAK;IAC5B,gBAAgB,EAAE,KAAK;IACvB,QAAQ,EAAE,IAAI;CACf,CAAA","sourcesContent":["import { INewUserAPI } from './interfaces'\n\n// The API user, should be provisioned on build with local Rocket.Chat\nexport const apiUser: INewUserAPI = {\n username: process.env.ADMIN_USERNAME || 'admin',\n password: process.env.ADMIN_PASS || 'pass'\n}\n\n// The Bot user, will attempt to login and run methods in tests\nexport const botUser: INewUserAPI = {\n email: 'bot@localhost',\n name: 'Bot',\n password: process.env.ROCKETCHAT_PASSWORD || 'pass',\n username: process.env.ROCKETCHAT_USER || 'bot',\n active: true,\n roles: ['bot'],\n joinDefaultChannels: true,\n requirePasswordChange: false,\n sendWelcomeEmail: false,\n verified: true\n}\n\n// The Mock user, will send messages via API for the bot to respond to\nexport const mockUser: INewUserAPI = {\n email: 'mock@localhost',\n name: 'Mock User',\n password: 'mock',\n username: 'mock',\n active: true,\n roles: ['user'],\n joinDefaultChannels: true,\n requirePasswordChange: false,\n sendWelcomeEmail: false,\n verified: true\n}\n"]}
|
@ -0,0 +1,154 @@
|
|||||||
|
/** Payload structure for `chat.postMessage` endpoint */
|
||||||
|
export interface IMessageAPI {
|
||||||
|
roomId: string;
|
||||||
|
channel?: string;
|
||||||
|
text?: string;
|
||||||
|
alias?: string;
|
||||||
|
emoji?: string;
|
||||||
|
avatar?: string;
|
||||||
|
attachments?: IAttachmentAPI[];
|
||||||
|
}
|
||||||
|
/** Payload structure for `chat.update` endpoint */
|
||||||
|
export interface IMessageUpdateAPI {
|
||||||
|
roomId: string;
|
||||||
|
msgId: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
/** Message receipt returned after send (not the same as sent object) */
|
||||||
|
export interface IMessageReceiptAPI {
|
||||||
|
_id: string;
|
||||||
|
rid: string;
|
||||||
|
alias: string;
|
||||||
|
msg: string;
|
||||||
|
parseUrls: boolean;
|
||||||
|
groupable: boolean;
|
||||||
|
ts: string;
|
||||||
|
u: {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
_updatedAt: string;
|
||||||
|
editedAt?: string;
|
||||||
|
editedBy?: {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/** Payload structure for message attachments */
|
||||||
|
export interface IAttachmentAPI {
|
||||||
|
color?: string;
|
||||||
|
text?: string;
|
||||||
|
ts?: string;
|
||||||
|
thumb_url?: string;
|
||||||
|
message_link?: string;
|
||||||
|
collapsed?: boolean;
|
||||||
|
author_name?: string;
|
||||||
|
author_link?: string;
|
||||||
|
author_icon?: string;
|
||||||
|
title?: string;
|
||||||
|
title_link?: string;
|
||||||
|
title_link_download_true?: string;
|
||||||
|
image_url?: string;
|
||||||
|
audio_url?: string;
|
||||||
|
video_url?: string;
|
||||||
|
fields?: IAttachmentFieldAPI[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Payload structure for attachment field object
|
||||||
|
* The field property of the attachments allows for “tables” or “columns” to be displayed on messages
|
||||||
|
*/
|
||||||
|
export interface IAttachmentFieldAPI {
|
||||||
|
short?: boolean;
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
/** Result structure for message endpoints */
|
||||||
|
export interface IMessageResultAPI {
|
||||||
|
ts: number;
|
||||||
|
channel: string;
|
||||||
|
message: IMessageReceiptAPI;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
/** User object structure for creation endpoints */
|
||||||
|
export interface INewUserAPI {
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
password: string;
|
||||||
|
username: string;
|
||||||
|
active?: true;
|
||||||
|
roles?: string[];
|
||||||
|
joinDefaultChannels?: boolean;
|
||||||
|
requirePasswordChange?: boolean;
|
||||||
|
sendWelcomeEmail?: boolean;
|
||||||
|
verified?: true;
|
||||||
|
}
|
||||||
|
/** User object structure for queries (not including admin access level) */
|
||||||
|
export interface IUserAPI {
|
||||||
|
_id: string;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
active: boolean;
|
||||||
|
name: string;
|
||||||
|
utcOffset: number;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
/** Result structure for user data request (by non-admin) */
|
||||||
|
export interface IUserResultAPI {
|
||||||
|
user: IUserAPI;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
/** Room object structure */
|
||||||
|
export interface IRoomAPI {
|
||||||
|
_id: string;
|
||||||
|
_updatedAt: string;
|
||||||
|
t: 'c' | 'p' | 'd' | 'l';
|
||||||
|
msgs: number;
|
||||||
|
ts: string;
|
||||||
|
meta: {
|
||||||
|
revision: number;
|
||||||
|
created: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/** Channel result schema */
|
||||||
|
export interface IChannelAPI {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
t: 'c' | 'p' | 'l';
|
||||||
|
msgs: number;
|
||||||
|
u: {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
ts: string;
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
/** Group result schema */
|
||||||
|
export interface IGroupAPI {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
usernames: string[];
|
||||||
|
t: 'c' | 'p' | 'l';
|
||||||
|
msgs: number;
|
||||||
|
u: {
|
||||||
|
_id: string;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
ts: string;
|
||||||
|
default: boolean;
|
||||||
|
}
|
||||||
|
/** Result structure for room creation (e.g. DM) */
|
||||||
|
export interface IRoomResultAPI {
|
||||||
|
room: IRoomAPI;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
/** Result structure for channel creation */
|
||||||
|
export interface IChannelResultAPI {
|
||||||
|
channel: IChannelAPI;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
/** Result structure for group creation */
|
||||||
|
export interface IGroupResultAPI {
|
||||||
|
group: IGroupAPI;
|
||||||
|
success: boolean;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
//# sourceMappingURL=interfaces.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
export {};
|
@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/** On require, runs the test utils setup method */
|
||||||
|
const testing_1 = require("./testing");
|
||||||
|
const log_1 = require("../lib/log");
|
||||||
|
log_1.silence();
|
||||||
|
testing_1.setup().catch((e) => console.error(e));
|
||||||
|
//# sourceMappingURL=setup.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/utils/setup.ts"],"names":[],"mappings":";;AAAA,mDAAmD;AACnD,uCAAiC;AACjC,oCAAoC;AACpC,aAAO,EAAE,CAAA;AACT,eAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA","sourcesContent":["/** On require, runs the test utils setup method */\nimport { setup } from './testing'\nimport { silence } from '../lib/log'\nsilence()\nsetup().catch((e) => console.error(e))\n"]}
|
@ -0,0 +1 @@
|
|||||||
|
export {};
|
@ -0,0 +1,65 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
// Test script uses standard methods and env config to connect and log streams
|
||||||
|
const config_1 = require("./config");
|
||||||
|
const __1 = require("..");
|
||||||
|
const delay = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
|
||||||
|
// Start subscription to log message stream (used for e2e test and demo)
|
||||||
|
function start() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield __1.driver.connect();
|
||||||
|
yield __1.driver.login({ username: config_1.botUser.username, password: config_1.botUser.password });
|
||||||
|
yield __1.driver.subscribeToMessages();
|
||||||
|
yield __1.driver.respondToMessages((err, msg, msgOpts) => {
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
console.log('[respond]', JSON.stringify(msg), JSON.stringify(msgOpts));
|
||||||
|
demo(msg).catch((e) => console.error(e));
|
||||||
|
}, {
|
||||||
|
rooms: ['general'],
|
||||||
|
allPublic: false,
|
||||||
|
dm: true,
|
||||||
|
edited: true,
|
||||||
|
livechat: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Demo bot-style interactions
|
||||||
|
// A: Listen for "tell everyone <something>" and send that something to everyone
|
||||||
|
// B: Listen for "who's online" and tell that person who's online
|
||||||
|
function demo(message) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
console.log(message);
|
||||||
|
if (!message.msg)
|
||||||
|
return;
|
||||||
|
if (/tell everyone/i.test(message.msg)) {
|
||||||
|
const match = message.msg.match(/tell everyone (.*)/i);
|
||||||
|
if (!match || !match[1])
|
||||||
|
return;
|
||||||
|
const sayWhat = `@${message.u.username} says "${match[1]}"`;
|
||||||
|
const usernames = yield __1.api.users.allNames();
|
||||||
|
for (let username of usernames) {
|
||||||
|
if (username !== config_1.botUser.username) {
|
||||||
|
const toWhere = yield __1.driver.getDirectMessageRoomId(username);
|
||||||
|
yield __1.driver.sendToRoomId(sayWhat, toWhere); // DM ID hax
|
||||||
|
yield delay(200); // delay to prevent rate-limit error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (/who\'?s online/i.test(message.msg)) {
|
||||||
|
const names = yield __1.api.users.onlineNames();
|
||||||
|
const niceNames = names.join(', ').replace(/, ([^,]*)$/, ' and $1');
|
||||||
|
yield __1.driver.sendToRoomId(niceNames + ' are online', message.rid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
start().catch((e) => console.error(e));
|
||||||
|
//# sourceMappingURL=start.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/utils/start.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,8EAA8E;AAC9E,qCAAkC;AAElC,0BAAgC;AAChC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAEvF,wEAAwE;AACxE;;QACE,MAAM,UAAM,CAAC,OAAO,EAAE,CAAA;QACtB,MAAM,UAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,gBAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,gBAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC9E,MAAM,UAAM,CAAC,mBAAmB,EAAE,CAAA;QAClC,MAAM,UAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YACnD,EAAE,CAAC,CAAC,GAAG,CAAC;gBAAC,MAAM,GAAG,CAAA;YAClB,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;YACtE,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC1C,CAAC,EAAE;YACD,KAAK,EAAE,CAAC,SAAS,CAAC;YAClB,SAAS,EAAE,KAAK;YAChB,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAA;IACJ,CAAC;CAAA;AAED,8BAA8B;AAC9B,gFAAgF;AAChF,iEAAiE;AACjE,cAAqB,OAAiB;;QACpC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACpB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;YAAC,MAAM,CAAA;QACxB,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAA;YACtD,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAAC,MAAM,CAAA;YAC/B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,CAAE,CAAC,QAAQ,UAAU,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA;YAC5D,MAAM,SAAS,GAAG,MAAM,OAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;YAC5C,GAAG,CAAC,CAAC,IAAI,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC;gBAC/B,EAAE,CAAC,CAAC,QAAQ,KAAK,gBAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;oBAClC,MAAM,OAAO,GAAG,MAAM,UAAM,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAA;oBAC7D,MAAM,UAAM,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA,CAAC,YAAY;oBACxD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA,CAAC,oCAAoC;gBACvD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,MAAM,OAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;YAC3C,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;YACnE,MAAM,UAAM,CAAC,YAAY,CAAC,SAAS,GAAG,aAAa,EAAE,OAAO,CAAC,GAAI,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;CAAA;AAED,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA","sourcesContent":["// Test script uses standard methods and env config to connect and log streams\nimport { botUser } from './config'\nimport { IMessage } from '../config/messageInterfaces'\nimport { api, driver } from '..'\nconst delay = (ms: number) => new Promise((resolve, reject) => setTimeout(resolve, ms))\n\n// Start subscription to log message stream (used for e2e test and demo)\nasync function start () {\n await driver.connect()\n await driver.login({ username: botUser.username, password: botUser.password })\n await driver.subscribeToMessages()\n await driver.respondToMessages((err, msg, msgOpts) => {\n if (err) throw err\n console.log('[respond]', JSON.stringify(msg), JSON.stringify(msgOpts))\n demo(msg).catch((e) => console.error(e))\n }, {\n rooms: ['general'],\n allPublic: false,\n dm: true,\n edited: true,\n livechat: false\n })\n}\n\n// Demo bot-style interactions\n// A: Listen for \"tell everyone <something>\" and send that something to everyone\n// B: Listen for \"who's online\" and tell that person who's online\nasync function demo (message: IMessage) {\n console.log(message)\n if (!message.msg) return\n if (/tell everyone/i.test(message.msg)) {\n const match = message.msg.match(/tell everyone (.*)/i)\n if (!match || !match[1]) return\n const sayWhat = `@${message.u!.username} says \"${match[1]}\"`\n const usernames = await api.users.allNames()\n for (let username of usernames) {\n if (username !== botUser.username) {\n const toWhere = await driver.getDirectMessageRoomId(username)\n await driver.sendToRoomId(sayWhat, toWhere) // DM ID hax\n await delay(200) // delay to prevent rate-limit error\n }\n }\n } else if (/who\\'?s online/i.test(message.msg)) {\n const names = await api.users.onlineNames()\n const niceNames = names.join(', ').replace(/, ([^,]*)$/, ' and $1')\n await driver.sendToRoomId(niceNames + ' are online', message.rid!)\n }\n}\n\nstart().catch((e) => console.error(e))\n"]}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { IMessageUpdateAPI, IMessageResultAPI, INewUserAPI, IUserResultAPI, IRoomResultAPI, IChannelResultAPI, IGroupResultAPI } from './interfaces';
|
||||||
|
import { IMessage } from '../config/messageInterfaces';
|
||||||
|
/** Define common attributes for DRY tests */
|
||||||
|
export declare const testChannelName = "tests";
|
||||||
|
export declare const testPrivateName = "p-tests";
|
||||||
|
/** Get information about a user */
|
||||||
|
export declare function userInfo(username: string): Promise<IUserResultAPI>;
|
||||||
|
/** Create a user and catch the error if they exist already */
|
||||||
|
export declare function createUser(user: INewUserAPI): Promise<IUserResultAPI>;
|
||||||
|
/** Get information about a channel */
|
||||||
|
export declare function channelInfo(query: {
|
||||||
|
roomName?: string;
|
||||||
|
roomId?: string;
|
||||||
|
}): Promise<IChannelResultAPI>;
|
||||||
|
/** Get information about a private group */
|
||||||
|
export declare function privateInfo(query: {
|
||||||
|
roomName?: string;
|
||||||
|
roomId?: string;
|
||||||
|
}): Promise<IGroupResultAPI>;
|
||||||
|
/** Get the last messages sent to a channel (in last 10 minutes) */
|
||||||
|
export declare function lastMessages(roomId: string, count?: number): Promise<IMessage[]>;
|
||||||
|
/** Create a room for tests and catch the error if it exists already */
|
||||||
|
export declare function createChannel(name: string, members?: string[], readOnly?: boolean): Promise<IChannelResultAPI>;
|
||||||
|
/** Create a private group / room and catch if exists already */
|
||||||
|
export declare function createPrivate(name: string, members?: string[], readOnly?: boolean): Promise<IGroupResultAPI>;
|
||||||
|
/** Send message from mock user to channel for tests to listen and respond */
|
||||||
|
/** @todo Sometimes the post request completes before the change event emits
|
||||||
|
* the message to the streamer. That's why the interval is used for proof
|
||||||
|
* of receipt. It would be better for the endpoint to not resolve until
|
||||||
|
* server side handling is complete. Would require PR to core.
|
||||||
|
*/
|
||||||
|
export declare function sendFromUser(payload: any): Promise<IMessageResultAPI>;
|
||||||
|
/** Leave user from room, to generate `ul` message (test channel by default) */
|
||||||
|
export declare function leaveUser(room?: {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}): Promise<Boolean>;
|
||||||
|
/** Invite user to room, to generate `au` message (test channel by default) */
|
||||||
|
export declare function inviteUser(room?: {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}): Promise<Boolean>;
|
||||||
|
/** @todo : Join user into room (enter) to generate `uj` message type. */
|
||||||
|
/** Update message sent from mock user */
|
||||||
|
export declare function updateFromUser(payload: IMessageUpdateAPI): Promise<IMessageResultAPI>;
|
||||||
|
/** Create a direct message session with the mock user */
|
||||||
|
export declare function setupDirectFromUser(): Promise<IRoomResultAPI>;
|
||||||
|
/** Initialise testing instance with the required users for SDK/bot tests */
|
||||||
|
export declare function setup(): Promise<void>;
|
@ -0,0 +1,238 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const api_1 = require("../lib/api");
|
||||||
|
const config_1 = require("./config");
|
||||||
|
/** Define common attributes for DRY tests */
|
||||||
|
exports.testChannelName = 'tests';
|
||||||
|
exports.testPrivateName = 'p-tests';
|
||||||
|
/** Get information about a user */
|
||||||
|
function userInfo(username) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.get('users.info', { username }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.userInfo = userInfo;
|
||||||
|
/** Create a user and catch the error if they exist already */
|
||||||
|
function createUser(user) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.post('users.create', user, true, /already in use/i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.createUser = createUser;
|
||||||
|
/** Get information about a channel */
|
||||||
|
function channelInfo(query) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.get('channels.info', query, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.channelInfo = channelInfo;
|
||||||
|
/** Get information about a private group */
|
||||||
|
function privateInfo(query) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.get('groups.info', query, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.privateInfo = privateInfo;
|
||||||
|
/** Get the last messages sent to a channel (in last 10 minutes) */
|
||||||
|
function lastMessages(roomId, count = 1) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const now = new Date();
|
||||||
|
const latest = now.toISOString();
|
||||||
|
const oldest = new Date(now.setMinutes(now.getMinutes() - 10)).toISOString();
|
||||||
|
return (yield api_1.get('channels.history', { roomId, latest, oldest, count })).messages;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.lastMessages = lastMessages;
|
||||||
|
/** Create a room for tests and catch the error if it exists already */
|
||||||
|
function createChannel(name, members = [], readOnly = false) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.post('channels.create', { name, members, readOnly }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.createChannel = createChannel;
|
||||||
|
/** Create a private group / room and catch if exists already */
|
||||||
|
function createPrivate(name, members = [], readOnly = false) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
return api_1.post('groups.create', { name, members, readOnly }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.createPrivate = createPrivate;
|
||||||
|
/** Send message from mock user to channel for tests to listen and respond */
|
||||||
|
/** @todo Sometimes the post request completes before the change event emits
|
||||||
|
* the message to the streamer. That's why the interval is used for proof
|
||||||
|
* of receipt. It would be better for the endpoint to not resolve until
|
||||||
|
* server side handling is complete. Would require PR to core.
|
||||||
|
*/
|
||||||
|
function sendFromUser(payload) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const user = yield api_1.login({ username: config_1.mockUser.username, password: config_1.mockUser.password });
|
||||||
|
const endpoint = (payload.roomId && payload.roomId.indexOf(user.data.userId) !== -1)
|
||||||
|
? 'dm.history'
|
||||||
|
: 'channels.history';
|
||||||
|
const roomId = (payload.roomId)
|
||||||
|
? payload.roomId
|
||||||
|
: (yield channelInfo({ roomName: exports.testChannelName })).channel._id;
|
||||||
|
const messageDefaults = { roomId };
|
||||||
|
const data = Object.assign({}, messageDefaults, payload);
|
||||||
|
const oldest = new Date().toISOString();
|
||||||
|
const result = yield api_1.post('chat.postMessage', data, true);
|
||||||
|
const proof = new Promise((resolve, reject) => {
|
||||||
|
let looked = 0;
|
||||||
|
const look = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const { messages } = yield api_1.get(endpoint, { roomId, oldest });
|
||||||
|
const found = messages.some((message) => {
|
||||||
|
return result.message._id === message._id;
|
||||||
|
});
|
||||||
|
if (found || looked > 10) {
|
||||||
|
clearInterval(look);
|
||||||
|
if (found)
|
||||||
|
resolve();
|
||||||
|
else
|
||||||
|
reject('API send from user, proof of receipt timeout');
|
||||||
|
}
|
||||||
|
looked++;
|
||||||
|
}), 100);
|
||||||
|
});
|
||||||
|
yield proof;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.sendFromUser = sendFromUser;
|
||||||
|
/** Leave user from room, to generate `ul` message (test channel by default) */
|
||||||
|
function leaveUser(room = {}) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield api_1.login({ username: config_1.mockUser.username, password: config_1.mockUser.password });
|
||||||
|
if (!room.id && !room.name)
|
||||||
|
room.name = exports.testChannelName;
|
||||||
|
const roomId = (room.id)
|
||||||
|
? room.id
|
||||||
|
: (yield channelInfo({ roomName: room.name })).channel._id;
|
||||||
|
return api_1.post('channels.leave', { roomId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.leaveUser = leaveUser;
|
||||||
|
/** Invite user to room, to generate `au` message (test channel by default) */
|
||||||
|
function inviteUser(room = {}) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
let mockInfo = yield userInfo(config_1.mockUser.username);
|
||||||
|
yield api_1.login({ username: config_1.apiUser.username, password: config_1.apiUser.password });
|
||||||
|
if (!room.id && !room.name)
|
||||||
|
room.name = exports.testChannelName;
|
||||||
|
const roomId = (room.id)
|
||||||
|
? room.id
|
||||||
|
: (yield channelInfo({ roomName: room.name })).channel._id;
|
||||||
|
return api_1.post('channels.invite', { userId: mockInfo.user._id, roomId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.inviteUser = inviteUser;
|
||||||
|
/** @todo : Join user into room (enter) to generate `uj` message type. */
|
||||||
|
/** Update message sent from mock user */
|
||||||
|
function updateFromUser(payload) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield api_1.login({ username: config_1.mockUser.username, password: config_1.mockUser.password });
|
||||||
|
return api_1.post('chat.update', payload, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.updateFromUser = updateFromUser;
|
||||||
|
/** Create a direct message session with the mock user */
|
||||||
|
function setupDirectFromUser() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
yield api_1.login({ username: config_1.mockUser.username, password: config_1.mockUser.password });
|
||||||
|
return api_1.post('im.create', { username: config_1.botUser.username }, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.setupDirectFromUser = setupDirectFromUser;
|
||||||
|
/** Initialise testing instance with the required users for SDK/bot tests */
|
||||||
|
function setup() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
console.log('\nPreparing instance for tests...');
|
||||||
|
try {
|
||||||
|
// Verify API user can login
|
||||||
|
const loginInfo = yield api_1.login(config_1.apiUser);
|
||||||
|
if (loginInfo.status !== 'success') {
|
||||||
|
throw new Error(`API user (${config_1.apiUser.username}) could not login`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`API user (${config_1.apiUser.username}) logged in`);
|
||||||
|
}
|
||||||
|
// Verify or create user for bot
|
||||||
|
let botInfo = yield userInfo(config_1.botUser.username);
|
||||||
|
if (!botInfo || !botInfo.success) {
|
||||||
|
console.log(`Bot user (${config_1.botUser.username}) not found`);
|
||||||
|
botInfo = yield createUser(config_1.botUser);
|
||||||
|
if (!botInfo.success) {
|
||||||
|
throw new Error(`Bot user (${config_1.botUser.username}) could not be created`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Bot user (${config_1.botUser.username}) created`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Bot user (${config_1.botUser.username}) exists`);
|
||||||
|
}
|
||||||
|
// Verify or create mock user for talking to bot
|
||||||
|
let mockInfo = yield userInfo(config_1.mockUser.username);
|
||||||
|
if (!mockInfo || !mockInfo.success) {
|
||||||
|
console.log(`Mock user (${config_1.mockUser.username}) not found`);
|
||||||
|
mockInfo = yield createUser(config_1.mockUser);
|
||||||
|
if (!mockInfo.success) {
|
||||||
|
throw new Error(`Mock user (${config_1.mockUser.username}) could not be created`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Mock user (${config_1.mockUser.username}) created`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Mock user (${config_1.mockUser.username}) exists`);
|
||||||
|
}
|
||||||
|
// Verify or create channel for tests
|
||||||
|
let testChannelInfo = yield channelInfo({ roomName: exports.testChannelName });
|
||||||
|
if (!testChannelInfo || !testChannelInfo.success) {
|
||||||
|
console.log(`Test channel (${exports.testChannelName}) not found`);
|
||||||
|
testChannelInfo = yield createChannel(exports.testChannelName, [
|
||||||
|
config_1.apiUser.username, config_1.botUser.username, config_1.mockUser.username
|
||||||
|
]);
|
||||||
|
if (!testChannelInfo.success) {
|
||||||
|
throw new Error(`Test channel (${exports.testChannelName}) could not be created`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Test channel (${exports.testChannelName}) created`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Test channel (${exports.testChannelName}) exists`);
|
||||||
|
}
|
||||||
|
// Verify or create private room for tests
|
||||||
|
let testPrivateInfo = yield privateInfo({ roomName: exports.testPrivateName });
|
||||||
|
if (!testPrivateInfo || !testPrivateInfo.success) {
|
||||||
|
console.log(`Test private room (${exports.testPrivateName}) not found`);
|
||||||
|
testPrivateInfo = yield createPrivate(exports.testPrivateName, [
|
||||||
|
config_1.apiUser.username, config_1.botUser.username, config_1.mockUser.username
|
||||||
|
]);
|
||||||
|
if (!testPrivateInfo.success) {
|
||||||
|
throw new Error(`Test private room (${exports.testPrivateName}) could not be created`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Test private room (${exports.testPrivateName}) created`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Test private room (${exports.testPrivateName}) exists`);
|
||||||
|
}
|
||||||
|
yield api_1.logout();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.setup = setup;
|
||||||
|
//# sourceMappingURL=testing.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
export {};
|
@ -0,0 +1,50 @@
|
|||||||
|
"use strict";
|
||||||
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||||
|
return new (P || (P = Promise))(function (resolve, reject) {
|
||||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||||
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||||
|
result["default"] = mod;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
// Test script uses standard methods and env config to connect and log streams
|
||||||
|
const api = __importStar(require("../lib/api"));
|
||||||
|
const log_1 = require("../lib/log");
|
||||||
|
log_1.silence();
|
||||||
|
function users() {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
console.log(`
|
||||||
|
|
||||||
|
Demo of API user query helpers
|
||||||
|
|
||||||
|
ALL users \`api.users.all()\`:
|
||||||
|
${JSON.stringify(yield api.users.all(), null, '\t')}
|
||||||
|
|
||||||
|
ALL usernames \`api.users.allNames()\`:
|
||||||
|
${JSON.stringify(yield api.users.allNames(), null, '\t')}
|
||||||
|
|
||||||
|
ALL IDs \`api.users.allIDs()\`:
|
||||||
|
${JSON.stringify(yield api.users.allIDs(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE users \`api.users.online()\`:
|
||||||
|
${JSON.stringify(yield api.users.online(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE usernames \`api.users.onlineNames()\`:
|
||||||
|
${JSON.stringify(yield api.users.onlineNames(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE IDs \`api.users.onlineIds()\`:
|
||||||
|
${JSON.stringify(yield api.users.onlineIds(), null, '\t')}
|
||||||
|
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
users().catch((e) => console.error(e));
|
||||||
|
//# sourceMappingURL=users.js.map
|
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/utils/users.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,8EAA8E;AAC9E,gDAAiC;AACjC,oCAAoC;AACpC,aAAO,EAAE,CAAA;AAET;;QACE,OAAO,CAAC,GAAG,CAAC;;;;;EAKZ,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;;EAGjD,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;;EAGtD,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;;EAGpD,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;;EAGpD,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;;EAGzD,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC;;GAEtD,CAAC,CAAA;IACJ,CAAC;CAAA;AAED,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA","sourcesContent":["// Test script uses standard methods and env config to connect and log streams\nimport * as api from '../lib/api'\nimport { silence } from '../lib/log'\nsilence()\n\nasync function users () {\n console.log(`\n\nDemo of API user query helpers\n\nALL users \\`api.users.all()\\`:\n${JSON.stringify(await api.users.all(), null, '\\t')}\n\nALL usernames \\`api.users.allNames()\\`:\n${JSON.stringify(await api.users.allNames(), null, '\\t')}\n\nALL IDs \\`api.users.allIDs()\\`:\n${JSON.stringify(await api.users.allIDs(), null, '\\t')}\n\nONLINE users \\`api.users.online()\\`:\n${JSON.stringify(await api.users.online(), null, '\\t')}\n\nONLINE usernames \\`api.users.onlineNames()\\`:\n${JSON.stringify(await api.users.onlineNames(), null, '\\t')}\n\nONLINE IDs \\`api.users.onlineIds()\\`:\n${JSON.stringify(await api.users.onlineIds(), null, '\\t')}\n\n `)\n}\n\nusers().catch((e) => console.error(e))\n"]}
|
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"src": [{
|
||||||
|
"driver": [
|
||||||
|
"dist/index.js",
|
||||||
|
"dist/lib/driver.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guide": "README.md"
|
||||||
|
}],
|
||||||
|
"dest": "docs/",
|
||||||
|
"clean": true,
|
||||||
|
"jsdoc": {
|
||||||
|
"encoding": "utf8",
|
||||||
|
"recurse": false,
|
||||||
|
"pedantic": false,
|
||||||
|
"access": null,
|
||||||
|
"package": null,
|
||||||
|
"module": true,
|
||||||
|
"undocumented": false,
|
||||||
|
"undescribed": false,
|
||||||
|
"ignored": false,
|
||||||
|
"hierarchy": true,
|
||||||
|
"sort": "alphabetic",
|
||||||
|
"relativePath": null,
|
||||||
|
"filter": null,
|
||||||
|
"allowUnknownTags": true,
|
||||||
|
"plugins": ["plugins/markdown"]
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"title": "Rocket.Chat.js.SDK",
|
||||||
|
"routing": "path",
|
||||||
|
"entrance": "content:guide",
|
||||||
|
"favicon": "favicon.ico"
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"options": {
|
||||||
|
"title": {
|
||||||
|
"label": "Rocket.Chat.js.SDK",
|
||||||
|
"href": "/guide"
|
||||||
|
},
|
||||||
|
"symbols": {
|
||||||
|
"autoLink": true,
|
||||||
|
"params": "table",
|
||||||
|
"enums": "list",
|
||||||
|
"props": "list",
|
||||||
|
"meta": false
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"menu": [
|
||||||
|
{
|
||||||
|
"iconClass": "fas fa-puzzle-piece",
|
||||||
|
"label": "Driver Methods",
|
||||||
|
"href": "/api/driver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iconClass": "fas fa-book",
|
||||||
|
"label": "SDK Guide",
|
||||||
|
"href": "/guide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"iconClass": "fab fa-rocketchat",
|
||||||
|
"label": "",
|
||||||
|
"href": "https://github.com/RocketChat/Rocket.Chat.js.SDK",
|
||||||
|
"target": "_blank"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"enabled": true,
|
||||||
|
"outline": "flat"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"dark": "https://upload.wikimedia.org/wikipedia/commons/5/55/RocketChat_Logo_1024x1024.png",
|
||||||
|
"light": "https://upload.wikimedia.org/wikipedia/commons/5/55/RocketChat_Logo_1024x1024.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"name": "@rocket.chat/sdk",
|
||||||
|
"version": "0.2.9-1",
|
||||||
|
"description": "Node.js SDK for Rocket.Chat. Application interface for server methods and message streams.",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"repository": "https://github.com/RocketChat/Rocket.Chat.js.SDK.git",
|
||||||
|
"author": "Tim Kinnane <tim.kinnane@rocket.chat>",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"keywords": [
|
||||||
|
"adapter",
|
||||||
|
"rocketchat",
|
||||||
|
"rocket",
|
||||||
|
"chat",
|
||||||
|
"messaging",
|
||||||
|
"CUI",
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "> 8.0.0",
|
||||||
|
"npm": "> 5.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"pretest": "tslint -p . && ts-node src/utils/setup.ts",
|
||||||
|
"test": "nyc mocha './src/lib/**/*.spec.ts'",
|
||||||
|
"test:hook": "mocha './**/*.spec.ts'",
|
||||||
|
"test:debug": "mocha --inspect --debug-brk 'src/**/*.spec.ts'",
|
||||||
|
"test:package": "preview && mocha 'src/index.spec.ts'",
|
||||||
|
"start": "ts-node ./src/utils/start",
|
||||||
|
"docs": "rimraf ./docs && typedoc --options ./typedoc.json ./src/lib",
|
||||||
|
"js-docs": "./node_modules/.bin/docma -c docma.json",
|
||||||
|
"prebuild": "npm run test",
|
||||||
|
"build": "rimraf ./dist/* && tsc && npm run test:package"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-push": "npm run test:hook"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.1.2",
|
||||||
|
"@types/mocha": "^2.2.48",
|
||||||
|
"@types/sinon": "^4.3.0",
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"commitizen": "^2.9.6",
|
||||||
|
"cz-conventional-changelog": "^2.1.0",
|
||||||
|
"docma": "^3.2.2",
|
||||||
|
"dotenv": "^5.0.1",
|
||||||
|
"husky": "^0.14.3",
|
||||||
|
"mocha": "^5.0.1",
|
||||||
|
"nyc": "^11.4.1",
|
||||||
|
"package-preview": "^1.0.5",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"sinon": "^4.4.2",
|
||||||
|
"source-map-support": "^0.5.3",
|
||||||
|
"ts-node": "^5.0.1",
|
||||||
|
"tslint": "^5.9.1",
|
||||||
|
"tslint-config-standard": "^7.0.0",
|
||||||
|
"typedoc": "0.8.0",
|
||||||
|
"typedoc-plugin-external-module-name": "^1.1.1",
|
||||||
|
"typescript": "^2.7.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lru-cache": "^4.1.0",
|
||||||
|
"@types/node": "^9.4.6",
|
||||||
|
"asteroid": "rocketchat/asteroid",
|
||||||
|
"lru-cache": "^4.1.1",
|
||||||
|
"node-rest-client": "^3.1.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "./node_modules/cz-conventional-changelog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
// import { Map } from 'immutable'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid DDP - add known properties to avoid TS lint errors
|
||||||
|
*/
|
||||||
|
export interface IAsteroidDDP extends EventEmitter {
|
||||||
|
readyState: 1 | 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid type
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface IAsteroid extends EventEmitter {
|
||||||
|
connect: () => Promise<void>,
|
||||||
|
disconnect: () => Promise<void>,
|
||||||
|
createUser: (usernameOrEmail: string, password: string, profile: IUserOptions) => Promise<any>
|
||||||
|
loginWithLDAP: (...params: any[]) => Promise<any>
|
||||||
|
loginWithFacebook: (...params: any[]) => Promise<any>
|
||||||
|
loginWithGoogle: (...params: any[]) => Promise<any>
|
||||||
|
loginWithTwitter: (...params: any[]) => Promise<any>
|
||||||
|
loginWithGithub: (...params: any[]) => Promise<any>
|
||||||
|
loginWithPassword: (usernameOrEmail: string, password: string) => Promise<any>
|
||||||
|
logout: () => Promise<null>
|
||||||
|
subscribe: (name: string, ...params: any[]) => ISubscription
|
||||||
|
subscriptions: ISubscription[],
|
||||||
|
call: (method: string, ...params: any[]) => IMethodResult
|
||||||
|
apply: (method: string, params: any[]) => IMethodResult
|
||||||
|
getCollection: (name: string) => ICollection
|
||||||
|
resumeLoginPromise: Promise<string>
|
||||||
|
ddp: IAsteroidDDP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid user options type
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface IUserOptions {
|
||||||
|
username?: string,
|
||||||
|
email?: string,
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid subscription type.
|
||||||
|
* ID is populated when ready promise resolves.
|
||||||
|
* @todo Update with typing from definitely typed (when available)
|
||||||
|
*/
|
||||||
|
export interface ISubscription {
|
||||||
|
stop: () => void,
|
||||||
|
ready: Promise<IReady>,
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asteroid v1 only
|
||||||
|
export interface IReady { state: string, value: string }
|
||||||
|
|
||||||
|
/* // v2
|
||||||
|
export interface ISubscription extends EventEmitter {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the method is successful, the `result` promise will be resolved with the
|
||||||
|
* return value passed by the server. The `updated` promise will be resolved
|
||||||
|
* with nothing once the server emits the updated message, that tells the client
|
||||||
|
* that any side-effect that the method execution caused on the database has
|
||||||
|
* been reflected on the client (for example, if the method caused the insertion
|
||||||
|
* of an item into a collection, the client has been notified of said
|
||||||
|
* insertion).
|
||||||
|
*
|
||||||
|
* If the method fails, the `result` promise will be rejected with the error
|
||||||
|
* returned by the server. The `updated` promise will be rejected as well
|
||||||
|
* (with nothing).
|
||||||
|
*/
|
||||||
|
export interface IMethodResult {
|
||||||
|
result: Promise<any>,
|
||||||
|
updated: Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface ICollection {
|
||||||
|
name: string,
|
||||||
|
insert: (item: any) => ICollectionResult,
|
||||||
|
update: (id: string, item: any) => ICollectionResult,
|
||||||
|
remove: (id: string) => ICollectionResult,
|
||||||
|
reactiveQuery: (selector: object | Function) => IReactiveQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `local` promise is immediately resolved with the `_id` of the updated
|
||||||
|
* item. That is, unless an error occurred. In that case, an exception will be
|
||||||
|
* raised.
|
||||||
|
* The `remote` promise is resolved with the `_id` of the updated item if the
|
||||||
|
* remote update is successful. Otherwise it's rejected with the reason of the
|
||||||
|
* failure.
|
||||||
|
*/
|
||||||
|
export interface ICollectionResult {
|
||||||
|
local: Promise<any>,
|
||||||
|
remote: Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reactive subset of a collection. Possible events are:
|
||||||
|
* `change`: emitted whenever the result of the query changes. The id of the
|
||||||
|
* item that changed is passed to the handler.
|
||||||
|
*/
|
||||||
|
export interface IReactiveQuery {
|
||||||
|
on: (event: string, handler: Function) => void,
|
||||||
|
result: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Credentials for Asteroid login method */
|
||||||
|
export interface ICredentials {
|
||||||
|
password: string,
|
||||||
|
username?: string,
|
||||||
|
email?: string,
|
||||||
|
ldap?: boolean,
|
||||||
|
ldapOptions?: object
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Connection options type
|
||||||
|
* @param host Rocket.Chat instance Host URL:PORT (without protocol)
|
||||||
|
* @param timeout How long to wait (ms) before abandoning connection
|
||||||
|
*/
|
||||||
|
export interface IConnectOptions {
|
||||||
|
host?: string,
|
||||||
|
useSsl?: boolean,
|
||||||
|
timeout?: number,
|
||||||
|
integration?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message respond options
|
||||||
|
* @param rooms Respond to only selected room/s (names or IDs)
|
||||||
|
* @param allPublic Respond on all public channels (ignores rooms if true)
|
||||||
|
* @param dm Respond to messages in DM / private chats
|
||||||
|
* @param livechat Respond to messages in livechat
|
||||||
|
* @param edited Respond to edited messages
|
||||||
|
*/
|
||||||
|
export interface IRespondOptions {
|
||||||
|
rooms?: string[],
|
||||||
|
allPublic?: boolean,
|
||||||
|
dm?: boolean,
|
||||||
|
livechat?: boolean,
|
||||||
|
edited?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loggers need to provide the same set of methods
|
||||||
|
*/
|
||||||
|
export interface ILogger {
|
||||||
|
debug: (...args: any[]) => void
|
||||||
|
info: (...args: any[]) => void
|
||||||
|
warning: (...args: any[]) => void
|
||||||
|
warn: (...args: any[]) => void
|
||||||
|
error: (...args: any[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error-first callback param type
|
||||||
|
*/
|
||||||
|
export interface ICallback {
|
||||||
|
(error: Error | null, ...args: any[]): void
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/** @todo contribute these to @types/rocketchat and require */
|
||||||
|
|
||||||
|
export interface IMessage {
|
||||||
|
rid: string | null // room ID
|
||||||
|
_id?: string // generated by Random.id()
|
||||||
|
t?: string // type e.g. rm
|
||||||
|
msg?: string // text content
|
||||||
|
alias?: string // ??
|
||||||
|
emoji?: string // emoji to use as avatar
|
||||||
|
avatar?: string // url
|
||||||
|
groupable?: boolean // ?
|
||||||
|
bot?: any // integration details
|
||||||
|
urls?: string[] // ?
|
||||||
|
mentions?: string[] // ?
|
||||||
|
attachments?: IMessageAttachment[]
|
||||||
|
reactions?: IMessageReaction
|
||||||
|
location ?: IMessageLocation
|
||||||
|
u?: IUser // User that sent the message
|
||||||
|
editedBy?: IUser // User that edited the message
|
||||||
|
editedAt?: Date // When the message was edited
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
_id: string
|
||||||
|
username: string
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageAttachment {
|
||||||
|
fields?: IAttachmentField[]
|
||||||
|
actions?: IMessageAction[]
|
||||||
|
color?: string
|
||||||
|
text?: string
|
||||||
|
ts?: string
|
||||||
|
thumb_url?: string
|
||||||
|
message_link?: string
|
||||||
|
collapsed?: boolean
|
||||||
|
author_name?: string
|
||||||
|
author_link?: string
|
||||||
|
author_icon?: string
|
||||||
|
title?: string
|
||||||
|
title_link?: string
|
||||||
|
title_link_download?: string
|
||||||
|
image_url?: string
|
||||||
|
audio_url?: string
|
||||||
|
video_url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAttachmentField {
|
||||||
|
short?: boolean
|
||||||
|
title?: string
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageAction {
|
||||||
|
type?: string
|
||||||
|
text?: string
|
||||||
|
url?: string
|
||||||
|
image_url?: string
|
||||||
|
is_webview?: boolean
|
||||||
|
webview_height_ratio?: 'compact' | 'tall' | 'full'
|
||||||
|
msg?: string
|
||||||
|
msg_in_chat_window?: boolean
|
||||||
|
button_alignment?: 'vertical' | 'horizontal'
|
||||||
|
temporary_buttons?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageLocation {
|
||||||
|
type: string // e.g. Point
|
||||||
|
coordinates: string[] // longitude latitude
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessageReaction {
|
||||||
|
[emoji: string]: { usernames: string[] } // emoji: [usernames that reacted]
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import * as rocketchat from '@rocket.chat/sdk'
|
||||||
|
|
||||||
|
describe('index:', () => {
|
||||||
|
it('exports all lib members', () => {
|
||||||
|
expect(Object.keys(rocketchat)).to.eql([
|
||||||
|
'driver',
|
||||||
|
'methodCache',
|
||||||
|
'api',
|
||||||
|
'settings'
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,10 @@
|
|||||||
|
import * as driver from './lib/driver'
|
||||||
|
import * as methodCache from './lib/methodCache'
|
||||||
|
import * as api from './lib/api'
|
||||||
|
import * as settings from './lib/settings'
|
||||||
|
export {
|
||||||
|
driver,
|
||||||
|
methodCache,
|
||||||
|
api,
|
||||||
|
settings
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
import 'mocha'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { silence, logger } from './log'
|
||||||
|
const initEnv = process.env // store configs to restore after tests
|
||||||
|
import { botUser, mockUser } from '../utils/config'
|
||||||
|
import * as utils from '../utils/testing'
|
||||||
|
import * as driver from './driver'
|
||||||
|
import * as api from './api'
|
||||||
|
|
||||||
|
silence() // suppress log during tests (disable this while developing tests)
|
||||||
|
|
||||||
|
describe('api', () => {
|
||||||
|
before(async () => { // wait for connection
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
after(() => process.env = initEnv)
|
||||||
|
afterEach(() => api.logout())
|
||||||
|
describe('.success', () => {
|
||||||
|
it('returns true when result status is success', () => {
|
||||||
|
expect(api.success({ status: 'success' })).to.equal(true)
|
||||||
|
})
|
||||||
|
it('returns true when success is true', () => {
|
||||||
|
expect(api.success({ success: true })).to.equal(true)
|
||||||
|
})
|
||||||
|
it('returns false when result status is not success', () => {
|
||||||
|
expect(api.success({ status: 'error' })).to.equal(false)
|
||||||
|
})
|
||||||
|
it('returns false when success is not true', () => {
|
||||||
|
expect(api.success({ success: false })).to.equal(false)
|
||||||
|
})
|
||||||
|
it('returns true if neither status or success given', () => {
|
||||||
|
expect(api.success({})).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.getQueryString', () => {
|
||||||
|
it('converts object to query params string', () => {
|
||||||
|
expect(api.getQueryString({
|
||||||
|
foo: 'bar',
|
||||||
|
baz: 'qux'
|
||||||
|
})).to.equal('?foo=bar&baz=qux')
|
||||||
|
})
|
||||||
|
it('returns empty if nothing in object', () => {
|
||||||
|
expect(api.getQueryString({})).to.equal('')
|
||||||
|
})
|
||||||
|
it('returns nested objects without serialising', () => {
|
||||||
|
expect(api.getQueryString({
|
||||||
|
fields: { 'username': 1 }
|
||||||
|
})).to.equal('?fields={"username":1}')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.get', () => {
|
||||||
|
it('returns data from basic call without auth', async () => {
|
||||||
|
const server = await driver.callMethod('getServerInfo')
|
||||||
|
const result = await api.get('info', {}, false)
|
||||||
|
expect(result).to.eql({
|
||||||
|
info: { version: server.version },
|
||||||
|
success: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('returns data from complex calls with auth and parameters', async () => {
|
||||||
|
await api.login()
|
||||||
|
const result = await api.get('users.list', {
|
||||||
|
fields: { 'username': 1 },
|
||||||
|
query: { type: { $in: ['user', 'bot'] } }
|
||||||
|
}, true)
|
||||||
|
const users = result.users.map((user) => user.username)
|
||||||
|
expect(users).to.include(botUser.username, mockUser.username)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.login', () => {
|
||||||
|
it('logs in with the default user without arguments', async () => {
|
||||||
|
const login = await api.login()
|
||||||
|
expect(login.data.userId).to.equal(driver.userId)
|
||||||
|
})
|
||||||
|
it('logs in with another user if given credentials', async () => {
|
||||||
|
await api.login({
|
||||||
|
username: mockUser.username,
|
||||||
|
password: mockUser.password
|
||||||
|
})
|
||||||
|
const mockInfo = await api.get('users.info', { username: mockUser.username })
|
||||||
|
expect(api.currentLogin.userId).to.equal(mockInfo.user._id)
|
||||||
|
})
|
||||||
|
it('stores logged in user result', async () => {
|
||||||
|
await api.login()
|
||||||
|
expect(api.currentLogin.userId).to.equal(driver.userId)
|
||||||
|
})
|
||||||
|
it('stores user and token in auth headers', async () => {
|
||||||
|
await api.login()
|
||||||
|
expect(api.authHeaders['X-User-Id']).to.equal(driver.userId)
|
||||||
|
expect(api.authHeaders['X-Auth-Token']).to.have.lengthOf(43)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.logout', () => {
|
||||||
|
it('resets auth headers and clears user ID', async () => {
|
||||||
|
await api.login().catch(e => console.log('login error', e))
|
||||||
|
await api.logout().catch(e => console.log('logout error', e))
|
||||||
|
expect(api.authHeaders).to.eql({})
|
||||||
|
expect(api.currentLogin).to.eql(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.getHeaders', () => {
|
||||||
|
beforeEach(() => api.clearHeaders())
|
||||||
|
it('returns headers for API call', () => {
|
||||||
|
expect(api.getHeaders()).to.eql({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('should fail if called before login when auth required', () => {
|
||||||
|
expect(() => api.getHeaders(true)).to.throw()
|
||||||
|
})
|
||||||
|
it('should return auth headers if required when logged in', async () => {
|
||||||
|
api.authHeaders['X-User-Id'] = 'test'
|
||||||
|
api.authHeaders['X-Auth-Token'] = 'test'
|
||||||
|
expect(api.getHeaders(true)).to.eql({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-User-Id': 'test',
|
||||||
|
'X-Auth-Token': 'test'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,219 @@
|
|||||||
|
import { Client } from 'node-rest-client'
|
||||||
|
import * as settings from './settings'
|
||||||
|
import { logger } from './log'
|
||||||
|
import { IUserAPI } from '../utils/interfaces'
|
||||||
|
|
||||||
|
/** Result object from an API login */
|
||||||
|
export interface ILoginResultAPI {
|
||||||
|
status: string // e.g. 'success'
|
||||||
|
data: { authToken: string, userId: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Structure for passing and keeping login credentials */
|
||||||
|
export interface ILoginCredentials {
|
||||||
|
username: string,
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
export let currentLogin: {
|
||||||
|
username: string,
|
||||||
|
userId: string,
|
||||||
|
authToken: string,
|
||||||
|
result: ILoginResultAPI
|
||||||
|
} | null = null
|
||||||
|
|
||||||
|
/** Check for existing login */
|
||||||
|
export function loggedIn (): boolean {
|
||||||
|
return (currentLogin !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialise client and configs */
|
||||||
|
export const client = new Client()
|
||||||
|
export const host = settings.host
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend protocol (or put back if removed from env settings for driver)
|
||||||
|
* Hard code endpoint prefix, because all syntax depends on this version
|
||||||
|
*/
|
||||||
|
export const url = ((host.indexOf('http') === -1)
|
||||||
|
? host.replace(/^(\/\/)?/, 'http://')
|
||||||
|
: host) + '/api/v1/'
|
||||||
|
|
||||||
|
/** Convert payload data to query string for GET requests */
|
||||||
|
export function getQueryString (data: any) {
|
||||||
|
if (!data || typeof data !== 'object' || !Object.keys(data).length) return ''
|
||||||
|
return '?' + Object.keys(data).map((k) => {
|
||||||
|
const value = (typeof data[k] === 'object')
|
||||||
|
? JSON.stringify(data[k])
|
||||||
|
: encodeURIComponent(data[k])
|
||||||
|
return `${encodeURIComponent(k)}=${value}`
|
||||||
|
}).join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setup default headers with empty auth for now */
|
||||||
|
export const basicHeaders = { 'Content-Type': 'application/json' }
|
||||||
|
export const authHeaders = { 'X-Auth-Token': '', 'X-User-Id': '' }
|
||||||
|
|
||||||
|
/** Populate auth headers (from response data on login) */
|
||||||
|
export function setAuth (authData: {authToken: string, userId: string}) {
|
||||||
|
authHeaders['X-Auth-Token'] = authData.authToken
|
||||||
|
authHeaders['X-User-Id'] = authData.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Join basic headers with auth headers if required */
|
||||||
|
export function getHeaders (authRequired = false) {
|
||||||
|
if (!authRequired) return basicHeaders
|
||||||
|
if (
|
||||||
|
(!('X-Auth-Token' in authHeaders) || !('X-User-Id' in authHeaders)) ||
|
||||||
|
authHeaders['X-Auth-Token'] === '' ||
|
||||||
|
authHeaders['X-User-Id'] === ''
|
||||||
|
) {
|
||||||
|
throw new Error('Auth required endpoint cannot be called before login')
|
||||||
|
}
|
||||||
|
return Object.assign({}, basicHeaders, authHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear headers so they can't be used without logging in again */
|
||||||
|
export function clearHeaders () {
|
||||||
|
delete authHeaders['X-Auth-Token']
|
||||||
|
delete authHeaders['X-User-Id']
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check result data for success, allowing override to ignore some errors */
|
||||||
|
export function success (result: any, ignore?: RegExp) {
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
typeof result.error === 'undefined' &&
|
||||||
|
typeof result.status === 'undefined' &&
|
||||||
|
typeof result.success === 'undefined'
|
||||||
|
) ||
|
||||||
|
(result.status && result.status === 'success') ||
|
||||||
|
(result.success && result.success === true) ||
|
||||||
|
(ignore && result.error && !ignore.test(result.error))
|
||||||
|
) ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a POST request to an API endpoint.
|
||||||
|
* If it needs a token, login first (with defaults) to set auth headers.
|
||||||
|
* @todo Look at why some errors return HTML (caught as buffer) instead of JSON
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `chat.update`
|
||||||
|
* @param data Payload for POST request to endpoint
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
export async function post (
|
||||||
|
endpoint: string,
|
||||||
|
data: any,
|
||||||
|
auth: boolean = true,
|
||||||
|
ignore?: RegExp
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
logger.debug(`[API] POST: ${endpoint}`, JSON.stringify(data))
|
||||||
|
if (auth && !loggedIn()) await login()
|
||||||
|
let headers = getHeaders(auth)
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
client.post(url + endpoint, { headers, data }, (result: any) => {
|
||||||
|
if (Buffer.isBuffer(result)) reject('Result was buffer (HTML, not JSON)')
|
||||||
|
else if (!success(result, ignore)) reject(result)
|
||||||
|
else resolve(result)
|
||||||
|
}).on('error', (err: Error) => reject(err))
|
||||||
|
})
|
||||||
|
logger.debug('[API] POST result:', result)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
logger.error(`[API] POST error (${endpoint}):`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a GET request to an API endpoint
|
||||||
|
* @param endpoint The API endpoint (including version) e.g. `users.info`
|
||||||
|
* @param data Object to serialise for GET request query string
|
||||||
|
* @param auth Require auth headers for endpoint, default true
|
||||||
|
* @param ignore Allows certain matching error messages to not count as errors
|
||||||
|
*/
|
||||||
|
export async function get (
|
||||||
|
endpoint: string,
|
||||||
|
data?: any,
|
||||||
|
auth: boolean = true,
|
||||||
|
ignore?: RegExp
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
logger.debug(`[API] GET: ${endpoint}`, data)
|
||||||
|
if (auth && !loggedIn()) await login()
|
||||||
|
let headers = getHeaders(auth)
|
||||||
|
const query = getQueryString(data)
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
client.get(url + endpoint + query, { headers }, (result: any) => {
|
||||||
|
if (Buffer.isBuffer(result)) reject('Result was buffer (HTML, not JSON)')
|
||||||
|
else if (!success(result, ignore)) reject(result)
|
||||||
|
else resolve(result)
|
||||||
|
}).on('error', (err: Error) => reject(err))
|
||||||
|
})
|
||||||
|
logger.debug('[API] GET result:', result)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[API] GET error (${endpoint}):`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login a user for further API calls
|
||||||
|
* Result should come back with a token, to authorise following requests.
|
||||||
|
* Use env default credentials, unless overridden by login arguments.
|
||||||
|
*/
|
||||||
|
export async function login (user: ILoginCredentials = {
|
||||||
|
username: settings.username,
|
||||||
|
password: settings.password
|
||||||
|
}): Promise<ILoginResultAPI> {
|
||||||
|
logger.info(`[API] Logging in ${user.username}`)
|
||||||
|
if (currentLogin !== null) {
|
||||||
|
logger.debug(`[API] Already logged in`)
|
||||||
|
if (currentLogin.username === user.username) {
|
||||||
|
return currentLogin.result
|
||||||
|
} else {
|
||||||
|
await logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = await post('login', user, false)
|
||||||
|
if (result && result.data && result.data.authToken) {
|
||||||
|
currentLogin = {
|
||||||
|
result: result, // keep to return if login requested again for same user
|
||||||
|
username: user.username, // keep to compare with following login attempt
|
||||||
|
authToken: result.data.authToken,
|
||||||
|
userId: result.data.userId
|
||||||
|
}
|
||||||
|
setAuth(currentLogin)
|
||||||
|
logger.info(`[API] Logged in ID ${ currentLogin.userId }`)
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
throw new Error(`[API] Login failed for ${user.username}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logout a user at end of API calls */
|
||||||
|
export function logout () {
|
||||||
|
if (currentLogin === null) {
|
||||||
|
logger.debug(`[API] Already logged out`)
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
logger.info(`[API] Logging out ${ currentLogin.username }`)
|
||||||
|
return get('logout', null, true).then(() => {
|
||||||
|
clearHeaders()
|
||||||
|
currentLogin = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Defaults for user queries */
|
||||||
|
export const userFields = { name: 1, username: 1, status: 1, type: 1 }
|
||||||
|
|
||||||
|
/** Query helpers for user collection requests */
|
||||||
|
export const users: any = {
|
||||||
|
all: (fields: any = userFields) => get('users.list', { fields }).then((r) => r.users),
|
||||||
|
allNames: () => get('users.list', { fields: { 'username': 1 } }).then((r) => r.users.map((u: IUserAPI) => u.username)),
|
||||||
|
allIDs: () => get('users.list', { fields: { '_id': 1 } }).then((r) => r.users.map((u: IUserAPI) => u._id)),
|
||||||
|
online: (fields: any = userFields) => get('users.list', { fields, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users),
|
||||||
|
onlineNames: () => get('users.list', { fields: { 'username': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users.map((u: IUserAPI) => u.username)),
|
||||||
|
onlineIds: () => get('users.list', { fields: { '_id': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r) => r.users.map((u: IUserAPI) => u._id))
|
||||||
|
}
|
@ -0,0 +1,458 @@
|
|||||||
|
import 'mocha'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { silence } from './log'
|
||||||
|
import { botUser, mockUser, apiUser } from '../utils/config'
|
||||||
|
import * as api from './api'
|
||||||
|
import * as utils from '../utils/testing'
|
||||||
|
import * as driver from './driver'
|
||||||
|
import * as methodCache from './methodCache'
|
||||||
|
|
||||||
|
const delay = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms))
|
||||||
|
let clock
|
||||||
|
let tId
|
||||||
|
let pId
|
||||||
|
const tName = utils.testChannelName
|
||||||
|
const pName = utils.testPrivateName
|
||||||
|
|
||||||
|
silence() // suppress log during tests (disable this while developing tests)
|
||||||
|
|
||||||
|
describe('driver', () => {
|
||||||
|
before(async () => {
|
||||||
|
const testChannel = await utils.channelInfo({ roomName: tName })
|
||||||
|
tId = testChannel.channel._id
|
||||||
|
const testPrivate = await utils.privateInfo({ roomName: pName })
|
||||||
|
pId = testPrivate.group._id
|
||||||
|
})
|
||||||
|
after(async () => {
|
||||||
|
await api.logout()
|
||||||
|
await driver.logout()
|
||||||
|
await driver.disconnect()
|
||||||
|
})
|
||||||
|
describe('.connect', () => {
|
||||||
|
context('with localhost connection', () => {
|
||||||
|
it('without args, returns a promise', () => {
|
||||||
|
const promise = driver.connect()
|
||||||
|
expect(promise.then).to.be.a('function')
|
||||||
|
promise.catch((err) => console.error(err))
|
||||||
|
return promise
|
||||||
|
})
|
||||||
|
it('accepts an error-first callback, providing asteroid', (done) => {
|
||||||
|
driver.connect({}, (err, asteroid) => {
|
||||||
|
expect(err).to.equal(null)
|
||||||
|
expect(asteroid).to.be.an('object')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('without url takes localhost as default', (done) => {
|
||||||
|
driver.connect({}, (err, asteroid) => {
|
||||||
|
expect(err).to.eql(null)
|
||||||
|
// const connectionHost = asteroid.endpoint
|
||||||
|
const connectionHost = asteroid._host
|
||||||
|
expect(connectionHost).to.contain('localhost:3000')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('promise resolves with asteroid in successful state', () => {
|
||||||
|
return driver.connect({}).then((asteroid) => {
|
||||||
|
const isActive = (asteroid.ddp.readyState === 1)
|
||||||
|
// const isActive = asteroid.ddp.status === 'connected'
|
||||||
|
expect(isActive).to.equal(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('provides the asteroid instance to method cache', () => {
|
||||||
|
return driver.connect().then((asteroid) => {
|
||||||
|
expect(methodCache.instance).to.eql(asteroid)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
context('with timeout, on expiry', () => {
|
||||||
|
before(() => clock = sinon.useFakeTimers(0))
|
||||||
|
after(() => clock.restore())
|
||||||
|
it('with url, attempts connection at URL', (done) => {
|
||||||
|
driver.connect({ host: 'localhost:9999', timeout: 100 }, (err, asteroid) => {
|
||||||
|
expect(err).to.be.an('error')
|
||||||
|
const connectionHost = asteroid.endpoint || asteroid._host
|
||||||
|
expect(connectionHost).to.contain('localhost:9999')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
clock.tick(200)
|
||||||
|
})
|
||||||
|
it('returns error', (done) => {
|
||||||
|
let opts = { host: 'localhost:9999', timeout: 100 }
|
||||||
|
driver.connect(opts, (err, asteroid) => {
|
||||||
|
const isActive = (asteroid.ddp.readyState === 1)
|
||||||
|
expect(err).to.be.an('error')
|
||||||
|
expect(isActive).to.eql(false)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
clock.tick(200)
|
||||||
|
})
|
||||||
|
it('without callback, triggers promise catch', () => {
|
||||||
|
const promise = driver.connect({ host: 'localhost:9999', timeout: 100 })
|
||||||
|
.catch((err) => expect(err).to.be.an('error'))
|
||||||
|
clock.tick(200)
|
||||||
|
return promise
|
||||||
|
})
|
||||||
|
it('with callback, provides error to callback', (done) => {
|
||||||
|
driver.connect({ host: 'localhost:9999', timeout: 100 }, (err) => {
|
||||||
|
expect(err).to.be.an('error')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
clock.tick(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// describe('disconnect', () => {
|
||||||
|
// Disabled for now, as only Asteroid v2 has a disconnect method
|
||||||
|
// it('disconnects from asteroid', async () => {
|
||||||
|
// await driver.connect()
|
||||||
|
// const asteroid = await driver.connect()
|
||||||
|
// await driver.disconnect()
|
||||||
|
// const isActive = asteroid.ddp.readyState === 1
|
||||||
|
// // const isActive = asteroid.ddp.status === 'connected'
|
||||||
|
// expect(isActive).to.equal(false)
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
describe('.login', () => {
|
||||||
|
it('sets the bot user status to online', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await utils
|
||||||
|
const result = await utils.userInfo(botUser.username)
|
||||||
|
expect(result.user.status).to.equal('online')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.subscribeToMessages', () => {
|
||||||
|
it('resolves with subscription object', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
const subscription = await driver.subscribeToMessages()
|
||||||
|
expect(subscription).to.have.property('ready')
|
||||||
|
// expect(subscription.ready).to.have.property('state', 'fulfilled') ????
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.reactToMessages', () => {
|
||||||
|
afterEach(() => delay(500)) // avoid rate limit
|
||||||
|
it('calls callback on every subscription update', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.reactToMessages(callback)
|
||||||
|
await utils.sendFromUser({ text: 'SDK test `reactToMessages` 1' })
|
||||||
|
await delay(500)
|
||||||
|
await utils.sendFromUser({ text: 'SDK test `reactToMessages` 2' })
|
||||||
|
expect(callback.callCount).to.equal(2)
|
||||||
|
})
|
||||||
|
it('calls callback with sent message object', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.reactToMessages(callback)
|
||||||
|
await utils.sendFromUser({ text: 'SDK test `reactToMessages` 3' })
|
||||||
|
const messageArgs = callback.getCall(0).args[1]
|
||||||
|
expect(messageArgs.msg).to.equal('SDK test `reactToMessages` 3')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.sendMessage', () => {
|
||||||
|
before(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('sends a custom message', async () => {
|
||||||
|
const message = driver.prepareMessage({
|
||||||
|
rid: tId,
|
||||||
|
msg: ':point_down:',
|
||||||
|
emoji: ':point_right:',
|
||||||
|
reactions: { ':thumbsup:': { usernames: [botUser.username] } },
|
||||||
|
groupable: false
|
||||||
|
})
|
||||||
|
await driver.sendMessage(message)
|
||||||
|
const last = (await utils.lastMessages(tId))[0]
|
||||||
|
expect(last).to.have.deep.property('reactions', message.reactions)
|
||||||
|
expect(last).to.have.property('emoji', ':point_right:')
|
||||||
|
expect(last).to.have.property('msg', ':point_down:')
|
||||||
|
})
|
||||||
|
it('sends a message with actions', async () => {
|
||||||
|
const attachments = [{
|
||||||
|
actions: [
|
||||||
|
{ type: 'button', text: 'Action 1', msg: 'Testing Action 1', msg_in_chat_window: true },
|
||||||
|
{ type: 'button', text: 'Action 2', msg: 'Testing Action 2', msg_in_chat_window: true }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
await driver.sendMessage({
|
||||||
|
rid: tId,
|
||||||
|
msg: 'SDK test `prepareMessage` actions',
|
||||||
|
attachments
|
||||||
|
})
|
||||||
|
const last = (await utils.lastMessages(tId))[0]
|
||||||
|
expect(last.attachments).to.eql(attachments)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.editMessage', () => {
|
||||||
|
before(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('edits the last sent message', async () => {
|
||||||
|
const original = driver.prepareMessage({
|
||||||
|
msg: ':point_down:',
|
||||||
|
emoji: ':point_right:',
|
||||||
|
groupable: false,
|
||||||
|
rid: tId
|
||||||
|
})
|
||||||
|
await driver.sendMessage(original)
|
||||||
|
const sent = (await utils.lastMessages(tId))[0]
|
||||||
|
const update = Object.assign({}, original, {
|
||||||
|
_id: sent._id,
|
||||||
|
msg: ':point_up:'
|
||||||
|
})
|
||||||
|
await driver.editMessage(update)
|
||||||
|
const last = (await utils.lastMessages(tId))[0]
|
||||||
|
expect(last).to.have.property('msg', ':point_up:')
|
||||||
|
expect(last).to.have.deep.property('editedBy', {
|
||||||
|
_id: driver.userId, username: botUser.username
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.setReaction', () => {
|
||||||
|
before(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('adds emoji reaction to message', async () => {
|
||||||
|
let sent = await driver.sendToRoomId('test reactions', tId)
|
||||||
|
if (Array.isArray(sent)) sent = sent[0] // see todo on `sendToRoomId`
|
||||||
|
await driver.setReaction(':thumbsup:', sent._id)
|
||||||
|
const last = (await utils.lastMessages(tId))[0]
|
||||||
|
expect(last.reactions).to.have.deep.property(':thumbsup:', {
|
||||||
|
usernames: [ botUser.username ]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('removes if used when emoji reaction exists', async () => {
|
||||||
|
const sent = await driver.sendMessage(driver.prepareMessage({
|
||||||
|
msg: 'test reactions -',
|
||||||
|
reactions: { ':thumbsup:': { usernames: [botUser.username] } },
|
||||||
|
rid: tId
|
||||||
|
}))
|
||||||
|
await driver.setReaction(':thumbsup:', sent._id)
|
||||||
|
const last = (await utils.lastMessages(tId))[0]
|
||||||
|
expect(last).to.not.have.property('reactions')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.sendToRoomId', () => {
|
||||||
|
it('sends string to the given room id', async () => {
|
||||||
|
const result = await driver.sendToRoomId('SDK test `sendToRoomId`', tId)
|
||||||
|
expect(result).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
it('sends array of strings to the given room id', async () => {
|
||||||
|
const result = await driver.sendToRoomId([
|
||||||
|
'SDK test `sendToRoomId` A',
|
||||||
|
'SDK test `sendToRoomId` B'
|
||||||
|
], tId)
|
||||||
|
expect(result).to.be.an('array')
|
||||||
|
expect(result[0]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
expect(result[1]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.sendToRoom', () => {
|
||||||
|
it('sends string to the given room name', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
const result = await driver.sendToRoom('SDK test `sendToRoom`', tName)
|
||||||
|
expect(result).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
it('sends array of strings to the given room name', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
const result = await driver.sendToRoom([
|
||||||
|
'SDK test `sendToRoom` A',
|
||||||
|
'SDK test `sendToRoom` B'
|
||||||
|
], tName)
|
||||||
|
expect(result).to.be.an('array')
|
||||||
|
expect(result[0]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
expect(result[1]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.sendDirectToUser', () => {
|
||||||
|
before(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('sends string to the given room name', async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
const result = await driver.sendDirectToUser('SDK test `sendDirectToUser`', mockUser.username)
|
||||||
|
expect(result).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
it('sends array of strings to the given room name', async () => {
|
||||||
|
const result = await driver.sendDirectToUser([
|
||||||
|
'SDK test `sendDirectToUser` A',
|
||||||
|
'SDK test `sendDirectToUser` B'
|
||||||
|
], mockUser.username)
|
||||||
|
expect(result).to.be.an('array')
|
||||||
|
expect(result[0]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
expect(result[1]).to.include.all.keys(['msg', 'rid', '_id'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.respondToMessages', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
})
|
||||||
|
it('joins rooms if not already joined', async () => {
|
||||||
|
expect(driver.joinedIds).to.have.lengthOf(0)
|
||||||
|
await driver.respondToMessages(() => null, { rooms: ['general', tName] })
|
||||||
|
expect(driver.joinedIds).to.have.lengthOf(2)
|
||||||
|
})
|
||||||
|
it('ignores messages sent from bot', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback)
|
||||||
|
await driver.sendToRoomId('SDK test `respondToMessages`', tId)
|
||||||
|
sinon.assert.notCalled(callback)
|
||||||
|
})
|
||||||
|
it('fires callback on messages in joined rooms', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
await utils.sendFromUser({ text: 'SDK test `respondToMessages` 1' })
|
||||||
|
sinon.assert.calledOnce(callback)
|
||||||
|
})
|
||||||
|
it('by default ignores edited messages', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
const sentMessage = await utils.sendFromUser({
|
||||||
|
text: 'SDK test `respondToMessages` sent'
|
||||||
|
})
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
await utils.updateFromUser({
|
||||||
|
roomId: tId,
|
||||||
|
msgId: sentMessage.message._id,
|
||||||
|
text: 'SDK test `respondToMessages` edited'
|
||||||
|
})
|
||||||
|
sinon.assert.notCalled(callback)
|
||||||
|
})
|
||||||
|
it('ignores edited messages, after receiving original', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
const sentMessage = await utils.sendFromUser({
|
||||||
|
text: 'SDK test `respondToMessages` sent'
|
||||||
|
})
|
||||||
|
await utils.updateFromUser({
|
||||||
|
roomId: tId,
|
||||||
|
msgId: sentMessage.message._id,
|
||||||
|
text: 'SDK test `respondToMessages` edited'
|
||||||
|
})
|
||||||
|
sinon.assert.calledOnce(callback)
|
||||||
|
})
|
||||||
|
it('fires callback on edited message if configured', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
const sentMessage = await utils.sendFromUser({
|
||||||
|
text: 'SDK test `respondToMessages` sent'
|
||||||
|
})
|
||||||
|
driver.respondToMessages(callback, { edited: true, rooms: [tName] })
|
||||||
|
await utils.updateFromUser({
|
||||||
|
roomId: tId,
|
||||||
|
msgId: sentMessage.message._id,
|
||||||
|
text: 'SDK test `respondToMessages` edited'
|
||||||
|
})
|
||||||
|
sinon.assert.calledOnce(callback)
|
||||||
|
})
|
||||||
|
it('by default ignores DMs', async () => {
|
||||||
|
const dmResult = await utils.setupDirectFromUser()
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
await utils.sendFromUser({
|
||||||
|
text: 'SDK test `respondToMessages` DM',
|
||||||
|
roomId: dmResult.room._id
|
||||||
|
})
|
||||||
|
sinon.assert.notCalled(callback)
|
||||||
|
})
|
||||||
|
it('fires callback on DMs if configured', async () => {
|
||||||
|
const dmResult = await utils.setupDirectFromUser()
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { dm: true, rooms: [tName] })
|
||||||
|
await utils.sendFromUser({
|
||||||
|
text: 'SDK test `respondToMessages` DM',
|
||||||
|
roomId: dmResult.room._id
|
||||||
|
})
|
||||||
|
sinon.assert.calledOnce(callback)
|
||||||
|
})
|
||||||
|
it('fires callback on ul (user leave) message types', async () => {
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
await utils.leaveUser()
|
||||||
|
sinon.assert.calledWithMatch(callback, null, sinon.match({ t: 'ul' }))
|
||||||
|
await utils.inviteUser()
|
||||||
|
})
|
||||||
|
it('fires callback on au (user added) message types', async () => {
|
||||||
|
await utils.leaveUser()
|
||||||
|
const callback = sinon.spy()
|
||||||
|
driver.respondToMessages(callback, { rooms: [tName] })
|
||||||
|
await utils.inviteUser()
|
||||||
|
sinon.assert.calledWithMatch(callback, null, sinon.match({ t: 'au' }))
|
||||||
|
})
|
||||||
|
// it('appends room name to event meta in channels', async () => {
|
||||||
|
// const callback = sinon.spy()
|
||||||
|
// driver.respondToMessages(callback, { dm: true, rooms: [tName] })
|
||||||
|
// await utils.sendFromUser({ text: 'SDK test `respondToMessages` DM' })
|
||||||
|
// expect(callback.firstCall.args[2].roomName).to.equal(tName)
|
||||||
|
// })
|
||||||
|
// it('room name is undefined in direct messages', async () => {
|
||||||
|
// const dmResult = await utils.setupDirectFromUser()
|
||||||
|
// const callback = sinon.spy()
|
||||||
|
// driver.respondToMessages(callback, { dm: true, rooms: [tName] })
|
||||||
|
// await utils.sendFromUser({
|
||||||
|
// text: 'SDK test `respondToMessages` DM',
|
||||||
|
// roomId: dmResult.room._id
|
||||||
|
// })
|
||||||
|
// expect(callback.firstCall.args[2].roomName).to.equal(undefined)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
describe('.getRoomId', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('returns the ID for a channel by ID', async () => {
|
||||||
|
const room = await driver.getRoomId(tName)
|
||||||
|
expect(room).to.equal(tId)
|
||||||
|
})
|
||||||
|
it('returns the ID for a private room name', async () => {
|
||||||
|
const room = await driver.getRoomId(pName)
|
||||||
|
expect(room).to.equal(pId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.getRoomName', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
})
|
||||||
|
it('returns the name for a channel by ID', async () => {
|
||||||
|
const room = await driver.getRoomName(tId)
|
||||||
|
expect(room).to.equal(tName)
|
||||||
|
})
|
||||||
|
it('returns the name for a private group by ID', async () => {
|
||||||
|
const room = await driver.getRoomName(pId)
|
||||||
|
expect(room).to.equal(pName)
|
||||||
|
})
|
||||||
|
it('returns undefined for a DM room', async () => {
|
||||||
|
const dmResult = await utils.setupDirectFromUser()
|
||||||
|
const room = await driver.getRoomName(dmResult.room._id)
|
||||||
|
expect(room).to.equal(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.joinRooms', () => {
|
||||||
|
it('joins all the rooms in array, keeping IDs', async () => {
|
||||||
|
driver.joinedIds.splice(0, driver.joinedIds.length) // clear const array
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login()
|
||||||
|
await driver.joinRooms(['general', tName])
|
||||||
|
expect(driver.joinedIds).to.have.members(['GENERAL', tId])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,568 @@
|
|||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import Asteroid from 'asteroid'
|
||||||
|
import * as settings from './settings'
|
||||||
|
import * as methodCache from './methodCache'
|
||||||
|
import { Message } from './message'
|
||||||
|
import {
|
||||||
|
IConnectOptions,
|
||||||
|
IRespondOptions,
|
||||||
|
ICallback,
|
||||||
|
ILogger
|
||||||
|
} from '../config/driverInterfaces'
|
||||||
|
import {
|
||||||
|
IAsteroid,
|
||||||
|
ICredentials,
|
||||||
|
ISubscription,
|
||||||
|
ICollection
|
||||||
|
} from '../config/asteroidInterfaces'
|
||||||
|
import { IMessage } from '../config/messageInterfaces'
|
||||||
|
import { logger, replaceLog } from './log'
|
||||||
|
import { IMessageReceiptAPI } from '../utils/interfaces'
|
||||||
|
|
||||||
|
/** Collection names */
|
||||||
|
const _messageCollectionName = 'stream-room-messages'
|
||||||
|
const _messageStreamName = '__my_messages__'
|
||||||
|
|
||||||
|
// CONNECTION SETUP AND CONFIGURE
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Internal for comparing message update timestamps */
|
||||||
|
export let lastReadTime: Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The integration property is applied as an ID on sent messages `bot.i` param
|
||||||
|
* Should be replaced when connection is invoked by a package using the SDK
|
||||||
|
* e.g. The Hubot adapter would pass its integration ID with credentials, like:
|
||||||
|
*/
|
||||||
|
export const integrationId = settings.integrationId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event Emitter for listening to connection.
|
||||||
|
* @example
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* driver.events.on('connected', () => console.log('driver connected'))
|
||||||
|
*/
|
||||||
|
export const events = new EventEmitter()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Asteroid instance for interacting with Rocket.Chat.
|
||||||
|
* Variable not initialised until `connect` called.
|
||||||
|
*/
|
||||||
|
export let asteroid: IAsteroid
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid subscriptions, exported for direct polling by adapters
|
||||||
|
* Variable not initialised until `prepMeteorSubscriptions` called.
|
||||||
|
*/
|
||||||
|
export let subscriptions: ISubscription[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current user object populated from resolved login
|
||||||
|
*/
|
||||||
|
export let userId: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of joined room IDs (for reactive queries)
|
||||||
|
*/
|
||||||
|
export let joinedIds: string[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of messages received from reactive collection
|
||||||
|
*/
|
||||||
|
export let messages: ICollection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow override of default logging with adapter's log instance
|
||||||
|
*/
|
||||||
|
export function useLog (externalLog: ILogger) {
|
||||||
|
replaceLog(externalLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise asteroid instance with given options or defaults.
|
||||||
|
* Returns promise, resolved with Asteroid instance. Callback follows
|
||||||
|
* error-first-pattern. Error returned or promise rejected on timeout.
|
||||||
|
* Removes http/s protocol to get connection hostname if taken from URL.
|
||||||
|
* @example <caption>Use with callback</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect({}, (err) => {
|
||||||
|
* if (err) throw err
|
||||||
|
* else console.log('connected')
|
||||||
|
* })
|
||||||
|
* @example <caption>Using promise</caption>
|
||||||
|
* import { driver } from '@rocket.chat/sdk'
|
||||||
|
* driver.connect()
|
||||||
|
* .then(() => console.log('connected'))
|
||||||
|
* .catch((err) => console.error(err))
|
||||||
|
*/
|
||||||
|
export function connect (
|
||||||
|
options: IConnectOptions = {},
|
||||||
|
callback?: ICallback
|
||||||
|
): Promise<IAsteroid> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const config = Object.assign({}, settings, options) // override defaults
|
||||||
|
config.host = config.host.replace(/(^\w+:|^)\/\//, '')
|
||||||
|
logger.info('[connect] Connecting', config)
|
||||||
|
asteroid = new Asteroid(config.host, config.useSsl)
|
||||||
|
|
||||||
|
setupMethodCache(asteroid) // init instance for later caching method calls
|
||||||
|
asteroid.on('connected', () => {
|
||||||
|
asteroid.resumeLoginPromise.catch(function () {
|
||||||
|
// pass
|
||||||
|
})
|
||||||
|
events.emit('connected')
|
||||||
|
})
|
||||||
|
asteroid.on('reconnected', () => events.emit('reconnected'))
|
||||||
|
let cancelled = false
|
||||||
|
const rejectionTimeout = setTimeout(function () {
|
||||||
|
logger.info(`[connect] Timeout (${config.timeout})`)
|
||||||
|
const err = new Error('Asteroid connection timeout')
|
||||||
|
cancelled = true
|
||||||
|
events.removeAllListeners('connected')
|
||||||
|
callback ? callback(err, asteroid) : reject(err)
|
||||||
|
}, config.timeout)
|
||||||
|
|
||||||
|
// if to avoid condition where timeout happens before listener to 'connected' is added
|
||||||
|
// and this listener is not removed (because it was added after the removal)
|
||||||
|
if (!cancelled) {
|
||||||
|
events.once('connected', () => {
|
||||||
|
logger.info('[connect] Connected')
|
||||||
|
// if (cancelled) return asteroid.ddp.disconnect() // cancel if already rejected
|
||||||
|
clearTimeout(rejectionTimeout)
|
||||||
|
if (callback) callback(null, asteroid)
|
||||||
|
resolve(asteroid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Remove all active subscriptions, logout and disconnect from Rocket.Chat */
|
||||||
|
export function disconnect (): Promise<void> {
|
||||||
|
logger.info('Unsubscribing, logging out, disconnecting')
|
||||||
|
unsubscribeAll()
|
||||||
|
return logout()
|
||||||
|
.then(() => Promise.resolve())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASYNC AND CACHE METHOD UTILS
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup method cache configs from env or defaults, before they are called.
|
||||||
|
* @param asteroid The asteroid instance to cache method calls
|
||||||
|
*/
|
||||||
|
function setupMethodCache (asteroid: IAsteroid): void {
|
||||||
|
methodCache.use(asteroid)
|
||||||
|
methodCache.create('getRoomIdByNameOrId', {
|
||||||
|
max: settings.roomCacheMaxSize,
|
||||||
|
maxAge: settings.roomCacheMaxAge
|
||||||
|
}),
|
||||||
|
methodCache.create('getRoomNameById', {
|
||||||
|
max: settings.roomCacheMaxSize,
|
||||||
|
maxAge: settings.roomCacheMaxAge
|
||||||
|
})
|
||||||
|
methodCache.create('createDirectMessage', {
|
||||||
|
max: settings.dmCacheMaxSize,
|
||||||
|
maxAge: settings.dmCacheMaxAge
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps method calls to ensure they return a Promise with caught exceptions.
|
||||||
|
* @param method The Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param params Single or array of parameters of the method to call
|
||||||
|
*/
|
||||||
|
export function asyncCall (method: string, params: any | any[]): Promise<any> {
|
||||||
|
if (!Array.isArray(params)) params = [params] // cast to array for apply
|
||||||
|
logger.info(`[${method}] Calling (async): ${JSON.stringify(params)}`)
|
||||||
|
return Promise.resolve(asteroid.apply(method, params).result)
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.error(`[${method}] Error:`, err)
|
||||||
|
throw err // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
(result)
|
||||||
|
? logger.debug(`[${method}] Success: ${JSON.stringify(result)}`)
|
||||||
|
: logger.debug(`[${method}] Success`)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a method as async via Asteroid, or through cache if one is created.
|
||||||
|
* If the method doesn't have or need parameters, it can't use them for caching
|
||||||
|
* so it will always call asynchronously.
|
||||||
|
* @param name The Rocket.Chat server method to call
|
||||||
|
* @param params Single or array of parameters of the method to call
|
||||||
|
*/
|
||||||
|
export function callMethod (name: string, params?: any | any[]): Promise<any> {
|
||||||
|
return (methodCache.has(name) || typeof params === 'undefined')
|
||||||
|
? asyncCall(name, params)
|
||||||
|
: cacheCall(name, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps Asteroid method calls, passed through method cache if cache is valid.
|
||||||
|
* @param method The Rocket.Chat server method, to call through Asteroid
|
||||||
|
* @param key Single string parameters only, required to use as cache key
|
||||||
|
*/
|
||||||
|
export function cacheCall (method: string, key: string): Promise<any> {
|
||||||
|
return methodCache.call(method, key)
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.error(`[${method}] Error:`, err)
|
||||||
|
throw err // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
.then((result: any) => {
|
||||||
|
(result)
|
||||||
|
? logger.debug(`[${method}] Success: ${JSON.stringify(result)}`)
|
||||||
|
: logger.debug(`[${method}] Success`)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOGIN AND SUBSCRIBE TO ROOMS
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Login to Rocket.Chat via Asteroid */
|
||||||
|
export function login (credentials: ICredentials = {
|
||||||
|
username: settings.username,
|
||||||
|
password: settings.password,
|
||||||
|
ldap: settings.ldap
|
||||||
|
}): Promise<any> {
|
||||||
|
let login: Promise<any>
|
||||||
|
// if (credentials.ldap) {
|
||||||
|
// logger.info(`[login] Logging in ${credentials.username} with LDAP`)
|
||||||
|
// login = asteroid.loginWithLDAP(
|
||||||
|
// credentials.email || credentials.username,
|
||||||
|
// credentials.password,
|
||||||
|
// { ldap: true, ldapOptions: credentials.ldapOptions || {} }
|
||||||
|
// )
|
||||||
|
// } else {
|
||||||
|
logger.info(`[login] Logging in ${credentials.username}`)
|
||||||
|
login = asteroid.loginWithPassword(
|
||||||
|
credentials.email || credentials.username!,
|
||||||
|
credentials.password
|
||||||
|
)
|
||||||
|
// }
|
||||||
|
return login
|
||||||
|
.then((loggedInUserId) => {
|
||||||
|
userId = loggedInUserId
|
||||||
|
return loggedInUserId
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.info('[login] Error:', err)
|
||||||
|
throw err // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logout of Rocket.Chat via Asteroid */
|
||||||
|
export function logout (): Promise<void | null> {
|
||||||
|
return asteroid.logout()
|
||||||
|
.catch((err: Error) => {
|
||||||
|
logger.error('[Logout] Error:', err)
|
||||||
|
throw err // throw after log to stop async chain
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to Meteor subscription
|
||||||
|
* Resolves with subscription (added to array), with ID property
|
||||||
|
* @todo - 3rd param of asteroid.subscribe is deprecated in Rocket.Chat?
|
||||||
|
*/
|
||||||
|
export function subscribe (
|
||||||
|
topic: string,
|
||||||
|
roomId: string
|
||||||
|
): Promise<ISubscription> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logger.info(`[subscribe] Preparing subscription: ${topic}: ${roomId}`)
|
||||||
|
const subscription = asteroid.subscribe(topic, roomId, true)
|
||||||
|
subscriptions.push(subscription)
|
||||||
|
return subscription.ready
|
||||||
|
.then((id) => {
|
||||||
|
logger.info(`[subscribe] Stream ready: ${id}`)
|
||||||
|
resolve(subscription)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unsubscribe from Meteor subscription */
|
||||||
|
export function unsubscribe (subscription: ISubscription): void {
|
||||||
|
const index = subscriptions.indexOf(subscription)
|
||||||
|
if (index === -1) return
|
||||||
|
subscription.stop()
|
||||||
|
// asteroid.unsubscribe(subscription.id) // v2
|
||||||
|
subscriptions.splice(index, 1) // remove from collection
|
||||||
|
logger.info(`[${subscription.id}] Unsubscribed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unsubscribe from all subscriptions in collection */
|
||||||
|
export function unsubscribeAll (): void {
|
||||||
|
subscriptions.map((s: ISubscription) => unsubscribe(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin subscription to room events for user.
|
||||||
|
* Older adapters used an option for this method but it was always the default.
|
||||||
|
*/
|
||||||
|
export function subscribeToMessages (): Promise<ISubscription> {
|
||||||
|
return subscribe(_messageCollectionName, _messageStreamName)
|
||||||
|
.then((subscription) => {
|
||||||
|
messages = asteroid.getCollection(_messageCollectionName)
|
||||||
|
return subscription
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once a subscription is created, using `subscribeToMessages` this method
|
||||||
|
* can be used to attach a callback to changes in the message stream.
|
||||||
|
* This can be called directly for custom extensions, but for most usage (e.g.
|
||||||
|
* for bots) the respondToMessages is more useful to only receive messages
|
||||||
|
* matching configuration.
|
||||||
|
*
|
||||||
|
* If the bot hasn't been joined to any rooms at this point, it will attempt to
|
||||||
|
* join now based on environment config, otherwise it might not receive any
|
||||||
|
* messages. It doesn't matter that this happens asynchronously because the
|
||||||
|
* bot's joined rooms can change after the reactive query is set up.
|
||||||
|
*
|
||||||
|
* @todo `reactToMessages` should call `subscribeToMessages` if not already
|
||||||
|
* done, so it's not required as an arbitrary step for simpler adapters.
|
||||||
|
* Also make `login` call `connect` for the same reason, the way
|
||||||
|
* `respondToMessages` calls `respondToMessages`, so all that's really
|
||||||
|
* required is:
|
||||||
|
* `driver.login(credentials).then(() => driver.respondToMessages(callback))`
|
||||||
|
* @param callback Function called with every change in subscriptions.
|
||||||
|
* - Uses error-first callback pattern
|
||||||
|
* - Second argument is the changed item
|
||||||
|
* - Third argument is additional attributes, such as `roomType`
|
||||||
|
*/
|
||||||
|
export function reactToMessages (callback: ICallback): void {
|
||||||
|
logger.info(`[reactive] Listening for change events in collection ${messages.name}`)
|
||||||
|
|
||||||
|
messages.reactiveQuery({}).on('change', (_id: string) => {
|
||||||
|
const changedMessageQuery = messages.reactiveQuery({ _id })
|
||||||
|
if (changedMessageQuery.result && changedMessageQuery.result.length > 0) {
|
||||||
|
const changedMessage = changedMessageQuery.result[0]
|
||||||
|
if (Array.isArray(changedMessage.args)) {
|
||||||
|
logger.info(`[received] Message in room ${ changedMessage.args[0].rid }`)
|
||||||
|
callback(null, changedMessage.args[0], changedMessage.args[1])
|
||||||
|
} else {
|
||||||
|
logger.debug('[received] Update without message args')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug('[received] Reactive query at ID ${ _id } without results')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy for `reactToMessages` with some filtering of messages based on config.
|
||||||
|
*
|
||||||
|
* @param callback Function called 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`
|
||||||
|
* @param options Sets filters for different event/message types.
|
||||||
|
*/
|
||||||
|
export function respondToMessages (
|
||||||
|
callback: ICallback,
|
||||||
|
options: IRespondOptions = {}
|
||||||
|
): Promise<void | void[]> {
|
||||||
|
const config = Object.assign({}, settings, options)
|
||||||
|
// return value, may be replaced by async ops
|
||||||
|
let promise: Promise<void | void[]> = Promise.resolve()
|
||||||
|
|
||||||
|
// Join configured rooms if they haven't been already, unless listening to all
|
||||||
|
// public rooms, in which case it doesn't matter
|
||||||
|
if (
|
||||||
|
!config.allPublic &&
|
||||||
|
joinedIds.length === 0 &&
|
||||||
|
config.rooms &&
|
||||||
|
config.rooms.length > 0
|
||||||
|
) {
|
||||||
|
promise = joinRooms(config.rooms)
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(`[joinRooms] Failed to join configured rooms (${config.rooms.join(', ')}): ${err.message}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
lastReadTime = new Date() // init before any message read
|
||||||
|
reactToMessages(async (err, message, meta) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`[received] Unable to receive: ${err.message}`)
|
||||||
|
callback(err) // bubble errors back to adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore bot's own messages
|
||||||
|
if (message.u._id === userId) return
|
||||||
|
|
||||||
|
// Ignore DMs unless configured not to
|
||||||
|
const isDM = meta.roomType === 'd'
|
||||||
|
if (isDM && !config.dm) return
|
||||||
|
|
||||||
|
// Ignore Livechat unless configured not to
|
||||||
|
const isLC = meta.roomType === 'l'
|
||||||
|
if (isLC && !config.livechat) return
|
||||||
|
|
||||||
|
// Ignore messages in un-joined public rooms unless configured not to
|
||||||
|
if (!config.allPublic && !isDM && !meta.roomParticipant) return
|
||||||
|
|
||||||
|
// Set current time for comparison to incoming
|
||||||
|
let currentReadTime = new Date(message.ts.$date)
|
||||||
|
|
||||||
|
// Ignore edited messages if configured to
|
||||||
|
if (!config.edited && message.editedAt) return
|
||||||
|
|
||||||
|
// Set read time as time of edit, if message is edited
|
||||||
|
if (message.editedAt) currentReadTime = new Date(message.editedAt.$date)
|
||||||
|
|
||||||
|
// Ignore messages in stream that aren't new
|
||||||
|
if (currentReadTime <= lastReadTime) return
|
||||||
|
|
||||||
|
// At this point, message has passed checks and can be responded to
|
||||||
|
logger.info(`[received] Message ${message._id} from ${message.u.username}`)
|
||||||
|
lastReadTime = currentReadTime
|
||||||
|
|
||||||
|
// Processing completed, call callback to respond to message
|
||||||
|
callback(null, message, meta)
|
||||||
|
})
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
// PREPARE AND SEND MESSAGES
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Get ID for a room by name (or ID). */
|
||||||
|
export function getRoomId (name: string): Promise<string> {
|
||||||
|
return cacheCall('getRoomIdByNameOrId', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get name for a room by ID. */
|
||||||
|
export function getRoomName (id: string): Promise<string> {
|
||||||
|
return cacheCall('getRoomNameById', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ID for a DM room by its recipient's name.
|
||||||
|
* Will create a DM (with the bot) if it doesn't exist already.
|
||||||
|
* @todo test why create resolves with object instead of simply ID
|
||||||
|
*/
|
||||||
|
export function getDirectMessageRoomId (username: string): Promise<string> {
|
||||||
|
return cacheCall('createDirectMessage', username)
|
||||||
|
.then((DM) => DM.rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Join the bot into a room by its name or ID */
|
||||||
|
export async function joinRoom (room: string): Promise<void> {
|
||||||
|
let roomId = await getRoomId(room)
|
||||||
|
let joinedIndex = joinedIds.indexOf(room)
|
||||||
|
if (joinedIndex !== -1) {
|
||||||
|
logger.error(`[joinRoom] room was already joined`)
|
||||||
|
} else {
|
||||||
|
await asyncCall('joinRoom', roomId)
|
||||||
|
joinedIds.push(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Exit a room the bot has joined */
|
||||||
|
export async function leaveRoom (room: string): Promise<void> {
|
||||||
|
let roomId = await getRoomId(room)
|
||||||
|
let joinedIndex = joinedIds.indexOf(room)
|
||||||
|
if (joinedIndex === -1) {
|
||||||
|
logger.error(`[leaveRoom] failed because bot has not joined ${room}`)
|
||||||
|
} else {
|
||||||
|
await asyncCall('leaveRoom', roomId)
|
||||||
|
delete joinedIds[joinedIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Join a set of rooms by array of names or IDs */
|
||||||
|
export function joinRooms (rooms: string[]): Promise<void[]> {
|
||||||
|
return Promise.all(rooms.map((room) => joinRoom(room)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure message content, optionally addressing to room ID.
|
||||||
|
* Accepts message text string or a structured message object.
|
||||||
|
*/
|
||||||
|
export function prepareMessage (
|
||||||
|
content: string | IMessage,
|
||||||
|
roomId?: string
|
||||||
|
): Message {
|
||||||
|
const message = new Message(content, integrationId)
|
||||||
|
if (roomId) message.setRoomId(roomId)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a prepared message object (with pre-defined room ID).
|
||||||
|
* Usually prepared and called by sendMessageByRoomId or sendMessageByRoom.
|
||||||
|
*/
|
||||||
|
export function sendMessage (message: IMessage): Promise<IMessageReceiptAPI> {
|
||||||
|
return asyncCall('sendMessage', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to specified room ID.
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param roomId ID of the target room to use in send.
|
||||||
|
* @todo Returning one or many gets complicated with type checking not allowing
|
||||||
|
* use of a property because result may be array, when you know it's not.
|
||||||
|
* Solution would probably be to always return an array, even for single
|
||||||
|
* send. This would be a breaking change, should hold until major version.
|
||||||
|
*/
|
||||||
|
export function sendToRoomId (
|
||||||
|
content: string | string[] | IMessage,
|
||||||
|
roomId: string
|
||||||
|
): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI> {
|
||||||
|
if (!Array.isArray(content)) {
|
||||||
|
return sendMessage(prepareMessage(content, roomId))
|
||||||
|
} else {
|
||||||
|
return Promise.all(content.map((text) => {
|
||||||
|
return sendMessage(prepareMessage(text, roomId))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to specified room name (or ID).
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param room A name (or ID) to resolve as ID to use in send.
|
||||||
|
*/
|
||||||
|
export function sendToRoom (
|
||||||
|
content: string | string[] | IMessage,
|
||||||
|
room: string
|
||||||
|
): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI> {
|
||||||
|
return getRoomId(room)
|
||||||
|
.then((roomId) => sendToRoomId(content, roomId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare and send string/s to a user in a DM.
|
||||||
|
* @param content Accepts message text string or array of strings.
|
||||||
|
* @param username Name to create (or get) DM for room ID to use in send.
|
||||||
|
*/
|
||||||
|
export function sendDirectToUser (
|
||||||
|
content: string | string[] | IMessage,
|
||||||
|
username: string
|
||||||
|
): Promise<IMessageReceiptAPI[] | IMessageReceiptAPI> {
|
||||||
|
return getDirectMessageRoomId(username)
|
||||||
|
.then((rid) => sendToRoomId(content, rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit an existing message, replacing any attributes with those provided.
|
||||||
|
* The given message object should have the ID of an existing message.
|
||||||
|
*/
|
||||||
|
export function editMessage (message: IMessage): Promise<IMessage> {
|
||||||
|
return asyncCall('updateMessage', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a reaction to an existing message. Simple proxy for method call.
|
||||||
|
* @param emoji Accepts string like `:thumbsup:` to add 👍 reaction
|
||||||
|
* @param messageId ID for a previously sent message
|
||||||
|
*/
|
||||||
|
export function setReaction (emoji: string, messageId: string) {
|
||||||
|
return asyncCall('setReaction', [emoji, messageId])
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { ILogger } from '../config/driverInterfaces'
|
||||||
|
|
||||||
|
/** Temp logging, should override form adapter's log */
|
||||||
|
class InternalLog implements ILogger {
|
||||||
|
debug (...args: any[]) {
|
||||||
|
console.log(...args)
|
||||||
|
}
|
||||||
|
info (...args: any[]) {
|
||||||
|
console.log(...args)
|
||||||
|
}
|
||||||
|
warning (...args: any[]) {
|
||||||
|
console.warn(...args)
|
||||||
|
}
|
||||||
|
warn (...args: any[]) { // legacy method
|
||||||
|
return this.warning(...args)
|
||||||
|
}
|
||||||
|
error (...args: any[]) {
|
||||||
|
console.error(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let logger: ILogger = new InternalLog()
|
||||||
|
|
||||||
|
function replaceLog (externalLog: ILogger) {
|
||||||
|
logger = externalLog
|
||||||
|
}
|
||||||
|
|
||||||
|
function silence () {
|
||||||
|
replaceLog({
|
||||||
|
debug: () => null,
|
||||||
|
info: () => null,
|
||||||
|
warn: () => null,
|
||||||
|
warning: () => null,
|
||||||
|
error: () => null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
logger,
|
||||||
|
replaceLog,
|
||||||
|
silence
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
import 'mocha'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { Message } from './message'
|
||||||
|
|
||||||
|
describe('message', () => {
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('creates message object from content string', () => {
|
||||||
|
let message = new Message('hello world', 'test')
|
||||||
|
expect(message.msg).to.equal('hello world')
|
||||||
|
})
|
||||||
|
it('uses second param as integration ID attribute', () => {
|
||||||
|
let message = new Message('hello world', 'test')
|
||||||
|
expect(message.bot.i).to.equal('test')
|
||||||
|
})
|
||||||
|
it('accepts existing message and assigns new properties', () => {
|
||||||
|
let message = new Message({
|
||||||
|
msg: 'hello world',
|
||||||
|
rid: 'GENERAL'
|
||||||
|
}, 'test')
|
||||||
|
expect(message).to.eql({
|
||||||
|
msg: 'hello world',
|
||||||
|
rid: 'GENERAL',
|
||||||
|
bot: { i: 'test' }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.setRoomId', () => {
|
||||||
|
it('sets rid property', () => {
|
||||||
|
let message = new Message('hello world', 'test')
|
||||||
|
message.setRoomId('111')
|
||||||
|
expect(message.rid).to.equal('111')
|
||||||
|
})
|
||||||
|
it('updates rid property', () => {
|
||||||
|
let message = new Message({
|
||||||
|
msg: 'hello world',
|
||||||
|
rid: 'GENERAL'
|
||||||
|
}, 'test')
|
||||||
|
message.setRoomId('111')
|
||||||
|
expect(message.rid).to.equal('111')
|
||||||
|
})
|
||||||
|
it('returns message instance', () => {
|
||||||
|
let message = new Message('hello world', 'test')
|
||||||
|
expect(message.setRoomId('111')).to.eql(message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,23 @@
|
|||||||
|
import { IMessage } from '../config/messageInterfaces'
|
||||||
|
|
||||||
|
// Message class declaration implicitly implements interface
|
||||||
|
// https://github.com/Microsoft/TypeScript/issues/340
|
||||||
|
export interface Message extends IMessage {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rocket.Chat message class.
|
||||||
|
* Sets integration param to allow tracing source of automated sends.
|
||||||
|
* @param content Accepts message text or a preformed message object
|
||||||
|
* @todo Potential for SDK usage that isn't bots, bot prop should be optional?
|
||||||
|
*/
|
||||||
|
export class Message {
|
||||||
|
constructor (content: string | IMessage, integrationId: string) {
|
||||||
|
if (typeof content === 'string') this.msg = content
|
||||||
|
else Object.assign(this, content)
|
||||||
|
this.bot = { i: integrationId }
|
||||||
|
}
|
||||||
|
setRoomId (roomId: string): Message {
|
||||||
|
this.rid = roomId
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
import sinon from 'sinon'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import LRU from 'lru-cache'
|
||||||
|
import * as methodCache from './methodCache'
|
||||||
|
|
||||||
|
// Instance method variance for testing cache
|
||||||
|
const mockInstance = { call: sinon.stub() }
|
||||||
|
mockInstance.call.withArgs('methodOne').onCall(0).returns({ result: 'foo' })
|
||||||
|
mockInstance.call.withArgs('methodOne').onCall(1).returns({ result: 'bar' })
|
||||||
|
mockInstance.call.withArgs('methodTwo', 'key1').returns({ result: 'value1' })
|
||||||
|
mockInstance.call.withArgs('methodTwo', 'key2').returns({ result: 'value2' })
|
||||||
|
|
||||||
|
describe('methodCache', () => {
|
||||||
|
beforeEach(() => mockInstance.call.resetHistory())
|
||||||
|
afterEach(() => methodCache.resetAll())
|
||||||
|
describe('.use', () => {
|
||||||
|
it('calls apply to instance', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
expect(mockInstance.call.callCount).to.equal(1)
|
||||||
|
})
|
||||||
|
it('accepts a class instance', () => {
|
||||||
|
class MyClass {}
|
||||||
|
const myInstance = new MyClass()
|
||||||
|
const shouldWork = () => methodCache.use(myInstance)
|
||||||
|
expect(shouldWork).to.not.throw()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.create', () => {
|
||||||
|
it('returns a cache for method calls', () => {
|
||||||
|
expect(methodCache.create('anyMethod')).to.be.instanceof(LRU)
|
||||||
|
})
|
||||||
|
it('accepts options overriding defaults', () => {
|
||||||
|
const cache = methodCache.create('methodOne', { maxAge: 3000 })
|
||||||
|
expect(cache.max).to.equal(methodCache.defaults.max)
|
||||||
|
expect(cache.maxAge).to.equal(3000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.call', () => {
|
||||||
|
it('throws if instance not in use', () => {
|
||||||
|
const badUse = () => methodCache.call('methodOne', 'key1')
|
||||||
|
expect(badUse).to.throw()
|
||||||
|
})
|
||||||
|
it('throws if method does not exist', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
const badUse = () => methodCache.call('bad', 'key1')
|
||||||
|
expect(badUse).to.throw()
|
||||||
|
})
|
||||||
|
it('returns a promise', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
expect(methodCache.call('methodOne', 'key1').then).to.be.a('function')
|
||||||
|
})
|
||||||
|
it('calls the method with the key', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
return methodCache.call('methodTwo', 'key1').then((result) => {
|
||||||
|
expect(result).to.equal('value1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('only calls the method once', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
expect(mockInstance.call.callCount).to.equal(1)
|
||||||
|
})
|
||||||
|
it('returns cached result on subsequent calls', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
return Promise.all([
|
||||||
|
methodCache.call('methodOne', 'key1'),
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
]).then((results) => {
|
||||||
|
expect(results[0]).to.equal(results[1])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('calls again if cache expired', () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.create('methodOne', { maxAge: 10 })
|
||||||
|
const result1 = methodCache.call('methodOne', 'key1')
|
||||||
|
clock.tick(20)
|
||||||
|
const result2 = methodCache.call('methodOne', 'key1')
|
||||||
|
clock.restore()
|
||||||
|
return Promise.all([result1, result2]).then((results) => {
|
||||||
|
expect(mockInstance.call.callCount).to.equal(2)
|
||||||
|
expect(results[0]).to.not.equal(results[1])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.has', () => {
|
||||||
|
it('returns true if the method cache was created', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.create('methodOne')
|
||||||
|
expect(methodCache.has('methodOne')).to.equal(true)
|
||||||
|
})
|
||||||
|
it('returns true if the method was called with cache', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodOne', 'key')
|
||||||
|
expect(methodCache.has('methodOne')).to.equal(true)
|
||||||
|
})
|
||||||
|
it('returns false if the method is not cached', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
expect(methodCache.has('methodThree')).to.equal(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.get', () => {
|
||||||
|
it('returns cached result from last call with key', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
return methodCache.call('methodOne', 'key1').then((result) => {
|
||||||
|
expect(methodCache.get('methodOne', 'key1')).to.equal(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.reset', () => {
|
||||||
|
it('removes cached results for a method and key', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
const result1 = methodCache.call('methodOne', 'key1')
|
||||||
|
methodCache.reset('methodOne', 'key1')
|
||||||
|
const result2 = methodCache.call('methodOne', 'key1')
|
||||||
|
expect(result1).not.to.equal(result2)
|
||||||
|
})
|
||||||
|
it('does not remove cache of calls with different key', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodTwo', 'key1')
|
||||||
|
methodCache.call('methodTwo', 'key2')
|
||||||
|
methodCache.reset('methodTwo', 'key1')
|
||||||
|
const result = methodCache.get('methodTwo', 'key2')
|
||||||
|
expect(result).to.equal('value2')
|
||||||
|
})
|
||||||
|
it('without key, removes all results for method', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodTwo', 'key1')
|
||||||
|
methodCache.call('methodTwo', 'key2')
|
||||||
|
methodCache.reset('methodTwo')
|
||||||
|
const result1 = methodCache.get('methodTwo', 'key1')
|
||||||
|
const result2 = methodCache.get('methodTwo', 'key2')
|
||||||
|
expect(result1).to.equal(undefined)
|
||||||
|
expect(result2).to.equal(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('.resetAll', () => {
|
||||||
|
it('resets all cached methods', () => {
|
||||||
|
methodCache.use(mockInstance)
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
methodCache.call('methodTwo', 'key1')
|
||||||
|
methodCache.resetAll()
|
||||||
|
methodCache.call('methodOne', 'key1')
|
||||||
|
methodCache.call('methodTwo', 'key1')
|
||||||
|
expect(mockInstance.call.callCount).to.equal(4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,90 @@
|
|||||||
|
import LRU from 'lru-cache'
|
||||||
|
import { logger } from './log'
|
||||||
|
|
||||||
|
/** @TODO: Remove ! post-fix expression when TypeScript #9619 resolved */
|
||||||
|
export let instance: any
|
||||||
|
export const results: Map<string, LRU.Cache<string, any>> = new Map()
|
||||||
|
export const defaults: LRU.Options = {
|
||||||
|
max: 100,
|
||||||
|
maxAge: 300 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the instance to call methods on, with cached results.
|
||||||
|
* @param instanceToUse Instance of a class
|
||||||
|
*/
|
||||||
|
export function use (instanceToUse: object): void {
|
||||||
|
instance = instanceToUse
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a cache for a method call.
|
||||||
|
* @param method Method name, for index of cached results
|
||||||
|
* @param options.max Maximum size of cache
|
||||||
|
* @param options.maxAge Maximum age of cache
|
||||||
|
*/
|
||||||
|
export function create (method: string, options: LRU.Options = {}): LRU.Cache<string, any> | undefined {
|
||||||
|
options = Object.assign(defaults, options)
|
||||||
|
results.set(method, new LRU(options))
|
||||||
|
return results.get(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call or call and cache.
|
||||||
|
* @param method Method name, to call on instance in use
|
||||||
|
* @param key Key to pass to method call and save results against
|
||||||
|
*/
|
||||||
|
export function call (method: string, key: string): Promise<any> {
|
||||||
|
if (!results.has(method)) create(method) // create as needed
|
||||||
|
const methodCache = results.get(method)!
|
||||||
|
let callResults
|
||||||
|
|
||||||
|
if (methodCache.has(key)) {
|
||||||
|
logger.debug(`[${method}] Calling (cached): ${key}`)
|
||||||
|
// return from cache if key has been used on method before
|
||||||
|
callResults = methodCache.get(key)
|
||||||
|
} else {
|
||||||
|
// call and cache for next time, returning results
|
||||||
|
logger.debug(`[${method}] Calling (caching): ${key}`)
|
||||||
|
callResults = instance.call(method, key).result
|
||||||
|
methodCache.set(key, callResults)
|
||||||
|
}
|
||||||
|
return Promise.resolve(callResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy for checking if method has been cached.
|
||||||
|
* Cache may exist from manual creation, or prior call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
*/
|
||||||
|
export function has (method: string): boolean {
|
||||||
|
return results.has(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get results of a prior method call.
|
||||||
|
* @param method Method name for cache to get
|
||||||
|
* @param key Key for method result set to return
|
||||||
|
*/
|
||||||
|
export function get (method: string, key: string): LRU.Cache<string, any> | undefined {
|
||||||
|
if (results.has(method)) return results.get(method)!.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset a cached method call's results (all or only for given key).
|
||||||
|
* @param method Method name for cache to clear
|
||||||
|
* @param key Key for method result set to clear
|
||||||
|
*/
|
||||||
|
export function reset (method: string, key?: string): void {
|
||||||
|
if (results.has(method)) {
|
||||||
|
if (key) return results.get(method)!.del(key)
|
||||||
|
else return results.get(method)!.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset cached results for all methods.
|
||||||
|
*/
|
||||||
|
export function resetAll (): void {
|
||||||
|
results.forEach((cache) => cache.reset())
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'mocha'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
const initEnv = process.env // store configs to restore after tests
|
||||||
|
|
||||||
|
describe('settings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.ROCKETCHAT_URL
|
||||||
|
delete process.env.ROCKETCHAT_USE_SSL
|
||||||
|
delete process.env.ROCKETCHAT_ROOM
|
||||||
|
delete process.env.LISTEN_ON_ALL_PUBLIC
|
||||||
|
delete process.env.RESPOND_TO_DM
|
||||||
|
delete process.env.RESPOND_TO_LIVECHAT
|
||||||
|
delete process.env.RESPOND_TO_EDITED
|
||||||
|
delete require.cache[require.resolve('./settings')] // clear modules memory
|
||||||
|
})
|
||||||
|
afterEach(() => process.env = initEnv)
|
||||||
|
it('uses localhost URL without SSL if env undefined', () => {
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings).to.deep.include({
|
||||||
|
host: 'localhost:3000',
|
||||||
|
useSsl: false,
|
||||||
|
timeout: 20000
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('sets SSL from env if defined', () => {
|
||||||
|
process.env.ROCKETCHAT_USE_SSL = 'true'
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings.useSsl).to.equal(true)
|
||||||
|
})
|
||||||
|
it('uses SSL if https protocol URL in env', () => {
|
||||||
|
process.env.ROCKETCHAT_URL = 'https://localhost:3000'
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings.useSsl).to.equal(true)
|
||||||
|
})
|
||||||
|
it('does not use SSL if http protocol URL in env', () => {
|
||||||
|
process.env.ROCKETCHAT_URL = 'http://localhost:3000'
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings.useSsl).to.equal(false)
|
||||||
|
})
|
||||||
|
it('SSL overrides protocol detection', () => {
|
||||||
|
process.env.ROCKETCHAT_URL = 'https://localhost:3000'
|
||||||
|
process.env.ROCKETCHAT_USE_SSL = 'false'
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings.useSsl).to.equal(false)
|
||||||
|
})
|
||||||
|
it('all respond configs default to false if env undefined', () => {
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings).to.deep.include({
|
||||||
|
rooms: [],
|
||||||
|
allPublic: false,
|
||||||
|
dm: false,
|
||||||
|
livechat: false,
|
||||||
|
edited: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('inherits config from env settings', () => {
|
||||||
|
process.env.ROCKETCHAT_ROOM = 'GENERAL'
|
||||||
|
process.env.LISTEN_ON_ALL_PUBLIC = 'false'
|
||||||
|
process.env.RESPOND_TO_DM = 'true'
|
||||||
|
process.env.RESPOND_TO_LIVECHAT = 'true'
|
||||||
|
process.env.RESPOND_TO_EDITED = 'true'
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings).to.deep.include({
|
||||||
|
rooms: ['GENERAL'],
|
||||||
|
allPublic: false,
|
||||||
|
dm: true,
|
||||||
|
livechat: true,
|
||||||
|
edited: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
it('creates room array from csv list', () => {
|
||||||
|
process.env.ROCKETCHAT_ROOM = `general, foo`
|
||||||
|
const settings = require('./settings')
|
||||||
|
expect(settings.rooms).to.eql(['general', 'foo'])
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
// Login settings - LDAP needs to be explicitly enabled
|
||||||
|
export let username = process.env.ROCKETCHAT_USER || 'bot'
|
||||||
|
export let password = process.env.ROCKETCHAT_PASSWORD || 'pass'
|
||||||
|
export let ldap = (process.env.ROCKETCHAT_AUTH === 'ldap')
|
||||||
|
|
||||||
|
// Connection settings - Enable SSL by default if Rocket.Chat URL contains https
|
||||||
|
export let host = process.env.ROCKETCHAT_URL || 'localhost:3000'
|
||||||
|
export let useSsl = (process.env.ROCKETCHAT_USE_SSL)
|
||||||
|
? ((process.env.ROCKETCHAT_USE_SSL || '').toString().toLowerCase() === 'true')
|
||||||
|
: ((process.env.ROCKETCHAT_URL || '').toString().toLowerCase().startsWith('https'))
|
||||||
|
export let timeout = 20 * 1000 // 20 seconds
|
||||||
|
|
||||||
|
// Respond settings - reactive callback filters for .respondToMessages
|
||||||
|
export let rooms = (process.env.ROCKETCHAT_ROOM)
|
||||||
|
? (process.env.ROCKETCHAT_ROOM || '').split(',').map((room) => room.trim())
|
||||||
|
: []
|
||||||
|
export let allPublic = (process.env.LISTEN_ON_ALL_PUBLIC || 'false').toLowerCase() === 'true'
|
||||||
|
export let dm = (process.env.RESPOND_TO_DM || 'false').toLowerCase() === 'true'
|
||||||
|
export let livechat = (process.env.RESPOND_TO_LIVECHAT || 'false').toLowerCase() === 'true'
|
||||||
|
export let edited = (process.env.RESPOND_TO_EDITED || 'false').toLowerCase() === 'true'
|
||||||
|
|
||||||
|
// Message attribute settings
|
||||||
|
export let integrationId = process.env.INTEGRATION_ID || 'js.SDK'
|
||||||
|
|
||||||
|
// Cache settings
|
||||||
|
export let roomCacheMaxSize = parseInt(process.env.ROOM_CACHE_SIZE || '10', 10)
|
||||||
|
export let roomCacheMaxAge = 1000 * parseInt(process.env.ROOM_CACHE_MAX_AGE || '300', 10)
|
||||||
|
export let dmCacheMaxSize = parseInt(process.env.DM_ROOM_CACHE_SIZE || '10', 10)
|
||||||
|
export let dmCacheMaxAge = 1000 * parseInt(process.env.DM_ROOM_CACHE_MAX_AGE || '100', 10)
|
@ -0,0 +1 @@
|
|||||||
|
declare module 'asteroid'
|
@ -0,0 +1 @@
|
|||||||
|
declare module 'node-rest-client'
|
@ -0,0 +1 @@
|
|||||||
|
declare module 'asteroid-immutable-collections-mixin'
|
Binary file not shown.
@ -0,0 +1,35 @@
|
|||||||
|
import { INewUserAPI } from './interfaces'
|
||||||
|
|
||||||
|
// The API user, should be provisioned on build with local Rocket.Chat
|
||||||
|
export const apiUser: INewUserAPI = {
|
||||||
|
username: process.env.ADMIN_USERNAME || 'admin',
|
||||||
|
password: process.env.ADMIN_PASS || 'pass'
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Bot user, will attempt to login and run methods in tests
|
||||||
|
export const botUser: INewUserAPI = {
|
||||||
|
email: 'bot@localhost',
|
||||||
|
name: 'Bot',
|
||||||
|
password: process.env.ROCKETCHAT_PASSWORD || 'pass',
|
||||||
|
username: process.env.ROCKETCHAT_USER || 'bot',
|
||||||
|
active: true,
|
||||||
|
roles: ['bot'],
|
||||||
|
joinDefaultChannels: true,
|
||||||
|
requirePasswordChange: false,
|
||||||
|
sendWelcomeEmail: false,
|
||||||
|
verified: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Mock user, will send messages via API for the bot to respond to
|
||||||
|
export const mockUser: INewUserAPI = {
|
||||||
|
email: 'mock@localhost',
|
||||||
|
name: 'Mock User',
|
||||||
|
password: 'mock',
|
||||||
|
username: 'mock',
|
||||||
|
active: true,
|
||||||
|
roles: ['user'],
|
||||||
|
joinDefaultChannels: true,
|
||||||
|
requirePasswordChange: false,
|
||||||
|
sendWelcomeEmail: false,
|
||||||
|
verified: true
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/** Payload structure for `chat.postMessage` endpoint */
|
||||||
|
export interface IMessageAPI {
|
||||||
|
roomId: string // The room id of where the message is to be sent
|
||||||
|
channel?: string // The channel name with the prefix in front of it
|
||||||
|
text?: string // The text of the message to send, is optional because of attachments
|
||||||
|
alias?: string // This will cause the messenger name to appear as the given alias, but username will still display
|
||||||
|
emoji?: string // If provided, this will make the avatar on this message be an emoji
|
||||||
|
avatar?: string // If provided, this will make the avatar use the provided image url
|
||||||
|
attachments?: IAttachmentAPI[] // See attachment interface below
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Payload structure for `chat.update` endpoint */
|
||||||
|
export interface IMessageUpdateAPI {
|
||||||
|
roomId: string // The room id of where the message is
|
||||||
|
msgId: string // The message id to update
|
||||||
|
text: string // Updated text for the message
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message receipt returned after send (not the same as sent object) */
|
||||||
|
export interface IMessageReceiptAPI {
|
||||||
|
_id: string // ID of sent message
|
||||||
|
rid: string // Room ID of sent message
|
||||||
|
alias: string // ?
|
||||||
|
msg: string // Content of message
|
||||||
|
parseUrls: boolean // URL parsing enabled on message hooks
|
||||||
|
groupable: boolean // Grouping message enabled
|
||||||
|
ts: string // Timestamp of message creation
|
||||||
|
u: { // User details of sender
|
||||||
|
_id: string
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
_updatedAt: string // Time message last updated
|
||||||
|
editedAt?: string // Time updated by edit
|
||||||
|
editedBy?: { // User details for the updater
|
||||||
|
_id: string
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Payload structure for message attachments */
|
||||||
|
export interface IAttachmentAPI {
|
||||||
|
color?: string // The color you want the order on the left side to be, any value background-css supports
|
||||||
|
text?: string // The text to display for this attachment, it is different than the message text
|
||||||
|
ts?: string // ISO timestamp, displays the time next to the text portion
|
||||||
|
thumb_url?: string // An image that displays to the left of the text, looks better when this is relatively small
|
||||||
|
message_link?: string // Only applicable if the ts is provided, as it makes the time clickable to this link
|
||||||
|
collapsed?: boolean // Causes the image, audio, and video sections to be hiding when collapsed is true
|
||||||
|
author_name?: string // Name of the author
|
||||||
|
author_link?: string // Providing this makes the author name clickable and points to this link
|
||||||
|
author_icon?: string // Displays a tiny icon to the left of the author's name
|
||||||
|
title?: string // Title to display for this attachment, displays under the author
|
||||||
|
title_link?: string // Providing this makes the title clickable, pointing to this link
|
||||||
|
title_link_download_true?: string // When this is true, a download icon appears and clicking this saves the link to file
|
||||||
|
image_url?: string // The image to display, will be “big” and easy to see
|
||||||
|
audio_url?: string // Audio file to play, only supports what html audio does
|
||||||
|
video_url?: string // Video file to play, only supports what html video does
|
||||||
|
fields?: IAttachmentFieldAPI[] // An array of Attachment Field Objects
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload structure for attachment field object
|
||||||
|
* The field property of the attachments allows for “tables” or “columns” to be displayed on messages
|
||||||
|
*/
|
||||||
|
export interface IAttachmentFieldAPI {
|
||||||
|
short?: boolean // Whether this field should be a short field
|
||||||
|
title: string // The title of this field
|
||||||
|
value: string // The value of this field, displayed underneath the title value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result structure for message endpoints */
|
||||||
|
export interface IMessageResultAPI {
|
||||||
|
ts: number // Seconds since unix epoch
|
||||||
|
channel: string // Name of channel without prefix
|
||||||
|
message: IMessageReceiptAPI // Sent message
|
||||||
|
success: boolean // Send status
|
||||||
|
}
|
||||||
|
|
||||||
|
/** User object structure for creation endpoints */
|
||||||
|
export interface INewUserAPI {
|
||||||
|
email?: string // Email address
|
||||||
|
name?: string // Full name
|
||||||
|
password: string // User pass
|
||||||
|
username: string // Username
|
||||||
|
active?: true // Subscription is active
|
||||||
|
roles?: string[] // Role IDs
|
||||||
|
joinDefaultChannels?: boolean // Auto join channels marked as default
|
||||||
|
requirePasswordChange?: boolean // Direct to password form on next login
|
||||||
|
sendWelcomeEmail?: boolean // Send new credentials in email
|
||||||
|
verified?: true // Email address verification status
|
||||||
|
}
|
||||||
|
|
||||||
|
/** User object structure for queries (not including admin access level) */
|
||||||
|
export interface IUserAPI {
|
||||||
|
_id: string // MongoDB user doc ID
|
||||||
|
type: string // user / bot ?
|
||||||
|
status: string // online | offline
|
||||||
|
active: boolean // Subscription is active
|
||||||
|
name: string // Full name
|
||||||
|
utcOffset: number // Hours off UTC/GMT
|
||||||
|
username: string // Username
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result structure for user data request (by non-admin) */
|
||||||
|
export interface IUserResultAPI {
|
||||||
|
user: IUserAPI // The requested user
|
||||||
|
success: boolean // Status of request
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Room object structure */
|
||||||
|
export interface IRoomAPI {
|
||||||
|
_id: string // Room ID
|
||||||
|
_updatedAt: string // ISO timestamp
|
||||||
|
t: 'c' | 'p' | 'd' | 'l' // Room type (channel, private, direct, livechat)
|
||||||
|
msgs: number // Count of messages in room
|
||||||
|
ts: string // ISO timestamp (current time in room?)
|
||||||
|
meta: {
|
||||||
|
revision: number // ??
|
||||||
|
created: number // Unix ms>epoch time
|
||||||
|
version: number // ??
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Channel result schema */
|
||||||
|
export interface IChannelAPI {
|
||||||
|
_id: string // Channel ID
|
||||||
|
name: string // Channel name
|
||||||
|
t: 'c' | 'p' | 'l' // Channel type (channel always c)
|
||||||
|
msgs: number // Count of messages in room
|
||||||
|
u: {
|
||||||
|
_id: string // Owner user ID
|
||||||
|
username: string // Owner username
|
||||||
|
}
|
||||||
|
ts: string // ISO timestamp (current time in room?)
|
||||||
|
default: boolean // Is default channel
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Group result schema */
|
||||||
|
export interface IGroupAPI {
|
||||||
|
_id: string // Group ID
|
||||||
|
name: string // Group name
|
||||||
|
usernames: string[] // Users in group
|
||||||
|
t: 'c' | 'p' | 'l' // Group type (private always p)
|
||||||
|
msgs: number // Count of messages in room
|
||||||
|
u: {
|
||||||
|
_id: string // Owner user ID
|
||||||
|
username: string // Owner username
|
||||||
|
}
|
||||||
|
ts: string // ISO timestamp (current time in room?)
|
||||||
|
default: boolean // Is default channel (would be false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result structure for room creation (e.g. DM) */
|
||||||
|
export interface IRoomResultAPI {
|
||||||
|
room: IRoomAPI
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result structure for channel creation */
|
||||||
|
export interface IChannelResultAPI {
|
||||||
|
channel: IChannelAPI
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result structure for group creation */
|
||||||
|
export interface IGroupResultAPI {
|
||||||
|
group: IGroupAPI
|
||||||
|
success: boolean
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
/** On require, runs the test utils setup method */
|
||||||
|
import { setup } from './testing'
|
||||||
|
import { silence } from '../lib/log'
|
||||||
|
silence()
|
||||||
|
setup().catch((e) => console.error(e))
|
@ -0,0 +1,50 @@
|
|||||||
|
// Test script uses standard methods and env config to connect and log streams
|
||||||
|
import { botUser } from './config'
|
||||||
|
import { IMessage } from '../config/messageInterfaces'
|
||||||
|
import { api, driver } from '..'
|
||||||
|
const delay = (ms: number) => new Promise((resolve, reject) => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
// Start subscription to log message stream (used for e2e test and demo)
|
||||||
|
async function start () {
|
||||||
|
await driver.connect()
|
||||||
|
await driver.login({ username: botUser.username, password: botUser.password })
|
||||||
|
await driver.subscribeToMessages()
|
||||||
|
await driver.respondToMessages((err, msg, msgOpts) => {
|
||||||
|
if (err) throw err
|
||||||
|
console.log('[respond]', JSON.stringify(msg), JSON.stringify(msgOpts))
|
||||||
|
demo(msg).catch((e) => console.error(e))
|
||||||
|
}, {
|
||||||
|
rooms: ['general'],
|
||||||
|
allPublic: false,
|
||||||
|
dm: true,
|
||||||
|
edited: true,
|
||||||
|
livechat: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo bot-style interactions
|
||||||
|
// A: Listen for "tell everyone <something>" and send that something to everyone
|
||||||
|
// B: Listen for "who's online" and tell that person who's online
|
||||||
|
async function demo (message: IMessage) {
|
||||||
|
console.log(message)
|
||||||
|
if (!message.msg) return
|
||||||
|
if (/tell everyone/i.test(message.msg)) {
|
||||||
|
const match = message.msg.match(/tell everyone (.*)/i)
|
||||||
|
if (!match || !match[1]) return
|
||||||
|
const sayWhat = `@${message.u!.username} says "${match[1]}"`
|
||||||
|
const usernames = await api.users.allNames()
|
||||||
|
for (let username of usernames) {
|
||||||
|
if (username !== botUser.username) {
|
||||||
|
const toWhere = await driver.getDirectMessageRoomId(username)
|
||||||
|
await driver.sendToRoomId(sayWhat, toWhere) // DM ID hax
|
||||||
|
await delay(200) // delay to prevent rate-limit error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (/who\'?s online/i.test(message.msg)) {
|
||||||
|
const names = await api.users.onlineNames()
|
||||||
|
const niceNames = names.join(', ').replace(/, ([^,]*)$/, ' and $1')
|
||||||
|
await driver.sendToRoomId(niceNames + ' are online', message.rid!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start().catch((e) => console.error(e))
|
@ -0,0 +1,214 @@
|
|||||||
|
import { get, post, login, logout } from '../lib/api'
|
||||||
|
import { apiUser, botUser, mockUser } from './config'
|
||||||
|
import {
|
||||||
|
IMessageAPI,
|
||||||
|
IMessageUpdateAPI,
|
||||||
|
IMessageResultAPI,
|
||||||
|
INewUserAPI,
|
||||||
|
IUserResultAPI,
|
||||||
|
IRoomResultAPI,
|
||||||
|
IChannelResultAPI,
|
||||||
|
IGroupResultAPI,
|
||||||
|
IMessageReceiptAPI
|
||||||
|
} from './interfaces'
|
||||||
|
import { IMessage } from '../config/messageInterfaces'
|
||||||
|
|
||||||
|
/** Define common attributes for DRY tests */
|
||||||
|
export const testChannelName = 'tests'
|
||||||
|
export const testPrivateName = 'p-tests'
|
||||||
|
|
||||||
|
/** Get information about a user */
|
||||||
|
export async function userInfo (username: string): Promise<IUserResultAPI> {
|
||||||
|
return get('users.info', { username }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a user and catch the error if they exist already */
|
||||||
|
export async function createUser (user: INewUserAPI): Promise<IUserResultAPI> {
|
||||||
|
return post('users.create', user, true, /already in use/i)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get information about a channel */
|
||||||
|
export async function channelInfo (query: { roomName?: string, roomId?: string }): Promise<IChannelResultAPI> {
|
||||||
|
return get('channels.info', query, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get information about a private group */
|
||||||
|
export async function privateInfo (query: { roomName?: string, roomId?: string }): Promise<IGroupResultAPI> {
|
||||||
|
return get('groups.info', query, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the last messages sent to a channel (in last 10 minutes) */
|
||||||
|
export async function lastMessages (roomId: string, count: number = 1): Promise<IMessage[]> {
|
||||||
|
const now = new Date()
|
||||||
|
const latest = now.toISOString()
|
||||||
|
const oldest = new Date(now.setMinutes(now.getMinutes() - 10)).toISOString()
|
||||||
|
return (await get('channels.history', { roomId, latest, oldest, count })).messages
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a room for tests and catch the error if it exists already */
|
||||||
|
export async function createChannel (
|
||||||
|
name: string,
|
||||||
|
members: string[] = [],
|
||||||
|
readOnly: boolean = false
|
||||||
|
): Promise<IChannelResultAPI> {
|
||||||
|
return post('channels.create', { name, members, readOnly }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a private group / room and catch if exists already */
|
||||||
|
export async function createPrivate (
|
||||||
|
name: string,
|
||||||
|
members: string[] = [],
|
||||||
|
readOnly: boolean = false
|
||||||
|
): Promise<IGroupResultAPI> {
|
||||||
|
return post('groups.create', { name, members, readOnly }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send message from mock user to channel for tests to listen and respond */
|
||||||
|
/** @todo Sometimes the post request completes before the change event emits
|
||||||
|
* the message to the streamer. That's why the interval is used for proof
|
||||||
|
* of receipt. It would be better for the endpoint to not resolve until
|
||||||
|
* server side handling is complete. Would require PR to core.
|
||||||
|
*/
|
||||||
|
export async function sendFromUser (payload: any): Promise<IMessageResultAPI> {
|
||||||
|
const user = await login({ username: mockUser.username, password: mockUser.password })
|
||||||
|
const endpoint = (payload.roomId && payload.roomId.indexOf(user.data.userId) !== -1)
|
||||||
|
? 'dm.history'
|
||||||
|
: 'channels.history'
|
||||||
|
const roomId = (payload.roomId)
|
||||||
|
? payload.roomId
|
||||||
|
: (await channelInfo({ roomName: testChannelName })).channel._id
|
||||||
|
const messageDefaults: IMessageAPI = { roomId }
|
||||||
|
const data: IMessageAPI = Object.assign({}, messageDefaults, payload)
|
||||||
|
const oldest = new Date().toISOString()
|
||||||
|
const result = await post('chat.postMessage', data, true)
|
||||||
|
const proof = new Promise((resolve, reject) => {
|
||||||
|
let looked = 0
|
||||||
|
const look = setInterval(async () => {
|
||||||
|
const { messages } = await get(endpoint, { roomId, oldest })
|
||||||
|
const found = messages.some((message: IMessageReceiptAPI) => {
|
||||||
|
return result.message._id === message._id
|
||||||
|
})
|
||||||
|
if (found || looked > 10) {
|
||||||
|
clearInterval(look)
|
||||||
|
if (found) resolve()
|
||||||
|
else reject('API send from user, proof of receipt timeout')
|
||||||
|
}
|
||||||
|
looked++
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
await proof
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Leave user from room, to generate `ul` message (test channel by default) */
|
||||||
|
export async function leaveUser (room: { id?: string, name?: string } = {}): Promise<Boolean> {
|
||||||
|
await login({ username: mockUser.username, password: mockUser.password })
|
||||||
|
if (!room.id && !room.name) room.name = testChannelName
|
||||||
|
const roomId = (room.id)
|
||||||
|
? room.id
|
||||||
|
: (await channelInfo({ roomName: room.name })).channel._id
|
||||||
|
return post('channels.leave', { roomId })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Invite user to room, to generate `au` message (test channel by default) */
|
||||||
|
export async function inviteUser (room: { id?: string, name?: string } = {}): Promise<Boolean> {
|
||||||
|
let mockInfo = await userInfo(mockUser.username)
|
||||||
|
await login({ username: apiUser.username, password: apiUser.password })
|
||||||
|
if (!room.id && !room.name) room.name = testChannelName
|
||||||
|
const roomId = (room.id)
|
||||||
|
? room.id
|
||||||
|
: (await channelInfo({ roomName: room.name })).channel._id
|
||||||
|
return post('channels.invite', { userId: mockInfo.user._id, roomId })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @todo : Join user into room (enter) to generate `uj` message type. */
|
||||||
|
|
||||||
|
/** Update message sent from mock user */
|
||||||
|
export async function updateFromUser (payload: IMessageUpdateAPI): Promise<IMessageResultAPI> {
|
||||||
|
await login({ username: mockUser.username, password: mockUser.password })
|
||||||
|
return post('chat.update', payload, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a direct message session with the mock user */
|
||||||
|
export async function setupDirectFromUser (): Promise<IRoomResultAPI> {
|
||||||
|
await login({ username: mockUser.username, password: mockUser.password })
|
||||||
|
return post('im.create', { username: botUser.username }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialise testing instance with the required users for SDK/bot tests */
|
||||||
|
export async function setup () {
|
||||||
|
console.log('\nPreparing instance for tests...')
|
||||||
|
try {
|
||||||
|
// Verify API user can login
|
||||||
|
const loginInfo = await login(apiUser)
|
||||||
|
if (loginInfo.status !== 'success') {
|
||||||
|
throw new Error(`API user (${apiUser.username}) could not login`)
|
||||||
|
} else {
|
||||||
|
console.log(`API user (${apiUser.username}) logged in`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify or create user for bot
|
||||||
|
let botInfo = await userInfo(botUser.username)
|
||||||
|
if (!botInfo || !botInfo.success) {
|
||||||
|
console.log(`Bot user (${botUser.username}) not found`)
|
||||||
|
botInfo = await createUser(botUser)
|
||||||
|
if (!botInfo.success) {
|
||||||
|
throw new Error(`Bot user (${botUser.username}) could not be created`)
|
||||||
|
} else {
|
||||||
|
console.log(`Bot user (${botUser.username}) created`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Bot user (${botUser.username}) exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify or create mock user for talking to bot
|
||||||
|
let mockInfo = await userInfo(mockUser.username)
|
||||||
|
if (!mockInfo || !mockInfo.success) {
|
||||||
|
console.log(`Mock user (${mockUser.username}) not found`)
|
||||||
|
mockInfo = await createUser(mockUser)
|
||||||
|
if (!mockInfo.success) {
|
||||||
|
throw new Error(`Mock user (${mockUser.username}) could not be created`)
|
||||||
|
} else {
|
||||||
|
console.log(`Mock user (${mockUser.username}) created`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Mock user (${mockUser.username}) exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify or create channel for tests
|
||||||
|
let testChannelInfo = await channelInfo({ roomName: testChannelName })
|
||||||
|
if (!testChannelInfo || !testChannelInfo.success) {
|
||||||
|
console.log(`Test channel (${testChannelName}) not found`)
|
||||||
|
testChannelInfo = await createChannel(testChannelName, [
|
||||||
|
apiUser.username, botUser.username, mockUser.username
|
||||||
|
])
|
||||||
|
if (!testChannelInfo.success) {
|
||||||
|
throw new Error(`Test channel (${testChannelName}) could not be created`)
|
||||||
|
} else {
|
||||||
|
console.log(`Test channel (${testChannelName}) created`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Test channel (${testChannelName}) exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify or create private room for tests
|
||||||
|
let testPrivateInfo = await privateInfo({ roomName: testPrivateName })
|
||||||
|
if (!testPrivateInfo || !testPrivateInfo.success) {
|
||||||
|
console.log(`Test private room (${testPrivateName}) not found`)
|
||||||
|
testPrivateInfo = await createPrivate(testPrivateName, [
|
||||||
|
apiUser.username, botUser.username, mockUser.username
|
||||||
|
])
|
||||||
|
if (!testPrivateInfo.success) {
|
||||||
|
throw new Error(`Test private room (${testPrivateName}) could not be created`)
|
||||||
|
} else {
|
||||||
|
console.log(`Test private room (${testPrivateName}) created`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Test private room (${testPrivateName}) exists`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await logout()
|
||||||
|
} catch (e) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
// Test script uses standard methods and env config to connect and log streams
|
||||||
|
import * as api from '../lib/api'
|
||||||
|
import { silence } from '../lib/log'
|
||||||
|
silence()
|
||||||
|
|
||||||
|
async function users () {
|
||||||
|
console.log(`
|
||||||
|
|
||||||
|
Demo of API user query helpers
|
||||||
|
|
||||||
|
ALL users \`api.users.all()\`:
|
||||||
|
${JSON.stringify(await api.users.all(), null, '\t')}
|
||||||
|
|
||||||
|
ALL usernames \`api.users.allNames()\`:
|
||||||
|
${JSON.stringify(await api.users.allNames(), null, '\t')}
|
||||||
|
|
||||||
|
ALL IDs \`api.users.allIDs()\`:
|
||||||
|
${JSON.stringify(await api.users.allIDs(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE users \`api.users.online()\`:
|
||||||
|
${JSON.stringify(await api.users.online(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE usernames \`api.users.onlineNames()\`:
|
||||||
|
${JSON.stringify(await api.users.onlineNames(), null, '\t')}
|
||||||
|
|
||||||
|
ONLINE IDs \`api.users.onlineIds()\`:
|
||||||
|
${JSON.stringify(await api.users.onlineIds(), null, '\t')}
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
users().catch((e) => console.error(e))
|
@ -0,0 +1,7 @@
|
|||||||
|
--require dotenv/config
|
||||||
|
--require ts-node/register
|
||||||
|
--require source-map-support/register
|
||||||
|
--recursive
|
||||||
|
--timeout 3000
|
||||||
|
--reporter list
|
||||||
|
--exit
|
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Basic Options */
|
||||||
|
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [ "node_modules/@types" ], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [ "node" ], /* Type declaration files to be included in compilation. */
|
||||||
|
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
"inlineSources": true /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["src/**/*.spec.ts", "node_modules"]
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "tslint-config-standard",
|
||||||
|
"linterOptions": {
|
||||||
|
"exclude": [
|
||||||
|
"**/*.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"semicolon": [
|
||||||
|
true,
|
||||||
|
"never"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"includeDeclarations": "true",
|
||||||
|
"excludeExternals": "true",
|
||||||
|
"mode": "modules",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES6",
|
||||||
|
"out": "./docs",
|
||||||
|
"theme": "default",
|
||||||
|
"exclude": "**/*.spec.ts"
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = function (wallaby) {
|
||||||
|
return {
|
||||||
|
name: 'Rocket.Chat.js.SDK',
|
||||||
|
files: [
|
||||||
|
"src/**/*.ts",
|
||||||
|
{ pattern: "src/**/*.spec.ts", ignore: true },
|
||||||
|
{ pattern: "src/**/*.d.ts", ignore: true },
|
||||||
|
],
|
||||||
|
tests: ["src/**/*.spec.ts"],
|
||||||
|
testFramework: 'mocha',
|
||||||
|
env: {
|
||||||
|
type: 'node'
|
||||||
|
},
|
||||||
|
compilers: {
|
||||||
|
'**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' })
|
||||||
|
},
|
||||||
|
debug: true,
|
||||||
|
slowTestThreshold: 200,
|
||||||
|
delays: {
|
||||||
|
run: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue