app.ts 3.3 KB

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