// many of these are also found in endorser-mobile utility.ts
import axios , { AxiosResponse } from "axios" ;
import { useClipboard } from "@vueuse/core" ;
import { DEFAULT_PUSH_SERVER } from "@/constants/app" ;
import { accountsDB , db } from "@/db/index" ;
import { Account } from "@/db/tables/accounts" ;
import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES ,
MASTER_SETTINGS_KEY ,
} from "@/db/tables/settings" ;
import { deriveAddress , generateSeed , newIdentifier } from "@/libs/crypto" ;
import {
containsHiddenDid ,
GenericCredWrapper ,
GenericVerifiableCredential ,
OfferVerifiableCredential ,
} from "@/libs/endorserServer" ;
import * as serverUtil from "@/libs/endorserServer" ;
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer" ;
import { Buffer } from "buffer" ;
import { KeyMeta } from "@/libs/crypto/vc" ;
import { createPeerDid } from "@/libs/crypto/vc/didPeer" ;
export const PRIVACY_MESSAGE =
"The data you send will be visible to the world -- except: your IDs and the IDs of anyone you tag will stay private, only visible to them and others you explicitly allow." ;
/* eslint-disable prettier/prettier */
export const UNIT_SHORT : Record < string , string > = {
"BX" : "BX" ,
"BTC" : "BTC" ,
"ETH" : "ETH" ,
"HUR" : "Hours" ,
"USD" : "US $" ,
} ;
/* eslint-enable prettier/prettier */
/* eslint-disable prettier/prettier */
export const UNIT_LONG : Record < string , string > = {
"BX" : "Buxbe" ,
"BTC" : "Bitcoin" ,
"ETH" : "Ethereum" ,
"HUR" : "hours" ,
"USD" : "dollars" ,
} ;
/* eslint-enable prettier/prettier */
const UNIT_CODES : Record < string , Record < string , string > > = {
BTC : {
name : "Bitcoin" ,
faIcon : "bitcoin-sign" ,
} ,
HUR : {
name : "hours" ,
faIcon : "clock" ,
} ,
USD : {
name : "US Dollars" ,
faIcon : "dollar" ,
} ,
} ;
export function iconForUnitCode ( unitCode : string ) {
return UNIT_CODES [ unitCode ] ? . faIcon || "question" ;
}
// from https://stackoverflow.com/a/175787/845494
// ... though it appears even this isn't precisely right so keep doing "|| 0" or something in sensitive places
//
export function isNumeric ( str : string ) : boolean {
// This ignore commentary is because typescript complains when you pass a string to isNaN.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return ! isNaN ( str ) && ! isNaN ( parseFloat ( str ) ) ;
}
export function numberOrZero ( str : string ) : number {
return isNumeric ( str ) ? + str : 0 ;
}
export const isGlobalUri = ( uri : string ) = > {
return uri && uri . match ( new RegExp ( /^[A-Za-z][A-Za-z0-9+.-]+:/ ) ) ;
} ;
export const isGiveAction = (
veriClaim : GenericCredWrapper < GenericVerifiableCredential > ,
) = > {
return veriClaim . claimType === "GiveAction" ;
} ;
export const doCopyTwoSecRedo = ( text : string , fn : ( ) = > void ) = > {
fn ( ) ;
useClipboard ( )
. copy ( text )
. then ( ( ) = > setTimeout ( fn , 2000 ) ) ;
} ;
/ * *
* @returns true if the user can confirm the claim
* @param veriClaim is expected to have fields : claim , claimType , and issuer
* /
export const isGiveRecordTheUserCanConfirm = (
veriClaim : GenericCredWrapper < GenericVerifiableCredential > ,
activeDid : string ,
confirmerIdList : string [ ] = [ ] ,
) = > {
return (
isGiveAction ( veriClaim ) &&
! confirmerIdList . includes ( activeDid ) &&
veriClaim . issuer !== activeDid &&
! containsHiddenDid ( veriClaim . claim )
) ;
} ;
/ * *
* @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 < OfferVerifiableCredential > ,
) = > string | undefined = ( veriClaim ) = > {
let giver ;
if (
veriClaim . claim . offeredBy ? . identifier &&
! serverUtil . isHiddenDid ( veriClaim . claim . offeredBy . identifier as string )
) {
giver = veriClaim . claim . offeredBy . identifier ;
} else if ( veriClaim . issuer && ! serverUtil . isHiddenDid ( veriClaim . issuer ) ) {
giver = veriClaim . issuer ;
}
return giver ;
} ;
/ * *
* @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 < GenericVerifiableCredential > ,
) = > {
return ! ! (
veriClaim . claimType === "Offer" &&
offerGiverDid ( veriClaim as GenericCredWrapper < OfferVerifiableCredential > )
) ;
} ;
// return object with paths and arrays of DIDs for any keys ending in "VisibleToDid"
export function findAllVisibleToDids (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input : any ,
humanReadable = false ,
) : Record < string , Array < string > > {
if ( Array . isArray ( input ) ) {
const result : Record < string , Array < string > > = { } ;
for ( let i = 0 ; i < input . length ; i ++ ) {
const inside = findAllVisibleToDids ( input [ i ] , humanReadable ) ;
for ( const key in inside ) {
const pathKey = humanReadable
? "#" + ( i + 1 ) + " " + key
: "[" + i + "]" + key ;
result [ pathKey ] = inside [ key ] ;
}
}
return result ;
} else if ( input instanceof Object ) {
// regular map (non-array) object
const result : Record < string , Array < string > > = { } ;
for ( const key in input ) {
if ( key . endsWith ( "VisibleToDids" ) ) {
const newKey = key . slice ( 0 , - "VisibleToDids" . length ) ;
const pathKey = humanReadable ? newKey : "." + newKey ;
result [ pathKey ] = input [ key ] ;
} else {
const inside = findAllVisibleToDids ( input [ key ] , humanReadable ) ;
for ( const insideKey in inside ) {
const pathKey = humanReadable
? key + "'s " + insideKey
: "." + key + insideKey ;
result [ pathKey ] = inside [ insideKey ] ;
}
}
}
return result ;
} else {
return { } ;
}
}
/ * *
* Test findAllVisibleToDids
*
pkgx + deno . land sh
deno
import * as R from 'ramda' ;
//import { findAllVisibleToDids } from './src/libs/util'; // doesn't work because other dependencies fail so gotta copy-and-paste function
console . log ( R . equals ( findAllVisibleToDids ( null ) , { } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( 9 ) , { } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( [ ] ) , { } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( { } ) , { } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( { issuer : "abc" } ) , { } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( { issuerVisibleToDids : [ "abc" ] } ) , { ".issuer" : [ "abc" ] } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( [ { issuerVisibleToDids : [ "abc" ] } ] ) , { "[0].issuer" : [ "abc" ] } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( [ "xyz" , { fluff : { issuerVisibleToDids : [ "abc" ] } } ] ) , { "[1].fluff.issuer" : [ "abc" ] } ) ) ;
console . log ( R . equals ( findAllVisibleToDids ( [ "xyz" , { fluff : { issuerVisibleToDids : [ "abc" ] } , stuff : [ { did : "HIDDEN" , agentDidVisibleToDids : [ "def" , "ghi" ] } ] } ] ) , { "[1].fluff.issuer" : [ "abc" ] , "[1].stuff[0].agentDid" : [ "def" , "ghi" ] } ) ) ;
*
* * /
export interface AccountKeyInfo extends Account , KeyMeta { }
export const getAccount = async (
activeDid : string ,
) : Promise < AccountKeyInfo | undefined > = > {
await accountsDB . open ( ) ;
const account = ( await accountsDB . accounts
. where ( "did" )
. equals ( activeDid )
. first ( ) ) as Account ;
return account ;
} ;
/ * *
* Generates a new identity , saves it to the database , and sets it as the active identity .
* @return { Promise < string > } with the DID of the new identity
* /
export const generateSaveAndActivateIdentity = async ( ) : Promise < string > = > {
const mnemonic = generateSeed ( ) ;
// address is 0x... ETH address, without "did:eth:"
const [ address , privateHex , publicHex , derivationPath ] =
deriveAddress ( mnemonic ) ;
const newId = newIdentifier ( address , publicHex , privateHex , derivationPath ) ;
const identity = JSON . stringify ( newId ) ;
await accountsDB . open ( ) ;
await accountsDB . accounts . add ( {
dateCreated : new Date ( ) . toISOString ( ) ,
derivationPath : derivationPath ,
did : newId.did ,
identity : identity ,
mnemonic : mnemonic ,
publicKeyHex : newId.keys [ 0 ] . publicKeyHex ,
} ) ;
await db . settings . update ( MASTER_SETTINGS_KEY , {
activeDid : newId.did ,
} ) ;
return newId . did ;
} ;
export const registerAndSavePasskey = async (
keyName : string ,
) : Promise < Account > = > {
const cred = await registerCredential ( keyName ) ;
const publicKeyBytes = cred . publicKeyBytes ;
const did = createPeerDid ( publicKeyBytes as Uint8Array ) ;
const passkeyCredIdHex = cred . credIdHex as string ;
const account = {
dateCreated : new Date ( ) . toISOString ( ) ,
did ,
passkeyCredIdHex ,
publicKeyHex : Buffer.from ( publicKeyBytes ) . toString ( "hex" ) ,
} ;
await accountsDB . open ( ) ;
await accountsDB . accounts . add ( account ) ;
return account ;
} ;
export const registerSaveAndActivatePasskey = async (
keyName : string ,
) : Promise < Account > = > {
const account = await registerAndSavePasskey ( keyName ) ;
await db . open ( ) ;
await db . settings . update ( MASTER_SETTINGS_KEY , {
activeDid : account.did ,
} ) ;
return account ;
} ;
export const getPasskeyExpirationSeconds = async ( ) : Promise < number > = > {
await db . open ( ) ;
const settings = await db . settings . get ( MASTER_SETTINGS_KEY ) ;
const passkeyExpirationSeconds =
( settings ? . passkeyExpirationMinutes ? ? DEFAULT_PASSKEY_EXPIRATION_MINUTES ) *
60 ;
return passkeyExpirationSeconds ;
} ;
export const sendTestThroughPushServer = async (
subscriptionJSON : PushSubscriptionJSON ,
skipFilter : boolean ,
) : Promise < AxiosResponse > = > {
await db . open ( ) ;
const settings = await db . settings . get ( MASTER_SETTINGS_KEY ) ;
let pushUrl : string = DEFAULT_PUSH_SERVER as string ;
if ( settings ? . webPushServer ) {
pushUrl = settings . webPushServer ;
}
// This is a special value that tells the service worker to send a direct notification to the device, skipping filters.
// This is shared with the service worker and should be a constant. Look for the same name in additional-scripts.js
// Use something other than "Daily Update" https://gitea.anomalistdesign.com/trent_larson/py-push-server/src/commit/3c0e196c11bc98060ec5934e99e7dbd591b5da4d/app.py#L213
const DIRECT_PUSH_TITLE = "DIRECT_NOTIFICATION" ;
const newPayload = {
// eslint-disable-next-line prettier/prettier
message : ` Test, where you will see this message ${ skipFilter ? "un" : "" } filtered. ` ,
title : skipFilter ? DIRECT_PUSH_TITLE : "Your Web Push" ,
. . . subscriptionJSON ,
} ;
console . log ( "Sending a test web push message:" , newPayload ) ;
const payloadStr = JSON . stringify ( newPayload ) ;
const response = await axios . post (
pushUrl + "/web-push/send-test" ,
payloadStr ,
{
headers : {
"Content-Type" : "application/json" ,
} ,
} ,
) ;
console . log ( "Got response from web push server:" , response ) ;
return response ;
} ;