claude-code-inspector 0.1.0
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/.github/workflows/ci.yml +31 -0
- package/.github/workflows/publish-npm.yml +33 -0
- package/README.md +199 -0
- package/app/api/events/route.ts +35 -0
- package/app/api/proxy/route.ts +42 -0
- package/app/api/requests/[id]/route.ts +82 -0
- package/app/api/requests/export/route.ts +124 -0
- package/app/api/requests/route.ts +32 -0
- package/app/dashboard/page.tsx +562 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +26 -0
- package/app/layout.tsx +34 -0
- package/app/page.tsx +5 -0
- package/app/v1/messages/route.ts +30 -0
- package/components/JsonModal.tsx +155 -0
- package/components/JsonViewer.tsx +185 -0
- package/dev.sh +19 -0
- package/eslint.config.mjs +18 -0
- package/lib/env.ts +52 -0
- package/lib/pricing.ts +131 -0
- package/lib/proxy/forwarder.test.ts +171 -0
- package/lib/proxy/forwarder.ts +96 -0
- package/lib/proxy/handlers.test.ts +276 -0
- package/lib/proxy/handlers.ts +340 -0
- package/lib/proxy/ws-server.ts +76 -0
- package/lib/recorder/index.ts +152 -0
- package/lib/recorder/schema.ts +41 -0
- package/lib/recorder/store.ts +141 -0
- package/next.config.ts +59 -0
- package/package.json +42 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/server.ts +64 -0
- package/tsconfig.json +34 -0
- package/tsconfig.server.json +11 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { CREATE_TABLES } from './schema';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
export interface RequestLog {
|
|
7
|
+
id: string;
|
|
8
|
+
session_id?: string | null;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
method: string;
|
|
11
|
+
request_headers?: string | null;
|
|
12
|
+
request_body?: string | null;
|
|
13
|
+
response_status?: number | null;
|
|
14
|
+
response_headers?: string | null;
|
|
15
|
+
response_body?: string | null;
|
|
16
|
+
streaming_events?: string | null;
|
|
17
|
+
input_tokens: number;
|
|
18
|
+
output_tokens: number;
|
|
19
|
+
cache_read_tokens: number;
|
|
20
|
+
cache_creation_tokens: number;
|
|
21
|
+
latency_ms?: number | null;
|
|
22
|
+
first_token_ms?: number | null;
|
|
23
|
+
status_code?: number | null;
|
|
24
|
+
error_message?: string | null;
|
|
25
|
+
model?: string | null;
|
|
26
|
+
cost_usd?: number | null;
|
|
27
|
+
created_at: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RequestStore {
|
|
31
|
+
private db: ReturnType<typeof Database>;
|
|
32
|
+
|
|
33
|
+
constructor(dbPath: string) {
|
|
34
|
+
const fullPath = path.resolve(dbPath);
|
|
35
|
+
// 确保数据库目录存在
|
|
36
|
+
const dir = path.dirname(fullPath);
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
this.db = new Database(fullPath);
|
|
41
|
+
this.db.pragma('journal_mode = WAL');
|
|
42
|
+
this.initialize();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private initialize() {
|
|
46
|
+
this.db.exec(CREATE_TABLES);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public insert(log: RequestLog): void {
|
|
50
|
+
const stmt = this.db.prepare(`
|
|
51
|
+
INSERT INTO request_logs (
|
|
52
|
+
id, session_id, endpoint, method,
|
|
53
|
+
request_headers, request_body,
|
|
54
|
+
response_status, response_headers, response_body,
|
|
55
|
+
streaming_events,
|
|
56
|
+
input_tokens, output_tokens,
|
|
57
|
+
cache_read_tokens, cache_creation_tokens,
|
|
58
|
+
latency_ms, first_token_ms, status_code, error_message,
|
|
59
|
+
model, cost_usd,
|
|
60
|
+
created_at
|
|
61
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
stmt.run(
|
|
65
|
+
log.id,
|
|
66
|
+
log.session_id || null,
|
|
67
|
+
log.endpoint,
|
|
68
|
+
log.method,
|
|
69
|
+
log.request_headers ? JSON.stringify(JSON.parse(log.request_headers)) : null,
|
|
70
|
+
log.request_body ? JSON.stringify(JSON.parse(log.request_body)) : null,
|
|
71
|
+
log.response_status || null,
|
|
72
|
+
log.response_headers ? JSON.stringify(JSON.parse(log.response_headers)) : null,
|
|
73
|
+
log.response_body ? JSON.stringify(JSON.parse(log.response_body)) : null,
|
|
74
|
+
log.streaming_events ? JSON.stringify(JSON.parse(log.streaming_events)) : null,
|
|
75
|
+
log.input_tokens,
|
|
76
|
+
log.output_tokens,
|
|
77
|
+
log.cache_read_tokens,
|
|
78
|
+
log.cache_creation_tokens,
|
|
79
|
+
log.latency_ms || null,
|
|
80
|
+
log.first_token_ms || null,
|
|
81
|
+
log.status_code || null,
|
|
82
|
+
log.error_message || null,
|
|
83
|
+
log.model || null,
|
|
84
|
+
log.cost_usd || 0,
|
|
85
|
+
log.created_at
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public findById(id: string): RequestLog | undefined {
|
|
90
|
+
const stmt = this.db.prepare('SELECT * FROM request_logs WHERE id = ?');
|
|
91
|
+
return stmt.get(id) as RequestLog | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public findAll(limit: number = 100): RequestLog[] {
|
|
95
|
+
const stmt = this.db.prepare(`
|
|
96
|
+
SELECT * FROM request_logs
|
|
97
|
+
ORDER BY created_at DESC
|
|
98
|
+
LIMIT ?
|
|
99
|
+
`);
|
|
100
|
+
return stmt.all(limit) as RequestLog[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public findBySessionId(sessionId: string): RequestLog[] {
|
|
104
|
+
const stmt = this.db.prepare(`
|
|
105
|
+
SELECT * FROM request_logs
|
|
106
|
+
WHERE session_id = ?
|
|
107
|
+
ORDER BY created_at DESC
|
|
108
|
+
`);
|
|
109
|
+
return stmt.all(sessionId) as RequestLog[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public getRecentRequests(limit: number = 50): RequestLog[] {
|
|
113
|
+
const stmt = this.db.prepare(`
|
|
114
|
+
SELECT * FROM request_logs
|
|
115
|
+
ORDER BY created_at DESC
|
|
116
|
+
LIMIT ?
|
|
117
|
+
`);
|
|
118
|
+
return stmt.all(limit) as RequestLog[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public getTotalStats() {
|
|
122
|
+
const stmt = this.db.prepare(`
|
|
123
|
+
SELECT
|
|
124
|
+
COUNT(*) as total_requests,
|
|
125
|
+
COALESCE(SUM(input_tokens), 0) as total_input_tokens,
|
|
126
|
+
COALESCE(SUM(output_tokens), 0) as total_output_tokens,
|
|
127
|
+
COALESCE(AVG(latency_ms), 0) as avg_latency_ms
|
|
128
|
+
FROM request_logs
|
|
129
|
+
`);
|
|
130
|
+
return stmt.get() as {
|
|
131
|
+
total_requests: number;
|
|
132
|
+
total_input_tokens: number;
|
|
133
|
+
total_output_tokens: number;
|
|
134
|
+
avg_latency_ms: number;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public close() {
|
|
139
|
+
this.db.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
// 禁用 HMR 的自动刷新,防止 WebSocket 错误时页面重新加载
|
|
5
|
+
reactStrictMode: false,
|
|
6
|
+
// 开发服务器配置 - 防止页面被 unloaded
|
|
7
|
+
onDemandEntries: {
|
|
8
|
+
// 页面在不活动后保持活跃的毫秒数(设置为最大值)
|
|
9
|
+
maxInactiveAge: 24 * 60 * 60 * 1000, // 24 小时
|
|
10
|
+
// 保持多少个页面活跃
|
|
11
|
+
pagesBufferLength: 10,
|
|
12
|
+
},
|
|
13
|
+
// 完全禁用 Fast Refresh
|
|
14
|
+
compiler: {
|
|
15
|
+
reactRemoveProperties: true,
|
|
16
|
+
},
|
|
17
|
+
// 空的 turbopack 配置,避免警告
|
|
18
|
+
turbopack: {},
|
|
19
|
+
// webpack 配置
|
|
20
|
+
webpack: (config, { dev, isServer }) => {
|
|
21
|
+
if (dev) {
|
|
22
|
+
// 禁用 HMR 插件
|
|
23
|
+
config.plugins = config.plugins.filter(
|
|
24
|
+
(plugin: any) => plugin?.constructor?.name !== 'HotModuleReplacementPlugin'
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// 禁用所有文件监听
|
|
28
|
+
config.watchOptions = {
|
|
29
|
+
// 超长聚合延迟,等效于禁用
|
|
30
|
+
aggregateTimeout: 300000, // 5 分钟
|
|
31
|
+
// 忽略所有非源代码文件
|
|
32
|
+
ignored: [
|
|
33
|
+
'**/db/**',
|
|
34
|
+
'**/*.sqlite',
|
|
35
|
+
'**/*.sqlite-wal',
|
|
36
|
+
'**/*.sqlite-shm',
|
|
37
|
+
'**/node_modules/**',
|
|
38
|
+
'**/.git/**',
|
|
39
|
+
'**/*.log',
|
|
40
|
+
'**/dist/**',
|
|
41
|
+
'**/build/**',
|
|
42
|
+
'**/*.json', // 忽略 JSON 文件
|
|
43
|
+
],
|
|
44
|
+
// 禁用轮询
|
|
45
|
+
poll: false,
|
|
46
|
+
// 完全禁用跟随符号链接
|
|
47
|
+
followSymlinks: false,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// 禁用性能监听
|
|
51
|
+
config.performance = {
|
|
52
|
+
hints: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default nextConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-inspector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"registry": "https://registry.npmjs.org"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "tsx server.ts",
|
|
10
|
+
"build": "next build",
|
|
11
|
+
"start": "tsx server.ts",
|
|
12
|
+
"start:next": "next start",
|
|
13
|
+
"lint": "eslint",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"axios": "^1.13.6",
|
|
19
|
+
"better-sqlite3": "^12.8.0",
|
|
20
|
+
"next": "16.1.6",
|
|
21
|
+
"react": "19.2.3",
|
|
22
|
+
"react-dom": "19.2.3",
|
|
23
|
+
"uuid": "^13.0.0",
|
|
24
|
+
"ws": "^8.19.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@tailwindcss/postcss": "^4",
|
|
28
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
29
|
+
"@types/node": "^20",
|
|
30
|
+
"@types/react": "^19",
|
|
31
|
+
"@types/react-dom": "^19",
|
|
32
|
+
"@types/uuid": "^10.0.0",
|
|
33
|
+
"@types/ws": "^8.18.1",
|
|
34
|
+
"eslint": "^9",
|
|
35
|
+
"eslint-config-next": "16.1.6",
|
|
36
|
+
"tailwindcss": "^4",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"tsx": "^4.21.0",
|
|
39
|
+
"typescript": "^5",
|
|
40
|
+
"vitest": "^4.0.18"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/next.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
package/server.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import next from 'next';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import { parse } from 'url';
|
|
5
|
+
import { setWssInstance } from './lib/proxy/ws-server';
|
|
6
|
+
import { initEnv } from './lib/env';
|
|
7
|
+
|
|
8
|
+
const port = parseInt(process.env.PORT || '3000', 10);
|
|
9
|
+
const dev = process.env.NODE_ENV !== 'production';
|
|
10
|
+
|
|
11
|
+
// 初始化环境变量
|
|
12
|
+
initEnv();
|
|
13
|
+
|
|
14
|
+
// 禁用 Turbopack,使用 Webpack
|
|
15
|
+
if (dev) {
|
|
16
|
+
// 正确禁用 Turbopack:删除环境变量或使用空字符串
|
|
17
|
+
// 注意:'0' 在 JavaScript 中是 truthy,会导致 if (process.env.TURBOPACK) 判断为 true
|
|
18
|
+
delete process.env.TURBOPACK;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const app = next({ dev });
|
|
22
|
+
const handle = app.getRequestHandler();
|
|
23
|
+
|
|
24
|
+
app.prepare().then(() => {
|
|
25
|
+
const upgradeHandler = app.getUpgradeHandler();
|
|
26
|
+
const server = createServer((req, res) => {
|
|
27
|
+
const parsedUrl = parse(req.url!, true);
|
|
28
|
+
handle(req, res, parsedUrl);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 注册 WebSocket upgrade 处理器,用于处理 Next.js HMR 连接
|
|
32
|
+
server.on('upgrade', async (req, socket, head) => {
|
|
33
|
+
console.log('[SERVER] Upgrade request received for URL:', req.url);
|
|
34
|
+
try {
|
|
35
|
+
await upgradeHandler(req, socket, head);
|
|
36
|
+
console.log('[SERVER] Upgrade handler completed successfully');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[SERVER] Upgrade handler error:', error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// WebSocket 服务器
|
|
43
|
+
const wss = new WebSocketServer({ server, path: '/api/ws' });
|
|
44
|
+
|
|
45
|
+
// 注册 WebSocket 服务器实例
|
|
46
|
+
setWssInstance(wss);
|
|
47
|
+
|
|
48
|
+
wss.on('connection', (ws) => {
|
|
49
|
+
console.log('Client connected. Total clients:', wss.clients.size);
|
|
50
|
+
|
|
51
|
+
ws.on('close', () => {
|
|
52
|
+
console.log('Client disconnected. Total clients:', wss.clients.size);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
ws.on('error', (error) => {
|
|
56
|
+
console.error('WebSocket error:', error);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
server.listen(port, () => {
|
|
61
|
+
console.log(`> Ready on http://localhost:${port}`);
|
|
62
|
+
console.log(`> WebSocket available on ws://localhost:${port}/api/ws`);
|
|
63
|
+
});
|
|
64
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
package/vitest.config.ts
ADDED