Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion src/composables/__tests__/useActiveSession.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

import { flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { ref } from 'vue'
import { reactive, ref } from 'vue'
import { useStore } from 'vuex'
import { SESSION } from '../../constants.ts'

const mocks = vi.hoisted(() => ({
token: { current: null },
isInCall: { current: null },
joinedConversation: { current: null },
actorStore: { current: null },
}))

vi.mock('vuex', async () => {
Expand All @@ -28,6 +29,9 @@ vi.mock('../useIsInCall.js', () => ({
vi.mock('../useJoinedConversation.ts', () => ({
useJoinedConversation: () => mocks.joinedConversation.current,
}))
vi.mock('../../stores/actor.ts', () => ({
useActorStore: () => mocks.actorStore.current,
}))
vi.mock('../../services/participantsService.js', () => ({
setSessionState: vi.fn(),
}))
Expand Down Expand Up @@ -90,6 +94,7 @@ describe('useActiveSession', () => {
mocks.token.current = ref('XXTOKENXX')
mocks.isInCall.current = ref(false)
mocks.joinedConversation.current = ref(null)
mocks.actorStore.current = reactive({ sessionId: 'session-1' })
useStore.mockReturnValue({ dispatch: vi.fn() })
await mountActiveSession()
})
Expand Down Expand Up @@ -199,4 +204,69 @@ describe('useActiveSession', () => {
expect(setSessionState).toHaveBeenCalledWith('YYTOKENYY', SESSION.STATE.INACTIVE)
expect(useIsSessionActive().value).toBe(false)
})

describe('rejoining the conversation with a new session', () => {
beforeEach(async () => {
mocks.joinedConversation.current.value = 'XXTOKENXX'
await flushPromises()
})

test('marks the new session as inactive right away when it was inactive before', async () => {
blurWindow()
await runInactiveTimer()
setSessionState.mockClear()

mocks.actorStore.current.sessionId = 'session-2'
await flushPromises()

expect(setSessionState).toHaveBeenCalledExactlyOnceWith('XXTOKENXX', SESSION.STATE.INACTIVE)
expect(useIsSessionActive().value).toBe(false)
})

test('keeps counting down when the session was still active before', async () => {
blurWindow()

mocks.actorStore.current.sessionId = 'session-2'
await flushPromises()

expect(setSessionState).not.toHaveBeenCalled()
expect(useIsSessionActive().value).toBe(true)

await runInactiveTimer()

expect(setSessionState).toHaveBeenCalledExactlyOnceWith('XXTOKENXX', SESSION.STATE.INACTIVE)
})

test('follows the new session when the window is active again', async () => {
// console.error is set up to fail the test otherwise
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
blurWindow()
await runInactiveTimer()
// Marking as active failed, so the client still has the old session as inactive
setSessionState.mockImplementationOnce(() => Promise.reject(new Error('Network Error')))
focusWindow()
await flushPromises()
expect(useIsSessionActive().value).toBe(false)
setSessionState.mockClear()

mocks.actorStore.current.sessionId = 'session-2'
await flushPromises()

expect(setSessionState).not.toHaveBeenCalled()
expect(useIsSessionActive().value).toBe(true)
consoleError.mockRestore()
})

test('ignores a new session of a conversation that is not joined yet', async () => {
blurWindow()
await runInactiveTimer()
setSessionState.mockClear()

mocks.token.current.value = 'YYTOKENYY'
mocks.actorStore.current.sessionId = 'session-2'
await flushPromises()

expect(setSessionState).not.toHaveBeenCalled()
})
})
})
18 changes: 14 additions & 4 deletions src/composables/useActiveSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useStore } from 'vuex'
import { SESSION } from '../constants.ts'
import { hasTalkFeature } from '../services/CapabilitiesManager.ts'
import { setSessionState } from '../services/participantsService.js'
import { useActorStore } from '../stores/actor.ts'
import { useTokenStore } from '../stores/token.ts'
import { isAxiosErrorResponse } from '../types/guards.ts'
import { useDocumentVisibility } from './useDocumentVisibility.ts'
Expand Down Expand Up @@ -44,6 +45,7 @@ export function useActiveSession() {
const store = useStore()
const token = useGetToken()
const tokenStore = useTokenStore()
const actorStore = useActorStore()
// Without 'session-state' feature support - conversation is always considered active
const supportSessionState = computed(() => hasTalkFeature(token.value, 'session-state'))

Expand All @@ -68,14 +70,22 @@ export function useActiveSession() {
inactiveTimer = setTimeout(setSessionAsInactive, INACTIVE_TIME_MS)
}

// Wait for the token to actually be joined
watch(currentJoinedConversation, (joinedToken) => {
// Joined conversation has active state by default; a rejoin creates a new session, which is active on the server
watch([currentJoinedConversation, () => actorStore.sessionId], ([joinedToken], [previousJoinedToken]) => {
if (joinedToken !== token.value || !supportSessionState.value) {
return
}
// Joined conversation has active state by default
const isRejoin = joinedToken === previousJoinedToken
if (isRejoin && currentState.value !== SESSION.STATE.INACTIVE) {
return
}
currentState.value = SESSION.STATE.ACTIVE
if (!isWindowActive()) {
if (isWindowActive()) {
return
}
if (isRejoin) {
setSessionAsInactive()
} else {
scheduleSessionAsInactive()
}
})
Expand Down
Loading