1import { Platform } from 'expo-modules-core';
2import qs from 'qs';
3
4export type Headers = Record<string, string> & {
5  'Content-Type': string;
6  Authorization?: string;
7  Accept?: string;
8};
9
10export type FetchRequest = {
11  headers?: Headers;
12  body?: Record<string, string>;
13  dataType?: string;
14  method?: string;
15};
16
17// TODO(Bacon): pending react-native-adapter publish after sdk 38
18const isDOMAvailable =
19  Platform.OS === 'web' &&
20  typeof window !== 'undefined' &&
21  !!window.document?.createElement &&
22  typeof URL !== 'undefined';
23
24export async function requestAsync<T>(requestUrl: string, fetchRequest: FetchRequest): Promise<T> {
25  if (Platform.OS === 'web' && !isDOMAvailable) {
26    // @ts-ignore
27    return;
28  }
29  const url = new URL(requestUrl);
30
31  const request: Omit<RequestInit, 'headers'> & { headers: HeadersInit } = {
32    method: fetchRequest.method,
33    mode: 'cors',
34    headers: {},
35  };
36
37  const isJsonDataType = fetchRequest.dataType?.toLowerCase() === 'json';
38
39  if (fetchRequest.headers) {
40    for (const i in fetchRequest.headers) {
41      if (i in fetchRequest.headers) {
42        request.headers[i] = fetchRequest.headers[i] as string;
43      }
44    }
45  }
46
47  if (fetchRequest.body) {
48    if (fetchRequest.method?.toUpperCase() === 'POST') {
49      request.body = qs.stringify(fetchRequest.body);
50    } else {
51      for (const key of Object.keys(fetchRequest.body)) {
52        url.searchParams.append(key, fetchRequest.body[key]);
53      }
54    }
55  }
56
57  if (isJsonDataType && !('Accept' in request.headers)) {
58    // NOTE: Github authentication will return XML if this includes the standard `*/*`
59    request.headers['Accept'] = 'application/json, text/javascript; q=0.01';
60  }
61
62  // Fix a problem with React Native `URL` causing a trailing slash to be added.
63  const correctedUrl = url.toString().replace(/\/$/, '');
64
65  const response = await fetch(correctedUrl, request);
66
67  const contentType = response.headers.get('content-type');
68  if (isJsonDataType || contentType?.includes('application/json')) {
69    return response.json();
70  }
71  // @ts-ignore: Type 'string' is not assignable to type 'T'.
72  return response.text();
73}
74