Implemented sync functionality with sheets and email sending
This commit is contained in:
@@ -98,6 +98,122 @@ export async function getUserInfo(accessToken: string): Promise<{ email: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate with Google using OAuth popup flow
|
||||
* @returns Authentication result with success status and tokens
|
||||
*/
|
||||
export async function authenticateWithGoogle(): Promise<{
|
||||
success: boolean;
|
||||
refreshToken?: string;
|
||||
userEmail?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
if (!browser) {
|
||||
return { success: false, error: 'Not in browser environment' };
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
// Open popup window for OAuth
|
||||
const popup = window.open(
|
||||
'/auth/google',
|
||||
'google-auth',
|
||||
'width=500,height=600,scrollbars=yes,resizable=yes,left=' +
|
||||
Math.round(window.screen.width / 2 - 250) + ',top=' +
|
||||
Math.round(window.screen.height / 2 - 300)
|
||||
);
|
||||
|
||||
if (!popup) {
|
||||
resolve({ success: false, error: 'Failed to open popup window. Please allow popups for this site.' });
|
||||
return;
|
||||
}
|
||||
|
||||
let authCompleted = false;
|
||||
let popupTimer: number | null = null;
|
||||
|
||||
// Store current timestamp to detect changes in localStorage
|
||||
const startTimestamp = localStorage.getItem('google_auth_timestamp') ?? '0';
|
||||
|
||||
// Poll localStorage for auth completion
|
||||
const pollInterval = setInterval(() => {
|
||||
try {
|
||||
const currentTimestamp = localStorage.getItem('google_auth_timestamp');
|
||||
|
||||
// If timestamp has changed, auth is complete
|
||||
if (currentTimestamp && currentTimestamp !== startTimestamp) {
|
||||
handleAuthSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error checking auth timestamp:', e);
|
||||
}
|
||||
}, 500); // Poll every 500ms
|
||||
|
||||
// Common handler for authentication success
|
||||
function handleAuthSuccess() {
|
||||
if (authCompleted) return; // Prevent duplicate handling
|
||||
|
||||
authCompleted = true;
|
||||
|
||||
// Clean up timers
|
||||
clearInterval(pollInterval);
|
||||
if (popupTimer) clearTimeout(popupTimer);
|
||||
|
||||
// Get tokens from localStorage
|
||||
const refreshToken = localStorage.getItem('google_refresh_token');
|
||||
const userEmail = localStorage.getItem('google_user_email');
|
||||
|
||||
if (refreshToken) {
|
||||
resolve({
|
||||
success: true,
|
||||
refreshToken,
|
||||
userEmail: userEmail ?? undefined
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: 'No refresh token found after authentication' });
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up function to handle all cleanup in one place
|
||||
const cleanUp = () => {
|
||||
clearInterval(pollInterval);
|
||||
if (popupTimer) clearTimeout(popupTimer);
|
||||
};
|
||||
|
||||
// Set a timeout for initial auth check
|
||||
popupTimer = setTimeout(() => {
|
||||
if (!authCompleted) {
|
||||
cleanUp();
|
||||
// Check if tokens were stored by the popup before it was closed
|
||||
const refreshToken = localStorage.getItem('google_refresh_token');
|
||||
const userEmail = localStorage.getItem('google_user_email');
|
||||
|
||||
if (refreshToken) {
|
||||
resolve({
|
||||
success: true,
|
||||
refreshToken,
|
||||
userEmail: userEmail ?? undefined
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: 'Authentication timeout or cancelled' });
|
||||
}
|
||||
}
|
||||
}, 30 * 1000) as unknown as number;
|
||||
|
||||
// Final cleanup timeout
|
||||
setTimeout(() => {
|
||||
if (!authCompleted) {
|
||||
cleanUp();
|
||||
resolve({ success: false, error: 'Authentication timeout' });
|
||||
}
|
||||
}, 60 * 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error connecting to Google:', error);
|
||||
resolve({ success: false, error: error instanceof Error ? error.message : 'Unknown error' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a Google access token
|
||||
* @param accessToken - Google access token to revoke
|
||||
|
||||
120
src/lib/google/auth/manager.ts
Normal file
120
src/lib/google/auth/manager.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { authenticateWithGoogle } from '$lib/google/auth/client.js';
|
||||
|
||||
export interface GoogleAuthState {
|
||||
isConnected: boolean;
|
||||
checking: boolean;
|
||||
connecting: boolean;
|
||||
showCancelOption: boolean;
|
||||
token: string | null;
|
||||
error: string | null;
|
||||
userEmail: string | null;
|
||||
}
|
||||
|
||||
export function createGoogleAuthState(): GoogleAuthState {
|
||||
return {
|
||||
isConnected: false,
|
||||
checking: false,
|
||||
connecting: false,
|
||||
showCancelOption: false,
|
||||
token: null,
|
||||
error: null,
|
||||
userEmail: null
|
||||
};
|
||||
}
|
||||
|
||||
export class GoogleAuthManager {
|
||||
private readonly state: GoogleAuthState;
|
||||
private cancelTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(state: GoogleAuthState) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
checkConnection(): void {
|
||||
this.state.checking = true;
|
||||
this.state.error = null;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('google_refresh_token');
|
||||
const email = localStorage.getItem('google_user_email');
|
||||
|
||||
this.state.isConnected = !!token;
|
||||
this.state.token = token;
|
||||
this.state.userEmail = email;
|
||||
} catch (error) {
|
||||
console.error('Error checking connection:', error);
|
||||
this.state.error = 'Failed to check connection status';
|
||||
} finally {
|
||||
this.state.checking = false;
|
||||
}
|
||||
}
|
||||
|
||||
async connectToGoogle(): Promise<void> {
|
||||
if (this.state.connecting) return;
|
||||
|
||||
this.state.connecting = true;
|
||||
this.state.error = null;
|
||||
this.state.showCancelOption = false;
|
||||
|
||||
// Show cancel option after 5 seconds
|
||||
this.cancelTimeout = setTimeout(() => {
|
||||
this.state.showCancelOption = true;
|
||||
}, 5000);
|
||||
|
||||
try {
|
||||
const result = await authenticateWithGoogle();
|
||||
|
||||
if (result.success && result.refreshToken) {
|
||||
// Store tokens
|
||||
localStorage.setItem('google_refresh_token', result.refreshToken);
|
||||
if (result.userEmail) {
|
||||
localStorage.setItem('google_user_email', result.userEmail);
|
||||
}
|
||||
|
||||
// Update state
|
||||
this.state.isConnected = true;
|
||||
this.state.token = result.refreshToken;
|
||||
this.state.userEmail = result.userEmail;
|
||||
} else {
|
||||
throw new Error(result.error ?? 'Authentication failed');
|
||||
}
|
||||
} catch (error) {
|
||||
this.state.error = error instanceof Error ? error.message : 'Failed to connect to Google';
|
||||
} finally {
|
||||
this.state.connecting = false;
|
||||
this.state.showCancelOption = false;
|
||||
if (this.cancelTimeout) {
|
||||
clearTimeout(this.cancelTimeout);
|
||||
this.cancelTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelGoogleAuth(): void {
|
||||
this.state.connecting = false;
|
||||
this.state.showCancelOption = false;
|
||||
this.state.error = null;
|
||||
|
||||
if (this.cancelTimeout) {
|
||||
clearTimeout(this.cancelTimeout);
|
||||
this.cancelTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectGoogle(): Promise<void> {
|
||||
try {
|
||||
// Clear local storage
|
||||
localStorage.removeItem('google_refresh_token');
|
||||
localStorage.removeItem('google_user_email');
|
||||
|
||||
// Reset state
|
||||
this.state.isConnected = false;
|
||||
this.state.token = null;
|
||||
this.state.userEmail = null;
|
||||
this.state.error = null;
|
||||
} catch (error) {
|
||||
console.error('Error disconnecting:', error);
|
||||
this.state.error = 'Failed to disconnect';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user