mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
148 lines
4.8 KiB
TypeScript
148 lines
4.8 KiB
TypeScript
import { createClient as createSSEClient } from 'graphql-sse';
|
|
import Session from 'supertokens-auth-react/recipe/session';
|
|
import { createClient, fetchExchange, subscriptionExchange } from 'urql';
|
|
import { env } from '@/env/frontend';
|
|
import schema from '@/gql/schema';
|
|
import { authExchange } from '@urql/exchange-auth';
|
|
import { cacheExchange } from '@urql/exchange-graphcache';
|
|
import { persistedExchange } from '@urql/exchange-persisted';
|
|
import { Mutation } from './urql-cache';
|
|
import { networkStatusExchange } from './urql-exchanges/state';
|
|
|
|
const noKey = (): null => null;
|
|
|
|
const isSome = <T>(value: T | null | undefined): value is T => value != null;
|
|
|
|
const sseClient = createSSEClient({
|
|
url: env.graphqlPublicSubscriptionEndpoint,
|
|
credentials: 'include',
|
|
});
|
|
|
|
const usePersistedOperations = env.graphql.persistedOperations;
|
|
|
|
export const urqlClient = createClient({
|
|
url: env.graphqlPublicEndpoint,
|
|
fetchOptions: {
|
|
headers: {
|
|
'graphql-client-name': 'Hive App',
|
|
'graphql-client-version': env.release,
|
|
},
|
|
},
|
|
exchanges: [
|
|
cacheExchange({
|
|
schema,
|
|
updates: {
|
|
Mutation,
|
|
},
|
|
keys: {
|
|
// Member.id is not globally unique, it's really User.id
|
|
// In order to avoid conflicts or cache issues, let's use Member.temporaryFixId.
|
|
// This is a temporary solution until we have a better way to handle this.
|
|
Member: ({ temporaryFixId }) => `Member:${temporaryFixId}`,
|
|
RequestsOverTime: noKey,
|
|
FailuresOverTime: noKey,
|
|
DurationOverTime: noKey,
|
|
SchemaCoordinateStats: noKey,
|
|
ClientStats: noKey,
|
|
ClientStatsValues: noKey,
|
|
OperationsStats: noKey,
|
|
DurationValues: noKey,
|
|
OrganizationPayload: noKey,
|
|
SchemaCompareResult: noKey,
|
|
SchemaChange: noKey,
|
|
SchemaDiff: noKey,
|
|
GitHubIntegration: noKey,
|
|
GitHubRepository: noKey,
|
|
SchemaExplorer: noKey,
|
|
UnusedSchemaExplorer: noKey,
|
|
OrganizationGetStarted: noKey,
|
|
GraphQLObjectType: noKey,
|
|
GraphQLInterfaceType: noKey,
|
|
GraphQLUnionType: noKey,
|
|
GraphQLEnumType: noKey,
|
|
GraphQLInputObjectType: noKey,
|
|
GraphQLScalarType: noKey,
|
|
GraphQLField: noKey,
|
|
GraphQLInputField: noKey,
|
|
GraphQLArgument: noKey,
|
|
SchemaCoordinateUsage: noKey,
|
|
SuccessfulSchemaCheck: ({ id }) => `SchemaCheck:${id}`,
|
|
FailedSchemaCheck: ({ id }) => `SchemaCheck:${id}`,
|
|
},
|
|
globalIDs: ['SuccessfulSchemaCheck', 'FailedSchemaCheck'],
|
|
}),
|
|
networkStatusExchange,
|
|
authExchange(async () => {
|
|
let action: 'NEEDS_REFRESH' | 'VERIFY_EMAIL' | 'UNAUTHENTICATED' = 'UNAUTHENTICATED';
|
|
|
|
return {
|
|
addAuthToOperation(operation) {
|
|
return operation;
|
|
},
|
|
willAuthError() {
|
|
return false;
|
|
},
|
|
didAuthError(error) {
|
|
if (error.graphQLErrors.some(e => e.extensions?.code === 'UNAUTHENTICATED')) {
|
|
action = 'UNAUTHENTICATED';
|
|
return true;
|
|
}
|
|
|
|
if (error.graphQLErrors.some(e => e.extensions?.code === 'VERIFY_EMAIL')) {
|
|
action = 'VERIFY_EMAIL';
|
|
return true;
|
|
}
|
|
|
|
if (error.graphQLErrors.some(e => e.extensions?.code === 'NEEDS_REFRESH')) {
|
|
action = 'NEEDS_REFRESH';
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
async refreshAuth() {
|
|
if (action === 'NEEDS_REFRESH' && (await Session.attemptRefreshingSession())) {
|
|
location.reload();
|
|
} else if (action === 'VERIFY_EMAIL') {
|
|
window.location.href = '/auth/verify-email';
|
|
} else {
|
|
window.location.href = `/auth?redirectToPath=${encodeURIComponent(window.location.pathname)}`;
|
|
}
|
|
},
|
|
};
|
|
}),
|
|
usePersistedOperations
|
|
? persistedExchange({
|
|
enforcePersistedQueries: true,
|
|
enableForMutation: true,
|
|
enableForSubscriptions: true,
|
|
generateHash: (_, document) => {
|
|
// TODO: improve types here
|
|
return Promise.resolve((document as any)?.['__meta__']?.['hash'] ?? '');
|
|
},
|
|
})
|
|
: null,
|
|
fetchExchange,
|
|
subscriptionExchange({
|
|
forwardSubscription(operation) {
|
|
return {
|
|
subscribe: sink => {
|
|
const dispose = sseClient.subscribe(
|
|
{
|
|
// @ts-expect-error SSE client expects string, we pass undefined 😇
|
|
query: usePersistedOperations ? undefined : operation.query,
|
|
operationName: operation.operationName,
|
|
variables: operation.variables,
|
|
extensions: operation.extensions,
|
|
},
|
|
sink,
|
|
);
|
|
return {
|
|
unsubscribe: () => dispose(),
|
|
};
|
|
},
|
|
};
|
|
},
|
|
}),
|
|
].filter(isSome),
|
|
});
|