From 57d0b0f856737f9fdc56825ca817b7379f3e5a0f Mon Sep 17 00:00:00 2001 From: knotteye Date: Wed, 14 Oct 2020 00:03:45 -0500 Subject: [PATCH 1/7] initial work on client-side templating --- package.json | 3 ++- site/index.html | 37 +++++++++++++++++++++++++++++++++++++ src/http.ts | 9 ++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 site/index.html diff --git a/package.json b/package.json index e2b210f..dfb5249 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "cli": "ts-node src/cli.ts", "setup": "sh install/setup.sh", "migrate": "ts-node src/migrate.ts", - "invite": "ts-node src/cli.ts --invite" + "invite": "ts-node src/cli.ts --invite", + "make-templates": "cp node_modules/nunjucks/browser/nunjucks-slim.js site &&nunjucks-precompile -i [\"\\.html$\",\"\\.njk$\"] templates > site/templates.js" }, "repository": { "type": "git", diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..b7a72bd --- /dev/null +++ b/site/index.html @@ -0,0 +1,37 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/http.ts b/src/http.ts index 8eb06dc..1576883 100644 --- a/src/http.ts +++ b/src/http.ts @@ -56,11 +56,12 @@ async function init(){ } app.disable('x-powered-by'); //site handlers - await initSite(config['satyr']['registration']); + //await initSite(config['satyr']['registration']); //api handlers await initAPI(); //static files if nothing else matches first app.use(express.static(config['http']['directory'])); + await initFE(); //404 Handler app.use(function (req, res, next) { if(tryDecode(req.cookies.Authorization)) { @@ -73,6 +74,12 @@ async function init(){ server.listen(config['http']['port']); } +async function initFE(){ + app.get('*', (req, res) => { + res.sendFile(process.cwd()+'/'+config['http']['directory']+'/index.html'); + }); +} + async function newNick(socket, skip?: boolean, i?: number) { if(socket.handshake.headers['cookie'] && !skip){ let c = await parseCookie(socket.handshake.headers['cookie']); From 988e3473a74f7a57e1f87a30133197c450318560 Mon Sep 17 00:00:00 2001 From: knotteye Date: Wed, 14 Oct 2020 07:44:19 -0500 Subject: [PATCH 2/7] Big commit. Implement handlers for everything that's currently rendered server side in the client-side frontend. Add compiled templates file to .gitignore, will work out a system for making sure templates are compiled later. Fix a couple bugs in the API and templates. TODO for client-side rendering: Make sure templates get compiled before running the server. Add a config option to switch between server-side and client-side rendering Fancy SPA stuff like intercepting links to render changes without a page-reload --- .gitignore | 1 + package-lock.json | 7 +- package.json | 2 +- site/index.html | 40 ++++------- site/index.js | 141 +++++++++++++++++++++++++++++++++++++++ src/api.ts | 4 +- src/http.ts | 11 +++ templates/managevods.njk | 2 +- 8 files changed, 177 insertions(+), 31 deletions(-) create mode 100644 site/index.js diff --git a/.gitignore b/.gitignore index 8c8153b..4714669 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ config/**/* !config/.gitkeep install/db_setup.sql build/** +site/templates.js \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 78361b5..e6e1e5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.9.3", + "version": "0.9.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -963,6 +963,11 @@ "asn1.js": "^5.2.0" } }, + "jwt-decode": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.0.0.tgz", + "integrity": "sha512-RBQv2MTm3FNKQkdzhEyQwh5MbdNgMa+FyIJIK5RMWEn6hRgRHr7j55cRxGhRe6vGJDElyi6f6u/yfkP7AoXddA==" + }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", diff --git a/package.json b/package.json index dfb5249..f7f4fab 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "setup": "sh install/setup.sh", "migrate": "ts-node src/migrate.ts", "invite": "ts-node src/cli.ts --invite", - "make-templates": "cp node_modules/nunjucks/browser/nunjucks-slim.js site &&nunjucks-precompile -i [\"\\.html$\",\"\\.njk$\"] templates > site/templates.js" + "make-templates": "nunjucks-precompile -i [\"\\.html$\",\"\\.njk$\"] templates > site/templates.js" }, "repository": { "type": "git", diff --git a/site/index.html b/site/index.html index b7a72bd..af4c9bb 100644 --- a/site/index.html +++ b/site/index.html @@ -1,37 +1,23 @@ + + + + - + \ No newline at end of file diff --git a/site/index.js b/site/index.js new file mode 100644 index 0000000..c94fc3a --- /dev/null +++ b/site/index.js @@ -0,0 +1,141 @@ +async function render(){ + var context = await getContext(); + switch(window.location.pathname){ + //nothing but context + case (window.location.pathname.match(/^\/about\/?$/) || {}).input: + document.body.innerHTML = nunjucks.render('about.njk', context); + break; + case (window.location.pathname.match(/^\/login\/?$/) || {}).input: + document.body.innerHTML = nunjucks.render('login.njk', context); + break; + case (window.location.pathname.match(/^\/register\/?$/) || {}).input: + if(!context.registration) window.location = '/'; + document.body.innerHTML = nunjucks.render('registration.njk', context); + break; + case (window.location.pathname.match(/^\/changepwd\/?$/) || {}).input: + document.body.innerHTML = nunjucks.render('changepwd.njk', context); + break; + case (window.location.pathname.match(/^\/chat\/?$/) || {}).input: + document.body.innerHTML = nunjucks.render('chat.html', context); + break; + case (window.location.pathname.match(/^\/help\/?$/) || {}).input: + document.body.innerHTML = nunjucks.render('help.njk', context); + break; + //need to hit the API + case (window.location.pathname.match(/^\/users\/live\/?$/) || {}).input: + var list = JSON.parse(await makeRequest("POST", "/api/users/live", JSON.stringify({num: 50}))); + document.body.innerHTML = nunjucks.render('live.njk', Object.assign({list: list.users}, context)); + break; + case (window.location.pathname.match(/^\/users\/?$/) || {}).input: + var list = JSON.parse(await makeRequest("POST", "/api/users/all", JSON.stringify({num: 50}))); + document.body.innerHTML = nunjucks.render('list.njk', Object.assign({list: list.users}, context)); + break; + case (window.location.pathname.match(/^\/profile\/chat\/?$/) || {}).input: + if(!context.auth.name) window.location = '/login'; + var config = JSON.parse(await makeRequest("GET", '/api/'+context.auth.name+'/config')); + config = { + integ: { + twitch: config.twitch, + xmpp: config.xmpp, + irc: config.irc, + discord: config.discord + } + }; + document.body.innerHTML = nunjucks.render('chat_integ.njk', Object.assign(config, context)); + break; + case (window.location.pathname.match(/^\/profile\/?$/) || {}).input: + if(!context.auth.name) window.location = '/login'; + var config = JSON.parse(await makeRequest("GET", '/api/'+context.auth.name+'/config')); + config = { + meta: { + title: config.title, + about: config.about + }, + rflag: {record_flag: config.record_flag}, + twitch: config.twitch_mirror + }; + document.body.innerHTML = nunjucks.render('profile.njk', Object.assign(config, context)); + break; + //parsing slugs + case (window.location.pathname.match(/^\/invite\//) || {}).input: // /invite/:code + document.body.innerHTML = nunjucks.render('invite.njk', Object.assign({icode: window.location.pathname.substring(8)}, context)); + break; + //slugs and API + case (window.location.pathname.match(/^\/users\/.+\/?$/) || {}).input: // /users/:user + if(window.location.pathname.substring(window.location.pathname.length - 1).indexOf('/') !== -1) + var usr = window.location.pathname.substring(7, window.location.pathname.length - 1); + else var usr = window.location.pathname.substring(7); + var config = JSON.parse(await makeRequest("GET", '/api/'+usr+'/config')); + if(!config.title){document.body.innerHTML = nunjucks.render('404.njk', context); break;} + document.body.innerHTML = nunjucks.render('user.njk', Object.assign({about: config.about, title: config.title, username: config.username}, context)); + break; + case (window.location.pathname.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage + var usr = window.location.pathname.substring(6, (window.location.pathname.length - 7)); + if(context.auth.name !== usr) window.location = '/vods/'+usr; + var vods = JSON.parse(await makeRequest("GET", '/api/'+usr+'/vods')); + document.body.innerHTML = nunjucks.render('managevods.njk', Object.assign({user: usr, list: vods.vods.filter(fn => fn.name.endsWith('.mp4'))}, context)); + break; + case (window.location.pathname.match(/^\/vods\/.+\/?$/) || {}).input: // /vods/:user + if(window.location.pathname.substring(window.location.pathname.length - 1).indexOf('/') !== -1) + var usr = window.location.pathname.substring(6, window.location.pathname.length - 1); + else var usr = window.location.pathname.substring(6); + var vods = JSON.parse(await makeRequest("GET", '/api/'+usr+'/vods')); + document.body.innerHTML = nunjucks.render('vods.njk', Object.assign({user: usr, list: vods.vods.filter(fn => fn.name.endsWith('.mp4'))}, context)); + break; + //root + case "/": + window.location = '/users/live'; + break; + case "": + window.location = '/users/live'; + break; + //404 + default: + document.body.innerHTML = nunjucks.render('404.njk', context); + } +} + +async function getContext(){ + var info = JSON.parse(await makeRequest('GET', '/api/instance/info')); + info.sitename = info.name; + info.name = null; + info.auth = { + is: document.cookie.match(/^(.*;)?\s*X-Auth-As\s*=\s*[^;]+(.*)?$/) !== null, + name: parseCookie(document.cookie)['X-Auth-As'] + } + return info; +} + +function makeRequest(method, url, payload) { + return new Promise(function (resolve, reject) { + let xhr = new XMLHttpRequest(); + xhr.open(method, url); + xhr.onload = function () { + if (this.status >= 200 && this.status < 300) { + resolve(xhr.response); + } else { + reject({ + status: this.status, + statusText: xhr.statusText + }); + } + }; + xhr.onerror = function () { + reject({ + status: this.status, + statusText: xhr.statusText + }); + }; + !payload ? xhr.send() : xhr.send(payload); + }); +} + +function parseCookie(c){ + if(typeof(c) !== 'string' || !c.includes('=')) return {}; + return Object.assign({[c.split('=')[0].trim()]:c.split('=')[1].split(';')[0].trim()}, parseCookie(c.split(/;(.+)/)[1])); +} + +function handleLoad() { + var r = JSON.parse(document.getElementById('responseFrame').contentDocument.documentElement.textContent).success + if (typeof(r) !== 'undefined') window.location.href = '/profile' +} \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index d421bab..27b0d68 100644 --- a/src/api.ts +++ b/src/api.ts @@ -87,9 +87,11 @@ async function getConfig(username: string, all?: boolean): Promise{ let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username)); if(users[0]) Object.assign(t, users[0]); let usermeta = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username)); - if(usermeta[0]) Object.assign(t, users[0]); + if(usermeta[0]) Object.assign(t, usermeta[0]); let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username)); if(ci[0]) Object.assign(t, ci[0]); + let tw = await db.query('SELECT enabled,twitch_key FROM twitch_mirror WHERE username='+db.raw.escape(username)); + if(tw[0]) t['twitch_mirror'] = Object.assign({}, tw[0]); } else { let um = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username)); diff --git a/src/http.ts b/src/http.ts index 1576883..967a1f3 100644 --- a/src/http.ts +++ b/src/http.ts @@ -75,6 +75,15 @@ async function init(){ } async function initFE(){ + app.get('/', (req, res) => { + res.redirect(config['satyr']['rootredirect']); + }); + app.get('/nunjucks-slim.js', (req, res) => { + res.sendFile(process.cwd()+'/node_modules/nunjucks/browser/nunjucks-slim.js'); + }); + app.get('/chat', (req, res) => { + res.sendFile(process.cwd()+'/templates/chat.html'); + }); app.get('*', (req, res) => { res.sendFile(process.cwd()+'/'+config['http']['directory']+'/index.html'); }); @@ -368,6 +377,7 @@ async function initAPI() { if(req.cookies.Authorization) validToken(req.cookies.Authorization).then((t) => { if(t) { if(t['exp'] - 86400 < Math.floor(Date.now() / 1000)){ + res.cookie('X-Auth-As', t['username'], {maxAge: 604800000, httpOnly: false, sameSite: 'Lax'}); return genToken(t['username']).then((t) => { res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); res.json({success:""}); @@ -389,6 +399,7 @@ async function initAPI() { if(!result){ genToken(req.body.username).then((t) => { res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); + res.cookie('X-Auth-As', req.body.username, {maxAge: 604800000, httpOnly: false, sameSite: 'Lax'}); res.json({success:""}); }) } diff --git a/templates/managevods.njk b/templates/managevods.njk index 20928b1..0e8f2dc 100644 --- a/templates/managevods.njk +++ b/templates/managevods.njk @@ -8,7 +8,7 @@ {% else %} No recordings found! {% endeach %} - +
{% endblock %} \ No newline at end of file From 4ec89d71f8a1616798dbc51106e2f306351fac86 Mon Sep 17 00:00:00 2001 From: knotteye Date: Wed, 14 Oct 2020 07:51:26 -0500 Subject: [PATCH 3/7] Bump major version. There were some breaking changes in there somewhere --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7f4fab..e921a3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.9.4", + "version": "0.10.0", "description": "A livestreaming server.", "license": "AGPL-3.0", "author": "knotteye", From 961b5fe648429e11c937c14d15c45fc5efcb7b23 Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 16 Oct 2020 21:31:23 -0500 Subject: [PATCH 4/7] Add config option to turn server side rendering off. Ensure templates are precompiled before starting the server. --- README.md | 6 ++++-- install/config.example.yml | 1 + src/cleanup.ts | 14 ++++++++++++-- src/config.ts | 5 ++++- src/index.ts | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2018270..db204c1 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,13 @@ Follow the instructions after setup runs. ```bash npm run start ``` -You can also run this to skip checking the database version on startup. +You can also skip checking the database version and compiling templates (if you don't use server-side rendering) on startup. ```bash -npm run start -- --skip-migrate +npm run start -- --skip-migrate --skip-compile # don't forget to migrate manually when you update npm run migrate +# and compile templates after any changes +npm run make-templates ``` ## Contributing diff --git a/install/config.example.yml b/install/config.example.yml index c67d16b..be1a5a0 100644 --- a/install/config.example.yml +++ b/install/config.example.yml @@ -14,6 +14,7 @@ rtmp: http: # uncomment to set HSTS when SSL is ready #hsts: true + server_side_render: false database: user: '' diff --git a/src/cleanup.ts b/src/cleanup.ts index 56c9dc9..4a3fd58 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -1,8 +1,9 @@ import * as db from "./database"; import {readdirSync} from "fs"; +import { execSync } from "child_process"; -async function init(m?: boolean) { - if(!m){ +async function init() { + if(process.argv.indexOf('--skip-migrate') === -1){ console.log('Checking database version.'); var tmp: string[] = await db.query('show tables like \"db_meta\"'); if(tmp.length === 0){ @@ -17,6 +18,15 @@ async function init(m?: boolean) { else { console.log('Skipping database version check.'); } + + if(!require('./config').config['http']['server_side_render'] && process.argv.indexOf('--skip-compile') === -1) { + console.log("Compiling templates for client-side frontend."); + execSync(process.cwd()+'/node_modules/.bin/nunjucks-precompile -i [\"\\.html$\",\"\\.njk$\"] templates > site/templates.js'); + } + else if(!require('./config').config['http']['server_side_render']){ + console.log("Skipped compiling templates for client-side frontend."); + } + //If satyr is restarted in the middle of a stream //it causes problems //Live flags in the database stay live diff --git a/src/config.ts b/src/config.ts index 0fdeaa4..d9edb2e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -36,7 +36,10 @@ const config: Object = { ping: 30, ping_timeout: 60 }, localconfig['rtmp']), http: Object.assign({ - hsts: false, directory: './site', port: 8000 + hsts: false, + directory: './site', + port: 8000, + server_side_render: true }, localconfig['http']), media: Object.assign({ record: false, diff --git a/src/index.ts b/src/index.ts index 889c20f..2573948 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { config } from "./config"; async function run() { await initDB(); - await clean(process.argv.indexOf('--skip-migrate') !== -1); + await clean(); await initHTTP(); await initRTMP(); await initChat(); From 95837beaf7818f3159d40fb630bc43a436728d8e Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 16 Oct 2020 21:57:24 -0500 Subject: [PATCH 5/7] Make server side rendering fully configurable --- src/http.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/http.ts b/src/http.ts index 967a1f3..f83b61b 100644 --- a/src/http.ts +++ b/src/http.ts @@ -12,9 +12,6 @@ import * as chatInteg from "./chat"; import { config } from "./config"; import { readdir, readFileSync, writeFileSync } from "fs"; import { JWT, JWK } from "jose"; -import { strict } from "assert"; -import { parse } from "path"; -import { isBuffer } from "util"; const app = express(); const server = http.createServer(app); @@ -55,12 +52,15 @@ async function init(){ }); } app.disable('x-powered-by'); - //site handlers - //await initSite(config['satyr']['registration']); - //api handlers + //server-side site routes + if(config['http']['server_side_render']) + await initSite(config['satyr']['registration']); + //api routes await initAPI(); - //static files if nothing else matches first + //static files if nothing else matches app.use(express.static(config['http']['directory'])); + //client-side site routes + if(!config['http']['server_side_render']) await initFE(); //404 Handler app.use(function (req, res, next) { From 54a891dac1eea53cdf36187b8a084518e76dcafb Mon Sep 17 00:00:00 2001 From: knotteye Date: Fri, 16 Oct 2020 22:25:24 -0500 Subject: [PATCH 6/7] Update documentation --- docs/REST.md | 2 +- templates/base.njk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index c6004a0..9058efb 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -115,7 +115,7 @@ Obtain a signed json web token for authentication **Response**: If succesful, will return `{success: ""}` or `{success: "already verified"}` if the JWT provided is too early to be renewed. If unsuccesful, will return `{error: "invalid password"}` or `{error: "Username or Password Incorrect"}` depending on the authentication method. Note that if a JWT is available, the parameters will be ignored. -**Notes**: I've already listed nearly every response. My final note is that the JWT is set as the cookie 'Authorization', not returned in the response. +**Notes**: The returned JWT is set as the cookie httponly 'Authorization'. It will also return a non httponly cookie X-Auth-As with the username of the authenticated user. ## /api/user/update diff --git a/templates/base.njk b/templates/base.njk index 9d57b04..f832bc3 100644 --- a/templates/base.njk +++ b/templates/base.njk @@ -6,7 +6,7 @@ {{ sitename }} - + \ No newline at end of file diff --git a/site/index.js b/site/index.js index c94fc3a..8a06b30 100644 --- a/site/index.js +++ b/site/index.js @@ -1,36 +1,44 @@ -async function render(){ +async function render(path){ var context = await getContext(); - switch(window.location.pathname){ + switch(path){ //nothing but context - case (window.location.pathname.match(/^\/about\/?$/) || {}).input: + case (path.match(/^\/about\/?$/) || {}).input: document.body.innerHTML = nunjucks.render('about.njk', context); + modifyLinks(); break; - case (window.location.pathname.match(/^\/login\/?$/) || {}).input: + case (path.match(/^\/login\/?$/) || {}).input: document.body.innerHTML = nunjucks.render('login.njk', context); + modifyLinks(); break; - case (window.location.pathname.match(/^\/register\/?$/) || {}).input: + case (path.match(/^\/register\/?$/) || {}).input: if(!context.registration) window.location = '/'; document.body.innerHTML = nunjucks.render('registration.njk', context); + modifyLinks(); break; - case (window.location.pathname.match(/^\/changepwd\/?$/) || {}).input: + case (path.match(/^\/changepwd\/?$/) || {}).input: document.body.innerHTML = nunjucks.render('changepwd.njk', context); + modifyLinks(); break; - case (window.location.pathname.match(/^\/chat\/?$/) || {}).input: + case (path.match(/^\/chat\/?$/) || {}).input: document.body.innerHTML = nunjucks.render('chat.html', context); + modifyLinks(); break; - case (window.location.pathname.match(/^\/help\/?$/) || {}).input: + case (path.match(/^\/help\/?$/) || {}).input: document.body.innerHTML = nunjucks.render('help.njk', context); + modifyLinks(); break; //need to hit the API - case (window.location.pathname.match(/^\/users\/live\/?$/) || {}).input: + case (path.match(/^\/users\/live\/?$/) || {}).input: var list = JSON.parse(await makeRequest("POST", "/api/users/live", JSON.stringify({num: 50}))); document.body.innerHTML = nunjucks.render('live.njk', Object.assign({list: list.users}, context)); + modifyLinks(); break; - case (window.location.pathname.match(/^\/users\/?$/) || {}).input: + case (path.match(/^\/users\/?$/) || {}).input: var list = JSON.parse(await makeRequest("POST", "/api/users/all", JSON.stringify({num: 50}))); document.body.innerHTML = nunjucks.render('list.njk', Object.assign({list: list.users}, context)); + modifyLinks(); break; - case (window.location.pathname.match(/^\/profile\/chat\/?$/) || {}).input: + case (path.match(/^\/profile\/chat\/?$/) || {}).input: if(!context.auth.name) window.location = '/login'; var config = JSON.parse(await makeRequest("GET", '/api/'+context.auth.name+'/config')); config = { @@ -42,8 +50,9 @@ async function render(){ } }; document.body.innerHTML = nunjucks.render('chat_integ.njk', Object.assign(config, context)); + modifyLinks(); break; - case (window.location.pathname.match(/^\/profile\/?$/) || {}).input: + case (path.match(/^\/profile\/?$/) || {}).input: if(!context.auth.name) window.location = '/login'; var config = JSON.parse(await makeRequest("GET", '/api/'+context.auth.name+'/config')); config = { @@ -55,43 +64,49 @@ async function render(){ twitch: config.twitch_mirror }; document.body.innerHTML = nunjucks.render('profile.njk', Object.assign(config, context)); + modifyLinks(); break; //parsing slugs - case (window.location.pathname.match(/^\/invite\//) || {}).input: // /invite/:code - document.body.innerHTML = nunjucks.render('invite.njk', Object.assign({icode: window.location.pathname.substring(8)}, context)); + case (path.match(/^\/invite\//) || {}).input: // /invite/:code + document.body.innerHTML = nunjucks.render('invite.njk', Object.assign({icode: path.substring(8)}, context)); + modifyLinks(); break; //slugs and API - case (window.location.pathname.match(/^\/users\/.+\/?$/) || {}).input: // /users/:user - if(window.location.pathname.substring(window.location.pathname.length - 1).indexOf('/') !== -1) - var usr = window.location.pathname.substring(7, window.location.pathname.length - 1); - else var usr = window.location.pathname.substring(7); + case (path.match(/^\/users\/.+\/?$/) || {}).input: // /users/:user + if(path.substring(path.length - 1).indexOf('/') !== -1) + var usr = path.substring(7, path.length - 1); + else var usr = path.substring(7); var config = JSON.parse(await makeRequest("GET", '/api/'+usr+'/config')); if(!config.title){document.body.innerHTML = nunjucks.render('404.njk', context); break;} document.body.innerHTML = nunjucks.render('user.njk', Object.assign({about: config.about, title: config.title, username: config.username}, context)); + modifyLinks(); break; - case (window.location.pathname.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage - var usr = window.location.pathname.substring(6, (window.location.pathname.length - 7)); + case (path.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage + var usr = path.substring(6, (path.length - 7)); if(context.auth.name !== usr) window.location = '/vods/'+usr; var vods = JSON.parse(await makeRequest("GET", '/api/'+usr+'/vods')); document.body.innerHTML = nunjucks.render('managevods.njk', Object.assign({user: usr, list: vods.vods.filter(fn => fn.name.endsWith('.mp4'))}, context)); + modifyLinks(); break; - case (window.location.pathname.match(/^\/vods\/.+\/?$/) || {}).input: // /vods/:user - if(window.location.pathname.substring(window.location.pathname.length - 1).indexOf('/') !== -1) - var usr = window.location.pathname.substring(6, window.location.pathname.length - 1); - else var usr = window.location.pathname.substring(6); + case (path.match(/^\/vods\/.+\/?$/) || {}).input: // /vods/:user + if(path.substring(path.length - 1).indexOf('/') !== -1) + var usr = path.substring(6, path.length - 1); + else var usr = path.substring(6); var vods = JSON.parse(await makeRequest("GET", '/api/'+usr+'/vods')); document.body.innerHTML = nunjucks.render('vods.njk', Object.assign({user: usr, list: vods.vods.filter(fn => fn.name.endsWith('.mp4'))}, context)); + modifyLinks(); break; //root case "/": - window.location = '/users/live'; + render('/users/live'); break; case "": - window.location = '/users/live'; + render('/users/live'); break; //404 default: document.body.innerHTML = nunjucks.render('404.njk', context); + modifyLinks(); } } @@ -138,4 +153,18 @@ function parseCookie(c){ function handleLoad() { var r = JSON.parse(document.getElementById('responseFrame').contentDocument.documentElement.textContent).success if (typeof(r) !== 'undefined') window.location.href = '/profile' +} + +function modifyLinks() { + for (var ls = document.links, numLinks = ls.length, i=0; i