livedesk 0.1.618 → 0.1.620
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/README.md +6 -5
- package/client/README.md +4 -3
- package/electron/after-pack.cjs +31 -0
- package/electron/desktop-auto-start.mjs +190 -0
- package/electron/electron-builder.linux-x64.yml +3 -2
- package/electron/main.mjs +36 -12
- package/electron/preload.cjs +6 -4
- package/hub/src/server.js +26 -18
- package/hub/src/settings/settings-schema.js +13 -8
- package/hub/src/settings/settings-store.js +16 -6
- package/package.json +1 -1
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/LiveDeskApp-CawelBc0.js +179 -0
- package/web/dist/assets/{icons-tFaWqIIe.js → icons-GaObu1Hf.js} +1 -1
- package/web/dist/assets/index-BHHG5RLh.css +1 -0
- package/web/dist/assets/{index-DDscywyb.js → index-CK8VE5Gn.js} +3 -3
- package/web/dist/assets/{react-BQos8uGS.js → react-BF-iFH2F.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/dist/livedesk-build-evidence.json +24 -24
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/LiveDeskApp-CJlfOFdK.js +0 -179
- package/web/dist/assets/index-BIuqDxML.css +0 -1
package/README.md
CHANGED
|
@@ -69,11 +69,12 @@ and startup retry, then exits without looping.
|
|
|
69
69
|
npx -y --prefer-online livedesk@latest client 3
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
The `client` form remains as a legacy compatibility alias. It records the
|
|
73
|
-
Client role and uses the same unified runtime lock. The client signs in with
|
|
74
|
-
Google, discovers the active Hub, and connects to the wall.
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
The `client` form remains as a legacy compatibility alias. It records the
|
|
73
|
+
Client role and uses the same unified runtime lock. The client signs in with
|
|
74
|
+
Google, discovers the active Hub, and connects to the wall. Installed Windows,
|
|
75
|
+
macOS, and Linux desktop apps start automatically after sign-in by default,
|
|
76
|
+
reuse the saved session, and reconnect without opening the window. Turn this
|
|
77
|
+
off in **Settings > Desktop app** when a computer should remain manual.
|
|
77
78
|
|
|
78
79
|
## macOS physical diagnostic
|
|
79
80
|
|
package/client/README.md
CHANGED
|
@@ -30,9 +30,10 @@ discovers the newly published Hub without another Google login. Omit the number
|
|
|
30
30
|
for first-available placement, or pass `1` to `999` to pin this machine to a
|
|
31
31
|
screen wall slot.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
Installed VuvoDesk desktop apps start automatically after sign-in on Windows,
|
|
34
|
+
macOS, and Linux by default. The startup entry reuses the saved Google session,
|
|
35
|
+
reconnects quietly, and only opens the connection page again if sign-in or
|
|
36
|
+
first-run role setup is needed. Turn this off in **Settings > Desktop app**.
|
|
36
37
|
|
|
37
38
|
On the Hub computer, open the VuvoDesk dashboard, sign in, and run **Sync
|
|
38
39
|
Server** once. The dashboard transfers the signed-in session to the local Hub;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { chmodSync, existsSync, statSync } = require('node:fs');
|
|
2
|
+
const { join } = require('node:path');
|
|
3
|
+
|
|
4
|
+
module.exports = async function afterPack(context) {
|
|
5
|
+
if (context.electronPlatformName !== 'linux') {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const remoteFastExecutable = join(
|
|
10
|
+
context.appOutDir,
|
|
11
|
+
'resources',
|
|
12
|
+
'app.asar.unpacked',
|
|
13
|
+
'node_modules',
|
|
14
|
+
'@livedesk',
|
|
15
|
+
'fast-linux-x64',
|
|
16
|
+
'fast',
|
|
17
|
+
'livedesk-client-fast'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (!existsSync(remoteFastExecutable)) {
|
|
21
|
+
throw new Error(`Packaged Linux RemoteFast executable is missing: ${remoteFastExecutable}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
chmodSync(remoteFastExecutable, 0o755);
|
|
25
|
+
const executableMode = statSync(remoteFastExecutable).mode & 0o777;
|
|
26
|
+
if ((executableMode & 0o111) !== 0o111) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Packaged Linux RemoteFast must be executable before AppImage/deb assembly; mode=${executableMode.toString(8)}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const DESKTOP_AUTO_START_ARGUMENT = '--autostart';
|
|
6
|
+
export const DESKTOP_AUTO_START_ENTRY_NAME = 'LiveDesk Desktop';
|
|
7
|
+
const DESKTOP_AUTO_START_PREFERENCE_VERSION = 1;
|
|
8
|
+
const SUPPORTED_PLATFORMS = new Set(['win32', 'darwin', 'linux']);
|
|
9
|
+
|
|
10
|
+
function readPreference(path) {
|
|
11
|
+
try {
|
|
12
|
+
const record = JSON.parse(readFileSync(path, 'utf8'));
|
|
13
|
+
return record?.version === DESKTOP_AUTO_START_PREFERENCE_VERSION && typeof record.enabled === 'boolean'
|
|
14
|
+
? record.enabled
|
|
15
|
+
: null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writePreference(path, enabled) {
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
23
|
+
writeFileSync(path, `${JSON.stringify({
|
|
24
|
+
version: DESKTOP_AUTO_START_PREFERENCE_VERSION,
|
|
25
|
+
enabled: Boolean(enabled),
|
|
26
|
+
savedAt: new Date().toISOString()
|
|
27
|
+
}, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function quoteDesktopExecArgument(value) {
|
|
31
|
+
const escaped = String(value || '')
|
|
32
|
+
.replace(/\\/g, '\\\\')
|
|
33
|
+
.replace(/"/g, '\\"')
|
|
34
|
+
.replace(/\$/g, '\\$')
|
|
35
|
+
.replace(/`/g, '\\`');
|
|
36
|
+
return `"${escaped}"`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function linuxDesktopEntry(executablePath) {
|
|
40
|
+
return [
|
|
41
|
+
'[Desktop Entry]',
|
|
42
|
+
'Type=Application',
|
|
43
|
+
'Version=1.0',
|
|
44
|
+
'Name=VuvoDesk',
|
|
45
|
+
'Comment=Start VuvoDesk and reconnect this computer automatically',
|
|
46
|
+
`Exec=${quoteDesktopExecArgument(executablePath)} ${DESKTOP_AUTO_START_ARGUMENT}`,
|
|
47
|
+
'Terminal=false',
|
|
48
|
+
'StartupNotify=false',
|
|
49
|
+
'X-GNOME-Autostart-enabled=true',
|
|
50
|
+
'X-VuvoDesk-Managed=true',
|
|
51
|
+
''
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createDesktopAutoStartOwner({
|
|
56
|
+
app,
|
|
57
|
+
stateRoot,
|
|
58
|
+
platform = process.platform,
|
|
59
|
+
environment = process.env,
|
|
60
|
+
executablePath = process.execPath,
|
|
61
|
+
argv = process.argv,
|
|
62
|
+
userHome = homedir(),
|
|
63
|
+
isPackaged = app?.isPackaged === true,
|
|
64
|
+
log = () => {}
|
|
65
|
+
}) {
|
|
66
|
+
const supported = isPackaged && SUPPORTED_PLATFORMS.has(platform);
|
|
67
|
+
const preferencePath = join(stateRoot, 'desktop-auto-start.json');
|
|
68
|
+
const linuxConfigRoot = environment.XDG_CONFIG_HOME
|
|
69
|
+
? resolve(environment.XDG_CONFIG_HOME)
|
|
70
|
+
: join(userHome, '.config');
|
|
71
|
+
const linuxEntryPath = join(linuxConfigRoot, 'autostart', 'vuvodesk.desktop');
|
|
72
|
+
const linuxExecutablePath = resolve(String(environment.APPIMAGE || executablePath));
|
|
73
|
+
const windowsLoginOptions = {
|
|
74
|
+
path: executablePath,
|
|
75
|
+
args: [DESKTOP_AUTO_START_ARGUMENT]
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function desiredEnabled() {
|
|
79
|
+
return readPreference(preferencePath) ?? true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function platformRegistered() {
|
|
83
|
+
if (!supported) return false;
|
|
84
|
+
if (platform === 'linux') {
|
|
85
|
+
try {
|
|
86
|
+
const entry = readFileSync(linuxEntryPath, 'utf8');
|
|
87
|
+
return entry.includes('X-VuvoDesk-Managed=true')
|
|
88
|
+
&& entry.includes(DESKTOP_AUTO_START_ARGUMENT)
|
|
89
|
+
&& entry.includes(quoteDesktopExecArgument(linuxExecutablePath));
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const settings = platform === 'win32'
|
|
96
|
+
? app.getLoginItemSettings(windowsLoginOptions)
|
|
97
|
+
: app.getLoginItemSettings();
|
|
98
|
+
return settings?.openAtLogin === true;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function status(extra = {}) {
|
|
105
|
+
return {
|
|
106
|
+
supported,
|
|
107
|
+
enabled: desiredEnabled(),
|
|
108
|
+
registered: platformRegistered(),
|
|
109
|
+
platform,
|
|
110
|
+
...extra
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function apply(enabled) {
|
|
115
|
+
if (platform === 'linux') {
|
|
116
|
+
if (!enabled) {
|
|
117
|
+
rmSync(linuxEntryPath, { force: true });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
mkdirSync(dirname(linuxEntryPath), { recursive: true });
|
|
121
|
+
writeFileSync(linuxEntryPath, linuxDesktopEntry(linuxExecutablePath), { encoding: 'utf8', mode: 0o600 });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (platform === 'win32') {
|
|
125
|
+
app.setLoginItemSettings({
|
|
126
|
+
openAtLogin: enabled,
|
|
127
|
+
name: DESKTOP_AUTO_START_ENTRY_NAME,
|
|
128
|
+
...windowsLoginOptions
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
app.setLoginItemSettings({
|
|
133
|
+
openAtLogin: enabled,
|
|
134
|
+
openAsHidden: enabled
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function reconcile() {
|
|
139
|
+
if (!supported) return status({ ok: false, error: 'desktop-auto-start-unavailable' });
|
|
140
|
+
const saved = readPreference(preferencePath);
|
|
141
|
+
const enabled = saved ?? true;
|
|
142
|
+
if (saved === null) writePreference(preferencePath, enabled);
|
|
143
|
+
try {
|
|
144
|
+
apply(enabled);
|
|
145
|
+
const next = status();
|
|
146
|
+
const ok = next.registered === enabled;
|
|
147
|
+
if (!ok) log(`desktop auto-start reconcile did not reach enabled=${enabled}`);
|
|
148
|
+
return { ...next, ok, error: ok ? '' : 'desktop-auto-start-registration-mismatch' };
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const message = String(error?.message || error);
|
|
151
|
+
log(`desktop auto-start reconcile failed: ${message}`);
|
|
152
|
+
return status({ ok: false, error: message });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function setEnabled(enabled) {
|
|
157
|
+
if (!supported) return status({ ok: false, error: 'desktop-auto-start-unavailable' });
|
|
158
|
+
const desired = Boolean(enabled);
|
|
159
|
+
writePreference(preferencePath, desired);
|
|
160
|
+
try {
|
|
161
|
+
apply(desired);
|
|
162
|
+
const next = status();
|
|
163
|
+
const ok = next.registered === desired;
|
|
164
|
+
return { ...next, ok, error: ok ? '' : 'desktop-auto-start-registration-mismatch' };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const message = String(error?.message || error);
|
|
167
|
+
log(`desktop auto-start update failed: ${message}`);
|
|
168
|
+
return status({ ok: false, error: message });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function wasOpenedAutomatically() {
|
|
173
|
+
if (argv.includes(DESKTOP_AUTO_START_ARGUMENT)) return true;
|
|
174
|
+
if (platform !== 'darwin' || !supported) return false;
|
|
175
|
+
try {
|
|
176
|
+
return app.getLoginItemSettings()?.wasOpenedAtLogin === true;
|
|
177
|
+
} catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
getStatus: status,
|
|
184
|
+
reconcile,
|
|
185
|
+
setEnabled,
|
|
186
|
+
wasOpenedAutomatically,
|
|
187
|
+
preferencePath,
|
|
188
|
+
linuxEntryPath
|
|
189
|
+
};
|
|
190
|
+
}
|
package/electron/main.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
} from './runtime-role-transition.mjs';
|
|
48
48
|
import { createDesktopClipboardOwner } from './desktop-clipboard-owner.mjs';
|
|
49
49
|
import { createRuntimeWindowLoadOwner } from './runtime-window-load-owner.mjs';
|
|
50
|
+
import { createDesktopAutoStartOwner } from './desktop-auto-start.mjs';
|
|
50
51
|
|
|
51
52
|
// Chromium otherwise leaves an out-of-process Audio Service (~80-90 MiB on
|
|
52
53
|
// Windows) alive after the exact Remote Audio owner, AudioContext, worklet,
|
|
@@ -55,9 +56,8 @@ import { createRuntimeWindowLoadOwner } from './runtime-window-load-owner.mjs';
|
|
|
55
56
|
// warm-idle process baseline instead of retaining a hidden utility process.
|
|
56
57
|
app.commandLine.appendSwitch('disable-features', 'AudioServiceOutOfProcess');
|
|
57
58
|
|
|
58
|
-
const APP_NAME = 'VuvoDesk Desktop';
|
|
59
|
-
const
|
|
60
|
-
const appWindowTitle = () => `${APP_NAME} · ${app.getVersion()}`;
|
|
59
|
+
const APP_NAME = 'VuvoDesk Desktop';
|
|
60
|
+
const appWindowTitle = () => `${APP_NAME} · ${app.getVersion()}`;
|
|
61
61
|
const LOCAL_RUNTIME_PORT = Number(process.env.LIVEDESK_LOCAL_RUNTIME_PORT || 5179);
|
|
62
62
|
const LOCAL_RUNTIME_URL = `http://127.0.0.1:${Number.isInteger(LOCAL_RUNTIME_PORT) && LOCAL_RUNTIME_PORT > 0 ? LOCAL_RUNTIME_PORT : 5179}/`;
|
|
63
63
|
function readDesktopPackageMetadata() {
|
|
@@ -172,6 +172,7 @@ let desktopSessionEpoch = 0;
|
|
|
172
172
|
let lastKnownDesktopSession = null;
|
|
173
173
|
let desktopClipboardOwner = null;
|
|
174
174
|
let runtimeWindowLoadOwner = null;
|
|
175
|
+
let desktopAutoStartOwner = null;
|
|
175
176
|
const desktopAuthInvalidationGuard = createDesktopAuthInvalidationGuard();
|
|
176
177
|
const installerQuitGate = createInstallerQuitGate({
|
|
177
178
|
requestQuit: () => app.quit()
|
|
@@ -1089,7 +1090,7 @@ function showWindow() {
|
|
|
1089
1090
|
requestDesktopResumeAuthRecovery('window-show');
|
|
1090
1091
|
}
|
|
1091
1092
|
|
|
1092
|
-
function createMainWindow() {
|
|
1093
|
+
function createMainWindow({ showOnInitialLoad = true } = {}) {
|
|
1093
1094
|
const iconPath = appResource('electron', 'icon.png');
|
|
1094
1095
|
Menu.setApplicationMenu(null);
|
|
1095
1096
|
runtimeWindowLoadOwner?.stop();
|
|
@@ -1126,7 +1127,7 @@ function createMainWindow() {
|
|
|
1126
1127
|
if (window.webContents.getURL() !== DESKTOP_RUNTIME_RECOVERY_PAGE_URL) {
|
|
1127
1128
|
await window.loadURL(DESKTOP_RUNTIME_RECOVERY_PAGE_URL);
|
|
1128
1129
|
}
|
|
1129
|
-
if (process.env.LIVEDESK_E2E_HEADLESS !== '1') showWindow();
|
|
1130
|
+
if (showOnInitialLoad && process.env.LIVEDESK_E2E_HEADLESS !== '1') showWindow();
|
|
1130
1131
|
},
|
|
1131
1132
|
loadRuntime: async () => {
|
|
1132
1133
|
if (window.isDestroyed() || window.webContents.isDestroyed()) {
|
|
@@ -1138,7 +1139,7 @@ function createMainWindow() {
|
|
|
1138
1139
|
loadFailureCount = 0;
|
|
1139
1140
|
updateTray('Running');
|
|
1140
1141
|
log(`desktop renderer loaded current runtime; generation=${context.generation}; attempt=${context.attempt}`);
|
|
1141
|
-
if (process.env.LIVEDESK_E2E_HEADLESS !== '1') showWindow();
|
|
1142
|
+
if (showOnInitialLoad && process.env.LIVEDESK_E2E_HEADLESS !== '1') showWindow();
|
|
1142
1143
|
},
|
|
1143
1144
|
onAttemptError: (error, context) => {
|
|
1144
1145
|
reportWindowLoadFailure(`${context.phase}: ${error?.message || error}`, context);
|
|
@@ -1258,9 +1259,13 @@ function registerIpc() {
|
|
|
1258
1259
|
// Supabase may emit a null renderer session after a temporary failure.
|
|
1259
1260
|
// Durable auth is cleared only by the explicit auth:sign-out IPC.
|
|
1260
1261
|
return { ok: true, ignoredImplicitSignOut: true };
|
|
1261
|
-
});
|
|
1262
|
-
handle('app:quit', () => { app.quit(); return { ok: true }; });
|
|
1263
|
-
handle('
|
|
1262
|
+
});
|
|
1263
|
+
handle('app:quit', () => { app.quit(); return { ok: true }; });
|
|
1264
|
+
handle('app:get-auto-start', () => desktopAutoStartOwner?.getStatus()
|
|
1265
|
+
|| { supported: false, enabled: false, registered: false, platform: process.platform });
|
|
1266
|
+
handle('app:set-auto-start', enabled => desktopAutoStartOwner?.setEnabled(Boolean(enabled))
|
|
1267
|
+
|| { ok: false, supported: false, enabled: false, registered: false, platform: process.platform, error: 'desktop-auto-start-unavailable' });
|
|
1268
|
+
handle('runtime:get-status', async () => {
|
|
1264
1269
|
try { return await fetch(`${LOCAL_RUNTIME_URL}api/runtime/status`).then(response => response.json()); }
|
|
1265
1270
|
catch (error) { return { ok: false, error: mask(error?.message || error) }; }
|
|
1266
1271
|
});
|
|
@@ -1321,7 +1326,15 @@ function createTray() {
|
|
|
1321
1326
|
{ label: 'About VuvoDesk', click: () => { void dialog.showMessageBox(mainWindow, { type: 'info', title: APP_NAME, message: APP_NAME, detail: `Version ${app.getVersion()}\nOne VuvoDesk runtime with Hub and Client roles.` }); } },
|
|
1322
1327
|
{ label: 'Open logs', click: () => { void shell.openPath(logDir); } },
|
|
1323
1328
|
{ type: 'separator' },
|
|
1324
|
-
{
|
|
1329
|
+
{
|
|
1330
|
+
label: 'Start automatically',
|
|
1331
|
+
type: 'checkbox',
|
|
1332
|
+
checked: desktopAutoStartOwner?.getStatus().enabled === true,
|
|
1333
|
+
click: item => {
|
|
1334
|
+
const result = desktopAutoStartOwner?.setEnabled(item.checked);
|
|
1335
|
+
if (result && result.ok !== true) item.checked = !item.checked;
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1325
1338
|
{ label: 'Quit VuvoDesk', click: () => { app.quit(); } }
|
|
1326
1339
|
]);
|
|
1327
1340
|
tray.setContextMenu(menu);
|
|
@@ -1350,10 +1363,18 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1350
1363
|
if (protocolUrl) void handleProtocolUrl(protocolUrl);
|
|
1351
1364
|
showWindow();
|
|
1352
1365
|
});
|
|
1353
|
-
app.whenReady().then(async () => {
|
|
1366
|
+
app.whenReady().then(async () => {
|
|
1354
1367
|
app.setAsDefaultProtocolClient('livedesk');
|
|
1355
1368
|
removeLegacyStartupEntries();
|
|
1356
1369
|
migrateLegacyPlaintextSession();
|
|
1370
|
+
desktopAutoStartOwner = createDesktopAutoStartOwner({
|
|
1371
|
+
app,
|
|
1372
|
+
stateRoot,
|
|
1373
|
+
isPackaged: app.isPackaged && DESKTOP_BUILD_FLAVOR !== 'e2e',
|
|
1374
|
+
log
|
|
1375
|
+
});
|
|
1376
|
+
const autoStartStatus = desktopAutoStartOwner.reconcile();
|
|
1377
|
+
log(`desktop auto-start enabled=${autoStartStatus.enabled} registered=${autoStartStatus.registered} platform=${autoStartStatus.platform} ok=${autoStartStatus.ok === true}`);
|
|
1357
1378
|
let lastProductUpdateLogSignature = '';
|
|
1358
1379
|
productUpdates = createProductUpdateManager({
|
|
1359
1380
|
app,
|
|
@@ -1422,7 +1443,10 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1422
1443
|
log(`secure auth session runtime sync failed: ${error?.message || error}`);
|
|
1423
1444
|
}
|
|
1424
1445
|
}
|
|
1425
|
-
|
|
1446
|
+
const showOnInitialLoad = !desktopAutoStartOwner.wasOpenedAutomatically()
|
|
1447
|
+
|| !startupSession
|
|
1448
|
+
|| !startupRole?.role;
|
|
1449
|
+
createMainWindow({ showOnInitialLoad });
|
|
1426
1450
|
createTray();
|
|
1427
1451
|
if (DESKTOP_BUILD_FLAVOR !== 'e2e') recurringProductUpdates.start();
|
|
1428
1452
|
if (DESKTOP_BUILD_FLAVOR === 'e2e') {
|
package/electron/preload.cjs
CHANGED
|
@@ -3,10 +3,12 @@ const { contextBridge, ipcRenderer } = require('electron');
|
|
|
3
3
|
contextBridge.exposeInMainWorld('liveDesk', {
|
|
4
4
|
app: {
|
|
5
5
|
getVersion: () => ipcRenderer.invoke('app:get-version'),
|
|
6
|
-
restart: () => ipcRenderer.invoke('app:restart'),
|
|
7
|
-
quit: () => ipcRenderer.invoke('app:quit'),
|
|
8
|
-
about: () => ipcRenderer.invoke('app:about'),
|
|
9
|
-
showWindow: () => ipcRenderer.invoke('app:show-window')
|
|
6
|
+
restart: () => ipcRenderer.invoke('app:restart'),
|
|
7
|
+
quit: () => ipcRenderer.invoke('app:quit'),
|
|
8
|
+
about: () => ipcRenderer.invoke('app:about'),
|
|
9
|
+
showWindow: () => ipcRenderer.invoke('app:show-window'),
|
|
10
|
+
getAutoStart: () => ipcRenderer.invoke('app:get-auto-start'),
|
|
11
|
+
setAutoStart: enabled => ipcRenderer.invoke('app:set-auto-start', Boolean(enabled))
|
|
10
12
|
},
|
|
11
13
|
auth: {
|
|
12
14
|
signInWithGoogle: () => ipcRenderer.invoke('auth:sign-in-google'),
|
package/hub/src/server.js
CHANGED
|
@@ -4342,27 +4342,35 @@ app.post('/api/settings/agent/summary', async (req, res) => {
|
|
|
4342
4342
|
}
|
|
4343
4343
|
});
|
|
4344
4344
|
|
|
4345
|
-
app.get('/api/remote/status', (_req, res) => {
|
|
4346
|
-
noStore(res);
|
|
4347
|
-
if (runtimeRole !== 'hub') {
|
|
4345
|
+
app.get('/api/remote/status', async (_req, res) => {
|
|
4346
|
+
noStore(res);
|
|
4347
|
+
if (runtimeRole !== 'hub') {
|
|
4348
4348
|
res.status(403).json({ ok: false, error: 'role-not-allowed' });
|
|
4349
4349
|
return;
|
|
4350
4350
|
}
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4351
|
+
try {
|
|
4352
|
+
const [secretStatus, wallPreferences] = await Promise.all([
|
|
4353
|
+
Promise.resolve(remoteHub.getStatus({ includeSecrets: true })),
|
|
4354
|
+
liveDeskSettingsStore.getWallPreferencesRecord()
|
|
4355
|
+
]);
|
|
4356
|
+
res.json({
|
|
4357
|
+
...remoteHub.getStatus({ includeSecrets: false }),
|
|
4358
|
+
pairingPin: secretStatus.pairingPin,
|
|
4359
|
+
product: 'LiveDesk',
|
|
4360
|
+
runtimeRole,
|
|
4361
|
+
deviceId: runtimeDeviceId,
|
|
4362
|
+
deviceName: runtimeDeviceName,
|
|
4363
|
+
roleSource: runtimeRoleSource,
|
|
4364
|
+
agentPackage: '@livedesk/client',
|
|
4365
|
+
wallPreferences,
|
|
4366
|
+
consoleRelay: hubConsoleRelay?.inspect() || { enabled: false, state: 'starting' },
|
|
4367
|
+
frameLanes: snapshotFrameLaneResourceHealth(),
|
|
4368
|
+
update: getLiveDeskUpdateStatus()
|
|
4369
|
+
});
|
|
4370
|
+
} catch (error) {
|
|
4371
|
+
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
4372
|
+
}
|
|
4373
|
+
});
|
|
4366
4374
|
|
|
4367
4375
|
app.post('/api/remote/diagnostics/transport/start', async (req, res) => {
|
|
4368
4376
|
noStore(res);
|
|
@@ -46,9 +46,11 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
|
46
46
|
rememberLastMonitor: true,
|
|
47
47
|
keepControlReadyBetweenPages: true
|
|
48
48
|
},
|
|
49
|
-
wall: {
|
|
50
|
-
performanceMode: 'auto',
|
|
51
|
-
|
|
49
|
+
wall: {
|
|
50
|
+
performanceMode: 'auto',
|
|
51
|
+
cadence: 'fast',
|
|
52
|
+
viewScale: 100,
|
|
53
|
+
autoStart: true,
|
|
52
54
|
connectedOnly: false,
|
|
53
55
|
keepEmptySlots: true,
|
|
54
56
|
showDeviceStatus: true,
|
|
@@ -110,9 +112,10 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
|
|
|
110
112
|
}
|
|
111
113
|
});
|
|
112
114
|
|
|
113
|
-
const ENUMS = {
|
|
115
|
+
const ENUMS = {
|
|
114
116
|
accessMode: new Set(['trusted-only', 'ask-every-time', 'view-only', 'block-remote-access']),
|
|
115
|
-
performanceMode: new Set(['auto', 'responsive', 'quality', 'custom']),
|
|
117
|
+
performanceMode: new Set(['auto', 'responsive', 'quality', 'custom']),
|
|
118
|
+
wallCadence: new Set(['slow', 'fast']),
|
|
116
119
|
permissionMode: new Set(['ask', 'safe-auto', 'full-access', 'custom']),
|
|
117
120
|
wallFrameMode: new Set(['auto', 'mode2-lzo', 'mode3-h264-hw', 'mode4-h264-atlas']),
|
|
118
121
|
controlFrameMode: new Set(['mode3-h264-hw', 'mode5-lzo-delta']),
|
|
@@ -169,9 +172,11 @@ const RULES = {
|
|
|
169
172
|
control: {
|
|
170
173
|
...bools(['allowKeyboardMouse', 'allowSystemShortcuts', 'allowClipboardText', 'allowRemoteRestart', 'reconnectAfterRemoteRestart', 'allowSwitchingMonitors', 'showConnectionToolbar', 'showRemoteCursor', 'openControlOnDoubleClick', 'startRemoteAudioWithControl', 'fitRemoteScreen', 'rememberLastMonitor', 'keepControlReadyBetweenPages'])
|
|
171
174
|
},
|
|
172
|
-
wall: {
|
|
173
|
-
performanceMode: { type: 'enum', values: ENUMS.performanceMode },
|
|
174
|
-
|
|
175
|
+
wall: {
|
|
176
|
+
performanceMode: { type: 'enum', values: ENUMS.performanceMode },
|
|
177
|
+
cadence: { type: 'enum', values: ENUMS.wallCadence },
|
|
178
|
+
viewScale: { type: 'number', min: 0, max: 100 },
|
|
179
|
+
...bools(['autoStart', 'connectedOnly', 'keepEmptySlots', 'showDeviceStatus', 'showPerformanceDetails', 'pauseHiddenTiles', 'reduceWhenHidden', 'autoAdjustTileQuality', 'rememberDevicePositions'])
|
|
175
180
|
},
|
|
176
181
|
filesAudio: {
|
|
177
182
|
...bools(['allowFileTransfer', 'allowFolderSync', 'askBeforeReceivingFiles', 'openReceivedFolder', 'notifyTransferComplete', 'allowOverwrite', 'allowRemoteAudio', 'startAudioMuted', 'rememberVolume', 'automaticallyRecoverAudio', 'showAudioTroubleshooting', 'rollingBufferEnabled', 'includeRemoteCursor']),
|
|
@@ -40,12 +40,22 @@ export class LiveDeskSettingsStore {
|
|
|
40
40
|
return structuredClone(this.record);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
getCached() {
|
|
44
|
-
if (!this.record) return DEFAULT_LIVEDESK_SETTINGS;
|
|
45
|
-
return { ...this.record.settings, revision: this.record.revision };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async
|
|
43
|
+
getCached() {
|
|
44
|
+
if (!this.record) return DEFAULT_LIVEDESK_SETTINGS;
|
|
45
|
+
return { ...this.record.settings, revision: this.record.revision };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getWallPreferencesRecord() {
|
|
49
|
+
if (!this.record) await this.getRecord();
|
|
50
|
+
return {
|
|
51
|
+
revision: this.record.revision,
|
|
52
|
+
updatedAt: this.record.updatedAt,
|
|
53
|
+
cadence: this.record.settings.wall.cadence,
|
|
54
|
+
viewScale: this.record.settings.wall.viewScale
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async get() {
|
|
49
59
|
return publicSettings((await this.getRecord()).settings);
|
|
50
60
|
}
|
|
51
61
|
|
package/package.json
CHANGED
package/web/dist/app.webmanifest
CHANGED