session.ts 5.5 KB

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