google-drive-picker.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Google Drive Picker API configuration
  2. let API_KEY = '';
  3. let CLIENT_ID = '';
  4. // Function to fetch credentials from backend config
  5. async function getCredentials() {
  6. const response = await fetch('/api/retrieval/config');
  7. if (!response.ok) {
  8. throw new Error('Failed to fetch Google Drive credentials');
  9. }
  10. const config = await response.json();
  11. API_KEY = config.google_drive?.api_key;
  12. CLIENT_ID = config.google_drive?.client_id;
  13. }
  14. const SCOPE = [
  15. 'https://www.googleapis.com/auth/drive.readonly',
  16. 'https://www.googleapis.com/auth/drive.file'
  17. ];
  18. // Validate required credentials
  19. const validateCredentials = () => {
  20. if (!API_KEY || !CLIENT_ID) {
  21. throw new Error('Google Drive API credentials not configured');
  22. }
  23. if (API_KEY === '' || CLIENT_ID === '') {
  24. throw new Error('Please configure valid Google Drive API credentials');
  25. }
  26. };
  27. let pickerApiLoaded = false;
  28. let oauthToken: string | null = null;
  29. let initialized = false;
  30. export const loadGoogleDriveApi = () => {
  31. return new Promise((resolve, reject) => {
  32. if (typeof gapi === 'undefined') {
  33. const script = document.createElement('script');
  34. script.src = 'https://apis.google.com/js/api.js';
  35. script.onload = () => {
  36. gapi.load('picker', () => {
  37. pickerApiLoaded = true;
  38. resolve(true);
  39. });
  40. };
  41. script.onerror = reject;
  42. document.body.appendChild(script);
  43. } else {
  44. gapi.load('picker', () => {
  45. pickerApiLoaded = true;
  46. resolve(true);
  47. });
  48. }
  49. });
  50. };
  51. export const loadGoogleAuthApi = () => {
  52. return new Promise((resolve, reject) => {
  53. if (typeof google === 'undefined') {
  54. const script = document.createElement('script');
  55. script.src = 'https://accounts.google.com/gsi/client';
  56. script.onload = resolve;
  57. script.onerror = reject;
  58. document.body.appendChild(script);
  59. } else {
  60. resolve(true);
  61. }
  62. });
  63. };
  64. export const getAuthToken = async () => {
  65. if (!oauthToken) {
  66. return new Promise((resolve, reject) => {
  67. const tokenClient = google.accounts.oauth2.initTokenClient({
  68. client_id: CLIENT_ID,
  69. scope: SCOPE.join(' '),
  70. callback: (response: any) => {
  71. if (response.access_token) {
  72. oauthToken = response.access_token;
  73. resolve(oauthToken);
  74. } else {
  75. reject(new Error('Failed to get access token'));
  76. }
  77. },
  78. error_callback: (error: any) => {
  79. reject(new Error(error.message || 'OAuth error occurred'));
  80. }
  81. });
  82. tokenClient.requestAccessToken();
  83. });
  84. }
  85. return oauthToken;
  86. };
  87. const initialize = async () => {
  88. if (!initialized) {
  89. await getCredentials();
  90. validateCredentials();
  91. await Promise.all([loadGoogleDriveApi(), loadGoogleAuthApi()]);
  92. initialized = true;
  93. }
  94. };
  95. export const createPicker = () => {
  96. return new Promise(async (resolve, reject) => {
  97. try {
  98. console.log('Initializing Google Drive Picker...');
  99. await initialize();
  100. console.log('Getting auth token...');
  101. const token = await getAuthToken();
  102. if (!token) {
  103. console.error('Failed to get OAuth token');
  104. throw new Error('Unable to get OAuth token');
  105. }
  106. console.log('Auth token obtained successfully');
  107. const picker = new google.picker.PickerBuilder()
  108. .enableFeature(google.picker.Feature.NAV_HIDDEN)
  109. .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
  110. .addView(
  111. new google.picker.DocsView()
  112. .setIncludeFolders(false)
  113. .setSelectFolderEnabled(false)
  114. .setMimeTypes(
  115. 'application/pdf,text/plain,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.google-apps.document,application/vnd.google-apps.spreadsheet,application/vnd.google-apps.presentation'
  116. )
  117. )
  118. .setOAuthToken(token)
  119. .setDeveloperKey(API_KEY)
  120. // Remove app ID setting as it's not needed and can cause 404 errors
  121. .setCallback(async (data: any) => {
  122. if (data[google.picker.Response.ACTION] === google.picker.Action.PICKED) {
  123. try {
  124. const doc = data[google.picker.Response.DOCUMENTS][0];
  125. const fileId = doc[google.picker.Document.ID];
  126. const fileName = doc[google.picker.Document.NAME];
  127. const fileUrl = doc[google.picker.Document.URL];
  128. if (!fileId || !fileName) {
  129. throw new Error('Required file details missing');
  130. }
  131. // Construct download URL based on MIME type
  132. const mimeType = doc[google.picker.Document.MIME_TYPE];
  133. let downloadUrl;
  134. let exportFormat;
  135. if (mimeType.includes('google-apps')) {
  136. // Handle Google Workspace files
  137. if (mimeType.includes('document')) {
  138. exportFormat = 'text/plain';
  139. } else if (mimeType.includes('spreadsheet')) {
  140. exportFormat = 'text/csv';
  141. } else if (mimeType.includes('presentation')) {
  142. exportFormat = 'text/plain';
  143. } else {
  144. exportFormat = 'application/pdf';
  145. }
  146. downloadUrl = `https://www.googleapis.com/drive/v3/files/${fileId}/export?mimeType=${encodeURIComponent(exportFormat)}`;
  147. } else {
  148. // Regular files use direct download URL
  149. downloadUrl = `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`;
  150. }
  151. // Create a Blob from the file download
  152. const response = await fetch(downloadUrl, {
  153. headers: {
  154. Authorization: `Bearer ${token}`,
  155. Accept: '*/*'
  156. }
  157. });
  158. if (!response.ok) {
  159. const errorText = await response.text();
  160. console.error('Download failed:', {
  161. status: response.status,
  162. statusText: response.statusText,
  163. error: errorText
  164. });
  165. throw new Error(`Failed to download file (${response.status}): ${errorText}`);
  166. }
  167. const blob = await response.blob();
  168. const result = {
  169. id: fileId,
  170. name: fileName,
  171. url: downloadUrl,
  172. blob: blob,
  173. headers: {
  174. Authorization: `Bearer ${token}`,
  175. Accept: '*/*'
  176. }
  177. };
  178. resolve(result);
  179. } catch (error) {
  180. reject(error);
  181. }
  182. } else if (data[google.picker.Response.ACTION] === google.picker.Action.CANCEL) {
  183. resolve(null);
  184. }
  185. })
  186. .build();
  187. picker.setVisible(true);
  188. } catch (error) {
  189. console.error('Google Drive Picker error:', error);
  190. reject(error);
  191. }
  192. });
  193. };