Browse Source

[frontend] Add generic Axios get wrapper to api utils

Johan Ahlen 4 years ago
parent
commit
81962ee7a8
1 changed files with 103 additions and 51 deletions
  1. 103 51
      desktop/core/src/desktop/js/api/utils.ts

+ 103 - 51
desktop/core/src/desktop/js/api/utils.ts

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import axios, { AxiosError, AxiosTransformer } from 'axios';
+import axios, { AxiosError, AxiosResponse, AxiosTransformer, CancelToken } from 'axios';
 import qs from 'qs';
 import huePubSub from 'utils/huePubSub';
 
@@ -31,6 +31,22 @@ export interface DefaultApiResponse {
   traceback?: string;
 }
 
+export interface ApiFetchOptions<T, E = string> {
+  silenceErrors?: boolean;
+  ignoreSuccessErrors?: boolean;
+  transformResponse?: AxiosTransformer;
+  handleSuccess?: (
+    response: T & DefaultApiResponse,
+    resolve: (val: T) => void,
+    reject: (err: unknown) => void
+  ) => void;
+  handleError?: (
+    errorResponse: AxiosError<E>,
+    resolve: (val: T) => void,
+    reject: (err: unknown) => void
+  ) => void;
+}
+
 export const successResponseIsError = (responseData?: DefaultApiResponse): boolean => {
   return (
     typeof responseData !== 'undefined' &&
@@ -77,74 +93,79 @@ export const extractErrorMessage = (
   return UNKNOWN_ERROR_MESSAGE;
 };
 
+const notifyError = <T>(
+  message: string,
+  response: unknown,
+  options?: Pick<ApiFetchOptions<T>, 'silenceErrors'>
+): void => {
+  if (!options || !options.silenceErrors) {
+    hueUtils.logError(response);
+    if (message.indexOf('AuthorizationException') === -1) {
+      huePubSub.publish('hue.error', message);
+    }
+  }
+};
+
+const handleErrorResponse = <T>(
+  err: AxiosError<DefaultApiResponse>,
+  reject: (reason?: unknown) => void,
+  options?: Pick<ApiFetchOptions<T>, 'silenceErrors'>
+): void => {
+  const errorMessage = extractErrorMessage(err.response && err.response.data);
+  reject(errorMessage);
+  notifyError(errorMessage, (err && err.response) || err, options);
+};
+
+const handleResponse = <T>(
+  response: AxiosResponse<T & DefaultApiResponse>,
+  resolve: (value?: T) => void,
+  reject: (reason?: unknown) => void,
+  options?: ApiFetchOptions<T>
+): void => {
+  if (options && options.handleSuccess) {
+    options.handleSuccess(response.data, resolve, reason => {
+      reject(reason);
+      notifyError(String(reason), response.data);
+    });
+  } else if ((!options || !options.ignoreSuccessErrors) && successResponseIsError(response.data)) {
+    const errorMessage = extractErrorMessage(response && response.data);
+    reject(errorMessage);
+    notifyError(errorMessage, response);
+  } else {
+    resolve(response.data);
+  }
+};
+
+const getCancelToken = (): { cancelToken: CancelToken; cancel: () => void } => {
+  const cancelTokenSource = axios.CancelToken.source();
+  return { cancelToken: cancelTokenSource.token, cancel: cancelTokenSource.cancel };
+};
+
 export const post = <T, U = unknown, E = string>(
   url: string,
   data?: U,
-  options?: {
-    silenceErrors?: boolean;
-    ignoreSuccessErrors?: boolean;
-    transformResponse?: AxiosTransformer;
-    handleSuccess?: (
-      response: T & DefaultApiResponse,
-      resolve: (val: T) => void,
-      reject: (err: unknown) => void
-    ) => void;
-    handleError?: (
-      errorResponse: AxiosError<E>,
-      resolve: (val: T) => void,
-      reject: (err: unknown) => void
-    ) => void;
-  }
+  options?: ApiFetchOptions<T>
 ): CancellablePromise<T> =>
   new CancellablePromise((resolve, reject, onCancel) => {
-    const notifyError = (message: string, response: unknown): void => {
-      if (!options || !options.silenceErrors) {
-        hueUtils.logError(response);
-        if (message.indexOf('AuthorizationException') === -1) {
-          huePubSub.publish('hue.error', message);
-        }
-      }
-    };
-
-    const handleErrorResponse = (err: AxiosError<DefaultApiResponse>): void => {
-      const errorMessage = extractErrorMessage(err.response && err.response.data);
-      reject(errorMessage);
-      notifyError(errorMessage, (err && err.response) || err);
-    };
-
-    const cancelTokenSource = axios.CancelToken.source();
+    const { cancelToken, cancel } = getCancelToken();
     let completed = false;
 
     axios
       .post<T & DefaultApiResponse>(url, qs.stringify(data), {
-        cancelToken: cancelTokenSource.token,
+        cancelToken,
         transformResponse: options && options.transformResponse
       })
       .then(response => {
-        if (options && options.handleSuccess) {
-          options.handleSuccess(response.data, resolve, reason => {
-            reject(reason);
-            notifyError(String(reason), response.data);
-          });
-        } else if (
-          (!options || !options.ignoreSuccessErrors) &&
-          successResponseIsError(response.data)
-        ) {
-          const errorMessage = extractErrorMessage(response && response.data);
-          reject(errorMessage);
-          notifyError(errorMessage, response);
-        } else {
-          resolve(response.data);
-        }
+        handleResponse(response, resolve, reject, options);
       })
       .catch((err: AxiosError) => {
         if (options && options.handleError) {
           options.handleError(err, resolve, reason => {
-            handleErrorResponse(err);
+            handleErrorResponse(err, reject, options);
             notifyError(String(reason), err);
           });
         } else {
-          handleErrorResponse(err);
+          handleErrorResponse(err, reject, options);
         }
       })
       .finally(() => {
@@ -153,7 +174,38 @@ export const post = <T, U = unknown, E = string>(
 
     onCancel(() => {
       if (!completed) {
-        cancelTokenSource.cancel();
+        cancel();
+      }
+    });
+  });
+
+export const get = <T, U = unknown>(
+  url: string,
+  data: U,
+  options?: ApiFetchOptions<T>
+): CancellablePromise<T> =>
+  new CancellablePromise((resolve, reject, onCancel) => {
+    const { cancelToken, cancel } = getCancelToken();
+    let completed = false;
+
+    axios
+      .get<T & DefaultApiResponse>(url, {
+        cancelToken,
+        params: data
+      })
+      .then(response => {
+        handleResponse(response, resolve, reject, options);
+      })
+      .catch((err: AxiosError) => {
+        handleErrorResponse(err, reject, options);
+      })
+      .finally(() => {
+        completed = true;
+      });
+
+    onCancel(() => {
+      if (!completed) {
+        cancel();
       }
     });
   });