Files
ai-job-application/src/lib/googleAuth.ts
T

207 lines
7.3 KiB
TypeScript

import { JobApplication } from '../types';
let cachedAccessToken: string | null = localStorage.getItem('ai_job_portal_google_token');
export const getCachedAccessToken = () => cachedAccessToken;
const getEnvVar = (key: string) => {
try {
return (import.meta as any).env?.[key];
} catch (e) {
return undefined;
}
};
const OAUTH_CLIENT_ID = getEnvVar('VITE_GOOGLE_OAUTH_CLIENT_ID') || '488548028456-tal52pta8fm2uc9t4o2u82itqvi91rp7.apps.googleusercontent.com';
/**
* Direct Google OAuth 2.0 Flow via Google Identity Services
* Obtains real access token directly from accounts.google.com for Google Drive/Sheets API calls.
*/
export const googleWorkspaceSignIn = async (): Promise<{ email: string; name: string; accessToken: string }> => {
return new Promise((resolve, reject) => {
try {
const scope = encodeURIComponent('https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/documents');
const redirectUri = encodeURIComponent(`${window.location.origin}`);
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${OAUTH_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=token&scope=${scope}&prompt=select_account`;
const width = 500;
const height = 650;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2;
const popup = window.open(
authUrl,
'Google Workspace OAuth Sign-In',
`width=${width},height=${height},left=${left},top=${top},scrollbars=yes`
);
if (!popup) {
throw new Error('Popup window was blocked by your browser. Please allow popups for http://localhost:3000.');
}
const interval = setInterval(async () => {
try {
if (popup.closed) {
clearInterval(interval);
if (!cachedAccessToken) {
reject(new Error('Sign-in window closed before completing authentication.'));
}
return;
}
const currentUrl = popup.location.href;
if (currentUrl && currentUrl.includes(window.location.origin)) {
const hash = popup.location.hash;
if (hash && hash.includes('access_token=')) {
clearInterval(interval);
popup.close();
const params = new URLSearchParams(hash.substring(1));
const token = params.get('access_token');
if (token) {
cachedAccessToken = token;
localStorage.setItem('ai_job_portal_google_token', token);
localStorage.setItem('ai_job_portal_google_connected', 'true');
try {
const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${token}` }
});
if (userRes.ok) {
const userData = await userRes.json();
resolve({
email: userData.email || 'dkifer19@gmail.com',
name: userData.name || 'David Kifer',
accessToken: token
});
return;
}
} catch (e) {}
resolve({
email: 'dkifer19@gmail.com',
name: 'David Kifer',
accessToken: token
});
}
}
}
} catch (e) {}
}, 400);
} catch (err: any) {
reject(err);
}
});
};
export const googleSignOut = async () => {
cachedAccessToken = null;
localStorage.removeItem('ai_job_portal_google_token');
localStorage.removeItem('ai_job_portal_google_connected');
localStorage.removeItem('ai_job_portal_google_user');
};
// Workspace Action Handlers
export async function exportApplicationsToGoogleSheets(
applications: JobApplication[],
accessToken?: string
): Promise<{ spreadsheetId: string; spreadsheetUrl: string; isLocalCsv?: boolean; message?: string }> {
const token = accessToken || cachedAccessToken;
const res = await fetch('/api/workspace/sheets/export', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({ applications, accessToken: token })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to export applications to Google Sheets');
}
return { spreadsheetId: data.spreadsheetId, spreadsheetUrl: data.spreadsheetUrl, isLocalCsv: data.isLocalCsv, message: data.message };
}
export async function createTailoredGoogleDoc(
title: string,
content: string,
docType: 'resume' | 'coverLetter' | 'note',
accessToken?: string
): Promise<{ documentId: string; documentUrl: string; isLocalDoc?: boolean; message?: string }> {
const token = accessToken || cachedAccessToken;
const res = await fetch('/api/workspace/docs/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({ title, content, docType, accessToken: token })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to create Google Doc');
}
return { documentId: data.documentId, documentUrl: data.documentUrl, isLocalDoc: data.isLocalDoc, message: data.message };
}
export async function uploadFileToGoogleDrive(
fileName: string,
fileContent: string,
mimeType: string = 'text/plain',
accessToken?: string
): Promise<{ fileId: string; driveUrl: string; isLocalFile?: boolean; message?: string }> {
const token = accessToken || cachedAccessToken;
const res = await fetch('/api/workspace/drive/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({ fileName, fileContent, mimeType, accessToken: token })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to upload file to Google Drive');
}
return { fileId: data.fileId, driveUrl: data.driveUrl, isLocalFile: data.isLocalFile, message: data.message };
}
export async function listGoogleDriveWorkspaceFiles(accessToken?: string): Promise<any[]> {
const token = accessToken || cachedAccessToken;
const res = await fetch('/api/workspace/drive/files', {
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {})
}
});
const data = await res.json();
return data.files || [];
}
export async function saveGoogleKeepNote(
title: string,
content: string,
color: string = 'DEFAULT',
accessToken?: string
): Promise<{ noteId: string; title: string; content: string; color: string; timestamp: string }> {
const token = accessToken || cachedAccessToken;
const res = await fetch('/api/workspace/keep/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({ title, content, color, accessToken: token })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.error || 'Failed to create Keep note');
}
return data.note;
}