@slack/client
Advanced tools
Comparing version 3.9.0 to 3.10.0
@@ -196,5 +196,7 @@ --- | ||
rtm.on(RTM_EVENTS.MESSAGE, function handleRtmMessage(message) { | ||
var channel = "#general"; //could also be a channel, group, DM, or user ID (C1234), or a username (@don) | ||
rtm.sendMessage("Hello <@" + message.user + ">!", message.channel); | ||
if (message.text === "Hello.") { | ||
var channel = "#general"; //could also be a channel, group, DM, or user ID (C1234), or a username (@don) | ||
rtm.sendMessage("Hello <@" + message.user + ">!", message.channel); | ||
} | ||
}); | ||
``` |
@@ -12,3 +12,2 @@ --- | ||
* [.transport](#BaseAPIClient+transport) : <code>function</code> | ||
* [.userAgent](#BaseAPIClient+userAgent) : <code>string</code> | ||
* [.retryConfig](#BaseAPIClient+retryConfig) | ||
@@ -31,3 +30,2 @@ * [.logger](#BaseAPIClient+logger) : <code>function</code> | ||
| opts.slackAPIUrl | <code>String</code> | The Slack API URL. | | ||
| opts.userAgent | <code>String</code> | The user-agent to use, defaults to node-slack. | | ||
| opts.transport | <code>function</code> | Function to call to make an HTTP call to the Slack API. | | ||
@@ -47,10 +45,7 @@ | [opts.logLevel] | <code>string</code> | The log level for the logger. | | ||
**Kind**: instance property of <code>[BaseAPIClient](#BaseAPIClient)</code> | ||
<a name="BaseAPIClient+userAgent"></a> | ||
### baseAPIClient.userAgent : <code>string</code> | ||
**Kind**: instance property of <code>[BaseAPIClient](#BaseAPIClient)</code> | ||
<a name="BaseAPIClient+retryConfig"></a> | ||
### baseAPIClient.retryConfig | ||
Default to attempting 5 retries within 5 minutes, with exponential backoff. | ||
Default to retrying forever with an exponential backoff, capped at thirty | ||
minutes but with some randomization. | ||
@@ -57,0 +52,0 @@ **Kind**: instance property of <code>[BaseAPIClient](#BaseAPIClient)</code> |
@@ -16,4 +16,4 @@ --- | ||
* [.activeTeamId](#RTMClient+activeTeamId) : <code>string</code> | ||
* [.dataStore](#RTMClient+dataStore) : <code>[SlackDataStore](#SlackDataStore)</code> | ||
* [._pingTimer](#RTMClient+_pingTimer) : <code>?</code> | ||
* [.dataStore](#RTMClient+dataStore) : <code>[SlackDataStore](#SlackDataStore)</code> | ||
* [._createFacets()](#RTMClient+_createFacets) | ||
@@ -30,4 +30,5 @@ * [.start(opts)](#RTMClient+start) | ||
* [.handleWsError(err)](#RTMClient+handleWsError) | ||
* [.handleWsClose()](#RTMClient+handleWsClose) | ||
* [.handleWsClose(code, reason)](#RTMClient+handleWsClose) | ||
* [._handlePong(message)](#RTMClient+_handlePong) | ||
* [._maybeKeepAlive(message)](#RTMClient+_maybeKeepAlive) | ||
* [._handleHello()](#RTMClient+_handleHello) | ||
@@ -37,2 +38,3 @@ * [.sendMessage(text, channelId, [optCb])](#RTMClient+sendMessage) | ||
* [.sendTyping(channelId)](#RTMClient+sendTyping) | ||
* [.subscribePresence(userIds)](#RTMClient+subscribePresence) | ||
* [.send(message, [optCb])](#RTMClient+send) | ||
@@ -43,16 +45,20 @@ | ||
### new RTMClient(token, opts) | ||
Creates a new instance of RTM client. | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| token | <code>String</code> | | | ||
| opts | <code>object</code> | | | ||
| opts.socketFn | <code>function</code> | A function to call, passing in a websocket URL, that should return a websocket instance connected to that URL. | | ||
| opts.dataStore | <code>object</code> | A store to cache Slack info, e.g. channels, users etc. in. If you don't want a store, pass false or null as the value for this. | | ||
| opts.autoReconnect | <code>boolean</code> | Whether or not to automatically reconnect when the connection closes. | | ||
| opts.maxReconnectionAttempts | <code>number</code> | | | ||
| opts.reconnectionBackoff | <code>number</code> | | | ||
| opts.wsPingInterval | <code>number</code> | | | ||
| opts.maxPongInterval | <code>number</code> | | | ||
| opts.logLevel | <code>string</code> | The log level for the logger. | | ||
| opts.logger | <code>function</code> | Function to use for log calls, takes (logLevel, logString) parameters. | | ||
| token | <code>String</code> | The token to use for connecting | | ||
| opts | <code>Object</code> | | | ||
| opts.socketFn | <code>function</code> | A function to call, passing in a websocket URL, that should return a websocket instance connected to that URL | | ||
| opts.dataStore | <code>Object</code> | A store to cache Slack info, e.g. channels, users etc. in. Pass null or false to use no store | | ||
| opts.autoReconnect | <code>Boolean</code> | Whether or not to automatically reconnect when the connection closes. Defaults to true | | ||
| opts.useRtmConnect | <code>Boolean</code> | True to use rtm.connect rather than rtm.start | | ||
| opts.retryConfig | <code>Object</code> | The retry policy to use, defaults to forever with exponential backoff {@see https://github.com/SEAPUNK/node-retry} | | ||
| opts.maxReconnectionAttempts | <code>Number</code> | DEPRECATED: Use retryConfig instead | | ||
| opts.reconnectionBackoff | <code>Number</code> | DEPRECATED: Use retryConfig instead | | ||
| opts.wsPingInterval | <code>Number</code> | The time to wait between pings with the server | | ||
| opts.maxPongInterval | <code>Number</code> | The max time (in ms) to wait for a pong before reconnecting | | ||
| opts.logLevel | <code>String</code> | The log level for the logger | | ||
| opts.logger | <code>function</code> | Function to use for log calls, takes (logLevel, logString) parameters | | ||
@@ -89,2 +95,6 @@ <a name="RTMClient+_socketFn"></a> | ||
**Kind**: instance property of <code>[RTMClient](#RTMClient)</code> | ||
<a name="RTMClient+dataStore"></a> | ||
### rtmClient.dataStore : <code>[SlackDataStore](#SlackDataStore)</code> | ||
**Kind**: instance property of <code>[RTMClient](#RTMClient)</code> | ||
<a name="RTMClient+_pingTimer"></a> | ||
@@ -96,6 +106,2 @@ | ||
**Kind**: instance property of <code>[RTMClient](#RTMClient)</code> | ||
<a name="RTMClient+dataStore"></a> | ||
### rtmClient.dataStore : <code>[SlackDataStore](#SlackDataStore)</code> | ||
**Kind**: instance property of <code>[RTMClient](#RTMClient)</code> | ||
<a name="RTMClient+_createFacets"></a> | ||
@@ -108,2 +114,4 @@ | ||
### rtmClient.start(opts) | ||
Begin an RTM session. | ||
**Kind**: instance method of <code>[RTMClient](#RTMClient)</code> | ||
@@ -138,3 +146,3 @@ | ||
| --- | --- | --- | | ||
| socketUrl | <code>string</code> | The URL of the websocket to connect to. | | ||
| socketUrl | <code>String</code> | The URL of the websocket to connect to. | | ||
@@ -148,6 +156,6 @@ <a name="RTMClient+disconnect"></a> | ||
| Param | | ||
| --- | | ||
| optReason | | ||
| optCode | | ||
| Param | Type | | ||
| --- | --- | | ||
| optReason | <code>Error</code> | | ||
| optCode | <code>Number</code> | | ||
@@ -157,2 +165,4 @@ <a name="RTMClient+reconnect"></a> | ||
### rtmClient.reconnect() | ||
Attempts to reconnect to the websocket by retrying the start method. | ||
**Kind**: instance method of <code>[RTMClient](#RTMClient)</code> | ||
@@ -202,4 +212,12 @@ <a name="RTMClient+handleWsOpen"></a> | ||
### rtmClient.handleWsClose() | ||
### rtmClient.handleWsClose(code, reason) | ||
Occurs when the websocket closes. | ||
**Kind**: instance method of <code>[RTMClient](#RTMClient)</code> | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| code | <code>String</code> | The error code | | ||
| reason | <code>String</code> | The reason for closing | | ||
<a name="RTMClient+_handlePong"></a> | ||
@@ -216,5 +234,19 @@ | ||
<a name="RTMClient+_maybeKeepAlive"></a> | ||
### rtmClient._maybeKeepAlive(message) | ||
If we haven't received a pong in too long, treat any incoming message as a pong | ||
to prevent unnecessary disconnects. | ||
**Kind**: instance method of <code>[RTMClient](#RTMClient)</code> | ||
| Param | Type | | ||
| --- | --- | | ||
| message | <code>Object</code> | | ||
<a name="RTMClient+_handleHello"></a> | ||
### rtmClient._handleHello() | ||
Occurs when the socket connection is opened. | ||
Begin ping-pong with the server. | ||
[hello](https://api.slack.com/events/hello) | ||
@@ -259,2 +291,14 @@ | ||
<a name="RTMClient+subscribePresence"></a> | ||
### rtmClient.subscribePresence(userIds) | ||
Subscribes this socket to presence changes for only the given `userIds`. | ||
This requires `presence_sub` to have been passed as an argument to `start`. | ||
**Kind**: instance method of <code>[RTMClient](#RTMClient)</code> | ||
| Param | Type | Description | | ||
| --- | --- | --- | | ||
| userIds | <code>Array</code> | The user IDs to subscribe to | | ||
<a name="RTMClient+send"></a> | ||
@@ -261,0 +305,0 @@ |
@@ -23,2 +23,3 @@ --- | ||
* [.getDMByName(name)](#SlackDataStore+getDMByName) ⇒ <code>Object</code> | ||
* [.getDMByUserId(id)](#SlackDataStore+getDMByUserId) ⇒ <code>Object</code> | ||
* [.getBotById(botId)](#SlackDataStore+getBotById) ⇒ <code>Object</code> | ||
@@ -191,2 +192,13 @@ * [.getBotByName(name)](#SlackDataStore+getBotByName) ⇒ <code>Object</code> | ||
<a name="SlackDataStore+getDMByUserId"></a> | ||
### slackDataStore.getDMByUserId(id) ⇒ <code>Object</code> | ||
Returns the DM object between the registered user and the user with the supplied id. | ||
**Kind**: instance method of <code>[SlackDataStore](#SlackDataStore)</code> | ||
| Param | | ||
| --- | | ||
| id | | ||
<a name="SlackDataStore+getBotById"></a> | ||
@@ -193,0 +205,0 @@ |
@@ -26,2 +26,3 @@ --- | ||
* [.getDMByName()](#SlackMemoryDataStore+getDMByName) | ||
* [.getDMByUserId()](#SlackMemoryDataStore+getDMByUserId) | ||
* [.getBotById()](#SlackMemoryDataStore+getBotById) | ||
@@ -119,2 +120,6 @@ * [.getBotByName()](#SlackMemoryDataStore+getBotByName) | ||
**Kind**: instance method of <code>[SlackMemoryDataStore](#SlackMemoryDataStore)</code> | ||
<a name="SlackMemoryDataStore+getDMByUserId"></a> | ||
### slackMemoryDataStore.getDMByUserId() | ||
**Kind**: instance method of <code>[SlackMemoryDataStore](#SlackMemoryDataStore)</code> | ||
<a name="SlackMemoryDataStore+getBotById"></a> | ||
@@ -121,0 +126,0 @@ |
var events = require('./lib/clients/events'); | ||
var retryPolicies = require('./lib/clients/retry-policies'); | ||
@@ -8,2 +9,3 @@ module.exports = { | ||
LegacyRtmClient: require('./lib/clients/default/legacy-rtm'), | ||
MemoryDataStore: require('./lib/data-store/memory-data-store'), | ||
CLIENT_EVENTS: { | ||
@@ -15,3 +17,3 @@ WEB: events.CLIENT_EVENTS.WEB, | ||
RTM_MESSAGE_SUBTYPES: events.RTM_MESSAGE_SUBTYPES, | ||
MemoryDataStore: require('./lib/data-store/memory-data-store') | ||
RETRY_POLICIES: retryPolicies | ||
}; |
@@ -11,2 +11,3 @@ /** | ||
var partial = require('lodash').partial; | ||
var pick = require('lodash').pick; | ||
var retry = require('retry'); | ||
@@ -54,5 +55,7 @@ | ||
/** | ||
* Default to attempting 5 retries within 5 minutes, with exponential backoff. | ||
* Default to retrying forever with an exponential backoff, capped at thirty | ||
* minutes but with some randomization. | ||
*/ | ||
this.retryConfig = clientOpts.retryConfig || retryPolicies.FIVE_RETRIES_IN_FIVE_MINUTES; | ||
this.retryConfig = clientOpts.retryConfig || | ||
retryPolicies.RETRY_FOREVER_EXPONENTIAL_CAPPED_RANDOM; | ||
@@ -64,3 +67,3 @@ /** | ||
*/ | ||
this.requestQueue = async.priorityQueue( | ||
this.requestQueue = async.queue( | ||
bind(this._callTransport, this), | ||
@@ -124,2 +127,3 @@ clientOpts.maxRequestConcurrency || 3 | ||
retryOp.attempt(function attemptTransportCall() { | ||
_this.logger('verbose', 'Retrying', pick(task.args, 'url')); | ||
_this.transport(task.args, partial(callTransport.handleTransportResponse, retryArgs)); | ||
@@ -134,5 +138,5 @@ }); | ||
* @param {String} endpoint The API endpoint to send to. | ||
* @param {Object=} apiArgs | ||
* @param {Object=} apiOptArgs | ||
* @param {function=} optCb The callback to run on completion. | ||
* @param {Object} apiArgs | ||
* @param {Object} apiOptArgs | ||
* @param {Function} optCb The callback to run on completion. | ||
* @private | ||
@@ -139,0 +143,0 @@ */ |
@@ -79,6 +79,4 @@ /** | ||
optArgs = apiOptArgs; | ||
} else { | ||
if (isFunction(apiOptArgs)) { | ||
cb = apiOptArgs; | ||
} | ||
} else if (isFunction(apiOptArgs)) { | ||
cb = apiOptArgs; | ||
} | ||
@@ -85,0 +83,0 @@ |
@@ -7,2 +7,33 @@ /** | ||
/** | ||
* Keep retrying forever, with an exponential backoff. | ||
*/ | ||
var RETRY_FOREVER_EXPONENTIAL = { | ||
forever: true | ||
}; | ||
/** | ||
* Same as {@link RETRY_FOREVER_EXPONENTIAL}, but capped at 30 minutes. | ||
*/ | ||
var RETRY_FOREVER_EXPONENTIAL_CAPPED = { | ||
forever: true, | ||
maxTimeout: 30 * 60 * 1000 | ||
}; | ||
/** | ||
* Same as {@link RETRY_FOREVER_EXPONENTIAL_CAPPED}, but with randomization to | ||
* prevent stampeding herds. | ||
*/ | ||
var RETRY_FOREVER_EXPONENTIAL_CAPPED_RANDOM = { | ||
forever: true, | ||
maxTimeout: 30 * 60 * 1000, | ||
randomize: true | ||
}; | ||
/** | ||
* Short & sweet, five retries in five minutes and then bail. | ||
*/ | ||
var FIVE_RETRIES_IN_FIVE_MINUTES = { | ||
@@ -14,2 +45,5 @@ retries: 5, | ||
/** | ||
* This policy is just to keep the tests running fast. | ||
*/ | ||
var TEST_RETRY_POLICY = { | ||
@@ -21,3 +55,20 @@ minTimeout: 0, | ||
module.exports.RETRY_FOREVER_EXPONENTIAL = RETRY_FOREVER_EXPONENTIAL; | ||
module.exports.RETRY_FOREVER_EXPONENTIAL_CAPPED = RETRY_FOREVER_EXPONENTIAL_CAPPED; | ||
module.exports.RETRY_FOREVER_EXPONENTIAL_CAPPED_RANDOM = RETRY_FOREVER_EXPONENTIAL_CAPPED_RANDOM; | ||
module.exports.FIVE_RETRIES_IN_FIVE_MINUTES = FIVE_RETRIES_IN_FIVE_MINUTES; | ||
module.exports.TEST_RETRY_POLICY = TEST_RETRY_POLICY; | ||
/** | ||
* Uses legacy RTM client options to make a retry policy. | ||
* @param {Object} opts | ||
* @param {Number} opts.maxReconnectionAttempts Maximum number of attempts before emitting error | ||
* @param {Number} opts.reconnectionBackoff Time to wait between attempts | ||
*/ | ||
module.exports.retryPolicyFromOptions = function retryPolicyFromOptions(opts) { | ||
return opts.maxReconnectionAttempts || opts.reconnectionBackoff ? { | ||
retries: opts.maxReconnectionAttempts || 10, | ||
minTimeout: opts.reconnectionBackoff || 3000, | ||
maxTimeout: opts.reconnectionBackoff || 3000 | ||
} : null; | ||
}; |
/** | ||
* | ||
* See [the RTM client events](../events/client) for details of the client event lifecycle. | ||
@@ -38,20 +37,24 @@ */ | ||
var wsSocketFn = require('../transports/ws'); | ||
var retryPolicyFromOptions = require('../retry-policies').retryPolicyFromOptions; | ||
/** | ||
* | ||
* @param {String} token | ||
* @param {object?} opts | ||
* @param {Function} opts.socketFn A function to call, passing in a websocket URL, that should | ||
* return a websocket instance connected to that URL. | ||
* @param {object} opts.dataStore A store to cache Slack info, e.g. channels, users etc. in. | ||
* If you don't want a store, pass false or null as the value for this. | ||
* @param {boolean} opts.autoReconnect Whether or not to automatically reconnect when the connection | ||
* closes. | ||
* @param {number} opts.maxReconnectionAttempts | ||
* @param {number} opts.reconnectionBackoff | ||
* @param {number} opts.wsPingInterval | ||
* @param {number} opts.maxPongInterval | ||
* @param {string} opts.logLevel The log level for the logger. | ||
* @param {Function} opts.logger Function to use for log calls, takes (logLevel, logString) | ||
* parameters. | ||
* Creates a new instance of RTM client. | ||
* @param {String} token The token to use for connecting | ||
* @param {Object} opts | ||
* @param {Function} opts.socketFn A function to call, passing in a websocket URL, that should | ||
* return a websocket instance connected to that URL | ||
* @param {Object} opts.dataStore A store to cache Slack info, e.g. channels, users etc. in. | ||
* Pass null or false to use no store | ||
* @param {Boolean} opts.autoReconnect Whether or not to automatically reconnect when the connection | ||
* closes. Defaults to true | ||
* @param {Boolean} opts.useRtmConnect True to use rtm.connect rather than rtm.start | ||
* @param {Object} opts.retryConfig The retry policy to use, defaults to forever with exponential | ||
* backoff {@see https://github.com/SEAPUNK/node-retry} | ||
* @param {Number} opts.maxReconnectionAttempts DEPRECATED: Use retryConfig instead | ||
* @param {Number} opts.reconnectionBackoff DEPRECATED: Use retryConfig instead | ||
* @param {Number} opts.wsPingInterval The time to wait between pings with the server | ||
* @param {Number} opts.maxPongInterval The max time (in ms) to wait for a pong before reconnecting | ||
* @param {String} opts.logLevel The log level for the logger | ||
* @param {Function} opts.logger Function to use for log calls, takes (logLevel, logString) | ||
* parameters | ||
* @constructor | ||
@@ -66,2 +69,7 @@ */ | ||
// Migrate deprecated parameters to a retry policy. | ||
if (!clientOpts.retryConfig) { | ||
clientOpts.retryConfig = retryPolicyFromOptions(clientOpts); | ||
} | ||
BaseAPIClient.call(this, token, clientOpts); | ||
@@ -107,2 +115,9 @@ | ||
/** | ||
* If true, we'll use rtm.connect everywhere in place of rtm.start | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
this._useRtmConnect = clientOpts.useRtmConnect; | ||
if (clientOpts.dataStore instanceof DataStore) { | ||
@@ -117,8 +132,6 @@ this.registerDataStore(clientOpts.dataStore); | ||
this.MAX_RECONNECTION_ATTEMPTS = clientOpts.maxReconnectionAttempts || 10; | ||
this.RECONNECTION_BACKOFF = clientOpts.reconnectionBackoff || 3000; | ||
// NOTE: see the "Ping and Pong" section of https://api.slack.com/rtm | ||
// these are to do with the RTM API level connection and not the underlying ws connection. | ||
this.MAX_PONG_INTERVAL = clientOpts.maxPongInterval || 10000; | ||
// See the "Ping and Pong" section of https://api.slack.com/rtm | ||
// These are to do with the RTM API level connection and not the underlying | ||
// socket connection. | ||
this.MAX_PONG_INTERVAL = clientOpts.maxPongInterval || 20000; | ||
this.WS_PING_INTERVAL = clientOpts.wsPingInterval || 5000; | ||
@@ -159,2 +172,8 @@ | ||
/** | ||
* @type {SlackDataStore} | ||
*/ | ||
RTMClient.prototype.dataStore = undefined; | ||
/** | ||
* The timer repeatedly pinging the server to let it know the client is still alive. | ||
@@ -175,3 +194,3 @@ * @type {?} | ||
/** | ||
* | ||
* A running count of socket connection attempts. | ||
* @type {number} | ||
@@ -192,13 +211,15 @@ * @private | ||
/** | ||
* Whether the server is currently re-connecting. | ||
* @type {boolean} | ||
* Options passed to `start`, for use when reconnecting. | ||
* @type {object} | ||
* @private | ||
*/ | ||
RTMClient.prototype._reconnecting = false; | ||
RTMClient.prototype._startOpts = null; | ||
/** | ||
* @type {SlackDataStore} | ||
* Whether the server is currently reconnecting. | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
RTMClient.prototype.dataStore = undefined; | ||
RTMClient.prototype._reconnecting = false; | ||
@@ -227,3 +248,3 @@ | ||
/** | ||
* | ||
* Begin an RTM session. | ||
* @param {object} opts | ||
@@ -234,7 +255,12 @@ */ | ||
if (!this._connecting) { | ||
this.logger('verbose', 'attempting to connect via the RTM API'); | ||
this.logger('verbose', 'Attempting to connect via the RTM API'); | ||
this.emit(CLIENT_EVENTS.CONNECTING); | ||
this._connecting = true; | ||
this._startOpts = opts; | ||
this._rtm.start(opts, bind(this._onStart, this)); | ||
if (this._useRtmConnect) { | ||
this._rtm.connect(opts, bind(this._onStart, this)); | ||
} else { | ||
this._rtm.start(opts, bind(this._onStart, this)); | ||
} | ||
} | ||
@@ -263,29 +289,41 @@ }; | ||
/** | ||
* | ||
* @param err | ||
* @param data | ||
* Occurs when we've received a response to the API call made in start. | ||
* Connect to the socket using the URL from the response, or if it went wrong, | ||
* check the error and potentially retry or permanently disconnect. | ||
* @param requestError - An error that occurred during the request | ||
* @param data - The response data | ||
* @private | ||
*/ | ||
RTMClient.prototype._onStart = function _onStart(err, data) { | ||
var errMsg; | ||
RTMClient.prototype._onStart = function _onStart(requestError, data) { | ||
// There may have been an HTTP error or an error returned from the Slack API | ||
var error = requestError || data.error; | ||
var startMethod = this._useRtmConnect ? 'rtm.connect' : 'rtm.start'; | ||
var disconnectWithReason = bind(function disconnectWithReason(reason) { | ||
this.logger('error', 'Disconnecting because ' + reason); | ||
this.logger('error', error); | ||
this.disconnect(reason, error); | ||
}, this); | ||
this._connecting = false; | ||
this._reconnecting = false; | ||
if (err || !data.url) { | ||
this.emit(CLIENT_EVENTS.UNABLE_TO_RTM_START, err || data.error); | ||
if (error || (!data || !data.url)) { | ||
this.emit(CLIENT_EVENTS.UNABLE_TO_RTM_START, error); | ||
// Any of these mean this client is unusable, so don't attempt to auto-reconnect | ||
if (data && includes(UNRECOVERABLE_RTM_START_ERRS, data.error)) { | ||
errMsg = 'unrecoverable failure connecting to the RTM API'; | ||
this.logger('error', errMsg + ': ' + data.error); | ||
this.disconnect(errMsg, data.error); | ||
disconnectWithReason(data.error + ' is not recoverable'); | ||
} else { | ||
this.logger('info', 'unable to RTM start, attempting reconnect: ' + err || data.error); | ||
this.authenticated = false; | ||
if (this.autoReconnect) { | ||
this.logger('info', 'Unable to ' + startMethod + ', attempting reconnect'); | ||
this.reconnect(); | ||
} else { | ||
disconnectWithReason('auto-reconnect is disabled'); | ||
} | ||
} | ||
} else { | ||
this.logger('verbose', 'rtm.start successful, attempting to open websocket URL'); | ||
this.logger('verbose', startMethod + ' successful, attempting to open websocket URL'); | ||
this.authenticated = true; | ||
@@ -326,3 +364,3 @@ this.activeUserId = data.self.id; | ||
* Connects to the RTM API. | ||
* @param {string} socketUrl The URL of the websocket to connect to. | ||
* @param {String} socketUrl The URL of the websocket to connect to. | ||
*/ | ||
@@ -342,4 +380,4 @@ RTMClient.prototype.connect = function connect(socketUrl) { | ||
* Disconnects from the RTM API. | ||
* @param optReason | ||
* @param optCode | ||
* @param {Error} optReason | ||
* @param {Number} optCode | ||
*/ | ||
@@ -354,3 +392,3 @@ RTMClient.prototype.disconnect = function disconnect(optErr, optCode) { | ||
/** | ||
* | ||
* Attempts to reconnect to the websocket by retrying the start method. | ||
*/ | ||
@@ -363,31 +401,7 @@ RTMClient.prototype.reconnect = function reconnect() { | ||
// TODO(leah): Update this to remove the reconn logic in the RTM client as it should be covered | ||
// by the web client policy | ||
this._connAttempts++; | ||
if (this._connAttempts > this.MAX_RECONNECTION_ATTEMPTS) { | ||
this.emit( | ||
CLIENT_EVENTS.UNABLE_TO_RTM_START, | ||
'unable to connect to Slack RTM API, failed after max reconnection attempts' | ||
); | ||
} | ||
setTimeout(bind(this.start, this), this._connAttempts * this.RECONNECTION_BACKOFF); | ||
} | ||
}; | ||
this.logger('warn', 'Reconnecting, on attempt', this._connAttempts); | ||
/** | ||
* Pings the remote server to let it know the client is still alive. | ||
* @private | ||
*/ | ||
RTMClient.prototype._pingServer = function _pingServer() { | ||
var pongInterval; | ||
if (this.connected) { | ||
// If the last pong was more than MAX_PONG_INTERVAL, force a reconnect | ||
pongInterval = Date.now() - this._lastPong; | ||
if (pongInterval > this.MAX_PONG_INTERVAL) { | ||
this.reconnect(); | ||
} else { | ||
this.send({ type: 'ping' }, noop); | ||
} | ||
// Ensure we use the same arguments to `start` when reconnecting | ||
this.start(this._startOpts); | ||
} | ||
@@ -403,9 +417,2 @@ }; | ||
this.emit(CLIENT_EVENTS.WS_OPENED); | ||
this._lastPong = Date.now(); | ||
this._connAttempts = 0; | ||
if (this._pingTimer) { | ||
clearInterval(this._pingTimer); | ||
} else { | ||
this._pingTimer = setInterval(bind(this._pingServer, this), this.WS_PING_INTERVAL); | ||
} | ||
}; | ||
@@ -426,4 +433,3 @@ | ||
} catch (err) { | ||
// TODO(leah): Emit an event here? | ||
this.logger('debug', 'unable to parse message: ' + err); | ||
this.logger('error', 'Unable to parse message: ' + err); | ||
return; | ||
@@ -435,2 +441,3 @@ } | ||
} else { | ||
this._maybeKeepAlive(message); | ||
this._handleWsMessageViaEventHandler(message.type, message); | ||
@@ -442,3 +449,3 @@ } | ||
/** | ||
* | ||
* Handler for messages we need to deal with internally. | ||
* @param {String} messageType | ||
@@ -609,3 +616,5 @@ * @param {Object} message | ||
/** | ||
* | ||
* Occurs when the websocket closes. | ||
* @param {String} code The error code | ||
* @param {String} reason The reason for closing | ||
*/ | ||
@@ -637,6 +646,54 @@ RTMClient.prototype.handleWsClose = function handleWsClose(code, reason) { | ||
/** {@link https://api.slack.com/events/hello|hello} */ | ||
/** | ||
* Pings the remote server to let it know the client is still alive. | ||
* @private | ||
*/ | ||
RTMClient.prototype._pingServer = function _pingServer() { | ||
var pongInterval; | ||
if (this.connected) { | ||
// Get the delta between ping sent & pong received, remember this timer | ||
// fires sometime later so deduct that to get the real latency | ||
pongInterval = Math.abs(Date.now() - this._lastPong - this.WS_PING_INTERVAL); | ||
this.logger('debug', 'waited ' + pongInterval + ' ms for a pong'); | ||
// If we didn't receive a response to the last pong in some duration, | ||
// force a reconnect | ||
if (pongInterval > this.MAX_PONG_INTERVAL) { | ||
this.reconnect(); | ||
} else { | ||
this.send({ type: 'ping' }, noop); | ||
} | ||
} | ||
}; | ||
/** | ||
* If we haven't received a pong in too long, treat any incoming message as a pong | ||
* to prevent unnecessary disconnects. | ||
* @param {Object} message | ||
*/ | ||
RTMClient.prototype._maybeKeepAlive = function _maybeKeepAlive(message) { | ||
var pongInterval = Date.now() - this._lastPong; | ||
if (pongInterval > this.MAX_PONG_INTERVAL) { | ||
this.logger('warn', 'No pong in ' + pongInterval + | ||
'ms, treating ' + message.type + ' as keep-alive'); | ||
this._lastPong = Date.now(); | ||
} | ||
}; | ||
/** | ||
* Occurs when the socket connection is opened. | ||
* Begin ping-pong with the server. | ||
* {@link https://api.slack.com/events/hello|hello} | ||
*/ | ||
RTMClient.prototype._handleHello = function _handleHello() { | ||
this.connected = true; | ||
this.emit(CLIENT_EVENTS.RTM_CONNECTION_OPENED); | ||
this._lastPong = Date.now(); | ||
this._connAttempts = 0; | ||
if (this._pingTimer) clearInterval(this._pingTimer); | ||
this._pingTimer = setInterval(bind(this._pingServer, this), this.WS_PING_INTERVAL); | ||
}; | ||
@@ -659,2 +716,3 @@ | ||
/** | ||
@@ -670,2 +728,3 @@ * Helper for updating a sent message via the 'chat.update' API call | ||
/** | ||
@@ -684,2 +743,15 @@ * Sends a typing indicator to indicate that the user with `activeUserId` is typing. | ||
/** | ||
* Subscribes this socket to presence changes for only the given `userIds`. | ||
* This requires `presence_sub` to have been passed as an argument to `start`. | ||
* @param {Array} userIds The user IDs to subscribe to | ||
*/ | ||
RTMClient.prototype.subscribePresence = function subscribePresence(userIds) { | ||
this.send({ | ||
type: 'presence_sub', | ||
ids: userIds | ||
}, noop); | ||
}; | ||
/** | ||
* Sends a message over the websocket to the server. | ||
@@ -686,0 +758,0 @@ * @param {*} message The message to send back to the server. |
@@ -58,4 +58,2 @@ // For some reason, I can't turn this off only for functions, so suppress all cases | ||
/** | ||
* | ||
* | ||
* If this is reached, it means an error outside the normal error logic was received. These | ||
@@ -72,3 +70,3 @@ * should be very unusual as standard errors come back with a 200 code and an "error" | ||
var handleHttpErr = function handleHttpErr(retryOp, apiCb, statusCode) { | ||
var httpErr = new Error('Unable to process request, received bad ' + statusCode + ' error'); | ||
var httpErr = new Error('Unable to process request, received status ' + statusCode); | ||
if (!retryOp.retry(httpErr)) { | ||
@@ -108,3 +106,3 @@ apiCb(httpErr, null); | ||
jsonError = new Error(jsonResponse.error); | ||
client.logger('error', jsonResponse.error); | ||
client.logger('error', 'Response not OK: ', jsonResponse.error); | ||
} | ||
@@ -111,0 +109,0 @@ |
@@ -133,5 +133,7 @@ /** | ||
* @param {string} unfurls - a map of URLs to structured message attachments | ||
* @param {Object=} opts | ||
* @param {?} opts.user_auth_required - Pass true to require user authorization. | ||
* @param {function=} optCb Optional callback, if not using promises. | ||
*/ | ||
ChatFacet.prototype.unfurl = function unfurl(ts, channel, unfurls, optCb) { | ||
ChatFacet.prototype.unfurl = function unfurl(ts, channel, unfurls, opts, optCb) { | ||
var requiredArgs = { | ||
@@ -143,5 +145,5 @@ ts: ts, | ||
return this.makeAPICall('chat.unfurl', requiredArgs, {}, optCb); | ||
return this.makeAPICall('chat.unfurl', requiredArgs, opts, optCb); | ||
}; | ||
module.exports = ChatFacet; |
@@ -10,2 +10,3 @@ /** | ||
* - open: {@link https://api.slack.com/methods/im.open|im.open} | ||
* - replies: {@link https://api.slack.com/methods/im.replies|im.replies} | ||
* | ||
@@ -103,3 +104,20 @@ */ | ||
/** | ||
* Returns an entire thread (a message plus all the messages in reply to it). | ||
* @see {@link https://api.slack.com/methods/im.replies|im.replies} | ||
* | ||
* @param {?} channel - Direct message channel to get replies from. | ||
* @param {?} thread_ts - Timestamp of the parent message. | ||
* @param {function=} optCb Optional callback, if not using promises. | ||
*/ | ||
ImFacet.prototype.replies = function replies(channel, threadTs, optCb) { | ||
var requiredArgs = { | ||
channel: channel, | ||
thread_ts: threadTs | ||
}; | ||
return this.makeAPICall('im.replies', requiredArgs, null, optCb); | ||
}; | ||
module.exports = ImFacet; |
@@ -6,3 +6,3 @@ /** | ||
* - start: {@link https://api.slack.com/methods/rtm.start|rtm.start} | ||
* | ||
* - connect: {@link https://api.slack.com/methods/rtm.connect|rtm.connect} | ||
*/ | ||
@@ -21,8 +21,9 @@ | ||
* | ||
* @param {Object=} opts | ||
* @param {?} opts.simple_latest - Return timestamp only for latest message object of each channel | ||
* (improves performance). | ||
* @param {?} opts.no_unreads - Skip unread counts for each channel (improves performance). | ||
* @param {?} opts.mpim_aware - Returns MPIMs to the client in the API response. | ||
* @param {function=} optCb Optional callback, if not using promises. | ||
* @param {Object} opts | ||
* @param {Boolean} opts.simple_latest Return timestamp only for latest message object of each | ||
* channel (improves performance). | ||
* @param {Boolean} opts.no_unreads Skip unread counts for each channel (improves performance). | ||
* @param {Boolean} opts.mpim_aware Returns MPIMs to the client in the API response. | ||
* @param {Boolean} opts.presence_sub Support presence subscriptions on this socket connection. | ||
* @param {Function} optCb Optional callback, if not using promises. | ||
*/ | ||
@@ -34,2 +35,16 @@ RtmFacet.prototype.start = function start(opts, optCb) { | ||
/** | ||
* Starts a Real Time Messaging session using the lighter-weight rtm.connect. | ||
* This will give us a WebSocket URL without the payload of `rtm.start`. | ||
* @see {@link https://api.slack.com/methods/rtm.connect|rtm.connect} | ||
* | ||
* @param {Object} opts | ||
* @param {Boolean} opts.presence_sub Support presence subscriptions on this socket connection. | ||
* @param {Function} optCb Optional callback, if not using promises. | ||
*/ | ||
RtmFacet.prototype.connect = function connect(opts, optCb) { | ||
return this.makeAPICall('rtm.connect', null, opts, optCb); | ||
}; | ||
module.exports = RtmFacet; |
@@ -397,2 +397,3 @@ /* eslint no-unused-vars: 0 */ | ||
SlackDataStore.prototype.cacheRtmStart = function cacheRtmStart(data) { | ||
var self; | ||
this.clear(); | ||
@@ -417,3 +418,9 @@ | ||
this.getUserById(data.self.id).update(data.self); | ||
self = this.getUserById(data.self.id); | ||
if (self) { | ||
self.update(data.self); | ||
} else { | ||
// If we got here from an rtm.connect, we may not have any users | ||
this.setUser(new models.User(data.self)); | ||
} | ||
this.setTeam(data.team); | ||
@@ -420,0 +427,0 @@ }; |
@@ -135,3 +135,3 @@ /** | ||
var user = this.getUserByName(name); | ||
return find(this.dms, ['user', user.id]); | ||
return (user) ? find(this.dms, ['user', user.id]) : undefined; | ||
}; | ||
@@ -138,0 +138,0 @@ |
{ | ||
"name": "@slack/client", | ||
"version": "3.9.0", | ||
"version": "3.10.0", | ||
"description": "A library for creating a Slack client", | ||
@@ -39,3 +39,3 @@ "main": "./index", | ||
"devDependencies": { | ||
"chai": "^3.3.0", | ||
"chai": "^3.5.0", | ||
"codecov": "^1.0.1", | ||
@@ -46,3 +46,3 @@ "eslint": "^2.2.0", | ||
"jsdoc-to-markdown": "^1.3.7", | ||
"mocha": "~2.3.3", | ||
"mocha": "^3.4.1", | ||
"mocha-lcov-reporter": "^1.0.0", | ||
@@ -49,0 +49,0 @@ "nock": "^7.2.2", |
@@ -10,3 +10,3 @@ # Node Library for the Slack APIs | ||
So you want to build a Slack app with Node.js? We've got you covered. {{ site.product_name }} is aimed at making | ||
So you want to build a Slack app with Node.js? We've got you covered. `node-slack-sdk` is aimed at making | ||
building Slack apps ridiculously easy. This module will help you build on all aspects of the Slack platform, | ||
@@ -28,3 +28,3 @@ from dropping notifications in channels to fully interactive bots. | ||
Most Slack apps are interested in posting messages into Slack channels, and generally working with our [Web API](https://api.slack.com/web). Read on | ||
to learn how to use {{ site.product_name }} to accomplish these tasks. Bots, on the other hand, are a bit more complex, | ||
to learn how to use `node-slack-sdk` to accomplish these tasks. Bots, on the other hand, are a bit more complex, | ||
so we have them covered in [Building Bots](https://slackapi.github.io/node-slack-sdk/bots). | ||
@@ -52,7 +52,7 @@ | ||
webhook.send('Hello there', function(err, res) { | ||
if (err) { | ||
console.log('Error:', err); | ||
} else { | ||
console.log('Message sent: ', res); | ||
} | ||
if (err) { | ||
console.log('Error:', err); | ||
} else { | ||
console.log('Message sent: ', res); | ||
} | ||
}); | ||
@@ -76,7 +76,7 @@ ``` | ||
web.chat.postMessage('C1232456', 'Hello there', function(err, res) { | ||
if (err) { | ||
console.log('Error:', err); | ||
} else { | ||
console.log('Message sent: ', res); | ||
} | ||
if (err) { | ||
console.log('Error:', err); | ||
} else { | ||
console.log('Message sent: ', res); | ||
} | ||
}); | ||
@@ -100,4 +100,9 @@ ``` | ||
// The client will emit an RTM.AUTHENTICATED event on successful connection, with the `rtm.start` payload if you want to cache it | ||
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, function (rtmStartData) { | ||
let channel; | ||
// The client will emit an RTM.AUTHENTICATED event on successful connection, with the `rtm.start` payload | ||
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (rtmStartData) => { | ||
for (const c of rtmStartData.channels) { | ||
if (c.is_member && c.name ==='general') { channel = c.id } | ||
} | ||
console.log(`Logged in as ${rtmStartData.self.name} of team ${rtmStartData.team.name}, but not yet connected to a channel`); | ||
@@ -104,0 +109,0 @@ }); |
@@ -75,2 +75,33 @@ var expect = require('chai').expect; | ||
it('should make API calls in the order they are executed', function (done) { | ||
var args1 = { | ||
headers: {}, | ||
statusCode: 200, | ||
body: '{"test": 10}' | ||
}; | ||
var args2 = { | ||
headers: {}, | ||
statusCode: 200, | ||
body: '{"test": 20}' | ||
}; | ||
var client = new WebAPIClient('test-token', { transport: mockTransport }); | ||
sinon.spy(client, 'transport'); | ||
client._makeAPICall('test', args1, null, function () { | ||
expect(client.transport.callCount).to.equal(1); | ||
expect(client.transport.args[0][0].data.body).to.equal('{"test": 10}'); | ||
expect(client.transport.args.length).to.equal(1); | ||
}); | ||
client._makeAPICall('test', args2, null, function () { | ||
expect(client.transport.callCount).to.equal(2); | ||
expect(client.transport.args[0][0].data.body).to.equal('{"test": 10}'); | ||
expect(client.transport.args[1][0].data.body).to.equal('{"test": 20}'); | ||
expect(client.transport.args.length).to.equal(2); | ||
}); | ||
done(); | ||
}); | ||
it('should not crash when no callback is supplied to an API request', function () { | ||
@@ -77,0 +108,0 @@ var client = new WebAPIClient('test-token', { transport: mockTransport }); |
@@ -8,5 +8,10 @@ var expect = require('chai').expect; | ||
describe('RTM API Message Handlers: Raw Events', function () { | ||
var rtmClient; | ||
afterEach(function () { | ||
rtmClient.disconnect(); | ||
}); | ||
it('emits raw messages with all lower case keys unchanged', function (done) { | ||
var rtmClient = getRtmClient(); | ||
rtmClient = getRtmClient(); | ||
rtmClient.on('raw_message', function (rawMsg) { | ||
@@ -13,0 +18,0 @@ expect(rawMsg).to.equal(JSON.stringify(getRTMMessageFixture('im_open'))); |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
477688
181
7689
113
2