Skip to content

Commit

Permalink
proxy BBB responses and color logs
Browse files Browse the repository at this point in the history
  • Loading branch information
hmt committed Apr 26, 2021
1 parent c1461b3 commit baecc69
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 38 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ Then create a `servers.json` file like this here:

Now you are ready to start the script with setting a port and a secret:

TINYSCALE_SECRET=some_secret_string deno run --allow-net --allow-read --allow-env https://deno.land/x/tinyscale@v1.2.0/mod.ts
TINYSCALE_SECRET=some_secret_string deno run --allow-net --allow-read --allow-env https://deno.land/x/tinyscale@v1.3.0/mod.ts

tinyscales then runs on port 3005 and you will have to set up your reverse proxy so that it can pick up requests or you leave it on that port. If you prefer a different port you can set one with another env-var: `PORT 3006`

When started, tinyscale will connect to each server and make a single call to check if your configuration is correct. If there is a problem tinyscale will abort. If your configuration works you can start using it in your environment by replacing your existing BBB settings with the new tinyscale url in your third party apps. Make sure to also replace the BBB secrets with your new `TINYSCALE_SECRET`.

tinyscale has been tested to work with NextCloud and Moodle.
tinyscale has been tested to work with NextCloud, Moodle and Greenlight.

If you want to use recordings they will work but you cannot get a list of all recordings. tinyscale will only respond with the next available server since it is a call to bbb without a `meetingID`. The same goes for the call to get infos on all meetings.

Expand Down
46 changes: 31 additions & 15 deletions app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { createError, opine, ErrorRequestHandler, Router, server, createHash } from "./deps.ts";
import { opine, ErrorRequestHandler, Router, createHash, server, createError, Color } from "./deps.ts";
import { BBB } from './bbb.ts';

const date = () => new Date().toLocaleTimeString('de')
const VERSION = 'v1.3.0'
console.log(date() + Color.green(` Starting tinyscale ${VERSION}`))
// 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"
Expand All @@ -22,46 +25,59 @@ servers.forEach(async s => {
if (!res.ok) throw "Connection error. Please check your host configuration"
const body = await res.text()
const ok = body.includes('SUCCESS')
console.log(`${s.host} is ${ok ? 'ok':'misconfigured. Please check your secret in servers.json'}`)
console.log(`${s.host} is ${ok ? Color.green('ok') : Color.red('misconfigured. Please check your secret in servers.json')}`)
if (!ok) throw "Configuration error. Exiting …"
} catch (e) {
// exit tinyscale if an error is encountered in servers.json
console.log(e)
console.log(Color.brightRed(e))
Deno.exit(1);
}
})

// pick the next server, using an iterator to cycle through all servers available
function get_available_server(): server {
let candidate = iterator.next()
if (candidate.done) {
iterator = servers[Symbol.iterator]()
candidate = iterator.next()
}
console.log(`Using next server ${candidate.value.host}`)
console.log(`Using next server ${Color.green(candidate.value.host)}`)
return candidate.value;
}

const router = Router()
// the api itself answering to every call
router.all("/bigbluebutton/api/:call", async (req, res, next) => {
router.all("/:call", async (req, res, next) => {
const handler = new BBB(req)
console.log(`${date()} New call to ${Color.green(handler.call)}`)
if (!handler.authenticated(secret)) {
res.setStatus(401).end()
console.log(`${Color.red("Rejected incoming call to "+handler.call)}`)
next(createError(401))
return
}
let server: server
try {
server = await handler.find_meeting_id(servers)
} catch (e) {
console.log(`Found no server with Meeting ID ${handler.meeting_id}`)
console.log(`Found no server with Meeting ID ${Color.yellow(handler.meeting_id)}`)
server = get_available_server()
}
console.log(`Redirecting to ${server.host}`)
res.redirect(handler.rewritten_query(server))
const redirect = handler.rewritten_query(server)
if (handler.call === 'join') {
res.redirect(redirect)
} else {
try {
const data = await fetch(redirect)
const body = await data.text()
res.set('Content-Type', 'text/xml');
res.send(body)
} catch (e) {
next(createError(500))
}
}
});
// the fake answering machine to make sure we are recognized as a proper api
router.get("/bigbluebutton/api", (req, res, next) => {
router.get("/", (req, res, next) => {
console.log('sending fake xml response')
res.set('Content-Type', 'text/xml');
res.send(`<response>
Expand All @@ -72,13 +88,13 @@ router.get("/bigbluebutton/api", (req, res, next) => {

const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
res.setStatus(err.status ?? 500);
console.log(res.status, req.originalUrl)
res.end();
console.log(`${Color.red(`${res.status}`)} ${req.originalUrl}`)
};

const app = opine()
.use("/", router)
.use((req, res, next) => { next(createError(404)); })
.use(errorHandler);
.use("/bigbluebutton/api", router)
.use((req, res, next) => next(createError(404)))
.use(errorHandler);

export default app;
export default app;
10 changes: 4 additions & 6 deletions bbb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request, ParamsDictionary, server, createHash } from "./deps.ts";
import { Request, ParamsDictionary, createHash, server } from "./deps.ts";

export class BBB {
call: string
Expand All @@ -12,10 +12,9 @@ export class BBB {
this.call = req.params.call
this.checksum_incoming = req.query.checksum
this.query = req._parsedUrl?.query || ""
this.params = this.query.replace(/[?|&]checksum.*$/, '')
this.params = this.query.replace(/[?&]?checksum.*$/, '')
this.meeting_id = req.query.meetingID
this.url = req.originalUrl
console.log(`New call to ${this.call}`)
}
// generate a checksum for various calls
generate_checksum = (secret: string, call: string = this.call, params: string = this.params) => {
Expand All @@ -31,13 +30,12 @@ export class BBB {
// write new query for target bbb server
rewritten_query = (server: server) => {
const checksum_outgoing = this.generate_checksum(server.secret)
return `${server.host}/${this.url.replace(this.checksum_incoming, checksum_outgoing)}`
return `${server.host}${this.url.replace(this.checksum_incoming, checksum_outgoing)}`
}
// check if request is autheticated with correct checksum
authenticated = (secret: string) => {
const checksum = this.generate_checksum(secret)
const ok = checksum === this.checksum_incoming
if (!ok) console.log(`Rejected incoming call to ${this.call}`)
return ok
}
find_meeting_id = (servers: server[]): Promise<server> => {
Expand All @@ -51,4 +49,4 @@ export class BBB {
})
return Promise.any(promises)
}
}
}
21 changes: 6 additions & 15 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
export {
join,
} from "https://deno.land/std@0.94.0/path/mod.ts";
export {
json,
opine,
Router,
} from "https://deno.land/x/opine@1.3.2/mod.ts";
export type {
ErrorRequestHandler,
Request,
ParamsDictionary
} from "https://deno.land/x/opine@1.3.2/mod.ts";
export interface server { host: string; secret: string };
export { join, } from "https://deno.land/std@0.95.0/path/mod.ts";
export { createHash } from "https://deno.land/std@0.95.0/hash/mod.ts";
export * as Color from "https://deno.land/std@0.95.0/fmt/colors.ts";
export { createError } from "https://deno.land/x/http_errors@3.0.0/mod.ts";
export { createHash } from "https://deno.land/std@0.94.0/hash/mod.ts";
export { opine, Router } from "https://deno.land/x/opine@1.3.2/mod.ts";
export type { ErrorRequestHandler, Request, ParamsDictionary } from "https://deno.land/x/opine@1.3.2/mod.ts";
export interface server { host: string; secret: string };

0 comments on commit baecc69

Please sign in to comment.