feat: complete migration logic

This commit is contained in:
sethburkart123
2024-11-13 09:27:14 +11:00
parent 172021d0d0
commit e5c05c0dca
7 changed files with 242 additions and 69 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
const base64ToBlob = (base64: string, contentType: string = ''): Blob => {
const byteCharacters = atob(base64.split(',')[1]);
const byteCharacters = atob(base64);
const byteArrays: Uint8Array[] = [];
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
+122
View File
@@ -0,0 +1,122 @@
import browser from 'webextension-polyfill';
import base64ToBlob from './base64ToBlob';
import { delay } from './delay';
import { openDatabase, writeData } from '@/interface/hooks/BackgroundDataLoader';
import { backgroundUpdates } from '@/interface/hooks/BackgroundUpdates';
const MIGRATION_STATE_KEY = 'background_migration_state';
interface MigrationState {
lastProcessedId: string | null;
total: number;
processed: number;
completed: boolean;
}
export const migrateBackgrounds = async (): Promise<void> => {
console.info('Migrating backgrounds...');
const savedState = localStorage.getItem(MIGRATION_STATE_KEY);
const migrationState: MigrationState = savedState
? JSON.parse(savedState)
: { lastProcessedId: null, total: 0, processed: 0, completed: false };
if (migrationState.completed) {
console.info('Migration already completed');
return;
}
return new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
const handleMessage = async (event: MessageEvent) => {
if (event.source !== iframe.contentWindow) return;
switch (event.data.type) {
case 'GET_LAST_PROCESSED_ID':
iframe.contentWindow?.postMessage({
type: 'LAST_PROCESSED_ID',
id: migrationState.lastProcessedId
}, '*');
break;
case 'BACKGROUND_DATA':
try {
const { id, data, mediaType, total, processed } = event.data.payload;
const mimeType = mediaType === 'image' ? 'image/png' : 'video/mp4';
const blob = base64ToBlob(data, mimeType);
await storeBackground({
id,
blob,
type: mediaType
});
migrationState.lastProcessedId = id;
migrationState.total = total;
migrationState.processed = processed;
localStorage.setItem(MIGRATION_STATE_KEY, JSON.stringify(migrationState));
console.log(`Migrated background: ${id} (${processed}/${total})`);
} catch (error) {
console.error('Error handling background data:', error);
}
break;
case 'MIGRATION_COMPLETE':
console.info('Migration completed successfully');
migrationState.completed = true;
localStorage.setItem(MIGRATION_STATE_KEY, JSON.stringify(migrationState));
window.removeEventListener('message', handleMessage);
iframe.remove();
resolve();
break;
case 'MIGRATION_ERROR':
console.error('Migration failed:', event.data.error);
window.removeEventListener('message', handleMessage);
iframe.remove();
reject(new Error(event.data.error));
break;
}
};
window.addEventListener('message', handleMessage);
const startPinging = () => {
const pingInterval = setInterval(() => {
iframe.contentWindow?.postMessage({ type: 'PING' }, '*');
}, 500);
const messageHandler = (event: MessageEvent) => {
if (event.source === iframe.contentWindow) {
clearInterval(pingInterval);
window.removeEventListener('message', messageHandler);
iframe.contentWindow?.postMessage({ type: 'START_MIGRATION' }, '*');
}
};
window.addEventListener('message', messageHandler);
};
iframe.src = browser.runtime.getURL('seqta/utils/migration/migrate.html');
document.body.appendChild(iframe);
startPinging();
});
};
const storeBackground = async (data: {
id: string;
blob: Blob;
type: 'image' | 'video';
}): Promise<void> => {
try {
await openDatabase();
await writeData(data.id, data.type, data.blob);
backgroundUpdates.triggerUpdate();
} catch (error) {
console.error('Error storing background:', error);
throw error;
}
};
+10
View File
@@ -0,0 +1,10 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Background Migration</title>
</head>
<body>
<script type="module" src="./migration-iframe.ts"></script>
</body>
</html>
@@ -0,0 +1,103 @@
// This goes in your migration.html's script
interface Data {
id: string;
blob: Blob;
type: 'image' | 'video';
}
const openDB = (): Promise<IDBDatabase> => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
};
const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result as string;
resolve(base64.split(',')[1]); // Remove data URL prefix
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(blob);
});
};
const getAllBackgrounds = async (): Promise<Data[]> => {
const db = await openDB();
const tx = db.transaction('backgrounds', 'readonly');
const store = tx.objectStore('backgrounds');
const request = store.getAll();
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
const startMigration = async () => {
try {
console.info('Starting background extraction...');
const backgrounds = await getAllBackgrounds();
console.info(`Found ${backgrounds.length} backgrounds`);
window.parent.postMessage({ type: 'GET_LAST_PROCESSED_ID' }, '*');
const lastProcessedId = await new Promise<string | null>(resolve => {
const handler = (event: MessageEvent) => {
if (event.data.type === 'LAST_PROCESSED_ID') {
window.removeEventListener('message', handler);
resolve(event.data.id);
}
};
window.addEventListener('message', handler);
});
const remainingBackgrounds = lastProcessedId
? backgrounds.slice(backgrounds.findIndex(b => b.id === lastProcessedId) + 1)
: backgrounds;
console.info(`Processing ${remainingBackgrounds.length} remaining backgrounds`);
for (let i = 0; i < remainingBackgrounds.length; i++) {
const background = remainingBackgrounds[i];
const base64Data = await blobToBase64(background.blob);
window.parent.postMessage({
type: 'BACKGROUND_DATA',
payload: {
id: background.id,
data: base64Data,
mediaType: background.type,
total: backgrounds.length,
processed: i + 1
}
}, '*');
await new Promise(resolve => setTimeout(resolve, 100));
}
window.parent.postMessage({ type: 'MIGRATION_COMPLETE' }, '*');
} catch (error: any) {
console.error('Extraction failed:', error);
window.parent.postMessage({
type: 'MIGRATION_ERROR',
error: error.message || 'Unknown error'
}, '*');
}
};
window.addEventListener('message', (event) => {
switch (event.data.type) {
case 'PING':
window.parent.postMessage({ type: 'PONG' }, '*');
break;
case 'START_MIGRATION':
startMigration();
break;
}
});