208 lines
6.5 KiB
TypeScript
208 lines
6.5 KiB
TypeScript
import { writable } from 'svelte/store';
|
|
import { env } from '$env/dynamic/public';
|
|
|
|
export const isGoogleApiReady = writable(false);
|
|
export const isSignedIn = writable(false);
|
|
|
|
let tokenClient: google.accounts.oauth2.TokenClient;
|
|
|
|
const TOKEN_KEY = 'google_oauth_token';
|
|
export function initGoogleClient(callback: () => void) {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://apis.google.com/js/api.js';
|
|
script.onload = () => {
|
|
gapi.load('client', async () => {
|
|
await gapi.client.init({
|
|
discoveryDocs: [
|
|
'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest',
|
|
'https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest',
|
|
],
|
|
});
|
|
isGoogleApiReady.set(true);
|
|
// Restore token from storage if available
|
|
const saved = localStorage.getItem(TOKEN_KEY);
|
|
if (saved) {
|
|
try {
|
|
const data = JSON.parse(saved);
|
|
if (data.access_token && data.expires_at && data.expires_at > Date.now()) {
|
|
gapi.client.setToken({ access_token: data.access_token });
|
|
isSignedIn.set(true);
|
|
} else {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
}
|
|
} catch {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
}
|
|
}
|
|
callback();
|
|
});
|
|
};
|
|
document.body.appendChild(script);
|
|
|
|
const scriptGsi = document.createElement('script');
|
|
scriptGsi.src = 'https://accounts.google.com/gsi/client';
|
|
scriptGsi.onload = () => {
|
|
const clientId = env.PUBLIC_GOOGLE_CLIENT_ID;
|
|
if (!clientId) {
|
|
console.error('PUBLIC_GOOGLE_CLIENT_ID is not set in the environment.');
|
|
return;
|
|
}
|
|
tokenClient = google.accounts.oauth2.initTokenClient({
|
|
client_id: clientId,
|
|
scope: 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets.readonly',
|
|
callback: (tokenResponse) => {
|
|
if (tokenResponse?.access_token) {
|
|
// Set token in gapi client
|
|
gapi.client.setToken({ access_token: tokenResponse.access_token });
|
|
isSignedIn.set(true);
|
|
// Persist token with expiration
|
|
const expiresInSeconds = tokenResponse.expires_in
|
|
? Number(tokenResponse.expires_in)
|
|
: 0;
|
|
const expiresInMs = expiresInSeconds * 1000;
|
|
const record = {
|
|
access_token: tokenResponse.access_token,
|
|
expires_at: expiresInMs ? Date.now() + expiresInMs : Date.now() + 3600 * 1000
|
|
};
|
|
localStorage.setItem(TOKEN_KEY, JSON.stringify(record));
|
|
}
|
|
},
|
|
});
|
|
};
|
|
document.body.appendChild(scriptGsi);
|
|
}
|
|
|
|
export function handleSignIn() {
|
|
if (gapi.client.getToken() === null) {
|
|
tokenClient.requestAccessToken({ prompt: 'consent' });
|
|
} else {
|
|
tokenClient.requestAccessToken({ prompt: '' });
|
|
}
|
|
}
|
|
|
|
export function handleSignOut() {
|
|
const token = gapi.client.getToken();
|
|
if (token !== null) {
|
|
google.accounts.oauth2.revoke(token.access_token, () => {
|
|
gapi.client.setToken(null);
|
|
isSignedIn.set(false);
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function searchSheets(query: string) {
|
|
if (!gapi.client.drive) {
|
|
throw new Error('Google Drive API not loaded');
|
|
}
|
|
const response = await gapi.client.drive.files.list({
|
|
q: `mimeType='application/vnd.google-apps.spreadsheet' and name contains '${query}'`,
|
|
fields: 'files(id, name, iconLink, webViewLink)',
|
|
pageSize: 20,
|
|
});
|
|
return response.result.files || [];
|
|
}
|
|
|
|
export async function getSheetNames(spreadsheetId: string) {
|
|
if (!gapi.client.sheets) {
|
|
throw new Error('Google Sheets API not loaded');
|
|
}
|
|
const response = await gapi.client.sheets.spreadsheets.get({
|
|
spreadsheetId,
|
|
fields: 'sheets.properties'
|
|
});
|
|
|
|
if (!response.result.sheets) {
|
|
return [];
|
|
}
|
|
|
|
return response.result.sheets.map(sheet => sheet.properties?.title || '');
|
|
}
|
|
|
|
export async function getSheetData(spreadsheetId: string, range: string) {
|
|
if (!gapi.client.sheets) {
|
|
throw new Error('Google Sheets API not loaded');
|
|
}
|
|
const response = await gapi.client.sheets.spreadsheets.values.get({
|
|
spreadsheetId,
|
|
range,
|
|
});
|
|
return response.result.values || [];
|
|
}
|
|
|
|
// Extract Google Drive file ID from various URL formats
|
|
export function extractDriveFileId(url: string): string | null {
|
|
if (!url) return null;
|
|
|
|
// Handle different Google Drive URL formats
|
|
const patterns = [
|
|
/\/file\/d\/([a-zA-Z0-9-_]+)/, // https://drive.google.com/file/d/FILE_ID/view
|
|
/id=([a-zA-Z0-9-_]+)/, // https://drive.google.com/open?id=FILE_ID
|
|
/\/d\/([a-zA-Z0-9-_]+)/, // https://drive.google.com/uc?id=FILE_ID&export=download
|
|
/^([a-zA-Z0-9-_]{25,})$/ // Direct file ID
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = pattern.exec(url);
|
|
if (match) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Check if URL is a Google Drive URL
|
|
export function isGoogleDriveUrl(url: string): boolean {
|
|
return url.includes('drive.google.com') || url.includes('googleapis.com');
|
|
}
|
|
|
|
// Download image from Google Drive using the API
|
|
export async function downloadDriveImage(url: string): Promise<Blob> {
|
|
const fileId = extractDriveFileId(url);
|
|
|
|
if (!fileId) {
|
|
throw new Error('Could not extract file ID from Google Drive URL');
|
|
}
|
|
|
|
if (!gapi.client.drive) {
|
|
throw new Error('Google Drive API not loaded');
|
|
}
|
|
|
|
try {
|
|
// Get file metadata first to check if it exists and is accessible
|
|
const metadata = await gapi.client.drive.files.get({
|
|
fileId: fileId,
|
|
fields: 'id,name,mimeType,size'
|
|
});
|
|
|
|
if (!metadata.result.mimeType?.startsWith('image/')) {
|
|
throw new Error('File is not an image');
|
|
}
|
|
|
|
// Download the file content
|
|
const response = await gapi.client.drive.files.get({
|
|
fileId: fileId,
|
|
alt: 'media'
|
|
});
|
|
|
|
// The response body is already binary data, convert to blob
|
|
const binaryString = response.body;
|
|
const bytes = new Uint8Array(binaryString.length);
|
|
for (let i = 0; i < binaryString.length; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
|
|
return new Blob([bytes], { type: metadata.result.mimeType });
|
|
|
|
} catch (error) {
|
|
console.error('Error downloading from Google Drive:', error);
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
throw new Error(`Failed to download image from Google Drive: ${errorMessage}`);
|
|
}
|
|
}
|
|
|
|
// Create an object URL from image data for display
|
|
export function createImageObjectUrl(blob: Blob): string {
|
|
return URL.createObjectURL(blob);
|
|
}
|