dsh-jiuling-remote-mobile 1.0.19 → 1.0.20
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 +157 -8
- package/lib/index.js +25 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -284,6 +284,68 @@ window.__ModuleLoader__.load({
|
|
|
284
284
|
*/
|
|
285
285
|
var remoteSessionIds = {};
|
|
286
286
|
|
|
287
|
+
/**
|
|
288
|
+
* 电脑镜像工作区的 slug 集合 —— 隔离的**真正判据**。
|
|
289
|
+
*
|
|
290
|
+
* ⚠️⚠️ **为什么不用 `remoteSessionIds`(那是第一代,已被用户报障推翻)。**
|
|
291
|
+
*
|
|
292
|
+
* 第一代按 **sessionId** 判定,只在「用户从电脑文件夹对话框里建会话」
|
|
293
|
+
* 或「宿主拦截器绑过」时才知道。而用户在**侧边栏点「新建会话」**时,
|
|
294
|
+
* 官方前端直接 `sessionController.create({workspaceId})`,**不经过本插件**;
|
|
295
|
+
* 宿主的 create 拦截器认得出镜像目录(消息发得对),但前端不知道
|
|
296
|
+
* ⇒ 模型菜单仍按「本地」筛,显示手机模型。
|
|
297
|
+
*
|
|
298
|
+
* 用户原话:「只有我手动添加工作区的时候,才会触发模型和预设的刷新」——
|
|
299
|
+
* 说的就是判据挂在了「走了哪条路」上,而不是「会话的工作区在哪」。
|
|
300
|
+
*
|
|
301
|
+
* 正解:**按 cwd 判定**,与宿主 `desktopPathOfMirror` 同一个轴。
|
|
302
|
+
* 前端从 `sessions.list` 里同步读到会话的 `cwd`,看它是不是
|
|
303
|
+
* `…/workspaces/<slug>` 且 slug 属于电脑镜像。这条路与「谁建的会话」
|
|
304
|
+
* 完全无关,所以**任何入口进来都一致,不需要任何刷新触发**。
|
|
305
|
+
*/
|
|
306
|
+
var mirrorSlugs = {};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 从 cwd 取出镜像工作区的 slug(与宿主 `desktopPathOfMirror` 同构)。
|
|
310
|
+
*
|
|
311
|
+
* ⚠️ 用 `/workspaces/` 定位而不是前缀比对:`workspace.path` 是 realpath,
|
|
312
|
+
* 真机上 `/data/user/0` 与 `/data/data` 会差一层,前缀一比就永远不命中
|
|
313
|
+
* (宿主那边踩过这个坑,注释见 `desktopPathOfMirror`)。
|
|
314
|
+
* 两种分隔符都认:手机是 posix,但在 Windows 上调试时是 `\`。
|
|
315
|
+
*
|
|
316
|
+
* @param cwd - 会话的 cwd。
|
|
317
|
+
* @returns 镜像工作区的 slug;不是镜像目录时 `undefined`。
|
|
318
|
+
*/
|
|
319
|
+
function mirrorSlugOf(cwd) {
|
|
320
|
+
if (typeof cwd !== 'string' || cwd === '') return undefined;
|
|
321
|
+
var at = cwd.search(/[\\/]workspaces[\\/]/);
|
|
322
|
+
if (at < 0) return undefined;
|
|
323
|
+
var slug = cwd.slice(at).split(/[\\/]/)[2];
|
|
324
|
+
return slug === undefined || slug === '' ? undefined : slug;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 这个会话是不是跑在电脑上 —— **同步**判定,任何入口都用同一条。
|
|
329
|
+
*
|
|
330
|
+
* ⚠️ **cwd 读得到时,它以 cwd 为唯一判据**(不看 `remoteSessionIds`):
|
|
331
|
+
* 工作区就是隔离条件本身。解绑后 `mirrorSlugs` 清空 ⇒ 全部回到本地,
|
|
332
|
+
* 不需要再去清每个会话的旧标记。
|
|
333
|
+
*
|
|
334
|
+
* 只有列表行还没带 cwd 的那一两帧(会话刚建好)才退回 `remoteSessionIds`。
|
|
335
|
+
*
|
|
336
|
+
* @param sessionId - 会话 id。
|
|
337
|
+
* @param cwdOf - `sessionId → cwd` 的同步读取函数。
|
|
338
|
+
* @returns 是不是电脑工作区的会话。
|
|
339
|
+
*/
|
|
340
|
+
function isRemoteSession(sessionId, cwdOf) {
|
|
341
|
+
var cwd = cwdOf === undefined ? undefined : cwdOf(sessionId);
|
|
342
|
+
if (typeof cwd === 'string' && cwd !== '') {
|
|
343
|
+
var slug = mirrorSlugOf(cwd);
|
|
344
|
+
return slug !== undefined && mirrorSlugs[slug] === true;
|
|
345
|
+
}
|
|
346
|
+
return remoteSessionIds[sessionId] === true;
|
|
347
|
+
}
|
|
348
|
+
|
|
287
349
|
/** 从宿主拉一次全量远程会话清单(失败就按「全是本地」处理)。 */
|
|
288
350
|
function loadRemoteSessionIds() {
|
|
289
351
|
return bridge('remoteSessions', {}).then(
|
|
@@ -300,6 +362,28 @@ window.__ModuleLoader__.load({
|
|
|
300
362
|
);
|
|
301
363
|
}
|
|
302
364
|
|
|
365
|
+
/**
|
|
366
|
+
* 从宿主拉一次「电脑镜像工作区的 slug 集合」(隔离的真正判据)。
|
|
367
|
+
*
|
|
368
|
+
* 拉不到就保持原样(空集合 = 全部按本地处理):最坏是本地工作区多看到
|
|
369
|
+
* 电脑模型,**不会误发** —— 消息该发给谁由宿主 `prompt` 拦截器按
|
|
370
|
+
* `remoteSessions` 决定,与这里无关。
|
|
371
|
+
*/
|
|
372
|
+
function loadMirrorSlugs() {
|
|
373
|
+
return bridge('mirrorSlugs', {}).then(
|
|
374
|
+
function (result) {
|
|
375
|
+
var slugs = result && result.slugs;
|
|
376
|
+
if (!Array.isArray(slugs)) return;
|
|
377
|
+
var next = {};
|
|
378
|
+
for (var at = 0; at < slugs.length; at += 1) next[slugs[at]] = true;
|
|
379
|
+
mirrorSlugs = next;
|
|
380
|
+
},
|
|
381
|
+
function () {
|
|
382
|
+
/* 同上:最坏是多显示几项,不会误发 */
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
303
387
|
/**
|
|
304
388
|
* 按「这个会话是不是电脑的」筛出该显示的组与失败项。
|
|
305
389
|
*
|
|
@@ -459,13 +543,17 @@ window.__ModuleLoader__.load({
|
|
|
459
543
|
*
|
|
460
544
|
* @param directory - 官方的 `ModelDirectory` 实例。
|
|
461
545
|
* @param sessionId - 它归属的会话。
|
|
546
|
+
* @param cwdOf - `sessionId → cwd` 的**同步**读取函数(隔离判据要用它)。
|
|
462
547
|
* @returns 读路径被筛过的同构对象。
|
|
463
548
|
*/
|
|
464
|
-
function scopeDirectoryToWorkspace(directory, sessionId) {
|
|
549
|
+
function scopeDirectoryToWorkspace(directory, sessionId, cwdOf) {
|
|
465
550
|
if (!directory || !directory.store || typeof directory.store.getSnapshot !== 'function') {
|
|
466
551
|
return directory;
|
|
467
552
|
}
|
|
468
|
-
|
|
553
|
+
// ⚠️ 判据是**每次读时现算**的(不是建包装时算一次):会话的 cwd 可能
|
|
554
|
+
// 在列表行到达后才出现,而且解绑/换工作区都会改结论。
|
|
555
|
+
// `isRemoteSession` 见其注释 —— 它看的 cwd,跟宿主 `desktopPathOfMirror` 同一个轴。
|
|
556
|
+
const isRemote = function () { return isRemoteSession(sessionId, cwdOf); };
|
|
469
557
|
const wrapper = Object.create(Object.getPrototypeOf(directory));
|
|
470
558
|
Object.assign(wrapper, directory);
|
|
471
559
|
|
|
@@ -551,9 +639,10 @@ window.__ModuleLoader__.load({
|
|
|
551
639
|
* 旧行为,不会报错)。返回一个撤销函数。
|
|
552
640
|
*
|
|
553
641
|
* @param ctx - client 根上下文。
|
|
642
|
+
* @param cwdOf - `sessionId → cwd` 的**同步**读取函数(隔离判据要用它)。
|
|
554
643
|
* @returns 撤销函数。
|
|
555
644
|
*/
|
|
556
|
-
function patchModelDirectories(ctx) {
|
|
645
|
+
function patchModelDirectories(ctx, cwdOf) {
|
|
557
646
|
var models = ctx.get === undefined ? undefined : ctx.get('modelDirectories');
|
|
558
647
|
if (!models || typeof models.directoryFor !== 'function') return function () {};
|
|
559
648
|
|
|
@@ -595,7 +684,7 @@ window.__ModuleLoader__.load({
|
|
|
595
684
|
raw.directoryFor = function (sessionId) {
|
|
596
685
|
var directory = call(sessionId);
|
|
597
686
|
if (directory.__dshRemoteScoped !== undefined) return directory.__dshRemoteScoped;
|
|
598
|
-
var scoped = scopeDirectoryToWorkspace(directory, sessionId);
|
|
687
|
+
var scoped = scopeDirectoryToWorkspace(directory, sessionId, cwdOf);
|
|
599
688
|
try {
|
|
600
689
|
directory.__dshRemoteScoped = scoped;
|
|
601
690
|
} catch (error) {
|
|
@@ -609,6 +698,41 @@ window.__ModuleLoader__.load({
|
|
|
609
698
|
};
|
|
610
699
|
}
|
|
611
700
|
|
|
701
|
+
/**
|
|
702
|
+
* 造一个 `sessionId → cwd` 的**同步**读取函数。
|
|
703
|
+
*
|
|
704
|
+
* 会话的 cwd 在客户端是**同步可读**的:`sessions` 服务的 `list` 是
|
|
705
|
+
* `createSnapshotStore({ids, byId, …})`,`projectList()` 给每一行算出了
|
|
706
|
+
* `cwd`(`dsh-api-session-controller/lib/client.js` 的 `byId[id] = {id,
|
|
707
|
+
* displayTitle, running, …, cwd?, …}`)。
|
|
708
|
+
*
|
|
709
|
+
* ⚠️ 之所以要「同步」:模型菜单每渲染一帧都会读一次目录快照
|
|
710
|
+
* (`getSnapshot`),那里**不能**是异步的。
|
|
711
|
+
*
|
|
712
|
+
* 全用显式 `if` 判空 —— chrome73 语法约束,不能用 `?.`。
|
|
713
|
+
*
|
|
714
|
+
* @param ctx - client 根上下文。
|
|
715
|
+
* @returns 读取函数;读不到时返回 `undefined`。
|
|
716
|
+
*/
|
|
717
|
+
function makeCwdOf(ctx) {
|
|
718
|
+
return function (sessionId) {
|
|
719
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined;
|
|
720
|
+
var sessions = ctx.sessions;
|
|
721
|
+
if (sessions === undefined || sessions === null) return undefined;
|
|
722
|
+
var list = sessions.list;
|
|
723
|
+
if (list === undefined || list === null) return undefined;
|
|
724
|
+
if (typeof list.getSnapshot !== 'function') return undefined;
|
|
725
|
+
var snapshot = list.getSnapshot();
|
|
726
|
+
if (snapshot === undefined || snapshot === null) return undefined;
|
|
727
|
+
var byId = snapshot.byId;
|
|
728
|
+
if (byId === undefined || byId === null) return undefined;
|
|
729
|
+
var row = byId[sessionId];
|
|
730
|
+
if (row === undefined || row === null) return undefined;
|
|
731
|
+
var cwd = row.cwd;
|
|
732
|
+
return typeof cwd === 'string' && cwd !== '' ? cwd : undefined;
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
612
736
|
/**
|
|
613
737
|
* 调宿主桥的一条 RPC(令牌只在引擎进程里,浏览器侧不持有)。
|
|
614
738
|
*
|
|
@@ -707,8 +831,10 @@ window.__ModuleLoader__.load({
|
|
|
707
831
|
var registerLocal = props.registerLocal;
|
|
708
832
|
/** 兜底:拉一次全量会话列表(登记失败时用)。 */
|
|
709
833
|
var refreshSessions = props.refreshSessions;
|
|
710
|
-
/**
|
|
834
|
+
/** 把会话标记成「电脑的」(cwd 还没进列表行时兜底,见 `isRemoteSession`)。 */
|
|
711
835
|
var markRemote = props.markRemote;
|
|
836
|
+
/** 重拉一次镜像工作区 slug 集合(新建镜像工作区之后必须调)。 */
|
|
837
|
+
var refreshMirrorSlugs = props.refreshMirrorSlugs;
|
|
712
838
|
/** 解绑后把所有会话退回「本地」。 */
|
|
713
839
|
var clearRemote = props.clearRemote;
|
|
714
840
|
|
|
@@ -927,6 +1053,10 @@ window.__ModuleLoader__.load({
|
|
|
927
1053
|
// 不作废的话用户接下来看到的仍然是配对前那份手机模型列表 ——
|
|
928
1054
|
// 就是用户报的「模型列表还是没同步」。
|
|
929
1055
|
reloadModelCatalog();
|
|
1056
|
+
// ⚠️ **镜像工作区 slug 集合也要重拉**:换一台电脑后 `workspaceMap`
|
|
1057
|
+
// 是另一份(slug 由电脑路径哈希得来,同名不同机 slug 不同)。
|
|
1058
|
+
// 不重拉的话,新电脑的镜像工作区会被判成「本地」—— 又是那个 bug。
|
|
1059
|
+
if (refreshMirrorSlugs !== undefined) refreshMirrorSlugs();
|
|
930
1060
|
},
|
|
931
1061
|
function (reason) {
|
|
932
1062
|
if (!aliveRef.current) return;
|
|
@@ -1229,9 +1359,13 @@ window.__ModuleLoader__.load({
|
|
|
1229
1359
|
*/
|
|
1230
1360
|
var settleLocal = function (result) {
|
|
1231
1361
|
if (!result || typeof result.sessionId !== 'string') return Promise.resolve();
|
|
1232
|
-
// ⚠️
|
|
1233
|
-
//
|
|
1362
|
+
// ⚠️ **先标记「这是电脑的」再登记**:列表行的 `cwd` 要等宿主那一行
|
|
1363
|
+
// 回来才带上,之前那几帧判据读不到 cwd,靠这个标记兜住。
|
|
1234
1364
|
if (markRemote !== undefined) markRemote(result.sessionId);
|
|
1365
|
+
// ⚠️ **重拉镜像工作区 slug 集合**:宿主刚为这个电脑目录建好(或复用)
|
|
1366
|
+
// 镜像工作区,`workspaceMap` 可能**刚多出一条**。前端那份集合是快照,
|
|
1367
|
+
// 不重拉的话这个工作区在侧边栏里点进去仍被判成「本地」。
|
|
1368
|
+
if (refreshMirrorSlugs !== undefined) refreshMirrorSlugs();
|
|
1235
1369
|
var local = registerLocal === undefined
|
|
1236
1370
|
? Promise.resolve()
|
|
1237
1371
|
: Promise.resolve(registerLocal(result.sessionId, result.cwd)).then(null, function () {
|
|
@@ -1674,17 +1808,32 @@ window.__ModuleLoader__.load({
|
|
|
1674
1808
|
markRemote: (sessionId) => {
|
|
1675
1809
|
if (typeof sessionId === 'string' && sessionId !== '') remoteSessionIds[sessionId] = true;
|
|
1676
1810
|
},
|
|
1811
|
+
/**
|
|
1812
|
+
* 重拉一次「电脑镜像工作区 slug 集合」。
|
|
1813
|
+
*
|
|
1814
|
+
* ⚠️ **必须在「刚给某个电脑目录建好工作区」之后调**:宿主
|
|
1815
|
+
* `ensureDesktopWorkspace()` 每碰到一个新的电脑目录就往 `workspaceMap`
|
|
1816
|
+
* 里加一条 slug,而前端这份集合是**快照**。不重拉的话,新加的镜像工作区
|
|
1817
|
+
* 在侧边栏里点进去仍会被判成「本地」—— 正是用户报的那个 bug。
|
|
1818
|
+
*/
|
|
1819
|
+
refreshMirrorSlugs: () => loadMirrorSlugs(),
|
|
1677
1820
|
/** 解绑后把所有会话退回「本地」(电脑已经不在了)。 */
|
|
1678
1821
|
clearRemote: () => {
|
|
1679
1822
|
remoteSessionIds = {};
|
|
1823
|
+
mirrorSlugs = {};
|
|
1680
1824
|
},
|
|
1681
1825
|
});
|
|
1682
1826
|
|
|
1683
1827
|
// 拉一次全量远程会话清单(重启 App 后已有会话也要能正确识别)。
|
|
1684
1828
|
loadRemoteSessionIds();
|
|
1829
|
+
// 拉一次镜像工作区 slug 集合(隔离的**真正判据**,见 `mirrorSlugs`)。
|
|
1830
|
+
loadMirrorSlugs();
|
|
1685
1831
|
|
|
1686
1832
|
// 把「按工作区筛模型目录」接到官方服务上。
|
|
1687
|
-
ctx.effect(
|
|
1833
|
+
ctx.effect(
|
|
1834
|
+
() => patchModelDirectories(ctx, makeCwdOf(ctx)),
|
|
1835
|
+
'dsh-remote-mobile: workspace-scoped model catalog',
|
|
1836
|
+
);
|
|
1688
1837
|
|
|
1689
1838
|
// 负 priority:single slot 取 entries[0],entries 按 priority 升序 ⇒ 最先渲染。
|
|
1690
1839
|
const register = (name) =>
|
package/lib/index.js
CHANGED
|
@@ -1918,6 +1918,31 @@ async function handleBridge(ctx, config, io) {
|
|
|
1918
1918
|
case 'remoteSessions':
|
|
1919
1919
|
return { ids: [...io.remoteSessions] };
|
|
1920
1920
|
|
|
1921
|
+
/**
|
|
1922
|
+
* 电脑镜像工作区的 slug 集合(`workspaceMap` 的 keys)。
|
|
1923
|
+
*
|
|
1924
|
+
* ⚠️⚠️ **这是「模型列表按工作区隔离」的第二代判据,用户 1.0.19 报障后的正解。**
|
|
1925
|
+
*
|
|
1926
|
+
* 上一代用 `remoteSessions`(按 **sessionId**)当判据,漏掉了这条真实路径:
|
|
1927
|
+
* 用户在侧边栏点「新建会话」时,官方前端直接
|
|
1928
|
+
* `sessionController.create({workspaceId})` —— **不经过本插件的桥**。
|
|
1929
|
+
* 宿主的 create 拦截器认得出镜像目录(所以消息发得对),但**前端不知道**,
|
|
1930
|
+
* 于是模型菜单仍按「本地」筛,显示手机模型。
|
|
1931
|
+
*
|
|
1932
|
+
* 用户原话:「只有我手动添加工作区的时候,才会触发模型和预设的刷新」——
|
|
1933
|
+
* 一针见血:判据挂在了「走了哪条路」上,而不是「会话的工作区在哪」。
|
|
1934
|
+
*
|
|
1935
|
+
* 正解是**按 cwd 判定**,与宿主 `desktopPathOfMirror` 同一个轴:
|
|
1936
|
+
* 前端从 `sessions.list` 的会话行里**同步**读到 cwd,看它是不是
|
|
1937
|
+
* `<STATE_DIR>/workspaces/<slug>` 且 `slug` 在这里面。这条路
|
|
1938
|
+
* **与「谁建的会话」完全无关**,所以任何入口进来都一致。
|
|
1939
|
+
*
|
|
1940
|
+
* 只回 slug 列表而不是整张 `workspaceMap`:前端不需要电脑路径,
|
|
1941
|
+
* 少传一份数据也少一处可能不一致的地方。
|
|
1942
|
+
*/
|
|
1943
|
+
case 'mirrorSlugs':
|
|
1944
|
+
return { slugs: Object.keys(io.workspaceMap ?? {}) };
|
|
1945
|
+
|
|
1921
1946
|
/**
|
|
1922
1947
|
* 配对:用电脑上生成的 6 位码换回这台电脑的隧道地址与令牌。
|
|
1923
1948
|
*
|