app.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { createError, opine } from "./deps.ts";
  2. import { ErrorRequestHandler, Router, Request, ParamsDictionary } from "./deps.ts";
  3. import { createHash } from "https://deno.land/std@0.87.0/hash/mod.ts";
  4. interface server { host: string; secret: string }
  5. // store your BBB servers in servers.json
  6. const file: string = await Deno.readTextFile('servers.json')
  7. const servers: server[] = JSON.parse(file)
  8. // create an iterator so that we can trat all servers equally
  9. let iterator = servers[Symbol.iterator]();
  10. console.log(servers)
  11. // give your tinyscale server a secret so it looks like a BBB server
  12. const secret = Deno.env.get("TINYSCALE_SECRET") || ""
  13. if (!secret) throw "No secret set for tinyscale"
  14. const router = Router()
  15. // check if request is autheticated with correct checksum
  16. function authenticated(req: Request<ParamsDictionary, any, any>): boolean {
  17. const hash = createHash("sha1");
  18. const checksum = req.query.checksum
  19. const query = req._parsedUrl?.query
  20. hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/, secret)}`);
  21. const hashInHex = hash.toString();
  22. return hashInHex === checksum
  23. }
  24. // pick the next server
  25. function get_available_server(): server {
  26. // simple server selection, just cycle through all servers available
  27. let candidate = iterator.next()
  28. if (candidate.done) {
  29. iterator = servers[Symbol.iterator]()
  30. candidate = iterator.next()
  31. }
  32. return candidate.value;
  33. }
  34. function gen_checksum(call: string, query: string, secret: string): string {
  35. const hash = createHash("sha1");
  36. console.log(`${call}${query}${secret}`)
  37. hash.update(`${call}${query}${secret}`);
  38. return hash.toString();
  39. }
  40. // fetch a getMeetingInfo from all available servers and see if meeting exists
  41. async function find_server(id: string): Promise<server> {
  42. if (!id) return get_available_server()
  43. const promises = servers.map(async s => {
  44. const checksum = gen_checksum(`getMeetingInfo`, `meetingID=${id}`, s.secret)
  45. const res = await fetch(`${s.host}/bigbluebutton/api/getMeetingInfo?meetingID=${id}&checksum=${checksum}`)
  46. if (!res.ok) throw Error
  47. const text = await res.text()
  48. if (text.includes(id)) return s
  49. else throw Error
  50. })
  51. try {
  52. // if any of the responses resolves it is our server
  53. const server = await Promise.any(promises)
  54. console.log("RES: ", server)
  55. // use that server for our response
  56. return server
  57. } catch (e) {
  58. console.log("Kein Server gefunden")
  59. return get_available_server()
  60. }
  61. }
  62. // redirect api call to proper BBB Server
  63. function rewrite_api_call(req: Request<ParamsDictionary, any, any>, server: server) {
  64. const hash = createHash("sha1");
  65. const checksum = req.query.checksum
  66. const query = req._parsedUrl?.query
  67. hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/g, server.secret)}`);
  68. const hashInHex = hash.toString();
  69. return `${server.host}/${req.originalUrl.replace(checksum, hashInHex)}`
  70. }
  71. // the api itself answering to every call
  72. router.get("/bigbluebutton/api/:call", async (req, res, next) => {
  73. console.log("Calling: ", req.params.call)
  74. if (!authenticated(req)) {
  75. res.setStatus(401).end()
  76. console.log('rejected')
  77. return
  78. }
  79. const meeting_id: string = req.query.meetingID;
  80. const server: server = await find_server(meeting_id)
  81. const url = rewrite_api_call(req, server)
  82. console.log(url)
  83. res.redirect(url)
  84. });
  85. // the fake answering machine to make sure we are recognized as a proper api
  86. router.get("/bigbluebutton/api", (req, res, next) => {
  87. console.log('sending fake xml response')
  88. res.set('Content-Type', 'text/xml');
  89. res.send(`<response>
  90. <returncode>SUCCESS</returncode>
  91. <version>2.0</version>
  92. </response>`);
  93. })
  94. const app = opine();
  95. // Mount our routers
  96. app.use("/", router);
  97. // catch 404 and forward to error handler
  98. app.use((req, res, next) => {
  99. next(createError(404));
  100. });
  101. // Error handler
  102. const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
  103. // Set locals, only providing error in development
  104. res.locals.message = err.message;
  105. res.locals.error = req.app.get("env") === "development" ? err : {};
  106. // Render the error page
  107. res.setStatus(err.status ?? 500);
  108. console.log(err, req)
  109. res.send(err);
  110. };
  111. app.use(errorHandler);
  112. export default app;