You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
6.2 KiB

import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt";
import { Axios, AxiosResponse } from "axios";
import { Contact } from "@/db/tables/contacts";
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
export const SERVICE_ID = "endorser.ch";
export interface AgreeVerifiableCredential {
"@context": string;
"@type": string;
// "any" because arbitrary objects can be subject of agreement
// eslint-disable-next-line @typescript-eslint/no-explicit-any
object: Record<any, any>;
}
export interface GiverInputInfo {
did?: string;
name?: string;
}
export interface ClaimResult {
success: { claimId: string; handleId: string };
error: { code: string; message: string };
}
export interface GenericClaim {
"@context": string;
"@type": string;
issuedAt: string;
// "any" because arbitrary objects can be subject of agreement
// eslint-disable-next-line @typescript-eslint/no-explicit-any
claim: Record<any, any>;
}
export interface GiveServerRecord {
agentDid: string;
amount: number;
amountConfirmed: number;
description: string;
fullClaim: GiveVerifiableCredential;
handleId: string;
issuedAt: string;
recipientDid: string;
unit: string;
}
export interface GiveVerifiableCredential {
"@context"?: string; // optional when embedded, eg. in an Agree
"@type": string;
agent?: { identifier: string };
description?: string;
fulfills?: { "@type": string; identifier: string };
identifier?: string;
object?: { amountOfThisGood: number; unitCode: string };
recipient: { identifier: string };
}
export interface RegisterVerifiableCredential {
"@context": string;
"@type": string;
agent: { identifier: string };
object: string;
recipient: { identifier: string };
}
export interface InternalError {
error: string; // for system logging
userMessage?: string; // for user display
}
// This is used to check for hidden info.
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
const HIDDEN_DID = "did:none:HIDDEN";
export function isHiddenDid(did) {
return did === HIDDEN_DID;
}
/**
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
**/
export function didInfo(did, activeDid, identifiers, contacts) {
const myId: IIdentifier | undefined = R.find(
(i) => i.did === did,
identifiers,
);
if (myId) {
return "You" + (myId.did !== activeDid ? " (Alt ID)" : "");
} else {
const contact: Contact | undefined = R.find((c) => c.did === did, contacts);
if (contact) {
return contact.name || "Someone Unnamed in Contacts";
} else if (!did) {
return "Unspecified Person";
} else if (isHiddenDid(did)) {
return "Someone Not In Network";
} else {
return "Someone Not In Contacts";
}
}
}
/**
* For result, see https://endorser.ch:3000/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 hours
* @param hours may be null; should have this or description
*/
export async function createAndSubmitGive(
axios: Axios,
apiServer: string,
identity: IIdentifier,
fromDid: string,
toDid: string,
description: string,
hours: number,
fulfillsProjectHandleId?: string,
): Promise<AxiosResponse<ClaimResult> | InternalError> {
// Make a claim
const vcClaim: GiveVerifiableCredential = {
"@context": "https://schema.org",
"@type": "GiveAction",
recipient: { identifier: toDid },
};
if (fromDid) {
vcClaim.agent = { identifier: fromDid };
}
if (description) {
vcClaim.description = description;
}
if (hours) {
vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" };
}
if (fulfillsProjectHandleId) {
vcClaim.fulfills = {
"@type": "PlanAction",
identifier: fulfillsProjectHandleId,
};
}
// 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) {
return new Promise<InternalError>((resolve, reject) => {
reject({
error: "No private key",
message:
"Your identifier " +
identity.did +
" is not configured correctly. Use a different identifier.",
});
});
}
// 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 url = apiServer + "/api/v2/claim";
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return axios.post(url, payload, { headers });
}
// from https://stackoverflow.com/a/175787/845494
//
export function isNumeric(str: string): boolean {
return !isNaN(+str);
}
export function numberOrZero(str: string): number {
return isNumeric(str) ? +str : 0;
}
export interface ErrorResponse {
error?: {
message?: string;
};
}
export interface RateLimits {
doneClaimsThisWeek: string;
doneRegistrationsThisMonth: string;
maxClaimsPerWeek: string;
maxRegistrationsPerMonth: string;
nextMonthBeginDateTime: string;
nextWeekBeginDateTime: string;
}
/**
* Represents data about a project
**/
export interface ProjectData {
/**
* Name of the project
**/
name: string;
/**
* Description of the project
**/
description: string;
/**
* URL referencing information about the project
**/
handleId: string;
/**
* The Identier of the project
**/
rowid: string;
}
export interface VerifiableCredential {
"@context": string;
"@type": string;
name: string;
description: string;
identifier?: string;
}
export interface WorldProperties {
startTime?: string;
endTime?: string;
}