|
1 | | -/** |
| 1 | +/* |
2 | 2 | * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
3 | 3 | * SPDX-License-Identifier: AGPL-3.0-or-later |
4 | 4 | */ |
5 | 5 |
|
| 6 | +import { createSharedComposable } from '@vueuse/core' |
6 | 7 | import { onBeforeUnmount, watch } from 'vue' |
7 | 8 | import { useAppIdle } from './composables/useAppIdle.ts' |
8 | 9 | import { useUserStatusStore } from './userStatus.store.ts' |
9 | 10 |
|
10 | | -/** How often to update the user status */ |
11 | | -const USER_STATUS_UPDATE_INTERVAL = 5 * 60 * 1000 // 5 minutes |
| 11 | +// General notes: |
| 12 | +// - Server has INVALIDATE_STATUS_THRESHOLD with 15 minutes, preventing immediate status update on heartbeat request |
| 13 | +// See: https://github.com/nextcloud/server/blob/v31.0.5/apps/user_status/lib/Service/StatusService.php |
| 14 | +// - However, "away" status has higher priority than "online" |
| 15 | +// See: https://github.com/nextcloud/server/blob/v31.0.5/apps/user_status/lib/Service/StatusService.php#L41-L48 |
| 16 | +// - Thus: |
| 17 | +// - Changing "Away -> Online" has a 15 minutes threshold |
| 18 | +// - Changing "Online -> Away" is immediate |
| 19 | +// - See: https://github.com/nextcloud/server/blob/master/apps/user_status/lib/Listener/UserLiveStatusListener.php#L75-L87 |
| 20 | +// - This might change in future to have symmetric behavior on heartbeat |
| 21 | + |
| 22 | +/** How often to send the heartbeat. Must be less than 15 min. */ |
| 23 | +const HEARTBEAT_INTERVAL = 5 * 60 * 1000 // 5 minutes |
12 | 24 |
|
13 | | -/** How long user is considered active */ |
14 | | -const USER_STATUS_ACTIVE_TIMEOUT = 2 * 60 * 1000 // 2 minutes |
| 25 | +/** How long user is considered active before going away */ |
| 26 | +const AWAY_THRESHOLD = 2 * 60 * 1000 // 2 minutes |
15 | 27 |
|
16 | 28 | /** |
17 | | - * Composable to update the user status in the background |
| 29 | + * Background heartbeat with user status update |
18 | 30 | */ |
19 | | -export function useUserStatusHeartbeat() { |
| 31 | +export const useHeartbeat = createSharedComposable(() => { |
20 | 32 | const userStatusStore = useUserStatusStore() |
21 | | - const isIdle = useAppIdle(USER_STATUS_ACTIVE_TIMEOUT) |
| 33 | + const isAway = useAppIdle(AWAY_THRESHOLD) |
| 34 | + |
| 35 | + let heartbeatTimeout: number | undefined |
22 | 36 |
|
23 | 37 | /** |
24 | 38 | * Send a heartbeat |
25 | 39 | */ |
26 | | - function heartbeat() { |
27 | | - userStatusStore.updateUserStatusWithHeartbeat(isIdle.value) |
| 40 | + async function heartbeat() { |
| 41 | + try { |
| 42 | + await userStatusStore.updateUserStatusWithHeartbeat(isAway.value) |
| 43 | + } catch (error) { |
| 44 | + console.error('Error on heartbeat:', error) |
| 45 | + } |
28 | 46 | } |
29 | 47 |
|
30 | | - let heartbeatInterval: number | undefined |
31 | | - const restartHeartbeat = () => { |
32 | | - if (heartbeatInterval) { |
33 | | - clearInterval(heartbeatInterval) |
| 48 | + /** |
| 49 | + * Start heartbeat interval |
| 50 | + */ |
| 51 | + async function restartHeartbeat() { |
| 52 | + if (heartbeatTimeout) { |
| 53 | + clearTimeout(heartbeatTimeout) |
34 | 54 | } |
35 | | - heartbeat() |
| 55 | + await heartbeat() |
36 | 56 | // TODO: fix when main and renderer process have separate tsconfig |
37 | | - heartbeatInterval = setInterval(heartbeat, USER_STATUS_UPDATE_INTERVAL) as unknown as number |
| 57 | + heartbeatTimeout = setTimeout(heartbeat, HEARTBEAT_INTERVAL) as unknown as number |
38 | 58 | } |
39 | 59 |
|
40 | | - watch(isIdle, () => { |
41 | | - if (!isIdle.value) { |
42 | | - restartHeartbeat() |
43 | | - } |
| 60 | + // Restart heartbeat to immediately notify server on state change |
| 61 | + watch(isAway, () => { |
| 62 | + // Note: both app and system level activity state changes to inactive with a threshold. |
| 63 | + // Only lock/unlock state can be changed many times in a short period, but it is unlikely |
| 64 | + // Thus it unlikely overloads with heartbeat, no need to debounce |
| 65 | + restartHeartbeat() |
44 | 66 | }, { immediate: true }) |
45 | 67 |
|
46 | 68 | onBeforeUnmount(() => { |
47 | | - clearInterval(heartbeatInterval) |
| 69 | + clearTimeout(heartbeatTimeout) |
48 | 70 | }) |
49 | | - |
50 | | - return { isIdle } |
51 | | -} |
| 71 | +}) |
0 commit comments