From 4a59424d05393c24c439f5924836a5d89e2baf0f Mon Sep 17 00:00:00 2001 From: AnisHADJARAB Date: Wed, 6 Nov 2024 15:30:37 +0000 Subject: [PATCH] refactor by separating sp address view from service --- public/style/4nk.css | 2 +- src/components/header/header.ts | 49 +++- src/pages/home/home.ts | 9 +- src/pages/process/process.ts | 34 +-- src/router.ts | 4 +- .../{routing.service.ts => modal.service.ts} | 31 +-- src/services/service.ts | 249 ++---------------- src/utils/sp-address.utils.ts | 152 +++++++++++ src/utils/subscription.utils.ts | 16 ++ 9 files changed, 270 insertions(+), 276 deletions(-) rename src/services/{routing.service.ts => modal.service.ts} (79%) create mode 100644 src/utils/sp-address.utils.ts create mode 100644 src/utils/subscription.utils.ts diff --git a/public/style/4nk.css b/public/style/4nk.css index bdde9a1..30cf2ac 100644 --- a/public/style/4nk.css +++ b/public/style/4nk.css @@ -193,7 +193,7 @@ body { align-items: center; } .process-container { - grid-column: 3 / 5; + grid-column: 3 / 6; grid-row: 3 ; .card { diff --git a/src/components/header/header.ts b/src/components/header/header.ts index 06cb1cc..16bef79 100644 --- a/src/components/header/header.ts +++ b/src/components/header/header.ts @@ -1,4 +1,5 @@ -import { currentRoute } from '../../router'; +import { INotification } from '~/models/notification.model'; +import { currentRoute, navigate } from '../../router'; import Services from '../../services/service'; let notifications = []; @@ -6,6 +7,7 @@ let notifications = []; export async function unpair() { const service = await Services.getInstance(); await service.unpairDevice(); + navigate('home') } (window as any).unpair = unpair; @@ -25,8 +27,7 @@ function toggleMenu() { async function getNotifications() { const service = await Services.getInstance(); notifications = service.getNotifications(); - const badge = document.querySelector('.notification-badge') as HTMLDivElement; - if (badge) badge.innerHTML = notifications.length.toString(); + return notifications } function openCloseNotifications() { const notifications = document.querySelector('.notification-board') as HTMLDivElement; @@ -39,7 +40,7 @@ export async function initHeader() { if (currentRoute === 'home') { hideSomeFunctionnalities(); } else { - getNotifications(); + launchNotificationWorker() } } @@ -55,3 +56,43 @@ function hideSomeFunctionnalities() { } } } + +async function setNotification(notifications: INotification[]): Promise { + const badge = document.querySelector('.notification-badge') as HTMLDivElement; + 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'; + } +} + +function launchNotificationWorker() { + if (window.Worker) { + // Initialize the worker + const worker = new Worker('/src/workers/notification.worker.ts', { type: 'module' }); + + // Listen for messages from the worker + worker.addEventListener('message', (event) => { + const data = event.data; + console.log('Received data from worker:', data); + setNotification(data); + }); +} else { + console.error('Your browser doesn\'t support web workers.'); +} +} + diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index 5a5c508..5a7d1dc 100644 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -1,6 +1,8 @@ import { Html5QrcodeScanner } from 'html5-qrcode'; -import Routing from '../../services/routing.service'; +import Routing from '../../services/modal.service'; import Services from '../../services/service'; +import { addSubscription } from '../../utils/subscription.utils'; +import { displayEmojis } from '../../utils/sp-address.utils'; let resultContainer = document.getElementById('qr-reader-results'); let lastResult: any, @@ -21,9 +23,10 @@ export async function initHomePage(): Promise { const service = await Services.getInstance(); const spAddress = await service.getDeviceAddress() service.generateQRCode(spAddress) - service.displayEmojis(spAddress) + displayEmojis(spAddress) - document.getElementById('notification-bell')?.addEventListener('click', openCloseNotifications); + const notifBell = document.getElementById('notification-bell') + if(notifBell) addSubscription(notifBell, 'click', openCloseNotifications) var html5QrcodeScanner = new Html5QrcodeScanner('qr-reader', { fps: 10, qrbox: 250 }, undefined); html5QrcodeScanner.render(onScanSuccess, undefined); diff --git a/src/pages/process/process.ts b/src/pages/process/process.ts index 924e7ce..1db1373 100644 --- a/src/pages/process/process.ts +++ b/src/pages/process/process.ts @@ -1,3 +1,4 @@ +import { addSubscription } from '../../utils/subscription.utils'; import { navigate } from '../../router'; import Services from '../../services/service'; @@ -6,7 +7,7 @@ export async function init() { const element = document.querySelector('select') as HTMLSelectElement; // 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); + if(wrapper) addSubscription(wrapper, 'click', clickOnWrapper) wrapper.classList.add('multi-select-component'); wrapper.classList.add('input-field'); @@ -17,14 +18,16 @@ export async function init() { 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); + if(input) { + addSubscription(input, 'keyup', inputChange) + addSubscription(input, 'keydown', deletePressed) + addSubscription(input, 'click', openOptions) + } const dropdown_icon = document.createElement('a'); dropdown_icon.classList.add('dropdown-icon'); - dropdown_icon.addEventListener('click', clickDropdown); + if(dropdown_icon) addSubscription(dropdown_icon, 'click', clickDropdown) const autocomplete_list = document.createElement('ul'); autocomplete_list.classList.add('autocomplete-list'); search_div.appendChild(input); @@ -128,7 +131,7 @@ function createToken(wrapper: HTMLElement, value: any) { close.setAttribute('data-option', value); close.setAttribute('data-hits', '0'); close.innerText = 'x'; - close.addEventListener('click', removeToken); + if(close) addSubscription(close, 'click', removeToken) token.appendChild(token_span); token.appendChild(close); inputInderline?.appendChild(token); @@ -185,7 +188,7 @@ function populateAutocompleteList(select: HTMLSelectElement, query: string, drop const li = document.createElement('li'); li.innerText = options_to_show[0]; li.setAttribute('data-value', options_to_show[0]); - li.addEventListener('click', selectOption); + if(li) addSubscription(li, 'click', selectOption) autocomplete_list?.appendChild(li); if (query.length == options_to_show[0].length) { const event = new Event('click'); @@ -196,7 +199,7 @@ function populateAutocompleteList(select: HTMLSelectElement, query: string, drop const li = document.createElement('li'); li.innerText = options_to_show[i]; li.setAttribute('data-value', options_to_show[i]); - li.addEventListener('click', selectOption); + if(li) addSubscription(li, 'click', selectOption) autocomplete_list?.appendChild(li); } } else { @@ -299,10 +302,10 @@ function removeToken(e: Event) { } // Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist -function deletePressed(e: KeyboardEvent) { +function deletePressed(e: Event) { const input_search = e.target as HTMLInputElement; const wrapper = input_search?.parentNode?.parentNode; - const key = e.keyCode || e.charCode; + const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode; const tokens = wrapper?.querySelectorAll('.selected-wrapper'); if (tokens?.length) { @@ -327,7 +330,7 @@ function deletePressed(e: KeyboardEvent) { } // Dismiss on outside click -document.addEventListener('click', () => { +addSubscription(document, 'click', () => { // get select that has the options available const select = document.querySelectorAll('[data-multi-select-plugin]'); for (let i = 0; i < select.length; i++) { @@ -345,7 +348,7 @@ document.addEventListener('click', () => { } } } -}); +}) async function showSelectedProcess(elem: MouseEvent) { if (elem) { @@ -369,9 +372,7 @@ async function showSelectedProcess(elem: MouseEvent) { zoneElement.setAttribute('process-title', process[1].title); zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`); zoneElement.innerHTML = `${zone.title}: ${zone.description}`; - const service = await Services.getInstance(); - // service.addSubscription(zoneElement, 'click', 'goToProcessPage'); - zoneElement.addEventListener('click', select); + addSubscription(zoneElement, 'click', select); processDiv.appendChild(zoneElement); } if (cardContent) cardContent.appendChild(processDiv); @@ -379,7 +380,7 @@ async function showSelectedProcess(elem: MouseEvent) { } } -function select(event: MouseEvent) { +function select(event: Event) { const target = event.target as HTMLElement; const oldSelectedProcess = document.querySelector('.selected-process-zone'); oldSelectedProcess?.classList.remove('selected-process-zone'); @@ -406,7 +407,6 @@ function goToProcessPage() { async function getProcesses(): Promise { const service = await Services.getInstance(); const processes = await service.getProcesses(); - console.log('🚀 ~ Services ~ getProcesses ~ processes:', processes); return processes; } diff --git a/src/router.ts b/src/router.ts index a4d926b..b9b5eb9 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,6 +1,7 @@ import '../public/style/4nk.css'; import { initHeader } from './components/header/header'; import Services from './services/service'; +import { cleanSubscriptions } from './utils/subscription.utils'; const routes: { [key: string]: string } = { home: '/src/pages/home/home.html', @@ -11,6 +12,7 @@ const routes: { [key: string]: string } = { export let currentRoute = ''; export async function navigate(path: string) { + cleanSubscriptions() path = path.replace(/^\//, ''); if (path.includes('/')) { const parsedPath = path.split('/')[0]; @@ -86,7 +88,7 @@ async function init(): Promise { if (pairingAddress) { setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000); } - await navigate('home'); + await navigate('process'); } }, 200); } catch (error) { diff --git a/src/services/routing.service.ts b/src/services/modal.service.ts similarity index 79% rename from src/services/routing.service.ts rename to src/services/modal.service.ts index 5109013..5752652 100644 --- a/src/services/routing.service.ts +++ b/src/services/modal.service.ts @@ -5,9 +5,10 @@ import confirmationModalScript from '../html/confirmation-modal.js?raw'; import Services from './service'; import { U32_MAX } from './service'; import { navigate } from '../router'; +import { addressToEmoji } from '../utils/sp-address.utils'; -export default class Routing { - private static instance: Routing; +export default class ModalService { + private static instance: ModalService; private sdkClient: any; private currentPrd: any; private currentOutpoint?: string; @@ -15,16 +16,11 @@ export default class Routing { 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(); + public static async getInstance(): Promise { + if (!ModalService.instance) { + ModalService.instance = new ModalService(); } - return Routing.instance; - } - - public async init(): Promise { - this.sdkClient = await import('../../dist/pkg/sdk_client'); + return ModalService.instance; } public openLoginModal(myAddress: string, receiverAddress: string) { @@ -43,23 +39,22 @@ export default class Routing { } 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(); + const service = await Services.getInstance() + const localAddress = await service.getDeviceAddress(); console.log('🚀 ~ Routing ~ openConfirmationModal ~ pcd:', pcd); for (const address of members[0]['sp_addresses']) { - if (address !== local_address) { + if (address !== localAddress) { 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])); + html = html.replace('{{device1}}', await addressToEmoji(members[0]['sp_addresses'][0])); + html = html.replace('{{device2}}', await addressToEmoji(members[0]['sp_addresses'][1])); this.currentOutpoint = outpointCommitment as string; const container = document.querySelector('.page-container'); @@ -99,7 +94,7 @@ export default class Routing { const commitmentOutpoint = `${emptyTxid}:${U32_MAX}`; // We take the paired device(s) from the contract - await this.sdkClient.pair_device(commitmentOutpoint, this.paired_addresses); + await service.pairDevice(commitmentOutpoint, this.paired_addresses); this.paired_addresses = []; const newDevice = await service.dumpDevice(); await service.saveDevice(newDevice); diff --git a/src/services/service.ts b/src/services/service.ts index 7aa1286..41063e7 100644 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -5,8 +5,9 @@ import { IProcess } from '~/models/process.model'; import { WebSocketClient } from '../websockets'; import QRCode from 'qrcode'; import { ApiReturn, Member } from '../../dist/pkg/sdk_client'; -import Routing from './routing.service'; -import { currentRoute, navigate } from '../router'; +import ModalService from './modal.service'; +import { navigate } from '../router'; +import { copyToClipboard } from '../utils/sp-address.utils'; type ProcessesCache = { [key: string]: any; @@ -25,6 +26,7 @@ export default class Services { private notifications: INotification[] | null = null; private subscriptions: { element: Element; event: string; eventHandler: string }[] = []; private database: any; + private routingInstance!: ModalService; // Private constructor to prevent direct instantiation from outside private constructor() {} @@ -38,6 +40,7 @@ export default class Services { Services.initializing = (async () => { const instance = new Services(); await instance.init(); + instance.routingInstance = await ModalService.getInstance() return instance; })(); } @@ -79,162 +82,13 @@ export default class Services { } const copyBtn = document.getElementById('copyBtn'); if (copyBtn) { - copyBtn.addEventListener('click', () => this.copyToClipboard(currentUrl + '?sp_address=' + text)); + copyBtn.addEventListener('click', () => 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 - 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(); @@ -382,8 +236,7 @@ export default class Services { 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); + await this.routingInstance.openConfirmationModal(actual_proposal, processCommitment); } } catch (e) { console.error(e); @@ -409,15 +262,18 @@ export default class Services { }, 0); } - async pairDevice(prd: any, outpointCommitment: string) { + async pairDevice(commitmentTx: string, spAddressList: string[]) { + await this.sdkClient.pair_device(commitmentTx, spAddressList); + } + + async validatePairingDevice(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); + const proposal = this.sdkClient.get_update_proposals(outpointCommitment); console.log('🚀 ~ Services ~ pairDevice ~ proposal:', proposal); // const pairingTx = proposal.pairing_tx.replace(/^\"+|\"+$/g, '') const parsedProposal = JSON.parse(proposal[0]); @@ -436,7 +292,7 @@ export default class Services { let txid = '0'.repeat(64); console.log('🚀 ~ Services ~ pairDevice ~ pairingTx:', pairingTx, `${txid}:4294967295`); - const pairing = await service.sdkClient.pair_device(`${txid}:4294967295`, [senderAddress]); + const pairing = await this.sdkClient.pair_device(`${txid}:4294967295`, [senderAddress]); const device = this.dumpDevice(); console.log('🚀 ~ Services ~ pairDevice ~ device:', device); this.saveDevice(device); @@ -446,10 +302,10 @@ export default class Services { 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); + let tx = await this.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)); + tx.ciphers_to_send.forEach((cipher: string) => this.websocketConnection?.sendMessage('Cipher', cipher)); } navigate('process'); } @@ -505,12 +361,7 @@ export default class Services { 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]); - } - + async createNewDevice() { let spAddress = ''; try { @@ -696,48 +547,6 @@ export default class Services { } } - 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[]) { - console.log('🚀 ~ Services ~ setProcessesInSelectElement ~ processList:', processList); - 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 [ { @@ -763,28 +572,4 @@ export default class Services { }, ]; } - - 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/utils/sp-address.utils.ts b/src/utils/sp-address.utils.ts new file mode 100644 index 0000000..1188495 --- /dev/null +++ b/src/utils/sp-address.utils.ts @@ -0,0 +1,152 @@ +import Services from "../services/service"; +import { addSubscription } from "./subscription.utils"; + +//Copy Address +export async function 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 +export function 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 +export async function addressToEmoji(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 = generateEmojiList(); + const emojis = Array.from(bytes) + .map((byte) => emojiList[byte]) + .join(''); + return emojis; +}; + + + //Get emojis from other device +async function 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 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 + export async function displayEmojis(text: string) { + console.log('🚀 ~ Services ~ adressToEmoji'); + try { + const container = document.getElementById('containerId'); + const emojis = await addressToEmoji(text); + const emojiDisplay = container?.querySelector('.emoji-display'); + + if (emojiDisplay) { + emojiDisplay.textContent = emojis; + } + + emojisPairingRequest(); + + initAddressInput(); + } catch (err) { + console.error(err); + } + }; + + // Verify Other address + export function initAddressInput() { + const addressInput = document.getElementById('addressInput') as HTMLInputElement; + const emojiDisplay = document.getElementById('emoji-display-2'); + const okButton = document.getElementById('okButton'); + addSubscription(addressInput, '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 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', () => { + onOkButtonClick(); + }); + } + } + +async function onOkButtonClick() { + const service = await Services.getInstance() + const addressInput = (document.getElementById('addressInput') as HTMLInputElement).value; + await service.sendPairingTx(addressInput); + } \ No newline at end of file diff --git a/src/utils/subscription.utils.ts b/src/utils/subscription.utils.ts new file mode 100644 index 0000000..0b79ef3 --- /dev/null +++ b/src/utils/subscription.utils.ts @@ -0,0 +1,16 @@ +let subscriptions: { element: Element | Document, event: any, eventHandler: EventListenerOrEventListenerObject }[] = []; + +export function cleanSubscriptions(): void { + console.log("🚀 ~ cleanSubscriptions ~ sub:", subscriptions) + for (const sub of subscriptions) { + const el = sub.element; + const eventHandler = sub.eventHandler; + el.removeEventListener(sub.event, eventHandler); + } + subscriptions = []; + } + +export function addSubscription(element: Element | Document, event: any, eventHandler: EventListenerOrEventListenerObject): void { + subscriptions.push({ element, event, eventHandler }); + element.addEventListener(event, eventHandler); + } \ No newline at end of file