app.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { opine, ErrorRequestHandler, Router, createHash, server, createError, Color } from "./deps.ts";
  2. import { BBB } from './bbb.ts';
  3. const date = () => new Date().toLocaleTimeString('de')
  4. const VERSION = 'v1.3.0'
  5. console.log(date() + Color.green(` Starting tinyscale ${VERSION}`))
  6. // give your tinyscale server a secret so it looks like a BBB server
  7. const secret = Deno.env.get("TINYSCALE_SECRET") || ""
  8. if (!secret) throw "No secret set for tinyscale"
  9. // store your BBB servers in servers.json
  10. const file: string = await Deno.readTextFile('servers.json')
  11. const servers: server[] = JSON.parse(file)
  12. // create an iterator so that we can treat all servers equally
  13. let iterator = servers[Symbol.iterator]();
  14. console.log(servers)
  15. console.log('Checking servers first …')
  16. // check servers for connectivity and if the secret is correct
  17. servers.forEach(async s => {
  18. const hash = createHash("sha1");
  19. hash.update(`getMeetings${s.secret}`)
  20. try {
  21. // throw an error if cannot connect or if secret fails
  22. const res = await fetch(`${s.host}/bigbluebutton/api/getMeetings?checksum=${hash.toString()}`)
  23. if (!res.ok) throw "Connection error. Please check your host configuration"
  24. const body = await res.text()
  25. const ok = body.includes('SUCCESS')
  26. console.log(`${s.host} is ${ok ? Color.green('ok') : Color.red('misconfigured. Please check your secret in servers.json')}`)
  27. if (!ok) throw "Configuration error. Exiting …"
  28. } catch (e) {
  29. // exit tinyscale if an error is encountered in servers.json
  30. console.log(Color.brightRed(e))
  31. Deno.exit(1);
  32. }
  33. })
  34. // pick the next server, using an iterator to cycle through all servers available
  35. function get_available_server(): server {
  36. let candidate = iterator.next()
  37. if (candidate.done) {
  38. iterator = servers[Symbol.iterator]()
  39. candidate = iterator.next()
  40. }
  41. console.log(`Using next server ${Color.green(candidate.value.host)}`)
  42. return candidate.value;
  43. }
  44. const router = Router()
  45. // the api itself answering to every call
  46. router.all("/:call", async (req, res, next) => {
  47. const handler = new BBB(req)
  48. console.log(`${date()} New call to ${Color.green(handler.call)}`)
  49. if (!handler.authenticated(secret)) {
  50. console.log(`${Color.red("Rejected incoming call to "+handler.call)}`)
  51. next(createError(401))
  52. return
  53. }
  54. let server: server
  55. try {
  56. server = await handler.find_meeting_id(servers)
  57. } catch (e) {
  58. console.log(`Found no server with Meeting ID ${Color.yellow(handler.meeting_id)}`)
  59. server = get_available_server()
  60. }
  61. console.log(`Redirecting to ${server.host}`)
  62. const redirect = handler.rewritten_query(server)
  63. if (handler.call === 'join') {
  64. res.redirect(redirect)
  65. } else {
  66. try {
  67. const data = await fetch(redirect)
  68. const body = await data.text()
  69. res.set('Content-Type', 'text/xml');
  70. res.send(body)
  71. } catch (e) {
  72. next(createError(500))
  73. }
  74. }
  75. });
  76. // the fake answering machine to make sure we are recognized as a proper api
  77. router.get("/", (req, res, next) => {
  78. console.log('sending fake xml response')
  79. res.set('Content-Type', 'text/xml');
  80. res.send(`<response>
  81. <returncode>SUCCESS</returncode>
  82. <version>2.0</version>
  83. </response>`);
  84. })
  85. const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  86. res.setStatus(err.status ?? 500);
  87. res.end();
  88. console.log(`${Color.red(`${res.status}`)} ${req.originalUrl}`)
  89. };
  90. const app = opine()
  91. .use("/bigbluebutton/api", router)
  92. .use((req, res, next) => next(createError(404)))
  93. .use(errorHandler);
  94. export default app;