@lobehub/lobehub 2.0.0-next.141 → 2.0.0-next.143
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 +50 -0
- package/Dockerfile +2 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/controllers/__tests__/McpInstallCtr.test.ts +286 -0
- package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +347 -0
- package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +645 -0
- package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +372 -0
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +276 -0
- package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +171 -0
- package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +573 -0
- package/apps/desktop/src/main/core/browser/__tests__/BrowserManager.test.ts +415 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/I18nManager.test.ts +353 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +156 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +348 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/StaticFileServerManager.test.ts +481 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/StoreManager.test.ts +164 -0
- package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +513 -0
- package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
- package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
- package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
- package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
- package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
- package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
- package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
- package/apps/desktop/src/preload/electronApi.test.ts +142 -0
- package/apps/desktop/src/preload/invoke.test.ts +145 -0
- package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
- package/apps/desktop/src/preload/streamer.test.ts +365 -0
- package/apps/desktop/vitest.config.mts +1 -0
- package/changelog/v1.json +18 -0
- package/docs/self-hosting/environment-variables/model-provider.mdx +31 -0
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +30 -0
- package/locales/ar/marketAuth.json +13 -0
- package/locales/bg-BG/marketAuth.json +13 -0
- package/locales/de-DE/marketAuth.json +13 -0
- package/locales/en-US/marketAuth.json +13 -0
- package/locales/es-ES/marketAuth.json +13 -0
- package/locales/fa-IR/marketAuth.json +13 -0
- package/locales/fr-FR/marketAuth.json +13 -0
- package/locales/it-IT/marketAuth.json +13 -0
- package/locales/ja-JP/marketAuth.json +13 -0
- package/locales/ko-KR/marketAuth.json +13 -0
- package/locales/nl-NL/marketAuth.json +13 -0
- package/locales/pl-PL/marketAuth.json +13 -0
- package/locales/pt-BR/marketAuth.json +13 -0
- package/locales/ru-RU/marketAuth.json +13 -0
- package/locales/tr-TR/marketAuth.json +13 -0
- package/locales/vi-VN/marketAuth.json +13 -0
- package/locales/zh-CN/marketAuth.json +13 -0
- package/locales/zh-TW/marketAuth.json +13 -0
- package/package.json +1 -1
- package/packages/database/src/models/user.ts +2 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +6 -3
- package/packages/types/src/discover/mcp.ts +2 -1
- package/packages/types/src/tool/plugin.ts +2 -1
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
- package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
- package/src/config/modelProviders/vertexai.ts +1 -1
- package/src/envs/llm.ts +4 -0
- package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
- package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
- package/src/libs/mcp/types.ts +8 -0
- package/src/locales/default/marketAuth.ts +13 -0
- package/src/server/modules/ModelRuntime/index.ts +4 -4
- package/src/server/routers/lambda/market/index.ts +85 -2
- package/src/server/services/discover/index.ts +45 -4
- package/src/services/discover.ts +1 -1
- package/src/services/mcp.ts +18 -3
- package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
- package/src/store/tool/slices/mcpStore/action.ts +153 -11
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { app } from 'electron';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { getProtocolScheme, parseProtocolUrl } from '@/utils/protocol';
|
|
5
|
+
|
|
6
|
+
import type { App as AppCore } from '../../App';
|
|
7
|
+
import { ProtocolManager } from '../ProtocolManager';
|
|
8
|
+
|
|
9
|
+
// Use vi.hoisted to define mocks before hoisting
|
|
10
|
+
const { mockApp, mockGetProtocolScheme, mockParseProtocolUrl } = vi.hoisted(() => ({
|
|
11
|
+
mockApp: {
|
|
12
|
+
getPath: vi.fn().mockReturnValue('/mock/exe/path'),
|
|
13
|
+
isDefaultProtocolClient: vi.fn().mockReturnValue(true),
|
|
14
|
+
isReady: vi.fn().mockReturnValue(true),
|
|
15
|
+
name: 'LobeHub',
|
|
16
|
+
on: vi.fn(),
|
|
17
|
+
setAsDefaultProtocolClient: vi.fn().mockReturnValue(true),
|
|
18
|
+
},
|
|
19
|
+
mockGetProtocolScheme: vi.fn().mockReturnValue('lobehub'),
|
|
20
|
+
mockParseProtocolUrl: vi.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Mock electron app
|
|
24
|
+
vi.mock('electron', () => ({
|
|
25
|
+
app: mockApp,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock logger
|
|
29
|
+
vi.mock('@/utils/logger', () => ({
|
|
30
|
+
createLogger: () => ({
|
|
31
|
+
debug: vi.fn(),
|
|
32
|
+
error: vi.fn(),
|
|
33
|
+
info: vi.fn(),
|
|
34
|
+
warn: vi.fn(),
|
|
35
|
+
}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Mock protocol utils
|
|
39
|
+
vi.mock('@/utils/protocol', () => ({
|
|
40
|
+
getProtocolScheme: mockGetProtocolScheme,
|
|
41
|
+
parseProtocolUrl: mockParseProtocolUrl,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Mock isDev
|
|
45
|
+
vi.mock('@/const/env', () => ({
|
|
46
|
+
isDev: false,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
describe('ProtocolManager', () => {
|
|
50
|
+
let manager: ProtocolManager;
|
|
51
|
+
let mockAppCore: AppCore;
|
|
52
|
+
let mockShowMainWindow: ReturnType<typeof vi.fn>;
|
|
53
|
+
let mockHandleProtocolRequest: ReturnType<typeof vi.fn>;
|
|
54
|
+
|
|
55
|
+
// Store event handlers
|
|
56
|
+
let openUrlHandler: ((event: any, url: string) => void) | undefined;
|
|
57
|
+
let secondInstanceHandler: ((event: any, commandLine: string[]) => void) | undefined;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.clearAllMocks();
|
|
61
|
+
|
|
62
|
+
// Reset electron app mock
|
|
63
|
+
mockApp.isDefaultProtocolClient.mockReturnValue(true);
|
|
64
|
+
mockApp.setAsDefaultProtocolClient.mockReturnValue(true);
|
|
65
|
+
mockApp.isReady.mockReturnValue(true);
|
|
66
|
+
|
|
67
|
+
// Capture event handlers
|
|
68
|
+
openUrlHandler = undefined;
|
|
69
|
+
secondInstanceHandler = undefined;
|
|
70
|
+
mockApp.on.mockImplementation((event: string, handler: any) => {
|
|
71
|
+
if (event === 'open-url') {
|
|
72
|
+
openUrlHandler = handler;
|
|
73
|
+
} else if (event === 'second-instance') {
|
|
74
|
+
secondInstanceHandler = handler;
|
|
75
|
+
}
|
|
76
|
+
return mockApp;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Reset protocol utils mock
|
|
80
|
+
mockGetProtocolScheme.mockReturnValue('lobehub');
|
|
81
|
+
mockParseProtocolUrl.mockReturnValue({
|
|
82
|
+
action: 'install',
|
|
83
|
+
params: { url: 'https://example.com' },
|
|
84
|
+
urlType: 'plugin',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Create mock App core
|
|
88
|
+
mockShowMainWindow = vi.fn();
|
|
89
|
+
mockHandleProtocolRequest = vi.fn().mockResolvedValue(true);
|
|
90
|
+
|
|
91
|
+
mockAppCore = {
|
|
92
|
+
browserManager: {
|
|
93
|
+
showMainWindow: mockShowMainWindow,
|
|
94
|
+
},
|
|
95
|
+
handleProtocolRequest: mockHandleProtocolRequest,
|
|
96
|
+
} as unknown as AppCore;
|
|
97
|
+
|
|
98
|
+
manager = new ProtocolManager(mockAppCore);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('constructor', () => {
|
|
102
|
+
it('should initialize with protocol scheme from getProtocolScheme', () => {
|
|
103
|
+
expect(getProtocolScheme).toHaveBeenCalled();
|
|
104
|
+
expect(manager.getScheme()).toBe('lobehub');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('initialize', () => {
|
|
109
|
+
it('should register protocol handlers', () => {
|
|
110
|
+
manager.initialize();
|
|
111
|
+
|
|
112
|
+
expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should set up event listeners', () => {
|
|
116
|
+
manager.initialize();
|
|
117
|
+
|
|
118
|
+
expect(app.on).toHaveBeenCalledWith('open-url', expect.any(Function));
|
|
119
|
+
expect(app.on).toHaveBeenCalledWith('second-instance', expect.any(Function));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('protocol registration', () => {
|
|
124
|
+
it('should use simple registration in production mode', () => {
|
|
125
|
+
manager.initialize();
|
|
126
|
+
|
|
127
|
+
expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should use explicit parameters in development mode', async () => {
|
|
131
|
+
vi.doMock('@/const/env', () => ({ isDev: true }));
|
|
132
|
+
vi.resetModules();
|
|
133
|
+
|
|
134
|
+
const { ProtocolManager: DevProtocolManager } = await import('../ProtocolManager');
|
|
135
|
+
const devManager = new DevProtocolManager(mockAppCore);
|
|
136
|
+
devManager.initialize();
|
|
137
|
+
|
|
138
|
+
// In dev mode, should be called with additional arguments
|
|
139
|
+
expect(app.setAsDefaultProtocolClient).toHaveBeenCalledWith(
|
|
140
|
+
'lobehub',
|
|
141
|
+
expect.any(String),
|
|
142
|
+
expect.any(Array),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should verify registration status after registering', () => {
|
|
147
|
+
manager.initialize();
|
|
148
|
+
|
|
149
|
+
expect(app.isDefaultProtocolClient).toHaveBeenCalledWith('lobehub');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('getProtocolUrlFromArgs', () => {
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
manager.initialize();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should extract protocol URL from command line arguments', () => {
|
|
159
|
+
// Access private method through prototype
|
|
160
|
+
const result = manager['getProtocolUrlFromArgs']([
|
|
161
|
+
'/path/to/app',
|
|
162
|
+
'lobehub://plugin/install?url=https://example.com',
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
expect(result).toBe('lobehub://plugin/install?url=https://example.com');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should return null when no matching URL found', () => {
|
|
169
|
+
const result = manager['getProtocolUrlFromArgs'](['/path/to/app', '--some-flag']);
|
|
170
|
+
|
|
171
|
+
expect(result).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return first matching URL when multiple exist', () => {
|
|
175
|
+
const result = manager['getProtocolUrlFromArgs']([
|
|
176
|
+
'lobehub://first/action',
|
|
177
|
+
'lobehub://second/action',
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
expect(result).toBe('lobehub://first/action');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('handleProtocolUrl', () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
manager.initialize();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should store URL when app is not ready', () => {
|
|
190
|
+
mockApp.isReady.mockReturnValue(false);
|
|
191
|
+
|
|
192
|
+
manager['handleProtocolUrl']('lobehub://plugin/install');
|
|
193
|
+
|
|
194
|
+
expect(manager['pendingUrls']).toContain('lobehub://plugin/install');
|
|
195
|
+
expect(mockShowMainWindow).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should process URL immediately when app is ready', async () => {
|
|
199
|
+
mockApp.isReady.mockReturnValue(true);
|
|
200
|
+
|
|
201
|
+
manager['handleProtocolUrl']('lobehub://plugin/install');
|
|
202
|
+
|
|
203
|
+
// Allow async processing
|
|
204
|
+
await vi.waitFor(() => {
|
|
205
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should ignore URLs with invalid protocol scheme', async () => {
|
|
210
|
+
mockApp.isReady.mockReturnValue(true);
|
|
211
|
+
|
|
212
|
+
manager['handleProtocolUrl']('invalid://plugin/install');
|
|
213
|
+
|
|
214
|
+
await Promise.resolve(); // Allow any async work
|
|
215
|
+
|
|
216
|
+
expect(mockHandleProtocolRequest).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('event listeners', () => {
|
|
221
|
+
beforeEach(() => {
|
|
222
|
+
manager.initialize();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should handle open-url event', async () => {
|
|
226
|
+
expect(openUrlHandler).toBeDefined();
|
|
227
|
+
|
|
228
|
+
const mockEvent = { preventDefault: vi.fn() };
|
|
229
|
+
openUrlHandler!(mockEvent, 'lobehub://plugin/install');
|
|
230
|
+
|
|
231
|
+
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
|
232
|
+
await vi.waitFor(() => {
|
|
233
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should handle second-instance event with protocol URL', async () => {
|
|
238
|
+
expect(secondInstanceHandler).toBeDefined();
|
|
239
|
+
|
|
240
|
+
const mockEvent = {};
|
|
241
|
+
secondInstanceHandler!(mockEvent, ['/path/to/app', 'lobehub://plugin/install']);
|
|
242
|
+
|
|
243
|
+
await vi.waitFor(() => {
|
|
244
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should show main window even without protocol URL in second-instance', () => {
|
|
249
|
+
expect(secondInstanceHandler).toBeDefined();
|
|
250
|
+
|
|
251
|
+
const mockEvent = {};
|
|
252
|
+
secondInstanceHandler!(mockEvent, ['/path/to/app', '--some-flag']);
|
|
253
|
+
|
|
254
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('processPendingUrls', () => {
|
|
259
|
+
beforeEach(() => {
|
|
260
|
+
manager.initialize();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should process all pending URLs', async () => {
|
|
264
|
+
// Add pending URLs
|
|
265
|
+
manager['pendingUrls'] = ['lobehub://action1', 'lobehub://action2'];
|
|
266
|
+
|
|
267
|
+
await manager.processPendingUrls();
|
|
268
|
+
|
|
269
|
+
// Should have shown main window for each URL
|
|
270
|
+
expect(mockShowMainWindow).toHaveBeenCalledTimes(2);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should clear pending URLs after processing', async () => {
|
|
274
|
+
manager['pendingUrls'] = ['lobehub://action1'];
|
|
275
|
+
|
|
276
|
+
await manager.processPendingUrls();
|
|
277
|
+
|
|
278
|
+
expect(manager['pendingUrls']).toHaveLength(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should skip when no pending URLs', async () => {
|
|
282
|
+
manager['pendingUrls'] = [];
|
|
283
|
+
|
|
284
|
+
await manager.processPendingUrls();
|
|
285
|
+
|
|
286
|
+
expect(mockShowMainWindow).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('getScheme', () => {
|
|
291
|
+
it('should return the protocol scheme', () => {
|
|
292
|
+
expect(manager.getScheme()).toBe('lobehub');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('isRegistered', () => {
|
|
297
|
+
it('should return true when registered', () => {
|
|
298
|
+
mockApp.isDefaultProtocolClient.mockReturnValue(true);
|
|
299
|
+
|
|
300
|
+
expect(manager.isRegistered()).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should return false when not registered', () => {
|
|
304
|
+
mockApp.isDefaultProtocolClient.mockReturnValue(false);
|
|
305
|
+
|
|
306
|
+
expect(manager.isRegistered()).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('processProtocolUrl', () => {
|
|
311
|
+
beforeEach(() => {
|
|
312
|
+
manager.initialize();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should show main window and dispatch to handler', async () => {
|
|
316
|
+
vi.mocked(parseProtocolUrl).mockReturnValue({
|
|
317
|
+
action: 'install',
|
|
318
|
+
params: { url: 'https://example.com' },
|
|
319
|
+
urlType: 'plugin',
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await manager['processProtocolUrl']('lobehub://plugin/install');
|
|
323
|
+
|
|
324
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
325
|
+
expect(mockHandleProtocolRequest).toHaveBeenCalledWith('plugin', 'install', {
|
|
326
|
+
url: 'https://example.com',
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should warn and return when parseProtocolUrl returns null', async () => {
|
|
331
|
+
vi.mocked(parseProtocolUrl).mockReturnValue(null);
|
|
332
|
+
|
|
333
|
+
await manager['processProtocolUrl']('lobehub://invalid');
|
|
334
|
+
|
|
335
|
+
expect(mockShowMainWindow).toHaveBeenCalled();
|
|
336
|
+
expect(mockHandleProtocolRequest).not.toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should handle errors gracefully', async () => {
|
|
340
|
+
mockHandleProtocolRequest.mockRejectedValue(new Error('Handler error'));
|
|
341
|
+
|
|
342
|
+
// Should not throw
|
|
343
|
+
await expect(
|
|
344
|
+
manager['processProtocolUrl']('lobehub://plugin/install'),
|
|
345
|
+
).resolves.not.toThrow();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
});
|