User:Thundercraft5/sw.js

/* global self, Request */ /* jshint undef:true, esversion: 6 */

import { parse } from "/wiki/User:Thundercraft5/sw.js/node-html-parser.js?action=raw&ctype=text/javascript"; // jshint ignore:line self.parseHTML = parse; const map = { "test": "User:Thundercraft5/sw.js/module.js", "test2": "User:Thundercraft5/sw.js/module2.js", }; const pathAliases = { "self": "User:${USER}/${PATH}", "wiki": "MediaWiki:${PATH}", };

const blockedDomains = [ "https://www.google-analytics.com/", "/extensions-ucp/MsUpload/", "https://beacon.wikia-services.com/", "https://services.fandom.com/wiki-recommendations/", ];

const blacklistedCSSModules = [ "skin.fandomdesktop.FanFeed.css", "ext.fandom.SearchExperiment.css", "ext.fandom.curatedContentTool.css", "ext.fandomVideo.css", "ext.fandom.GlobalComponents.GlobalFooter.css", "ext.fandom.CreatePage.css", ];

const userInfo = { id: null, name: null, messages: null, groups: null, groupmemberships: null, implicitgroups: null, rights: null, changeablegroups: null, editcount: null, ratelimits: null, latestcontrib: null, };

const npmProviders = { "unpkg": "https://unpkg.com/${PACKAGE}@${VERSION|latest}/${PATH}", "esm-sh": "https://esm.sh/${PACKAGE}@${VERSION|latest}/${PATH}", "jsdelivr": "https://cdn.jsdelivr.net/npm/${PACKAGE}@${VERSION|latest}/${PATH}", };

function apiFetch(method, params = {}, fetchOptions = {}) { method = method.toUpperCase; const defaults = { format: "json", };	/* jshint ignore:start */ const entries = Object.entries({ ...params, ...defaults }); const toString = => entries.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join("|") : v}`).join("&"); /* jshint ignore:end */

if (method === "POST") { /* jshint ignore:start */ return fetch("/api.php", {			body: toString,			method,			headers: {				["content-type"]: "application/x-www-form-urlencoded; charset=UTF-8"			},			...fetchOptions,		}).then(r => r.json); /* jshint ignore:end */ } else { /* jshint ignore:start */ return fetch(`/api.php?${ toString }`, { method, ...fetchOptions }).then(r => r.json); /* jshint ignore:end */ } }

function formatString(s, replacements) { return s.replace(/\${(? \w+)(?:\|(?.+?)?)?}/g, (...args) => {		const { name, defaultValue } = args.pop;		return replacements[name] || defaultValue || "";	}); }

function normalizeUrl(pagename) { const url = new URL(location); url.searchParams.append("action", "raw"); url.searchParams.append("ctype", "text/javascript"); url.searchParams.set("title", pagename); url.pathname = `/index.php`; return url; }

function redirectToPage(pagename) { return Response.redirect(`/@${pagename}`) }

function resolveImportPath(path) { let url; const isSpecifier = ![".", "/"].some(s => path.includes(s)); const { groups: providerMatch } = path.match(/^(? [\w\-]+):(? (? [a-zA-Z\-0-9]+)(?:@(? [\d\.]{1,3}\d+|latest))?(? [^\&\?\#]+)?)/) || {groups:{}}; if (npmProviders[providerMatch.provider]) { const { provider, path, version = "latest", package: packageName } = providerMatch; url = new URL(formatString(npmProviders[provider], { PACKAGE: packageName, PATH: path, VERSION: version, }));	} else if (pathAliases[providerMatch.provider?.toLowerCase]) { const { provider, text } = providerMatch; console.log(userInfo); return redirectToPage(formatString(pathAliases[provider], { USER: userInfo.name, PATH: text, }));	} else { if (isSpecifier) { normalized = map[normalized]; if (!normalized) throw new Error(`Module specifier "${normalized}" could not be resolved`); return redirectToPage(normalized); }		console.log(normalized); url = normalizeUrl(normalized); } 	return url; }

async function eventHandler { let { request: { url }, request } = this; const rawUrl = url; url = new URL(url); console.log(request.destination, this.clientId, url, Object.fromEntries(request.headers)); if (blockedDomains.some(d => rawUrl.includes(d))) return Promise.reject; if (url.searchParams.get("modules")?.includes("ext.fandom.ArticleInterlang.css") && url.pathname === "/load.php") { const modules = url.searchParams.get("modules").split("|").filter(m => !blacklistedCSSModules.includes(m)); url.searchParams.set("modules", modules.join("|")); console.log(new Request(url.toString, request)); return fetch(new Request(url.toString, request)); }	if (url.pathname.match(/^\/@(?!.*\/wiki\/).*(\.[mc]?js$)?/)) { let normalized = url.pathname.replace(/^\/@/g, ""); const resolved = resolveImportPath(normalized); if (resolved instanceof Response) return resolved; else url = resolved; return fetch(new Request(url, { ...request })) .then(async response => {				const headers = Object.fromEntries(response.headers);				const validTypes = ["application/javascript", "text/javascript"];				const type = headers?.["content-type"]?.split(";").shift;				if (!validTypes.includes(type)) 					throw new Error(`Server responded with a MIME type of "${type}", expected ${validTypes.map(s => `"${s}"`).join(" or ")} instead`);				return [					{ ...response, url }, 					headers,					await response.text,				];			}) .then(([response, headers, text]) => {				const imports = text.split("; ").slice(0, -1);				return new Response(new Blob([text], { type: headers["content-type"] }), { ...response, });			}); 	} else { return fetch(request).then(async response => {			const { status, headers } = response;			if (headers.get("content-type")?.includes("text/html") && url.pathname.startsWith("/wiki/") && response.status !== 204) {				const html = parse(await response.text, { blockTextElements: { script: true, // keep text content when parsing },					});				html.querySelectorAll("script").forEach(s => { const { rawText } = s;					if ([						"typeof window.__tcfapi ===",						"function genUID",						"var _plc={",						"var ads={",						"function trackFCPPageView(config)",					].some(s => rawText.includes(s))					|| [						"https://www.fastly-insights.com/",						"https://static.wikia.nocookie.net/silversurfer/",						"https://services.fandom.com/icbm/",					].some(site => s.rawAttrs.includes(site))) s.remove; });				html.querySelector(".global-footer")?.remove;				html.querySelectorAll(".global-navigation__links > *, .top-ads-container, .bottom-ads-container").forEach(n => n.remove);				return new Response(new Blob([html.toString], { type: headers.get("content-type") }), { status, headers })			}				return response;		}); } };

self.addEventListener('fetch', event => { 	const result = eventHandler.call(event);	event.respondWith(result.then(r => {		if (r === undefined) return fetch(event.request);		else return r;	})); });

self.addEventListener('activate', event => {	console.log(event); });

self.addEventListener('install', event => {	console.log(event);		event.waitUntil(Promise.all([		apiFetch("GET", { "action": "query", "format": "json", "meta": "userinfo", "uiprop": "latestcontrib|ratelimits|editcount|changeablegroups|rights|implicitgroups|groups|groupmemberships|blockinfo|hasmsg" }).then(({ query: { userinfo } }) => Object.assign(userInfo, userinfo)),		apiFetch("GET", { "action": "query", "format": "json", "assert": "user", "prop": "revisions", "generator": "allpages", "rvprop": "ids|content", "rvslots": "*", "gapnamespace": "8", "gaplimit": "max", }),	])); });