app.ts 3.1 KB

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