diff --git a/CHANGELOG.md b/CHANGELOG.md index 08deb09..1cb0391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Changed: Fixed: +1. (#265) Fix the offset infinite loop bug (#36) (by @GochoMugo) 1. Fix game example (by @MCSH) diff --git a/doc/api.md b/doc/api.md index adb8c25..f5ae4d2 100644 --- a/doc/api.md +++ b/doc/api.md @@ -122,6 +122,7 @@ Emits `message` when a message arrives. | [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 | | [options.filepath] | Boolean | true | Allow passing file-paths as arguments when sending files, such as photos using `TelegramBot#sendPhoto()`. See [usage information][usage-sending-files-performance] for more information on this option and its consequences. | +| [options.badRejection] | Boolean | false | Set to `true` **if and only if** the Node.js version you're using terminates the process on unhandled rejections. This option is only for *forward-compatibility purposes*. | diff --git a/doc/usage.md b/doc/usage.md index 3a17700..987855b 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -36,6 +36,7 @@ that emits the following events: 1. `pre_checkout_query`: Received a new incoming pre-checkout query 1. `polling_error`: Error occurred during polling. See [polling errors](#polling-errors). 1. `webhook_error`: Error occurred handling a webhook request. See [webhook errors](#webhook-errors). +1. `error`: Unexpected error occurred, usually fatal! **Tip:** Its much better to listen a specific event rather than on `message` in order to stay safe from the content. diff --git a/src/telegram.js b/src/telegram.js index b69fa59..df94cd8 100644 --- a/src/telegram.js +++ b/src/telegram.js @@ -174,6 +174,10 @@ class TelegramBot extends EventEmitter { * @param {Boolean} [options.filepath=true] Allow passing file-paths as arguments when sending files, * such as photos using `TelegramBot#sendPhoto()`. See [usage information][usage-sending-files-performance] * for more information on this option and its consequences. + * @param {Boolean} [options.badRejection=false] Set to `true` + * **if and only if** the Node.js version you're using terminates the + * process on unhandled rejections. This option is only for + * *forward-compatibility purposes*. * @see https://core.telegram.org/bots/api */ constructor(token, options = {}) { @@ -184,6 +188,7 @@ class TelegramBot extends EventEmitter { this.options.webHook = (typeof options.webHook === 'undefined') ? false : options.webHook; this.options.baseApiUrl = options.baseApiUrl || 'https://api.telegram.org'; this.options.filepath = (typeof options.filepath === 'undefined') ? true : options.filepath; + this.options.badRejection = (typeof options.badRejection === 'undefined') ? false : options.badRejection; this._textRegexpCallbacks = []; this._replyListenerId = 0; this._replyListeners = []; diff --git a/src/telegramPolling.js b/src/telegramPolling.js index d3f102f..d867404 100644 --- a/src/telegramPolling.js +++ b/src/telegramPolling.js @@ -1,3 +1,4 @@ +const errors = require('./errors'); const debug = require('debug')('node-telegram-bot-api'); const deprecate = require('depd')('node-telegram-bot-api'); const ANOTHER_WEB_HOOK_USED = 409; @@ -79,6 +80,18 @@ class TelegramBotPolling { return !!this._lastRequest; } + /** + * Handle error thrown during polling. + * @private + * @param {Error} error + */ + _error(error) { + if (!this.bot.listeners('polling_error').length) { + return console.error('error: [polling_error] %j', error); // eslint-disable-line no-console + } + return this.bot.emit('polling_error', error); + } + /** * Invokes polling (with recursion!) * @return {Promise} promise of the current request @@ -93,18 +106,59 @@ class TelegramBotPolling { updates.forEach(update => { this.options.params.offset = update.update_id + 1; debug('updated offset: %s', this.options.params.offset); - this.bot.processUpdate(update); + try { + this.bot.processUpdate(update); + } catch (err) { + err._processing = true; + throw err; + } }); return null; }) .catch(err => { debug('polling error: %s', err.message); - if (this.bot.listeners('polling_error').length) { - this.bot.emit('polling_error', err); - } else { - console.error('error: [polling_error] %j', err); // eslint-disable-line no-console + if (!err._processing) { + return this._error(err); } - return null; + delete err._processing; + /* + * An error occured while processing the items, + * i.e. in `this.bot.processUpdate()` above. + * We need to mark the already-processed items + * to avoid fetching them again once the application + * is restarted, or moves to next polling interval + * (in cases where unhandled rejections do not terminate + * the process). + * See https://github.com/yagop/node-telegram-bot-api/issues/36#issuecomment-268532067 + */ + if (!this.bot.options.badRejection) { + return this._error(err); + } + const opts = { + offset: this.options.params.offset, + limit: 1, + timeout: 0, + }; + return this.bot.getUpdates(opts).then(() => { + return this._error(err); + }).catch(requestErr => { + /* + * We have been unable to handle this error. + * We have to log this to stderr to ensure devops + * understands that they may receive already-processed items + * on app restart. + * We simply can not rescue this situation, emit "error" + * event, with the hope that the application exits. + */ + /* eslint-disable no-console */ + const bugUrl = 'https://github.com/yagop/node-telegram-bot-api/issues/36#issuecomment-268532067'; + console.error('error: Internal handling of The Offset Infinite Loop failed'); + console.error(`error: Due to error '${requestErr}'`); + console.error('error: You may receive already-processed updates on app restart'); + console.error(`error: Please see ${bugUrl} for more information`); + /* eslint-enable no-console */ + return this.bot.emit('error', new errors.FatalError(err)); + }); }) .finally(() => { if (this._abort) {