From 009a09596baa42fa9e0334b3037946f6801cf6e7 Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 28 Nov 2024 14:52:00 +0100 Subject: [PATCH] feat(SessionApi): Send save request via `sendBeacon` at `beforeunload` This will send a final save request on unsaved changes via the browsers native `navigator.sendBeacon()` function when navigating away from the website or the tab/browser is closed. Fixes: #6606 Signed-off-by: Jonas --- src/components/Editor.vue | 10 ++++++++++ src/services/SessionApi.js | 15 ++++++++++++--- src/services/SyncService.js | 12 ++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 76893fca873..1ebda122df2 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -678,7 +678,10 @@ export default { ) { this.dirty = state.dirty if (this.dirty) { + window.addEventListener('beforeunload', this.saveBeforeUnload) this.$syncService.autosave() + } else { + window.removeEventListener('beforeunload', this.saveBeforeUnload) } } } @@ -887,6 +890,13 @@ export default { updateEditorWidth(newWidth) { document.documentElement.style.setProperty('--text-editor-max-width', newWidth) }, + + async saveBeforeUnload(event) { + if (!this.dirty && !this.document.hasUnsavedChanges) { + return + } + await this.$syncService.saveNoWait() + }, }, } diff --git a/src/services/SessionApi.js b/src/services/SessionApi.js index 73b7ed1a789..b25422ac2c0 100644 --- a/src/services/SessionApi.js +++ b/src/services/SessionApi.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import axios from '@nextcloud/axios' +import { getRequestToken } from '@nextcloud/auth' import { generateUrl } from '@nextcloud/router' export class ConnectionClosedError extends Error { @@ -110,8 +111,9 @@ export class Connection { }) } - save({ version, autosaveContent, documentState, force, manualSave }) { - return this.#post(this.#url(`session/${this.#document.id}/save`), { + save({ version, autosaveContent, documentState, force, manualSave, useSendBeacon = false }) { + const url = this.#url(`session/${this.#document.id}/save`) + const data = { ...this.#defaultParams, filePath: this.#options.filePath, baseVersionEtag: this.#document.baseVersionEtag, @@ -120,7 +122,14 @@ export class Connection { documentState, force, manualSave, - }) + } + + if (useSendBeacon) { + data.requesttoken = getRequestToken() ?? '' + const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }) + return navigator.sendBeacon(url, blob) + } + return this.#post(url, data) } push({ steps, version, awareness }) { diff --git a/src/services/SyncService.js b/src/services/SyncService.js index 61860b18fd9..a85c376ad38 100644 --- a/src/services/SyncService.js +++ b/src/services/SyncService.js @@ -299,6 +299,18 @@ class SyncService { } } + async saveNoWait() { + await this.#connection.save({ + version: this.version, + autosaveContent: this._getContent(), + documentState: this.getDocumentState(), + force: false, + manualSave: true, + useSendBeacon: true, + }) + logger.debug('[SyncService] saved using sendBeacon (nowait)') + } + forceSave() { return this.save({ force: true }) }