Refactor uptimeObject to use a Map instead of an object

Signed-off-by: Scott Gould <greysilly7@gmail.com>
This commit is contained in:
Scott Gould 2024-09-25 16:11:09 -04:00
parent be40162fc5
commit cd7135518a
No known key found for this signature in database
6 changed files with 74 additions and 77 deletions

View file

@ -71,7 +71,7 @@ app.use("/services/oembed", (req: Request, res: Response)=>{
}); });
app.use("/uptime", (req: Request, res: Response)=>{ app.use("/uptime", (req: Request, res: Response)=>{
const instanceUptime = uptime[req.query.name as string]; const instanceUptime = uptime.get(req.query.name as string);
res.send(instanceUptime); res.send(instanceUptime);
}); });

View file

@ -2,6 +2,7 @@ import fs from"node:fs";
import path from"node:path"; import path from"node:path";
import{ getApiUrls }from"./utils.js"; import{ getApiUrls }from"./utils.js";
import{ fileURLToPath }from"node:url"; import{ fileURLToPath }from"node:url";
import{ setTimeout, clearTimeout }from"node:timers";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -11,10 +12,6 @@ interface UptimeEntry {
online: boolean; online: boolean;
} }
interface UptimeObject {
[key: string]: UptimeEntry[];
}
interface Instance { interface Instance {
name: string; name: string;
urls?: { api: string }; urls?: { api: string };
@ -27,37 +24,46 @@ interface Instance {
}; };
} }
const uptimeObject: UptimeObject = loadUptimeObject(); const uptimeObject: Map<string, UptimeEntry[]> = loadUptimeObject();
export{ uptimeObject as uptime }; export{ uptimeObject as uptime };
function loadUptimeObject(): UptimeObject{ function loadUptimeObject(): Map<string, UptimeEntry[]>{
const filePath = path.join(__dirname, "..", "uptime.json"); const filePath = path.join(__dirname, "..", "uptime.json");
if(fs.existsSync(filePath)){ if(fs.existsSync(filePath)){
try{ try{
return JSON.parse(fs.readFileSync(filePath, "utf8")); const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
return new Map(Object.entries(data));
}catch(error){ }catch(error){
console.error("Error reading uptime.json:", error); console.error("Error reading uptime.json:", error);
return{}; return new Map();
} }
} }
return{}; return new Map();
} }
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
function saveUptimeObject(): void{ function saveUptimeObject(): void{
if(saveTimeout){
clearTimeout(saveTimeout);
}
saveTimeout = setTimeout(()=>{
const data = Object.fromEntries(uptimeObject);
fs.writeFile( fs.writeFile(
path.join(__dirname, "..", "uptime.json"), path.join(__dirname, "..", "uptime.json"),
JSON.stringify(uptimeObject), JSON.stringify(data),
error=>{ error=>{
if(error){ if(error){
console.error("Error saving uptime.json:", error); console.error("Error saving uptime.json:", error);
} }
} }
); );
}, 5000); // Batch updates every 5 seconds
} }
function removeUndefinedKey(): void{ function removeUndefinedKey(): void{
if(uptimeObject.undefined){ if(uptimeObject.has("undefined")){
delete uptimeObject.undefined; uptimeObject.delete("undefined");
saveUptimeObject(); saveUptimeObject();
} }
} }
@ -84,7 +90,7 @@ async function resolveInstance(
return; return;
} }
activeInstances.add(instance.name); activeInstances.add(instance.name);
await checkHealth(instance, api); // Ensure health is checked immediately await checkHealth(instance, api);
scheduleHealthCheck(instance, api); scheduleHealthCheck(instance, api);
}catch(error){ }catch(error){
console.error("Error resolving instance:", error); console.error("Error resolving instance:", error);
@ -126,7 +132,6 @@ async function checkHealth(
const response = await fetch(`${api}/ping`, { method: "HEAD" }); const response = await fetch(`${api}/ping`, { method: "HEAD" });
console.log(`Checking health for ${instance.name}: ${response.status}`); console.log(`Checking health for ${instance.name}: ${response.status}`);
if(response.ok || tries > 3){ if(response.ok || tries > 3){
console.log(`Setting status for ${instance.name} to ${response.ok}`);
setStatus(instance, response.ok); setStatus(instance, response.ok);
}else{ }else{
retryHealthCheck(instance, api, tries); retryHealthCheck(instance, api, tries);
@ -150,7 +155,7 @@ function retryHealthCheck(
} }
function updateInactiveInstances(activeInstances: Set<string>): void{ function updateInactiveInstances(activeInstances: Set<string>): void{
for(const key of Object.keys(uptimeObject)){ for(const key of uptimeObject.keys()){
if(!activeInstances.has(key)){ if(!activeInstances.has(key)){
setStatus(key, false); setStatus(key, false);
} }
@ -158,7 +163,7 @@ function updateInactiveInstances(activeInstances: Set<string>): void{
} }
function calcStats(instance: Instance): void{ function calcStats(instance: Instance): void{
const obj = uptimeObject[instance.name]; const obj = uptimeObject.get(instance.name);
if(!obj)return; if(!obj)return;
const now = Date.now(); const now = Date.now();
@ -234,11 +239,11 @@ function calculateUptimeStats(
function setStatus(instance: string | Instance, status: boolean): void{ function setStatus(instance: string | Instance, status: boolean): void{
const name = typeof instance === "string" ? instance : instance.name; const name = typeof instance === "string" ? instance : instance.name;
let obj = uptimeObject[name]; let obj = uptimeObject.get(name);
if(!obj){ if(!obj){
obj = []; obj = [];
uptimeObject[name] = obj; uptimeObject.set(name, obj);
} }
const lastEntry = obj.at(-1); const lastEntry = obj.at(-1);

View file

@ -24,9 +24,7 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
url += "/"; url += "/";
} }
try{ try{
const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then( const info: ApiUrls = await fetch(`${url}.well-known/spacebar`).then(res=>res.json());
res=>res.json() as Promise<ApiUrls>
);
const api = info.api; const api = info.api;
const apiUrl = new URL(api); const apiUrl = new URL(api);
const policies: any = await fetch( const policies: any = await fetch(
@ -44,14 +42,11 @@ export async function getApiUrls(url: string): Promise<ApiUrls | null>{
} }
} }
export async function inviteResponse( export async function inviteResponse(req: Request, res: Response): Promise<void>{
req: Request,
res: Response
): Promise<void>{
let url: URL; let url: URL;
if(URL.canParse(req.query.url as string)){ try{
url = new URL(req.query.url as string); url = new URL(req.query.url as string);
}else{ }catch{
const scheme = req.secure ? "https" : "http"; const scheme = req.secure ? "https" : "http";
const host = `${scheme}://${req.get("Host")}`; const host = `${scheme}://${req.get("Host")}`;
url = new URL(host); url = new URL(host);
@ -67,45 +62,37 @@ export async function inviteResponse(
if(!instance){ if(!instance){
throw new Error("Instance not specified"); throw new Error("Instance not specified");
} }
const urls = await getApiUrls(instance); const urls = await getApiUrls(instance);
if(!urls){ if(!urls){
throw new Error("Failed to get API URLs"); throw new Error("Failed to get API URLs");
} }
const invite = await fetch(`${urls.api}/invites/${code}`).then( const invite = await fetch(`${urls.api}/invites/${code}`).then(json=>json.json() as Promise<Invite>);
res=>res.json() as Promise<Invite>
);
const title = invite.guild.name; const title = invite.guild.name;
const description = invite.inviter const description = invite.inviter
? `${invite.inviter.username} has invited you to ${invite.guild.name}${ ? `${invite.inviter.username} has invited you to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`
invite.guild.description ? `\n${invite.guild.description}` : "" : `You've been invited to ${invite.guild.name}${invite.guild.description ? `\n${invite.guild.description}` : ""}`;
}`
: `You've been invited to ${invite.guild.name}${
invite.guild.description ? `\n${invite.guild.description}` : ""
}`;
const thumbnail = invite.guild.icon const thumbnail = invite.guild.icon
? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png` ? `${urls.cdn}/icons/${invite.guild.id}/${invite.guild.icon}.png`
: ""; : "";
const jsonResponse = { res.json({
type: "link", type: "link",
version: "1.0", version: "1.0",
title, title,
thumbnail, thumbnail,
description, description,
}; });
res.json(jsonResponse);
}catch(error){ }catch(error){
console.error("Error processing invite response:", error); console.error("Error processing invite response:", error);
const jsonResponse = { res.json({
type: "link", type: "link",
version: "1.0", version: "1.0",
title: "Jank Client", title: "Jank Client",
thumbnail: "/logo.webp", thumbnail: "/logo.webp",
description: "A spacebar client that has DMs, replying and more", description: "A spacebar client that has DMs, replying and more",
url: url.toString(), url: url.toString(),
}; });
res.json(jsonResponse);
} }
} }

View file

@ -17,9 +17,8 @@ class Emoji{
get guild(){ get guild(){
if(this.owner instanceof Guild){ if(this.owner instanceof Guild){
return this.owner; return this.owner;
}else{
return undefined;
} }
return null;
} }
get localuser(){ get localuser(){
if(this.owner instanceof Guild){ if(this.owner instanceof Guild){
@ -83,7 +82,7 @@ class Emoji{
array[i] = read8(); array[i] = read8();
} }
//console.log(array); //console.log(array);
return new TextDecoder("utf-8").decode(array.buffer); return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
} }
const build: { name: string; emojis: { name: string; emoji: string }[] }[] = const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[]; [];

View file

@ -11,7 +11,7 @@ offset: number
private readonly maxDist = 6000; private readonly maxDist = 6000;
HTMLElements: [HTMLElement, string][] = []; HTMLElements: [HTMLElement, string][] = [];
div: HTMLDivElement | null = null; div: HTMLDivElement | null = null;
timeout: NodeJS.Timeout | null = null; timeout: ReturnType<typeof setTimeout> | null = null;
beenloaded = false; beenloaded = false;
scrollBottom = 0; scrollBottom = 0;
scrollTop = 0; scrollTop = 0;
@ -239,7 +239,6 @@ offset: number
try{ try{
if(!this.div){ if(!this.div){
res(false); res(false);
return false;
} }
const out = (await Promise.allSettled([ const out = (await Promise.allSettled([
this.watchForTop(), this.watchForTop(),
@ -250,11 +249,9 @@ offset: number
this.timeout = setTimeout(this.updatestuff.bind(this), 300); this.timeout = setTimeout(this.updatestuff.bind(this), 300);
} }
res(Boolean(changed)); res(Boolean(changed));
return Boolean(changed);
}catch(e){ }catch(e){
console.error(e); console.error(e);
res(false); res(false);
return false;
}finally{ }finally{
setTimeout(()=>{ setTimeout(()=>{
this.changePromise = undefined; this.changePromise = undefined;
@ -281,9 +278,13 @@ offset: number
behavior: "smooth", behavior: "smooth",
block: "center", block: "center",
}); });
await new Promise(resolve=>setTimeout(resolve, 1000)); await new Promise(resolve=>{
setTimeout(resolve, 1000);
});
element.classList.remove("jumped"); element.classList.remove("jumped");
await new Promise(resolve=>setTimeout(resolve, 100)); await new Promise(resolve=>{
setTimeout(resolve, 100);
});
element.classList.add("jumped"); element.classList.add("jumped");
}else{ }else{
element.scrollIntoView(); element.scrollIntoView();
@ -296,7 +297,9 @@ offset: number
await this.firstElement(id); await this.firstElement(id);
this.updatestuff(); this.updatestuff();
await this.watchForChange(); await this.watchForChange();
await new Promise(resolve=>setTimeout(resolve, 100)); await new Promise(resolve=>{
setTimeout(resolve, 100);
});
await this.focus(id, true); await this.focus(id, true);
} }
} }

View file

@ -199,7 +199,7 @@ function adduser(user: typeof Specialuser.prototype.json){
return user; return user;
} }
const instancein = document.getElementById("instancein") as HTMLInputElement; const instancein = document.getElementById("instancein") as HTMLInputElement;
let timeout: string | number | NodeJS.Timeout | undefined; let timeout: ReturnType<typeof setTimeout> | string | number | undefined | null = null;
// let instanceinfo; // let instanceinfo;
const stringURLMap = new Map<string, string>(); const stringURLMap = new Map<string, string>();
@ -231,8 +231,8 @@ async function getapiurls(str: string): Promise<
if(stringURLMap.size!==0){ if(stringURLMap.size!==0){
res(); res();
} }
},100) },100);
}) });
} }
if(val){ if(val){
str = val; str = val;
@ -335,7 +335,7 @@ async function checkInstance(instance?: string){
gateway: string; gateway: string;
login: string; login: string;
value: string; value: string;
} };
if(instanceinfo){ if(instanceinfo){
instanceinfo.value = instanceValue; instanceinfo.value = instanceValue;
localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo)); localStorage.setItem("instanceinfo", JSON.stringify(instanceinfo));
@ -360,10 +360,12 @@ async function checkInstance(instance?: string){
if(instancein){ if(instancein){
console.log(instancein); console.log(instancein);
instancein.addEventListener("keydown", _=>{ instancein.addEventListener("keydown", ()=>{
const verify = document.getElementById("verify"); const verify = document.getElementById("verify");
verify!.textContent = "Waiting to check Instance"; verify!.textContent = "Waiting to check Instance";
if(timeout !== null && typeof timeout !== "string"){
clearTimeout(timeout); clearTimeout(timeout);
}
timeout = setTimeout(()=>checkInstance(), 1000); timeout = setTimeout(()=>checkInstance(), 1000);
}); });
if(localStorage.getItem("instanceinfo")){ if(localStorage.getItem("instanceinfo")){
@ -410,7 +412,9 @@ async function login(username: string, password: string, captcha: string){
if(response.captcha_sitekey){ if(response.captcha_sitekey){
const capt = document.getElementById("h-captcha"); const capt = document.getElementById("h-captcha");
if(!capt!.children.length){ if(capt!.children.length){
eval("hcaptcha.reset()");
}else{
const capty = document.createElement("div"); const capty = document.createElement("div");
capty.classList.add("h-captcha"); capty.classList.add("h-captcha");
@ -419,8 +423,6 @@ async function login(username: string, password: string, captcha: string){
script.src = "https://js.hcaptcha.com/1/api.js"; script.src = "https://js.hcaptcha.com/1/api.js";
capt!.append(script); capt!.append(script);
capt!.append(capty); capt!.append(capty);
}else{
eval("hcaptcha.reset()");
} }
}else{ }else{
console.log(response); console.log(response);
@ -434,6 +436,7 @@ async function login(username: string, password: string, captcha: string){
"", "",
"", "",
function(this: HTMLInputElement){ function(this: HTMLInputElement){
// eslint-disable-next-line no-invalid-this
onetimecode = this.value; onetimecode = this.value;
}, },
], ],
@ -453,18 +456,18 @@ async function login(username: string, password: string, captcha: string){
}), }),
}) })
.then(r=>r.json()) .then(r=>r.json())
.then(response=>{ .then(res=>{
if(response.message){ if(res.message){
alert(response.message); alert(res.message);
}else{ }else{
console.warn(response); console.warn(res);
if(!response.token)return; if(!res.token)return;
adduser({ adduser({
serverurls: JSON.parse( serverurls: JSON.parse(
localStorage.getItem("instanceinfo")! localStorage.getItem("instanceinfo")!
), ),
email: username, email: username,
token: response.token, token: res.token,
}).username = username; }).username = username;
const redir = new URLSearchParams( const redir = new URLSearchParams(
window.location.search window.location.search
@ -579,7 +582,7 @@ export function getInstances(){
} }
fetch("/instances.json") fetch("/instances.json")
.then(_=>_.json()) .then(res=>res.json())
.then( .then(
(json: { (json: {
name: string; name: string;