From 29f09e92b7a086e2b33a6ff5535237183d4b002a Mon Sep 17 00:00:00 2001 From: AnisHADJARAB Date: Tue, 29 Oct 2024 12:49:13 +0000 Subject: [PATCH] add prettify to project --- .prettierignore | 5 + .prettierrc | 14 + package-lock.json | 135 ++- package.json | 4 +- src/index.ts | 79 +- src/models/notification.model.ts | 41 +- src/models/process.model.ts | 46 +- src/pages/home/home.ts | 221 +++-- src/pages/process/process.ts | 937 ++++++++++--------- src/router.ts | 167 ++-- src/services/routing.service.ts | 234 ++--- src/services/service.ts | 1465 +++++++++++++++--------------- src/websockets.ts | 273 +++--- 13 files changed, 1867 insertions(+), 1754 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..103bd51 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage +/.nx/cache +.angular diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ac2f96c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "printWidth": 300, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "requirePragma": false, + "insertPragma": false, + "endOfLine": "crlf" +} diff --git a/package-lock.json b/package-lock.json index 6e4b538..63e903c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,13 @@ "vite": "^5.4.9", "vite-plugin-copy": "^0.1.6", "vite-plugin-html": "^3.2.2", + "vite-plugin-terminal": "^1.2.0", "vite-plugin-wasm": "^3.3.0" }, "devDependencies": { "copy-webpack-plugin": "^12.0.2", "html-webpack-plugin": "^5.6.0", + "prettier": "^3.3.3", "ts-loader": "^9.5.1", "typescript": "^5.3.3", "vite-plugin-static-copy": "^1.0.6", @@ -902,6 +904,56 @@ "node": ">=14" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "license": "MIT" + }, + "node_modules/@rollup/plugin-strip": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-strip/-/plugin-strip-3.0.4.tgz", + "integrity": "sha512-LDRV49ZaavxUo2YoKKMQjCxzCxugu1rCPQa0lDYBOWLj6vtzBMr8DcoJjsmg+s450RbKbe3qI9ZLaSO+O1oNbg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-strip/node_modules/@rollup/pluginutils": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", + "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", @@ -4136,6 +4188,12 @@ "node": ">=0.10.0" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, "node_modules/launch-editor": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.0.tgz", @@ -4192,7 +4250,6 @@ "version": "0.30.10", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -4334,6 +4391,15 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4739,6 +4805,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -5430,6 +5512,20 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -5808,6 +5904,15 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tree-dump": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", @@ -5966,6 +6071,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -6201,6 +6312,28 @@ "node": ">=14.14" } }, + "node_modules/vite-plugin-terminal": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-terminal/-/vite-plugin-terminal-1.2.0.tgz", + "integrity": "sha512-IIw1V+IySth8xlrGmH4U7YmfTp681vTzYpa7b8A3KNCJ2oW1BGPPwW8tSz6BQTvSgbRmrP/9NsBLsfXkN4e8sA==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-strip": "^3.0.2", + "debug": "^4.3.4", + "kolorist": "^1.7.0", + "sirv": "^2.0.2", + "ufo": "^1.1.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/patak-dev" + }, + "peerDependencies": { + "vite": "^2.0.0||^3.0.0||^4.0.0||^5.0.0" + } + }, "node_modules/vite-plugin-wasm": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.3.0.tgz", diff --git a/package.json b/package.json index b7d95e8..93662af 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "build_wasm": "wasm-pack build --out-dir ../ihm_client/dist/pkg ../sdk_client --target bundler --dev", "start": "vite --host 0.0.0.0", "build": "tsc && vite build", - "deploy": "sudo cp -r dist/* /var/www/html/" + "deploy": "sudo cp -r dist/* /var/www/html/", + "prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html.scss}\"" }, "keywords": [], "author": "", @@ -16,6 +17,7 @@ "devDependencies": { "copy-webpack-plugin": "^12.0.2", "html-webpack-plugin": "^5.6.0", + "prettier": "^3.3.3", "ts-loader": "^9.5.1", "typescript": "^5.3.3", "vite-plugin-static-copy": "^1.0.6", diff --git a/src/index.ts b/src/index.ts index 60d65b4..76a0723 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,40 +1,39 @@ -// import Services from './services/service'; - -// document.addEventListener('DOMContentLoaded', async () => { -// try { - -// const services = await Services.getInstance(); -// setTimeout( async () => { -// let device = await services.getDevice() -// console.log("๐Ÿš€ ~ setTimeout ~ device:", device) - -// if(!device) { -// device = await services.createNewDevice(); -// } else { -// await services.restoreDevice(device) -// } -// await services.restoreProcesses(); -// await services.restoreMessages(); - -// const amount = await services.getAmount(); - -// if (amount === 0n) { -// const faucetMsg = await services.createFaucetMessage(); -// await services.sendFaucetMessage(faucetMsg); -// } -// if (services.isPaired()) { await services.injectProcessListPage() } -// else { -// const queryString = window.location.search; -// const urlParams = new URLSearchParams(queryString) -// const pairingAddress = urlParams.get('sp_address') - -// if(pairingAddress) { -// setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000) -// } -// } -// }, 500); -// } catch (error) { -// console.error(error); -// } -// }); - +// import Services from './services/service'; + +// document.addEventListener('DOMContentLoaded', async () => { +// try { + +// const services = await Services.getInstance(); +// setTimeout( async () => { +// let device = await services.getDevice() +// console.log("๐Ÿš€ ~ setTimeout ~ device:", device) + +// if(!device) { +// device = await services.createNewDevice(); +// } else { +// await services.restoreDevice(device) +// } +// await services.restoreProcesses(); +// await services.restoreMessages(); + +// const amount = await services.getAmount(); + +// if (amount === 0n) { +// const faucetMsg = await services.createFaucetMessage(); +// await services.sendFaucetMessage(faucetMsg); +// } +// if (services.isPaired()) { await services.injectProcessListPage() } +// else { +// const queryString = window.location.search; +// const urlParams = new URLSearchParams(queryString) +// const pairingAddress = urlParams.get('sp_address') + +// if(pairingAddress) { +// setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000) +// } +// } +// }, 500); +// } catch (error) { +// console.error(error); +// } +// }); diff --git a/src/models/notification.model.ts b/src/models/notification.model.ts index 2648e48..c99c41e 100644 --- a/src/models/notification.model.ts +++ b/src/models/notification.model.ts @@ -1,22 +1,19 @@ -export interface INotification { - id: number; - title: string; - description: string; - sendToNotificationPage?: boolean; - path?: string; - -} - - -// Quelles sont les donnรฉes utiles pour le user ??? -export interface IUser { - id: string; - information?: any -} - - -// Quelles sont les donnรฉes utiles pour les messages ??? -export interface IMessage { - id: string; - message: any -} \ No newline at end of file +export interface INotification { + id: number; + title: string; + description: string; + sendToNotificationPage?: boolean; + path?: string; +} + +// Quelles sont les donnรฉes utiles pour le user ??? +export interface IUser { + id: string; + information?: any; +} + +// Quelles sont les donnรฉes utiles pour les messages ??? +export interface IMessage { + id: string; + message: any; +} diff --git a/src/models/process.model.ts b/src/models/process.model.ts index c6f147e..b236f53 100644 --- a/src/models/process.model.ts +++ b/src/models/process.model.ts @@ -1,23 +1,23 @@ -export interface IProcess { - id: number; - name: string; - description: string; - icon?: string; - zoneList: IZone[], -} - -export interface IZone { - id: number; - name: string; - path: string; - // Est-ce que la zone a besoin d'une icone ? - icon?: string; - } - - export interface INotification { - id: number; - title: string; - description: string; - sendToNotificationPage?: boolean; - path?: string; -} \ No newline at end of file +export interface IProcess { + id: number; + name: string; + description: string; + icon?: string; + zoneList: IZone[]; +} + +export interface IZone { + id: number; + name: string; + path: string; + // Est-ce que la zone a besoin d'une icone ? + icon?: string; +} + +export interface INotification { + id: number; + title: string; + description: string; + sendToNotificationPage?: boolean; + path?: string; +} diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index f8c9bfd..462c1c4 100644 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -1,114 +1,107 @@ -import { Html5QrcodeScanner } from "html5-qrcode"; -import Routing from "../../services/routing.service"; -import Services from "../../services/service"; - -let resultContainer = document.getElementById('qr-reader-results'); -let lastResult: any, countResults = 0; - -export function initHomePage(): void { - console.log('INIT') - document.querySelectorAll('.tab').forEach(tab => { - - tab.addEventListener('click', () => { - document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); - tab.classList.add('active'); - - document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); - document.getElementById(tab.getAttribute('data-tab') as string)?.classList.add('active'); - - }); - }); - -document.getElementById('notification-bell')?.addEventListener('click', openCloseNotifications); - -var html5QrcodeScanner = new Html5QrcodeScanner( - "qr-reader", { fps: 10, qrbox: 250 }, undefined); -html5QrcodeScanner.render(onScanSuccess, undefined); - -docReady(() => { - scanDevice(); - - -}); -} - - -async function onScanSuccess(decodedText: any, decodedResult: any) { - if (lastResult === decodedText) { return; } - lastResult = decodedText; - ++countResults; - // Handle on success condition with the decoded message. - console.log(`Scan result ${decodedText}`, decodedResult); - try { - // Attempt to parse the decoded text as a URL - const scannedUrl = new URL(decodedText); - - // Extract the 'sp_address' parameter - const spAddress = scannedUrl.searchParams.get('sp_address'); - - if (spAddress) { - html5QrcodeScanner.clear(); - const service = await Services.getInstance() - // Call the sendPairingTx function with the extracted sp_address - await service.sendPairingTx(spAddress); - } else { - console.error('The scanned URL does not contain the sp_address parameter.'); - alert('Invalid QR code: sp_address parameter missing.'); - } - } catch (error) { - // Handle cases where decodedText is not a valid URL - console.error('Scanned text is not a valid URL:', error); - alert('Invalid QR code: Unable to parse URL.'); - } -} -export function toggleMenu() { - let menu = document.getElementById('menu'); - if(menu) { - if (menu.style.display === 'block') { - menu.style.display = 'none'; - } else { - menu.style.display = 'block'; - } - } -} - - //// Modal - export async function openModal(myAddress: string, receiverAddress: string) { - const router = await Routing.getInstance(); - router.openLoginModal(myAddress, receiverAddress) - } - - - function openCloseNotifications() { - const notifications = document.querySelector('.notification-board') as HTMLDivElement - notifications.style.display = notifications?.style.display === 'none' ? 'block' : 'none' - } - -// const service = await Services.getInstance() -// service.setNotification() - -(window as any).toggleMenu = toggleMenu; -(window as any).openModal = openModal; - -/// Scan QR Code -function docReady(fn: any) { - // see if DOM is already available - if (document.readyState === "complete" - || document.readyState === "interactive") { - // call on next available tick - setTimeout(fn, 1); - } else { - document.addEventListener("DOMContentLoaded", fn); - } -} - - - -function scanDevice() { - const scannerImg = document.querySelector('#scanner') as HTMLElement - if(scannerImg) scannerImg.style.display = 'none' - const scannerQrCode = document.querySelector('.qr-code-scanner') as HTMLElement - if(scannerQrCode) scannerQrCode.style.display = 'block' -} - -(window as any).scanDevice = scanDevice \ No newline at end of file +import { Html5QrcodeScanner } from 'html5-qrcode'; +import Routing from '../../services/routing.service'; +import Services from '../../services/service'; + +let resultContainer = document.getElementById('qr-reader-results'); +let lastResult: any, + countResults = 0; + +export function initHomePage(): void { + console.log('INIT'); + document.querySelectorAll('.tab').forEach((tab) => { + tab.addEventListener('click', () => { + document.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); + + document.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active')); + document.getElementById(tab.getAttribute('data-tab') as string)?.classList.add('active'); + }); + }); + + document.getElementById('notification-bell')?.addEventListener('click', openCloseNotifications); + + var html5QrcodeScanner = new Html5QrcodeScanner('qr-reader', { fps: 10, qrbox: 250 }, undefined); + html5QrcodeScanner.render(onScanSuccess, undefined); + + docReady(() => { + scanDevice(); + }); +} + +async function onScanSuccess(decodedText: any, decodedResult: any) { + if (lastResult === decodedText) { + return; + } + lastResult = decodedText; + ++countResults; + // Handle on success condition with the decoded message. + console.log(`Scan result ${decodedText}`, decodedResult); + try { + // Attempt to parse the decoded text as a URL + const scannedUrl = new URL(decodedText); + + // Extract the 'sp_address' parameter + const spAddress = scannedUrl.searchParams.get('sp_address'); + + if (spAddress) { + html5QrcodeScanner.clear(); + const service = await Services.getInstance(); + // Call the sendPairingTx function with the extracted sp_address + await service.sendPairingTx(spAddress); + } else { + console.error('The scanned URL does not contain the sp_address parameter.'); + alert('Invalid QR code: sp_address parameter missing.'); + } + } catch (error) { + // Handle cases where decodedText is not a valid URL + console.error('Scanned text is not a valid URL:', error); + alert('Invalid QR code: Unable to parse URL.'); + } +} +export function toggleMenu() { + let menu = document.getElementById('menu'); + if (menu) { + if (menu.style.display === 'block') { + menu.style.display = 'none'; + } else { + menu.style.display = 'block'; + } + } +} + +//// Modal +export async function openModal(myAddress: string, receiverAddress: string) { + const router = await Routing.getInstance(); + router.openLoginModal(myAddress, receiverAddress); +} + +function openCloseNotifications() { + const notifications = document.querySelector('.notification-board') as HTMLDivElement; + notifications.style.display = notifications?.style.display === 'none' ? 'block' : 'none'; +} + +// const service = await Services.getInstance() +// service.setNotification() + +(window as any).toggleMenu = toggleMenu; +(window as any).openModal = openModal; + +/// Scan QR Code +function docReady(fn: any) { + // see if DOM is already available + if (document.readyState === 'complete' || document.readyState === 'interactive') { + // call on next available tick + setTimeout(fn, 1); + } else { + document.addEventListener('DOMContentLoaded', fn); + } +} + +function scanDevice() { + const scannerImg = document.querySelector('#scanner') as HTMLElement; + if (scannerImg) scannerImg.style.display = 'none'; + const scannerQrCode = document.querySelector('.qr-code-scanner') as HTMLElement; + if (scannerQrCode) scannerQrCode.style.display = 'block'; +} + +(window as any).scanDevice = scanDevice; diff --git a/src/pages/process/process.ts b/src/pages/process/process.ts index ca4abae..58c524d 100644 --- a/src/pages/process/process.ts +++ b/src/pages/process/process.ts @@ -1,476 +1,461 @@ -import Services from "../../services/service"; -import {IProcess } from "~/models/process.model"; -function toggleMenu() { - const menu = document.getElementById("menu") as HTMLDivElement; - if (menu.style.display === "block") { - menu.style.display = "none"; - } else { - menu.style.display = "block"; - } -} - - -// Initialize function, create initial tokens with itens that are already selected by the user -function init(element: HTMLElement) { - // Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside - const wrapper = document.createElement("div"); - wrapper.addEventListener("click", clickOnWrapper); - wrapper.classList.add("multi-select-component"); - wrapper.classList.add("input-field"); - - // Create elements of search - const search_div = document.createElement("div"); - search_div.classList.add("search-container"); - const input = document.createElement("input"); - input.classList.add("selected-input"); - input.setAttribute("autocomplete", "off"); - input.setAttribute("tabindex", "0"); - input.addEventListener("keyup", inputChange); - input.addEventListener("keydown", deletePressed); - input.addEventListener("click", openOptions); - - const dropdown_icon = document.createElement("a"); - dropdown_icon.classList.add("dropdown-icon"); - - dropdown_icon.addEventListener("click", clickDropdown); - const autocomplete_list = document.createElement("ul"); - autocomplete_list.classList.add("autocomplete-list"); - search_div.appendChild(input); - search_div.appendChild(autocomplete_list); - search_div.appendChild(dropdown_icon); - - // set the wrapper as child (instead of the element) - element.parentNode?.replaceChild(wrapper, element); - // set element as child of wrapper - wrapper.appendChild(element); - wrapper.appendChild(search_div); - - addPlaceholder(wrapper); - -// const select = document.querySelector(".select-field"); -// for (const process of processeList) { -// const option = document.createElement("option"); -// option.setAttribute("value", process.name); -// option.innerText = process.name; - -// select.appendChild(option); -// } -} - -function removePlaceholder(wrapper: HTMLElement) { - const input_search = wrapper.querySelector(".selected-input"); - input_search?.removeAttribute("placeholder"); -} - -function addPlaceholder(wrapper: HTMLElement) { - const input_search = wrapper.querySelector(".selected-input"); - const tokens = wrapper.querySelectorAll(".selected-wrapper"); - if (!tokens.length && !(document.activeElement === input_search)) - input_search?.setAttribute("placeholder", "---------"); -} - -// Listener of user search -function inputChange(e: Event) { - const target = e.target as HTMLInputElement - const wrapper = target?.parentNode?.parentNode; - const select = wrapper?.querySelector("select") as HTMLSelectElement; - const dropdown = wrapper?.querySelector(".dropdown-icon"); - - const input_val = target?.value; - - if (input_val) { - dropdown?.classList.add("active"); - populateAutocompleteList(select, input_val.trim()); - } else { - dropdown?.classList.remove("active"); - const event = new Event("click"); - dropdown?.dispatchEvent(event); - } -} - -// Listen for clicks on the wrapper, if click happens focus on the input -function clickOnWrapper(e: Event) { - const wrapper = e.target as HTMLElement; - if (wrapper.tagName == "DIV") { - const input_search = wrapper.querySelector(".selected-input"); - const dropdown = wrapper.querySelector(".dropdown-icon"); - if (!dropdown?.classList.contains("active")) { - const event = new Event("click"); - dropdown?.dispatchEvent(event); - } - (input_search as HTMLInputElement)?.focus(); - removePlaceholder(wrapper); - } -} - -function openOptions(e: Event) { - const input_search = e.target as HTMLElement; - const wrapper = input_search?.parentElement?.parentElement; - const dropdown = wrapper?.querySelector(".dropdown-icon"); - if (!dropdown?.classList.contains("active")) { - const event = new Event("click"); - dropdown?.dispatchEvent(event); - } - e.stopPropagation(); -} - -// Function that create a token inside of a wrapper with the given value -function createToken(wrapper: HTMLElement, value: any) { - const search = wrapper.querySelector(".search-container"); - const inputInderline = document.querySelector(".selected-processes"); - // Create token wrapper - const token = document.createElement("div"); - token.classList.add("selected-wrapper"); - const token_span = document.createElement("span"); - token_span.classList.add("selected-label"); - token_span.innerText = value; - const close = document.createElement("a"); - close.classList.add("selected-close"); - close.setAttribute("tabindex", "-1"); - close.setAttribute("data-option", value); - close.setAttribute("data-hits", '0'); - close.setAttribute("href", "#"); - close.innerText = "x"; - close.addEventListener("click", removeToken); - token.appendChild(token_span); - token.appendChild(close); - inputInderline?.appendChild(token); -} - -// Listen for clicks in the dropdown option -function clickDropdown(e: Event) { - const dropdown = e.target as HTMLElement; - const wrapper = dropdown?.parentNode?.parentNode; - const input_search = wrapper?.querySelector(".selected-input") as HTMLInputElement; - const select = wrapper?.querySelector("select") as HTMLSelectElement; - dropdown.classList.toggle("active"); - - if (dropdown.classList.contains("active")) { - removePlaceholder(wrapper as HTMLElement); - input_search?.focus(); - - if (!input_search?.value) { - populateAutocompleteList(select, "", true); - } else { - populateAutocompleteList(select, input_search.value); - } - } else { - clearAutocompleteList(select); - addPlaceholder(wrapper as HTMLElement); - } -} - -// Clears the results of the autocomplete list -function clearAutocompleteList(select: HTMLSelectElement) { - const wrapper = select.parentNode; - - const autocomplete_list = wrapper?.querySelector(".autocomplete-list"); - if(autocomplete_list) autocomplete_list.innerHTML = ""; -} - -// Populate the autocomplete list following a given query from the user -function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { - const { autocomplete_options } = getOptions(select); - - let options_to_show; - - if (dropdown) options_to_show = autocomplete_options; - else options_to_show = autocomplete(query, autocomplete_options); - - const wrapper = select.parentNode; - const input_search = wrapper?.querySelector(".search-container"); - const autocomplete_list = wrapper?.querySelector(".autocomplete-list"); - if(autocomplete_list) autocomplete_list.innerHTML = ""; - const result_size = options_to_show.length; - - if (result_size == 1) { - const li = document.createElement("li"); - li.innerText = options_to_show[0]; - li.setAttribute("data-value", options_to_show[0]); - li.addEventListener("click", selectOption); - autocomplete_list?.appendChild(li); - if (query.length == options_to_show[0].length) { - const event = new Event("click"); - li.dispatchEvent(event); - } - } else if (result_size > 1) { - for (let i = 0; i < result_size; i++) { - const li = document.createElement("li"); - li.innerText = options_to_show[i]; - li.setAttribute("data-value", options_to_show[i]); - li.addEventListener("click", selectOption); - autocomplete_list?.appendChild(li); - } - } else { - const li = document.createElement("li"); - li.classList.add("not-cursor"); - li.innerText = "No options found"; - autocomplete_list?.appendChild(li); - } -} - -// Listener to autocomplete results when clicked set the selected property in the select option -function selectOption(e: any) { - const wrapper = e.target.parentNode.parentNode.parentNode; - const input_search = wrapper.querySelector(".selected-input"); - const option = wrapper.querySelector( - `select option[value="${e.target.dataset.value}"]` - ); - - option.setAttribute("selected", ""); - createToken(wrapper, e.target.dataset.value); - if (input_search.value) { - input_search.value = ""; - } - - // showSelectedProcess(e.target.dataset.value); - - input_search.focus(); - - e.target.remove(); - const autocomplete_list = wrapper.querySelector(".autocomplete-list"); - - if (!autocomplete_list.children.length) { - const li = document.createElement("li"); - li.classList.add("not-cursor"); - li.innerText = "No options found"; - autocomplete_list.appendChild(li); - } - - const event = new Event("keyup"); - input_search.dispatchEvent(event); - e.stopPropagation(); -} - -// function that returns a list with the autcomplete list of matches -function autocomplete(query: string, options: any) { - // No query passed, just return entire list - if (!query) { - return options; - } - let options_return = []; - - for (let i = 0; i < options.length; i++) { - if ( - query.toLowerCase() === options[i].slice(0, query.length).toLowerCase() - ) { - options_return.push(options[i]); - } - } - return options_return; -} - -// Returns the options that are selected by the user and the ones that are not -function getOptions(select: HTMLSelectElement) { - // Select all the options available - const all_options = Array.from(select.querySelectorAll("option")).map( - (el) => el.value - ); - - // Get the options that are selected from the user - const options_selected = Array.from( - select.querySelectorAll("option:checked") - ).map((el: any) => el.value); - - // Create an autocomplete options array with the options that are not selected by the user - const autocomplete_options: any[] = []; - all_options.forEach((option) => { - if (!options_selected.includes(option)) { - autocomplete_options.push(option); - } - }); - - autocomplete_options.sort(); - - return { - options_selected, - autocomplete_options, - }; -} - -// Listener for when the user wants to remove a given token. -function removeToken(e: Event) { - // Get the value to remove - const target = e.target as HTMLSelectElement - const value_to_remove = target.dataset.option; - const wrapper = target.parentNode?.parentNode?.parentNode; - const input_search = wrapper?.querySelector(".selected-input"); - const dropdown = wrapper?.querySelector(".dropdown-icon"); - // Get the options in the select to be unselected - const option_to_unselect = wrapper?.querySelector( - `select option[value="${value_to_remove}"]` - ); - option_to_unselect?.removeAttribute("selected"); - // Remove token attribute - (target.parentNode as any)?.remove(); - dropdown?.classList.remove("active"); - const process = document.querySelector("#" + target.dataset.option); - process?.remove(); -} - -// Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist -function deletePressed(e: KeyboardEvent) { - const input_search = e.target as HTMLInputElement; - const wrapper = input_search?.parentNode?.parentNode; - const key = e.keyCode || e.charCode; - const tokens = wrapper?.querySelectorAll(".selected-wrapper"); - - if (tokens?.length) { - const last_token_x = tokens[tokens.length - 1].querySelector("a"); - let hits = +(last_token_x?.dataset?.hits || 0); - - if (key == 8 || key == 46) { - if (!input_search.value) { - if (hits > 1) { - // Trigger delete event - const event = new Event("click"); - last_token_x?.dispatchEvent(event); - } else { - if(last_token_x?.dataset.hits) last_token_x.dataset.hits = '2'; - } - } - } else { - if(last_token_x?.dataset.hits) last_token_x.dataset.hits = '0'; - } - } - return true; -} - -// function addOption(target, val, text) { -// const select = document.querySelector(target); -// let opt = document.createElement("option"); -// opt.value = val; -// opt.innerHTML = text; -// select.appendChild(opt); -// } - -// get select that has the options available -const select = document.querySelectorAll("[data-multi-select-plugin]") as NodeListOf; -select.forEach((select) => { - console.log(select); - init(select); -}); - -// Dismiss on outside click -document.addEventListener("click", () => { - // get select that has the options available - const select = document.querySelectorAll("[data-multi-select-plugin]"); - for (let i = 0; i < select.length; i++) { - if (event) { - var isClickInside = select[i].parentElement?.parentElement?.contains( - event.target as Node - ); - - if (!isClickInside) { - const wrapper = select[i].parentElement?.parentElement; - const dropdown = wrapper?.querySelector(".dropdown-icon"); - const autocomplete_list = wrapper?.querySelector(".autocomplete-list"); - //the click was outside the specifiedElement, do something - dropdown?.classList.remove("active"); - if(autocomplete_list) autocomplete_list.innerHTML = ""; - addPlaceholder(wrapper as HTMLElement); - } - } - } -}); - - -export async function unpair() { - const service = await Services.getInstance() - await service.unpairDevice() -} - -(window as any).unpair = unpair; - - -async function showSelectedProcess(event: MouseEvent) { - const elem = event.target; - if(elem) { - - const cardContent = document.querySelector(".card-content"); - - const processes = await 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}`; - const service = await Services.getInstance() - service.addSubscription(zoneElement, 'click', 'goToProcessPage') - processDiv.appendChild(zoneElement); - } - if(cardContent) cardContent.appendChild(processDiv); - } - } -} - -function 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 function getProcesses(): Promise { - const service = await Services.getInstance() - // const processes = service.getProcesses() - // 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' - }, - ], - }, - ]; -} \ No newline at end of file +import Services from '../../services/service'; +import { IProcess } from '~/models/process.model'; +function toggleMenu() { + const menu = document.getElementById('menu') as HTMLDivElement; + if (menu.style.display === 'block') { + menu.style.display = 'none'; + } else { + menu.style.display = 'block'; + } +} + +// Initialize function, create initial tokens with itens that are already selected by the user +function init(element: HTMLElement) { + // Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside + const wrapper = document.createElement('div'); + wrapper.addEventListener('click', clickOnWrapper); + wrapper.classList.add('multi-select-component'); + wrapper.classList.add('input-field'); + + // Create elements of search + const search_div = document.createElement('div'); + search_div.classList.add('search-container'); + const input = document.createElement('input'); + input.classList.add('selected-input'); + input.setAttribute('autocomplete', 'off'); + input.setAttribute('tabindex', '0'); + input.addEventListener('keyup', inputChange); + input.addEventListener('keydown', deletePressed); + input.addEventListener('click', openOptions); + + const dropdown_icon = document.createElement('a'); + dropdown_icon.classList.add('dropdown-icon'); + + dropdown_icon.addEventListener('click', clickDropdown); + const autocomplete_list = document.createElement('ul'); + autocomplete_list.classList.add('autocomplete-list'); + search_div.appendChild(input); + search_div.appendChild(autocomplete_list); + search_div.appendChild(dropdown_icon); + + // set the wrapper as child (instead of the element) + element.parentNode?.replaceChild(wrapper, element); + // set element as child of wrapper + wrapper.appendChild(element); + wrapper.appendChild(search_div); + + addPlaceholder(wrapper); + + // const select = document.querySelector(".select-field"); + // for (const process of processeList) { + // const option = document.createElement("option"); + // option.setAttribute("value", process.name); + // option.innerText = process.name; + + // select.appendChild(option); + // } +} + +function removePlaceholder(wrapper: HTMLElement) { + const input_search = wrapper.querySelector('.selected-input'); + input_search?.removeAttribute('placeholder'); +} + +function addPlaceholder(wrapper: HTMLElement) { + const input_search = wrapper.querySelector('.selected-input'); + const tokens = wrapper.querySelectorAll('.selected-wrapper'); + if (!tokens.length && !(document.activeElement === input_search)) input_search?.setAttribute('placeholder', '---------'); +} + +// Listener of user search +function inputChange(e: Event) { + const target = e.target as HTMLInputElement; + const wrapper = target?.parentNode?.parentNode; + const select = wrapper?.querySelector('select') as HTMLSelectElement; + const dropdown = wrapper?.querySelector('.dropdown-icon'); + + const input_val = target?.value; + + if (input_val) { + dropdown?.classList.add('active'); + populateAutocompleteList(select, input_val.trim()); + } else { + dropdown?.classList.remove('active'); + const event = new Event('click'); + dropdown?.dispatchEvent(event); + } +} + +// Listen for clicks on the wrapper, if click happens focus on the input +function clickOnWrapper(e: Event) { + const wrapper = e.target as HTMLElement; + if (wrapper.tagName == 'DIV') { + const input_search = wrapper.querySelector('.selected-input'); + const dropdown = wrapper.querySelector('.dropdown-icon'); + if (!dropdown?.classList.contains('active')) { + const event = new Event('click'); + dropdown?.dispatchEvent(event); + } + (input_search as HTMLInputElement)?.focus(); + removePlaceholder(wrapper); + } +} + +function openOptions(e: Event) { + const input_search = e.target as HTMLElement; + const wrapper = input_search?.parentElement?.parentElement; + const dropdown = wrapper?.querySelector('.dropdown-icon'); + if (!dropdown?.classList.contains('active')) { + const event = new Event('click'); + dropdown?.dispatchEvent(event); + } + e.stopPropagation(); +} + +// Function that create a token inside of a wrapper with the given value +function createToken(wrapper: HTMLElement, value: any) { + const search = wrapper.querySelector('.search-container'); + const inputInderline = document.querySelector('.selected-processes'); + // Create token wrapper + const token = document.createElement('div'); + token.classList.add('selected-wrapper'); + const token_span = document.createElement('span'); + token_span.classList.add('selected-label'); + token_span.innerText = value; + const close = document.createElement('a'); + close.classList.add('selected-close'); + close.setAttribute('tabindex', '-1'); + close.setAttribute('data-option', value); + close.setAttribute('data-hits', '0'); + close.setAttribute('href', '#'); + close.innerText = 'x'; + close.addEventListener('click', removeToken); + token.appendChild(token_span); + token.appendChild(close); + inputInderline?.appendChild(token); +} + +// Listen for clicks in the dropdown option +function clickDropdown(e: Event) { + const dropdown = e.target as HTMLElement; + const wrapper = dropdown?.parentNode?.parentNode; + const input_search = wrapper?.querySelector('.selected-input') as HTMLInputElement; + const select = wrapper?.querySelector('select') as HTMLSelectElement; + dropdown.classList.toggle('active'); + + if (dropdown.classList.contains('active')) { + removePlaceholder(wrapper as HTMLElement); + input_search?.focus(); + + if (!input_search?.value) { + populateAutocompleteList(select, '', true); + } else { + populateAutocompleteList(select, input_search.value); + } + } else { + clearAutocompleteList(select); + addPlaceholder(wrapper as HTMLElement); + } +} + +// Clears the results of the autocomplete list +function clearAutocompleteList(select: HTMLSelectElement) { + const wrapper = select.parentNode; + + const autocomplete_list = wrapper?.querySelector('.autocomplete-list'); + if (autocomplete_list) autocomplete_list.innerHTML = ''; +} + +// Populate the autocomplete list following a given query from the user +function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) { + const { autocomplete_options } = getOptions(select); + + let options_to_show; + + if (dropdown) options_to_show = autocomplete_options; + else options_to_show = autocomplete(query, autocomplete_options); + + const wrapper = select.parentNode; + const input_search = wrapper?.querySelector('.search-container'); + const autocomplete_list = wrapper?.querySelector('.autocomplete-list'); + if (autocomplete_list) autocomplete_list.innerHTML = ''; + const result_size = options_to_show.length; + + if (result_size == 1) { + const li = document.createElement('li'); + li.innerText = options_to_show[0]; + li.setAttribute('data-value', options_to_show[0]); + li.addEventListener('click', selectOption); + autocomplete_list?.appendChild(li); + if (query.length == options_to_show[0].length) { + const event = new Event('click'); + li.dispatchEvent(event); + } + } else if (result_size > 1) { + for (let i = 0; i < result_size; i++) { + const li = document.createElement('li'); + li.innerText = options_to_show[i]; + li.setAttribute('data-value', options_to_show[i]); + li.addEventListener('click', selectOption); + autocomplete_list?.appendChild(li); + } + } else { + const li = document.createElement('li'); + li.classList.add('not-cursor'); + li.innerText = 'No options found'; + autocomplete_list?.appendChild(li); + } +} + +// Listener to autocomplete results when clicked set the selected property in the select option +function selectOption(e: any) { + const wrapper = e.target.parentNode.parentNode.parentNode; + const input_search = wrapper.querySelector('.selected-input'); + const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`); + + option.setAttribute('selected', ''); + createToken(wrapper, e.target.dataset.value); + if (input_search.value) { + input_search.value = ''; + } + + // showSelectedProcess(e.target.dataset.value); + + input_search.focus(); + + e.target.remove(); + const autocomplete_list = wrapper.querySelector('.autocomplete-list'); + + if (!autocomplete_list.children.length) { + const li = document.createElement('li'); + li.classList.add('not-cursor'); + li.innerText = 'No options found'; + autocomplete_list.appendChild(li); + } + + const event = new Event('keyup'); + input_search.dispatchEvent(event); + e.stopPropagation(); +} + +// function that returns a list with the autcomplete list of matches +function autocomplete(query: string, options: any) { + // No query passed, just return entire list + if (!query) { + return options; + } + let options_return = []; + + for (let i = 0; i < options.length; i++) { + if (query.toLowerCase() === options[i].slice(0, query.length).toLowerCase()) { + options_return.push(options[i]); + } + } + return options_return; +} + +// Returns the options that are selected by the user and the ones that are not +function getOptions(select: HTMLSelectElement) { + // Select all the options available + const all_options = Array.from(select.querySelectorAll('option')).map((el) => el.value); + + // Get the options that are selected from the user + const options_selected = Array.from(select.querySelectorAll('option:checked')).map((el: any) => el.value); + + // Create an autocomplete options array with the options that are not selected by the user + const autocomplete_options: any[] = []; + all_options.forEach((option) => { + if (!options_selected.includes(option)) { + autocomplete_options.push(option); + } + }); + + autocomplete_options.sort(); + + return { + options_selected, + autocomplete_options, + }; +} + +// Listener for when the user wants to remove a given token. +function removeToken(e: Event) { + // Get the value to remove + const target = e.target as HTMLSelectElement; + const value_to_remove = target.dataset.option; + const wrapper = target.parentNode?.parentNode?.parentNode; + const input_search = wrapper?.querySelector('.selected-input'); + const dropdown = wrapper?.querySelector('.dropdown-icon'); + // Get the options in the select to be unselected + const option_to_unselect = wrapper?.querySelector(`select option[value="${value_to_remove}"]`); + option_to_unselect?.removeAttribute('selected'); + // Remove token attribute + (target.parentNode as any)?.remove(); + dropdown?.classList.remove('active'); + const process = document.querySelector('#' + target.dataset.option); + process?.remove(); +} + +// Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist +function deletePressed(e: KeyboardEvent) { + const input_search = e.target as HTMLInputElement; + const wrapper = input_search?.parentNode?.parentNode; + const key = e.keyCode || e.charCode; + const tokens = wrapper?.querySelectorAll('.selected-wrapper'); + + if (tokens?.length) { + const last_token_x = tokens[tokens.length - 1].querySelector('a'); + let hits = +(last_token_x?.dataset?.hits || 0); + + if (key == 8 || key == 46) { + if (!input_search.value) { + if (hits > 1) { + // Trigger delete event + const event = new Event('click'); + last_token_x?.dispatchEvent(event); + } else { + if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2'; + } + } + } else { + if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0'; + } + } + return true; +} + +// function addOption(target, val, text) { +// const select = document.querySelector(target); +// let opt = document.createElement("option"); +// opt.value = val; +// opt.innerHTML = text; +// select.appendChild(opt); +// } + +// get select that has the options available +const select = document.querySelectorAll('[data-multi-select-plugin]') as NodeListOf; +select.forEach((select) => { + console.log(select); + init(select); +}); + +// Dismiss on outside click +document.addEventListener('click', () => { + // get select that has the options available + const select = document.querySelectorAll('[data-multi-select-plugin]'); + for (let i = 0; i < select.length; i++) { + if (event) { + var isClickInside = select[i].parentElement?.parentElement?.contains(event.target as Node); + + if (!isClickInside) { + const wrapper = select[i].parentElement?.parentElement; + const dropdown = wrapper?.querySelector('.dropdown-icon'); + const autocomplete_list = wrapper?.querySelector('.autocomplete-list'); + //the click was outside the specifiedElement, do something + dropdown?.classList.remove('active'); + if (autocomplete_list) autocomplete_list.innerHTML = ''; + addPlaceholder(wrapper as HTMLElement); + } + } + } +}); + +export async function unpair() { + const service = await Services.getInstance(); + await service.unpairDevice(); +} + +(window as any).unpair = unpair; + +async function showSelectedProcess(event: MouseEvent) { + const elem = event.target; + if (elem) { + const cardContent = document.querySelector('.card-content'); + + const processes = await 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}`; + const service = await Services.getInstance(); + service.addSubscription(zoneElement, 'click', 'goToProcessPage'); + processDiv.appendChild(zoneElement); + } + if (cardContent) cardContent.appendChild(processDiv); + } + } +} + +function 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 function getProcesses(): Promise { + const service = await Services.getInstance(); + // const processes = service.getProcesses() + // console.log("๐Ÿš€ ~ Services ~ getProcesses ~ processes:", processes) + const process = [ + { + title: 'Messaging', + description: 'Messagerie chiffrรฉe', + html: '
', + css: '', + script: '', + zones: ['zone 1', 'zone 2'], + roles: { + owner: { + members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'], + validation_rules: [ + { + quorum: 1.0, + fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'], + min_sig_member: 1.0, + }, + ], + }, + }, + }, + { + title: 'Database', + description: 'Database chiffrรฉe', + html: '
', + css: '', + script: '', + zones: ['zone 1', 'zone 2'], + roles: { + owner: { + members: ['dfdsfdfdsf', 'dfdfdfdsfsfdfdsf'], + validation_rules: [ + { + quorum: 1.0, + fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'], + min_sig_member: 1.0, + }, + ], + }, + }, + }, + ]; + return process +} diff --git a/src/router.ts b/src/router.ts index 0eae8d4..8f48b4c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,85 +1,82 @@ -import '../public/style/4nk.css'; -import { initHomePage } from './pages/home/home'; -import Services from './services/service'; - -const routes: { [key: string]: string } = { - 'home': '/src/pages/home/home.html', - 'process': '/src/pages/process/process.html', -}; - -export async function navigate(path: string) { - path = path.replace(/^\//, ''); - if (!routes[path]) { - path = 'home'; - } - - await handleLocation(path); -} - -async function handleLocation(path: string) { - const routeHtml = routes[path] || routes['home']; - - const content = document.getElementById('containerId'); - if (content) { - const html = await fetch(routeHtml).then(data => data.text()); - content.innerHTML = html; - - await new Promise(requestAnimationFrame); - - if(path === "home") { - const { initHomePage } = await import('./pages/home/home'); - initHomePage() - } - else if (path === "process") { - await import('./pages/process/process'); - } - } -} - -window.onpopstate = () => handleLocation('home'); - -async function init(): Promise { - let isPaired = false; - try { - - const services = await Services.getInstance(); - setTimeout( async () => { - let device = await services.getDevice() - console.log("๐Ÿš€ ~ setTimeout ~ device:", device) - - if(!device) { - device = await services.createNewDevice(); - } else { - await services.restoreDevice(device) - } - await services.restoreProcesses(); - await services.restoreMessages(); - - if (services.isPaired()) { - isPaired = true; - console.log("๐Ÿš€ ~ setTimeout ~ isPaired:", isPaired) - await navigate('process'); - return isPaired - } else { - const queryString = window.location.search; - const urlParams = new URLSearchParams(queryString) - const pairingAddress = urlParams.get('sp_address') - if(pairingAddress) { - setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000) - } - return isPaired - } - }, 200); -} catch (error) { - console.error(error); - return isPaired -} -return isPaired - -} - -(async () => { - const isPaired = await init() - console.log("๐Ÿš€ ~ handleLocation ~ isPaired:", isPaired) - await navigate('home'); -})(); +import '../public/style/4nk.css'; +import { initHomePage } from './pages/home/home'; +import Services from './services/service'; + +const routes: { [key: string]: string } = { + home: '/src/pages/home/home.html', + process: '/src/pages/process/process.html', +}; + +export async function navigate(path: string) { + path = path.replace(/^\//, ''); + if (!routes[path]) { + path = 'home'; + } + + await handleLocation(path); +} + +async function handleLocation(path: string) { + const routeHtml = routes[path] || routes['home']; + + const content = document.getElementById('containerId'); + if (content) { + const html = await fetch(routeHtml).then((data) => data.text()); + content.innerHTML = html; + + await new Promise(requestAnimationFrame); + + if (path === 'home') { + const { initHomePage } = await import('./pages/home/home'); + initHomePage(); + } else if (path === 'process') { + await import('./pages/process/process'); + } + } +} + +window.onpopstate = () => handleLocation('home'); + +async function init(): Promise { + let isPaired = false; + try { + const services = await Services.getInstance(); + setTimeout(async () => { + let device = await services.getDevice(); + console.log('๐Ÿš€ ~ setTimeout ~ device:', device); + + if (!device) { + device = await services.createNewDevice(); + } else { + await services.restoreDevice(device); + } + await services.restoreProcesses(); + await services.restoreMessages(); + + if (services.isPaired()) { + isPaired = true; + console.log('๐Ÿš€ ~ setTimeout ~ isPaired:', isPaired); + await navigate('process'); + return isPaired; + } else { + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const pairingAddress = urlParams.get('sp_address'); + if (pairingAddress) { + setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000); + } + return isPaired; + } + }, 200); + } catch (error) { + console.error(error); + return isPaired; + } + return isPaired; +} + +(async () => { + const isPaired = await init(); + console.log('๐Ÿš€ ~ handleLocation ~ isPaired:', isPaired); + await navigate('home'); +})(); diff --git a/src/services/routing.service.ts b/src/services/routing.service.ts index 91e7ed3..cf38442 100644 --- a/src/services/routing.service.ts +++ b/src/services/routing.service.ts @@ -1,117 +1,117 @@ -import modalHtml from '../html/login-modal.html?raw'; -import confirmationModalHtml from '../html/confirmation-modal.html?raw'; -import modalScript from '../html/login-modal.js?raw'; -import confirmationModalScript from '../html/confirmation-modal.js?raw'; -import Services from './service'; -import { U32_MAX } from './service'; -import { navigate } from '../router'; - - -export default class Routing { - private static instance: Routing; - private sdkClient: any; - private currentPrd: any; - private currentOutpoint?: string; - private constructor() {} - private paired_addresses: string[] = []; - - // Method to access the singleton instance of Services - public static async getInstance(): Promise { - if (!Routing.instance) { - Routing.instance = new Routing(); - await Routing.instance.init(); - } - return Routing.instance; - } - - public async init(): Promise { - this.sdkClient = await import("../../dist/pkg/sdk_client"); - } - - public openLoginModal(myAddress: string, receiverAddress: string) { - const container = document.querySelector('.page-container'); - let html = modalHtml - html = html.replace('{{device1}}', myAddress) - html = html.replace('{{device2}}', receiverAddress) - if (container) container.innerHTML += html; - const modal = document.getElementById('login-modal') - if (modal) modal.style.display = 'flex'; - const newScript = document.createElement('script'); - - - newScript.setAttribute('type', 'module') - newScript.textContent = modalScript; - document.head.appendChild(newScript).parentNode?.removeChild(newScript); - } - - public async openConfirmationModal(pcd: any, outpointCommitment: string) { - console.log("") - let roles = JSON.parse(pcd['roles']);// ['members'][0]; - console.log(roles); - let members = roles['owner']['members']; - console.log(members); - // We take all the addresses except our own - const local_address = this.sdkClient.get_address(); - console.log("๐Ÿš€ ~ Routing ~ openConfirmationModal ~ pcd:", pcd) - for (const address of members[0]['sp_addresses']) { - if (address !== local_address) { this.paired_addresses.push(address) } - } - let html = confirmationModalHtml; - let services = await Services.getInstance(); - html = html.replace('{{device1}}', await services.addressToEmoji(members[0]['sp_addresses'][0])); - html = html.replace('{{device2}}', await services.addressToEmoji(members[0]['sp_addresses'][1])); - this.currentOutpoint = outpointCommitment as string; - const container = document.querySelector('.page-container'); - - if (container) container.innerHTML += html; - const modal = document.getElementById('modal') - if (modal) modal.style.display = 'flex'; - const newScript = document.createElement('script'); - newScript.setAttribute('type', 'module') - newScript.textContent = confirmationModalScript; - document.head.appendChild(newScript).parentNode?.removeChild(newScript); - - // Add correct text - - // Close modal when clicking outside of it - window.onclick = (event) => { - const modal = document.getElementById('modal'); - if (event.target === modal) { - this.closeConfirmationModal(); - } - } - } - confirmLogin() { - console.log('=============> Confirm Login') - const loginTx = this.sdkClient.create_login_transaction(1) - this.sdkClient.login('LOGIN', loginTx) - } - async closeLoginModal() { - const modal = document.getElementById('login-modal') - if (modal) modal.style.display = 'none'; - } - - async confirmPairing() { - const service = await Services.getInstance() - const modal = document.getElementById('modal') - // console.log("๐Ÿš€ ~ Routing ~ confirm ~ prd:", prd) - if (modal) modal.style.display = 'none'; - // Just make an empty commitment for now - const emptyTxid = '0'.repeat(64) - const commitmentOutpoint = `${emptyTxid}:${U32_MAX}`; - - // We take the paired device(s) from the contract - await this.sdkClient.pair_device(commitmentOutpoint, this.paired_addresses) - this.paired_addresses = [] - const newDevice = await service.dumpDevice(); - await service.saveDevice(newDevice); - navigate('process'); - } - - async closeConfirmationModal() { - const service = await Services.getInstance() - await service.unpairDevice() - const modal = document.getElementById('modal') - if (modal) modal.style.display = 'none'; - } -} \ No newline at end of file +import modalHtml from '../html/login-modal.html?raw'; +import confirmationModalHtml from '../html/confirmation-modal.html?raw'; +import modalScript from '../html/login-modal.js?raw'; +import confirmationModalScript from '../html/confirmation-modal.js?raw'; +import Services from './service'; +import { U32_MAX } from './service'; +import { navigate } from '../router'; + +export default class Routing { + private static instance: Routing; + private sdkClient: any; + private currentPrd: any; + private currentOutpoint?: string; + private constructor() {} + private paired_addresses: string[] = []; + + // Method to access the singleton instance of Services + public static async getInstance(): Promise { + if (!Routing.instance) { + Routing.instance = new Routing(); + await Routing.instance.init(); + } + return Routing.instance; + } + + public async init(): Promise { + this.sdkClient = await import('../../dist/pkg/sdk_client'); + } + + public openLoginModal(myAddress: string, receiverAddress: string) { + const container = document.querySelector('.page-container'); + let html = modalHtml; + html = html.replace('{{device1}}', myAddress); + html = html.replace('{{device2}}', receiverAddress); + if (container) container.innerHTML += html; + const modal = document.getElementById('login-modal'); + if (modal) modal.style.display = 'flex'; + const newScript = document.createElement('script'); + + newScript.setAttribute('type', 'module'); + newScript.textContent = modalScript; + document.head.appendChild(newScript).parentNode?.removeChild(newScript); + } + + public async openConfirmationModal(pcd: any, outpointCommitment: string) { + console.log(''); + let roles = JSON.parse(pcd['roles']); // ['members'][0]; + console.log(roles); + let members = roles['owner']['members']; + console.log(members); + // We take all the addresses except our own + const local_address = this.sdkClient.get_address(); + console.log('๐Ÿš€ ~ Routing ~ openConfirmationModal ~ pcd:', pcd); + for (const address of members[0]['sp_addresses']) { + if (address !== local_address) { + this.paired_addresses.push(address); + } + } + let html = confirmationModalHtml; + let services = await Services.getInstance(); + html = html.replace('{{device1}}', await services.addressToEmoji(members[0]['sp_addresses'][0])); + html = html.replace('{{device2}}', await services.addressToEmoji(members[0]['sp_addresses'][1])); + this.currentOutpoint = outpointCommitment as string; + const container = document.querySelector('.page-container'); + + if (container) container.innerHTML += html; + const modal = document.getElementById('modal'); + if (modal) modal.style.display = 'flex'; + const newScript = document.createElement('script'); + newScript.setAttribute('type', 'module'); + newScript.textContent = confirmationModalScript; + document.head.appendChild(newScript).parentNode?.removeChild(newScript); + + // Add correct text + + // Close modal when clicking outside of it + window.onclick = (event) => { + const modal = document.getElementById('modal'); + if (event.target === modal) { + this.closeConfirmationModal(); + } + }; + } + confirmLogin() { + console.log('=============> Confirm Login'); + const loginTx = this.sdkClient.create_login_transaction(1); + this.sdkClient.login('LOGIN', loginTx); + } + async closeLoginModal() { + const modal = document.getElementById('login-modal'); + if (modal) modal.style.display = 'none'; + } + + async confirmPairing() { + const service = await Services.getInstance(); + const modal = document.getElementById('modal'); + // console.log("๐Ÿš€ ~ Routing ~ confirm ~ prd:", prd) + if (modal) modal.style.display = 'none'; + // Just make an empty commitment for now + const emptyTxid = '0'.repeat(64); + const commitmentOutpoint = `${emptyTxid}:${U32_MAX}`; + + // We take the paired device(s) from the contract + await this.sdkClient.pair_device(commitmentOutpoint, this.paired_addresses); + this.paired_addresses = []; + const newDevice = await service.dumpDevice(); + await service.saveDevice(newDevice); + navigate('process'); + } + + async closeConfirmationModal() { + const service = await Services.getInstance(); + await service.unpairDevice(); + const modal = document.getElementById('modal'); + if (modal) modal.style.display = 'none'; + } +} diff --git a/src/services/service.ts b/src/services/service.ts index 7b5ff1a..7635bfb 100644 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -1,739 +1,726 @@ -// import { WebSocketClient } from '../websockets'; -import { INotification } from '~/models/notification.model'; -import homePage from '../pages/home/home.html?raw'; -import homeScript from '../pages/home/home.ts?raw'; -import processPage from '../pages/process/process.html?raw'; -import processScript from '../pages/process/process.js?raw'; -import { IProcess } from '~/models/process.model'; -// import Database from './database'; -import { WebSocketClient } from '../websockets'; -import QRCode from 'qrcode' -import { ApiReturn } from '../../dist/pkg/sdk_client'; -import Routing from './routing.service'; - -type ProcessesCache = { - [key: string]: any; -}; - -export const U32_MAX = 4294967295; -const wsurl = `https://demo.4nkweb.com/ws/`; - -export default class Services { - private static initializing: Promise | null = null; - private static instance: Services; - private current_process: string | null = null; - private sdkClient: any; - private websocketConnection: WebSocketClient | 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) { - return Services.instance; - } - - if (!Services.initializing) { - Services.initializing = (async () => { - const instance = new Services(); - await instance.init(); - return instance; - })(); - } - - console.log('initializing services'); - Services.instance = await Services.initializing; - Services.initializing = null; // Reset for potential future use - return Services.instance; - } - - public async init(): Promise { - this.notifications = this.getNotifications(); - this.sdkClient = await import("../../dist/pkg/sdk_client"); - this.sdkClient.setup(); - await this.addWebsocketConnection(wsurl); - } - - public async addWebsocketConnection(url: string): Promise { - // const services = await Services.getInstance(); - if (!this.websocketConnection) { - console.log('Opening new websocket connection'); - const newClient = new WebSocketClient(url, this); - this.websocketConnection = newClient; - } - } - - private generateQRCode = async (text: string) => { - try { - const container = document.getElementById('containerId'); - const currentUrl = 'https://' + window.location.host; - const url = await QRCode.toDataURL(currentUrl + "?sp_address=" + text); - const qrCode = container?.querySelector('.qr-code img'); - qrCode?.setAttribute('src', url) - - //Generate Address CopyBtn - const address = container?.querySelector('.sp-address-btn') - if (address) { - address.textContent = "Copy address"; - } - const copyBtn = document.getElementById('copyBtn'); - if (copyBtn) { - copyBtn.addEventListener('click', () => this.copyToClipboard(currentUrl + "?sp_address=" + text)); - } - } catch (err) { - console.error(err); - } - } - - //Copy Address - public async copyToClipboard(fullAddress: string) { - try { - await navigator.clipboard.writeText(fullAddress); - alert("Adresse copiรฉe dans le presse-papiers !"); - } catch (err) { - console.error("Failed to copy the address: ", err); - } - } - - //Generate emojis list - public generateEmojiList(): string[] { - const emojiRanges = [ - [0x1F600, 0x1F64F], - [0x1F300, 0x1F5FF], - [0x1F680, 0x1F6FF], - [0x1F700, 0x1F77F] - ]; - - const emojiList: string[] = []; - for (const range of emojiRanges) { - const [start, end] = range; - for (let i = start; i <= end && emojiList.length < 256; i++) { - emojiList.push(String.fromCodePoint(i)); - } - if (emojiList.length >= 256) { - break; - } - } - - return emojiList.slice(0, 256); - } - - //Adress to emojis - public addressToEmoji = async (text: string): Promise => { - //Adress to Hash - const encoder = new TextEncoder(); - const data = encoder.encode(text); - const hashBuffer = await crypto.subtle.digest('SHA-256', data); - - const hash = new Uint8Array(hashBuffer); - const bytes = hash.slice(-4); - - //Hash slice to emojis - const emojiList = this.generateEmojiList(); - const emojis = Array.from(bytes).map(byte => emojiList[byte]).join(''); - return emojis; - } - - //Get emojis from other device - public async emojisPairingRequest () { - try { - const container = document.getElementById('containerId'); - - const urlParams: URLSearchParams = new URLSearchParams(window.location.search); - const sp_adress: string | null = urlParams.get('sp_address'); - - if (!sp_adress) { - // console.error("No 'sp_adress' parameter found in the URL."); - return; - } - - const emojis = await this.addressToEmoji(sp_adress); - const emojiDisplay = container?.querySelector('.pairing-request'); - - if (emojiDisplay) { - emojiDisplay.textContent = "(Request from: " +emojis+")"; - } - } catch (err) { - console.error(err); - } - } - - // Display address emojis and other device emojis - private displayEmojis = async (text: string) => { - console.log("๐Ÿš€ ~ Services ~ adressToEmoji") - try { - const container = document.getElementById('containerId'); - const emojis = await this.addressToEmoji(text); - const emojiDisplay = container?.querySelector('.emoji-display'); - - if (emojiDisplay) { - emojiDisplay.textContent = emojis; - } - - this.emojisPairingRequest(); - - this.initAddressInput(); - - } catch (err) { - console.error(err); - } - } - - // Verify Other address - public initAddressInput() { - const addressInput = document.getElementById('addressInput') as HTMLInputElement; - const emojiDisplay = document.getElementById('emoji-display-2'); - const okButton = document.getElementById('okButton'); - - addressInput.addEventListener('input', async () => { - let address = addressInput.value; - - // Vรฉrifie si l'adresse est une URL - try { - const url = new URL(address); - // Si c'est une URL valide, extraire le paramรจtre sp_address - const urlParams = new URLSearchParams(url.search); - const extractedAddress = urlParams.get('sp_address') || ''; // Prend sp_address ou une chaรฎne vide - - if (extractedAddress) { - address = extractedAddress; - addressInput.value = address; // Met ร  jour l'input pour afficher uniquement l'adresse extraite - } - } catch (e) { - // Si ce n'est pas une URL valide, on garde l'adresse originale - console.log("Ce n'est pas une URL valide, on garde l'adresse originale."); - } - if (address) { - const emojis = await this.addressToEmoji(address); - if (emojiDisplay) { - emojiDisplay.innerHTML = emojis; - } - if (okButton) { - okButton.style.display = 'inline-block'; - } - } else { - if (emojiDisplay) { - emojiDisplay.innerHTML = ''; - } - if (okButton) { - okButton.style.display = 'none'; - } - } - }); - - if (okButton) { - okButton.addEventListener('click', () => { - this.onOkButtonClick(); - }); - } - } - - private async onOkButtonClick() { - const addressInput = (document.getElementById('addressInput') as HTMLInputElement).value; - await this.sendPairingTx(addressInput); - } - - public isPaired(): boolean | undefined { - try { - return this.sdkClient.is_linking(); - } catch (e) { - console.error("isPaired ~ Error:", e); - } - } - - public async unpairDevice(): Promise { - const service = await Services.getInstance() - await service.sdkClient.unpair_device() - const newDevice = await this.dumpDevice(); - await this.saveDevice(newDevice); - } - - private prepareProcessTx(myAddress: string, recipientAddress: string) { - const initial_session_privkey = new Uint8Array(32); - const initial_session_pubkey = new Uint8Array(32); - const pairingTemplate = { - "description": "AliceBob", - "roles": { - "owner": { - "members": - [{sp_addresses: [myAddress, recipientAddress]}], - "validation_rules": - [ - { - "quorum": 1.0, - "fields": [ - "description", - "roles", - "session_privkey", - "session_pubkey", - "key_parity" - ], - "min_sig_member": 1.0 - } - ] - } - }, - "session_privkey": initial_session_privkey, - "session_pubkey": initial_session_pubkey, - "key_parity": true, - } - - - const apiReturn = this.sdkClient.create_update_transaction(undefined, JSON.stringify(pairingTemplate), 1) - return apiReturn - } - - public async sendPairingTx(spAddress: string): Promise { - - const localAddress = this.sdkClient.get_address(); - const emptyTxid = '0'.repeat(64) - - try { - let commitmentOutpoint = `${emptyTxid}:${U32_MAX}`; - this.sdkClient.pair_device(commitmentOutpoint, [spAddress]) - } catch (e) { - console.error("Services ~ Error:", e); - return - } - - setTimeout( async () => { - const apiReturn = this.prepareProcessTx(localAddress, spAddress) - await this.handleApiReturn(apiReturn); - }, 1500) - - return - } - - async resetDevice() { - await this.sdkClient.reset_device() - } - - async sendNewTxMessage(message: string) { - if (!this.websocketConnection) { - throw new Error('No websocket connection'); - } - // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ newTxMessage:", message) - await this.websocketConnection.sendMessage('NewTx', message) - } - - async sendCommitMessage(message: string) { - // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ CommitMessage:", message) - await this.websocketConnection?.sendMessage('Commit', message) - } - - async sendCipherMessages(ciphers: string[]) { - for (let i = 0; i < ciphers.length; i++) { - const cipher = ciphers[i]; - await this.websocketConnection?.sendMessage('Cipher', cipher); - } - } - - async sendFaucetMessage(message: string): Promise { - // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", message) - await this.websocketConnection?.sendMessage('Faucet', message); - } - async parseCipher(message: string) { - // try { - // JSON.parse(message) - // const router = await Routing.getInstance(); - // router.closeLoginModal() - // this.injectProcessListPage() - // } catch { - // console.log('Not proper format for cipher') - // } - try { - console.log('parsing new cipher'); - const apiReturn = await this.sdkClient.parse_cipher(message, 0.00001); - console.log("๐Ÿš€ ~ Services ~ parseCipher ~ apiReturn:", apiReturn); - await this.handleApiReturn(apiReturn) - } catch (e) { - console.log("Cipher isn't for us"); - } - // await this.saveCipherTxToDb(parsedTx) - } - - async parseNewTx(tx: string) { - try { - // console.log('==============> sending txxxxxxx parser', tx) - const parsedTx = await this.sdkClient.parse_new_tx(tx, 0, 0.0001) - if(parsedTx) { - console.log("๐Ÿš€ ~ Services ~ parseNewTx ~ parsedTx:", parsedTx) - try { - await this.handleApiReturn(parsedTx); - const newDevice = await this.dumpDevice(); - await this.saveDevice(newDevice); - } catch (e) { - console.error("Failed to update device with new tx"); - } - } - } catch(e) { - console.trace(e); - } - } - - private async handleApiReturn(apiReturn: ApiReturn) { - if (apiReturn.ciphers_to_send && apiReturn.ciphers_to_send.length) { - await this.sendCipherMessages(apiReturn.ciphers_to_send) - } - - setTimeout(async () => { - if (apiReturn.updated_process && apiReturn.updated_process.length) { - const [processCommitment, process] = apiReturn.updated_process; - console.debug('Updated Process Commitment:', processCommitment); - console.debug('Process Details:', process); - - // Save process to storage - localStorage.setItem(processCommitment, JSON.stringify(process)); - // Check if the newly updated process reveals some new information - try { - const proposals: string[] = this.sdkClient.get_update_proposals(processCommitment); - if (proposals && proposals.length != 0) { - const actual_proposal = JSON.parse(proposals[0]); // We just don't acknowledge concurrent proposals for now - console.info(actual_proposal); - let router = await Routing.getInstance(); - await router.openConfirmationModal(actual_proposal, processCommitment); - } - } catch (e) { - console.error(e); - } - } - - if(apiReturn.updated_cached_msg && apiReturn.updated_cached_msg.length) { - apiReturn.updated_cached_msg.forEach((msg, index) => { - // console.debug(`CachedMessage ${index}:`, msg); - // Save the message to local storage - localStorage.setItem(msg.id.toString(), JSON.stringify(msg)); - }); - } - - if (apiReturn.commit_to_send) { - const commit = apiReturn.commit_to_send; - await this.sendCommitMessage(JSON.stringify(commit)); - } - - if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) { - await this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send)) - } - }, 0) - } - - 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(): Promise { - const amount = await this.sdkClient.get_available_amount() - return amount - } - - async getDeviceAddress() { - return await this.sdkClient.get_address(); - } - - async dumpDevice() { - const device = await this.sdkClient.dump_device() - // console.log("๐Ÿš€ ~ Services ~ dumpDevice ~ 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 wallet = await this.sdkClient.dump_wallet() - console.log("๐Ÿš€ ~ Services ~ dumpWallet ~ wallet:", wallet) - return wallet - } - - async createFaucetMessage() { - const message = await this.sdkClient.create_faucet_msg() - console.log("๐Ÿš€ ~ Services ~ createFaucetMessage ~ message:", message) - return message; - } - - addSubscription(element: Element, event: string, eventHandler: string): void { - this.subscriptions.push({ element, event, eventHandler }); - element.addEventListener(event, (this as any)[eventHandler].bind(this)); - } - - async createNewDevice() { - let spAddress = ''; - try { - spAddress = await this.sdkClient.create_new_device(0, 'regtest') - const device = await this.dumpDevice() - console.log("๐Ÿš€ ~ Services ~ device:", device) - await this.saveDevice(device) - } catch (e) { - console.error("Services ~ Error:", e); - } - - this.generateQRCode(spAddress || '') - //Adress to Emoji integration - this.displayEmojis(spAddress) - //Adress to Emoji integration - return spAddress; - } - - async restoreDevice(device: string) { - try { - await this.sdkClient.restore_device(device) - const spAddress = this.sdkClient.get_address(); - this.generateQRCode(spAddress || '') - //Adress to Emoji integration - this.displayEmojis(spAddress) - //Adress to Emoji integration - } catch (e) { - console.error(e); - } - } - - private getProcessesCache(): ProcessesCache { - // Regular expression to match 64-character hexadecimal strings - const hexU32KeyRegex: RegExp = /^[0-9a-fA-F]{64}:\d+$/; - const hexObjects: ProcessesCache = {} - - // Iterate over all keys in localStorage - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - - if (!key) { - return hexObjects; - } - - // Check if the key matches the 32-byte hex pattern - if (hexU32KeyRegex.test(key)) { - const value = localStorage.getItem(key); - if (!value) { - continue; - } - - hexObjects[key] = JSON.parse(value); - } - } - - return hexObjects; - } - - private getCachedMessages(): string[] { - const u32KeyRegex = /^\d+$/; - const U32_MAX = 4294967295; - const messages: string[] = []; - - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - - if (!key) { - return messages; - } - - if (u32KeyRegex.test(key)) { - const num = parseInt(key, 10); - if (num < 0 || num > U32_MAX) { - console.warn(`Key ${key} is outside the u32 range and will be ignored.`); - continue; - } - - const value = localStorage.getItem(key); - if (!value) { - console.warn(`No value found for key: ${key}`); - continue; - } - - messages.push(value); - } - } - - return messages; - } - - public async restoreProcesses() { - const processesCache = this.getProcessesCache(); - console.log("๐Ÿš€ ~ Services ~ restoreProcesses ~ processesCache:", processesCache); - - if (processesCache.length == 0) { - console.debug("๐Ÿš€ ~ Services ~ restoreProcesses ~ no processes in local storage"); - return; - } - - try { - await this.sdkClient.set_process_cache(JSON.stringify(processesCache)); - } catch (e) { - console.error('Services ~ restoreProcesses ~ Error:', e); - } - } - - public async restoreMessages() { - const cachedMessages = this.getCachedMessages(); - console.log("๐Ÿš€ ~ Services ~ restoreMessages ~ chachedMessages:", cachedMessages); - - if (cachedMessages && cachedMessages.length != 0) { - try { - await this.sdkClient.set_message_cache(cachedMessages); - } catch (e) { - console.error('Services ~ restoreMessages ~ Error:', e); - } - } - - } - - private cleanSubscriptions(): 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 = []; - } - - 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') - } - - - - 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' - - } - } -} +// import { WebSocketClient } from '../websockets'; +import { INotification } from '~/models/notification.model'; +import homePage from '../pages/home/home.html?raw'; +import homeScript from '../pages/home/home.ts?raw'; +import processPage from '../pages/process/process.html?raw'; +import processScript from '../pages/process/process.js?raw'; +import { IProcess } from '~/models/process.model'; +// import Database from './database'; +import { WebSocketClient } from '../websockets'; +import QRCode from 'qrcode'; +import { ApiReturn } from '../../dist/pkg/sdk_client'; +import Routing from './routing.service'; + +type ProcessesCache = { + [key: string]: any; +}; + +export const U32_MAX = 4294967295; +const wsurl = `https://demo.4nkweb.com/ws/`; + +export default class Services { + private static initializing: Promise | null = null; + private static instance: Services; + private current_process: string | null = null; + private sdkClient: any; + private websocketConnection: WebSocketClient | 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) { + return Services.instance; + } + + if (!Services.initializing) { + Services.initializing = (async () => { + const instance = new Services(); + await instance.init(); + return instance; + })(); + } + + console.log('initializing services'); + Services.instance = await Services.initializing; + Services.initializing = null; // Reset for potential future use + return Services.instance; + } + + public async init(): Promise { + this.notifications = this.getNotifications(); + this.sdkClient = await import('../../dist/pkg/sdk_client'); + this.sdkClient.setup(); + await this.addWebsocketConnection(wsurl); + } + + public async addWebsocketConnection(url: string): Promise { + // const services = await Services.getInstance(); + if (!this.websocketConnection) { + console.log('Opening new websocket connection'); + const newClient = new WebSocketClient(url, this); + this.websocketConnection = newClient; + } + } + + private generateQRCode = async (text: string) => { + try { + const container = document.getElementById('containerId'); + const currentUrl = 'https://' + window.location.host; + const url = await QRCode.toDataURL(currentUrl + '?sp_address=' + text); + const qrCode = container?.querySelector('.qr-code img'); + qrCode?.setAttribute('src', url); + + //Generate Address CopyBtn + const address = container?.querySelector('.sp-address-btn'); + if (address) { + address.textContent = 'Copy address'; + } + const copyBtn = document.getElementById('copyBtn'); + if (copyBtn) { + copyBtn.addEventListener('click', () => this.copyToClipboard(currentUrl + '?sp_address=' + text)); + } + } catch (err) { + console.error(err); + } + }; + + //Copy Address + public async copyToClipboard(fullAddress: string) { + try { + await navigator.clipboard.writeText(fullAddress); + alert('Adresse copiรฉe dans le presse-papiers !'); + } catch (err) { + console.error('Failed to copy the address: ', err); + } + } + + //Generate emojis list + public generateEmojiList(): string[] { + const emojiRanges = [ + [0x1f600, 0x1f64f], + [0x1f300, 0x1f5ff], + [0x1f680, 0x1f6ff], + [0x1f700, 0x1f77f], + ]; + + const emojiList: string[] = []; + for (const range of emojiRanges) { + const [start, end] = range; + for (let i = start; i <= end && emojiList.length < 256; i++) { + emojiList.push(String.fromCodePoint(i)); + } + if (emojiList.length >= 256) { + break; + } + } + + return emojiList.slice(0, 256); + } + + //Adress to emojis + public addressToEmoji = async (text: string): Promise => { + //Adress to Hash + const encoder = new TextEncoder(); + const data = encoder.encode(text); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + + const hash = new Uint8Array(hashBuffer); + const bytes = hash.slice(-4); + + //Hash slice to emojis + const emojiList = this.generateEmojiList(); + const emojis = Array.from(bytes) + .map((byte) => emojiList[byte]) + .join(''); + return emojis; + }; + + //Get emojis from other device + public async emojisPairingRequest() { + try { + const container = document.getElementById('containerId'); + + const urlParams: URLSearchParams = new URLSearchParams(window.location.search); + const sp_adress: string | null = urlParams.get('sp_address'); + + if (!sp_adress) { + // console.error("No 'sp_adress' parameter found in the URL."); + return; + } + + const emojis = await this.addressToEmoji(sp_adress); + const emojiDisplay = container?.querySelector('.pairing-request'); + + if (emojiDisplay) { + emojiDisplay.textContent = '(Request from: ' + emojis + ')'; + } + } catch (err) { + console.error(err); + } + } + + // Display address emojis and other device emojis + private displayEmojis = async (text: string) => { + console.log('๐Ÿš€ ~ Services ~ adressToEmoji'); + try { + const container = document.getElementById('containerId'); + const emojis = await this.addressToEmoji(text); + const emojiDisplay = container?.querySelector('.emoji-display'); + + if (emojiDisplay) { + emojiDisplay.textContent = emojis; + } + + this.emojisPairingRequest(); + + this.initAddressInput(); + } catch (err) { + console.error(err); + } + }; + + // Verify Other address + public initAddressInput() { + const addressInput = document.getElementById('addressInput') as HTMLInputElement; + const emojiDisplay = document.getElementById('emoji-display-2'); + const okButton = document.getElementById('okButton'); + + addressInput.addEventListener('input', async () => { + let address = addressInput.value; + + // Vรฉrifie si l'adresse est une URL + try { + const url = new URL(address); + // Si c'est une URL valide, extraire le paramรจtre sp_address + const urlParams = new URLSearchParams(url.search); + const extractedAddress = urlParams.get('sp_address') || ''; // Prend sp_address ou une chaรฎne vide + + if (extractedAddress) { + address = extractedAddress; + addressInput.value = address; // Met ร  jour l'input pour afficher uniquement l'adresse extraite + } + } catch (e) { + // Si ce n'est pas une URL valide, on garde l'adresse originale + console.log("Ce n'est pas une URL valide, on garde l'adresse originale."); + } + if (address) { + const emojis = await this.addressToEmoji(address); + if (emojiDisplay) { + emojiDisplay.innerHTML = emojis; + } + if (okButton) { + okButton.style.display = 'inline-block'; + } + } else { + if (emojiDisplay) { + emojiDisplay.innerHTML = ''; + } + if (okButton) { + okButton.style.display = 'none'; + } + } + }); + + if (okButton) { + okButton.addEventListener('click', () => { + this.onOkButtonClick(); + }); + } + } + + private async onOkButtonClick() { + const addressInput = (document.getElementById('addressInput') as HTMLInputElement).value; + await this.sendPairingTx(addressInput); + } + + public isPaired(): boolean | undefined { + try { + return this.sdkClient.is_linking(); + } catch (e) { + console.error('isPaired ~ Error:', e); + } + } + + public async unpairDevice(): Promise { + const service = await Services.getInstance(); + await service.sdkClient.unpair_device(); + const newDevice = await this.dumpDevice(); + await this.saveDevice(newDevice); + } + + private prepareProcessTx(myAddress: string, recipientAddress: string) { + const initial_session_privkey = new Uint8Array(32); + const initial_session_pubkey = new Uint8Array(32); + const pairingTemplate = { + description: 'AliceBob', + roles: { + owner: { + members: [{ sp_addresses: [myAddress, recipientAddress] }], + validation_rules: [ + { + quorum: 1.0, + fields: ['description', 'roles', 'session_privkey', 'session_pubkey', 'key_parity'], + min_sig_member: 1.0, + }, + ], + }, + }, + session_privkey: initial_session_privkey, + session_pubkey: initial_session_pubkey, + key_parity: true, + }; + + const apiReturn = this.sdkClient.create_update_transaction(undefined, JSON.stringify(pairingTemplate), 1); + return apiReturn; + } + + public async sendPairingTx(spAddress: string): Promise { + const localAddress = this.sdkClient.get_address(); + const emptyTxid = '0'.repeat(64); + + try { + let commitmentOutpoint = `${emptyTxid}:${U32_MAX}`; + this.sdkClient.pair_device(commitmentOutpoint, [spAddress]); + } catch (e) { + console.error('Services ~ Error:', e); + return; + } + + setTimeout(async () => { + const apiReturn = this.prepareProcessTx(localAddress, spAddress); + await this.handleApiReturn(apiReturn); + }, 1500); + + return; + } + + async resetDevice() { + await this.sdkClient.reset_device(); + } + + async sendNewTxMessage(message: string) { + if (!this.websocketConnection) { + throw new Error('No websocket connection'); + } + // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ newTxMessage:", message) + await this.websocketConnection.sendMessage('NewTx', message); + } + + async sendCommitMessage(message: string) { + // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ CommitMessage:", message) + await this.websocketConnection?.sendMessage('Commit', message); + } + + async sendCipherMessages(ciphers: string[]) { + for (let i = 0; i < ciphers.length; i++) { + const cipher = ciphers[i]; + await this.websocketConnection?.sendMessage('Cipher', cipher); + } + } + + async sendFaucetMessage(message: string): Promise { + // console.log("๐Ÿš€ ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", message) + await this.websocketConnection?.sendMessage('Faucet', message); + } + async parseCipher(message: string) { + // try { + // JSON.parse(message) + // const router = await Routing.getInstance(); + // router.closeLoginModal() + // this.injectProcessListPage() + // } catch { + // console.log('Not proper format for cipher') + // } + try { + console.log('parsing new cipher'); + const apiReturn = await this.sdkClient.parse_cipher(message, 0.00001); + console.log('๐Ÿš€ ~ Services ~ parseCipher ~ apiReturn:', apiReturn); + await this.handleApiReturn(apiReturn); + } catch (e) { + console.log("Cipher isn't for us"); + } + // await this.saveCipherTxToDb(parsedTx) + } + + async parseNewTx(tx: string) { + try { + // console.log('==============> sending txxxxxxx parser', tx) + const parsedTx = await this.sdkClient.parse_new_tx(tx, 0, 0.0001); + if (parsedTx) { + console.log('๐Ÿš€ ~ Services ~ parseNewTx ~ parsedTx:', parsedTx); + try { + await this.handleApiReturn(parsedTx); + const newDevice = await this.dumpDevice(); + await this.saveDevice(newDevice); + } catch (e) { + console.error('Failed to update device with new tx'); + } + } + } catch (e) { + console.trace(e); + } + } + + private async handleApiReturn(apiReturn: ApiReturn) { + if (apiReturn.ciphers_to_send && apiReturn.ciphers_to_send.length) { + await this.sendCipherMessages(apiReturn.ciphers_to_send); + } + + setTimeout(async () => { + if (apiReturn.updated_process && apiReturn.updated_process.length) { + const [processCommitment, process] = apiReturn.updated_process; + console.debug('Updated Process Commitment:', processCommitment); + console.debug('Process Details:', process); + + // Save process to storage + localStorage.setItem(processCommitment, JSON.stringify(process)); + // Check if the newly updated process reveals some new information + try { + const proposals: string[] = this.sdkClient.get_update_proposals(processCommitment); + if (proposals && proposals.length != 0) { + const actual_proposal = JSON.parse(proposals[0]); // We just don't acknowledge concurrent proposals for now + console.info(actual_proposal); + let router = await Routing.getInstance(); + await router.openConfirmationModal(actual_proposal, processCommitment); + } + } catch (e) { + console.error(e); + } + } + + if (apiReturn.updated_cached_msg && apiReturn.updated_cached_msg.length) { + apiReturn.updated_cached_msg.forEach((msg, index) => { + // console.debug(`CachedMessage ${index}:`, msg); + // Save the message to local storage + localStorage.setItem(msg.id.toString(), JSON.stringify(msg)); + }); + } + + if (apiReturn.commit_to_send) { + const commit = apiReturn.commit_to_send; + await this.sendCommitMessage(JSON.stringify(commit)); + } + + if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) { + await this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send)); + } + }, 0); + } + + 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(): Promise { + const amount = await this.sdkClient.get_available_amount(); + return amount; + } + + async getDeviceAddress() { + return await this.sdkClient.get_address(); + } + + async dumpDevice() { + const device = await this.sdkClient.dump_device(); + // console.log("๐Ÿš€ ~ Services ~ dumpDevice ~ 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 wallet = await this.sdkClient.dump_wallet(); + console.log('๐Ÿš€ ~ Services ~ dumpWallet ~ wallet:', wallet); + return wallet; + } + + async createFaucetMessage() { + const message = await this.sdkClient.create_faucet_msg(); + console.log('๐Ÿš€ ~ Services ~ createFaucetMessage ~ message:', message); + return message; + } + + addSubscription(element: Element, event: string, eventHandler: string): void { + this.subscriptions.push({ element, event, eventHandler }); + element.addEventListener(event, (this as any)[eventHandler].bind(this)); + } + + async createNewDevice() { + let spAddress = ''; + try { + spAddress = await this.sdkClient.create_new_device(0, 'regtest'); + const device = await this.dumpDevice(); + console.log('๐Ÿš€ ~ Services ~ device:', device); + await this.saveDevice(device); + } catch (e) { + console.error('Services ~ Error:', e); + } + + this.generateQRCode(spAddress || ''); + //Adress to Emoji integration + this.displayEmojis(spAddress); + //Adress to Emoji integration + return spAddress; + } + + async restoreDevice(device: string) { + try { + await this.sdkClient.restore_device(device); + const spAddress = this.sdkClient.get_address(); + this.generateQRCode(spAddress || ''); + //Adress to Emoji integration + this.displayEmojis(spAddress); + //Adress to Emoji integration + } catch (e) { + console.error(e); + } + } + + private getProcessesCache(): ProcessesCache { + // Regular expression to match 64-character hexadecimal strings + const hexU32KeyRegex: RegExp = /^[0-9a-fA-F]{64}:\d+$/; + const hexObjects: ProcessesCache = {}; + + // Iterate over all keys in localStorage + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + + if (!key) { + return hexObjects; + } + + // Check if the key matches the 32-byte hex pattern + if (hexU32KeyRegex.test(key)) { + const value = localStorage.getItem(key); + if (!value) { + continue; + } + + hexObjects[key] = JSON.parse(value); + } + } + + return hexObjects; + } + + private getCachedMessages(): string[] { + const u32KeyRegex = /^\d+$/; + const U32_MAX = 4294967295; + const messages: string[] = []; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + + if (!key) { + return messages; + } + + if (u32KeyRegex.test(key)) { + const num = parseInt(key, 10); + if (num < 0 || num > U32_MAX) { + console.warn(`Key ${key} is outside the u32 range and will be ignored.`); + continue; + } + + const value = localStorage.getItem(key); + if (!value) { + console.warn(`No value found for key: ${key}`); + continue; + } + + messages.push(value); + } + } + + return messages; + } + + public async restoreProcesses() { + const processesCache = this.getProcessesCache(); + console.log('๐Ÿš€ ~ Services ~ restoreProcesses ~ processesCache:', processesCache); + + if (processesCache.length == 0) { + console.debug('๐Ÿš€ ~ Services ~ restoreProcesses ~ no processes in local storage'); + return; + } + + try { + await this.sdkClient.set_process_cache(JSON.stringify(processesCache)); + } catch (e) { + console.error('Services ~ restoreProcesses ~ Error:', e); + } + } + + public async restoreMessages() { + const cachedMessages = this.getCachedMessages(); + console.log('๐Ÿš€ ~ Services ~ restoreMessages ~ chachedMessages:', cachedMessages); + + if (cachedMessages && cachedMessages.length != 0) { + try { + await this.sdkClient.set_message_cache(cachedMessages); + } catch (e) { + console.error('Services ~ restoreMessages ~ Error:', e); + } + } + } + + private cleanSubscriptions(): 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 = []; + } + + 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'); + } + + 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'; + } + } +} diff --git a/src/websockets.ts b/src/websockets.ts index 57a0a09..c6b12a0 100644 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -1,136 +1,137 @@ -import { AnkFlag, CachedMessage } from "dist/pkg/sdk_client"; -import Services from "./services/service"; -// import { AnkFlag, AnkNetworkMsg, CachedMessage } from "../dist/pkg/sdk_client"; - -class WebSocketClient { - private ws: WebSocket; - private messageQueue: string[] = []; - - constructor(url: string, private services: Services) { - this.ws = new WebSocket(url); - - if(this.ws !== null) { - this.ws.onopen = async (event) => { - console.log('WebSocket connection established'); - - const amount = await services.getAmount(); - - if (amount === 0n) { - const faucetMsg = await services.createFaucetMessage(); - await services.sendFaucetMessage(faucetMsg); - } - while (this.messageQueue.length > 0) { - const message = this.messageQueue.shift(); - if (message) { - this.ws.send(message); - } - } - }; - - // Listen for messages - this.ws.onmessage = (event) => { - const msgData = event.data; - - // console.log("Received text message: ", msgData); - (async () => { - if (typeof msgData === 'string') { - try { - const feeRate = 0.0001; - const parsedMessage = JSON.parse(msgData) - // console.log("๐Ÿš€ ~ WebSocketClient ~ parsedMessage:", parsedMessage) - const services = await Services.getInstance() - switch(parsedMessage.flag) { - case 'NewTx': - await services.parseNewTx(parsedMessage.content) - break; - case 'Cipher': - await services.parseCipher(parsedMessage.content) - // device = await services.dumpWallet() - // console.log("๐Ÿš€ ~ WebSocketClient ~ device:", device) - // await services.saveDevice(device) - break; - } - // By parsing the message, we can link it with existing cached message and return the updated version of the message - // if (res.status === 'FaucetComplete') { - // // we received a faucet tx, there's nothing else to do - // window.alert(`New faucet output\n${res.commited_in}`); - // } else if (res.status === 'TxWaitingCipher') { - // // we received a tx but we don't have the cipher - // console.debug(`received notification in output ${res.commited_in}, waiting for cipher message`); - // } else if (res.status === 'CipherWaitingTx') { - // // we received a cipher but we don't have the key - // console.debug(`received a cipher`); - // } else if (res.status === 'SentWaitingConfirmation') { - // // We are sender and we're waiting for the challenge that will confirm recipient got the transaction and the message - // } else if (res.status === 'MustSpendConfirmation') { - // // we received a challenge for a notification we made - // // that means we can stop rebroadcasting the tx and we must spend the challenge to confirm - // window.alert(`Spending ${res.confirmed_by} to prove our identity`); - // console.debug(`sending confirm message to ${res.recipient}`); - // } else if (res.status === 'ReceivedMustConfirm') { - // // we found a notification and decrypted the cipher - // window.alert(`Received message from ${res.sender}\n${res.plaintext}`); - // // we must spend the commited_in output to sender - // } else if (res.status === 'Complete') { - // window.alert(`Received confirmation that ${res.sender} is the author of message ${res.plaintext}`) - // } else { - // console.debug('Received an unimplemented valid message'); - // } - } catch (error) { - console.error('Received an invalid message:', error); - } - } else { - console.error('Received a non-string message'); - } - })(); - }; - - // Listen for possible errors - this.ws.onerror = (event) => { - console.error('WebSocket error:', event); - }; - - // Listen for when the connection is closed - this.ws.onclose = (event) => { - console.log('WebSocket is closed now.'); - } - } - - } - - // Method to send messages - public async sendMessage(flag: AnkFlag, message: string): Promise { - if (this.ws.readyState === WebSocket.OPEN) { - const networkMessage = { - 'flag': flag, - 'content': message - } - // if(flag === 'Cipher') { - // const services = await Services.getInstance(); - // services.parseCipher(JSON.stringify(networkMessage)) - // } - console.log("Sending message:", JSON.stringify(networkMessage)); - this.ws.send(JSON.stringify(networkMessage)); - } else { - console.error('WebSocket is not open. ReadyState:', this.ws.readyState); - this.messageQueue.push(message); - } - } - - public getUrl(): string { - return this.ws.url; - } - - public sendNormalMessage(message: string) { - this.ws.send(message); - console.log("Sending message:", JSON.stringify(message)); - - } - - // Method to close the WebSocket connection - public close(): void { - this.ws.close(); - } -} - -export { WebSocketClient }; +import { AnkFlag, CachedMessage } from 'dist/pkg/sdk_client'; +import Services from './services/service'; +// import { AnkFlag, AnkNetworkMsg, CachedMessage } from "../dist/pkg/sdk_client"; + +class WebSocketClient { + private ws: WebSocket; + private messageQueue: string[] = []; + + constructor( + url: string, + private services: Services, + ) { + this.ws = new WebSocket(url); + + if (this.ws !== null) { + this.ws.onopen = async (event) => { + console.log('WebSocket connection established'); + + const amount = await services.getAmount(); + + if (amount === 0n) { + const faucetMsg = await services.createFaucetMessage(); + await services.sendFaucetMessage(faucetMsg); + } + while (this.messageQueue.length > 0) { + const message = this.messageQueue.shift(); + if (message) { + this.ws.send(message); + } + } + }; + + // Listen for messages + this.ws.onmessage = (event) => { + const msgData = event.data; + + // console.log("Received text message: ", msgData); + (async () => { + if (typeof msgData === 'string') { + try { + const feeRate = 0.0001; + const parsedMessage = JSON.parse(msgData); + // console.log("๐Ÿš€ ~ WebSocketClient ~ parsedMessage:", parsedMessage) + const services = await Services.getInstance(); + switch (parsedMessage.flag) { + case 'NewTx': + await services.parseNewTx(parsedMessage.content); + break; + case 'Cipher': + await services.parseCipher(parsedMessage.content); + // device = await services.dumpWallet() + // console.log("๐Ÿš€ ~ WebSocketClient ~ device:", device) + // await services.saveDevice(device) + break; + } + // By parsing the message, we can link it with existing cached message and return the updated version of the message + // if (res.status === 'FaucetComplete') { + // // we received a faucet tx, there's nothing else to do + // window.alert(`New faucet output\n${res.commited_in}`); + // } else if (res.status === 'TxWaitingCipher') { + // // we received a tx but we don't have the cipher + // console.debug(`received notification in output ${res.commited_in}, waiting for cipher message`); + // } else if (res.status === 'CipherWaitingTx') { + // // we received a cipher but we don't have the key + // console.debug(`received a cipher`); + // } else if (res.status === 'SentWaitingConfirmation') { + // // We are sender and we're waiting for the challenge that will confirm recipient got the transaction and the message + // } else if (res.status === 'MustSpendConfirmation') { + // // we received a challenge for a notification we made + // // that means we can stop rebroadcasting the tx and we must spend the challenge to confirm + // window.alert(`Spending ${res.confirmed_by} to prove our identity`); + // console.debug(`sending confirm message to ${res.recipient}`); + // } else if (res.status === 'ReceivedMustConfirm') { + // // we found a notification and decrypted the cipher + // window.alert(`Received message from ${res.sender}\n${res.plaintext}`); + // // we must spend the commited_in output to sender + // } else if (res.status === 'Complete') { + // window.alert(`Received confirmation that ${res.sender} is the author of message ${res.plaintext}`) + // } else { + // console.debug('Received an unimplemented valid message'); + // } + } catch (error) { + console.error('Received an invalid message:', error); + } + } else { + console.error('Received a non-string message'); + } + })(); + }; + + // Listen for possible errors + this.ws.onerror = (event) => { + console.error('WebSocket error:', event); + }; + + // Listen for when the connection is closed + this.ws.onclose = (event) => { + console.log('WebSocket is closed now.'); + }; + } + } + + // Method to send messages + public async sendMessage(flag: AnkFlag, message: string): Promise { + if (this.ws.readyState === WebSocket.OPEN) { + const networkMessage = { + flag: flag, + content: message, + }; + // if(flag === 'Cipher') { + // const services = await Services.getInstance(); + // services.parseCipher(JSON.stringify(networkMessage)) + // } + console.log('Sending message:', JSON.stringify(networkMessage)); + this.ws.send(JSON.stringify(networkMessage)); + } else { + console.error('WebSocket is not open. ReadyState:', this.ws.readyState); + this.messageQueue.push(message); + } + } + + public getUrl(): string { + return this.ws.url; + } + + public sendNormalMessage(message: string) { + this.ws.send(message); + console.log('Sending message:', JSON.stringify(message)); + } + + // Method to close the WebSocket connection + public close(): void { + this.ws.close(); + } +} + +export { WebSocketClient };