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.
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,15 +40,20 @@ import {
|
|||||||
import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc";
|
import { createEndorserJwtForKey, KeyMeta } from "../libs/crypto/vc";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
GenericCredWrapper,
|
||||||
|
GenericVerifiableCredential,
|
||||||
|
AxiosErrorResponse,
|
||||||
|
UserInfo,
|
||||||
|
CreateAndSubmitClaimResult,
|
||||||
|
PlanSummaryRecord,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
RegisterVerifiableCredential,
|
RegisterVerifiableCredential,
|
||||||
GenericVerifiableCredential,
|
ClaimObject,
|
||||||
GenericCredWrapper,
|
VerifiableCredentialClaim,
|
||||||
PlanSummaryRecord,
|
Agent,
|
||||||
UserInfo,
|
QuantitativeValue
|
||||||
CreateAndSubmitClaimResult,
|
} from "../interfaces/common";
|
||||||
} from "../interfaces";
|
|
||||||
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)) {
|
||||||
|
return input.some((item) => testRecursivelyOnStrings(item, test));
|
||||||
|
} else if (input && typeof input === "object") {
|
||||||
|
return Object.values(input as Record<string, unknown>).some((value) =>
|
||||||
|
testRecursivelyOnStrings(value, test)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Recursively test objects and arrays
|
return false;
|
||||||
else if (input instanceof Object) {
|
};
|
||||||
if (!Array.isArray(input)) {
|
|
||||||
// Handle plain objects
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 };
|
||||||
vcClaim.description = description || undefined;
|
|
||||||
vcClaim.object =
|
|
||||||
amount && !isNaN(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
|
if (toDid) {
|
||||||
// I realize the following doesn't change any elements that are not PlanAction or Offer or Trade/Action.
|
vcClaim.recipient = { identifier: toDid };
|
||||||
|
}
|
||||||
|
vcClaim.description = description || undefined;
|
||||||
|
|
||||||
|
if (amount && !isNaN(amount)) {
|
||||||
|
const quantitativeValue: QuantitativeValue = {
|
||||||
|
"@context": SCHEMA_ORG_CONTEXT,
|
||||||
|
"@type": "QuantitativeValue",
|
||||||
|
amountOfThisGood: amount,
|
||||||
|
unitCode: unitCode || "HUR"
|
||||||
|
};
|
||||||
|
vcClaim.object = quantitativeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize fulfills array if not present
|
||||||
|
if (!Array.isArray(vcClaim.fulfills)) {
|
||||||
|
vcClaim.fulfills = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user