termdock 1.4.123 → 1.4.125
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/.desktop-dist/main.js +151 -43
- package/dist/client/assets/{ModelPreview-DGvy0ViD.js → ModelPreview-ChnYwKkY.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-BRUTM7Ij.js → abnfDiagram-VRR7QNED-rm8HZhdr.js} +1 -1
- package/dist/client/assets/{arc-DeKie3_7.js → arc-lmX0JkP8.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-CAorViVK.js → architectureDiagram-ZJ3FMSHR-B-fNsEWy.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-DHq1C73O.js → blockDiagram-677ZJIJ3-CcKyp1yy.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-DluWWKuh.js → c4Diagram-LMCZKHZV-BceB7WNk.js} +1 -1
- package/dist/client/assets/channel-BgyemVuE.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BPSQsAYp.js → chunk-2Q5K7J3B-CKl1IhTX.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-g93sHdJF.js → chunk-32BRIVSS-Gu2AH7Vq.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CvtNtBa-.js → chunk-5VM5RSS4-DdzVDT3j.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-DWnR2mqn.js → chunk-EX3LRPZG-CiOx9i27.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-Dzbpe3Xo.js → chunk-JWPE2WC7-Dm5NkQb2.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-Du2Jw6Ga.js → chunk-MOJQB5TN-BlzxmFLN.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-DIqqwReI.js → chunk-RYQCIY6F-CwC-3LxS.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BDK2KnFE.js → chunk-V7JOEXUC-BPqNLPST.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BncB3kMk.js → chunk-VR4S4FIN-DcBWXics.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CRx51cPn.js → chunk-XXDRQBXY-BoqBy-W-.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-RYUGciz-.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-RYUGciz-.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-C4eOHhIl.js → cose-bilkent-JH36ORCC-DNQ-dGzx.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-BZuh5fJN.js → cynefin-VYW2F7L2-CiP8cZ32.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-BdyBy9fD.js → cynefinDiagram-TSTJHNR4-DOBKA-xo.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-tlpcqDkv.js → dagre-VKFMJZFB-hvy3FbtL.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-DmryDLBx.js → diagram-FQU43EPY-CNwULa6G.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-DyaVVj3y.js → diagram-G47NLZAW-DHADT4lq.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-DfwqogJh.js → diagram-NH7WQ7WH-BmmJLM_2.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-BHGytgzq.js → diagram-OA4YK3LP-6UeJa1HZ.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-BW70jDhO.js → diagram-WEI45ONY-Di2QIkMn.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-BkvYsVdX.js → ebnfDiagram-CCIWWBDH-Clm1K5fA.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-B-qynR5w.js → erDiagram-Q63AITRT-Dp2qDi_n.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-C0uDIjVs.js → flowDiagram-23GEKE2U-C46rLAR2.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-jHMmeGdm.js → ganttDiagram-NO4QXBWP-DOiDcHms.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-Cyp33RGh.js → gitGraphDiagram-IHSO6WYX-BOrXbgha.js} +1 -1
- package/dist/client/assets/{index-DPxgzRe_.js → index-COymhGhN.js} +72 -72
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BX1T5ZRV.js → infoDiagram-FWYZ7A6U-CdkS3Dfg.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-BehchXvq.js → ishikawaDiagram-FXEZZL3T-rqxY2FYN.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-Bdy1ed1k.js → journeyDiagram-5HDEW3XC-C9qqTWX3.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-C7n-XF4G.js → kanban-definition-HUTT4EX6-DmiQ6p00.js} +1 -1
- package/dist/client/assets/{linear-Cj0QkFq-.js → linear-CV4QbX1j.js} +1 -1
- package/dist/client/assets/{mermaid.core-kFgOOXNm.js → mermaid.core-ioXNdjpL.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-X3xTk9oE.js → mindmap-definition-LN4V7U3C-qmk3HlIh.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DHr3LWfT.js → pegDiagram-2B236MQR-B5x--HGM.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-Cgk4qJXy.js → pieDiagram-ENE6RG2P-BiPzRE89.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-Db452gS6.js → quadrantDiagram-ABIIQ3AL-DWSljFGl.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-Ch79_eAD.js → railroadDiagram-RFXS5EU6-DC5NPyZJ.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-WeVLc483.js → requirementDiagram-TGXJPOKE-DQRJohSF.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-2ClPBKFi.js → sankeyDiagram-HTMAVEWB-DSA0rGbL.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-DMOUB-GA.js → sequenceDiagram-DBY2YBRQ-C5P5a5rg.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-QASuTy1-.js → sizeCapture-X5ZJPWSS-C0-OJWn8.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-BNeDpvX-.js → stateDiagram-2N3HPSRC-Biektd4J.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-CqyLBZuz.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-DvuVVUtW.js → swimlanes-5IMT3BWC-CeZS6uBf.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-Cl_Lmma0.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-DuTo7eYA.js → timeline-definition-FHXFAJF6-DbcHM5pN.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-Do7RZnUg.js → vennDiagram-L72KCM5P-Dhv6WXnx.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-BSvQIlfU.js → wardleyDiagram-EHGQE667-8ef6C53J.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-BONZO9-F.js → xychartDiagram-FW5EYKEG-DihvzNhK.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/sw.js +1 -1
- package/dist/server/cli.js +17 -2
- package/dist/server/entry.js +1 -0
- package/dist/server/routes/filesystem.js +9 -3
- package/dist/server/routes/terminal.js +30 -2
- package/dist/server/utils/terminalColorEnvironment.js +46 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-DXiU4T4d.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CreSS-cc.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CreSS-cc.js +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-DUPNitKK.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-BhOFXgln.js +0 -8
package/.desktop-dist/main.js
CHANGED
|
@@ -9,7 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { desktopStatusTooltip, formatCompactDesktopStatus, menuBarStatusRows, mergeServiceActivity, nextServiceOrigin, normalizeServiceActivity, summarizeServiceActivity, } from './activityStatus.js';
|
|
12
|
-
import { checkForDesktopUpdates, configureDesktopUpdater, ensureLatestRuntime, getDesktopUpdateState, installDownloadedDesktopUpdate, subscribeDesktopUpdateState, } from './updater.js';
|
|
12
|
+
import { checkForDesktopUpdates, checkForRuntimeUpdates, configureDesktopUpdater, ensureLatestRuntime, getDesktopUpdateState, getDesktopRuntimeUpdateState, installDownloadedDesktopUpdate, markDesktopRuntimeRestarting, markDesktopRuntimeRunning, subscribeDesktopRuntimeUpdateState, subscribeDesktopUpdateState, } from './updater.js';
|
|
13
13
|
import { resolvePackagedRuntime, rollbackDownloadedRuntime, } from './runtime.js';
|
|
14
14
|
import { isExternalLinkStagingUrl, isSafeExternalUrl } from './externalLinks.js';
|
|
15
15
|
import { shouldThrottleDesktopRenderer } from './windowPolicy.js';
|
|
@@ -24,6 +24,7 @@ const DEFAULT_LOCAL_URL = 'http://localhost:9834';
|
|
|
24
24
|
const PROTOCOL_VERSION = 1;
|
|
25
25
|
const HEALTH_TIMEOUT_MS = 3_500;
|
|
26
26
|
const START_TIMEOUT_MS = 90_000;
|
|
27
|
+
const RESTORE_LOAD_TIMEOUT_MS = 15_000;
|
|
27
28
|
const localServiceCertificatePath = path.join(termdockDir, 'certs', 'termdock-local.pem');
|
|
28
29
|
const sessionTrustedCertificateTargets = new Set();
|
|
29
30
|
const sessionTrustedCertificateAuthorities = new Map();
|
|
@@ -51,6 +52,8 @@ function triggerLocalNetworkPermission() {
|
|
|
51
52
|
socket.send(Buffer.from('Termdock'), 5353, '224.0.0.251', close);
|
|
52
53
|
}
|
|
53
54
|
let mainWindow = null;
|
|
55
|
+
let startupRestoreActive = false;
|
|
56
|
+
let startupProgressMessage = '正在启动 Termdock Desktop…';
|
|
54
57
|
const serviceWindows = new Map();
|
|
55
58
|
const windowServiceOrigins = new WeakMap();
|
|
56
59
|
const restorableServiceWindows = new WeakSet();
|
|
@@ -752,6 +755,7 @@ async function probeServiceWithCertificateAuthority(rawUrl, certificateAuthority
|
|
|
752
755
|
protocolVersion: typeof metadata?.protocolVersion === 'number'
|
|
753
756
|
? metadata.protocolVersion
|
|
754
757
|
: undefined,
|
|
758
|
+
desktopManaged: metadata?.desktopManaged === true,
|
|
755
759
|
};
|
|
756
760
|
}
|
|
757
761
|
}
|
|
@@ -795,6 +799,7 @@ async function probeService(rawUrl) {
|
|
|
795
799
|
protocolVersion: typeof metadata.protocolVersion === 'number'
|
|
796
800
|
? metadata.protocolVersion
|
|
797
801
|
: undefined,
|
|
802
|
+
desktopManaged: metadata.desktopManaged === true,
|
|
798
803
|
};
|
|
799
804
|
}
|
|
800
805
|
}
|
|
@@ -811,7 +816,7 @@ async function probeService(rawUrl) {
|
|
|
811
816
|
return { ok: false, url, error: message };
|
|
812
817
|
}
|
|
813
818
|
}
|
|
814
|
-
async function probeServiceWithLocalNetworkPermission(rawUrl) {
|
|
819
|
+
async function probeServiceWithLocalNetworkPermission(rawUrl, options = {}) {
|
|
815
820
|
let normalizedUrl;
|
|
816
821
|
try {
|
|
817
822
|
normalizedUrl = normalizeServiceUrl(rawUrl);
|
|
@@ -838,6 +843,8 @@ async function probeServiceWithLocalNetworkPermission(rawUrl) {
|
|
|
838
843
|
return probe;
|
|
839
844
|
}
|
|
840
845
|
if (canOfferCertificateTrust(probe.url) && isCertificateTrustError(probe.error)) {
|
|
846
|
+
if (options.interactive === false)
|
|
847
|
+
return probe;
|
|
841
848
|
const certificate = await requestCertificateTrust(probe.url);
|
|
842
849
|
if (!certificate) {
|
|
843
850
|
return { ...probe, error: 'HTTPS 证书尚未受信任' };
|
|
@@ -847,6 +854,8 @@ async function probeServiceWithLocalNetworkPermission(rawUrl) {
|
|
|
847
854
|
if (!isLocalNetworkTarget(parsedProbeUrl) || !looksLikeLocalNetworkPermissionError(probe.error)) {
|
|
848
855
|
return probe;
|
|
849
856
|
}
|
|
857
|
+
if (options.interactive === false)
|
|
858
|
+
return probe;
|
|
850
859
|
if (!(await requestLocalNetworkPermissionRetry(probe.url))) {
|
|
851
860
|
return { ...probe, error: 'Termdock 尚未获得 macOS 本地网络权限' };
|
|
852
861
|
}
|
|
@@ -967,11 +976,16 @@ async function snapshot() {
|
|
|
967
976
|
}
|
|
968
977
|
function desktopRuntimeEnv(runtime = runtimePaths()) {
|
|
969
978
|
const currentPath = process.env.PATH ?? '/usr/bin:/bin:/usr/sbin:/sbin';
|
|
979
|
+
const userBinPaths = [
|
|
980
|
+
path.join(os.homedir(), '.local', 'bin'),
|
|
981
|
+
path.join(os.homedir(), 'bin'),
|
|
982
|
+
path.join(os.homedir(), '.npm-global', 'bin'),
|
|
983
|
+
];
|
|
970
984
|
return {
|
|
971
985
|
...process.env,
|
|
972
986
|
LANG: process.env.LANG || 'en_US.UTF-8',
|
|
973
987
|
LC_CTYPE: process.env.LC_CTYPE || process.env.LANG || 'en_US.UTF-8',
|
|
974
|
-
PATH: [runtime.toolchainBin, '/opt/homebrew/bin', '/usr/local/bin', currentPath]
|
|
988
|
+
PATH: [runtime.toolchainBin, ...userBinPaths, '/opt/homebrew/bin', '/usr/local/bin', currentPath]
|
|
975
989
|
.filter(Boolean)
|
|
976
990
|
.join(path.delimiter),
|
|
977
991
|
TERMDOCK_DESKTOP: '1',
|
|
@@ -1114,9 +1128,35 @@ async function startLocalService() {
|
|
|
1114
1128
|
throw new Error(`桌面版服务启动失败:${probe.error ?? '未知错误'}。日志:${path.join(termdockDir, 'server.log')}`);
|
|
1115
1129
|
}
|
|
1116
1130
|
await ensureTmuxUtf8Environment(runtime);
|
|
1131
|
+
markDesktopRuntimeRunning(runtime.version);
|
|
1117
1132
|
await connectWindow(probe.url);
|
|
1118
1133
|
return probe;
|
|
1119
1134
|
}
|
|
1135
|
+
async function restartDesktopManagedRuntime() {
|
|
1136
|
+
const status = await getLocalServiceStatus();
|
|
1137
|
+
if (status.running) {
|
|
1138
|
+
if (status.probe?.desktopManaged !== true) {
|
|
1139
|
+
throw new Error('当前本机服务不是由桌面版启动的,不能由桌面版自动重启。');
|
|
1140
|
+
}
|
|
1141
|
+
markDesktopRuntimeRestarting();
|
|
1142
|
+
const pid = status.state?.pid;
|
|
1143
|
+
if (!pid || !isProcessRunning(pid)) {
|
|
1144
|
+
throw new Error('桌面服务状态已失效,请重新打开连接中心。');
|
|
1145
|
+
}
|
|
1146
|
+
process.kill(pid, 'SIGTERM');
|
|
1147
|
+
const deadline = Date.now() + 15_000;
|
|
1148
|
+
while (isProcessRunning(pid) && Date.now() < deadline) {
|
|
1149
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
1150
|
+
}
|
|
1151
|
+
if (isProcessRunning(pid))
|
|
1152
|
+
throw new Error(`桌面服务 PID ${pid} 未能正常退出。`);
|
|
1153
|
+
const latest = readServerState();
|
|
1154
|
+
if (latest?.pid === pid)
|
|
1155
|
+
fs.rmSync(serverStatePath, { force: true });
|
|
1156
|
+
}
|
|
1157
|
+
await startLocalService();
|
|
1158
|
+
return getDesktopRuntimeUpdateState();
|
|
1159
|
+
}
|
|
1120
1160
|
function shellQuote(value) {
|
|
1121
1161
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1122
1162
|
}
|
|
@@ -1158,46 +1198,16 @@ async function installCli() {
|
|
|
1158
1198
|
return snapshot();
|
|
1159
1199
|
}
|
|
1160
1200
|
async function connectWindow(rawUrl, options = {}) {
|
|
1161
|
-
let probe = await probeServiceWithLocalNetworkPermission(rawUrl
|
|
1201
|
+
let probe = await probeServiceWithLocalNetworkPermission(rawUrl, {
|
|
1202
|
+
interactive: options.focus !== false,
|
|
1203
|
+
});
|
|
1162
1204
|
if (!probe.ok)
|
|
1163
1205
|
return probe;
|
|
1164
1206
|
const parsed = new URL(probe.url);
|
|
1165
1207
|
const key = parsed.origin;
|
|
1166
1208
|
const existingWindow = serviceWindows.get(key);
|
|
1167
1209
|
if (existingWindow && !existingWindow.isDestroyed()) {
|
|
1168
|
-
|
|
1169
|
-
if (options.updateLastConnection !== false)
|
|
1170
|
-
config.lastConnectionUrl = probe.url;
|
|
1171
|
-
config.openConnectionUrls = [
|
|
1172
|
-
...config.openConnectionUrls.filter((url) => {
|
|
1173
|
-
try {
|
|
1174
|
-
return new URL(url).origin !== key;
|
|
1175
|
-
}
|
|
1176
|
-
catch {
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
1179
|
-
}),
|
|
1180
|
-
probe.url,
|
|
1181
|
-
];
|
|
1182
|
-
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
1183
|
-
if (existing)
|
|
1184
|
-
existing.lastConnectedAt = Date.now();
|
|
1185
|
-
writeDesktopConfig(config);
|
|
1186
|
-
if (options.focus !== false) {
|
|
1187
|
-
mainWindow?.hide();
|
|
1188
|
-
showAndFocusWindow(existingWindow);
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
existingWindow.showInactive();
|
|
1192
|
-
}
|
|
1193
|
-
return probe;
|
|
1194
|
-
}
|
|
1195
|
-
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
1196
|
-
serviceWindows.set(key, workspaceWindow);
|
|
1197
|
-
broadcastServiceActivity();
|
|
1198
|
-
while (!workspaceWindow.isDestroyed()) {
|
|
1199
|
-
try {
|
|
1200
|
-
await workspaceWindow.loadURL(probe.url);
|
|
1210
|
+
if (options.persist !== false) {
|
|
1201
1211
|
const config = readDesktopConfig();
|
|
1202
1212
|
if (options.updateLastConnection !== false)
|
|
1203
1213
|
config.lastConnectionUrl = probe.url;
|
|
@@ -1216,6 +1226,54 @@ async function connectWindow(rawUrl, options = {}) {
|
|
|
1216
1226
|
if (existing)
|
|
1217
1227
|
existing.lastConnectedAt = Date.now();
|
|
1218
1228
|
writeDesktopConfig(config);
|
|
1229
|
+
}
|
|
1230
|
+
if (options.focus !== false) {
|
|
1231
|
+
mainWindow?.hide();
|
|
1232
|
+
showAndFocusWindow(existingWindow);
|
|
1233
|
+
}
|
|
1234
|
+
else {
|
|
1235
|
+
existingWindow.showInactive();
|
|
1236
|
+
}
|
|
1237
|
+
return probe;
|
|
1238
|
+
}
|
|
1239
|
+
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
1240
|
+
serviceWindows.set(key, workspaceWindow);
|
|
1241
|
+
broadcastServiceActivity();
|
|
1242
|
+
while (!workspaceWindow.isDestroyed()) {
|
|
1243
|
+
try {
|
|
1244
|
+
const load = workspaceWindow.loadURL(probe.url);
|
|
1245
|
+
if (options.focus === false) {
|
|
1246
|
+
await Promise.race([
|
|
1247
|
+
load,
|
|
1248
|
+
new Promise((_resolve, reject) => {
|
|
1249
|
+
const timer = setTimeout(() => reject(new Error('恢复连接超时')), RESTORE_LOAD_TIMEOUT_MS);
|
|
1250
|
+
timer.unref();
|
|
1251
|
+
}),
|
|
1252
|
+
]);
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
await load;
|
|
1256
|
+
}
|
|
1257
|
+
if (options.persist !== false) {
|
|
1258
|
+
const config = readDesktopConfig();
|
|
1259
|
+
if (options.updateLastConnection !== false)
|
|
1260
|
+
config.lastConnectionUrl = probe.url;
|
|
1261
|
+
config.openConnectionUrls = [
|
|
1262
|
+
...config.openConnectionUrls.filter((url) => {
|
|
1263
|
+
try {
|
|
1264
|
+
return new URL(url).origin !== key;
|
|
1265
|
+
}
|
|
1266
|
+
catch {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
}),
|
|
1270
|
+
probe.url,
|
|
1271
|
+
];
|
|
1272
|
+
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
1273
|
+
if (existing)
|
|
1274
|
+
existing.lastConnectedAt = Date.now();
|
|
1275
|
+
writeDesktopConfig(config);
|
|
1276
|
+
}
|
|
1219
1277
|
restorableServiceWindows.add(workspaceWindow);
|
|
1220
1278
|
if (options.focus !== false) {
|
|
1221
1279
|
mainWindow?.hide();
|
|
@@ -1231,6 +1289,10 @@ async function connectWindow(rawUrl, options = {}) {
|
|
|
1231
1289
|
}
|
|
1232
1290
|
catch (error) {
|
|
1233
1291
|
const message = networkErrorDetails(error);
|
|
1292
|
+
if (options.focus === false) {
|
|
1293
|
+
workspaceWindow.destroy();
|
|
1294
|
+
return { ...probe, ok: false, error: message };
|
|
1295
|
+
}
|
|
1234
1296
|
if (isLocalNetworkTarget(parsed) && looksLikeLocalNetworkPermissionError(error)) {
|
|
1235
1297
|
if (await requestLocalNetworkPermissionRetry(probe.url))
|
|
1236
1298
|
continue;
|
|
@@ -1260,6 +1322,29 @@ async function showConnectionCenter() {
|
|
|
1260
1322
|
if (mainWindow)
|
|
1261
1323
|
showAndFocusWindow(mainWindow);
|
|
1262
1324
|
}
|
|
1325
|
+
async function showStartupProgress(message) {
|
|
1326
|
+
if (!startupRestoreActive)
|
|
1327
|
+
return;
|
|
1328
|
+
startupProgressMessage = message;
|
|
1329
|
+
await showConnectionCenter();
|
|
1330
|
+
if (!startupRestoreActive || !mainWindow || mainWindow.isDestroyed())
|
|
1331
|
+
return;
|
|
1332
|
+
mainWindow.webContents.send('desktop:startup-progress', startupProgressMessage);
|
|
1333
|
+
}
|
|
1334
|
+
function updateStartupProgress(message) {
|
|
1335
|
+
if (!startupRestoreActive)
|
|
1336
|
+
return;
|
|
1337
|
+
startupProgressMessage = message;
|
|
1338
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
1339
|
+
mainWindow.webContents.send('desktop:startup-progress', startupProgressMessage);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
function finishStartupProgress() {
|
|
1343
|
+
startupRestoreActive = false;
|
|
1344
|
+
if (!mainWindow || mainWindow.isDestroyed())
|
|
1345
|
+
return;
|
|
1346
|
+
mainWindow.webContents.send('desktop:startup-progress', null);
|
|
1347
|
+
}
|
|
1263
1348
|
function createDesktopWindow(options) {
|
|
1264
1349
|
const serviceSession = options
|
|
1265
1350
|
? session.fromPartition(`persist:termdock-service-${crypto
|
|
@@ -1555,6 +1640,9 @@ function installIpcHandlers() {
|
|
|
1555
1640
|
ipcMain.handle('desktop:update-state', () => getDesktopUpdateState());
|
|
1556
1641
|
ipcMain.handle('desktop:check-update', () => checkForDesktopUpdates());
|
|
1557
1642
|
ipcMain.handle('desktop:install-update', () => installDownloadedDesktopUpdate());
|
|
1643
|
+
ipcMain.handle('desktop:runtime-update-state', () => getDesktopRuntimeUpdateState());
|
|
1644
|
+
ipcMain.handle('desktop:check-runtime-update', () => checkForRuntimeUpdates());
|
|
1645
|
+
ipcMain.handle('desktop:restart-runtime', () => restartDesktopManagedRuntime());
|
|
1558
1646
|
ipcMain.handle('desktop:show-connection-center', () => showConnectionCenter());
|
|
1559
1647
|
ipcMain.handle('desktop:reveal-data-directory', async () => {
|
|
1560
1648
|
fs.mkdirSync(termdockDir, { recursive: true, mode: 0o700 });
|
|
@@ -1838,14 +1926,34 @@ app.whenReady().then(async () => {
|
|
|
1838
1926
|
window.webContents.send('desktop:update-state-changed', state);
|
|
1839
1927
|
}
|
|
1840
1928
|
});
|
|
1929
|
+
subscribeDesktopRuntimeUpdateState((state) => {
|
|
1930
|
+
for (const window of BrowserWindow.getAllWindows()) {
|
|
1931
|
+
if (!window.isDestroyed())
|
|
1932
|
+
window.webContents.send('desktop:runtime-update-state-changed', state);
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1841
1935
|
const startupConfig = readDesktopConfig();
|
|
1842
1936
|
const restoreUrls = startupConfig.openConnectionUrls;
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1937
|
+
startupRestoreActive = true;
|
|
1938
|
+
const startupProgressTimer = setTimeout(() => {
|
|
1939
|
+
void showStartupProgress('正在检查本机 Runtime 和已保存连接…');
|
|
1940
|
+
}, 350);
|
|
1941
|
+
startupProgressTimer.unref();
|
|
1942
|
+
let completedRestores = 0;
|
|
1943
|
+
updateStartupProgress(`正在恢复连接 0/${restoreUrls.length}…`);
|
|
1944
|
+
const restoreResults = await Promise.all(restoreUrls.map(async (url) => {
|
|
1945
|
+
const probe = await connectWindow(url, {
|
|
1946
|
+
focus: false,
|
|
1947
|
+
updateLastConnection: false,
|
|
1948
|
+
persist: false,
|
|
1949
|
+
});
|
|
1950
|
+
completedRestores += 1;
|
|
1951
|
+
updateStartupProgress(`正在恢复连接 ${completedRestores}/${restoreUrls.length}…`);
|
|
1952
|
+
return probe;
|
|
1953
|
+
}));
|
|
1954
|
+
const restoredWindowCount = restoreResults.filter((probe) => probe.ok).length;
|
|
1955
|
+
clearTimeout(startupProgressTimer);
|
|
1956
|
+
finishStartupProgress();
|
|
1849
1957
|
if (restoredWindowCount > 0) {
|
|
1850
1958
|
let preferredOrigin = null;
|
|
1851
1959
|
try {
|