Compare commits

..

1 Commits

Author SHA1 Message Date
nav
31f41cb49b Fix strict TypeScript checks for server 2026-04-09 04:38:07 +00:00
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 {
return Number.isInteger(value) && value >= 1 && value <= 65535;
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
}
function isValidWsUrl(value: string): boolean {
@@ -67,18 +67,18 @@ export function validateYonexusServerConfig(raw: unknown): YonexusServerConfig {
issues.push("followerIdentifiers must not contain duplicates");
}
const notifyBotToken = source.notifyBotToken;
if (!isNonEmptyString(notifyBotToken)) {
const rawNotifyBotToken = source.notifyBotToken;
if (!isNonEmptyString(rawNotifyBotToken)) {
issues.push("notifyBotToken is required");
}
const adminUserId = source.adminUserId;
if (!isNonEmptyString(adminUserId)) {
const rawAdminUserId = source.adminUserId;
if (!isNonEmptyString(rawAdminUserId)) {
issues.push("adminUserId is required");
}
const listenPort = source.listenPort;
if (!isValidPort(listenPort)) {
const rawListenPort = source.listenPort;
if (!isValidPort(rawListenPort)) {
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);
}
const notifyBotToken = rawNotifyBotToken as string;
const adminUserId = rawAdminUserId as string;
const listenPort = rawListenPort as number;
return {
followerIdentifiers,
notifyBotToken: notifyBotToken.trim(),

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ interface CreateDmChannelResponse {
interface SendDiscordDirectMessageOptions {
config: DiscordNotificationConfig;
message: string;
fetcher?: DiscordFetch;
fetcher: DiscordFetch;
}
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 { createYonexusServerRuntime } from "../plugin/core/runtime.js";
import { createMockNotificationService } from "../plugin/notifications/discord.js";
import {
createYonexusServerStore,
loadServerStore,
@@ -90,6 +91,7 @@ describe("YNX-1105e: Server state recovery", () => {
},
store,
transport: firstTransport.transport,
notificationService: createMockNotificationService(),
now: () => now
});
@@ -145,6 +147,7 @@ describe("YNX-1105e: Server state recovery", () => {
},
store,
transport: secondTransport.transport,
notificationService: createMockNotificationService(),
now: () => now
});

View File

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