diff --git a/surfsense_backend/app/connectors/onedrive/folder_manager.py b/surfsense_backend/app/connectors/onedrive/folder_manager.py index ad04e12ff..7f286453c 100644 --- a/surfsense_backend/app/connectors/onedrive/folder_manager.py +++ b/surfsense_backend/app/connectors/onedrive/folder_manager.py @@ -24,6 +24,10 @@ async def list_folder_contents( for item in items: item["isFolder"] = is_folder(item) + if item["isFolder"]: + item.setdefault("mimeType", "application/vnd.ms-folder") + else: + item.setdefault("mimeType", item.get("file", {}).get("mimeType", "application/octet-stream")) items.sort(key=lambda x: (not x["isFolder"], x.get("name", "").lower())) diff --git a/surfsense_backend/app/routes/onedrive_add_connector_route.py b/surfsense_backend/app/routes/onedrive_add_connector_route.py index 64f5b2461..19bcbe6ff 100644 --- a/surfsense_backend/app/routes/onedrive_add_connector_route.py +++ b/surfsense_backend/app/routes/onedrive_add_connector_route.py @@ -5,8 +5,7 @@ Endpoints: - GET /auth/onedrive/connector/add - Initiate OAuth - GET /auth/onedrive/connector/callback - Handle OAuth callback - GET /auth/onedrive/connector/reauth - Re-authenticate existing connector -- GET /connectors/{connector_id}/onedrive/folders - List folder contents (legacy custom browser) -- GET /connectors/{connector_id}/onedrive/picker-token - Get SharePoint token for File Picker v8 +- GET /connectors/{connector_id}/onedrive/folders - List folder contents """ import logging @@ -396,121 +395,6 @@ async def list_onedrive_folders( raise HTTPException(status_code=500, detail=f"Failed to list OneDrive contents: {e!s}") from e -@router.get("/connectors/{connector_id}/onedrive/picker-token") -async def get_onedrive_picker_token( - connector_id: int, - resource: str | None = None, - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -): - """Get an access token scoped for the OneDrive File Picker v8. - - The picker requires SharePoint-audience tokens, not Graph tokens. - If *resource* is omitted the user's OneDrive root URL is resolved via - Graph and used as the resource. - """ - try: - result = await session.execute( - select(SearchSourceConnector).filter( - SearchSourceConnector.id == connector_id, - SearchSourceConnector.user_id == user.id, - SearchSourceConnector.connector_type == SearchSourceConnectorType.ONEDRIVE_CONNECTOR, - ) - ) - connector = result.scalars().first() - if not connector: - raise HTTPException(status_code=404, detail="OneDrive connector not found or access denied") - - token_encryption = get_token_encryption() - is_encrypted = connector.config.get("_token_encrypted", False) - - # Resolve the SharePoint base URL when the caller doesn't provide one - if not resource: - access_token = connector.config.get("access_token") - if is_encrypted and access_token: - access_token = token_encryption.decrypt_token(access_token) - - # Refresh the Graph token if it has expired - expires_at_str = connector.config.get("expires_at") - if expires_at_str: - from dateutil.parser import parse as parse_date - if datetime.now(UTC) >= parse_date(expires_at_str): - connector = await refresh_onedrive_token(session, connector) - access_token = connector.config.get("access_token") - if connector.config.get("_token_encrypted") and access_token: - access_token = token_encryption.decrypt_token(access_token) - - async with httpx.AsyncClient() as client: - drive_resp = await client.get( - "https://graph.microsoft.com/v1.0/me/drive/root", - headers={"Authorization": f"Bearer {access_token}"}, - timeout=30.0, - ) - if drive_resp.status_code != 200: - raise HTTPException( - status_code=500, - detail="Failed to resolve OneDrive base URL from Graph API", - ) - from urllib.parse import urlparse - web_url = drive_resp.json().get("webUrl", "") - parsed = urlparse(web_url) - resource = f"{parsed.scheme}://{parsed.hostname}" - - # Exchange the refresh token for a SharePoint-audience token - refresh_token = connector.config.get("refresh_token") - if is_encrypted and refresh_token: - refresh_token = token_encryption.decrypt_token(refresh_token) - if not refresh_token: - raise HTTPException(status_code=400, detail="No refresh token available") - - token_data = { - "client_id": config.MICROSOFT_CLIENT_ID, - "client_secret": config.MICROSOFT_CLIENT_SECRET, - "grant_type": "refresh_token", - "refresh_token": refresh_token, - "scope": f"{resource}/.default", - } - async with httpx.AsyncClient() as client: - token_response = await client.post( - TOKEN_URL, - data=token_data, - headers={"Content-Type": "application/x-www-form-urlencoded"}, - timeout=30.0, - ) - - if token_response.status_code != 200: - error_detail = "Failed to acquire picker token" - try: - error_json = token_response.json() - error_detail = error_json.get("error_description", error_detail) - except Exception: - pass - logger.error("Picker token exchange failed for connector %s: %s", connector_id, error_detail) - raise HTTPException(status_code=400, detail=error_detail) - - token_json = token_response.json() - - # Persist new refresh token when Microsoft rotates it - new_refresh = token_json.get("refresh_token") - if new_refresh: - cfg = dict(connector.config) - cfg["refresh_token"] = token_encryption.encrypt_token(new_refresh) - connector.config = cfg - flag_modified(connector, "config") - await session.commit() - - return { - "access_token": token_json["access_token"], - "base_url": resource, - } - - except HTTPException: - raise - except Exception as e: - logger.error("Error getting OneDrive picker token: %s", str(e), exc_info=True) - raise HTTPException(status_code=500, detail=f"Failed to get picker token: {e!s}") from e - - async def refresh_onedrive_token( session: AsyncSession, connector: SearchSourceConnector ) -> SearchSourceConnector: diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index b725ab703..14c481960 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -22,10 +22,6 @@ import { Tabs, TabsContent } from "@/components/ui/tabs"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { useConnectorsSync } from "@/hooks/use-connectors-sync"; import { PICKER_CLOSE_EVENT, PICKER_OPEN_EVENT } from "@/hooks/use-google-picker"; -import { - ONEDRIVE_PICKER_CLOSE_EVENT, - ONEDRIVE_PICKER_OPEN_EVENT, -} from "@/hooks/use-onedrive-picker"; import { cn } from "@/lib/utils"; import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header"; import { ConnectorConnectView } from "./connector-popup/connector-configs/views/connector-connect-view"; @@ -153,13 +149,9 @@ export const ConnectorIndicator = forwardRef setPickerOpen(false); window.addEventListener(PICKER_OPEN_EVENT, onOpen); window.addEventListener(PICKER_CLOSE_EVENT, onClose); - window.addEventListener(ONEDRIVE_PICKER_OPEN_EVENT, onOpen); - window.addEventListener(ONEDRIVE_PICKER_CLOSE_EVENT, onClose); return () => { window.removeEventListener(PICKER_OPEN_EVENT, onOpen); window.removeEventListener(PICKER_CLOSE_EVENT, onClose); - window.removeEventListener(ONEDRIVE_PICKER_OPEN_EVENT, onOpen); - window.removeEventListener(ONEDRIVE_PICKER_CLOSE_EVENT, onClose); }; }, []); diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/composio-drive-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/composio-drive-config.tsx index f7f490774..0f6044050 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/composio-drive-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/composio-drive-config.tsx @@ -13,7 +13,7 @@ import { } from "lucide-react"; import type { FC } from "react"; import { useCallback, useEffect, useState } from "react"; -import { ComposioDriveFolderTree } from "@/components/connectors/composio-drive-folder-tree"; +import { DriveFolderTree, type SelectedFolder } from "@/components/connectors/drive-folder-tree"; import { Label } from "@/components/ui/label"; import { Select, @@ -23,13 +23,9 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { connectorsApiService } from "@/lib/apis/connectors-api.service"; import type { ConnectorConfigProps } from "../index"; -interface SelectedFolder { - id: string; - name: string; -} - interface IndexingOptions { max_files_per_folder: number; incremental_sync: boolean; @@ -102,6 +98,16 @@ export const ComposioDriveConfig: FC = ({ connector, onCon setAuthError(true); }, []); + const fetchItems = useCallback( + async (parentId?: string) => { + return connectorsApiService.listComposioDriveFolders({ + connector_id: connector.id, + parent_id: parentId, + }); + }, + [connector.id] + ); + const [isEditMode] = useState(() => existingFolders.length > 0 || existingFiles.length > 0); const [isFolderTreeOpen, setIsFolderTreeOpen] = useState(!isEditMode); @@ -255,24 +261,28 @@ export const ComposioDriveConfig: FC = ({ connector, onCon )} {isFolderTreeOpen && ( - )} ) : ( - )} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/onedrive-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/onedrive-config.tsx index 792c3f1c0..250a353cd 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/onedrive-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/onedrive-config.tsx @@ -1,6 +1,8 @@ "use client"; import { + ChevronDown, + ChevronRight, File, FileSpreadsheet, FileText, @@ -11,7 +13,7 @@ import { } from "lucide-react"; import type { FC } from "react"; import { useCallback, useEffect, useState } from "react"; -import { Button } from "@/components/ui/button"; +import { DriveFolderTree, type SelectedFolder } from "@/components/connectors/drive-folder-tree"; import { Label } from "@/components/ui/label"; import { Select, @@ -20,17 +22,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; -import { type OneDrivePickerResult, useOneDrivePicker } from "@/hooks/use-onedrive-picker"; +import { connectorsApiService } from "@/lib/apis/connectors-api.service"; import type { ConnectorConfigProps } from "../index"; -interface SelectedItem { - id: string; - name: string; - driveId?: string; -} - interface IndexingOptions { max_files_per_folder: number; incremental_sync: boolean; @@ -43,7 +38,7 @@ const DEFAULT_INDEXING_OPTIONS: IndexingOptions = { include_subfolders: true, }; -function getFileIconFromName(fileName: string, className = "size-3.5 shrink-0") { +function getFileIconFromName(fileName: string, className: string = "size-3.5 shrink-0") { const lowerName = fileName.toLowerCase(); if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls") || lowerName.endsWith(".csv")) { return ; @@ -61,18 +56,39 @@ function getFileIconFromName(fileName: string, className = "size-3.5 shrink-0") } export const OneDriveConfig: FC = ({ connector, onConfigChange }) => { - const existingFolders = (connector.config?.selected_folders as SelectedItem[] | undefined) || []; - const existingFiles = (connector.config?.selected_files as SelectedItem[] | undefined) || []; + const existingFolders = + (connector.config?.selected_folders as SelectedFolder[] | undefined) || []; + const existingFiles = (connector.config?.selected_files as SelectedFolder[] | undefined) || []; const existingIndexingOptions = (connector.config?.indexing_options as IndexingOptions | undefined) || DEFAULT_INDEXING_OPTIONS; - const [selectedFolders, setSelectedFolders] = useState(existingFolders); - const [selectedFiles, setSelectedFiles] = useState(existingFiles); + const [selectedFolders, setSelectedFolders] = useState(existingFolders); + const [selectedFiles, setSelectedFiles] = useState(existingFiles); const [indexingOptions, setIndexingOptions] = useState(existingIndexingOptions); + const [authError, setAuthError] = useState(false); + + const isAuthExpired = connector.config?.auth_expired === true || authError; + + const handleAuthError = useCallback(() => { + setAuthError(true); + }, []); + + const fetchItems = useCallback( + async (parentId?: string) => { + return connectorsApiService.listOneDriveFolders({ + connector_id: connector.id, + parent_id: parentId, + }); + }, + [connector.id] + ); + + const [isEditMode] = useState(() => existingFolders.length > 0 || existingFiles.length > 0); + const [isFolderTreeOpen, setIsFolderTreeOpen] = useState(!isEditMode); useEffect(() => { - const folders = (connector.config?.selected_folders as SelectedItem[] | undefined) || []; - const files = (connector.config?.selected_files as SelectedItem[] | undefined) || []; + const folders = (connector.config?.selected_folders as SelectedFolder[] | undefined) || []; + const files = (connector.config?.selected_files as SelectedFolder[] | undefined) || []; const options = (connector.config?.indexing_options as IndexingOptions | undefined) || DEFAULT_INDEXING_OPTIONS; @@ -82,9 +98,9 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh }, [connector.config]); const updateConfig = ( - folders: SelectedItem[], - files: SelectedItem[], - options: IndexingOptions, + folders: SelectedFolder[], + files: SelectedFolder[], + options: IndexingOptions ) => { if (onConfigChange) { onConfigChange({ @@ -96,30 +112,15 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh } }; - const handlePicked = useCallback( - (result: OneDrivePickerResult) => { - const folders = result.folders.map((f) => ({ id: f.id, name: f.name, driveId: f.driveId })); - const files = result.files.map((f) => ({ id: f.id, name: f.name, driveId: f.driveId })); - setSelectedFolders(folders); - setSelectedFiles(files); - updateConfig(folders, files, indexingOptions); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [indexingOptions, connector.config], - ); + const handleSelectFolders = (folders: SelectedFolder[]) => { + setSelectedFolders(folders); + updateConfig(folders, selectedFiles, indexingOptions); + }; - const { - openPicker, - loading: pickerLoading, - error: pickerError, - } = useOneDrivePicker({ - connectorId: connector.id, - onPicked: handlePicked, - }); - - const isAuthExpired = - connector.config?.auth_expired === true || - (!!pickerError && pickerError.toLowerCase().includes("authentication expired")); + const handleSelectFiles = (files: SelectedFolder[]) => { + setSelectedFiles(files); + updateConfig(selectedFolders, files, indexingOptions); + }; const handleIndexingOptionChange = (key: keyof IndexingOptions, value: number | boolean) => { const newOptions = { ...indexingOptions, [key]: value }; @@ -128,13 +129,13 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh }; const handleRemoveFolder = (folderId: string) => { - const newFolders = selectedFolders.filter((f) => f.id !== folderId); + const newFolders = selectedFolders.filter((folder) => folder.id !== folderId); setSelectedFolders(newFolders); updateConfig(newFolders, selectedFiles, indexingOptions); }; const handleRemoveFile = (fileId: string) => { - const newFiles = selectedFiles.filter((f) => f.id !== fileId); + const newFiles = selectedFiles.filter((file) => file.id !== fileId); setSelectedFiles(newFiles); updateConfig(selectedFolders, newFiles, indexingOptions); }; @@ -142,13 +143,13 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh const totalSelected = selectedFolders.length + selectedFiles.length; return ( -
+
{/* Folder & File Selection */}

Folder & File Selection

- Select specific folders and/or individual files to index. + Select specific folders and/or individual files to index from your OneDrive.

@@ -159,7 +160,7 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh const parts: string[] = []; if (selectedFolders.length > 0) { parts.push( - `${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`, + `${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}` ); } if (selectedFiles.length > 0) { @@ -209,23 +210,52 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh
)} - - {isAuthExpired && (

Your OneDrive authentication has expired. Please re-authenticate using the button below.

)} + + {isEditMode ? ( +
+ + {isFolderTreeOpen && ( + + )} +
+ ) : ( + + )}
{/* Indexing Options */} @@ -237,6 +267,7 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh

+ {/* Max files per folder */}
@@ -260,16 +291,27 @@ export const OneDriveConfig: FC = ({ connector, onConfigCh - 50 files - 100 files - 250 files - 500 files - 1000 files + + 50 files + + + 100 files + + + 250 files + + + 500 files + + + 1000 files +
+ {/* Incremental sync toggle */}
+ {/* Include subfolders toggle */}
@@ -372,17 +378,15 @@ export function ComposioDriveFolderTree({ {!isLoadingRoot && rootError && (
- {(rootError instanceof Error ? rootError.message : String(rootError)).includes( - "authentication expired" - ) - ? "Google Drive authentication has expired. Please re-authenticate above." - : "Failed to load Google Drive contents."} + {rootError.message.includes("authentication expired") + ? `${providerName} authentication has expired. Please re-authenticate above.` + : `Failed to load ${providerName} contents.`}
)} {!isLoadingRoot && !rootError && rootItems.length === 0 && (
- No files or folders found in your Google Drive + No files or folders found in your {providerName}
)}
diff --git a/surfsense_web/contracts/types/connector.types.ts b/surfsense_web/contracts/types/connector.types.ts index 82d509a4b..ef089f1f5 100644 --- a/surfsense_web/contracts/types/connector.types.ts +++ b/surfsense_web/contracts/types/connector.types.ts @@ -54,7 +54,7 @@ export const searchSourceConnector = z.object({ export const googleDriveItem = z.object({ id: z.string(), name: z.string(), - mimeType: z.string(), + mimeType: z.string().optional().default("application/octet-stream"), isFolder: z.boolean(), parents: z.array(z.string()).optional(), size: z.coerce.number().optional(), diff --git a/surfsense_web/hooks/use-composio-drive-folders.ts b/surfsense_web/hooks/use-composio-drive-folders.ts deleted file mode 100644 index 31e516286..000000000 --- a/surfsense_web/hooks/use-composio-drive-folders.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { connectorsApiService } from "@/lib/apis/connectors-api.service"; -import { cacheKeys } from "@/lib/query-client/cache-keys"; - -interface UseComposioDriveFoldersOptions { - connectorId: number; - parentId?: string; - enabled?: boolean; -} - -export function useComposioDriveFolders({ - connectorId, - parentId, - enabled = true, -}: UseComposioDriveFoldersOptions) { - return useQuery({ - queryKey: cacheKeys.connectors.composioDrive.folders(connectorId, parentId), - queryFn: async () => { - return connectorsApiService.listComposioDriveFolders({ - connector_id: connectorId, - parent_id: parentId, - }); - }, - enabled: enabled && !!connectorId, - staleTime: 5 * 60 * 1000, // 5 minutes - retry: 2, - }); -} diff --git a/surfsense_web/hooks/use-onedrive-picker.ts b/surfsense_web/hooks/use-onedrive-picker.ts deleted file mode 100644 index d94d7da50..000000000 --- a/surfsense_web/hooks/use-onedrive-picker.ts +++ /dev/null @@ -1,254 +0,0 @@ -"use client"; - -import { useCallback, useEffect, useRef, useState } from "react"; -import { toast } from "sonner"; -import { authenticatedFetch } from "@/lib/auth-utils"; - -export interface OneDrivePickerItem { - id: string; - name: string; - isFolder: boolean; - driveId?: string; -} - -export interface OneDrivePickerResult { - folders: OneDrivePickerItem[]; - files: OneDrivePickerItem[]; -} - -interface UseOneDrivePickerOptions { - connectorId: number; - onPicked: (result: OneDrivePickerResult) => void; -} - -export const ONEDRIVE_PICKER_OPEN_EVENT = "onedrive-picker-open"; -export const ONEDRIVE_PICKER_CLOSE_EVENT = "onedrive-picker-close"; - -async function fetchPickerToken( - connectorId: number, - resource?: string, -): Promise<{ access_token: string; base_url: string }> { - const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000"; - const params = new URLSearchParams(); - if (resource) params.set("resource", resource); - const qs = params.toString(); - const url = `${backendUrl}/api/v1/connectors/${connectorId}/onedrive/picker-token${qs ? `?${qs}` : ""}`; - const response = await authenticatedFetch(url); - if (!response.ok) { - const data = await response.json().catch(() => ({})); - throw new Error(data.detail || `Failed to get picker token (${response.status})`); - } - return response.json(); -} - -export function useOneDrivePicker({ connectorId, onPicked }: UseOneDrivePickerOptions) { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const onPickedRef = useRef(onPicked); - onPickedRef.current = onPicked; - const openingRef = useRef(false); - const winRef = useRef(null); - const portRef = useRef(null); - const messageHandlerRef = useRef<((e: MessageEvent) => void) | null>(null); - const pollRef = useRef | null>(null); - - const closePicker = useCallback(() => { - window.dispatchEvent(new Event(ONEDRIVE_PICKER_CLOSE_EVENT)); - if (pollRef.current) { - clearInterval(pollRef.current); - pollRef.current = null; - } - if (messageHandlerRef.current) { - window.removeEventListener("message", messageHandlerRef.current); - messageHandlerRef.current = null; - } - if (winRef.current && !winRef.current.closed) { - winRef.current.close(); - } - winRef.current = null; - portRef.current = null; - openingRef.current = false; - }, []); - - useEffect(() => { - const onEscape = (e: KeyboardEvent) => { - if (e.key === "Escape" && winRef.current) { - closePicker(); - } - }; - window.addEventListener("keydown", onEscape); - return () => { - window.removeEventListener("keydown", onEscape); - closePicker(); - }; - }, [closePicker]); - - const openPicker = useCallback(async () => { - if (openingRef.current) return; - openingRef.current = true; - setLoading(true); - setError(null); - - try { - const { access_token, base_url } = await fetchPickerToken(connectorId); - - const win = window.open("", "OneDrivePicker", "width=1080,height=680"); - if (!win) { - throw new Error("Popup blocked. Please allow popups for this site."); - } - winRef.current = win; - - const channelId = crypto.randomUUID(); - - const pickerConfig = { - sdk: "8.0", - entry: { oneDrive: { files: {} } }, - authentication: {}, - messaging: { - origin: window.location.origin, - channelId, - }, - selection: { mode: "multiple" }, - typesAndSources: { - mode: "all" as const, - pivots: { oneDrive: true, recent: true }, - }, - }; - - const qs = new URLSearchParams({ - filePicker: JSON.stringify(pickerConfig), - locale: navigator.language || "en-us", - }); - const pickerUrl = `${base_url}/_layouts/15/FilePicker.aspx?${qs}`; - - const form = win.document.createElement("form"); - form.setAttribute("action", pickerUrl); - form.setAttribute("method", "POST"); - const input = win.document.createElement("input"); - input.setAttribute("type", "hidden"); - input.setAttribute("name", "access_token"); - input.setAttribute("value", access_token); - form.appendChild(input); - win.document.body.append(form); - form.submit(); - - const handleMessage = (event: MessageEvent) => { - if (event.source !== win) return; - const msg = event.data; - if (msg?.type !== "initialize" || msg.channelId !== channelId) return; - - const port = event.ports[0]; - portRef.current = port; - - port.addEventListener("message", async (portEvent: MessageEvent) => { - const payload = portEvent.data; - if (payload.type !== "command") return; - - port.postMessage({ type: "acknowledge", id: payload.id }); - - const cmd = payload.data; - switch (cmd.command) { - case "authenticate": { - try { - const result = await fetchPickerToken(connectorId, cmd.resource); - port.postMessage({ - type: "result", - id: payload.id, - data: { result: "token", token: result.access_token }, - }); - } catch (err) { - port.postMessage({ - type: "result", - id: payload.id, - data: { - result: "error", - error: { - code: "unableToObtainToken", - message: err instanceof Error ? err.message : "Token error", - }, - }, - }); - } - break; - } - case "pick": { - const items: Record[] = cmd.items || []; - const folders: OneDrivePickerItem[] = []; - const files: OneDrivePickerItem[] = []; - - for (const item of items) { - const isFolder = - item.folder != null || - (typeof item["@odata.type"] === "string" && - (item["@odata.type"] as string).includes("folder")); - const parentRef = item.parentReference as - | { driveId?: string } - | undefined; - const pickerItem: OneDrivePickerItem = { - id: item.id as string, - name: (item.name as string) || "Untitled", - isFolder, - driveId: parentRef?.driveId, - }; - if (isFolder) { - folders.push(pickerItem); - } else { - files.push(pickerItem); - } - } - - onPickedRef.current({ folders, files }); - port.postMessage({ - type: "result", - id: payload.id, - data: { result: "success" }, - }); - closePicker(); - break; - } - case "close": { - closePicker(); - break; - } - default: { - port.postMessage({ - type: "result", - id: payload.id, - data: { - result: "error", - error: { code: "unsupportedCommand", message: cmd.command }, - }, - }); - break; - } - } - }); - - port.start(); - port.postMessage({ type: "activate" }); - }; - - messageHandlerRef.current = handleMessage; - window.addEventListener("message", handleMessage); - - pollRef.current = setInterval(() => { - if (win.closed) { - closePicker(); - } - }, 500); - - window.dispatchEvent(new Event(ONEDRIVE_PICKER_OPEN_EVENT)); - } catch (err) { - openingRef.current = false; - const msg = err instanceof Error ? err.message : "Failed to open OneDrive Picker"; - setError(msg); - toast.error("OneDrive Picker failed", { description: msg }); - console.error("OneDrive Picker error:", err); - window.dispatchEvent(new Event(ONEDRIVE_PICKER_CLOSE_EVENT)); - } finally { - setLoading(false); - } - }, [connectorId, closePicker]); - - return { openPicker, closePicker, loading, error }; -} diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 883c40a77..17f0e5d1a 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -79,10 +79,6 @@ export const cacheKeys = { folders: (connectorId: number, parentId?: string) => ["connectors", "google-drive", connectorId, "folders", parentId] as const, }, - composioDrive: { - folders: (connectorId: number, parentId?: string) => - ["connectors", "composio-drive", connectorId, "folders", parentId] as const, - }, }, comments: { byMessage: (messageId: number) => ["comments", "message", messageId] as const,