claude-code-monitor 1.1.1 → 1.1.2
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/dist/hooks/useServer.d.ts.map +1 -1
- package/dist/hooks/useServer.js +14 -7
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +18 -3
- package/dist/utils/focus.d.ts +1 -1
- package/dist/utils/focus.d.ts.map +1 -1
- package/dist/utils/focus.js +8 -6
- package/package.json +1 -1
- package/public/index.html +35 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useServer.d.ts","sourceRoot":"","sources":["../../src/hooks/useServer.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAID,wBAAgB,SAAS,CAAC,IAAI,SAAe,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"useServer.d.ts","sourceRoot":"","sources":["../../src/hooks/useServer.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAID,wBAAgB,SAAS,CAAC,IAAI,SAAe,GAAG,eAAe,CA6C9D"}
|
package/dist/hooks/useServer.js
CHANGED
|
@@ -8,17 +8,24 @@ export function useServer(port = DEFAULT_PORT) {
|
|
|
8
8
|
const [loading, setLoading] = useState(true);
|
|
9
9
|
const [error, setError] = useState(null);
|
|
10
10
|
useEffect(() => {
|
|
11
|
-
|
|
11
|
+
// Use a ref-like object to track server across async boundaries
|
|
12
|
+
// This prevents race condition where cleanup runs before async completes
|
|
13
|
+
const serverRef = { current: null };
|
|
12
14
|
let isMounted = true;
|
|
13
15
|
async function startServer() {
|
|
14
16
|
try {
|
|
15
|
-
|
|
17
|
+
const info = await createMobileServer(port);
|
|
16
18
|
if (isMounted) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
serverRef.current = info;
|
|
20
|
+
setUrl(info.url);
|
|
21
|
+
setQrCode(info.qrCode);
|
|
22
|
+
setActualPort(info.port);
|
|
20
23
|
setLoading(false);
|
|
21
24
|
}
|
|
25
|
+
else {
|
|
26
|
+
// Component unmounted during async operation - stop server immediately
|
|
27
|
+
info.stop();
|
|
28
|
+
}
|
|
22
29
|
}
|
|
23
30
|
catch (err) {
|
|
24
31
|
if (isMounted) {
|
|
@@ -30,8 +37,8 @@ export function useServer(port = DEFAULT_PORT) {
|
|
|
30
37
|
startServer();
|
|
31
38
|
return () => {
|
|
32
39
|
isMounted = false;
|
|
33
|
-
if (
|
|
34
|
-
|
|
40
|
+
if (serverRef.current) {
|
|
41
|
+
serverRef.current.stop();
|
|
35
42
|
}
|
|
36
43
|
};
|
|
37
44
|
}, [port]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAyOA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,wBAAgB,UAAU,IAAI,MAAM,CAOnC;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5D;AAkGD,wBAAsB,kBAAkB,CAAC,IAAI,SAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAoBjF;AAGD,wBAAsB,WAAW,CAAC,IAAI,SAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BpE"}
|
package/dist/server/index.js
CHANGED
|
@@ -21,6 +21,7 @@ function isPortAvailable(port) {
|
|
|
21
21
|
return new Promise((resolve) => {
|
|
22
22
|
const server = createNetServer();
|
|
23
23
|
server.once('error', () => {
|
|
24
|
+
server.close(); // Ensure server is closed on error
|
|
24
25
|
resolve(false);
|
|
25
26
|
});
|
|
26
27
|
server.once('listening', () => {
|
|
@@ -175,6 +176,11 @@ function setupWebSocketHandlers(wss, validToken) {
|
|
|
175
176
|
}
|
|
176
177
|
sendSessionsToClient(ws);
|
|
177
178
|
ws.on('message', (data) => handleWebSocketMessage(ws, data));
|
|
179
|
+
// Handle client connection errors to prevent process crashes
|
|
180
|
+
ws.on('error', (error) => {
|
|
181
|
+
// Log error but don't crash - client disconnections are expected
|
|
182
|
+
console.error('WebSocket client error:', error.message);
|
|
183
|
+
});
|
|
178
184
|
});
|
|
179
185
|
}
|
|
180
186
|
export function getLocalIP() {
|
|
@@ -260,9 +266,15 @@ function createServerComponents(token) {
|
|
|
260
266
|
}
|
|
261
267
|
/**
|
|
262
268
|
* Stop all server components.
|
|
269
|
+
* Terminates all WebSocket clients before closing to prevent hanging.
|
|
263
270
|
*/
|
|
264
271
|
function stopServerComponents({ watcher, wss, server }) {
|
|
265
|
-
watcher
|
|
272
|
+
// Close file watcher (async but we don't wait - acceptable for shutdown)
|
|
273
|
+
void watcher.close();
|
|
274
|
+
// Terminate all WebSocket clients before closing server
|
|
275
|
+
for (const client of wss.clients) {
|
|
276
|
+
client.terminate();
|
|
277
|
+
}
|
|
266
278
|
wss.close();
|
|
267
279
|
server.close();
|
|
268
280
|
}
|
|
@@ -301,9 +313,12 @@ export async function startServer(port = DEFAULT_PORT) {
|
|
|
301
313
|
qrcode.generate(url, { small: true });
|
|
302
314
|
console.log('\n Press Ctrl+C to stop the server.\n');
|
|
303
315
|
});
|
|
304
|
-
|
|
316
|
+
// Graceful shutdown handler for both SIGINT (Ctrl+C) and SIGTERM (Docker/K8s)
|
|
317
|
+
const shutdown = () => {
|
|
305
318
|
console.log('\n Shutting down...');
|
|
306
319
|
stopServerComponents(components);
|
|
307
320
|
process.exit(0);
|
|
308
|
-
}
|
|
321
|
+
};
|
|
322
|
+
process.on('SIGINT', shutdown);
|
|
323
|
+
process.on('SIGTERM', shutdown);
|
|
309
324
|
}
|
package/dist/utils/focus.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sanitize a string for safe use in AppleScript.
|
|
3
|
-
* Escapes backslashes, double quotes,
|
|
3
|
+
* Escapes backslashes, double quotes, control characters, and AppleScript special chars.
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
6
6
|
export declare function sanitizeForAppleScript(str: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../../src/utils/focus.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"focus.d.ts","sourceRoot":"","sources":["../../src/utils/focus.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS1D;AAWD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAgED,wBAAgB,OAAO,IAAI,OAAO,CAEjC;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASjD;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD"}
|
package/dist/utils/focus.js
CHANGED
|
@@ -2,16 +2,18 @@ import { executeAppleScript } from './applescript.js';
|
|
|
2
2
|
import { executeWithTerminalFallback } from './terminal-strategy.js';
|
|
3
3
|
/**
|
|
4
4
|
* Sanitize a string for safe use in AppleScript.
|
|
5
|
-
* Escapes backslashes, double quotes,
|
|
5
|
+
* Escapes backslashes, double quotes, control characters, and AppleScript special chars.
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
export function sanitizeForAppleScript(str) {
|
|
9
9
|
return str
|
|
10
|
-
.replace(/\\/g, '\\\\')
|
|
11
|
-
.replace(/"/g, '\\"')
|
|
12
|
-
.replace(/\n/g, '\\n')
|
|
13
|
-
.replace(/\r/g, '\\r')
|
|
14
|
-
.replace(/\t/g, '\\t')
|
|
10
|
+
.replace(/\\/g, '\\\\') // Backslash (must be first)
|
|
11
|
+
.replace(/"/g, '\\"') // Double quote
|
|
12
|
+
.replace(/\n/g, '\\n') // Newline
|
|
13
|
+
.replace(/\r/g, '\\r') // Carriage return
|
|
14
|
+
.replace(/\t/g, '\\t') // Tab
|
|
15
|
+
.replace(/\$/g, '\\$') // Dollar sign (variable reference in some contexts)
|
|
16
|
+
.replace(/`/g, '\\`'); // Backtick
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* TTY path pattern for validation.
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -890,6 +890,14 @@
|
|
|
890
890
|
let pendingSend = null;
|
|
891
891
|
let sessionsData = [];
|
|
892
892
|
|
|
893
|
+
// Event delegation for card clicks (safer than inline onclick)
|
|
894
|
+
$sessions.addEventListener('click', (e) => {
|
|
895
|
+
const card = e.target.closest('.card');
|
|
896
|
+
if (card && card.dataset.sessionId) {
|
|
897
|
+
openModal(card.dataset.sessionId);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
|
|
893
901
|
const STATUS = {
|
|
894
902
|
running: 'Running',
|
|
895
903
|
waiting_input: 'Waiting for Input',
|
|
@@ -960,6 +968,23 @@
|
|
|
960
968
|
return clean.slice(0, maxLen) + '...';
|
|
961
969
|
}
|
|
962
970
|
|
|
971
|
+
/**
|
|
972
|
+
* Escape HTML special characters to prevent XSS attacks.
|
|
973
|
+
*/
|
|
974
|
+
function escapeHtml(text) {
|
|
975
|
+
if (!text) return '';
|
|
976
|
+
const div = document.createElement('div');
|
|
977
|
+
div.textContent = text;
|
|
978
|
+
return div.innerHTML;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Validate session ID format (alphanumeric, hyphens, underscores only).
|
|
983
|
+
*/
|
|
984
|
+
function isValidSessionId(id) {
|
|
985
|
+
return /^[a-zA-Z0-9_-]+$/.test(id);
|
|
986
|
+
}
|
|
987
|
+
|
|
963
988
|
function renderMarkdown(markdownText) {
|
|
964
989
|
if (!markdownText) return '<span class="modal-message-empty">No messages yet</span>';
|
|
965
990
|
if (typeof marked !== 'undefined') {
|
|
@@ -1118,13 +1143,18 @@
|
|
|
1118
1143
|
}
|
|
1119
1144
|
|
|
1120
1145
|
$sessions.innerHTML = list.map(session => {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1146
|
+
// Skip sessions with invalid IDs to prevent XSS
|
|
1147
|
+
if (!isValidSessionId(session.session_id)) return '';
|
|
1148
|
+
|
|
1149
|
+
// Escape all user-provided content to prevent XSS
|
|
1150
|
+
const dirName = escapeHtml(getDirName(session.cwd));
|
|
1151
|
+
const shortPath = escapeHtml(formatPath(session.cwd));
|
|
1152
|
+
const statusLabel = escapeHtml(STATUS[session.status]);
|
|
1153
|
+
const msgPreview = escapeHtml(truncateMessage(session.lastMessage));
|
|
1154
|
+
const safeStatus = escapeHtml(session.status);
|
|
1125
1155
|
|
|
1126
1156
|
return `
|
|
1127
|
-
<div class="card ${
|
|
1157
|
+
<div class="card ${safeStatus}" data-session-id="${escapeHtml(session.session_id)}">
|
|
1128
1158
|
<div class="card-status">
|
|
1129
1159
|
<span class="card-status-dot"></span>
|
|
1130
1160
|
<span>${statusLabel}</span>
|