app.ts 2.9 KB

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