Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ui/desktop/src/components/ExtensionInstallModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const mockElectron = {

(window as any).electron = mockElectron;

vi.mock('./ConfigContext', () => ({
useConfig: () => ({
extensionsList: [],
getExtensions: vi.fn().mockResolvedValue([]),
}),
}));

describe('ExtensionInstallModal', () => {
const mockAddExtension = vi.fn();
const mockSetView = vi.fn();
Expand Down
34 changes: 32 additions & 2 deletions ui/desktop/src/components/ExtensionInstallModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useEffect, useRef } from 'react';
import { IpcRendererEvent } from 'electron';
import {
Dialog,
Expand All @@ -13,6 +13,8 @@ import { extractExtensionName } from './settings/extensions/utils';
import { addExtensionFromDeepLink } from './settings/extensions/deeplink';
import type { ExtensionConfig } from '../api/types.gen';
import { View, ViewOptions } from '../utils/navigationUtils';
import { useConfig } from './ConfigContext';
import { toastService } from '../toasts';

type ModalType = 'blocked' | 'untrusted' | 'trusted';

Expand Down Expand Up @@ -66,6 +68,14 @@ function extractRemoteUrl(link: string): string | null {
}

export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstallModalProps) {
const { getExtensions } = useConfig();
const getExtensionsRef = useRef(getExtensions);
const processingLinkRef = useRef<string | null>(null);

useEffect(() => {
getExtensionsRef.current = getExtensions;
}, [getExtensions]);

const [modalState, setModalState] = useState<ExtensionModalState>({
isOpen: false,
modalType: 'trusted',
Expand Down Expand Up @@ -149,12 +159,30 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
};

const handleExtensionRequest = useCallback(async (link: string): Promise<void> => {
if (processingLinkRef.current === link) {
console.log(`Skipping duplicate extension request (already processing): ${link}`);
return;
}
processingLinkRef.current = link;

try {
console.log(`Processing extension request: ${link}`);

const command = extractCommand(link);
const remoteUrl = extractRemoteUrl(link);
const extName = extractExtensionName(link);
const extensionsList = await getExtensionsRef.current(true);

if (extensionsList?.find((ext) => ext.name === extName)) {
console.log(`Extension Already Installed: ${extName}`);

toastService.success({
title: `Extension '${extName}' Already Installed`,
msg: `'${extName}' extension has already been installed successfully. Start a new chat session to use it.`,
});
return;
}
console.log('Extension not found, continuing to show modal');

const extensionInfo: ExtensionInfo = {
name: extName,
Expand Down Expand Up @@ -182,6 +210,8 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
...prev,
error: error instanceof Error ? error.message : 'Unknown error',
}));
} finally {
processingLinkRef.current = null;
}
}, []);

Expand Down Expand Up @@ -285,7 +315,7 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className={getTitleClassName()}>{config.title}</DialogTitle>
<DialogDescription className="whitespace-pre-wrap text-left">
<DialogDescription className="text-left whitespace-pre-wrap">
{config.message}
</DialogDescription>
</DialogHeader>
Expand Down