function parseURI(url: string, requireProto: boolean = false): LbryUrlObj {
  // Break into components. Empty sub-matches are converted to null

  const componentsRegex = new RegExp(
    regexPartProtocol + // protocol
      regexPartStreamOrChannelName + // stream or channel name (stops at the first separator or end)
      regexPartModifierSeparator + // modifier separator, modifier (stops at the first path separator or end)
      '(/?)' + // path separator, there should only be one (optional) slash to separate the stream and channel parts
      regexPartStreamOrChannelName +
      regexPartModifierSeparator
  );
  // chop off the querystring first
  let QSStrippedURL, qs;
  const qsRegexResult = separateQuerystring.exec(url);
  if (qsRegexResult) {
    [QSStrippedURL, qs] = qsRegexResult.slice(1).map((match) => match || null);
  }

  const cleanURL = QSStrippedURL || url;
  const regexMatch = componentsRegex.exec(cleanURL) || [];
  const [proto, ...rest] = regexMatch.slice(1).map((match) => match || null);
  const path = rest.join('');
  const [
    streamNameOrChannelName,
    primaryModSeparator,
    primaryModValue,
    pathSep, // eslint-disable-line no-unused-vars
    possibleStreamName,
    secondaryModSeparator,
    secondaryModValue,
  ] = rest;
  const searchParams = new URLSearchParams(qs || '');
  const startTime = searchParams.get('t');

  // Validate protocol
  if (requireProto && !proto) {
    throw new Error(__('LBRY URLs must include a protocol prefix (lbry://).'));
  }

  // Validate and process name
  if (!streamNameOrChannelName) {
    throw new Error(__('URL does not include name.'));
  }

  rest.forEach((urlPiece) => {
    if (urlPiece && urlPiece.includes(' ')) {
      throw new Error(__('URL can not include a space'));
    }
  });

  const includesChannel = streamNameOrChannelName.startsWith('@');
  const isChannel = streamNameOrChannelName.startsWith('@') && !possibleStreamName;
  const channelName = includesChannel && streamNameOrChannelName.slice(1);

  if (includesChannel) {
    if (!channelName) {
      throw new Error(__('No channel name after @.'));
    }

    if (channelName.length < channelNameMinLength) {
      throw new Error(
        __(`Channel names must be at least %channelNameMinLength% characters.`, {
          channelNameMinLength,
        })
      );
    }
  }

  // Validate and process modifier
  const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(
    primaryModSeparator,
    primaryModValue
  );
  const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(
    secondaryModSeparator,
    secondaryModValue
  );
  const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
  const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
  const channelClaimId = includesChannel && primaryClaimId;

  return {
    isChannel,
    path,
    ...(streamName ? { streamName } : {}),
    ...(streamClaimId ? { streamClaimId } : {}),
    ...(channelName ? { channelName } : {}),
    ...(channelClaimId ? { channelClaimId } : {}),
    ...(primaryClaimSequence ? { primaryClaimSequence: parseInt(primaryClaimSequence, 10) } : {}),
    ...(secondaryClaimSequence ? { secondaryClaimSequence: parseInt(secondaryClaimSequence, 10) } : {}),
    ...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
    ...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
    ...(startTime ? { startTime: parseInt(startTime, 10) } : {}),

    // The values below should not be used for new uses of parseURI
    // They will not work properly with canonical_urls
    claimName: streamNameOrChannelName,
    claimId: primaryClaimId,
    ...(streamName ? { contentName: streamName } : {}),
    ...(qs ? { queryString: qs } : {}),
  };
}
Parses a LBRY name into its component parts. Throws errors with user-friendly
messages for invalid names.

Returns a dictionary with keys:
  - path (string)
  - isChannel (boolean)
  - streamName (string, if present)
  - streamClaimId (string, if present)
  - channelName (string, if present)
  - channelClaimId (string, if present)
  - primaryClaimSequence (int, if present)
  - secondaryClaimSequence (int, if present)
  - primaryBidPosition (int, if present)
  - secondaryBidPosition (int, if present)