From 66895202701d4bb20a458acdf4e93cb6a6374d24 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 18 Jun 2025 15:53:16 -0600 Subject: [PATCH] fix all copies for externally-shared links to redirected deep links --- src/components/HiddenDidDialog.vue | 15 ++++++--- src/libs/endorserServer.ts | 3 +- src/services/deepLinks.ts | 1 - src/views/ClaimView.vue | 33 +++++++++++++------ src/views/ConfirmGiftView.vue | 13 +++++--- src/views/ContactQRScanFullView.vue | 16 +++++++-- src/views/ContactQRScanShowView.vue | 17 ++++++++-- src/views/ContactsView.vue | 4 +-- src/views/InviteOneView.vue | 2 +- src/views/OnboardMeetingSetupView.vue | 2 +- src/views/ProjectViewView.vue | 47 ++++++++++++++++++++++++--- src/views/ShareMyContactInfoView.vue | 2 +- src/views/UserProfileView.vue | 33 +++++++++++++++++++ 13 files changed, 151 insertions(+), 37 deletions(-) diff --git a/src/components/HiddenDidDialog.vue b/src/components/HiddenDidDialog.vue index 980a3852..8593009e 100644 --- a/src/components/HiddenDidDialog.vue +++ b/src/components/HiddenDidDialog.vue @@ -77,7 +77,7 @@ If you'd like an introduction, click here to copy this page, paste it into a message, and ask if they'll tell you more about the {{ roleName }}. @@ -104,7 +104,7 @@ import * as R from "ramda"; import { useClipboard } from "@vueuse/core"; import { Contact } from "../db/tables/contacts"; import * as serverUtil from "../libs/endorserServer"; -import { NotificationIface } from "../constants/app"; +import { APP_SERVER, NotificationIface } from "../constants/app"; @Component export default class HiddenDidDialog extends Vue { @@ -117,7 +117,8 @@ export default class HiddenDidDialog extends Vue { activeDid = ""; allMyDids: Array = []; canShare = false; - windowLocation = window.location.href; + deepLinkPathSuffix = ""; + deepLinkUrl = window.location.href; // this is changed to a deep link in the setup R = R; serverUtil = serverUtil; @@ -129,17 +130,21 @@ export default class HiddenDidDialog extends Vue { } open( + deepLinkPathSuffix: string, roleName: string, visibleToDids: string[], allContacts: Array, activeDid: string, allMyDids: Array, ) { + this.deepLinkPathSuffix = deepLinkPathSuffix; this.roleName = roleName; this.visibleToDids = visibleToDids; this.allContacts = allContacts; this.activeDid = activeDid; this.allMyDids = allMyDids; + + this.deepLinkUrl = APP_SERVER + "/deep-link/" + this.deepLinkPathSuffix; this.isOpen = true; } @@ -173,11 +178,11 @@ export default class HiddenDidDialog extends Vue { } onClickShareClaim() { - this.copyToClipboard("A link to this page", this.windowLocation); + this.copyToClipboard("A link to this page", this.deepLinkUrl); window.navigator.share({ title: "Help Connect Me", text: "I'm trying to find the people who recorded this. Can you help me?", - url: this.windowLocation, + url: this.deepLinkUrl, }); } } diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index cbdcbaee..f2dc79a4 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -1074,7 +1074,8 @@ export async function generateEndorserJwtUrlForAccount( const vcJwt = await createEndorserJwtForDid(account.did, contactInfo); - const viewPrefix = APP_SERVER + CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI; + const viewPrefix = + APP_SERVER + "/deep-link" + CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI; return viewPrefix + vcJwt; } diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index 93fe9aa3..8c8aa501 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -52,7 +52,6 @@ import { } from "../interfaces/deepLinks"; import { logConsoleAndDb } from "../db/databaseUtil"; import type { DeepLinkError } from "../interfaces/deepLinks"; -import { logger } from "@/utils/logger"; /** * Handles processing and routing of deep links in the application. diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index 1d647d7a..8edf3bf8 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -49,21 +49,32 @@ v-if="veriClaim.id" :to="'/claim-cert/' + encodeURIComponent(veriClaim.id)" class="text-blue-500 mt-2" - title="Printable Certificate" + title="View Printable Certificate" > +
@@ -405,7 +416,7 @@ contacts can see more details: click to copy this page info and see if they can make an introduction. Someone is connected to @@ -428,7 +439,7 @@ If you'd like an introduction, share this page with them and ask if they'll tell you more about about the participants. @@ -546,7 +557,7 @@ import { useClipboard } from "@vueuse/core"; import { GenericVerifiableCredential } from "../interfaces"; import GiftedDialog from "../components/GiftedDialog.vue"; import QuickNav from "../components/QuickNav.vue"; -import { NotificationIface, USE_DEXIE_DB } from "../constants/app"; +import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app"; import * as databaseUtil from "../db/databaseUtil"; import { db } from "../db/index"; import { logConsoleAndDb } from "../db/databaseUtil"; @@ -593,8 +604,9 @@ export default class ClaimView extends Vue { veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaimDump = ""; veriClaimDidsVisible: { [key: string]: string[] } = {}; - windowLocation = window.location.href; + windowDeepLink = window.location.href; // changed in the setup for deep linking + APP_SERVER = APP_SERVER; R = R; yaml = yaml; libsUtil = libsUtil; @@ -671,6 +683,7 @@ export default class ClaimView extends Vue { 5000, ); } + this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`; this.canShare = !!navigator.share; } @@ -1006,11 +1019,11 @@ export default class ClaimView extends Vue { } onClickShareClaim() { - this.copyToClipboard("A link to this page", this.windowLocation); + this.copyToClipboard("A link to this page", this.windowDeepLink); window.navigator.share({ title: "Help Connect Me", text: "I'm trying to find the people who recorded this. Can you help me?", - url: this.windowLocation, + url: this.windowDeepLink, }); } diff --git a/src/views/ConfirmGiftView.vue b/src/views/ConfirmGiftView.vue index 63225259..793df516 100644 --- a/src/views/ConfirmGiftView.vue +++ b/src/views/ConfirmGiftView.vue @@ -436,7 +436,7 @@ import { Component, Vue } from "vue-facing-decorator"; import { useClipboard } from "@vueuse/core"; import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import QuickNav from "../components/QuickNav.vue"; -import { NotificationIface, USE_DEXIE_DB } from "../constants/app"; +import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; import { Contact } from "../db/tables/contacts"; import * as databaseUtil from "../db/databaseUtil"; @@ -494,7 +494,7 @@ export default class ConfirmGiftView extends Vue { veriClaim = serverUtil.BLANK_GENERIC_SERVER_RECORD; veriClaimDump = ""; veriClaimDidsVisible: { [key: string]: string[] } = {}; - windowLocation = window.location.href; + windowLocation = window.location.href; // this is changed to a deep link in the setup R = R; yaml = yaml; @@ -566,6 +566,9 @@ export default class ConfirmGiftView extends Vue { } const claimId = decodeURIComponent(pathParam); + + this.windowLocation = APP_SERVER + "/deep-link/confirm-gift/" + claimId; + await this.loadClaim(claimId, this.activeDid); } @@ -676,12 +679,12 @@ export default class ConfirmGiftView extends Vue { /** * Add participant (giver/recipient) name & URL info */ - if (this.giveDetails?.agentDid) { - this.giverName = this.didInfo(this.giveDetails.agentDid); + this.giverName = this.didInfo(this.giveDetails?.agentDid); + if (this.giveDetails?.agentDid) { this.urlForNewGive += `&giverDid=${encodeURIComponent(this.giveDetails.agentDid)}&giverName=${encodeURIComponent(this.giverName)}`; } + this.recipientName = this.didInfo(this.giveDetails?.recipientDid); if (this.giveDetails?.recipientDid) { - this.recipientName = this.didInfo(this.giveDetails.recipientDid); this.urlForNewGive += `&recipientDid=${encodeURIComponent(this.giveDetails.recipientDid)}&recipientName=${encodeURIComponent(this.recipientName)}`; } diff --git a/src/views/ContactQRScanFullView.vue b/src/views/ContactQRScanFullView.vue index eba485ca..1ca08edc 100644 --- a/src/views/ContactQRScanFullView.vue +++ b/src/views/ContactQRScanFullView.vue @@ -124,12 +124,14 @@ import * as databaseUtil from "../db/databaseUtil"; import { CONTACT_CSV_HEADER, CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI, + generateEndorserJwtUrlForAccount, setVisibilityUtil, } from "../libs/endorserServer"; import UserNameDialog from "../components/UserNameDialog.vue"; import { retrieveAccountMetadata } from "../libs/util"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { parseJsonField } from "../db/databaseUtil"; +import { Account } from "@/db/tables/accounts"; interface QRScanResult { rawValue?: string; @@ -157,6 +159,7 @@ export default class ContactQRScanFull extends Vue { apiServer = ""; givenName = ""; isRegistered = false; + profileImageUrl = ""; qrValue = ""; ETHR_DID_PREFIX = ETHR_DID_PREFIX; @@ -179,6 +182,7 @@ export default class ContactQRScanFull extends Vue { this.apiServer = settings.apiServer || ""; this.givenName = settings.firstName || ""; this.isRegistered = !!settings.isRegistered; + this.profileImageUrl = settings.profileImageUrl || ""; const account = await retrieveAccountMetadata(this.activeDid); if (account) { @@ -588,9 +592,17 @@ export default class ContactQRScanFull extends Vue { ); } - onCopyUrlToClipboard() { + async onCopyUrlToClipboard() { + const account = await libsUtil.retrieveFullyDecryptedAccount(this.activeDid) as Account; + const jwtUrl = await generateEndorserJwtUrlForAccount( + account, + this.isRegistered, + this.givenName, + this.profileImageUrl, + true, + ); useClipboard() - .copy(this.qrValue) + .copy(jwtUrl) .then(() => { this.$notify( { diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue index 076ee279..675559e7 100644 --- a/src/views/ContactQRScanShowView.vue +++ b/src/views/ContactQRScanShowView.vue @@ -177,6 +177,7 @@ import { getContactJwtFromJwtUrl } from "../libs/crypto"; import { CONTACT_CSV_HEADER, CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI, + generateEndorserJwtUrlForAccount, register, setVisibilityUtil, } from "../libs/endorserServer"; @@ -187,6 +188,7 @@ import { logger } from "../utils/logger"; import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory"; import { CameraState } from "@/services/QRScanner/types"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; +import { Account } from "@/db/tables/accounts"; interface QRScanResult { rawValue?: string; @@ -216,6 +218,7 @@ export default class ContactQRScanShow extends Vue { isRegistered = false; qrValue = ""; isScanning = false; + profileImageUrl = ""; error: string | null = null; // QR Scanner properties @@ -253,6 +256,7 @@ export default class ContactQRScanShow extends Vue { this.hideRegisterPromptOnNewContact = !!settings.hideRegisterPromptOnNewContact; this.isRegistered = !!settings.isRegistered; + this.profileImageUrl = settings.profileImageUrl || ""; const account = await libsUtil.retrieveAccountMetadata(this.activeDid); if (account) { @@ -667,10 +671,17 @@ export default class ContactQRScanShow extends Vue { }); } - onCopyUrlToClipboard() { - //this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing + async onCopyUrlToClipboard() { + const account = await libsUtil.retrieveFullyDecryptedAccount(this.activeDid) as Account; + const jwtUrl = await generateEndorserJwtUrlForAccount( + account, + this.isRegistered, + this.givenName, + this.profileImageUrl, + true, + ); useClipboard() - .copy(this.qrValue) + .copy(jwtUrl) .then(() => { this.$notify( { diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 02f8c6a4..faf63dbf 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -126,7 +126,6 @@
+
@@ -55,7 +61,11 @@ {{ issuerInfoObject?.displayName }} - + +
@@ -632,7 +642,7 @@ import TopMessage from "../components/TopMessage.vue"; import QuickNav from "../components/QuickNav.vue"; import EntityIcon from "../components/EntityIcon.vue"; import ProjectIcon from "../components/ProjectIcon.vue"; -import { NotificationIface, USE_DEXIE_DB } from "../constants/app"; +import { APP_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app"; import * as databaseUtil from "../db/databaseUtil"; import { db, @@ -646,6 +656,7 @@ import { retrieveAccountDids } from "../libs/util"; import HiddenDidDialog from "../components/HiddenDidDialog.vue"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; +import { useClipboard } from "@vueuse/core"; /** * Project View Component * @author Matthew Raymer @@ -842,6 +853,28 @@ export default class ProjectViewView extends Vue { }); } + onCopyLinkClick() { + const shortestProjectId = this.projectId.startsWith( + serverUtil.ENDORSER_CH_HANDLE_PREFIX, + ) + ? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length) + : this.projectId; + const deepLink = `${APP_SERVER}/deep-link/project/${shortestProjectId}`; + useClipboard() + .copy(deepLink) + .then(() => { + this.$notify( + { + group: "alert", + type: "toast", + title: "Copied", + text: "A link to this project was copied to the clipboard.", + }, + 2000, + ); + }); + } + // Isn't there a better way to make this available to the template? expandText() { this.expanded = true; @@ -1304,7 +1337,7 @@ export default class ProjectViewView extends Vue { } // return an HTTPS URL if it's not a global URL - addScheme(url: string) { + ensureScheme(url: string) { if (!libsUtil.isGlobalUri(url)) { return "https://" + url; } @@ -1465,7 +1498,13 @@ export default class ProjectViewView extends Vue { } openHiddenDidDialog() { + const shortestProjectId = this.projectId.startsWith( + serverUtil.ENDORSER_CH_HANDLE_PREFIX, + ) + ? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length) + : this.projectId; (this.$refs.hiddenDidDialog as HiddenDidDialog).open( + "project/" + shortestProjectId, "creator", this.issuerVisibleToDids, this.allContacts, diff --git a/src/views/ShareMyContactInfoView.vue b/src/views/ShareMyContactInfoView.vue index 445c2775..ca1f2e94 100644 --- a/src/views/ShareMyContactInfoView.vue +++ b/src/views/ShareMyContactInfoView.vue @@ -105,7 +105,7 @@ export default class ShareMyContactInfoView extends Vue { group: "alert", type: "info", title: "Copied", - text: "Your contact info was copied to the clipboard. Have them paste it in the box on their 'Contacts' screen.", + text: "Your contact info was copied to the clipboard. Have them click on it, or paste it in the box on their 'Contacts' screen.", }, 5000, ); diff --git a/src/views/UserProfileView.vue b/src/views/UserProfileView.vue index 0e1471b8..11db4a09 100644 --- a/src/views/UserProfileView.vue +++ b/src/views/UserProfileView.vue @@ -16,6 +16,9 @@ Individual Profile +
+ +
@@ -32,6 +35,12 @@
{{ didInfo(profile.issuerDid, activeDid, allMyDids, allContacts) }} +

{{ profile.description }} @@ -100,6 +109,7 @@ import { Router, RouteLocationNormalizedLoaded } from "vue-router"; import QuickNav from "../components/QuickNav.vue"; import TopMessage from "../components/TopMessage.vue"; import { + APP_SERVER, DEFAULT_PARTNER_API_SERVER, NotificationIface, USE_DEXIE_DB, @@ -113,6 +123,7 @@ import { retrieveAccountDids } from "../libs/util"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { Settings } from "@/db/tables/settings"; +import { useClipboard } from "@vueuse/core"; @Component({ components: { LMap, @@ -186,6 +197,10 @@ export default class UserProfileView extends Vue { if (response.status === 200) { const result = await response.json(); this.profile = result.data; + if (this.profile && this.profile.rowId !== profileId) { + // currently the server returns "rowid" with lowercase "i"; remove when that's fixed + this.profile.rowId = profileId; + } } else { throw new Error("Failed to load profile"); } @@ -204,5 +219,23 @@ export default class UserProfileView extends Vue { this.isLoading = false; } } + + onCopyLinkClick() { + console.log("onCopyLinkClick", this.profile); + const deepLink = `${APP_SERVER}/deep-link/user-profile/${this.profile?.rowId}`; + useClipboard() + .copy(deepLink) + .then(() => { + this.$notify( + { + group: "alert", + type: "toast", + title: "Copied", + text: "A link to this profile was copied to the clipboard.", + }, + 2000, + ); + }); + } }