Compare commits
No commits in common. "6fa04317b68d76b21c4cf54eb868d91936b114ca" and "3eeef3fc9af94b586dcab5dac6bfbeb12d638d45" have entirely different histories.
6fa04317b6
...
3eeef3fc9a
@ -1,141 +1,95 @@
|
|||||||
const EMPTY32BYTES = String('').padStart(64, '0');
|
const EMPTY32BYTES = String('').padStart(64, '0');
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// SERVICE WORKER LIFECYCLE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
event.waitUntil(self.skipWaiting());
|
event.waitUntil(self.skipWaiting()); // Activate worker immediately
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener('activate', (event) => {
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim()); // Become available to all pages
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================
|
// Event listener for messages from clients
|
||||||
// MESSAGE HANDLER
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
self.addEventListener('message', async (event) => {
|
self.addEventListener('message', async (event) => {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
console.log('[Service Worker] Message received:', data.type);
|
console.log(data);
|
||||||
|
|
||||||
if (data.type === 'SCAN') {
|
if (data.type === 'SCAN') {
|
||||||
try {
|
try {
|
||||||
const myProcessesId = data.payload;
|
const myProcessesId = data.payload;
|
||||||
if (myProcessesId && myProcessesId.length != 0) {
|
if (myProcessesId && myProcessesId.length != 0) {
|
||||||
const scanResult = await scanMissingData(myProcessesId, event.source);
|
const toDownload = await scanMissingData(myProcessesId);
|
||||||
|
if (toDownload.length != 0) {
|
||||||
if (scanResult.toDownload.length != 0) {
|
console.log('Sending TO_DOWNLOAD message');
|
||||||
console.log('[Service Worker] Sending TO_DOWNLOAD message');
|
event.source.postMessage({ type: 'TO_DOWNLOAD', data: toDownload});
|
||||||
event.source.postMessage({ type: 'TO_DOWNLOAD', data: scanResult.toDownload });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanResult.diffsToCreate.length > 0) {
|
|
||||||
console.log('[Service Worker] Sending DIFFS_TO_CREATE message');
|
|
||||||
event.source.postMessage({ type: 'DIFFS_TO_CREATE', data: scanResult.diffsToCreate });
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
event.source.postMessage({ status: 'error', message: 'Empty lists' });
|
event.source.postMessage({ status: 'error', message: 'Empty lists' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Service Worker] Scan error:', error);
|
|
||||||
event.source.postMessage({ status: 'error', message: error.message });
|
event.source.postMessage({ status: 'error', message: error.message });
|
||||||
}
|
}
|
||||||
|
} else if (data.type === 'ADD_OBJECT') {
|
||||||
|
try {
|
||||||
|
const { storeName, object, key } = data.payload;
|
||||||
|
const db = await openDatabase();
|
||||||
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
await store.put(object, key);
|
||||||
|
} else {
|
||||||
|
await store.put(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.ports[0].postMessage({ status: 'success', message: '' });
|
||||||
|
} catch (error) {
|
||||||
|
event.ports[0].postMessage({ status: 'error', message: error.message });
|
||||||
|
}
|
||||||
|
} else if (data.type === 'BATCH_WRITING') {
|
||||||
|
const { storeName, objects } = data.payload;
|
||||||
|
const db = await openDatabase();
|
||||||
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
|
||||||
|
for (const { key, object } of objects) {
|
||||||
|
if (key) {
|
||||||
|
await store.put(object, key);
|
||||||
|
} else {
|
||||||
|
await store.put(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.done;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================
|
async function scanMissingData(processesToScan) {
|
||||||
// DATABASE COMMUNICATION
|
console.log('Scanning for missing data...');
|
||||||
// ============================================
|
const myProcesses = await getProcesses(processesToScan);
|
||||||
|
|
||||||
async function requestFromMainThread(client, action, payload) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const messageId = `sw_${Date.now()}_${Math.random()}`;
|
|
||||||
|
|
||||||
const messageHandler = (event) => {
|
|
||||||
if (event.data.id === messageId) {
|
|
||||||
self.removeEventListener('message', messageHandler);
|
|
||||||
if (event.data.type === 'DB_RESPONSE') {
|
|
||||||
resolve(event.data.result);
|
|
||||||
} else if (event.data.type === 'DB_ERROR') {
|
|
||||||
reject(new Error(event.data.error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.addEventListener('message', messageHandler);
|
|
||||||
|
|
||||||
client.postMessage({
|
|
||||||
type: 'DB_REQUEST',
|
|
||||||
id: messageId,
|
|
||||||
action,
|
|
||||||
payload
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
self.removeEventListener('message', messageHandler);
|
|
||||||
reject(new Error('Database request timeout'));
|
|
||||||
}, 10000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// SCAN LOGIC
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function scanMissingData(processesToScan, client) {
|
|
||||||
console.log('[Service Worker] Scanning for missing data...');
|
|
||||||
|
|
||||||
const myProcesses = await requestFromMainThread(client, 'GET_MULTIPLE_OBJECTS', {
|
|
||||||
storeName: 'processes',
|
|
||||||
keys: processesToScan
|
|
||||||
});
|
|
||||||
|
|
||||||
let toDownload = new Set();
|
let toDownload = new Set();
|
||||||
let diffsToCreate = [];
|
// Iterate on each process
|
||||||
|
|
||||||
if (myProcesses && myProcesses.length != 0) {
|
if (myProcesses && myProcesses.length != 0) {
|
||||||
for (const process of myProcesses) {
|
for (const process of myProcesses) {
|
||||||
|
// Iterate on states
|
||||||
const firstState = process.states[0];
|
const firstState = process.states[0];
|
||||||
const processId = firstState.commited_in;
|
const processId = firstState.commited_in;
|
||||||
for (const state of process.states) {
|
for (const state of process.states) {
|
||||||
if (state.state_id === EMPTY32BYTES) continue;
|
if (state.state_id === EMPTY32BYTES) continue;
|
||||||
|
// iterate on pcd_commitment
|
||||||
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
||||||
|
// Skip public fields
|
||||||
if (state.public_data[field] !== undefined || field === 'roles') continue;
|
if (state.public_data[field] !== undefined || field === 'roles') continue;
|
||||||
|
// Check if we have the data in db
|
||||||
const existingData = await requestFromMainThread(client, 'GET_OBJECT', {
|
const existingData = await getBlob(hash);
|
||||||
storeName: 'data',
|
|
||||||
key: hash
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingData) {
|
if (!existingData) {
|
||||||
toDownload.add(hash);
|
toDownload.add(hash);
|
||||||
|
// We also add an entry in diff, in case it doesn't already exist
|
||||||
const existingDiff = await requestFromMainThread(client, 'GET_OBJECT', {
|
await addDiff(processId, state.state_id, hash, state.roles, field);
|
||||||
storeName: 'diffs',
|
|
||||||
key: hash
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingDiff) {
|
|
||||||
diffsToCreate.push({
|
|
||||||
process_id: processId,
|
|
||||||
state_id: state.state_id,
|
|
||||||
value_commitment: hash,
|
|
||||||
roles: state.roles,
|
|
||||||
field: field,
|
|
||||||
description: null,
|
|
||||||
previous_value: null,
|
|
||||||
new_value: null,
|
|
||||||
notify_user: false,
|
|
||||||
need_validation: false,
|
|
||||||
validation_status: 'None'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// We remove it if we have it in the set
|
||||||
if (toDownload.delete(hash)) {
|
if (toDownload.delete(hash)) {
|
||||||
console.log(`[Service Worker] Removing ${hash} from the set`);
|
console.log(`Removing ${hash} from the set`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,10 +97,185 @@ async function scanMissingData(processesToScan, client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Service Worker] Scan complete:', { toDownload: toDownload.size, diffsToCreate: diffsToCreate.length });
|
console.log(toDownload);
|
||||||
return {
|
return Array.from(toDownload);
|
||||||
toDownload: Array.from(toDownload),
|
|
||||||
diffsToCreate: diffsToCreate
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openDatabase() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = indexedDB.open('4nk', 1);
|
||||||
|
request.onerror = (event) => {
|
||||||
|
reject(request.error);
|
||||||
|
};
|
||||||
|
request.onsuccess = (event) => {
|
||||||
|
resolve(request.result);
|
||||||
|
};
|
||||||
|
request.onupgradeneeded = (event) => {
|
||||||
|
const db = event.target.result;
|
||||||
|
if (!db.objectStoreNames.contains('wallet')) {
|
||||||
|
db.createObjectStore('wallet', { keyPath: 'pre_id' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get all processes because it is asynchronous
|
||||||
|
async function getAllProcesses() {
|
||||||
|
const db = await openDatabase();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!db) {
|
||||||
|
reject(new Error('Database is not available'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tx = db.transaction('processes', 'readonly');
|
||||||
|
const store = tx.objectStore('processes');
|
||||||
|
const request = store.getAll();
|
||||||
|
|
||||||
|
request.onsuccess = () => {
|
||||||
|
resolve(request.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = () => {
|
||||||
|
reject(request.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getProcesses(processIds) {
|
||||||
|
if (!processIds || processIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await openDatabase();
|
||||||
|
if (!db) {
|
||||||
|
throw new Error('Database is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = db.transaction('processes', 'readonly');
|
||||||
|
const store = tx.objectStore('processes');
|
||||||
|
|
||||||
|
const requests = Array.from(processIds).map((processId) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const request = store.get(processId);
|
||||||
|
request.onsuccess = () => resolve(request.result);
|
||||||
|
request.onerror = () => {
|
||||||
|
console.error(`Error fetching process ${processId}:`, request.error);
|
||||||
|
resolve(undefined);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.all(requests);
|
||||||
|
return results.filter(result => result !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllDiffsNeedValidation() {
|
||||||
|
const db = await openDatabase();
|
||||||
|
|
||||||
|
const allProcesses = await getAllProcesses();
|
||||||
|
const tx = db.transaction('diffs', 'readonly');
|
||||||
|
const store = tx.objectStore('diffs');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = store.getAll();
|
||||||
|
request.onsuccess = (event) => {
|
||||||
|
const allItems = event.target.result;
|
||||||
|
const itemsWithFlag = allItems.filter((item) => item.need_validation);
|
||||||
|
|
||||||
|
const processMap = {};
|
||||||
|
|
||||||
|
for (const diff of itemsWithFlag) {
|
||||||
|
const currentProcess = allProcesses.find((item) => {
|
||||||
|
return item.states.some((state) => state.merkle_root === diff.new_state_merkle_root);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentProcess) {
|
||||||
|
const processKey = currentProcess.merkle_root;
|
||||||
|
|
||||||
|
if (!processMap[processKey]) {
|
||||||
|
processMap[processKey] = {
|
||||||
|
process: currentProcess.states,
|
||||||
|
processId: currentProcess.key,
|
||||||
|
diffs: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
processMap[processKey].diffs.push(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = Object.values(processMap).map((entry) => {
|
||||||
|
const diffs = []
|
||||||
|
for(const state of entry.process) {
|
||||||
|
const filteredDiff = entry.diffs.filter(diff => diff.new_state_merkle_root === state.merkle_root);
|
||||||
|
if(filteredDiff && filteredDiff.length) {
|
||||||
|
diffs.push(filteredDiff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
process: entry.process,
|
||||||
|
processId: entry.processId,
|
||||||
|
diffs: diffs,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(results);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = (event) => {
|
||||||
|
reject(event.target.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBlob(hash) {
|
||||||
|
const db = await openDatabase();
|
||||||
|
const storeName = 'data';
|
||||||
|
const tx = db.transaction(storeName, 'readonly');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
const getRequest = store.get(hash);
|
||||||
|
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDiff(processId, stateId, hash, roles, field) {
|
||||||
|
const db = await openDatabase();
|
||||||
|
const storeName = 'diffs';
|
||||||
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
|
||||||
|
// Check if the diff already exists
|
||||||
|
const existingDiff = await new Promise((resolve, reject) => {
|
||||||
|
const getRequest = store.get(hash);
|
||||||
|
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingDiff) {
|
||||||
|
const newDiff = {
|
||||||
|
process_id: processId,
|
||||||
|
state_id: stateId,
|
||||||
|
value_commitment: hash,
|
||||||
|
roles: roles,
|
||||||
|
field: field,
|
||||||
|
description: null,
|
||||||
|
previous_value: null,
|
||||||
|
new_value: null,
|
||||||
|
notify_user: false,
|
||||||
|
need_validation: false,
|
||||||
|
validation_status: 'None'
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertResult = await new Promise((resolve, reject) => {
|
||||||
|
const putRequest = store.put(newDiff);
|
||||||
|
putRequest.onsuccess = () => resolve(putRequest.result);
|
||||||
|
putRequest.onerror = () => reject(putRequest.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return insertResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingDiff;
|
||||||
|
}
|
||||||
|
|||||||
@ -9,11 +9,12 @@ async function bootstrap() {
|
|||||||
console.log("🚀 Démarrage de l'application 4NK...");
|
console.log("🚀 Démarrage de l'application 4NK...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Initialisation des Services (WASM, Sockets, Database...)
|
// 1. Initialisation de la Base de données
|
||||||
const services = await Services.getInstance();
|
const db = await Database.getInstance();
|
||||||
|
db.registerServiceWorker('/database.worker.js');
|
||||||
|
|
||||||
// 2. Initialisation de la base de données (Web Worker + Service Worker)
|
// 2. Initialisation des Services (WASM, Sockets...)
|
||||||
await Database.getInstance();
|
const services = await Services.getInstance();
|
||||||
|
|
||||||
// Injection du Header dans le slot prévu dans index.html
|
// Injection du Header dans le slot prévu dans index.html
|
||||||
const headerSlot = document.getElementById('header-slot');
|
const headerSlot = document.getElementById('header-slot');
|
||||||
|
|||||||
@ -1,137 +1,158 @@
|
|||||||
import Services from './service';
|
import Services from './service';
|
||||||
|
|
||||||
/**
|
|
||||||
* Database service managing IndexedDB operations via Web Worker and Service Worker
|
|
||||||
*/
|
|
||||||
export class Database {
|
export class Database {
|
||||||
// ============================================
|
|
||||||
// PRIVATE PROPERTIES
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
private static instance: Database;
|
private static instance: Database;
|
||||||
|
private db: IDBDatabase | null = null;
|
||||||
|
private dbName: string = '4nk';
|
||||||
|
private dbVersion: number = 1;
|
||||||
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
|
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
|
||||||
|
private messageChannel: MessageChannel | null = null;
|
||||||
|
private messageChannelForGet: MessageChannel | null = null;
|
||||||
private serviceWorkerCheckIntervalId: number | null = null;
|
private serviceWorkerCheckIntervalId: number | null = null;
|
||||||
private indexedDBWorker: Worker | null = null;
|
private storeDefinitions = {
|
||||||
private messageIdCounter: number = 0;
|
AnkLabels: {
|
||||||
private pendingMessages: Map<number, { resolve: (value: any) => void; reject: (error: any) => void }> = new Map();
|
name: 'labels',
|
||||||
|
options: { keyPath: 'emoji' },
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
AnkWallet: {
|
||||||
|
name: 'wallet',
|
||||||
|
options: { keyPath: 'pre_id' },
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
AnkProcess: {
|
||||||
|
name: 'processes',
|
||||||
|
options: {},
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
AnkSharedSecrets: {
|
||||||
|
name: 'shared_secrets',
|
||||||
|
options: {},
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
AnkUnconfirmedSecrets: {
|
||||||
|
name: 'unconfirmed_secrets',
|
||||||
|
options: { autoIncrement: true },
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
AnkPendingDiffs: {
|
||||||
|
name: 'diffs',
|
||||||
|
options: { keyPath: 'value_commitment' },
|
||||||
|
indices: [
|
||||||
|
{ name: 'byStateId', keyPath: 'state_id', options: { unique: false } },
|
||||||
|
{ name: 'byNeedValidation', keyPath: 'need_validation', options: { unique: false } },
|
||||||
|
{ name: 'byStatus', keyPath: 'validation_status', options: { unique: false } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
AnkData: {
|
||||||
|
name: 'data',
|
||||||
|
options: {},
|
||||||
|
indices: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================
|
// Private constructor to prevent direct instantiation from outside
|
||||||
// INITIALIZATION & SINGLETON
|
private constructor() {}
|
||||||
// ============================================
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.initIndexedDBWorker();
|
|
||||||
this.initServiceWorker();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Method to access the singleton instance of Database
|
||||||
public static async getInstance(): Promise<Database> {
|
public static async getInstance(): Promise<Database> {
|
||||||
if (!Database.instance) {
|
if (!Database.instance) {
|
||||||
Database.instance = new Database();
|
Database.instance = new Database();
|
||||||
await Database.instance.waitForWorkerReady();
|
await Database.instance.init();
|
||||||
}
|
}
|
||||||
return Database.instance;
|
return Database.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// Initialize the database
|
||||||
// INDEXEDDB WEB WORKER
|
private async init(): Promise<void> {
|
||||||
// ============================================
|
|
||||||
|
|
||||||
private initIndexedDBWorker(): void {
|
|
||||||
this.indexedDBWorker = new Worker(new URL('../workers/indexeddb.worker.js', import.meta.url), { type: 'module' });
|
|
||||||
|
|
||||||
this.indexedDBWorker.onmessage = (event) => {
|
|
||||||
const { id, type, result, error } = event.data;
|
|
||||||
const pending = this.pendingMessages.get(id);
|
|
||||||
|
|
||||||
if (pending) {
|
|
||||||
this.pendingMessages.delete(id);
|
|
||||||
|
|
||||||
if (type === 'SUCCESS') {
|
|
||||||
pending.resolve(result);
|
|
||||||
} else if (type === 'ERROR') {
|
|
||||||
pending.reject(new Error(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.indexedDBWorker.onerror = (error) => {
|
|
||||||
console.error('[Database] IndexedDB Worker error:', error);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async waitForWorkerReady(): Promise<void> {
|
|
||||||
return this.sendMessageToWorker('INIT', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendMessageToWorker<T = any>(type: string, payload: any): Promise<T> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.indexedDBWorker) {
|
const request = indexedDB.open(this.dbName, this.dbVersion);
|
||||||
reject(new Error('IndexedDB Worker not initialized'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = this.messageIdCounter++;
|
request.onupgradeneeded = () => {
|
||||||
this.pendingMessages.set(id, { resolve, reject });
|
const db = request.result;
|
||||||
|
|
||||||
this.indexedDBWorker.postMessage({ type, payload, id });
|
Object.values(this.storeDefinitions).forEach(({ name, options, indices }) => {
|
||||||
|
if (!db.objectStoreNames.contains(name)) {
|
||||||
|
let store = db.createObjectStore(name, options as IDBObjectStoreParameters);
|
||||||
|
|
||||||
// Timeout de sécurité (30 secondes)
|
indices.forEach(({ name, keyPath, options }) => {
|
||||||
setTimeout(() => {
|
store.createIndex(name, keyPath, options);
|
||||||
if (this.pendingMessages.has(id)) {
|
});
|
||||||
this.pendingMessages.delete(id);
|
}
|
||||||
reject(new Error(`Worker message timeout for type: ${type}`));
|
});
|
||||||
}
|
};
|
||||||
}, 30000);
|
|
||||||
|
request.onsuccess = async () => {
|
||||||
|
this.db = request.result;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onerror = () => {
|
||||||
|
console.error('Database error:', request.error);
|
||||||
|
reject(request.error);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
public async getDb(): Promise<IDBDatabase> {
|
||||||
// SERVICE WORKER
|
if (!this.db) {
|
||||||
// ============================================
|
await this.init();
|
||||||
|
}
|
||||||
private initServiceWorker(): void {
|
return this.db!;
|
||||||
this.registerServiceWorker('/database.worker.js');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async registerServiceWorker(path: string): Promise<void> {
|
public getStoreList(): { [key: string]: string } {
|
||||||
|
const objectList: { [key: string]: string } = {};
|
||||||
|
Object.keys(this.storeDefinitions).forEach((key) => {
|
||||||
|
objectList[key] = this.storeDefinitions[key as keyof typeof this.storeDefinitions].name;
|
||||||
|
});
|
||||||
|
return objectList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async registerServiceWorker(path: string) {
|
||||||
if (!('serviceWorker' in navigator)) return;
|
if (!('serviceWorker' in navigator)) return;
|
||||||
console.log('[Database] Initializing Service Worker:', path);
|
console.log('[Database] Initialisation du Service Worker sur :', path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 1. NETTOYAGE DES ANCIENS WORKERS (ZOMBIES)
|
||||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||||
|
|
||||||
for (const registration of registrations) {
|
for (const registration of registrations) {
|
||||||
const scriptURL = registration.active?.scriptURL || registration.installing?.scriptURL || registration.waiting?.scriptURL;
|
const scriptURL = registration.active?.scriptURL || registration.installing?.scriptURL || registration.waiting?.scriptURL;
|
||||||
const scope = registration.scope;
|
const scope = registration.scope;
|
||||||
|
|
||||||
|
// On détecte spécifiquement l'ancien dossier qui pose problème
|
||||||
|
// L'erreur mentionne : scope ('.../src/service-workers/')
|
||||||
if (scope.includes('/src/service-workers/') || (scriptURL && scriptURL.includes('/src/service-workers/'))) {
|
if (scope.includes('/src/service-workers/') || (scriptURL && scriptURL.includes('/src/service-workers/'))) {
|
||||||
console.warn(`[Database] Removing old Service Worker (${scope})`);
|
console.warn(`[Database] 🚨 ANCIEN Service Worker détecté (${scope}). Suppression immédiate...`);
|
||||||
await registration.unregister();
|
await registration.unregister();
|
||||||
|
// On continue la boucle, ne pas retourner ici, il faut installer le nouveau après
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. INSTALLATION DU NOUVEAU WORKER (PROPRE)
|
||||||
|
// On vérifie s'il est déjà installé à la BONNE adresse
|
||||||
const existingValidWorker = registrations.find((r) => {
|
const existingValidWorker = registrations.find((r) => {
|
||||||
const url = r.active?.scriptURL || r.installing?.scriptURL || r.waiting?.scriptURL;
|
const url = r.active?.scriptURL || r.installing?.scriptURL || r.waiting?.scriptURL;
|
||||||
return url && url.endsWith(path.replace(/^\//,''));
|
// On compare la fin de l'URL pour éviter les soucis http/https/localhost
|
||||||
|
return url && url.endsWith(path.replace(/^\//, ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingValidWorker) {
|
if (!existingValidWorker) {
|
||||||
console.log('[Database] Registering new Service Worker');
|
console.log('[Database] Enregistrement du nouveau Service Worker...');
|
||||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, { type: 'module', scope: '/' });
|
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, { type: 'module', scope: '/' });
|
||||||
} else {
|
} else {
|
||||||
console.log('[Database] Service Worker already active');
|
console.log('[Database] Service Worker déjà actif et valide.');
|
||||||
this.serviceWorkerRegistration = existingValidWorker;
|
this.serviceWorkerRegistration = existingValidWorker;
|
||||||
await this.serviceWorkerRegistration.update();
|
await this.serviceWorkerRegistration.update();
|
||||||
}
|
}
|
||||||
|
// Set up listeners
|
||||||
navigator.serviceWorker.addEventListener('message', async (event) => {
|
navigator.serviceWorker.addEventListener('message', async (event) => {
|
||||||
if (event.data.type === 'DB_REQUEST') {
|
// console.log('Received message from service worker:', event.data);
|
||||||
await this.handleDatabaseRequest(event.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.handleServiceWorkerMessage(event.data);
|
await this.handleServiceWorkerMessage(event.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Periodic check
|
||||||
if (this.serviceWorkerCheckIntervalId) clearInterval(this.serviceWorkerCheckIntervalId);
|
if (this.serviceWorkerCheckIntervalId) clearInterval(this.serviceWorkerCheckIntervalId);
|
||||||
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
|
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
|
||||||
const activeWorker = this.serviceWorkerRegistration?.active || (await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration!));
|
const activeWorker = this.serviceWorkerRegistration?.active || (await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration!));
|
||||||
@ -142,10 +163,11 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Database] Service Worker error:', error);
|
console.error('[Database] 💥 Erreur critique Service Worker:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to wait for service worker activation
|
||||||
private async waitForServiceWorkerActivation(registration: ServiceWorkerRegistration): Promise<ServiceWorker | null> {
|
private async waitForServiceWorkerActivation(registration: ServiceWorkerRegistration): Promise<ServiceWorker | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (registration.active) {
|
if (registration.active) {
|
||||||
@ -162,11 +184,13 @@ export class Database {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkForUpdates(): Promise<void> {
|
private async checkForUpdates() {
|
||||||
if (this.serviceWorkerRegistration) {
|
if (this.serviceWorkerRegistration) {
|
||||||
|
// Check for updates to the service worker
|
||||||
try {
|
try {
|
||||||
await this.serviceWorkerRegistration.update();
|
await this.serviceWorkerRegistration.update();
|
||||||
|
|
||||||
|
// If there's a new worker waiting, activate it immediately
|
||||||
if (this.serviceWorkerRegistration.waiting) {
|
if (this.serviceWorkerRegistration.waiting) {
|
||||||
this.serviceWorkerRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
this.serviceWorkerRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||||
}
|
}
|
||||||
@ -176,85 +200,24 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// SERVICE WORKER MESSAGE HANDLERS
|
|
||||||
// ============================================
|
|
||||||
private async handleDatabaseRequest(request: any): Promise<void> {
|
|
||||||
const { id, action, payload } = request;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'GET_OBJECT':
|
|
||||||
result = await this.getObject(payload.storeName, payload.key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_MULTIPLE_OBJECTS':
|
|
||||||
result = await this.sendMessageToWorker('GET_MULTIPLE_OBJECTS', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS':
|
|
||||||
result = await this.sendMessageToWorker('GET_ALL_OBJECTS', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS_WITH_FILTER':
|
|
||||||
result = await this.sendMessageToWorker('GET_ALL_OBJECTS_WITH_FILTER', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown database action: ${action}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.serviceWorkerRegistration?.active) {
|
|
||||||
this.serviceWorkerRegistration.active.postMessage({
|
|
||||||
type: 'DB_RESPONSE',
|
|
||||||
id,
|
|
||||||
result
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('[Database] Error handling database request:', error);
|
|
||||||
|
|
||||||
if (this.serviceWorkerRegistration?.active) {
|
|
||||||
this.serviceWorkerRegistration.active.postMessage({
|
|
||||||
type: 'DB_ERROR',
|
|
||||||
id,
|
|
||||||
error: error.message || String(error)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleServiceWorkerMessage(message: any) {
|
private async handleServiceWorkerMessage(message: any) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'TO_DOWNLOAD':
|
case 'TO_DOWNLOAD':
|
||||||
await this.handleDownloadList(message.data);
|
await this.handleDownloadList(message.data);
|
||||||
break;
|
break;
|
||||||
case 'DIFFS_TO_CREATE':
|
|
||||||
await this.handleDiffsToCreate(message.data);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
console.warn('Unknown message type received from service worker:', message);
|
console.warn('Unknown message type received from service worker:', message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDiffsToCreate(diffs: any[]): Promise<void> {
|
|
||||||
console.log(`[Database] Creating ${diffs.length} diffs from Service Worker scan`);
|
|
||||||
try {
|
|
||||||
await this.saveDiffs(diffs);
|
|
||||||
console.log('[Database] Diffs created successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Database] Error creating diffs:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleDownloadList(downloadList: string[]): Promise<void> {
|
private async handleDownloadList(downloadList: string[]): Promise<void> {
|
||||||
|
// Download the missing data
|
||||||
let requestedStateId: string[] = [];
|
let requestedStateId: string[] = [];
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
for (const hash of downloadList) {
|
for (const hash of downloadList) {
|
||||||
const diff = await service.getDiffByValue(hash);
|
const diff = await service.getDiffByValue(hash);
|
||||||
if (!diff) {
|
if (!diff) {
|
||||||
|
// This should never happen
|
||||||
console.warn(`Missing a diff for hash ${hash}`);
|
console.warn(`Missing a diff for hash ${hash}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -264,15 +227,22 @@ export class Database {
|
|||||||
try {
|
try {
|
||||||
const valueBytes = await service.fetchValueFromStorage(hash);
|
const valueBytes = await service.fetchValueFromStorage(hash);
|
||||||
if (valueBytes) {
|
if (valueBytes) {
|
||||||
|
// Save data to db
|
||||||
const blob = new Blob([valueBytes], { type: 'application/octet-stream' });
|
const blob = new Blob([valueBytes], { type: 'application/octet-stream' });
|
||||||
await service.saveBlobToDb(hash, blob);
|
await service.saveBlobToDb(hash, blob);
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent('newDataReceived', {
|
new CustomEvent('newDataReceived', {
|
||||||
detail: { processId, stateId, hash },
|
detail: {
|
||||||
|
processId,
|
||||||
|
stateId,
|
||||||
|
hash,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// We first request the data from managers
|
||||||
console.log('Request data from managers of the process');
|
console.log('Request data from managers of the process');
|
||||||
|
// get the diff from db
|
||||||
if (!requestedStateId.includes(stateId)) {
|
if (!requestedStateId.includes(stateId)) {
|
||||||
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||||
requestedStateId.push(stateId);
|
requestedStateId.push(stateId);
|
||||||
@ -284,179 +254,213 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
private handleAddObjectResponse = async (event: MessageEvent) => {
|
||||||
// GENERIC INDEXEDDB OPERATIONS
|
const data = event.data;
|
||||||
// ============================================
|
console.log('Received response from service worker (ADD_OBJECT):', data);
|
||||||
|
const service = await Services.getInstance();
|
||||||
|
if (data.type === 'NOTIFICATIONS') {
|
||||||
|
service.setNotifications(data.data);
|
||||||
|
} else if (data.type === 'TO_DOWNLOAD') {
|
||||||
|
console.log(`Received missing data ${data}`);
|
||||||
|
// Download the missing data
|
||||||
|
let requestedStateId: string[] = [];
|
||||||
|
for (const hash of data.data) {
|
||||||
|
try {
|
||||||
|
const valueBytes = await service.fetchValueFromStorage(hash);
|
||||||
|
if (valueBytes) {
|
||||||
|
// Save data to db
|
||||||
|
const blob = new Blob([valueBytes], { type: 'application/octet-stream' });
|
||||||
|
await service.saveBlobToDb(hash, blob);
|
||||||
|
} else {
|
||||||
|
// We first request the data from managers
|
||||||
|
console.log('Request data from managers of the process');
|
||||||
|
// get the diff from db
|
||||||
|
const diff = await service.getDiffByValue(hash);
|
||||||
|
if (diff === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const processId = diff!.process_id;
|
||||||
|
const stateId = diff!.state_id;
|
||||||
|
const roles = diff!.roles;
|
||||||
|
if (!requestedStateId.includes(stateId)) {
|
||||||
|
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||||
|
requestedStateId.push(stateId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public async getStoreList(): Promise<{ [key: string]: string }> {
|
private handleGetObjectResponse = (event: MessageEvent) => {
|
||||||
return this.sendMessageToWorker('GET_STORE_LIST', {});
|
console.log('Received response from service worker (GET_OBJECT):', event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
// Check if the service worker is active
|
||||||
|
if (!this.serviceWorkerRegistration) {
|
||||||
|
// console.warn('Service worker registration is not ready. Waiting...');
|
||||||
|
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeWorker = await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration);
|
||||||
|
|
||||||
|
// Create a message channel for communication
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
// Handle the response from the service worker
|
||||||
|
messageChannel.port1.onmessage = (event) => {
|
||||||
|
if (event.data.status === 'success') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const error = event.data.message;
|
||||||
|
reject(new Error(error || 'Unknown error occurred while adding object'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the add object request to the service worker
|
||||||
|
try {
|
||||||
|
activeWorker?.postMessage(
|
||||||
|
{
|
||||||
|
type: 'ADD_OBJECT',
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
[messageChannel.port2],
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error(`Failed to send message to service worker: ${error}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
public batchWriting(payload: { storeName: string; objects: { key: any; object: any }[] }): Promise<void> {
|
||||||
await this.sendMessageToWorker('ADD_OBJECT', payload);
|
return new Promise(async (resolve, reject) => {
|
||||||
}
|
if (!this.serviceWorkerRegistration) {
|
||||||
|
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||||
|
}
|
||||||
|
|
||||||
public async batchWriting(payload: { storeName: string; objects: { key: any; object: any }[] }): Promise<void> {
|
const activeWorker = await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration);
|
||||||
await this.sendMessageToWorker('BATCH_WRITING', payload);
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
messageChannel.port1.onmessage = (event) => {
|
||||||
|
if (event.data.status === 'success') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const error = event.data.message;
|
||||||
|
reject(new Error(error || 'Unknown error occurred while adding objects'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
activeWorker?.postMessage(
|
||||||
|
{
|
||||||
|
type: 'BATCH_WRITING',
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
[messageChannel.port2],
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error(`Failed to send message to service worker: ${error}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getObject(storeName: string, key: string): Promise<any | null> {
|
public async getObject(storeName: string, key: string): Promise<any | null> {
|
||||||
return this.sendMessageToWorker('GET_OBJECT', { storeName, key });
|
const db = await this.getDb();
|
||||||
|
const tx = db.transaction(storeName, 'readonly');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
const getRequest = store.get(key);
|
||||||
|
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
});
|
||||||
|
return result ?? null; // Convert undefined to null
|
||||||
}
|
}
|
||||||
|
|
||||||
public async dumpStore(storeName: string): Promise<Record<string, any>> {
|
public async dumpStore(storeName: string): Promise<Record<string, any>> {
|
||||||
return this.sendMessageToWorker('DUMP_STORE', { storeName });
|
const db = await this.getDb();
|
||||||
|
const tx = db.transaction(storeName, 'readonly');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const result: Record<string, any> = {};
|
||||||
|
const cursor = store.openCursor();
|
||||||
|
|
||||||
|
cursor.onsuccess = (event) => {
|
||||||
|
const request = event.target as IDBRequest<IDBCursorWithValue | null>;
|
||||||
|
const cursor = request.result;
|
||||||
|
if (cursor) {
|
||||||
|
result[cursor.key as string] = cursor.value;
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cursor.onerror = () => {
|
||||||
|
reject(cursor.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data from IndexedDB:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteObject(storeName: string, key: string): Promise<void> {
|
public async deleteObject(storeName: string, key: string): Promise<void> {
|
||||||
await this.sendMessageToWorker('DELETE_OBJECT', { storeName, key });
|
const db = await this.getDb();
|
||||||
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
try {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const getRequest = store.delete(key);
|
||||||
|
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||||
|
getRequest.onerror = () => reject(getRequest.error);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async clearStore(storeName: string): Promise<void> {
|
public async clearStore(storeName: string): Promise<void> {
|
||||||
await this.sendMessageToWorker('CLEAR_STORE', { storeName });
|
const db = await this.getDb();
|
||||||
}
|
const tx = db.transaction(storeName, 'readwrite');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
public async requestStoreByIndex(storeName: string, indexName: string, request: string): Promise<any[]> {
|
|
||||||
return this.sendMessageToWorker('REQUEST_STORE_BY_INDEX', { storeName, indexName, request });
|
|
||||||
}
|
|
||||||
|
|
||||||
public async clearMultipleStores(storeNames: string[]): Promise<void> {
|
|
||||||
for (const storeName of storeNames) {
|
|
||||||
await this.clearStore(storeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// BUSINESS METHODS - DEVICE
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
public async saveDevice(device: any): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
const existing = await this.getObject('wallet', '1');
|
await new Promise((resolve, reject) => {
|
||||||
if (existing) {
|
const clearRequest = store.clear();
|
||||||
await this.deleteObject('wallet', '1');
|
clearRequest.onsuccess = () => resolve(clearRequest.result);
|
||||||
}
|
clearRequest.onerror = () => reject(clearRequest.error);
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'wallet',
|
|
||||||
object: { pre_id: '1', device },
|
|
||||||
key: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getDevice(): Promise<any | null> {
|
|
||||||
const result = await this.getObject('wallet', '1');
|
|
||||||
return result ? result['device'] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// BUSINESS METHODS - PROCESS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
public async saveProcess(processId: string, process: any): Promise<void> {
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'processes',
|
|
||||||
object: process,
|
|
||||||
key: processId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async saveProcessesBatch(processes: Record<string, any>): Promise<void> {
|
|
||||||
if (Object.keys(processes).length === 0) return;
|
|
||||||
|
|
||||||
await this.batchWriting({
|
|
||||||
storeName: 'processes',
|
|
||||||
objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getProcess(processId: string): Promise<any | null> {
|
|
||||||
return this.getObject('processes', processId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllProcesses(): Promise<Record<string, any>> {
|
|
||||||
return this.dumpStore('processes');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// BUSINESS METHODS - BLOBS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
public async saveBlob(hash: string, data: Blob): Promise<void> {
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'data',
|
|
||||||
object: data,
|
|
||||||
key: hash,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getBlob(hash: string): Promise<Blob | null> {
|
|
||||||
return this.getObject('data', hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// BUSINESS METHODS - DIFFS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
public async saveDiffs(diffs: any[]): Promise<void> {
|
|
||||||
if (diffs.length === 0) return;
|
|
||||||
|
|
||||||
for (const diff of diffs) {
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'diffs',
|
|
||||||
object: diff,
|
|
||||||
key: null,
|
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDiff(hash: string): Promise<any | null> {
|
// Request a store by index
|
||||||
return this.getObject('diffs', hash);
|
public async requestStoreByIndex(storeName: string, indexName: string, request: string): Promise<any[]> {
|
||||||
}
|
const db = await this.getDb();
|
||||||
|
const tx = db.transaction(storeName, 'readonly');
|
||||||
|
const store = tx.objectStore(storeName);
|
||||||
|
const index = store.index(indexName);
|
||||||
|
|
||||||
public async getAllDiffs(): Promise<Record<string, any>> {
|
try {
|
||||||
return this.dumpStore('diffs');
|
return new Promise((resolve, reject) => {
|
||||||
}
|
const getAllRequest = index.getAll(request);
|
||||||
|
getAllRequest.onsuccess = () => {
|
||||||
// ============================================
|
const allItems = getAllRequest.result;
|
||||||
// BUSINESS METHODS - SECRETS
|
const filtered = allItems.filter((item) => item.state_id === request);
|
||||||
// ============================================
|
resolve(filtered);
|
||||||
|
};
|
||||||
public async getSharedSecret(address: string): Promise<string | null> {
|
getAllRequest.onerror = () => reject(getAllRequest.error);
|
||||||
return this.getObject('shared_secrets', address);
|
});
|
||||||
}
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
public async saveSecretsBatch(unconfirmedSecrets: any[], sharedSecrets: { key: string; value: any }[]): Promise<void> {
|
|
||||||
if (unconfirmedSecrets && unconfirmedSecrets.length > 0) {
|
|
||||||
for (const secret of unconfirmedSecrets) {
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'unconfirmed_secrets',
|
|
||||||
object: secret,
|
|
||||||
key: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedSecrets && sharedSecrets.length > 0) {
|
|
||||||
for (const { key, value } of sharedSecrets) {
|
|
||||||
await this.addObject({
|
|
||||||
storeName: 'shared_secrets',
|
|
||||||
object: value,
|
|
||||||
key: key,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getAllSecrets(): Promise<{ shared_secrets: Record<string, any>; unconfirmed_secrets: any[] }> {
|
|
||||||
const sharedSecrets = await this.dumpStore('shared_secrets');
|
|
||||||
const unconfirmedSecrets = await this.dumpStore('unconfirmed_secrets');
|
|
||||||
|
|
||||||
return {
|
|
||||||
shared_secrets: sharedSecrets,
|
|
||||||
unconfirmed_secrets: Object.values(unconfirmedSecrets),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export default class Services {
|
|||||||
private notifications: any[] | null = null;
|
private notifications: any[] | null = null;
|
||||||
private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
|
private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
|
||||||
private database: any;
|
private database: any;
|
||||||
private db!: Database; // Database singleton
|
|
||||||
private relayAddresses: { [wsurl: string]: string } = {};
|
private relayAddresses: { [wsurl: string]: string } = {};
|
||||||
private membersList: Record<string, Member> = {};
|
private membersList: Record<string, Member> = {};
|
||||||
private currentBlockHeight: number = -1;
|
private currentBlockHeight: number = -1;
|
||||||
@ -59,7 +58,6 @@ export default class Services {
|
|||||||
this.notifications = this.getNotifications();
|
this.notifications = this.getNotifications();
|
||||||
this.sdkClient = await import('../../pkg/sdk_client');
|
this.sdkClient = await import('../../pkg/sdk_client');
|
||||||
this.sdkClient.setup();
|
this.sdkClient.setup();
|
||||||
this.db = await Database.getInstance(); // Initialiser l'instance DB
|
|
||||||
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
||||||
this.updateRelay(wsurl, '');
|
this.updateRelay(wsurl, '');
|
||||||
}
|
}
|
||||||
@ -194,15 +192,26 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getSecretForAddress(address: string): Promise<string | null> {
|
public async getSecretForAddress(address: string): Promise<string | null> {
|
||||||
return await this.db.getSharedSecret(address);
|
const db = await Database.getInstance();
|
||||||
|
return await db.getObject('shared_secrets', address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllSecrets(): Promise<SecretsStore> {
|
public async getAllSecrets(): Promise<SecretsStore> {
|
||||||
return await this.db.getAllSecrets();
|
const db = await Database.getInstance();
|
||||||
|
const sharedSecrets = await db.dumpStore('shared_secrets');
|
||||||
|
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets'); // keys are numeric values
|
||||||
|
|
||||||
|
const secretsStore = {
|
||||||
|
shared_secrets: sharedSecrets,
|
||||||
|
unconfirmed_secrets: Object.values(unconfirmedSecrets),
|
||||||
|
};
|
||||||
|
|
||||||
|
return secretsStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllDiffs(): Promise<Record<string, UserDiff>> {
|
public async getAllDiffs(): Promise<Record<string, UserDiff>> {
|
||||||
return await this.db.getAllDiffs();
|
const db = await Database.getInstance();
|
||||||
|
return await db.dumpStore('diffs');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,7 +234,10 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getDiffByValue(value: string): Promise<UserDiff | null> {
|
public async getDiffByValue(value: string): Promise<UserDiff | null> {
|
||||||
return await this.db.getDiff(value);
|
const db = await Database.getInstance();
|
||||||
|
const store = 'diffs';
|
||||||
|
const res = await db.getObject(store, value);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTokensFromFaucet(): Promise<void> {
|
private async getTokensFromFaucet(): Promise<void> {
|
||||||
@ -829,7 +841,12 @@ export default class Services {
|
|||||||
this.sdkClient.reset_device();
|
this.sdkClient.reset_device();
|
||||||
|
|
||||||
// Clear all stores
|
// Clear all stores
|
||||||
await this.db.clearMultipleStores(['wallet', 'shared_secrets', 'unconfirmed_secrets', 'processes', 'diffs']);
|
const db = await Database.getInstance();
|
||||||
|
await db.clearStore('wallet');
|
||||||
|
await db.clearStore('shared_secrets');
|
||||||
|
await db.clearStore('unconfirmed_secrets');
|
||||||
|
await db.clearStore('processes');
|
||||||
|
await db.clearStore('diffs');
|
||||||
console.warn('[Services:resetDevice] ✅ Réinitialisation terminée.');
|
console.warn('[Services:resetDevice] ✅ Réinitialisation terminée.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1033,19 +1050,39 @@ export default class Services {
|
|||||||
|
|
||||||
private async handleSecrets(secrets: any) {
|
private async handleSecrets(secrets: any) {
|
||||||
const { unconfirmed_secrets, shared_secrets } = secrets;
|
const { unconfirmed_secrets, shared_secrets } = secrets;
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
|
||||||
const unconfirmedList = unconfirmed_secrets && unconfirmed_secrets.length > 0 ? unconfirmed_secrets : [];
|
// Sauvegarder les secrets non confirmés
|
||||||
const sharedList = shared_secrets && Object.keys(shared_secrets).length > 0
|
if (unconfirmed_secrets && unconfirmed_secrets.length > 0) {
|
||||||
? Object.entries(shared_secrets).map(([key, value]) => ({ key, value }))
|
console.log(`[Services:handleSecrets] 💾 Sauvegarde de ${unconfirmed_secrets.length} secret(s) non confirmé(s)`);
|
||||||
: [];
|
for (const secret of unconfirmed_secrets) {
|
||||||
|
try {
|
||||||
|
await db.addObject({
|
||||||
|
storeName: 'unconfirmed_secrets',
|
||||||
|
object: secret,
|
||||||
|
key: null,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[Services:handleSecrets] 💥 Échec de sauvegarde d'un secret non confirmé:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (unconfirmedList.length > 0 || sharedList.length > 0) {
|
// Sauvegarder les secrets partagés (confirmés)
|
||||||
console.log(`[Services:handleSecrets] 💾 Sauvegarde batch: ${unconfirmedList.length} secret(s) non confirmé(s) + ${sharedList.length} secret(s) partagé(s)`);
|
if (shared_secrets && Object.keys(shared_secrets).length > 0) {
|
||||||
try {
|
const entries = Object.entries(shared_secrets).map(([key, value]) => ({ key, value }));
|
||||||
await this.db.saveSecretsBatch(unconfirmedList, sharedList);
|
console.log(`[Services:handleSecrets] 💾 Sauvegarde de ${entries.length} secret(s) partagé(s)`);
|
||||||
console.log('[Services:handleSecrets] ✅ Secrets sauvegardés en batch.');
|
for (const entry of entries) {
|
||||||
} catch (e) {
|
try {
|
||||||
console.error('[Services:handleSecrets] 💥 Échec de sauvegarde batch des secrets:', e);
|
await db.addObject({
|
||||||
|
storeName: 'shared_secrets',
|
||||||
|
object: entry.value,
|
||||||
|
key: entry.key,
|
||||||
|
});
|
||||||
|
console.log(`[Services:handleSecrets] ✅ Secret partagé pour ${entry.key} sauvegardé.`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[Services:handleSecrets] 💥 Échec de l'ajout du secret partagé pour ${entry.key}:`, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1351,22 +1388,49 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveDeviceInDatabase(device: Device): Promise<void> {
|
async saveDeviceInDatabase(device: Device): Promise<void> {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const walletStore = 'wallet';
|
||||||
try {
|
try {
|
||||||
console.log("[Services:saveDeviceInDatabase] 💾 Sauvegarde de l'appareil en BDD...", {
|
console.log("[Services:saveDeviceInDatabase] 💾 Sauvegarde de l'appareil en BDD...", {
|
||||||
pairing_process_commitment: device.pairing_process_commitment,
|
pairing_process_commitment: device.pairing_process_commitment,
|
||||||
paired_member: device.paired_member,
|
paired_member: device.paired_member,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.db.saveDevice(device);
|
const prevDevice = await this.getDeviceFromDatabase();
|
||||||
|
if (prevDevice) {
|
||||||
|
// console.debug('[Services:saveDeviceInDatabase] ℹ️ Appareil précédent trouvé, suppression...');
|
||||||
|
await db.deleteObject(walletStore, '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.addObject({
|
||||||
|
storeName: walletStore,
|
||||||
|
object: { pre_id: '1', device },
|
||||||
|
key: null,
|
||||||
|
});
|
||||||
|
|
||||||
console.log('[Services:saveDeviceInDatabase] ✅ Appareil sauvegardé avec succès');
|
console.log('[Services:saveDeviceInDatabase] ✅ Appareil sauvegardé avec succès');
|
||||||
|
|
||||||
|
// // Verify save
|
||||||
|
// const savedDevice = await this.getDeviceFromDatabase();
|
||||||
|
// console.log('[Services:saveDeviceInDatabase] 🔎 Vérification:', {
|
||||||
|
// pairing_process_commitment: savedDevice?.pairing_process_commitment,
|
||||||
|
// paired_member: savedDevice?.paired_member,
|
||||||
|
// });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Services:saveDeviceInDatabase] 💥 Erreur lors de la sauvegarde:', e);
|
console.error('[Services:saveDeviceInDatabase] 💥 Erreur lors de la sauvegarde:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeviceFromDatabase(): Promise<Device | null> {
|
async getDeviceFromDatabase(): Promise<Device | null> {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const walletStore = 'wallet';
|
||||||
try {
|
try {
|
||||||
return await this.db.getDevice();
|
const dbRes = await db.getObject(walletStore, '1');
|
||||||
|
if (dbRes) {
|
||||||
|
return dbRes['device'];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[Services:getDeviceFromDatabase] 💥 Échec: ${e}`);
|
throw new Error(`[Services:getDeviceFromDatabase] 💥 Échec: ${e}`);
|
||||||
}
|
}
|
||||||
@ -1517,22 +1581,44 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async removeProcess(processId: string): Promise<void> {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const storeName = 'processes';
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`[Services:removeProcess] 🗑️ Suppression du processus ${processId}`);
|
||||||
|
await db.deleteObject(storeName, processId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async batchSaveProcessesToDb(processes: Record<string, Process>) {
|
public async batchSaveProcessesToDb(processes: Record<string, Process>) {
|
||||||
if (Object.keys(processes).length === 0) {
|
if (Object.keys(processes).length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`[Services:batchSaveProcessesToDb] 💾 Sauvegarde de ${Object.keys(processes).length} processus en BDD...`);
|
console.log(`[Services:batchSaveProcessesToDb] 💾 Sauvegarde de ${Object.keys(processes).length} processus en BDD...`);
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const storeName = 'processes';
|
||||||
try {
|
try {
|
||||||
await this.db.saveProcessesBatch(processes);
|
await db.batchWriting({ storeName, objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })) });
|
||||||
this.processesCache = { ...this.processesCache, ...processes };
|
this.processesCache = { ...this.processesCache, ...processes };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Services:batchSaveProcessesToDb] 💥 Échec:', e);
|
console.error('[Services:batchSaveProcessesToDb] 💥 Échec:', e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveProcessToDb(processId: string, process: Process) {
|
public async saveProcessToDb(processId: string, process: Process) {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const storeName = 'processes';
|
||||||
try {
|
try {
|
||||||
await this.db.saveProcess(processId, process);
|
await db.addObject({
|
||||||
|
storeName,
|
||||||
|
object: process,
|
||||||
|
key: processId,
|
||||||
|
});
|
||||||
|
|
||||||
// Update the process in the cache
|
// Update the process in the cache
|
||||||
this.processesCache[processId] = process;
|
this.processesCache[processId] = process;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1541,81 +1627,27 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async saveBlobToDb(hash: string, data: Blob) {
|
public async saveBlobToDb(hash: string, data: Blob) {
|
||||||
|
const db = await Database.getInstance();
|
||||||
try {
|
try {
|
||||||
await this.db.saveBlob(hash, data);
|
await db.addObject({
|
||||||
|
storeName: 'data',
|
||||||
|
object: data,
|
||||||
|
key: hash,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[Services:saveBlobToDb] 💥 Échec de la sauvegarde du blob ${hash}: ${e}`);
|
console.error(`[Services:saveBlobToDb] 💥 Échec de la sauvegarde du blob ${hash}: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getBlobFromDb(hash: string): Promise<Blob | null> {
|
public async getBlobFromDb(hash: string): Promise<Blob | null> {
|
||||||
|
const db = await Database.getInstance();
|
||||||
try {
|
try {
|
||||||
return await this.db.getBlob(hash);
|
return await db.getObject('data', hash);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getProcess(processId: string): Promise<Process | null> {
|
|
||||||
// 1. Essayer le cache en mémoire
|
|
||||||
if (this.processesCache[processId]) {
|
|
||||||
return this.processesCache[processId];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Si non trouvé, essayer la BDD
|
|
||||||
try {
|
|
||||||
const process = await this.db.getProcess(processId);
|
|
||||||
if (process) {
|
|
||||||
this.processesCache[processId] = process; // Mettre en cache
|
|
||||||
}
|
|
||||||
return process;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Services:getProcess] 💥 Échec de la récupération du processus ${processId}: ${e}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getProcesses(): Promise<Record<string, Process>> {
|
|
||||||
// 1. Essayer le cache en mémoire
|
|
||||||
if (Object.keys(this.processesCache).length > 0) {
|
|
||||||
return this.processesCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Si non trouvé, charger depuis la BDD
|
|
||||||
try {
|
|
||||||
console.log('[Services:getProcesses] ℹ️ Cache de processus vide. Chargement depuis la BDD...');
|
|
||||||
this.processesCache = await this.db.getAllProcesses();
|
|
||||||
console.log(`[Services:getProcesses] ✅ ${Object.keys(this.processesCache).length} processus chargés en cache.`);
|
|
||||||
return this.processesCache;
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[Services:getProcesses] 💥 Échec:', e);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async restoreProcessesFromBackUp(processes: Record<string, Process>) {
|
|
||||||
console.log(`[Services:restoreProcessesFromBackUp] 💾 Restauration de ${Object.keys(processes).length} processus depuis un backup...`);
|
|
||||||
try {
|
|
||||||
await this.db.saveProcessesBatch(processes);
|
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore processes cache from persistent storage
|
|
||||||
public async restoreProcessesFromDB() {
|
|
||||||
try {
|
|
||||||
const processes: Record<string, Process> = await this.db.getAllProcesses();
|
|
||||||
if (processes && Object.keys(processes).length != 0) {
|
|
||||||
console.log(`[Services:restoreProcessesFromDB] 🔄 Restauration de ${Object.keys(processes).length} processus depuis la BDD vers le cache...`);
|
|
||||||
this.processesCache = processes;
|
|
||||||
console.log('[Services:restoreProcessesFromDB] ✅ Processus restaurés.');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async saveDataToStorage(storages: string[], hash: string, data: Blob, ttl: number | null) {
|
public async saveDataToStorage(storages: string[], hash: string, data: Blob, ttl: number | null) {
|
||||||
try {
|
try {
|
||||||
await storeData(storages, hash, data, ttl);
|
await storeData(storages, hash, data, ttl);
|
||||||
@ -1631,23 +1663,113 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getDiffByValueFromDb(hash: string): Promise<UserDiff | null> {
|
public async getDiffByValueFromDb(hash: string): Promise<UserDiff | null> {
|
||||||
return await this.db.getDiff(hash);
|
const db = await Database.getInstance();
|
||||||
|
const diff = await db.getObject('diffs', hash);
|
||||||
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveDiffsToDb(diffs: UserDiff[]) {
|
public async saveDiffsToDb(diffs: UserDiff[]) {
|
||||||
|
const db = await Database.getInstance();
|
||||||
try {
|
try {
|
||||||
await this.db.saveDiffs(diffs);
|
for (const diff of diffs) {
|
||||||
|
await db.addObject({
|
||||||
|
storeName: 'diffs',
|
||||||
|
object: diff,
|
||||||
|
key: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`[Services:saveDiffsToDb] 💥 Échec: ${e}`);
|
throw new Error(`[Services:saveDiffsToDb] 💥 Échec: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getProcess(processId: string): Promise<Process | null> {
|
||||||
|
// 1. Essayer le cache en mémoire
|
||||||
|
if (this.processesCache[processId]) {
|
||||||
|
return this.processesCache[processId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Si non trouvé, essayer la BDD
|
||||||
|
try {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const process = await db.getObject('processes', processId);
|
||||||
|
if (process) {
|
||||||
|
this.processesCache[processId] = process; // Mettre en cache
|
||||||
|
}
|
||||||
|
return process;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[Services:getProcess] 💥 Échec de récupération du processus ${processId}:`, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getProcesses(): Promise<Record<string, Process>> {
|
||||||
|
// 1. Essayer le cache en mémoire
|
||||||
|
if (Object.keys(this.processesCache).length > 0) {
|
||||||
|
return this.processesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Si non trouvé, charger depuis la BDD
|
||||||
|
try {
|
||||||
|
console.log('[Services:getProcesses] ℹ️ Cache de processus vide. Chargement depuis la BDD...');
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
this.processesCache = await db.dumpStore('processes');
|
||||||
|
console.log(`[Services:getProcesses] ✅ ${Object.keys(this.processesCache).length} processus chargés en cache.`);
|
||||||
|
return this.processesCache;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Services:getProcesses] 💥 Échec du chargement des processus:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async restoreProcessesFromBackUp(processes: Record<string, Process>) {
|
||||||
|
console.log(`[Services:restoreProcessesFromBackUp] 💾 Restauration de ${Object.keys(processes).length} processus depuis un backup...`);
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
const storeName = 'processes';
|
||||||
|
try {
|
||||||
|
await db.batchWriting({ storeName, objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })) });
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.restoreProcessesFromDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore processes cache from persistent storage
|
||||||
|
public async restoreProcessesFromDB() {
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
try {
|
||||||
|
const processes: Record<string, Process> = await db.dumpStore('processes');
|
||||||
|
if (processes && Object.keys(processes).length != 0) {
|
||||||
|
console.log(`[Services:restoreProcessesFromDB] 🔄 Restauration de ${Object.keys(processes).length} processus depuis la BDD vers le cache...`);
|
||||||
|
this.processesCache = processes;
|
||||||
|
} else {
|
||||||
|
console.log('[Services:restoreProcessesFromDB] ℹ️ Aucun processus à restaurer.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async restoreSecretsFromBackUp(secretsStore: SecretsStore) {
|
public async restoreSecretsFromBackUp(secretsStore: SecretsStore) {
|
||||||
console.log('[Services:restoreSecretsFromBackUp] 💾 Restauration des secrets depuis un backup...');
|
console.log('[Services:restoreSecretsFromBackUp] 💾 Restauration des secrets depuis un backup...');
|
||||||
|
const db = await Database.getInstance();
|
||||||
|
|
||||||
const sharedList = Object.entries(secretsStore.shared_secrets).map(([key, value]) => ({ key, value }));
|
for (const secret of secretsStore.unconfirmed_secrets) {
|
||||||
await this.db.saveSecretsBatch(secretsStore.unconfirmed_secrets, sharedList);
|
await db.addObject({
|
||||||
console.log('[Services:restoreSecretsFromBackUp] ✅ Secrets restaurés en batch.');
|
storeName: 'unconfirmed_secrets',
|
||||||
|
object: secret,
|
||||||
|
key: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const entries = Object.entries(secretsStore.shared_secrets).map(([key, value]) => ({ key, value }));
|
||||||
|
for (const entry of entries) {
|
||||||
|
await db.addObject({
|
||||||
|
storeName: 'shared_secrets',
|
||||||
|
object: entry.value,
|
||||||
|
key: entry.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Now we can transfer them to memory
|
// Now we can transfer them to memory
|
||||||
await this.restoreSecretsFromDB();
|
await this.restoreSecretsFromDB();
|
||||||
@ -1655,10 +1777,16 @@ export default class Services {
|
|||||||
|
|
||||||
public async restoreSecretsFromDB() {
|
public async restoreSecretsFromDB() {
|
||||||
console.log('[Services:restoreSecretsFromDB] 🔄 Restauration des secrets depuis la BDD vers la mémoire SDK...');
|
console.log('[Services:restoreSecretsFromDB] 🔄 Restauration des secrets depuis la BDD vers la mémoire SDK...');
|
||||||
|
const db = await Database.getInstance();
|
||||||
try {
|
try {
|
||||||
const secretsStore = await this.db.getAllSecrets();
|
const sharedSecrets: Record<string, string> = await db.dumpStore('shared_secrets');
|
||||||
|
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets');
|
||||||
|
const secretsStore = {
|
||||||
|
shared_secrets: sharedSecrets,
|
||||||
|
unconfirmed_secrets: Object.values(unconfirmedSecrets),
|
||||||
|
};
|
||||||
this.sdkClient.set_shared_secrets(JSON.stringify(secretsStore));
|
this.sdkClient.set_shared_secrets(JSON.stringify(secretsStore));
|
||||||
console.log(`[Services:restoreSecretsFromDB] ✅ ${Object.keys(secretsStore.shared_secrets).length} secrets partagés restaurés.`);
|
console.log(`[Services:restoreSecretsFromDB] ✅ ${Object.keys(sharedSecrets).length} secrets partagés restaurés.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,381 +0,0 @@
|
|||||||
/**
|
|
||||||
* Database Web Worker - Handles all IndexedDB operations in background
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {
|
|
||||||
StoreDefinition,
|
|
||||||
WorkerMessagePayload,
|
|
||||||
WorkerMessageResponse,
|
|
||||||
BatchWriteItem
|
|
||||||
} from './worker.types';
|
|
||||||
|
|
||||||
const DB_NAME = '4nk';
|
|
||||||
const DB_VERSION = 1;
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// STORE DEFINITIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
const STORE_DEFINITIONS: Record<string, StoreDefinition> = {
|
|
||||||
AnkLabels: {
|
|
||||||
name: 'labels',
|
|
||||||
options: { keyPath: 'emoji' },
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
AnkWallet: {
|
|
||||||
name: 'wallet',
|
|
||||||
options: { keyPath: 'pre_id' },
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
AnkProcess: {
|
|
||||||
name: 'processes',
|
|
||||||
options: {},
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
AnkSharedSecrets: {
|
|
||||||
name: 'shared_secrets',
|
|
||||||
options: {},
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
AnkUnconfirmedSecrets: {
|
|
||||||
name: 'unconfirmed_secrets',
|
|
||||||
options: { autoIncrement: true },
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
AnkPendingDiffs: {
|
|
||||||
name: 'diffs',
|
|
||||||
options: { keyPath: 'value_commitment' },
|
|
||||||
indices: [
|
|
||||||
{ name: 'byStateId', keyPath: 'state_id', options: { unique: false } },
|
|
||||||
{ name: 'byNeedValidation', keyPath: 'need_validation', options: { unique: false } },
|
|
||||||
{ name: 'byStatus', keyPath: 'validation_status', options: { unique: false } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
AnkData: {
|
|
||||||
name: 'data',
|
|
||||||
options: {},
|
|
||||||
indices: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let db: IDBDatabase | null = null;
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// DATABASE INITIALIZATION
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function openDatabase(): Promise<IDBDatabase> {
|
|
||||||
if (db) {
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
||||||
|
|
||||||
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
|
||||||
const database = (event.target as IDBOpenDBRequest).result;
|
|
||||||
|
|
||||||
Object.values(STORE_DEFINITIONS).forEach(({ name, options, indices }) => {
|
|
||||||
if (!database.objectStoreNames.contains(name)) {
|
|
||||||
const store = database.createObjectStore(name, options);
|
|
||||||
|
|
||||||
indices.forEach(({ name: indexName, keyPath, options: indexOptions }) => {
|
|
||||||
store.createIndex(indexName, keyPath, indexOptions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onsuccess = () => {
|
|
||||||
db = request.result;
|
|
||||||
resolve(db);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onerror = () => {
|
|
||||||
reject(request.error);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// WRITE OPERATIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function addObject(storeName: string, object: any, key?: IDBValidKey): Promise<{ success: boolean }> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readwrite');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let request: IDBRequest;
|
|
||||||
if (key !== null && key !== undefined) {
|
|
||||||
request = store.put(object, key);
|
|
||||||
} else {
|
|
||||||
request = store.put(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.onsuccess = () => resolve({ success: true });
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function batchWriting(storeName: string, objects: BatchWriteItem[]): Promise<{ success: boolean }> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readwrite');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
for (const { key, object } of objects) {
|
|
||||||
if (key !== null && key !== undefined) {
|
|
||||||
store.put(object, key);
|
|
||||||
} else {
|
|
||||||
store.put(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
tx.oncomplete = () => resolve({ success: true });
|
|
||||||
tx.onerror = () => reject(tx.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// READ OPERATIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function getObject(storeName: string, key: IDBValidKey): Promise<any> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = store.get(key);
|
|
||||||
request.onsuccess = () => resolve(request.result ?? null);
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dumpStore(storeName: string): Promise<Record<string, any>> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const result: Record<string, any> = {};
|
|
||||||
const request = store.openCursor();
|
|
||||||
|
|
||||||
request.onsuccess = (event) => {
|
|
||||||
const cursor = (event.target as IDBRequest<IDBCursorWithValue | null>).result;
|
|
||||||
if (cursor) {
|
|
||||||
result[cursor.key as string] = cursor.value;
|
|
||||||
cursor.continue();
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllObjects(storeName: string): Promise<any[]> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = store.getAll();
|
|
||||||
request.onsuccess = () => resolve(request.result || []);
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMultipleObjects(storeName: string, keys: IDBValidKey[]): Promise<any[]> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
const requests = keys.map((key) => {
|
|
||||||
return new Promise<any>((resolve) => {
|
|
||||||
const request = store.get(key);
|
|
||||||
request.onsuccess = () => resolve(request.result || null);
|
|
||||||
request.onerror = () => {
|
|
||||||
console.error(`Error fetching key ${key}:`, request.error);
|
|
||||||
resolve(null);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await Promise.all(requests);
|
|
||||||
return results.filter(result => result !== null);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllObjectsWithFilter(storeName: string, filterFn?: string): Promise<any[]> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = store.getAll();
|
|
||||||
request.onsuccess = () => {
|
|
||||||
const allItems = request.result || [];
|
|
||||||
if (filterFn) {
|
|
||||||
const filter = new Function('item', `return ${filterFn}`) as (item: any) => boolean;
|
|
||||||
resolve(allItems.filter(filter));
|
|
||||||
} else {
|
|
||||||
resolve(allItems);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// DELETE OPERATIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function deleteObject(storeName: string, key: IDBValidKey): Promise<{ success: boolean }> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readwrite');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = store.delete(key);
|
|
||||||
request.onsuccess = () => resolve({ success: true });
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clearStore(storeName: string): Promise<{ success: boolean }> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readwrite');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = store.clear();
|
|
||||||
request.onsuccess = () => resolve({ success: true });
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// INDEX OPERATIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function requestStoreByIndex(storeName: string, indexName: string, requestValue: IDBValidKey): Promise<any[]> {
|
|
||||||
const database = await openDatabase();
|
|
||||||
const tx = database.transaction(storeName, 'readonly');
|
|
||||||
const store = tx.objectStore(storeName);
|
|
||||||
const index = store.index(indexName);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = index.getAll(requestValue);
|
|
||||||
request.onsuccess = () => {
|
|
||||||
const allItems = request.result;
|
|
||||||
const filtered = allItems.filter((item: any) => item.state_id === requestValue);
|
|
||||||
resolve(filtered);
|
|
||||||
};
|
|
||||||
request.onerror = () => reject(request.error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// UTILITY FUNCTIONS
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
function getStoreList(): Record<string, string> {
|
|
||||||
const storeList: Record<string, string> = {};
|
|
||||||
Object.keys(STORE_DEFINITIONS).forEach((key) => {
|
|
||||||
storeList[key] = STORE_DEFINITIONS[key].name;
|
|
||||||
});
|
|
||||||
return storeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// MESSAGE HANDLER
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
self.addEventListener('message', async (event: MessageEvent<WorkerMessagePayload>) => {
|
|
||||||
const { type, payload, id } = event.data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result: any;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'INIT':
|
|
||||||
await openDatabase();
|
|
||||||
result = { success: true };
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ADD_OBJECT':
|
|
||||||
result = await addObject(payload.storeName, payload.object, payload.key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'BATCH_WRITING':
|
|
||||||
result = await batchWriting(payload.storeName, payload.objects);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_OBJECT':
|
|
||||||
result = await getObject(payload.storeName, payload.key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'DUMP_STORE':
|
|
||||||
result = await dumpStore(payload.storeName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'DELETE_OBJECT':
|
|
||||||
result = await deleteObject(payload.storeName, payload.key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'CLEAR_STORE':
|
|
||||||
result = await clearStore(payload.storeName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'REQUEST_STORE_BY_INDEX':
|
|
||||||
result = await requestStoreByIndex(
|
|
||||||
payload.storeName,
|
|
||||||
payload.indexName,
|
|
||||||
payload.request
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS':
|
|
||||||
result = await getAllObjects(payload.storeName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_MULTIPLE_OBJECTS':
|
|
||||||
result = await getMultipleObjects(payload.storeName, payload.keys);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS_WITH_FILTER':
|
|
||||||
result = await getAllObjectsWithFilter(payload.storeName, payload.filterFn);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_STORE_LIST':
|
|
||||||
result = getStoreList();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown message type: ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.postMessage({
|
|
||||||
id,
|
|
||||||
type: 'SUCCESS',
|
|
||||||
result,
|
|
||||||
} as WorkerMessageResponse);
|
|
||||||
} catch (error) {
|
|
||||||
self.postMessage({
|
|
||||||
id,
|
|
||||||
type: 'ERROR',
|
|
||||||
error: (error as Error).message || String(error),
|
|
||||||
} as WorkerMessageResponse);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// INITIALIZATION
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
openDatabase().catch((error) => {
|
|
||||||
console.error('[Database Worker] Failed to initialize database:', error);
|
|
||||||
});
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Shared types for Web Workers
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface StoreDefinition {
|
|
||||||
name: string;
|
|
||||||
options: IDBObjectStoreParameters;
|
|
||||||
indices: IndexDefinition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IndexDefinition {
|
|
||||||
name: string;
|
|
||||||
keyPath: string | string[];
|
|
||||||
options: IDBIndexParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkerMessagePayload {
|
|
||||||
type: string;
|
|
||||||
payload?: any;
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorkerMessageResponse {
|
|
||||||
id: number;
|
|
||||||
type: 'SUCCESS' | 'ERROR';
|
|
||||||
result?: any;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BatchWriteItem {
|
|
||||||
key?: IDBValidKey;
|
|
||||||
object: any;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user