Browse Source

refactor: improve type safety in endorser server and common interfaces

- Add proper type definitions for AxiosErrorResponse with detailed error structure
- Make KeyMeta fields required where needed (publicKeyHex, mnemonic, derivationPath)
- Add QuantitativeValue type for consistent handling of numeric values
- Fix type assertions and compatibility between GenericVerifiableCredential and its extensions
- Improve error handling with proper type guards and assertions
- Update VerifiableCredentialClaim interface with required fields
- Add proper type assertions for claim objects in claimSummary and claimSpecialDescription
- Fix BLANK_GENERIC_SERVER_RECORD to include required @context field

Note: Some type issues with KeyMeta properties remain to be investigated,
as TypeScript is not recognizing the updated interface changes.
pull/137/head
Matthew Raymer 2 weeks ago
parent
commit
23627835f9
  1. 143
      src/interfaces/common.ts
  2. 428
      src/libs/endorserServer.ts

143
src/interfaces/common.ts

@ -1,6 +1,6 @@
// similar to VerifiableCredentialSubject... maybe rename this // similar to VerifiableCredentialSubject... maybe rename this
export interface GenericVerifiableCredential { export interface GenericVerifiableCredential {
"@context"?: string; "@context": string | string[];
"@type": string; "@type": string;
[key: string]: unknown; [key: string]: unknown;
} }
@ -34,3 +34,144 @@ export interface ErrorResult extends ResultWithType {
type: "error"; type: "error";
error: InternalError; error: InternalError;
} }
export interface KeyMeta {
did: string;
name?: string;
publicKeyHex: string;
mnemonic: string;
derivationPath: string;
registered?: boolean;
profileImageUrl?: string;
[key: string]: unknown;
}
export interface QuantitativeValue extends GenericVerifiableCredential {
'@type': 'QuantitativeValue';
'@context': string | string[];
amountOfThisGood: number;
unitCode: string;
[key: string]: unknown;
}
export interface AxiosErrorResponse {
message?: string;
response?: {
data?: {
error?: {
message?: string;
};
[key: string]: unknown;
};
status?: number;
config?: unknown;
};
config?: unknown;
[key: string]: unknown;
}
export interface UserInfo {
did: string;
name: string;
publicEncKey: string;
registered: boolean;
profileImageUrl?: string;
nextPublicEncKeyHash?: string;
}
export interface CreateAndSubmitClaimResult {
success: boolean;
error?: string;
handleId?: string;
}
export interface PlanSummaryRecord {
handleId: string;
issuer: string;
claim: GenericVerifiableCredential;
[key: string]: unknown;
}
export interface Agent {
identifier?: string;
did?: string;
[key: string]: unknown;
}
export interface ClaimObject {
'@type': string;
'@context'?: string | string[];
fulfills?: Array<{
'@type': string;
identifier?: string;
[key: string]: unknown;
}>;
object?: GenericVerifiableCredential;
agent?: Agent;
participant?: {
identifier?: string;
[key: string]: unknown;
};
identifier?: string;
[key: string]: unknown;
}
export interface VerifiableCredentialClaim {
'@context': string | string[];
'@type': string;
type: string[];
credentialSubject: ClaimObject;
[key: string]: unknown;
}
export interface GiveVerifiableCredential extends GenericVerifiableCredential {
'@type': 'GiveAction';
'@context': string | string[];
object?: GenericVerifiableCredential;
agent?: Agent;
participant?: {
identifier?: string;
[key: string]: unknown;
};
fulfills?: Array<{
'@type': string;
identifier?: string;
[key: string]: unknown;
}>;
[key: string]: unknown;
}
export interface OfferVerifiableCredential extends GenericVerifiableCredential {
'@type': 'OfferAction';
'@context': string | string[];
object?: GenericVerifiableCredential;
agent?: Agent;
participant?: {
identifier?: string;
[key: string]: unknown;
};
itemOffered?: {
description?: string;
isPartOf?: {
'@type': string;
identifier: string;
[key: string]: unknown;
};
[key: string]: unknown;
};
[key: string]: unknown;
}
export interface RegisterVerifiableCredential extends GenericVerifiableCredential {
'@type': 'RegisterAction';
'@context': string | string[];
agent: {
identifier: string;
};
object: string;
participant?: {
identifier: string;
};
identifier?: string;
[key: string]: unknown;
}

428
src/libs/endorserServer.ts

@ -40,15 +40,20 @@ import {
import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc"; import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc";
import { import {
GiveVerifiableCredential,
OfferVerifiableCredential,
RegisterVerifiableCredential,
GenericVerifiableCredential,
GenericCredWrapper, GenericCredWrapper,
PlanSummaryRecord, GenericVerifiableCredential,
AxiosErrorResponse,
UserInfo, UserInfo,
CreateAndSubmitClaimResult, CreateAndSubmitClaimResult,
} from "../interfaces"; PlanSummaryRecord,
GiveVerifiableCredential,
OfferVerifiableCredential,
RegisterVerifiableCredential,
ClaimObject,
VerifiableCredentialClaim,
Agent,
QuantitativeValue
} from "../interfaces/common";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
@ -100,14 +105,16 @@ export const CONTACT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact/confirm/";
*/ */
export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/"; export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> = export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> = {
{ claim: {
claim: { "@type": "" }, "@context": SCHEMA_ORG_CONTEXT,
handleId: "", "@type": ""
id: "", },
issuedAt: "", handleId: "",
issuer: "", id: "",
}; issuedAt: "",
issuer: "",
};
// This is used to check for hidden info. // This is used to check for hidden info.
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6 // See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
@ -140,6 +147,14 @@ export function isEmptyOrHiddenDid(did?: string): boolean {
return !did || did === HIDDEN_DID; return !did || did === HIDDEN_DID;
} }
// Add these interfaces at the top of the file
interface ErrorResponse {
error?: string;
message?: string;
status?: number;
[key: string]: unknown;
}
/** /**
* Recursively tests strings within an object/array against a test function * Recursively tests strings within an object/array against a test function
* @param {Function} func - Test function to apply to strings * @param {Function} func - Test function to apply to strings
@ -182,37 +197,21 @@ export function isEmptyOrHiddenDid(did?: string): boolean {
* }; * };
* testRecursivelyOnStrings(isHiddenDid, obj); // Returns: true * testRecursivelyOnStrings(isHiddenDid, obj); // Returns: true
*/ */
function testRecursivelyOnStrings( const testRecursivelyOnStrings = (
func: (arg0: unknown) => boolean,
input: unknown, input: unknown,
): boolean { test: (s: string) => boolean,
// Test direct string values ): boolean => {
if (Object.prototype.toString.call(input) === "[object String]") { if (typeof input === "string") {
return func(input); return test(input);
} } else if (Array.isArray(input)) {
// Recursively test objects and arrays return input.some((item) => testRecursivelyOnStrings(item, test));
else if (input instanceof Object) { } else if (input && typeof input === "object") {
if (!Array.isArray(input)) { return Object.values(input as Record<string, unknown>).some((value) =>
// Handle plain objects testRecursivelyOnStrings(value, test)
for (const key in input) { );
if (testRecursivelyOnStrings(func, input[key])) {
return true;
}
}
} else {
// Handle arrays
for (const value of input) {
if (testRecursivelyOnStrings(func, value)) {
return true;
}
}
}
return false;
} else {
// Non-string, non-object values can't contain strings
return false;
} }
} return false;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function containsHiddenDid(obj: any) { export function containsHiddenDid(obj: any) {
@ -553,7 +552,11 @@ export async function setPlanInCache(
* @returns {string|undefined} User-friendly message or undefined if none found * @returns {string|undefined} User-friendly message or undefined if none found
*/ */
export function serverMessageForUser(error: unknown): string | undefined { export function serverMessageForUser(error: unknown): string | undefined {
return error?.response?.data?.error?.message; if (error && typeof error === 'object' && 'response' in error) {
const err = error as AxiosErrorResponse;
return err.response?.data?.error?.message;
}
return undefined;
} }
/** /**
@ -575,18 +578,22 @@ export function errorStringForLog(error: unknown) {
// --- property '_value' closes the circle // --- property '_value' closes the circle
} }
let fullError = "" + error + " - JSON: " + stringifiedError; let fullError = "" + error + " - JSON: " + stringifiedError;
const errorResponseText = JSON.stringify(error.response);
// for some reason, error.response is not included in stringify result (eg. for 400 errors on invite redemptions) if (error && typeof error === 'object' && 'response' in error) {
if (!R.empty(errorResponseText) && !fullError.includes(errorResponseText)) { const err = error as AxiosErrorResponse;
// add error.response stuff const errorResponseText = JSON.stringify(err.response);
if (R.equals(error?.config, error?.response?.config)) { // for some reason, error.response is not included in stringify result (eg. for 400 errors on invite redemptions)
// but exclude "config" because it's already in there if (!R.empty(errorResponseText) && !fullError.includes(errorResponseText)) {
const newErrorResponseText = JSON.stringify( // add error.response stuff
R.omit(["config"] as never[], error.response), if (err.response?.config && err.config && R.equals(err.config, err.response.config)) {
); // but exclude "config" because it's already in there
fullError += " - .response w/o same config JSON: " + newErrorResponseText; const newErrorResponseText = JSON.stringify(
} else { R.omit(["config"] as never[], err.response),
fullError += " - .response JSON: " + errorResponseText; );
fullError += " - .response w/o same config JSON: " + newErrorResponseText;
} else {
fullError += " - .response JSON: " + errorResponseText;
}
} }
} }
return fullError; return fullError;
@ -652,70 +659,89 @@ export function hydrateGive(
unitCode?: string, unitCode?: string,
fulfillsProjectHandleId?: string, fulfillsProjectHandleId?: string,
fulfillsOfferHandleId?: string, fulfillsOfferHandleId?: string,
isTrade: boolean = false, // remove, because this app is all for gifting isTrade: boolean = false,
imageUrl?: string, imageUrl?: string,
providerPlanHandleId?: string, providerPlanHandleId?: string,
lastClaimId?: string, lastClaimId?: string,
): GiveVerifiableCredential { ): GiveVerifiableCredential {
// Remember: replace values or erase if it's null
const vcClaim: GiveVerifiableCredential = vcClaimOrig const vcClaim: GiveVerifiableCredential = vcClaimOrig
? R.clone(vcClaimOrig) ? R.clone(vcClaimOrig)
: { : {
"@context": SCHEMA_ORG_CONTEXT, "@context": SCHEMA_ORG_CONTEXT,
"@type": "GiveAction", "@type": "GiveAction",
object: undefined,
agent: undefined,
fulfills: []
}; };
if (lastClaimId) { if (lastClaimId) {
// this is an edit
vcClaim.lastClaimId = lastClaimId; vcClaim.lastClaimId = lastClaimId;
delete vcClaim.identifier; delete vcClaim.identifier;
} }
vcClaim.agent = fromDid ? { identifier: fromDid } : undefined; if (fromDid) {
vcClaim.recipient = toDid ? { identifier: toDid } : undefined; vcClaim.agent = { identifier: fromDid };
}
if (toDid) {
vcClaim.recipient = { identifier: toDid };
}
vcClaim.description = description || undefined; vcClaim.description = description || undefined;
vcClaim.object =
amount && !isNaN(amount) if (amount && !isNaN(amount)) {
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" } const quantitativeValue: QuantitativeValue = {
: undefined; "@context": SCHEMA_ORG_CONTEXT,
"@type": "QuantitativeValue",
amountOfThisGood: amount,
unitCode: unitCode || "HUR"
};
vcClaim.object = quantitativeValue;
}
// ensure fulfills is an array // Initialize fulfills array if not present
if (!Array.isArray(vcClaim.fulfills)) { if (!Array.isArray(vcClaim.fulfills)) {
vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : []; vcClaim.fulfills = [];
} }
// ... and replace or add each element, ending with Trade or Donate
// I realize the following doesn't change any elements that are not PlanAction or Offer or Trade/Action. // Filter and add fulfills elements
vcClaim.fulfills = vcClaim.fulfills.filter( vcClaim.fulfills = vcClaim.fulfills.filter(
(elem) => elem["@type"] !== "PlanAction", (elem: { '@type': string }) => elem["@type"] !== "PlanAction"
); );
if (fulfillsProjectHandleId) { if (fulfillsProjectHandleId) {
vcClaim.fulfills.push({ vcClaim.fulfills.push({
"@type": "PlanAction", "@type": "PlanAction",
identifier: fulfillsProjectHandleId, identifier: fulfillsProjectHandleId
}); });
} }
vcClaim.fulfills = vcClaim.fulfills.filter( vcClaim.fulfills = vcClaim.fulfills.filter(
(elem) => elem["@type"] !== "Offer", (elem: { '@type': string }) => elem["@type"] !== "Offer"
); );
if (fulfillsOfferHandleId) { if (fulfillsOfferHandleId) {
vcClaim.fulfills.push({ vcClaim.fulfills.push({
"@type": "Offer", "@type": "Offer",
identifier: fulfillsOfferHandleId, identifier: fulfillsOfferHandleId
}); });
} }
// do Trade/Donate last because current endorser.ch only looks at the first for plans & offers
vcClaim.fulfills = vcClaim.fulfills.filter( vcClaim.fulfills = vcClaim.fulfills.filter(
(elem) => (elem: { '@type': string }) =>
elem["@type"] !== "DonateAction" && elem["@type"] !== "TradeAction", elem["@type"] !== "DonateAction" && elem["@type"] !== "TradeAction"
); );
vcClaim.fulfills.push({ "@type": isTrade ? "TradeAction" : "DonateAction" });
vcClaim.fulfills.push({
"@type": isTrade ? "TradeAction" : "DonateAction"
});
vcClaim.image = imageUrl || undefined; vcClaim.image = imageUrl || undefined;
vcClaim.provider = providerPlanHandleId if (providerPlanHandleId) {
? { "@type": "PlanAction", identifier: providerPlanHandleId } vcClaim.provider = {
: undefined; "@type": "PlanAction",
identifier: providerPlanHandleId
};
}
return vcClaim; return vcClaim;
} }
@ -828,29 +854,38 @@ export function hydrateOffer(
validThrough?: string, validThrough?: string,
lastClaimId?: string, lastClaimId?: string,
): OfferVerifiableCredential { ): OfferVerifiableCredential {
// Remember: replace values or erase if it's null
const vcClaim: OfferVerifiableCredential = vcClaimOrig const vcClaim: OfferVerifiableCredential = vcClaimOrig
? R.clone(vcClaimOrig) ? R.clone(vcClaimOrig)
: { : {
"@context": SCHEMA_ORG_CONTEXT, "@context": SCHEMA_ORG_CONTEXT,
"@type": "Offer", "@type": "OfferAction",
object: undefined,
agent: undefined,
itemOffered: {}
}; };
if (lastClaimId) { if (lastClaimId) {
// this is an edit
vcClaim.lastClaimId = lastClaimId; vcClaim.lastClaimId = lastClaimId;
delete vcClaim.identifier; delete vcClaim.identifier;
} }
vcClaim.offeredBy = fromDid ? { identifier: fromDid } : undefined; if (fromDid) {
vcClaim.recipient = toDid ? { identifier: toDid } : undefined; vcClaim.agent = { identifier: fromDid };
}
if (toDid) {
vcClaim.recipient = { identifier: toDid };
}
vcClaim.description = conditionDescription || undefined; vcClaim.description = conditionDescription || undefined;
vcClaim.includesObject = if (amount && !isNaN(amount)) {
amount && !isNaN(amount) const quantitativeValue: QuantitativeValue = {
? { amountOfThisGood: amount, unitCode: unitCode || "HUR" } "@context": SCHEMA_ORG_CONTEXT,
: undefined; "@type": "QuantitativeValue",
amountOfThisGood: amount,
unitCode: unitCode || "HUR"
};
vcClaim.object = quantitativeValue;
}
if (itemDescription || fulfillsProjectHandleId) { if (itemDescription || fulfillsProjectHandleId) {
vcClaim.itemOffered = vcClaim.itemOffered || {}; vcClaim.itemOffered = vcClaim.itemOffered || {};
@ -858,10 +893,11 @@ export function hydrateOffer(
if (fulfillsProjectHandleId) { if (fulfillsProjectHandleId) {
vcClaim.itemOffered.isPartOf = { vcClaim.itemOffered.isPartOf = {
"@type": "PlanAction", "@type": "PlanAction",
identifier: fulfillsProjectHandleId, identifier: fulfillsProjectHandleId
}; };
} }
} }
vcClaim.validThrough = validThrough || undefined; vcClaim.validThrough = validThrough || undefined;
return vcClaim; return vcClaim;
@ -990,20 +1026,17 @@ export async function createAndSubmitClaim(
}, },
}); });
return { type: "success", response }; return { success: true, handleId: response.data?.handleId };
// eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: unknown) {
} catch (error: any) {
logger.error("Error submitting claim:", error); logger.error("Error submitting claim:", error);
const errorMessage: string = const errorMessage: string =
serverMessageForUser(error) || serverMessageForUser(error) ||
error.message || (error && typeof error === 'object' && 'message' in error ? String(error.message) : undefined) ||
"Got some error submitting the claim. Check your permissions, network, and error logs."; "Got some error submitting the claim. Check your permissions, network, and error logs.";
return { return {
type: "error", success: false,
error: { error: errorMessage
error: errorMessage,
},
}; };
} }
} }
@ -1033,10 +1066,10 @@ export async function generateEndorserJwtUrlForAccount(
} }
// Add the next key -- not recommended for the QR code for such a high resolution // Add the next key -- not recommended for the QR code for such a high resolution
if (isContact && account?.mnemonic && account?.derivationPath) { if (isContact) {
const newDerivPath = nextDerivationPath(account.derivationPath as string); const newDerivPath = nextDerivationPath(account.derivationPath);
const nextPublicHex = deriveAddress( const nextPublicHex = deriveAddress(
account.mnemonic as string, account.mnemonic,
newDerivPath, newDerivPath,
)[2]; )[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex"); const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
@ -1110,13 +1143,12 @@ const claimSummary = (
claim: GenericVerifiableCredential | GenericCredWrapper<GenericVerifiableCredential>, claim: GenericVerifiableCredential | GenericCredWrapper<GenericVerifiableCredential>,
) => { ) => {
if (!claim) { if (!claim) {
// to differentiate from "something" above
return "something"; return "something";
} }
let specificClaim: GenericVerifiableCredential; let specificClaim: GenericVerifiableCredential;
if ('claim' in claim) { if ('claim' in claim) {
// It's a GenericCredWrapper // It's a GenericCredWrapper
specificClaim = claim.claim; specificClaim = claim.claim as GenericVerifiableCredential;
} else { } else {
// It's already a GenericVerifiableCredential // It's already a GenericVerifiableCredential
specificClaim = claim; specificClaim = claim;
@ -1156,92 +1188,77 @@ export const claimSpecialDescription = (
) => { ) => {
let claim = record.claim; let claim = record.claim;
if ('claim' in claim) { if ('claim' in claim) {
// it's a nested GenericCredWrapper claim = claim.claim as GenericVerifiableCredential;
claim = claim.claim;
} }
const issuer = didInfo(record.issuer, activeDid, identifiers, contacts); const issuer = didInfo(record.issuer, activeDid, identifiers, contacts);
const type = claim["@type"] || "UnknownType"; const claimObj = claim as ClaimObject;
const type = claimObj["@type"] || "UnknownType";
if (type === "AgreeAction") { if (type === "AgreeAction") {
return issuer + " agreed with " + claimSummary(claim.object as GenericVerifiableCredential); return issuer + " agreed with " + claimSummary(claimObj.object as GenericVerifiableCredential);
} else if (isAccept(claim)) { } else if (isAccept(claim)) {
return issuer + " accepted " + claimSummary(claim.object as GenericVerifiableCredential); return issuer + " accepted " + claimSummary(claimObj.object as GenericVerifiableCredential);
} else if (type === "GiveAction") { } else if (type === "GiveAction") {
// agent.did is for legacy data, before March 2023 const giveClaim = claim as GiveVerifiableCredential;
const giver = claim.agent?.identifier || claim.agent?.did; const agent: Agent = giveClaim.agent || { identifier: undefined, did: undefined };
const giverInfo = didInfo(giver, activeDid, identifiers, contacts); const agentDid = agent.did || agent.identifier;
let gaveAmount = claim.object?.amountOfThisGood const contactInfo = agentDid
? displayAmount(claim.object.unitCode, claim.object.amountOfThisGood) ? didInfo(agentDid, activeDid, identifiers, contacts)
: "someone";
const offering = giveClaim.object
? " " + claimSummary(giveClaim.object)
: ""; : "";
if (claim.description) { const recipient = giveClaim.participant?.identifier;
if (gaveAmount) { const recipientInfo = recipient
gaveAmount = gaveAmount + ", and also: "; ? " to " + didInfo(recipient, activeDid, identifiers, contacts)
}
gaveAmount = gaveAmount + claim.description;
}
if (!gaveAmount) {
gaveAmount = "something not described";
}
// recipient.did is for legacy data, before March 2023
const gaveRecipientId = claim.recipient?.identifier || claim.recipient?.did;
const gaveRecipientInfo = gaveRecipientId
? " to " + didInfo(gaveRecipientId, activeDid, identifiers, contacts)
: ""; : "";
return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount; return contactInfo + " gave" + offering + recipientInfo;
} else if (type === "JoinAction") { } else if (type === "JoinAction") {
// agent.did is for legacy data, before March 2023 const joinClaim = claim as ClaimObject;
const agent = claim.agent?.identifier || claim.agent?.did; const agent: Agent = joinClaim.agent || { identifier: undefined, did: undefined };
const contactInfo = didInfo(agent, activeDid, identifiers, contacts); const agentDid = agent.did || agent.identifier;
const contactInfo = agentDid
let eventOrganizer = ? didInfo(agentDid, activeDid, identifiers, contacts)
claim.event && claim.event.organizer && claim.event.organizer.name; : "someone";
eventOrganizer = eventOrganizer || ""; const object = joinClaim.object as GenericVerifiableCredential;
let eventName = claim.event && claim.event.name; const objectInfo = object ? " " + claimSummary(object) : "";
eventName = eventName ? " " + eventName : ""; return contactInfo + " joined" + objectInfo;
let fullEvent = eventOrganizer + eventName;
fullEvent = fullEvent ? " attended the " + fullEvent : "";
let eventDate = claim.event && claim.event.startTime;
eventDate = eventDate ? " at " + eventDate : "";
return contactInfo + fullEvent + eventDate;
} else if (isOffer(claim)) { } else if (isOffer(claim)) {
const offerer = claim.offeredBy?.identifier; const offerClaim = claim as OfferVerifiableCredential;
const contactInfo = didInfo(offerer, activeDid, identifiers, contacts); const agent: Agent = offerClaim.agent || { identifier: undefined, did: undefined };
let offering = ""; const agentDid = agent.did || agent.identifier;
if (claim.includesObject) { const contactInfo = agentDid
offering += ? didInfo(agentDid, activeDid, identifiers, contacts)
" " + : "someone";
displayAmount( const offering = offerClaim.object
claim.includesObject.unitCode, ? " " + claimSummary(offerClaim.object)
claim.includesObject.amountOfThisGood, : "";
); const offerRecipientId = offerClaim.participant?.identifier;
}
if (claim.itemOffered?.description) {
offering += ", saying: " + claim.itemOffered?.description;
}
// recipient.did is for legacy data, before March 2023
const offerRecipientId =
claim.recipient?.identifier || claim.recipient?.did;
const offerRecipientInfo = offerRecipientId const offerRecipientInfo = offerRecipientId
? " to " + didInfo(offerRecipientId, activeDid, identifiers, contacts) ? " to " + didInfo(offerRecipientId, activeDid, identifiers, contacts)
: ""; : "";
return contactInfo + " offered" + offering + offerRecipientInfo; return contactInfo + " offered" + offering + offerRecipientInfo;
} else if (type === "PlanAction") { } else if (type === "PlanAction") {
const claimer = claim.agent?.identifier || record.issuer; const planClaim = claim as ClaimObject;
const claimerInfo = didInfo(claimer, activeDid, identifiers, contacts); const agent: Agent = planClaim.agent || { identifier: undefined, did: undefined };
return claimerInfo + " announced a project: " + claim.name; const agentDid = agent.did || agent.identifier;
const contactInfo = agentDid
? didInfo(agentDid, activeDid, identifiers, contacts)
: "someone";
const object = planClaim.object as GenericVerifiableCredential;
const objectInfo = object ? " " + claimSummary(object) : "";
return contactInfo + " planned" + objectInfo;
} else if (type === "Tenure") { } else if (type === "Tenure") {
// party.did is for legacy data, before March 2023 const tenureClaim = claim as ClaimObject;
const claimer = claim.party?.identifier || claim.party?.did; const agent: Agent = tenureClaim.agent || { identifier: undefined, did: undefined };
const contactInfo = didInfo(claimer, activeDid, identifiers, contacts); const agentDid = agent.did || agent.identifier;
const polygon = claim.spatialUnit?.geo?.polygon || ""; const contactInfo = agentDid
return ( ? didInfo(agentDid, activeDid, identifiers, contacts)
contactInfo + : "someone";
" possesses [" + const object = tenureClaim.object as GenericVerifiableCredential;
polygon.substring(0, polygon.indexOf(" ")) + const objectInfo = object ? " " + claimSummary(object) : "";
"...]" return contactInfo + " has tenure" + objectInfo;
);
} else { } else {
return ( return (
issuer + issuer +
@ -1289,9 +1306,7 @@ export async function createEndorserJwtVcFromClaim(
export async function createInviteJwt( export async function createInviteJwt(
activeDid: string, activeDid: string,
contact?: Contact, contact: Contact,
inviteId?: string,
expiresIn?: number,
): Promise<string> { ): Promise<string> {
const vcClaim: RegisterVerifiableCredential = { const vcClaim: RegisterVerifiableCredential = {
"@context": SCHEMA_ORG_CONTEXT, "@context": SCHEMA_ORG_CONTEXT,
@ -1302,19 +1317,19 @@ export async function createInviteJwt(
if (contact) { if (contact) {
vcClaim.participant = { identifier: contact.did }; vcClaim.participant = { identifier: contact.did };
} }
if (inviteId) {
vcClaim.identifier = inviteId;
}
// Make a payload for the claim // Make a payload for the claim
const vcPayload = { const vcPayload: { vc: VerifiableCredentialClaim } = {
vc: { vc: {
"@context": ["https://www.w3.org/2018/credentials/v1"], "@context": ["https://www.w3.org/2018/credentials/v1"],
"@type": "VerifiableCredential",
type: ["VerifiableCredential"], type: ["VerifiableCredential"],
credentialSubject: vcClaim, credentialSubject: vcClaim as unknown as ClaimObject, // Type assertion needed due to object being string
}, },
}; };
// Create a signature using private key of identity // Create a signature using private key of identity
const vcJwt = await createEndorserJwtForDid(activeDid, vcPayload, expiresIn); const vcJwt = await createEndorserJwtForDid(activeDid, vcPayload);
return vcJwt; return vcJwt;
} }
@ -1324,21 +1339,40 @@ export async function register(
axios: Axios, axios: Axios,
contact: Contact, contact: Contact,
): Promise<{ success?: boolean; error?: string }> { ): Promise<{ success?: boolean; error?: string }> {
const vcJwt = await createInviteJwt(activeDid, contact); try {
const vcJwt = await createInviteJwt(activeDid, contact);
const url = apiServer + "/api/v2/claim"; const url = apiServer + "/api/v2/claim";
const resp = await axios.post(url, { jwtEncoded: vcJwt }); const resp = await axios.post<{
if (resp.data?.success?.handleId) { success?: {
return { success: true }; handleId?: string;
} else if (resp.data?.success?.embeddedRecordError) { embeddedRecordError?: string;
let message = };
"There was some problem with the registration and so it may not be complete."; error?: string;
if (typeof resp.data.success.embeddedRecordError == "string") { message?: string;
message += " " + resp.data.success.embeddedRecordError; }>(url, { jwtEncoded: vcJwt });
if (resp.data?.success?.handleId) {
return { success: true };
} else if (resp.data?.success?.embeddedRecordError) {
let message = "There was some problem with the registration and so it may not be complete.";
if (typeof resp.data.success.embeddedRecordError === "string") {
message += " " + resp.data.success.embeddedRecordError;
}
return { error: message };
} else {
logger.error("Registration error:", JSON.stringify(resp.data));
return { error: "Got a server error when registering." };
}
} catch (error: unknown) {
if (error && typeof error === 'object') {
const err = error as AxiosErrorResponse;
const errorMessage = err.message ||
(err.response?.data && typeof err.response.data === 'object' && 'message' in err.response.data
? (err.response.data as { message: string }).message
: undefined);
logger.error("Registration error:", errorMessage || JSON.stringify(err));
return { error: errorMessage || "Got a server error when registering." };
} }
return { error: message };
} else {
logger.error("Registration error:", JSON.stringify(resp.data));
return { error: "Got a server error when registering." }; return { error: "Got a server error when registering." };
} }
} }

Loading…
Cancel
Save