import{ Dialog }from"./dialog.js"; const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); function setTheme(){ let name = localStorage.getItem("theme"); if(!name){ localStorage.setItem("theme", "Dark"); name = "Dark"; } document.body.className = name + "-theme"; } let instances: | { name: string; description?: string; descriptionLong?: string; image?: string; url?: string; display?: boolean; online?: boolean; uptime: { alltime: number; daytime: number; weektime: number }; urls: { wellknown: string; api: string; cdn: string; gateway: string; login?: string; }; }[] | null; setTheme(); function getBulkUsers(){ const json = getBulkInfo(); for(const thing in json.users){ json.users[thing] = new Specialuser(json.users[thing]); } return json; } function trimswitcher(){ const json = getBulkInfo(); const map = new Map(); for(const thing in json.users){ const user = json.users[thing]; let wellknown = user.serverurls.wellknown; if(wellknown.at(-1) !== "/"){ wellknown += "/"; } wellknown += user.username; if(map.has(wellknown)){ const otheruser = map.get(wellknown); if(otheruser[1].serverurls.wellknown.at(-1) === "/"){ delete json.users[otheruser[0]]; map.set(wellknown, [thing, user]); }else{ delete json.users[thing]; } }else{ map.set(wellknown, [thing, user]); } } for(const thing in json.users){ if(thing.at(-1) === "/"){ const user = json.users[thing]; delete json.users[thing]; json.users[thing.slice(0, -1)] = user; } } localStorage.setItem("userinfos", JSON.stringify(json)); console.log(json); } function getBulkInfo(){ return JSON.parse(localStorage.getItem("userinfos")!); } function setDefaults(){ let userinfos = getBulkInfo(); if(!userinfos){ localStorage.setItem( "userinfos", JSON.stringify({ currentuser: null, users: {}, preferences: { theme: "Dark", notifications: false, notisound: "three", }, }) ); userinfos = getBulkInfo(); } if(userinfos.users === undefined){ userinfos.users = {}; } if(userinfos.accent_color === undefined){ userinfos.accent_color = "#3096f7"; } document.documentElement.style.setProperty( "--accent-color", userinfos.accent_color ); if(userinfos.preferences === undefined){ userinfos.preferences = { theme: "Dark", notifications: false, notisound: "three", }; } if(userinfos.preferences && userinfos.preferences.notisound === undefined){ userinfos.preferences.notisound = "three"; } localStorage.setItem("userinfos", JSON.stringify(userinfos)); } setDefaults(); class Specialuser{ serverurls: { api: string; cdn: string; gateway: string; wellknown: string; login: string; }; email: string; token: string; loggedin; json; constructor(json: any){ if(json instanceof Specialuser){ console.error("specialuser can't construct from another specialuser"); } this.serverurls = json.serverurls; let apistring = new URL(json.serverurls.api).toString(); apistring = apistring.replace(/\/(v\d+\/?)?$/, "") + "/v9"; this.serverurls.api = apistring; this.serverurls.cdn = new URL(json.serverurls.cdn) .toString() .replace(/\/$/, ""); this.serverurls.gateway = new URL(json.serverurls.gateway) .toString() .replace(/\/$/, ""); this.serverurls.wellknown = new URL(json.serverurls.wellknown) .toString() .replace(/\/$/, ""); this.serverurls.login = new URL(json.serverurls.login) .toString() .replace(/\/$/, ""); this.email = json.email; this.token = json.token; this.loggedin = json.loggedin; this.json = json; this.json.localuserStore ??= {}; if(!this.serverurls || !this.email || !this.token){ console.error( "There are fundamentally missing pieces of info missing from this user" ); } } set pfpsrc(e){ this.json.pfpsrc = e; this.updateLocal(); } get pfpsrc(){ return this.json.pfpsrc; } set username(e){ this.json.username = e; this.updateLocal(); } get username(){ return this.json.username; } set localuserStore(e){ this.json.localuserStore = e; this.updateLocal(); } get localuserStore(){ return this.json.localuserStore; } get uid(){ return this.email + this.serverurls.wellknown; } toJSON(){ return this.json; } updateLocal(){ const info = getBulkInfo(); info.users[this.uid] = this.toJSON(); localStorage.setItem("userinfos", JSON.stringify(info)); } } function adduser(user: typeof Specialuser.prototype.json){ user = new Specialuser(user); const info = getBulkInfo(); info.users[user.uid] = user; info.currentuser = user.uid; localStorage.setItem("userinfos", JSON.stringify(info)); return user; } const instancein = document.getElementById("instancein") as HTMLInputElement; let timeout: ReturnType | string | number | undefined | null = null; // let instanceinfo; const stringURLMap = new Map(); const stringURLsMap = new Map< string, { wellknown: string; api: string; cdn: string; gateway: string; login?: string; } >(); async function getapiurls(str: string): Promise< { api: string; cdn: string; gateway: string; wellknown: string; login: string; } | false >{ function appendApi(str:string){ return str.includes("api")?"" : (str.endsWith("/")? "api" : "/api"); } if(!URL.canParse(str)){ const val = stringURLMap.get(str); if(stringURLMap.size===0){ await new Promise(res=>{ setInterval(()=>{ if(stringURLMap.size!==0){ res(); } },100); }); } if(val){ str = val; }else{ const val = stringURLsMap.get(str); if(val){ const responce = await fetch( val.api + (val.api.endsWith("/") ? "" : "/") + "ping" ); if(responce.ok){ if(val.login){ return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; }else{ val.login = val.api; return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; } } } } } if(str.at(-1) !== "/"){ str += "/"; } let api: string; try{ const info = await fetch(`${str}.well-known/spacebar`).then(x=>x.json() ); api = info.api; }catch{ api=str; } if(!URL.canParse(api)){ return false; } const url = new URL(api); try{ const info = await fetch( `${api}${ url.pathname.includes("api") ? "" : "api" }/policies/instance/domains` ).then(x=>x.json()); const apiurl = new URL(info.apiEndpoint); return{ api: info.apiEndpoint+appendApi(apiurl.pathname), gateway: info.gateway, cdn: info.cdn, wellknown: str, login: info.apiEndpoint+appendApi(apiurl.pathname), }; }catch{ const val = stringURLsMap.get(str); if(val){ const responce = await fetch( val.api + (val.api.endsWith("/") ? "" : "/") + "ping" ); if(responce.ok){ if(val.login){ return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; }else{ val.login = val.api; return val as { wellknown: string; api: string; cdn: string; gateway: string; login: string; }; } } } return false; } } async function checkInstance(instance?: string){ const verify = document.getElementById("verify"); try{ verify!.textContent = "Checking Instance"; const instanceValue = instance || (instancein as HTMLInputElement).value; const instanceinfo = (await getapiurls(instanceValue)) as { wellknown: string; api: string; cdn: string; gateway: string; login: string; value: string; }; if(instanceinfo){ instanceinfo.value = instanceValue; localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); verify!.textContent = "Instance is all good"; // @ts-ignore if(checkInstance.alt){ // @ts-ignore checkInstance.alt(); } setTimeout((_: any)=>{ console.log(verify!.textContent); verify!.textContent = ""; }, 3000); }else{ verify!.textContent = "Invalid Instance, try again"; } }catch{ console.log("catch"); verify!.textContent = "Invalid Instance, try again"; } } if(instancein){ console.log(instancein); instancein.addEventListener("keydown", ()=>{ const verify = document.getElementById("verify"); verify!.textContent = "Waiting to check Instance"; if(timeout !== null && typeof timeout !== "string"){ clearTimeout(timeout); } timeout = setTimeout(()=>checkInstance(), 1000); }); if(localStorage.getItem("instanceinfo")){ const json = JSON.parse(localStorage.getItem("instanceinfo")!); if(json.value){ (instancein as HTMLInputElement).value = json.value; }else{ (instancein as HTMLInputElement).value = json.wellknown; } }else{ checkInstance("https://spacebar.chat/"); } } async function login(username: string, password: string, captcha: string){ if(captcha === ""){ captcha = ""; } const options = { method: "POST", body: JSON.stringify({ login: username, password, undelete: false, captcha_key: captcha, }), headers: { "Content-type": "application/json; charset=UTF-8", }, }; try{ const info = JSON.parse(localStorage.getItem("instanceinfo")!); const api = info.login + (info.login.startsWith("/") ? "/" : ""); return await fetch(api + "/auth/login", options) .then(response=>response.json()) .then(response=>{ console.log(response, response.message); if(response.message === "Invalid Form Body"){ return response.errors.login._errors[0].message; console.log("test"); } //this.serverurls||!this.email||!this.token console.log(response); if(response.captcha_sitekey){ const capt = document.getElementById("h-captcha"); if(capt!.children.length){ eval("hcaptcha.reset()"); }else{ const capty = document.createElement("div"); capty.classList.add("h-captcha"); capty.setAttribute("data-sitekey", response.captcha_sitekey); const script = document.createElement("script"); script.src = "https://js.hcaptcha.com/1/api.js"; capt!.append(script); capt!.append(capty); } }else{ console.log(response); if(response.ticket){ let onetimecode = ""; new Dialog([ "vdiv", ["title", "2FA code:"], [ "textbox", "", "", function(this: HTMLInputElement){ // eslint-disable-next-line no-invalid-this onetimecode = this.value; }, ], [ "button", "", "Submit", function(){ fetch(api + "/auth/mfa/totp", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ code: onetimecode, ticket: response.ticket, }), }) .then(r=>r.json()) .then(res=>{ if(res.message){ alert(res.message); }else{ console.warn(res); if(!res.token)return; adduser({ serverurls: JSON.parse( localStorage.getItem("instanceinfo")! ), email: username, token: res.token, }).username = username; const redir = new URLSearchParams( window.location.search ).get("goback"); if(redir){ window.location.href = redir; }else{ window.location.href = "/channels/@me"; } } }); }, ], ]).show(); }else{ console.warn(response); if(!response.token)return; adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")!), email: username, token: response.token, }).username = username; const redir = new URLSearchParams(window.location.search).get( "goback" ); if(redir){ window.location.href = redir; }else{ window.location.href = "/channels/@me"; } return""; } } }); }catch(error){ console.error("Error:", error); } } async function check(e: SubmitEvent){ e.preventDefault(); const target = e.target as HTMLFormElement; const h = await login( (target[1] as HTMLInputElement).value, (target[2] as HTMLInputElement).value, (target[3] as HTMLInputElement).value ); const wrongElement = document.getElementById("wrong"); if(wrongElement){ wrongElement.textContent = h; } console.log(h); } if(document.getElementById("form")){ const form = document.getElementById("form"); if(form){ form.addEventListener("submit", (e: SubmitEvent)=>check(e)); } } //this currently does not work, and need to be implemented better at some time. if(!localStorage.getItem("SWMode")){ localStorage.setItem("SWMode","true"); } class SW{ static worker:undefined|ServiceWorker; static setMode(mode:"false"|"offlineOnly"|"true"){ if(this.worker){ this.worker.postMessage({data:mode,code:"setMode"}); } } static checkUpdate(){ if(this.worker){ this.worker.postMessage({code:"CheckUpdate"}); } } static forceClear(){ if(this.worker){ this.worker.postMessage({code:"ForceClear"}); } } } export {SW}; if ("serviceWorker" in navigator){ navigator.serviceWorker.register("/service.js", { scope: "/", }).then((registration) => { let serviceWorker:ServiceWorker|undefined; if (registration.installing) { serviceWorker = registration.installing; console.log("installing"); } else if (registration.waiting) { serviceWorker = registration.waiting; console.log("waiting"); } else if (registration.active) { serviceWorker = registration.active; console.log("active"); } SW.worker=serviceWorker; SW.setMode(localStorage.getItem("SWMode") as "false"|"offlineOnly"|"true"); if (serviceWorker) { console.log(serviceWorker.state); serviceWorker.addEventListener("statechange", (_) => { console.log(serviceWorker.state); }); } }) } const switchurl = document.getElementById("switch") as HTMLAreaElement; if(switchurl){ switchurl.href += window.location.search; const instance = new URLSearchParams(window.location.search).get("instance"); console.log(instance); if(instance){ instancein.value = instance; checkInstance(""); } } export{ checkInstance }; trimswitcher(); export{ mobile, getBulkUsers, getBulkInfo, setTheme, Specialuser, getapiurls, adduser, }; const datalist = document.getElementById("instances"); console.warn(datalist); export function getInstances(){ return instances; } fetch("/instances.json") .then(res=>res.json()) .then( (json: { name: string; description?: string; descriptionLong?: string; image?: string; url?: string; display?: boolean; online?: boolean; uptime: { alltime: number; daytime: number; weektime: number }; urls: { wellknown: string; api: string; cdn: string; gateway: string; login?: string; } }[] )=>{ instances = json; if(datalist){ console.warn(json); if(instancein && instancein.value === ""){ instancein.value = json[0].name; } for(const instance of json){ if(instance.display === false){ continue; } const option = document.createElement("option"); option.disabled = !instance.online; option.value = instance.name; if(instance.url){ stringURLMap.set(option.value, instance.url); if(instance.urls){ stringURLsMap.set(instance.url, instance.urls); } }else if(instance.urls){ stringURLsMap.set(option.value, instance.urls); }else{ option.disabled = true; } if(instance.description){ option.label = instance.description; }else{ option.label = instance.name; } datalist.append(option); } checkInstance(""); } } );