From c16c1689a35fc137bc1f1ddbf0647f656563a40a Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Tue, 23 Jul 2024 20:14:07 -0600 Subject: [PATCH] add ability to edit a GiveAction --- src/components/GiftedDialog.vue | 4 +- src/libs/crypto/vc/index.ts | 2 +- src/libs/endorserServer.ts | 179 ++++++++++++++++++++++--------- src/libs/util.ts | 28 +++-- src/views/ClaimView.vue | 42 +++++++- src/views/ConfirmGiftView.vue | 11 +- src/views/GiftedDetails.vue | 145 ++++++++++++++++++------- src/views/HomeView.vue | 3 +- src/views/NewEditProjectView.vue | 3 + src/views/SharedPhotoView.vue | 2 +- src/vite-env.d.ts | 10 ++ 11 files changed, 327 insertions(+), 102 deletions(-) create mode 100644 src/vite-env.d.ts diff --git a/src/components/GiftedDialog.vue b/src/components/GiftedDialog.vue index 30274be..90cf77a 100644 --- a/src/components/GiftedDialog.vue +++ b/src/components/GiftedDialog.vue @@ -291,8 +291,8 @@ export default class GiftedDialog extends Vue { this.axios, this.apiServer, this.activeDid, - giverDid, - this.receiver?.did as string, + giverDid as string, + recipientDid as string, description, amount, unitCode, diff --git a/src/libs/crypto/vc/index.ts b/src/libs/crypto/vc/index.ts index 308f71c..f18949f 100644 --- a/src/libs/crypto/vc/index.ts +++ b/src/libs/crypto/vc/index.ts @@ -67,7 +67,7 @@ export async function createEndorserJwtForKey( * The SimpleSigner returns a configured function for signing data. * * @example - * const signer = SimpleSigner(import.meta.env.PRIVATE_KEY) + * const signer = SimpleSigner(privateKeyHexString) * signer(data, (err, signature) => { * ... * }) diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 126841c..3a89468 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -48,29 +48,31 @@ export interface ClaimResult { } export interface GenericVerifiableCredential { - "@context": string; + "@context"?: string; "@type": string; [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any } -export interface GenericCredWrapper extends GenericVerifiableCredential { +export interface GenericCredWrapper { + "@context": string; + "@type": string; handleId: string; id: string; issuedAt: string; issuer: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - claim: Record; + claim: T; claimType?: string; } -export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = { - "@context": SCHEMA_ORG_CONTEXT, - "@type": "", - claim: {}, - handleId: "", - id: "", - issuedAt: "", - issuer: "", -}; +export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper = + { + "@context": SCHEMA_ORG_CONTEXT, + "@type": "", + claim: { "@type": "" }, + handleId: "", + id: "", + issuedAt: "", + issuer: "", + }; // a summary record; the VC is found the fullClaim field export interface GiveSummaryRecord { @@ -123,7 +125,7 @@ export interface PlanSummaryRecord { // Note that previous VCs may have additional fields. // https://endorser.ch/doc/html/transactions.html#id4 -export interface GiveVerifiableCredential { +export interface GiveVerifiableCredential extends GenericVerifiableCredential { "@context"?: string; // optional when embedded, eg. in an Agree "@type": "GiveAction"; agent?: { identifier: string }; @@ -191,7 +193,7 @@ export interface PlanData { */ issuerDid: string; /** - * The Identier of the project -- different from jwtId, needs to be fixed + * The identifier of the project -- different from jwtId, needs to be fixed **/ rowid?: string; } @@ -562,8 +564,9 @@ export async function setPlanInCache( /** * Construct GiveAction VC for submission to server */ -export function constructGive( - fromDid?: string | null, +export function hydrateGive( + vcClaimOrig?: GiveVerifiableCredential, + fromDid?: string, toDid?: string, description?: string, amount?: number, @@ -572,42 +575,68 @@ export function constructGive( fulfillsOfferHandleId?: string, isTrade: boolean = false, imageUrl?: string, + lastClaimId?: string, ): GiveVerifiableCredential { - const vcClaim: GiveVerifiableCredential = { - "@context": SCHEMA_ORG_CONTEXT, - "@type": "GiveAction", - recipient: toDid ? { identifier: toDid } : undefined, - agent: fromDid ? { identifier: fromDid } : undefined, - description: description || undefined, - object: amount - ? { amountOfThisGood: amount, unitCode: unitCode || "HUR" } - : undefined, - fulfills: [{ "@type": isTrade ? "TradeAction" : "DonateAction" }], - }; + // Remember: replace values or erase if it's null + + const vcClaim: GiveVerifiableCredential = vcClaimOrig + ? R.clone(vcClaimOrig) + : { + "@context": SCHEMA_ORG_CONTEXT, + "@type": "GiveAction", + }; + + if (lastClaimId) { + vcClaim.lastClaimId = lastClaimId; + delete vcClaim.identifier; + } + + vcClaim.agent = fromDid ? { identifier: fromDid } : undefined; + vcClaim.recipient = toDid ? { identifier: toDid } : undefined; + vcClaim.description = description || undefined; + vcClaim.object = amount + ? { amountOfThisGood: amount, unitCode: unitCode || "HUR" } + : undefined; + + // ensure fulfills is an array + if (!Array.isArray(vcClaim.fulfills)) { + vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : []; + } + // ... and replace or add each element, ending with Trade or Donate + // I realize this doesn't change any elements that are not PlanAction or Offer or Trade/Action. if (fulfillsProjectHandleId) { - vcClaim.fulfills = vcClaim.fulfills || []; // weird that it won't typecheck without this + vcClaim.fulfills = vcClaim.fulfills.filter( + (elem) => elem["@type"] !== "PlanAction", + ); vcClaim.fulfills.push({ "@type": "PlanAction", identifier: fulfillsProjectHandleId, }); } if (fulfillsOfferHandleId) { - vcClaim.fulfills = vcClaim.fulfills || []; // weird that it won't typecheck without this + vcClaim.fulfills = vcClaim.fulfills.filter( + (elem) => elem["@type"] !== "Offer", + ); vcClaim.fulfills.push({ "@type": "Offer", identifier: fulfillsOfferHandleId, }); } - if (imageUrl) { - vcClaim.image = imageUrl; - } + // do Trade/Donate last because current endorser.ch only looks at the first for plans & offers + vcClaim.fulfills = vcClaim.fulfills.filter( + (elem) => + elem["@type"] !== "DonateAction" && elem["@type"] !== "TradeAction", + ); + vcClaim.fulfills.push({ "@type": isTrade ? "TradeAction" : "DonateAction" }); + + vcClaim.image = imageUrl || undefined; + return vcClaim; } /** - * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim + * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim * - * @param identity * @param fromDid may be null * @param toDid * @param description may be null; should have this or amount @@ -617,7 +646,50 @@ export async function createAndSubmitGive( axios: Axios, apiServer: string, issuerDid: string, - fromDid?: string | null, + fromDid?: string, + toDid?: string, + description?: string, + amount?: number, + unitCode?: string, + fulfillsProjectHandleId?: string, + fulfillsOfferHandleId?: string, + isTrade: boolean = false, + imageUrl?: string, +): Promise { + const vcClaim = hydrateGive( + undefined, + fromDid, + toDid, + description, + amount, + unitCode, + fulfillsProjectHandleId, + fulfillsOfferHandleId, + isTrade, + imageUrl, + ); + return createAndSubmitClaim( + vcClaim as GenericVerifiableCredential, + issuerDid, + apiServer, + axios, + ); +} + +/** + * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim + * + * @param fromDid may be null + * @param toDid + * @param description may be null; should have this or amount + * @param amount may be null; should have this or description + */ +export async function editAndSubmitGive( + axios: Axios, + apiServer: string, + fullClaim: GenericCredWrapper, + issuerDid: string, + fromDid?: string, toDid?: string, description?: string, amount?: number, @@ -627,7 +699,8 @@ export async function createAndSubmitGive( isTrade: boolean = false, imageUrl?: string, ): Promise { - const vcClaim = constructGive( + const vcClaim = hydrateGive( + fullClaim.claim, fromDid, toDid, description, @@ -637,9 +710,10 @@ export async function createAndSubmitGive( fulfillsOfferHandleId, isTrade, imageUrl, + fullClaim.id, ); return createAndSubmitClaim( - vcClaim as GenericCredWrapper, + vcClaim as GenericVerifiableCredential, issuerDid, apiServer, axios, @@ -692,7 +766,7 @@ export async function createAndSubmitOffer( }; } return createAndSubmitClaim( - vcClaim as GenericCredWrapper, + vcClaim as OfferVerifiableCredential, issuerDid, apiServer, axios, @@ -751,7 +825,7 @@ export async function createAndSubmitClaim( return { type: "success", response }; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { - console.error("Error creating claim:", error); + console.error("Error submitting claim:", error); const errorMessage: string = error.response?.data?.error?.message || error.message || @@ -820,24 +894,29 @@ export const capitalizeAndInsertSpacesBeforeCaps = (text: string) => { similar code is also contained in endorser-mobile **/ // eslint-disable-next-line @typescript-eslint/no-explicit-any -const claimSummary = (claim: Record) => { +const claimSummary = ( + claim: GenericCredWrapper, +) => { if (!claim) { // to differentiate from "something" above return "something"; } + let specificClaim: + | GenericVerifiableCredential + | GenericCredWrapper = claim; if (claim.claim) { // probably a Verified Credential // eslint-disable-next-line @typescript-eslint/no-explicit-any - claim = claim.claim as Record; + specificClaim = claim.claim; } - if (Array.isArray(claim)) { - if (claim.length === 1) { - claim = claim[0]; + if (Array.isArray(specificClaim)) { + if (specificClaim.length === 1) { + specificClaim = specificClaim[0]; } else { return "multiple claims"; } } - const type = claim["@type"]; + const type = specificClaim["@type"]; if (!type) { return "a claim"; } else { @@ -858,7 +937,7 @@ const claimSummary = (claim: Record) => { similar code is also contained in endorser-mobile **/ export const claimSpecialDescription = ( - record: GenericCredWrapper, + record: GenericCredWrapper, activeDid: string, identifiers: Array, contacts: Array, @@ -952,7 +1031,11 @@ export const claimSpecialDescription = ( "...]" ); } else { - return issuer + " declared " + claimSummary(claim as GenericCredWrapper); + return ( + issuer + + " declared " + + claimSummary(claim as GenericCredWrapper) + ); } }; diff --git a/src/libs/util.ts b/src/libs/util.ts index dc7fbf5..286d2a6 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -11,7 +11,12 @@ import { MASTER_SETTINGS_KEY, } from "@/db/tables/settings"; import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto"; -import { GenericCredWrapper, containsHiddenDid } from "@/libs/endorserServer"; +import { + containsHiddenDid, + GenericCredWrapper, + GenericVerifiableCredential, + OfferVerifiableCredential, +} from "@/libs/endorserServer"; import * as serverUtil from "@/libs/endorserServer"; import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer"; @@ -79,7 +84,9 @@ export const isGlobalUri = (uri: string) => { return uri && uri.match(new RegExp(/^[A-Za-z][A-Za-z0-9+.-]+:/)); }; -export const isGiveAction = (veriClaim: GenericCredWrapper) => { +export const isGiveAction = ( + veriClaim: GenericCredWrapper, +) => { return veriClaim.claimType === "GiveAction"; }; @@ -95,7 +102,7 @@ export const doCopyTwoSecRedo = (text: string, fn: () => void) => { * @param veriClaim is expected to have fields: claim, claimType, and issuer */ export const isGiveRecordTheUserCanConfirm = ( - veriClaim: GenericCredWrapper, + veriClaim: GenericCredWrapper, activeDid: string, confirmerIdList: string[] = [], ) => { @@ -111,9 +118,9 @@ export const isGiveRecordTheUserCanConfirm = ( * @returns the DID of the person who offered, or undefined if hidden * @param veriClaim is expected to have fields: claim and issuer */ -export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = ( - veriClaim, -) => { +export const offerGiverDid: ( + arg0: GenericCredWrapper, +) => string | undefined = (veriClaim) => { let giver; if ( veriClaim.claim.offeredBy?.identifier && @@ -130,8 +137,13 @@ export const offerGiverDid: (arg0: GenericCredWrapper) => string | undefined = ( * @returns true if the user can fulfill the offer * @param veriClaim is expected to have fields: claim, claimType, and issuer */ -export const canFulfillOffer = (veriClaim: GenericCredWrapper) => { - return !!(veriClaim.claimType === "Offer" && offerGiverDid(veriClaim)); +export const canFulfillOffer = ( + veriClaim: GenericCredWrapper, +) => { + return !!( + veriClaim.claimType === "Offer" && + offerGiverDid(veriClaim as GenericCredWrapper) + ); }; // return object with paths and arrays of DIDs for any keys ending in "VisibleToDid" diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index 71b8045..787b015 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -22,6 +22,16 @@

{{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }} +

@@ -368,6 +378,9 @@
+ + This record is an edited version. The latest version is being shown. +