dsh-mobile-plugin 0.1.72 → 0.1.73
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/client.js +2 -2
- package/lib/mobile/archive-clean.js +15 -23
- package/lib/mobile/credentials.js +8 -3
- package/lib/mobile/host-bridge/endpoints.js +1 -0
- package/lib/mobile/workspaces.js +12 -0
- package/lib/panel/index.html +1 -1
- package/lib/relay/lan-proxy.js +4 -1
- package/lib/relay/pairing-policy.js +15 -1
- package/lib/relay/tunnel.js +7 -2
- package/lib/routes/control-plane.js +3 -1
- package/lib/routes/mobile-api.js +3 -2
- package/lib/session-cleanup.cjs +60 -10
- package/lib/sidecar-facade-worker.cjs +6 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -514,7 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
514
514
|
webAccessOff: '公网网页管理已关闭',
|
|
515
515
|
webAccessOpen: '开放网页管理 2 小时',
|
|
516
516
|
webAccessClose: '立即关闭网页管理',
|
|
517
|
-
webAccessHint: '默认关闭;开启后 2
|
|
517
|
+
webAccessHint: '默认关闭;开启后 2 小时自动关闭,访问不会延长时间。关闭后公网不可访问,本机和局域网仍可访问;不影响手机 App。只可从本机 DSH 设置页开关。',
|
|
518
518
|
webAccessUnavailable: '正在读取本机访问状态…',
|
|
519
519
|
webAccessError: '更新网页访问状态失败',
|
|
520
520
|
},
|
|
@@ -531,7 +531,7 @@ window.__ModuleLoader__.load({
|
|
|
531
531
|
webAccessOff: 'Public web management is closed',
|
|
532
532
|
webAccessOpen: 'Open web management for 2 hours',
|
|
533
533
|
webAccessClose: 'Close web management now',
|
|
534
|
-
webAccessHint: 'Closed by default; opening lasts 2 hours and visits do not extend it. Mobile app access is unaffected. Only this local DSH settings page can toggle it.',
|
|
534
|
+
webAccessHint: 'Closed by default; opening lasts 2 hours and visits do not extend it. When closed, public access is blocked while this machine and its LAN can still access the page. Mobile app access is unaffected. Only this local DSH settings page can toggle it.',
|
|
535
535
|
webAccessUnavailable: 'Reading local access status…',
|
|
536
536
|
webAccessError: 'Failed to update web access status',
|
|
537
537
|
},
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* 移动端「清理所有已归档的对话」:按宿主归档集合确定清理范围,并交由落地函数删除。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 手机端「少点一次」不值得把它变成滑一下就没了的动作。
|
|
6
|
+
* 以 workspace.list 的 archivedSessionIds 为清理范围,不用可能过时的列表状态缩小范围;
|
|
7
|
+
* 请求必须显式带 confirm: true —— 磁盘删除不可恢复,手机端「少点一次」不值得把它变成滑一下就没了的动作。
|
|
9
8
|
*
|
|
10
|
-
*
|
|
9
|
+
* 删除动作不重写:读日志 header.id → 摘归档集合 → 删会话目录 + 投影缓存这套只在 session-cleanup.cjs
|
|
11
10
|
* 里存在(见地图 A3-17),这里只是把它的触发面从 agent 工具扩到移动端路由。
|
|
12
11
|
*/
|
|
13
12
|
|
|
@@ -16,7 +15,7 @@ const { withIdempotency, requestIdOf } = require('./idempotency.js');
|
|
|
16
15
|
const { canonicalDeviceKey } = require('./session-cache.js');
|
|
17
16
|
const { invalidateSessionList } = require('./sessions.js');
|
|
18
17
|
const { unwrap } = require('./envelope.js');
|
|
19
|
-
const { deleteSessionData } = require('../session-cleanup.cjs');
|
|
18
|
+
const { deleteSessionData, forgetArchivedSessions } = require('../session-cleanup.cjs');
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
* POST /api/mobile/v1/workspace/clear-archived(token)
|
|
@@ -29,29 +28,22 @@ async function handleClearArchived(ctx, req, res) {
|
|
|
29
28
|
return;
|
|
30
29
|
}
|
|
31
30
|
const outcome = await withIdempotency(ctx, canonicalDeviceKey(req), requestIdOf(req, body), async () => {
|
|
32
|
-
//
|
|
33
|
-
const listResponse = unwrap(await ctx.apiProxy.sessions.list({ rpcId: 'mobile', method: 'session.list', payload: {} }));
|
|
31
|
+
// 归档集合是清理范围的唯一依据;列表摘要里的 running / blank 可能过时,也不应缩小「全部归档」的范围。
|
|
34
32
|
const workspaceResponse = unwrap(await ctx.apiProxy.workspace.list({ rpcId: 'mobile', method: 'workspace.list', payload: {} }));
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return { status: 400, body: { ok: false, error: { ...response.error, message: `${source} 失败:${response.error.message || response.error.code || '未知错误'}` } } };
|
|
38
|
-
}
|
|
33
|
+
if (workspaceResponse.error) {
|
|
34
|
+
return { status: 400, body: { ok: false, error: { ...workspaceResponse.error, message: `workspace.list 失败:${workspaceResponse.error.message || workspaceResponse.error.code || '未知错误'}` } } };
|
|
39
35
|
}
|
|
40
|
-
const items = Array.isArray(listResponse.value?.items) ? listResponse.value.items : [];
|
|
41
36
|
const archivedIds = Array.isArray(workspaceResponse.value?.archivedSessionIds) ? workspaceResponse.value.archivedSessionIds : [];
|
|
42
|
-
|
|
43
|
-
const deletable = new Set(
|
|
44
|
-
items
|
|
45
|
-
.filter((item) => item && !item.blank && !item.running)
|
|
46
|
-
.map((item) => item.sessionId)
|
|
47
|
-
.filter((id) => typeof id === 'string' && id),
|
|
48
|
-
);
|
|
49
|
-
const selected = archivedIds.filter((id) => typeof id === 'string' && deletable.has(id));
|
|
37
|
+
const selected = archivedIds.filter((id) => typeof id === 'string' && id);
|
|
50
38
|
if (selected.length === 0) return { status: 200, body: { ok: true, deleted: 0, archivedSessionIds: archivedIds } };
|
|
51
|
-
|
|
39
|
+
// 先把它从宿主归档集合里摘掉,再删日志:宿主不会自己修剪那份集合,删完还留着
|
|
40
|
+
// 那些 id 就是「已归档」里一排点不开的死行。
|
|
41
|
+
await forgetArchivedSessions(ctx, selected, 'mobile');
|
|
42
|
+
const removed = await deleteSessionData(selected);
|
|
52
43
|
// 删的是列表里可见的行:手机端正开着列表,不立刻失效最长 10 秒还能点到死行。
|
|
53
44
|
invalidateSessionList(ctx);
|
|
54
|
-
|
|
45
|
+
const cleared = new Set(selected);
|
|
46
|
+
return { status: 200, body: { ok: true, deleted: removed.length, archivedSessionIds: archivedIds.filter((id) => !cleared.has(id)) } };
|
|
55
47
|
});
|
|
56
48
|
sendJson(res, outcome.status, outcome.body);
|
|
57
49
|
}
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* 密钥值只在这条路上出现一次(手机 → 宿主)。宿主从不回传值:describe 只给
|
|
16
16
|
* {configured, source?, writable},所以这里的响应里也永远不会有 value 字段。
|
|
17
17
|
*
|
|
18
|
+
* 宿主 describe **回的就是一张平表**:键是引用名、值是上面那个视图
|
|
19
|
+
* (`dsh-api-settings-controller/lib/types/credentials.js` 的
|
|
20
|
+
* `Object.fromEntries(branded.map(...))`,0.2.0 的网关与旧 apiProxy 两条路都打到
|
|
21
|
+
* 同一个 Remote 命名空间)。曾经在这里多拆一层 `value.credentials` —— 那一层从来
|
|
22
|
+
* 不存在,于是每个引用都落成「未配置」,设置页永远显示「密钥未设置」,而测试假体
|
|
23
|
+
* 照抄了同一个错形状,绿的。读了宿主源码才看出来。
|
|
24
|
+
*
|
|
18
25
|
* 「能不能写」由宿主的 credentials 服务说了算:如果某个引用是启动环境变量提供的,
|
|
19
26
|
* 宿主会回 writable:false,set 则回 credential-rejected("supplied read-only by the
|
|
20
27
|
* launching environment")。这里不自己判断,一律如实透传,让 App 显示宿主的原话。
|
|
@@ -76,9 +83,7 @@ async function handleCredentialsDescribe(ctx, req, res) {
|
|
|
76
83
|
}));
|
|
77
84
|
if (error) return sendJson(res, 400, { ok: false, error });
|
|
78
85
|
|
|
79
|
-
const source = value && typeof value
|
|
80
|
-
? value.credentials
|
|
81
|
-
: {};
|
|
86
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
82
87
|
const credentials = {};
|
|
83
88
|
for (const ref of refs) {
|
|
84
89
|
const view = normalizeCredentialView(source[ref]);
|
|
@@ -83,6 +83,7 @@ const ENDPOINTS = {
|
|
|
83
83
|
'workspace.unarchiveSession': inv('workspace', 'unarchiveSession'),
|
|
84
84
|
'workspace.pinSession': inv('workspace', 'pinSession'),
|
|
85
85
|
'workspace.unpinSession': inv('workspace', 'unpinSession'),
|
|
86
|
+
'workspace.insertBefore': inv('workspace', 'insertBefore'),
|
|
86
87
|
'workspace.insertSessionBefore': inv('workspace', 'insertSessionBefore'),
|
|
87
88
|
|
|
88
89
|
// host.* → directoryPicker.*(宿主服务 browse 时),否则回落到插件自带的 browse 原语。
|
package/lib/mobile/workspaces.js
CHANGED
|
@@ -145,6 +145,17 @@ async function handleDeleteWorkspace(ctx, req, res) {
|
|
|
145
145
|
* POST /api/mobile/v1/workspace/move-session(token){ workspaceId, sessionId, beforeSessionId? }
|
|
146
146
|
* 透传宿主 workspace.insertSessionBefore,使移动端和桌面端共享工作区排序。
|
|
147
147
|
*/
|
|
148
|
+
async function handleMoveWorkspace(ctx, req, res) {
|
|
149
|
+
const body = await readJsonBody(req, 64 * 1024);
|
|
150
|
+
const workspaceId = body && typeof body.workspaceId === 'string' ? body.workspaceId.trim() : '';
|
|
151
|
+
const beforeWorkspaceId = body && typeof body.beforeWorkspaceId === 'string' ? body.beforeWorkspaceId.trim() : '';
|
|
152
|
+
if (!workspaceId) return sendJson(res, 400, { ok: false, error: { code: 'bad-request', message: '需要 workspaceId' } });
|
|
153
|
+
const payload = { workspaceId, ...(beforeWorkspaceId ? { beforeWorkspaceId } : {}) };
|
|
154
|
+
const { value, error } = unwrap(await callWorkspace(ctx, 'insertBefore', payload));
|
|
155
|
+
if (error) return sendJson(res, 400, { ok: false, error });
|
|
156
|
+
sendJson(res, 200, { ok: true, workspaceIds: value && value.workspaceIds });
|
|
157
|
+
}
|
|
158
|
+
|
|
148
159
|
async function handleMoveSession(ctx, req, res) {
|
|
149
160
|
const body = await readJsonBody(req, 64 * 1024);
|
|
150
161
|
const workspaceId = body && typeof body.workspaceId === 'string' ? body.workspaceId.trim() : '';
|
|
@@ -255,6 +266,7 @@ async function handleUnpinSession(ctx, req, res) {
|
|
|
255
266
|
|
|
256
267
|
module.exports = {
|
|
257
268
|
buildWorkspacesBody,
|
|
269
|
+
handleMoveWorkspace,
|
|
258
270
|
handleWorkspaces,
|
|
259
271
|
handleListDirectory,
|
|
260
272
|
handleCreateDirectory,
|