From 81bab0b4695ca99f97b69c48b5af8c5b115a8cac Mon Sep 17 00:00:00 2001 From: AnisHADJARAB Date: Thu, 7 Nov 2024 15:04:07 +0000 Subject: [PATCH] add db and call it in a service worker --- src/router.ts | 2 + src/service-workers/database.worker.js | 58 ++++++++++ src/services/database.service.ts | 154 +++++++++++++++++++++++++ src/services/service.ts | 23 +++- tsconfig.json | 4 +- 5 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 src/service-workers/database.worker.js create mode 100644 src/services/database.service.ts diff --git a/src/router.ts b/src/router.ts index aa47771..f3b0f4c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,5 +1,6 @@ import '../public/style/4nk.css'; import { initHeader } from './components/header/header'; +import Database from './services/database.service'; import Services from './services/service'; import { cleanSubscriptions } from './utils/subscription.utils'; @@ -67,6 +68,7 @@ window.onpopstate = async () => { async function init(): Promise { try { const services = await Services.getInstance(); + await Database.getInstance(); setTimeout(async () => { let device = await services.getDevice(); console.log('🚀 ~ setTimeout ~ device:', device); diff --git a/src/service-workers/database.worker.js b/src/service-workers/database.worker.js new file mode 100644 index 0000000..b956440 --- /dev/null +++ b/src/service-workers/database.worker.js @@ -0,0 +1,58 @@ +import Database from '../services/database.service'; + +self.addEventListener('install', (event) => { + event.waitUntil(self.skipWaiting()); // Activate worker immediately +}); + +self.addEventListener('activate', (event) => { + event.waitUntil(self.clients.claim()); // Become available to all pages +}); + +// Event listener for messages from clients +self.addEventListener('message', async (event) => { + const data = event.data; + const db = await Database.getInstance(); + + 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); + console.log("🚀 ~ self.addEventListener ~ store:", store, storeName, key, object) + await store.put(object); + event.ports[0].postMessage({ status: 'success', message: 'Object added or replaced successfully' }); + } catch(error) { + event.ports[0].postMessage({ status: 'error', message: error.message }); + } + } + + if (data.type === 'GET_OBJECT') { + const { storeName, key } = data.payload; + const db = await openDatabase(); + const tx = db.transaction(storeName, 'readonly'); + const store = tx.objectStore(storeName); + const result = await store.get(key); + + event.ports[0].postMessage({ type: 'GET_OBJECT_RESULT', payload: result }); + } + +}); + +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' }); + } + }; + }); +} \ No newline at end of file diff --git a/src/services/database.service.ts b/src/services/database.service.ts new file mode 100644 index 0000000..ad242a3 --- /dev/null +++ b/src/services/database.service.ts @@ -0,0 +1,154 @@ +class Database { + private static instance: Database; + private db: IDBDatabase | null = null; + private dbName: string = '4nk'; + private dbVersion: number = 1; + private serviceWorkerRegistration: ServiceWorkerRegistration | null = null; + private messageChannel: MessageChannel = new MessageChannel(); + private messageChannelForGet: MessageChannel = new MessageChannel(); + private storeDefinitions = { + AnkWallet: { + name: 'wallet', + options: { keyPath: 'pre_id' }, + indices: [], + }, + AnkProcess: { + name: 'process', + options: { keyPath: 'id' }, + indices: [ + { + name: 'by_name', + keyPath: 'name', + options: { + unique: true, + }, + }, + ], + }, + AnkMessages: { + name: 'messages', + options: { keyPath: 'id' }, + indices: [], + }, + }; + + // Private constructor to prevent direct instantiation from outside + private constructor() {} + + // Method to access the singleton instance of Database + public static async getInstance(): Promise { + if (!Database.instance) { + Database.instance = new Database(); + await Database.instance.init(); + } + return Database.instance; + } + + // Initialize the database + private async init(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(this.dbName, this.dbVersion); + + request.onupgradeneeded = () => { + const db = request.result; + + Object.values(this.storeDefinitions).forEach(({ name, options, indices }) => { + if (!db.objectStoreNames.contains(name)) { + let store = db.createObjectStore(name, options); + + indices.forEach(({ name, keyPath, options }) => { + store.createIndex(name, keyPath, options); + }); + } + }); + }; + + request.onsuccess = () => { + this.db = request.result; + this.initServiceWorker(); + resolve(); + }; + + request.onerror = () => { + console.error('Database error:', request.error); + reject(request.error); + }; + }); + } + + public async getDb(): Promise { + if (!this.db) { + await this.init(); + } + return this.db!; + } + + 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; + } + + private createMessageChannel(responseHandler: (event: MessageEvent) => void): MessageChannel { + const messageChannel = new MessageChannel(); + messageChannel.port1.onmessage = responseHandler; + return messageChannel; + } + + private async initServiceWorker() { + if ('serviceWorker' in navigator) { + try { + const registration = await navigator.serviceWorker.register('/src/service-workers/database.worker.js', { type: 'module' }); + console.log('Service Worker registered with scope:', registration.scope); + + this.serviceWorkerRegistration = registration; + + // Set up the message channels + this.messageChannel.port1.onmessage = this.handleAddObjectResponse; + this.messageChannelForGet.port1.onmessage = this.handleGetObjectResponse; + + // Optionally, initialize service worker with some data + } catch (error) { + console.error('Service Worker registration failed:', error); + } + } + } + private handleAddObjectResponse = (event: MessageEvent) => { + console.log('Received response from service worker (ADD_OBJECT):', event.data); + }; + + private handleGetObjectResponse = (event: MessageEvent) => { + console.log('Received response from service worker (GET_OBJECT):', event.data); + }; + + public addObject(payload: { storeName: string; object: any; key: any }) { + if (this.serviceWorkerRegistration?.active) { + const messageChannel = this.createMessageChannel(this.handleAddObjectResponse); + this.serviceWorkerRegistration.active.postMessage( + { + type: 'ADD_OBJECT', + payload, + }, + [messageChannel.port2], + ); + } + } + + public getObject(storeName: string, key: string) { + if (this.serviceWorkerRegistration?.active) { + const messageChannel = this.createMessageChannel(this.handleGetObjectResponse); + + this.serviceWorkerRegistration.active.postMessage( + { + type: 'GET_OBJECT', + payload: { storeName, key }, + }, + [messageChannel.port2], + ); + } + } +} + +export default Database; diff --git a/src/services/service.ts b/src/services/service.ts index 6a63d30..4951bbb 100644 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -6,6 +6,7 @@ import { initWebsocket, sendMessage } from '../websockets'; import { ApiReturn, Member } from '../../dist/pkg/sdk_client'; import ModalService from './modal.service'; import { navigate } from '../router'; +import Database from './database.service'; type ProcessesCache = { [key: string]: any; @@ -199,6 +200,12 @@ export default class Services { // Save process to storage localStorage.setItem(processCommitment, JSON.stringify(process)); + const db = await Database.getInstance(); + db.addObject({ + storeName: 'process', + object: { id: processCommitment, process }, + key: processCommitment, + }); // Check if the newly updated process reveals some new information try { const proposals: string[] = this.sdkClient.get_update_proposals(processCommitment); @@ -213,10 +220,16 @@ export default class Services { } if (apiReturn.updated_cached_msg && apiReturn.updated_cached_msg.length) { - apiReturn.updated_cached_msg.forEach((msg, index) => { + apiReturn.updated_cached_msg.forEach(async (msg, index) => { // console.debug(`CachedMessage ${index}:`, msg); // Save the message to local storage localStorage.setItem(msg.id.toString(), JSON.stringify(msg)); + const db = await Database.getInstance(); + db.addObject({ + storeName: 'process', + object: { id: msg.id.toString(), msg }, + key: msg.id.toString(), + }); }); } @@ -312,10 +325,18 @@ export default class Services { async saveDevice(device: any): Promise { // console.log("🚀 ~ Services ~ saveDevice ~ device:", device) + const db = await Database.getInstance(); + db.addObject({ + storeName: 'wallet', + object: { pre_id: '1', device }, + key: '1', + }); localStorage.setItem('wallet', device); } async getDevice(): Promise { + const db = await Database.getInstance(); + db.getObject('wallet', '1'); return localStorage.getItem('wallet'); } diff --git a/tsconfig.json b/tsconfig.json index f52029d..fe4eca7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "declaration": true, "outDir": "./dist", "target": "ESNext", - "lib": ["DOM", "DOM.Iterable", "ESNext"], + "lib": ["DOM", "DOM.Iterable", "ESNext", "webworker"], "types": ["vite/client", "node"], "allowJs": true, "skipLibCheck": true, @@ -22,6 +22,6 @@ "~/*": ["src/*"] } }, - "include": ["src", "src/**/*", "./vite.config.ts", "src/index.d.ts", "src/router.ts"], + "include": ["src", "src/**/*", "./vite.config.ts", "src/*.d.ts", "src/router.ts"], "exclude": ["node_modules"] } \ No newline at end of file