File size: 3,941 Bytes
c0af099
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { QueryCache, MutationCache, QueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import i18n from "#/i18n";
import { I18nKey } from "./i18n/declaration";
import { retrieveAxiosErrorMessage } from "./utils/retrieve-axios-error-message";
import { displayErrorToast } from "./utils/custom-toast-handlers";
import { getActiveBackend } from "#/api/backend-registry/active-store";
import { recordBackendSuccess } from "#/api/backend-registry/health-store";

const handle401Error = (error: AxiosError, client: QueryClient) => {
  if (error?.response?.status === 401 || error?.status === 401) {
    client.invalidateQueries({ queryKey: ["user", "authenticated"] });
  }
};

const isActiveCloudBackendAuthError = (error: unknown) => {
  if (!(error instanceof AxiosError)) return false;
  if (error.response?.status !== 401 && error.status !== 401) return false;

  const activeBackend = getActiveBackend().backend;
  if (activeBackend.kind !== "cloud") return false;

  const requestUrl = error.config?.url;
  return (
    !requestUrl || requestUrl.startsWith(activeBackend.host.replace(/\/+$/, ""))
  );
};

const shownErrors = new Set<string>();

export const createAgentServerQueryClient = () => {
  const client = new QueryClient({
    queryCache: new QueryCache({
      onSuccess: (_data, query) => {
        const backendId =
          query.meta?.backendId ?? query.options.meta?.backendId;
        if (typeof backendId === "string") {
          recordBackendSuccess(backendId);
        }
      },
      onError: (error, query) => {
        const isAuthQuery =
          query.queryKey[0] === "user" && query.queryKey[1] === "authenticated";
        if (!isAuthQuery) {
          handle401Error(error, client);
        }

        const disableToast =
          query.meta?.disableToast ?? query.options.meta?.disableToast;

        if (!disableToast && !isActiveCloudBackendAuthError(error)) {
          const errorMessage = retrieveAxiosErrorMessage(error);

          if (!shownErrors.has(errorMessage || "")) {
            displayErrorToast(errorMessage || i18n.t(I18nKey.ERROR$GENERIC));
            shownErrors.add(errorMessage || "");

            setTimeout(() => {
              shownErrors.delete(errorMessage || "");
            }, 3000);
          }
        }
      },
    }),
    mutationCache: new MutationCache({
      onError: (error, _, __, mutation) => {
        handle401Error(error, client);

        const disableToast =
          mutation?.meta?.disableToast ?? mutation?.options.meta?.disableToast;

        if (!disableToast && !isActiveCloudBackendAuthError(error)) {
          const message = retrieveAxiosErrorMessage(error);
          displayErrorToast(message || i18n.t(I18nKey.ERROR$GENERIC));
        }
      },
    }),
  });

  return client;
};

let defaultQueryClient: QueryClient | null = null;
let activeQueryClient: QueryClient | null = null;

export const getDefaultQueryClient = () => {
  if (!defaultQueryClient) {
    defaultQueryClient = createAgentServerQueryClient();
    if (import.meta.env.DEV || import.meta.env.VITE_MOCK_API === "true") {
      (
        window as unknown as { __OH_QUERY_CLIENT__?: typeof defaultQueryClient }
      ).__OH_QUERY_CLIENT__ = defaultQueryClient;
    }
  }

  return defaultQueryClient;
};

export const getQueryClient = () =>
  activeQueryClient ?? getDefaultQueryClient();

export const setQueryClient = (client?: QueryClient | null) => {
  activeQueryClient = client ?? getDefaultQueryClient();
  return activeQueryClient;
};

export const queryClient = new Proxy({} as QueryClient, {
  get: (_target, prop) => {
    const client = getQueryClient();
    const value = Reflect.get(client, prop, client);
    return typeof value === "function" ? value.bind(client) : value;
  },
  set: (_target, prop, value) => {
    const client = getQueryClient();
    return Reflect.set(client, prop, value, client);
  },
}) as QueryClient;