2014-11-12 23:07:54 +08:00
|
|
|
_ = require("lodash")
|
2014-11-11 21:58:28 +08:00
|
|
|
express = require("express")
|
2014-11-12 01:07:05 +08:00
|
|
|
url = require("url")
|
|
|
|
|
|
|
|
config = require("./config")
|
2014-11-12 01:48:36 +08:00
|
|
|
Hook = require("./hook")
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger = require("./logger")
|
2014-11-12 03:36:01 +08:00
|
|
|
Utils = require("./utils")
|
2014-11-11 21:58:28 +08:00
|
|
|
|
|
|
|
# Web server that listens for API calls and process them.
|
|
|
|
module.exports = class WebServer
|
|
|
|
|
|
|
|
constructor: ->
|
|
|
|
@app = express()
|
2014-11-12 01:07:05 +08:00
|
|
|
@_registerRoutes()
|
2014-11-11 21:58:28 +08:00
|
|
|
|
|
|
|
start: (port) ->
|
|
|
|
@server = @app.listen(port)
|
|
|
|
unless @server.address()?
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger.error "Could not bind to port", port
|
|
|
|
Logger.error "Aborting."
|
2014-11-11 21:58:28 +08:00
|
|
|
process.exit(1)
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger.info "Server listening on port", port, "in", @app.settings.env.toUpperCase(), "mode"
|
2014-11-12 01:07:05 +08:00
|
|
|
|
|
|
|
_registerRoutes: ->
|
|
|
|
# Request logger
|
|
|
|
@app.all "*", (req, res, next) ->
|
2014-11-14 21:58:12 +08:00
|
|
|
unless fromMonit(req)
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger.info "<==", req.method, "request to", req.url, "from:", clientDataSimple(req)
|
2014-11-12 01:07:05 +08:00
|
|
|
next()
|
|
|
|
|
2014-11-12 21:28:49 +08:00
|
|
|
@app.get "/bigbluebutton/api/hooks/create", @_validateChecksum, @_create
|
|
|
|
@app.get "/bigbluebutton/api/hooks/destroy", @_validateChecksum, @_destroy
|
2014-11-12 01:07:05 +08:00
|
|
|
@app.get "/bigbluebutton/api/hooks/list", @_validateChecksum, @_list
|
2014-11-14 21:58:12 +08:00
|
|
|
@app.get "/bigbluebutton/api/hooks/ping", (req, res) ->
|
|
|
|
res.write "bbb-webhooks up!"
|
|
|
|
res.end()
|
2014-11-12 01:07:05 +08:00
|
|
|
|
2014-11-12 21:28:49 +08:00
|
|
|
_create: (req, res, next) ->
|
2014-11-12 01:48:36 +08:00
|
|
|
urlObj = url.parse(req.url, true)
|
2014-11-12 02:43:41 +08:00
|
|
|
callbackURL = urlObj.query["callbackURL"]
|
2014-11-12 01:48:36 +08:00
|
|
|
meetingID = urlObj.query["meetingID"]
|
|
|
|
|
2014-11-12 02:43:41 +08:00
|
|
|
unless callbackURL?
|
2014-11-12 01:48:36 +08:00
|
|
|
respondWithXML(res, config.api.responses.missingParamCallbackURL)
|
|
|
|
else
|
2014-11-12 02:43:41 +08:00
|
|
|
Hook.addSubscription callbackURL, meetingID, (error, hook) ->
|
2014-11-12 02:56:15 +08:00
|
|
|
if error? # the only error for now is for duplicated callbackURL
|
2014-11-14 21:05:20 +08:00
|
|
|
msg = config.api.responses.createDuplicated(hook.id)
|
2014-11-12 02:56:15 +08:00
|
|
|
else if hook?
|
2014-11-14 21:05:20 +08:00
|
|
|
msg = config.api.responses.createSuccess(hook.id)
|
2014-11-12 01:48:36 +08:00
|
|
|
else
|
2014-11-14 21:05:20 +08:00
|
|
|
msg = config.api.responses.createFailure
|
2014-11-12 01:48:36 +08:00
|
|
|
respondWithXML(res, msg)
|
2014-11-12 01:07:05 +08:00
|
|
|
|
2014-11-12 21:28:49 +08:00
|
|
|
_destroy: (req, res, next) ->
|
2014-11-12 01:48:36 +08:00
|
|
|
urlObj = url.parse(req.url, true)
|
2014-11-12 21:28:49 +08:00
|
|
|
hookID = urlObj.query["hookID"]
|
2014-11-12 01:48:36 +08:00
|
|
|
|
2014-11-12 21:28:49 +08:00
|
|
|
unless hookID?
|
|
|
|
respondWithXML(res, config.api.responses.missingParamHookID)
|
2014-11-12 01:48:36 +08:00
|
|
|
else
|
2014-11-12 21:28:49 +08:00
|
|
|
Hook.removeSubscription hookID, (error, result) ->
|
2014-11-12 01:48:36 +08:00
|
|
|
if error?
|
2014-11-12 21:28:49 +08:00
|
|
|
msg = config.api.responses.destroyFailure
|
2014-11-12 01:48:36 +08:00
|
|
|
else if !result
|
2014-11-12 21:28:49 +08:00
|
|
|
msg = config.api.responses.destroyNoHook
|
2014-11-12 01:48:36 +08:00
|
|
|
else
|
2014-11-12 21:28:49 +08:00
|
|
|
msg = config.api.responses.destroySuccess
|
2014-11-12 01:48:36 +08:00
|
|
|
respondWithXML(res, msg)
|
2014-11-12 01:07:05 +08:00
|
|
|
|
|
|
|
_list: (req, res, next) ->
|
2014-11-12 21:38:00 +08:00
|
|
|
urlObj = url.parse(req.url, true)
|
|
|
|
meetingID = urlObj.query["meetingID"]
|
|
|
|
|
|
|
|
if meetingID?
|
2014-11-12 23:07:54 +08:00
|
|
|
# all the hooks that receive events from this meeting
|
2014-11-12 21:38:00 +08:00
|
|
|
hooks = Hook.allGlobalSync()
|
2014-11-12 23:07:54 +08:00
|
|
|
hooks = hooks.concat(Hook.findByExternalMeetingIDSync(meetingID))
|
|
|
|
hooks = _.sortBy(hooks, (hook) -> hook.id)
|
|
|
|
else
|
|
|
|
# no meetingID, return all hooks
|
|
|
|
hooks = Hook.allSync()
|
2014-11-12 21:38:00 +08:00
|
|
|
|
|
|
|
msg = "<response><returncode>SUCCESS</returncode><hooks>"
|
|
|
|
hooks.forEach (hook) ->
|
|
|
|
msg += "<hook>"
|
|
|
|
msg += "<hookID>#{hook.id}</hookID>"
|
2014-11-14 21:58:12 +08:00
|
|
|
msg += "<callbackURL><![CDATA[#{hook.callbackURL}]]></callbackURL>"
|
|
|
|
msg += "<meetingID><![CDATA[#{hook.externalMeetingID}]]></meetingID>" unless hook.isGlobal()
|
2014-11-12 21:38:00 +08:00
|
|
|
msg += "</hook>"
|
|
|
|
msg += "</hooks></response>"
|
|
|
|
|
|
|
|
respondWithXML(res, msg)
|
2014-11-12 01:07:05 +08:00
|
|
|
|
|
|
|
# Validates the checksum in the request `req`.
|
|
|
|
# If it doesn't match BigBlueButton's shared secret, will send an XML response
|
|
|
|
# with an error code just like BBB does.
|
|
|
|
_validateChecksum: (req, res, next) =>
|
|
|
|
urlObj = url.parse(req.url, true)
|
|
|
|
checksum = urlObj.query["checksum"]
|
|
|
|
|
2014-11-12 03:36:01 +08:00
|
|
|
if checksum is Utils.checksumAPI(req.url, config.bbb.sharedSecret)
|
2014-11-12 01:07:05 +08:00
|
|
|
next()
|
|
|
|
else
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger.info "checksum check failed, sending a checksumError response"
|
2014-11-12 01:07:05 +08:00
|
|
|
res.setHeader("Content-Type", "text/xml")
|
2014-11-12 01:48:36 +08:00
|
|
|
res.send cleanupXML(config.api.responses.checksumError)
|
2014-11-12 01:07:05 +08:00
|
|
|
|
2014-11-12 01:48:36 +08:00
|
|
|
respondWithXML = (res, msg) ->
|
2014-11-12 21:38:00 +08:00
|
|
|
msg = cleanupXML(msg)
|
2014-11-15 00:12:37 +08:00
|
|
|
Logger.info "==> respond with:", msg
|
2014-11-12 01:48:36 +08:00
|
|
|
res.setHeader("Content-Type", "text/xml")
|
2014-11-12 21:38:00 +08:00
|
|
|
res.send msg
|
2014-11-12 01:48:36 +08:00
|
|
|
|
2014-11-12 01:07:05 +08:00
|
|
|
# Returns a simple string with a description of the client that made
|
|
|
|
# the request. It includes the IP address and the user agent.
|
|
|
|
clientDataSimple = (req) ->
|
2014-11-12 03:36:01 +08:00
|
|
|
"ip " + Utils.ipFromRequest(req) + ", using " + req.headers["user-agent"]
|
2014-11-12 01:48:36 +08:00
|
|
|
|
|
|
|
# Cleans up a string with an XML in it removing spaces and new lines from between the tags.
|
|
|
|
cleanupXML = (string) ->
|
|
|
|
string.trim().replace(/>\s*/g, '>')
|
2014-11-14 21:58:12 +08:00
|
|
|
|
|
|
|
# Was this request made by monit?
|
|
|
|
fromMonit = (req) ->
|
|
|
|
req.headers["user-agent"]? and req.headers["user-agent"].match(/^monit/)
|