aboutsummaryrefslogtreecommitdiffstats
path: root/core/src/session-heartbeat.ts
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/session-heartbeat.ts')
-rw-r--r--core/src/session-heartbeat.ts158
1 files changed, 158 insertions, 0 deletions
diff --git a/core/src/session-heartbeat.ts b/core/src/session-heartbeat.ts
new file mode 100644
index 00000000000..42a9bfccef7
--- /dev/null
+++ b/core/src/session-heartbeat.ts
@@ -0,0 +1,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)
+}