mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
fix(communication): send ready event when both sides are ready
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e6b9afc686
commit
f4a1999a8b
11 changed files with 271 additions and 142 deletions
|
@ -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', () => {
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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}`,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
Loading…
Reference in a new issue