@lobehub/lobehub 2.0.0-next.315 → 2.0.0-next.317

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/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.317](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.316...v2.0.0-next.317)
6
+
7
+ <sup>Released on **2026-01-19**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **desktop**: Resolve onboarding navigation issues after logout.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **desktop**: Resolve onboarding navigation issues after logout, closes [#11628](https://github.com/lobehub/lobe-chat/issues/11628) ([05a0873](https://github.com/lobehub/lobe-chat/commit/05a0873))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.316](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.315...v2.0.0-next.316)
31
+
32
+ <sup>Released on **2026-01-19**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: When use trpc client should include the credentials cookies.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: When use trpc client should include the credentials cookies, closes [#11629](https://github.com/lobehub/lobe-chat/issues/11629) ([8ece553](https://github.com/lobehub/lobe-chat/commit/8ece553))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.315](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.314...v2.0.0-next.315)
6
56
 
7
57
  <sup>Released on **2026-01-19**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-19",
5
+ "version": "2.0.0-next.317"
6
+ },
7
+ {
8
+ "children": {
9
+ "fixes": [
10
+ "When use trpc client should include the credentials cookies."
11
+ ]
12
+ },
13
+ "date": "2026-01-19",
14
+ "version": "2.0.0-next.316"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.315",
3
+ "version": "2.0.0-next.317",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -21,7 +21,9 @@ async function generateJwks() {
21
21
  console.error('正在生成 RSA 密钥对...');
22
22
 
23
23
  // 生成 RS256 密钥对
24
- const { privateKey } = await generateKeyPair('RS256');
24
+ const { privateKey } = await generateKeyPair('RS256', {
25
+ extractable: true,
26
+ });
25
27
 
26
28
  // 导出为 JWK 格式
27
29
  const jwk = await exportJWK(privateKey);
@@ -199,10 +199,13 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
199
199
  });
200
200
 
201
201
  const handleCancelAuth = async () => {
202
- await remoteServerService.cancelAuthorization();
202
+ setRemoteError(null);
203
+ clearRemoteServerSyncError();
204
+
203
205
  setCloudLoginStatus('idle');
204
206
  setSelfhostLoginStatus('idle');
205
207
  setAuthProgress(null);
208
+ await remoteServerService.cancelAuthorization();
206
209
  };
207
210
 
208
211
  // 渲染 Cloud 登录内容
@@ -238,10 +241,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
238
241
 
239
242
  if (cloudLoginStatus === 'error') {
240
243
  return (
241
- <>
244
+ <Flexbox style={{ width: '100%' }}>
242
245
  <Alert
243
246
  description={remoteError || t('authResult.failed.desc')}
244
- style={{ width: '100%' }}
245
247
  title={t('authResult.failed.title')}
246
248
  type={'secondary'}
247
249
  />
@@ -254,7 +256,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
254
256
  >
255
257
  {t('screen5.actions.tryAgain')}
256
258
  </Button>
257
- </>
259
+ </Flexbox>
258
260
  );
259
261
  }
260
262
 
@@ -340,10 +342,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
340
342
 
341
343
  if (selfhostLoginStatus === 'error') {
342
344
  return (
343
- <Flexbox gap={16}>
345
+ <Flexbox gap={16} style={{ width: '100%' }}>
344
346
  <Alert
345
347
  description={remoteError || t('authResult.failed.desc')}
346
- style={{ width: '100%' }}
347
348
  title={t('authResult.failed.title')}
348
349
  type={'secondary'}
349
350
  />
@@ -354,6 +355,47 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
354
355
  );
355
356
  }
356
357
 
358
+ if (selfhostLoginStatus === 'loading') {
359
+ const phaseText = t(authorizationPhaseI18nKeyMap[authProgress?.phase ?? 'browser_opened'], {
360
+ defaultValue: t('screen5.actions.connecting'),
361
+ });
362
+ const remainingSeconds = authProgress
363
+ ? Math.max(0, Math.ceil((authProgress.maxPollTime - authProgress.elapsed) / 1000))
364
+ : null;
365
+
366
+ return (
367
+ <Flexbox gap={8} style={{ width: '100%' }}>
368
+ <Button
369
+ block
370
+ disabled={true}
371
+ icon={Server}
372
+ loading={true}
373
+ size={'large'}
374
+ type={'primary'}
375
+ >
376
+ {t('screen5.actions.connecting')}
377
+ </Button>
378
+ <Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
379
+ {phaseText}
380
+ </Text>
381
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
382
+ {remainingSeconds !== null ? (
383
+ <Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
384
+ {t('screen5.auth.remaining', {
385
+ time: remainingSeconds,
386
+ })}
387
+ </Text>
388
+ ) : (
389
+ <div />
390
+ )}
391
+ <Button onClick={handleCancelAuth} size={'small'} type={'text'}>
392
+ {t('screen5.actions.cancel')}
393
+ </Button>
394
+ </Flexbox>
395
+ </Flexbox>
396
+ );
397
+ }
398
+
357
399
  return (
358
400
  <Flexbox gap={16} style={{ width: '100%' }}>
359
401
  <Text color={cssVar.colorTextSecondary}>{t(loginMethodMetas.selfhost.descriptionKey)}</Text>
@@ -365,6 +407,11 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
365
407
  const { electronSystemService } = await import('@/services/electron/system');
366
408
  await electronSystemService.showContextMenu('edit');
367
409
  }}
410
+ onKeyDown={(e) => {
411
+ if (e.key === 'Enter') {
412
+ handleSelfhostConnect();
413
+ }
414
+ }}
368
415
  placeholder={t('screen5.selfhost.endpointPlaceholder')}
369
416
  prefix={<Icon icon={Server} style={{ marginRight: 4 }} />}
370
417
  size={'large'}
@@ -372,16 +419,14 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
372
419
  value={endpoint}
373
420
  />
374
421
  <Button
375
- disabled={!endpoint.trim() || selfhostLoginStatus === 'loading' || isConnectingServer}
376
- loading={selfhostLoginStatus === 'loading'}
422
+ disabled={!endpoint.trim() || isConnectingServer}
423
+ loading={false}
377
424
  onClick={handleSelfhostConnect}
378
425
  size={'large'}
379
426
  style={{ width: '100%' }}
380
427
  type={'primary'}
381
428
  >
382
- {selfhostLoginStatus === 'loading'
383
- ? t('screen5.actions.connecting')
384
- : t('screen5.actions.connectToServer')}
429
+ {t('screen5.actions.connectToServer')}
385
430
  </Button>
386
431
  </Flexbox>
387
432
  );
@@ -14,40 +14,79 @@ import LoginStep from './features/LoginStep';
14
14
  import PermissionsStep from './features/PermissionsStep';
15
15
  import WelcomeStep from './features/WelcomeStep';
16
16
  import {
17
- clearDesktopOnboardingStep,
18
- getDesktopOnboardingStep,
17
+ clearDesktopOnboardingScreen,
18
+ getDesktopOnboardingScreen,
19
19
  setDesktopOnboardingCompleted,
20
- setDesktopOnboardingStep,
20
+ setDesktopOnboardingScreen,
21
21
  } from './storage';
22
+ import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
22
23
 
23
24
  const DesktopOnboardingPage = memo(() => {
24
25
  const [searchParams, setSearchParams] = useSearchParams();
25
26
  const [isMac, setIsMac] = useState(true);
26
27
  const [isLoading, setIsLoading] = useState(true);
27
28
 
28
- // localStorage 或 URL query 参数获取初始步骤
29
- // 优先使用 localStorage 以支持重启后恢复
30
- const getInitialStep = useCallback(() => {
31
- // First try localStorage (for app restart scenario)
32
- const savedStep = getDesktopOnboardingStep();
33
- if (savedStep !== null) {
34
- return savedStep;
35
- }
36
- // Then try URL params
37
- const stepParam = searchParams.get('step');
38
- if (stepParam) {
39
- const step = parseInt(stepParam, 10);
40
- if (step >= 1 && step <= 4) return step;
41
- }
42
- return 1;
29
+ const flow = isMac
30
+ ? [
31
+ DesktopOnboardingScreen.Welcome,
32
+ DesktopOnboardingScreen.Permissions,
33
+ DesktopOnboardingScreen.DataMode,
34
+ DesktopOnboardingScreen.Login,
35
+ ]
36
+ : [
37
+ DesktopOnboardingScreen.Welcome,
38
+ DesktopOnboardingScreen.DataMode,
39
+ DesktopOnboardingScreen.Login,
40
+ ];
41
+
42
+ const resolveScreenForPlatform = useCallback(
43
+ (screen: DesktopOnboardingScreen) => {
44
+ if (!isMac && screen === DesktopOnboardingScreen.Permissions)
45
+ return DesktopOnboardingScreen.DataMode;
46
+ return screen;
47
+ },
48
+ [isMac],
49
+ );
50
+
51
+ const getRequestedScreenFromUrl = useCallback((): DesktopOnboardingScreen | null => {
52
+ const screenParam = searchParams.get('screen');
53
+ if (isDesktopOnboardingScreen(screenParam)) return screenParam;
54
+
55
+ return null;
43
56
  }, [searchParams]);
44
57
 
45
- const [currentStep, setCurrentStep] = useState(getInitialStep);
58
+ const [currentScreen, setCurrentScreen] = useState<DesktopOnboardingScreen>(
59
+ DesktopOnboardingScreen.Welcome,
60
+ );
61
+
62
+ useEffect(() => {
63
+ if (isLoading) return;
64
+
65
+ const saved = getDesktopOnboardingScreen();
66
+ const requested = getRequestedScreenFromUrl();
46
67
 
47
- // 持久化当前步骤到 localStorage
68
+ const initial = resolveScreenForPlatform(requested ?? saved ?? DesktopOnboardingScreen.Welcome);
69
+
70
+ setCurrentScreen(initial);
71
+
72
+ // Canonicalize URL to `?screen=...`
73
+ const currentUrlScreen = searchParams.get('screen');
74
+ if (currentUrlScreen !== initial) {
75
+ setSearchParams({ screen: initial });
76
+ }
77
+ }, [
78
+ getRequestedScreenFromUrl,
79
+ isLoading,
80
+ resolveScreenForPlatform,
81
+ searchParams,
82
+ setSearchParams,
83
+ ]);
84
+
85
+ // Persist current screen to localStorage.
48
86
  useEffect(() => {
49
- setDesktopOnboardingStep(currentStep);
50
- }, [currentStep]);
87
+ if (isLoading) return;
88
+ setDesktopOnboardingScreen(currentScreen);
89
+ }, [currentScreen, isLoading]);
51
90
 
52
91
  // 设置窗口大小和可调整性
53
92
  useEffect(() => {
@@ -91,79 +130,48 @@ const DesktopOnboardingPage = memo(() => {
91
130
  };
92
131
  }, []);
93
132
 
94
- // 监听 URL query 参数变化
133
+ // Listen URL changes: allow deep-linking between screens.
95
134
  useEffect(() => {
96
- const stepParam = searchParams.get('step');
97
- if (stepParam) {
98
- const step = parseInt(stepParam, 10);
99
- if (step >= 1 && step <= 4 && step !== currentStep) {
100
- setCurrentStep(step);
101
- }
102
- }
103
- }, [searchParams, currentStep]);
135
+ if (isLoading) return;
136
+ const requested = getRequestedScreenFromUrl();
137
+ if (!requested) return;
138
+ const resolved = resolveScreenForPlatform(requested);
139
+ if (resolved !== currentScreen) setCurrentScreen(resolved);
140
+ }, [currentScreen, getRequestedScreenFromUrl, isLoading, resolveScreenForPlatform]);
104
141
 
105
142
  const goToNextStep = useCallback(() => {
106
- setCurrentStep((prev) => {
107
- let nextStep: number;
108
- // 如果是第1步(WelcomeStep),下一步根据平台决定
109
- switch (prev) {
110
- case 1: {
111
- nextStep = isMac ? 2 : 3; // macOS 显示权限页,其他平台跳过
112
-
113
- break;
114
- }
115
- case 2: {
116
- // 如果是第2步(PermissionsStep,仅 macOS),下一步是第3步
117
- nextStep = 3;
118
-
119
- break;
120
- }
121
- case 3: {
122
- // 如果是第3步(DataModeStep),下一步是第4步
123
- nextStep = 4;
124
-
125
- break;
126
- }
127
- case 4: {
128
- // 如果是第4步(LoginStep),完成 onboarding
129
- setDesktopOnboardingCompleted();
130
- clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
131
- // Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
132
- electronSystemService
133
- .setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
134
- .catch(console.error)
135
- .finally(() => {
136
- // Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
137
- window.location.replace('/');
138
- });
139
- return prev;
140
- }
141
- default: {
142
- nextStep = prev + 1;
143
- }
143
+ setCurrentScreen((prev) => {
144
+ const idx = flow.indexOf(prev);
145
+ const next = flow[idx + 1];
146
+
147
+ if (!next) {
148
+ // Complete onboarding.
149
+ setDesktopOnboardingCompleted();
150
+ clearDesktopOnboardingScreen();
151
+
152
+ // Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
153
+ electronSystemService
154
+ .setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
155
+ .catch(console.error)
156
+ .finally(() => {
157
+ // Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
158
+ window.location.replace('/');
159
+ });
160
+
161
+ return prev;
144
162
  }
145
- // 更新 URL query 参数
146
- setSearchParams({ step: nextStep.toString() });
147
- return nextStep;
163
+
164
+ setSearchParams({ screen: next });
165
+ return next;
148
166
  });
149
167
  }, [isMac, setSearchParams]);
150
168
 
151
169
  const goToPreviousStep = useCallback(() => {
152
- setCurrentStep((prev) => {
153
- if (prev <= 1) return 1;
154
- let prevStep: number;
155
- // 如果当前是第3步(DataModeStep),上一步根据平台决定
156
- if (prev === 3) {
157
- prevStep = isMac ? 2 : 1;
158
- } else if (prev === 2) {
159
- // 如果当前是第2步(PermissionsStep),上一步是第1步
160
- prevStep = 1;
161
- } else {
162
- prevStep = prev - 1;
163
- }
164
- // 更新 URL query 参数
165
- setSearchParams({ step: prevStep.toString() });
166
- return prevStep;
170
+ setCurrentScreen((prev) => {
171
+ const idx = flow.indexOf(prev);
172
+ const prevScreen = flow[Math.max(0, idx - 1)] ?? DesktopOnboardingScreen.Welcome;
173
+ setSearchParams({ screen: prevScreen });
174
+ return prevScreen;
167
175
  });
168
176
  }, [isMac, setSearchParams]);
169
177
 
@@ -172,21 +180,22 @@ const DesktopOnboardingPage = memo(() => {
172
180
  }
173
181
 
174
182
  const renderStep = () => {
175
- switch (currentStep) {
176
- case 1: {
183
+ switch (currentScreen) {
184
+ case DesktopOnboardingScreen.Welcome: {
177
185
  return <WelcomeStep onNext={goToNextStep} />;
178
186
  }
179
- case 2: {
180
- // macOS 显示权限页
187
+ case DesktopOnboardingScreen.Permissions: {
188
+ // macOS-only screen; fallback to DataMode if platform doesn't support.
181
189
  if (!isMac) {
182
- return <DataModeStep onBack={goToPreviousStep} onNext={goToNextStep} />;
190
+ setCurrentScreen(DesktopOnboardingScreen.DataMode);
191
+ return null;
183
192
  }
184
193
  return <PermissionsStep onBack={goToPreviousStep} onNext={goToNextStep} />;
185
194
  }
186
- case 3: {
195
+ case DesktopOnboardingScreen.DataMode: {
187
196
  return <DataModeStep onBack={goToPreviousStep} onNext={goToNextStep} />;
188
197
  }
189
- case 4: {
198
+ case DesktopOnboardingScreen.Login: {
190
199
  return <LoginStep onBack={goToPreviousStep} onNext={goToNextStep} />;
191
200
  }
192
201
  default: {
@@ -0,0 +1,11 @@
1
+ import { DesktopOnboardingScreen } from './types';
2
+
3
+ const DESKTOP_ONBOARDING_ROUTE = '/desktop-onboarding';
4
+ export const getDesktopOnboardingPath = (screen?: DesktopOnboardingScreen) => {
5
+ if (!screen) return DESKTOP_ONBOARDING_ROUTE;
6
+ return `${DESKTOP_ONBOARDING_ROUTE}?screen=${encodeURIComponent(screen)}`;
7
+ };
8
+
9
+ export const navigateToDesktopOnboarding = (screen?: DesktopOnboardingScreen) => {
10
+ location.href = getDesktopOnboardingPath(screen);
11
+ };
@@ -1,5 +1,7 @@
1
+ import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
2
+
1
3
  export const DESKTOP_ONBOARDING_STORAGE_KEY = 'lobechat:desktop:onboarding:completed:v1';
2
- export const DESKTOP_ONBOARDING_STEP_KEY = 'lobechat:desktop:onboarding:step:v1';
4
+ export const DESKTOP_ONBOARDING_SCREEN_KEY = 'lobechat:desktop:onboarding:screen:v1';
3
5
 
4
6
  export const getDesktopOnboardingCompleted = () => {
5
7
  if (typeof window === 'undefined') return true;
@@ -35,33 +37,29 @@ export const clearDesktopOnboardingCompleted = () => {
35
37
  };
36
38
 
37
39
  /**
38
- * Get the persisted onboarding step (for restoring after app restart)
40
+ * Get the persisted onboarding screen (for restoring after app restart)
39
41
  */
40
- export const getDesktopOnboardingStep = (): number | null => {
42
+ export const getDesktopOnboardingScreen = () => {
41
43
  if (typeof window === 'undefined') return null;
42
44
 
43
45
  try {
44
- const step = window.localStorage.getItem(DESKTOP_ONBOARDING_STEP_KEY);
45
- if (step) {
46
- const parsedStep = Number.parseInt(step, 10);
47
- if (parsedStep >= 1 && parsedStep <= 4) {
48
- return parsedStep;
49
- }
50
- }
51
- return null;
46
+ const screen = window.localStorage.getItem(DESKTOP_ONBOARDING_SCREEN_KEY);
47
+ if (!screen) return null;
48
+ if (!isDesktopOnboardingScreen(screen)) return null;
49
+ return screen;
52
50
  } catch {
53
51
  return null;
54
52
  }
55
53
  };
56
54
 
57
55
  /**
58
- * Persist the current onboarding step
56
+ * Persist the current onboarding screen
59
57
  */
60
- export const setDesktopOnboardingStep = (step: number) => {
58
+ export const setDesktopOnboardingScreen = (screen: DesktopOnboardingScreen) => {
61
59
  if (typeof window === 'undefined') return false;
62
60
 
63
61
  try {
64
- window.localStorage.setItem(DESKTOP_ONBOARDING_STEP_KEY, step.toString());
62
+ window.localStorage.setItem(DESKTOP_ONBOARDING_SCREEN_KEY, screen);
65
63
  return true;
66
64
  } catch {
67
65
  return false;
@@ -69,13 +67,13 @@ export const setDesktopOnboardingStep = (step: number) => {
69
67
  };
70
68
 
71
69
  /**
72
- * Clear the persisted onboarding step (called when onboarding completes)
70
+ * Clear the persisted onboarding screen (called when onboarding completes)
73
71
  */
74
- export const clearDesktopOnboardingStep = () => {
72
+ export const clearDesktopOnboardingScreen = () => {
75
73
  if (typeof window === 'undefined') return false;
76
74
 
77
75
  try {
78
- window.localStorage.removeItem(DESKTOP_ONBOARDING_STEP_KEY);
76
+ window.localStorage.removeItem(DESKTOP_ONBOARDING_SCREEN_KEY);
79
77
  return true;
80
78
  } catch {
81
79
  return false;
@@ -0,0 +1,11 @@
1
+ export enum DesktopOnboardingScreen {
2
+ DataMode = 'data-mode',
3
+ Login = 'login',
4
+ Permissions = 'permissions',
5
+ Welcome = 'welcome',
6
+ }
7
+
8
+ export const isDesktopOnboardingScreen = (value: unknown): value is DesktopOnboardingScreen => {
9
+ if (typeof value !== 'string') return false;
10
+ return (Object.values(DesktopOnboardingScreen) as string[]).includes(value);
11
+ };
@@ -1,15 +1,17 @@
1
1
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
2
  import { Flexbox } from '@lobehub/ui';
3
- import { useRouter } from '@/libs/next/navigation';
4
3
  import { memo } from 'react';
5
- import { Link, useNavigate } from 'react-router-dom';
4
+ import { Link } from 'react-router-dom';
6
5
 
6
+ import { navigateToDesktopOnboarding } from '@/app/[variants]/(desktop)/desktop-onboarding/navigation';
7
7
  import { clearDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
8
+ import { DesktopOnboardingScreen } from '@/app/[variants]/(desktop)/desktop-onboarding/types';
8
9
  import BusinessPanelContent from '@/business/client/features/User/BusinessPanelContent';
9
10
  import BrandWatermark from '@/components/BrandWatermark';
10
11
  import Menu from '@/components/Menu';
11
12
  import { isDesktop } from '@/const/version';
12
13
  import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
14
+ import { useRouter } from '@/libs/next/navigation';
13
15
  import { useUserStore } from '@/store/user';
14
16
  import { authSelectors } from '@/store/user/selectors';
15
17
 
@@ -21,7 +23,7 @@ import { useMenu } from './useMenu';
21
23
 
22
24
  const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
23
25
  const router = useRouter();
24
- const navigate = useNavigate();
26
+
25
27
  const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
26
28
  const [openSignIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
27
29
  const { mainItems, logoutItems } = useMenu();
@@ -35,17 +37,16 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
35
37
  if (isDesktop) {
36
38
  closePopover();
37
39
 
38
- // Desktop: clear OIDC tokens (electron main) + re-enter desktop onboarding at Screen5.
39
40
  try {
40
41
  const { remoteServerService } = await import('@/services/electron/remoteServer');
41
42
  await remoteServerService.clearRemoteServerConfig();
42
- } catch {
43
- // Ignore: even if IPC is unavailable, still proceed to onboarding.
43
+ } catch (error) {
44
+ console.error(error);
45
+ } finally {
46
+ clearDesktopOnboardingCompleted();
47
+ signOut();
48
+ navigateToDesktopOnboarding(DesktopOnboardingScreen.Login);
44
49
  }
45
-
46
- clearDesktopOnboardingCompleted();
47
- signOut();
48
- navigate('/desktop-onboarding#5', { replace: true });
49
50
  return;
50
51
  }
51
52
 
@@ -77,15 +77,22 @@ const errorHandlingLink: TRPCLink<LambdaRouter> = () => {
77
77
  const linkOptions = {
78
78
  // eslint-disable-next-line no-undef
79
79
  fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
80
+ // Ensure credentials are included to send cookies (like mp_token)
81
+ // eslint-disable-next-line no-undef
82
+ const fetchOptions: RequestInit = {
83
+ ...init,
84
+ credentials: 'include',
85
+ };
86
+
80
87
  if (isDesktop) {
81
88
  // eslint-disable-next-line no-undef
82
- const res = await fetch(input as string, init as RequestInit);
89
+ const res = await fetch(input as string, fetchOptions);
83
90
 
84
91
  if (res) return res;
85
92
  }
86
93
 
87
94
  // eslint-disable-next-line no-undef
88
- return await fetch(input, init as RequestInit);
95
+ return await fetch(input, fetchOptions);
89
96
  },
90
97
  headers: async () => {
91
98
  // dynamic import to avoid circular dependency