@astralyn/sash 0.1.1 → 0.1.2
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/CHANGELOG.md +25 -0
- package/README.md +17 -5
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/api.js +14 -45
- package/dist/app-state.js +99 -0
- package/dist/autostart/command.js +24 -0
- package/dist/autostart/context.js +43 -0
- package/dist/autostart/files.js +60 -0
- package/dist/autostart/installation.js +48 -0
- package/dist/autostart/start.js +47 -0
- package/dist/autostart/windows-registry.js +88 -0
- package/dist/autostart/windows.js +59 -0
- package/dist/autostart-contract.js +31 -0
- package/dist/autostart-entry.js +10 -0
- package/dist/autostart.js +69 -0
- package/dist/cli.js +11 -11
- package/dist/commands/auto.js +15 -0
- package/dist/commands/lifecycle.js +5 -15
- package/dist/commands/logs.js +11 -4
- package/dist/commands/shared.js +2 -4
- package/dist/commands/status.js +5 -1
- package/dist/commands/update.js +7 -3
- package/dist/commands/web.js +14 -50
- package/dist/contracts.js +168 -348
- package/dist/core-config-validation.js +22 -38
- package/dist/core-update.js +126 -762
- package/dist/core.js +20 -46
- package/dist/daemon/app.js +129 -117
- package/dist/daemon/context.js +13 -7
- package/dist/daemon/entry.js +21 -38
- package/dist/daemon/errors.js +3 -0
- package/dist/daemon/handlers/autostart.js +18 -0
- package/dist/daemon/handlers/core.js +32 -9
- package/dist/daemon/handlers/daemon.js +17 -6
- package/dist/daemon/handlers/profiles.js +12 -2
- package/dist/daemon/handlers/settings.js +9 -36
- package/dist/daemon/router.js +26 -21
- package/dist/daemon/scheduler.js +3 -1
- package/dist/daemon/server.js +5 -2
- package/dist/daemon-client.js +13 -3
- package/dist/daemon-http.js +6 -0
- package/dist/daemon-lifecycle.js +19 -196
- package/dist/github.js +7 -2
- package/dist/http.js +7 -5
- package/dist/mihomo-config.js +2 -1
- package/dist/paths.js +5 -7
- package/dist/profile-model.js +87 -0
- package/dist/profile-service.js +250 -587
- package/dist/profiles.js +43 -171
- package/dist/runtime-lifecycle.js +112 -194
- package/dist/runtime-owner.js +37 -76
- package/dist/sash-client.js +40 -19
- package/dist/settings-service.js +29 -224
- package/dist/settings.js +61 -289
- package/dist/status.js +24 -16
- package/dist/supervisor.js +1 -14
- package/dist/sysproxy/common.js +5 -45
- package/dist/sysproxy/factory.js +24 -65
- package/dist/sysproxy/snapshot.js +24 -257
- package/dist/sysproxy.js +1 -4
- package/dist/system-proxy-manager.js +37 -35
- package/dist/ui/assets/{ConnectionsView-CfaOG2JV.js → ConnectionsView-BgXQM6Z4.js} +1 -1
- package/dist/ui/assets/{LogsView-BDiRATXN.js → LogsView-2f542P8z.js} +1 -1
- package/dist/ui/assets/{PaginationFooter-DTytr1iu.js → PaginationFooter-cj1tcZej.js} +1 -1
- package/dist/ui/assets/ProfileEditorDialog-C9M8uKZC.css +1 -0
- package/dist/ui/assets/ProfileEditorDialog-DD4y4GBC.js +14 -0
- package/dist/ui/assets/ProfilesView-BXU2DOA7.css +1 -0
- package/dist/ui/assets/ProfilesView-DNnzYIEY.js +7 -0
- package/dist/ui/assets/{RulesView-CjEyCN1A.js → RulesView-CtKvlckZ.js} +1 -1
- package/dist/ui/assets/SettingsView-BaeKFF_N.js +1 -0
- package/dist/ui/assets/SettingsView-CWjIe05v.css +1 -0
- package/dist/ui/assets/index-CBInDvdJ.js +19 -0
- package/dist/ui/assets/index-mOLy7fkB.css +1 -0
- package/dist/ui/assets/{theme-BwDBMsKO.js → theme-D40MF8cQ.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/docs/architecture-proposal.md +109 -0
- package/docs/autostart.md +101 -0
- package/docs/backend.md +92 -224
- package/docs/frontend.md +60 -106
- package/docs/usage.md +92 -164
- package/package.json +3 -3
- package/dist/commands/upgrade.js +0 -43
- package/dist/core-install-transaction.js +0 -114
- package/dist/core-update-coordination.js +0 -105
- package/dist/core-update-service.js +0 -245
- package/dist/managed-state-transaction.js +0 -375
- package/dist/offline-mutation.js +0 -58
- package/dist/profile-migration.js +0 -101
- package/dist/runtime-recovery.js +0 -31
- package/dist/sysproxy/darwin.js +0 -287
- package/dist/sysproxy/gnome.js +0 -181
- package/dist/sysproxy/legacy.js +0 -58
- package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +0 -1
- package/dist/ui/assets/CodeEditorModal-D1zn-jRS.js +0 -14
- package/dist/ui/assets/ProfileEditorDialog-CyyP6OF1.js +0 -1
- package/dist/ui/assets/ProfilesView-ByqdFNHN.css +0 -1
- package/dist/ui/assets/ProfilesView-hFa7-O72.js +0 -2
- package/dist/ui/assets/SettingsFileDialog-CjBSyH4K.js +0 -1
- package/dist/ui/assets/SettingsView-D5QvJId6.css +0 -1
- package/dist/ui/assets/SettingsView-DsRntRfn.js +0 -2
- package/dist/ui/assets/index-CxQTf_P9.css +0 -1
- package/dist/ui/assets/index-_mb4OfYp.js +0 -19
package/dist/http.js
CHANGED
|
@@ -87,6 +87,7 @@ export async function fetchWithRetry(url, opts = {}) {
|
|
|
87
87
|
const bodyInactivityTimeoutMs = positiveTimeout(opts.bodyInactivityTimeoutMs, 30_000, "bodyInactivityTimeoutMs");
|
|
88
88
|
const deadlineMs = positiveTimeout(opts.deadlineMs, 60_000, "deadlineMs");
|
|
89
89
|
const deadline = new AbortController();
|
|
90
|
+
const signal = opts.signal ? AbortSignal.any([deadline.signal, opts.signal]) : deadline.signal;
|
|
90
91
|
const deadlineTimer = setTimeout(() => {
|
|
91
92
|
deadline.abort(new Error(`HTTP request deadline exceeded after ${deadlineMs}ms`));
|
|
92
93
|
}, deadlineMs);
|
|
@@ -108,7 +109,7 @@ export async function fetchWithRetry(url, opts = {}) {
|
|
|
108
109
|
body: opts.body,
|
|
109
110
|
headersTimeout: headersTimeoutMs,
|
|
110
111
|
bodyTimeout: bodyInactivityTimeoutMs,
|
|
111
|
-
signal
|
|
112
|
+
signal,
|
|
112
113
|
dispatcher: pickDispatcher(opts),
|
|
113
114
|
});
|
|
114
115
|
if (RETRYABLE_STATUS.has(res.statusCode) && attempt < attempts) {
|
|
@@ -166,10 +167,10 @@ export async function fetchWithRetry(url, opts = {}) {
|
|
|
166
167
|
}
|
|
167
168
|
catch (err) {
|
|
168
169
|
lastErr = err;
|
|
169
|
-
if (
|
|
170
|
+
if (signal.aborted || attempt === attempts)
|
|
170
171
|
break;
|
|
171
|
-
await sleep(Math.min(4_000, 300 * 2 ** (attempt - 1)) + Math.random() * 200,
|
|
172
|
-
if (
|
|
172
|
+
await sleep(Math.min(4_000, 300 * 2 ** (attempt - 1)) + Math.random() * 200, signal);
|
|
173
|
+
if (signal.aborted)
|
|
173
174
|
break;
|
|
174
175
|
}
|
|
175
176
|
}
|
|
@@ -202,6 +203,7 @@ export async function downloadToFile(url, dest, opts) {
|
|
|
202
203
|
const deadlineMs = positiveTimeout(opts.deadlineMs, 15 * 60_000, "deadlineMs");
|
|
203
204
|
const maxRedirects = 5;
|
|
204
205
|
const deadline = new AbortController();
|
|
206
|
+
const signal = opts.signal ? AbortSignal.any([deadline.signal, opts.signal]) : deadline.signal;
|
|
205
207
|
const deadlineTimer = setTimeout(() => {
|
|
206
208
|
deadline.abort(new Error(`Download deadline exceeded after ${deadlineMs}ms`));
|
|
207
209
|
}, deadlineMs);
|
|
@@ -217,7 +219,7 @@ export async function downloadToFile(url, dest, opts) {
|
|
|
217
219
|
headers: { "user-agent": USER_AGENT, ...opts.headers },
|
|
218
220
|
headersTimeout: 30_000,
|
|
219
221
|
bodyTimeout: stallMs,
|
|
220
|
-
signal
|
|
222
|
+
signal,
|
|
221
223
|
dispatcher,
|
|
222
224
|
});
|
|
223
225
|
const isRedirect = res.statusCode >= 300 && res.statusCode < 400 && Boolean(res.headers.location);
|
package/dist/mihomo-config.js
CHANGED
|
@@ -227,7 +227,7 @@ export function resolveSubscriptionRedirect(initial, current, location) {
|
|
|
227
227
|
}
|
|
228
228
|
return target;
|
|
229
229
|
}
|
|
230
|
-
export async function fetchSubscriptionProfile(url) {
|
|
230
|
+
export async function fetchSubscriptionProfile(url, signal) {
|
|
231
231
|
let initial;
|
|
232
232
|
try {
|
|
233
233
|
initial = new URL(url);
|
|
@@ -247,6 +247,7 @@ export async function fetchSubscriptionProfile(url) {
|
|
|
247
247
|
throw new Error(`Subscription fetch deadline exceeded after ${PROFILE_FETCH_DEADLINE_MS}ms`);
|
|
248
248
|
}
|
|
249
249
|
res = await fetchWithRetry(current.href, {
|
|
250
|
+
signal,
|
|
250
251
|
attempts: 3,
|
|
251
252
|
deadlineMs: remainingDeadlineMs,
|
|
252
253
|
manualRedirect: true,
|
package/dist/paths.js
CHANGED
|
@@ -24,27 +24,25 @@ export function sashRoot() {
|
|
|
24
24
|
}
|
|
25
25
|
export function sashLayout(root = sashRoot()) {
|
|
26
26
|
const exeName = process.platform === "win32" ? "mihomo.exe" : "mihomo";
|
|
27
|
+
const userControlDir = process.platform === "win32"
|
|
28
|
+
? path.join(envPathOr(path.join(os.homedir(), "AppData", "Local"), process.env.LOCALAPPDATA), "Sash")
|
|
29
|
+
: path.join(root, "state");
|
|
27
30
|
return {
|
|
28
31
|
root,
|
|
29
32
|
binDir: path.join(root, "bin"),
|
|
30
33
|
coreExe: path.join(root, "bin", exeName),
|
|
31
|
-
configFile: path.join(root, "config.yaml"),
|
|
34
|
+
configFile: path.join(root, "runtime", "config.yaml"),
|
|
32
35
|
settingsFile: path.join(root, "sash.json"),
|
|
33
36
|
profilesDir: path.join(root, "profiles"),
|
|
34
|
-
profilesIndexFile: path.join(root, "profiles", "index.json"),
|
|
35
37
|
uiDir: path.join(root, "ui"),
|
|
36
38
|
stateDir: path.join(root, "state"),
|
|
37
39
|
pidFile: path.join(root, "state", "sash.pid"),
|
|
38
40
|
daemonPidFile: path.join(root, "state", "sashd.pid"),
|
|
39
41
|
daemonLeaseFile: path.join(root, "state", "sashd.lock"),
|
|
40
42
|
daemonStartLockFile: path.join(root, "state", "sashd-start.lock"),
|
|
41
|
-
runtimeOperationLockFile: path.join(root, "state", "runtime.lock"),
|
|
42
|
-
mutationLockFile: path.join(root, "state", "mutation.lock"),
|
|
43
|
-
settingsLockFile: path.join(root, "state", "settings.lock"),
|
|
44
43
|
systemProxyStateFile: path.join(root, "state", "system-proxy.json"),
|
|
45
|
-
|
|
44
|
+
systemProxyLockFile: path.join(userControlDir, "system-proxy.lock"),
|
|
46
45
|
installFile: path.join(root, "state", "install.json"),
|
|
47
|
-
coreInstallTransactionFile: path.join(root, "state", "core-install-transaction.json"),
|
|
48
46
|
coreUpdateTransactionFile: path.join(root, "state", "core-update-transaction.json"),
|
|
49
47
|
logsDir: path.join(root, "logs"),
|
|
50
48
|
coreLogFile: path.join(root, "logs", "mihomo.log"),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isCanonicalIsoTimestamp, isPlainObject } from "./json-shape.js";
|
|
2
|
+
export const DEFAULT_PROFILE_INTERVAL_HOURS = 24;
|
|
3
|
+
export const MAX_PROFILE_INTERVAL_HOURS = 24 * 365;
|
|
4
|
+
export function parseProfilesIndex(value) {
|
|
5
|
+
if (!isPlainObject(value) ||
|
|
6
|
+
Object.keys(value).some((key) => key !== "activeId" && key !== "profiles") ||
|
|
7
|
+
!Array.isArray(value.profiles) ||
|
|
8
|
+
(value.activeId !== null && typeof value.activeId !== "string"))
|
|
9
|
+
throw new Error("Profiles index has an invalid shape");
|
|
10
|
+
const profiles = value.profiles.map(parseProfileMeta);
|
|
11
|
+
const ids = new Set(profiles.map((profile) => profile.id));
|
|
12
|
+
if (ids.size !== profiles.length)
|
|
13
|
+
throw new Error("Profiles index has duplicate profile ids");
|
|
14
|
+
if (value.activeId !== null && !ids.has(value.activeId))
|
|
15
|
+
throw new Error("Selected profile is missing");
|
|
16
|
+
return { activeId: value.activeId, profiles };
|
|
17
|
+
}
|
|
18
|
+
export function parseProfileMeta(item) {
|
|
19
|
+
if (!isPlainObject(item) ||
|
|
20
|
+
typeof item.id !== "string" ||
|
|
21
|
+
!/^[0-9]+$/.test(item.id) ||
|
|
22
|
+
typeof item.revision !== "number" ||
|
|
23
|
+
!Number.isSafeInteger(item.revision) ||
|
|
24
|
+
item.revision < 1 ||
|
|
25
|
+
typeof item.name !== "string" ||
|
|
26
|
+
!item.name.trim() ||
|
|
27
|
+
item.name.length > 120 ||
|
|
28
|
+
typeof item.url !== "string" ||
|
|
29
|
+
typeof item.intervalHours !== "number" ||
|
|
30
|
+
!Number.isSafeInteger(item.intervalHours) ||
|
|
31
|
+
item.intervalHours < 0 ||
|
|
32
|
+
item.intervalHours > MAX_PROFILE_INTERVAL_HOURS ||
|
|
33
|
+
!isCanonicalIsoTimestamp(item.createdAt) ||
|
|
34
|
+
!isCanonicalIsoTimestamp(item.updatedAt))
|
|
35
|
+
throw new Error("Profiles index has invalid profile metadata");
|
|
36
|
+
if (item.url) {
|
|
37
|
+
const url = new URL(item.url);
|
|
38
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
39
|
+
throw new Error("Invalid profile URL");
|
|
40
|
+
}
|
|
41
|
+
let subInfo;
|
|
42
|
+
if (item.subInfo !== undefined) {
|
|
43
|
+
const info = item.subInfo;
|
|
44
|
+
if (!isPlainObject(info) ||
|
|
45
|
+
typeof info.upload !== "number" ||
|
|
46
|
+
!Number.isFinite(info.upload) ||
|
|
47
|
+
info.upload < 0 ||
|
|
48
|
+
typeof info.download !== "number" ||
|
|
49
|
+
!Number.isFinite(info.download) ||
|
|
50
|
+
info.download < 0 ||
|
|
51
|
+
typeof info.total !== "number" ||
|
|
52
|
+
!Number.isFinite(info.total) ||
|
|
53
|
+
info.total < 0 ||
|
|
54
|
+
(info.expire !== undefined &&
|
|
55
|
+
(typeof info.expire !== "number" || !Number.isFinite(info.expire) || info.expire < 0)))
|
|
56
|
+
throw new Error("Invalid subscription quota");
|
|
57
|
+
subInfo = {
|
|
58
|
+
upload: info.upload,
|
|
59
|
+
download: info.download,
|
|
60
|
+
total: info.total,
|
|
61
|
+
...(typeof info.expire === "number" ? { expire: info.expire } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (item.homePage !== undefined) {
|
|
65
|
+
if (typeof item.homePage !== "string")
|
|
66
|
+
throw new Error("Invalid profile home page");
|
|
67
|
+
const url = new URL(item.homePage);
|
|
68
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
69
|
+
throw new Error("Invalid profile home page");
|
|
70
|
+
}
|
|
71
|
+
if (item.lastError !== undefined &&
|
|
72
|
+
(typeof item.lastError !== "string" || item.lastError.length > 300)) {
|
|
73
|
+
throw new Error("Invalid profile update error");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
id: item.id,
|
|
77
|
+
revision: item.revision,
|
|
78
|
+
name: item.name,
|
|
79
|
+
url: item.url,
|
|
80
|
+
intervalHours: item.intervalHours,
|
|
81
|
+
createdAt: item.createdAt,
|
|
82
|
+
updatedAt: item.updatedAt,
|
|
83
|
+
...(subInfo ? { subInfo } : {}),
|
|
84
|
+
...(typeof item.homePage === "string" ? { homePage: item.homePage } : {}),
|
|
85
|
+
...(typeof item.lastError === "string" ? { lastError: item.lastError } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|