Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mastodon - Replace web client with specified assets
// ==UserScript==
// @name Mastodon - Replace web client
// @namespace https://github.com/unarist/
// @version 0.2
// @description Replace web client with local assets
// @author unarist
// @match https://mstdn.maud.io/
// @match https://mstdn.maud.io/favicon.ico
// @match https://mstdn.maud.io/web/*
// @grant none
// ==/UserScript==
/*
* Assets need to be accessed via HTTPS for https instances
* Asset path will be resolved using manifest.json, so check contents of the file.
* You may need to modify some Webpack config to use auto reloading in webpack-dev-server
* sw.js cannot be injected with this script. Please consider to use debug proxy (Fiddler, Charles, etc.)
*/
(function() {
const assetBase = 'https://localhost:8080/',
dummyPath = '/favicon.ico',
launchKey = '#local-web',
tag = (name, props) => Object.assign(document.createElement(name), props);
if (location.pathname !== dummyPath) {
console.log('Replace web client: Redirecting to dummyPath...');
location.href = dummyPath + launchKey + location.pathname;
return;
} else if (!location.hash.startsWith(launchKey)) {
return;
}
history.replaceState(null, null, location.hash.slice(launchKey.length));
document.documentElement.innerHTML = '<body id="mastodon" style="background: #282c37; color: #9baec8;">Replace web client: loading...</body>';
const getManifest = fetch(assetBase + 'manifest.json').then(resp => resp.json());
const getHtml = fetch(location.origin + '/', { credentials: 'same-origin' })
.then(resp => {
if (resp.ok && resp.headers.get('Content-Type').startsWith('text/html')) return resp.text();
else throw new Error(`Fetching failed: ${resp.statusText}, Content-Type: ${resp.headers.get('Content-Type')}`);
});
Promise.all([getManifest, getHtml])
.then(([manifest, html]) => {
const parser = new DOMParser();
const dom = parser.parseFromString(html, 'text/html');
dom.head.querySelectorAll('script[src], link[rel="stylesheet"]').forEach(e => e.remove());
['common', 'default'].forEach(k => dom.head.appendChild(tag('link', { href: assetBase.slice(0,-1) + manifest[k + '.css'], rel: 'stylesheet' })));
document.documentElement.innerHTML = dom.documentElement.innerHTML;
let prev = Promise.resolve();
const features = [...Object.keys(manifest)].filter(x => /^(features|modal|emoji|status).+\.js$/.test(x)).map(x=>x.slice(0,-3));
const load = name => new Promise(done => document.head.appendChild(tag('script', { src: assetBase.slice(0,-1) + manifest[name + '.js'] })).onload = done);
['common', ['locale_en', ...features], 'application'].forEach(k => {
console.log('loading: ', k);
if (Array.isArray(k)) {
prev = prev.then(() => Promise.all(k.map(load)));
} else {
prev = prev.then(() => load(k));
}
});
console.log('done');
})
.catch(e => {
document.body.textContent = e;
throw e;
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment