Compare commits
1 Commits
2972c4750e
...
31f41cb49b
| Author | SHA1 | Date | |
|---|---|---|---|
| 31f41cb49b |
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
21
plugin/types/ws.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user