livedesk 0.1.554 → 0.1.563
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/client/bin/livedesk-client.js +19 -16
- package/client/src/runtime/packaged-executable-path.js +23 -0
- package/electron/electron-builder.linux-x64.yml +8 -6
- package/electron/electron-builder.mac-arm64.yml +8 -6
- package/electron/electron-builder.mac-x64.yml +8 -6
- package/electron/electron-builder.win-dev.yml +9 -0
- package/electron/electron-builder.win.yml +8 -6
- package/electron/main.mjs +40 -19
- package/electron/oauth-pkce.mjs +11 -8
- package/electron/recurring-update-owner.mjs +98 -0
- package/electron/update-manager.mjs +43 -22
- package/hub/src/remote-hub.js +34 -8
- package/package.json +7 -8
- package/runtime-core/src/auth-config.js +1 -1
- package/web/dist/_headers +19 -2
- package/web/dist/assets/{icons-Bna5_o78.js → icons-DmufuJkU.js} +1 -1
- package/web/dist/assets/{index-bwoEoZuf.css → index-9Nf9bDf1.css} +1 -1
- package/web/dist/assets/{index-Bzov0Sti.js → index-DfvqA5oA.js} +18 -18
- package/web/dist/assets/{react-DYqrNwY4.js → react-CJ2ozJwj.js} +1 -1
- package/web/dist/desktop-auth-callback.html +133 -0
- package/web/dist/desktop-auth-callback.js +109 -0
- package/web/dist/index.html +4 -4
- package/web/dist/livedesk-build-evidence.json +32 -22
- package/web/dist/sw.js +1 -1
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
signalAgentTree
|
|
20
20
|
} from '../src/runtime/agent-process-lifecycle.js';
|
|
21
21
|
import { writeWindowsOwnedProcessManifest } from '../src/runtime/windows-owned-process-manifest.js';
|
|
22
|
-
import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
|
|
22
|
+
import { createHubWakeListener } from '../src/runtime/hub-wake-listener.js';
|
|
23
|
+
import { resolveSpawnablePackagedPath } from '../src/runtime/packaged-executable-path.js';
|
|
23
24
|
import {
|
|
24
25
|
inspectLinuxVideoAcceleration,
|
|
25
26
|
installLinuxVideoAcceleration
|
|
@@ -4428,14 +4429,14 @@ function getFastRuntime() {
|
|
|
4428
4429
|
const arch = os.arch();
|
|
4429
4430
|
const resolvePackagedRuntime = (packageName, rid, executableName) => {
|
|
4430
4431
|
try {
|
|
4431
|
-
const packagePath = require.resolve(`${packageName}/package.json`);
|
|
4432
|
-
const fastRoot = join(dirname(packagePath), 'fast');
|
|
4433
|
-
return {
|
|
4434
|
-
rid,
|
|
4435
|
-
packageName,
|
|
4436
|
-
executable: join(fastRoot, executableName),
|
|
4437
|
-
dll: join(fastRoot, 'livedesk-client-fast.dll')
|
|
4438
|
-
};
|
|
4432
|
+
const packagePath = require.resolve(`${packageName}/package.json`);
|
|
4433
|
+
const fastRoot = join(dirname(packagePath), 'fast');
|
|
4434
|
+
return {
|
|
4435
|
+
rid,
|
|
4436
|
+
packageName,
|
|
4437
|
+
executable: resolveSpawnablePackagedPath(join(fastRoot, executableName)),
|
|
4438
|
+
dll: resolveSpawnablePackagedPath(join(fastRoot, 'livedesk-client-fast.dll'))
|
|
4439
|
+
};
|
|
4439
4440
|
} catch {
|
|
4440
4441
|
return {
|
|
4441
4442
|
rid,
|
|
@@ -4570,13 +4571,15 @@ function clearFastPreflightCache() {
|
|
|
4570
4571
|
}
|
|
4571
4572
|
}
|
|
4572
4573
|
|
|
4573
|
-
function resolveBundledFfmpegPaths() {
|
|
4574
|
-
const paths = [];
|
|
4575
|
-
const addPath = (candidate) => {
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4574
|
+
function resolveBundledFfmpegPaths() {
|
|
4575
|
+
const paths = [];
|
|
4576
|
+
const addPath = (candidate) => {
|
|
4577
|
+
// Electron can resolve ffmpeg-static through app.asar, but RemoteFast
|
|
4578
|
+
// is a native process and must receive the physical unpacked path.
|
|
4579
|
+
const ffmpegPath = resolveSpawnablePackagedPath(String(candidate || '').trim());
|
|
4580
|
+
if (ffmpegPath && existsSync(ffmpegPath) && !paths.includes(ffmpegPath)) {
|
|
4581
|
+
paths.push(ffmpegPath);
|
|
4582
|
+
}
|
|
4580
4583
|
};
|
|
4581
4584
|
|
|
4582
4585
|
const addFfmpegInstaller = () => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
const ASAR_PATH_SEGMENT = /([\\/])app\.asar([\\/])/i;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Electron's Node loader can read JavaScript through the virtual app.asar
|
|
7
|
+
* path, but the operating system cannot spawn an executable from that virtual
|
|
8
|
+
* archive. electron-builder places executable payloads in the matching
|
|
9
|
+
* app.asar.unpacked tree, so only spawn targets cross that boundary.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveSpawnablePackagedPath(value, pathExists = existsSync) {
|
|
12
|
+
const candidate = String(value || '');
|
|
13
|
+
if (!candidate || !ASAR_PATH_SEGMENT.test(candidate)) {
|
|
14
|
+
return candidate;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const unpacked = candidate.replace(ASAR_PATH_SEGMENT, '$1app.asar.unpacked$2');
|
|
18
|
+
try {
|
|
19
|
+
return pathExists(unpacked) ? unpacked : candidate;
|
|
20
|
+
} catch {
|
|
21
|
+
return candidate;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
extends: file:packages/livedesk/electron/electron-builder.yml
|
|
2
2
|
files:
|
|
3
3
|
- bin/**
|
|
4
|
-
- bootstrap/**
|
|
5
|
-
- client/**
|
|
6
|
-
-
|
|
4
|
+
- bootstrap/**
|
|
5
|
+
- client/**
|
|
6
|
+
- diagnostics/**
|
|
7
|
+
- electron/**
|
|
7
8
|
- hub/**
|
|
8
9
|
- runtime-core/**
|
|
9
10
|
- web/**
|
|
@@ -14,9 +15,10 @@ files:
|
|
|
14
15
|
- "!client/fast/osx-arm64/**"
|
|
15
16
|
asarUnpack:
|
|
16
17
|
- bin/**
|
|
17
|
-
- bootstrap/**
|
|
18
|
-
- client/**
|
|
19
|
-
-
|
|
18
|
+
- bootstrap/**
|
|
19
|
+
- client/**
|
|
20
|
+
- diagnostics/**
|
|
21
|
+
- electron/icon.*
|
|
20
22
|
- electron/tray.*
|
|
21
23
|
- hub/**
|
|
22
24
|
- runtime-core/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
extends: file:packages/livedesk/electron/electron-builder.yml
|
|
2
2
|
files:
|
|
3
3
|
- bin/**
|
|
4
|
-
- bootstrap/**
|
|
5
|
-
- client/**
|
|
6
|
-
-
|
|
4
|
+
- bootstrap/**
|
|
5
|
+
- client/**
|
|
6
|
+
- diagnostics/**
|
|
7
|
+
- electron/**
|
|
7
8
|
- hub/**
|
|
8
9
|
- runtime-core/**
|
|
9
10
|
- web/**
|
|
@@ -14,9 +15,10 @@ files:
|
|
|
14
15
|
- "!client/fast/osx-x64/**"
|
|
15
16
|
asarUnpack:
|
|
16
17
|
- bin/**
|
|
17
|
-
- bootstrap/**
|
|
18
|
-
- client/**
|
|
19
|
-
-
|
|
18
|
+
- bootstrap/**
|
|
19
|
+
- client/**
|
|
20
|
+
- diagnostics/**
|
|
21
|
+
- electron/icon.*
|
|
20
22
|
- electron/tray.*
|
|
21
23
|
- hub/**
|
|
22
24
|
- runtime-core/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
extends: file:packages/livedesk/electron/electron-builder.yml
|
|
2
2
|
files:
|
|
3
3
|
- bin/**
|
|
4
|
-
- bootstrap/**
|
|
5
|
-
- client/**
|
|
6
|
-
-
|
|
4
|
+
- bootstrap/**
|
|
5
|
+
- client/**
|
|
6
|
+
- diagnostics/**
|
|
7
|
+
- electron/**
|
|
7
8
|
- hub/**
|
|
8
9
|
- runtime-core/**
|
|
9
10
|
- web/**
|
|
@@ -14,9 +15,10 @@ files:
|
|
|
14
15
|
- "!client/fast/osx-arm64/**"
|
|
15
16
|
asarUnpack:
|
|
16
17
|
- bin/**
|
|
17
|
-
- bootstrap/**
|
|
18
|
-
- client/**
|
|
19
|
-
-
|
|
18
|
+
- bootstrap/**
|
|
19
|
+
- client/**
|
|
20
|
+
- diagnostics/**
|
|
21
|
+
- electron/icon.*
|
|
20
22
|
- electron/tray.*
|
|
21
23
|
- hub/**
|
|
22
24
|
- runtime-core/**
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
extends: file:packages/livedesk/electron/electron-builder.win.yml
|
|
2
|
+
artifactName: LiveDesk-${version}-dev-${arch}.${ext}
|
|
3
|
+
directories:
|
|
4
|
+
output: dist/desktop-dev
|
|
5
|
+
extraMetadata:
|
|
6
|
+
desktopUpdateChannel: dev
|
|
7
|
+
publish:
|
|
8
|
+
provider: generic
|
|
9
|
+
url: https://livedesk-desktop-updates.lovecrdm.workers.dev/dev/windows/x64
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
extends: file:packages/livedesk/electron/electron-builder.yml
|
|
2
2
|
files:
|
|
3
3
|
- bin/**
|
|
4
|
-
- bootstrap/**
|
|
5
|
-
- client/**
|
|
6
|
-
-
|
|
4
|
+
- bootstrap/**
|
|
5
|
+
- client/**
|
|
6
|
+
- diagnostics/**
|
|
7
|
+
- electron/**
|
|
7
8
|
- hub/**
|
|
8
9
|
- runtime-core/**
|
|
9
10
|
- web/**
|
|
@@ -14,9 +15,10 @@ files:
|
|
|
14
15
|
- "!client/fast/osx-arm64/**"
|
|
15
16
|
asarUnpack:
|
|
16
17
|
- bin/**
|
|
17
|
-
- bootstrap/**
|
|
18
|
-
- client/**
|
|
19
|
-
-
|
|
18
|
+
- bootstrap/**
|
|
19
|
+
- client/**
|
|
20
|
+
- diagnostics/**
|
|
21
|
+
- electron/icon.*
|
|
20
22
|
- electron/tray.*
|
|
21
23
|
- hub/**
|
|
22
24
|
- runtime-core/**
|
package/electron/main.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import { createProductUpdateManager } from './update-manager.mjs';
|
|
6
|
+
import { createProductUpdateManager } from './update-manager.mjs';
|
|
7
|
+
import { createRecurringProductUpdateOwner } from './recurring-update-owner.mjs';
|
|
7
8
|
import { createPkceAuthorizationUrl, exchangePkceCode, fetchSupabaseUser, parsePkceCallbackUrl, refreshSupabaseSession } from './oauth-pkce.mjs';
|
|
8
9
|
import { getRuntimeAuthEnvironment, resolveDesktopAuthConfig, resolveDesktopBuildFlavor } from './auth-config.mjs';
|
|
9
10
|
import { createBoundedDesktopLogger } from './bounded-desktop-log.mjs';
|
|
@@ -86,7 +87,13 @@ function appResource(...parts) {
|
|
|
86
87
|
return existsSync(unpacked) ? unpacked : resolve(appRoot, ...parts);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
// Electron's ASAR loader keeps unpacked files addressable through the virtual
|
|
91
|
+
// app.asar path while resolving their package dependencies from the same tree.
|
|
92
|
+
// Starting from app.asar.unpacked would strand ESM imports such as `ws` and
|
|
93
|
+
// `@supabase/supabase-js` in the sibling archive.
|
|
94
|
+
const launcherEntry = app.isPackaged
|
|
95
|
+
? resolve(appPath, 'bin', 'livedesk.js')
|
|
96
|
+
: appResource('bin', 'livedesk.js');
|
|
90
97
|
let mainWindow = null;
|
|
91
98
|
let tray = null;
|
|
92
99
|
let runtimeChild = null;
|
|
@@ -98,7 +105,8 @@ let runtimeRestartPromise = null;
|
|
|
98
105
|
let runtimeLaunchStartedAt = 0;
|
|
99
106
|
let quitSequenceStarted = false;
|
|
100
107
|
let updateInstallSequenceStarted = false;
|
|
101
|
-
let productUpdates = null;
|
|
108
|
+
let productUpdates = null;
|
|
109
|
+
let recurringProductUpdates = null;
|
|
102
110
|
let pendingOAuth = null;
|
|
103
111
|
let pendingProtocolUrl = '';
|
|
104
112
|
let runtimeLogoutPromise = null;
|
|
@@ -390,11 +398,14 @@ function startRuntime(options = {}) {
|
|
|
390
398
|
runtimeRestartCount += 1;
|
|
391
399
|
if (runtimeRestartCount <= 5) {
|
|
392
400
|
runtimeRestartTimer = setTimeout(() => {
|
|
393
|
-
runtimeRestartTimer = null;
|
|
394
|
-
if (isQuitting || runtimeChild) return;
|
|
395
|
-
startRuntime();
|
|
396
|
-
|
|
397
|
-
|
|
401
|
+
runtimeRestartTimer = null;
|
|
402
|
+
if (isQuitting || runtimeChild) return;
|
|
403
|
+
startRuntime();
|
|
404
|
+
// Keep the mounted renderer alive while its same-role runtime
|
|
405
|
+
// recovers. Reloading here turns each bounded restart into a white
|
|
406
|
+
// flash and discards useful on-screen state.
|
|
407
|
+
void waitForRuntime().then(ready => { if (ready) updateTray('Running'); });
|
|
408
|
+
}, 1200);
|
|
398
409
|
} else {
|
|
399
410
|
updateTray('Runtime stopped');
|
|
400
411
|
log(`runtime supervisor stopped after ${runtimeRestartCount - 1} retries`);
|
|
@@ -530,12 +541,13 @@ function requestRuntimeRestart(source = 'manual') {
|
|
|
530
541
|
});
|
|
531
542
|
}
|
|
532
543
|
|
|
533
|
-
async function prepareRuntimeForUpdateInstall() {
|
|
544
|
+
async function prepareRuntimeForUpdateInstall() {
|
|
534
545
|
if (updateInstallSequenceStarted) {
|
|
535
546
|
throw new Error('desktop-update-install-already-started');
|
|
536
547
|
}
|
|
537
|
-
updateInstallSequenceStarted = true;
|
|
538
|
-
isQuitting = true;
|
|
548
|
+
updateInstallSequenceStarted = true;
|
|
549
|
+
isQuitting = true;
|
|
550
|
+
recurringProductUpdates?.stop();
|
|
539
551
|
try {
|
|
540
552
|
await stopRuntime();
|
|
541
553
|
} catch (error) {
|
|
@@ -1052,7 +1064,7 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1052
1064
|
app.setAsDefaultProtocolClient('livedesk');
|
|
1053
1065
|
removeLegacyStartupEntries();
|
|
1054
1066
|
migrateLegacyPlaintextSession();
|
|
1055
|
-
productUpdates = createProductUpdateManager({
|
|
1067
|
+
productUpdates = createProductUpdateManager({
|
|
1056
1068
|
app,
|
|
1057
1069
|
onStatus: status => {
|
|
1058
1070
|
if (mainWindow && !mainWindow.isDestroyed() && !mainWindow.webContents.isDestroyed()) {
|
|
@@ -1063,8 +1075,15 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1063
1075
|
else if (status.state === 'downloaded') updateTray(`Update ${status.availableVersion} ready`);
|
|
1064
1076
|
},
|
|
1065
1077
|
beforeInstall: prepareRuntimeForUpdateInstall,
|
|
1066
|
-
onInstallError: recoverRuntimeAfterUpdateInstallError
|
|
1067
|
-
});
|
|
1078
|
+
onInstallError: recoverRuntimeAfterUpdateInstallError
|
|
1079
|
+
});
|
|
1080
|
+
recurringProductUpdates = createRecurringProductUpdateOwner({
|
|
1081
|
+
manager: productUpdates,
|
|
1082
|
+
canRun: () => !isQuitting && !quitSequenceStarted && !updateInstallSequenceStarted,
|
|
1083
|
+
initialDelayMs: 15_000,
|
|
1084
|
+
intervalMs: 30_000,
|
|
1085
|
+
onError: error => log(`recurring desktop update failed: ${error?.message || error}`)
|
|
1086
|
+
});
|
|
1068
1087
|
registerIpc();
|
|
1069
1088
|
powerMonitor.on('resume', () => requestDesktopResumeAuthRecovery('system-resume'));
|
|
1070
1089
|
powerMonitor.on('unlock-screen', () => requestDesktopResumeAuthRecovery('screen-unlock'));
|
|
@@ -1083,8 +1102,9 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1083
1102
|
log(`secure auth session runtime sync failed: ${error?.message || error}`);
|
|
1084
1103
|
}
|
|
1085
1104
|
}
|
|
1086
|
-
createMainWindow();
|
|
1087
|
-
createTray();
|
|
1105
|
+
createMainWindow();
|
|
1106
|
+
createTray();
|
|
1107
|
+
if (DESKTOP_BUILD_FLAVOR !== 'e2e') recurringProductUpdates.start();
|
|
1088
1108
|
if (DESKTOP_BUILD_FLAVOR === 'e2e') {
|
|
1089
1109
|
const state = String(process.env.LIVEDESK_E2E_OAUTH_STATE || '');
|
|
1090
1110
|
const codeVerifier = String(process.env.LIVEDESK_E2E_OAUTH_CODE_VERIFIER || '');
|
|
@@ -1097,9 +1117,10 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1097
1117
|
if (initialProtocolUrl) void handleProtocolUrl(initialProtocolUrl);
|
|
1098
1118
|
});
|
|
1099
1119
|
app.on('window-all-closed', () => { /* tray-owned desktop stays alive */ });
|
|
1100
|
-
app.on('before-quit', event => {
|
|
1101
|
-
if (quitSequenceStarted) return;
|
|
1102
|
-
event.preventDefault();
|
|
1120
|
+
app.on('before-quit', event => {
|
|
1121
|
+
if (quitSequenceStarted) return;
|
|
1122
|
+
event.preventDefault();
|
|
1123
|
+
recurringProductUpdates?.stop();
|
|
1103
1124
|
quitSequenceStarted = true;
|
|
1104
1125
|
isQuitting = true;
|
|
1105
1126
|
void stopRuntime().then(() => {
|
package/electron/oauth-pkce.mjs
CHANGED
|
@@ -26,14 +26,17 @@ export function createPkceAuthorizationUrl({
|
|
|
26
26
|
if (!baseUrl || !publishableKey || !redirectUri || !codeVerifier || !state) {
|
|
27
27
|
throw new Error('oauth-pkce-parameters-required');
|
|
28
28
|
}
|
|
29
|
-
const url = new URL(`${baseUrl}/auth/v1/authorize`);
|
|
30
|
-
url.searchParams.set('provider', 'google');
|
|
31
|
-
url.searchParams.set('redirect_to', redirectUri);
|
|
32
|
-
url.searchParams.set('code_challenge', createCodeChallenge(codeVerifier));
|
|
33
|
-
url.searchParams.set('code_challenge_method', 'S256');
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const url = new URL(`${baseUrl}/auth/v1/authorize`);
|
|
30
|
+
url.searchParams.set('provider', 'google');
|
|
31
|
+
url.searchParams.set('redirect_to', redirectUri);
|
|
32
|
+
url.searchParams.set('code_challenge', createCodeChallenge(codeVerifier));
|
|
33
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
34
|
+
// Supabase Auth owns the provider-facing OAuth state for social sign-in.
|
|
35
|
+
// Overriding it here makes Google echo a value Supabase cannot validate and
|
|
36
|
+
// sends the user to the Site URL with `bad_oauth_state`. Keep this nonce only
|
|
37
|
+
// in Electron Main alongside the one-time PKCE verifier.
|
|
38
|
+
url.searchParams.set('scopes', 'email profile');
|
|
39
|
+
url.searchParams.set('apikey', publishableKey);
|
|
37
40
|
return { url: url.toString(), state, codeVerifier };
|
|
38
41
|
}
|
|
39
42
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const MINIMUM_UPDATE_DELAY_MS = 1_000;
|
|
2
|
+
|
|
3
|
+
function normalizeDelay(value, fallback) {
|
|
4
|
+
const delay = Number(value);
|
|
5
|
+
return Number.isFinite(delay) && delay >= MINIMUM_UPDATE_DELAY_MS
|
|
6
|
+
? Math.round(delay)
|
|
7
|
+
: fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createRecurringProductUpdateOwner({
|
|
11
|
+
manager,
|
|
12
|
+
canRun = () => true,
|
|
13
|
+
initialDelayMs = 15_000,
|
|
14
|
+
intervalMs = 30 * 60_000,
|
|
15
|
+
setTimer = setTimeout,
|
|
16
|
+
clearTimer = clearTimeout,
|
|
17
|
+
onError = () => undefined
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (!manager || typeof manager.check !== 'function' || typeof manager.install !== 'function') {
|
|
20
|
+
throw new TypeError('recurring-product-update-manager-required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const initialDelay = normalizeDelay(initialDelayMs, 15_000);
|
|
24
|
+
const interval = normalizeDelay(intervalMs, 30 * 60_000);
|
|
25
|
+
let stopped = true;
|
|
26
|
+
let generation = 0;
|
|
27
|
+
let timer = null;
|
|
28
|
+
let running = null;
|
|
29
|
+
|
|
30
|
+
const clearOwnedTimer = () => {
|
|
31
|
+
if (!timer) return;
|
|
32
|
+
clearTimer(timer);
|
|
33
|
+
timer = null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const arm = (delay, ownerGeneration) => {
|
|
37
|
+
if (stopped || ownerGeneration !== generation || !canRun()) return;
|
|
38
|
+
clearOwnedTimer();
|
|
39
|
+
timer = setTimer(() => {
|
|
40
|
+
if (stopped || ownerGeneration !== generation) return;
|
|
41
|
+
timer = null;
|
|
42
|
+
void runCycle(ownerGeneration);
|
|
43
|
+
}, delay);
|
|
44
|
+
timer?.unref?.();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const runCycle = ownerGeneration => {
|
|
48
|
+
if (stopped || ownerGeneration !== generation || !canRun()) {
|
|
49
|
+
return Promise.resolve(manager.getStatus?.() || { state: 'stopped' });
|
|
50
|
+
}
|
|
51
|
+
if (running) return running;
|
|
52
|
+
|
|
53
|
+
const operation = (async () => {
|
|
54
|
+
const checked = await manager.check();
|
|
55
|
+
if (!stopped
|
|
56
|
+
&& ownerGeneration === generation
|
|
57
|
+
&& canRun()
|
|
58
|
+
&& checked?.downloaded === true) {
|
|
59
|
+
return manager.install();
|
|
60
|
+
}
|
|
61
|
+
return checked;
|
|
62
|
+
})();
|
|
63
|
+
running = operation;
|
|
64
|
+
void operation.catch(error => onError(error)).finally(() => {
|
|
65
|
+
if (running === operation) running = null;
|
|
66
|
+
arm(interval, ownerGeneration);
|
|
67
|
+
});
|
|
68
|
+
return operation;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
start() {
|
|
73
|
+
if (!stopped) return;
|
|
74
|
+
stopped = false;
|
|
75
|
+
generation += 1;
|
|
76
|
+
arm(initialDelay, generation);
|
|
77
|
+
},
|
|
78
|
+
stop() {
|
|
79
|
+
stopped = true;
|
|
80
|
+
generation += 1;
|
|
81
|
+
clearOwnedTimer();
|
|
82
|
+
},
|
|
83
|
+
runNow() {
|
|
84
|
+
clearOwnedTimer();
|
|
85
|
+
return runCycle(generation);
|
|
86
|
+
},
|
|
87
|
+
inspect() {
|
|
88
|
+
return {
|
|
89
|
+
stopped,
|
|
90
|
+
generation,
|
|
91
|
+
timerCount: timer ? 1 : 0,
|
|
92
|
+
running: Boolean(running),
|
|
93
|
+
initialDelayMs: initialDelay,
|
|
94
|
+
intervalMs: interval
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -11,21 +11,30 @@ export function createProductUpdateManager({
|
|
|
11
11
|
beforeInstall = async () => undefined,
|
|
12
12
|
onInstallError = async () => undefined,
|
|
13
13
|
updater = null,
|
|
14
|
-
platform = process.platform,
|
|
15
|
-
arch = process.arch,
|
|
16
|
-
startupCheckTimeoutMs = 2_500,
|
|
17
|
-
updateFeedConfig = null
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
platform = process.platform,
|
|
15
|
+
arch = process.arch,
|
|
16
|
+
startupCheckTimeoutMs = 2_500,
|
|
17
|
+
updateFeedConfig = null,
|
|
18
|
+
updateChannel = ''
|
|
19
|
+
} = {}) {
|
|
20
|
+
if (!updater) updater = require('electron-updater').autoUpdater;
|
|
21
|
+
let feedConfig = updateFeedConfig;
|
|
22
|
+
let packageMetadata = {};
|
|
23
|
+
try {
|
|
24
|
+
packageMetadata = JSON.parse(readFileSync(join(app.getAppPath(), 'package.json'), 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
packageMetadata = {};
|
|
27
|
+
}
|
|
28
|
+
if (!feedConfig) {
|
|
29
|
+
try {
|
|
30
|
+
feedConfig = JSON.parse(readFileSync(join(app.getAppPath(), 'electron', 'update-feed.json'), 'utf8'));
|
|
24
31
|
} catch {
|
|
25
|
-
feedConfig = {};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const
|
|
32
|
+
feedConfig = {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const packagedChannel = String(updateChannel || packageMetadata?.desktopUpdateChannel || '').trim().toLowerCase();
|
|
36
|
+
if (packagedChannel) feedConfig = { ...feedConfig, channel: packagedChannel };
|
|
37
|
+
const feed = resolveDesktopUpdateFeed({
|
|
29
38
|
config: feedConfig,
|
|
30
39
|
overrideUrl: process.env.LIVEDESK_UPDATE_URL,
|
|
31
40
|
platform,
|
|
@@ -46,8 +55,9 @@ export function createProductUpdateManager({
|
|
|
46
55
|
startupCheck: 'idle'
|
|
47
56
|
};
|
|
48
57
|
let installPromise = null;
|
|
49
|
-
let checkPromise = null;
|
|
50
|
-
let downloadPromise = null;
|
|
58
|
+
let checkPromise = null;
|
|
59
|
+
let downloadPromise = null;
|
|
60
|
+
let cyclePromise = null;
|
|
51
61
|
let startupPromise = null;
|
|
52
62
|
let checkOutcome = 'idle';
|
|
53
63
|
let installRuntimePrepared = false;
|
|
@@ -170,11 +180,20 @@ export function createProductUpdateManager({
|
|
|
170
180
|
}
|
|
171
181
|
};
|
|
172
182
|
|
|
173
|
-
const checkAndDownload = async () => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
183
|
+
const checkAndDownload = async () => {
|
|
184
|
+
if (cyclePromise) return cyclePromise;
|
|
185
|
+
const operation = (async () => {
|
|
186
|
+
const checked = await checkForUpdates();
|
|
187
|
+
if (checked.state === 'available') return downloadUpdate();
|
|
188
|
+
return checked;
|
|
189
|
+
})();
|
|
190
|
+
cyclePromise = operation;
|
|
191
|
+
try {
|
|
192
|
+
return await operation;
|
|
193
|
+
} finally {
|
|
194
|
+
if (cyclePromise === operation) cyclePromise = null;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
178
197
|
|
|
179
198
|
return {
|
|
180
199
|
getStatus() {
|
|
@@ -227,7 +246,9 @@ export function createProductUpdateManager({
|
|
|
227
246
|
await beforeInstall();
|
|
228
247
|
installRuntimePrepared = true;
|
|
229
248
|
publish({ state: 'installing', error: '' });
|
|
230
|
-
|
|
249
|
+
// Long-running desktop Clients must not stop at the assisted NSIS
|
|
250
|
+
// wizard after the runtime has already shut down for replacement.
|
|
251
|
+
updater.quitAndInstall(true, true);
|
|
231
252
|
return snapshot();
|
|
232
253
|
} catch (error) {
|
|
233
254
|
await recoverPreparedRuntime(error);
|
package/hub/src/remote-hub.js
CHANGED
|
@@ -770,7 +770,7 @@ function buildRemoteFrameTransferDescriptorFromMessage(message = {}, fallback =
|
|
|
770
770
|
};
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
-
function normalizeIncomingFrameModeProfile(value) {
|
|
773
|
+
function normalizeIncomingFrameModeProfile(value) {
|
|
774
774
|
if (!value) {
|
|
775
775
|
return null;
|
|
776
776
|
}
|
|
@@ -794,8 +794,27 @@ function normalizeIncomingFrameModeProfile(value) {
|
|
|
794
794
|
profile: safeString(value.profile || value.frameProfile, 80) || profile.frameProfile,
|
|
795
795
|
modeVersion: Number.isFinite(Number(value.modeVersion)) ? Math.max(0, Math.floor(Number(value.modeVersion))) : 1,
|
|
796
796
|
implemented: value.implemented !== false
|
|
797
|
-
};
|
|
798
|
-
}
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function deviceExplicitlyRejectsFrameMode(device, frameMode) {
|
|
801
|
+
const requestedMode = safeString(frameMode, 80).toLowerCase();
|
|
802
|
+
if (!requestedMode) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const advertisedModes = [
|
|
807
|
+
...(Array.isArray(device?.frameModes) ? device.frameModes : []),
|
|
808
|
+
...(Array.isArray(device?.frameProtocol?.modes) ? device.frameProtocol.modes : []),
|
|
809
|
+
...(Array.isArray(device?.capabilities?.frameModes) ? device.capabilities.frameModes : []),
|
|
810
|
+
...(Array.isArray(device?.capabilities?.frameProtocol?.modes)
|
|
811
|
+
? device.capabilities.frameProtocol.modes
|
|
812
|
+
: [])
|
|
813
|
+
];
|
|
814
|
+
return advertisedModes.some(profile =>
|
|
815
|
+
safeString(profile?.mode || profile?.frameMode, 80).toLowerCase() === requestedMode
|
|
816
|
+
&& profile?.implemented === false);
|
|
817
|
+
}
|
|
799
818
|
|
|
800
819
|
function isLoopbackHost(value) {
|
|
801
820
|
const host = String(value || '').trim().toLowerCase();
|
|
@@ -9660,11 +9679,18 @@ export function createRemoteHub(options = {}) {
|
|
|
9660
9679
|
}
|
|
9661
9680
|
|
|
9662
9681
|
const now = new Date().toISOString();
|
|
9663
|
-
const normalized = normalizeLiveStreamStartOptions({ ...options, platform: device.platform });
|
|
9664
|
-
const modePolicyError = liveStreamModePolicyError(normalized);
|
|
9665
|
-
if (modePolicyError) return { ok: false, error: modePolicyError };
|
|
9666
|
-
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
9667
|
-
|
|
9682
|
+
const normalized = normalizeLiveStreamStartOptions({ ...options, platform: device.platform });
|
|
9683
|
+
const modePolicyError = liveStreamModePolicyError(normalized);
|
|
9684
|
+
if (modePolicyError) return { ok: false, error: modePolicyError };
|
|
9685
|
+
const { fps, maxWidth, maxHeight, quality, monitorIndex, transfer, streamPurpose } = normalized;
|
|
9686
|
+
if (deviceExplicitlyRejectsFrameMode(device, transfer.frameMode)) {
|
|
9687
|
+
return {
|
|
9688
|
+
ok: false,
|
|
9689
|
+
error: 'device-frame-mode-unavailable',
|
|
9690
|
+
frameMode: transfer.frameMode
|
|
9691
|
+
};
|
|
9692
|
+
}
|
|
9693
|
+
const streamId = safeString(options.streamId, 128) || makeStableLiveStreamId(deviceId, streamPurpose);
|
|
9668
9694
|
if (!reserveDeviceLiveStreamDescriptor(device, streamId)) {
|
|
9669
9695
|
return {
|
|
9670
9696
|
ok: false,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"livedeskClientVersion": "0.1.248",
|
|
3
|
+
"version": "0.1.563",
|
|
4
|
+
"livedeskClientVersion": "0.1.248",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "LiveDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -40,22 +40,21 @@
|
|
|
40
40
|
"node": ">=20"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
44
|
-
"ffmpeg-static": "^5.3.0",
|
|
45
43
|
"@openai/codex-sdk": "0.145.0",
|
|
46
44
|
"@supabase/supabase-js": "^2.110.0",
|
|
47
45
|
"cors": "^2.8.5",
|
|
48
46
|
"electron-updater": "^6.8.9",
|
|
49
47
|
"express": "^4.21.2",
|
|
48
|
+
"ffmpeg-static": "^5.3.0",
|
|
50
49
|
"node-screenshots": "^0.2.8",
|
|
51
50
|
"path-to-regexp": "0.1.13",
|
|
52
51
|
"ws": "^8.18.3"
|
|
53
52
|
},
|
|
54
53
|
"optionalDependencies": {
|
|
55
|
-
"@livedesk/fast-linux-x64": "0.1.445",
|
|
56
|
-
"@livedesk/fast-osx-arm64": "0.1.445",
|
|
57
|
-
"@livedesk/fast-osx-x64": "0.1.445",
|
|
58
|
-
"@livedesk/fast-win-x64": "0.1.445"
|
|
54
|
+
"@livedesk/fast-linux-x64": "0.1.445",
|
|
55
|
+
"@livedesk/fast-osx-arm64": "0.1.445",
|
|
56
|
+
"@livedesk/fast-osx-x64": "0.1.445",
|
|
57
|
+
"@livedesk/fast-win-x64": "0.1.445"
|
|
59
58
|
},
|
|
60
59
|
"publishConfig": {
|
|
61
60
|
"access": "public"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const PRODUCTION_SUPABASE_URL = 'https://otbyfkjxrkngvjziawki.supabase.co';
|
|
2
2
|
export const PRODUCTION_SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ';
|
|
3
|
-
export const PRODUCTION_OAUTH_REDIRECT_URI = 'livedesk
|
|
3
|
+
export const PRODUCTION_OAUTH_REDIRECT_URI = 'https://livedesk.pages.dev/desktop-auth-callback';
|
|
4
4
|
export const PRODUCTION_BUILD_FLAVOR = 'production';
|
|
5
5
|
export const E2E_BUILD_FLAVOR = 'e2e';
|
|
6
6
|
|