openrag/frontend/components/cloud-picker/provider-handlers.ts
2025-11-19 12:39:39 -08:00

274 lines
8.7 KiB
TypeScript

"use client";
import {
CloudFile,
CloudProvider,
GooglePickerData,
GooglePickerDocument,
} from "./types";
export class GoogleDriveHandler {
private accessToken: string;
private onPickerStateChange?: (isOpen: boolean) => void;
constructor(
accessToken: string,
onPickerStateChange?: (isOpen: boolean) => void,
) {
this.accessToken = accessToken;
this.onPickerStateChange = onPickerStateChange;
}
async loadPickerApi(): Promise<boolean> {
return new Promise((resolve) => {
if (typeof window !== "undefined" && window.gapi) {
window.gapi.load("picker", {
callback: () => resolve(true),
onerror: () => resolve(false),
});
} else {
// Load Google API script
const script = document.createElement("script");
script.src = "https://apis.google.com/js/api.js";
script.async = true;
script.defer = true;
script.onload = () => {
window.gapi.load("picker", {
callback: () => resolve(true),
onerror: () => resolve(false),
});
};
script.onerror = () => resolve(false);
document.head.appendChild(script);
}
});
}
openPicker(onFileSelected: (files: CloudFile[]) => void): void {
if (!window.google?.picker) {
return;
}
try {
this.onPickerStateChange?.(true);
// Create a view for regular documents
const docsView = new window.google.picker.DocsView()
.setIncludeFolders(true)
.setSelectFolderEnabled(true);
const picker = new window.google.picker.PickerBuilder()
.addView(docsView)
.setOAuthToken(this.accessToken)
.enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED)
.setTitle("Select files or folders from Google Drive")
.setCallback((data) => this.pickerCallback(data, onFileSelected))
.build();
picker.setVisible(true);
// Apply z-index fix
setTimeout(() => {
const pickerElements = document.querySelectorAll(
".picker-dialog, .goog-modalpopup",
);
pickerElements.forEach((el) => {
(el as HTMLElement).style.zIndex = "10000";
});
const bgElements = document.querySelectorAll(
".picker-dialog-bg, .goog-modalpopup-bg",
);
bgElements.forEach((el) => {
(el as HTMLElement).style.zIndex = "9999";
});
}, 100);
} catch (error) {
console.error("Error creating picker:", error);
this.onPickerStateChange?.(false);
}
}
private async pickerCallback(
data: GooglePickerData,
onFileSelected: (files: CloudFile[]) => void,
): Promise<void> {
if (data.action === window.google.picker.Action.PICKED) {
const files: CloudFile[] = data.docs.map((doc: GooglePickerDocument) => ({
id: doc[window.google.picker.Document.ID],
name: doc[window.google.picker.Document.NAME],
mimeType: doc[window.google.picker.Document.MIME_TYPE],
webViewLink: doc[window.google.picker.Document.URL],
iconLink: doc[window.google.picker.Document.ICON_URL],
size: doc["sizeBytes"] ? parseInt(doc["sizeBytes"]) : undefined,
modifiedTime: doc["lastEditedUtc"],
isFolder:
doc[window.google.picker.Document.MIME_TYPE] ===
"application/vnd.google-apps.folder",
}));
// Enrich with additional file data if needed
if (files.some((f) => !f.size && !f.isFolder)) {
try {
const enrichedFiles = await Promise.all(
files.map(async (file) => {
if (!file.size && !file.isFolder) {
try {
const response = await fetch(
`https://www.googleapis.com/drive/v3/files/${file.id}?fields=size,modifiedTime`,
{
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
},
);
if (response.ok) {
const fileDetails = await response.json();
return {
...file,
size: fileDetails.size
? parseInt(fileDetails.size)
: undefined,
modifiedTime:
fileDetails.modifiedTime || file.modifiedTime,
};
}
} catch (error) {
console.warn("Failed to fetch file details:", error);
}
}
return file;
}),
);
onFileSelected(enrichedFiles);
} catch (error) {
console.warn("Failed to enrich file data:", error);
onFileSelected(files);
}
} else {
onFileSelected(files);
}
}
this.onPickerStateChange?.(false);
}
}
export class OneDriveHandler {
private accessToken: string;
private clientId: string;
private provider: CloudProvider;
private baseUrl?: string;
constructor(
accessToken: string,
clientId: string,
provider: CloudProvider = "onedrive",
baseUrl?: string,
) {
this.accessToken = accessToken;
this.clientId = clientId;
this.provider = provider;
this.baseUrl = baseUrl;
}
async loadPickerApi(): Promise<boolean> {
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = "https://js.live.net/v7.2/OneDrive.js";
script.onload = () => resolve(true);
script.onerror = () => resolve(false);
document.head.appendChild(script);
});
}
openPicker(onFileSelected: (files: CloudFile[]) => void): void {
if (!window.OneDrive) {
return;
}
window.OneDrive.open({
clientId: this.clientId,
action: "query",
multiSelect: true,
advanced: {
endpointHint: "api.onedrive.com",
accessToken: this.accessToken,
},
success: (response: any) => {
const newFiles: CloudFile[] =
response.value?.map((item: any) => {
// Extract mimeType from file object or infer from name
let mimeType = item.file?.mimeType;
if (!mimeType && item.name) {
// Infer from extension if mimeType not provided
const ext = item.name.split('.').pop()?.toLowerCase();
const mimeTypes: { [key: string]: string } = {
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
txt: 'text/plain',
csv: 'text/csv',
json: 'application/json',
xml: 'application/xml',
html: 'text/html',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
svg: 'image/svg+xml',
};
mimeType = mimeTypes[ext || ''] || 'application/octet-stream';
}
return {
id: item.id,
name: item.name || `${this.getProviderName()} File`,
mimeType: mimeType || "application/octet-stream",
webUrl: item.webUrl || "",
downloadUrl: item["@microsoft.graph.downloadUrl"] || "",
size: item.size,
modifiedTime: item.lastModifiedDateTime,
isFolder: !!item.folder,
};
}) || [];
onFileSelected(newFiles);
},
cancel: () => {
console.log("Picker cancelled");
},
error: (error: any) => {
console.error("Picker error:", error);
},
});
}
private getProviderName(): string {
return this.provider === "sharepoint" ? "SharePoint" : "OneDrive";
}
}
export const createProviderHandler = (
provider: CloudProvider,
accessToken: string,
onPickerStateChange?: (isOpen: boolean) => void,
clientId?: string,
baseUrl?: string,
) => {
switch (provider) {
case "google_drive":
return new GoogleDriveHandler(accessToken, onPickerStateChange);
case "onedrive":
case "sharepoint":
if (!clientId) {
throw new Error("Client ID required for OneDrive/SharePoint");
}
return new OneDriveHandler(accessToken, clientId, provider, baseUrl);
default:
throw new Error(`Unsupported provider: ${provider}`);
}
};