session.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { computed, ref } from 'vue'
  3. import { defineStore } from 'pinia'
  4. import { cloneDeep } from 'lodash-es'
  5. import { useSessionLazyQuery } from '#shared/graphql/queries/session.api.ts'
  6. import { useCurrentUserLazyQuery } from '#shared/graphql/queries/currentUser.api.ts'
  7. import {
  8. QueryHandler,
  9. SubscriptionHandler,
  10. } from '#shared/server/apollo/handler/index.ts'
  11. import type { UserData } from '#shared/types/store.ts'
  12. import hasPermission from '#shared/utils/hasPermission.ts'
  13. import type { RequiredPermission } from '#shared/types/permission.ts'
  14. import { useCurrentUserUpdatesSubscription } from '#shared/graphql/subscriptions/currentUserUpdates.api.ts'
  15. import type {
  16. CurrentUserQuery,
  17. CurrentUserQueryVariables,
  18. CurrentUserUpdatesSubscription,
  19. CurrentUserUpdatesSubscriptionVariables,
  20. SessionAfterAuth,
  21. SessionQuery,
  22. SessionQueryVariables,
  23. } from '#shared/graphql/types.ts'
  24. import useFingerprint from '#shared/composables/useFingerprint.ts'
  25. import testFlags from '#shared/utils/testFlags.ts'
  26. import log from '#shared/utils/log.ts'
  27. import { useLocaleStore } from './locale.ts'
  28. let sessionQuery: QueryHandler<SessionQuery, SessionQueryVariables>
  29. const getSessionQuery = () => {
  30. if (sessionQuery) return sessionQuery
  31. const { fingerprint } = useFingerprint()
  32. sessionQuery = new QueryHandler(
  33. useSessionLazyQuery({
  34. fetchPolicy: 'network-only',
  35. context: {
  36. error: {
  37. logLevel: 'silent',
  38. },
  39. headers: {
  40. 'X-Browser-Fingerprint': fingerprint.value,
  41. },
  42. },
  43. }),
  44. {
  45. errorShowNotification: false,
  46. },
  47. )
  48. return sessionQuery
  49. }
  50. let currentUserQuery: QueryHandler<CurrentUserQuery, CurrentUserQueryVariables>
  51. const getCurrentUserQuery = () => {
  52. if (currentUserQuery) return currentUserQuery
  53. currentUserQuery = new QueryHandler(
  54. useCurrentUserLazyQuery({ fetchPolicy: 'network-only' }),
  55. )
  56. return currentUserQuery
  57. }
  58. export const useSessionStore = defineStore(
  59. 'session',
  60. () => {
  61. const id = ref<Maybe<string>>(null)
  62. const afterAuth = ref<Maybe<SessionAfterAuth>>(null)
  63. const initialized = ref(false)
  64. const locale = useLocaleStore()
  65. const checkSession = async (): Promise<string | null> => {
  66. const sessionQuery = getSessionQuery()
  67. const { data: result } = await sessionQuery.query()
  68. // Refresh the current sessionId state.
  69. id.value = result?.session.id || null
  70. afterAuth.value = result?.session.afterAuth || null
  71. return id.value
  72. }
  73. const user = ref<Maybe<UserData>>(null)
  74. let currentUserUpdateSubscription: SubscriptionHandler<
  75. CurrentUserUpdatesSubscription,
  76. CurrentUserUpdatesSubscriptionVariables
  77. >
  78. const getCurrentUser = async (): Promise<Maybe<UserData>> => {
  79. if (currentUserQuery && !user.value) {
  80. currentUserQuery.start()
  81. }
  82. const userQuery = getCurrentUserQuery()
  83. const { data: result } = await userQuery.query()
  84. user.value = cloneDeep(result?.currentUser) || null
  85. log.debug('currentUserUpdate', user.value)
  86. // Check if the locale is different, then a update is needed.
  87. const userLocale = user.value?.preferences?.locale as string | undefined
  88. if (
  89. userLocale &&
  90. (!locale.localeData || userLocale !== locale.localeData.locale)
  91. ) {
  92. await locale.setLocale(userLocale)
  93. }
  94. if (user.value) {
  95. if (!currentUserUpdateSubscription) {
  96. currentUserUpdateSubscription = new SubscriptionHandler(
  97. useCurrentUserUpdatesSubscription(() => ({
  98. userId: (user.value as UserData)?.id,
  99. })),
  100. )
  101. currentUserUpdateSubscription.onResult((result) => {
  102. const updatedUser = result.data?.userUpdates.user
  103. if (!updatedUser) {
  104. testFlags.set('useCurrentUserUpdatesSubscription.subscribed')
  105. } else {
  106. user.value = updatedUser
  107. }
  108. })
  109. } else {
  110. currentUserUpdateSubscription.start()
  111. }
  112. testFlags.set('useSessionUserStore.getCurrentUser.loaded')
  113. }
  114. return user.value
  115. }
  116. const resetCurrentSession = () => {
  117. if (currentUserUpdateSubscription) currentUserUpdateSubscription.stop()
  118. if (currentUserQuery) currentUserQuery.stop()
  119. id.value = null
  120. user.value = null
  121. }
  122. const userHasPermission = (
  123. requiredPermission: RequiredPermission,
  124. ): boolean => {
  125. return hasPermission(
  126. requiredPermission,
  127. user.value?.permissions?.names || [],
  128. )
  129. }
  130. // In case of unauthenticated users, current user ID may be an empty string.
  131. // Use with care.
  132. const userId = computed(() => user.value?.id || '')
  133. return {
  134. id,
  135. afterAuth,
  136. initialized,
  137. checkSession,
  138. user,
  139. userId,
  140. getCurrentUser,
  141. resetCurrentSession,
  142. hasPermission: userHasPermission,
  143. }
  144. },
  145. {
  146. requiresAuth: false,
  147. },
  148. )