/* * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { MessageTransporter } from '../message-transporters/index.js' import { Message, MessageType } from '../message-transporters/message.js' import { InMemoryConnectionTransportAdapter } from './in-memory-connection-transport-adapter.js' import { RealtimeDoc } from './realtime-doc.js' import { YDocSyncClientAdapter } from './y-doc-sync-client-adapter.js' import { YDocSyncServerAdapter } from './y-doc-sync-server-adapter.js' import { describe, expect, it, beforeAll, jest, afterAll } from '@jest/globals' describe('y-doc-sync-adapter', () => { beforeAll(() => { jest.useFakeTimers() }) afterAll(() => { jest.useRealTimers() }) it('server client communication', async () => { const docServer: RealtimeDoc = new RealtimeDoc('This is a test note') const docClient1: RealtimeDoc = new RealtimeDoc() const docClient2: RealtimeDoc = new RealtimeDoc() const textServer = docServer.getMarkdownContentChannel() const textClient1 = docClient1.getMarkdownContentChannel() const textClient2 = docClient2.getMarkdownContentChannel() textServer.observe(() => // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log('textServer', new Date(), textServer.toString()) ) textClient1.observe(() => // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log('textClient1', new Date(), textClient1.toString()) ) textClient2.observe(() => // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log('textClient2', new Date(), textClient2.toString()) ) const transporterAdapterServerTo1 = new InMemoryConnectionTransportAdapter( 's>1' ) const transporterAdapterServerTo2 = new InMemoryConnectionTransportAdapter( 's>2' ) const transporterAdapterClient1 = new InMemoryConnectionTransportAdapter( '1>s' ) const transporterAdapterClient2 = new InMemoryConnectionTransportAdapter( '2>s' ) const messageTransporterServerTo1 = new MessageTransporter() const messageTransporterServerTo2 = new MessageTransporter() const messageTransporterClient1 = new MessageTransporter() const messageTransporterClient2 = new MessageTransporter() messageTransporterServerTo1.on(MessageType.NOTE_CONTENT_UPDATE, () => console.log('Received NOTE_CONTENT_UPDATE from client 1 to server') ) messageTransporterServerTo2.on(MessageType.NOTE_CONTENT_UPDATE, () => console.log('Received NOTE_CONTENT_UPDATE from client 2 to server') ) messageTransporterClient1.on(MessageType.NOTE_CONTENT_UPDATE, () => console.log('Received NOTE_CONTENT_UPDATE from server to client 1') ) messageTransporterClient2.on(MessageType.NOTE_CONTENT_UPDATE, () => console.log('Received NOTE_CONTENT_UPDATE from server to client 2') ) messageTransporterServerTo1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => console.log('Received NOTE_CONTENT_REQUEST from client 1 to server') ) messageTransporterServerTo2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => console.log('Received NOTE_CONTENT_REQUEST from client 2 to server') ) messageTransporterClient1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => console.log('Received NOTE_CONTENT_REQUEST from server to client 1') ) messageTransporterClient2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => console.log('Received NOTE_CONTENT_REQUEST from server to client 2') ) messageTransporterClient1.doAsSoonAsConnected(() => console.log('1>s is connected') ) messageTransporterClient2.doAsSoonAsConnected(() => console.log('2>s is connected') ) messageTransporterServerTo1.doAsSoonAsConnected(() => console.log('s>1 is connected') ) messageTransporterServerTo2.doAsSoonAsConnected(() => console.log('s>2 is connected') ) messageTransporterClient1.doAsSoonAsReady(() => console.log('1>s is ready')) messageTransporterClient2.doAsSoonAsReady(() => console.log('2>s is ready')) messageTransporterServerTo1.doAsSoonAsReady(() => console.log('s>1 is connected') ) messageTransporterServerTo2.doAsSoonAsReady(() => console.log('s>2 is connected') ) docServer.on('update', (update: number[], origin: unknown) => { const message: Message = { type: MessageType.NOTE_CONTENT_UPDATE, payload: update } if (origin !== messageTransporterServerTo1) { console.log('YDoc on Server updated. Sending to Client 1') messageTransporterServerTo1.sendMessage(message) } if (origin !== messageTransporterServerTo2) { console.log('YDoc on Server updated. Sending to Client 2') messageTransporterServerTo2.sendMessage(message) } }) docClient1.on('update', (update: number[], origin: unknown) => { if (origin !== messageTransporterClient1) { console.log('YDoc on client 1 updated. Sending to Server') } }) docClient2.on('update', (update: number[], origin: unknown) => { if (origin !== messageTransporterClient2) { console.log('YDoc on client 2 updated. Sending to Server') } }) const yDocSyncAdapter1 = new YDocSyncClientAdapter( messageTransporterClient1, docClient1 ) const yDocSyncAdapter2 = new YDocSyncClientAdapter( messageTransporterClient2, docClient2 ) const yDocSyncAdapterServerTo1 = new YDocSyncServerAdapter( messageTransporterServerTo1, docServer, () => true ) const yDocSyncAdapterServerTo2 = new YDocSyncServerAdapter( messageTransporterServerTo2, docServer, () => true ) const waitForClient1Sync = new Promise((resolve) => { yDocSyncAdapter1.doAsSoonAsSynced(() => { console.log('client 1 received the first sync') resolve() }) }) const waitForClient2Sync = new Promise((resolve) => { yDocSyncAdapter2.doAsSoonAsSynced(() => { console.log('client 2 received the first sync') resolve() }) }) const waitForServerTo11Sync = new Promise((resolve) => { yDocSyncAdapterServerTo1.doAsSoonAsSynced(() => { console.log('server 1 received the first sync') resolve() }) }) const waitForServerTo21Sync = new Promise((resolve) => { yDocSyncAdapterServerTo2.doAsSoonAsSynced(() => { console.log('server 2 received the first sync') resolve() }) }) transporterAdapterClient1.connect(transporterAdapterServerTo1) transporterAdapterClient2.connect(transporterAdapterServerTo2) messageTransporterClient1.setAdapter(transporterAdapterClient1) messageTransporterClient2.setAdapter(transporterAdapterClient2) messageTransporterServerTo1.setAdapter(transporterAdapterServerTo1) messageTransporterServerTo2.setAdapter(transporterAdapterServerTo2) messageTransporterClient1.markAsReady() messageTransporterClient2.markAsReady() messageTransporterServerTo1.markAsReady() messageTransporterServerTo2.markAsReady() jest.advanceTimersByTime(1000) expect(messageTransporterClient1.isReady()).toBeTruthy() expect(messageTransporterClient2.isReady()).toBeTruthy() expect(messageTransporterServerTo1.isReady()).toBeTruthy() expect(messageTransporterServerTo2.isReady()).toBeTruthy() yDocSyncAdapter1.requestDocumentState() yDocSyncAdapter2.requestDocumentState() await Promise.all([ waitForClient1Sync, waitForClient2Sync, waitForServerTo11Sync, waitForServerTo21Sync ]) textClient1.insert(0, 'test2') textClient1.insert(0, 'test3') textClient2.insert(0, 'test4') // eslint-disable-next-line @typescript-eslint/no-base-to-string expect(textServer.toString()).toBe('test4test3test2This is a test note') // eslint-disable-next-line @typescript-eslint/no-base-to-string expect(textClient1.toString()).toBe('test4test3test2This is a test note') // eslint-disable-next-line @typescript-eslint/no-base-to-string expect(textClient2.toString()).toBe('test4test3test2This is a test note') docServer.destroy() docClient1.destroy() docClient2.destroy() }) })