diff --git a/cypress/e2e/api/SessionApi.spec.js b/cypress/e2e/api/SessionApi.spec.js index d98c81727ec..36d8f94e1e5 100644 --- a/cypress/e2e/api/SessionApi.spec.js +++ b/cypress/e2e/api/SessionApi.spec.js @@ -301,6 +301,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 2260dd4a328..7380401c962 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 session() { @@ -99,7 +109,7 @@ export class Connection { } sync({ version }) { - 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 { } save({ version, autosaveContent, documentState, force, manualSave }) { - return axios.post(this.#url('session/save'), { + return this.#post(this.#url('session/save'), { ...this.#defaultParams, filePath: this.#options.filePath, version, @@ -119,7 +129,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, @@ -130,7 +140,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 }) => { @@ -146,7 +156,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', }, @@ -154,7 +164,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, @@ -163,7 +173,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) {