feature/IO-3497-Ant-Design-v5-to-v6 - Checkpoint (Apollo)
This commit is contained in:
@@ -1,131 +1,88 @@
|
||||
// file: client/src/utils/GraphQLClient.js
|
||||
import { ApolloClient, ApolloLink, InMemoryCache, split } from "@apollo/client";
|
||||
import { setContext } from "@apollo/client/link/context";
|
||||
import { HttpLink } from "@apollo/client/link/http"; //"apollo-link-http";
|
||||
import { HttpLink } from "@apollo/client/link/http";
|
||||
import { RetryLink } from "@apollo/client/link/retry";
|
||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
//import { split } from "apollo-link";
|
||||
|
||||
import apolloLogger from "apollo-link-logger";
|
||||
//import axios from "axios";
|
||||
import { SentryLink } from "apollo-link-sentry";
|
||||
import { createClient } from "graphql-ws";
|
||||
import { map } from "rxjs/operators";
|
||||
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import errorLink from "../graphql/apollo-error-handling";
|
||||
|
||||
//import { store } from "../redux/store";
|
||||
const httpLink = new HttpLink({
|
||||
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT
|
||||
});
|
||||
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT_WS,
|
||||
options: {
|
||||
const wsLink = new GraphQLWsLink(
|
||||
createClient({
|
||||
url: import.meta.env.VITE_APP_GRAPHQL_ENDPOINT_WS,
|
||||
lazy: true,
|
||||
reconnect: true,
|
||||
connectionParams: async () => {
|
||||
const token = auth.currentUser && (await auth.currentUser.getIdToken());
|
||||
if (token) {
|
||||
return {
|
||||
headers: {
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
headers: {
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const roundTripLink = new ApolloLink((operation, forward) => {
|
||||
// Called before operation is sent to server
|
||||
operation.setContext({ start: new Date() });
|
||||
|
||||
return forward(operation).map((data) => {
|
||||
// Called after server responds
|
||||
const time = new Date() - operation.getContext().start;
|
||||
// console.log(
|
||||
// `Operation ${operation.operationName} took ${time} to complete`
|
||||
// );
|
||||
TrackExecutionTime(operation.operationName, time);
|
||||
return data;
|
||||
});
|
||||
});
|
||||
|
||||
const TrackExecutionTime = async () => {
|
||||
// if (process.env.NODE_ENV === "development") return;
|
||||
// const rdxStore = store.getState();
|
||||
// try {
|
||||
// axios.post("/ioevent", {
|
||||
// operationName,
|
||||
// time,
|
||||
// dbevent: true,
|
||||
// user:
|
||||
// rdxStore.user &&
|
||||
// rdxStore.user.currentUser &&
|
||||
// rdxStore.user.currentUser.email,
|
||||
// imexshopid:
|
||||
// rdxStore.user &&
|
||||
// rdxStore.user.bodyshop &&
|
||||
// rdxStore.user.bodyshop.imexshopid,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// console.log("IOEvent Error", error);
|
||||
// }
|
||||
};
|
||||
|
||||
const subscriptionMiddleware = {
|
||||
applyMiddleware: async (options, next) => {
|
||||
options.authToken = auth.currentUser && (await auth.currentUser.getIdToken());
|
||||
next();
|
||||
}
|
||||
};
|
||||
wsLink.subscriptionClient.use([subscriptionMiddleware]);
|
||||
|
||||
const link = split(
|
||||
// split based on operation type
|
||||
// Split based on operation type (subscription -> ws, everything else -> http)
|
||||
const terminatingLink = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
// console.log(
|
||||
// "##Intercepted GQL Transaction : " +
|
||||
// definition.operation +
|
||||
// "|" +
|
||||
// definition.name.value +
|
||||
// "##",
|
||||
// query
|
||||
// );
|
||||
return definition.kind === "OperationDefinition" && definition.operation === "subscription";
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
return (
|
||||
auth.currentUser &&
|
||||
auth.currentUser
|
||||
.getIdToken()
|
||||
.then((token) => {
|
||||
if (token) {
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: token ? `Bearer ${token}` : ""
|
||||
}
|
||||
};
|
||||
} else {
|
||||
console.error("Authentication error. Unable to add authorization token because it was empty.");
|
||||
return { headers };
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Authentication error. Unable to add authorization token.", error.message);
|
||||
return { headers };
|
||||
})
|
||||
// function TrackExecutionTime(operationName, timeMs) {
|
||||
// keep your existing implementation/commented code here
|
||||
// This signature now matches the call site.
|
||||
// }
|
||||
|
||||
// Apollo Client 4 uses RxJS under the hood; use pipe(map(...)) instead of .map(...)
|
||||
const roundTripLink = new ApolloLink((operation, forward) => {
|
||||
operation.setContext({ start: Date.now() });
|
||||
|
||||
return forward(operation).pipe(
|
||||
map((result) => {
|
||||
// const start = operation.getContext().start;
|
||||
// const timeMs = Date.now() - start;
|
||||
// TrackExecutionTime(operation.operationName, timeMs);
|
||||
return result;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const authLink = setContext(async (_, { headers }) => {
|
||||
try {
|
||||
const token = auth.currentUser && (await auth.currentUser.getIdToken());
|
||||
if (!token) return { headers };
|
||||
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
authorization: `Bearer ${token}`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Authentication error. Unable to add authorization token.", error?.message || error);
|
||||
return { headers };
|
||||
}
|
||||
});
|
||||
|
||||
const retryLink = new RetryLink({
|
||||
delay: {
|
||||
initial: 500,
|
||||
max: 5,
|
||||
// Keeping your intent (a cap), but make it milliseconds (5ms is almost certainly not intended).
|
||||
max: 5000,
|
||||
jitter: true
|
||||
},
|
||||
attempts: {
|
||||
@@ -134,22 +91,21 @@ const retryLink = new RetryLink({
|
||||
}
|
||||
});
|
||||
|
||||
const middlewares = [];
|
||||
const links = [];
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
middlewares.push(apolloLogger);
|
||||
links.push(apolloLogger);
|
||||
}
|
||||
|
||||
middlewares.push(
|
||||
new SentryLink().concat(roundTripLink.concat(retryLink.concat(errorLink.concat(authLink.concat(link)))))
|
||||
);
|
||||
// Order: timing -> retry -> error -> auth -> network split
|
||||
links.push(roundTripLink, retryLink, errorLink, authLink, terminatingLink);
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
typePolicies: {
|
||||
Query: {
|
||||
fields: {
|
||||
// Note: This is required because we switch from a read to an unread state with a toggle,
|
||||
conversations: {
|
||||
keyArgs: ["where", "order_by"], // keep separate caches for archived/unarchived + sort
|
||||
keyArgs: ["where", "order_by"],
|
||||
merge(existing = [], incoming = [], { args, readField }) {
|
||||
const offset = args?.offset ?? 0;
|
||||
const merged = existing ? existing.slice(0) : [];
|
||||
@@ -158,7 +114,6 @@ const cache = new InMemoryCache({
|
||||
merged[offset + i] = incoming[i];
|
||||
}
|
||||
|
||||
// Deduplicate by id (important when you also upsert via sockets)
|
||||
const seen = new Set();
|
||||
return merged.filter((ref) => {
|
||||
const id = readField("id", ref);
|
||||
@@ -168,28 +123,21 @@ const cache = new InMemoryCache({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
notifications: {
|
||||
merge(existing = [], incoming = [], { readField }) {
|
||||
// Create a map to deduplicate by __ref
|
||||
const merged = new Map();
|
||||
|
||||
// Add existing items to retain cached data
|
||||
existing.forEach((item) => {
|
||||
const ref = readField("__ref", item);
|
||||
if (ref) {
|
||||
merged.set(ref, item);
|
||||
}
|
||||
if (ref) merged.set(ref, item);
|
||||
});
|
||||
|
||||
// Add incoming items, overwriting duplicates
|
||||
incoming.forEach((item) => {
|
||||
const ref = readField("__ref", item);
|
||||
if (ref) {
|
||||
merged.set(ref, item);
|
||||
}
|
||||
if (ref) merged.set(ref, item);
|
||||
});
|
||||
|
||||
// Return incoming to respect the current query’s filter (e.g., unread-only or all)
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
@@ -199,7 +147,7 @@ const cache = new InMemoryCache({
|
||||
});
|
||||
|
||||
const client = new ApolloClient({
|
||||
link: ApolloLink.from(middlewares),
|
||||
link: ApolloLink.from(links),
|
||||
cache,
|
||||
devtools: {
|
||||
name: "Imex Client",
|
||||
|
||||
Reference in New Issue
Block a user