felicity-lims/webapp/urql.ts

163 lines
4.7 KiB
TypeScript
Raw Normal View History

2023-11-10 14:05:15 +08:00
import {
createClient,
dedupExchange,
cacheExchange,
fetchExchange,
errorExchange,
2023-11-22 21:52:18 +08:00
subscriptionExchange,
2023-11-10 14:05:15 +08:00
CombinedError,
Operation,
Exchange,
} from 'urql';
import { makeOperation } from '@urql/core';
import { authExchange } from '@urql/exchange-auth';
2023-11-22 21:52:18 +08:00
import { SubscriptionClient } from 'subscriptions-transport-ws';
2023-11-10 14:05:15 +08:00
import { pipe, tap } from 'wonka';
import { getAuthData, authLogout } from './auth';
2023-11-22 21:52:18 +08:00
import { GQL_BASE_URL, WS_BASE_URL } from './conf';
2023-11-10 14:05:15 +08:00
import { useNotifyToast } from './composables';
import jwtDecode from 'jwt-decode';
const { toastError } = useNotifyToast();
2023-11-22 21:52:18 +08:00
const subscriptionClient = new SubscriptionClient(WS_BASE_URL, {
reconnect: true,
lazy: true,
connectionParams: () => {
const authData = getAuthData();
return {
headers: {
...(authData?.auth?.token && {
'x-felicity-user-id': 'felicity-user-x',
'x-felicity-role': 'felicity-role-x',
Authorization: `Bearer ${authData?.auth?.token}`,
}),
},
};
},
});
2023-11-10 14:05:15 +08:00
const getAuth = async ({ authState }) => {
const authData = getAuthData();
if (!authState) {
if (authData?.auth?.token) {
return { token: authData?.auth?.token };
}
return null;
}
if (authState.token) {
return { token: authState.token };
}
toastError('Faied to get Auth Data. Login');
authLogout();
return null;
};
const addAuthToOperation = ({ authState, operation }) => {
if (!authState || !authState.token) {
return operation;
}
const fetchOptions =
typeof operation.context.fetchOptions === 'function' ? operation.context.fetchOptions() : operation.context.fetchOptions || {};
return makeOperation(operation?.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: `Bearer ${authState.token}`,
},
credentials: 'include',
},
});
};
const didAuthError = (error: any) => {
if (!error.graphQLErrors || error.graphQLErrors.length === 0) {
return error.message == '[Network] Failed to fetch';
}
return error.graphQLErrors.some((e: any) => e.extensions?.code === 'FORBIDDEN');
};
const willAuthError = (authState: any) => {
2023-11-24 06:22:22 +08:00
return false;
2023-11-10 14:05:15 +08:00
if (!authState) return true;
try {
const decodedToken: any = jwtDecode(authState.token);
const currentTime = new Date().getTime() / 1000;
if (decodedToken.exp < currentTime) {
// JWT is expired
return true;
}
} catch (error) {
return true;
}
return false;
};
const resultInterceptorExchange: Exchange =
({ forward }) =>
ops$ =>
pipe(
ops$,
forward,
tap(operationResult => {})
);
export const urqlClient = createClient({
url: GQL_BASE_URL,
exchanges: [
dedupExchange,
cacheExchange,
errorExchange({
onError: (error: CombinedError, operation: Operation) => {
let isAuthError = false;
if (!error.graphQLErrors || error.graphQLErrors.length === 0) {
isAuthError = error.message === '[Network] Failed to fetch';
} else {
isAuthError = error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
}
if (isAuthError) {
toastError('Unknown Network Error Encountered');
authLogout();
}
},
}),
authExchange({
addAuthToOperation,
willAuthError,
didAuthError,
getAuth,
}),
resultInterceptorExchange,
fetchExchange,
2023-11-22 21:52:18 +08:00
subscriptionExchange({
forwardSubscription: operation => subscriptionClient.request(operation) as any,
}),
2023-11-10 14:05:15 +08:00
],
fetchOptions: () => {
const authData = getAuthData();
return {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization',
...(authData?.auth?.token && {
Authorization: `Bearer ${authData?.auth?.token}`,
}),
},
};
},
});