forked from OpenNeo/impress
Matchu
e300b2d342
Looks like the version of Prettier I just installed is v3, whereas our last run in the impress-2020 repo was with v2. I don't think we had any special config in that project, I think these are just changes to Prettier's defaults, and I'm comfortable accepting them! (Mostly seems like a lot of trailing commas.)
237 lines
7.4 KiB
JavaScript
237 lines
7.4 KiB
JavaScript
import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
|
|
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
|
|
import { setContext } from "@apollo/client/link/context";
|
|
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
|
|
|
import { getAuthModeFeatureFlag } from "./components/useCurrentUser";
|
|
|
|
// Use Apollo's error messages in development.
|
|
if (process.env["NODE_ENV"] === "development") {
|
|
loadErrorMessages();
|
|
loadDevMessages();
|
|
}
|
|
|
|
// Teach Apollo to load certain fields from the cache, to avoid extra network
|
|
// requests. This happens a lot - e.g. reusing data from item search on the
|
|
// outfit immediately!
|
|
const typePolicies = {
|
|
Query: {
|
|
fields: {
|
|
closetList: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "ClosetList", id: args.id }, true);
|
|
},
|
|
items: (_, { args, toReference }) => {
|
|
return args.ids.map((id) =>
|
|
toReference({ __typename: "Item", id }, true),
|
|
);
|
|
},
|
|
item: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "Item", id: args.id }, true);
|
|
},
|
|
petAppearanceById: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "PetAppearance", id: args.id }, true);
|
|
},
|
|
species: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "Species", id: args.id }, true);
|
|
},
|
|
color: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "Color", id: args.id }, true);
|
|
},
|
|
outfit: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "Outfit", id: args.id }, true);
|
|
},
|
|
user: (_, { args, toReference }) => {
|
|
return toReference({ __typename: "User", id: args.id }, true);
|
|
},
|
|
},
|
|
},
|
|
|
|
Item: {
|
|
fields: {
|
|
appearanceOn: (appearance, { args, readField, toReference }) => {
|
|
// If we already have this exact appearance in the cache, serve it!
|
|
if (appearance) {
|
|
return appearance;
|
|
}
|
|
|
|
// Otherwise, we're going to see if this is a standard color, in which
|
|
// case we can reuse the standard color appearance if we already have
|
|
// it! This helps for fast loading when switching between standard
|
|
// colors.
|
|
const { speciesId, colorId } = args;
|
|
console.debug(
|
|
"[appearanceOn] seeking cached appearance",
|
|
speciesId,
|
|
colorId,
|
|
readField("id"),
|
|
);
|
|
const speciesStandardBodyId = readField(
|
|
"standardBodyId",
|
|
toReference({ __typename: "Species", id: speciesId }),
|
|
);
|
|
const colorIsStandard = readField(
|
|
"isStandard",
|
|
toReference({ __typename: "Color", id: colorId }),
|
|
);
|
|
if (speciesStandardBodyId == null || colorIsStandard == null) {
|
|
// We haven't loaded all the species/colors into cache yet. We might
|
|
// be loading them, depending on the page? Either way, return
|
|
// `undefined`, meaning we don't know how to serve this from cache.
|
|
// This will cause us to start loading it from the server.
|
|
console.debug("[appearanceOn] species/colors not loaded yet");
|
|
return undefined;
|
|
}
|
|
|
|
if (colorIsStandard) {
|
|
const itemId = readField("id");
|
|
console.debug(
|
|
"[appearanceOn] standard color, will read:",
|
|
`item-${itemId}-body-${speciesStandardBodyId}`,
|
|
);
|
|
return toReference({
|
|
__typename: "ItemAppearance",
|
|
id: `item-${itemId}-body-${speciesStandardBodyId}`,
|
|
});
|
|
} else {
|
|
console.debug("[appearanceOn] non-standard color, failure");
|
|
// This isn't a standard color, so we don't support special
|
|
// cross-color caching for it. Return `undefined`, meaning we don't
|
|
// know how to serve this from cache. This will cause us to start
|
|
// loading it from the server.
|
|
return undefined;
|
|
}
|
|
},
|
|
|
|
currentUserOwnsThis: (cachedValue, { readField }) => {
|
|
if (cachedValue != null) {
|
|
return cachedValue;
|
|
}
|
|
|
|
// Do we know what items this user owns? If so, scan for this item.
|
|
const currentUserRef = readField("currentUser", {
|
|
__ref: "ROOT_QUERY",
|
|
});
|
|
if (!currentUserRef) {
|
|
return undefined;
|
|
}
|
|
const thisItemId = readField("id");
|
|
const itemsTheyOwn = readField("itemsTheyOwn", currentUserRef);
|
|
if (!itemsTheyOwn) {
|
|
return undefined;
|
|
}
|
|
|
|
const theyOwnThisItem = itemsTheyOwn.some(
|
|
(itemRef) => readField("id", itemRef) === thisItemId,
|
|
);
|
|
return theyOwnThisItem;
|
|
},
|
|
currentUserWantsThis: (cachedValue, { readField }) => {
|
|
if (cachedValue != null) {
|
|
return cachedValue;
|
|
}
|
|
|
|
// Do we know what items this user owns? If so, scan for this item.
|
|
const currentUserRef = readField("currentUser", {
|
|
__ref: "ROOT_QUERY",
|
|
});
|
|
if (!currentUserRef) {
|
|
return undefined;
|
|
}
|
|
const thisItemId = readField("id");
|
|
const itemsTheyWant = readField("itemsTheyWant", currentUserRef);
|
|
if (!itemsTheyWant) {
|
|
return undefined;
|
|
}
|
|
|
|
const theyWantThisItem = itemsTheyWant.some(
|
|
(itemRef) => readField("id", itemRef) === thisItemId,
|
|
);
|
|
return theyWantThisItem;
|
|
},
|
|
},
|
|
},
|
|
|
|
ClosetList: {
|
|
fields: {
|
|
// When loading the updated contents of a list, replace it entirely.
|
|
items: { merge: false },
|
|
},
|
|
},
|
|
};
|
|
|
|
const httpLink = createHttpLink({
|
|
uri: "https://impress-2020.openneo.net/api/graphql",
|
|
});
|
|
const buildAuthLink = (getAuth0) =>
|
|
setContext(async (_, { headers = {}, sendAuth = false }) => {
|
|
if (!sendAuth) {
|
|
return;
|
|
}
|
|
|
|
const token = await getAccessToken(getAuth0);
|
|
if (token) {
|
|
return {
|
|
headers: {
|
|
...headers,
|
|
authorization: token ? `Bearer ${token}` : "",
|
|
},
|
|
};
|
|
}
|
|
});
|
|
|
|
// This is a temporary way to pass the DTIAuthMode feature flag back to the
|
|
// server!
|
|
const authModeLink = setContext((_, { headers = {} }) => {
|
|
const authMode = getAuthModeFeatureFlag();
|
|
return {
|
|
headers: {
|
|
...headers,
|
|
"DTI-Auth-Mode": authMode,
|
|
},
|
|
};
|
|
});
|
|
|
|
async function getAccessToken(getAuth0) {
|
|
// Wait for auth0 to stop loading, so we can maybe get a token!
|
|
// We'll do this hackily by checking every 100ms until it's true.
|
|
await new Promise((resolve) => {
|
|
function check() {
|
|
if (getAuth0().isLoading) {
|
|
setTimeout(check, 100);
|
|
} else {
|
|
resolve();
|
|
}
|
|
}
|
|
check();
|
|
});
|
|
|
|
const { isAuthenticated, getAccessTokenSilently } = getAuth0();
|
|
if (isAuthenticated) {
|
|
const token = await getAccessTokenSilently();
|
|
return token;
|
|
}
|
|
}
|
|
|
|
const buildLink = (getAuth0) =>
|
|
buildAuthLink(getAuth0)
|
|
.concat(authModeLink)
|
|
.concat(
|
|
createPersistedQueryLink({
|
|
useGETForHashedQueries: true,
|
|
}),
|
|
)
|
|
.concat(httpLink);
|
|
|
|
/**
|
|
* apolloClient is the global Apollo Client instance we use for GraphQL
|
|
* queries. This is how we communicate with the server!
|
|
*/
|
|
const buildClient = ({ getAuth0, initialCacheState }) => {
|
|
return new ApolloClient({
|
|
link: buildLink(getAuth0),
|
|
cache: new InMemoryCache({ typePolicies }).restore(initialCacheState),
|
|
connectToDevTools: true,
|
|
});
|
|
};
|
|
|
|
export default buildClient;
|