@livedesk/hub 0.1.35 → 0.1.37
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/package.json +2 -2
- package/src/agents/agent-audit-store.js +6 -16
- package/src/agents/agent-permissions.js +27 -33
- package/src/agents/agent-tool-registry.js +322 -340
- package/src/captures/capture-store.js +252 -299
- package/src/filesystem/shared-folders.js +181 -189
- package/src/filesystem/transfer-jobs.js +314 -345
- package/src/live-desk-update.js +615 -683
- package/src/remote-hub.js +59 -643
- package/src/server.js +111 -924
- package/src/settings/settings-schema.js +40 -32
- package/src/settings/settings-store.js +2 -7
- package/src/transport/relay-hub-control.js +1376 -1703
- package/src/transport/udp-hub-transport.js +520 -543
- package/src/transport/udp-rendezvous.js +408 -574
- package/src/security/device-credential-authority.js +0 -406
- package/src/security/security-audit-store.js +0 -260
- package/src/transport/secure-direct-acceptor.js +0 -543
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const SETTINGS_SCHEMA_VERSION =
|
|
4
|
+
export const SETTINGS_SCHEMA_VERSION = 1;
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
7
7
|
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
8
|
-
connection: {
|
|
8
|
+
connection: {
|
|
9
|
+
usePinToAddDevices: true,
|
|
10
|
+
pinValidityMinutes: 30,
|
|
11
|
+
rotatePinAfterPairing: true,
|
|
12
|
+
allowNewDevices: true,
|
|
13
|
+
approveNewDevices: true,
|
|
14
|
+
startWithComputer: true,
|
|
15
|
+
keepRunningInTray: true,
|
|
16
|
+
showConnectionNotifications: true
|
|
17
|
+
},
|
|
9
18
|
security: {
|
|
10
19
|
accessMode: 'trusted-only',
|
|
11
|
-
|
|
20
|
+
requireEncryptedConnections: true,
|
|
21
|
+
allowInternetConnections: false,
|
|
12
22
|
allowLanConnections: true,
|
|
23
|
+
allowUnencryptedLanFallback: false,
|
|
24
|
+
requireNewDeviceApproval: true,
|
|
25
|
+
requireAdministratorForSecurityChanges: true,
|
|
26
|
+
visibleControlIndicator: true,
|
|
13
27
|
lockOnControlEnd: true,
|
|
14
|
-
|
|
28
|
+
disconnectIdleControlSessions: true,
|
|
29
|
+
idleControlMinutes: 30,
|
|
30
|
+
keepSessionHistory: true,
|
|
31
|
+
sessionHistoryDays: 30
|
|
15
32
|
},
|
|
16
33
|
control: {
|
|
17
34
|
allowKeyboardMouse: true,
|
|
@@ -43,8 +60,10 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
|
43
60
|
filesAudio: {
|
|
44
61
|
allowFileTransfer: true,
|
|
45
62
|
allowFolderSync: false,
|
|
63
|
+
askBeforeReceivingFiles: true,
|
|
46
64
|
openReceivedFolder: false,
|
|
47
65
|
notifyTransferComplete: true,
|
|
66
|
+
allowOverwrite: false,
|
|
48
67
|
defaultReceiveFolder: 'Desktop/LiveDeskFiles',
|
|
49
68
|
maxFileSizeBytes: 1024 * 1024 * 1024,
|
|
50
69
|
allowRemoteAudio: true,
|
|
@@ -84,6 +103,7 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
|
84
103
|
controlMaxHeight: 1080,
|
|
85
104
|
controlQuality: 60,
|
|
86
105
|
transport: 'auto',
|
|
106
|
+
allowPlainLanFallback: false,
|
|
87
107
|
verboseLogs: false,
|
|
88
108
|
frameStatistics: false
|
|
89
109
|
}
|
|
@@ -95,7 +115,7 @@ const ENUMS = {
|
|
|
95
115
|
permissionMode: new Set(['ask', 'safe-auto', 'full-access', 'custom']),
|
|
96
116
|
wallFrameMode: new Set(['auto', 'mode2-lzo', 'mode3-h264-hw', 'mode4-h264-atlas']),
|
|
97
117
|
controlFrameMode: new Set(['mode3-h264-hw', 'mode5-lzo-delta']),
|
|
98
|
-
transport: new Set(['auto', 'encrypted']),
|
|
118
|
+
transport: new Set(['auto', 'encrypted', 'plain-lan']),
|
|
99
119
|
recordingQuality: new Set(['standard', 'high']),
|
|
100
120
|
captureAutoDelete: new Set(['never', '7-days', '30-days'])
|
|
101
121
|
};
|
|
@@ -136,11 +156,14 @@ const bools = keys => Object.fromEntries(keys.map(key => [key, { type: 'boolean'
|
|
|
136
156
|
const numbers = (entries) => Object.fromEntries(entries.map(([key, min, max]) => [key, { type: 'number', min, max }]));
|
|
137
157
|
|
|
138
158
|
const RULES = {
|
|
139
|
-
connection: {
|
|
159
|
+
connection: {
|
|
160
|
+
...bools(['usePinToAddDevices', 'rotatePinAfterPairing', 'allowNewDevices', 'approveNewDevices', 'startWithComputer', 'keepRunningInTray', 'showConnectionNotifications']),
|
|
161
|
+
...numbers([['pinValidityMinutes', 10, 1440]])
|
|
162
|
+
},
|
|
140
163
|
security: {
|
|
141
164
|
accessMode: { type: 'enum', values: ENUMS.accessMode },
|
|
142
|
-
...bools(['allowInternetConnections', 'allowLanConnections', 'lockOnControlEnd']),
|
|
143
|
-
...numbers([['idleControlMinutes', 5, 1440]])
|
|
165
|
+
...bools(['requireEncryptedConnections', 'allowInternetConnections', 'allowLanConnections', 'allowUnencryptedLanFallback', 'requireNewDeviceApproval', 'requireAdministratorForSecurityChanges', 'visibleControlIndicator', 'lockOnControlEnd', 'disconnectIdleControlSessions', 'keepSessionHistory']),
|
|
166
|
+
...numbers([['idleControlMinutes', 5, 1440], ['sessionHistoryDays', 7, 365]])
|
|
144
167
|
},
|
|
145
168
|
control: {
|
|
146
169
|
...bools(['allowKeyboardMouse', 'allowSystemShortcuts', 'allowClipboardText', 'allowRemoteRestart', 'reconnectAfterRemoteRestart', 'allowSwitchingMonitors', 'showConnectionToolbar', 'showRemoteCursor', 'openControlOnDoubleClick', 'startRemoteAudioWithControl', 'fitRemoteScreen', 'rememberLastMonitor', 'keepControlReadyBetweenPages'])
|
|
@@ -150,7 +173,7 @@ const RULES = {
|
|
|
150
173
|
...bools(['autoStart', 'connectedOnly', 'keepEmptySlots', 'showDeviceStatus', 'showPerformanceDetails', 'pauseHiddenTiles', 'reduceWhenHidden', 'autoAdjustTileQuality', 'rememberDevicePositions'])
|
|
151
174
|
},
|
|
152
175
|
filesAudio: {
|
|
153
|
-
...bools(['allowFileTransfer', 'allowFolderSync', 'openReceivedFolder', 'notifyTransferComplete', 'allowRemoteAudio', 'startAudioMuted', 'rememberVolume', 'automaticallyRecoverAudio', 'showAudioTroubleshooting', 'rollingBufferEnabled', 'includeRemoteCursor']),
|
|
176
|
+
...bools(['allowFileTransfer', 'allowFolderSync', 'askBeforeReceivingFiles', 'openReceivedFolder', 'notifyTransferComplete', 'allowOverwrite', 'allowRemoteAudio', 'startAudioMuted', 'rememberVolume', 'automaticallyRecoverAudio', 'showAudioTroubleshooting', 'rollingBufferEnabled', 'includeRemoteCursor']),
|
|
154
177
|
recordingQuality: { type: 'enum', values: ENUMS.recordingQuality },
|
|
155
178
|
captureAutoDelete: { type: 'enum', values: ENUMS.captureAutoDelete },
|
|
156
179
|
timelapseIntervalSeconds: { type: 'number', min: 5, max: 60 },
|
|
@@ -167,23 +190,11 @@ const RULES = {
|
|
|
167
190
|
wallFrameMode: { type: 'enum', values: ENUMS.wallFrameMode },
|
|
168
191
|
controlFrameMode: { type: 'enum', values: ENUMS.controlFrameMode },
|
|
169
192
|
transport: { type: 'enum', values: ENUMS.transport },
|
|
170
|
-
...bools(['verboseLogs', 'frameStatistics']),
|
|
193
|
+
...bools(['allowPlainLanFallback', 'verboseLogs', 'frameStatistics']),
|
|
171
194
|
...numbers([['wallFps', 1, 60], ['wallMaxWidth', 320, 3840], ['wallMaxHeight', 180, 2160], ['wallQuality', 20, 95], ['controlFps', 20, 60], ['controlMaxWidth', 640, 3840], ['controlMaxHeight', 360, 2160], ['controlQuality', 20, 95]])
|
|
172
195
|
}
|
|
173
196
|
};
|
|
174
197
|
|
|
175
|
-
export function migrateLiveDeskSettings(value = {}) {
|
|
176
|
-
const source = value && typeof value === 'object' && !Array.isArray(value) ? structuredClone(value) : {};
|
|
177
|
-
const sourceVersion = Math.max(0, Number(source.settingsSchemaVersion) || 0);
|
|
178
|
-
if (sourceVersion < 3) {
|
|
179
|
-
source.security = {
|
|
180
|
-
...(source.security && typeof source.security === 'object' && !Array.isArray(source.security) ? source.security : {}),
|
|
181
|
-
allowInternetConnections: true
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
return normalizeLiveDeskSettings(source);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
198
|
export function normalizeLiveDeskSettings(value = {}) {
|
|
188
199
|
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
189
200
|
const settings = {
|
|
@@ -196,6 +207,11 @@ export function normalizeLiveDeskSettings(value = {}) {
|
|
|
196
207
|
agent: normalizeSection(source.agent, DEFAULT_LIVEDESK_SETTINGS.agent, RULES.agent),
|
|
197
208
|
advanced: normalizeSection(source.advanced, DEFAULT_LIVEDESK_SETTINGS.advanced, RULES.advanced)
|
|
198
209
|
};
|
|
210
|
+
// Security invariants are enforced at the authority boundary, not just in
|
|
211
|
+
// the browser. Internet access can never use an unencrypted transport.
|
|
212
|
+
if (settings.security.requireEncryptedConnections) {
|
|
213
|
+
settings.security.allowUnencryptedLanFallback = false;
|
|
214
|
+
}
|
|
199
215
|
if (!settings.security.allowInternetConnections) {
|
|
200
216
|
settings.advanced.transport = settings.advanced.transport === 'encrypted' ? 'encrypted' : 'auto';
|
|
201
217
|
}
|
|
@@ -231,16 +247,8 @@ export function effectiveDevicePolicy(settings = DEFAULT_LIVEDESK_SETTINGS) {
|
|
|
231
247
|
accessMode,
|
|
232
248
|
allowInternetConnections: normalized.security.allowInternetConnections,
|
|
233
249
|
allowLanConnections: normalized.security.allowLanConnections,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
requireEncryptedConnections: true,
|
|
237
|
-
allowUnencryptedLanFallback: false,
|
|
238
|
-
visibleControlIndicator: true,
|
|
239
|
-
lockOnControlEnd: normalized.security.lockOnControlEnd,
|
|
240
|
-
disconnectIdleControlSessions: true,
|
|
241
|
-
idleControlMinutes: normalized.security.idleControlMinutes,
|
|
242
|
-
allowFileOverwrite: false,
|
|
243
|
-
maxFileSizeBytes: normalized.filesAudio.maxFileSizeBytes,
|
|
250
|
+
requireEncryptedConnections: normalized.security.requireEncryptedConnections,
|
|
251
|
+
allowUnencryptedLanFallback: normalized.security.allowUnencryptedLanFallback,
|
|
244
252
|
allowControl: allowMutation && normalized.control.allowKeyboardMouse,
|
|
245
253
|
allowClipboardText: allowMutation && normalized.control.allowClipboardText,
|
|
246
254
|
allowPowerActions: allowMutation && normalized.control.allowRemoteRestart,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { DEFAULT_LIVEDESK_SETTINGS,
|
|
3
|
+
import { DEFAULT_LIVEDESK_SETTINGS, defaultSettingsPath, normalizeLiveDeskSettings, publicSettings } from './settings-schema.js';
|
|
4
4
|
|
|
5
5
|
export class SettingsConflictError extends Error {
|
|
6
6
|
constructor(settings) {
|
|
@@ -29,16 +29,11 @@ export class LiveDeskSettingsStore {
|
|
|
29
29
|
if (this.record) return structuredClone(this.record);
|
|
30
30
|
try {
|
|
31
31
|
const raw = JSON.parse(await readFile(this.filePath, 'utf8'));
|
|
32
|
-
const sourceSettings = raw?.settings || raw;
|
|
33
|
-
const sourceSchemaVersion = Math.max(0, Number(sourceSettings?.settingsSchemaVersion) || 0);
|
|
34
32
|
this.record = {
|
|
35
33
|
revision: Math.max(0, Number(raw?.revision) || 0),
|
|
36
34
|
updatedAt: String(raw?.updatedAt || ''),
|
|
37
|
-
settings:
|
|
35
|
+
settings: normalizeLiveDeskSettings(raw?.settings || raw)
|
|
38
36
|
};
|
|
39
|
-
if (sourceSchemaVersion < SETTINGS_SCHEMA_VERSION) {
|
|
40
|
-
await writeJsonAtomic(this.filePath, this.record);
|
|
41
|
-
}
|
|
42
37
|
} catch {
|
|
43
38
|
this.record = { revision: 0, updatedAt: '', settings: normalizeLiveDeskSettings(DEFAULT_LIVEDESK_SETTINGS) };
|
|
44
39
|
}
|