diff --git a/src/SEQTA.ts b/src/SEQTA.ts index 2d9911c5..0043c620 100644 --- a/src/SEQTA.ts +++ b/src/SEQTA.ts @@ -38,6 +38,7 @@ import documentLoadCSS from '@/css/documentload.scss?inline' import renderSvelte from '@/interface/main' import Settings from '@/interface/pages/settings.svelte' import { settingsPopup } from './interface/hooks/SettingsPopup' +import { migrateBackgrounds } from './seqta/utils/migrateBackgrounds' let SettingsClicked = false export let MenuOptionsOpen = false @@ -458,7 +459,10 @@ export async function finishLoad() { if (settingsState.justupdated && !document.getElementById('whatsnewbk')) { OpenWhatsNewPopup(); + + /* Background Migration script */ } + migrateBackgrounds(); } async function DeleteWhatsNew() { diff --git a/src/background.ts b/src/background.ts index f727dc2d..e95640ce 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,20 +1,5 @@ import browser from 'webextension-polyfill' import type { SettingsState } from "@/types/storage"; -import { blobToBase64 } from './seqta/utils/blobToBase64'; - -interface Data { - id: string; - blob: Blob; - type: 'image' | 'video'; -} - -interface DatabaseEventTarget extends EventTarget { - result: IDBDatabase; -} - -interface DatabaseEvent extends Event { - target: DatabaseEventTarget; -} export const openDB = () => { return new Promise((resolve, reject) => { @@ -284,63 +269,11 @@ async function UpdateCurrentValues() { } } -const getAllBackgrounds = async (): Promise => { - const db = await openDB() as IDBDatabase; - 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 sendBackgroundToTab = async (background: Data): Promise => { - const base64Data = await blobToBase64(background.blob); - - // Get the current active tab - const [tab] = await browser.tabs.query({ active: true, currentWindow: true }); - if (!tab?.id) return; - - // Send message to the tab - await browser.tabs.sendMessage(tab.id, { - type: 'MIGRATE_BACKGROUND', - payload: { - id: background.id, - data: base64Data, - mediaType: background.type - } - }); -}; - -const migrateBackgrounds = async (): Promise => { - try { - console.log('Starting background migration...'); - const backgrounds = await getAllBackgrounds(); - console.log(`Found ${backgrounds.length} backgrounds to migrate`); - - // Process backgrounds sequentially - for (const background of backgrounds) { - console.log(`Migrating background: ${background.id}`); - await sendBackgroundToTab(background); - console.log(`Successfully migrated background: ${background.id}`); - } - - console.log('Migration completed successfully'); - } catch (error) { - console.error('Migration failed:', error); - throw error; - } -}; - - browser.runtime.onInstalled.addListener(function (event) { browser.storage.local.remove(['justupdated']); browser.storage.local.remove(['data']); UpdateCurrentValues(); - migrateBackgrounds(); if ( event.reason == 'install', event.reason == 'update' ) { browser.storage.local.set({ justupdated: true }); } diff --git a/src/seqta/utils/base64ToBlob.ts b/src/seqta/utils/base64ToBlob.ts index 00410d4f..3a428e3b 100644 --- a/src/seqta/utils/base64ToBlob.ts +++ b/src/seqta/utils/base64ToBlob.ts @@ -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) { diff --git a/src/seqta/utils/migrateBackgrounds.ts b/src/seqta/utils/migrateBackgrounds.ts new file mode 100644 index 00000000..0c796125 --- /dev/null +++ b/src/seqta/utils/migrateBackgrounds.ts @@ -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 => { + 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 => { + try { + await openDatabase(); + await writeData(data.id, data.type, data.blob); + backgroundUpdates.triggerUpdate(); + } catch (error) { + console.error('Error storing background:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/seqta/utils/migration/migrate.html b/src/seqta/utils/migration/migrate.html new file mode 100644 index 00000000..94c36953 --- /dev/null +++ b/src/seqta/utils/migration/migrate.html @@ -0,0 +1,10 @@ + + + + + Background Migration + + + + + \ No newline at end of file diff --git a/src/seqta/utils/migration/migration-iframe.ts b/src/seqta/utils/migration/migration-iframe.ts new file mode 100644 index 00000000..45bfe98c --- /dev/null +++ b/src/seqta/utils/migration/migration-iframe.ts @@ -0,0 +1,103 @@ +// This goes in your migration.html's script +interface Data { + id: string; + blob: Blob; + type: 'image' | 'video'; +} + +const openDB = (): Promise => { + 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 => { + 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 => { + 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(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; + } +}); \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 5db69c70..1e8ead94 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -60,7 +60,8 @@ export default defineConfig({ minify: false, rollupOptions: { input: { - settings: join(__dirname, 'src', 'interface', 'index.html') + settings: join(__dirname, 'src', 'interface', 'index.html'), + migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html') } } }