fetcher.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { Platform } from 'react-native';
  2. import i18n from 'i18next';
  3. import { appSetting, UNA_URL, APP_URL, APP_ORIGIN, MULTITENANT } from 'app/config';
  4. const USE_PROXY_WEB = appSetting('config', 'use_proxy_web');
  5. const USE_PROXY_NATIVE = appSetting('config', 'use_proxy_native');
  6. const FETCH_TIMEOUT_MS = appSetting('config', 'fetch_timeout_ms') || 15000;
  7. export async function fetcher (mixed, useProxy = false, fetchOptions = {}) {
  8. let prefix = UNA_URL;
  9. if ((Platform.OS === 'web' && USE_PROXY_WEB) || useProxy){
  10. const webBaseUrl = typeof window !== 'undefined' ? window.location.origin : APP_URL;
  11. prefix = MULTITENANT ? webBaseUrl : webBaseUrl + "/api";
  12. }
  13. if ((Platform.OS != 'web' && USE_PROXY_NATIVE)){
  14. prefix = APP_URL + "/api";
  15. }
  16. const r = await fetcherRaw(prefix, mixed, fetchOptions).then(async (r) => {
  17. let a;
  18. try {
  19. a = await r.json();
  20. } catch (error) {
  21. a = {};
  22. }
  23. return a;
  24. });
  25. return r;
  26. }
  27. async function fetcherRawOnce(url, { data, headers, callback, signal, timeoutMs }) {
  28. const controller = new AbortController();
  29. const timeout = timeoutMs ?? FETCH_TIMEOUT_MS;
  30. const onExternalAbort = () => controller.abort();
  31. if (signal?.aborted) {
  32. const err = new Error('Aborted');
  33. err.name = 'AbortError';
  34. err.aborted = true;
  35. throw err;
  36. }
  37. signal?.addEventListener('abort', onExternalAbort);
  38. const timer = setTimeout(() => controller.abort(), timeout);
  39. try {
  40. const r = await fetch(url, {
  41. method: data ? 'POST' : 'GET',
  42. body: data ? data : null,
  43. headers: headers,
  44. cache: 'no-store',
  45. credentials: 'include', // Set to true on UNA side - Access-Control-Allow-Credentials
  46. signal: controller.signal,
  47. });
  48. if (callback)
  49. callback(r);
  50. return r;
  51. } catch (error) {
  52. if (error?.name === 'AbortError') {
  53. if (signal?.aborted) {
  54. const err = new Error('Aborted');
  55. err.name = 'AbortError';
  56. err.aborted = true;
  57. throw err;
  58. }
  59. throw new Error('Timeout');
  60. }
  61. throw error;
  62. } finally {
  63. clearTimeout(timer);
  64. signal?.removeEventListener('abort', onExternalAbort);
  65. }
  66. }
  67. export async function fetcherRaw(host, mixed, fetchOptions = {}) {
  68. let path, token, data, origin, headers, callback;
  69. if (Array.isArray(mixed)){
  70. [path, token, data, origin, headers, callback] = mixed;
  71. }
  72. else {
  73. path = mixed;
  74. }
  75. if (undefined === headers)
  76. headers = {};
  77. // add token and origin headers when necessary
  78. if (token)
  79. headers['Authorization'] = 'Bearer ' + token;
  80. if (origin)
  81. headers['Origin'] = origin;
  82. else if ('web' !== Platform.OS)
  83. headers['Origin'] = APP_ORIGIN;
  84. // headers['Content-Type'] = "application/json";
  85. headers['Cache-Control'] = "no-cache";
  86. headers['Pragma'] = "no-cache";
  87. headers['Expires'] = "0";
  88. // perform fetch
  89. const lang = i18n.language;
  90. const url = host + path + "&lang=" + lang;
  91. const {
  92. signal,
  93. timeoutMs,
  94. maxAttempts: maxAttemptsOverride,
  95. silent = false,
  96. } = fetchOptions;
  97. const maxAttempts = maxAttemptsOverride ?? (data ? 1 : 3);
  98. const baseDelayMs = 300;
  99. let lastError = null;
  100. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  101. if (signal?.aborted) {
  102. const err = new Error('Aborted');
  103. err.name = 'AbortError';
  104. err.aborted = true;
  105. throw err;
  106. }
  107. try {
  108. return await fetcherRawOnce(url, { data, headers, callback, signal, timeoutMs });
  109. } catch (error) {
  110. lastError = error;
  111. if (error?.aborted) {
  112. throw error;
  113. }
  114. const isRetriable = error?.message === 'Timeout' || error?.name === 'TypeError';
  115. if (!isRetriable || attempt >= maxAttempts) {
  116. if (!silent) {
  117. if (error?.message === 'Timeout') {
  118. console.error('Api call timeout:', timeoutMs ?? FETCH_TIMEOUT_MS, url);
  119. } else {
  120. console.error('Api call error: ', error, url);
  121. }
  122. }
  123. throw error;
  124. }
  125. await new Promise((resolve) => setTimeout(resolve, baseDelayMs * attempt));
  126. }
  127. }
  128. throw lastError;
  129. }