fix(communication): send ready event when both sides are ready

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-06-28 19:25:16 +02:00
parent e6b9afc686
commit f4a1999a8b
11 changed files with 271 additions and 142 deletions

View file

@ -8,7 +8,6 @@ import {
MessageTransporter, MessageTransporter,
MessageType, MessageType,
MockedBackendTransportAdapter, MockedBackendTransportAdapter,
waitForOtherPromisesToFinish,
} from '@hedgedoc/commons'; } from '@hedgedoc/commons';
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter'; import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';
@ -41,6 +40,14 @@ describe('realtime user status adapter', () => {
let messageTransporterNotReady: MessageTransporter; let messageTransporterNotReady: MessageTransporter;
let messageTransporterDecline: MessageTransporter; let messageTransporterDecline: MessageTransporter;
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
beforeEach(async () => { beforeEach(async () => {
clientLoggedIn1 = undefined; clientLoggedIn1 = undefined;
clientLoggedIn2 = undefined; clientLoggedIn2 = undefined;
@ -139,11 +146,12 @@ describe('realtime user status adapter', () => {
'sendMessage', 'sendMessage',
); );
messageTransporterLoggedIn1.sendReady(); messageTransporterLoggedIn1.startSendingOfReadyRequests();
messageTransporterLoggedIn2.sendReady(); messageTransporterLoggedIn2.startSendingOfReadyRequests();
messageTransporterGuest.sendReady(); messageTransporterGuest.startSendingOfReadyRequests();
messageTransporterDecline.sendReady(); messageTransporterDecline.startSendingOfReadyRequests();
await waitForOtherPromisesToFinish();
jest.advanceTimersByTime(500);
}); });
it('can answer a state request', () => { it('can answer a state request', () => {

View file

@ -102,9 +102,9 @@ describe('backend websocket adapter', () => {
}); });
it('can send messages', () => { it('can send messages', () => {
const value: Message<MessageType> = { type: MessageType.READY }; const value: Message<MessageType> = { type: MessageType.READY_REQUEST };
sut.send(value); sut.send(value);
expect(mockedSocket.send).toHaveBeenCalledWith('{"type":"READY"}'); expect(mockedSocket.send).toHaveBeenCalledWith('{"type":"READY_REQUEST"}');
}); });
it('can read the connection state when open', () => { it('can read the connection state when open', () => {

View file

@ -106,7 +106,7 @@ export class WebsocketGateway implements OnGatewayConnection {
realtimeNote.addClient(connection); realtimeNote.addClient(connection);
websocketTransporter.sendReady(); websocketTransporter.startSendingOfReadyRequests();
} catch (error: unknown) { } catch (error: unknown) {
this.logger.error( this.logger.error(
`Error occurred while initializing: ${(error as Error).message}`, `Error occurred while initializing: ${(error as Error).message}`,

View file

@ -7,7 +7,7 @@ import { Message, MessagePayloads, MessageType } from './message.js'
import { TransportAdapter } from './transport-adapter.js' import { TransportAdapter } from './transport-adapter.js'
import { EventEmitter2, Listener } from 'eventemitter2' import { EventEmitter2, Listener } from 'eventemitter2'
export type MessageEvents = MessageType | 'connected' | 'disconnected' export type MessageEvents = MessageType | 'connected' | 'disconnected' | 'ready'
type MessageEventPayloadMap = { type MessageEventPayloadMap = {
[E in MessageEvents]: E extends keyof MessagePayloads [E in MessageEvents]: E extends keyof MessagePayloads
@ -26,11 +26,13 @@ export enum ConnectionState {
*/ */
export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> { export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
private transportAdapter: TransportAdapter | undefined private transportAdapter: TransportAdapter | undefined
private readyMessageReceived = false
private destroyOnMessageEventHandler: undefined | (() => void) private destroyOnMessageEventHandler: undefined | (() => void)
private destroyOnErrorEventHandler: undefined | (() => void) private destroyOnErrorEventHandler: undefined | (() => void)
private destroyOnCloseEventHandler: undefined | (() => void) private destroyOnCloseEventHandler: undefined | (() => void)
private destroyOnConnectedEventHandler: undefined | (() => void) private destroyOnConnectedEventHandler: undefined | (() => void)
private thisSideReady = false
private otherSideReady = false
private readyInterval: NodeJS.Timer | undefined
public sendMessage<M extends MessageType>(content: Message<M>): void { public sendMessage<M extends MessageType>(content: Message<M>): void {
if (!this.isConnected()) { if (!this.isConnected()) {
@ -59,6 +61,8 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
throw new Error('Websocket must be connected') throw new Error('Websocket must be connected')
} }
this.unbindEventsFromPreviousWebsocket() this.unbindEventsFromPreviousWebsocket()
this.thisSideReady = false
this.otherSideReady = false
this.transportAdapter = websocket this.transportAdapter = websocket
this.bindWebsocketEvents(websocket) this.bindWebsocketEvents(websocket)
@ -72,14 +76,41 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
} }
protected receiveMessage<L extends MessageType>(message: Message<L>): void { protected receiveMessage<L extends MessageType>(message: Message<L>): void {
if (message.type === MessageType.READY) { if (!this.thisSideReady) {
this.readyMessageReceived = true return
}
if (message.type === MessageType.READY_REQUEST) {
this.sendMessage({ type: MessageType.READY_ANSWER })
return
}
if (message.type === MessageType.READY_ANSWER) {
this.processReadyAnswer()
return
}
if (!this.isReady()) {
return
} }
this.emit(message.type, message) this.emit(message.type, message)
} }
private processReadyAnswer() {
this.stopSendingOfReadyRequests()
if (this.otherSideReady) {
return
}
this.otherSideReady = true
this.emit('ready')
}
private stopSendingOfReadyRequests() {
if (this.readyInterval !== undefined) {
clearInterval(this.readyInterval)
}
}
public disconnect(): void { public disconnect(): void {
this.transportAdapter?.disconnect() this.transportAdapter?.disconnect()
this.onDisconnecting()
} }
public getConnectionState(): ConnectionState { public getConnectionState(): ConnectionState {
@ -123,9 +154,11 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
if (this.transportAdapter === undefined) { if (this.transportAdapter === undefined) {
return return
} }
this.stopSendingOfReadyRequests()
this.unbindEventsFromPreviousWebsocket() this.unbindEventsFromPreviousWebsocket()
this.thisSideReady = false
this.otherSideReady = false
this.transportAdapter = undefined this.transportAdapter = undefined
this.readyMessageReceived = false
this.emit('disconnected') this.emit('disconnected')
} }
@ -137,10 +170,10 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
} }
/** /**
* Indicates if the message transporter has receives a {@link MessageType.READY ready message} yet. * Indicates if the message transporter is ready to receives messages.
*/ */
public isReady(): boolean { public isReady(): boolean {
return this.readyMessageReceived return this.thisSideReady && this.otherSideReady
} }
/** /**
@ -151,10 +184,10 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
* @return The event listener that waits for ready messages * @return The event listener that waits for ready messages
*/ */
public doAsSoonAsReady(callback: () => void): Listener { public doAsSoonAsReady(callback: () => void): Listener {
if (this.readyMessageReceived) { if (this.isReady()) {
callback() callback()
} }
return this.on(MessageType.READY, callback, { return this.on('ready', callback, {
objectify: true objectify: true
}) as Listener }) as Listener
} }
@ -175,9 +208,17 @@ export class MessageTransporter extends EventEmitter2<MessageEventPayloadMap> {
}) as Listener }) as Listener
} }
public sendReady(): void { /**
* Marks the transporter as ready for communication and starts sending of ready requests to the other side.
* This method should be called after all preparations are done and messages can be processed.
*/
public startSendingOfReadyRequests(): void {
this.thisSideReady = true
this.readyInterval = setInterval(() => {
this.sendMessage({ this.sendMessage({
type: MessageType.READY type: MessageType.READY_REQUEST
}) })
}, 100)
} }
} }

View file

@ -18,7 +18,8 @@ export enum MessageType {
REALTIME_USER_STATE_REQUEST = 'REALTIME_USER_STATE_REQUEST', REALTIME_USER_STATE_REQUEST = 'REALTIME_USER_STATE_REQUEST',
REALTIME_USER_SET_ACTIVITY = 'REALTIME_USER_SET_ACTIVITY', REALTIME_USER_SET_ACTIVITY = 'REALTIME_USER_SET_ACTIVITY',
READY = 'READY' READY_REQUEST = 'READY_REQUEST',
READY_ANSWER = 'READY_ANSWER'
} }
export interface MessagePayloads { export interface MessagePayloads {

View file

@ -70,18 +70,22 @@ export class MockedBackendTransportAdapter implements TransportAdapter {
send(value: Message<MessageType>): void { send(value: Message<MessageType>): void {
if (value.type === MessageType.NOTE_CONTENT_STATE_REQUEST) { if (value.type === MessageType.NOTE_CONTENT_STATE_REQUEST) {
new Promise(() => { setTimeout(
() =>
this.messageHandler?.({ this.messageHandler?.({
type: MessageType.NOTE_CONTENT_UPDATE, type: MessageType.NOTE_CONTENT_UPDATE,
payload: this.doc.encodeStateAsUpdate(value.payload) payload: this.doc.encodeStateAsUpdate(value.payload)
}) }),
}).catch((error: Error) => console.error(error)) 0
} else if (value.type === MessageType.READY) { )
new Promise(() => { } else if (value.type === MessageType.READY_REQUEST) {
setTimeout(
() =>
this.messageHandler?.({ this.messageHandler?.({
type: MessageType.READY type: MessageType.READY_ANSWER
}) }),
}).catch((error: Error) => console.error(error)) 0
)
} }
} }
} }

View file

@ -1,52 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
ConnectionState,
MessageTransporter
} from '../message-transporters/message-transporter.js'
import { Message, MessageType } from '../message-transporters/message.js'
/**
* Message transporter for testing purposes that redirects message to another in memory connection message transporter instance.
*/
export class InMemoryConnectionMessageTransporter extends MessageTransporter {
private otherSide: InMemoryConnectionMessageTransporter | undefined
constructor(private name: string) {
super()
}
public connect(other: InMemoryConnectionMessageTransporter): void {
this.otherSide = other
other.otherSide = this
this.onConnected()
other.onConnected()
}
public disconnect(): void {
this.onDisconnecting()
if (this.otherSide) {
this.otherSide.onDisconnecting()
this.otherSide.otherSide = undefined
this.otherSide = undefined
}
}
sendMessage(content: Message<MessageType>): void {
if (this.otherSide === undefined) {
throw new Error('Disconnected')
}
console.debug(`${this.name}`, 'Sending', content)
this.otherSide?.receiveMessage(content)
}
getConnectionState(): ConnectionState {
return this.otherSide !== undefined
? ConnectionState.CONNECTED
: ConnectionState.DISCONNECTED
}
}

View file

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { TransportAdapter } from '../message-transporters/index.js'
import { ConnectionState } from '../message-transporters/index.js'
import { Message, MessageType } from '../message-transporters/message.js'
/**
* Message transporter for testing purposes that redirects message to another in memory connection message transporter instance.
*/
export class InMemoryConnectionTransportAdapter implements TransportAdapter {
private otherSide: InMemoryConnectionTransportAdapter | undefined
private onCloseHandler: undefined | (() => void)
private onConnectedHandler: undefined | (() => void)
private onErrorHandler: undefined | (() => void)
private onMessageHandler: undefined | ((value: Message<MessageType>) => void)
constructor(private name: string) {}
getConnectionState(): ConnectionState {
return this.otherSide !== undefined
? ConnectionState.CONNECTED
: ConnectionState.DISCONNECTED
}
bindOnCloseEvent(handler: () => void): () => void {
this.onCloseHandler = handler
return () => (this.onCloseHandler = undefined)
}
bindOnConnectedEvent(handler: () => void): () => void {
this.onConnectedHandler = handler
return () => (this.onConnectedHandler = undefined)
}
bindOnErrorEvent(handler: () => void): () => void {
this.onErrorHandler = handler
return () => (this.onErrorHandler = undefined)
}
bindOnMessageEvent(
handler: (value: Message<MessageType>) => void
): () => void {
this.onMessageHandler = handler
return () => (this.onMessageHandler = undefined)
}
receiveMessage(content: Message<MessageType>): void {
this.onMessageHandler?.(content)
}
send(content: Message<MessageType>): void {
if (this.otherSide === undefined) {
throw new Error('Disconnected')
}
console.debug(`${this.name}`, 'Sending', content)
this.otherSide?.receiveMessage(content)
}
public connect(other: InMemoryConnectionTransportAdapter): void {
this.otherSide = other
other.otherSide = this
}
disconnect(): void {
this.onCloseHandler?.()
if (this.otherSide) {
this.otherSide.onCloseHandler?.()
this.otherSide.otherSide = undefined
this.otherSide = undefined
}
}
}

View file

@ -3,14 +3,22 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { MessageTransporter } from '../message-transporters/index.js'
import { Message, MessageType } from '../message-transporters/message.js' import { Message, MessageType } from '../message-transporters/message.js'
import { InMemoryConnectionMessageTransporter } from './in-memory-connection-message.transporter.js' import { InMemoryConnectionTransportAdapter } from './in-memory-connection-transport-adapter.js'
import { RealtimeDoc } from './realtime-doc.js' import { RealtimeDoc } from './realtime-doc.js'
import { YDocSyncClientAdapter } from './y-doc-sync-client-adapter.js' import { YDocSyncClientAdapter } from './y-doc-sync-client-adapter.js'
import { YDocSyncServerAdapter } from './y-doc-sync-server-adapter.js' import { YDocSyncServerAdapter } from './y-doc-sync-server-adapter.js'
import { describe, expect, it } from '@jest/globals' import { describe, expect, it, beforeAll, jest, afterAll } from '@jest/globals'
describe('y-doc-sync-adapter', () => {
beforeAll(() => {
jest.useFakeTimers()
})
afterAll(() => {
jest.useRealTimers()
})
describe('message transporter', () => {
it('server client communication', async () => { it('server client communication', async () => {
const docServer: RealtimeDoc = new RealtimeDoc('This is a test note') const docServer: RealtimeDoc = new RealtimeDoc('This is a test note')
const docClient1: RealtimeDoc = new RealtimeDoc() const docClient1: RealtimeDoc = new RealtimeDoc()
@ -21,51 +29,76 @@ describe('message transporter', () => {
const textClient2 = docClient2.getMarkdownContentChannel() const textClient2 = docClient2.getMarkdownContentChannel()
textServer.observe(() => textServer.observe(() =>
console.debug('textServer', new Date(), textServer.toString()) console.log('textServer', new Date(), textServer.toString())
) )
textClient1.observe(() => textClient1.observe(() =>
console.debug('textClient1', new Date(), textClient1.toString()) console.log('textClient1', new Date(), textClient1.toString())
) )
textClient2.observe(() => textClient2.observe(() =>
console.debug('textClient2', new Date(), textClient2.toString()) console.log('textClient2', new Date(), textClient2.toString())
) )
const transporterServerTo1 = new InMemoryConnectionMessageTransporter('s>1') const transporterAdapterServerTo1 = new InMemoryConnectionTransportAdapter(
const transporterServerTo2 = new InMemoryConnectionMessageTransporter('s>2') 's>1'
const transporterClient1 = new InMemoryConnectionMessageTransporter('1>s') )
const transporterClient2 = new InMemoryConnectionMessageTransporter('2>s') const transporterAdapterServerTo2 = new InMemoryConnectionTransportAdapter(
's>2'
)
const transporterAdapterClient1 = new InMemoryConnectionTransportAdapter(
'1>s'
)
const transporterAdapterClient2 = new InMemoryConnectionTransportAdapter(
'2>s'
)
transporterServerTo1.on(MessageType.NOTE_CONTENT_UPDATE, () => const messageTransporterServerTo1 = new MessageTransporter()
console.debug('Received NOTE_CONTENT_UPDATE from client 1 to server') 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')
) )
transporterServerTo2.on(MessageType.NOTE_CONTENT_UPDATE, () => messageTransporterServerTo2.on(MessageType.NOTE_CONTENT_UPDATE, () =>
console.debug('Received NOTE_CONTENT_UPDATE from client 2 to server') console.log('Received NOTE_CONTENT_UPDATE from client 2 to server')
) )
transporterClient1.on(MessageType.NOTE_CONTENT_UPDATE, () => messageTransporterClient1.on(MessageType.NOTE_CONTENT_UPDATE, () =>
console.debug('Received NOTE_CONTENT_UPDATE from server to client 1') console.log('Received NOTE_CONTENT_UPDATE from server to client 1')
) )
transporterClient2.on(MessageType.NOTE_CONTENT_UPDATE, () => messageTransporterClient2.on(MessageType.NOTE_CONTENT_UPDATE, () =>
console.debug('Received NOTE_CONTENT_UPDATE from server to client 2') console.log('Received NOTE_CONTENT_UPDATE from server to client 2')
) )
transporterServerTo1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => messageTransporterServerTo1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () =>
console.debug('Received NOTE_CONTENT_REQUEST from client 1 to server') console.log('Received NOTE_CONTENT_REQUEST from client 1 to server')
) )
transporterServerTo2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => messageTransporterServerTo2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () =>
console.debug('Received NOTE_CONTENT_REQUEST from client 2 to server') console.log('Received NOTE_CONTENT_REQUEST from client 2 to server')
) )
transporterClient1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => messageTransporterClient1.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () =>
console.debug('Received NOTE_CONTENT_REQUEST from server to client 1') console.log('Received NOTE_CONTENT_REQUEST from server to client 1')
) )
transporterClient2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () => messageTransporterClient2.on(MessageType.NOTE_CONTENT_STATE_REQUEST, () =>
console.debug('Received NOTE_CONTENT_REQUEST from server to client 2') console.log('Received NOTE_CONTENT_REQUEST from server to client 2')
) )
transporterClient1.on('connected', () => console.debug('1>s is connected')) messageTransporterClient1.doAsSoonAsConnected(() =>
transporterClient2.on('connected', () => console.debug('2>s is connected')) console.log('1>s is connected')
transporterServerTo1.on('connected', () =>
console.debug('s>1 is connected')
) )
transporterServerTo2.on('connected', () => messageTransporterClient2.doAsSoonAsConnected(() =>
console.debug('s>2 is connected') 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) => { docServer.on('update', (update: number[], origin: unknown) => {
@ -73,74 +106,91 @@ describe('message transporter', () => {
type: MessageType.NOTE_CONTENT_UPDATE, type: MessageType.NOTE_CONTENT_UPDATE,
payload: update payload: update
} }
if (origin !== transporterServerTo1) { if (origin !== messageTransporterServerTo1) {
console.debug('YDoc on Server updated. Sending to Client 1') console.log('YDoc on Server updated. Sending to Client 1')
transporterServerTo1.sendMessage(message) messageTransporterServerTo1.sendMessage(message)
} }
if (origin !== transporterServerTo2) { if (origin !== messageTransporterServerTo2) {
console.debug('YDoc on Server updated. Sending to Client 2') console.log('YDoc on Server updated. Sending to Client 2')
transporterServerTo2.sendMessage(message) messageTransporterServerTo2.sendMessage(message)
} }
}) })
docClient1.on('update', (update: number[], origin: unknown) => { docClient1.on('update', (update: number[], origin: unknown) => {
if (origin !== transporterClient1) { if (origin !== messageTransporterClient1) {
console.debug('YDoc on client 1 updated. Sending to Server') console.log('YDoc on client 1 updated. Sending to Server')
} }
}) })
docClient2.on('update', (update: number[], origin: unknown) => { docClient2.on('update', (update: number[], origin: unknown) => {
if (origin !== transporterClient2) { if (origin !== messageTransporterClient2) {
console.debug('YDoc on client 2 updated. Sending to Server') console.log('YDoc on client 2 updated. Sending to Server')
} }
}) })
const yDocSyncAdapter1 = new YDocSyncClientAdapter( const yDocSyncAdapter1 = new YDocSyncClientAdapter(
transporterClient1, messageTransporterClient1,
docClient1 docClient1
) )
const yDocSyncAdapter2 = new YDocSyncClientAdapter( const yDocSyncAdapter2 = new YDocSyncClientAdapter(
transporterClient2, messageTransporterClient2,
docClient2 docClient2
) )
const yDocSyncAdapterServerTo1 = new YDocSyncServerAdapter( const yDocSyncAdapterServerTo1 = new YDocSyncServerAdapter(
transporterServerTo1, messageTransporterServerTo1,
docServer, docServer,
() => true () => true
) )
const yDocSyncAdapterServerTo2 = new YDocSyncServerAdapter( const yDocSyncAdapterServerTo2 = new YDocSyncServerAdapter(
transporterServerTo2, messageTransporterServerTo2,
docServer, docServer,
() => true () => true
) )
const waitForClient1Sync = new Promise<void>((resolve) => { const waitForClient1Sync = new Promise<void>((resolve) => {
yDocSyncAdapter1.doAsSoonAsSynced(() => { yDocSyncAdapter1.doAsSoonAsSynced(() => {
console.debug('client 1 received the first sync') console.log('client 1 received the first sync')
resolve() resolve()
}) })
}) })
const waitForClient2Sync = new Promise<void>((resolve) => { const waitForClient2Sync = new Promise<void>((resolve) => {
yDocSyncAdapter2.doAsSoonAsSynced(() => { yDocSyncAdapter2.doAsSoonAsSynced(() => {
console.debug('client 2 received the first sync') console.log('client 2 received the first sync')
resolve() resolve()
}) })
}) })
const waitForServerTo11Sync = new Promise<void>((resolve) => { const waitForServerTo11Sync = new Promise<void>((resolve) => {
yDocSyncAdapterServerTo1.doAsSoonAsSynced(() => { yDocSyncAdapterServerTo1.doAsSoonAsSynced(() => {
console.debug('server 1 received the first sync') console.log('server 1 received the first sync')
resolve() resolve()
}) })
}) })
const waitForServerTo21Sync = new Promise<void>((resolve) => { const waitForServerTo21Sync = new Promise<void>((resolve) => {
yDocSyncAdapterServerTo2.doAsSoonAsSynced(() => { yDocSyncAdapterServerTo2.doAsSoonAsSynced(() => {
console.debug('server 2 received the first sync') console.log('server 2 received the first sync')
resolve() resolve()
}) })
}) })
transporterClient1.connect(transporterServerTo1) transporterAdapterClient1.connect(transporterAdapterServerTo1)
transporterClient2.connect(transporterServerTo2) transporterAdapterClient2.connect(transporterAdapterServerTo2)
messageTransporterClient1.setAdapter(transporterAdapterClient1)
messageTransporterClient2.setAdapter(transporterAdapterClient2)
messageTransporterServerTo1.setAdapter(transporterAdapterServerTo1)
messageTransporterServerTo2.setAdapter(transporterAdapterServerTo2)
messageTransporterClient1.startSendingOfReadyRequests()
messageTransporterClient2.startSendingOfReadyRequests()
messageTransporterServerTo1.startSendingOfReadyRequests()
messageTransporterServerTo2.startSendingOfReadyRequests()
jest.advanceTimersByTime(1000)
expect(messageTransporterClient1.isReady()).toBeTruthy()
expect(messageTransporterClient2.isReady()).toBeTruthy()
expect(messageTransporterServerTo1.isReady()).toBeTruthy()
expect(messageTransporterServerTo2.isReady()).toBeTruthy()
yDocSyncAdapter1.requestDocumentState() yDocSyncAdapter1.requestDocumentState()
yDocSyncAdapter2.requestDocumentState() yDocSyncAdapter2.requestDocumentState()

View file

@ -135,7 +135,7 @@ export const EditorPane: React.FC<EditorPaneProps> = ({ scrollState, onScroll, o
const mayEdit = useMayEdit() const mayEdit = useMayEdit()
useEffect(() => { useEffect(() => {
const listener = messageTransporter.doAsSoonAsConnected(() => messageTransporter.sendReady()) const listener = messageTransporter.doAsSoonAsConnected(() => messageTransporter.startSendingOfReadyRequests())
return () => { return () => {
listener.off() listener.off()
} }

View file

@ -89,9 +89,9 @@ describe('frontend websocket', () => {
it('can send messages', () => { it('can send messages', () => {
mockSocket() mockSocket()
const value: Message<MessageType> = { type: MessageType.READY } const value: Message<MessageType> = { type: MessageType.READY_REQUEST }
adapter.send(value) adapter.send(value)
expect(sendSpy).toHaveBeenCalledWith('{"type":"READY"}') expect(sendSpy).toHaveBeenCalledWith('{"type":"READY_REQUEST"}')
}) })
it('can read the connection state when open', () => { it('can read the connection state when open', () => {