import { PublicClientApplication } from '@azure/msal-browser'; import type { PopupRequest } from '@azure/msal-browser'; import { v4 as uuidv4 } from 'uuid'; class OneDriveConfig { private static instance: OneDriveConfig; private clientId: string = ''; private sharepointUrl: string = ''; private msalInstance: PublicClientApplication | null = null; private currentAuthorityType: 'personal' | 'organizations' = 'personal'; private constructor() {} public static getInstance(): OneDriveConfig { if (!OneDriveConfig.instance) { OneDriveConfig.instance = new OneDriveConfig(); } return OneDriveConfig.instance; } public async initialize(authorityType?: 'personal' | 'organizations'): Promise { if (authorityType && this.currentAuthorityType !== authorityType) { this.currentAuthorityType = authorityType; this.msalInstance = null; } await this.getCredentials(); } public async ensureInitialized(authorityType?: 'personal' | 'organizations'): Promise { await this.initialize(authorityType); } private async getCredentials(): Promise { const headers: HeadersInit = { 'Content-Type': 'application/json' }; const response = await fetch('/api/config', { headers, credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch OneDrive credentials'); } const config = await response.json(); const newClientId = config.onedrive?.client_id; const newSharepointUrl = config.onedrive?.sharepoint_url; if (!newClientId) { throw new Error('OneDrive configuration is incomplete'); } this.clientId = newClientId; this.sharepointUrl = newSharepointUrl; } public async getMsalInstance( authorityType?: 'personal' | 'organizations' ): Promise { await this.ensureInitialized(authorityType); if (!this.msalInstance) { const authorityEndpoint = this.currentAuthorityType === 'organizations' ? 'common' : 'consumers'; const msalParams = { auth: { authority: `https://login.microsoftonline.com/${authorityEndpoint}`, clientId: this.clientId } }; this.msalInstance = new PublicClientApplication(msalParams); if (this.msalInstance.initialize) { await this.msalInstance.initialize(); } } return this.msalInstance; } public getAuthorityType(): 'personal' | 'organizations' { return this.currentAuthorityType; } public getSharepointUrl(): string { return this.sharepointUrl; } public getBaseUrl(): string { if (this.currentAuthorityType === 'organizations') { if (!this.sharepointUrl || this.sharepointUrl === '') { throw new Error('Sharepoint URL not configured'); } let sharePointBaseUrl = this.sharepointUrl.replace(/^https?:\/\//, ''); sharePointBaseUrl = sharePointBaseUrl.replace(/\/$/, ''); return `https://${sharePointBaseUrl}`; } else { return 'https://onedrive.live.com/picker'; } } } // Retrieve OneDrive access token async function getToken( resource?: string, authorityType?: 'personal' | 'organizations' ): Promise { const config = OneDriveConfig.getInstance(); await config.ensureInitialized(authorityType); const currentAuthorityType = config.getAuthorityType(); const scopes = currentAuthorityType === 'organizations' ? [`${resource || config.getBaseUrl()}/.default`] : ['OneDrive.ReadWrite']; const authParams: PopupRequest = { scopes }; let accessToken = ''; try { const msalInstance = await config.getMsalInstance(authorityType); const resp = await msalInstance.acquireTokenSilent(authParams); accessToken = resp.accessToken; } catch (err) { const msalInstance = await config.getMsalInstance(authorityType); try { const resp = await msalInstance.loginPopup(authParams); msalInstance.setActiveAccount(resp.account); if (resp.idToken) { const resp2 = await msalInstance.acquireTokenSilent(authParams); accessToken = resp2.accessToken; } } catch (popupError) { throw new Error( 'Failed to login: ' + (popupError instanceof Error ? popupError.message : String(popupError)) ); } } if (!accessToken) { throw new Error('Failed to acquire access token'); } return accessToken; } interface PickerParams { sdk: string; entry: { oneDrive: Record; }; authentication: Record; messaging: { origin: string; channelId: string; }; typesAndSources: { mode: string; pivots: Record; }; } interface PickerResult { command?: string; items?: OneDriveFileInfo[]; [key: string]: any; } // Get picker parameters based on account type function getPickerParams(): PickerParams { const channelId = uuidv4(); const config = OneDriveConfig.getInstance(); const params: PickerParams = { sdk: '8.0', entry: { oneDrive: {} }, authentication: {}, messaging: { origin: window?.location?.origin || '', channelId }, typesAndSources: { mode: 'files', pivots: { oneDrive: true, recent: true } } }; // For personal accounts, set files object in oneDrive if (config.getAuthorityType() !== 'organizations') { params.entry.oneDrive = { files: {} }; } return params; } interface OneDriveFileInfo { id: string; name: string; parentReference: { driveId: string; }; '@sharePoint.endpoint': string; [key: string]: any; } // Download file from OneDrive async function downloadOneDriveFile( fileInfo: OneDriveFileInfo, authorityType?: 'personal' | 'organizations' ): Promise { const accessToken = await getToken(undefined, authorityType); if (!accessToken) { throw new Error('Unable to retrieve OneDrive access token.'); } // The endpoint URL is provided in the file info const fileInfoUrl = `${fileInfo['@sharePoint.endpoint']}/drives/${fileInfo.parentReference.driveId}/items/${fileInfo.id}`; const response = await fetch(fileInfoUrl, { headers: { Authorization: `Bearer ${accessToken}` } }); if (!response.ok) { throw new Error(`Failed to fetch file information: ${response.status} ${response.statusText}`); } const fileData = await response.json(); const downloadUrl = fileData['@content.downloadUrl']; if (!downloadUrl) { throw new Error('Download URL not found in file data'); } const downloadResponse = await fetch(downloadUrl); if (!downloadResponse.ok) { throw new Error( `Failed to download file: ${downloadResponse.status} ${downloadResponse.statusText}` ); } return await downloadResponse.blob(); } // Open OneDrive file picker and return selected file metadata export async function openOneDrivePicker( authorityType?: 'personal' | 'organizations' ): Promise { if (typeof window === 'undefined') { throw new Error('Not in browser environment'); } // Initialize OneDrive config with the specified authority type const config = OneDriveConfig.getInstance(); await config.initialize(authorityType); return new Promise((resolve, reject) => { let pickerWindow: Window | null = null; let channelPort: MessagePort | null = null; const params = getPickerParams(); const baseUrl = config.getBaseUrl(); const handleWindowMessage = (event: MessageEvent) => { if (event.source !== pickerWindow) return; const message = event.data; if (message?.type === 'initialize' && message?.channelId === params.messaging.channelId) { channelPort = event.ports?.[0]; if (!channelPort) return; channelPort.addEventListener('message', handlePortMessage); channelPort.start(); channelPort.postMessage({ type: 'activate' }); } }; const handlePortMessage = async (portEvent: MessageEvent) => { const portData = portEvent.data; switch (portData.type) { case 'notification': break; case 'command': { channelPort?.postMessage({ type: 'acknowledge', id: portData.id }); const command = portData.data; switch (command.command) { case 'authenticate': { try { // Pass the resource from the command for org accounts const resource = config.getAuthorityType() === 'organizations' ? command.resource : undefined; const newToken = await getToken(resource, authorityType); if (newToken) { channelPort?.postMessage({ type: 'result', id: portData.id, data: { result: 'token', token: newToken } }); } else { throw new Error('Could not retrieve auth token'); } } catch (err) { channelPort?.postMessage({ type: 'result', id: portData.id, data: { result: 'error', error: { code: 'tokenError', message: 'Failed to get token' } } }); } break; } case 'close': { cleanup(); resolve(null); break; } case 'pick': { channelPort?.postMessage({ type: 'result', id: portData.id, data: { result: 'success' } }); cleanup(); resolve(command); break; } default: { channelPort?.postMessage({ result: 'error', error: { code: 'unsupportedCommand', message: command.command }, isExpected: true }); break; } } break; } } }; function cleanup() { window.removeEventListener('message', handleWindowMessage); if (channelPort) { channelPort.removeEventListener('message', handlePortMessage); } if (pickerWindow) { pickerWindow.close(); pickerWindow = null; } } const initializePicker = async () => { try { const authToken = await getToken(undefined, authorityType); if (!authToken) { return reject(new Error('Failed to acquire access token')); } pickerWindow = window.open('', 'OneDrivePicker', 'width=800,height=600'); if (!pickerWindow) { return reject(new Error('Failed to open OneDrive picker window')); } const queryString = new URLSearchParams({ filePicker: JSON.stringify(params) }); let url = ''; if (config.getAuthorityType() === 'organizations') { url = baseUrl + `/_layouts/15/FilePicker.aspx?${queryString}`; } else { url = baseUrl + `?${queryString}`; } const form = pickerWindow.document.createElement('form'); form.setAttribute('action', url); form.setAttribute('method', 'POST'); const input = pickerWindow.document.createElement('input'); input.setAttribute('type', 'hidden'); input.setAttribute('name', 'access_token'); input.setAttribute('value', authToken); form.appendChild(input); pickerWindow.document.body.appendChild(form); form.submit(); window.addEventListener('message', handleWindowMessage); } catch (err) { if (pickerWindow) { pickerWindow.close(); } reject(err); } }; initializePicker(); }); } // Pick and download file from OneDrive export async function pickAndDownloadFile( authorityType?: 'personal' | 'organizations' ): Promise<{ blob: Blob; name: string } | null> { const pickerResult = await openOneDrivePicker(authorityType); if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) { return null; } const selectedFile = pickerResult.items[0]; const blob = await downloadOneDriveFile(selectedFile, authorityType); return { blob, name: selectedFile.name }; } export { downloadOneDriveFile };