@livedesk/hub 0.1.36 → 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.
@@ -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 = 3;
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
- allowInternetConnections: true,
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
- idleControlMinutes: 30
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
- // 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,
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, SETTINGS_SCHEMA_VERSION, defaultSettingsPath, migrateLiveDeskSettings, normalizeLiveDeskSettings, publicSettings } from './settings-schema.js';
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: migrateLiveDeskSettings(sourceSettings)
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
  }