From a9cf43fe897fd0cd06614edbabe04080f82f19a4 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Sat, 13 Dec 2025 15:36:02 -0600 Subject: [PATCH] traces --- src/webpage/localuser.ts | 21 +++++++++- src/webpage/service.ts | 32 ++++++++++++++- src/webpage/settings.ts | 68 +++++++++++++++++--------------- src/webpage/utils/serviceType.ts | 8 ++++ src/webpage/utils/utils.ts | 23 ++++++++++- translations/en.json | 3 +- 6 files changed, 119 insertions(+), 36 deletions(-) diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index f387361..8657780 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -644,6 +644,11 @@ class Localuser { this.rights.update(rights); this.perminfo.user.rights = rights; } + traceSub() { + SW.captureEvent("trace", (e) => { + this.handleTrace(e.trace); + }); + } async handleEvent(temp: wsjson) { if (temp.d._trace) this.handleTrace(temp.d._trace); if (localStorage.getItem("logGateway")) console.debug(temp); @@ -3247,9 +3252,23 @@ class Localuser { SW.postMessage({code: "isDev", dev: e}); }; devSettings.addText(I18n.devSettings.cacheDesc()); + + const box5 = devSettings.addCheckboxInput(I18n.devSettings.captureTrace(), () => {}, { + initState: !!localStorage.getItem("capTrace"), + }); + box5.onchange = (e) => { + if (e) { + localStorage.setItem("capTrace", "true"); + } else { + localStorage.removeItem("capTrace"); + } + SW.traceInit(); + }; } if (this.trace.length && localStorage.getItem("traces")) { - const traces = settings.addButton(I18n.localuser.trace()); + const traces = settings.addButton(I18n.localuser.trace(), { + noSubmit: true, + }); const traceArr = this.trace; const sel = traces.addSelect( diff --git a/src/webpage/service.ts b/src/webpage/service.ts index 24e1dd4..a0943c5 100644 --- a/src/webpage/service.ts +++ b/src/webpage/service.ts @@ -171,8 +171,30 @@ async function getfile(event: FetchEvent): Promise { } } -self.addEventListener("fetch", (e) => { +self.addEventListener("fetch", async (e) => { const event = e as FetchEvent; + + if (URL.canParse(event.request.url) && apiHosts?.has(new URL(event.request.url).host)) { + try { + const responce = await fetch(event.request.clone()); + try { + event.respondWith(responce.clone()); + } catch {} + + const json = await responce.json(); + if (json._trace) { + sendAll({ + code: "trace", + trace: json._trace, + }); + } + } catch (e) { + console.error(e); + //Wasn't meant to be ig lol + } + return; + } + if (event.request.method === "POST") { return; } @@ -187,6 +209,7 @@ self.addEventListener("fetch", (e) => { }); const ports = new Set(); let dev = false; +let apiHosts: Set | void; function listenToPort(port: MessagePort) { function sendMessage(message: messageFrom) { port.postMessage(message); @@ -233,6 +256,13 @@ function listenToPort(port: MessagePort) { } break; } + case "apiUrls": { + if (data.hosts) { + apiHosts = new Set(data.hosts); + } else { + apiHosts = undefined; + } + } } }; port.addEventListener("close", () => { diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 60b7221..4c457d2 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -1112,7 +1112,7 @@ class Options implements OptionsElement { headers = {}, method = "POST", traditionalSubmit = false, - tfaCheck=true + tfaCheck = true, } = {}, ) { const options = new Form(name, this, onSubmit, { @@ -1122,7 +1122,7 @@ class Options implements OptionsElement { headers, method, traditionalSubmit, - tfaCheck + tfaCheck, }); this.subOptions = options; this.genTop(); @@ -1304,10 +1304,10 @@ class Options implements OptionsElement { container.append(div); } } - deleteElm(opt:OptionsElement){ + deleteElm(opt: OptionsElement) { const html = this.html.get(opt)?.deref(); - this.options=this.options.filter(_=>_!==opt); - if(!html) return; + this.options = this.options.filter((_) => _ !== opt); + if (!html) return; html.remove(); this.html.delete(opt); } @@ -1559,42 +1559,48 @@ class FormError extends Error { } async function handle2fa(json: any, api: string): Promise { if (json.ticket) { - if(json.webauthn){ - const challenge = JSON.parse(json.webauthn).publicKey as PublicKeyCredentialRequestOptionsJSON; - challenge.challenge=challenge.challenge.split("=")[0].replaceAll("+","-").replaceAll("/","_"); - challenge.allowCredentials?.forEach(_=>_.id=_.id.split("=")[0].replaceAll("+","-").replaceAll("/","_")) + if (json.webauthn) { + const challenge = JSON.parse(json.webauthn) + .publicKey as PublicKeyCredentialRequestOptionsJSON; + challenge.challenge = challenge.challenge + .split("=")[0] + .replaceAll("+", "-") + .replaceAll("/", "_"); + challenge.allowCredentials?.forEach( + (_) => (_.id = _.id.split("=")[0].replaceAll("+", "-").replaceAll("/", "_")), + ); console.log(challenge); const options = PublicKeyCredential.parseRequestOptionsFromJSON(challenge); - const credential = await navigator.credentials.get({publicKey: options}) as unknown as { - rawId:ArrayBuffer, - response:{ - [key:string]:ArrayBuffer, - } + const credential = (await navigator.credentials.get({publicKey: options})) as unknown as { + rawId: ArrayBuffer; + response: { + [key: string]: ArrayBuffer; + }; }; - if(!credential) return false; - function toBase64(buf:ArrayBuffer){ - return btoa(String.fromCharCode(...new Uint8Array(buf))) + if (!credential) return false; + function toBase64(buf: ArrayBuffer) { + return btoa(String.fromCharCode(...new Uint8Array(buf))); } - const keys = ["authenticatorData","clientDataJSON","signature"]; + const keys = ["authenticatorData", "clientDataJSON", "signature"]; const response = {} as any; - for(const key of keys){ - response[key]=toBase64(credential.response[key] as ArrayBuffer); + for (const key of keys) { + response[key] = toBase64(credential.response[key] as ArrayBuffer); } const res = { - rawId:toBase64(credential.rawId), - response + rawId: toBase64(credential.rawId), + response, }; - const resObj = await fetch(api + "/auth/mfa/webauthn",{ + const resObj = await fetch(api + "/auth/mfa/webauthn", { method: "POST", headers: { "Content-Type": "application/json", }, - body:JSON.stringify({code:JSON.stringify(res),ticket:json.ticket}) + body: JSON.stringify({code: JSON.stringify(res), ticket: json.ticket}), }); - if(!resObj.ok) return false; + if (!resObj.ok) return false; const jsonRes = await resObj.json(); return jsonRes; - }else{ + } else { return new Promise((resolution) => { const better = new Dialog(""); const form = better.options.addForm( @@ -1656,7 +1662,7 @@ class Form implements OptionsElement { value!: object; traditionalSubmit: boolean; values: {[key: string]: any} = {}; - tfaCheck:boolean; + tfaCheck: boolean; constructor( name: string, owner: Options, @@ -1669,12 +1675,12 @@ class Form implements OptionsElement { method = "POST", traditionalSubmit = false, vsmaller = false, - tfaCheck=true + tfaCheck = true, } = {}, ) { this.traditionalSubmit = traditionalSubmit; this.name = name; - this.tfaCheck=tfaCheck; + this.tfaCheck = tfaCheck; this.method = method; this.submitText = submitText; this.options = new Options(name, this, {ltr, vsmaller}); @@ -2153,8 +2159,8 @@ class Settings extends Buttons { constructor(name: string) { super(name); } - addButton(name: string, {ltr = false, optName = name} = {}): Options { - const options = new Options(optName, this, {ltr}); + addButton(name: string, {ltr = false, optName = name, noSubmit = false} = {}): Options { + const options = new Options(optName, this, {ltr, noSubmit}); this.add(name, options); return options; } diff --git a/src/webpage/utils/serviceType.ts b/src/webpage/utils/serviceType.ts index cb3389d..bdaacd8 100644 --- a/src/webpage/utils/serviceType.ts +++ b/src/webpage/utils/serviceType.ts @@ -18,6 +18,10 @@ export type messageTo = | { code: "isDev"; dev: boolean; + } + | { + code: "apiUrls"; + hosts?: string[]; }; export type messageFrom = | { @@ -38,4 +42,8 @@ export type messageFrom = code: "isValid"; url: string; valid: boolean; + } + | { + code: "trace"; + trace: string[]; }; diff --git a/src/webpage/utils/utils.ts b/src/webpage/utils/utils.ts index 12d2fa3..ec52821 100644 --- a/src/webpage/utils/utils.ts +++ b/src/webpage/utils/utils.ts @@ -35,10 +35,22 @@ export function setTheme() { } export function getBulkUsers() { const json = getBulkInfo(); + apiDoms.clear(); for (const thing in json.users) { const user = (json.users[thing] = new Specialuser(json.users[thing])); apiDoms.add(new URL(user.serverurls.api).host); } + if (localStorage.getItem("capTrace")) { + SW.postMessage({ + code: "apiUrls", + hosts: [...apiDoms], + }); + } else { + SW.postMessage({ + code: "apiUrls", + hosts: undefined, + }); + } return json; } export function getBulkInfo() { @@ -883,9 +895,16 @@ export class SW { } }); } + static traceInit() { + getBulkUsers(); + } static needsUpdate = false; - static postMessage(message: messageTo) { - this.port?.postMessage(message); + static async postMessage(message: messageTo) { + if (!("serviceWorker" in navigator)) return; + while (!this.port) { + await new Promise((res) => setTimeout(res, 100)); + } + this.port.postMessage(message); } static eventListeners = new Map< messageFrom["code"], diff --git a/translations/en.json b/translations/en.json index c489e17..ef69006 100644 --- a/translations/en.json +++ b/translations/en.json @@ -297,7 +297,8 @@ "logGateway":"Log received gateway events (log level info):", "traces":"Expose traces:", "cache":"Enable Service Worker Caching map files:", - "cacheDesc":"map files will still load either way, this'll just make sure they're in cache when a new update rolls out." + "cacheDesc":"map files will still load either way, this'll just make sure they're in cache when a new update rolls out.", + "captureTrace":"This setting tells Fermi to capture _trace properties from the server, enabling this may cause progressive JSON decoding to not work (might require a reload)" }, "htmlPages": { "idpermissions": "This will allow the bot to:",