diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index b2f8dccca..91cc2e992 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -1570,6 +1570,18 @@ GOOGLE_DRIVE_API_KEY = PersistentConfig(
os.environ.get("GOOGLE_DRIVE_API_KEY", ""),
)
+ENABLE_ONEDRIVE_INTEGRATION = PersistentConfig(
+ "ENABLE_ONEDRIVE_INTEGRATION",
+ "onedrive.enable",
+ os.getenv("ENABLE_ONEDRIVE_INTEGRATION", "False").lower() == "true",
+)
+
+ONEDRIVE_CLIENT_ID = PersistentConfig(
+ "ONEDRIVE_CLIENT_ID",
+ "onedrive.client_id",
+ os.environ.get("ONEDRIVE_CLIENT_ID", ""),
+)
+
# RAG Content Extraction
CONTENT_EXTRACTION_ENGINE = PersistentConfig(
"CONTENT_EXTRACTION_ENGINE",
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index 1371f7d15..62e53e34c 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -95,6 +95,7 @@ from open_webui.config import (
OLLAMA_API_CONFIGS,
# OpenAI
ENABLE_OPENAI_API,
+ ONEDRIVE_CLIENT_ID,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
OPENAI_API_CONFIGS,
@@ -217,11 +218,13 @@ from open_webui.config import (
GOOGLE_PSE_ENGINE_ID,
GOOGLE_DRIVE_CLIENT_ID,
GOOGLE_DRIVE_API_KEY,
+ ONEDRIVE_CLIENT_ID,
ENABLE_RAG_HYBRID_SEARCH,
ENABLE_RAG_LOCAL_WEB_FETCH,
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
ENABLE_RAG_WEB_SEARCH,
ENABLE_GOOGLE_DRIVE_INTEGRATION,
+ ENABLE_ONEDRIVE_INTEGRATION,
UPLOAD_DIR,
# WebUI
WEBUI_AUTH,
@@ -568,6 +571,7 @@ app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT = RAG_WEB_SEARCH_FULL_CONTEXT
app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = RAG_WEB_SEARCH_DOMAIN_FILTER_LIST
app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = ENABLE_GOOGLE_DRIVE_INTEGRATION
+app.state.config.ENABLE_ONEDRIVE_INTEGRATION = ENABLE_ONEDRIVE_INTEGRATION
app.state.config.SEARXNG_QUERY_URL = SEARXNG_QUERY_URL
app.state.config.GOOGLE_PSE_API_KEY = GOOGLE_PSE_API_KEY
app.state.config.GOOGLE_PSE_ENGINE_ID = GOOGLE_PSE_ENGINE_ID
@@ -1150,6 +1154,7 @@ async def get_app_config(request: Request):
"enable_admin_export": ENABLE_ADMIN_EXPORT,
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
+ "enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
}
if user is not None
else {}
@@ -1181,6 +1186,9 @@ async def get_app_config(request: Request):
"client_id": GOOGLE_DRIVE_CLIENT_ID.value,
"api_key": GOOGLE_DRIVE_API_KEY.value,
},
+ "onedrive": {
+ "client_id": ONEDRIVE_CLIENT_ID.value
+ }
}
if user is not None
else {}
diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py
index c2cb68c5d..51f77d6b1 100644
--- a/backend/open_webui/routers/retrieval.py
+++ b/backend/open_webui/routers/retrieval.py
@@ -353,6 +353,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES,
"RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT,
"enable_google_drive_integration": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
+ "enable_onedrive_integration": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
"content_extraction": {
"engine": request.app.state.config.CONTENT_EXTRACTION_ENGINE,
"tika_server_url": request.app.state.config.TIKA_SERVER_URL,
@@ -381,6 +382,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
"search": {
"enabled": request.app.state.config.ENABLE_RAG_WEB_SEARCH,
"drive": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
+ "onedrive": request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
"engine": request.app.state.config.RAG_WEB_SEARCH_ENGINE,
"searxng_query_url": request.app.state.config.SEARXNG_QUERY_URL,
"google_pse_api_key": request.app.state.config.GOOGLE_PSE_API_KEY,
@@ -478,6 +480,7 @@ class ConfigUpdateForm(BaseModel):
RAG_FULL_CONTEXT: Optional[bool] = None
pdf_extract_images: Optional[bool] = None
enable_google_drive_integration: Optional[bool] = None
+ enable_onedrive_integration: Optional[bool] = None
file: Optional[FileConfig] = None
content_extraction: Optional[ContentExtractionConfig] = None
chunk: Optional[ChunkParamUpdateForm] = None
@@ -507,6 +510,12 @@ async def update_rag_config(
else request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION
)
+ request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION = (
+ form_data.enable_onedrive_integration
+ if form_data.enable_onedrive_integration is not None
+ else request.app.state.config.ENABLE_ONEDRIVE_INTEGRATION
+ )
+
if form_data.file is not None:
request.app.state.config.FILE_MAX_SIZE = form_data.file.max_size
request.app.state.config.FILE_MAX_COUNT = form_data.file.max_count
diff --git a/package-lock.json b/package-lock.json
index 066cf2be5..c65870772 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,6 @@
"name": "open-webui",
"version": "0.5.16",
"dependencies": {
- "@azure/msal-browser": "^4.4.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/language-data": "^6.5.1",
@@ -135,27 +134,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@azure/msal-browser": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.4.0.tgz",
- "integrity": "sha512-rU6juYXk67CKQmpgi6fDgZoPQ9InZ1760z1BSAH7RbeIc4lHZM/Tu+H0CyRk7cnrfvTkexyYE4pjYhMghpzheA==",
- "license": "MIT",
- "dependencies": {
- "@azure/msal-common": "15.2.0"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/@azure/msal-common": {
- "version": "15.2.0",
- "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.2.0.tgz",
- "integrity": "sha512-HiYfGAKthisUYqHG1nImCf/uzcyS31wng3o+CycWLIM9chnYJ9Lk6jZ30Y6YiYYpTQ9+z/FGUpiKKekd3Arc0A==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/@babel/runtime": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
diff --git a/package.json b/package.json
index 0e8fe2bc6..86568869f 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,6 @@
},
"type": "module",
"dependencies": {
- "@azure/msal-browser": "^4.4.0",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
"@codemirror/language-data": "^6.5.1",
diff --git a/src/lib/apis/retrieval/index.ts b/src/lib/apis/retrieval/index.ts
index ed07ab5d0..31317fe0b 100644
--- a/src/lib/apis/retrieval/index.ts
+++ b/src/lib/apis/retrieval/index.ts
@@ -52,6 +52,7 @@ type YoutubeConfigForm = {
type RAGConfigForm = {
pdf_extract_images?: boolean;
enable_google_drive_integration?: boolean;
+ enable_onedrive_integration?: boolean;
chunk?: ChunkConfigForm;
content_extraction?: ContentExtractConfigForm;
web_loader_ssl_verification?: boolean;
diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte
index b79086309..248f6e9f5 100644
--- a/src/lib/components/admin/Settings/Documents.svelte
+++ b/src/lib/components/admin/Settings/Documents.svelte
@@ -61,6 +61,7 @@
let RAG_FULL_CONTEXT = false;
let enableGoogleDriveIntegration = false;
+ let enableOneDriveIntegration = false;
let OpenAIUrl = '';
let OpenAIKey = '';
@@ -189,6 +190,7 @@
const res = await updateRAGConfig(localStorage.token, {
pdf_extract_images: pdfExtractImages,
enable_google_drive_integration: enableGoogleDriveIntegration,
+ enable_onedrive_integration: enableOneDriveIntegration,
file: {
max_size: fileMaxSize === '' ? null : fileMaxSize,
max_count: fileMaxCount === '' ? null : fileMaxCount
@@ -271,6 +273,7 @@
fileMaxCount = res?.file.max_count ?? '';
enableGoogleDriveIntegration = res.enable_google_drive_integration;
+ enableOneDriveIntegration = res.enable_onedrive_integration;
}
});
@@ -653,6 +656,18 @@
+
{$i18n.t('OneDrive')}
+
+
+
+
{$i18n.t('Enable OneDrive')}
+
+
+
+
+
+
+
diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte
index a81139d2f..bf2f5cddb 100644
--- a/src/lib/components/chat/MessageInput.svelte
+++ b/src/lib/components/chat/MessageInput.svelte
@@ -2,7 +2,7 @@
import { toast } from 'svelte-sonner';
import { v4 as uuidv4 } from 'uuid';
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
- import { openOneDrivePicker } from '$lib/utils/onedrive-file-picker';
+ import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
const dispatch = createEventDispatcher();
@@ -1111,10 +1111,10 @@
}}
uploadOneDriveHandler={async () => {
try {
- const fileData = await openOneDrivePicker();
+ const fileData = await pickAndDownloadFile();
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
- type: fileData.blob.type
+ type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
} else {
@@ -1122,11 +1122,6 @@
}
} catch (error) {
console.error('OneDrive Error:', error);
- toast.error(
- $i18n.t('Error accessing OneDrive: {{error}}', {
- error: error.message
- })
- );
}
}}
onClose={async () => {
diff --git a/src/lib/components/chat/MessageInput/InputMenu.svelte b/src/lib/components/chat/MessageInput/InputMenu.svelte
index 91f9cf81b..7f7660f19 100644
--- a/src/lib/components/chat/MessageInput/InputMenu.svelte
+++ b/src/lib/components/chat/MessageInput/InputMenu.svelte
@@ -228,30 +228,41 @@
{/if}
- {#if $config?.features?.enable_onedrive_integration || true}
+ {#if $config?.features?.enable_onedrive_integration}
{
uploadOneDriveHandler();
}}
>
-
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts
index f96670cb6..1f6b400e0 100644
--- a/src/lib/stores/index.ts
+++ b/src/lib/stores/index.ts
@@ -204,6 +204,7 @@ type Config = {
enable_login_form: boolean;
enable_web_search?: boolean;
enable_google_drive_integration: boolean;
+ enable_onedrive_integration: boolean;
enable_image_generation: boolean;
enable_admin_export: boolean;
enable_admin_chat_access: boolean;
diff --git a/src/lib/utils/onedrive-auth.ts b/src/lib/utils/onedrive-auth.ts
deleted file mode 100644
index be2de44a0..000000000
--- a/src/lib/utils/onedrive-auth.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { PublicClientApplication } from '@azure/msal-browser';
-
-const msalParams = {
- auth: {
- authority: 'https://login.microsoftonline.com/consumers',
- clientId: '2ab80a1e-7300-4cb1-beac-c38c730e8b7f'
- }
-};
-
-// MSAL 초기화
-const app = new PublicClientApplication(msalParams);
-
-export async function initializeMsal() {
- try {
- await app.initialize();
- console.log('MSAL initialized successfully');
- } catch (error) {
- console.error('MSAL initialization error:', error);
- }
- }
-
- export async function getToken(): Promise
{
- const authParams = { scopes: ['OneDrive.ReadWrite'] };
- let accessToken = '';
-
- try {
- // Ensure initialization happens early
- await initializeMsal();
- const resp = await app.acquireTokenSilent(authParams);
- accessToken = resp.accessToken;
- } catch (err) {
- const resp = await app.loginPopup(authParams);
- app.setActiveAccount(resp.account);
-
- if (resp.idToken) {
- const resp2 = await app.acquireTokenSilent(authParams);
- accessToken = resp2.accessToken;
- }
- }
-
- return accessToken;
- }
diff --git a/src/lib/utils/onedrive-file-picker.ts b/src/lib/utils/onedrive-file-picker.ts
index d003e38ec..e3a80c912 100644
--- a/src/lib/utils/onedrive-file-picker.ts
+++ b/src/lib/utils/onedrive-file-picker.ts
@@ -1,211 +1,266 @@
-// src/lib/utils/onedrive-file-picker.ts
-import { getToken } from './onedrive-auth';
+let CLIENT_ID = '';
+async function getCredentials() {
+ if (CLIENT_ID) return;
+ const response = await fetch('/api/config');
+ if (!response.ok) {
+ throw new Error('Failed to fetch OneDrive credentials');
+ }
+ const config = await response.json();
+ CLIENT_ID = config.onedrive?.client_id;
+ if (!CLIENT_ID) {
+ throw new Error('OneDrive client ID not configured');
+ }
+}
+
+function loadMsalScript(): Promise {
+ return new Promise((resolve, reject) => {
+ const win = window;
+ if (win.msal) {
+ resolve();
+ return;
+ }
+ const script = document.createElement('script');
+ script.src = 'https://alcdn.msauth.net/browser/2.19.0/js/msal-browser.min.js';
+ script.async = true;
+ script.onload = () => resolve();
+ script.onerror = () => reject(new Error('Failed to load MSAL script'));
+ document.head.appendChild(script);
+ });
+}
+
+let msalInstance: any;
+
+// Initialize MSAL authentication
+async function initializeMsal() {
+ if (!CLIENT_ID) {
+ await getCredentials();
+ }
+ const msalParams = {
+ auth: {
+ authority: 'https://login.microsoftonline.com/consumers',
+ clientId: CLIENT_ID
+ }
+ };
+ try {
+ await loadMsalScript();
+ const win = window;
+ msalInstance = new win.msal.PublicClientApplication(msalParams);
+ if (msalInstance.initialize) {
+ await msalInstance.initialize();
+ }
+ } catch (error) {
+ console.error('MSAL initialization error:', error);
+ }
+}
+
+// Retrieve OneDrive access token
+async function getToken(): Promise {
+ const authParams = { scopes: ['OneDrive.ReadWrite'] };
+ let accessToken = '';
+ try {
+ await initializeMsal();
+ const resp = await msalInstance.acquireTokenSilent(authParams);
+ accessToken = resp.accessToken;
+ } catch (err) {
+ const resp = await msalInstance.loginPopup(authParams);
+ msalInstance.setActiveAccount(resp.account);
+ if (resp.idToken) {
+ const resp2 = await msalInstance.acquireTokenSilent(authParams);
+ accessToken = resp2.accessToken;
+ }
+ }
+ return accessToken;
+}
const baseUrl = "https://onedrive.live.com/picker";
const params = {
- sdk: '8.0',
- entry: {
- oneDrive: {
- files: {}
- }
- },
- authentication: {},
- messaging: {
- origin: 'http://localhost:3000', // 현재 부모 페이지의 origin
- channelId: '27' // 메시징 채널용 임의의 ID
- },
- typesAndSources: {
- mode: 'files',
- pivots: {
- oneDrive: true,
- recent: true
- }
- }
+ sdk: '8.0',
+ entry: {
+ oneDrive: {
+ files: {}
+ }
+ },
+ authentication: {},
+ messaging: {
+ origin: window?.location?.origin,
+ channelId: crypto.randomUUID()
+ },
+ typesAndSources: {
+ mode: 'files',
+ pivots: {
+ oneDrive: true,
+ recent: true
+ }
+ }
};
-/**
- * OneDrive 파일 피커 창을 열고, 사용자가 선택한 파일 메타데이터를 받아오는 함수
- */
-export async function openOneDrivePicker(): Promise {
- // SSR 환경(SvelteKit)에서 window 객체가 없을 수 있으므로 가드
- if (typeof window === 'undefined') {
- throw new Error('Not in browser environment');
- }
-
- return new Promise(async (resolve, reject) => {
- let pickerWindow: Window | null = null;
- let channelPort: MessagePort | null = null;
-
- try {
- const authToken = await getToken();
- 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)
- });
- const url = `${baseUrl}?${queryString.toString()}`;
-
- // 새로 연 window에 form을 동적으로 추가하여 POST
- 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();
-
- // 부모 창에서 message 이벤트 수신
- const handleWindowMessage = (event: MessageEvent) => {
- // pickerWindow가 아닌 다른 window에서 온 메시지는 무시
- if (event.source !== pickerWindow) return;
-
- const message = event.data;
-
- // 초기화 메시지 => SharedWorker(MessageChannel) 식으로 포트 받기
- if (
- message?.type === 'initialize' &&
- message?.channelId === params.messaging.channelId
- ) {
- channelPort = event.ports?.[0];
- if (!channelPort) return;
-
- channelPort.addEventListener('message', handlePortMessage);
- channelPort.start();
-
- // picker iframe에 'activate' 전달
- channelPort.postMessage({
- type: 'activate'
- });
- }
- };
-
- // 포트 메시지 핸들러
- const handlePortMessage = async (portEvent: MessageEvent) => {
- const portData = portEvent.data;
- switch (portData.type) {
- case 'notification':
- console.log('notification:', portData);
- break;
-
- case 'command': {
- // picker에 응답
- channelPort?.postMessage({
- type: 'acknowledge',
- id: portData.id
- });
-
- const command = portData.data;
-
- switch (command.command) {
- case 'authenticate': {
- // 재인증
- try {
- const newToken = await getToken();
- 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) {
- console.error(err);
- channelPort?.postMessage({
- result: 'error',
- error: {
- code: 'tokenError',
- message: 'Failed to get token'
- },
- isExpected: true
- });
- }
- break;
- }
-
- case 'close': {
- // 사용자가 취소하거나 닫았을 경우
- cleanup();
- resolve(null);
- break;
- }
-
- case 'pick': {
- // 사용자가 파일 선택 완료
- console.log('Picked:', command);
- /**
- * command 안에는 사용자가 선택한 파일들의 메타데이터 정보가 들어있습니다.
- * 필요하다면 Microsoft Graph API 등을 통해 Blob(실제 파일 데이터)을 받아와야 할 수 있습니다.
- */
-
- // picker에 응답
- channelPort?.postMessage({
- type: 'result',
- id: portData.id,
- data: {
- result: 'success'
- }
- });
-
- // 선택한 파일들(메타정보)을 resolve
- cleanup();
- resolve(command);
- break;
- }
-
- default: {
- console.warn('Unsupported command:', command);
- 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;
- }
- }
-
- // 메시지 이벤트 등록
- window.addEventListener('message', handleWindowMessage);
- } catch (err) {
- if (pickerWindow) pickerWindow.close();
- reject(err);
- }
- });
+// Download file from OneDrive
+async function downloadOneDriveFile(fileInfo: any): Promise {
+ const accessToken = await getToken();
+ if (!accessToken) {
+ throw new Error('Unable to retrieve OneDrive access token.');
+ }
+ 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.');
+ }
+ const fileData = await response.json();
+ const downloadUrl = fileData['@content.downloadUrl'];
+ const downloadResponse = await fetch(downloadUrl);
+ if (!downloadResponse.ok) {
+ throw new Error('Failed to download file.');
+ }
+ return await downloadResponse.blob();
}
+
+// Open OneDrive file picker and return selected file metadata
+export async function openOneDrivePicker(): Promise {
+ if (typeof window === 'undefined') {
+ throw new Error('Not in browser environment');
+ }
+ return new Promise((resolve, reject) => {
+ let pickerWindow: Window | null = null;
+ let channelPort: MessagePort | null = null;
+
+ 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 {
+ const newToken = await getToken();
+ 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) {
+ console.error(err);
+ channelPort?.postMessage({
+ result: 'error',
+ error: { code: 'tokenError', message: 'Failed to get token' },
+ isExpected: true
+ });
+ }
+ break;
+ }
+ case 'close': {
+ cleanup();
+ resolve(null);
+ break;
+ }
+ case 'pick': {
+ channelPort?.postMessage({
+ type: 'result',
+ id: portData.id,
+ data: { result: 'success' }
+ });
+ cleanup();
+ resolve(command);
+ break;
+ }
+ default: {
+ console.warn('Unsupported command:', command);
+ 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();
+ 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)
+ });
+ const url = `${baseUrl}?${queryString.toString()}`;
+ 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(): Promise<{ blob: Blob; name: string } | null> {
+ try {
+ const pickerResult = await openOneDrivePicker();
+ if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) {
+ return null;
+ }
+ const selectedFile = pickerResult.items[0];
+ const blob = await downloadOneDriveFile(selectedFile);
+ return { blob, name: selectedFile.name };
+ } catch (error) {
+ console.error('Error occurred during OneDrive file pick/download:', error);
+ throw error;
+ }
+}
+
+export { downloadOneDriveFile };