Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"export-frontend-graphics": "tsx src/scripts/exportFrontendOptimizedGraphics.ts",
"export-frontend-maps": "tsx src/scripts/exportFrontendOptimizedMaps.ts",
"export-client-objs": "tsx src/scripts/exportClientObjs.ts",
"test:client-activity": "tsx src/scripts/testClientActivityPolicy.ts",
"start": "node dist/server.js",
"lint": "eslint './src/**/*.ts'",
"world-map": "node scripts/build-world-map.cjs",
Expand Down
41 changes: 41 additions & 0 deletions server/src/scripts/testClientActivityPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";

const source = readFileSync(resolve(process.cwd(), "src/server.ts"), "utf8");
const start = source.indexOf("function trackClientActivity");
const end = source.indexOf("\n}\n\n(async () => {", start);

assert.ok(start >= 0 && end > start, "trackClientActivity must remain present");

const fn = source.slice(start, end + 2);
const pingMatch = fn.match(/if \(isPingPacket\) \{([\s\S]*?)\n \}/);
assert.ok(pingMatch, "ping branch must remain explicit");

const pingBranch = pingMatch[1];
assert.match(fn, /ws\.packetCount = Number\(ws\.packetCount \?\? 0\) \+ 1;/, "all packets increment packetCount");
assert.match(pingBranch, /ws\.lastActivityAt = now;/, "ping must refresh client liveness");
assert.match(pingBranch, /return;/, "ping must return before non-ping accounting");

for (const forbidden of [
"lastPacketAt",
"packetCountNonPing",
"recentPacketTimestamps",
"recentPacketIntervalsMs",
"packetTypeCounts",
]) {
assert.ok(!pingBranch.includes(forbidden), `ping must not mutate non-ping metric: ${forbidden}`);
}

const pingBlockEnd = fn.indexOf("}", fn.indexOf("if (isPingPacket)"));
const nonPingTail = fn.slice(pingBlockEnd + 1);
assert.match(nonPingTail, /ws\.lastPacketAt = now;/, "non-ping packets still refresh lastPacketAt");
assert.match(nonPingTail, /ws\.lastActivityAt = now;/, "non-ping packets still refresh activity");

const idleStart = source.indexOf("function processIdleCharactersTick");
const idleEnd = source.indexOf("\n}\n\nfunction getScoutIdleReferenceAt", idleStart);
assert.ok(idleStart >= 0 && idleEnd > idleStart, "idle sweep must remain present");
const idleFn = source.slice(idleStart, idleEnd + 2);
assert.match(idleFn, /client\.lastActivityAt/, "idle cleanup must continue to use lastActivityAt");

console.log("client activity policy tests passed");
3 changes: 3 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ function trackClientActivity(ws: RuntimeClient, packageID: number) {
ws.packetCount = Number(ws.packetCount ?? 0) + 1;

if (isPingPacket) {
// A valid keepalive ping proves the client is still connected.
// Refresh only the idle timestamp; keep ping traffic out of non-ping accounting.
ws.lastActivityAt = now;
return;
Comment thread
gitar-bot[bot] marked this conversation as resolved.
}

Expand Down
Loading