|
@@ -1,93 +1,47 @@
|
|
import { createError, opine } from "./deps.ts";
|
|
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";
|
|
|
|
|
|
+import { ErrorRequestHandler, Router, server } from "./deps.ts";
|
|
|
|
+import { BBB } from './bbb.ts';
|
|
|
|
|
|
-interface server { host: string; secret: string }
|
|
|
|
|
|
+// give your tinyscale server a secret so it looks like a BBB server
|
|
|
|
+const secret = Deno.env.get("TINYSCALE_SECRET") || ""
|
|
|
|
+if (!secret) throw "No secret set for tinyscale"
|
|
|
|
|
|
// store your BBB servers in servers.json
|
|
// store your BBB servers in servers.json
|
|
const file: string = await Deno.readTextFile('servers.json')
|
|
const file: string = await Deno.readTextFile('servers.json')
|
|
const servers: server[] = JSON.parse(file)
|
|
const servers: server[] = JSON.parse(file)
|
|
-
|
|
|
|
// create an iterator so that we can trat all servers equally
|
|
// create an iterator so that we can trat all servers equally
|
|
let iterator = servers[Symbol.iterator]();
|
|
let iterator = servers[Symbol.iterator]();
|
|
console.log(servers)
|
|
console.log(servers)
|
|
|
|
|
|
-// give your tinyscale server a secret so it looks like a BBB server
|
|
|
|
-const secret = Deno.env.get("TINYSCALE_SECRET") || ""
|
|
|
|
-if (!secret) throw "No secret set for tinyscale"
|
|
|
|
-
|
|
|
|
-const router = Router()
|
|
|
|
-
|
|
|
|
-// check if request is autheticated with correct checksum
|
|
|
|
-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
|
|
|
|
-}
|
|
|
|
-// pick the next server
|
|
|
|
|
|
+// pick the next server, using an iterator to cycle through all servers available
|
|
function get_available_server(): server {
|
|
function get_available_server(): server {
|
|
- // simple server selection, just cycle through all servers available
|
|
|
|
let candidate = iterator.next()
|
|
let candidate = iterator.next()
|
|
if (candidate.done) {
|
|
if (candidate.done) {
|
|
iterator = servers[Symbol.iterator]()
|
|
iterator = servers[Symbol.iterator]()
|
|
candidate = iterator.next()
|
|
candidate = iterator.next()
|
|
}
|
|
}
|
|
|
|
+ console.log(`Using next server ${candidate.value}`)
|
|
return candidate.value;
|
|
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();
|
|
|
|
-}
|
|
|
|
-// fetch a getMeetingInfo from all available servers and see if meeting exists
|
|
|
|
-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 {
|
|
|
|
- // if any of the responses resolves it is our server
|
|
|
|
- const server = await Promise.any(promises)
|
|
|
|
- console.log("RES: ", server)
|
|
|
|
- // use that server for our response
|
|
|
|
- return server
|
|
|
|
- } catch (e) {
|
|
|
|
- console.log("Kein Server gefunden")
|
|
|
|
- return get_available_server()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-// redirect api call to proper BBB 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)}`
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
+const router = Router()
|
|
// the api itself answering to every call
|
|
// the api itself answering to every call
|
|
router.get("/bigbluebutton/api/:call", async (req, res, next) => {
|
|
router.get("/bigbluebutton/api/:call", async (req, res, next) => {
|
|
- console.log("Calling: ", req.params.call)
|
|
|
|
- if (!authenticated(req)) {
|
|
|
|
|
|
+ const handler = new BBB(req)
|
|
|
|
+ if (!handler.authenticated(secret)) {
|
|
res.setStatus(401).end()
|
|
res.setStatus(401).end()
|
|
- console.log('rejected')
|
|
|
|
|
|
+ console.log(`rejected incoming call to ${req.params.call}`)
|
|
return
|
|
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)
|
|
|
|
|
|
+ let server: server
|
|
|
|
+ try {
|
|
|
|
+ server = await handler.find_meeting_id(servers)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ server = get_available_server()
|
|
|
|
+ console.log(`Found no server with Meeting ID ${handler.meeting_id}`)
|
|
|
|
+ }
|
|
|
|
+ console.log(`Redirecting to ${server.host}`)
|
|
|
|
+ res.redirect(handler.rewritten_query(server))
|
|
});
|
|
});
|
|
// the fake answering machine to make sure we are recognized as a proper api
|
|
// the fake answering machine to make sure we are recognized as a proper api
|
|
router.get("/bigbluebutton/api", (req, res, next) => {
|
|
router.get("/bigbluebutton/api", (req, res, next) => {
|
|
@@ -99,28 +53,18 @@ router.get("/bigbluebutton/api", (req, res, next) => {
|
|
</response>`);
|
|
</response>`);
|
|
})
|
|
})
|
|
|
|
|
|
-const app = opine();
|
|
|
|
-
|
|
|
|
-// Mount our routers
|
|
|
|
-app.use("/", router);
|
|
|
|
-
|
|
|
|
-// catch 404 and forward to error handler
|
|
|
|
-app.use((req, res, next) => {
|
|
|
|
- next(createError(404));
|
|
|
|
-});
|
|
|
|
-
|
|
|
|
-// Error handler
|
|
|
|
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
|
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
|
// Set locals, only providing error in development
|
|
// Set locals, only providing error in development
|
|
res.locals.message = err.message;
|
|
res.locals.message = err.message;
|
|
res.locals.error = req.app.get("env") === "development" ? err : {};
|
|
res.locals.error = req.app.get("env") === "development" ? err : {};
|
|
-
|
|
|
|
// Render the error page
|
|
// Render the error page
|
|
res.setStatus(err.status ?? 500);
|
|
res.setStatus(err.status ?? 500);
|
|
console.log(err, req)
|
|
console.log(err, req)
|
|
res.send(err);
|
|
res.send(err);
|
|
};
|
|
};
|
|
-
|
|
|
|
-app.use(errorHandler);
|
|
|
|
|
|
+const app = opine()
|
|
|
|
+ .use("/", router)
|
|
|
|
+ .use((req, res, next) => { next(createError(404)); })
|
|
|
|
+ .use(errorHandler);
|
|
|
|
|
|
export default app;
|
|
export default app;
|