Browse Source

for scan on QR code screen, import and keep on that screen

pull/115/head
Trent Larson 7 months ago
parent
commit
bd148e88a3
  1. 47
      src/libs/endorserServer.ts
  2. 2
      src/libs/util.ts
  3. 13
      src/views/AccountViewView.vue
  4. 155
      src/views/ContactQRScanShowView.vue
  5. 140
      src/views/ContactsView.vue

47
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<string, any>) => {
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." };
}
}

2
src/libs/util.ts

@ -201,7 +201,7 @@ export const getIdentity = async (activeDid: string): Promise<IIdentifier> => {
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;

13
src/views/AccountViewView.vue

@ -55,7 +55,7 @@
</p>
<router-link
:to="{ name: 'start' }"
class="inline-block text-md bg-gradient-to-b from-amber-400 to-amber-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
class="inline-block text-md 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-4 py-2 rounded-md"
>
Create An Identifier
</router-link>
@ -71,10 +71,13 @@
</router-link>
</h2>
</div>
<span v-else>
<span
v-else
class="block w-full text-center text-md bg-amber-200 text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
>
<router-link
:to="{ name: 'new-edit-account' }"
class="block w-full text-center text-md bg-amber-200 text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
class="inline-block text-md 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-4 py-2 rounded-md"
>
Set Your Name
</router-link>
@ -163,7 +166,7 @@
</p>
<router-link
:to="{ name: 'contact-qr' }"
class="inline-block text-md uppercase bg-gradient-to-b from-amber-400 to-amber-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
class="inline-block text-md uppercase 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-4 py-2 rounded-md"
>
Share Your Info
</router-link>
@ -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.",
},

155
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(() => {

140
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<Contact> = [];
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<IIdentifier> {
@ -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<void> {
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"
);
}

Loading…
Cancel
Save