@digitalcredentials/did-method-web
Version:
A did:web method resolver.
135 lines (127 loc) • 3.55 kB
JavaScript
/*!
* Copyright (c) 2023-2024 Digital Bazaar, Inc. All rights reserved.
*/
import {assertDidWebUrl, assertHttpsUrl} from './assertions.js';
import {
contextsBySuite,
didFile,
didPrefix,
fileSuffix
} from './constants.js';
import {klona} from 'klona';
/**
* Takes in an HTTPS (well-known) DID web url and returns a `did:web` DID URL.
*
* @param {URL|string} url - A url.
*
* @returns {string} A `did:web` URL.
*/
export function httpsUrlToDidUrl(url) {
assertHttpsUrl(url);
const {
host,
hash,
search,
pathname
} = new URL(url);
const base = `${didPrefix}${encodeURIComponent(host)}`;
// replace all / with :
const paths = _encodePaths(pathname);
// preserve the params and hash aka fragment
const did = base + paths;
const fullUrl = did + search + hash;
return {did, fullUrl};
}
export function didUrlToHttpsUrl(did) {
assertDidWebUrl(did);
const {
pathname,
hash,
searchParams,
search
} = new URL(did);
const ids = pathname.split(':');
// remove the first id web
ids.shift();
const [domain, ...paths] = ids;
const origin = `https://${decodeURIComponent(domain)}`;
const baseUrl = `${origin}/${_decodePaths(paths)}`;
const urlWithoutFragment = `${baseUrl}${search}`;
const fullUrl = `${urlWithoutFragment}${hash}`;
return {
origin, baseUrl, urlWithoutFragment, fullUrl,
searchParams, search, fragment: hash,
domain: decodeURIComponent(domain)
};
}
/**
* Returns the subnode of a DID document based on its `id`.
*
* @param {object} options - Options hashmap.
* @param {object} options.didDocument - The DID Document that might contain
* the subnode identified by `id`.
* @param {string} options.id - The identifier for the subnode.
*
* @returns {object} Returns the subnode, with `@context`.
*/
export function getNode({didDocument, id}) {
// do verification method search first
let match = didDocument?.verificationMethod?.find(vm => vm?.id === id);
if(!match) {
// check other top-level nodes
for(const [key, value] of Object.entries(didDocument)) {
if(key === '@context' || key === 'verificationMethod') {
continue;
}
if(Array.isArray(value)) {
match = value.find(e => e?.id === id);
} else if(value?.id === id) {
match = value;
}
if(match) {
break;
}
}
}
if(!match) {
throw new Error(`DID document entity with id "${id}" not found.`);
}
return {
'@context': klona(
contextsBySuite.get(match.type) ?? didDocument['@context']),
...klona(match)
};
}
/**
* Takes in the pathname from URL, splits the paths
* on "/", uri encodes them, & then rejoins them with ":".
*
* @param {string} paths - The paths to encode.
*
* @returns {string} The paths in did format.
*/
function _encodePaths(paths) {
// remove the fileSuffix, then possibly did.json
const basePaths = paths.replace(fileSuffix, '').replace(didFile, '');
if(basePaths === '/') {
return '';
}
// split on "/" & remove empty strings
const _paths = basePaths.split('/').filter(Boolean);
return ':' + _paths.map(encodeURIComponent).join(':');
}
/**
* If no paths just return well-known/did.json else return paths with
* did.json at the end.
*
* @private
* @param {Array<string>} paths - Potential url paths.
*
* @returns {string} The resulting paths for the url.
*/
function _decodePaths(paths) {
if(paths.length === 0) {
return fileSuffix;
}
return `${paths.map(decodeURIComponent).join('/')}/${didFile}`;
}