diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index a810dfde..d99149dc 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -3,6 +3,7 @@ import fs from 'node:fs/promises'; import { createDebug } from 'obug'; import Database from '#server/utils/Database'; +import { mergeClientStatuses } from '#server/utils/clientStatus'; import { OLD_ENV, WG_ENV } from '#server/utils/config'; import { firewall } from '#server/utils/firewall'; import { encodeQRCode } from '#server/utils/qr'; @@ -103,21 +104,7 @@ class WireGuard { // Loop WireGuard status const dump = await wg.dump(wgInterface.name); - dump.forEach( - ({ publicKey, latestHandshakeAt, endpoint, transferRx, transferTx }) => { - const client = clients.find((client) => client.publicKey === publicKey); - if (!client) { - return; - } - - client.latestHandshakeAt = latestHandshakeAt; - client.endpoint = endpoint; - client.transferRx = transferRx; - client.transferTx = transferTx; - } - ); - - return clients; + return mergeClientStatuses(clients, dump); } async dumpByPublicKey(publicKey: string) { @@ -146,21 +133,7 @@ class WireGuard { // Loop WireGuard status const dump = await wg.dump(wgInterface.name); - dump.forEach( - ({ publicKey, latestHandshakeAt, endpoint, transferRx, transferTx }) => { - const client = clients.find((client) => client.publicKey === publicKey); - if (!client) { - return; - } - - client.latestHandshakeAt = latestHandshakeAt; - client.endpoint = endpoint; - client.transferRx = transferRx; - client.transferTx = transferTx; - } - ); - - return clients; + return mergeClientStatuses(clients, dump); } async getClientConfiguration({ clientId }: { clientId: ID }) { diff --git a/src/server/utils/clientStatus.ts b/src/server/utils/clientStatus.ts new file mode 100644 index 00000000..1c44f0a0 --- /dev/null +++ b/src/server/utils/clientStatus.ts @@ -0,0 +1,30 @@ +type StatusFields = { + publicKey: string; + latestHandshakeAt: Date | null; + endpoint: string | null; + transferRx: number | null; + transferTx: number | null; +}; + +export function mergeClientStatuses( + clients: T[], + statuses: readonly StatusFields[] +): T[] { + const clientsByPublicKey = new Map( + clients.map((client) => [client.publicKey, client]) + ); + + for (const status of statuses) { + const client = clientsByPublicKey.get(status.publicKey); + if (!client) { + continue; + } + + client.latestHandshakeAt = status.latestHandshakeAt; + client.endpoint = status.endpoint; + client.transferRx = status.transferRx; + client.transferTx = status.transferTx; + } + + return clients; +} diff --git a/src/test/unit/clientStatus.spec.ts b/src/test/unit/clientStatus.spec.ts new file mode 100644 index 00000000..aecaacaa --- /dev/null +++ b/src/test/unit/clientStatus.spec.ts @@ -0,0 +1,53 @@ +import { describe, expect, test } from 'vitest'; + +import { mergeClientStatuses } from '#server/utils/clientStatus'; + +describe('mergeClientStatuses', () => { + test('merges matching status records by public key', () => { + const clients = [ + { + id: 1, + publicKey: 'first', + latestHandshakeAt: null, + endpoint: null, + transferRx: null, + transferTx: null, + }, + { + id: 2, + publicKey: 'second', + latestHandshakeAt: null, + endpoint: null, + transferRx: null, + transferTx: null, + }, + ]; + const handshake = new Date('2026-07-20T00:00:00Z'); + + const result = mergeClientStatuses(clients, [ + { + publicKey: 'second', + latestHandshakeAt: handshake, + endpoint: '192.0.2.1:51820', + transferRx: 100, + transferTx: 200, + }, + { + publicKey: 'unknown', + latestHandshakeAt: null, + endpoint: null, + transferRx: 0, + transferTx: 0, + }, + ]); + + expect(result).toBe(clients); + expect(result[0]?.endpoint).toBeNull(); + expect(result[1]).toMatchObject({ + latestHandshakeAt: handshake, + endpoint: '192.0.2.1:51820', + transferRx: 100, + transferTx: 200, + }); + }); +});