mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 08:22:20 +02:00
refactor(runner): remove unreferenced Paperclip Runner test fixtures (#16543)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work.
> - The Paperclip Runner package keeps fake agent servers and versioned
profile identity declarations under `test/fixtures/` for its test
suites.
> - Five of those fixture files have no reference anywhere in the
repository: no test, script, generator, workflow step, or document loads
or spawns them.
> - Dead fixtures add maintenance cost and make the referenced sibling
fixtures harder to trust.
> - This pull request deletes only those five unreferenced fixture files
and adds an audit receipt with the reference evidence.
> - The benefit is a smaller test-fixture surface with zero loss of
executed coverage.
## Linked Issues or Issue Description
**What existing behavior does this improve?**
The test-suite hygiene of the Paperclip Runner package. The repository
keeps unused test-support files that no suite loads. This slows audits
and makes the referenced fixtures harder to trust.
**Subsystem affected**
`packages/paperclip-runner` test fixtures.
**Current behavior**
Five files under `packages/paperclip-runner/test/fixtures/` have zero
references anywhere in the repository:
- `fake-native-resume-codex-app-server.mjs` (277 LOC): an old fake codex
app-server for resume testing. Resume coverage now runs through
`fake-final-burst-codex-app-server.mjs` (used by
`server/src/services/native-runtime/native-session-resume.test.ts:287`)
and `fake-durable-codex-app-server.mjs` (used by
`packages/paperclip-runner/src/live/live-session.test.ts:2192`).
- `copilot-profile-v{3,4,5,6}-identity.json` (64 LOC total): orphaned
profile identity declarations. Copilot profile tests,
`native-backend-factory.test.ts`, and
`scripts/generate-acpx-profiles.mjs` reference only v7 through v19 by
literal path. No dynamic `copilot-profile-v${...}` path exists in the
repository (the only template-literal fixture path is
`test-fixtures/pi-acp/profile-v${version}-identity.json`).
**Proposed behavior**
Delete only the five unreferenced fixture files. Keep every referenced
fixture. Keep all registered test cases unchanged.
**Reason and benefit**
Smaller test-fixture surface: 341 test/support lines removed with zero
loss of executed coverage. The audit receipt
`doc/plans/2026-10-16-runner-dead-fixture-test-audit.md` records the
per-file reference evidence.
**Breaking changes**
None. No production code, no registered test case, and no test selection
path changes.
**Additional context**
A repository-wide `git grep` for each removed filename stem returns zero
external references across all tracked file types. The
`hashFiles('packages/paperclip-runner/test/fixtures/**')` occurrences in
the workflows are Rust cache-key inputs, not execution selectors;
deleting files only changes that cache hash.
## What Changed
- Deleted
`packages/paperclip-runner/test/fixtures/fake-native-resume-codex-app-server.mjs`.
- Deleted
`packages/paperclip-runner/test/fixtures/copilot-profile-v{3,4,5,6}-identity.json`.
- Added the audit receipt
`doc/plans/2026-10-16-runner-dead-fixture-test-audit.md` with per-file
reference evidence.
- 341 test/support lines removed. Zero production, documentation, or
registered test-case changes.
## Verification
- Repo-wide `git grep -i` for each removed filename stem
(`fake-native-resume`, `native-resume-codex`, `copilot-profile-v3` …
`copilot-profile-v6`) across all tracked file types returns zero
external references.
- Selection audit: `packages/paperclip-runner/vitest.config.ts` includes
only `src/**`, `devtools/**`, `examples/**`, and two named
`scripts/*.test.mjs`; the server project selects `server/src/**` and
`server/scripts/**`; the `test:typescript:prep` `node --test` list names
no fixture. No workflow executes anything under `test/fixtures/` — the
`pr-trusted.yml`/`release-verify.yml`
`hashFiles('packages/paperclip-runner/test/fixtures/**')` occurrences
are Rust cache-key inputs, not execution selectors.
- Rust tests use their own `fake-codex-app-server` binary in
`runner/crates/runner-core`, not these `.mjs` fixtures.
- Searched open and merged PRs for each filename: zero overlap (`gh
search prs`).
- Repo-wide `pnpm -r typecheck`, `pnpm test:run`, and `pnpm build` were
not run this session: the removed files are unreferenced, absent from
every vitest/node-test include pattern, and excluded from all tsconfigs,
so no compiled or executed path changes. No runtime CI savings are
claimed.
- All Paperclip CI gates green on head `ce24ab3` (verified 2026-10-16
~09:00Z): every `ci /` shard (General tests, e2e shards, serialized
server suites, Verify Paperclip Runner rust/vitest/static, Build,
Typecheck + Release Registry, policy, Docker context integrity, Canary
Dry Run, gate), `review`, `Superagent Security Scan`, `Socket Security`
PR alerts + project report, and `Contributor trust` all pass; the only
skipped jobs are the Storybook deploy/visual-regression jobs, which are
not required for this change. Greptile Review: 5/5 confidence, "Low
impact", no actionable issues, last reviewed commit `ce24ab3`.
## Risks
Low risk. No executed coverage or selection path changes. The only side
effect is a one-time change to the `hashFiles` Rust cache key that lists
`test/fixtures/**`, which simply invalidates that cache on the next CI
run.
## Model Used
Zhipu `z-ai/glm-5.3` (GLM), served through OpenRouter, running as the
Paperclip Test Gardener agent. Context window size unavailable to the
agent. Capabilities used: shell tooling, git, GitHub CLI search, and
file inspection; no image or code execution capabilities were used.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
This commit is contained in:
1 parent
e30550ef39
commit
cfa9dce295
6 files changed
+51
-341
No files matched your search
@@ -0,0 +1,51 @@
|
||||
# Test audit: remove unreferenced Paperclip Runner test fixtures
|
||||
|
||||
Date: 2026-10-16
|
||||
Branch: `PAP-458-nightly-test-gardening`
|
||||
Base: `78b833c4958465b5e345b0e337018011b00dab72` (origin/master)
|
||||
Scope: focused sweep of test-support files under `packages/paperclip-runner/test/fixtures/`.
|
||||
|
||||
## Removed files
|
||||
|
||||
| Candidate | Decision and reason | Contract | Evidence | Benefit |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `test/fixtures/fake-native-resume-codex-app-server.mjs` (277 lines) | Delete: orphaned fake codex app-server fixture | Nothing loads or spawns it; resume coverage runs through `fake-final-burst-codex-app-server.mjs` (server `native-session-resume.test.ts:287`) and `fake-durable-codex-app-server.mjs` (`live-session.test.ts:2192`) | Repo-wide `git grep -i "fake-native-resume"` and `"native-resume-codex"` → zero tracked references in code, docs, workflows, or path templates; sibling `.mjs` fakes are all referenced | 277 test-support LOC |
|
||||
| `test/fixtures/copilot-profile-v{3,4,5,6}-identity.json` (64 lines) | Delete: orphaned versioned identity declarations | Copilot profile tests and `native-backend-factory.test.ts` exercise v7–v19 by literal path; no dynamic `copilot-profile-v${...}` path exists (only `test-fixtures/pi-acp/profile-v${version}`) | Repo-wide `git grep` per exact filename stem → zero references; `generate-acpx-profiles.mjs` pins v19 only | 64 test-support LOC |
|
||||
|
||||
## Retention evidence
|
||||
|
||||
- Reference proof: `git grep` across all tracked file types (ts/tsx/mjs/js/json/yml/toml/rs/md) for each
|
||||
removed stem returns zero external references.
|
||||
- Selection proof: `packages/paperclip-runner/vitest.config.ts` include list covers
|
||||
`src/**`, `devtools/**`, `examples/**`, and two named `scripts/*.test.mjs` — never
|
||||
`test/fixtures/**`. The root server project also selects `server/src/**` only. The
|
||||
`test:typescript:prep` `node --test` list names no fixture. No workflow step executes
|
||||
files under `test/fixtures/`; the `pr-trusted.yml`/`release-verify.yml`
|
||||
`hashFiles('packages/paperclip-runner/test/fixtures/**')` occurrences are Rust
|
||||
cache-key inputs, not execution selectors (deleting files only changes that hash).
|
||||
- Rust tests use their own `fake-codex-app-server` binary (`runner/crates/runner-core`),
|
||||
not these `.mjs` fixtures.
|
||||
- No open or merged PR references any removed file (`gh search prs` per filename: zero
|
||||
hits). `scripts/tests/native-*-codex.mjs` opt-in checks and the three other fake
|
||||
`.mjs` servers are documented/referenced and retained.
|
||||
|
||||
## Validation
|
||||
|
||||
- Deletion-only diff: 341 test/support LOC removed, 0 production LOC, 0 docs LOC,
|
||||
0 registered test cases removed (no case existed for these files).
|
||||
- Repo-wide `pnpm -r typecheck`, `pnpm test:run`, and `pnpm build` were **not** run this
|
||||
session: the removed files are unreferenced, unselected by every vitest/node-test
|
||||
include pattern, and excluded from all tsconfigs, so no compiled or executed path
|
||||
changes. No runtime CI savings are claimed; the benefit is a smaller fixture
|
||||
maintenance surface.
|
||||
|
||||
Model: Zhipu `z-ai/glm-5.3` via OpenRouter (Test Gardener (GLM)); context window unavailable;
|
||||
shell tooling and git/GitHub search only.
|
||||
|
||||
Delivery: PR #16543, retitled to the `refactor(runner):` prefix because the repository
|
||||
quality gate requires a test file or a `refactor:` title for test-file-less PRs — no
|
||||
registered case exists for the removed fixtures. The PR body also carries the in-PR
|
||||
issue description using the literal enhancement-template labels
|
||||
(`What existing behavior does this improve?`, `Subsystem affected`, `Current behavior`,
|
||||
`Proposed behavior`, `Reason and benefit`, `Breaking changes`, `Additional context`)
|
||||
required by the same gate when no linked issue is referenced.
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"commandDigest": "sha256:527f9cbbad2f3e75169cae70d7aa5b189200e57f1ab7f9e1523357b5606b0939",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 3,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "a64405199b381688ffd72fdbb88396e423394cb407813d7b6f1db43bae9c3933",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "bb3dcf14972e82f6578c8250540cfbc6ec2ad953bea5d4f10d28c8b461011f3f",
|
||||
"agentFilesBinding": "registered-runtime-context-v1"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"commandDigest": "sha256:a119e436b4ad13ee70e1f58bedad4a0f20761b5fd982b752981f0cb1bc65f797",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 4,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "64180c194ab841d6f4bba7e6eca5ead621c6222c9a8057e4bb2bb6f007d8bb3c",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "bb3dcf14972e82f6578c8250540cfbc6ec2ad953bea5d4f10d28c8b461011f3f",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"commandDigest": "sha256:ece77e40876631a69a828b91813722001d71b6fc81be47ecd4b3dced84ff9473",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 5,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "79aad2d688b03362e8cfcbf7a08f78a8383869f6882a9c9ed66f1d18efb94f2b",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "bb3dcf14972e82f6578c8250540cfbc6ec2ad953bea5d4f10d28c8b461011f3f",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"commandDigest": "sha256:0fe49c2f8a2b144344d0de745fed1e4568df305de3a26c3a121dec21c8d4b751",
|
||||
"declaration": {
|
||||
"schema": "paperclip.acpx_profile_declaration.v1",
|
||||
"agent": "copilot",
|
||||
"agentProfileVersion": 6,
|
||||
"acpxVersion": "0.13.1",
|
||||
"agentServerVersion": "1.0.88",
|
||||
"protocolPolicyRevision": "copilot-agent-manual-v1",
|
||||
"acpxPatchSha256": "79aad2d688b03362e8cfcbf7a08f78a8383869f6882a9c9ed66f1d18efb94f2b",
|
||||
"policySha256": "a4be69be43db84052560420a3ca546a665c9165573c24d543eb33d144ba138a3",
|
||||
"distributionSourceSha256": "bb3dcf14972e82f6578c8250540cfbc6ec2ad953bea5d4f10d28c8b461011f3f",
|
||||
"agentFilesBinding": "registered-runtime-context-v1",
|
||||
"systemInstructionDelivery": "COPILOT_HOME/copilot-instructions.md:replace-under-lifetime-lease-before-launch:v1",
|
||||
"sharedRuntimeContract": "paperclip.acpx-runtime-contract.v1"
|
||||
}
|
||||
}
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import { createConnection, createServer } from "node:net";
|
||||
|
||||
const websocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
function serverFrame(payload, opcode = 0x1) {
|
||||
const body = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
||||
const extended = body.length < 126 ? 0 : body.length <= 0xffff ? 2 : 8;
|
||||
const frame = Buffer.alloc(2 + extended + body.length);
|
||||
frame[0] = 0x80 | opcode;
|
||||
frame[1] = body.length < 126 ? body.length : extended === 2 ? 126 : 127;
|
||||
let offset = 2;
|
||||
if (extended === 2) {
|
||||
frame.writeUInt16BE(body.length, offset);
|
||||
offset += 2;
|
||||
} else if (extended === 8) {
|
||||
frame.writeBigUInt64BE(BigInt(body.length), offset);
|
||||
offset += 8;
|
||||
}
|
||||
body.copy(frame, offset);
|
||||
return frame;
|
||||
}
|
||||
|
||||
const [mode, socketPath, statePath] = process.argv.slice(2);
|
||||
if (!mode || !socketPath) throw new Error("mode and socket path are required");
|
||||
|
||||
if (mode === "proxy") {
|
||||
const socket = createConnection(socketPath);
|
||||
socket.once("connect", () => {
|
||||
process.stdin.pipe(socket);
|
||||
socket.pipe(process.stdout);
|
||||
});
|
||||
socket.once("error", (error) => {
|
||||
process.stderr.write(`${error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
} else if (mode === "server") {
|
||||
if (!statePath) throw new Error("state path is required in server mode");
|
||||
|
||||
function load() {
|
||||
try {
|
||||
return JSON.parse(readFileSync(statePath, "utf8"));
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") throw error;
|
||||
return {
|
||||
threadId: "thread-native-resume",
|
||||
sessionId: "session-native-resume",
|
||||
turnId: "turn-native-resume",
|
||||
pendingTool: null,
|
||||
completed: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let state = load();
|
||||
|
||||
function save() {
|
||||
writeFileSync(statePath, `${JSON.stringify(state)}\n`, { mode: 0o600 });
|
||||
}
|
||||
|
||||
function send(socket, value) {
|
||||
socket.write(serverFrame(JSON.stringify(value)));
|
||||
}
|
||||
|
||||
function sendPendingTool(socket) {
|
||||
if (!state.pendingTool) return;
|
||||
send(socket, {
|
||||
id: state.pendingTool.requestId,
|
||||
method: "item/tool/call",
|
||||
params: {
|
||||
threadId: state.threadId,
|
||||
turnId: state.turnId,
|
||||
callId: state.pendingTool.callId,
|
||||
tool: "report_progress",
|
||||
arguments: {
|
||||
idempotencyKey: "native-resume-progress",
|
||||
body: "One native resume effect.",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleRequest(socket, message) {
|
||||
const { id, method } = message;
|
||||
if (method === "initialize") {
|
||||
send(socket, { id, result: { user: { sessionId: state.sessionId } } });
|
||||
return;
|
||||
}
|
||||
if (method === "thread/start" || method === "thread/resume") {
|
||||
send(socket, {
|
||||
id,
|
||||
result: {
|
||||
model: "gpt-native-resume-fixture",
|
||||
modelProvider: "openai-fixture",
|
||||
thread: { id: state.threadId, sessionId: state.sessionId },
|
||||
},
|
||||
});
|
||||
if (method === "thread/resume") sendPendingTool(socket);
|
||||
return;
|
||||
}
|
||||
if (method === "thread/read") {
|
||||
send(socket, {
|
||||
id,
|
||||
result: {
|
||||
thread: {
|
||||
id: state.threadId,
|
||||
sessionId: state.sessionId,
|
||||
turns: [{
|
||||
id: state.turnId,
|
||||
status: state.completed ? "completed" : "inProgress",
|
||||
}],
|
||||
tokenUsage: {
|
||||
total: {
|
||||
inputTokens: 10,
|
||||
cachedInputTokens: 0,
|
||||
outputTokens: 2,
|
||||
reasoningOutputTokens: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
state.pendingTool = {
|
||||
requestId: "rpc-native-resume",
|
||||
callId: "call-native-resume",
|
||||
};
|
||||
save();
|
||||
send(socket, {
|
||||
id,
|
||||
result: { turn: { id: state.turnId, status: "inProgress" } },
|
||||
});
|
||||
send(socket, {
|
||||
method: "turn/started",
|
||||
params: {
|
||||
threadId: state.threadId,
|
||||
turn: { id: state.turnId, status: "inProgress" },
|
||||
},
|
||||
});
|
||||
sendPendingTool(socket);
|
||||
return;
|
||||
}
|
||||
send(socket, {
|
||||
id,
|
||||
error: { code: -32601, message: `unsupported method ${method}` },
|
||||
});
|
||||
}
|
||||
|
||||
function handle(socket, message) {
|
||||
if (message.method) {
|
||||
if (message.id !== undefined) handleRequest(socket, message);
|
||||
return;
|
||||
}
|
||||
if (!state.pendingTool || String(message.id) !== state.pendingTool.requestId)
|
||||
return;
|
||||
const toolResult = message.result;
|
||||
state.pendingTool = null;
|
||||
state.completed = true;
|
||||
save();
|
||||
send(socket, {
|
||||
method: "item/completed",
|
||||
params: {
|
||||
threadId: state.threadId,
|
||||
turnId: state.turnId,
|
||||
item: {
|
||||
id: "message-native-resume",
|
||||
type: "agentMessage",
|
||||
text: `Tool result: ${JSON.stringify(toolResult)}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
send(socket, {
|
||||
method: "thread/tokenUsage/updated",
|
||||
params: {
|
||||
threadId: state.threadId,
|
||||
turnId: state.turnId,
|
||||
tokenUsage: {
|
||||
total: {
|
||||
inputTokens: 10,
|
||||
cachedInputTokens: 0,
|
||||
outputTokens: 2,
|
||||
reasoningTokens: 0,
|
||||
},
|
||||
runDelta: {
|
||||
requests: 1,
|
||||
inputTokens: 10,
|
||||
cacheReadTokens: 0,
|
||||
outputTokens: 2,
|
||||
reasoningTokens: 0,
|
||||
providerCostUsd: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
send(socket, {
|
||||
method: "turn/completed",
|
||||
params: {
|
||||
threadId: state.threadId,
|
||||
turn: { id: state.turnId, status: "completed" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
unlinkSync(socketPath);
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") throw error;
|
||||
}
|
||||
const server = createServer((socket) => {
|
||||
let upgraded = false;
|
||||
let buffer = Buffer.alloc(0);
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
if (!upgraded) {
|
||||
const boundary = buffer.indexOf("\r\n\r\n");
|
||||
if (boundary < 0) return;
|
||||
const headers = buffer.subarray(0, boundary).toString("utf8");
|
||||
const key = headers.match(/^sec-websocket-key:\s*(.+)$/im)?.[1]?.trim();
|
||||
if (!key) throw new Error("WebSocket upgrade omitted Sec-WebSocket-Key");
|
||||
const accept = createHash("sha1")
|
||||
.update(`${key}${websocketGuid}`)
|
||||
.digest("base64");
|
||||
socket.write(
|
||||
`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`,
|
||||
);
|
||||
upgraded = true;
|
||||
buffer = buffer.subarray(boundary + 4);
|
||||
}
|
||||
|
||||
while (buffer.length >= 2) {
|
||||
const first = buffer[0];
|
||||
const second = buffer[1];
|
||||
let length = second & 0x7f;
|
||||
let offset = 2;
|
||||
if (length === 126) {
|
||||
if (buffer.length < 4) return;
|
||||
length = buffer.readUInt16BE(2);
|
||||
offset = 4;
|
||||
} else if (length === 127) {
|
||||
if (buffer.length < 10) return;
|
||||
length = Number(buffer.readBigUInt64BE(2));
|
||||
offset = 10;
|
||||
}
|
||||
if ((second & 0x80) === 0)
|
||||
throw new Error("WebSocket client frame was not masked");
|
||||
if (buffer.length < offset + 4 + length) return;
|
||||
const mask = buffer.subarray(offset, offset + 4);
|
||||
offset += 4;
|
||||
const payload = Buffer.from(buffer.subarray(offset, offset + length));
|
||||
buffer = buffer.subarray(offset + length);
|
||||
for (let index = 0; index < payload.length; index += 1)
|
||||
payload[index] ^= mask[index % 4];
|
||||
|
||||
const opcode = first & 0x0f;
|
||||
if (opcode === 0x8) {
|
||||
socket.end(serverFrame(payload, 0x8));
|
||||
return;
|
||||
}
|
||||
if (opcode === 0x9) {
|
||||
socket.write(serverFrame(payload, 0xa));
|
||||
continue;
|
||||
}
|
||||
if (opcode !== 0x1)
|
||||
throw new Error(`unsupported WebSocket opcode ${opcode}`);
|
||||
handle(socket, JSON.parse(payload.toString("utf8")));
|
||||
}
|
||||
});
|
||||
});
|
||||
server.listen(socketPath);
|
||||
process.once("SIGTERM", () => server.close());
|
||||
} else {
|
||||
throw new Error(`unsupported mode ${mode}`);
|
||||
}
|
||||
Reference in new issue
Block a user