import { createUserReturn, User, Process } from '../dist/pkg/sdk_client'; import IndexedDB from './database' import { WebSocketClient } from './websockets'; class Services { private static instance: Services; private sdkClient: any; private current_process: string | null = null; private websocketConnection: WebSocketClient[] = []; // 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; } // The init method is now part of the instance, and should only be called once private async init(): Promise { this.sdkClient = await import("../dist/pkg/sdk_client"); this.sdkClient.setup(); await this.updateProcesses(); } public async addWebsocketConnection(url: string): Promise { const services = await Services.getInstance(); const newClient = new WebSocketClient(url, services); if (!services.websocketConnection.includes(newClient)) { services.websocketConnection.push(newClient); } } public async isNewUser(): Promise { let isNew = false; try { const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); let userListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkUser); if (userListObject.length == 0) { isNew = true; } } catch (error) { console.error("Failed to retrieve isNewUser :", error); } return isNew; } public async displayCreateId(): Promise { const services = await Services.getInstance(); await services.createIdInjectHtml(); services.attachSubmitListener("form4nk", (event) => services.createId(event)); services.attachClickListener("displayrecover", services.displayRecover); await services.displayProcess(); } public async createId(event: Event): Promise { event.preventDefault(); const passwordElement = document.getElementById("password") as HTMLInputElement; const processElement = document.getElementById("selectProcess") as HTMLSelectElement; if (!passwordElement || !processElement) { console.error("One or more elements not found"); return; } const password = passwordElement.value; this.current_process = processElement.value; // console.log("JS password: " + password + " process: " + this.current_process); // To comment if test // if (!Services.instance.isPasswordValid(password)) return; const label = null; const birthday_signet = 50000; const birthday_main = 500000; const services = await Services.getInstance(); let createUserReturn: createUserReturn = services.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process); let user = createUserReturn.user; const shares = user.shares; // send the shares on the network const revokeData = user.revoke_data; if (!revokeData) { console.error('Failed to get revoke data from wasm'); return; } user.shares = []; user.revoke_data = null; try { const indexedDb = await IndexedDB.getInstance(); const db = await indexedDb.getDb(); await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user, null); } catch (error) { console.error("Failed to write user object :", error); } let sp_address = ""; try { sp_address = services.sdkClient.get_receiving_address(user.pre_id); console.info('Using sp_address:', sp_address); } catch (error) { console.error(error); } await services.obtainTokenWithFaucet(sp_address); await services.displayRevokeImage(new Uint8Array(revokeData)); } public async displayRecover(): Promise { const services = await Services.getInstance(); await services.recoverInjectHtml(); services.attachSubmitListener("form4nk", (event) => services.recover(event)); services.attachClickListener("displaycreateid", services.displayCreateId); services.attachClickListener("displayrevoke", services.displayRevoke); services.attachClickListener("submitButtonRevoke", services.revoke); await services.displayProcess(); } public async recover(event: Event) { event.preventDefault(); console.log("JS recover submit "); const passwordElement = document.getElementById("password") as HTMLInputElement; const processElement = document.getElementById("selectProcess") as HTMLSelectElement; if (!passwordElement || !processElement) { console.error("One or more elements not found"); return; } const password = passwordElement.value; const process = processElement.value; console.log("JS password: " + password + " process: " + process); // To comment if test // if (!Services.instance.isPasswordValid(password)) return; // TODO alert("Recover submit to do ..."); } public async displayRevokeImage(revokeData: Uint8Array): Promise { const services = await Services.getInstance(); await services.revokeImageInjectHtml(); services.attachClickListener("displayupdateanid", services.displayUpdateAnId); let imageBytes = await services.getRecoverImage('assets/4nk_revoke.jpg'); if (imageBytes != null) { var elem = document.getElementById("revoke") as HTMLAnchorElement; if (elem != null) { let imageWithData = services.sdkClient.add_data_to_image(imageBytes, revokeData, true); } } } private async getRecoverImage(imageUrl:string): Promise { let imageBytes = null; try { const response = await fetch(imageUrl); if (!response.ok) { throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`); } const arrayBuffer = await response.arrayBuffer(); imageBytes = new Uint8Array(arrayBuffer); } catch (error) { console.error("Failed to get image : "+imageUrl, error); } return imageBytes; } public async displayRevoke(): Promise { const services = await Services.getInstance(); await services.revokeInjectHtml(); services.attachClickListener("displayrecover", Services.instance.displayRecover); services.attachSubmitListener("form4nk", Services.instance.revoke); } public async revoke(event: Event): Promise { event.preventDefault(); console.log("JS revoke click "); // TODO alert("revoke click to do ..."); } public async displayUpdateAnId() { const services = await Services.getInstance(); await services.updateIdInjectHtml(); services.attachSubmitListener("form4nk", services.updateAnId); } public async parseNetworkMessage(raw: string): Promise { const services = await Services.getInstance(); try { const msg: parseNetworkMsgReturn = services.sdkClient.parse_network_msg(raw); return msg; } catch (error) { console.error(error); return null; } } public async updateAnId(event: Event): Promise { event.preventDefault(); // TODO get values const firstNameElement = 'firstName'; const lastNameElement = 'lastName'; const firstName = document.getElementById(firstNameElement) as HTMLInputElement; const lastName = document.getElementById(lastNameElement) as HTMLInputElement; console.log("JS updateAnId submit "); // TODO alert("updateAnId submit to do ... Name : "+firstName.value + " "+lastName.value); // TODO Mock add user member to process } public async displayProcess(): Promise { const services = await Services.getInstance(); const processList = await services.getAllProcess(); const selectProcess = document.getElementById("selectProcess"); if (selectProcess) { processList.forEach((process) => { let child = new Option(process.name, process.name); if (!selectProcess.contains(child)) { selectProcess.appendChild(child); } }) } } public async addProcess(process: Process): Promise { try { const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null); } catch (error) { console.log('addProcess failed: ',error); } } public async getAllProcess(): Promise { try { const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); let processListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkProcess); return processListObject; } catch (error) { console.log('getAllProcess failed: ',error); return []; } } public async checkTransaction(tx: string, tweak_data: string, blkheight: number): Promise { const services = await Services.getInstance(); try { const updated_user: string = services.sdkClient.check_transaction_for_silent_payments(tx, blkheight, tweak_data); await services.updateOwnedOutputsForUser(updated_user); return updated_user; } catch (error) { console.error(error); return null; } } public async getAllProcessForUser(pre_id: string): Promise { const services = await Services.getInstance(); let user: User; let userProcessList: Process[] = []; try { const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); user = await indexedDB.getObject(db, indexedDB.getStoreList().AnkUser, pre_id); } catch (error) { console.error('getAllUserProcess failed: ',error); return []; } try { const processListObject = await services.getAllProcess(); processListObject.forEach(async (processObject) => { if (processObject.members.includes(user.pre_id)) { userProcessList.push(processObject); } }) } catch (error) { console.error('getAllUserProcess failed: ',error); return []; } return userProcessList; } public async getProcessByName(name: string): Promise { console.log('getProcessByName name: '+name); const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); const process = await indexedDB.getFirstMatchWithIndex(db, indexedDB.getStoreList().AnkProcess, 'by_name', name); console.log('getProcessByName process: '+process); return process; } public async updateProcesses(): Promise { const services = await Services.getInstance(); const processList: Process[] = services.sdkClient.get_processes(); processList.forEach(async (process: Process) => { const indexedDB = await IndexedDB.getInstance(); const db = await indexedDB.getDb(); try { const processStore = await indexedDB.getObject(db, indexedDB.getStoreList().AnkProcess, process.id); if (!processStore) { await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null); } } catch (error) { console.error('Error while writing process', process.name, 'to indexedDB:', error); } }) } public attachClickListener(elementId: string, callback: (event: Event) => void): void { const element = document.getElementById(elementId); element?.removeEventListener("click", callback); element?.addEventListener("click", callback); } public attachSubmitListener(elementId: string, callback: (event: Event) => void): void { const element = document.getElementById(elementId); element?.removeEventListener("submit", callback); element?.addEventListener("submit", callback); } public async revokeInjectHtml() { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } container.innerHTML = `

Revoke an Id



`; } public async revokeImageInjectHtml() { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } container.innerHTML = `

Revoke image

`; } public async recoverInjectHtml() { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } const services = await Services.getInstance(); await services.updateProcesses(); container.innerHTML = `

Recover my Id



Revoke

`; } public async createIdInjectHtml() { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } container.innerHTML = `

Create an Id




`; } public async updateIdInjectHtml() { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } container.innerHTML = `

Update an Id


`; } public async injectHtml(processName: string) { const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } const services = await Services.getInstance(); // do we have all processes in db? const knownProcesses = await services.getAllProcess(); const processesFromNetwork: Process[] = services.sdkClient.get_processes(); const processToAdd = processesFromNetwork.filter(processFromNetwork => !knownProcesses.some(knownProcess => knownProcess.id === processFromNetwork.id)); processToAdd.forEach(async p => { await services.addProcess(p); }) // get the process we need const process = await services.getProcessByName(processName); if (process) { container.innerHTML = process.html; } else { console.error("No process ", processName); } } // public async getCurrentProcess(): Promise { // let currentProcess = ""; // try { // const indexedDB = await IndexedDB.getInstance(); // const db = indexedDB.getDb(); // currentProcess = await indexedDB.getObject(db, indexedDB.getStoreList().AnkSession, Services.CURRENT_PROCESS); // } catch (error) { // console.error("Failed to retrieve currentprocess object :", error); // } // return currentProcess; // } public isPasswordValid(password: string) { var alertElem = document.getElementById("passwordalert"); var success = true; var strength = 0; if (password.match(/[a-z]+/)) { var strength = 0; strength += 1; } if (password.match(/[A-Z]+/)) { strength += 1; } if (password.match(/[0-9]+/)) { strength += 1; } if (password.match(/[$@#&!]+/)) { strength += 1; } if (alertElem !== null) { // TODO Passer à 18 if (password.length < 4) { alertElem.innerHTML = "Password size is < 4"; success = false; } else { if (password.length > 30) { alertElem.innerHTML = "Password size is > 30"; success = false; } else { if (strength < 4) { alertElem.innerHTML = "Password need [a-z] [A-Z] [0-9]+ [$@#&!]+"; success = false; } } } } return success; } private async pickWebsocketConnectionRandom(): Promise { const services = await Services.getInstance(); const websockets = services.websocketConnection; if (websockets.length === 0) { console.error("No websocket connection available at the moment"); return null; } else { const random = Math.floor(Math.random() * websockets.length); return websockets[random]; } } public async obtainTokenWithFaucet(spaddress: string): Promise { const services = await Services.getInstance(); const connection = await services.pickWebsocketConnectionRandom(); if (!connection) { return null; } try { const flag: AnkFlag = "Faucet"; const faucetMsg: FaucetMessage = { 'sp_address': spaddress } connection.sendMessage(flag, JSON.stringify(faucetMsg)); } catch (error) { console.error("Failed to obtain tokens with relay ", connection.getUrl()); return null; } return null; } } export default Services;