cc-viewer 1.4.4 → 1.4.5
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.
|
@@ -14,7 +14,7 @@ var gT=Object.defineProperty;var vT=(e,t,n)=>t in e?gT(e,t,{enumerable:!0,config
|
|
|
14
14
|
*
|
|
15
15
|
* This source code is licensed under the MIT license found in the
|
|
16
16
|
* LICENSE file in the root directory of this source tree.
|
|
17
|
-
*/var ZS;function bT(){if(ZS)return Bc;ZS=1;var e=uy(),t=Symbol.for("react.element"),n=Symbol.for("react.fragment"),r=Object.prototype.hasOwnProperty,i=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,o={key:!0,ref:!0,__self:!0,__source:!0};function a(c,u,d){var f,m={},g=null,v=null;d!==void 0&&(g=""+d),u.key!==void 0&&(g=""+u.key),u.ref!==void 0&&(v=u.ref);for(f in u)r.call(u,f)&&!o.hasOwnProperty(f)&&(m[f]=u[f]);if(c&&c.defaultProps)for(f in u=c.defaultProps,u)m[f]===void 0&&(m[f]=u[f]);return{$$typeof:t,type:c,key:g,ref:v,props:m,_owner:i.current}}return Bc.Fragment=n,Bc.jsx=a,Bc.jsxs=a,Bc}var e1;function _T(){return e1||(e1=1,Qp.exports=bT()),Qp.exports}var x=_T(),h=uy();const J=ju(h),Nf=y2({__proto__:null,default:J},[h]);var nh={},eg={exports:{}},ki={},tg={exports:{}},ng={};/**
|
|
17
|
+
*/var ZS;function bT(){if(ZS)return Bc;ZS=1;var e=uy(),t=Symbol.for("react.element"),n=Symbol.for("react.fragment"),r=Object.prototype.hasOwnProperty,i=e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,o={key:!0,ref:!0,__self:!0,__source:!0};function a(c,u,d){var f,m={},g=null,v=null;d!==void 0&&(g=""+d),u.key!==void 0&&(g=""+u.key),u.ref!==void 0&&(v=u.ref);for(f in u)r.call(u,f)&&!o.hasOwnProperty(f)&&(m[f]=u[f]);if(c&&c.defaultProps)for(f in u=c.defaultProps,u)m[f]===void 0&&(m[f]=u[f]);return{$$typeof:t,type:c,key:g,ref:v,props:m,_owner:i.current}}return Bc.Fragment=n,Bc.jsx=a,Bc.jsxs=a,Bc}var e1;function _T(){return e1||(e1=1,Qp.exports=bT()),Qp.exports}var x=_T();(function(){let e=null;function t(){return e||(e=new URLSearchParams(window.location.search).get("token")||"",e)}function n(a){const c=t();if(!c)return a;if(a.startsWith("/")){const u=a.includes("?")?"&":"?";return`${a}${u}token=${encodeURIComponent(c)}`}try{const u=new URL(a);if(u.origin===window.location.origin)return u.searchParams.set("token",c),u.toString()}catch{}return a}const r=window.fetch;window.fetch=function(a,c){return r(n(a),c)};const i=window.EventSource;window.EventSource=function(a,c){return new i(n(a),c)};const o=window.WebSocket;window.WebSocket=function(a,c){return new o(n(a),c)},window.WebSocket.prototype=o.prototype,window.WebSocket.CONNECTING=o.CONNECTING,window.WebSocket.OPEN=o.OPEN,window.WebSocket.CLOSING=o.CLOSING,window.WebSocket.CLOSED=o.CLOSED})();var h=uy();const J=ju(h),Nf=y2({__proto__:null,default:J},[h]);var nh={},eg={exports:{}},ki={},tg={exports:{}},ng={};/**
|
|
18
18
|
* @license React
|
|
19
19
|
* scheduler.production.min.js
|
|
20
20
|
*
|
package/dist/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Claude Code Viewer</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico?v=1">
|
|
8
8
|
<link rel="shortcut icon" href="/favicon.ico?v=1">
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-Usb6v9iJ.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/index-BDl542y5.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
+
import { createConnection } from 'node:net';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
2
4
|
import { readFileSync, writeFileSync, existsSync, watchFile, unwatchFile, statSync, readdirSync, renameSync, unlinkSync, openSync, readSync, closeSync } from 'node:fs';
|
|
3
5
|
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { dirname, join, extname } from 'node:path';
|
|
@@ -46,7 +48,10 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
46
48
|
const __dirname = dirname(__filename);
|
|
47
49
|
const START_PORT = 7008;
|
|
48
50
|
const MAX_PORT = 7099;
|
|
49
|
-
const HOST = '
|
|
51
|
+
const HOST = '0.0.0.0';
|
|
52
|
+
|
|
53
|
+
// 局域网访问 token(本地 127.0.0.1 免验证)
|
|
54
|
+
const ACCESS_TOKEN = randomBytes(16).toString('hex');
|
|
50
55
|
|
|
51
56
|
let clients = [];
|
|
52
57
|
let server;
|
|
@@ -180,7 +185,14 @@ function startWatching() {
|
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
function handleRequest(req, res) {
|
|
183
|
-
const
|
|
188
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
189
|
+
const url = parsedUrl.pathname;
|
|
190
|
+
const method = req.method;
|
|
191
|
+
|
|
192
|
+
// WebSocket 路径不处理,交给 upgrade 事件
|
|
193
|
+
if (url === '/ws/terminal') {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
184
196
|
|
|
185
197
|
// CORS headers
|
|
186
198
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
@@ -193,6 +205,19 @@ function handleRequest(req, res) {
|
|
|
193
205
|
return;
|
|
194
206
|
}
|
|
195
207
|
|
|
208
|
+
// 局域网访问 token 验证(本地 127.0.0.1 / ::1 免验证,静态资源免验证)
|
|
209
|
+
const remoteIp = req.socket.remoteAddress;
|
|
210
|
+
const isLocal = remoteIp === '127.0.0.1' || remoteIp === '::1' || remoteIp === '::ffff:127.0.0.1';
|
|
211
|
+
const isStaticAsset = url.startsWith('/assets/') || url === '/favicon.ico';
|
|
212
|
+
if (!isLocal && !isStaticAsset) {
|
|
213
|
+
const urlToken = parsedUrl.searchParams.get('token');
|
|
214
|
+
if (urlToken !== ACCESS_TOKEN) {
|
|
215
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
216
|
+
res.end(JSON.stringify({ error: 'Forbidden: invalid token' }));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
196
221
|
// User preferences API
|
|
197
222
|
if (url === '/api/preferences' && method === 'GET') {
|
|
198
223
|
let prefs = {};
|
|
@@ -519,7 +544,7 @@ function handleRequest(req, res) {
|
|
|
519
544
|
if (localIp !== '127.0.0.1') break;
|
|
520
545
|
}
|
|
521
546
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
522
|
-
res.end(JSON.stringify({ url: `http://${localIp}:${actualPort}` }));
|
|
547
|
+
res.end(JSON.stringify({ url: `http://${localIp}:${actualPort}?token=${ACCESS_TOKEN}` }));
|
|
523
548
|
return;
|
|
524
549
|
}
|
|
525
550
|
|
|
@@ -719,38 +744,48 @@ export async function startViewer() {
|
|
|
719
744
|
return;
|
|
720
745
|
}
|
|
721
746
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const url = `http://${HOST}:${port}`;
|
|
728
|
-
console.error(t('server.started', { host: HOST, port }));
|
|
729
|
-
// v2.0.69 之前的版本会清空控制台,自动打开浏览器确保用户能看到界面
|
|
730
|
-
try {
|
|
731
|
-
const ccPkgPath = join(__dirname, '..', '@anthropic-ai', 'claude-code', 'package.json');
|
|
732
|
-
const ccVer = JSON.parse(readFileSync(ccPkgPath, 'utf-8')).version;
|
|
733
|
-
const [maj, min, pat] = ccVer.split('.').map(Number);
|
|
734
|
-
if (maj < 2 || (maj === 2 && min === 0 && pat < 69)) {
|
|
735
|
-
const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
|
|
736
|
-
execSync(`${cmd} ${url}`, { stdio: 'ignore', timeout: 5000 });
|
|
737
|
-
}
|
|
738
|
-
} catch { }
|
|
739
|
-
startWatching();
|
|
740
|
-
startStatsWorker();
|
|
741
|
-
// CLI 模式下启动 WebSocket 服务
|
|
742
|
-
if (isCliMode) {
|
|
743
|
-
setupTerminalWebSocket(currentServer);
|
|
744
|
-
}
|
|
745
|
-
resolve(server);
|
|
747
|
+
// 先检测 127.0.0.1:port 是否已被占用(避免 0.0.0.0 和 127.0.0.1 绑定不冲突的问题)
|
|
748
|
+
const probe = createConnection({ host: '127.0.0.1', port });
|
|
749
|
+
probe.on('connect', () => {
|
|
750
|
+
probe.destroy();
|
|
751
|
+
tryListen(port + 1); // 端口已被占用,尝试下一个
|
|
746
752
|
});
|
|
753
|
+
probe.on('error', () => {
|
|
754
|
+
probe.destroy();
|
|
755
|
+
// 端口空闲,绑定 0.0.0.0
|
|
756
|
+
const currentServer = createServer(handleRequest);
|
|
757
|
+
|
|
758
|
+
currentServer.listen(port, HOST, () => {
|
|
759
|
+
server = currentServer;
|
|
760
|
+
actualPort = port;
|
|
761
|
+
const url = `http://${HOST}:${port}`;
|
|
762
|
+
console.error(t('server.started', { host: HOST, port }));
|
|
763
|
+
// v2.0.69 之前的版本会清空控制台,自动打开浏览器确保用户能看到界面
|
|
764
|
+
try {
|
|
765
|
+
const ccPkgPath = join(__dirname, '..', '@anthropic-ai', 'claude-code', 'package.json');
|
|
766
|
+
const ccVer = JSON.parse(readFileSync(ccPkgPath, 'utf-8')).version;
|
|
767
|
+
const [maj, min, pat] = ccVer.split('.').map(Number);
|
|
768
|
+
if (maj < 2 || (maj === 2 && min === 0 && pat < 69)) {
|
|
769
|
+
const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
|
|
770
|
+
execSync(`${cmd} ${url}`, { stdio: 'ignore', timeout: 5000 });
|
|
771
|
+
}
|
|
772
|
+
} catch { }
|
|
773
|
+
startWatching();
|
|
774
|
+
startStatsWorker();
|
|
775
|
+
// CLI 模式下启动 WebSocket 服务
|
|
776
|
+
if (isCliMode) {
|
|
777
|
+
setupTerminalWebSocket(currentServer);
|
|
778
|
+
}
|
|
779
|
+
resolve(server);
|
|
780
|
+
});
|
|
747
781
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
782
|
+
currentServer.on('error', (err) => {
|
|
783
|
+
if (err.code === 'EADDRINUSE') {
|
|
784
|
+
tryListen(port + 1);
|
|
785
|
+
} else {
|
|
786
|
+
reject(err);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
754
789
|
});
|
|
755
790
|
}
|
|
756
791
|
|
|
@@ -763,7 +798,18 @@ async function setupTerminalWebSocket(httpServer) {
|
|
|
763
798
|
const { WebSocketServer } = await import('ws');
|
|
764
799
|
const { writeToPty, resizePty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } = await import('./pty-manager.js');
|
|
765
800
|
|
|
766
|
-
const wss = new WebSocketServer({
|
|
801
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
802
|
+
|
|
803
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
804
|
+
const pathname = new URL(req.url, `http://${req.headers.host}`).pathname;
|
|
805
|
+
if (pathname === '/ws/terminal') {
|
|
806
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
807
|
+
wss.emit('connection', ws, req);
|
|
808
|
+
});
|
|
809
|
+
} else {
|
|
810
|
+
socket.destroy();
|
|
811
|
+
}
|
|
812
|
+
});
|
|
767
813
|
|
|
768
814
|
wss.on('connection', (ws) => {
|
|
769
815
|
// 发送当前 PTY 状态
|