onedrive-file-picker.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import { PublicClientApplication } from '@azure/msal-browser';
  2. import type { PopupRequest } from '@azure/msal-browser';
  3. let CLIENT_ID = '521ada3e-6154-4a35-b9d3-51faac8ac944';
  4. async function getCredentials() {
  5. if (CLIENT_ID) return;
  6. const response = await fetch('/api/config');
  7. if (!response.ok) {
  8. throw new Error('Failed to fetch OneDrive credentials');
  9. }
  10. const config = await response.json();
  11. CLIENT_ID = config.onedrive?.client_id;
  12. if (!CLIENT_ID) {
  13. throw new Error('OneDrive client ID not configured');
  14. }
  15. }
  16. let msalInstance: PublicClientApplication | null = null;
  17. // Initialize MSAL authentication
  18. async function initializeMsal() {
  19. try {
  20. if (!CLIENT_ID) {
  21. await getCredentials();
  22. }
  23. const msalParams = {
  24. auth: {
  25. authority: 'https://login.microsoftonline.com/consumers',
  26. clientId: CLIENT_ID
  27. }
  28. };
  29. if (!msalInstance) {
  30. msalInstance = new PublicClientApplication(msalParams);
  31. if (msalInstance.initialize) {
  32. await msalInstance.initialize();
  33. }
  34. }
  35. return msalInstance;
  36. } catch (error) {
  37. throw new Error('MSAL initialization failed: ' + (error instanceof Error ? error.message : String(error)));
  38. }
  39. }
  40. // Retrieve OneDrive access token
  41. async function getToken(): Promise<string> {
  42. const authParams: PopupRequest = { scopes: ['OneDrive.ReadWrite'] };
  43. let accessToken = '';
  44. try {
  45. msalInstance = await initializeMsal();
  46. if (!msalInstance) {
  47. throw new Error('MSAL not initialized');
  48. }
  49. const resp = await msalInstance.acquireTokenSilent(authParams);
  50. accessToken = resp.accessToken;
  51. } catch (err) {
  52. if (!msalInstance) {
  53. throw new Error('MSAL not initialized');
  54. }
  55. try {
  56. const resp = await msalInstance.loginPopup(authParams);
  57. msalInstance.setActiveAccount(resp.account);
  58. if (resp.idToken) {
  59. const resp2 = await msalInstance.acquireTokenSilent(authParams);
  60. accessToken = resp2.accessToken;
  61. }
  62. } catch (popupError) {
  63. throw new Error('Failed to login: ' + (popupError instanceof Error ? popupError.message : String(popupError)));
  64. }
  65. }
  66. if (!accessToken) {
  67. throw new Error('Failed to acquire access token');
  68. }
  69. return accessToken;
  70. }
  71. const baseUrl = 'https://onedrive.live.com/picker';
  72. const params = {
  73. sdk: '8.0',
  74. entry: {
  75. oneDrive: {
  76. files: {}
  77. }
  78. },
  79. authentication: {},
  80. messaging: {
  81. origin: window?.location?.origin,
  82. channelId: crypto.randomUUID()
  83. },
  84. typesAndSources: {
  85. mode: 'files',
  86. pivots: {
  87. oneDrive: true,
  88. recent: true
  89. }
  90. }
  91. };
  92. // Download file from OneDrive
  93. async function downloadOneDriveFile(fileInfo: any): Promise<Blob> {
  94. const accessToken = await getToken();
  95. if (!accessToken) {
  96. throw new Error('Unable to retrieve OneDrive access token.');
  97. }
  98. const fileInfoUrl = `${fileInfo['@sharePoint.endpoint']}/drives/${fileInfo.parentReference.driveId}/items/${fileInfo.id}`;
  99. const response = await fetch(fileInfoUrl, {
  100. headers: {
  101. Authorization: `Bearer ${accessToken}`
  102. }
  103. });
  104. if (!response.ok) {
  105. throw new Error('Failed to fetch file information.');
  106. }
  107. const fileData = await response.json();
  108. const downloadUrl = fileData['@content.downloadUrl'];
  109. const downloadResponse = await fetch(downloadUrl);
  110. if (!downloadResponse.ok) {
  111. throw new Error('Failed to download file.');
  112. }
  113. return await downloadResponse.blob();
  114. }
  115. // OneDrive 피커 결과 인터페이스 정의
  116. // Open OneDrive file picker and return selected file metadata
  117. export async function openOneDrivePicker(): Promise<any | null> {
  118. if (typeof window === 'undefined') {
  119. throw new Error('Not in browser environment');
  120. }
  121. return new Promise((resolve, reject) => {
  122. let pickerWindow: Window | null = null;
  123. let channelPort: MessagePort | null = null;
  124. const handleWindowMessage = (event: MessageEvent) => {
  125. if (event.source !== pickerWindow) return;
  126. const message = event.data;
  127. if (message?.type === 'initialize' && message?.channelId === params.messaging.channelId) {
  128. channelPort = event.ports?.[0];
  129. if (!channelPort) return;
  130. channelPort.addEventListener('message', handlePortMessage);
  131. channelPort.start();
  132. channelPort.postMessage({ type: 'activate' });
  133. }
  134. };
  135. const handlePortMessage = async (portEvent: MessageEvent) => {
  136. const portData = portEvent.data;
  137. switch (portData.type) {
  138. case 'notification':
  139. break;
  140. case 'command': {
  141. channelPort?.postMessage({ type: 'acknowledge', id: portData.id });
  142. const command = portData.data;
  143. switch (command.command) {
  144. case 'authenticate': {
  145. try {
  146. const newToken = await getToken();
  147. if (newToken) {
  148. channelPort?.postMessage({
  149. type: 'result',
  150. id: portData.id,
  151. data: { result: 'token', token: newToken }
  152. });
  153. } else {
  154. throw new Error('Could not retrieve auth token');
  155. }
  156. } catch (err) {
  157. channelPort?.postMessage({
  158. result: 'error',
  159. error: { code: 'tokenError', message: 'Failed to get token' },
  160. isExpected: true
  161. });
  162. }
  163. break;
  164. }
  165. case 'close': {
  166. cleanup();
  167. resolve(null);
  168. break;
  169. }
  170. case 'pick': {
  171. channelPort?.postMessage({
  172. type: 'result',
  173. id: portData.id,
  174. data: { result: 'success' }
  175. });
  176. cleanup();
  177. resolve(command);
  178. break;
  179. }
  180. default: {
  181. channelPort?.postMessage({
  182. result: 'error',
  183. error: { code: 'unsupportedCommand', message: command.command },
  184. isExpected: true
  185. });
  186. break;
  187. }
  188. }
  189. break;
  190. }
  191. }
  192. };
  193. function cleanup() {
  194. window.removeEventListener('message', handleWindowMessage);
  195. if (channelPort) {
  196. channelPort.removeEventListener('message', handlePortMessage);
  197. }
  198. if (pickerWindow) {
  199. pickerWindow.close();
  200. pickerWindow = null;
  201. }
  202. }
  203. const initializePicker = async () => {
  204. try {
  205. const authToken = await getToken();
  206. if (!authToken) {
  207. return reject(new Error('Failed to acquire access token'));
  208. }
  209. pickerWindow = window.open('', 'OneDrivePicker', 'width=800,height=600');
  210. if (!pickerWindow) {
  211. return reject(new Error('Failed to open OneDrive picker window'));
  212. }
  213. const queryString = new URLSearchParams({
  214. filePicker: JSON.stringify(params)
  215. });
  216. const url = `${baseUrl}?${queryString.toString()}`;
  217. const form = pickerWindow.document.createElement('form');
  218. form.setAttribute('action', url);
  219. form.setAttribute('method', 'POST');
  220. const input = pickerWindow.document.createElement('input');
  221. input.setAttribute('type', 'hidden');
  222. input.setAttribute('name', 'access_token');
  223. input.setAttribute('value', authToken);
  224. form.appendChild(input);
  225. pickerWindow.document.body.appendChild(form);
  226. form.submit();
  227. window.addEventListener('message', handleWindowMessage);
  228. } catch (err) {
  229. if (pickerWindow) {
  230. pickerWindow.close();
  231. }
  232. reject(err);
  233. }
  234. };
  235. initializePicker();
  236. });
  237. }
  238. // Pick and download file from OneDrive
  239. export async function pickAndDownloadFile(): Promise<{ blob: Blob; name: string } | null> {
  240. const pickerResult = await openOneDrivePicker();
  241. if (!pickerResult || !pickerResult.items || pickerResult.items.length === 0) {
  242. return null;
  243. }
  244. const selectedFile = pickerResult.items[0];
  245. const blob = await downloadOneDriveFile(selectedFile);
  246. return { blob, name: selectedFile.name };
  247. }
  248. export { downloadOneDriveFile };