From 5f110fbaaf68b2e398ed798b41cce3df4d5fe569 Mon Sep 17 00:00:00 2001 From: AnisHADJARAB Date: Wed, 14 Aug 2024 08:39:31 +0000 Subject: [PATCH] WIP pairing --- package-lock.json | 6 ++ package.json | 1 + public/style/4nk.css | 3 + src/html/home.html | 18 ++-- src/html/home.js | 63 +++++++++---- src/html/login-modal.html | 9 ++ src/html/login-modal.js | 14 +++ src/index.html | 2 +- src/index.ts | 11 ++- src/scanner.js | 16 ++++ src/services/routing.service.ts | 53 +++++++++++ src/services/service.ts | 56 ++++++++++-- src/websockets.ts | 151 +++++++++++++++++++------------- 13 files changed, 311 insertions(+), 92 deletions(-) create mode 100644 src/html/login-modal.html create mode 100644 src/html/login-modal.js create mode 100644 src/scanner.js diff --git a/package-lock.json b/package-lock.json index 89fbad1..32b5057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@types/qrcode": "^1.5.5", "@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-vue": "^5.0.5", + "html5-qrcode": "^2.3.8", "qrcode": "^1.5.3", "vite": "^5.3.3", "vite-plugin-copy": "^0.1.6", @@ -3571,6 +3572,11 @@ } } }, + "node_modules/html5-qrcode": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.3.8.tgz", + "integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==" + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", diff --git a/package.json b/package.json index 3575293..dd26221 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/qrcode": "^1.5.5", "@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-vue": "^5.0.5", + "html5-qrcode": "^2.3.8", "qrcode": "^1.5.3", "vite": "^5.3.3", "vite-plugin-copy": "^0.1.6", diff --git a/public/style/4nk.css b/public/style/4nk.css index d481fe2..abf34bd 100644 --- a/public/style/4nk.css +++ b/public/style/4nk.css @@ -360,6 +360,9 @@ body { border-bottom: none; } + .qr-code-scanner { + display: none; + } diff --git a/src/html/home.html b/src/html/home.html index 09bab2d..3cc3c57 100644 --- a/src/html/home.html +++ b/src/html/home.html @@ -56,18 +56,24 @@ Scan your other device :
- QR Code + QR Code +
+
+
+
- OK + OK
- + diff --git a/src/html/home.js b/src/html/home.js index 97c0a42..caf702d 100644 --- a/src/html/home.js +++ b/src/html/home.js @@ -1,3 +1,4 @@ +import Routing from "/src/services/routing.service.ts"; import Services from "/src/services/service.ts"; document.querySelectorAll('.tab').forEach(tab => { @@ -22,25 +23,14 @@ document.querySelectorAll('.tab').forEach(tab => { } } - + //// Modal - export function openModal() { - document.getElementById('modal').style.display = 'flex'; + export async function openModal() { + const router = await Routing.getInstance(); + router.openLoginModal() } - function closeModal() { - document.getElementById('modal').style.display = 'none'; - } - - // Close modal when clicking outside of it - window.onclick = function(event) { - const modal = document.getElementById('modal'); - if (event.target === modal) { - closeModal(); - } - } - function openCloseNotifications() { const notifications = document.querySelector('.notification-board') notifications.style.display = notifications?.style.display === 'none' ? 'block' : 'none' @@ -50,4 +40,45 @@ const service = await Services.getInstance() service.setNotification() window.toggleMenu = toggleMenu; -window.openModal = openModal; \ No newline at end of file +window.openModal = openModal; + +/// Scan QR Code +function docReady(fn) { + // 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); + } +} + +docReady(function () { + var resultContainer = document.getElementById('qr-reader-results'); + var lastResult, countResults = 0; + function onScanSuccess(decodedText, decodedResult) { + // if (decodedText !== lastResult) { + ++countResults; + lastResult = decodedText; + // Handle on success condition with the decoded message. + console.log(`Scan result ${decodedText}`, decodedResult); + service.sendPairingTx(decodedText) + // } + } + + var html5QrcodeScanner = new Html5QrcodeScanner( + "qr-reader", { fps: 10, qrbox: 250 }); + html5QrcodeScanner.render(onScanSuccess); +}); + +function scanDevice() { + service.sendPairingTx('decodedText') + + // const scannerImg = document.querySelector('#scanner') + // if(scannerImg) scannerImg.style.display = 'none' + // const scannerQrCode = document.querySelector('.qr-code-scanner') + // if(scannerQrCode) scannerQrCode.style.display = 'block' +} + +window.scanDevice = scanDevice \ No newline at end of file diff --git a/src/html/login-modal.html b/src/html/login-modal.html new file mode 100644 index 0000000..48677b1 --- /dev/null +++ b/src/html/login-modal.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/src/html/login-modal.js b/src/html/login-modal.js new file mode 100644 index 0000000..f5fe78a --- /dev/null +++ b/src/html/login-modal.js @@ -0,0 +1,14 @@ +import Routing from "/src/services/routing.service.ts"; + +const router = await Routing.getInstance(); +export async function confirmLogin() { + router.confirmLogin() +} + +export async function closeLoginModal() { + router.closeLoginModal() +} + + +window.confirmLogin = confirmLogin; +window.closeLoginModal = closeLoginModal; \ No newline at end of file diff --git a/src/index.html b/src/index.html index cc7b87b..2e696aa 100644 --- a/src/index.html +++ b/src/index.html @@ -7,6 +7,7 @@ + 4NK Application @@ -14,6 +15,5 @@ - \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1ccdb1e..5db8ed8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,23 @@ import Services from './services/service'; // import { WebSocketClient } from './websockets'; -const wsurl = `wss://${window.location.hostname}:3001/ws/`; +const wsurl = `ws://74.234.68.175:8090`; document.addEventListener('DOMContentLoaded', async () => { try { const services = await Services.getInstance(); let user = await services.getWallet() console.log("🚀 ~ document.addEventListener ~ user:", user); - + const device = await services.dumpDevice() + console.log("🚀 ~ Services ~ sendPairingTx ~ device:", device) + const wallet = await services.dumpWallet() + console.log("🚀 ~ Services ~ sendPairingTx ~ wallet:", wallet) + const amount = await services.getAmount() + console.log("🚀 ~ Services ~ sendPairingTx ~ amount:", amount) + // services.sendPairingTx('sprt1qqfgf9xm5cxsyj49h3d9kqd0f0f0q0zj6pu5we9k4g7l7axkwsqavqqjsj2dhfsdqf92t0z6tvq67j7j7q7995regajtd23ala6dvaqp6cqravfjh') if(!user) { const sp_adress = await services.createNewDevice(); user = await services.getWallet() - console.log("🚀 ~ document.addEventListener ~ sp_adress:", sp_adress) } await services.getAdresses() diff --git a/src/scanner.js b/src/scanner.js new file mode 100644 index 0000000..fe4d1c2 --- /dev/null +++ b/src/scanner.js @@ -0,0 +1,16 @@ +function onScanSuccess(decodedText, decodedResult) { + // handle the scanned code as you like, for example: + console.log(`Code matched = ${decodedText}`, decodedResult); + } + + function onScanFailure(error) { + // handle scan failure, usually better to ignore and keep scanning. + // for example: + console.warn(`Code scan error = ${error}`); + } + + let html5QrcodeScanner = new Html5QrcodeScanner( + "reader", + { fps: 10, qrbox: {width: 250, height: 250} }, + /* verbose= */ false); + html5QrcodeScanner.render(onScanSuccess, onScanFailure); \ No newline at end of file diff --git a/src/services/routing.service.ts b/src/services/routing.service.ts index e69de29..32b4763 100644 --- a/src/services/routing.service.ts +++ b/src/services/routing.service.ts @@ -0,0 +1,53 @@ +import Database from './database'; +import modalHtml from '../html/login-modal.html?raw'; +import modalScript from '../html/login-modal.js?raw'; + + +export default class Routing { + private static instance: Routing; + private database: any; + private sdkClient: any; + private constructor() {} + + // 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"); + this.database = Database.getInstance() + } + + public openLoginModal() { + const container = document.querySelector('.page-container'); + if (container) container.innerHTML += modalHtml; + const modal = document.getElementById('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); + // Close modal when clicking outside of it + window.onclick = (event) => { + const modal = document.getElementById('modal'); + if (event.target === modal) { + this.closeLoginModal(); + } + } + } + confirmLogin() { + console.log('=============> Confirm Login') + const loginTx = this.sdkClient.create_login_transaction(1) + console.log("🚀 ~ Routing ~ confirmLogin ~ loginTx:", loginTx) + this.sdkClient.login('LOGIN', loginTx) + } + closeLoginModal() { + const modal = document.getElementById('modal') + if (modal) modal.style.display = 'none'; + } +} \ No newline at end of file diff --git a/src/services/service.ts b/src/services/service.ts index d947e9d..d6549d2 100644 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -13,7 +13,7 @@ export default class Services { private static instance: Services; private current_process: string | null = null; private sdkClient: any; - // private websocketConnection: WebSocketClient[] = []; + private websocketConnection: WebSocketClient | null = null; private sp_address: string | null = null; private processes: IProcess[] | null = null; private notifications: INotification[] | null = null; @@ -41,9 +41,9 @@ export default class Services { public async addWebsocketConnection(url: string): Promise { const services = await Services.getInstance(); const newClient = new WebSocketClient(url, services); - // if (!services.websocketConnection.includes(newClient)) { - // services.websocketConnection.push(newClient); - // } + if (!services.websocketConnection) { + services.websocketConnection = newClient; + } } public async recoverInjectHtml(): Promise { const container = document.getElementById('containerId'); @@ -53,7 +53,6 @@ export default class Services { return; } - const services = await Services.getInstance(); container.innerHTML = homePage; const newScript = document.createElement('script') @@ -81,6 +80,51 @@ export default class Services { } } + async sendPairingTx(sp_address: string): Promise { + const services = await Services.getInstance(); + const faucetMessage = await services.createFaucetMessage() + console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage, services.websocketConnection) + services.websocketConnection?.sendNormalMessage(faucetMessage) + const amount = await services.sdkClient.get_available_amount() + console.log("🚀 ~ Services ~ sendPairingTx ~ amount:", amount) + // setTimeout(() => services.sdkClient.create_pairing_transaction('sprt1qqfgf9xm5cxsyj49h3d9kqd0f0f0q0zj6pu5we9k4g7l7axkwsqavqqjsj2dhfsdqf92t0z6tvq67j7j7q7995regajtd23ala6dvaqp6cqravfjh', 0.00000001), 5000) + setTimeout(() => services.sdkClient.create_pairing_transaction('sprt1qqv35lxum62lpm4zmfekjqsskx62clzx5qgsfyrgdu78vj52fync52q7fp2h6lugyfnuk8z32lycandds97nc74sz7nc7e90f9tl2dtzpeshg8nss', 0.00001), 5000) + } + + async parseNewTx(tx: string) { + try { + console.log('==============> sendind txxxxxxx parser', tx) + const services = await Services.getInstance(); + const parsedTx = await services.sdkClient.parse_new_tx(tx, 0, 0.00001) + console.log("🚀 ~ Services ~ parseNewTx ~ parsedTx:", parsedTx) + } catch(e) { + console.log(e) + } + } + + async getAmount() { + const services = await Services.getInstance(); + const amount = await services.sdkClient.get_available_amount() + console.log("🚀 ~ Services ~ sendPairingTx ~ amount:", amount) + } + + async dumpDevice() { + const services = await Services.getInstance(); + const device = await services.sdkClient.dump_device() + console.log("🚀 ~ Services ~ sendPairingTx ~ device:", device) + } + async dumpWallet() { + const services = await Services.getInstance(); + const wallet = await services.sdkClient.dump_wallet() + console.log("🚀 ~ Services ~ sendPairingTx ~ wallet:", wallet) + } + + async createFaucetMessage() { + const services = await Services.getInstance() + const message = await services.sdkClient.create_faucet_msg() + return message; + } + private addSubscription(element: Element, event: string, eventHandler: string): void { this.subscriptions.push({ element, event, eventHandler }); element.addEventListener(event, (this as any)[eventHandler].bind(this)); @@ -233,6 +277,8 @@ export default class Services { } async getProcesses(): Promise { + const processes = this.sdkClient.get_processes() + console.log("🚀 ~ Services ~ getProcesses ~ processes:", processes) return [ { id: 1, diff --git a/src/websockets.ts b/src/websockets.ts index 9389eee..12b2099 100644 --- a/src/websockets.ts +++ b/src/websockets.ts @@ -9,71 +9,94 @@ class WebSocketClient { constructor(url: string, private services: Services) { this.ws = new WebSocket(url); - this.ws.addEventListener('open', (event) => { - console.log('WebSocket connection established'); - // Once the connection is open, send all messages in the queue - while (this.messageQueue.length > 0) { - const message = this.messageQueue.shift(); - if (message) { - this.ws.send(message); - } - } - }); + if(this.ws !== null) { + this.ws.onopen = async (event) => { + console.log('WebSocket connection established'); + // Once the connection is open, send all messages in the queue + const services = await Services.getInstance() + // const faucetMessage = await services.createFaucetMessage() + // console.log("🚀 ~ WebSocketClient ~ this.ws.onopen= ~ faucetMessage:", faucetMessage) + // this.sendNormalMessage(faucetMessage) - // Listen for messages - this.ws.addEventListener('message', (event) => { - const msgData = event.data; - - (async () => { - console.log(msgData); - if (typeof(msgData) === 'string') { - console.log("Received text message: "+msgData); - try { - const feeRate = 1; - // 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); + while (this.messageQueue.length > 0) { + const message = this.messageQueue.shift(); + if (message) { + this.ws.send(message); } - } else { - console.error('Received a non-string message'); } - })(); - }); + }; + + // Listen for messages + this.ws.onmessage = (event) => { + const msgData = event.data; + + console.log("Received text message: ", msgData); + (async () => { + const services = await Services.getInstance() - // Listen for possible errors - this.ws.addEventListener('error', (event) => { - console.error('WebSocket error:', event); - }); + if (typeof msgData === 'string') { + try { + const feeRate = 0.0001; + const parsedMessage = JSON.parse(msgData) + console.log("🚀 ~ WebSocketClient ~ parsedMessage:", parsedMessage) + if(parsedMessage?.flag === 'NewTx') { + // const content = parsedMessage?.content; + // if(content) { + // const parsedContent = JSON.parse(content) + // if(parsedContent?.transaction) + console.log("🚀 ~ WebSocketClient ~ msgData:", msgData) + const services = await Services.getInstance() - // Listen for when the connection is closed - this.ws.addEventListener('close', (event) => { - console.log('WebSocket is closed now.'); - }); + await services.parseNewTx(parsedMessage.content) + // } + + } + // 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 @@ -83,7 +106,7 @@ class WebSocketClient { 'flag': flag, 'content': message } - // console.debug("Sending message:", JSON.stringify(networkMessage)); + console.log("Sending message:", JSON.stringify(networkMessage)); this.ws.send(JSON.stringify(networkMessage)); } else { console.warn('WebSocket is not open. ReadyState:', this.ws.readyState); @@ -95,6 +118,12 @@ class WebSocketClient { 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();