From 43cc4d9dba3b1b75ab0c2bbfcee1fef8cac73d23 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Sep 2023 10:42:23 +0200 Subject: [PATCH] fix(api): prevent sending requests with closed session. Signed-off-by: Max Signed-off-by: Jonas --- cypress/e2e/SessionApi.spec.js | 13 +++++++++++++ src/services/SessionApi.js | 31 +++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/cypress/e2e/SessionApi.spec.js b/cypress/e2e/SessionApi.spec.js index 179f5b966f1..3a3276fc483 100644 --- a/cypress/e2e/SessionApi.spec.js +++ b/cypress/e2e/SessionApi.spec.js @@ -298,6 +298,19 @@ describe('The session Api', function() { }) }) + it('signals closing connection', function() { + cy.then(() => { + return new Promise((resolve, reject) => { + connection.close() + connection.push({ steps: [messages.update], version, awareness: '' }) + .then( + () => reject(new Error('Push should have thrown ConnectionClosed()')), + resolve, + ) + }) + }) + }) + it('sends initial content if other session is alive but did not push any steps', function() { let joining cy.createTextSession(undefined, { filePath: '', shareToken }) diff --git a/src/services/SessionApi.js b/src/services/SessionApi.js index 5544fd5075b..a5676564ec8 100644 --- a/src/services/SessionApi.js +++ b/src/services/SessionApi.js @@ -22,6 +22,14 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' +export class ConnectionClosedError extends Error { + + constructor(message = 'Close has already been called on the connection', ...rest) { + super(message, ...rest) + } + +} + class SessionApi { #options @@ -50,6 +58,7 @@ class SessionApi { export class Connection { #content + #closed #documentState #document #session @@ -66,6 +75,7 @@ export class Connection { this.#content = content this.#documentState = documentState this.#options = options + this.closed = false } get document() { @@ -95,7 +105,7 @@ export class Connection { } sync({ version, autosaveContent, documentState, force, manualSave }) { - return axios.post(this.#url('session/sync'), { + return this.#post(this.#url('session/sync'), { ...this.#defaultParams, filePath: this.#options.filePath, version, @@ -107,7 +117,7 @@ export class Connection { } push({ steps, version, awareness }) { - return axios.post(this.#url('session/push'), { + return this.#post(this.#url('session/push'), { ...this.#defaultParams, filePath: this.#options.filePath, steps, @@ -118,7 +128,7 @@ export class Connection { // TODO: maybe return a new connection here so connections have immutable state update(guestName) { - return axios.post(this.#url('session'), { + return this.#post(this.#url('session'), { ...this.#defaultParams, guestName, }).then(({ data }) => { @@ -134,7 +144,7 @@ export class Connection { + '&sessionId=' + encodeURIComponent(this.#session.id) + '&sessionToken=' + encodeURIComponent(this.#session.token) + '&shareToken=' + encodeURIComponent(this.#options.shareToken || '') - return axios.post(url, formData, { + return this.#post(url, formData, { headers: { 'Content-Type': 'multipart/form-data', }, @@ -142,7 +152,7 @@ export class Connection { } insertAttachmentFile(filePath) { - return axios.post(_endpointUrl('attachment/filepath'), { + return this.#post(_endpointUrl('attachment/filepath'), { documentId: this.#document.id, sessionId: this.#session.id, sessionToken: this.#session.token, @@ -151,7 +161,16 @@ export class Connection { } close() { - return axios.post(this.#url('session/close'), this.#defaultParams) + const promise = this.#post(this.#url('session/close'), this.#defaultParams) + this.closed = true + return promise + } + + #post(...args) { + if (this.closed) { + return Promise.reject(new ConnectionClosedError()) + } + return axios.post(...args) } #url(endpoint) {