mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
167 lines
5.0 KiB
TypeScript
167 lines
5.0 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
import logger from "./logger";
|
|
import { SmapiToken, SmapiAuthTokens } from "./smapi_auth";
|
|
import { either as E } from "fp-ts";
|
|
|
|
export { SQLiteSmapiTokenStore } from "./sqlite_smapi_token_store";
|
|
|
|
export interface SmapiTokenStore {
|
|
get(token: string): SmapiToken | undefined;
|
|
set(token: string, fullSmapiToken: SmapiToken): void;
|
|
delete(token: string): void;
|
|
getAll(): { [tokenKey: string]: SmapiToken };
|
|
cleanupExpired(smapiAuthTokens: SmapiAuthTokens): number;
|
|
}
|
|
|
|
export class InMemorySmapiTokenStore implements SmapiTokenStore {
|
|
private tokens: { [tokenKey: string]: SmapiToken } = {};
|
|
|
|
get(token: string): SmapiToken | undefined {
|
|
return this.tokens[token];
|
|
}
|
|
|
|
set(token: string, fullSmapiToken: SmapiToken): void {
|
|
this.tokens[token] = fullSmapiToken;
|
|
}
|
|
|
|
delete(token: string): void {
|
|
delete this.tokens[token];
|
|
}
|
|
|
|
getAll(): { [tokenKey: string]: SmapiToken } {
|
|
return this.tokens;
|
|
}
|
|
|
|
cleanupExpired(smapiAuthTokens: SmapiAuthTokens): number {
|
|
const tokenKeys = Object.keys(this.tokens);
|
|
let deletedCount = 0;
|
|
|
|
for (const tokenKey of tokenKeys) {
|
|
const smapiToken = this.tokens[tokenKey];
|
|
if (smapiToken) {
|
|
const verifyResult = smapiAuthTokens.verify(smapiToken);
|
|
// Only delete if token verification fails with InvalidTokenError
|
|
// Do NOT delete ExpiredTokenError as those can still be refreshed
|
|
if (E.isLeft(verifyResult)) {
|
|
const error = verifyResult.left;
|
|
// Only delete invalid tokens, not expired ones (which can be refreshed)
|
|
if (error._tag === 'InvalidTokenError') {
|
|
logger.debug(`Deleting invalid token from in-memory store`);
|
|
delete this.tokens[tokenKey];
|
|
deletedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deletedCount > 0) {
|
|
logger.info(`Cleaned up ${deletedCount} invalid token(s) from in-memory store`);
|
|
}
|
|
|
|
return deletedCount;
|
|
}
|
|
}
|
|
|
|
export class FileSmapiTokenStore implements SmapiTokenStore {
|
|
private tokens: { [tokenKey: string]: SmapiToken } = {};
|
|
private readonly filePath: string;
|
|
|
|
constructor(filePath: string) {
|
|
this.filePath = filePath;
|
|
this.loadFromFile();
|
|
}
|
|
|
|
private loadFromFile(): void {
|
|
try {
|
|
// Ensure the directory exists
|
|
const dir = path.dirname(this.filePath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
logger.info(`Created token storage directory: ${dir}`);
|
|
}
|
|
|
|
// Load existing tokens if file exists
|
|
if (fs.existsSync(this.filePath)) {
|
|
const data = fs.readFileSync(this.filePath, "utf8");
|
|
this.tokens = JSON.parse(data);
|
|
logger.info(
|
|
`Loaded ${Object.keys(this.tokens).length} token(s) from ${this.filePath}`
|
|
);
|
|
} else {
|
|
logger.info(`No existing token file found at ${this.filePath}, starting fresh`);
|
|
this.tokens = {};
|
|
this.saveToFile();
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Failed to load tokens from ${this.filePath}`, { error });
|
|
this.tokens = {};
|
|
}
|
|
}
|
|
|
|
private saveToFile(): void {
|
|
try {
|
|
// Ensure the directory exists before writing
|
|
const dir = path.dirname(this.filePath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
logger.info(`Created token storage directory: ${dir}`);
|
|
}
|
|
|
|
const data = JSON.stringify(this.tokens, null, 2);
|
|
fs.writeFileSync(this.filePath, data, "utf8");
|
|
logger.debug(`Saved ${Object.keys(this.tokens).length} token(s) to ${this.filePath}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to save tokens to ${this.filePath}`, { error });
|
|
}
|
|
}
|
|
|
|
get(token: string): SmapiToken | undefined {
|
|
return this.tokens[token];
|
|
}
|
|
|
|
set(token: string, fullSmapiToken: SmapiToken): void {
|
|
this.tokens[token] = fullSmapiToken;
|
|
this.saveToFile();
|
|
}
|
|
|
|
delete(token: string): void {
|
|
delete this.tokens[token];
|
|
this.saveToFile();
|
|
}
|
|
|
|
getAll(): { [tokenKey: string]: SmapiToken } {
|
|
return this.tokens;
|
|
}
|
|
|
|
cleanupExpired(smapiAuthTokens: SmapiAuthTokens): number {
|
|
const tokenKeys = Object.keys(this.tokens);
|
|
let deletedCount = 0;
|
|
|
|
for (const tokenKey of tokenKeys) {
|
|
const smapiToken = this.tokens[tokenKey];
|
|
if (smapiToken) {
|
|
const verifyResult = smapiAuthTokens.verify(smapiToken);
|
|
// Only delete if token verification fails with InvalidTokenError
|
|
// Do NOT delete ExpiredTokenError as those can still be refreshed
|
|
if (E.isLeft(verifyResult)) {
|
|
const error = verifyResult.left;
|
|
// Only delete invalid tokens, not expired ones (which can be refreshed)
|
|
if (error._tag === 'InvalidTokenError') {
|
|
logger.debug(`Deleting invalid token from file store`);
|
|
delete this.tokens[tokenKey];
|
|
deletedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deletedCount > 0) {
|
|
logger.info(`Cleaned up ${deletedCount} invalid token(s) from file store`);
|
|
this.saveToFile();
|
|
}
|
|
|
|
return deletedCount;
|
|
}
|
|
}
|