diff --git a/appinfo/routes.php b/appinfo/routes.php index 145af75a70..b563180375 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -33,6 +33,7 @@ ['name' => 'document#createFromTemplate', 'url' => 'indexTemplate', 'verb' => 'GET'], ['name' => 'document#publicPage', 'url' => '/public', 'verb' => 'GET'], ['name' => 'document#token', 'url' => '/token', 'verb' => 'POST'], + ['name' => 'document#heartbeat', 'url' => '/heartbeat', 'verb' => 'GET'], ['name' => 'document#editOnline', 'url' => 'editonline', 'verb' => 'GET'], ['name' => 'document#editOnlineTarget', 'url' => 'editonline/{fileId}/{target}', 'verb' => 'GET'], diff --git a/lib/Controller/DocumentController.php b/lib/Controller/DocumentController.php index f721341086..c2b02374be 100644 --- a/lib/Controller/DocumentController.php +++ b/lib/Controller/DocumentController.php @@ -393,6 +393,14 @@ public function token(int $fileId, ?string $shareToken = null, ?string $path = n } } + /** + * Since collabora does not extend the session on interaction we need to manually trigger this while editing + */ + #[NoAdminRequired] + public function heartbeat(): DataResponse { + return new DataResponse(); + } + /** * @throws NotPermittedException * @throws NotFoundException diff --git a/src/mixins/autoLogout.js b/src/mixins/autoLogout.js new file mode 100644 index 0000000000..49573b131a --- /dev/null +++ b/src/mixins/autoLogout.js @@ -0,0 +1,111 @@ +/* + * @copyright Copyright (c) 2023 Julius Härtl + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import axios from '@nextcloud/axios' +import { loadState } from '@nextcloud/initial-state' +import { generateUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' + +const config = loadState('core', 'config', {}) + +const getInterval = () => { + let interval = NaN + if (config.session_lifetime) { + interval = Math.floor(config.session_lifetime / 2) + } + + return Math.min( + 24 * 3600, + Math.max( + 60, + isNaN(interval) ? 900 : interval + ) + ) +} + +export default { + + data() { + return { + autoLogoutInterval: null, + autoLogoutRegistered: false, + } + }, + + mounted() { + if (!getCurrentUser()) { + return + } + + if (config?.auto_logout || !config?.session_keepalive) { + this.autoLogoutInterval = setInterval(this.registerCheck, 10000) + } + }, + + beforeDestroy() { + if (this.autoLogoutInterval) { + clearInterval(this.autoLogoutInterval) + } + }, + + methods: { + extendAutoLogout() { + const oldValue = localStorage.getItem('lastActive') + const newValue = Date.now().toString() + localStorage.setItem('lastActive', newValue) + const event = new StorageEvent('storage', { + key: 'lastActive', + oldValue, + newValue, + }) + window.dispatchEvent(event) + }, + registerCheck() { + if (!this.autoLogoutRegistered && this.postMessage) { + this.postMessage.registerPostMessageHandler(this.handleGetUserStateResponse) + this.autoLogoutRegistered = true + } + if (this.autoLogoutRegistered && this.postMessage) { + this.sendPostMessage('Get_User_State') + } + }, + handleGetUserStateResponse({ parsed: { msgId, args } }) { + if (msgId !== 'Get_User_State_Resp') { + return + } + + const elapsed = args.Elapsed + const lastActive = Number(localStorage.getItem('lastActive')) + const timeSinceLastActive = Date.now() - lastActive + const recheckAfter = (getInterval() * 1000) + + if (!config.session_keepalive && timeSinceLastActive > recheckAfter) { + axios.get(generateUrl('/apps/richdocuments/heartbeat')) + } + + if (elapsed < 30) { + console.debug('[richdocuments] Extending auto logout timeout, office idle since ' + elapsed, timeSinceLastActive, recheckAfter, timeSinceLastActive > recheckAfter) + this.extendAutoLogout() + } + }, + }, + +} diff --git a/src/view/Office.vue b/src/view/Office.vue index d2cd28f785..96be724aa3 100644 --- a/src/view/Office.vue +++ b/src/view/Office.vue @@ -99,6 +99,7 @@ import { getUIDefaults, } from '../helpers/coolParameters.js' import Config from '../services/config.tsx' +import autoLogout from '../mixins/autoLogout.js' import openLocal from '../mixins/openLocal.js' import pickLink from '../mixins/pickLink.js' import saveAs from '../mixins/saveAs.js' @@ -124,7 +125,7 @@ export default { ZoteroHint, }, mixins: [ - openLocal, pickLink, saveAs, uiMention, version, + autoLogout, openLocal, pickLink, saveAs, uiMention, version, ], props: { filename: {