Disconnect session after 30 minutes of idle timeout

Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Julius Härtl 2020-11-09 12:51:09 +01:00
Родитель 58c3491460
Коммит 08f8725db8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4C614C6ED2CDE6DF
3 изменённых файлов: 43 добавлений и 6 удалений

Просмотреть файл

@ -22,12 +22,15 @@
<template>
<div id="editor-container">
<div v-if="currentSession && active">
<div v-if="currentSession && active" class="document-status">
<p v-if="idle" class="msg icon-info">
{{ t('text', 'Document idle for {timeout} minutes, click to continue editing', { timeout: IDLE_TIMEOUT }) }} <a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
<p v-if="hasSyncCollission" class="msg icon-error">
{{ t('text', 'The document has been changed outside of the editor. The changes cannot be applied.') }}
</p>
<p v-if="hasConnectionIssue" class="msg icon-info">
{{ t('text', 'File could not be loaded. Please check your internet connection.') }} <a class="button primary" @click="reconnect">{{ t('text', 'Retry') }}</a>
{{ t('text', 'File could not be loaded. Please check your internet connection.') }} <a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
</div>
<div v-if="currentSession && active" id="editor-wrapper" :class="{'has-conflicts': hasSyncCollission, 'icon-loading': !initialLoading || hasConnectionIssue, 'richEditor': isRichEditor}">
@ -72,7 +75,7 @@ import Vue from 'vue'
import escapeHtml from 'escape-html'
import moment from '@nextcloud/moment'
import { SyncService, ERROR_TYPE } from './../services/SyncService'
import { SyncService, ERROR_TYPE, IDLE_TIMEOUT } from './../services/SyncService'
import { endpointUrl, getRandomGuestName } from './../helpers'
import { extensionHighlight } from '../helpers/mappings'
import { createEditor, markdownit, createMarkdownSerializer, serializePlainText, loadSyntaxHighlight } from './../EditorFactory'
@ -143,6 +146,8 @@ export default {
},
data() {
return {
IDLE_TIMEOUT,
tiptap: null,
/** @type SyncService */
syncService: null,
@ -153,6 +158,7 @@ export default {
filteredSessions: {},
idle: false,
dirty: false,
initialLoading: false,
lastSavedString: '',
@ -393,6 +399,12 @@ export default {
this.dirty = state.dirty
}
})
.on('idle', () => {
this.syncService.close()
this.idle = true
this.readOnly = true
this.tiptap.setOptions({ editable: !this.readOnly })
})
if (this.initialSession === null) {
this.syncService.open({
fileId: this.fileId,
@ -421,6 +433,7 @@ export default {
},
reconnect() {
this.initialLoading = true
if (this.syncService) {
this.syncService.close().then(() => {
this.syncService = null
@ -434,6 +447,7 @@ export default {
this.tiptap.destroy()
this.initSession()
}
this.idle = false
},
updateSessions(sessions) {
@ -508,10 +522,10 @@ export default {
width: 100%;
}
.msg.icon-error {
.document-status .msg {
padding: 12px;
border-bottom:1px solid var(--color-border);
padding-left: 30px;
border-bottom: 1px solid var(--color-border);
background-position: 8px center;
}

Просмотреть файл

@ -125,6 +125,9 @@ class PollingBackend {
this._authority.sessions = response.data.sessions
if (response.data.steps.length === 0) {
if (this._authority.checkIdle()) {
return
}
this.lock = false
if (response.data.sessions.filter((session) => session.lastContact > Date.now() / 1000 - COLLABORATOR_DISCONNECT_TIME).length < 2) {
this.maximumRefetchTimer()

Просмотреть файл

@ -31,6 +31,13 @@ const defaultOptions = {
serialize: (document) => document,
}
/**
* Timeout after which the editor will consider a document without changes being synced as idle
* The session will be terminated and the document will stay open in read-only mode with a button to reconnect if needed
* @type {number}
*/
const IDLE_TIMEOUT = 30
const ERROR_TYPE = {
/**
* Failed to save collaborative document due to external change
@ -68,6 +75,8 @@ class SyncService {
change: [],
/* Emitted after successful save */
save: [],
/* Emitted once a document becomes idle */
idle: [],
}
this.backend = new PollingBackend(this)
@ -81,6 +90,8 @@ class SyncService {
this.steps = []
this.stepClientIDs = []
this.lastStepPush = Date.now()
return this
}
@ -202,10 +213,19 @@ class SyncService {
})
})
}
this.lastStepPush = Date.now()
this.emit('sync', { steps: newSteps, document })
console.debug('receivedSteps', 'newVersion', this._getVersion())
}
checkIdle() {
const lastPushMinutesAgo = (Date.now() - this.lastStepPush) / 1000 / 60
if (lastPushMinutesAgo > IDLE_TIMEOUT) {
console.debug(`[SyncService] Document is idle for ${this.IDLE_TIMEOUT} minutes, suspending connection`)
this.emit('idle')
}
}
_getVersion() {
if (this.state) {
return getVersion(this.state)
@ -294,4 +314,4 @@ class SyncService {
}
export default SyncService
export { SyncService, ERROR_TYPE }
export { SyncService, ERROR_TYPE, IDLE_TIMEOUT }