2017-09-11 22:54:15 +08:00
|
|
|
const _ = require("lodash");
|
|
|
|
const async = require("async");
|
|
|
|
const redis = require("redis");
|
|
|
|
const request = require("request");
|
|
|
|
const config = require("./config.js");
|
|
|
|
const Hook = require("./hook.js");
|
|
|
|
const IDMapping = require("./id_mapping.js");
|
|
|
|
const Logger = require("./logger.js");
|
|
|
|
const MessageMapping = require("./messageMapping.js");
|
2017-09-15 01:09:02 +08:00
|
|
|
const UserMapping = require("./userMapping.js");
|
2017-09-11 22:54:15 +08:00
|
|
|
|
|
|
|
// Web hooks will listen for events on redis coming from BigBlueButton and
|
|
|
|
// perform HTTP calls with them to all registered hooks.
|
2017-08-25 00:22:30 +08:00
|
|
|
module.exports = class WebHooks {
|
2017-09-11 22:54:15 +08:00
|
|
|
|
|
|
|
constructor() {
|
2017-09-12 03:16:11 +08:00
|
|
|
this.subscriberEvents = config.redis.pubSubClient;
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
|
2017-11-07 02:45:35 +08:00
|
|
|
start(callback) {
|
2017-09-11 22:54:15 +08:00
|
|
|
this._subscribeToEvents();
|
2017-11-07 02:45:35 +08:00
|
|
|
typeof callback === 'function' ? callback(null,"w") : undefined;
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe to the events on pubsub that might need to be sent in callback calls.
|
|
|
|
_subscribeToEvents() {
|
|
|
|
this.subscriberEvents.on("psubscribe", (channel, count) => Logger.info(`[WebHooks] subscribed to:${channel}`));
|
|
|
|
|
|
|
|
this.subscriberEvents.on("pmessage", (pattern, channel, message) => {
|
|
|
|
|
|
|
|
let raw;
|
|
|
|
const processMessage = () => {
|
|
|
|
Logger.info(`[WebHooks] processing message on [${channel}]:`, JSON.stringify(message));
|
|
|
|
this._processEvent(message, raw);
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
raw = JSON.parse(message);
|
|
|
|
let messageMapped = new MessageMapping();
|
|
|
|
messageMapped.mapMessage(JSON.parse(message));
|
|
|
|
message = messageMapped.mappedObject;
|
2018-07-18 11:42:02 +08:00
|
|
|
if (!_.isEmpty(message)) {
|
2017-09-15 01:09:02 +08:00
|
|
|
const intId = message.data.attributes.meeting["internal-meeting-id"];
|
|
|
|
IDMapping.reportActivity(intId);
|
2017-09-11 22:54:15 +08:00
|
|
|
|
|
|
|
// First treat meeting events to add/remove ID mappings
|
2017-09-15 01:09:02 +08:00
|
|
|
switch (message.data.id) {
|
|
|
|
case "meeting-created":
|
|
|
|
Logger.info(`[WebHooks] got create message on meetings channel [${channel}]:`, message);
|
|
|
|
IDMapping.addOrUpdateMapping(intId, message.data.attributes.meeting["external-meeting-id"], (error, result) => {
|
2017-09-11 22:54:15 +08:00
|
|
|
// has to be here, after the meeting was created, otherwise create calls won't generate
|
|
|
|
// callback calls for meeting hooks
|
2017-09-15 01:09:02 +08:00
|
|
|
processMessage();
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case "user-joined":
|
2018-10-25 10:49:56 +08:00
|
|
|
UserMapping.addOrUpdateMapping(message.data.attributes.user["internal-user-id"],message.data.attributes.user["external-user-id"], intId, message.data.attributes.user, () => {
|
2017-09-15 01:09:02 +08:00
|
|
|
processMessage();
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case "user-left":
|
|
|
|
UserMapping.removeMapping(message.data.attributes.user["internal-user-id"], () => { processMessage(); });
|
|
|
|
break;
|
|
|
|
case "meeting-ended":
|
|
|
|
UserMapping.removeMappingMeetingId(intId, () => { processMessage(); });
|
|
|
|
break;
|
2017-09-15 01:27:09 +08:00
|
|
|
default:
|
|
|
|
processMessage();
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2018-08-25 04:02:17 +08:00
|
|
|
Logger.error("[WebHooks] error processing the message:", JSON.stringify(raw), ":", e.message);
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
});
|
2017-11-07 02:45:35 +08:00
|
|
|
|
2017-09-12 03:16:11 +08:00
|
|
|
for (let k in config.hooks.channels) {
|
|
|
|
const channel = config.hooks.channels[k];
|
|
|
|
this.subscriberEvents.psubscribe(channel);
|
|
|
|
}
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send raw data to hooks that are not expecting mapped messages
|
|
|
|
_processRaw(message) {
|
|
|
|
let idFromMessage;
|
|
|
|
let hooks = Hook.allGlobalSync();
|
|
|
|
|
|
|
|
// Add hooks for the specific meeting that expect raw data
|
2017-09-13 02:42:38 +08:00
|
|
|
// Get meetingId for a raw message that was previously mapped by another webhook application or if it's straight from redis
|
|
|
|
idFromMessage = this._findMeetingID(message);
|
2017-09-11 22:54:15 +08:00
|
|
|
if (idFromMessage != null) {
|
|
|
|
const eMeetingID = IDMapping.getExternalMeetingID(idFromMessage);
|
|
|
|
hooks = hooks.concat(Hook.findByExternalMeetingIDSync(eMeetingID));
|
2017-09-07 03:41:01 +08:00
|
|
|
// Notify the hooks that expect raw data
|
2017-09-13 02:42:38 +08:00
|
|
|
async.forEach(hooks, (hook) => {
|
|
|
|
if (hook.getRaw) {
|
|
|
|
Logger.info("[WebHooks] enqueueing a raw message in the hook:", hook.callbackURL);
|
|
|
|
hook.enqueue(message);
|
|
|
|
}
|
2017-09-07 03:41:01 +08:00
|
|
|
});
|
|
|
|
} // Put foreach inside the if to avoid pingpong events
|
2017-09-11 22:54:15 +08:00
|
|
|
}
|
|
|
|
|
2017-09-13 02:42:38 +08:00
|
|
|
_findMeetingID(message) {
|
|
|
|
if (message.data) {
|
|
|
|
return message.data.attributes.meeting["internal-meeting-id"];
|
|
|
|
}
|
|
|
|
if (message.payload) {
|
|
|
|
return message.payload.meeting_id;
|
|
|
|
}
|
|
|
|
if (message.envelope && message.envelope.routing && message.envelope.routing.meetingId) {
|
|
|
|
return message.envelope.routing.meetingId;
|
|
|
|
}
|
|
|
|
if (message.header && message.header.body && message.header.body.meetingId) {
|
|
|
|
return message.header.body.meetingId;
|
|
|
|
}
|
|
|
|
if (message.core && message.core.body) {
|
|
|
|
return message.core.body.props ? message.core.body.props.meetingProp.intId : message.core.body.meetingId;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2017-09-11 22:54:15 +08:00
|
|
|
// Processes an event received from redis. Will get all hook URLs that
|
|
|
|
// should receive this event and start the process to perform the callback.
|
|
|
|
_processEvent(message, raw) {
|
|
|
|
// Get all global hooks
|
|
|
|
let hooks = Hook.allGlobalSync();
|
|
|
|
|
|
|
|
// filter the hooks that need to receive this event
|
|
|
|
// add hooks that are registered for this specific meeting
|
|
|
|
const idFromMessage = message.data != null ? message.data.attributes.meeting["internal-meeting-id"] : undefined;
|
|
|
|
if (idFromMessage != null) {
|
|
|
|
const eMeetingID = IDMapping.getExternalMeetingID(idFromMessage);
|
|
|
|
hooks = hooks.concat(Hook.findByExternalMeetingIDSync(eMeetingID));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notify every hook asynchronously, if hook N fails, it won't block hook N+k from receiving its message
|
2017-09-13 02:42:38 +08:00
|
|
|
async.forEach(hooks, (hook) => {
|
|
|
|
if (!hook.getRaw) {
|
|
|
|
Logger.info("[WebHooks] enqueueing a message in the hook:", hook.callbackURL);
|
|
|
|
hook.enqueue(message);
|
|
|
|
}
|
2017-09-11 22:54:15 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
const sendRaw = hooks.some(hook => { return hook.getRaw });
|
2018-07-18 12:20:42 +08:00
|
|
|
if (sendRaw && config.hooks.getRaw) {
|
2017-09-11 22:54:15 +08:00
|
|
|
this._processRaw(raw);
|
|
|
|
}
|
|
|
|
}
|
2017-08-25 00:22:30 +08:00
|
|
|
};
|