1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { emit } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { getCurrentUser } from '@nextcloud/auth'
import { generateUrl } from '@nextcloud/router'
import {
fetchRequestToken,
getRequestToken,
} from './OC/requesttoken.ts'
import logger from './logger.js'
interface OcJsConfig {
auto_logout: boolean
session_keepalive: boolean
session_lifetime: number
}
// This is always set, exception would be e.g. error pages where this is undefined
const {
auto_logout: autoLogout,
session_keepalive: keepSessionAlive,
session_lifetime: sessionLifetime,
} = loadState<Partial<OcJsConfig>>('core', 'config', {})
/**
* Calls the server periodically to ensure that session and CSRF
* token doesn't expire
*/
export function initSessionHeartBeat() {
registerAutoLogout()
if (!keepSessionAlive) {
logger.info('Session heartbeat disabled')
return
}
let interval = startPolling()
window.addEventListener('online', async () => {
logger.info('Browser is online again, resuming heartbeat')
interval = startPolling()
try {
await poll()
logger.info('Session token successfully updated after resuming network')
// Let apps know we're online and requests will have the new token
emit('networkOnline', {
success: true,
})
} catch (error) {
logger.error('could not update session token after resuming network', { error })
// Let apps know we're online but requests might have an outdated token
emit('networkOnline', {
success: false,
})
}
})
window.addEventListener('offline', () => {
logger.info('Browser is offline, stopping heartbeat')
// Let apps know we're offline
emit('networkOffline', {})
clearInterval(interval)
logger.info('Session heartbeat polling stopped')
})
}
/**
* Get interval in seconds
*/
function getInterval(): number {
const interval = sessionLifetime
? Math.floor(sessionLifetime / 2)
: 900
// minimum one minute, max 24 hours, default 15 minutes
return Math.min(
24 * 3600,
Math.max(
60,
interval,
),
)
}
/**
* Poll the CSRF token for changes.
* This will also extend the current session if needed.
*/
async function poll() {
try {
await fetchRequestToken()
} catch (error) {
logger.error('session heartbeat failed', { error })
}
}
/**
* Start an window interval with the polling as the callback.
*
* @return The interval id
*/
function startPolling(): number {
const interval = window.setInterval(poll, getInterval() * 1000)
logger.info('session heartbeat polling started')
return interval
}
/**
* If enabled this will register event listeners to track if a user is active.
* If not the user will be automatically logged out after the configured IDLE time.
*/
function registerAutoLogout() {
if (!autoLogout || !getCurrentUser()) {
return
}
let lastActive = Date.now()
window.addEventListener('mousemove', () => {
lastActive = Date.now()
localStorage.setItem('lastActive', JSON.stringify(lastActive))
})
window.addEventListener('touchstart', () => {
lastActive = Date.now()
localStorage.setItem('lastActive', JSON.stringify(lastActive))
})
window.addEventListener('storage', (event) => {
if (event.key !== 'lastActive') {
return
}
if (event.newValue === null) {
return
}
lastActive = JSON.parse(event.newValue)
})
let intervalId = 0
const logoutCheck = () => {
const timeout = Date.now() - (sessionLifetime ?? 86400) * 1000
if (lastActive < timeout) {
clearTimeout(intervalId)
logger.info('Inactivity timout reached, logging out')
const logoutUrl = generateUrl('/logout') + '?requesttoken=' + encodeURIComponent(getRequestToken())
window.location.href = logoutUrl
}
}
intervalId = window.setInterval(logoutCheck, 1000)
}
|