const _ = require('lodash'); const request = require("request"); const url = require('url'); const EventEmitter = require('events').EventEmitter; const config = require("./config.js"); const Logger = require("./logger.js"); const Utils = require("./utils.js"); // Use to perform a callback. Will try several times until the callback is // properly emitted and stop when successful (or after a given number of tries). // Used to emit a single callback. Destroy it and create a new class for a new callback. // Emits "success" on success, "failure" on error and "stopped" when gave up trying // to perform the callback. module.exports = class CallbackEmitter extends EventEmitter { constructor(callbackURL, message, permanent) { super(); this.callbackURL = callbackURL; this.message = message; this.nextInterval = 0; this.timestamp = 0; this.permanent = permanent; } start() { this.timestamp = new Date().getTime(); this.nextInterval = 0; this._scheduleNext(0); } _scheduleNext(timeout) { setTimeout( () => { this._emitMessage((error, result) => { if ((error == null) && result) { this.emit("success"); } else { this.emit("failure", error); // get the next interval we have to wait and schedule a new try const interval = config.hooks.retryIntervals[this.nextInterval]; if (interval != null) { Logger.warn(`[Emitter] trying the callback again in ${interval/1000.0} secs`); this.nextInterval++; this._scheduleNext(interval); // no intervals anymore, time to give up } else { this.nextInterval = config.hooks.permanentIntervalReset; // Reset interval to permanent hooks if(this.permanent){ this._scheduleNext(this.nextInterval); } else { return this.emit("stopped"); } } } }); } , timeout); } _emitMessage(callback) { let data,requestOptions; const serverDomain = process.env.SERVER_DOMAIN || config.bbb.serverDomain; const sharedSecret = process.env.SHARED_SECRET || config.bbb.sharedSecret; const bearerAuth = process.env.BEARER_AUTH || config.bbb.auth2_0; // data to be sent // note: keep keys in alphabetical order data = { event: "[" + this.message + "]", timestamp: this.timestamp, domain: serverDomain }; if (bearerAuth) { const callbackURL = this.callbackURL; requestOptions = { followRedirect: true, maxRedirects: 10, uri: callbackURL, method: "POST", form: data, auth: { bearer: sharedSecret } }; } else { // calculate the checksum const checksum = Utils.checksum(`${this.callbackURL}${JSON.stringify(data)}${sharedSecret}`); // get the final callback URL, including the checksum const urlObj = url.parse(this.callbackURL, true); let callbackURL = this.callbackURL; callbackURL += _.isEmpty(urlObj.search) ? "?" : "&"; callbackURL += `checksum=${checksum}`; requestOptions = { followRedirect: true, maxRedirects: 10, uri: callbackURL, method: "POST", form: data }; } const responseFailed = (response) => { var statusCode = (response != null ? response.statusCode : undefined) return !((statusCode >= 200) && (statusCode < 300)) }; request(requestOptions, function(error, response, body) { if ((error != null) || responseFailed(response)) { Logger.warn(`[Emitter] error in the callback call to: [${requestOptions.uri}] for ${simplifiedEvent(data)}`, "error:", error, "status:", response != null ? response.statusCode : undefined); callback(error, false); } else { Logger.info(`[Emitter] successful callback call to: [${requestOptions.uri}] for ${simplifiedEvent(data)}`); callback(null, true); } }); } }; // A simple string that identifies the event var simplifiedEvent = function(event) { if (event.event != null) { event = event.event } try { const eventJs = JSON.parse(event); return `event: { name: ${(eventJs.data != null ? eventJs.data.id : undefined)}, timestamp: ${(eventJs.data.event != null ? eventJs.data.event.ts : undefined)} }`; } catch (e) { return `event: ${event}`; } };