link.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. import { ApolloLink, createHttpLink, from } from '@apollo/client/core'
  3. import { BatchHttpLink } from '@apollo/client/link/batch-http'
  4. import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename'
  5. import { getMainDefinition } from '@apollo/client/utilities'
  6. import ActionCableLink from 'graphql-ruby-client/subscriptions/ActionCableLink'
  7. import { consumer } from '#shared/server/action_cable/consumer.ts'
  8. import connectedStateLink from './link/connectedState.ts'
  9. import csrfLink from './link/csrf.ts'
  10. import debugLink from './link/debug.ts'
  11. import errorLink from './link/error.ts'
  12. import setAuthorizationLink from './link/setAuthorization.ts'
  13. import testFlagsLink from './link/testFlags.ts'
  14. import getBatchContext from './utils/getBatchContext.ts'
  15. import getWebsocketContext from './utils/getWebsocketContext.ts'
  16. import type { Operation } from '@apollo/client/core'
  17. import type { FragmentDefinitionNode, OperationDefinitionNode } from 'graphql'
  18. // Should subsequent HTTP calls be batched together?
  19. const enableBatchLink = true
  20. // Should queries and mutations be sent over ActionCable?
  21. const enableQueriesOverWebsocket = false
  22. const connectionSettings = {
  23. uri: '/graphql',
  24. credentials: 'same-origin', // Must have for CSRF validation via Rails.
  25. }
  26. const noBatchLink = createHttpLink(connectionSettings)
  27. const batchLink = new BatchHttpLink({
  28. ...connectionSettings,
  29. batchMax: 5,
  30. batchInterval: 20,
  31. })
  32. const operationIsLoginLogout = (
  33. definition: OperationDefinitionNode | FragmentDefinitionNode,
  34. ) => {
  35. return !!(
  36. definition.kind === 'OperationDefinition' &&
  37. definition.operation === 'mutation' &&
  38. definition.name?.value &&
  39. ['login', 'logout'].includes(definition.name?.value)
  40. )
  41. }
  42. const requiresBatchLink = (op: Operation) => {
  43. if (!enableBatchLink) return false
  44. const batchContext = getBatchContext(op)
  45. return batchContext.active
  46. }
  47. const httpLink = ApolloLink.split(requiresBatchLink, batchLink, noBatchLink)
  48. const requiresHttpLink = (op: Operation) => {
  49. const definition = getMainDefinition(op.query)
  50. const websocketContext = getWebsocketContext(op)
  51. if (!enableQueriesOverWebsocket) {
  52. // Only subscriptions over websocket.
  53. return (
  54. !(
  55. definition.kind === 'OperationDefinition' &&
  56. definition.operation === 'subscription'
  57. ) && !websocketContext.active
  58. )
  59. }
  60. // Everything over websocket except login/logout as that changes cookies.
  61. return operationIsLoginLogout(definition)
  62. }
  63. const actionCableLink = new ActionCableLink({ cable: consumer })
  64. const splitLink = ApolloLink.split(requiresHttpLink, httpLink, actionCableLink)
  65. const link = from([
  66. ...(VITE_TEST_MODE ? [testFlagsLink] : []),
  67. csrfLink,
  68. errorLink,
  69. setAuthorizationLink,
  70. debugLink,
  71. connectedStateLink,
  72. removeTypenameFromVariables(),
  73. splitLink,
  74. ])
  75. export default link