dsh-clean-desktop-shell 0.1.11 → 0.1.13
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/CONTRIBUTORS.md +25 -19
- package/README.en.md +422 -333
- package/README.md +363 -278
- package/electron/aumid.js +17 -17
- package/electron/crashGuard.js +60 -60
- package/electron/error.html +113 -113
- package/electron/main.js +203 -187
- package/electron/outage.js +60 -0
- package/electron/preload.js +216 -49
- package/electron/progress-preload.js +11 -11
- package/electron/progress.html +79 -79
- package/electron/progress.js +56 -56
- package/electron/service.js +559 -458
- package/electron/shortcut.js +122 -122
- package/electron/tray.js +232 -232
- package/electron/window.js +603 -264
- package/lib/client.js +41 -6
- package/lib/common.js +74 -74
- package/lib/icon.js +85 -51
- package/lib/index.js +90 -15
- package/lib/runtime.js +423 -423
- package/package.json +140 -127
- package/scripts/check-syntax.mjs +54 -54
- package/scripts/selftest-recovery.mjs +140 -0
- package/scripts/selftest-runtime.mjs +90 -90
- package/src/client/client.js +41 -6
- package/src/host/common.js +74 -74
- package/src/host/icon.js +85 -51
- package/src/host/index.js +90 -15
- package/src/host/runtime.js +423 -423
- package/version.txt +1 -1
package/electron/aumid.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { app } from 'electron'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* AppUserModelID, shared by main (process-level), window (taskbar button)
|
|
5
|
-
* and shortcut (Start-menu .lnk) so the three always agree.
|
|
6
|
-
*
|
|
7
|
-
* Why plugin mode gets its own ID: Windows caches the taskbar button icon
|
|
8
|
-
* per AUMID. If the plugin shell reuses the installer's ID that ever ran
|
|
9
|
-
* with Electron's default icon (an early dev launch / an old build), the
|
|
10
|
-
* taskbar keeps showing that stale icon — no window `icon`, `setAppDetails`
|
|
11
|
-
* or exe patch can override the cache. A distinct ID makes Windows treat it
|
|
12
|
-
* as a brand-new app and re-read the icon. It also keeps the plugin shell
|
|
13
|
-
* from merging with an installed copy on the same machine.
|
|
14
|
-
*/
|
|
15
|
-
export const APP_USER_MODEL_ID = app.isPackaged
|
|
16
|
-
? 'com.icather.dsh-clean-desktop-shell'
|
|
17
|
-
: 'com.icather.dsh-clean-desktop-shell.plugin'
|
|
1
|
+
import { app } from 'electron'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AppUserModelID, shared by main (process-level), window (taskbar button)
|
|
5
|
+
* and shortcut (Start-menu .lnk) so the three always agree.
|
|
6
|
+
*
|
|
7
|
+
* Why plugin mode gets its own ID: Windows caches the taskbar button icon
|
|
8
|
+
* per AUMID. If the plugin shell reuses the installer's ID that ever ran
|
|
9
|
+
* with Electron's default icon (an early dev launch / an old build), the
|
|
10
|
+
* taskbar keeps showing that stale icon — no window `icon`, `setAppDetails`
|
|
11
|
+
* or exe patch can override the cache. A distinct ID makes Windows treat it
|
|
12
|
+
* as a brand-new app and re-read the icon. It also keeps the plugin shell
|
|
13
|
+
* from merging with an installed copy on the same machine.
|
|
14
|
+
*/
|
|
15
|
+
export const APP_USER_MODEL_ID = app.isPackaged
|
|
16
|
+
? 'com.icather.dsh-clean-desktop-shell'
|
|
17
|
+
: 'com.icather.dsh-clean-desktop-shell.plugin'
|
package/electron/crashGuard.js
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Crash guard — last-resort observability for the Electron main process.
|
|
3
|
-
*
|
|
4
|
-
* Unhandled errors used to vanish (a GUI process has no console), leaving
|
|
5
|
-
* only "the window didn't come up". Mirror the host half's diagnostics
|
|
6
|
-
* philosophy (reportLaunchFailure): append one entry per event to
|
|
7
|
-
* userData/shell-crash.log so a user can attach something actionable to a
|
|
8
|
-
* bug report.
|
|
9
|
-
*
|
|
10
|
-
* The log is capped: past 128 KB the oldest content is dropped, so a
|
|
11
|
-
* repeating failure can never grow the file without bound.
|
|
12
|
-
*/
|
|
13
|
-
import { app } from 'electron'
|
|
14
|
-
import { appendFileSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
15
|
-
import { join } from 'node:path'
|
|
16
|
-
|
|
17
|
-
const MAX_BYTES = 128 * 1024
|
|
18
|
-
const KEEP_BYTES = 32 * 1024
|
|
19
|
-
|
|
20
|
-
function crashLogFile() {
|
|
21
|
-
return join(app.getPath('userData'), 'shell-crash.log')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Where the crash log lives (named in docs / bug-report guidance). */
|
|
25
|
-
export function crashLogPath() {
|
|
26
|
-
return crashLogFile()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function append(entry) {
|
|
30
|
-
try {
|
|
31
|
-
const file = crashLogFile()
|
|
32
|
-
try {
|
|
33
|
-
if (statSync(file).size > MAX_BYTES) {
|
|
34
|
-
// Cap: keep only the newest KEEP_BYTES when the log outgrows itself.
|
|
35
|
-
const tail = readFileSync(file, 'utf8').slice(-KEEP_BYTES)
|
|
36
|
-
writeFileSync(file, tail, 'utf8')
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
// First write (file missing) — nothing to cap.
|
|
40
|
-
}
|
|
41
|
-
appendFileSync(file, entry, 'utf8')
|
|
42
|
-
} catch {
|
|
43
|
-
// Nowhere left to report to — stay silent rather than crash the guard.
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Install process-level handlers. Errors are logged and swallowed: for a
|
|
49
|
-
* tray-resident shell, surviving one stray async rejection beats dying on
|
|
50
|
-
* it — the process-level state after a handled uncaughtException is the
|
|
51
|
-
* same "best effort" the shell already applies everywhere else.
|
|
52
|
-
*/
|
|
53
|
-
export function setupCrashGuard() {
|
|
54
|
-
process.on('uncaughtException', (err) => {
|
|
55
|
-
append(`[${new Date().toISOString()}] uncaughtException: ${err?.stack || err}\n`)
|
|
56
|
-
})
|
|
57
|
-
process.on('unhandledRejection', (reason) => {
|
|
58
|
-
append(`[${new Date().toISOString()}] unhandledRejection: ${reason?.stack || reason}\n`)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Crash guard — last-resort observability for the Electron main process.
|
|
3
|
+
*
|
|
4
|
+
* Unhandled errors used to vanish (a GUI process has no console), leaving
|
|
5
|
+
* only "the window didn't come up". Mirror the host half's diagnostics
|
|
6
|
+
* philosophy (reportLaunchFailure): append one entry per event to
|
|
7
|
+
* userData/shell-crash.log so a user can attach something actionable to a
|
|
8
|
+
* bug report.
|
|
9
|
+
*
|
|
10
|
+
* The log is capped: past 128 KB the oldest content is dropped, so a
|
|
11
|
+
* repeating failure can never grow the file without bound.
|
|
12
|
+
*/
|
|
13
|
+
import { app } from 'electron'
|
|
14
|
+
import { appendFileSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
const MAX_BYTES = 128 * 1024
|
|
18
|
+
const KEEP_BYTES = 32 * 1024
|
|
19
|
+
|
|
20
|
+
function crashLogFile() {
|
|
21
|
+
return join(app.getPath('userData'), 'shell-crash.log')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Where the crash log lives (named in docs / bug-report guidance). */
|
|
25
|
+
export function crashLogPath() {
|
|
26
|
+
return crashLogFile()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function append(entry) {
|
|
30
|
+
try {
|
|
31
|
+
const file = crashLogFile()
|
|
32
|
+
try {
|
|
33
|
+
if (statSync(file).size > MAX_BYTES) {
|
|
34
|
+
// Cap: keep only the newest KEEP_BYTES when the log outgrows itself.
|
|
35
|
+
const tail = readFileSync(file, 'utf8').slice(-KEEP_BYTES)
|
|
36
|
+
writeFileSync(file, tail, 'utf8')
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// First write (file missing) — nothing to cap.
|
|
40
|
+
}
|
|
41
|
+
appendFileSync(file, entry, 'utf8')
|
|
42
|
+
} catch {
|
|
43
|
+
// Nowhere left to report to — stay silent rather than crash the guard.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Install process-level handlers. Errors are logged and swallowed: for a
|
|
49
|
+
* tray-resident shell, surviving one stray async rejection beats dying on
|
|
50
|
+
* it — the process-level state after a handled uncaughtException is the
|
|
51
|
+
* same "best effort" the shell already applies everywhere else.
|
|
52
|
+
*/
|
|
53
|
+
export function setupCrashGuard() {
|
|
54
|
+
process.on('uncaughtException', (err) => {
|
|
55
|
+
append(`[${new Date().toISOString()}] uncaughtException: ${err?.stack || err}\n`)
|
|
56
|
+
})
|
|
57
|
+
process.on('unhandledRejection', (reason) => {
|
|
58
|
+
append(`[${new Date().toISOString()}] unhandledRejection: ${reason?.stack || reason}\n`)
|
|
59
|
+
})
|
|
60
|
+
}
|
package/electron/error.html
CHANGED
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'" />
|
|
6
|
-
<title>后端未连接</title>
|
|
7
|
-
<style>
|
|
8
|
-
html, body {
|
|
9
|
-
height: 100%;
|
|
10
|
-
margin: 0;
|
|
11
|
-
background: #10131A;
|
|
12
|
-
color: #9aa3b2;
|
|
13
|
-
font-family: system-ui, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
14
|
-
-webkit-user-select: none;
|
|
15
|
-
user-select: none;
|
|
16
|
-
}
|
|
17
|
-
.wrap {
|
|
18
|
-
height: 100%;
|
|
19
|
-
display: flex;
|
|
20
|
-
flex-direction: column;
|
|
21
|
-
align-items: center;
|
|
22
|
-
justify-content: center;
|
|
23
|
-
gap: 10px;
|
|
24
|
-
}
|
|
25
|
-
.whale {
|
|
26
|
-
width: 72px;
|
|
27
|
-
height: 72px;
|
|
28
|
-
border-radius: 18px;
|
|
29
|
-
background: #1b2330;
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
font-size: 22px;
|
|
34
|
-
font-weight: 700;
|
|
35
|
-
letter-spacing: 0.5px;
|
|
36
|
-
color: #cdd3de;
|
|
37
|
-
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.45));
|
|
38
|
-
}
|
|
39
|
-
h2 {
|
|
40
|
-
margin: 0;
|
|
41
|
-
font-size: 17px;
|
|
42
|
-
font-weight: 600;
|
|
43
|
-
color: #e8eaf0;
|
|
44
|
-
}
|
|
45
|
-
p {
|
|
46
|
-
margin: 0;
|
|
47
|
-
font-size: 13px;
|
|
48
|
-
}
|
|
49
|
-
button {
|
|
50
|
-
padding: 9px 22px;
|
|
51
|
-
font-size: 13px;
|
|
52
|
-
color: #e8eaf0;
|
|
53
|
-
background: #232d3d;
|
|
54
|
-
border: 1px solid #33405a;
|
|
55
|
-
border-radius: 8px;
|
|
56
|
-
cursor: pointer;
|
|
57
|
-
}
|
|
58
|
-
button:hover { background: #2b3750; }
|
|
59
|
-
button:active { background: #202a3c; }
|
|
60
|
-
.actions {
|
|
61
|
-
display: flex;
|
|
62
|
-
gap: 10px;
|
|
63
|
-
margin-top: 4px;
|
|
64
|
-
}
|
|
65
|
-
.folder-btn {
|
|
66
|
-
margin-top: 2px;
|
|
67
|
-
padding: 4px 8px;
|
|
68
|
-
font-size: 12px;
|
|
69
|
-
color: #6b7686;
|
|
70
|
-
background: none;
|
|
71
|
-
border: none;
|
|
72
|
-
border-radius: 6px;
|
|
73
|
-
text-decoration: underline;
|
|
74
|
-
}
|
|
75
|
-
.folder-btn:hover { color: #9aa3b2; background: none; }
|
|
76
|
-
.hint {
|
|
77
|
-
font-size: 12px;
|
|
78
|
-
opacity: 0.55;
|
|
79
|
-
}
|
|
80
|
-
</style>
|
|
81
|
-
</head>
|
|
82
|
-
<body>
|
|
83
|
-
<div class="wrap">
|
|
84
|
-
<div class="whale">DSH</div>
|
|
85
|
-
<h2>后端未连接</h2>
|
|
86
|
-
<p>请启动 dsh 后端,或使用下方按钮</p>
|
|
87
|
-
<button id="retry">重新加载</button>
|
|
88
|
-
<div class="actions">
|
|
89
|
-
<button id="start">启动后端</button>
|
|
90
|
-
<button id="detect">自动探测后端</button>
|
|
91
|
-
</div>
|
|
92
|
-
<button id="folder" class="folder-btn">设置后端安装文件夹</button>
|
|
93
|
-
<p class="hint">后端启动后本窗口会自动刷新</p>
|
|
94
|
-
</div>
|
|
95
|
-
<script>
|
|
96
|
-
// Injected by preload (sandbox-safe). Falls back to a plain reload.
|
|
97
|
-
var api = window.shellAPI || {};
|
|
98
|
-
document.getElementById('retry').addEventListener('click', function () {
|
|
99
|
-
if (api.reload) api.reload();
|
|
100
|
-
else location.reload();
|
|
101
|
-
});
|
|
102
|
-
var startBtn = document.getElementById('start');
|
|
103
|
-
if (api.startBackend) startBtn.addEventListener('click', api.startBackend);
|
|
104
|
-
else startBtn.style.display = 'none';
|
|
105
|
-
var detectBtn = document.getElementById('detect');
|
|
106
|
-
if (api.detectBackend) detectBtn.addEventListener('click', api.detectBackend);
|
|
107
|
-
else detectBtn.style.display = 'none';
|
|
108
|
-
var folderBtn = document.getElementById('folder');
|
|
109
|
-
if (api.chooseBackendFolder) folderBtn.addEventListener('click', api.chooseBackendFolder);
|
|
110
|
-
else folderBtn.style.display = 'none';
|
|
111
|
-
</script>
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'" />
|
|
6
|
+
<title>后端未连接</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body {
|
|
9
|
+
height: 100%;
|
|
10
|
+
margin: 0;
|
|
11
|
+
background: #10131A;
|
|
12
|
+
color: #9aa3b2;
|
|
13
|
+
font-family: system-ui, "Segoe UI", "Microsoft YaHei", sans-serif;
|
|
14
|
+
-webkit-user-select: none;
|
|
15
|
+
user-select: none;
|
|
16
|
+
}
|
|
17
|
+
.wrap {
|
|
18
|
+
height: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
gap: 10px;
|
|
24
|
+
}
|
|
25
|
+
.whale {
|
|
26
|
+
width: 72px;
|
|
27
|
+
height: 72px;
|
|
28
|
+
border-radius: 18px;
|
|
29
|
+
background: #1b2330;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
font-size: 22px;
|
|
34
|
+
font-weight: 700;
|
|
35
|
+
letter-spacing: 0.5px;
|
|
36
|
+
color: #cdd3de;
|
|
37
|
+
filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.45));
|
|
38
|
+
}
|
|
39
|
+
h2 {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: 17px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: #e8eaf0;
|
|
44
|
+
}
|
|
45
|
+
p {
|
|
46
|
+
margin: 0;
|
|
47
|
+
font-size: 13px;
|
|
48
|
+
}
|
|
49
|
+
button {
|
|
50
|
+
padding: 9px 22px;
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
color: #e8eaf0;
|
|
53
|
+
background: #232d3d;
|
|
54
|
+
border: 1px solid #33405a;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
}
|
|
58
|
+
button:hover { background: #2b3750; }
|
|
59
|
+
button:active { background: #202a3c; }
|
|
60
|
+
.actions {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 10px;
|
|
63
|
+
margin-top: 4px;
|
|
64
|
+
}
|
|
65
|
+
.folder-btn {
|
|
66
|
+
margin-top: 2px;
|
|
67
|
+
padding: 4px 8px;
|
|
68
|
+
font-size: 12px;
|
|
69
|
+
color: #6b7686;
|
|
70
|
+
background: none;
|
|
71
|
+
border: none;
|
|
72
|
+
border-radius: 6px;
|
|
73
|
+
text-decoration: underline;
|
|
74
|
+
}
|
|
75
|
+
.folder-btn:hover { color: #9aa3b2; background: none; }
|
|
76
|
+
.hint {
|
|
77
|
+
font-size: 12px;
|
|
78
|
+
opacity: 0.55;
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<div class="wrap">
|
|
84
|
+
<div class="whale">DSH</div>
|
|
85
|
+
<h2>后端未连接</h2>
|
|
86
|
+
<p>请启动 dsh 后端,或使用下方按钮</p>
|
|
87
|
+
<button id="retry">重新加载</button>
|
|
88
|
+
<div class="actions">
|
|
89
|
+
<button id="start">启动后端</button>
|
|
90
|
+
<button id="detect">自动探测后端</button>
|
|
91
|
+
</div>
|
|
92
|
+
<button id="folder" class="folder-btn">设置后端安装文件夹</button>
|
|
93
|
+
<p class="hint">后端启动后本窗口会自动刷新</p>
|
|
94
|
+
</div>
|
|
95
|
+
<script>
|
|
96
|
+
// Injected by preload (sandbox-safe). Falls back to a plain reload.
|
|
97
|
+
var api = window.shellAPI || {};
|
|
98
|
+
document.getElementById('retry').addEventListener('click', function () {
|
|
99
|
+
if (api.reload) api.reload();
|
|
100
|
+
else location.reload();
|
|
101
|
+
});
|
|
102
|
+
var startBtn = document.getElementById('start');
|
|
103
|
+
if (api.startBackend) startBtn.addEventListener('click', api.startBackend);
|
|
104
|
+
else startBtn.style.display = 'none';
|
|
105
|
+
var detectBtn = document.getElementById('detect');
|
|
106
|
+
if (api.detectBackend) detectBtn.addEventListener('click', api.detectBackend);
|
|
107
|
+
else detectBtn.style.display = 'none';
|
|
108
|
+
var folderBtn = document.getElementById('folder');
|
|
109
|
+
if (api.chooseBackendFolder) folderBtn.addEventListener('click', api.chooseBackendFolder);
|
|
110
|
+
else folderBtn.style.display = 'none';
|
|
111
|
+
</script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|