export function callApi()

in src/amo/api/index.js [89:230]


export function callApi({
  endpoint,
  params = {},
  auth = false,
  apiState = initialApiState,
  method = 'GET',
  body,
  credentials,
  errorHandler,
  _config = config,
  version = _config.get('apiVersion'),
  _log = log,
}: CallApiParams): Promise<Object> {
  if (!endpoint) {
    return Promise.reject(
      new Error(`endpoint URL cannot be falsy: "${endpoint}"`),
    );
  }
  if (errorHandler) {
    errorHandler.clear();
  }

  const apiPath = `${config.get('apiPath')}${version}`;

  const parsedUrl = url.parse(endpoint, true);
  let adjustedEndpoint = parsedUrl.pathname || '';
  if (!parsedUrl.host) {
    // If it's a relative URL, add the API prefix.
    const slash = !adjustedEndpoint.startsWith('/') ? '/' : '';
    adjustedEndpoint = `${apiPath}${slash}${adjustedEndpoint}`;
  } else if (!adjustedEndpoint.startsWith(apiPath)) {
    // If it's an absolute URL, it must have the correct prefix.
    return Promise.reject(
      new Error(`Absolute URL "${endpoint}" has an unexpected prefix.`),
    );
  }

  // Preserve the original query string if there is one.
  // This might happen when we parse `next` URLs returned by the API.
  const queryString = makeQueryString({
    ...parsedUrl.query,
    ...params,
    lang: apiState.lang,
  });

  const options = {
    headers: {},
    // Always make sure the method is upper case so that the browser won't
    // complain about CORS problems.
    method: method.toUpperCase(),
    credentials: undefined,
    body: undefined,
  };
  if (credentials) {
    options.credentials = 'include';
  }
  if (body) {
    if (body instanceof FormData) {
      options.body = body;
      // Let the browser sets this header, including the boundary value.
      // $FlowIgnore
      delete options.headers['Content-type'];
    } else {
      options.body = JSON.stringify(body);
      options.headers['Content-type'] = 'application/json';
    }
  }
  if (auth) {
    if (apiState.token) {
      options.headers.authorization = `Session ${apiState.token}`;
    }
  }
  if (apiState.regionCode) {
    options.headers[REGION_CODE_HEADER] = apiState.regionCode;
  }

  adjustedEndpoint = adjustedEndpoint.endsWith('/')
    ? adjustedEndpoint
    : `${adjustedEndpoint}/`;
  const apiURL = `${config.get('apiHost')}${adjustedEndpoint}${queryString}`;

  // Flow expects headers['Content-type'] to be a string, but we sometimes
  // delete it at line 148, above.
  // $FlowIgnore
  return fetch(apiURL, options)
    .then((response) => {
      // There isn't always a 'Content-Type' in headers, e.g., with a DELETE
      // method or 5xx responses.
      let contentType = response.headers.get('Content-Type');
      contentType = contentType && contentType.toLowerCase();

      // This is a bit paranoid, but we ensure the API returns a JSON response
      // (see https://github.com/mozilla/addons-frontend/issues/1701).
      // If not we'll store the text response in JSON and log an error.
      // If the JSON parsing fails; we log the error and return an "unknown
      // error".
      if (contentType === 'application/json') {
        return response
          .json()
          .then((jsonResponse) => ({ response, jsonResponse }));
      }

      return response.text().then((text) => {
        // eslint-disable-next-line amo/only-log-strings
        _log.warn(
          oneLine`Response from API was not JSON (was Content-Type:
          ${contentType}) %o`,
          {
            body: text ? text.substring(0, 100) : '[empty]',
            status: response.status || '[unknown]',
            url: response.url || '[unknown]',
          },
        );

        // jsonResponse should be an empty object in this case. Otherwise, its
        // keys could be treated as generic API errors.
        return { jsonResponse: {}, response };
      });
    })
    .then(
      ({ response, jsonResponse }) => {
        if (response.ok) {
          return jsonResponse;
        }

        // If response is not ok we'll throw an error.
        const apiError = createApiError({ apiURL, response, jsonResponse });
        if (errorHandler) {
          errorHandler.handle(apiError);
        }
        throw apiError;
      },
      (fetchError) => {
        // This actually handles the case when the call to fetch() is
        // rejected, say, for a network connection error, etc.
        if (errorHandler) {
          errorHandler.handle(fetchError);
        }
        throw fetchError;
      },
    );
}