import type { DIDResolutionResult , DIDResolutionOptions , DIDDocument , DIDDocumentMetadata , ParsedDID , Resolver , ResolverRegistry , VerificationMethod , } from 'did-resolver' ; import type { StreamState , MultiQuery , CeramicApi , LogEntry } from '@ceramicnetwork/common' ; import { TileDocument } from '@ceramicnetwork/stream-tile' ; import LegacyResolver from './legacyResolver' ; import * as u8a from 'uint8arrays' ; import { StreamID } from '@ceramicnetwork/streamid' ; import CID from 'cids' ; import { errorRepresentation , withResolutionError } from './error-representation' ; import { CommitType } from '@ceramicnetwork/common' ; const DID_LD_JSON = 'application/did+ld+json' ; const DID_JSON = 'application/did+json' ; const isLegacyDid = ( didId ) : boolean => { try { new CID ( didId ) ; return true ; } catch ( e ) { return false ; } } ; /**
 * Converts unix timestamp to ISO string without ms.
 */ const formatTime = ( timestamp ) : string => { return new Date ( timestamp * 1000 ) . toISOString ( ) . split ( '.' ) [ 0 ] + 'Z' ; } ; param content param did export function wrapDocument ( content , did ) { if ( ! ( content && content . publicKeys ) ) return null ; const startDoc = { id : did , verificationMethod : [ ] , authentication : [ ] , keyAgreement : [ ] , } ; return Object . entries ( content . publicKeys as string [ ] ) . reduce ( ( diddoc , [ keyName , keyValue ] ) => { const keyBuf = u8a . fromString ( keyValue . slice ( 1 ) , 'base58btc' ) ; const entry = { id : `${ did }#${ keyName }` , type : '' , controller : did , publicKeyBase58 : u8a . toString ( keyBuf . slice ( 2 ) , 'base58btc' ) , } ; if ( keyBuf [ 0 ] === 0xe7 ) { entry . type = 'EcdsaSecp256k1Signature2019' ; diddoc . verificationMethod . push ( entry ) ; diddoc . authentication . push ( entry ) ; } else if ( keyBuf [ 0 ] === 0xec ) { entry . type = 'X25519KeyAgreementKey2019' ; diddoc . verificationMethod . push ( entry ) ; diddoc . keyAgreement . push ( entry ) ; } return diddoc ; } , startDoc ) ; } /**
 * Return last anchor log entry, or genesis if no anchors found
 */ function lastAnchorOrGenesisEntry ( log ) { for ( let index = log . length - 1 ; index >= 0 ; index -- ) { const entry = log [ index ] ; if ( entry . type === CommitType . ANCHOR ) { return entry ; } } return log [ 0 ] ; } param requestedVersionState param latestVersionState function extractMetadata ( requestedVersionState , latestVersionState ) { const metadata = { } ; const { timestamp : updated , cid : versionId } = lastAnchorOrGenesisEntry ( requestedVersionState . log ) ; const { timestamp : nextUpdate , cid : nextVersionId } = latestVersionState . log . find ( ( { timestamp } ) => timestamp > updated || ( ! updated && timestamp ) ) || { } ; if ( updated ) { metadata . updated = formatTime ( updated ) ; } if ( nextUpdate ) { metadata . nextUpdate = formatTime ( nextUpdate ) ; } if ( versionId ) { metadata . versionId = requestedVersionState . log . length === 1 ? '0' : versionId ?. toString ( ) ; } if ( nextVersionId ) { metadata . nextVersionId = nextVersionId . toString ( ) ; } return metadata ; } interface VersionInfo { commit ? : string ; timestamp ? : number ; } param query function getVersionInfo ( query = '' ) { const versionId = query . split ( '&' ) . find ( ( e ) => e . includes ( 'versionId' ) || e . includes ( 'version-id' ) ) ; const versionTime = query . split ( '&' ) . find ( ( e ) => e . includes ( 'versionTime' ) ) ; return { commit : versionId ? versionId . split ( '=' ) [ 1 ] : undefined , timestamp : versionTime ? Math . floor ( new Date ( versionTime . split ( '=' ) [ 1 ] ) . getTime ( ) / 1000 ) : undefined , } ; } const legacyResolve = async ( ceramic , didId , verNfo ) : Promise < DIDResolutionResult > => { const legacyPublicKeys = await LegacyResolver ( didId ) ; const metadata = { controllers : [ legacyPublicKeys . keyDid ] , family : '3id' , deterministic : true } ; const streamid = ( await TileDocument . create ( ceramic , null , metadata , { anchor : false , publish : false } ) ) . id ; const didResult = await resolve ( ceramic , streamid . toString ( ) , verNfo , didId ) ; if ( didResult . didDocument === null ) { didResult . didDocument = wrapDocument ( legacyPublicKeys , `did:3:${ didId }` ) ; } return didResult ; } ; const resolve = async ( ceramic , didId , verNfo , v03ID ? ) : Promise < DIDResolutionResult > => { const streamId = StreamID . fromString ( didId ) ; let commitId ; const query = [ { streamId } ] ; if ( verNfo . commit ) { commitId = streamId . atCommit ( verNfo . commit ) ; query . push ( { streamId : commitId } ) ; } else if ( verNfo . timestamp ) { query . push ( { streamId , atTime : verNfo . timestamp , } ) ; } const resp = await ceramic . multiQuery ( query ) ; const latestVersionState = resp [ didId ] . state ; const commitIdStr = commitId ?. toString ( ) || Object . keys ( resp ) . find ( ( k ) => k !== didId ) ; const requestedVersionState = resp [ commitIdStr ] ?. state || latestVersionState ; const metadata = extractMetadata ( requestedVersionState , latestVersionState ) ; const tile = resp [ commitIdStr || didId ] as TileDocument | undefined ; if ( commitIdStr && ! tile ) { throw new Error ( `No resolution for commit ${ commitIdStr }` ) ; } const content = tile . state . content ; const document = wrapDocument ( content , `did:3:${ v03ID || didId }` ) ; return { didResolutionMetadata : { contentType : DID_JSON } , didDocument : document , didDocumentMetadata : metadata , } ; } ; export function getResolver ( ceramic ) { return { '3' : ( did , parsed , resolver , options ) : Promise < DIDResolutionResult > => { return withResolutionError ( async ( ) => { const contentType = options . accept || DID_JSON ; const verNfo = getVersionInfo ( parsed . query ) ; const id = parsed . id ; const didResult = ( ) => { if ( isLegacyDid ( id ) ) { return legacyResolve ( ceramic , id , verNfo ) ; } else { return resolve ( ceramic , id , verNfo ) ; } } ; switch ( contentType ) { case DID_JSON : return didResult ( ) ; case DID_LD_JSON : { const result = await didResult ( ) ; result . didDocument [ '@context' ] = 'https://www.w3.org/ns/did/v1' ; result . didResolutionMetadata . contentType = DID_LD_JSON ; return result ; } default : return errorRepresentation ( { error : 'representationNotSupported' } ) ; } } ) ; } , } ; } export default { getResolver } ;