session.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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, 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. sessionQuery = new QueryHandler(
  34. useSessionLazyQuery({
  35. fetchPolicy: 'network-only',
  36. context: {
  37. error: {
  38. logLevel: 'silent',
  39. },
  40. headers: {
  41. 'X-Browser-Fingerprint': fingerprint.value,
  42. },
  43. },
  44. }),
  45. {
  46. errorShowNotification: false,
  47. },
  48. )
  49. return sessionQuery
  50. }
  51. let currentUserQuery: QueryHandler<CurrentUserQuery, CurrentUserQueryVariables>
  52. const getCurrentUserQuery = () => {
  53. if (currentUserQuery) return currentUserQuery
  54. currentUserQuery = new QueryHandler(
  55. useCurrentUserLazyQuery({ fetchPolicy: 'network-only' }),
  56. )
  57. return currentUserQuery
  58. }
  59. export const useSessionStore = defineStore(
  60. 'session',
  61. () => {
  62. const id = ref<Maybe<string>>(null)
  63. const afterAuth = ref<Maybe<SessionAfterAuth>>(null)
  64. const initialized = ref(false)
  65. const locale = useLocaleStore()
  66. const checkSession = async (): Promise<string | null> => {
  67. const sessionQuery = getSessionQuery()
  68. const { data: result } = await sessionQuery.query()
  69. // Refresh the current sessionId state.
  70. id.value = result?.session.id || null
  71. afterAuth.value = result?.session.afterAuth || null
  72. return id.value
  73. }
  74. const user = ref<Maybe<UserData>>(null)
  75. let currentUserUpdateSubscription: SubscriptionHandler<
  76. CurrentUserUpdatesSubscription,
  77. CurrentUserUpdatesSubscriptionVariables
  78. >
  79. const getCurrentUser = async (): Promise<Maybe<UserData>> => {
  80. if (currentUserQuery && !user.value) {
  81. currentUserQuery.start()
  82. }
  83. const userQuery = getCurrentUserQuery()
  84. const { data: result } = await userQuery.query()
  85. user.value = cloneDeep(result?.currentUser) || null
  86. log.debug('currentUserUpdate', user.value)
  87. // Check if the locale is different, then a update is needed.
  88. const userLocale = user.value?.preferences?.locale as string | undefined
  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 updatedUser = result.data?.userUpdates.user
  104. if (!updatedUser) {
  105. testFlags.set('useCurrentUserUpdatesSubscription.subscribed')
  106. } else {
  107. user.value = updatedUser
  108. }
  109. })
  110. } else {
  111. currentUserUpdateSubscription.start()
  112. }
  113. testFlags.set('useSessionUserStore.getCurrentUser.loaded')
  114. }
  115. return user.value
  116. }
  117. const resetCurrentSession = () => {
  118. if (currentUserUpdateSubscription) currentUserUpdateSubscription.stop()
  119. if (currentUserQuery) currentUserQuery.stop()
  120. id.value = null
  121. user.value = null
  122. }
  123. const userHasPermission = (
  124. requiredPermission: RequiredPermission,
  125. ): boolean => {
  126. return hasPermission(
  127. requiredPermission,
  128. user.value?.permissions?.names || [],
  129. )
  130. }
  131. // In case of unauthenticated users, current user ID may be an empty string.
  132. // Use with care.
  133. const userId = computed(() => user.value?.id || '')
  134. const setUserPreference = (key: string, value: JsonValue) => {
  135. if (!user.value) return
  136. user.value.preferences[key] = value
  137. return user.value
  138. }
  139. return {
  140. id,
  141. afterAuth,
  142. initialized,
  143. checkSession,
  144. user,
  145. userId,
  146. getCurrentUser,
  147. resetCurrentSession,
  148. hasPermission: userHasPermission,
  149. setUserPreference,
  150. }
  151. },
  152. {
  153. requiresAuth: false,
  154. },
  155. )