|
@@ -0,0 +1,126 @@
|
|
|
+import { createError, opine } from "./deps.ts";
|
|
|
+import { ErrorRequestHandler, Router, Request, ParamsDictionary } from "./deps.ts";
|
|
|
+import { createHash } from "https://deno.land/std@0.87.0/hash/mod.ts";
|
|
|
+
|
|
|
+interface server { host: string; secret: string }
|
|
|
+
|
|
|
+
|
|
|
+const file: string = await Deno.readTextFile('servers.json')
|
|
|
+const servers: server[] = JSON.parse(file)
|
|
|
+
|
|
|
+
|
|
|
+let iterator = servers[Symbol.iterator]();
|
|
|
+console.log(servers)
|
|
|
+
|
|
|
+
|
|
|
+const secret = Deno.env.get("TINYSCALE_SECRET") || ""
|
|
|
+if (!secret) throw "No secret set for tinyscale"
|
|
|
+
|
|
|
+const router = Router()
|
|
|
+
|
|
|
+
|
|
|
+function authenticated(req: Request<ParamsDictionary, any, any>): boolean {
|
|
|
+ const hash = createHash("sha1");
|
|
|
+ const checksum = req.query.checksum
|
|
|
+ const query = req._parsedUrl?.query
|
|
|
+ hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/, secret)}`);
|
|
|
+ const hashInHex = hash.toString();
|
|
|
+ return hashInHex === checksum
|
|
|
+}
|
|
|
+
|
|
|
+function get_available_server(): server {
|
|
|
+
|
|
|
+ let candidate = iterator.next()
|
|
|
+ if (candidate.done) {
|
|
|
+ iterator = servers[Symbol.iterator]()
|
|
|
+ candidate = iterator.next()
|
|
|
+ }
|
|
|
+ return candidate.value;
|
|
|
+}
|
|
|
+function gen_checksum(call: string, query: string, secret: string): string {
|
|
|
+ const hash = createHash("sha1");
|
|
|
+ console.log(`${call}${query}${secret}`)
|
|
|
+ hash.update(`${call}${query}${secret}`);
|
|
|
+ return hash.toString();
|
|
|
+}
|
|
|
+
|
|
|
+async function find_server(id: string): Promise<server> {
|
|
|
+ if (!id) return get_available_server()
|
|
|
+ const promises = servers.map(async s => {
|
|
|
+ const checksum = gen_checksum(`getMeetingInfo`, `meetingID=${id}`, s.secret)
|
|
|
+ const res = await fetch(`${s.host}/bigbluebutton/api/getMeetingInfo?meetingID=${id}&checksum=${checksum}`)
|
|
|
+ if (!res.ok) throw Error
|
|
|
+ const text = await res.text()
|
|
|
+ if (text.includes(id)) return s
|
|
|
+ else throw Error
|
|
|
+ })
|
|
|
+ try {
|
|
|
+
|
|
|
+ const server = await Promise.any(promises)
|
|
|
+ console.log("RES: ", server)
|
|
|
+
|
|
|
+ return server
|
|
|
+ } catch (e) {
|
|
|
+ console.log("Kein Server gefunden")
|
|
|
+ return get_available_server()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function rewrite_api_call(req: Request<ParamsDictionary, any, any>, server: server) {
|
|
|
+ const hash = createHash("sha1");
|
|
|
+ const checksum = req.query.checksum
|
|
|
+ const query = req._parsedUrl?.query
|
|
|
+ hash.update(`${req.params.call}${query?.replace(/[?|&]checksum.*$/g, server.secret)}`);
|
|
|
+ const hashInHex = hash.toString();
|
|
|
+ return `${server.host}/${req.originalUrl.replace(checksum, hashInHex)}`
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+router.get("/bigbluebutton/api/:call", async (req, res, next) => {
|
|
|
+ console.log("Calling: ", req.params.call)
|
|
|
+ if (!authenticated(req)) {
|
|
|
+ res.setStatus(401).end()
|
|
|
+ console.log('rejected')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const meeting_id: string = req.query.meetingID;
|
|
|
+ const server: server = await find_server(meeting_id)
|
|
|
+ const url = rewrite_api_call(req, server)
|
|
|
+ console.log(url)
|
|
|
+ res.redirect(url)
|
|
|
+});
|
|
|
+
|
|
|
+router.get("/bigbluebutton/api", (req, res, next) => {
|
|
|
+ console.log('sending fake xml response')
|
|
|
+ res.set('Content-Type', 'text/xml');
|
|
|
+ res.send(`<response>
|
|
|
+<returncode>SUCCESS</returncode>
|
|
|
+<version>2.0</version>
|
|
|
+</response>`);
|
|
|
+})
|
|
|
+
|
|
|
+const app = opine();
|
|
|
+
|
|
|
+
|
|
|
+app.use("/", router);
|
|
|
+
|
|
|
+
|
|
|
+app.use((req, res, next) => {
|
|
|
+ next(createError(404));
|
|
|
+});
|
|
|
+
|
|
|
+
|
|
|
+const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
|
|
+
|
|
|
+ res.locals.message = err.message;
|
|
|
+ res.locals.error = req.app.get("env") === "development" ? err : {};
|
|
|
+
|
|
|
+
|
|
|
+ res.setStatus(err.status ?? 500);
|
|
|
+ console.log(err, req)
|
|
|
+ res.send(err);
|
|
|
+};
|
|
|
+
|
|
|
+app.use(errorHandler);
|
|
|
+
|
|
|
+export default app;
|