dsh-pocket 1.4.8 → 1.6.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/lib/index.js +2 -0
- package/lib/proxy.mjs +32 -0
- package/lib/service.mjs +38 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -148,6 +148,8 @@ export function apply(ctx, config = {}, internals = {}) {
|
|
|
148
148
|
// 代理随插件自动启动(局域网二维码开箱即用,零配置)
|
|
149
149
|
void service.startProxy().then((proxy) => {
|
|
150
150
|
logger.info('dsh-pocket: proxy ready on :%d | 局域网代理已就绪', proxy.port);
|
|
151
|
+
// 自动恢复上次开启的公网隧道(DSH 重启后 cloudflared 子进程被杀,issue #11)
|
|
152
|
+
void service.restoreTunnelIfNeeded?.().catch(() => {});
|
|
151
153
|
}).catch((err) => {
|
|
152
154
|
logger.error('dsh-pocket: proxy start failed | 代理启动失败: %s', err?.message ?? err);
|
|
153
155
|
});
|
package/lib/proxy.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { createServer } from 'node:http';
|
|
15
15
|
import { request as httpRequest } from 'node:http';
|
|
16
|
+
import { createGzip, createBrotliCompress } from 'node:zlib';
|
|
16
17
|
|
|
17
18
|
const DEFAULT_UPSTREAM = { host: '127.0.0.1', port: 3080 };
|
|
18
19
|
|
|
@@ -98,6 +99,37 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
|
|
|
98
99
|
proxyRes.on('error', () => res.destroy());
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
102
|
+
// 大 JSON/text 响应**流式压缩**(issue #12):长会话历史一次返回 17MB+,
|
|
103
|
+
// 局域网直连与隧道段都吃满带宽;压缩到 ~1.3MB。跳过已压缩、SSE 流
|
|
104
|
+
// (/api/events.* 原样透传)、HTML(走上面的注入分支)。
|
|
105
|
+
const acceptEncoding = String(req.headers['accept-encoding'] ?? '');
|
|
106
|
+
const canGzip = /\bgzip\b/.test(acceptEncoding);
|
|
107
|
+
const canBr = /\bbr\b/.test(acceptEncoding);
|
|
108
|
+
const isEventStream = contentType.includes('text/event-stream');
|
|
109
|
+
const knownLen = Number(proxyRes.headers['content-length'] || 0);
|
|
110
|
+
const shouldCompress = (canGzip || canBr)
|
|
111
|
+
&& !isCompressed(proxyRes.headers)
|
|
112
|
+
&& !isEventStream
|
|
113
|
+
&& (contentType.includes('application/json') || contentType.startsWith('text/'))
|
|
114
|
+
&& (knownLen === 0 || knownLen >= 1024);
|
|
115
|
+
if (shouldCompress) {
|
|
116
|
+
const enc = canBr ? 'br' : 'gzip';
|
|
117
|
+
const outHeaders = { ...proxyRes.headers };
|
|
118
|
+
delete outHeaders['content-length'];
|
|
119
|
+
delete outHeaders['transfer-encoding'];
|
|
120
|
+
outHeaders['content-encoding'] = enc;
|
|
121
|
+
res.writeHead(proxyRes.statusCode ?? 200, outHeaders);
|
|
122
|
+
const z = enc === 'br' ? createBrotliCompress() : createGzip();
|
|
123
|
+
proxyRes.pipe(z).pipe(res);
|
|
124
|
+
// 任一端断开都要清理(含压缩流)。注意:不能用 proxyRes 的 'close'
|
|
125
|
+
// 来掐 res——正常结束后 close 也会触发,此时压缩流可能还没写完,
|
|
126
|
+
// 会误杀连接;异常中止用 'aborted'。
|
|
127
|
+
res.on('close', () => { proxyRes.destroy(); z.destroy(); });
|
|
128
|
+
proxyRes.on('error', () => { z.destroy(); res.destroy(); });
|
|
129
|
+
proxyRes.on('aborted', () => { z.destroy(); res.destroy(); });
|
|
130
|
+
z.on('error', () => res.destroy());
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
101
133
|
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
|
102
134
|
proxyRes.pipe(res);
|
|
103
135
|
// 任一端断开都要清理另一端:客户端断连销毁上游流(不留僵尸),
|
package/lib/service.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import { networkInterfaces } from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
11
13
|
import { createPocketProxy } from './proxy.mjs';
|
|
12
14
|
import { startQuickTunnel } from './tunnel.mjs';
|
|
13
15
|
|
|
@@ -112,6 +114,21 @@ export function createPocketService({
|
|
|
112
114
|
return qrCache.get(text);
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
// 公网隧道自动恢复(issue #11):DSH 重启后 cloudflared 子进程被杀、隧道消失,
|
|
118
|
+
// 插件无从知晓。启动时检查持久化的「隧道开启中」标记,自动重新拉起。
|
|
119
|
+
const autoStatePath = home ? join(home, 'dsh-pocket', 'tunnel-auto.json') : null;
|
|
120
|
+
async function persistAutoTunnel() {
|
|
121
|
+
if (!autoStatePath) return;
|
|
122
|
+
try {
|
|
123
|
+
await mkdir(dirname(autoStatePath), { recursive: true });
|
|
124
|
+
await writeFile(autoStatePath, JSON.stringify({ at: Date.now() }), 'utf8');
|
|
125
|
+
} catch { /* 忽略 */ }
|
|
126
|
+
}
|
|
127
|
+
async function clearAutoTunnel() {
|
|
128
|
+
if (!autoStatePath) return;
|
|
129
|
+
try { await rm(autoStatePath, { force: true }); } catch { /* 忽略 */ }
|
|
130
|
+
}
|
|
131
|
+
|
|
115
132
|
return {
|
|
116
133
|
dshPort,
|
|
117
134
|
/** 启动局域网代理(幂等)。端口被占(EADDRINUSE,如桌面版与普通环境同时运行)时自动尝试下一个端口。 */
|
|
@@ -166,6 +183,8 @@ export function createPocketService({
|
|
|
166
183
|
tunnelState.phase = 'error';
|
|
167
184
|
tunnelState.detail = `隧道进程退出(code=${code})| tunnel process exited`;
|
|
168
185
|
});
|
|
186
|
+
// 记录「隧道开启中」,供重启后自动恢复(issue #11)
|
|
187
|
+
void persistAutoTunnel();
|
|
169
188
|
return tunnel.url;
|
|
170
189
|
} catch (err) {
|
|
171
190
|
// stopTunnel 触发的 abort 不算错误:保持 idle,别把状态刷成 error
|
|
@@ -195,6 +214,25 @@ export function createPocketService({
|
|
|
195
214
|
tunnelState.phase = 'idle';
|
|
196
215
|
tunnelState.detail = '';
|
|
197
216
|
tunnelState.startedAt = null;
|
|
217
|
+
void clearAutoTunnel(); // 手动关闭后不再自动恢复
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/** 启动时自动恢复上次开启的公网隧道(DSH 重启后 cloudflared 子进程被杀,issue #11)。 */
|
|
221
|
+
async restoreTunnelIfNeeded() {
|
|
222
|
+
if (!autoStatePath || tunnel || tunnelPromise) return;
|
|
223
|
+
let has = false;
|
|
224
|
+
try {
|
|
225
|
+
const raw = await readFile(autoStatePath, 'utf8');
|
|
226
|
+
has = /"at"\s*:/.test(raw);
|
|
227
|
+
} catch { return; } // 无标记 → 不恢复
|
|
228
|
+
if (!has) return;
|
|
229
|
+
try {
|
|
230
|
+
await this.startTunnel();
|
|
231
|
+
console.log('dsh-pocket: public tunnel auto-restored | 已自动恢复公网隧道');
|
|
232
|
+
} catch (err) {
|
|
233
|
+
// 恢复失败保留标记(下次启动再试);网络问题见 README 排障
|
|
234
|
+
console.warn('dsh-pocket: tunnel auto-restore failed | 自动恢复隧道失败: %s', err?.message ?? err);
|
|
235
|
+
}
|
|
198
236
|
},
|
|
199
237
|
|
|
200
238
|
/** 状态快照(RPC 返回,不含敏感信息;二维码 data URL 本地生成 + 缓存)。 */
|
package/package.json
CHANGED