fix(extension): harden desktop handoff

This commit is contained in:
NimBold
2026-06-24 21:06:20 +03:30
parent 6f426c1a18
commit 2fe1683f4b
8 changed files with 122 additions and 35 deletions
+48 -4
View File
@@ -55,6 +55,12 @@ describe('useDownloadStore', () => {
downloads: [],
backendRegisteredIds: new Set(),
pendingOrder: [],
isAddModalOpen: false,
pendingAddUrls: '',
pendingAddReferer: '',
pendingAddFilename: '',
pendingAddHeaders: '',
pendingAddCookies: '',
});
});
@@ -367,8 +373,8 @@ describe('useDownloadStore', () => {
)?.[1] as any).item.queue_id).toBe('queue-a');
});
it('preserves extension request headers and cookies for the Add modal', () => {
useDownloadStore.getState().handleExtensionDownload({
it('preserves extension request headers and cookies for the Add modal', async () => {
await useDownloadStore.getState().handleExtensionDownload({
urls: ['https://example.com/file.bin'],
referer: 'https://example.com/page',
silent: false,
@@ -382,7 +388,45 @@ describe('useDownloadStore', () => {
expect(state.pendingAddUrls).toBe('https://example.com/file.bin');
expect(state.pendingAddReferer).toBe('https://example.com/page');
expect(state.pendingAddFilename).toBe('file.bin');
expect(state.pendingAddHeaders).toBe('X-Test: value');
expect(state.pendingAddCookies).toBe('session=secret');
expect(state.pendingAddHeaders).toBe('X-Test: value');
expect(state.pendingAddCookies).toBe('session=secret');
});
it('starts silent extension captures through backend queue', async () => {
vi.mocked(ipc.invokeCommand).mockImplementation(async (command: string) => {
if (command === 'get_pending_order') return ['silent-id'];
return undefined;
});
vi.spyOn(crypto, 'randomUUID').mockReturnValueOnce('silent-id' as `${string}-${string}-${string}-${string}-${string}`);
await useDownloadStore.getState().handleExtensionDownload({
urls: ['https://example.com/downloads/report.pdf'],
referer: 'https://example.com/page',
silent: true,
filename: 'report.pdf',
headers: 'User-Agent: Test',
cookies: 'session=secret'
});
const state = useDownloadStore.getState();
expect(state.isAddModalOpen).toBe(false);
expect(state.downloads[0]).toEqual(expect.objectContaining({
id: 'silent-id',
url: 'https://example.com/downloads/report.pdf',
fileName: 'report.pdf',
category: 'Documents',
queueId: '00000000-0000-0000-0000-000000000001',
hasBeenDispatched: true
}));
expect(ipc.invokeCommand).toHaveBeenCalledWith(
'enqueue_download',
expect.objectContaining({
item: expect.objectContaining({
id: 'silent-id',
headers: 'User-Agent: Test',
cookies: 'session=secret'
})
})
);
});
});
+31 -3
View File
@@ -7,7 +7,7 @@ import type { DownloadStatus } from '../bindings/DownloadStatus';
import type { ExtensionDownload } from '../bindings/ExtensionDownload';
import type { Queue } from '../bindings/Queue';
import { useSettingsStore } from './useSettingsStore';
import { categoryForFileName, isActiveDownloadStatus, normalizeSpeedLimitForBackend, redactDownloadForPersistence } from '../utils/downloads';
import { canonicalizeDownloadFileName, categoryForFileName, isActiveDownloadStatus, normalizeSpeedLimitForBackend, redactDownloadForPersistence } from '../utils/downloads';
import {
resolveCategoryDestination
} from '../utils/downloadLocations';
@@ -125,6 +125,16 @@ export const getSiteLogin = (url: string, settings: ReturnType<typeof useSetting
return null;
};
const suggestedFileNameFromUrl = (rawUrl: string): string => {
try {
const url = new URL(rawUrl);
const lastSegment = decodeURIComponent(url.pathname.split('/').filter(Boolean).pop() || '');
return lastSegment || 'download';
} catch {
return 'download';
}
};
const syncSystemIntegrations = () => {
const settings = useSettingsStore.getState();
const activeCount = useDownloadStore.getState().downloads.filter(d => d.status === 'downloading').length;
@@ -192,7 +202,7 @@ interface DownloadState {
headers?: string | null,
cookies?: string | null
) => void;
handleExtensionDownload: (request: ExtensionDownloadRequest) => void;
handleExtensionDownload: (request: ExtensionDownloadRequest) => Promise<void>;
deleteModalState: DeleteModalState;
openDeleteModal: (downloadIds?: string | string[]) => void;
closeDeleteModal: () => void;
@@ -302,10 +312,28 @@ export const useDownloadStore = create<DownloadState>((set, get) => ({
pendingAddCookies: cookies?.trim() || state.pendingAddCookies || ''
};
}),
handleExtensionDownload: (request) => {
handleExtensionDownload: async (request) => {
const urls = [...new Set(request.urls.map(url => url.trim()).filter(Boolean))];
if (urls.length === 0) return;
if (request.silent) {
for (const url of urls) {
const filename = canonicalizeDownloadFileName(
urls.length === 1 && request.filename ? request.filename : suggestedFileNameFromUrl(url)
);
await get().addDownload({
id: crypto.randomUUID(),
url,
fileName: filename,
category: categoryForFileName(filename),
dateAdded: new Date().toISOString(),
headers: request.headers?.trim() || undefined,
cookies: urls.length === 1 ? request.cookies?.trim() || undefined : undefined,
}, { type: 'start-now' });
}
return;
}
get().openAddModalWithUrls(
urls.join('\n'),
request.referer,