feature/IO-3497-Ant-Design-v5-to-v6 - Checkpoint (Apollo)

This commit is contained in:
Dave
2026-01-13 12:15:19 -05:00
parent f99f8ab7f8
commit 912d503ef8
263 changed files with 800 additions and 681 deletions

View File

@@ -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 querys 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",