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. let currentUserUpdateSubscription: SubscriptionHandler<
  82. CurrentUserUpdatesSubscription,
  83. CurrentUserUpdatesSubscriptionVariables
  84. >
  85. const getCurrentUser = async (): Promise<Maybe<UserData>> => {
  86. if (currentUserQuery && !user.value) {
  87. currentUserQuery.start()
  88. }
  89. const userQuery = getCurrentUserQuery()
  90. const { data: result } = await userQuery.query()
  91. user.value = cloneDeep(result?.currentUser) || null
  92. log.debug('currentUserUpdate', user.value)
  93. // Check if the locale is different, then a update is needed.
  94. const userLocale = user.value?.preferences?.locale as string | undefined
  95. if (
  96. userLocale &&
  97. (!locale.localeData || userLocale !== locale.localeData.locale)
  98. ) {
  99. await locale.setLocale(userLocale)
  100. }
  101. if (user.value) {
  102. if (!currentUserUpdateSubscription) {
  103. const scope = effectScope()
  104. scope.run(() => {
  105. currentUserUpdateSubscription = new SubscriptionHandler(
  106. useCurrentUserUpdatesSubscription(() => ({
  107. userId: (user.value as UserData)?.id,
  108. })),
  109. )
  110. currentUserUpdateSubscription.onResult((result) => {
  111. const updatedUser = result.data?.userUpdates.user
  112. if (!updatedUser) {
  113. testFlags.set('useCurrentUserUpdatesSubscription.subscribed')
  114. } else {
  115. user.value = updatedUser
  116. }
  117. })
  118. })
  119. } else {
  120. currentUserUpdateSubscription.start()
  121. }
  122. testFlags.set('useSessionUserStore.getCurrentUser.loaded')
  123. }
  124. return user.value
  125. }
  126. const resetCurrentSession = () => {
  127. if (currentUserUpdateSubscription) currentUserUpdateSubscription.stop()
  128. if (currentUserQuery) currentUserQuery.stop()
  129. id.value = null
  130. user.value = null
  131. }
  132. const userHasPermission = (
  133. requiredPermission: RequiredPermission,
  134. ): boolean => {
  135. return hasPermission(
  136. requiredPermission,
  137. user.value?.permissions?.names || [],
  138. )
  139. }
  140. // In case of unauthenticated users, current user ID may be an empty string.
  141. // Use with care.
  142. const userId = computed(() => user.value?.id || '')
  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. )