@vibelet/cli 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.json +5 -0
- package/dist/advertised-hosts.d.ts +34 -0
- package/dist/advertised-hosts.d.ts.map +1 -0
- package/dist/advertised-hosts.js +176 -0
- package/dist/advertised-hosts.js.map +1 -0
- package/dist/advertised-hosts.test.d.ts +2 -0
- package/dist/advertised-hosts.test.d.ts.map +1 -0
- package/dist/advertised-hosts.test.js +96 -0
- package/dist/advertised-hosts.test.js.map +1 -0
- package/dist/audit.d.ts +30 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +73 -0
- package/dist/audit.js.map +1 -0
- package/dist/audit.test.d.ts +2 -0
- package/dist/audit.test.d.ts.map +1 -0
- package/dist/audit.test.js +33 -0
- package/dist/audit.test.js.map +1 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +27 -0
- package/dist/auth.js.map +1 -0
- package/dist/claude-hooks.d.ts +58 -0
- package/dist/claude-hooks.d.ts.map +1 -0
- package/dist/claude-hooks.js +129 -0
- package/dist/claude-hooks.js.map +1 -0
- package/dist/cli-version.d.ts +3 -0
- package/dist/cli-version.d.ts.map +1 -0
- package/dist/cli-version.js +35 -0
- package/dist/cli-version.js.map +1 -0
- package/dist/cli-version.test.d.ts +2 -0
- package/dist/cli-version.test.d.ts.map +1 -0
- package/dist/cli-version.test.js +38 -0
- package/dist/cli-version.test.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +327 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +184 -0
- package/dist/config.test.js.map +1 -0
- package/dist/dev-auth.test.d.ts +2 -0
- package/dist/dev-auth.test.d.ts.map +1 -0
- package/dist/dev-auth.test.js +154 -0
- package/dist/dev-auth.test.js.map +1 -0
- package/dist/dev-script.test.d.ts +2 -0
- package/dist/dev-script.test.d.ts.map +1 -0
- package/dist/dev-script.test.js +412 -0
- package/dist/dev-script.test.js.map +1 -0
- package/dist/drivers/claude.d.ts +34 -0
- package/dist/drivers/claude.d.ts.map +1 -0
- package/dist/drivers/claude.js +413 -0
- package/dist/drivers/claude.js.map +1 -0
- package/dist/drivers/claude.test.d.ts +2 -0
- package/dist/drivers/claude.test.d.ts.map +1 -0
- package/dist/drivers/claude.test.js +951 -0
- package/dist/drivers/claude.test.js.map +1 -0
- package/dist/drivers/codex.d.ts +38 -0
- package/dist/drivers/codex.d.ts.map +1 -0
- package/dist/drivers/codex.js +771 -0
- package/dist/drivers/codex.js.map +1 -0
- package/dist/drivers/codex.test.d.ts +2 -0
- package/dist/drivers/codex.test.d.ts.map +1 -0
- package/dist/drivers/codex.test.js +939 -0
- package/dist/drivers/codex.test.js.map +1 -0
- package/dist/drivers/types.d.ts +14 -0
- package/dist/drivers/types.d.ts.map +1 -0
- package/dist/drivers/types.js +2 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/e2e.test.d.ts +2 -0
- package/dist/e2e.test.d.ts.map +1 -0
- package/dist/e2e.test.js +111 -0
- package/dist/e2e.test.js.map +1 -0
- package/dist/identity.d.ts +10 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +66 -0
- package/dist/identity.js.map +1 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +25 -0
- package/dist/identity.test.js.map +1 -0
- package/dist/index-entry.test.d.ts +2 -0
- package/dist/index-entry.test.d.ts.map +1 -0
- package/dist/index-entry.test.js +272 -0
- package/dist/index-entry.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +707 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +31 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +75 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +52 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +89 -0
- package/dist/metrics.js.map +1 -0
- package/dist/pairing-store.d.ts +29 -0
- package/dist/pairing-store.d.ts.map +1 -0
- package/dist/pairing-store.js +131 -0
- package/dist/pairing-store.js.map +1 -0
- package/dist/pairing-store.test.d.ts +2 -0
- package/dist/pairing-store.test.d.ts.map +1 -0
- package/dist/pairing-store.test.js +47 -0
- package/dist/pairing-store.test.js.map +1 -0
- package/dist/paths.d.ts +16 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +18 -0
- package/dist/paths.js.map +1 -0
- package/dist/perf-compare.d.ts +13 -0
- package/dist/perf-compare.d.ts.map +1 -0
- package/dist/perf-compare.js +125 -0
- package/dist/perf-compare.js.map +1 -0
- package/dist/port-conflict.d.ts +9 -0
- package/dist/port-conflict.d.ts.map +1 -0
- package/dist/port-conflict.js +33 -0
- package/dist/port-conflict.js.map +1 -0
- package/dist/port-conflict.test.d.ts +2 -0
- package/dist/port-conflict.test.d.ts.map +1 -0
- package/dist/port-conflict.test.js +38 -0
- package/dist/port-conflict.test.js.map +1 -0
- package/dist/process-scanner.d.ts +43 -0
- package/dist/process-scanner.d.ts.map +1 -0
- package/dist/process-scanner.js +453 -0
- package/dist/process-scanner.js.map +1 -0
- package/dist/process-scanner.perf.test.d.ts +2 -0
- package/dist/process-scanner.perf.test.d.ts.map +1 -0
- package/dist/process-scanner.perf.test.js +186 -0
- package/dist/process-scanner.perf.test.js.map +1 -0
- package/dist/process-scanner.test.d.ts +2 -0
- package/dist/process-scanner.test.d.ts.map +1 -0
- package/dist/process-scanner.test.js +399 -0
- package/dist/process-scanner.test.js.map +1 -0
- package/dist/push-protocol.d.ts +15 -0
- package/dist/push-protocol.d.ts.map +1 -0
- package/dist/push-protocol.js +23 -0
- package/dist/push-protocol.js.map +1 -0
- package/dist/push-protocol.test.d.ts +2 -0
- package/dist/push-protocol.test.d.ts.map +1 -0
- package/dist/push-protocol.test.js +57 -0
- package/dist/push-protocol.test.js.map +1 -0
- package/dist/push-store.d.ts +22 -0
- package/dist/push-store.d.ts.map +1 -0
- package/dist/push-store.js +103 -0
- package/dist/push-store.js.map +1 -0
- package/dist/push-store.test.d.ts +2 -0
- package/dist/push-store.test.d.ts.map +1 -0
- package/dist/push-store.test.js +79 -0
- package/dist/push-store.test.js.map +1 -0
- package/dist/push.d.ts +65 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +202 -0
- package/dist/push.js.map +1 -0
- package/dist/push.test.d.ts +2 -0
- package/dist/push.test.d.ts.map +1 -0
- package/dist/push.test.js +199 -0
- package/dist/push.test.js.map +1 -0
- package/dist/safe-stdio.d.ts +3 -0
- package/dist/safe-stdio.d.ts.map +1 -0
- package/dist/safe-stdio.js +46 -0
- package/dist/safe-stdio.js.map +1 -0
- package/dist/scanner.d.ts +30 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +859 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scanner.perf.test.d.ts +2 -0
- package/dist/scanner.perf.test.d.ts.map +1 -0
- package/dist/scanner.perf.test.js +320 -0
- package/dist/scanner.perf.test.js.map +1 -0
- package/dist/scanner.test.d.ts +2 -0
- package/dist/scanner.test.d.ts.map +1 -0
- package/dist/scanner.test.js +948 -0
- package/dist/scanner.test.js.map +1 -0
- package/dist/session-inventory.d.ts +63 -0
- package/dist/session-inventory.d.ts.map +1 -0
- package/dist/session-inventory.js +525 -0
- package/dist/session-inventory.js.map +1 -0
- package/dist/session-inventory.perf.test.d.ts +2 -0
- package/dist/session-inventory.perf.test.d.ts.map +1 -0
- package/dist/session-inventory.perf.test.js +220 -0
- package/dist/session-inventory.perf.test.js.map +1 -0
- package/dist/session-inventory.test.d.ts +2 -0
- package/dist/session-inventory.test.d.ts.map +1 -0
- package/dist/session-inventory.test.js +712 -0
- package/dist/session-inventory.test.js.map +1 -0
- package/dist/session-manager.d.ts +75 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +1515 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/session-manager.test.d.ts +2 -0
- package/dist/session-manager.test.d.ts.map +1 -0
- package/dist/session-manager.test.js +2861 -0
- package/dist/session-manager.test.js.map +1 -0
- package/dist/session-store.d.ts +42 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +163 -0
- package/dist/session-store.js.map +1 -0
- package/dist/session-store.test.d.ts +2 -0
- package/dist/session-store.test.d.ts.map +1 -0
- package/dist/session-store.test.js +236 -0
- package/dist/session-store.test.js.map +1 -0
- package/dist/session-title.d.ts +6 -0
- package/dist/session-title.d.ts.map +1 -0
- package/dist/session-title.js +105 -0
- package/dist/session-title.js.map +1 -0
- package/dist/session-title.perf.test.d.ts +2 -0
- package/dist/session-title.perf.test.d.ts.map +1 -0
- package/dist/session-title.perf.test.js +99 -0
- package/dist/session-title.perf.test.js.map +1 -0
- package/dist/session-title.test.d.ts +2 -0
- package/dist/session-title.test.d.ts.map +1 -0
- package/dist/session-title.test.js +199 -0
- package/dist/session-title.test.js.map +1 -0
- package/dist/shutdown-endpoint.test.d.ts +2 -0
- package/dist/shutdown-endpoint.test.d.ts.map +1 -0
- package/dist/shutdown-endpoint.test.js +93 -0
- package/dist/shutdown-endpoint.test.js.map +1 -0
- package/dist/storage-housekeeping.d.ts +28 -0
- package/dist/storage-housekeeping.d.ts.map +1 -0
- package/dist/storage-housekeeping.js +76 -0
- package/dist/storage-housekeeping.js.map +1 -0
- package/dist/storage-housekeeping.test.d.ts +2 -0
- package/dist/storage-housekeeping.test.d.ts.map +1 -0
- package/dist/storage-housekeeping.test.js +65 -0
- package/dist/storage-housekeeping.test.js.map +1 -0
- package/dist/test-daemon-harness.d.ts +31 -0
- package/dist/test-daemon-harness.d.ts.map +1 -0
- package/dist/test-daemon-harness.js +337 -0
- package/dist/test-daemon-harness.js.map +1 -0
- package/dist/token-auth.test.d.ts +2 -0
- package/dist/token-auth.test.d.ts.map +1 -0
- package/dist/token-auth.test.js +52 -0
- package/dist/token-auth.test.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +40 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.test.d.ts +2 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +54 -0
- package/dist/utils.test.js.map +1 -0
- package/dist/ws-data.d.ts +4 -0
- package/dist/ws-data.d.ts.map +1 -0
- package/dist/ws-data.js +20 -0
- package/dist/ws-data.js.map +1 -0
- package/dist/ws-data.test.d.ts +2 -0
- package/dist/ws-data.test.d.ts.map +1 -0
- package/dist/ws-data.test.js +17 -0
- package/dist/ws-data.test.js.map +1 -0
- package/package.json +24 -27
- package/perf-reporter.mjs +138 -0
- package/scripts/build-release.mjs +41 -0
- package/scripts/dev.mjs +537 -0
- package/src/advertised-hosts.test.ts +125 -0
- package/src/advertised-hosts.ts +225 -0
- package/src/audit.test.ts +38 -0
- package/src/audit.ts +117 -0
- package/src/auth.ts +31 -0
- package/src/claude-hooks.ts +195 -0
- package/src/cli-version.test.ts +36 -0
- package/src/cli-version.ts +46 -0
- package/src/config.test.ts +254 -0
- package/src/config.ts +324 -0
- package/src/dev-auth.test.ts +183 -0
- package/src/dev-script.test.ts +511 -0
- package/src/drivers/claude.test.ts +1186 -0
- package/src/drivers/claude.ts +443 -0
- package/src/drivers/codex.test.ts +1096 -0
- package/src/drivers/codex.ts +879 -0
- package/src/drivers/types.ts +15 -0
- package/src/e2e.test.ts +139 -0
- package/src/identity.test.ts +26 -0
- package/src/identity.ts +82 -0
- package/src/index-entry.test.ts +336 -0
- package/src/index.ts +781 -0
- package/src/logger.ts +112 -0
- package/src/metrics.ts +117 -0
- package/src/pairing-store.test.ts +53 -0
- package/src/pairing-store.ts +154 -0
- package/src/paths.ts +19 -0
- package/src/perf-compare.ts +164 -0
- package/src/port-conflict.test.ts +45 -0
- package/src/port-conflict.ts +44 -0
- package/src/process-scanner.perf.test.ts +222 -0
- package/src/process-scanner.test.ts +575 -0
- package/src/process-scanner.ts +514 -0
- package/src/push-protocol.test.ts +74 -0
- package/src/push-protocol.ts +36 -0
- package/src/push-store.test.ts +89 -0
- package/src/push-store.ts +126 -0
- package/src/push.test.ts +234 -0
- package/src/push.ts +318 -0
- package/src/safe-stdio.ts +51 -0
- package/src/scanner.perf.test.ts +359 -0
- package/src/scanner.test.ts +1045 -0
- package/src/scanner.ts +924 -0
- package/src/session-inventory.perf.test.ts +250 -0
- package/src/session-inventory.test.ts +1002 -0
- package/src/session-inventory.ts +721 -0
- package/src/session-manager.test.ts +3430 -0
- package/src/session-manager.ts +1775 -0
- package/src/session-store.test.ts +276 -0
- package/src/session-store.ts +202 -0
- package/src/session-title.perf.test.ts +118 -0
- package/src/session-title.test.ts +286 -0
- package/src/session-title.ts +108 -0
- package/src/shutdown-endpoint.test.ts +95 -0
- package/src/storage-housekeeping.test.ts +78 -0
- package/src/storage-housekeeping.ts +111 -0
- package/src/test-daemon-harness.ts +410 -0
- package/src/token-auth.test.ts +67 -0
- package/src/utils.test.ts +65 -0
- package/src/utils.ts +47 -0
- package/src/ws-data.test.ts +20 -0
- package/src/ws-data.ts +26 -0
- package/tsconfig.json +12 -0
- package/README.md +0 -80
- package/bin/cloudflared-quick-tunnel.mjs +0 -11
- package/bin/cloudflared-resolver.mjs +0 -68
- package/bin/vibelet-runtime-policy.mjs +0 -36
- package/bin/vibelet.cjs +0 -12
- package/bin/vibelet.mjs +0 -1019
- package/dist/index.cjs +0 -123
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
3
|
+
import { chmodSync, existsSync, rmSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { createServer, Socket } from 'node:net';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import WebSocket from 'ws';
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const srcDir = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const daemonDir = path.resolve(srcDir, '..');
|
|
14
|
+
const tsxPackageJsonPath = require.resolve('tsx/package.json');
|
|
15
|
+
const tsxCliPath = path.resolve(path.dirname(tsxPackageJsonPath), 'dist/cli.mjs');
|
|
16
|
+
|
|
17
|
+
export interface SpawnedDaemon {
|
|
18
|
+
child: ChildProcess;
|
|
19
|
+
port: number;
|
|
20
|
+
homeDir: string;
|
|
21
|
+
token: string;
|
|
22
|
+
getStdout: () => string;
|
|
23
|
+
getStderr: () => string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function getFreePort(): Promise<number> {
|
|
27
|
+
const server = createServer();
|
|
28
|
+
server.listen(0, '127.0.0.1');
|
|
29
|
+
await new Promise<void>((resolve) => server.once('listening', () => resolve()));
|
|
30
|
+
const address = server.address();
|
|
31
|
+
assert.ok(address && typeof address === 'object');
|
|
32
|
+
const { port } = address;
|
|
33
|
+
await new Promise<void>((resolve, reject) => {
|
|
34
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
35
|
+
});
|
|
36
|
+
return port;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function spawnDaemon(options: {
|
|
40
|
+
port?: number;
|
|
41
|
+
token?: string;
|
|
42
|
+
useFakeClaudeCli?: boolean;
|
|
43
|
+
env?: Record<string, string>;
|
|
44
|
+
} = {}): Promise<SpawnedDaemon> {
|
|
45
|
+
const port = options.port ?? await getFreePort();
|
|
46
|
+
const token = options.token ?? 'test-token';
|
|
47
|
+
const homeDir = mkdtempSync(path.join(tmpdir(), 'vibelet-daemon-entry-'));
|
|
48
|
+
const fakeClaudeCliPath = options.useFakeClaudeCli ? createFakeClaudeCli(homeDir) : undefined;
|
|
49
|
+
let stdout = '';
|
|
50
|
+
let stderr = '';
|
|
51
|
+
|
|
52
|
+
const child = spawn(process.execPath, [tsxCliPath, 'src/index.ts'], {
|
|
53
|
+
cwd: daemonDir,
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
HOME: homeDir,
|
|
57
|
+
VIBE_TEST: '0',
|
|
58
|
+
VIBE_PORT: String(port),
|
|
59
|
+
VIBE_TOKEN: token,
|
|
60
|
+
CLAUDE_PATH: fakeClaudeCliPath ?? process.execPath,
|
|
61
|
+
CODEX_PATH: process.execPath,
|
|
62
|
+
VIBE_FAKE_CLAUDE_HOME: homeDir,
|
|
63
|
+
...options.env,
|
|
64
|
+
},
|
|
65
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
child.stdout?.setEncoding('utf8');
|
|
69
|
+
child.stdout?.on('data', (chunk) => {
|
|
70
|
+
stdout += chunk;
|
|
71
|
+
});
|
|
72
|
+
child.stderr?.setEncoding('utf8');
|
|
73
|
+
child.stderr?.on('data', (chunk) => {
|
|
74
|
+
stderr += chunk;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await waitForPortState(port, true, 15_000);
|
|
78
|
+
await waitForHealth(port, 15_000);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
child,
|
|
82
|
+
port,
|
|
83
|
+
homeDir,
|
|
84
|
+
token,
|
|
85
|
+
getStdout: () => stdout,
|
|
86
|
+
getStderr: () => stderr,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function cleanupDaemon(daemon: SpawnedDaemon): Promise<void> {
|
|
91
|
+
const { child, port, homeDir } = daemon;
|
|
92
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
93
|
+
child.kill('SIGTERM');
|
|
94
|
+
try {
|
|
95
|
+
await waitForExit(child, 5_000);
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
await waitForPortState(port, false, 5_000).catch(() => {});
|
|
99
|
+
rmSync(homeDir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function waitForHealth(port: number, timeoutMs = 10_000): Promise<Record<string, unknown>> {
|
|
103
|
+
const deadline = Date.now() + timeoutMs;
|
|
104
|
+
while (Date.now() < deadline) {
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`);
|
|
107
|
+
if (response.ok) {
|
|
108
|
+
return await response.json() as Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
await delay(150);
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`Timed out waiting for health on port ${port}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function openWebSocket(url: string): Promise<WebSocket> {
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const ws = new WebSocket(url);
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
ws.terminate();
|
|
121
|
+
reject(new Error(`Timed out waiting for websocket open: ${url}`));
|
|
122
|
+
}, 5_000);
|
|
123
|
+
|
|
124
|
+
ws.once('open', () => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
resolve(ws);
|
|
127
|
+
});
|
|
128
|
+
ws.once('error', (error) => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
reject(error);
|
|
131
|
+
});
|
|
132
|
+
ws.once('close', (code, reason) => {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
reject(new Error(`WebSocket closed before opening (${code}): ${reason.toString()}`));
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function waitForWebSocketMessage(
|
|
140
|
+
ws: WebSocket,
|
|
141
|
+
predicate: (message: Record<string, unknown>) => boolean,
|
|
142
|
+
timeoutMs = 5_000,
|
|
143
|
+
): Promise<Record<string, unknown>> {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const timer = setTimeout(() => {
|
|
146
|
+
ws.off('message', onMessage);
|
|
147
|
+
reject(new Error('Timed out waiting for websocket message'));
|
|
148
|
+
}, timeoutMs);
|
|
149
|
+
|
|
150
|
+
const onMessage = (data: WebSocket.Data) => {
|
|
151
|
+
const message = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
152
|
+
if (predicate(message)) {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
ws.off('message', onMessage);
|
|
155
|
+
resolve(message);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
ws.on('message', onMessage);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function waitForWebSocketClose(ws: WebSocket, timeoutMs = 5_000): Promise<number> {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
const timer = setTimeout(() => {
|
|
166
|
+
ws.terminate();
|
|
167
|
+
reject(new Error('Timed out waiting for websocket close'));
|
|
168
|
+
}, timeoutMs);
|
|
169
|
+
|
|
170
|
+
ws.once('close', (code) => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
resolve(code);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface WebSocketRecorder {
|
|
178
|
+
messages: Array<Record<string, unknown>>;
|
|
179
|
+
waitFor: (
|
|
180
|
+
predicate: (message: Record<string, unknown>) => boolean,
|
|
181
|
+
timeoutMs?: number,
|
|
182
|
+
) => Promise<Record<string, unknown>>;
|
|
183
|
+
stop: () => void;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function createWebSocketRecorder(ws: WebSocket): WebSocketRecorder {
|
|
187
|
+
const messages: Array<Record<string, unknown>> = [];
|
|
188
|
+
const waiters = new Set<{
|
|
189
|
+
predicate: (message: Record<string, unknown>) => boolean;
|
|
190
|
+
resolve: (message: Record<string, unknown>) => void;
|
|
191
|
+
reject: (error: Error) => void;
|
|
192
|
+
timer: ReturnType<typeof setTimeout>;
|
|
193
|
+
}>();
|
|
194
|
+
|
|
195
|
+
const finishWaiter = (
|
|
196
|
+
waiter: {
|
|
197
|
+
predicate: (message: Record<string, unknown>) => boolean;
|
|
198
|
+
resolve: (message: Record<string, unknown>) => void;
|
|
199
|
+
reject: (error: Error) => void;
|
|
200
|
+
timer: ReturnType<typeof setTimeout>;
|
|
201
|
+
},
|
|
202
|
+
error?: Error,
|
|
203
|
+
message?: Record<string, unknown>,
|
|
204
|
+
) => {
|
|
205
|
+
clearTimeout(waiter.timer);
|
|
206
|
+
waiters.delete(waiter);
|
|
207
|
+
if (error) {
|
|
208
|
+
waiter.reject(error);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
waiter.resolve(message as Record<string, unknown>);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const onMessage = (data: WebSocket.Data) => {
|
|
215
|
+
const message = JSON.parse(data.toString()) as Record<string, unknown>;
|
|
216
|
+
messages.push(message);
|
|
217
|
+
for (const waiter of [...waiters]) {
|
|
218
|
+
if (waiter.predicate(message)) {
|
|
219
|
+
finishWaiter(waiter, undefined, message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
ws.on('message', onMessage);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
messages,
|
|
228
|
+
waitFor(predicate, timeoutMs = 5_000) {
|
|
229
|
+
const existing = messages.find(predicate);
|
|
230
|
+
if (existing) {
|
|
231
|
+
return Promise.resolve(existing);
|
|
232
|
+
}
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
const waiter = {
|
|
235
|
+
predicate,
|
|
236
|
+
resolve,
|
|
237
|
+
reject,
|
|
238
|
+
timer: setTimeout(() => {
|
|
239
|
+
finishWaiter(waiter, new Error('Timed out waiting for recorded websocket message'));
|
|
240
|
+
}, timeoutMs),
|
|
241
|
+
};
|
|
242
|
+
waiters.add(waiter);
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
stop() {
|
|
246
|
+
ws.off('message', onMessage);
|
|
247
|
+
for (const waiter of [...waiters]) {
|
|
248
|
+
finishWaiter(waiter, new Error('Recorder stopped'));
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function readAuditLog(homeDir: string, timeoutMs = 5_000): Promise<string> {
|
|
255
|
+
const auditPath = path.join(homeDir, '.vibelet', 'data', 'audit.jsonl');
|
|
256
|
+
const deadline = Date.now() + timeoutMs;
|
|
257
|
+
while (Date.now() < deadline) {
|
|
258
|
+
if (existsSync(auditPath)) {
|
|
259
|
+
return readFileSync(auditPath, 'utf8');
|
|
260
|
+
}
|
|
261
|
+
await delay(100);
|
|
262
|
+
}
|
|
263
|
+
throw new Error(`Timed out waiting for audit log at ${auditPath}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export async function waitForStdout(
|
|
267
|
+
daemon: SpawnedDaemon,
|
|
268
|
+
pattern: RegExp,
|
|
269
|
+
timeoutMs = 5_000,
|
|
270
|
+
): Promise<string> {
|
|
271
|
+
const deadline = Date.now() + timeoutMs;
|
|
272
|
+
while (Date.now() < deadline) {
|
|
273
|
+
const stdout = daemon.getStdout();
|
|
274
|
+
if (pattern.test(stdout)) {
|
|
275
|
+
return stdout;
|
|
276
|
+
}
|
|
277
|
+
await delay(100);
|
|
278
|
+
}
|
|
279
|
+
throw new Error(`Timed out waiting for stdout pattern ${pattern}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function waitForExit(child: ChildProcess, timeoutMs: number): Promise<void> {
|
|
283
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
284
|
+
|
|
285
|
+
await new Promise<void>((resolve, reject) => {
|
|
286
|
+
const timer = setTimeout(() => reject(new Error('Timed out waiting for daemon to exit.')), timeoutMs);
|
|
287
|
+
child.once('exit', () => {
|
|
288
|
+
clearTimeout(timer);
|
|
289
|
+
resolve();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function isPortListening(port: number): Promise<boolean> {
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
const socket = new Socket();
|
|
297
|
+
|
|
298
|
+
const finish = (result: boolean) => {
|
|
299
|
+
socket.removeAllListeners();
|
|
300
|
+
socket.destroy();
|
|
301
|
+
resolve(result);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
socket.setTimeout(200);
|
|
305
|
+
socket.once('connect', () => finish(true));
|
|
306
|
+
socket.once('timeout', () => finish(false));
|
|
307
|
+
socket.once('error', () => finish(false));
|
|
308
|
+
socket.connect(port, '127.0.0.1');
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function waitForPortState(port: number, expectedListening: boolean, timeoutMs: number): Promise<void> {
|
|
313
|
+
const deadline = Date.now() + timeoutMs;
|
|
314
|
+
while (Date.now() < deadline) {
|
|
315
|
+
if ((await isPortListening(port)) === expectedListening) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
await delay(100);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
throw new Error(`Timed out waiting for port ${port} to become ${expectedListening ? 'open' : 'closed'}.`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function delay(ms: number): Promise<void> {
|
|
325
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function createFakeClaudeCli(homeDir: string): string {
|
|
329
|
+
const scriptPath = path.join(homeDir, 'fake-claude.mjs');
|
|
330
|
+
writeFileSync(scriptPath, `#!/usr/bin/env node
|
|
331
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
332
|
+
import path from 'node:path';
|
|
333
|
+
import { randomUUID } from 'node:crypto';
|
|
334
|
+
|
|
335
|
+
function readArg(name) {
|
|
336
|
+
const index = process.argv.indexOf(name);
|
|
337
|
+
return index >= 0 ? process.argv[index + 1] ?? '' : '';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function encodeCwd(cwd) {
|
|
341
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, '-');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function emit(payload) {
|
|
345
|
+
process.stdout.write(JSON.stringify(payload) + '\\n');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function appendJsonl(filePath, payload) {
|
|
349
|
+
appendFileSync(filePath, JSON.stringify(payload) + '\\n');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const prompt = readArg('-p');
|
|
353
|
+
const resumeId = readArg('--resume');
|
|
354
|
+
const cwd = process.cwd();
|
|
355
|
+
const transcriptHome = process.env.VIBE_FAKE_CLAUDE_HOME || process.env.HOME || cwd;
|
|
356
|
+
const sessionId = resumeId || 'fake-claude-' + randomUUID();
|
|
357
|
+
const responseText = 'fake claude reply: ' + prompt;
|
|
358
|
+
const projectDir = path.join(transcriptHome, '.claude', 'projects', encodeCwd(cwd));
|
|
359
|
+
const transcriptPath = path.join(projectDir, sessionId + '.jsonl');
|
|
360
|
+
|
|
361
|
+
mkdirSync(projectDir, { recursive: true });
|
|
362
|
+
|
|
363
|
+
appendJsonl(transcriptPath, {
|
|
364
|
+
type: 'user',
|
|
365
|
+
cwd,
|
|
366
|
+
sessionId,
|
|
367
|
+
message: {
|
|
368
|
+
role: 'user',
|
|
369
|
+
content: [{ type: 'text', text: prompt }],
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
emit({ type: 'system', subtype: 'init', session_id: sessionId });
|
|
374
|
+
|
|
375
|
+
for (const chunk of responseText.match(/.{1,10}/g) ?? []) {
|
|
376
|
+
emit({
|
|
377
|
+
type: 'stream_event',
|
|
378
|
+
event: {
|
|
379
|
+
type: 'content_block_delta',
|
|
380
|
+
delta: {
|
|
381
|
+
type: 'text_delta',
|
|
382
|
+
text: chunk,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
appendJsonl(transcriptPath, {
|
|
389
|
+
type: 'assistant',
|
|
390
|
+
cwd,
|
|
391
|
+
sessionId,
|
|
392
|
+
message: {
|
|
393
|
+
role: 'assistant',
|
|
394
|
+
content: [{ type: 'text', text: responseText }],
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
emit({
|
|
399
|
+
type: 'result',
|
|
400
|
+
result: responseText,
|
|
401
|
+
total_cost_usd: 0,
|
|
402
|
+
usage: {
|
|
403
|
+
input_tokens: prompt.length,
|
|
404
|
+
output_tokens: responseText.length,
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
`, 'utf8');
|
|
408
|
+
chmodSync(scriptPath, 0o755);
|
|
409
|
+
return scriptPath;
|
|
410
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
isAuthorizedToken,
|
|
5
|
+
isLegacyToken,
|
|
6
|
+
isLoopbackAddress,
|
|
7
|
+
readBearerToken,
|
|
8
|
+
resolveRequestToken,
|
|
9
|
+
} from './auth.js';
|
|
10
|
+
|
|
11
|
+
test('isLoopbackAddress accepts IPv4 and IPv6 loopback addresses', () => {
|
|
12
|
+
assert.equal(isLoopbackAddress('127.0.0.1'), true);
|
|
13
|
+
assert.equal(isLoopbackAddress('::1'), true);
|
|
14
|
+
assert.equal(isLoopbackAddress('::ffff:127.0.0.1'), true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('isLoopbackAddress rejects non-loopback addresses', () => {
|
|
18
|
+
assert.equal(isLoopbackAddress('192.168.1.10'), false);
|
|
19
|
+
assert.equal(isLoopbackAddress(undefined), false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('readBearerToken parses bearer authorization header values', () => {
|
|
23
|
+
assert.equal(readBearerToken('Bearer abc123'), 'abc123');
|
|
24
|
+
assert.equal(readBearerToken('bearer abc123'), 'abc123');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('readBearerToken returns null for missing or malformed headers', () => {
|
|
28
|
+
assert.equal(readBearerToken(undefined), null);
|
|
29
|
+
assert.equal(readBearerToken('Basic abc123'), null);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('resolveRequestToken prefers Authorization bearer token over query token', () => {
|
|
33
|
+
assert.equal(resolveRequestToken('Bearer header-token', 'query-token'), 'header-token');
|
|
34
|
+
assert.equal(resolveRequestToken(undefined, 'query-token'), 'query-token');
|
|
35
|
+
assert.equal(resolveRequestToken(undefined, null), null);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('isLegacyToken matches only when both token and configured legacy token are present', () => {
|
|
39
|
+
assert.equal(isLegacyToken('legacy', 'legacy'), true);
|
|
40
|
+
assert.equal(isLegacyToken('legacy', ''), false);
|
|
41
|
+
assert.equal(isLegacyToken(null, 'legacy'), false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('isAuthorizedToken accepts the configured legacy token without consulting pairings', () => {
|
|
45
|
+
let called = false;
|
|
46
|
+
assert.equal(isAuthorizedToken('legacy', 'legacy', () => {
|
|
47
|
+
called = true;
|
|
48
|
+
return false;
|
|
49
|
+
}), true);
|
|
50
|
+
assert.equal(called, false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('isAuthorizedToken falls back to pair-token validation for non-legacy tokens', () => {
|
|
54
|
+
const calls: Array<{ token: string; touch: boolean | undefined }> = [];
|
|
55
|
+
const authorized = isAuthorizedToken('pair-token', 'legacy', (token, touch) => {
|
|
56
|
+
calls.push({ token, touch });
|
|
57
|
+
return token === 'pair-token';
|
|
58
|
+
}, false);
|
|
59
|
+
|
|
60
|
+
assert.equal(authorized, true);
|
|
61
|
+
assert.deepEqual(calls, [{ token: 'pair-token', touch: false }]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('isAuthorizedToken rejects missing or invalid tokens', () => {
|
|
65
|
+
assert.equal(isAuthorizedToken(null, 'legacy', () => true), false);
|
|
66
|
+
assert.equal(isAuthorizedToken('wrong', 'legacy', () => false), false);
|
|
67
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join, resolve } from 'path';
|
|
5
|
+
import { expandPath, normalizeFileRequestPath, resolveFileRequestPath } from './utils.js';
|
|
6
|
+
|
|
7
|
+
test('expandPath: returns absolute paths unchanged', () => {
|
|
8
|
+
assert.equal(expandPath('/usr/local/bin'), '/usr/local/bin');
|
|
9
|
+
assert.equal(expandPath('/Volumes/T5/project'), '/Volumes/T5/project');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('expandPath: expands ~ to home directory', () => {
|
|
13
|
+
const home = homedir();
|
|
14
|
+
assert.equal(expandPath('~/projects'), join(home, 'projects'));
|
|
15
|
+
assert.equal(expandPath('~/a/b/c'), join(home, 'a/b/c'));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('expandPath: expands standalone ~', () => {
|
|
19
|
+
assert.equal(expandPath('~'), homedir());
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('expandPath: expands fullwidth ~ to home directory', () => {
|
|
23
|
+
const home = homedir();
|
|
24
|
+
assert.equal(expandPath('~/projects'), join(home, 'projects'));
|
|
25
|
+
assert.equal(expandPath('~'), homedir());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('expandPath: does not expand ~ in the middle of path', () => {
|
|
29
|
+
assert.equal(expandPath('/some/~/path'), '/some/~/path');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('expandPath: returns empty string unchanged', () => {
|
|
33
|
+
assert.equal(expandPath(''), '');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('expandPath: returns relative paths unchanged', () => {
|
|
37
|
+
assert.equal(expandPath('relative/path'), 'relative/path');
|
|
38
|
+
assert.equal(expandPath('Volumes/T5/project'), 'Volumes/T5/project');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('normalizeFileRequestPath: strips local file location suffixes', () => {
|
|
42
|
+
assert.equal(normalizeFileRequestPath('/tmp/hello.md#L12'), '/tmp/hello.md');
|
|
43
|
+
assert.equal(normalizeFileRequestPath('/tmp/hello.md:12:3'), '/tmp/hello.md');
|
|
44
|
+
assert.equal(normalizeFileRequestPath('./README.md:9'), './README.md');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('resolveFileRequestPath: keeps absolute paths unchanged', () => {
|
|
48
|
+
assert.equal(resolveFileRequestPath('/tmp/hello.md', '/repo'), '/tmp/hello.md');
|
|
49
|
+
assert.equal(resolveFileRequestPath('/tmp/hello.md#L12', '/repo'), '/tmp/hello.md');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('resolveFileRequestPath: resolves relative paths against cwd', () => {
|
|
53
|
+
assert.equal(resolveFileRequestPath('docs/plan.md', '/repo/project'), resolve('/repo/project', 'docs/plan.md'));
|
|
54
|
+
assert.equal(resolveFileRequestPath('./README.md', '/repo/project'), resolve('/repo/project', './README.md'));
|
|
55
|
+
assert.equal(resolveFileRequestPath('./README.md:12', '/repo/project'), resolve('/repo/project', './README.md'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('resolveFileRequestPath: expands tilde cwd before resolving', () => {
|
|
59
|
+
const home = homedir();
|
|
60
|
+
assert.equal(resolveFileRequestPath('notes/today.md', '~/workspace'), resolve(join(home, 'workspace'), 'notes/today.md'));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('resolveFileRequestPath: returns relative path unchanged when cwd is missing', () => {
|
|
64
|
+
assert.equal(resolveFileRequestPath('docs/plan.md'), 'docs/plan.md');
|
|
65
|
+
});
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isAbsolute, join, resolve } from 'path';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
|
|
4
|
+
export function expandPath(p: string): string {
|
|
5
|
+
// Handle both normal ~ and fullwidth ~
|
|
6
|
+
p = p.replace(/^~/, '~');
|
|
7
|
+
if (p.startsWith('~/') || p === '~') {
|
|
8
|
+
return join(homedir(), p.slice(2));
|
|
9
|
+
}
|
|
10
|
+
return p;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeFileRequestPath(path: string): string {
|
|
14
|
+
let normalized = path.trim();
|
|
15
|
+
if (!normalized) {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const hashIndex = normalized.indexOf('#');
|
|
20
|
+
if (hashIndex > 0) {
|
|
21
|
+
normalized = normalized.slice(0, hashIndex);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const locationMatch = normalized.match(/^(.*?)(:\d+(?::\d+)?)$/);
|
|
25
|
+
if (locationMatch) {
|
|
26
|
+
normalized = locationMatch[1] ?? normalized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveFileRequestPath(path: string, cwd?: string): string {
|
|
33
|
+
const expandedPath = expandPath(normalizeFileRequestPath(path));
|
|
34
|
+
if (!expandedPath) {
|
|
35
|
+
return expandedPath;
|
|
36
|
+
}
|
|
37
|
+
if (isAbsolute(expandedPath)) {
|
|
38
|
+
return expandedPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const expandedCwd = expandPath(cwd ?? '');
|
|
42
|
+
if (!expandedCwd) {
|
|
43
|
+
return expandedPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return resolve(expandedCwd, expandedPath);
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readRawDataAsText } from './ws-data.js';
|
|
4
|
+
|
|
5
|
+
test('readRawDataAsText returns string data unchanged', () => {
|
|
6
|
+
assert.equal(readRawDataAsText('{"ok":true}'), '{"ok":true}');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('readRawDataAsText decodes Buffer data', () => {
|
|
10
|
+
assert.equal(readRawDataAsText(Buffer.from('{"ok":true}', 'utf8')), '{"ok":true}');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('readRawDataAsText decodes ArrayBuffer data', () => {
|
|
14
|
+
const data = new TextEncoder().encode('{"ok":true}').buffer;
|
|
15
|
+
assert.equal(readRawDataAsText(data), '{"ok":true}');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('readRawDataAsText decodes Buffer array payloads', () => {
|
|
19
|
+
assert.equal(readRawDataAsText([Buffer.from('{"ok":'), Buffer.from('true}')]), '{"ok":true}');
|
|
20
|
+
});
|
package/src/ws-data.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type WebSocketRawTextData = string | Buffer | ArrayBuffer | ArrayBufferView | Buffer[];
|
|
2
|
+
|
|
3
|
+
export function readRawDataAsText(data: WebSocketRawTextData): string {
|
|
4
|
+
if (typeof data === 'string') {
|
|
5
|
+
return data;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Buffer.isBuffer(data)) {
|
|
9
|
+
return data.toString('utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (Array.isArray(data)) {
|
|
13
|
+
return Buffer.concat(data).toString('utf8');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (data instanceof ArrayBuffer) {
|
|
17
|
+
return Buffer.from(data).toString('utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (ArrayBuffer.isView(data)) {
|
|
21
|
+
const view = data as ArrayBufferView;
|
|
22
|
+
return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString('utf8');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return String(data);
|
|
26
|
+
}
|
package/tsconfig.json
ADDED