UNPKG

gmail-to-exchange365

Version:

Complete Gmail to Exchange 365 migration tool with UI - Migrate emails, attachments, and folders seamlessly

187 lines (152 loc) 5.59 kB
import { fetchAllEmails, fetchGmailLabels } from "./gmailFetcher"; import { uploadEmail, getGraphClient } from "./exchangePusher"; import { mapGmailLabelsToFolders, getFolderIdForLabel } from "./folderMapper"; import { EmailMessage, MigrationProgress } from "./types"; interface GmailToken { access_token: string; refresh_token?: string; expiry_date?: number; } interface MSToken { access_token: string; refresh_token?: string; expires_in?: number; } export interface MigrationOptions { batchSize?: number; delayBetweenBatches?: number; retryAttempts?: number; retryDelay?: number; } export class Migrator { private progress: MigrationProgress = { current: 0, total: 0, status: 'idle' }; private onProgressCallback?: (progress: MigrationProgress) => void; private shouldPause: boolean = false; private shouldStop: boolean = false; constructor( private gToken: GmailToken, private msToken: MSToken, private options: MigrationOptions = {} ) { this.options = { batchSize: options.batchSize || 10, delayBetweenBatches: options.delayBetweenBatches || 1000, retryAttempts: options.retryAttempts || 3, retryDelay: options.retryDelay || 5000 }; } onProgress(callback: (progress: MigrationProgress) => void) { this.onProgressCallback = callback; } pause() { this.shouldPause = true; this.progress.status = 'paused'; this.notifyProgress(); } resume() { this.shouldPause = false; this.progress.status = 'running'; this.notifyProgress(); } stop() { this.shouldStop = true; this.progress.status = 'idle'; this.notifyProgress(); } private notifyProgress() { if (this.onProgressCallback) { this.onProgressCallback({ ...this.progress }); } } private async waitIfPaused() { while (this.shouldPause && !this.shouldStop) { await new Promise(resolve => setTimeout(resolve, 1000)); } } async migrate(): Promise<{ total: number; successful: number; failed: number }> { this.progress.status = 'running'; this.progress.current = 0; this.shouldStop = false; this.shouldPause = false; try { this.progress.message = "Fetching emails from Gmail..."; this.notifyProgress(); const emails = await fetchAllEmails(this.gToken); this.progress.total = emails.length; this.progress.message = `Found ${emails.length} emails. Starting migration...`; this.notifyProgress(); const client = getGraphClient(this.msToken); // Fetch and map folders const labels = await fetchGmailLabels(this.gToken); const folderMappings = await mapGmailLabelsToFolders(client, labels); let successful = 0; let failed = 0; const batchSize = this.options.batchSize!; for (let i = 0; i < emails.length; i += batchSize) { if (this.shouldStop) { this.progress.message = "Migration stopped by user"; this.progress.status = 'idle'; this.notifyProgress(); break; } await this.waitIfPaused(); const batch = emails.slice(i, i + batchSize); for (const email of batch) { if (this.shouldStop) break; await this.waitIfPaused(); const folderId = getFolderIdForLabel(folderMappings, email.labels || []); let retries = 0; let success = false; while (retries < this.options.retryAttempts! && !success) { try { await uploadEmail(client, email, folderId); successful++; success = true; } catch (error: any) { retries++; if (retries >= this.options.retryAttempts!) { console.error(`Failed to migrate email ${email.id} after ${retries} attempts:`, error.message); failed++; } else { await new Promise(resolve => setTimeout(resolve, this.options.retryDelay!)); } } } this.progress.current = i + batch.indexOf(email) + 1; this.progress.message = `Migrated ${this.progress.current}/${this.progress.total} emails`; this.notifyProgress(); } // Delay between batches to avoid rate limiting if (i + batchSize < emails.length) { await new Promise(resolve => setTimeout(resolve, this.options.delayBetweenBatches!)); } } this.progress.status = 'completed'; this.progress.message = `Migration completed! ${successful} successful, ${failed} failed`; this.notifyProgress(); return { total: emails.length, successful, failed }; } catch (error: any) { this.progress.status = 'error'; this.progress.error = error.message; this.progress.message = `Migration failed: ${error.message}`; this.notifyProgress(); throw error; } } } export async function migrateUser( gToken: GmailToken, msToken: MSToken, onProgress: (current: number, total: number, message?: string) => void, options?: MigrationOptions ): Promise<{ total: number; successful: number; failed: number }> { const migrator = new Migrator(gToken, msToken, options); migrator.onProgress((progress) => { onProgress(progress.current, progress.total, progress.message); }); return await migrator.migrate(); }