@livedesk/hub 0.1.32 → 0.1.33
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 +32 -32
- package/src/remote-hub.js +728 -722
- package/src/server.js +1006 -979
- package/src/settings/settings-schema.js +251 -239
- package/src/settings/settings-store.js +82 -77
- package/src/transport/secure-direct-acceptor.js +440 -433
|
@@ -1,239 +1,251 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export const SETTINGS_SCHEMA_VERSION =
|
|
5
|
-
|
|
6
|
-
export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
7
|
-
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
8
|
-
connection: {},
|
|
9
|
-
security: {
|
|
10
|
-
accessMode: 'trusted-only',
|
|
11
|
-
allowInternetConnections:
|
|
12
|
-
allowLanConnections: true,
|
|
13
|
-
lockOnControlEnd: true,
|
|
14
|
-
idleControlMinutes: 30
|
|
15
|
-
},
|
|
16
|
-
control: {
|
|
17
|
-
allowKeyboardMouse: true,
|
|
18
|
-
allowSystemShortcuts: true,
|
|
19
|
-
allowClipboardText: true,
|
|
20
|
-
allowRemoteRestart: false,
|
|
21
|
-
reconnectAfterRemoteRestart: true,
|
|
22
|
-
allowSwitchingMonitors: true,
|
|
23
|
-
showConnectionToolbar: true,
|
|
24
|
-
showRemoteCursor: true,
|
|
25
|
-
openControlOnDoubleClick: true,
|
|
26
|
-
startRemoteAudioWithControl: false,
|
|
27
|
-
fitRemoteScreen: true,
|
|
28
|
-
rememberLastMonitor: true,
|
|
29
|
-
keepControlReadyBetweenPages: true
|
|
30
|
-
},
|
|
31
|
-
wall: {
|
|
32
|
-
performanceMode: 'auto',
|
|
33
|
-
autoStart: true,
|
|
34
|
-
connectedOnly: false,
|
|
35
|
-
keepEmptySlots: true,
|
|
36
|
-
showDeviceStatus: true,
|
|
37
|
-
showPerformanceDetails: false,
|
|
38
|
-
pauseHiddenTiles: true,
|
|
39
|
-
reduceWhenHidden: true,
|
|
40
|
-
autoAdjustTileQuality: true,
|
|
41
|
-
rememberDevicePositions: true
|
|
42
|
-
},
|
|
43
|
-
filesAudio: {
|
|
44
|
-
allowFileTransfer: true,
|
|
45
|
-
allowFolderSync: false,
|
|
46
|
-
openReceivedFolder: false,
|
|
47
|
-
notifyTransferComplete: true,
|
|
48
|
-
defaultReceiveFolder: 'Desktop/LiveDeskFiles',
|
|
49
|
-
maxFileSizeBytes: 1024 * 1024 * 1024,
|
|
50
|
-
allowRemoteAudio: true,
|
|
51
|
-
startAudioMuted: false,
|
|
52
|
-
rememberVolume: true,
|
|
53
|
-
automaticallyRecoverAudio: true,
|
|
54
|
-
showAudioTroubleshooting: false,
|
|
55
|
-
rollingBufferEnabled: false,
|
|
56
|
-
recordingQuality: 'standard',
|
|
57
|
-
timelapseIntervalSeconds: 10,
|
|
58
|
-
includeRemoteCursor: true,
|
|
59
|
-
captureSaveLocation: 'LiveDesk Captures',
|
|
60
|
-
captureAutoDelete: 'never'
|
|
61
|
-
},
|
|
62
|
-
agent: {
|
|
63
|
-
enabled: false,
|
|
64
|
-
defaultPermissionMode: 'safe-auto',
|
|
65
|
-
askBeforeDestructive: true,
|
|
66
|
-
allowProcessManagement: true,
|
|
67
|
-
allowServiceManagement: true,
|
|
68
|
-
allowFileChanges: true,
|
|
69
|
-
allowCommandExecution: false,
|
|
70
|
-
allowSoftwareInstallation: false,
|
|
71
|
-
allowPowerActions: false,
|
|
72
|
-
keepAuditHistory: true,
|
|
73
|
-
timeoutMinutes: 10
|
|
74
|
-
},
|
|
75
|
-
advanced: {
|
|
76
|
-
wallFrameMode: 'auto',
|
|
77
|
-
wallFps: 20,
|
|
78
|
-
wallMaxWidth: 960,
|
|
79
|
-
wallMaxHeight: 540,
|
|
80
|
-
wallQuality: 55,
|
|
81
|
-
controlFrameMode: 'mode3-h264-hw',
|
|
82
|
-
controlFps: 30,
|
|
83
|
-
controlMaxWidth: 1920,
|
|
84
|
-
controlMaxHeight: 1080,
|
|
85
|
-
controlQuality: 60,
|
|
86
|
-
transport: 'auto',
|
|
87
|
-
verboseLogs: false,
|
|
88
|
-
frameStatistics: false
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const ENUMS = {
|
|
93
|
-
accessMode: new Set(['trusted-only', 'ask-every-time', 'view-only', 'block-remote-access']),
|
|
94
|
-
performanceMode: new Set(['auto', 'responsive', 'quality', 'custom']),
|
|
95
|
-
permissionMode: new Set(['ask', 'safe-auto', 'full-access', 'custom']),
|
|
96
|
-
wallFrameMode: new Set(['auto', 'mode2-lzo', 'mode3-h264-hw', 'mode4-h264-atlas']),
|
|
97
|
-
controlFrameMode: new Set(['mode3-h264-hw', 'mode5-lzo-delta']),
|
|
98
|
-
transport: new Set(['auto', 'encrypted']),
|
|
99
|
-
recordingQuality: new Set(['standard', 'high']),
|
|
100
|
-
captureAutoDelete: new Set(['never', '7-days', '30-days'])
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
function booleanValue(value, fallback) {
|
|
104
|
-
return typeof value === 'boolean' ? value : fallback;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function numberValue(value, min, max, fallback) {
|
|
108
|
-
const number = Number(value);
|
|
109
|
-
return Number.isFinite(number) ? Math.max(min, Math.min(max, Math.round(number))) : fallback;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function enumValue(value, allowed, fallback) {
|
|
113
|
-
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
114
|
-
return allowed.has(normalized) ? normalized : fallback;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function stringValue(value, fallback, maxLength = 600) {
|
|
118
|
-
return typeof value === 'string' ? value.replace(/[\0\r\n]/g, ' ').trim().slice(0, maxLength) : fallback;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function normalizeSection(source, defaults, rules = {}) {
|
|
122
|
-
const input = source && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
123
|
-
const result = {};
|
|
124
|
-
for (const [key, fallback] of Object.entries(defaults)) {
|
|
125
|
-
const rule = rules[key];
|
|
126
|
-
if (rule?.type === 'boolean') result[key] = booleanValue(input[key], fallback);
|
|
127
|
-
else if (rule?.type === 'number') result[key] = numberValue(input[key], rule.min, rule.max, fallback);
|
|
128
|
-
else if (rule?.type === 'enum') result[key] = enumValue(input[key], rule.values, fallback);
|
|
129
|
-
else if (rule?.type === 'string') result[key] = stringValue(input[key], fallback, rule.maxLength);
|
|
130
|
-
else result[key] = input[key] === undefined ? fallback : fallback;
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const bools = keys => Object.fromEntries(keys.map(key => [key, { type: 'boolean' }]));
|
|
136
|
-
const numbers = (entries) => Object.fromEntries(entries.map(([key, min, max]) => [key, { type: 'number', min, max }]));
|
|
137
|
-
|
|
138
|
-
const RULES = {
|
|
139
|
-
connection: {},
|
|
140
|
-
security: {
|
|
141
|
-
accessMode: { type: 'enum', values: ENUMS.accessMode },
|
|
142
|
-
...bools(['allowInternetConnections', 'allowLanConnections', 'lockOnControlEnd']),
|
|
143
|
-
...numbers([['idleControlMinutes', 5, 1440]])
|
|
144
|
-
},
|
|
145
|
-
control: {
|
|
146
|
-
...bools(['allowKeyboardMouse', 'allowSystemShortcuts', 'allowClipboardText', 'allowRemoteRestart', 'reconnectAfterRemoteRestart', 'allowSwitchingMonitors', 'showConnectionToolbar', 'showRemoteCursor', 'openControlOnDoubleClick', 'startRemoteAudioWithControl', 'fitRemoteScreen', 'rememberLastMonitor', 'keepControlReadyBetweenPages'])
|
|
147
|
-
},
|
|
148
|
-
wall: {
|
|
149
|
-
performanceMode: { type: 'enum', values: ENUMS.performanceMode },
|
|
150
|
-
...bools(['autoStart', 'connectedOnly', 'keepEmptySlots', 'showDeviceStatus', 'showPerformanceDetails', 'pauseHiddenTiles', 'reduceWhenHidden', 'autoAdjustTileQuality', 'rememberDevicePositions'])
|
|
151
|
-
},
|
|
152
|
-
filesAudio: {
|
|
153
|
-
...bools(['allowFileTransfer', 'allowFolderSync', 'openReceivedFolder', 'notifyTransferComplete', 'allowRemoteAudio', 'startAudioMuted', 'rememberVolume', 'automaticallyRecoverAudio', 'showAudioTroubleshooting', 'rollingBufferEnabled', 'includeRemoteCursor']),
|
|
154
|
-
recordingQuality: { type: 'enum', values: ENUMS.recordingQuality },
|
|
155
|
-
captureAutoDelete: { type: 'enum', values: ENUMS.captureAutoDelete },
|
|
156
|
-
timelapseIntervalSeconds: { type: 'number', min: 5, max: 60 },
|
|
157
|
-
defaultReceiveFolder: { type: 'string', maxLength: 600 },
|
|
158
|
-
captureSaveLocation: { type: 'string', maxLength: 120 },
|
|
159
|
-
maxFileSizeBytes: { type: 'number', min: 1, max: Number.MAX_SAFE_INTEGER }
|
|
160
|
-
},
|
|
161
|
-
agent: {
|
|
162
|
-
defaultPermissionMode: { type: 'enum', values: ENUMS.permissionMode },
|
|
163
|
-
...bools(['enabled', 'askBeforeDestructive', 'allowProcessManagement', 'allowServiceManagement', 'allowFileChanges', 'allowCommandExecution', 'allowSoftwareInstallation', 'allowPowerActions', 'keepAuditHistory']),
|
|
164
|
-
timeoutMinutes: { type: 'number', min: 1, max: 120 }
|
|
165
|
-
},
|
|
166
|
-
advanced: {
|
|
167
|
-
wallFrameMode: { type: 'enum', values: ENUMS.wallFrameMode },
|
|
168
|
-
controlFrameMode: { type: 'enum', values: ENUMS.controlFrameMode },
|
|
169
|
-
transport: { type: 'enum', values: ENUMS.transport },
|
|
170
|
-
...bools(['verboseLogs', 'frameStatistics']),
|
|
171
|
-
...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
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
export function
|
|
176
|
-
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const SETTINGS_SCHEMA_VERSION = 3;
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
7
|
+
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
8
|
+
connection: {},
|
|
9
|
+
security: {
|
|
10
|
+
accessMode: 'trusted-only',
|
|
11
|
+
allowInternetConnections: true,
|
|
12
|
+
allowLanConnections: true,
|
|
13
|
+
lockOnControlEnd: true,
|
|
14
|
+
idleControlMinutes: 30
|
|
15
|
+
},
|
|
16
|
+
control: {
|
|
17
|
+
allowKeyboardMouse: true,
|
|
18
|
+
allowSystemShortcuts: true,
|
|
19
|
+
allowClipboardText: true,
|
|
20
|
+
allowRemoteRestart: false,
|
|
21
|
+
reconnectAfterRemoteRestart: true,
|
|
22
|
+
allowSwitchingMonitors: true,
|
|
23
|
+
showConnectionToolbar: true,
|
|
24
|
+
showRemoteCursor: true,
|
|
25
|
+
openControlOnDoubleClick: true,
|
|
26
|
+
startRemoteAudioWithControl: false,
|
|
27
|
+
fitRemoteScreen: true,
|
|
28
|
+
rememberLastMonitor: true,
|
|
29
|
+
keepControlReadyBetweenPages: true
|
|
30
|
+
},
|
|
31
|
+
wall: {
|
|
32
|
+
performanceMode: 'auto',
|
|
33
|
+
autoStart: true,
|
|
34
|
+
connectedOnly: false,
|
|
35
|
+
keepEmptySlots: true,
|
|
36
|
+
showDeviceStatus: true,
|
|
37
|
+
showPerformanceDetails: false,
|
|
38
|
+
pauseHiddenTiles: true,
|
|
39
|
+
reduceWhenHidden: true,
|
|
40
|
+
autoAdjustTileQuality: true,
|
|
41
|
+
rememberDevicePositions: true
|
|
42
|
+
},
|
|
43
|
+
filesAudio: {
|
|
44
|
+
allowFileTransfer: true,
|
|
45
|
+
allowFolderSync: false,
|
|
46
|
+
openReceivedFolder: false,
|
|
47
|
+
notifyTransferComplete: true,
|
|
48
|
+
defaultReceiveFolder: 'Desktop/LiveDeskFiles',
|
|
49
|
+
maxFileSizeBytes: 1024 * 1024 * 1024,
|
|
50
|
+
allowRemoteAudio: true,
|
|
51
|
+
startAudioMuted: false,
|
|
52
|
+
rememberVolume: true,
|
|
53
|
+
automaticallyRecoverAudio: true,
|
|
54
|
+
showAudioTroubleshooting: false,
|
|
55
|
+
rollingBufferEnabled: false,
|
|
56
|
+
recordingQuality: 'standard',
|
|
57
|
+
timelapseIntervalSeconds: 10,
|
|
58
|
+
includeRemoteCursor: true,
|
|
59
|
+
captureSaveLocation: 'LiveDesk Captures',
|
|
60
|
+
captureAutoDelete: 'never'
|
|
61
|
+
},
|
|
62
|
+
agent: {
|
|
63
|
+
enabled: false,
|
|
64
|
+
defaultPermissionMode: 'safe-auto',
|
|
65
|
+
askBeforeDestructive: true,
|
|
66
|
+
allowProcessManagement: true,
|
|
67
|
+
allowServiceManagement: true,
|
|
68
|
+
allowFileChanges: true,
|
|
69
|
+
allowCommandExecution: false,
|
|
70
|
+
allowSoftwareInstallation: false,
|
|
71
|
+
allowPowerActions: false,
|
|
72
|
+
keepAuditHistory: true,
|
|
73
|
+
timeoutMinutes: 10
|
|
74
|
+
},
|
|
75
|
+
advanced: {
|
|
76
|
+
wallFrameMode: 'auto',
|
|
77
|
+
wallFps: 20,
|
|
78
|
+
wallMaxWidth: 960,
|
|
79
|
+
wallMaxHeight: 540,
|
|
80
|
+
wallQuality: 55,
|
|
81
|
+
controlFrameMode: 'mode3-h264-hw',
|
|
82
|
+
controlFps: 30,
|
|
83
|
+
controlMaxWidth: 1920,
|
|
84
|
+
controlMaxHeight: 1080,
|
|
85
|
+
controlQuality: 60,
|
|
86
|
+
transport: 'auto',
|
|
87
|
+
verboseLogs: false,
|
|
88
|
+
frameStatistics: false
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const ENUMS = {
|
|
93
|
+
accessMode: new Set(['trusted-only', 'ask-every-time', 'view-only', 'block-remote-access']),
|
|
94
|
+
performanceMode: new Set(['auto', 'responsive', 'quality', 'custom']),
|
|
95
|
+
permissionMode: new Set(['ask', 'safe-auto', 'full-access', 'custom']),
|
|
96
|
+
wallFrameMode: new Set(['auto', 'mode2-lzo', 'mode3-h264-hw', 'mode4-h264-atlas']),
|
|
97
|
+
controlFrameMode: new Set(['mode3-h264-hw', 'mode5-lzo-delta']),
|
|
98
|
+
transport: new Set(['auto', 'encrypted']),
|
|
99
|
+
recordingQuality: new Set(['standard', 'high']),
|
|
100
|
+
captureAutoDelete: new Set(['never', '7-days', '30-days'])
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function booleanValue(value, fallback) {
|
|
104
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function numberValue(value, min, max, fallback) {
|
|
108
|
+
const number = Number(value);
|
|
109
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, Math.round(number))) : fallback;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function enumValue(value, allowed, fallback) {
|
|
113
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
114
|
+
return allowed.has(normalized) ? normalized : fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stringValue(value, fallback, maxLength = 600) {
|
|
118
|
+
return typeof value === 'string' ? value.replace(/[\0\r\n]/g, ' ').trim().slice(0, maxLength) : fallback;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeSection(source, defaults, rules = {}) {
|
|
122
|
+
const input = source && typeof source === 'object' && !Array.isArray(source) ? source : {};
|
|
123
|
+
const result = {};
|
|
124
|
+
for (const [key, fallback] of Object.entries(defaults)) {
|
|
125
|
+
const rule = rules[key];
|
|
126
|
+
if (rule?.type === 'boolean') result[key] = booleanValue(input[key], fallback);
|
|
127
|
+
else if (rule?.type === 'number') result[key] = numberValue(input[key], rule.min, rule.max, fallback);
|
|
128
|
+
else if (rule?.type === 'enum') result[key] = enumValue(input[key], rule.values, fallback);
|
|
129
|
+
else if (rule?.type === 'string') result[key] = stringValue(input[key], fallback, rule.maxLength);
|
|
130
|
+
else result[key] = input[key] === undefined ? fallback : fallback;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const bools = keys => Object.fromEntries(keys.map(key => [key, { type: 'boolean' }]));
|
|
136
|
+
const numbers = (entries) => Object.fromEntries(entries.map(([key, min, max]) => [key, { type: 'number', min, max }]));
|
|
137
|
+
|
|
138
|
+
const RULES = {
|
|
139
|
+
connection: {},
|
|
140
|
+
security: {
|
|
141
|
+
accessMode: { type: 'enum', values: ENUMS.accessMode },
|
|
142
|
+
...bools(['allowInternetConnections', 'allowLanConnections', 'lockOnControlEnd']),
|
|
143
|
+
...numbers([['idleControlMinutes', 5, 1440]])
|
|
144
|
+
},
|
|
145
|
+
control: {
|
|
146
|
+
...bools(['allowKeyboardMouse', 'allowSystemShortcuts', 'allowClipboardText', 'allowRemoteRestart', 'reconnectAfterRemoteRestart', 'allowSwitchingMonitors', 'showConnectionToolbar', 'showRemoteCursor', 'openControlOnDoubleClick', 'startRemoteAudioWithControl', 'fitRemoteScreen', 'rememberLastMonitor', 'keepControlReadyBetweenPages'])
|
|
147
|
+
},
|
|
148
|
+
wall: {
|
|
149
|
+
performanceMode: { type: 'enum', values: ENUMS.performanceMode },
|
|
150
|
+
...bools(['autoStart', 'connectedOnly', 'keepEmptySlots', 'showDeviceStatus', 'showPerformanceDetails', 'pauseHiddenTiles', 'reduceWhenHidden', 'autoAdjustTileQuality', 'rememberDevicePositions'])
|
|
151
|
+
},
|
|
152
|
+
filesAudio: {
|
|
153
|
+
...bools(['allowFileTransfer', 'allowFolderSync', 'openReceivedFolder', 'notifyTransferComplete', 'allowRemoteAudio', 'startAudioMuted', 'rememberVolume', 'automaticallyRecoverAudio', 'showAudioTroubleshooting', 'rollingBufferEnabled', 'includeRemoteCursor']),
|
|
154
|
+
recordingQuality: { type: 'enum', values: ENUMS.recordingQuality },
|
|
155
|
+
captureAutoDelete: { type: 'enum', values: ENUMS.captureAutoDelete },
|
|
156
|
+
timelapseIntervalSeconds: { type: 'number', min: 5, max: 60 },
|
|
157
|
+
defaultReceiveFolder: { type: 'string', maxLength: 600 },
|
|
158
|
+
captureSaveLocation: { type: 'string', maxLength: 120 },
|
|
159
|
+
maxFileSizeBytes: { type: 'number', min: 1, max: Number.MAX_SAFE_INTEGER }
|
|
160
|
+
},
|
|
161
|
+
agent: {
|
|
162
|
+
defaultPermissionMode: { type: 'enum', values: ENUMS.permissionMode },
|
|
163
|
+
...bools(['enabled', 'askBeforeDestructive', 'allowProcessManagement', 'allowServiceManagement', 'allowFileChanges', 'allowCommandExecution', 'allowSoftwareInstallation', 'allowPowerActions', 'keepAuditHistory']),
|
|
164
|
+
timeoutMinutes: { type: 'number', min: 1, max: 120 }
|
|
165
|
+
},
|
|
166
|
+
advanced: {
|
|
167
|
+
wallFrameMode: { type: 'enum', values: ENUMS.wallFrameMode },
|
|
168
|
+
controlFrameMode: { type: 'enum', values: ENUMS.controlFrameMode },
|
|
169
|
+
transport: { type: 'enum', values: ENUMS.transport },
|
|
170
|
+
...bools(['verboseLogs', 'frameStatistics']),
|
|
171
|
+
...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
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
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
|
+
export function normalizeLiveDeskSettings(value = {}) {
|
|
188
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
189
|
+
const settings = {
|
|
190
|
+
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
191
|
+
connection: normalizeSection(source.connection, DEFAULT_LIVEDESK_SETTINGS.connection, RULES.connection),
|
|
192
|
+
security: normalizeSection(source.security, DEFAULT_LIVEDESK_SETTINGS.security, RULES.security),
|
|
193
|
+
control: normalizeSection(source.control, DEFAULT_LIVEDESK_SETTINGS.control, RULES.control),
|
|
194
|
+
wall: normalizeSection(source.wall, DEFAULT_LIVEDESK_SETTINGS.wall, RULES.wall),
|
|
195
|
+
filesAudio: normalizeSection(source.filesAudio, DEFAULT_LIVEDESK_SETTINGS.filesAudio, RULES.filesAudio),
|
|
196
|
+
agent: normalizeSection(source.agent, DEFAULT_LIVEDESK_SETTINGS.agent, RULES.agent),
|
|
197
|
+
advanced: normalizeSection(source.advanced, DEFAULT_LIVEDESK_SETTINGS.advanced, RULES.advanced)
|
|
198
|
+
};
|
|
199
|
+
if (!settings.security.allowInternetConnections) {
|
|
200
|
+
settings.advanced.transport = settings.advanced.transport === 'encrypted' ? 'encrypted' : 'auto';
|
|
201
|
+
}
|
|
202
|
+
if (settings.agent.defaultPermissionMode === 'safe-auto') {
|
|
203
|
+
settings.agent.askBeforeDestructive = true;
|
|
204
|
+
}
|
|
205
|
+
if (![5, 10, 30, 60].includes(settings.filesAudio.timelapseIntervalSeconds)) {
|
|
206
|
+
settings.filesAudio.timelapseIntervalSeconds = 10;
|
|
207
|
+
}
|
|
208
|
+
return settings;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function defaultSettingsPath(dataDir = undefined) {
|
|
212
|
+
return path.join(dataDir || path.join(os.homedir(), '.livedesk'), 'settings.json');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function publicSettings(settings) {
|
|
216
|
+
return normalizeLiveDeskSettings(settings);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function effectiveDevicePolicy(settings = DEFAULT_LIVEDESK_SETTINGS) {
|
|
220
|
+
const normalized = normalizeLiveDeskSettings(settings);
|
|
221
|
+
// The retired ask-every-time surface has no interactive approval authority,
|
|
222
|
+
// so a persisted legacy value must fail closed. View-only remains able to
|
|
223
|
+
// receive video/audio while every mutating operation is blocked.
|
|
224
|
+
const accessMode = normalized.security.accessMode === 'ask-every-time'
|
|
225
|
+
? 'block-remote-access'
|
|
226
|
+
: normalized.security.accessMode;
|
|
227
|
+
const allowMutation = accessMode === 'trusted-only';
|
|
228
|
+
return {
|
|
229
|
+
settingsSchemaVersion: normalized.settingsSchemaVersion,
|
|
230
|
+
policyRevision: Number(settings?.revision || 0),
|
|
231
|
+
accessMode,
|
|
232
|
+
allowInternetConnections: normalized.security.allowInternetConnections,
|
|
233
|
+
allowLanConnections: normalized.security.allowLanConnections,
|
|
234
|
+
// Encryption and no-overwrite are structural invariants. They are not
|
|
235
|
+
// user preferences and cannot be disabled through a legacy settings file.
|
|
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,
|
|
244
|
+
allowControl: allowMutation && normalized.control.allowKeyboardMouse,
|
|
245
|
+
allowClipboardText: allowMutation && normalized.control.allowClipboardText,
|
|
246
|
+
allowPowerActions: allowMutation && normalized.control.allowRemoteRestart,
|
|
247
|
+
allowFileTransfer: allowMutation && normalized.filesAudio.allowFileTransfer,
|
|
248
|
+
allowRemoteAudio: accessMode !== 'block-remote-access' && normalized.filesAudio.allowRemoteAudio,
|
|
249
|
+
allowAgent: allowMutation && normalized.agent.enabled
|
|
250
|
+
};
|
|
251
|
+
}
|