xref: /expo/packages/@expo/cli/src/utils/url.ts (revision 8c8eefe0)
18d307f52SEvan Baconimport dns from 'dns';
28d307f52SEvan Baconimport { URL } from 'url';
38d307f52SEvan Bacon
4*8c8eefe0SEvan Baconimport { fetchAsync } from '../api/rest/client';
5*8c8eefe0SEvan Bacon
68d307f52SEvan Bacon/** Check if a server is available based on the URL. */
78d307f52SEvan Baconexport function isUrlAvailableAsync(url: string): Promise<boolean> {
88d307f52SEvan Bacon  return new Promise<boolean>((resolve) => {
98d307f52SEvan Bacon    dns.lookup(url, (err) => {
108d307f52SEvan Bacon      resolve(!err);
118d307f52SEvan Bacon    });
128d307f52SEvan Bacon  });
138d307f52SEvan Bacon}
148d307f52SEvan Bacon
158d307f52SEvan Bacon/** Check if a request to the given URL is `ok` (status 200). */
168d307f52SEvan Baconexport async function isUrlOk(url: string): Promise<boolean> {
178d307f52SEvan Bacon  try {
18*8c8eefe0SEvan Bacon    const res = await fetchAsync(url);
198d307f52SEvan Bacon    return res.ok;
208d307f52SEvan Bacon  } catch {
218d307f52SEvan Bacon    return false;
228d307f52SEvan Bacon  }
238d307f52SEvan Bacon}
248d307f52SEvan Bacon
258d307f52SEvan Bacon/** Determine if a string is a valid URL, can optionally ensure certain protocols (like `https` or `exp`) are adhered to. */
268d307f52SEvan Baconexport function validateUrl(
278d307f52SEvan Bacon  urlString: string,
288d307f52SEvan Bacon  {
298d307f52SEvan Bacon    protocols,
308d307f52SEvan Bacon    requireProtocol,
318d307f52SEvan Bacon  }: {
328d307f52SEvan Bacon    /** Set of allowed protocols for the string to adhere to. @example ['exp', 'https'] */
338d307f52SEvan Bacon    protocols?: string[];
348d307f52SEvan Bacon    /** Ensure the URL has a protocol component (prefix before `://`). */
358d307f52SEvan Bacon    requireProtocol?: boolean;
368d307f52SEvan Bacon  } = {}
378d307f52SEvan Bacon) {
388d307f52SEvan Bacon  try {
398d307f52SEvan Bacon    const results = new URL(urlString);
408d307f52SEvan Bacon    if (!results.protocol && !requireProtocol) {
418d307f52SEvan Bacon      return true;
428d307f52SEvan Bacon    }
438d307f52SEvan Bacon    return protocols
448d307f52SEvan Bacon      ? results.protocol
458d307f52SEvan Bacon        ? protocols.map((x) => `${x.toLowerCase()}:`).includes(results.protocol)
468d307f52SEvan Bacon        : false
478d307f52SEvan Bacon      : true;
488d307f52SEvan Bacon  } catch {
498d307f52SEvan Bacon    return false;
508d307f52SEvan Bacon  }
518d307f52SEvan Bacon}
528d307f52SEvan Bacon
538d307f52SEvan Bacon/** Remove the port from a given `host` URL string. */
548d307f52SEvan Baconexport function stripPort(host?: string): string | null {
558d307f52SEvan Bacon  return coerceUrl(host)?.hostname ?? null;
568d307f52SEvan Bacon}
578d307f52SEvan Bacon
588d307f52SEvan Baconfunction coerceUrl(urlString?: string): URL | null {
598d307f52SEvan Bacon  if (!urlString) {
608d307f52SEvan Bacon    return null;
618d307f52SEvan Bacon  }
628d307f52SEvan Bacon  try {
638d307f52SEvan Bacon    return new URL('/', urlString);
6429975bfdSEvan Bacon  } catch (error: any) {
658d307f52SEvan Bacon    if (error.code !== 'ERR_INVALID_URL') {
668d307f52SEvan Bacon      throw error;
678d307f52SEvan Bacon    }
688d307f52SEvan Bacon    return new URL('/', `http://${urlString}`);
698d307f52SEvan Bacon  }
708d307f52SEvan Bacon}
718d307f52SEvan Bacon
728d307f52SEvan Bacon/** Strip a given extension from a URL string. */
738d307f52SEvan Baconexport function stripExtension(url: string, extension: string): string {
748d307f52SEvan Bacon  return url.replace(new RegExp(`.${extension}$`), '');
758d307f52SEvan Bacon}
76