link.ts 3.1 KB

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