From d8d147581c7b04f65abccdefc1e1e623d3fcbcb1 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Fri, 6 Mar 2026 00:36:38 -0600 Subject: [PATCH] Implement Contacts Page and Improve Identity Management Features - Added a new ContactsPage component for managing and displaying user contacts. - Introduced a sidebar link to navigate to the Contacts page. - Updated identity management features in IdentitiesPage, including options to export, copy, and upload identity keys. - Enhanced identity restoration process with improved user feedback and error handling. - Refactored existing components to streamline contact fetching and display logic. --- meshchatx/src/frontend/components/App.vue | 38 +- .../frontend/components/CommandPalette.vue | 3 +- .../frontend/components/about/AboutPage.vue | 194 -- .../components/contacts/ContactsPage.vue | 711 +++++ .../interfaces/AddInterfacePage.vue | 2423 ++++++----------- .../messages/ConversationViewer.vue | 4 +- .../components/messages/MessagesPage.vue | 11 +- .../network-visualiser/NetworkVisualiser.vue | 8 + .../nomadnetwork/NomadNetworkPage.vue | 17 +- .../components/settings/IdentitiesPage.vue | 203 +- 10 files changed, 1841 insertions(+), 1771 deletions(-) create mode 100644 meshchatx/src/frontend/components/contacts/ContactsPage.vue diff --git a/meshchatx/src/frontend/components/App.vue b/meshchatx/src/frontend/components/App.vue index 8556191..65cd00e 100644 --- a/meshchatx/src/frontend/components/App.vue +++ b/meshchatx/src/frontend/components/App.vue @@ -170,6 +170,19 @@ + +
  • + + + + +
  • +
  • @@ -481,7 +494,7 @@ >
    -

    LXMF Address QR

    +

    Identity QR (LXMA)

    @@ -1006,13 +1019,25 @@ export default { async openLxmfQr() { if (!this.config?.lxmf_address_hash) return; try { - const uri = `lxmf://${this.config.lxmf_address_hash}`; + const uri = this.getMyIdentityUri(); this.lxmfQrDataUrl = await QRCode.toDataURL(uri, { margin: 1, scale: 6 }); this.showLxmfQr = true; } catch { ToastUtils.error(this.$t("common.error")); } }, + getMyIdentityUri() { + if (!this.config?.lxmf_address_hash) return null; + const publicKey = this.config?.identity_public_key; + return publicKey + ? `lxma://${this.config.lxmf_address_hash}:${publicKey}` + : `lxmf://${this.config.lxmf_address_hash}`; + }, + async copyIdentityUri() { + const uri = this.getMyIdentityUri(); + if (!uri) return; + await this.copyValue(uri, "Identity URI"); + }, async updateConfig(config, label = null) { try { WebSocketConnection.send( @@ -1356,8 +1381,9 @@ export default { }, handleProtocolLink(url) { try { - // lxmf:// or rns:// - const hash = url.replace("lxmf://", "").replace("rns://", "").split("/")[0].replace("/", ""); + // lxma://: or lxmf:// or rns:// + const cleanUrl = url.replace("lxma://", "").replace("lxmf://", "").replace("rns://", ""); + const hash = cleanUrl.split(":")[0].split("/")[0].replace("/", ""); if (hash && hash.length === 32) { this.$router.push({ name: "messages", diff --git a/meshchatx/src/frontend/components/CommandPalette.vue b/meshchatx/src/frontend/components/CommandPalette.vue index c12a70c..e21e13f 100644 --- a/meshchatx/src/frontend/components/CommandPalette.vue +++ b/meshchatx/src/frontend/components/CommandPalette.vue @@ -438,7 +438,8 @@ export default { // fetch telephone contacts const contactResponse = await window.axios.get("/api/v1/telephone/contacts"); - this.contacts = Array.isArray(contactResponse.data) ? contactResponse.data : []; + this.contacts = + contactResponse.data?.contacts ?? (Array.isArray(contactResponse.data) ? contactResponse.data : []); } catch (e) { console.error("Failed to load command palette data:", e); } diff --git a/meshchatx/src/frontend/components/about/AboutPage.vue b/meshchatx/src/frontend/components/about/AboutPage.vue index 00343b3..f41edd2 100644 --- a/meshchatx/src/frontend/components/about/AboutPage.vue +++ b/meshchatx/src/frontend/components/about/AboutPage.vue @@ -804,94 +804,6 @@
    - - -
    -
    - -
    -
    Identity Key Control
    -
    - Critical Security Warning -
    -
    -
    - -
    - - -
    - -
    -
    - Restore Identity -
    -
    - - -
    - — or — -
    - -
    - - -
    - - -
    -
    -
    -
    @@ -943,21 +855,9 @@ export default { autoBackupsTotal: 0, autoBackupsOffset: 0, autoBackupsLimit: 3, - identityBackupMessage: "", - identityBackupError: "", - identityBase32: "", - identityBase32Message: "", - identityBase32Error: "", - identityRestoreInProgress: false, - identityRestoreMessage: "", - identityRestoreError: "", - identityRestoreFileName: "", - identityRestoreFile: null, - identityRestoreBase32: "", electronVersion: null, chromeVersion: null, nodeVersion: null, - showIdentityPaste: false, showContactDev: false, }; }, @@ -1354,100 +1254,6 @@ export default { this.restoreMessage = ""; } }, - async downloadIdentityFile() { - this.identityBackupMessage = ""; - this.identityBackupError = ""; - try { - const response = await window.axios.get("/api/v1/identity/backup/download", { - responseType: "blob", - }); - const blob = new Blob([response.data], { type: "application/octet-stream" }); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.setAttribute("download", "identity"); - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - this.identityBackupMessage = "Identity downloaded. Keep it secret."; - ToastUtils.success(this.$t("about.identity_exported")); - } catch { - this.identityBackupError = "Failed to download identity"; - } - }, - async copyIdentityBase32() { - this.identityBase32Message = ""; - this.identityBase32Error = ""; - try { - const response = await window.axios.get("/api/v1/identity/backup/base32"); - this.identityBase32 = response.data.identity_base32 || ""; - if (!this.identityBase32) { - this.identityBase32Error = "No identity available"; - return; - } - await navigator.clipboard.writeText(this.identityBase32); - this.identityBase32Message = "Identity copied. Clear your clipboard after use."; - ToastUtils.success(this.$t("about.identity_copied")); - } catch { - this.identityBase32Error = "Failed to copy identity"; - } - }, - onIdentityRestoreFileChange(event) { - const files = event.target.files; - if (files && files[0]) { - this.identityRestoreFile = files[0]; - this.identityRestoreFileName = files[0].name; - this.identityRestoreError = ""; - this.identityRestoreMessage = ""; - } - }, - async restoreIdentityFile() { - if (this.identityRestoreInProgress) { - return; - } - if (!this.identityRestoreFile) { - this.identityRestoreError = "Select an identity file to restore."; - return; - } - this.identityRestoreInProgress = true; - this.identityRestoreMessage = ""; - this.identityRestoreError = ""; - try { - const formData = new FormData(); - formData.append("file", this.identityRestoreFile); - const response = await window.axios.post("/api/v1/identity/restore", formData, { - headers: { "Content-Type": "multipart/form-data" }, - }); - this.identityRestoreMessage = response.data.message || "Identity imported."; - } catch { - this.identityRestoreError = "Identity restore failed"; - } finally { - this.identityRestoreInProgress = false; - } - }, - async restoreIdentityBase32() { - if (this.identityRestoreInProgress) { - return; - } - if (!this.identityRestoreBase32) { - this.identityRestoreError = "Provide a base32 key to restore."; - return; - } - this.identityRestoreInProgress = true; - this.identityRestoreMessage = ""; - this.identityRestoreError = ""; - try { - const response = await window.axios.post("/api/v1/identity/restore", { - base32: this.identityRestoreBase32.trim(), - }); - this.identityRestoreMessage = response.data.message || "Identity imported."; - } catch { - this.identityRestoreError = "Identity restore failed"; - } finally { - this.identityRestoreInProgress = false; - } - }, formatRecoveryResult(value) { if (value === null || value === undefined) { return "—"; diff --git a/meshchatx/src/frontend/components/contacts/ContactsPage.vue b/meshchatx/src/frontend/components/contacts/ContactsPage.vue new file mode 100644 index 0000000..5a4fbf7 --- /dev/null +++ b/meshchatx/src/frontend/components/contacts/ContactsPage.vue @@ -0,0 +1,711 @@ + + + + + diff --git a/meshchatx/src/frontend/components/interfaces/AddInterfacePage.vue b/meshchatx/src/frontend/components/interfaces/AddInterfacePage.vue index 3303344..58e70c4 100644 --- a/meshchatx/src/frontend/components/interfaces/AddInterfacePage.vue +++ b/meshchatx/src/frontend/components/interfaces/AddInterfacePage.vue @@ -1,1322 +1,806 @@