Source: utils/vc/document-loader.js

import cachedUris from './contexts';
import Resolver from "../../resolver/generic/resolver"; // eslint-disable-line
import jsonFetch from '../json-fetch';

function parseEmbeddedDataURI(embedded) {
  // Strip new lines
  const dataUri = embedded.replace(/\r?\n/g, '');

  // split the URI up into the "metadata" and the "data" portions
  const firstComma = dataUri.indexOf(',');
  if (firstComma === -1) {
    throw new Error('Malformed data URI');
  }

  // Remove the scheme and parse metadata
  const meta = dataUri.substring(5, firstComma).split(';'); // 'data:'.length = 5
  if (meta[0] !== 'application/json') {
    throw new Error(`Expected media type application/json but was ${meta[0]}`);
  }

  const isBase64 = meta.indexOf('base64') !== -1;
  if (isBase64) {
    throw new Error('Base64 embedded JSON is not yet supported');
  }

  // Extract data string
  const dataStr = decodeURIComponent(dataUri.substring(firstComma + 1));
  return JSON.parse(dataStr);
}

/**
 * Takes a resolver and returns a function that returns a document or throws an error when the document
 * cannot be found.
 * @param {Resolver} [resolver] - The resolver is optional but should be passed when
 * `DID`s / `StatusList2021Credential`s / `Blob`s / revocation registries and other documents need to be resolved.
 * @returns {loadDocument} - the returned function
 */
function documentLoader(resolver = null) {
  /**
   * Resolve a URI. If the URI is a DID, then the resolver is used to resolve it.
   * Else, the hardcoded contexts are used to resolve the URI and if that fails
   * it will be fetched using an HTTP client
   * @param {string} uri
   * @returns {Promise<{documentUrl: string, contextUrl: string, document: *}>}
   */
  async function loadDocument(uri) {
    let document;
    const uriString = uri.toString();

    if (uriString.startsWith('data:')) {
      document = parseEmbeddedDataURI(uriString);
    } else if (resolver?.supports(uriString)) {
      // Try to resolve a DID and throw if cannot resolve
      document = await resolver.resolve(uriString);
    } else {
      // Strip ending slash from uri to determine cache key
      const cacheKey = uriString.endsWith('/')
        ? uriString.substring(0, uri.length - 1)
        : uriString;

      // Check its not in data cache
      const cachedData = cachedUris.get(cacheKey);
      if (cachedData) {
        document = cachedData;
      } else {
        const doc = await jsonFetch(uriString);
        cachedUris.set(cacheKey, doc);
        document = doc;
      }
    }

    return {
      contextUrl: null,
      documentUrl: uri,
      document,
    };
  }

  return loadDocument;
}

export default documentLoader;