import { signProfileToken , extractProfile } from './profileTokens' ; import { getPersonFromLegacyFormat } from './profileSchemas' ; import { getName , getFamilyName , getGivenName , getAvatarUrl , getDescription , getVerifiedAccounts , getAddress , getBirthDate , getConnections , getOrganizations , } from './profileSchemas/personUtils' ; import { makeZoneFile , parseZoneFile } from 'zone-file' ; import * as inspector from 'schema-inspector' ; import { Logger , fetchPrivate } from '@stacks/common' ; const schemaDefinition = { type : 'object' , properties : { '@context' : { type : 'string' , optional : true } , '@type' : { type : 'string' } , } , } ; /**
 * Represents a user profile
 *
 */ export class Profile { _profile : { [ key ] : any ; } ; constructor ( profile = { } ) { this . _profile = Object . assign ( { } , { '@context' : 'http://schema.org/' , } , profile ) ; } toJSON ( ) { return Object . assign ( { } , this . _profile ) ; } toToken ( privateKey ) { return signProfileToken ( this . toJSON ( ) , privateKey ) ; } static validateSchema ( profile , strict = false ) { schemaDefinition . strict = strict ; return inspector . validate ( schemaDefinition , profile ) ; } static fromToken ( token , publicKeyOrAddress = null ) { const profile = extractProfile ( token , publicKeyOrAddress ) ; return new Profile ( profile ) ; } static makeZoneFile ( domainName , tokenFileURL ) { return makeProfileZoneFile ( domainName , tokenFileURL ) ; } } const personSchemaDefinition = { type : 'object' , strict : false , properties : { '@context' : { type : 'string' , optional : true } , '@type' : { type : 'string' } , '@id' : { type : 'string' , optional : true } , name : { type : 'string' , optional : true } , givenName : { type : 'string' , optional : true } , familyName : { type : 'string' , optional : true } , description : { type : 'string' , optional : true } , image : { type : 'array' , optional : true , items : { type : 'object' , properties : { '@type' : { type : 'string' } , name : { type : 'string' , optional : true } , contentUrl : { type : 'string' , optional : true } , } , } , } , website : { type : 'array' , optional : true , items : { type : 'object' , properties : { '@type' : { type : 'string' } , url : { type : 'string' , optional : true } , } , } , } , account : { type : 'array' , optional : true , items : { type : 'object' , properties : { '@type' : { type : 'string' } , service : { type : 'string' , optional : true } , identifier : { type : 'string' , optional : true } , proofType : { type : 'string' , optional : true } , proofUrl : { type : 'string' , optional : true } , proofMessage : { type : 'string' , optional : true } , proofSignature : { type : 'string' , optional : true } , } , } , } , worksFor : { type : 'array' , optional : true , items : { type : 'object' , properties : { '@type' : { type : 'string' } , '@id' : { type : 'string' , optional : true } , } , } , } , knows : { type : 'array' , optional : true , items : { type : 'object' , properties : { '@type' : { type : 'string' } , '@id' : { type : 'string' , optional : true } , } , } , } , address : { type : 'object' , optional : true , properties : { '@type' : { type : 'string' } , streetAddress : { type : 'string' , optional : true } , addressLocality : { type : 'string' , optional : true } , postalCode : { type : 'string' , optional : true } , addressCountry : { type : 'string' , optional : true } , } , } , birthDate : { type : 'string' , optional : true } , taxID : { type : 'string' , optional : true } , } , } ; ignore export class Person extends Profile { constructor ( profile = { } ) { super ( profile ) ; this . _profile = Object . assign ( { } , { '@type' : 'Person' , } , this . _profile ) ; } static validateSchema ( profile , strict = false ) { personSchemaDefinition . strict = strict ; return inspector . validate ( schemaDefinition , profile ) ; } static fromToken ( token , publicKeyOrAddress = null ) { const profile = extractProfile ( token , publicKeyOrAddress ) ; return new Person ( profile ) ; } static fromLegacyFormat ( legacyProfile ) { const profile = getPersonFromLegacyFormat ( legacyProfile ) ; return new Person ( profile ) ; } toJSON ( ) { return { profile : this . profile ( ) , name : this . name ( ) , givenName : this . givenName ( ) , familyName : this . familyName ( ) , description : this . description ( ) , avatarUrl : this . avatarUrl ( ) , verifiedAccounts : this . verifiedAccounts ( ) , address : this . address ( ) , birthDate : this . birthDate ( ) , connections : this . connections ( ) , organizations : this . organizations ( ) , } ; } profile ( ) { return Object . assign ( { } , this . _profile ) ; } name ( ) { return getName ( this . profile ( ) ) ; } givenName ( ) { return getGivenName ( this . profile ( ) ) ; } familyName ( ) { return getFamilyName ( this . profile ( ) ) ; } description ( ) { return getDescription ( this . profile ( ) ) ; } avatarUrl ( ) { return getAvatarUrl ( this . profile ( ) ) ; } verifiedAccounts ( verifications ? ) { return getVerifiedAccounts ( this . profile ( ) , verifications ) ; } address ( ) { return getAddress ( this . profile ( ) ) ; } birthDate ( ) { return getBirthDate ( this . profile ( ) ) ; } connections ( ) { return getConnections ( this . profile ( ) ) ; } organizations ( ) { return getOrganizations ( this . profile ( ) ) ; } } param origin param tokenFileUrl ignore export function makeProfileZoneFile ( origin , tokenFileUrl ) { if ( ! tokenFileUrl . includes ( '://' ) ) { throw new Error ( 'Invalid token file url' ) ; } const urlScheme = tokenFileUrl . split ( '://' ) [ 0 ] ; const urlParts = tokenFileUrl . split ( '://' ) [ 1 ] . split ( '/' ) ; const domain = urlParts [ 0 ] ; const pathname = `/${ urlParts . slice ( 1 ) . join ( '/' ) }` ; const zoneFile = { $origin : origin , $ttl : 3600 , uri : [ { name : '_http._tcp' , priority : 10 , weight : 1 , target : `${ urlScheme }://${ domain }${ pathname }` , } , ] , } ; const zoneFileTemplate = '{$origin}\n{$ttl}\n{uri}\n' ; return makeZoneFile ( zoneFile , zoneFileTemplate ) ; } param zoneFileJson ignore export function getTokenFileUrl ( zoneFileJson ) { if ( ! zoneFileJson . hasOwnProperty ( 'uri' ) ) { return null ; } if ( ! Array . isArray ( zoneFileJson . uri ) ) { return null ; } if ( zoneFileJson . uri . length < 1 ) { return null ; } const validRecords = zoneFileJson . uri . filter ( ( record ) => record . hasOwnProperty ( 'target' ) && record . name === '_http._tcp' ) ; if ( validRecords . length < 1 ) { return null ; } const firstValidRecord = validRecords [ 0 ] ; if ( ! firstValidRecord . hasOwnProperty ( 'target' ) ) { return null ; } let tokenFileUrl = firstValidRecord . target ; if ( tokenFileUrl . startsWith ( 'https' ) ) { } else if ( tokenFileUrl . startsWith ( 'http' ) ) { } else { tokenFileUrl = `https://${ tokenFileUrl }` ; } return tokenFileUrl ; } param zoneFile param publicKeyOrAddress ignore export function resolveZoneFileToProfile ( zoneFile , publicKeyOrAddress ) { return new Promise ( ( resolve , reject ) => { let zoneFileJson = null ; try { zoneFileJson = parseZoneFile ( zoneFile ) ; if ( ! zoneFileJson . hasOwnProperty ( '$origin' ) ) { zoneFileJson = null ; } } catch ( e ) { reject ( e ) ; } let tokenFileUrl = null ; if ( zoneFileJson && Object . keys ( zoneFileJson ) . length > 0 ) { tokenFileUrl = getTokenFileUrl ( zoneFileJson ) ; } else { let profile = null ; try { profile = JSON . parse ( zoneFile ) ; profile = Person . fromLegacyFormat ( profile ) . profile ( ) ; } catch ( error ) { reject ( error ) ; } resolve ( profile ) ; return ; } if ( tokenFileUrl ) { fetchPrivate ( tokenFileUrl ) . then ( response => response . text ( ) ) . then ( responseText => JSON . parse ( responseText ) ) . then ( responseJson => { const tokenRecords = responseJson ; const profile = extractProfile ( tokenRecords [ 0 ] . token , publicKeyOrAddress ) ; resolve ( profile ) ; } ) . catch ( error => { Logger . error ( `resolveZoneFileToProfile: error fetching token file ${ tokenFileUrl }: ${ error }` ) ; reject ( error ) ; } ) ; } else { Logger . debug ( 'Token file url not found. Resolving to blank profile.' ) ; resolve ( { } ) ; } } ) ; }