@mrrisega/dsh-remote 0.4.0 → 0.4.1
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.
|
@@ -95,6 +95,22 @@ function saveLocalConfig(cfg) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Harness 浏览器会话 Cookie(由 dsh-remote-ui 插件在进程内换取后写入
|
|
100
|
+
* <relayDir>/.harness-cookie.json)。新版 dsh web(0.1.2+)对每个请求校验该 Cookie,
|
|
101
|
+
* 不带则 401 → 手机端白页;bridge 对所有上游 HTTP/WS 请求自动携带,让手机表现为已授权浏览器。
|
|
102
|
+
*/
|
|
103
|
+
const HARNESS_COOKIE_FILE = ".harness-cookie.json";
|
|
104
|
+
function harnessCookieOf() {
|
|
105
|
+
try {
|
|
106
|
+
const p = path.join(path.dirname(CONFIG_PATH), HARNESS_COOKIE_FILE);
|
|
107
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
108
|
+
return j && typeof j.cookie === "string" && j.cookie ? j.cookie : "";
|
|
109
|
+
} catch {
|
|
110
|
+
return "";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
98
114
|
/**
|
|
99
115
|
* 解析稳定 deviceId:优先级 env DSH_BRIDGE_DEVICE_ID > argv[2] > 配置 device_id > 生成。
|
|
100
116
|
* 生成格式 dev-<12hex> 并持久化到 .dsh-config.json,保证每台 Mac 重启后 id 不变。
|
|
@@ -257,6 +273,8 @@ export function buildWsHeaders(headers) {
|
|
|
257
273
|
const up = new URL(UPSTREAM);
|
|
258
274
|
const out = sanitizeRequestHeaders(headers);
|
|
259
275
|
out.Host = up.host; // e.g. "127.0.0.1:3080"
|
|
276
|
+
const ck = harnessCookieOf(); // 新版 dsh web 的浏览器会话 Cookie
|
|
277
|
+
if (ck) out.Cookie = ck;
|
|
260
278
|
return out;
|
|
261
279
|
}
|
|
262
280
|
|
|
@@ -335,7 +353,10 @@ async function doHttp(method, path, reqHeaders, body, isB64) {
|
|
|
335
353
|
const safe = safePath(path);
|
|
336
354
|
if (safe === null) throw new Error("非法路径");
|
|
337
355
|
const url = `${UPSTREAM}${safe}`;
|
|
338
|
-
const
|
|
356
|
+
const reqHdrs = sanitizeRequestHeaders(reqHeaders);
|
|
357
|
+
const ck = harnessCookieOf(); // 新版 dsh web 的浏览器会话 Cookie(否则 401 白页)
|
|
358
|
+
if (ck) reqHdrs.Cookie = ck;
|
|
359
|
+
const init = { method, headers: reqHdrs };
|
|
339
360
|
if (body !== undefined && body !== null && body !== "") {
|
|
340
361
|
// 新协议 http 帧的 body 一律 base64;旧协议 body 是原始文本
|
|
341
362
|
init.body = isB64 ? Buffer.from(String(body), "base64") : String(body);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -119,6 +119,82 @@ function applySaaSMode(cfg) {
|
|
|
119
119
|
return cfg;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// ---------- Harness 浏览器会话代持(0.1.2-rc.1+ 的 ?token 鉴权) ----------
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 新版 dsh web(0.1.2-rc.1+)启动会打印带 ?token= 的 URL,并只给"换到了会话 Cookie"的浏览器放行,
|
|
126
|
+
* 其余请求一律 401(手机经隧道因此白页)。本插件与 Harness 同进程:
|
|
127
|
+
* - 通过 ctx.connection 服务拿到本进程 launch token(authenticatedUrl 自带 token);
|
|
128
|
+
* - 在本地向 /?token=… 发起 token 交换,捕获下发的 dsh-auth-* 会话 Cookie;
|
|
129
|
+
* - 写入 <relayDir>/.harness-cookie.json,bridge 上游转发时自动携带,让手机表现为已授权浏览器。
|
|
130
|
+
* 老版本(无该鉴权)下 connection 服务没有 authenticatedUrl → 静默跳过,行为不变。
|
|
131
|
+
*/
|
|
132
|
+
const HARNESS_COOKIE_FILE = ".harness-cookie.json";
|
|
133
|
+
const HARNESS_AUTH_RETRY_MS = 3000;
|
|
134
|
+
const HARNESS_AUTH_RETRY_MAX = 20; // 最多约 60s 等 connection 服务就绪
|
|
135
|
+
const HARNESS_AUTH_REFRESH_MS = 6 * 3600 * 1000;
|
|
136
|
+
|
|
137
|
+
/** 读取响应里的 set-cookie(兼容 getSetCookie / get 两种实现)。 */
|
|
138
|
+
function setCookieOf(res) {
|
|
139
|
+
try {
|
|
140
|
+
if (typeof res.headers.getSetCookie === "function") return res.headers.getSetCookie().join("; ");
|
|
141
|
+
} catch { /* ignore */ }
|
|
142
|
+
return res.headers.get("set-cookie") || "";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function mintHarnessCookie(ctx, relayDir) {
|
|
146
|
+
try {
|
|
147
|
+
const port = ctx.webServer?.port;
|
|
148
|
+
if (!port) return false;
|
|
149
|
+
// 不能把 "connection" 写进 inject(0.1.1 无该服务会拖死激活),只能运行时 try 获取
|
|
150
|
+
let holder;
|
|
151
|
+
try { holder = ctx.get("connection"); } catch { holder = void 0; }
|
|
152
|
+
if (!holder && ctx.connection !== void 0) { try { holder = ctx.connection; } catch { /* ignore */ } }
|
|
153
|
+
const svc = holder && typeof holder.authenticatedUrl === "function"
|
|
154
|
+
? holder
|
|
155
|
+
: holder && holder.connection && typeof holder.connection.authenticatedUrl === "function"
|
|
156
|
+
? holder.connection
|
|
157
|
+
: null;
|
|
158
|
+
if (!svc) return false; // 老版本无浏览器鉴权 → 无需 cookie
|
|
159
|
+
const tokenUrl = svc.authenticatedUrl(`http://127.0.0.1:${port}`);
|
|
160
|
+
const res = await fetch(tokenUrl, { redirect: "manual", signal: AbortSignal.timeout(6000) });
|
|
161
|
+
const cookie = setCookieOf(res).split(";")[0].trim();
|
|
162
|
+
if (!cookie || !cookie.startsWith("dsh-auth-")) return false;
|
|
163
|
+
const out = { authority: `127.0.0.1:${port}`, cookie, mintedAt: Date.now() };
|
|
164
|
+
mkdirSync(dirname(configPathOf(relayDir)), { recursive: true });
|
|
165
|
+
writeFileSync(join(relayDir, HARNESS_COOKIE_FILE), JSON.stringify(out, null, 2), { mode: 0o600 });
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** 后台调度:启动重试直到换取成功,成功后每 6h 刷新(与插件生命周期同进退)。 */
|
|
173
|
+
function scheduleHarnessMint(ctx, relayDir) {
|
|
174
|
+
let succeeded = false;
|
|
175
|
+
let retries = 0;
|
|
176
|
+
const attempt = async () => {
|
|
177
|
+
if (succeeded) return;
|
|
178
|
+
if (await mintHarnessCookie(ctx, relayDir)) succeeded = true;
|
|
179
|
+
};
|
|
180
|
+
const bootIv = setInterval(() => {
|
|
181
|
+
if (succeeded || ++retries > HARNESS_AUTH_RETRY_MAX) {
|
|
182
|
+
clearInterval(bootIv);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
void attempt();
|
|
186
|
+
}, HARNESS_AUTH_RETRY_MS);
|
|
187
|
+
bootIv.unref?.();
|
|
188
|
+
const refreshIv = setInterval(() => {
|
|
189
|
+
void mintHarnessCookie(ctx, relayDir).catch(() => {});
|
|
190
|
+
}, HARNESS_AUTH_REFRESH_MS);
|
|
191
|
+
refreshIv.unref?.();
|
|
192
|
+
return () => {
|
|
193
|
+
clearInterval(bootIv);
|
|
194
|
+
clearInterval(refreshIv);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
122
198
|
// ---------- bridge 服务状态 / 启停(launchctl,macOS) ----------
|
|
123
199
|
|
|
124
200
|
function launchAgentPath() {
|
|
@@ -736,5 +812,7 @@ function registerRoutes(ctx, relayDir) {
|
|
|
736
812
|
export function apply(ctx, config = {}) {
|
|
737
813
|
const relayDir = config.relayDir || process.env.DSH_RELAY_DIR || DEFAULT_RELAY_DIR;
|
|
738
814
|
ctx.effect(() => registerRoutes(ctx, relayDir), "dsh-remote-ui: /dsh-remote routes");
|
|
815
|
+
// 0.1.2-rc.1+ 浏览器会话代持:换取 Harness 会话 Cookie 供 bridge 上游携带(手机点设备不再 401 白页)
|
|
816
|
+
ctx.effect(() => scheduleHarnessMint(ctx, relayDir), "dsh-remote-ui: harness browser-session mint");
|
|
739
817
|
ctx.logger?.info?.(`dsh-remote-ui: /dsh-remote routes ready (relayDir=${relayDir})`);
|
|
740
818
|
}
|