livedesk 0.1.541 → 0.1.543
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/electron/auth-session-owner.mjs +40 -0
- package/electron/main.mjs +135 -55
- package/electron/oauth-pkce.mjs +10 -7
- package/hub/package.json +1 -1
- package/hub/src/server.js +19 -11
- package/package.json +1 -1
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/{index-f8vc8LV8.js → index-DtrLIGS3.js} +18 -18
- package/web/dist/assets/{index-D-_3NlIH.css → index-DuOQ-f4M.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/livedesk-build-evidence.json +17 -16
- package/web/dist/sw.js +2 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const DESKTOP_AUTH_REFRESH_SKEW_SECONDS = 5 * 60;
|
|
2
|
+
export const DESKTOP_AUTH_RETRY_MS = 15_000;
|
|
3
|
+
export const DESKTOP_AUTH_MINIMUM_TIMER_MS = 30_000;
|
|
4
|
+
|
|
5
|
+
export function desktopSessionNeedsRefresh(
|
|
6
|
+
session,
|
|
7
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
8
|
+
skewSeconds = DESKTOP_AUTH_REFRESH_SKEW_SECONDS
|
|
9
|
+
) {
|
|
10
|
+
const expiresAt = Number(session?.expires_at || 0);
|
|
11
|
+
return !Number.isFinite(expiresAt)
|
|
12
|
+
|| expiresAt <= Number(nowSeconds || 0) + Math.max(0, Number(skewSeconds || 0));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function desktopSessionIsUsable(session, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
16
|
+
const expiresAt = Number(session?.expires_at || 0);
|
|
17
|
+
return Boolean(session?.access_token && session?.refresh_token)
|
|
18
|
+
&& Number.isFinite(expiresAt)
|
|
19
|
+
&& expiresAt > Number(nowSeconds || 0) + 5;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function desktopSessionRefreshDelayMs(
|
|
23
|
+
session,
|
|
24
|
+
nowMs = Date.now(),
|
|
25
|
+
skewSeconds = DESKTOP_AUTH_REFRESH_SKEW_SECONDS
|
|
26
|
+
) {
|
|
27
|
+
const expiresAtMs = Number(session?.expires_at || 0) * 1000;
|
|
28
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= 0) {
|
|
29
|
+
return DESKTOP_AUTH_RETRY_MS;
|
|
30
|
+
}
|
|
31
|
+
return Math.max(
|
|
32
|
+
DESKTOP_AUTH_MINIMUM_TIMER_MS,
|
|
33
|
+
expiresAtMs - Number(nowMs || 0) - Math.max(0, Number(skewSeconds || 0)) * 1000
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isPermanentDesktopAuthRefreshFailure(error) {
|
|
38
|
+
const status = Number(error?.authStatus || 0);
|
|
39
|
+
return status === 400 || status === 401 || status === 403;
|
|
40
|
+
}
|
package/electron/main.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, nativeImage, safeStorage, shell, Tray } from 'electron';
|
|
1
|
+
import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, nativeImage, powerMonitor, safeStorage, shell, Tray } from 'electron';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
@@ -6,7 +6,14 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
import { createProductUpdateManager } from './update-manager.mjs';
|
|
7
7
|
import { createPkceAuthorizationUrl, exchangePkceCode, fetchSupabaseUser, parsePkceCallbackUrl, refreshSupabaseSession } from './oauth-pkce.mjs';
|
|
8
8
|
import { getRuntimeAuthEnvironment, resolveDesktopAuthConfig, resolveDesktopBuildFlavor } from './auth-config.mjs';
|
|
9
|
-
import { createBoundedDesktopLogger } from './bounded-desktop-log.mjs';
|
|
9
|
+
import { createBoundedDesktopLogger } from './bounded-desktop-log.mjs';
|
|
10
|
+
import {
|
|
11
|
+
DESKTOP_AUTH_RETRY_MS,
|
|
12
|
+
desktopSessionIsUsable,
|
|
13
|
+
desktopSessionNeedsRefresh,
|
|
14
|
+
desktopSessionRefreshDelayMs,
|
|
15
|
+
isPermanentDesktopAuthRefreshFailure
|
|
16
|
+
} from './auth-session-owner.mjs';
|
|
10
17
|
import { resolveDeviceRole } from '../bootstrap/device-role.js';
|
|
11
18
|
import {
|
|
12
19
|
registerOwnedWindowsChild,
|
|
@@ -92,8 +99,11 @@ let quitSequenceStarted = false;
|
|
|
92
99
|
let updateInstallSequenceStarted = false;
|
|
93
100
|
let productUpdates = null;
|
|
94
101
|
let pendingOAuth = null;
|
|
95
|
-
let pendingProtocolUrl = '';
|
|
96
|
-
let runtimeLogoutPromise = null;
|
|
102
|
+
let pendingProtocolUrl = '';
|
|
103
|
+
let runtimeLogoutPromise = null;
|
|
104
|
+
let desktopAuthRefreshTimer = null;
|
|
105
|
+
let desktopSessionRestorePromise = null;
|
|
106
|
+
let lastDesktopResumeRecoveryAt = 0;
|
|
97
107
|
const FALLBACK_ICON_DATA_URL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none"><rect x="1" y="1" width="30" height="30" rx="7" fill="#111827"/><g transform="translate(5 5) scale(.785714)"><rect x="2.5" y="4" width="14" height="9" rx="1.5" stroke="#F8FAFC" stroke-width="1.7" opacity=".42"/><path d="M7 16v1.5h5" stroke="#F8FAFC" stroke-width="1.7" stroke-linecap="round" opacity=".42"/><rect x="10.5" y="2.5" width="14" height="9" rx="1.5" stroke="#F8FAFC" stroke-width="1.7" opacity=".62"/><path d="M15 14.5V16h5" stroke="#F8FAFC" stroke-width="1.7" stroke-linecap="round" opacity=".62"/><rect x="6.5" y="8.5" width="15" height="10" rx="1.7" fill="#F8FAFC" fill-opacity=".12" stroke="#F8FAFC" stroke-width="1.9"/><path d="M12 21v1.5H9.5M16 21v1.5h2.5M9.5 22.5h9" stroke="#F8FAFC" stroke-width="1.9" stroke-linecap="round"/></g></svg>')}`;
|
|
98
108
|
const desktopLogger = createBoundedDesktopLogger({ logPath });
|
|
99
109
|
|
|
@@ -179,9 +189,10 @@ function readEncryptedSession() {
|
|
|
179
189
|
}
|
|
180
190
|
}
|
|
181
191
|
|
|
182
|
-
function clearEncryptedSession() {
|
|
183
|
-
|
|
184
|
-
}
|
|
192
|
+
function clearEncryptedSession() {
|
|
193
|
+
clearDesktopAuthRefreshTimer();
|
|
194
|
+
try { rmSync(authSessionPath, { force: true }); } catch { /* best effort */ }
|
|
195
|
+
}
|
|
185
196
|
|
|
186
197
|
function migrateLegacyPlaintextSession() {
|
|
187
198
|
if (!safeStorage.isEncryptionAvailable()) return;
|
|
@@ -608,7 +619,7 @@ async function clearRuntimeSessionWithRecovery() {
|
|
|
608
619
|
}
|
|
609
620
|
}
|
|
610
621
|
|
|
611
|
-
async function validateSessionWithSupabase(session) {
|
|
622
|
+
async function validateSessionWithSupabase(session) {
|
|
612
623
|
const normalized = normalizeSession(session);
|
|
613
624
|
if (!normalized) throw new Error('oauth-session-invalid');
|
|
614
625
|
const user = await fetchSupabaseUser({
|
|
@@ -619,35 +630,97 @@ async function validateSessionWithSupabase(session) {
|
|
|
619
630
|
if (normalized.user?.id && String(normalized.user.id) !== String(user.id)) {
|
|
620
631
|
throw new Error('oauth-session-user-mismatch');
|
|
621
632
|
}
|
|
622
|
-
return { ...normalized, user };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
633
|
+
return { ...normalized, user };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function clearDesktopAuthRefreshTimer() {
|
|
637
|
+
if (!desktopAuthRefreshTimer) return;
|
|
638
|
+
clearTimeout(desktopAuthRefreshTimer);
|
|
639
|
+
desktopAuthRefreshTimer = null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function scheduleDesktopAuthRefresh(session, delayMs = desktopSessionRefreshDelayMs(session)) {
|
|
643
|
+
clearDesktopAuthRefreshTimer();
|
|
644
|
+
if (!session || isQuitting) return;
|
|
645
|
+
desktopAuthRefreshTimer = setTimeout(() => {
|
|
646
|
+
desktopAuthRefreshTimer = null;
|
|
647
|
+
void restoreEncryptedSession({ syncRuntime: true }).then(restored => {
|
|
648
|
+
if (desktopSessionIsUsable(restored)) {
|
|
649
|
+
sendAuthEvent({ ok: true, session: restored, restored: true });
|
|
650
|
+
}
|
|
651
|
+
}).catch(error => {
|
|
652
|
+
log(`scheduled secure auth session recovery failed: ${error?.message || error}`);
|
|
653
|
+
});
|
|
654
|
+
}, Math.max(1_000, Number(delayMs) || DESKTOP_AUTH_RETRY_MS));
|
|
655
|
+
desktopAuthRefreshTimer.unref?.();
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async function restoreEncryptedSessionOwned({ syncRuntime = true } = {}) {
|
|
659
|
+
let session = readEncryptedSession();
|
|
660
|
+
if (!session) return null;
|
|
661
|
+
if (desktopSessionNeedsRefresh(session)) {
|
|
662
|
+
try {
|
|
663
|
+
const refreshed = await refreshSupabaseSession({
|
|
664
|
+
supabaseUrl: SUPABASE_URL,
|
|
665
|
+
publishableKey: SUPABASE_PUBLISHABLE_KEY,
|
|
666
|
+
refreshToken: session.refresh_token
|
|
667
|
+
});
|
|
668
|
+
session = {
|
|
669
|
+
...session,
|
|
670
|
+
...refreshed,
|
|
671
|
+
user: refreshed?.user && typeof refreshed.user === 'object' ? refreshed.user : session.user
|
|
672
|
+
};
|
|
673
|
+
saveEncryptedSession(session);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
log(`secure auth session refresh failed: ${error?.message || error}`);
|
|
676
|
+
if (isPermanentDesktopAuthRefreshFailure(error)) {
|
|
677
|
+
clearEncryptedSession();
|
|
678
|
+
sendAuthEvent({ ok: true, signedOut: true, reason: 'refresh-rejected' });
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
// A sleeping computer commonly resumes before DNS and Wi-Fi. Keep the
|
|
682
|
+
// encrypted refresh token and retry instead of turning an outage into a
|
|
683
|
+
// user-visible sign-out.
|
|
684
|
+
scheduleDesktopAuthRefresh(session, DESKTOP_AUTH_RETRY_MS);
|
|
685
|
+
return session;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (syncRuntime) {
|
|
689
|
+
try {
|
|
690
|
+
await syncSessionToRuntime(session);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
log(`secure auth session runtime sync failed: ${error?.message || error}`);
|
|
693
|
+
scheduleDesktopAuthRefresh(session, DESKTOP_AUTH_RETRY_MS);
|
|
694
|
+
return session;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
scheduleDesktopAuthRefresh(session);
|
|
698
|
+
return session;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function restoreEncryptedSession(options = {}) {
|
|
702
|
+
if (desktopSessionRestorePromise) return desktopSessionRestorePromise;
|
|
703
|
+
const operation = restoreEncryptedSessionOwned(options);
|
|
704
|
+
desktopSessionRestorePromise = operation;
|
|
705
|
+
try {
|
|
706
|
+
return await operation;
|
|
707
|
+
} finally {
|
|
708
|
+
if (desktopSessionRestorePromise === operation) desktopSessionRestorePromise = null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function requestDesktopResumeAuthRecovery(source) {
|
|
713
|
+
const now = Date.now();
|
|
714
|
+
if (now - lastDesktopResumeRecoveryAt < 1_500) return;
|
|
715
|
+
lastDesktopResumeRecoveryAt = now;
|
|
716
|
+
void restoreEncryptedSession({ syncRuntime: true }).then(session => {
|
|
717
|
+
if (!desktopSessionIsUsable(session)) return;
|
|
718
|
+
log(`secure auth session recovered after ${source}`);
|
|
719
|
+
sendAuthEvent({ ok: true, session, restored: true, resumed: true });
|
|
720
|
+
}).catch(error => {
|
|
721
|
+
log(`secure auth session ${source} recovery failed: ${error?.message || error}`);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
651
724
|
|
|
652
725
|
async function resolveStartupRole(session) {
|
|
653
726
|
try {
|
|
@@ -739,9 +812,10 @@ async function handleProtocolUrl(rawUrl) {
|
|
|
739
812
|
code,
|
|
740
813
|
codeVerifier: request.codeVerifier
|
|
741
814
|
}));
|
|
742
|
-
pendingOAuth = null;
|
|
743
|
-
await syncSessionToRuntime(session);
|
|
744
|
-
|
|
815
|
+
pendingOAuth = null;
|
|
816
|
+
await syncSessionToRuntime(session);
|
|
817
|
+
scheduleDesktopAuthRefresh(session);
|
|
818
|
+
sendAuthEvent({ ok: true, session });
|
|
745
819
|
showWindow();
|
|
746
820
|
} catch (error) {
|
|
747
821
|
const message = String(error?.message || error);
|
|
@@ -792,11 +866,14 @@ function createMainWindow() {
|
|
|
792
866
|
void shell.openExternal(url);
|
|
793
867
|
return { action: 'deny' };
|
|
794
868
|
});
|
|
795
|
-
mainWindow.loadURL(LOCAL_RUNTIME_URL);
|
|
796
|
-
mainWindow.webContents.on('did-finish-load', () => {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
869
|
+
mainWindow.loadURL(LOCAL_RUNTIME_URL);
|
|
870
|
+
mainWindow.webContents.on('did-finish-load', () => {
|
|
871
|
+
void restoreEncryptedSession({ syncRuntime: true }).then(session => {
|
|
872
|
+
if (desktopSessionIsUsable(session)) sendAuthEvent({ ok: true, session, restored: true });
|
|
873
|
+
}).catch(error => {
|
|
874
|
+
log(`secure auth session window restore failed: ${error?.message || error}`);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
800
877
|
mainWindow.once('ready-to-show', () => { updateTray('Running'); if (process.env.LIVEDESK_E2E_HEADLESS !== '1') showWindow(); });
|
|
801
878
|
mainWindow.on('close', event => {
|
|
802
879
|
if (!isQuitting) {
|
|
@@ -841,12 +918,13 @@ function registerIpc() {
|
|
|
841
918
|
}
|
|
842
919
|
});
|
|
843
920
|
handle('auth:sign-in-google', () => beginGoogleAuth());
|
|
844
|
-
handle('auth:get-session', () =>
|
|
845
|
-
handle('auth:persist-session', async session => {
|
|
846
|
-
if (session) {
|
|
847
|
-
try {
|
|
848
|
-
saveEncryptedSession(await validateSessionWithSupabase(session));
|
|
849
|
-
|
|
921
|
+
handle('auth:get-session', () => restoreEncryptedSession({ syncRuntime: true }));
|
|
922
|
+
handle('auth:persist-session', async session => {
|
|
923
|
+
if (session) {
|
|
924
|
+
try {
|
|
925
|
+
const validated = saveEncryptedSession(await validateSessionWithSupabase(session));
|
|
926
|
+
scheduleDesktopAuthRefresh(validated);
|
|
927
|
+
return { ok: true };
|
|
850
928
|
} catch (error) {
|
|
851
929
|
const message = String(error?.message || error);
|
|
852
930
|
log(`renderer auth session validation failed: ${message}`);
|
|
@@ -934,16 +1012,18 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
934
1012
|
app.setAsDefaultProtocolClient('livedesk');
|
|
935
1013
|
removeLegacyStartupEntries();
|
|
936
1014
|
migrateLegacyPlaintextSession();
|
|
937
|
-
productUpdates = createProductUpdateManager({
|
|
1015
|
+
productUpdates = createProductUpdateManager({
|
|
938
1016
|
app,
|
|
939
1017
|
onStatus: status => {
|
|
940
1018
|
if (status.state === 'available') updateTray(`Update ${status.availableVersion} available`);
|
|
941
1019
|
},
|
|
942
1020
|
beforeInstall: prepareRuntimeForUpdateInstall,
|
|
943
1021
|
onInstallError: recoverRuntimeAfterUpdateInstallError
|
|
944
|
-
});
|
|
945
|
-
registerIpc();
|
|
946
|
-
|
|
1022
|
+
});
|
|
1023
|
+
registerIpc();
|
|
1024
|
+
powerMonitor.on('resume', () => requestDesktopResumeAuthRecovery('system-resume'));
|
|
1025
|
+
powerMonitor.on('unlock-screen', () => requestDesktopResumeAuthRecovery('screen-unlock'));
|
|
1026
|
+
const startupSession = await restoreEncryptedSession({ syncRuntime: false });
|
|
947
1027
|
const forceBootstrapForE2e = DESKTOP_BUILD_FLAVOR === 'e2e' && process.env.LIVEDESK_E2E_ROLE_BOOTSTRAP === '1';
|
|
948
1028
|
const startupRole = forceBootstrapForE2e ? null : await resolveStartupRole(startupSession);
|
|
949
1029
|
startRuntime(startupRole || {});
|
package/electron/oauth-pkce.mjs
CHANGED
|
@@ -115,7 +115,7 @@ export async function exchangePkceCode({
|
|
|
115
115
|
return payload;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
export async function refreshSupabaseSession({
|
|
118
|
+
export async function refreshSupabaseSession({
|
|
119
119
|
supabaseUrl,
|
|
120
120
|
publishableKey,
|
|
121
121
|
refreshToken,
|
|
@@ -135,9 +135,12 @@ export async function refreshSupabaseSession({
|
|
|
135
135
|
},
|
|
136
136
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
137
137
|
}, timeoutMs);
|
|
138
|
-
const payload = await response.json().catch(() => ({}));
|
|
139
|
-
if (!response.ok || !payload?.access_token || !payload?.refresh_token) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
const payload = await response.json().catch(() => ({}));
|
|
139
|
+
if (!response.ok || !payload?.access_token || !payload?.refresh_token) {
|
|
140
|
+
const error = new Error(String(payload?.msg || payload?.error_description || payload?.error || `oauth-refresh-failed:${response.status}`));
|
|
141
|
+
error.authStatus = response.status;
|
|
142
|
+
error.authCode = String(payload?.error_code || payload?.code || payload?.error || '');
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
return payload;
|
|
146
|
+
}
|
package/hub/package.json
CHANGED
package/hub/src/server.js
CHANGED
|
@@ -513,11 +513,19 @@ async function getRuntimeAccessToken() {
|
|
|
513
513
|
const accessToken = String(runtimeAccessToken || '').trim();
|
|
514
514
|
const expiresSoon = runtimeAccessTokenExpiresAt > 0
|
|
515
515
|
&& runtimeAccessTokenExpiresAt <= Date.now() + HUB_ACCESS_TOKEN_REFRESH_SKEW_MS;
|
|
516
|
-
if (accessToken && !expiresSoon) {
|
|
517
|
-
return accessToken;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
516
|
+
if (accessToken && !expiresSoon) {
|
|
517
|
+
return accessToken;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// The packaged Electron main process owns the encrypted refresh token and
|
|
521
|
+
// proactively sends each rotation to this child runtime. A second refresh
|
|
522
|
+
// owner here could consume the one-time token first and make the desktop
|
|
523
|
+
// shell appear signed out after sleep.
|
|
524
|
+
if (desktopMainAuthConfig) {
|
|
525
|
+
return accessToken;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const refreshToken = String(runtimeRefreshToken || '').trim();
|
|
521
529
|
if (!refreshToken) {
|
|
522
530
|
return accessToken;
|
|
523
531
|
}
|
|
@@ -621,9 +629,9 @@ async function queryAuthoritativeRuntimeRole() {
|
|
|
621
629
|
},
|
|
622
630
|
SUPABASE_AUTH_TIMEOUT_MS
|
|
623
631
|
);
|
|
624
|
-
if (response.status === 401 || response.status === 403) {
|
|
625
|
-
clearRuntimeSession();
|
|
626
|
-
throw new Error(`hub-role-query-not-authenticated:${response.status}`);
|
|
632
|
+
if (response.status === 401 || response.status === 403) {
|
|
633
|
+
if (!desktopMainAuthConfig) clearRuntimeSession();
|
|
634
|
+
throw new Error(`hub-role-query-not-authenticated:${response.status}`);
|
|
627
635
|
}
|
|
628
636
|
if (!response.ok) {
|
|
629
637
|
throw new Error(`hub-role-query-failed:${response.status}`);
|
|
@@ -4601,9 +4609,9 @@ async function callHubSupabaseRpc(name, payload, accessToken) {
|
|
|
4601
4609
|
);
|
|
4602
4610
|
const data = await response.json().catch(() => null);
|
|
4603
4611
|
const result = Array.isArray(data) ? data[0] : data;
|
|
4604
|
-
if (response.status === 401 || response.status === 403) {
|
|
4605
|
-
clearRuntimeSession();
|
|
4606
|
-
throw new Error(`${name}-not-authenticated:${response.status}`);
|
|
4612
|
+
if (response.status === 401 || response.status === 403) {
|
|
4613
|
+
if (!desktopMainAuthConfig) clearRuntimeSession();
|
|
4614
|
+
throw new Error(`${name}-not-authenticated:${response.status}`);
|
|
4607
4615
|
}
|
|
4608
4616
|
if (!response.ok) {
|
|
4609
4617
|
throw new Error(`${name}-failed:${response.status}`);
|
package/package.json
CHANGED
package/web/dist/app.webmanifest
CHANGED