// import { WebSocketClient } from '../websockets'; import { INotification } from '~/models/notification.model'; import homePage from '../html/home.html?raw'; import homeScript from '../html/home.js?raw'; import processPage from '../html/process.html?raw'; import processScript from '../html/process.js?raw'; import { IProcess } from '~/models/process.model'; import Database from './database'; import { WebSocketClient } from '../websockets'; import QRCode from 'qrcode' import { servicesVersion } from 'typescript'; import { ApiReturn, CachedMessage, Member } from '../../dist/pkg/sdk_client'; import Routing from './routing.service'; export default class Services { private static instance: Services; private current_process: string | null = null; private sdkClient: any; private websocketConnection: WebSocketClient | null = null; private sp_address: string | null = null; private processes: IProcess[] | null = null; private notifications: INotification[] | null = null; private subscriptions: {element: Element; event: string; eventHandler: string;}[] = [] ; private database: any // Private constructor to prevent direct instantiation from outside private constructor() {} // Method to access the singleton instance of Services public static async getInstance(): Promise { if (!Services.instance) { Services.instance = new Services(); await Services.instance.init(); } return Services.instance; } public async init(): Promise { this.notifications = this.getNotifications(); this.sdkClient = await import("../../dist/pkg/sdk_client"); this.database = Database.getInstance() } public async addWebsocketConnection(url: string): Promise { const services = await Services.getInstance(); const newClient = new WebSocketClient(url, services); if (!services.websocketConnection) { services.websocketConnection = newClient; } } public async recoverInjectHtml(): Promise { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } container.innerHTML = homePage; const newScript = document.createElement('script') newScript.setAttribute('type', 'module') newScript.textContent = homeScript; document.head.appendChild(newScript).parentNode?.removeChild(newScript); const btn = container.querySelector('#scan-this-device') if(btn) { this.addSubscription(btn, 'click', 'injectProcessListPage') } const url = location.href this.generateQRCode(this.sp_address || '') } private generateQRCode = async (text: string) => { console.log("🚀 ~ Services ~ generateQRCode= ~ text:", text) try { const container = document.getElementById('containerId'); const url = await QRCode.toDataURL(text); const qrCode = container?.querySelector('.qr-code img'); qrCode?.setAttribute('src', url) } catch (err) { console.error(err); } } async prepareProcessTx(myAddress: string, recipientAddress: string) { const txid = '0'.repeat(64) var vout = Number.MAX_SAFE_INTEGER; const paringTemplate = { "html": "", "style": "", "script": "", "description": "AliceBob", "roles": { "owner": { "members": [{sp_addresses: [myAddress]}, {sp_addresses:[recipientAddress]}], "validation_rules": [ { "quorum": 1.0, "fields": [ "roles", "pairing_tx" ], "min_sig_member": 1.0 } ] } }, "pairing_tx": `${txid}:4294967295`, } const service = await Services.getInstance(); const process = await service.sdkClient.create_update_transaction(undefined, JSON.stringify(paringTemplate), 1) return process } async sendPairingTx(sp_address: string): Promise { const services = await Services.getInstance(); const amount = await this.getAmount() as any console.log("🚀 ~ Services ~ sendPairingTx ~ amount:", amount) // if(amount === 0n) { // const faucetMessage = await services.createFaucetMessage() // console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage) // services.websocketConnection?.sendNormalMessage(faucetMessage) // } const spAddress = await this.getDeviceAddress() as any let txid = '0'.repeat(64) setTimeout(async () => { let pairing = await services.sdkClient.pair_device(`${txid}:4294967295`, [sp_address]) const process = await this.prepareProcessTx(spAddress, sp_address) const tx = process.new_tx_to_send const parsedTx = JSON.parse(tx) const transaction = parsedTx.transaction txid = await services.sdkClient.get_txid(transaction) const root_commitment = process.updated_process[0]; const init_process = process.updated_process[1]; console.log("🚀 ~ Services ~ setTimeout ~ init_process:", init_process, init_process.payload) console.log("🚀 ~ Services ~ setTimeout ~ txToSend:", tx) const prd = JSON.stringify(init_process.impending_requests[0]); services.websocketConnection?.sendMessage('NewTx', tx) pairing = await services.sdkClient.pair_device(`${txid}:0`, [sp_address]) const dump = await services.sdkClient.dump_device() const prd_response = await services.sdkClient.response_prd(root_commitment, prd, true) console.log("🚀 ~ Services ~ setTimeout ~ prd_response:", prd_response, prd, root_commitment) if(process?.ciphers_to_send && process.ciphers_to_send.length) { await this.sendCipherMessages(process.ciphers_to_send) } const router = await Routing.getInstance(); router.openLoginModal(spAddress, sp_address) }, 2000) } async resetDevice() { const service = await Services.getInstance() await service.sdkClient.reset_device() } async sendCipherMessages(messages: string[]) { const service = await Services.getInstance(); messages.forEach((message) => { service.websocketConnection?.sendMessage('Cipher', message) }) } async sendFaucetMessage(): Promise { const services = await Services.getInstance(); const faucetMessage = await services.createFaucetMessage() console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage) services.websocketConnection?.sendNormalMessage(faucetMessage) } async parseCipher(message: string) { try { const services = await Services.getInstance(); try { JSON.parse(message) const router = await Routing.getInstance(); router.closeLoginModal() this.injectProcessListPage() } catch { console.log('Not proper format for cipher') } const parsedTx = await services.sdkClient.parse_cipher(message, 0.00001) console.log("🚀 ~ Services ~ parseCipher ~ parsedTx:", parsedTx) await this.processTx(parsedTx) // await this.saveCipherTxToDb(parsedTx) } catch(e) { console.log(e) } } async parseNewTx(tx: string) { try { console.log('==============> sending txxxxxxx parser', tx) const services = await Services.getInstance(); const parsedTx = await services.sdkClient.parse_new_tx(tx, 0, 0.01) console.log("🚀 ~ Services ~ parseNewTx ~ parsedTx:", parsedTx) if(parsedTx) { await this.processTx(parsedTx) // await this.saveTxToDb(parsedTx) } } catch(e) { console.log(e) } } async processTx(tx: ApiReturn) { const service = await Services.getInstance() if(tx.ciphers_to_send && tx.ciphers_to_send.length) { await this.sendCipherMessages(tx.ciphers_to_send) } if(tx.new_tx_to_send) { service.websocketConnection?.sendMessage('NewTx', tx.new_tx_to_send) } if(tx.updated_process && tx.updated_process.length) { // if(tx.ciphers_to_send?.length != 1 || tx.updated_process?.length != 2) return const impendingRequest = tx.updated_process[1].impending_requests[0] const pcdCommitment = impendingRequest.payload const outpointCommitment = tx.updated_process[0] console.log("🚀 ~ Services ~ processTx ~ pcdCommitment:", pcdCommitment) if(impendingRequest.prd_type === 'Update' && tx.ciphers_to_send.length === 0) { const router = await Routing.getInstance(); // Mocker le fait que c'est une transaction de connection const services = await Services.getInstance(); // const faucetMessage = await services.createFaucetMessage() // console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage) // services.websocketConnection?.sendNormalMessage(faucetMessage) router.openConfirmationModal(impendingRequest, outpointCommitment) } } if(tx.updated_cached_msg && tx.updated_cached_msg.length) { tx.updated_cached_msg.forEach(message => this.saveTxToDb(message)) } // if(tx.prd) { // const parsedPrd = JSON.parse(tx.prd) // console.log("🚀 ~ Services ~ processTx ~ Opened:", parsedPrd) // const router = await Routing.getInstance(); // // Mocker le fait que c'est une transaction de connection // if(parsedPrd && parsedPrd.prd_type === 'Init') { // const services = await Services.getInstance(); // const faucetMessage = await services.createFaucetMessage() // console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage) // services.websocketConnection?.sendNormalMessage(faucetMessage) // router.openConfirmationModal(parsedPrd) // } else if(parsedPrd && parsedPrd.prd_type === 'Signed') { // router.closeLoginModal() // } // } } async pairDevice(prd: any, outpointCommitment: string) { console.log("🚀 ~ Services ~ pairDevice ~ prd:", prd) const service = await Services.getInstance(); const spAddress = await this.getDeviceAddress() as any; const sender = JSON.parse(prd?.sender) const senderAddress = sender?.sp_addresses?.find((address: string) => address !== spAddress) console.log("🚀 ~ Services ~ pairDevice ~ senderAddress:", senderAddress) if(senderAddress) { const proposal = service.sdkClient.get_update_proposals(outpointCommitment); console.log("🚀 ~ Services ~ pairDevice ~ proposal:", proposal) // const pairingTx = proposal.pairing_tx.replace(/^\"+|\"+$/g, '') const parsedProposal = JSON.parse(proposal[0]) console.log("🚀 ~ Services ~ pairDevice ~ parsedProposal:", parsedProposal) const roles = JSON.parse(parsedProposal.roles) console.log("🚀 ~ Services ~ pairDevice ~ roles:", roles, Array.isArray(roles), !roles.owner) if(Array.isArray(roles) || !roles.owner) return const proposalMembers = roles?.owner?.members const isFirstDevice = proposalMembers.some((member: Member) => member.sp_addresses.some(address => address === spAddress)) const isSecondDevice = proposalMembers.some((member: Member) => member.sp_addresses.some(address => address === senderAddress)) console.log("🚀 ~ Services ~ pairDevice ~ proposalMembers:", proposalMembers) if(proposalMembers?.length !== 2 || !isFirstDevice || !isSecondDevice) return const pairingTx = parsedProposal?.pairing_tx?.replace(/^\"+|\"+$/g, '') let txid = '0'.repeat(64) console.log("🚀 ~ Services ~ pairDevice ~ pairingTx:", pairingTx, `${txid}:4294967295`) const pairing = await service.sdkClient.pair_device(`${txid}:4294967295`, [senderAddress]) const device = this.dumpDevice() console.log("🚀 ~ Services ~ pairDevice ~ device:", device) this.saveDevice(device) // await service.sdkClient.pair_device(pairingTx, [senderAddress]) console.log("🚀 ~ Services ~ pairDevice ~ pairing:", pairing) // const process = await this.prepareProcessTx(spAddress, senderAddress) console.log("🚀 ~ Services ~ pairDevice ~ process:", outpointCommitment, prd, prd.payload) const prdString = JSON.stringify(prd).trim() console.log("🚀 ~ Services ~ pairDevice ~ prdString:", prdString) let tx = await service.sdkClient.response_prd(outpointCommitment, prdString, true) console.log("🚀 ~ Services ~ pairDevice ~ tx:", tx) if(tx.ciphers_to_send) { tx.ciphers_to_send.forEach((cipher: string) => service.websocketConnection?.sendMessage('Cipher', cipher)) } this.injectProcessListPage() } } async saveTxToDb(tx: CachedMessage) { const database = await Database.getInstance(); const indexedDb = await database.getDb(); await database.writeObject(indexedDb, 'messages', tx, null); } async saveCipherTxToDb(tx: string) { const database = await Database.getInstance(); const indexedDb = await database.getDb(); if(tx) { await database.writeObject(indexedDb, database.getStoreList().AnkCipherMessages, tx, null); } } async getAmount() { const services = await Services.getInstance(); const amount = await services.sdkClient.get_available_amount() console.log("🚀 ~ Services ~ sendPairingTx ~ amount:", amount) return amount } async getDeviceAddress() { const services = await Services.getInstance(); const address = await services.sdkClient.get_address() console.log("🚀 ~ Services ~ sendPairingTx ~ address:", address) return address } async dumpDevice() { const services = await Services.getInstance(); const device = await services.sdkClient.dump_device() console.log("🚀 ~ Services ~ sendPairingTx ~ device:", device) return device } async saveDevice(device: any): Promise { console.log("🚀 ~ Services ~ saveDevice ~ device:", device) localStorage.setItem('wallet', device); } async getDevice(): Promise { return localStorage.getItem('wallet') } async dumpWallet() { const services = await Services.getInstance(); const wallet = await services.sdkClient.dump_wallet() console.log("🚀 ~ Services ~ sendPairingTx ~ wallet:", wallet) return wallet } async createFaucetMessage() { const services = await Services.getInstance() const message = await services.sdkClient.create_faucet_msg() return message; } private addSubscription(element: Element, event: string, eventHandler: string): void { this.subscriptions.push({ element, event, eventHandler }); element.addEventListener(event, (this as any)[eventHandler].bind(this)); } async getWallet(): Promise { const database = await Database.getInstance(); const indexedDb = await database.getDb(); const wallet = await database.getAll(indexedDb, database.getStoreList().AnkSpAddress) console.log("🚀 ~ Services ~ getWallet ~ wallet:", wallet) if(wallet.length) return wallet; return undefined; } async createNewDevice() { const service = await Services.getInstance(); const sp_address = await service.sdkClient.create_new_device(0, 'regtest') if(sp_address) { const database = await Database.getInstance(); const indexedDb = await database.getDb(); await database.writeObject(indexedDb, database.getStoreList().AnkSpAddress, {sp_address: sp_address}, null); const device = await service.dumpDevice() console.log("🚀 ~ WebSocketClient ~ device:", device) await service.saveDevice(device) } this.sp_address = sp_address; return sp_address; } async getAdresses() { const database = await Database.getInstance(); const indexedDb = await database.getDb(); const wallet = await database.getAll(indexedDb, database.getStoreList().AnkSpAddress) as {sp_address: string}[] console.log("🚀 ~ Services ~ getWallet ~ wallet:", wallet) if(wallet.length) { this.sp_address = wallet[0].sp_address } return this.sp_address } async restoreDevice(address: string) { const services = await Services.getInstance(); // const sp_wallet = JSON.parse(address)?.sp_wallet console.log("🚀 ~ Services ~ restoreDevice ~ services?.sdkClient:", address) const res = await services?.sdkClient?.restore_device(address) console.log("🚀 ~ Services ~ restoreDevice ~ res:", res) } private cleanSubsciptions(): void { for (const sub of this.subscriptions) { const el = sub.element; const eventHandler = sub.eventHandler; el.removeEventListener(sub.event, (this as any)[eventHandler].bind(this)); } this.subscriptions = []; } async injectProcessListPage(): Promise { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } this.cleanSubsciptions() const services = await Services.getInstance(); // const user = services.sdkClient.create_user('Test', 'test', 10, 1, 'Messaging') // console.log("🚀 ~ Services ~ injectProcessListPage ~ user:", user) // const database = await Database.getInstance(); // const indexedDb = await database.getDb(); // await database.writeObject(indexedDb, database.getStoreList().AnkUser, user.user, null); container.innerHTML = processPage; const newScript = document.createElement('script'); newScript.textContent = processScript; document.head.appendChild(newScript).parentNode?.removeChild(newScript); this.processes = await services.getProcesses(); if(this.processes) { services.setProcessesInSelectElement(this.processes) } } public async setProcessesInSelectElement(processList: any[]) { const select = document.querySelector(".select-field"); if(select) { for (const process of processList) { const option = document.createElement("option"); option.setAttribute("value", process.name); option.innerText = process.name; select.appendChild(option); } } const optionList = document.querySelector('.autocomplete-list'); if(optionList) { const observer = new MutationObserver((mutations, observer) => { const options = optionList.querySelectorAll('li') if(options) { for(const option of options) { this.addSubscription(option, 'click', 'showSelectedProcess') } } }); observer.observe(document, { subtree: true, attributes: true, }); } } public async listenToOptionListPopulating(event: Event) { const target = event.target as HTMLUListElement; const options = target?.querySelectorAll('li') } public async showSelectedProcess(event: MouseEvent) { const elem = event.target; if(elem) { const cardContent = document.querySelector(".card-content"); const services = await Services.getInstance(); const processes = await services.getProcesses(); console.log("🚀 ~ Services ~ showSelectedProcess ~ processes:", processes) const process = processes.find((process: any) => process.name === (elem as any).dataset.value); if (process) { const processDiv = document.createElement("div"); processDiv.className = "process"; processDiv.id = process.name; const titleDiv = document.createElement("div"); titleDiv.className = "process-title"; titleDiv.innerHTML = `${process.name} : ${process.description}`; processDiv.appendChild(titleDiv); for (const zone of process.zoneList) { const zoneElement = document.createElement("div"); zoneElement.className = "process-element"; zoneElement.setAttribute('zone-id', zone.id.toString()) zoneElement.innerHTML = `Zone ${zone.id} : ${zone.name}`; this.addSubscription(zoneElement, 'click', 'goToProcessPage') processDiv.appendChild(zoneElement); } if(cardContent) cardContent.appendChild(processDiv); } } } goToProcessPage(event: MouseEvent) { const target = event.target as HTMLDivElement; const zoneId = target?.getAttribute('zone-id'); const processList = document.querySelectorAll('.process-element'); if(processList) { for(const process of processList) { process.classList.remove('selected') } } target.classList.add('selected') console.log('=======================> going to process page', zoneId) } async getProcesses(): Promise { const processes = this.sdkClient.get_processes() console.log("🚀 ~ Services ~ getProcesses ~ processes:", processes) return [ { id: 1, name: "Messaging", description: "Encrypted messages", zoneList: [ { id: 1, name: "General", path: '/test' }, ], }, { id: 2, name: "Storage", description: "Distributed storage", zoneList: [ { id: 1, name: "Paris", path: '/test' }, { id: 2, name: "Normandy", path: '/test' }, { id: 3, name: "New York", path: '/test' }, { id: 4, name: "Moscow", path: '/test' }, ], }, ]; } getNotifications(): INotification[] { return [ { id: 1, title: 'Notif 1', description: 'A normal notification', sendToNotificationPage: false, path: '/notif1' }, { id: 2, title: 'Notif 2', description: 'A normal notification', sendToNotificationPage: false, path: '/notif2' }, { id: 3, title: 'Notif 3', description: 'A normal notification', sendToNotificationPage: false, path: '/notif3' } ] } async setNotification(): Promise { const badge = document.querySelector('.notification-badge') as HTMLDivElement const notifications = this.notifications const noNotifications = document.querySelector('.no-notification') as HTMLDivElement if(notifications?.length) { badge.innerText = notifications.length.toString() const notificationBoard = document.querySelector('.notification-board') as HTMLDivElement noNotifications.style.display = 'none' for(const notif of notifications) { const notifElement = document.createElement("div"); notifElement.className = "notification-element"; notifElement.setAttribute('notif-id', notif.id.toString()) notifElement.innerHTML = `
${notif.title}
${notif.description}
`; // this.addSubscription(notifElement, 'click', 'goToProcessPage') notificationBoard.appendChild(notifElement); } } else { noNotifications.style.display = 'block' } } }