Skip to content

Commit 94c5ff4

Browse files
committed
fix(sync): allow writing during short connection failures
Delay the error message and allow editing for 5 seconds. Also speed up the recovery by triggering push after a successful sync. Signed-off-by: Max <max@nextcloud.com>
1 parent 4da838b commit 94c5ff4

2 files changed

Lines changed: 35 additions & 20 deletions

File tree

cypress/e2e/sync.spec.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,23 @@ describe('Sync', () => {
6969
.should('include', 'after the lost connection')
7070
})
7171

72+
it('handles brief network outages', () => {
73+
cy.intercept('**/apps/text/session/*/*', req => req.destroy()).as('dead')
74+
cy.wait('@dead', { timeout: 30000 })
75+
// bring back the network connection
76+
cy.intercept('**/apps/text/session/*/*', req => { req.continue() }).as('alive')
77+
cy.wait('@alive', { timeout: 30000 })
78+
cy.getContent().type('staying alive')
79+
cy.getContent().should('contain', 'staying alive')
80+
})
81+
7282
it('reconnects via button after a short lost connection', () => {
7383
cy.intercept('**/apps/text/session/*/*', req => req.destroy()).as('dead')
7484
cy.wait('@dead', { timeout: 30000 })
7585
cy.get('#editor-container .document-status', { timeout: 30000 })
7686
.should('contain', 'The document could not be loaded.')
7787
cy.get('#editor-container .document-status')
7888
.find('.button.primary').click()
79-
cy.get('.toastify').should('contain', 'Connection failed.')
80-
cy.get('.toastify', { timeout: 30000 }).should('not.exist')
8189
cy.get('#editor-container .document-status', { timeout: 30000 })
8290
.should('contain', 'The document could not be loaded.')
8391
// bring back the network connection

src/components/Editor.vue

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<CollisionResolveDialog v-if="isResolvingConflict" :sync-error="syncError" />
1515
<Wrapper v-if="displayed"
1616
:is-resolving-conflict="isResolvingConflict"
17-
:has-connection-issue="hasConnectionIssue"
17+
:has-connection-issue="requireReconnect"
1818
:content-loaded="contentLoaded"
1919
:show-outline-outside="showOutlineOutside"
2020
@read-only-toggled="readOnlyToggled"
@@ -28,7 +28,7 @@
2828
:dirty="dirty"
2929
:sessions="filteredSessions"
3030
:sync-error="syncError"
31-
:has-connection-issue="hasConnectionIssue" />
31+
:has-connection-issue="requireReconnect" />
3232
</ReadonlyBar>
3333
</slot>
3434
</div>
@@ -43,7 +43,7 @@
4343
:dirty="dirty"
4444
:sessions="filteredSessions"
4545
:sync-error="syncError"
46-
:has-connection-issue="hasConnectionIssue"
46+
:has-connection-issue="requireReconnect"
4747
@editor-width-change="handleEditorWidthChange" />
4848
<slot name="header" />
4949
</MenuBar>
@@ -58,7 +58,7 @@
5858
<DocumentStatus :idle="idle"
5959
:lock="lock"
6060
:sync-error="syncError"
61-
:has-connection-issue="hasConnectionIssue"
61+
:has-connection-issue="requireReconnect"
6262
@reconnect="reconnect" />
6363
</Wrapper>
6464
<Assistant v-if="hasEditor" />
@@ -241,7 +241,9 @@ export default {
241241
const maxWidth = Math.floor(value) - 36
242242
el.value.style.setProperty('--widget-full-width', `${maxWidth}px`)
243243
})
244-
return { el, width }
244+
const hasConnectionIssue = ref(false)
245+
const { delayed: requireReconnect } = useDelayedFlag(hasConnectionIssue)
246+
return { el, width, hasConnectionIssue, requireReconnect }
245247
},
246248
247249
data() {
@@ -259,7 +261,6 @@ export default {
259261
dirty: false,
260262
contentLoaded: false,
261263
syncError: null,
262-
hasConnectionIssue: false,
263264
hasEditor: false,
264265
readOnly: true,
265266
openReadOnlyEnabled: OCA.Text.OpenReadOnlyEnabled,
@@ -351,6 +352,14 @@ export default {
351352
window.removeEventListener('beforeunload', this.saveBeforeUnload)
352353
}
353354
},
355+
requireReconnect(val) {
356+
if (val) {
357+
this.emit('sync-service:error')
358+
}
359+
if (this.$editor?.isEditable === val) {
360+
this.$editor.setEditable(!val)
361+
}
362+
},
354363
},
355364
mounted() {
356365
if (this.active && (this.hasDocumentParameters)) {
@@ -596,7 +605,7 @@ export default {
596605
this.document = document
597606
598607
this.syncError = null
599-
const editable = this.editMode && !this.hasConnectionIssue
608+
const editable = this.editMode && !this.requireReconnect
600609
if (this.$editor.isEditable !== editable) {
601610
this.$editor.setEditable(editable)
602611
}
@@ -619,7 +628,13 @@ export default {
619628
},
620629
621630
onSync({ steps, document }) {
622-
this.hasConnectionIssue = this.$syncService.backend.fetcher === 0 || !this.$providers[0].wsconnected || this.$syncService.pushError > 0
631+
this.hasConnectionIssue = this.$syncService.backend.fetcher === 0
632+
|| !this.$providers[0].wsconnected
633+
|| this.$syncService.pushError > 0
634+
if (this.$syncService.pushError > 0) {
635+
// successfully received steps - so let's try and also push
636+
this.$syncService.sendStepsNow()
637+
}
623638
this.$nextTick(() => {
624639
this.emit('sync-service:sync')
625640
})
@@ -629,11 +644,6 @@ export default {
629644
},
630645
631646
onError({ type, data }) {
632-
this.$nextTick(() => {
633-
this.$editor?.setEditable(false)
634-
this.emit('sync-service:error')
635-
})
636-
637647
if (type === ERROR_TYPE.LOAD_ERROR) {
638648
this.syncError = {
639649
type,
@@ -648,11 +658,8 @@ export default {
648658
data,
649659
}
650660
}
651-
if (type === ERROR_TYPE.CONNECTION_FAILED && !this.hasConnectionIssue) {
652-
this.hasConnectionIssue = true
653-
OC.Notification.showTemporary(t('text', 'Connection failed.'))
654-
}
655-
if (type === ERROR_TYPE.SOURCE_NOT_FOUND) {
661+
if (type === ERROR_TYPE.CONNECTION_FAILED
662+
|| type === ERROR_TYPE.SOURCE_NOT_FOUND) {
656663
this.hasConnectionIssue = true
657664
}
658665

0 commit comments

Comments
 (0)