Skip to content

Commit 5d28d8f

Browse files
committed
chore(refactor): sync service with new connection
Signed-off-by: Max <max@nextcloud.com>
1 parent d4a09c4 commit 5d28d8f

8 files changed

Lines changed: 75 additions & 64 deletions

File tree

cypress/support/sessions.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import axios from '@nextcloud/axios'
77
import { SessionConnection } from '../../src/services/SessionConnection.js'
88
import { open, close } from '../../src/apis/Connect.ts'
9+
import { push } from '../../src/apis/Sync.ts'
910

1011
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
1112

1213
Cypress.Commands.add('createTextSession', async (fileId, options = {}) => {
13-
const { data } = await open({ fileId, token: options.shareToken, ...options })
14-
return new SessionConnection({ data }, options)
14+
const { connection, data } = await open({ fileId, token: options.shareToken, ...options })
15+
return new SessionConnection({ data }, connection)
1516
})
1617

1718
Cypress.Commands.add('destroySession', async (sessionConnection) => {
@@ -27,16 +28,20 @@ Cypress.Commands.add('failToCreateTextSession', (fileId, baseVersionEtag = null,
2728
}, (err) => err.response)
2829
})
2930

30-
Cypress.Commands.add('pushSteps', ({ connection, steps, version, awareness = '' }) => {
31-
return connection.push({ steps, version, awareness })
32-
.then(response => response.data)
31+
Cypress.Commands.add('pushSteps', ({ connection: sessionConnection, steps, version, awareness = '' }) => {
32+
return push(
33+
sessionConnection.connection,
34+
{ steps, version, awareness }
35+
).then(response => response.data)
3336
})
3437

35-
Cypress.Commands.add('failToPushSteps', ({ connection, steps, version, awareness = '' }) => {
36-
return connection.push({ steps, version, awareness })
37-
.then((response) => {
38-
throw new Error('Expected request to fail - but it succeeded!')
39-
}, (err) => err.response)
38+
Cypress.Commands.add('failToPushSteps', ({ connection: sessionConnection, steps, version, awareness = '' }) => {
39+
return push(
40+
sessionConnection.connection,
41+
{ steps, version, awareness }
42+
).then((_response) => {
43+
throw new Error('Expected request to fail - but it succeeded!')
44+
}, (err) => err.response)
4045
})
4146

4247
Cypress.Commands.add('syncSteps', (connection, options = { version: 0 }) => {

src/apis/Connect.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export async function open(
3535
documentId: document.id,
3636
sessionId: session.id,
3737
sessionToken: session.token,
38+
baseVersionEtag: document.baseVersionEtag,
39+
filePath: params.filePath,
40+
shareToken: params.token,
3841
}
3942
return { connection, data: response.data }
4043
}
@@ -46,6 +49,10 @@ export async function open(
4649
export async function close(connection: Connection) {
4750
const id = connection.documentId
4851
const url = generateUrl(`/apps/text/session/${id}/close`)
49-
const response = await axios.post(url, connection)
52+
const response = await axios.post(url, {
53+
documentId: connection.documentId,
54+
sessionId: connection.sessionId,
55+
sessionToken: connection.sessionToken,
56+
})
5057
return response.data
5158
}

src/apis/Mention.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { unref, type ShallowRef } from 'vue'
1818
export function emitMention(
1919
mention: string,
2020
scope: object,
21-
{ connection }: { connection: ShallowRef<Connection> },
21+
{ connection }: { connection: ShallowRef<Connection> | Connection },
2222
): Promise<void> {
2323
// TODO: Require actual connection - handle disconnected state early on
2424
const con = unref(connection)
@@ -27,8 +27,11 @@ export function emitMention(
2727
console.warn(err.message, { err, mention })
2828
return Promise.resolve()
2929
}
30-
return axios.put(generateUrl(`apps/text/session/${con.documentId}/mention`), {
31-
...con,
30+
const url = generateUrl(`apps/text/session/${con.documentId}/mention`)
31+
return axios.put(url, {
32+
documentId: con.documentId,
33+
sessionId: con.sessionId,
34+
sessionToken: con.sessionToken,
3235
mention,
3336
scope,
3437
})

src/components/Editor.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ export default defineComponent({
246246
]
247247
const { connection, openConnection, baseVersionEtag } =
248248
provideConnection(props)
249-
const { syncService } = provideSyncService(connection, openConnection, props)
249+
const { syncService } = provideSyncService(connection, openConnection)
250250
const editor = isRichEditor
251251
? createRichEditor({
252252
connection,

src/composables/useConnection.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export interface Connection {
1111
documentId: number
1212
sessionId: number
1313
sessionToken: string
14+
baseVersionEtag: string
15+
filePath: string
16+
shareToken?: string
1417
}
1518

1619
export interface InitialData {
@@ -70,14 +73,24 @@ export const useConnection = () => {
7073
/**
7174
* Get the connection and additional data from the initial session if available.
7275
* @param props Props of the editor component
73-
* @param props.initialSession InitialSession to use.
76+
* @param props.relativePath Relative path to the file.
77+
* @param props.initialSession Initial session handed to the editor in direct editing
78+
* @param props.shareToken Share token of the file.
7479
*/
75-
function openInitialSession(props: { initialSession?: InitialData }) {
80+
function openInitialSession(props: {
81+
relativePath: string
82+
initialSession?: InitialData
83+
shareToken?: string
84+
}) {
7685
if (props.initialSession) {
86+
const { document, session } = props.initialSession
7787
const connection = {
78-
documentId: props.initialSession.document.id,
79-
sessionId: props.initialSession.session.id,
80-
sessionToken: props.initialSession.session.token,
88+
documentId: document.id,
89+
sessionId: session.id,
90+
sessionToken: session.token,
91+
baseVersionEtag: document.baseVersionEtag,
92+
filePath: props.relativePath,
93+
shareToken: props.shareToken,
8194
}
8295
return { connection, data: props.initialSession }
8396
}

src/composables/useSyncService.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,14 @@ const syncServiceKey = Symbol('text:sync') as InjectionKey<SyncService>
1313
* Define a sync service and provide it to child components
1414
* @param connection Connection to the text api.
1515
* @param openConnection Function to open the connection.
16-
* @param props Props of the editor component.
17-
* @param props.shareToken Share token of the file.
18-
* @param props.relativePath Relative path to the file.
1916
*/
2017
export function provideSyncService(
2118
connection: ShallowRef<Connection>,
2219
openConnection: () => Promise<InitialData>,
23-
props: { shareToken: string; relativePath: string },
2420
) {
2521
const syncService = new SyncService({
2622
connection,
2723
openConnection,
28-
shareToken: props.shareToken,
29-
filePath: props.relativePath,
3024
})
3125
provide(syncServiceKey, syncService)
3226
return { syncService }

src/services/SessionConnection.js

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ export class SessionConnection {
2424
#lock
2525
#readOnly
2626
#hasOwner
27-
#options
27+
connection
2828

29-
constructor(response, options) {
29+
constructor(response, connection) {
3030
const {
3131
document,
3232
session,
@@ -43,8 +43,8 @@ export class SessionConnection {
4343
this.#content = content
4444
this.#documentState = documentState
4545
this.#hasOwner = hasOwner
46-
this.#options = options
47-
this.isPublic = !!options.shareToken
46+
this.connection = connection
47+
this.isPublic = !!connection.shareToken
4848
this.closed = false
4949
}
5050

@@ -82,14 +82,14 @@ export class SessionConnection {
8282
documentId: this.#document.id,
8383
sessionId: this.#session.id,
8484
sessionToken: this.#session.token,
85-
token: this.#options.shareToken,
85+
token: this.connection.shareToken,
8686
}
8787
}
8888

8989
sync({ version }) {
9090
return this.#post(this.#url(`session/${this.#document.id}/sync`), {
9191
...this.#defaultParams,
92-
filePath: this.#options.filePath,
92+
filePath: this.connection.filePath,
9393
baseVersionEtag: this.#document.baseVersionEtag,
9494
version,
9595
})
@@ -99,7 +99,7 @@ export class SessionConnection {
9999
const url = this.#url(`session/${this.#document.id}/save`)
100100
const postData = {
101101
...this.#defaultParams,
102-
filePath: this.#options.filePath,
102+
filePath: this.connection.filePath,
103103
baseVersionEtag: this.#document.baseVersionEtag,
104104
...data,
105105
}
@@ -111,7 +111,7 @@ export class SessionConnection {
111111
const url = this.#url(`session/${this.#document.id}/save`)
112112
const postData = {
113113
...this.#defaultParams,
114-
filePath: this.#options.filePath,
114+
filePath: this.connection.filePath,
115115
baseVersionEtag: this.#document.baseVersionEtag,
116116
...data,
117117
requestToken: getRequestToken() ?? '',
@@ -123,17 +123,6 @@ export class SessionConnection {
123123
return navigator.sendBeacon(url, blob)
124124
}
125125

126-
push({ steps, version, awareness }) {
127-
return this.#post(this.#url(`session/${this.#document.id}/push`), {
128-
...this.#defaultParams,
129-
filePath: this.#options.filePath,
130-
baseVersionEtag: this.#document.baseVersionEtag,
131-
steps,
132-
version,
133-
awareness,
134-
})
135-
}
136-
137126
// TODO: maybe return a new connection here so connections have immutable state
138127
update(guestName) {
139128
return this.#post(this.#url(`session/${this.#document.id}/session`), {
@@ -156,7 +145,7 @@ export class SessionConnection {
156145
+ '&sessionToken='
157146
+ encodeURIComponent(this.#session.token)
158147
+ '&token='
159-
+ encodeURIComponent(this.#options.shareToken || '')
148+
+ encodeURIComponent(this.connection.shareToken || '')
160149
return this.#post(url, formData, {
161150
headers: {
162151
'Content-Type': 'multipart/form-data',

src/services/SyncService.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { logger } from '../helpers/logger.js'
1515
import type { ShallowRef } from 'vue'
1616
import type { InitialData, Connection } from '../composables/useConnection.js'
1717
import { close } from '../apis/Connect.js'
18+
import { push } from '../apis/Sync.js'
1819

1920
/**
2021
* Timeout after which the editor will consider a document without changes being synced as idle
@@ -51,7 +52,7 @@ const ERROR_TYPE = {
5152
/*
5253
* Step as what we expect to be returned from the server right now.
5354
*/
54-
interface Step {
55+
export interface Step {
5556
data: string[]
5657
version: number
5758
sessionId: number
@@ -109,7 +110,7 @@ export declare type EventTypes = {
109110
}
110111

111112
class SyncService {
112-
#connection: ShallowRef<Connection | undefined>
113+
connection: ShallowRef<Connection | undefined>
113114
sessionConnection?: SessionConnection
114115
version = -1
115116
pushError = 0
@@ -120,24 +121,16 @@ class SyncService {
120121
#openConnection: () => Promise<InitialData>
121122
#lastStepPush = Date.now()
122123
#sending = false
123-
#filePath?: string
124-
#shareToken?: string
125124

126125
constructor({
127126
connection,
128127
openConnection,
129-
filePath,
130-
shareToken,
131128
}: {
132129
connection: ShallowRef<Connection | undefined>
133130
openConnection: () => Promise<InitialData>
134-
filePath?: string
135-
shareToken?: string
136131
}) {
137-
this.#connection = connection
132+
this.connection = connection
138133
this.#openConnection = openConnection
139-
this.#filePath = filePath
140-
this.#shareToken = shareToken
141134
}
142135

143136
get isReadOnly() {
@@ -152,7 +145,10 @@ class SyncService {
152145
return this.sessionConnection?.session.guestName
153146
}
154147

155-
hasActiveConnection(): this is { sessionConnection: SessionConnection } {
148+
hasActiveConnection(): this is {
149+
sessionConnection: SessionConnection
150+
connection: ShallowRef<Connection>
151+
} {
156152
return !!this.sessionConnection && !this.sessionConnection.isClosed
157153
}
158154

@@ -165,8 +161,10 @@ class SyncService {
165161
// Error was already emitted above
166162
return
167163
}
168-
const options = { shareToken: this.#shareToken, filePath: this.#filePath }
169-
this.sessionConnection = new SessionConnection({ data }, options)
164+
this.sessionConnection = new SessionConnection({
165+
data,
166+
connection: this.connection.value,
167+
})
170168
this.backend = new PollingBackend(this, this.sessionConnection)
171169
this.version = this.sessionConnection.docStateVersion
172170
this.emit('opened', this.sessionConnection.state)
@@ -226,8 +224,10 @@ class SyncService {
226224
if (!this.hasActiveConnection()) {
227225
return
228226
}
229-
return this.sessionConnection
230-
.push({ ...sendable, version: this.version })
227+
return push(this.connection, {
228+
version: this.version,
229+
...sendable,
230+
})
231231
.then((response) => {
232232
this.#outbox.clearSentData(sendable)
233233
const { steps, documentState } = response.data as {
@@ -350,8 +350,8 @@ class SyncService {
350350
async close() {
351351
// Make sure to leave no pending requests behind.
352352
this.backend?.disconnect()
353-
if (this.#connection.value) {
354-
close(this.#connection.value)
353+
if (this.connection.value) {
354+
close(this.connection.value)
355355
// Log and ignore possible network issues.
356356
.catch((e) => {
357357
logger.info('Failed to close connection.', { e })

0 commit comments

Comments
 (0)