diff --git a/src/main.ts b/src/main.ts index 4549049..88dbedb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,6 +13,7 @@ import { faCalendar, faChevronLeft, faCircleCheck, + faCircleQuestion, faCircleUser, faCopy, faEllipsisVertical, @@ -23,6 +24,8 @@ import { faHouseChimney, faMagnifyingGlass, faPen, + faPersonCircleCheck, + faPersonCircleQuestion, faPlus, faQrcode, faRotate, @@ -38,6 +41,7 @@ library.add( faCalendar, faChevronLeft, faCircleCheck, + faCircleQuestion, faCircleUser, faCopy, faEllipsisVertical, @@ -48,6 +52,8 @@ library.add( faHouseChimney, faMagnifyingGlass, faPen, + faPersonCircleCheck, + faPersonCircleQuestion, faPlus, faQrcode, faRotate, diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index a0c8ba6..5507537 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -202,6 +202,29 @@ +
+ +
+ Rate Limits +

+ You have done {{ limits.doneClaimsThisWeek }} claims out of + {{ limits.maxClaimsPerWeek }} for this week. Your claims counter + resets at {{ readableTime(limits.nextWeekBeginDateTime) }} +

+

+ You have done {{ limits.doneRegistrationsThisMonth }} registrations + out of {{ limits.maxRegistrationsPerMonth }} for this month. Your + registrations counter resets at + {{ readableTime(limits.nextMonthBeginDateTime) }} +

+
+
+
+ - + + + +
to: {{ givenByMeTotals[contact.did] || 0 }} @@ -170,6 +185,8 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; // eslint-disable-next-line @typescript-eslint/no-var-requires const Buffer = require("buffer/").Buffer; +const SERVICE_ID = "endorser.ch"; + export interface GiveVerifiableCredential { "@context": string; "@type": string; @@ -179,6 +196,14 @@ export interface GiveVerifiableCredential { recipient: { identifier: string }; } +export interface RegisterVerifiableCredential { + "@context": string; + "@type": string; + agent: { identifier: string }; + object: string; + recipient: { identifier: string }; +} + @Options({ components: {}, }) @@ -319,7 +344,7 @@ export default class ContactsView extends Vue { this.nameForDid(this.contacts, contact.did) + " with DID " + contact.did + - "?" + " ?" ) ) { await db.open(); @@ -328,6 +353,88 @@ export default class ContactsView extends Vue { } } + async register(contact: Contact) { + if ( + confirm( + "Are you sure you want to use one of your registrations for " + + this.nameForDid(this.contacts, contact.did) + + "?" + ) + ) { + await accountsDB.open(); + const accounts = await accountsDB.accounts.toArray(); + const identity = JSON.parse(accounts[0].identity); + // Make a claim + const vcClaim: RegisterVerifiableCredential = { + "@context": "https://schema.org", + "@type": "RegisterAction", + agent: { identifier: identity.did }, + object: SERVICE_ID, + recipient: { identifier: contact.did }, + }; + // Make a payload for the claim + const vcPayload = { + vc: { + "@context": ["https://www.w3.org/2018/credentials/v1"], + type: ["VerifiableCredential"], + credentialSubject: vcClaim, + }, + }; + // Create a signature using private key of identity + if (identity.keys[0].privateKeyHex !== null) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const privateKeyHex: string = identity.keys[0].privateKeyHex!; + const signer = await SimpleSigner(privateKeyHex); + const alg = undefined; + // Create a JWT for the request + const vcJwt: string = await didJwt.createJWT(vcPayload, { + alg: alg, + issuer: identity.did, + signer: signer, + }); + + // Make the xhr request payload + const payload = JSON.stringify({ jwtEncoded: vcJwt }); + const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER; + const url = endorserApiServer + "/api/v2/claim"; + const token = await accessToken(identity); + const headers = { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }; + + try { + const resp = await this.axios.post(url, payload, { headers }); + //console.log("Got resp data:", resp.data); + if (resp.data?.success?.handleId) { + contact.registered = true; + db.contacts.update(contact.did, { registered: true }); + + this.alertTitle = "Registration Success"; + this.alertMessage = contact.name + " has been registered."; + this.isAlertVisible = true; + } + } catch (error) { + let userMessage = "There was an error. See logs for more info."; + const serverError = error as AxiosError; + if (serverError) { + if (serverError.message) { + userMessage = serverError.message; // Info for the user + } else { + userMessage = JSON.stringify(serverError.toJSON()); + } + } else { + userMessage = error as string; + } + // Now set that error for the user to see. + this.alertTitle = "Error with Server"; + this.alertMessage = userMessage; + this.isAlertVisible = true; + } + } + } + } + async setVisibility(contact: Contact, visibility: boolean) { const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER; const url = @@ -387,6 +494,7 @@ export default class ContactsView extends Vue { const visibility = resp.data; contact.seesMe = visibility; db.contacts.update(contact.did, { seesMe: visibility }); + this.alertTitle = "Refreshed"; this.alertMessage = this.nameForContact(contact, true) + @@ -396,7 +504,6 @@ export default class ContactsView extends Vue { this.isAlertVisible = true; } else { this.alertTitle = "Error from Server"; - console.log("Bad response checking visibility: ", resp.data); if (resp.data.error?.message) { this.alertMessage = resp.data.error?.message; } else {