did-resolver
Version:
Resolve DID documents
127 lines • 4.28 kB
JavaScript
// Copyright 2018 Consensys AG
export function inMemoryCache() {
const cache = new Map();
return async (parsed, resolve) => {
if (parsed.params && parsed.params['no-cache'] === 'true')
return await resolve();
const cached = cache.get(parsed.didUrl);
if (cached !== undefined)
return cached;
const result = await resolve();
if (result.didResolutionMetadata?.error !== 'notFound') {
cache.set(parsed.didUrl, result);
}
return result;
};
}
export function noCache(parsed, resolve) {
return resolve();
}
const PCT_ENCODED = '(?:%[0-9a-fA-F]{2})';
const ID_CHAR = `(?:[a-zA-Z0-9._-]|${PCT_ENCODED})`;
const METHOD = '([a-z0-9]+)';
const METHOD_ID = `((?:${ID_CHAR}*:)*(${ID_CHAR}+))`;
const PARAM_CHAR = '[a-zA-Z0-9_.:%-]';
const PARAM = `;${PARAM_CHAR}+=${PARAM_CHAR}*`;
const PARAMS = `((${PARAM})*)`;
const PATH = `(/[^#?]*)?`;
const QUERY = `([?][^#]*)?`;
const FRAGMENT = `(#.*)?`;
const DID_MATCHER = new RegExp(`^did:${METHOD}:${METHOD_ID}${PARAMS}${PATH}${QUERY}${FRAGMENT}$`);
/**
* Parses a DID URL and builds a {@link ParsedDID | ParsedDID object}
*
* @param didUrl - the DID URL string to be parsed
* @returns a ParsedDID object, or null if the input is not a DID URL
*/
export function parse(didUrl) {
if (didUrl === '' || !didUrl)
return null;
const sections = didUrl.match(DID_MATCHER);
if (sections) {
const parts = {
did: `did:${sections[1]}:${sections[2]}`,
method: sections[1],
id: sections[2],
didUrl,
};
if (sections[4]) {
const params = sections[4].slice(1).split(';');
parts.params = {};
for (const p of params) {
const kv = p.split('=');
parts.params[kv[0]] = kv[1];
}
}
if (sections[6])
parts.path = sections[6];
if (sections[7])
parts.query = sections[7].slice(1);
if (sections[8])
parts.fragment = sections[8].slice(1);
return parts;
}
return null;
}
const EMPTY_RESULT = {
didResolutionMetadata: {},
didDocument: null,
didDocumentMetadata: {},
};
export function wrapLegacyResolver(resolve) {
return async (did, parsed, resolver) => {
try {
const doc = await resolve(did, parsed, resolver);
return {
...EMPTY_RESULT,
didResolutionMetadata: { contentType: 'application/did+ld+json' },
didDocument: doc,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (e) {
return {
...EMPTY_RESULT,
didResolutionMetadata: {
error: 'notFound',
message: e.toString(), // This is not in spec, but may be helpful
},
};
}
};
}
/**
* This implementation of {@link Resolvable} bundles together multiple implementations of {@link DIDResolver} and
* presents a single function call to users.
*/
export class Resolver {
constructor(registry = {}, options = {}) {
this.registry = registry;
this.cache = options.cache === true ? inMemoryCache() : options.cache || noCache;
if (options.legacyResolvers) {
Object.keys(options.legacyResolvers).map((methodName) => {
if (!this.registry[methodName]) {
this.registry[methodName] = wrapLegacyResolver(options.legacyResolvers[methodName]);
}
});
}
}
async resolve(didUrl, options = {}) {
const parsed = parse(didUrl);
if (parsed === null) {
return {
...EMPTY_RESULT,
didResolutionMetadata: { error: 'invalidDid' },
};
}
const resolver = this.registry[parsed.method];
if (!resolver) {
return {
...EMPTY_RESULT,
didResolutionMetadata: { error: 'unsupportedDidMethod' },
};
}
return this.cache(parsed, () => resolver(parsed.did, parsed, this, options));
}
}
//# sourceMappingURL=resolver.js.map