import { useRef, useCallback } from 'react'; import { useRecoilState } from 'recoil'; import { useToastContext } from '@librechat/client'; import type { SPPickerConfig } from '~/components/SidePanel/Agents/config'; import { useLocalize, useAuthContext } from '~/hooks'; import { useGetStartupConfig } from '~/data-provider'; import useSharePointToken from './useSharePointToken'; import store from '~/store'; interface UseSharePointPickerProps { containerNode: HTMLDivElement | null; onFilesSelected?: (files: any[]) => void; onClose?: () => void; disabled?: boolean; maxSelectionCount?: number; } interface UseSharePointPickerReturn { openSharePointPicker: () => void; closeSharePointPicker: () => void; error: string | null; cleanup: () => void; isTokenLoading: boolean; } export default function useSharePointPicker({ containerNode, onFilesSelected, onClose, disabled = false, maxSelectionCount = 10, }: UseSharePointPickerProps): UseSharePointPickerReturn { const [langcode] = useRecoilState(store.lang); const { user } = useAuthContext(); const { showToast } = useToastContext(); const localize = useLocalize(); const iframeRef = useRef(null); const portRef = useRef(null); const channelIdRef = useRef(''); const { data: startupConfig } = useGetStartupConfig(); const sharePointBaseUrl = startupConfig?.sharePointBaseUrl; const isEntraIdUser = user?.provider === 'openid'; const { token, isLoading: isTokenLoading, error: tokenError, } = useSharePointToken({ enabled: isEntraIdUser && !disabled && !!sharePointBaseUrl, purpose: 'Pick', }); const generateChannelId = useCallback(() => { return `sharepoint-picker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; }, []); const portMessageHandler = useCallback( async (message: MessageEvent) => { const port = portRef.current; if (!port) { console.error('No port available for communication'); return; } try { switch (message.data.type) { case 'notification': console.log('SharePoint picker notification:', message.data); break; case 'command': { // Always acknowledge the command first port.postMessage({ type: 'acknowledge', id: message.data.id, }); const command = message.data.data; console.log('SharePoint picker command:', command); switch (command.command) { case 'authenticate': console.log('Authentication requested, providing token'); console.log('Command details:', command); // Add this line console.log('Token available:', !!token?.access_token); // Add this line if (token?.access_token) { port.postMessage({ type: 'result', id: message.data.id, data: { result: 'token', token: token.access_token, }, }); } else { console.error('No token available for authentication'); port.postMessage({ type: 'result', id: message.data.id, data: { result: 'error', error: { code: 'noToken', message: 'No authentication token available', }, }, }); } break; case 'close': console.log('Close command received'); port.postMessage({ type: 'result', id: message.data.id, data: { result: 'success', }, }); onClose?.(); break; case 'pick': { console.log('Files picked from SharePoint:', command); const items = command.items || command.files || []; console.log('Extracted items:', items); if (items && items.length > 0) { const selectedFiles = items.map((item: any) => ({ id: item.id || item.shareId || item.driveItem?.id, name: item.name || item.driveItem?.name, size: item.size || item.driveItem?.size, webUrl: item.webUrl || item.driveItem?.webUrl, downloadUrl: item.downloadUrl || item.driveItem?.['@microsoft.graph.downloadUrl'], driveId: item.driveId || item.parentReference?.driveId || item.driveItem?.parentReference?.driveId, itemId: item.id || item.driveItem?.id, sharePointItem: item, })); console.log('Processed SharePoint files:', selectedFiles); if (onFilesSelected) { onFilesSelected(selectedFiles); } showToast({ message: `Selected ${selectedFiles.length} file(s) from SharePoint`, status: 'success', }); } port.postMessage({ type: 'result', id: message.data.id, data: { result: 'success', }, }); break; } default: console.warn(`Unsupported command: ${command.command}`); port.postMessage({ type: 'result', id: message.data.id, data: { result: 'error', error: { code: 'unsupportedCommand', message: command.command, }, }, }); break; } break; } default: console.log('Unknown message type:', message.data.type); break; } } catch (error) { console.error('Error processing port message:', error); } }, [token, onFilesSelected, showToast, onClose], ); // Initialization message handler - establishes MessagePort communication const initMessageHandler = useCallback( (event: MessageEvent) => { console.log('=== SharePoint picker init message received ==='); console.log('Event source:', event.source); console.log('Event data:', event.data); console.log('Expected channelId:', channelIdRef.current); // Check if this message is from our iframe if (event.source && event.source === iframeRef.current?.contentWindow) { const message = event.data; if (message.type === 'initialize' && message.channelId === channelIdRef.current) { console.log('Establishing MessagePort communication'); // Get the MessagePort from the event portRef.current = event.ports[0]; if (portRef.current) { // Set up the port message listener portRef.current.addEventListener('message', portMessageHandler); portRef.current.start(); // Send activate message to start the picker portRef.current.postMessage({ type: 'activate', }); console.log('MessagePort established and activated'); } else { console.error('No MessagePort found in initialize event'); } } } }, [portMessageHandler], ); const openSharePointPicker = async () => { if (!token) { showToast({ message: 'Unable to access SharePoint. Please ensure you are logged in with Microsoft.', status: 'error', }); return; } if (!containerNode) { console.error('No container ref provided for SharePoint picker'); return; } try { const channelId = generateChannelId(); channelIdRef.current = channelId; console.log('=== SharePoint File Picker v8 (MessagePort) ==='); console.log('Token available:', { hasToken: !!token.access_token, tokenType: token.token_type, expiresIn: token.expires_in, scopes: token.scope, }); console.log('Channel ID:', channelId); const pickerOptions: SPPickerConfig = { sdk: '8.0', entry: { sharePoint: {}, }, messaging: { origin: window.location.origin, channelId: channelId, }, authentication: { enabled: false, // Host app handles authentication }, typesAndSources: { mode: 'files', pivots: { oneDrive: true, recent: true, shared: true, sharedLibraries: true, myOrganization: true, site: true, }, }, selection: { mode: 'multiple', maximumCount: maxSelectionCount, }, title: localize('com_files_sharepoint_picker_title'), commands: { upload: { enabled: false, }, createFolder: { enabled: false, }, }, search: { enabled: true }, }; const iframe = document.createElement('iframe'); iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.background = '#F5F5F5'; iframe.style.border = 'none'; iframe.title = 'SharePoint File Picker'; iframe.setAttribute( 'sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox', ); iframeRef.current = iframe; containerNode.innerHTML = ''; containerNode.appendChild(iframe); activeEventListenerRef.current = initMessageHandler; window.addEventListener('message', initMessageHandler); iframe.src = 'about:blank'; iframe.onload = () => { const win = iframe.contentWindow; if (!win) return; const queryString = new URLSearchParams({ filePicker: JSON.stringify(pickerOptions), locale: langcode || 'en-US', }); const url = sharePointBaseUrl + `/_layouts/15/FilePicker.aspx?${queryString}`; const form = win.document.createElement('form'); form.setAttribute('action', url); form.setAttribute('method', 'POST'); const tokenInput = win.document.createElement('input'); tokenInput.setAttribute('type', 'hidden'); tokenInput.setAttribute('name', 'access_token'); tokenInput.setAttribute('value', token.access_token); form.appendChild(tokenInput); win.document.body.appendChild(form); form.submit(); }; } catch (error) { console.error('SharePoint file picker error:', error); showToast({ message: 'Failed to open SharePoint file picker.', status: 'error', }); } }; const activeEventListenerRef = useRef<((event: MessageEvent) => void) | null>(null); const cleanup = useCallback(() => { if (activeEventListenerRef.current) { window.removeEventListener('message', activeEventListenerRef.current); activeEventListenerRef.current = null; } if (portRef.current) { portRef.current.close(); portRef.current = null; } if (containerNode) { containerNode.innerHTML = ''; } channelIdRef.current = ''; }, [containerNode]); const handleDialogClose = useCallback(() => { cleanup(); }, [cleanup]); const isAvailable = startupConfig?.sharePointFilePickerEnabled && isEntraIdUser && !tokenError; return { openSharePointPicker: isAvailable ? openSharePointPicker : () => {}, closeSharePointPicker: handleDialogClose, error: tokenError ? 'Failed to authenticate with SharePoint' : null, cleanup, isTokenLoading, }; }