diff --git a/bun.lock b/bun.lock index 3962d6d..b7bc54f 100644 --- a/bun.lock +++ b/bun.lock @@ -1,9 +1,11 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "nanokvm-mqtt", "dependencies": { + "exponential-backoff": "^3.1.3", "mqtt": "^5.14.1", "tsdown": "^0.16.4", "tslog": "^4.10.2", @@ -155,6 +157,8 @@ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + "fast-unique-numbers": ["fast-unique-numbers@9.0.24", "", { "dependencies": { "@babel/runtime": "^7.28.4", "tslib": "^2.8.1" } }, "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], diff --git a/package.json b/package.json index c5c179d..00afbee 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "typescript": "^5" }, "dependencies": { + "exponential-backoff": "^3.1.3", "mqtt": "^5.14.1", "tsdown": "^0.16.4", "tslog": "^4.10.2", diff --git a/src/clients/nano-kvm/nano-kvm-client.ts b/src/clients/nano-kvm/nano-kvm-client.ts index 7bab8c0..90f146b 100644 --- a/src/clients/nano-kvm/nano-kvm-client.ts +++ b/src/clients/nano-kvm/nano-kvm-client.ts @@ -1,4 +1,5 @@ import type { BodyInit } from "bun"; +import { backOff } from "exponential-backoff"; import type z from "zod"; import { encryptPassword } from "@/encryption"; import { logger } from "@/logger"; @@ -30,7 +31,21 @@ export class NanoKVMClient { return; } - this.loginAndSetToken(); + await this.initAuth(); + } + + async initAuth() { + logger.info("Initializing auth..."); + + await backOff(async () => await this.loginAndSetToken(), { + numOfAttempts: 50, + retry: (_, attempt) => { + logger.info(`Retrying for the ${attempt} time`); + + return true; + }, + startingDelay: 100, + }); } async fetch(path: string, method?: "POST" | "GET", body?: BodyInit) { @@ -75,15 +90,19 @@ export class NanoKVMClient { return; } case -2: { - logger.fatal(`Invalid password! Failed to get token.`); + logger.fatal( + `Invalid password! Failed to get token for ${this.options.host}`, + ); - return; + throw new Error("Failed to get token!"); } default: { logger.fatal( "Unknown error while getting token.", (result as z.output).msg, ); + + throw new Error("Failed to get token!"); } } } diff --git a/src/clients/nano-kvm/nano-kvm.service.ts b/src/clients/nano-kvm/nano-kvm.service.ts index a0605bc..dc92fdc 100644 --- a/src/clients/nano-kvm/nano-kvm.service.ts +++ b/src/clients/nano-kvm/nano-kvm.service.ts @@ -1,4 +1,5 @@ -import type z from "zod"; +import z from "zod"; +import { logger } from "@/logger"; import { NanoKVMClient } from "./nano-kvm-client"; import { type GpioSchema, @@ -26,7 +27,20 @@ export class NanoKVMService { async getInfo(): Promise> { const data = await (await this.client.fetch("/api/vm/info", "GET")).json(); - return InfoSuccess.parse(data).data; + const result = InfoSuccess.safeParse(data); + + if (!result.success) { + logger.error(data); + logger.fatal(`Failed getting info: `, z.prettifyError(result.error)); + + if (data === "unauthorized") { + await this.client.initAuth(); + } + + throw result.error; + } + + return result.data.data; } async getGpio(): Promise> { @@ -34,7 +48,20 @@ export class NanoKVMService { const data = await response.json(); - return GpioSuccess.parse(data).data; + const result = GpioSuccess.safeParse(data); + + if (!result.success) { + logger.error(data); + logger.fatal(`Failed getting gpio: `, z.prettifyError(result.error)); + + if (data === "unauthorized") { + await this.client.initAuth(); + } + + throw result.error; + } + + return result.data.data; } async triggerPower(input: z.input) {