Skip to content

Commit fe5192c

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

10 files changed

Lines changed: 137 additions & 75 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/apis/Sync.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
import axios from '@nextcloud/axios'
6+
import type { Connection } from '../composables/useConnection.js'
7+
import { unref, type ShallowRef } from 'vue'
8+
import { generateUrl } from '@nextcloud/router'
9+
import type { Step } from '../services/SyncService.js'
10+
11+
interface SyncData {
12+
version: number
13+
steps: string[]
14+
awareness: string
15+
}
16+
17+
interface SyncResponse {
18+
data: {
19+
steps: Step[]
20+
documentState: string
21+
awareness: Record<string, string>
22+
}
23+
}
24+
25+
/**
26+
* Send data to the server
27+
* @param connection the active connection
28+
* @param data data to push to the server
29+
*/
30+
export function push(
31+
connection: ShallowRef<Connection> | Connection,
32+
data: SyncData,
33+
): Promise<SyncResponse> {
34+
const con = unref(connection)
35+
const pub = con.shareToken ? '/public' : ''
36+
const url = generateUrl(`apps/text${pub}/session/${con.documentId}/push`)
37+
return axios.post(url, {
38+
documentId: con.documentId,
39+
sessionId: con.sessionId,
40+
sessionToken: con.sessionToken,
41+
token: con.shareToken,
42+
version: data.version,
43+
steps: data.steps.filter((s) => s),
44+
awareness: data.awareness,
45+
})
46+
}

src/components/Editor.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,14 @@ export default defineComponent({
239239
isRichEditor,
240240
props,
241241
)
242+
const { connection, openConnection, baseVersionEtag } =
243+
provideConnection(props)
244+
const { syncService } = provideSyncService(connection, openConnection)
242245
const extensions = [
243246
Autofocus.configure({ fileId: props.fileId }),
244247
Collaboration.configure({ document: ydoc }),
245248
CollaborationCursor.configure({ provider: { awareness } }),
246249
]
247-
const { connection, openConnection, baseVersionEtag } =
248-
provideConnection(props)
249-
const { syncService } = provideSyncService(connection, openConnection, props)
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',

0 commit comments

Comments
 (0)