claude-simple 0.2.0 → 0.2.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/client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { createApp, init } from './src/app.js';
2
+ import { createApp, init, hasBusyAgent } from './src/app.js';
3
+ import { startAutoUpdate } from './src/auto-update.js';
3
4
  import { WebSocket } from 'ws';
4
5
  import os from 'os';
5
6
  import crypto from 'crypto';
@@ -18,6 +19,10 @@ const { server } = createApp();
18
19
  await new Promise(resolve => server.listen(LOCAL_PORT, '127.0.0.1', resolve));
19
20
  console.log(`[client] Local app listening on 127.0.0.1:${LOCAL_PORT}`);
20
21
 
22
+ // Self-update from npm (default every 12h). Disable with AUTO_UPDATE=0.
23
+ // Requires global install + systemd Restart= (or equivalent) for full apply.
24
+ startAutoUpdate({ role: 'edge-client', isBusy: hasBusyAgent });
25
+
21
26
  // ─── Active WS tunnels: tunnelId → local WebSocket ───────────────────────────
22
27
  const tunnels = new Map();
23
28
 
@@ -25,6 +30,30 @@ const tunnels = new Map();
25
30
  let controlWs = null;
26
31
  let reconnectDelay = 1000;
27
32
 
33
+ /** Drop hop-by-hop / encoding headers so hub clients see the decoded body. */
34
+ function sanitizeProxyHeaders(headers) {
35
+ const out = {};
36
+ for (const [k, v] of Object.entries(headers || {})) {
37
+ const lk = k.toLowerCase();
38
+ if (
39
+ lk === 'content-encoding'
40
+ || lk === 'content-length'
41
+ || lk === 'transfer-encoding'
42
+ || lk === 'connection'
43
+ || lk === 'keep-alive'
44
+ || lk === 'proxy-authenticate'
45
+ || lk === 'proxy-authorization'
46
+ || lk === 'te'
47
+ || lk === 'trailer'
48
+ || lk === 'upgrade'
49
+ ) {
50
+ continue;
51
+ }
52
+ out[k] = v;
53
+ }
54
+ return out;
55
+ }
56
+
28
57
  function sendControl(msg) {
29
58
  if (controlWs?.readyState === WebSocket.OPEN) {
30
59
  controlWs.send(JSON.stringify(msg));
@@ -56,13 +85,22 @@ function connect() {
56
85
  if (msg.type === 'http-req') {
57
86
  try {
58
87
  const url = `http://127.0.0.1:${LOCAL_PORT}${msg.path}`;
88
+ // Node fetch auto-decompresses gzip/br. If we forward Content-Encoding
89
+ // with the already-plain body, browsers try to gunzip JSON and fail
90
+ // (chat history / large API payloads look "empty" or parse-error).
91
+ const reqHeaders = { ...(msg.headers || {}), host: `127.0.0.1:${LOCAL_PORT}` };
92
+ delete reqHeaders['accept-encoding'];
93
+ delete reqHeaders['Accept-Encoding'];
94
+ reqHeaders['Accept-Encoding'] = 'identity';
95
+
59
96
  const res = await fetch(url, {
60
97
  method: msg.method,
61
- headers: { ...msg.headers, host: `127.0.0.1:${LOCAL_PORT}` },
98
+ headers: reqHeaders,
62
99
  body: msg.body || undefined,
63
100
  });
64
- const headers = Object.fromEntries(res.headers.entries());
101
+ const headers = sanitizeProxyHeaders(Object.fromEntries(res.headers.entries()));
65
102
  const contentType = res.headers.get('content-type') || '';
103
+ const contentDisp = res.headers.get('content-disposition') || '';
66
104
 
67
105
  // SSE (and any other body-less-until-done response) is forwarded as
68
106
  // it arrives instead of buffered in full, so remote/hub viewers get
@@ -84,7 +122,30 @@ function connect() {
84
122
  return;
85
123
  }
86
124
 
125
+ // Binary responses (file downloads, images, …) must NOT go through
126
+ // res.text() — UTF-8 decoding corrupts bytes and downloads break on hub.
127
+ const isBinary =
128
+ /attachment/i.test(contentDisp)
129
+ || /^(application\/octet-stream|application\/pdf|application\/zip|application\/gzip|application\/x-|application\/vnd\.|image\/|audio\/|video\/|font\/)/i.test(contentType)
130
+ || (contentType !== '' && !/^(text\/|application\/(json|javascript|xml|problem\+json|x-www-form-urlencoded)|multipart\/)/i.test(contentType));
131
+
132
+ if (isBinary) {
133
+ const buf = Buffer.from(await res.arrayBuffer());
134
+ headers['content-length'] = String(buf.length);
135
+ sendControl({
136
+ type: 'http-res',
137
+ reqId: msg.reqId,
138
+ status: res.status,
139
+ headers,
140
+ body: buf.toString('base64'),
141
+ encoding: 'base64',
142
+ });
143
+ return;
144
+ }
145
+
87
146
  const body = await res.text();
147
+ // Body is always decoded text here — length must match UTF-8 bytes.
148
+ headers['content-length'] = String(Buffer.byteLength(body));
88
149
  sendControl({
89
150
  type: 'http-res',
90
151
  reqId: msg.reqId,