session.ts 4.9 KB

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