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(); } // public async getSpAddressDefaultClient(): Promise { // try { // const indexedDB = await IndexedDB.getInstance(); // const db = indexedDB.getDb(); // const spClient = await indexedDB.getObject(db, indexedDB.getStoreList().SpClient, "default"); // if (spClient) { // return this.sdkClient.get_receiving_address(spClient); // } else { // console.error("SP client not found"); // return null; // } // } catch (error) { // console.error("Failed to retrieve object or get sp address:", error); // return null; // } // } 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 = 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.injectHtml('CREATE_ID'); 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; let label = null; let birthday_signet = 50000; let birthday_main = 500000; const user: createUserReturn = this.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process); try { const indexedDb = await IndexedDB.getInstance(); const db = indexedDb.getDb(); await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user.user, null); await indexedDb.writeObject(db, indexedDb.getStoreList().SpOutputs, user.output_list_vec, null); } catch (error) { console.error("Failed to write user object :", error); } await Services.instance.displayRevokeImage(); } public async displayRecover(): Promise { const services = await Services.getInstance(); await services.injectHtml('RECOVER'); services.attachSubmitListener("form4nk", services.recover); 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(): Promise { const services = await Services.getInstance(); await services.injectHtml('REVOKE_IMAGE'); services.attachClickListener("displayupdateanid", services.displayUpdateAnId); let imageBytes = await services.getRecoverImage('assets/4nk_revoke.jpg'); if (imageBytes != null) { let blob = new Blob([imageBytes], {type: 'image/png'}); var elem = document.getElementById("revoke") as HTMLAnchorElement; if (elem != null) { elem.href = URL.createObjectURL(blob); } } } 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 parseBitcoinMessage(raw: Blob): Promise { try { const buffer = await raw.arrayBuffer(); const uint8Array = new Uint8Array(buffer); const msg: string = this.sdkClient.parse_bitcoin_network_msg(uint8Array); return msg; } catch (error) { console.error("Error processing the blob:", error); return null; } } public async displayRevoke(): Promise { const services = await Services.getInstance(); services.injectHtml('REVOKE'); 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(); console.log("JS displayUpdateAnId process : "+services.current_process); let body = ""; let style = ""; let script = ""; try { const processObject = await services.getProcessByName("UPDATE_ID"); if (processObject) { body = processObject.html; style = processObject.style; script = processObject.script; console.log("JS displayUpdateAnId body : "+body); } } catch (error) { console.error("Failed to retrieve process with Error:", error); } services.injectUpdateAnIdHtml(body, style, script); services.attachSubmitListener("form4nk", services.updateAnId); } public async parse4nkMessage(raw: string): Promise { const msg: string = this.sdkClient.parse_4nk_msg(raw); return msg; } public injectUpdateAnIdHtml(bodyToInject: string, styleToInject: string, scriptToInject: string) { console.log("JS html : "+bodyToInject); const body = document.getElementsByTagName('body')[0]; if (!body) { console.error("No body tag"); return; } body.innerHTML = styleToInject + bodyToInject; const script = document.createElement("script"); script.innerHTML = scriptToInject; document.body.appendChild(script); script.onload = () => { console.log('Script loaded successfuly'); }; script.onerror = () => { console.log('Error loading script'); }; } 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 getAllProcess(): Promise { try { const indexedDB = await IndexedDB.getInstance(); const db = 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): Promise { const services = await Services.getInstance(); try { return services.sdkClient.check_network_transaction(tx); } 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 = 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 = indexedDB.getDb(); const process = await indexedDB.getFirstMatchWithIndex(db, indexedDB.getStoreList().AnkProcess, 'by_name', name); console.log('getProcessByName process: '+process); return process; } public async loadProcesses(): Promise { const services = await Services.getInstance(); const processList: Process[] = services.sdkClient.get_processes(); console.error('processList size: '+processList.length); processList.forEach(async (process: Process) => { const indexedDB = await IndexedDB.getInstance(); const db = indexedDB.getDb(); try { const processStore = await indexedDB.getObject(db, indexedDB.getStoreList().AnkProcess, process.id); if (!processStore) { console.error('Add process.id : '+process.id); await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null); } } catch (error) { console.warn('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 injectHtml(processName: string) { // console.log("JS html : "+html); const container = document.getElementById('containerId'); if (!container) { console.error("No html container"); return; } const services = await Services.getInstance(); await services.loadProcesses(); 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; } public obtainTokenWithFaucet(wsclient: WebSocketClient, spaddress: string): string | null { try { wsclient.sendMessage(spaddress); } catch (error) { console.error("Failed to obtain tokens with relay ", wsclient.getUrl()); return null; } return null; } } export default Services;