app.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { opine, ErrorRequestHandler, Router, secret, createError, Color, deferred, Deferred } from "./deps.ts";
  2. import { BBB } from './bbb.ts';
  3. import { Servers, server } from './servers.ts'
  4. const date = () => new Date().toLocaleTimeString('de')
  5. const S = new Servers()
  6. await S.init()
  7. let queue: Record<string, Deferred<string>> = {}
  8. const router = Router()
  9. // if the param is call, check for races
  10. router.param('call', async (req, res, next, call) => {
  11. if (call !== 'create') next()
  12. const meeting_id = req.query.meetingID
  13. const existing_id = queue[meeting_id]
  14. if (existing_id) {
  15. console.log(`Race pending for meeting-ID: ${Color.red(meeting_id)}`)
  16. const body = await existing_id
  17. res.send(body)
  18. } else {
  19. queue[meeting_id] = deferred<string>();
  20. next()
  21. }
  22. })
  23. // the api itself answering to every call
  24. router.all("/:call", async (req, res, next) => {
  25. const handler = new BBB(req)
  26. console.log(`${date()} New call to ${Color.green(handler.call)}`)
  27. if (!handler.authenticated(secret)) {
  28. console.log(`${Color.red("Rejected incoming call to " + handler.call)}`)
  29. next(createError(401))
  30. return
  31. }
  32. let server: server
  33. try {
  34. server = await handler.find_meeting_id(S.servers)
  35. } catch (e) {
  36. console.log(`Found no server with Meeting ID ${Color.yellow(handler.meeting_id)}`)
  37. if (handler.call === 'create') { S.get_available_server() }
  38. server = S.current_server
  39. }
  40. console.log(`Redirecting to ${server.host}`)
  41. const redirect = handler.rewritten_query(server)
  42. if (handler.call === 'join') {
  43. res.redirect(redirect)
  44. } else {
  45. try {
  46. const data = await fetch(redirect)
  47. const body = await data.text()
  48. if (handler.call === 'create') { queue[handler.meeting_id].resolve(body); delete queue[handler.meeting_id] }
  49. res.set('Content-Type', 'text/xml');
  50. res.send(body)
  51. } catch (e) {
  52. if (handler.call === 'create') { queue[handler.meeting_id].resolve(e); delete queue[handler.meeting_id] }
  53. next(createError(500))
  54. }
  55. }
  56. });
  57. // the fake answering machine to make sure we are recognized as a proper api
  58. router.get("/", (req, res, next) => {
  59. console.log('sending fake xml response')
  60. res.set('Content-Type', 'text/xml');
  61. res.send(`<response>
  62. <returncode>SUCCESS</returncode>
  63. <version>2.0</version>
  64. </response>`);
  65. })
  66. const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  67. res.setStatus(err.status ?? 500);
  68. res.end();
  69. console.log(`${Color.red(`${res.status}`)} ${req.originalUrl}`)
  70. };
  71. const app = opine()
  72. .use("/bigbluebutton/api", router)
  73. .use((req, res, next) => next(createError(404)))
  74. .use(errorHandler);
  75. export default app;