diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts
index c450389f2..3d06d3594 100644
--- a/src/libs/endorserServer.ts
+++ b/src/libs/endorserServer.ts
@@ -6,6 +6,8 @@ import { IIdentifier } from "@veramo/core";
import { Contact } from "@/db/tables/contacts";
import { accessToken, SimpleSigner } from "@/libs/crypto";
+import { NonsensitiveDexie } from "@/db/index";
+import { getIdentity } from "@/libs/util";
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
// the object in RegisterAction claims
@@ -706,6 +708,12 @@ export async function createAndSubmitClaim(
}
}
+/**
+ * An AcceptAction is when someone accepts some contract or pledge.
+ *
+ * @param claim has properties '@context' & '@type'
+ * @return true if the claim is a schema.org AcceptAction
+ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const isAccept = (claim: Record) => {
return (
@@ -902,3 +910,42 @@ export const bvcMeetingJoinClaim = (did: string, startTime: string) => {
},
};
};
+
+export async function setVisibilityUtil(
+ activeDid: string,
+ apiServer: string,
+ axios: Axios,
+ db: NonsensitiveDexie,
+ contact: Contact,
+ visibility: boolean,
+) {
+ if (!activeDid) {
+ return { error: "Cannot set visibility without an identifier." };
+ }
+ const url =
+ apiServer + "/api/report/" + (visibility ? "canSeeMe" : "cannotSeeMe");
+ const identity = await getIdentity(activeDid);
+ const headers = await getHeaders(identity);
+ const payload = JSON.stringify({ did: contact.did });
+
+ try {
+ const resp = await axios.post(url, payload, { headers });
+ if (resp.status === 200) {
+ contact.seesMe = visibility;
+ db.contacts.update(contact.did, { seesMe: visibility });
+ return { success: true };
+ } else {
+ console.error(
+ "Got some bad server response when setting visibility: ",
+ resp.status,
+ resp,
+ );
+ const message =
+ resp.data.error?.message || "Got some error setting visibility.";
+ return { error: message };
+ }
+ } catch (err) {
+ console.error("Got some error when setting visibility:", err);
+ return { error: "Check connectivity and try again." };
+ }
+}
diff --git a/src/libs/util.ts b/src/libs/util.ts
index 10823174c..0bd80166d 100644
--- a/src/libs/util.ts
+++ b/src/libs/util.ts
@@ -201,7 +201,7 @@ export const getIdentity = async (activeDid: string): Promise => {
if (!identity) {
throw new Error(
- `Attempted to load Offer records for DID ${activeDid} but no identifier was found`,
+ `Attempted to load identity ${activeDid} but no identifier was found`,
);
}
return identity;
diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue
index 7b39a70a2..416150aaf 100644
--- a/src/views/AccountViewView.vue
+++ b/src/views/AccountViewView.vue
@@ -55,7 +55,7 @@
Create An Identifier
@@ -71,10 +71,13 @@
-
+
Set Your Name
@@ -163,7 +166,7 @@
Share Your Info
@@ -1168,7 +1171,7 @@ export default class AccountViewView extends Vue {
this.$notify(
{
group: "alert",
- type: "warning",
+ type: "danger",
title: "Update Error",
text: "Unable to update your settings. Check claim limits again.",
},
diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue
index 17cf14111..a335ad5e0 100644
--- a/src/views/ContactQRScanShowView.vue
+++ b/src/views/ContactQRScanShowView.vue
@@ -79,16 +79,25 @@ import { Component, Vue } from "vue-facing-decorator";
import { QrcodeStream } from "vue-qrcode-reader";
import { useClipboard } from "@vueuse/core";
+import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
-import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
-import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto";
-import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
+import { Contact } from "@/db/tables/contacts";
+import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
+import {
+ deriveAddress,
+ getContactPayloadFromJwtUrl,
+ nextDerivationPath,
+ SimpleSigner,
+} from "@/libs/crypto";
import {
CONTACT_URL_PREFIX,
ENDORSER_JWT_URL_LOCATION,
+ isDid,
+ setVisibilityUtil,
} from "@/libs/endorserServer";
+
import { Buffer } from "buffer/";
@Component({
@@ -106,29 +115,12 @@ export default class ContactQRScanShow extends Vue {
givenName = "";
qrValue = "";
- public async getIdentity(activeDid: string) {
- await accountsDB.open();
- const accounts = await accountsDB.accounts.toArray();
- const account: Account | undefined = R.find(
- (acc) => acc.did === activeDid,
- accounts,
- );
- const identity = JSON.parse(account?.identity || "null");
-
- if (!identity) {
- throw new Error(
- "Attempted to show contact info with no identifier available.",
- );
- }
- return identity;
- }
-
async created() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
- this.activeDid = settings?.activeDid || "";
- this.apiServer = settings?.apiServer || "";
- this.givenName = settings?.firstName || "";
+ this.activeDid = (settings?.activeDid as string) || "";
+ this.apiServer = (settings?.apiServer as string) || "";
+ this.givenName = (settings?.firstName as string) || "";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
@@ -172,25 +164,109 @@ export default class ContactQRScanShow extends Vue {
}
}
+ danger(message: string, title: string = "Error", timeout = 5000) {
+ this.$notify(
+ {
+ group: "alert",
+ type: "danger",
+ title: title,
+ text: message,
+ },
+ timeout,
+ );
+ }
+
+ public async getIdentity(activeDid: string) {
+ await accountsDB.open();
+ const accounts = await accountsDB.accounts.toArray();
+ const account: Account | undefined = R.find(
+ (acc) => acc.did === activeDid,
+ accounts,
+ );
+ const identity = JSON.parse((account?.identity as string) || "null");
+
+ if (!identity) {
+ throw new Error(
+ "Attempted to show contact info with no identifier available.",
+ );
+ }
+ return identity;
+ }
+
/**
*
* @param content is the result of a QR scan, an array with one item with a rawValue property
*/
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- onScanDetect(content: any) {
+ async onScanDetect(content: any) {
const url = content[0]?.rawValue;
if (url) {
+ let newContact: Contact;
try {
- localStorage.setItem("contactEndorserUrl", url);
- this.$router.push({ name: "contacts" });
+ const payload = getContactPayloadFromJwtUrl(url);
+ if (!payload) {
+ this.$notify(
+ {
+ group: "alert",
+ type: "danger",
+ title: "No Contact Info",
+ text: "The contact info could not be parsed.",
+ },
+ 3000,
+ );
+ return;
+ }
+ newContact = {
+ did: payload.iss as string,
+ name: payload.own.name,
+ nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
+ profileImageUrl: payload.own.profileImageUrl,
+ publicKeyBase64: payload.own.publicEncKey,
+ };
+ if (!newContact.did) {
+ this.danger("There is no DID.", "Incomplete Contact");
+ return;
+ }
+ if (!isDid(newContact.did)) {
+ this.danger("The DID must begin with 'did:'", "Invalid DID");
+ return;
+ }
} catch (e) {
+ console.error("Error parsing QR info:", e);
+ this.danger("Could not parse the QR info.", "Read Error");
+ return;
+ }
+
+ try {
+ await db.open();
+ await db.contacts.add(newContact);
+
+ let addedMessage;
+ if (this.activeDid) {
+ await this.setVisibility(newContact, true);
+ addedMessage =
+ "They were added, and your activity is visible to them.";
+ } else {
+ addedMessage = "They were added.";
+ }
+ this.$notify(
+ {
+ group: "alert",
+ type: "success",
+ title: "Contact Added",
+ text: addedMessage,
+ },
+ 3000,
+ );
+ } catch (e) {
+ console.error("Error saving contact info:", e);
this.$notify(
{
group: "alert",
- type: "warning",
- title: "Invalid Contact QR Code",
- text: "The QR code isn't in the right format.",
+ type: "danger",
+ title: "Contact Error",
+ text: "Could not save contact info. Check if it already exists.",
},
5000,
);
@@ -199,7 +275,7 @@ export default class ContactQRScanShow extends Vue {
this.$notify(
{
group: "alert",
- type: "warning",
+ type: "danger",
title: "Invalid Contact QR Code",
text: "No QR code detected with contact information.",
},
@@ -208,13 +284,29 @@ export default class ContactQRScanShow extends Vue {
}
}
+ async setVisibility(contact: Contact, visibility: boolean) {
+ const result = await setVisibilityUtil(
+ this.activeDid,
+ this.apiServer,
+ this.axios,
+ db,
+ contact,
+ visibility,
+ );
+ if (result.error) {
+ this.danger(result.error as string, "Error Setting Visibility");
+ } else if (!result.success) {
+ console.error("Got strange result from setting visibility:", result);
+ }
+ }
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onScanError(error: any) {
console.error("Scan was invalid:", error);
this.$notify(
{
group: "alert",
- type: "warning",
+ type: "danger",
title: "Invalid Scan",
text: "The scan was invalid.",
},
@@ -223,6 +315,7 @@ export default class ContactQRScanShow extends Vue {
}
onCopyToClipboard() {
+ //this.onScanDetect([{ rawValue: this.qrValue }]); // good for testing
useClipboard()
.copy(this.qrValue)
.then(() => {
diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue
index 7cbc880d2..7e257ab89 100644
--- a/src/views/ContactsView.vue
+++ b/src/views/ContactsView.vue
@@ -325,6 +325,7 @@ import {
isDid,
RegisterVerifiableCredential,
SERVICE_ID,
+ setVisibilityUtil,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
@@ -344,7 +345,6 @@ export default class ContactsView extends Vue {
activeDid = "";
apiServer = "";
contacts: Array = [];
- contactEndorserUrl = localStorage.getItem("contactEndorserUrl") || "";
contactInput = "";
contactEdit: Contact | null = null;
contactNewName = "";
@@ -388,12 +388,18 @@ export default class ContactsView extends Vue {
this.contacts = baseContacts.sort((a, b) =>
(a.name || "").localeCompare(b.name || ""),
);
+ }
- if (this.contactEndorserUrl) {
- await this.addContactFromScan(this.contactEndorserUrl);
- localStorage.removeItem("contactEndorserUrl");
- this.contactEndorserUrl = "";
- }
+ danger(message: string, title: string = "Error", timeout = 5000) {
+ this.$notify(
+ {
+ group: "alert",
+ type: "danger",
+ title: title,
+ text: message,
+ },
+ timeout,
+ );
}
public async getIdentity(activeDid: string): Promise {
@@ -528,22 +534,14 @@ export default class ContactsView extends Vue {
title: "Load Error",
text: "Got an error loading your gives.",
},
- -1,
+ 5000,
);
}
}
async onClickNewContact(): Promise {
if (!this.contactInput) {
- this.$notify(
- {
- group: "alert",
- type: "warning",
- title: "No Contact",
- text: "There was no contact info to add.",
- },
- 3000,
- );
+ this.danger("There was no contact info to add.", "No Contact");
return;
}
@@ -573,15 +571,7 @@ export default class ContactsView extends Vue {
3000, // keeping it up so that the "visibility" message is seen
);
} catch (e) {
- this.$notify(
- {
- group: "alert",
- type: "danger",
- title: "Contacts Maybe Added",
- text: "An error occurred. Some contacts may have been added.",
- },
- -1,
- );
+ this.danger("An error occurred. Some contacts may have been added.");
}
// .orderBy("name") wouldn't retrieve any entries with a blank name
@@ -697,30 +687,13 @@ export default class ContactsView extends Vue {
async addContact(newContact: Contact) {
if (!newContact.did) {
- this.$notify(
- {
- group: "alert",
- type: "danger",
- title: "Incomplete Contact",
- text: "Cannot add a contact without a DID.",
- },
- 5000,
- );
+ this.danger("Cannot add a contact without a DID.", "Incomplete Contact");
return;
}
if (!isDid(newContact.did)) {
- this.$notify(
- {
- group: "alert",
- type: "danger",
- title: "Invalid DID",
- text: "The DID is not valid. It must begin with 'did:'",
- },
- 5000,
- );
+ this.danger("The DID must begin with 'did:'", "Invalid DID");
return;
}
- newContact.seesMe = true; // since we will immediately set that on the server
return db.contacts
.add(newContact)
.then(() => {
@@ -737,6 +710,7 @@ export default class ContactsView extends Vue {
} else {
addedMessage = "They were added.";
}
+ this.contactInput = "";
if (this.isRegistered) {
this.$notify(
{
@@ -771,15 +745,7 @@ export default class ContactsView extends Vue {
message +=
" Check that the contact doesn't conflict with any you already have.";
}
- this.$notify(
- {
- group: "alert",
- type: "danger",
- title: "Contact Not Added",
- text: message,
- },
- -1,
- );
+ this.danger(message, "Contact Not Added", -1);
});
}
@@ -962,63 +928,42 @@ export default class ContactsView extends Vue {
visibility: boolean,
showSuccessAlert: boolean,
) {
- const url =
- this.apiServer +
- "/api/report/" +
- (visibility ? "canSeeMe" : "cannotSeeMe");
- const identity = await this.getIdentity(this.activeDid);
- const headers = await this.getHeaders(identity);
- const payload = JSON.stringify({ did: contact.did });
-
- try {
- const resp = await this.axios.post(url, payload, { headers });
- if (resp.status === 200) {
- if (showSuccessAlert) {
- this.$notify(
- {
- group: "alert",
- type: "success",
- title: "Visibility Set",
- text:
- this.nameForDid(this.contacts, contact.did) +
- " can " +
- (visibility ? "" : "not ") +
- "see your activity.",
- },
- 3000,
- );
- }
- contact.seesMe = visibility;
- db.contacts.update(contact.did, { seesMe: visibility });
- } else {
- console.error(
- "Got some bad server response when setting visibility: ",
- resp.status,
- resp,
- );
- const message =
- resp.data.error?.message || "Got some error setting visibility.";
+ const result = await setVisibilityUtil(
+ this.activeDid,
+ this.apiServer,
+ this.axios,
+ db,
+ contact,
+ visibility,
+ );
+ if (result.success) {
+ if (showSuccessAlert) {
this.$notify(
{
group: "alert",
- type: "danger",
- title: "Error Setting Visibility",
- text: message,
+ type: "success",
+ title: "Visibility Set",
+ text:
+ (contact.name || "That user") +
+ " can " +
+ (visibility ? "" : "not ") +
+ "see your activity.",
},
- 5000,
+ 3000,
);
}
- } catch (err) {
- console.error("Got some error when setting visibility:", err);
+ } else if (result.error) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Setting Visibility",
- text: "Check connectivity and try again.",
+ text: result.error as string,
},
5000,
);
+ } else {
+ console.error("Got strange result from setting visibility:", result);
}
}
@@ -1087,7 +1032,8 @@ export default class ContactsView extends Vue {
private nameForContact(contact?: Contact, capitalize?: boolean): string {
return (
- (contact?.name as string) || (capitalize ? "T" : "t") + "his unnamed user"
+ (contact?.name as string) ||
+ (capitalize ? "This" : "this") + " unnamed user"
);
}