diff --git a/CHANGELOG.md b/CHANGELOG.md index dc873c3..110fa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][Unreleased] +Added: + +1. Add health-check endpoint (by @mironov) * * * diff --git a/doc/api.md b/doc/api.md index 9fa7798..4da53b8 100644 --- a/doc/api.md +++ b/doc/api.md @@ -80,6 +80,7 @@ Emits `message` when a message arrives. | [options.webHook.pfx] | String | | Path to file with PFX private key and certificate chain for webHook server. The file is read **synchronously**! | | [options.webHook.autoOpen] | Boolean | true | Open webHook immediately | | [options.webHook.https] | Object | | Options to be passed to `https.createServer()`. Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be used to override `key`, `cert` and `pfx` in this object, respectively. See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. | +| [options.webHook.healthEndpoint] | String | /healthz | An endpoint for health checks that always responds with 200 OK | | [options.onlyFirstMatch] | Boolean | false | Set to true to stop after first match. Otherwise, all regexps are executed | | [options.request] | Object | | Options which will be added for all requests to telegram api. See https://github.com/request/request#requestoptions-callback for more information. | | [options.baseApiUrl] | String | https://api.telegram.org | API Base URl; useful for proxying and testing | diff --git a/src/telegram.js b/src/telegram.js index 8f6af71..1c4be1c 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -59,6 +59,7 @@ class TelegramBot extends EventEmitter { * Note that `options.webHook.key`, `options.webHook.cert` and `options.webHook.pfx`, if provided, will be * used to override `key`, `cert` and `pfx` in this object, respectively. * See https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener for more information. + * @param {String} [options.webHook.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK * @param {Boolean} [options.onlyFirstMatch=false] Set to true to stop after first match. Otherwise, all regexps are executed * @param {Object} [options.request] Options which will be added for all requests to telegram api. * See https://github.com/request/request#requestoptions-callback for more information. diff --git a/src/telegramWebHook.js b/src/telegramWebHook.js index 6ee33f2..d35c649 100644 --- a/src/telegramWebHook.js +++ b/src/telegramWebHook.js @@ -13,6 +13,7 @@ class TelegramBotWebHook { * @param {String} token Telegram API token * @param {Boolean|Object} options WebHook options * @param {Number} [options.port=8443] Port to bind to + * @param {String} [options.healthEndpoint=/healthz] An endpoint for health checks that always responds with 200 OK * @param {Function} callback Function for process a new update */ constructor(token, options, callback) { @@ -27,6 +28,7 @@ class TelegramBotWebHook { this.options.https = options.https || {}; this.callback = callback; this._regex = new RegExp(this.token); + this._healthRegex = new RegExp(options.healthEndpoint || '/healthz'); this._webServer = null; this._open = false; this._requestListener = this._requestListener.bind(this); @@ -133,20 +135,24 @@ class TelegramBotWebHook { debug('WebHook request URL: %s', req.url); debug('WebHook request headers: %j', req.headers); - // If there isn't token on URL - if (!this._regex.test(req.url)) { + if (this._regex.test(req.url)) { + if (req.method !== 'POST') { + debug('WebHook request isn\'t a POST'); + res.statusCode = 418; // I'm a teabot! + res.end(); + } else { + req + .pipe(bl(this._parseBody)) + .on('finish', () => res.end('OK')); + } + } else if (this._healthRegex.test(req.url)) { + debug('WebHook health check passed'); + res.statusCode = 200; + res.end('OK'); + } else { debug('WebHook request unauthorized'); res.statusCode = 401; res.end(); - } else if (req.method === 'POST') { - req - .pipe(bl(this._parseBody)) - .on('finish', () => res.end('OK')); - } else { - // Authorized but not a POST - debug('WebHook request isn\'t a POST'); - res.statusCode = 418; // I'm a teabot! - res.end(); } } } diff --git a/test/telegram.js b/test/telegram.js index b2c6059..7a5b89a 100644 --- a/test/telegram.js +++ b/test/telegram.js @@ -133,6 +133,12 @@ describe('TelegramBot', function telegramSuite() { }); describe('WebHook', function webHookSuite() { + it('returns 200 OK for health endpoint', function test(done) { + utils.sendWebHookRequest(webHookPort2, '/healthz', { json: false }).then(resp => { + assert.equal(resp, 'OK'); + return done(); + }); + }); it('returns 401 error if token is wrong', function test(done) { utils.sendWebHookMessage(webHookPort2, 'wrong-token').catch(resp => { assert.equal(resp.statusCode, 401); diff --git a/test/utils.js b/test/utils.js index c63c58e..044bf09 100644 --- a/test/utils.js +++ b/test/utils.js @@ -30,6 +30,17 @@ exports = module.exports = { * @return {Promise} */ isPollingMockServer, + /** + * Send a message to the webhook at the specified port and path. + * @param {Number} port + * @param {String} path + * @param {Object} [options] + * @param {String} [options.method=POST] Method to use + * @param {Object} [options.message] Message to send. Default to a generic text message + * @param {Boolean} [options.https=false] Use https + * @return {Promise} + */ + sendWebHookRequest, /** * Send a message to the webhook at the specified port. * @param {Number} port @@ -134,11 +145,11 @@ function hasOpenWebHook(port, reverse) { } -function sendWebHookMessage(port, token, options = {}) { +function sendWebHookRequest(port, path, options = {}) { assert.ok(port); - assert.ok(token); + assert.ok(path); const protocol = options.https ? 'https' : 'http'; - const url = `${protocol}://127.0.0.1:${port}/bot${token}`; + const url = `${protocol}://127.0.0.1:${port}${path}`; return request({ url, method: options.method || 'POST', @@ -146,11 +157,19 @@ function sendWebHookMessage(port, token, options = {}) { update_id: 1, message: options.message || { text: 'test' } }, - json: true, + json: options.json || true, }); } +function sendWebHookMessage(port, token, options = {}) { + assert.ok(port); + assert.ok(token); + const path = `/bot${token}`; + return sendWebHookRequest(port, path, options); +} + + function handleRatelimit(bot, methodName, suite) { const backupMethodName = `__${methodName}`; if (!bot[backupMethodName]) bot[backupMethodName] = bot[methodName];