jank-client-fork/src/webpage/utils/utils.ts
2025-03-29 08:33:39 -05:00

685 lines
17 KiB
TypeScript

import {I18n} from "../i18n.js";
import {Dialog} from "../settings.js";
setTheme();
export function setTheme() {
let name = localStorage.getItem("theme");
if (!name) {
localStorage.setItem("theme", "Dark");
name = "Dark";
}
document.body.className = name + "-theme";
}
export function getBulkUsers() {
const json = getBulkInfo();
for (const thing in json.users) {
json.users[thing] = new Specialuser(json.users[thing]);
}
return json;
}
export function getBulkInfo() {
return JSON.parse(localStorage.getItem("userinfos") as string);
}
export 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) {
console.warn("uhoh");
userinfos.preferences.notisound = "three";
}
localStorage.setItem("userinfos", JSON.stringify(userinfos));
}
setDefaults();
export 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");
}
}
remove() {
const info = getBulkInfo();
delete info.users[this.uid];
if (info.currentuser === this.uid) {
const user = info.users[0];
if (user) {
info.currentuser = new Specialuser(user).uid;
} else {
info.currentuser = null;
}
}
if (sessionStorage.getItem("currentuser") === this.uid) {
sessionStorage.removeItem("currentuser");
}
localStorage.setItem("userinfos", JSON.stringify(info));
}
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();
}
proxySave(e: Object) {
return new Proxy(e, {
set: (target, p, newValue, receiver) => {
const bool = Reflect.set(target, p, newValue, receiver);
try {
this.updateLocal();
} catch (_) {
Reflect.deleteProperty(target, p);
throw _;
}
return bool;
},
get: (target, p, receiver) => {
const value = Reflect.get(target, p, receiver) as unknown;
if (value instanceof Object) {
return this.proxySave(value);
}
return value;
},
});
}
get localuserStore() {
type jsonParse = {
[key: string | number]: any;
};
return this.proxySave(this.json.localuserStore) as {
[key: string | number]: jsonParse;
};
}
set id(e) {
this.json.id = e;
this.updateLocal();
}
get id() {
return this.json.id;
}
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));
}
}
class Directory {
static home = this.createHome();
handle: FileSystemDirectoryHandle;
writeWorker?: Worker;
private constructor(handle: FileSystemDirectoryHandle) {
this.handle = handle;
}
static async createHome(): Promise<Directory> {
navigator.storage.persist();
const home = new Directory(await navigator.storage.getDirectory());
return home;
}
async *getAllInDir() {
for await (const [name, handle] of this.handle.entries()) {
if (handle instanceof FileSystemDirectoryHandle) {
yield [name, new Directory(handle)] as [string, Directory];
} else if (handle instanceof FileSystemFileHandle) {
yield [name, await handle.getFile()] as [string, File];
} else {
console.log(handle, "oops :3");
}
}
console.log("done");
}
async getRawFileHandler(name: string) {
return await this.handle.getFileHandle(name);
}
async getRawFile(name: string) {
try {
return await (await this.handle.getFileHandle(name)).getFile();
} catch {
return undefined;
}
}
async getString(name: string): Promise<string | undefined> {
try {
return await (await this.getRawFile(name))!.text();
} catch {
return undefined;
}
}
initWorker() {
if (this.writeWorker) return this.writeWorker;
this.writeWorker = new Worker("/utils/dirrWorker.js");
this.writeWorker.onmessage = (event) => {
const res = this.wMap.get(event.data[0]);
this.wMap.delete(event.data[0]);
if (!res) throw new Error("Res is not defined here somehow");
res(event.data[1]);
};
return this.writeWorker;
}
wMap = new Map<number, (input: boolean) => void>();
async setStringWorker(name: FileSystemFileHandle, value: ArrayBuffer) {
const worker = this.initWorker();
const random = Math.random();
worker.postMessage([name, value, random]);
return new Promise<boolean>((res) => {
this.wMap.set(random, res);
});
}
async setString(name: string, value: string): Promise<boolean> {
const file = await this.handle.getFileHandle(name, {create: true});
const contents = new TextEncoder().encode(value);
if (file.createWritable as unknown) {
const stream = await file.createWritable({keepExistingData: false});
await stream.write(contents);
await stream.close();
return true;
} else {
//Curse you webkit!
return await this.setStringWorker(file, contents.buffer as ArrayBuffer);
}
}
async getDir(name: string) {
return new Directory(await this.handle.getDirectoryHandle(name, {create: true}));
}
}
export {Directory};
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const iOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
export {mobile, iOS};
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;
const datalist = document.getElementById("instances");
console.warn(datalist);
const instancefetch = fetch("/instances.json")
.then((res) => res.json())
.then(
async (
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;
};
}[],
) => {
await I18n.done;
instances = json;
if (datalist) {
console.warn(json);
const instancein = document.getElementById("instancein") as HTMLInputElement;
if (
instancein &&
instancein.value === "" &&
!new URLSearchParams(window.location.search).get("instance")
) {
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);
}
if (
json.length !== 0 &&
!localStorage.getItem("instanceinfo") &&
!new URLSearchParams(window.location.search).get("instance")
) {
checkInstance(json[0].name);
}
}
},
);
const stringURLMap = new Map<string, string>();
const stringURLsMap = new Map<
string,
{
wellknown: string;
api: string;
cdn: string;
gateway: string;
login?: string;
}
>();
/**
* this fucntion checks if a string is an instance, it'll either return the API urls or false
*/
export 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<void>((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);
function isloopback(str: string) {
return str.includes("localhost") || str.includes("127.0.0.1");
}
let urls:
| undefined
| {
api: string;
cdn: string;
gateway: string;
wellknown: string;
login: string;
};
try {
const info = await fetch(
`${api}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
).then((x) => x.json());
const apiurl = new URL(info.apiEndpoint);
urls = {
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) {
urls = val as {
wellknown: string;
api: string;
cdn: string;
gateway: string;
login: string;
};
} else {
val.login = val.api;
urls = val as {
wellknown: string;
api: string;
cdn: string;
gateway: string;
login: string;
};
}
}
}
}
if (urls) {
if (isloopback(urls.api) !== isloopback(str)) {
return new Promise<
| {
api: string;
cdn: string;
gateway: string;
wellknown: string;
login: string;
}
| false
>((res) => {
const menu = new Dialog("");
const options = menu.float.options;
options.addMDText(I18n.incorrectURLS());
const opt = options.addOptions("", {ltr: true});
let clicked = false;
opt.addButtonInput("", I18n.yes(), async () => {
if (clicked) return;
clicked = true;
const temp = new URL(str);
temp.port = "";
const newOrgin = temp.host;
const protical = temp.protocol;
const tempurls = {
api: new URL(urls.api),
cdn: new URL(urls.cdn),
gateway: new URL(urls.gateway),
wellknown: new URL(urls.wellknown),
login: new URL(urls.login),
};
tempurls.api.host = newOrgin;
tempurls.api.protocol = protical;
tempurls.cdn.host = newOrgin;
tempurls.api.protocol = protical;
tempurls.gateway.host = newOrgin;
tempurls.gateway.protocol = temp.protocol === "http:" ? "ws:" : "wss:";
tempurls.wellknown.host = newOrgin;
tempurls.wellknown.protocol = protical;
tempurls.login.host = newOrgin;
tempurls.login.protocol = protical;
try {
if (!(await fetch(tempurls.api + "/ping")).ok) {
res(false);
menu.hide();
return;
}
} catch {
res(false);
menu.hide();
return;
}
res({
api: tempurls.api.toString(),
cdn: tempurls.cdn.toString(),
gateway: tempurls.gateway.toString(),
wellknown: tempurls.wellknown.toString(),
login: tempurls.login.toString(),
});
menu.hide();
});
const no = opt.addButtonInput("", I18n.no(), async () => {
if (clicked) return;
clicked = true;
try {
if (!(await fetch(urls.api + "/ping")).ok) {
res(false);
menu.hide();
return;
}
} catch {
res(false);
return;
}
res(urls);
menu.hide();
});
const span = document.createElement("span");
options.addHTMLArea(span);
const t = setInterval(() => {
if (!document.contains(span)) {
clearInterval(t);
no.onClick();
}
}, 100);
menu.show();
});
}
//*/
try {
if (!(await fetch(urls.api + "/ping")).ok) {
return false;
}
} catch {
return false;
}
return urls;
}
return false;
}
/**
*
* This function takes in a string and checks if the string is a valid instance
* the string may be a URL or the name of the instance
* the alt property is something you may fire on success.
*/
const checkInstance = Object.assign(
async function (instance: string) {
await instancefetch;
const verify = document.getElementById("verify");
const loginButton = (document.getElementById("loginButton") ||
document.getElementById("createAccount") ||
document.createElement("button")) as HTMLButtonElement;
try {
loginButton.disabled = true;
verify!.textContent = I18n.getTranslation("login.checking");
const instanceValue = instance;
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 = I18n.getTranslation("login.allGood");
loginButton.disabled = false;
if (checkInstance.alt) {
checkInstance.alt();
}
setTimeout((_: any) => {
console.log(verify!.textContent);
verify!.textContent = "";
}, 3000);
} else {
verify!.textContent = I18n.getTranslation("login.invalid");
loginButton.disabled = true;
}
} catch {
console.log("catch");
verify!.textContent = I18n.getTranslation("login.invalid");
loginButton.disabled = true;
}
},
{} as {alt?: Function},
);
export {checkInstance};
export function getInstances() {
return instances;
}
export class SW {
static worker: undefined | ServiceWorker;
static setMode(mode: "false" | "offlineOnly" | "true") {
localStorage.setItem("SWMode", mode);
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"});
}
}
}
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);
});
}
});
}