forked from trent_larson/crowd-funder-for-time-pwa
switch so personal contact JWT is link to this server (not endorser.ch), make empty-did URL show user's info
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
|
||||
<div>
|
||||
<div class="text-center mt-8">
|
||||
<div class>
|
||||
<div>
|
||||
<fa
|
||||
icon="camera"
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
export interface ContactMethod {
|
||||
label: string;
|
||||
type: string; // eg. "EMAIL", "SMS", "WHATSAPP"
|
||||
type: string; // eg. "EMAIL", "SMS", "WHATSAPP", maybe someday "GOOGLE-CONTACT-API"
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
//
|
||||
// When adding a property, consider whether it should be added when exporting & sharing contacts.
|
||||
|
||||
did: string;
|
||||
contactMethods?: Array<ContactMethod>;
|
||||
name?: string;
|
||||
|
||||
@@ -5,8 +5,10 @@ import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
||||
import { HDNode } from "@ethersproject/hdnode";
|
||||
|
||||
import {
|
||||
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
|
||||
createEndorserJwtForDid,
|
||||
ENDORSER_JWT_URL_LOCATION,
|
||||
CONTACT_URL_PATH_ENDORSER_CH_OLD,
|
||||
CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
|
||||
} from "@/libs/endorserServer";
|
||||
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
||||
import { decodeEndorserJwt } from "@/libs/crypto/vc";
|
||||
@@ -101,17 +103,34 @@ export const accessToken = async (did?: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
@return results of uportJwtPayload:
|
||||
@return payload of JWT pulled out of the URL and decoded:
|
||||
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
|
||||
|
||||
Note that similar code is also contained in time-safari
|
||||
Result may be a single contact or it may be { contacts: [ contact, ... ] }
|
||||
*/
|
||||
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
|
||||
let jwtText = jwtUrlText;
|
||||
const endorserContextLoc = jwtText.indexOf(ENDORSER_JWT_URL_LOCATION);
|
||||
if (endorserContextLoc > -1) {
|
||||
const appImportConfirmUrlLoc = jwtText.indexOf(
|
||||
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
|
||||
);
|
||||
if (appImportConfirmUrlLoc > -1) {
|
||||
jwtText = jwtText.substring(
|
||||
endorserContextLoc + ENDORSER_JWT_URL_LOCATION.length,
|
||||
appImportConfirmUrlLoc +
|
||||
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI.length,
|
||||
);
|
||||
}
|
||||
const appImportOneUrlLoc = jwtText.indexOf(
|
||||
CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
|
||||
);
|
||||
if (appImportOneUrlLoc > -1) {
|
||||
jwtText = jwtText.substring(
|
||||
appImportOneUrlLoc + CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI.length,
|
||||
);
|
||||
}
|
||||
const endorserUrlPathLoc = jwtText.indexOf(CONTACT_URL_PATH_ENDORSER_CH_OLD);
|
||||
if (endorserUrlPathLoc > -1) {
|
||||
jwtText = jwtText.substring(
|
||||
endorserUrlPathLoc + CONTACT_URL_PATH_ENDORSER_CH_OLD.length,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import { sha256 } from "ethereum-cryptography/sha256";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import * as R from "ramda";
|
||||
|
||||
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
||||
import {
|
||||
APP_SERVER,
|
||||
DEFAULT_IMAGE_API_SERVER,
|
||||
NotificationIface,
|
||||
} from "@/constants/app";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { accessToken, deriveAddress, nextDerivationPath } from "@/libs/crypto";
|
||||
import { logConsoleAndDb, NonsensitiveDexie } from "@/db/index";
|
||||
@@ -22,10 +26,14 @@ export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||
export const SERVICE_ID = "endorser.ch";
|
||||
// the header line for contacts exported via Endorser Mobile
|
||||
export const CONTACT_CSV_HEADER = "name,did,pubKeyBase64,seesMe,registered";
|
||||
// the prefix for the contact URL
|
||||
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
||||
// the suffix for the contact URL
|
||||
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
|
||||
// the suffix for the contact URL in this app where they are confirmed before import
|
||||
export const CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact-import/";
|
||||
// the suffix for the contact URL in this app where a single one gets imported automatically
|
||||
export const CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI = "/contacts?contactJwt=";
|
||||
// the suffix for the old contact URL -- deprecated Jan 2025, though "endorser.ch/contact?jwt=" shows data on endorser.ch server
|
||||
export const CONTACT_URL_PATH_ENDORSER_CH_OLD = "/contact?jwt=";
|
||||
// unused now that we match on the URL path; just note that it was used for a while to create URLs that showed at endorser.ch
|
||||
//export const CONTACT_URL_PREFIX_ENDORSER_CH_OLD = "https://endorser.ch";
|
||||
// the prefix for handle IDs, the permanent ID for claims on Endorser
|
||||
export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
|
||||
|
||||
@@ -692,7 +700,6 @@ export async function getNewOffersToUser(
|
||||
url += "&beforeId=" + beforeOfferJwtId;
|
||||
}
|
||||
const headers = await getHeaders(activeDid);
|
||||
console.log("Using headers: ", headers);
|
||||
const response = await axios.get(url, { headers });
|
||||
return response.data;
|
||||
}
|
||||
@@ -1090,7 +1097,7 @@ export async function createAndSubmitClaim(
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateEndorserJwtForAccount(
|
||||
export async function generateEndorserJwtUrlForAccount(
|
||||
account: Account,
|
||||
isRegistered?: boolean,
|
||||
name?: string,
|
||||
@@ -1130,7 +1137,7 @@ export async function generateEndorserJwtForAccount(
|
||||
|
||||
const vcJwt = await createEndorserJwtForDid(account.did, contactInfo);
|
||||
|
||||
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
|
||||
const viewPrefix = APP_SERVER + CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI;
|
||||
return viewPrefix + vcJwt;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,6 @@ export default class ClaimCertificateView extends Vue {
|
||||
}
|
||||
|
||||
// Draw claim issuer
|
||||
console.log("claimData.issuer", claimData.issuer);
|
||||
if (
|
||||
claimData.issuer == null ||
|
||||
serverUtil.isHiddenDid(claimData.issuer) ||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Contact Name -->
|
||||
<div class="mt-4 flex">
|
||||
<div class="mt-4 flex" data-testId="contactName">
|
||||
<label
|
||||
for="contactName"
|
||||
class="block text-sm font-medium text-gray-700 mt-2"
|
||||
@@ -227,7 +227,7 @@ export default class ContactEditView extends Vue {
|
||||
this.$notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Notes Saved",
|
||||
title: "Contact Saved",
|
||||
text: "The contact info has been updated successfully.",
|
||||
});
|
||||
(this.$router as Router).push({
|
||||
|
||||
@@ -138,7 +138,7 @@ export default class ContactImportView extends Vue {
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
|
||||
// Retrieve the imported contacts from the query parameter
|
||||
// look for any imported contacts from the query parameter
|
||||
const importedContacts = (this.$route as Router).query[
|
||||
"contacts"
|
||||
] as string;
|
||||
@@ -146,7 +146,7 @@ export default class ContactImportView extends Vue {
|
||||
await this.setContactsSelected(JSON.parse(importedContacts));
|
||||
}
|
||||
|
||||
// match everything after /contact-import/ in the window.location.pathname
|
||||
// look for a JWT after /contact-import/ in the window.location.pathname
|
||||
const jwt = window.location.pathname.match(
|
||||
/\/contact-import\/(ey.+)$/,
|
||||
)?.[1];
|
||||
|
||||
@@ -93,17 +93,18 @@ import { AxiosError } from "axios";
|
||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { QrcodeStream } from "vue-qrcode-reader";
|
||||
import { Router } from "vue-router";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import UserNameDialog from "@/components/UserNameDialog.vue";
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
import { APP_SERVER, NotificationIface } from "@/constants/app";
|
||||
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
|
||||
import {
|
||||
generateEndorserJwtForAccount,
|
||||
generateEndorserJwtUrlForAccount,
|
||||
isDid,
|
||||
register,
|
||||
setVisibilityUtil,
|
||||
@@ -146,7 +147,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
(settings.firstName || "") +
|
||||
(settings.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3
|
||||
|
||||
this.qrValue = await generateEndorserJwtForAccount(
|
||||
this.qrValue = await generateEndorserJwtUrlForAccount(
|
||||
account,
|
||||
!!settings.isRegistered,
|
||||
name,
|
||||
@@ -192,6 +193,13 @@ export default class ContactQRScanShow extends Vue {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(payload.contacts)) {
|
||||
// reroute to the ContactsImport
|
||||
(this.$router as Router).push({
|
||||
path: '/contacts-import/' + url.substring(url.lastIndexOf('/') + 1),
|
||||
});
|
||||
return;
|
||||
}
|
||||
newContact = {
|
||||
did: payload.iss as string,
|
||||
name: payload.own.name,
|
||||
@@ -405,7 +413,7 @@ export default class ContactQRScanShow extends Vue {
|
||||
useClipboard()
|
||||
.copy(this.qrValue)
|
||||
.then(() => {
|
||||
console.log("Contact URL:", this.qrValue);
|
||||
// console.log("Contact URL:", this.qrValue);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
|
||||
@@ -344,7 +344,6 @@ import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
|
||||
import { decodeEndorserJwt } from "@/libs/crypto/vc";
|
||||
import {
|
||||
CONTACT_CSV_HEADER,
|
||||
CONTACT_URL_PREFIX,
|
||||
createEndorserJwtForDid,
|
||||
errorStringForLog,
|
||||
GiveSummaryRecord,
|
||||
@@ -354,6 +353,9 @@ import {
|
||||
setVisibilityUtil,
|
||||
UserInfo,
|
||||
VerifiableCredential,
|
||||
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
|
||||
CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI,
|
||||
CONTACT_URL_PATH_ENDORSER_CH_OLD,
|
||||
} from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||
@@ -426,7 +428,9 @@ export default class ContactsView extends Vue {
|
||||
);
|
||||
|
||||
// handle a contact sent via URL
|
||||
// @deprecated: use /contact-import/:jwt with a JWT that has an array of contacts
|
||||
//
|
||||
// Prefer use of /contact-import/:jwt with a JWT that has an array of contacts
|
||||
// unless you want them to import a single contact without confirmation.
|
||||
const importedContactJwt = (this.$route as RouteLocationNormalizedLoaded)
|
||||
.query["contactJwt"] as string;
|
||||
if (importedContactJwt) {
|
||||
@@ -709,7 +713,11 @@ export default class ContactsView extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (contactInput.startsWith(CONTACT_URL_PREFIX)) {
|
||||
if (
|
||||
contactInput.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI) ||
|
||||
contactInput.includes(CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI) ||
|
||||
contactInput.includes(CONTACT_URL_PATH_ENDORSER_CH_OLD)
|
||||
) {
|
||||
await this.addContactFromScan(contactInput);
|
||||
return;
|
||||
}
|
||||
@@ -872,6 +880,13 @@ export default class ContactsView extends Vue {
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
if (Array.isArray(payload.contacts)) {
|
||||
// reroute to the ContactsImport
|
||||
(this.$router as Router).push({
|
||||
path: '/contacts-import/' + url.substring(url.lastIndexOf('/') + 1),
|
||||
});
|
||||
return;
|
||||
}
|
||||
return this.addContact({
|
||||
did: payload.iss,
|
||||
name: payload.own.name,
|
||||
@@ -1278,9 +1293,25 @@ export default class ContactsView extends Vue {
|
||||
this.danger("You must select contacts to copy.");
|
||||
return;
|
||||
}
|
||||
const selectedContacts = this.contacts.filter((c) =>
|
||||
const selectedContactsFull = this.contacts.filter((c) =>
|
||||
this.contactsSelected.includes(c.did),
|
||||
);
|
||||
const selectedContacts: Array<Contact> = selectedContactsFull.map((c) => {
|
||||
const contact: Contact = {
|
||||
did: c.did,
|
||||
name: c.name,
|
||||
};
|
||||
if (c.nextPubKeyHashB64) {
|
||||
contact.nextPubKeyHashB64 = c.nextPubKeyHashB64;
|
||||
}
|
||||
if (c.profileImageUrl) {
|
||||
contact.profileImageUrl = c.profileImageUrl;
|
||||
}
|
||||
if (c.publicKeyBase64) {
|
||||
contact.publicKeyBase64 = c.publicKeyBase64;
|
||||
}
|
||||
return contact;
|
||||
});
|
||||
// console.log(
|
||||
// "Array of selected contacts:",
|
||||
// JSON.stringify(selectedContacts),
|
||||
|
||||
@@ -278,8 +278,23 @@ export default class DIDView extends Vue {
|
||||
this.apiServer = settings.apiServer || "";
|
||||
|
||||
const pathParam = window.location.pathname.substring("/did/".length);
|
||||
if (pathParam) {
|
||||
this.viewingDid = decodeURIComponent(pathParam);
|
||||
let showDid = pathParam;
|
||||
if (!showDid) {
|
||||
showDid = this.activeDid;
|
||||
if (showDid) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Your Info",
|
||||
text: "No user was specified so showing your info.",
|
||||
},
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (showDid) {
|
||||
this.viewingDid = decodeURIComponent(showDid);
|
||||
this.contactFromDid = await db.contacts.get(this.viewingDid);
|
||||
if (this.contactFromDid) {
|
||||
this.contactYaml = yaml.dump(this.contactFromDid);
|
||||
|
||||
@@ -331,10 +331,10 @@ export default class HelpNotificationsView extends Vue {
|
||||
}
|
||||
|
||||
alertWebPushSubscription() {
|
||||
console.log(
|
||||
"Web push subscription:",
|
||||
JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
|
||||
);
|
||||
// console.log(
|
||||
// "Web push subscription:",
|
||||
// JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
|
||||
// );
|
||||
alert(JSON.stringify(this.subscriptionJSON));
|
||||
}
|
||||
|
||||
|
||||
@@ -557,7 +557,6 @@ export default class HomeView extends Vue {
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserJwtId,
|
||||
);
|
||||
console.log("offersToUserData", offersToUserData);
|
||||
this.numNewOffersToUser = offersToUserData.data.length;
|
||||
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import QuickNav from "@/components/QuickNav.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||
import { generateEndorserJwtForAccount } from "@/libs/endorserServer";
|
||||
import { generateEndorserJwtUrlForAccount } from "@/libs/endorserServer";
|
||||
import { retrieveAccountMetadata } from "@/libs/util";
|
||||
|
||||
@Component({
|
||||
@@ -70,7 +70,7 @@ export default class ShareMyContactInfoView extends Vue {
|
||||
const numContacts = await db.contacts.count();
|
||||
|
||||
if (account) {
|
||||
const message = await generateEndorserJwtForAccount(
|
||||
const message = await generateEndorserJwtUrlForAccount(
|
||||
account,
|
||||
isRegistered,
|
||||
givenName,
|
||||
|
||||
Reference in New Issue
Block a user