@astralyn/sash 0.1.0 → 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.
Files changed (108) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +22 -7
  3. package/THIRD_PARTY_NOTICES.md +9 -0
  4. package/dist/api.js +14 -45
  5. package/dist/app-state.js +99 -0
  6. package/dist/autostart/command.js +24 -0
  7. package/dist/autostart/context.js +43 -0
  8. package/dist/autostart/files.js +60 -0
  9. package/dist/autostart/installation.js +48 -0
  10. package/dist/autostart/start.js +47 -0
  11. package/dist/autostart/windows-registry.js +88 -0
  12. package/dist/autostart/windows.js +59 -0
  13. package/dist/autostart-contract.js +31 -0
  14. package/dist/autostart-entry.js +10 -0
  15. package/dist/autostart.js +69 -0
  16. package/dist/cli.js +11 -11
  17. package/dist/commands/auto.js +15 -0
  18. package/dist/commands/lifecycle.js +5 -31
  19. package/dist/commands/logs.js +11 -4
  20. package/dist/commands/shared.js +2 -4
  21. package/dist/commands/status.js +6 -7
  22. package/dist/commands/update.js +7 -3
  23. package/dist/commands/web.js +19 -27
  24. package/dist/contracts.js +171 -335
  25. package/dist/core-config-validation.js +22 -38
  26. package/dist/core-update.js +126 -762
  27. package/dist/core.js +20 -46
  28. package/dist/daemon/app.js +132 -113
  29. package/dist/daemon/context.js +13 -7
  30. package/dist/daemon/entry.js +21 -38
  31. package/dist/daemon/errors.js +6 -1
  32. package/dist/daemon/handlers/autostart.js +18 -0
  33. package/dist/daemon/handlers/core.js +32 -9
  34. package/dist/daemon/handlers/daemon.js +32 -6
  35. package/dist/daemon/handlers/profiles.js +12 -2
  36. package/dist/daemon/handlers/settings.js +9 -36
  37. package/dist/daemon/router.js +40 -27
  38. package/dist/daemon/scheduler.js +3 -1
  39. package/dist/daemon/server.js +6 -3
  40. package/dist/daemon/web-auth.js +52 -0
  41. package/dist/daemon-auth.js +2 -2
  42. package/dist/daemon-client.js +18 -2
  43. package/dist/daemon-http.js +7 -0
  44. package/dist/daemon-lifecycle.js +19 -196
  45. package/dist/github.js +7 -2
  46. package/dist/http.js +7 -5
  47. package/dist/log-follow.js +2 -1
  48. package/dist/mihomo-config.js +7 -9
  49. package/dist/paths.js +5 -7
  50. package/dist/profile-model.js +87 -0
  51. package/dist/profile-service.js +250 -587
  52. package/dist/profiles.js +43 -171
  53. package/dist/runtime-lifecycle.js +112 -194
  54. package/dist/runtime-owner.js +37 -76
  55. package/dist/sash-client.js +63 -21
  56. package/dist/settings-service.js +29 -228
  57. package/dist/settings.js +61 -285
  58. package/dist/status.js +24 -40
  59. package/dist/supervisor.js +1 -14
  60. package/dist/sysproxy/common.js +5 -45
  61. package/dist/sysproxy/factory.js +24 -65
  62. package/dist/sysproxy/snapshot.js +24 -257
  63. package/dist/sysproxy.js +1 -4
  64. package/dist/system-proxy-manager.js +37 -35
  65. package/dist/ui/assets/{ConnectionsView-DNGmZBSU.js → ConnectionsView-BgXQM6Z4.js} +1 -1
  66. package/dist/ui/assets/{LogsView-fSiaxQ13.js → LogsView-2f542P8z.js} +1 -1
  67. package/dist/ui/assets/{PaginationFooter-B3kHzRfB.js → PaginationFooter-cj1tcZej.js} +1 -1
  68. package/dist/ui/assets/ProfileEditorDialog-C9M8uKZC.css +1 -0
  69. package/dist/ui/assets/ProfileEditorDialog-DD4y4GBC.js +14 -0
  70. package/dist/ui/assets/ProfilesView-BXU2DOA7.css +1 -0
  71. package/dist/ui/assets/ProfilesView-DNnzYIEY.js +7 -0
  72. package/dist/ui/assets/{RulesView-D9vZBiJ1.js → RulesView-CtKvlckZ.js} +1 -1
  73. package/dist/ui/assets/SettingsView-BaeKFF_N.js +1 -0
  74. package/dist/ui/assets/SettingsView-CWjIe05v.css +1 -0
  75. package/dist/ui/assets/{0be242294f7d791af850c6df38ac78a0-2cL6Ntwf.woff2 → e2a57555d97d0b02b45d9418eb6ee295-D9nhF3rM.woff2} +0 -0
  76. package/dist/ui/assets/index-CBInDvdJ.js +19 -0
  77. package/dist/ui/assets/index-mOLy7fkB.css +1 -0
  78. package/dist/ui/assets/{theme-BNq4FkXS.js → theme-D40MF8cQ.js} +1 -1
  79. package/dist/ui/index.html +2 -2
  80. package/dist/web-bootstrap.js +113 -0
  81. package/docs/architecture-proposal.md +109 -0
  82. package/docs/autostart.md +101 -0
  83. package/docs/backend.md +92 -216
  84. package/docs/frontend.md +61 -97
  85. package/docs/usage.md +88 -186
  86. package/package.json +10 -6
  87. package/dist/commands/upgrade.js +0 -43
  88. package/dist/core-install-transaction.js +0 -114
  89. package/dist/core-update-coordination.js +0 -105
  90. package/dist/core-update-service.js +0 -245
  91. package/dist/managed-state-transaction.js +0 -377
  92. package/dist/offline-mutation.js +0 -58
  93. package/dist/profile-migration.js +0 -101
  94. package/dist/runtime-recovery.js +0 -31
  95. package/dist/sysproxy/darwin.js +0 -287
  96. package/dist/sysproxy/gnome.js +0 -181
  97. package/dist/sysproxy/legacy.js +0 -58
  98. package/dist/tun-guidance.js +0 -11
  99. package/dist/ui/assets/CodeEditorModal-6m0TJWyF.css +0 -1
  100. package/dist/ui/assets/CodeEditorModal-CFEnWsyh.js +0 -14
  101. package/dist/ui/assets/ProfileEditorDialog-BydoZthX.js +0 -1
  102. package/dist/ui/assets/ProfilesView-Btc1DOxE.js +0 -2
  103. package/dist/ui/assets/ProfilesView-ByqdFNHN.css +0 -1
  104. package/dist/ui/assets/SettingsFileDialog-CNFEAVs4.js +0 -1
  105. package/dist/ui/assets/SettingsView-BlDhZkXQ.js +0 -2
  106. package/dist/ui/assets/SettingsView-CngS3vBM.css +0 -1
  107. package/dist/ui/assets/index-B61V60w_.js +0 -19
  108. package/dist/ui/assets/index-bkyxJG8J.css +0 -1
package/dist/contracts.js CHANGED
@@ -1,412 +1,248 @@
1
1
  import { isCanonicalIsoTimestamp, isPlainObject } from "./json-shape.js";
2
+ import { parseProfileMeta } from "./profile-model.js";
3
+ export { parseProfilesIndex } from "./profile-model.js";
2
4
  export const WEB_SOCKET_AUTH_PROTOCOL = "sash";
3
5
  export const WEB_SOCKET_TOKEN_PROTOCOL_PREFIX = "sash-token.";
4
- export function apiErrorBody(code, message) {
5
- return { error: { code, message } };
6
- }
7
- export function parseApiErrorBody(value) {
8
- if (!isPlainObject(value) || !isPlainObject(value.error))
9
- return undefined;
10
- const { code, message } = value.error;
11
- if (typeof code !== "string" || typeof message !== "string")
12
- return undefined;
13
- return { code, message };
14
- }
15
- function invalid(contract, path, expected) {
16
- throw new TypeError(`${contract} response is invalid: ${path} must be ${expected}`);
17
- }
18
- function objectValue(value, contract, path) {
6
+ function object(value, name) {
19
7
  if (!isPlainObject(value))
20
- invalid(contract, path, "a plain object");
21
- return value;
22
- }
23
- function required(source, key, contract, path) {
24
- if (!Object.hasOwn(source, key))
25
- invalid(contract, path, "present");
26
- return source[key];
27
- }
28
- function booleanValue(value, contract, path) {
29
- if (typeof value !== "boolean")
30
- invalid(contract, path, "a boolean");
31
- return value;
32
- }
33
- function stringValue(value, contract, path, nonEmpty = false) {
34
- if (typeof value !== "string" || (nonEmpty && !value.trim())) {
35
- invalid(contract, path, nonEmpty ? "a non-empty string" : "a string");
36
- }
8
+ throw new TypeError(`${name} must be a plain object`);
37
9
  return value;
38
10
  }
39
- function finiteNumber(value, contract, path) {
40
- if (typeof value !== "number" || !Number.isFinite(value)) {
41
- invalid(contract, path, "a finite number");
42
- }
11
+ function string(value, name, allowEmpty = false) {
12
+ if (typeof value !== "string" || (!allowEmpty && !value.trim()))
13
+ throw new TypeError(`${name} must be a string${allowEmpty ? "" : " with content"}`);
43
14
  return value;
44
15
  }
45
- function positiveSafeInteger(value, contract, path) {
46
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
47
- invalid(contract, path, "a positive safe integer");
48
- }
49
- return value;
50
- }
51
- function nonNegativeSafeInteger(value, contract, path) {
52
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
53
- invalid(contract, path, "a non-negative safe integer");
54
- }
16
+ function boolean(value, name) {
17
+ if (typeof value !== "boolean")
18
+ throw new TypeError(`${name} must be a boolean`);
55
19
  return value;
56
20
  }
57
- function portValue(value, contract, path) {
58
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 65_535) {
59
- invalid(contract, path, "an integer port from 1 to 65535");
60
- }
21
+ function integer(value, name, min = 0, max = Number.MAX_SAFE_INTEGER) {
22
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max)
23
+ throw new TypeError(`${name} must be an integer from ${min} to ${max}`);
61
24
  return value;
62
25
  }
63
- function timestampValue(value, contract, path) {
26
+ function timestamp(value, name) {
64
27
  if (!isCanonicalIsoTimestamp(value))
65
- invalid(contract, path, "a canonical ISO timestamp");
28
+ throw new TypeError(`${name} must be a canonical timestamp`);
66
29
  return value;
67
30
  }
68
- function optionalString(source, key, contract, path) {
69
- return Object.hasOwn(source, key) ? stringValue(source[key], contract, path) : undefined;
31
+ function optionalString(source, key) {
32
+ return Object.hasOwn(source, key) ? string(source[key], key, true) : undefined;
70
33
  }
71
- function knownFlag(source, key, contract, path) {
72
- return Object.hasOwn(source, key) ? booleanValue(source[key], contract, path) : false;
73
- }
74
- function parseSystemProxyState(value, contract, path) {
75
- const source = objectValue(value, contract, path);
76
- const server = optionalString(source, "server", contract, `${path}.server`);
77
- const details = optionalString(source, "details", contract, `${path}.details`);
78
- return {
79
- supported: booleanValue(required(source, "supported", contract, `${path}.supported`), contract, `${path}.supported`),
80
- enabled: booleanValue(required(source, "enabled", contract, `${path}.enabled`), contract, `${path}.enabled`),
81
- ...(server !== undefined ? { server } : {}),
82
- ...(details !== undefined ? { details } : {}),
83
- };
34
+ export function apiErrorBody(code, message) {
35
+ return { error: { code, message } };
84
36
  }
85
- function parseCoreState(value, contract) {
86
- const source = objectValue(value, contract, "core");
87
- const pid = Object.hasOwn(source, "pid")
88
- ? positiveSafeInteger(source.pid, contract, "core.pid")
89
- : undefined;
90
- const startedAt = Object.hasOwn(source, "startedAt")
91
- ? timestampValue(source.startedAt, contract, "core.startedAt")
92
- : undefined;
93
- const healthy = Object.hasOwn(source, "healthy")
94
- ? booleanValue(source.healthy, contract, "core.healthy")
95
- : undefined;
96
- const version = optionalString(source, "version", contract, "core.version");
97
- const tunActive = Object.hasOwn(source, "tunActive")
98
- ? booleanValue(source.tunActive, contract, "core.tunActive")
99
- : undefined;
100
- return {
101
- running: booleanValue(required(source, "running", contract, "core.running"), contract, "core.running"),
102
- ...(pid !== undefined ? { pid } : {}),
103
- ...(startedAt !== undefined ? { startedAt } : {}),
104
- ...(healthy !== undefined ? { healthy } : {}),
105
- ...(version !== undefined ? { version } : {}),
106
- ...(tunActive !== undefined ? { tunActive } : {}),
107
- };
37
+ export function parseApiErrorBody(value) {
38
+ if (!isPlainObject(value) ||
39
+ !isPlainObject(value.error) ||
40
+ typeof value.error.code !== "string" ||
41
+ typeof value.error.message !== "string")
42
+ return undefined;
43
+ return { code: value.error.code, message: value.error.message };
108
44
  }
109
- export function parsePublicSettings(value) {
110
- const contract = "sashd settings";
111
- const source = objectValue(value, contract, "settings");
45
+ export function parseHealthInfo(value) {
46
+ const source = object(value, "health");
112
47
  return {
113
- mixedPort: portValue(required(source, "mixedPort", contract, "settings.mixedPort"), contract, "settings.mixedPort"),
114
- controller: stringValue(required(source, "controller", contract, "settings.controller"), contract, "settings.controller", true),
115
- tun: booleanValue(required(source, "tun", contract, "settings.tun"), contract, "settings.tun"),
116
- allowLan: booleanValue(required(source, "allowLan", contract, "settings.allowLan"), contract, "settings.allowLan"),
117
- daemonPort: portValue(required(source, "daemonPort", contract, "settings.daemonPort"), contract, "settings.daemonPort"),
118
- systemProxy: booleanValue(required(source, "systemProxy", contract, "settings.systemProxy"), contract, "settings.systemProxy"),
48
+ token: string(source.token, "token"),
49
+ pid: integer(source.pid, "pid", 1),
50
+ startedAt: timestamp(source.startedAt, "startedAt"),
119
51
  };
120
52
  }
121
- function parseProfileMeta(value, contract, path) {
122
- const source = objectValue(value, contract, path);
123
- const subInfoValue = Object.hasOwn(source, "subInfo")
124
- ? objectValue(source.subInfo, contract, `${path}.subInfo`)
125
- : undefined;
126
- const subInfo = subInfoValue
127
- ? {
128
- upload: finiteNumber(required(subInfoValue, "upload", contract, `${path}.subInfo.upload`), contract, `${path}.subInfo.upload`),
129
- download: finiteNumber(required(subInfoValue, "download", contract, `${path}.subInfo.download`), contract, `${path}.subInfo.download`),
130
- total: finiteNumber(required(subInfoValue, "total", contract, `${path}.subInfo.total`), contract, `${path}.subInfo.total`),
131
- ...(Object.hasOwn(subInfoValue, "expire")
132
- ? {
133
- expire: finiteNumber(subInfoValue.expire, contract, `${path}.subInfo.expire`),
134
- }
135
- : {}),
136
- }
137
- : undefined;
138
- const homePage = optionalString(source, "homePage", contract, `${path}.homePage`);
139
- const lastError = optionalString(source, "lastError", contract, `${path}.lastError`);
53
+ export function parseWebBootstrapInfo(value) {
54
+ const source = object(value, "bootstrap");
140
55
  return {
141
- id: stringValue(required(source, "id", contract, `${path}.id`), contract, `${path}.id`, true),
142
- name: stringValue(required(source, "name", contract, `${path}.name`), contract, `${path}.name`),
143
- url: stringValue(required(source, "url", contract, `${path}.url`), contract, `${path}.url`),
144
- intervalHours: nonNegativeSafeInteger(required(source, "intervalHours", contract, `${path}.intervalHours`), contract, `${path}.intervalHours`),
145
- createdAt: timestampValue(required(source, "createdAt", contract, `${path}.createdAt`), contract, `${path}.createdAt`),
146
- updatedAt: timestampValue(required(source, "updatedAt", contract, `${path}.updatedAt`), contract, `${path}.updatedAt`),
147
- ...(subInfo !== undefined ? { subInfo } : {}),
148
- ...(homePage !== undefined ? { homePage } : {}),
149
- ...(lastError !== undefined ? { lastError } : {}),
56
+ token: string(source.token, "token"),
57
+ expiresAt: timestamp(source.expiresAt, "expiresAt"),
150
58
  };
151
59
  }
152
- function optionalProxyCount(source, contract, path) {
153
- return Object.hasOwn(source, "proxyCount")
154
- ? { proxyCount: nonNegativeSafeInteger(source.proxyCount, contract, `${path}.proxyCount`) }
155
- : {};
156
- }
157
- export function parseHealthInfo(value) {
158
- const contract = "sashd health";
159
- const source = objectValue(value, contract, "response");
60
+ export function parseWebSessionInfo(value) {
61
+ const source = object(value, "session");
160
62
  return {
161
- token: stringValue(required(source, "token", contract, "token"), contract, "token", true),
162
- pid: positiveSafeInteger(required(source, "pid", contract, "pid"), contract, "pid"),
163
- startedAt: timestampValue(required(source, "startedAt", contract, "startedAt"), contract, "startedAt"),
63
+ token: string(source.token, "token"),
64
+ daemonToken: string(source.daemonToken, "daemonToken"),
164
65
  };
165
66
  }
166
67
  export function parseCoreStartResult(value) {
167
- const contract = "sashd core start";
168
- const source = objectValue(value, contract, "response");
169
- const version = optionalString(source, "version", contract, "version");
170
- const tunActive = Object.hasOwn(source, "tunActive")
171
- ? booleanValue(source.tunActive, contract, "tunActive")
172
- : undefined;
173
- return {
174
- pid: positiveSafeInteger(required(source, "pid", contract, "pid"), contract, "pid"),
175
- ...(version !== undefined ? { version } : {}),
176
- ...(tunActive !== undefined ? { tunActive } : {}),
177
- };
178
- }
179
- export function parseCoreReloadResult(value) {
180
- const contract = "sashd core reload";
181
- const source = objectValue(value, contract, "response");
182
- const sourceKind = stringValue(required(source, "source", contract, "source"), contract, "source");
183
- if (sourceKind !== "subscription" && sourceKind !== "default") {
184
- invalid(contract, "source", `"subscription" or "default"`);
185
- }
186
- return {
187
- proxyCount: nonNegativeSafeInteger(required(source, "proxyCount", contract, "proxyCount"), contract, "proxyCount"),
188
- source: sourceKind,
189
- };
68
+ const source = object(value, "Core start");
69
+ const version = optionalString(source, "version");
70
+ return { pid: integer(source.pid, "pid", 1), ...(version !== undefined ? { version } : {}) };
190
71
  }
191
- export function parseShutdownResult(value) {
192
- const contract = "sashd shutdown";
193
- const source = objectValue(value, contract, "response");
194
- return {
195
- coreWasRunning: booleanValue(required(source, "coreWasRunning", contract, "coreWasRunning"), contract, "coreWasRunning"),
196
- };
72
+ export function parseCoreUpdateResponse(value) {
73
+ return { version: string(object(value, "Core update").version, "version") };
197
74
  }
198
- export function parseSystemProxyStatusResponse(value) {
199
- const contract = "sashd system proxy";
200
- const source = objectValue(value, contract, "response");
201
- const state = parseSystemProxyState(source, contract, "response");
202
- const queryError = optionalString(source, "queryError", contract, "queryError");
75
+ export function parsePublicSettings(value) {
76
+ const source = object(value, "settings");
203
77
  return {
204
- desired: booleanValue(required(source, "desired", contract, "desired"), contract, "desired"),
205
- applied: booleanValue(required(source, "applied", contract, "applied"), contract, "applied"),
206
- ...state,
207
- appliedKnown: knownFlag(source, "appliedKnown", contract, "appliedKnown"),
208
- stateKnown: knownFlag(source, "stateKnown", contract, "stateKnown"),
209
- ...(queryError !== undefined ? { queryError } : {}),
78
+ mixedPort: integer(source.mixedPort, "mixedPort", 1, 65535),
79
+ controller: string(source.controller, "controller"),
80
+ allowLan: boolean(source.allowLan, "allowLan"),
81
+ daemonPort: integer(source.daemonPort, "daemonPort", 1, 65535),
82
+ systemProxy: boolean(source.systemProxy, "systemProxy"),
210
83
  };
211
84
  }
212
- export function parseSettingsPatch(value, direction = "response") {
213
- const subject = `sashd settings patch ${direction}`;
214
- const fail = (path, expected) => {
215
- throw new TypeError(`${subject} is invalid: ${path} must be ${expected}`);
216
- };
217
- if (!isPlainObject(value))
218
- fail("body", "a plain object");
219
- const source = value;
220
- const allowed = new Set([
221
- "mixedPort",
222
- "allowLan",
223
- "tun",
224
- "systemProxy",
225
- "daemonPort",
226
- "daemonSecret",
227
- ]);
85
+ export function parseSettingsPatch(value) {
86
+ const source = object(value, "settings patch");
228
87
  for (const key of Object.keys(source)) {
229
- if (!allowed.has(key))
230
- fail(key, "a known settings patch field");
88
+ if (!["mixedPort", "allowLan", "systemProxy"].includes(key))
89
+ throw new TypeError(`Unknown settings field: ${key}`);
231
90
  }
232
- const portField = (key) => {
233
- if (!Object.hasOwn(source, key))
234
- return undefined;
235
- const field = source[key];
236
- if (typeof field !== "number" || !Number.isInteger(field) || field < 1 || field > 65_535) {
237
- return fail(key, "an integer port from 1 to 65535");
238
- }
239
- return field;
240
- };
241
- const boolField = (key) => {
242
- if (!Object.hasOwn(source, key))
243
- return undefined;
244
- const field = source[key];
245
- if (typeof field !== "boolean")
246
- return fail(key, "a boolean");
247
- return field;
91
+ return {
92
+ ...(Object.hasOwn(source, "mixedPort")
93
+ ? { mixedPort: integer(source.mixedPort, "mixedPort", 1, 65535) }
94
+ : {}),
95
+ ...(Object.hasOwn(source, "allowLan")
96
+ ? { allowLan: boolean(source.allowLan, "allowLan") }
97
+ : {}),
98
+ ...(Object.hasOwn(source, "systemProxy")
99
+ ? { systemProxy: boolean(source.systemProxy, "systemProxy") }
100
+ : {}),
248
101
  };
249
- const patch = {};
250
- const mixedPort = portField("mixedPort");
251
- if (mixedPort !== undefined)
252
- patch.mixedPort = mixedPort;
253
- const daemonPort = portField("daemonPort");
254
- if (daemonPort !== undefined)
255
- patch.daemonPort = daemonPort;
256
- const allowLan = boolField("allowLan");
257
- if (allowLan !== undefined)
258
- patch.allowLan = allowLan;
259
- const tun = boolField("tun");
260
- if (tun !== undefined)
261
- patch.tun = tun;
262
- const systemProxy = boolField("systemProxy");
263
- if (systemProxy !== undefined)
264
- patch.systemProxy = systemProxy;
265
- if (Object.hasOwn(source, "daemonSecret")) {
266
- const secret = source.daemonSecret;
267
- if (typeof secret !== "string" || !secret.trim()) {
268
- fail("daemonSecret", "a non-empty string");
269
- }
270
- patch.daemonSecret = secret;
271
- }
272
- return patch;
273
102
  }
274
103
  export function parseSettingsWriteResult(value) {
275
- const contract = "sashd settings write";
276
- const source = objectValue(value, contract, "response");
104
+ const source = object(value, "settings write");
277
105
  return {
278
- restartRequired: booleanValue(required(source, "restartRequired", contract, "restartRequired"), contract, "restartRequired"),
279
- settings: parsePublicSettings(required(source, "settings", contract, "settings")),
106
+ restartRequired: boolean(source.restartRequired, "restartRequired"),
107
+ settings: parsePublicSettings(source.settings),
280
108
  };
281
109
  }
282
- export function parseSettingsFileContent(value) {
283
- const contract = "sashd settings file";
284
- const source = objectValue(value, contract, "response");
110
+ function parseSystemProxyState(value) {
111
+ const source = object(value, "system proxy");
112
+ const server = optionalString(source, "server");
113
+ const details = optionalString(source, "details");
285
114
  return {
286
- content: stringValue(required(source, "content", contract, "content"), contract, "content"),
115
+ supported: boolean(source.supported, "supported"),
116
+ enabled: boolean(source.enabled, "enabled"),
117
+ ...(server !== undefined ? { server } : {}),
118
+ ...(details !== undefined ? { details } : {}),
287
119
  };
288
120
  }
289
- export function parseProfilesIndex(value) {
290
- const contract = "sashd profiles";
291
- const source = objectValue(value, contract, "response");
292
- const activeValue = required(source, "activeId", contract, "activeId");
293
- const profilesValue = required(source, "profiles", contract, "profiles");
294
- if (!Array.isArray(profilesValue))
295
- invalid(contract, "profiles", "an array");
296
- return {
297
- activeId: activeValue === null ? null : stringValue(activeValue, contract, "activeId", true),
298
- profiles: profilesValue.map((entry, index) => parseProfileMeta(entry, contract, `profiles[${index}]`)),
121
+ export function parseSystemProxyStatusResponse(value) {
122
+ const source = object(value, "system proxy status");
123
+ const queryError = optionalString(source, "queryError");
124
+ return {
125
+ ...parseSystemProxyState(source),
126
+ desired: boolean(source.desired, "desired"),
127
+ applied: boolean(source.applied, "applied"),
128
+ appliedKnown: boolean(source.appliedKnown, "appliedKnown"),
129
+ stateKnown: boolean(source.stateKnown, "stateKnown"),
130
+ ...(queryError !== undefined ? { queryError } : {}),
299
131
  };
300
132
  }
301
133
  export function parseProfileActionResponse(value) {
302
- const contract = "sashd profile action";
303
- const source = objectValue(value, contract, "response");
134
+ const source = object(value, "profile action");
304
135
  return {
305
- profile: parseProfileMeta(required(source, "profile", contract, "profile"), contract, "profile"),
306
- activated: booleanValue(required(source, "activated", contract, "activated"), contract, "activated"),
307
- ...optionalProxyCount(source, contract, "response"),
136
+ profile: parseProfileMeta(source.profile),
137
+ activated: boolean(source.activated, "activated"),
308
138
  };
309
139
  }
310
140
  export function parseProfileUpdateResponse(value) {
311
- const contract = "sashd profile update";
312
- const source = objectValue(value, contract, "response");
313
- return {
314
- profile: parseProfileMeta(required(source, "profile", contract, "profile"), contract, "profile"),
315
- ...optionalProxyCount(source, contract, "response"),
316
- };
317
- }
318
- export function parseProfilesUpdateAllResponse(value) {
319
- const contract = "sashd profiles update-all";
320
- const source = objectValue(value, contract, "response");
321
- const failedValue = required(source, "failed", contract, "failed");
322
- if (!Array.isArray(failedValue))
323
- invalid(contract, "failed", "an array");
324
- return {
325
- updated: nonNegativeSafeInteger(required(source, "updated", contract, "updated"), contract, "updated"),
326
- failed: failedValue.map((entry, index) => {
327
- const path = `failed[${index}]`;
328
- const failure = objectValue(entry, contract, path);
329
- return {
330
- id: stringValue(required(failure, "id", contract, `${path}.id`), contract, `${path}.id`),
331
- name: stringValue(required(failure, "name", contract, `${path}.name`), contract, `${path}.name`),
332
- error: stringValue(required(failure, "error", contract, `${path}.error`), contract, `${path}.error`),
333
- };
334
- }),
335
- ...optionalProxyCount(source, contract, "response"),
336
- };
141
+ return { profile: parseProfileMeta(object(value, "profile").profile) };
337
142
  }
143
+ export { parseProfileUpdateResponse as parseProfileRenameResponse };
338
144
  export function parseProfileContentResponse(value) {
339
- const contract = "sashd profile content";
340
- const source = objectValue(value, contract, "response");
145
+ const source = object(value, "profile content");
341
146
  return {
342
- name: stringValue(required(source, "name", contract, "name"), contract, "name"),
343
- content: stringValue(required(source, "content", contract, "content"), contract, "content"),
147
+ name: string(source.name, "name"),
148
+ content: string(source.content, "content", true),
149
+ revision: integer(source.revision, "revision", 1),
344
150
  };
345
151
  }
346
152
  export function parseProfileActivateResponse(value) {
347
- const contract = "sashd profile activate";
348
- const source = objectValue(value, contract, "response");
349
- const activeValue = required(source, "activeId", contract, "activeId");
153
+ const source = object(value, "profile selection");
350
154
  return {
351
- activeId: activeValue === null ? null : stringValue(activeValue, contract, "activeId", true),
352
- proxyCount: nonNegativeSafeInteger(required(source, "proxyCount", contract, "proxyCount"), contract, "proxyCount"),
155
+ activeId: source.activeId === null ? null : string(source.activeId, "activeId"),
156
+ proxyCount: integer(source.proxyCount, "proxyCount"),
353
157
  };
354
158
  }
355
159
  export function parseProfileRemoveResponse(value) {
356
- const contract = "sashd profile remove";
357
- const source = objectValue(value, contract, "response");
358
- return {
359
- wasActive: booleanValue(required(source, "wasActive", contract, "wasActive"), contract, "wasActive"),
360
- ...optionalProxyCount(source, contract, "response"),
361
- };
160
+ return { wasActive: boolean(object(value, "profile removal").wasActive, "wasActive") };
362
161
  }
363
- export function parseProfileRenameResponse(value) {
364
- const contract = "sashd profile rename";
365
- const source = objectValue(value, contract, "response");
162
+ export function parseProfilesUpdateAllResponse(value) {
163
+ const source = object(value, "profiles update");
164
+ if (!Array.isArray(source.failed))
165
+ throw new TypeError("failed must be an array");
366
166
  return {
367
- profile: parseProfileMeta(required(source, "profile", contract, "profile"), contract, "profile"),
167
+ updated: integer(source.updated, "updated"),
168
+ failed: source.failed.map((item) => {
169
+ const failure = object(item, "profile error");
170
+ return {
171
+ id: string(failure.id, "id"),
172
+ name: string(failure.name, "name"),
173
+ error: string(failure.error, "error"),
174
+ };
175
+ }),
368
176
  };
369
177
  }
370
178
  export function parseDaemonStatus(value) {
371
- const contract = "sashd status";
372
- const source = objectValue(value, contract, "response");
373
- const daemonSource = objectValue(required(source, "daemon", contract, "daemon"), contract, "daemon");
374
- const revisionsSource = objectValue(required(source, "revisions", contract, "revisions"), contract, "revisions");
375
- const proxySource = objectValue(required(source, "systemProxy", contract, "systemProxy"), contract, "systemProxy");
376
- const actual = Object.hasOwn(proxySource, "actual")
377
- ? parseSystemProxyState(proxySource.actual, contract, "systemProxy.actual")
378
- : undefined;
379
- const queryError = optionalString(proxySource, "queryError", contract, "systemProxy.queryError");
380
- const activeValue = required(source, "activeProfile", contract, "activeProfile");
381
- const activeProfile = activeValue === null
179
+ const source = object(value, "status");
180
+ const daemon = object(source.daemon, "daemon");
181
+ const revisions = object(source.revisions, "revisions");
182
+ const core = object(source.core, "core");
183
+ const proxy = object(source.systemProxy, "systemProxy");
184
+ const configuration = object(source.configuration, "configuration");
185
+ const selected = source.activeProfile === null ? null : object(source.activeProfile, "activeProfile");
186
+ const applied = configuration.appliedProfile === null
382
187
  ? null
383
- : (() => {
384
- const active = objectValue(activeValue, contract, "activeProfile");
385
- return {
386
- id: stringValue(required(active, "id", contract, "activeProfile.id"), contract, "activeProfile.id", true),
387
- name: stringValue(required(active, "name", contract, "activeProfile.name"), contract, "activeProfile.name"),
388
- url: stringValue(required(active, "url", contract, "activeProfile.url"), contract, "activeProfile.url"),
389
- };
390
- })();
188
+ : object(configuration.appliedProfile, "appliedProfile");
189
+ const appliedSettings = configuration.appliedSettings === null
190
+ ? null
191
+ : object(configuration.appliedSettings, "appliedSettings");
192
+ const version = optionalString(core, "version");
193
+ const queryError = optionalString(proxy, "queryError");
391
194
  return {
392
195
  daemon: {
393
- pid: positiveSafeInteger(required(daemonSource, "pid", contract, "daemon.pid"), contract, "daemon.pid"),
394
- startedAt: timestampValue(required(daemonSource, "startedAt", contract, "daemon.startedAt"), contract, "daemon.startedAt"),
395
- port: portValue(required(daemonSource, "port", contract, "daemon.port"), contract, "daemon.port"),
196
+ pid: integer(daemon.pid, "daemon.pid", 1),
197
+ bootId: string(daemon.bootId, "daemon.bootId"),
198
+ startedAt: timestamp(daemon.startedAt, "daemon.startedAt"),
199
+ port: integer(daemon.port, "daemon.port", 1, 65535),
396
200
  },
397
201
  revisions: {
398
- profiles: nonNegativeSafeInteger(required(revisionsSource, "profiles", contract, "revisions.profiles"), contract, "revisions.profiles"),
202
+ profiles: integer(revisions.profiles, "revisions.profiles"),
203
+ runtime: integer(revisions.runtime, "revisions.runtime"),
204
+ },
205
+ core: {
206
+ running: boolean(core.running, "core.running"),
207
+ ...(Object.hasOwn(core, "pid") ? { pid: integer(core.pid, "core.pid", 1) } : {}),
208
+ ...(Object.hasOwn(core, "startedAt")
209
+ ? { startedAt: timestamp(core.startedAt, "core.startedAt") }
210
+ : {}),
211
+ ...(Object.hasOwn(core, "healthy") ? { healthy: boolean(core.healthy, "core.healthy") } : {}),
212
+ ...(version !== undefined ? { version } : {}),
213
+ },
214
+ configuration: {
215
+ pending: boolean(configuration.pending, "configuration.pending"),
216
+ appliedProfile: applied
217
+ ? {
218
+ id: string(applied.id, "appliedProfile.id"),
219
+ revision: integer(applied.revision, "appliedProfile.revision", 1),
220
+ name: string(applied.name, "appliedProfile.name"),
221
+ url: string(applied.url, "appliedProfile.url", true),
222
+ }
223
+ : null,
224
+ appliedSettings: appliedSettings
225
+ ? {
226
+ mixedPort: integer(appliedSettings.mixedPort, "appliedSettings.mixedPort", 1, 65535),
227
+ allowLan: boolean(appliedSettings.allowLan, "appliedSettings.allowLan"),
228
+ }
229
+ : null,
399
230
  },
400
- core: parseCoreState(required(source, "core", contract, "core"), contract),
401
231
  systemProxy: {
402
- desired: booleanValue(required(proxySource, "desired", contract, "systemProxy.desired"), contract, "systemProxy.desired"),
403
- applied: booleanValue(required(proxySource, "applied", contract, "systemProxy.applied"), contract, "systemProxy.applied"),
404
- ...(actual !== undefined ? { actual } : {}),
405
- appliedKnown: knownFlag(proxySource, "appliedKnown", contract, "systemProxy.appliedKnown"),
406
- stateKnown: knownFlag(proxySource, "stateKnown", contract, "systemProxy.stateKnown"),
232
+ desired: boolean(proxy.desired, "systemProxy.desired"),
233
+ applied: boolean(proxy.applied, "systemProxy.applied"),
234
+ appliedKnown: boolean(proxy.appliedKnown, "systemProxy.appliedKnown"),
235
+ stateKnown: boolean(proxy.stateKnown, "systemProxy.stateKnown"),
236
+ ...(Object.hasOwn(proxy, "actual") ? { actual: parseSystemProxyState(proxy.actual) } : {}),
407
237
  ...(queryError !== undefined ? { queryError } : {}),
408
238
  },
409
- settings: parsePublicSettings(required(source, "settings", contract, "settings")),
410
- activeProfile,
239
+ settings: parsePublicSettings(source.settings),
240
+ activeProfile: selected
241
+ ? {
242
+ id: string(selected.id, "activeProfile.id"),
243
+ name: string(selected.name, "activeProfile.name"),
244
+ url: string(selected.url, "activeProfile.url", true),
245
+ }
246
+ : null,
411
247
  };
412
248
  }