Fix strict TypeScript checks for server

This commit is contained in:
nav
2026-04-09 04:38:07 +00:00
parent 2972c4750e
commit 31f41cb49b
7 changed files with 52 additions and 14 deletions

View File

@@ -35,7 +35,7 @@ function normalizeOptionalString(value: unknown): string | undefined {
} }
function isValidPort(value: unknown): value is number { function isValidPort(value: unknown): value is number {
return Number.isInteger(value) && value >= 1 && value <= 65535; return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
} }
function isValidWsUrl(value: string): boolean { function isValidWsUrl(value: string): boolean {
@@ -67,18 +67,18 @@ export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
issues.push("followerIdentifiers must not contain duplicates"); issues.push("followerIdentifiers must not contain duplicates");
} }
const notifyBotToken = source.notifyBotToken; const rawNotifyBotToken = source.notifyBotToken;
if (!isNonEmptyString(notifyBotToken)) { if (!isNonEmptyString(rawNotifyBotToken)) {
issues.push("notifyBotToken is required"); issues.push("notifyBotToken is required");
} }
const adminUserId = source.adminUserId; const rawAdminUserId = source.adminUserId;
if (!isNonEmptyString(adminUserId)) { if (!isNonEmptyString(rawAdminUserId)) {
issues.push("adminUserId is required"); issues.push("adminUserId is required");
} }
const listenPort = source.listenPort; const rawListenPort = source.listenPort;
if (!isValidPort(listenPort)) { if (!isValidPort(rawListenPort)) {
issues.push("listenPort must be an integer between 1 and 65535"); issues.push("listenPort must be an integer between 1 and 65535");
} }
@@ -93,6 +93,10 @@ export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
throw new YonexusServerConfigError(issues); throw new YonexusServerConfigError(issues);
} }
const notifyBotToken = rawNotifyBotToken as string;
const adminUserId = rawAdminUserId as string;
const listenPort = rawListenPort as number;
return { return {
followerIdentifiers, followerIdentifiers,
notifyBotToken: notifyBotToken.trim(), notifyBotToken: notifyBotToken.trim(),

View File

@@ -79,6 +79,9 @@ export interface ClientRecord {
/** Last successful authentication timestamp (UTC unix seconds) */ /** Last successful authentication timestamp (UTC unix seconds) */
lastAuthenticatedAt?: number; lastAuthenticatedAt?: number;
/** Last successful pairing timestamp (UTC unix seconds) */
pairedAt?: number;
/** /**
* Recent nonces used in authentication attempts. * Recent nonces used in authentication attempts.
* This is a rolling window that may be cleared on restart. * This is a rolling window that may be cleared on restart.
@@ -151,6 +154,7 @@ export interface SerializedClientRecord {
status: ClientLivenessStatus; status: ClientLivenessStatus;
lastHeartbeatAt?: number; lastHeartbeatAt?: number;
lastAuthenticatedAt?: number; lastAuthenticatedAt?: number;
pairedAt?: number;
createdAt: number; createdAt: number;
updatedAt: number; updatedAt: number;
// Note: recentNonces and recentHandshakeAttempts are intentionally // Note: recentNonces and recentHandshakeAttempts are intentionally
@@ -203,6 +207,7 @@ export function serializeClientRecord(record: ClientRecord): SerializedClientRec
status: record.status, status: record.status,
lastHeartbeatAt: record.lastHeartbeatAt, lastHeartbeatAt: record.lastHeartbeatAt,
lastAuthenticatedAt: record.lastAuthenticatedAt, lastAuthenticatedAt: record.lastAuthenticatedAt,
pairedAt: record.pairedAt,
createdAt: record.createdAt, createdAt: record.createdAt,
updatedAt: record.updatedAt updatedAt: record.updatedAt
}; };

View File

@@ -52,6 +52,7 @@ export interface YonexusServerRuntimeOptions {
config: YonexusServerConfig; config: YonexusServerConfig;
store: YonexusServerStore; store: YonexusServerStore;
transport: ServerTransport; transport: ServerTransport;
notificationService?: DiscordNotificationService;
now?: () => number; now?: () => number;
sweepIntervalMs?: number; sweepIntervalMs?: number;
} }
@@ -80,7 +81,9 @@ export class YonexusServerRuntime {
}; };
this.sweepIntervalMs = options.sweepIntervalMs ?? 30_000; this.sweepIntervalMs = options.sweepIntervalMs ?? 30_000;
this.pairingService = createPairingService({ now: this.now }); this.pairingService = createPairingService({ now: this.now });
this.notificationService = createDiscordNotificationService({ this.notificationService =
options.notificationService ??
createDiscordNotificationService({
botToken: options.config.notifyBotToken, botToken: options.config.notifyBotToken,
adminUserId: options.config.adminUserId adminUserId: options.config.adminUserId
}); });

View File

@@ -42,7 +42,7 @@ interface CreateDmChannelResponse {
interface SendDiscordDirectMessageOptions { interface SendDiscordDirectMessageOptions {
config: DiscordNotificationConfig; config: DiscordNotificationConfig;
message: string; message: string;
fetcher?: DiscordFetch; fetcher: DiscordFetch;
} }
const DISCORD_API_BASE_URL = "https://discord.com/api/v10"; const DISCORD_API_BASE_URL = "https://discord.com/api/v10";

21
plugin/types/ws.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
declare module "ws" {
export type RawData = Buffer | ArrayBuffer | Buffer[] | string;
export class WebSocket {
static readonly OPEN: number;
readonly readyState: number;
send(data: string): void;
close(code?: number, reason?: string): void;
on(event: "message", listener: (data: RawData) => void): this;
on(event: "close", listener: (code: number, reason: Buffer) => void): this;
on(event: "error", listener: (error: Error) => void): this;
}
export class WebSocketServer {
constructor(options: { host?: string; port: number });
on(event: "error", listener: (error: Error) => void): this;
on(event: "listening", listener: () => void): this;
on(event: "connection", listener: (ws: WebSocket, req: import("http").IncomingMessage) => void): this;
close(callback?: () => void): void;
}
}

View File

@@ -5,6 +5,7 @@ import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { createYonexusServerRuntime } from "../plugin/core/runtime.js"; import { createYonexusServerRuntime } from "../plugin/core/runtime.js";
import { createMockNotificationService } from "../plugin/notifications/discord.js";
import { import {
createYonexusServerStore, createYonexusServerStore,
loadServerStore, loadServerStore,
@@ -90,6 +91,7 @@ describe("YNX-1105e: Server state recovery", () => {
}, },
store, store,
transport: firstTransport.transport, transport: firstTransport.transport,
notificationService: createMockNotificationService(),
now: () => now now: () => now
}); });
@@ -145,6 +147,7 @@ describe("YNX-1105e: Server state recovery", () => {
}, },
store, store,
transport: secondTransport.transport, transport: secondTransport.transport,
notificationService: createMockNotificationService(),
now: () => now now: () => now
}); });

View File

@@ -4,7 +4,7 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"outDir": "dist", "outDir": "dist",
"rootDir": ".", "rootDir": "..",
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@@ -15,7 +15,9 @@
}, },
"include": [ "include": [
"plugin/**/*.ts", "plugin/**/*.ts",
"servers/**/*.ts" "plugin/**/*.d.ts",
"servers/**/*.ts",
"../Yonexus.Protocol/src/**/*.ts"
], ],
"exclude": [ "exclude": [
"dist", "dist",