mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-13 14:44:43 -04:00
refactor(realtime): solve circle dependencies in realtime-user-status-adapter.ts
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
8cd93918e2
commit
e3a3690b58
5 changed files with 259 additions and 191 deletions
|
@ -16,7 +16,10 @@ import { User } from '../../users/user.entity';
|
||||||
import * as NameRandomizerModule from './random-word-lists/name-randomizer';
|
import * as NameRandomizerModule from './random-word-lists/name-randomizer';
|
||||||
import { RealtimeConnection } from './realtime-connection';
|
import { RealtimeConnection } from './realtime-connection';
|
||||||
import { RealtimeNote } from './realtime-note';
|
import { RealtimeNote } from './realtime-note';
|
||||||
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';
|
import {
|
||||||
|
OtherAdapterCollector,
|
||||||
|
RealtimeUserStatusAdapter,
|
||||||
|
} from './realtime-user-status-adapter';
|
||||||
import * as RealtimeUserStatusModule from './realtime-user-status-adapter';
|
import * as RealtimeUserStatusModule from './realtime-user-status-adapter';
|
||||||
|
|
||||||
jest.mock('./random-word-lists/name-randomizer');
|
jest.mock('./random-word-lists/name-randomizer');
|
||||||
|
@ -77,18 +80,46 @@ describe('websocket connection', () => {
|
||||||
it.each([true, false])(
|
it.each([true, false])(
|
||||||
'returns the correct realtime user status with acceptEdits %s',
|
'returns the correct realtime user status with acceptEdits %s',
|
||||||
(acceptEdits) => {
|
(acceptEdits) => {
|
||||||
const realtimeUserStatus = Mock.of<RealtimeUserStatusAdapter>();
|
const realtimeUserStatus1 = Mock.of<RealtimeUserStatusAdapter>();
|
||||||
let usedConnection: RealtimeConnection | undefined = undefined;
|
const realtimeUserStatus2 = Mock.of<RealtimeUserStatusAdapter>();
|
||||||
|
const realtimeUserStatus3 = Mock.of<RealtimeUserStatusAdapter>();
|
||||||
|
|
||||||
|
const realtimeConnections = [
|
||||||
|
Mock.of<RealtimeConnection>({
|
||||||
|
getRealtimeUserStateAdapter: () => realtimeUserStatus1,
|
||||||
|
}),
|
||||||
|
Mock.of<RealtimeConnection>({
|
||||||
|
getRealtimeUserStateAdapter: () => realtimeUserStatus2,
|
||||||
|
}),
|
||||||
|
Mock.of<RealtimeConnection>({
|
||||||
|
getRealtimeUserStateAdapter: () => realtimeUserStatus3,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
jest
|
jest
|
||||||
|
.spyOn(mockedRealtimeNote, 'getConnections')
|
||||||
|
.mockImplementation(() => realtimeConnections);
|
||||||
|
|
||||||
|
const constructor = jest
|
||||||
.spyOn(RealtimeUserStatusModule, 'RealtimeUserStatusAdapter')
|
.spyOn(RealtimeUserStatusModule, 'RealtimeUserStatusAdapter')
|
||||||
.mockImplementation(
|
.mockImplementation(
|
||||||
(username, displayName, connection, acceptCursorUpdateProvider) => {
|
(
|
||||||
|
username,
|
||||||
|
displayName,
|
||||||
|
otherAdapterCollector: OtherAdapterCollector,
|
||||||
|
messageTransporter,
|
||||||
|
acceptCursorUpdateProvider,
|
||||||
|
) => {
|
||||||
expect(username).toBe(mockedUserName);
|
expect(username).toBe(mockedUserName);
|
||||||
expect(displayName).toBe(mockedDisplayName);
|
expect(displayName).toBe(mockedDisplayName);
|
||||||
usedConnection = connection;
|
expect(otherAdapterCollector()).toStrictEqual([
|
||||||
|
realtimeUserStatus1,
|
||||||
|
realtimeUserStatus2,
|
||||||
|
realtimeUserStatus3,
|
||||||
|
]);
|
||||||
|
expect(messageTransporter).toBe(mockedMessageTransporter);
|
||||||
expect(acceptCursorUpdateProvider()).toBe(acceptEdits);
|
expect(acceptCursorUpdateProvider()).toBe(acceptEdits);
|
||||||
return realtimeUserStatus;
|
return realtimeUserStatus1;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -99,8 +130,14 @@ describe('websocket connection', () => {
|
||||||
acceptEdits,
|
acceptEdits,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(usedConnection).toBe(sut);
|
expect(constructor).toHaveBeenCalledWith(
|
||||||
expect(sut.getRealtimeUserStateAdapter()).toBe(realtimeUserStatus);
|
mockedUserName,
|
||||||
|
mockedDisplayName,
|
||||||
|
expect.anything(),
|
||||||
|
mockedMessageTransporter,
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
expect(sut.getRealtimeUserStateAdapter()).toBe(realtimeUserStatus1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,11 @@ export class RealtimeConnection {
|
||||||
this.realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
|
this.realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
|
||||||
this.user?.username ?? null,
|
this.user?.username ?? null,
|
||||||
this.getDisplayName(),
|
this.getDisplayName(),
|
||||||
this,
|
() =>
|
||||||
|
this.realtimeNote
|
||||||
|
.getConnections()
|
||||||
|
.map((connection) => connection.getRealtimeUserStateAdapter()),
|
||||||
|
this.getTransporter(),
|
||||||
() => acceptEdits,
|
() => acceptEdits,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,25 +3,25 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { Message, MessageTransporter, MessageType } from '@hedgedoc/commons';
|
import {
|
||||||
import { Mock } from 'ts-mockery';
|
Message,
|
||||||
|
MessageType,
|
||||||
|
MockedBackendMessageTransporter,
|
||||||
|
} from '@hedgedoc/commons';
|
||||||
|
|
||||||
import { Note } from '../../notes/note.entity';
|
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';
|
||||||
import { RealtimeConnection } from './realtime-connection';
|
|
||||||
import { RealtimeNote } from './realtime-note';
|
|
||||||
import { MockConnectionBuilder } from './test-utils/mock-connection';
|
|
||||||
|
|
||||||
type SendMessageSpy = jest.SpyInstance<
|
type SendMessageSpy = jest.SpyInstance<
|
||||||
void,
|
void,
|
||||||
[Required<MessageTransporter['sendMessage']>]
|
[Required<MockedBackendMessageTransporter['sendMessage']>]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
describe('realtime user status adapter', () => {
|
describe('realtime user status adapter', () => {
|
||||||
let clientLoggedIn1: RealtimeConnection;
|
let clientLoggedIn1: RealtimeUserStatusAdapter | undefined;
|
||||||
let clientLoggedIn2: RealtimeConnection;
|
let clientLoggedIn2: RealtimeUserStatusAdapter | undefined;
|
||||||
let clientGuest: RealtimeConnection;
|
let clientGuest: RealtimeUserStatusAdapter | undefined;
|
||||||
let clientNotReady: RealtimeConnection;
|
let clientNotReady: RealtimeUserStatusAdapter | undefined;
|
||||||
let clientDecline: RealtimeConnection;
|
let clientDecline: RealtimeUserStatusAdapter | undefined;
|
||||||
|
|
||||||
let clientLoggedIn1SendMessageSpy: SendMessageSpy;
|
let clientLoggedIn1SendMessageSpy: SendMessageSpy;
|
||||||
let clientLoggedIn2SendMessageSpy: SendMessageSpy;
|
let clientLoggedIn2SendMessageSpy: SendMessageSpy;
|
||||||
|
@ -29,8 +29,6 @@ describe('realtime user status adapter', () => {
|
||||||
let clientNotReadySendMessageSpy: SendMessageSpy;
|
let clientNotReadySendMessageSpy: SendMessageSpy;
|
||||||
let clientDeclineSendMessageSpy: SendMessageSpy;
|
let clientDeclineSendMessageSpy: SendMessageSpy;
|
||||||
|
|
||||||
let realtimeNote: RealtimeNote;
|
|
||||||
|
|
||||||
const clientLoggedIn1Username = 'logged.in1';
|
const clientLoggedIn1Username = 'logged.in1';
|
||||||
const clientLoggedIn2Username = 'logged.in2';
|
const clientLoggedIn2Username = 'logged.in2';
|
||||||
const clientNotReadyUsername = 'not.ready';
|
const clientNotReadyUsername = 'not.ready';
|
||||||
|
@ -38,46 +36,96 @@ describe('realtime user status adapter', () => {
|
||||||
|
|
||||||
const guestDisplayName = 'Virtuous Mockingbird';
|
const guestDisplayName = 'Virtuous Mockingbird';
|
||||||
|
|
||||||
function spyOnSendMessage(connection: RealtimeConnection): jest.SpyInstance {
|
let messageTransporterLoggedIn1: MockedBackendMessageTransporter;
|
||||||
return jest.spyOn(connection.getTransporter(), 'sendMessage');
|
let messageTransporterLoggedIn2: MockedBackendMessageTransporter;
|
||||||
}
|
let messageTransporterGuest: MockedBackendMessageTransporter;
|
||||||
|
let messageTransporterNotReady: MockedBackendMessageTransporter;
|
||||||
|
let messageTransporterDecline: MockedBackendMessageTransporter;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
realtimeNote = new RealtimeNote(
|
clientLoggedIn1 = undefined;
|
||||||
Mock.of<Note>({ id: 9876 }),
|
clientLoggedIn2 = undefined;
|
||||||
'mockedContent',
|
clientGuest = undefined;
|
||||||
|
clientNotReady = undefined;
|
||||||
|
clientDecline = undefined;
|
||||||
|
|
||||||
|
messageTransporterLoggedIn1 = new MockedBackendMessageTransporter('');
|
||||||
|
messageTransporterLoggedIn2 = new MockedBackendMessageTransporter('');
|
||||||
|
messageTransporterGuest = new MockedBackendMessageTransporter('');
|
||||||
|
messageTransporterNotReady = new MockedBackendMessageTransporter('');
|
||||||
|
messageTransporterDecline = new MockedBackendMessageTransporter('');
|
||||||
|
|
||||||
|
function otherAdapterCollector(): RealtimeUserStatusAdapter[] {
|
||||||
|
return [
|
||||||
|
clientLoggedIn1,
|
||||||
|
clientLoggedIn2,
|
||||||
|
clientGuest,
|
||||||
|
clientNotReady,
|
||||||
|
clientDecline,
|
||||||
|
].filter((value) => value !== undefined) as RealtimeUserStatusAdapter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
clientLoggedIn1 = new RealtimeUserStatusAdapter(
|
||||||
|
clientLoggedIn1Username,
|
||||||
|
clientLoggedIn1Username,
|
||||||
|
otherAdapterCollector,
|
||||||
|
messageTransporterLoggedIn1,
|
||||||
|
() => true,
|
||||||
|
);
|
||||||
|
clientLoggedIn2 = new RealtimeUserStatusAdapter(
|
||||||
|
clientLoggedIn2Username,
|
||||||
|
clientLoggedIn2Username,
|
||||||
|
otherAdapterCollector,
|
||||||
|
messageTransporterLoggedIn2,
|
||||||
|
() => true,
|
||||||
|
);
|
||||||
|
clientGuest = new RealtimeUserStatusAdapter(
|
||||||
|
null,
|
||||||
|
guestDisplayName,
|
||||||
|
otherAdapterCollector,
|
||||||
|
messageTransporterGuest,
|
||||||
|
() => true,
|
||||||
|
);
|
||||||
|
clientNotReady = new RealtimeUserStatusAdapter(
|
||||||
|
clientNotReadyUsername,
|
||||||
|
clientNotReadyUsername,
|
||||||
|
otherAdapterCollector,
|
||||||
|
messageTransporterNotReady,
|
||||||
|
() => true,
|
||||||
|
);
|
||||||
|
clientDecline = new RealtimeUserStatusAdapter(
|
||||||
|
clientDeclineUsername,
|
||||||
|
clientDeclineUsername,
|
||||||
|
otherAdapterCollector,
|
||||||
|
messageTransporterDecline,
|
||||||
|
() => false,
|
||||||
);
|
);
|
||||||
clientLoggedIn1 = new MockConnectionBuilder(realtimeNote)
|
|
||||||
.withAcceptingRealtimeUserStatus()
|
|
||||||
.withLoggedInUser(clientLoggedIn1Username)
|
|
||||||
.build();
|
|
||||||
clientLoggedIn2 = new MockConnectionBuilder(realtimeNote)
|
|
||||||
.withAcceptingRealtimeUserStatus()
|
|
||||||
.withLoggedInUser(clientLoggedIn2Username)
|
|
||||||
.build();
|
|
||||||
clientGuest = new MockConnectionBuilder(realtimeNote)
|
|
||||||
.withAcceptingRealtimeUserStatus()
|
|
||||||
.withGuestUser(guestDisplayName)
|
|
||||||
.build();
|
|
||||||
clientNotReady = new MockConnectionBuilder(realtimeNote)
|
|
||||||
.withAcceptingRealtimeUserStatus()
|
|
||||||
.withLoggedInUser(clientNotReadyUsername)
|
|
||||||
.build();
|
|
||||||
clientDecline = new MockConnectionBuilder(realtimeNote)
|
|
||||||
.withDecliningRealtimeUserStatus()
|
|
||||||
.withLoggedInUser(clientDeclineUsername)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
clientLoggedIn1SendMessageSpy = spyOnSendMessage(clientLoggedIn1);
|
clientLoggedIn1SendMessageSpy = jest.spyOn(
|
||||||
clientLoggedIn2SendMessageSpy = spyOnSendMessage(clientLoggedIn2);
|
messageTransporterLoggedIn1,
|
||||||
clientGuestSendMessageSpy = spyOnSendMessage(clientGuest);
|
'sendMessage',
|
||||||
clientNotReadySendMessageSpy = spyOnSendMessage(clientNotReady);
|
);
|
||||||
clientDeclineSendMessageSpy = spyOnSendMessage(clientDecline);
|
clientLoggedIn2SendMessageSpy = jest.spyOn(
|
||||||
|
messageTransporterLoggedIn2,
|
||||||
|
'sendMessage',
|
||||||
|
);
|
||||||
|
clientGuestSendMessageSpy = jest.spyOn(
|
||||||
|
messageTransporterGuest,
|
||||||
|
'sendMessage',
|
||||||
|
);
|
||||||
|
clientNotReadySendMessageSpy = jest.spyOn(
|
||||||
|
messageTransporterNotReady,
|
||||||
|
'sendMessage',
|
||||||
|
);
|
||||||
|
clientDeclineSendMessageSpy = jest.spyOn(
|
||||||
|
messageTransporterDecline,
|
||||||
|
'sendMessage',
|
||||||
|
);
|
||||||
|
|
||||||
clientLoggedIn1.getTransporter().sendReady();
|
messageTransporterLoggedIn1.sendReady();
|
||||||
clientLoggedIn2.getTransporter().sendReady();
|
messageTransporterLoggedIn2.sendReady();
|
||||||
clientGuest.getTransporter().sendReady();
|
messageTransporterGuest.sendReady();
|
||||||
clientDecline.getTransporter().sendReady();
|
messageTransporterDecline.sendReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can answer a state request', () => {
|
it('can answer a state request', () => {
|
||||||
|
@ -87,9 +135,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_STATE_REQUEST);
|
||||||
.getTransporter()
|
|
||||||
.emit(MessageType.REALTIME_USER_STATE_REQUEST);
|
|
||||||
|
|
||||||
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
|
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
|
||||||
type: MessageType.REALTIME_USER_STATE_SET,
|
type: MessageType.REALTIME_USER_STATE_SET,
|
||||||
|
@ -149,9 +195,7 @@ describe('realtime user status adapter', () => {
|
||||||
const newFrom = Math.floor(Math.random() * 100);
|
const newFrom = Math.floor(Math.random() * 100);
|
||||||
const newTo = Math.floor(Math.random() * 100);
|
const newTo = Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SINGLE_UPDATE, {
|
||||||
.getTransporter()
|
|
||||||
.emit(MessageType.REALTIME_USER_SINGLE_UPDATE, {
|
|
||||||
type: MessageType.REALTIME_USER_SINGLE_UPDATE,
|
type: MessageType.REALTIME_USER_SINGLE_UPDATE,
|
||||||
payload: {
|
payload: {
|
||||||
from: newFrom,
|
from: newFrom,
|
||||||
|
@ -302,7 +346,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn2.getTransporter().disconnect();
|
messageTransporterLoggedIn2.disconnect();
|
||||||
|
|
||||||
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
|
const expectedMessage1: Message<MessageType.REALTIME_USER_STATE_SET> = {
|
||||||
type: MessageType.REALTIME_USER_STATE_SET,
|
type: MessageType.REALTIME_USER_STATE_SET,
|
||||||
|
@ -417,9 +461,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
||||||
.getTransporter()
|
|
||||||
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
|
||||||
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
payload: {
|
payload: {
|
||||||
active: false,
|
active: false,
|
||||||
|
@ -564,9 +606,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedInactivityMessage5,
|
expectedInactivityMessage5,
|
||||||
);
|
);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
||||||
.getTransporter()
|
|
||||||
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
|
||||||
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
payload: {
|
payload: {
|
||||||
active: false,
|
active: false,
|
||||||
|
@ -588,9 +628,14 @@ describe('realtime user status adapter', () => {
|
||||||
expectedInactivityMessage5,
|
expectedInactivityMessage5,
|
||||||
);
|
);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
||||||
.getTransporter()
|
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
payload: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
||||||
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
payload: {
|
payload: {
|
||||||
active: true,
|
active: true,
|
||||||
|
@ -735,9 +780,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedReactivityMessage5,
|
expectedReactivityMessage5,
|
||||||
);
|
);
|
||||||
|
|
||||||
clientLoggedIn1
|
messageTransporterLoggedIn1.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
||||||
.getTransporter()
|
|
||||||
.emit(MessageType.REALTIME_USER_SET_ACTIVITY, {
|
|
||||||
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
payload: {
|
payload: {
|
||||||
active: true,
|
active: true,
|
||||||
|
|
|
@ -3,11 +3,15 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { Message, MessageType, RealtimeUser } from '@hedgedoc/commons';
|
import {
|
||||||
|
Message,
|
||||||
|
MessageTransporter,
|
||||||
|
MessageType,
|
||||||
|
RealtimeUser,
|
||||||
|
} from '@hedgedoc/commons';
|
||||||
import { Listener } from 'eventemitter2';
|
import { Listener } from 'eventemitter2';
|
||||||
|
|
||||||
import { RealtimeConnection } from './realtime-connection';
|
export type OtherAdapterCollector = () => RealtimeUserStatusAdapter[];
|
||||||
import { RealtimeNote } from './realtime-note';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the current realtime status of a specific client and sends updates of changes to other clients.
|
* Saves the current realtime status of a specific client and sends updates of changes to other clients.
|
||||||
|
@ -16,30 +20,23 @@ export class RealtimeUserStatusAdapter {
|
||||||
private readonly realtimeUser: RealtimeUser;
|
private readonly realtimeUser: RealtimeUser;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
username: string | null,
|
private readonly username: string | null,
|
||||||
displayName: string,
|
private readonly displayName: string,
|
||||||
private connection: RealtimeConnection,
|
private collectOtherAdapters: OtherAdapterCollector,
|
||||||
|
private messageTransporter: MessageTransporter,
|
||||||
private acceptCursorUpdateProvider: () => boolean,
|
private acceptCursorUpdateProvider: () => boolean,
|
||||||
) {
|
) {
|
||||||
this.realtimeUser = this.createInitialRealtimeUserState(
|
this.realtimeUser = this.createInitialRealtimeUserState();
|
||||||
username,
|
this.bindRealtimeUserStateEvents();
|
||||||
displayName,
|
|
||||||
connection.getRealtimeNote(),
|
|
||||||
);
|
|
||||||
this.bindRealtimeUserStateEvents(connection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createInitialRealtimeUserState(
|
private createInitialRealtimeUserState(): RealtimeUser {
|
||||||
username: string | null,
|
|
||||||
displayName: string,
|
|
||||||
realtimeNote: RealtimeNote,
|
|
||||||
): RealtimeUser {
|
|
||||||
return {
|
return {
|
||||||
username: username,
|
username: this.username,
|
||||||
displayName: displayName,
|
displayName: this.displayName,
|
||||||
active: true,
|
active: true,
|
||||||
styleIndex: this.findLeastUsedStyleIndex(
|
styleIndex: this.findLeastUsedStyleIndex(
|
||||||
this.createStyleIndexToCountMap(realtimeNote),
|
this.createStyleIndexToCountMap(),
|
||||||
),
|
),
|
||||||
cursor: !this.acceptCursorUpdateProvider()
|
cursor: !this.acceptCursorUpdateProvider()
|
||||||
? null
|
? null
|
||||||
|
@ -50,51 +47,55 @@ export class RealtimeUserStatusAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private bindRealtimeUserStateEvents(connection: RealtimeConnection): void {
|
private bindRealtimeUserStateEvents(): void {
|
||||||
const realtimeNote = connection.getRealtimeNote();
|
const transporterMessagesListener = this.messageTransporter.on(
|
||||||
const transporterMessagesListener = connection.getTransporter().on(
|
|
||||||
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
||||||
(message: Message<MessageType.REALTIME_USER_SINGLE_UPDATE>) => {
|
(message: Message<MessageType.REALTIME_USER_SINGLE_UPDATE>) => {
|
||||||
this.realtimeUser.cursor = this.acceptCursorUpdateProvider()
|
this.realtimeUser.cursor = this.acceptCursorUpdateProvider()
|
||||||
? message.payload
|
? message.payload
|
||||||
: null;
|
: null;
|
||||||
this.sendRealtimeUserStatusUpdateEvent(connection);
|
this.collectOtherAdapters()
|
||||||
},
|
.filter((adapter) => adapter !== this)
|
||||||
{ objectify: true },
|
.forEach((adapter) => adapter.sendCompleteStateToClient());
|
||||||
) as Listener;
|
|
||||||
const transporterRequestMessageListener = connection.getTransporter().on(
|
|
||||||
MessageType.REALTIME_USER_STATE_REQUEST,
|
|
||||||
() => {
|
|
||||||
this.sendCompleteStateToClient(connection);
|
|
||||||
},
|
},
|
||||||
{ objectify: true },
|
{ objectify: true },
|
||||||
) as Listener;
|
) as Listener;
|
||||||
|
|
||||||
const clientRemoveListener = realtimeNote.on(
|
const transporterRequestMessageListener = this.messageTransporter.on(
|
||||||
'clientRemoved',
|
MessageType.REALTIME_USER_STATE_REQUEST,
|
||||||
(client: RealtimeConnection) => {
|
() => {
|
||||||
if (client === connection) {
|
this.sendCompleteStateToClient();
|
||||||
this.sendRealtimeUserStatusUpdateEvent(connection);
|
},
|
||||||
}
|
{ objectify: true },
|
||||||
|
) as Listener;
|
||||||
|
|
||||||
|
const clientRemoveListener = this.messageTransporter.on(
|
||||||
|
'disconnected',
|
||||||
|
() => {
|
||||||
|
this.collectOtherAdapters()
|
||||||
|
.filter((adapter) => adapter !== this)
|
||||||
|
.forEach((adapter) => adapter.sendCompleteStateToClient());
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
objectify: true,
|
objectify: true,
|
||||||
},
|
},
|
||||||
) as Listener;
|
) as Listener;
|
||||||
|
|
||||||
const realtimeUserSetActivityListener = connection.getTransporter().on(
|
const realtimeUserSetActivityListener = this.messageTransporter.on(
|
||||||
MessageType.REALTIME_USER_SET_ACTIVITY,
|
MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
(message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => {
|
(message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => {
|
||||||
if (this.realtimeUser.active === message.payload.active) {
|
if (this.realtimeUser.active === message.payload.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.realtimeUser.active = message.payload.active;
|
this.realtimeUser.active = message.payload.active;
|
||||||
this.sendRealtimeUserStatusUpdateEvent(connection);
|
this.collectOtherAdapters()
|
||||||
|
.filter((adapter) => adapter !== this)
|
||||||
|
.forEach((adapter) => adapter.sendCompleteStateToClient());
|
||||||
},
|
},
|
||||||
{ objectify: true },
|
{ objectify: true },
|
||||||
) as Listener;
|
) as Listener;
|
||||||
|
|
||||||
connection.getTransporter().on('disconnected', () => {
|
this.messageTransporter.on('disconnected', () => {
|
||||||
transporterMessagesListener?.off();
|
transporterMessagesListener?.off();
|
||||||
transporterRequestMessageListener.off();
|
transporterRequestMessageListener.off();
|
||||||
clientRemoveListener.off();
|
clientRemoveListener.off();
|
||||||
|
@ -102,45 +103,31 @@ export class RealtimeUserStatusAdapter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendRealtimeUserStatusUpdateEvent(
|
private getSendableState(): RealtimeUser | undefined {
|
||||||
exceptClient: RealtimeConnection,
|
return this.messageTransporter.isReady() ? this.realtimeUser : undefined;
|
||||||
): void {
|
|
||||||
this.collectAllConnectionsExcept(exceptClient).forEach(
|
|
||||||
this.sendCompleteStateToClient.bind(this),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendCompleteStateToClient(receivingClient: RealtimeConnection): void {
|
public sendCompleteStateToClient(): void {
|
||||||
const realtimeUser =
|
if (!this.messageTransporter.isReady()) {
|
||||||
receivingClient.getRealtimeUserStateAdapter().realtimeUser;
|
return;
|
||||||
const realtimeUsers = this.collectAllConnectionsExcept(receivingClient)
|
}
|
||||||
.map((client) => client.getRealtimeUserStateAdapter().realtimeUser)
|
const realtimeUsers = this.collectOtherAdapters()
|
||||||
.filter((realtimeUser) => realtimeUser !== null);
|
.filter((adapter) => adapter !== this)
|
||||||
|
.map((adapter) => adapter.getSendableState())
|
||||||
|
.filter((value) => value !== undefined) as RealtimeUser[];
|
||||||
|
|
||||||
receivingClient.getTransporter().sendMessage({
|
this.messageTransporter.sendMessage({
|
||||||
type: MessageType.REALTIME_USER_STATE_SET,
|
type: MessageType.REALTIME_USER_STATE_SET,
|
||||||
payload: {
|
payload: {
|
||||||
users: realtimeUsers,
|
users: realtimeUsers,
|
||||||
ownUser: {
|
ownUser: {
|
||||||
displayName: realtimeUser.displayName,
|
displayName: this.realtimeUser.displayName,
|
||||||
styleIndex: realtimeUser.styleIndex,
|
styleIndex: this.realtimeUser.styleIndex,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private collectAllConnectionsExcept(
|
|
||||||
exceptClient: RealtimeConnection,
|
|
||||||
): RealtimeConnection[] {
|
|
||||||
return this.connection
|
|
||||||
.getRealtimeNote()
|
|
||||||
.getConnections()
|
|
||||||
.filter(
|
|
||||||
(client) =>
|
|
||||||
client !== exceptClient && client.getTransporter().isReady(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private findLeastUsedStyleIndex(map: Map<number, number>): number {
|
private findLeastUsedStyleIndex(map: Map<number, number>): number {
|
||||||
let leastUsedStyleIndex = 0;
|
let leastUsedStyleIndex = 0;
|
||||||
let leastUsedStyleIndexCount = map.get(0) ?? 0;
|
let leastUsedStyleIndexCount = map.get(0) ?? 0;
|
||||||
|
@ -154,15 +141,9 @@ export class RealtimeUserStatusAdapter {
|
||||||
return leastUsedStyleIndex;
|
return leastUsedStyleIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createStyleIndexToCountMap(
|
private createStyleIndexToCountMap(): Map<number, number> {
|
||||||
realtimeNote: RealtimeNote,
|
return this.collectOtherAdapters()
|
||||||
): Map<number, number> {
|
.map((adapter) => adapter.realtimeUser.styleIndex)
|
||||||
return realtimeNote
|
|
||||||
.getConnections()
|
|
||||||
.map(
|
|
||||||
(connection) =>
|
|
||||||
connection.getRealtimeUserStateAdapter().realtimeUser?.styleIndex,
|
|
||||||
)
|
|
||||||
.reduce((map, styleIndex) => {
|
.reduce((map, styleIndex) => {
|
||||||
if (styleIndex !== undefined) {
|
if (styleIndex !== undefined) {
|
||||||
const count = (map.get(styleIndex) ?? 0) + 1;
|
const count = (map.get(styleIndex) ?? 0) + 1;
|
||||||
|
|
|
@ -82,8 +82,21 @@ export class MockConnectionBuilder {
|
||||||
const displayName = this.deriveDisplayName();
|
const displayName = this.deriveDisplayName();
|
||||||
|
|
||||||
const transporter = new MockedBackendMessageTransporter('');
|
const transporter = new MockedBackendMessageTransporter('');
|
||||||
let realtimeUserStateAdapter: RealtimeUserStatusAdapter =
|
const realtimeUserStateAdapter: RealtimeUserStatusAdapter =
|
||||||
Mock.of<RealtimeUserStatusAdapter>({});
|
this.includeRealtimeUserStatus === RealtimeUserState.WITHOUT
|
||||||
|
? Mock.of<RealtimeUserStatusAdapter>({})
|
||||||
|
: new RealtimeUserStatusAdapter(
|
||||||
|
this.username ?? null,
|
||||||
|
displayName,
|
||||||
|
() =>
|
||||||
|
this.realtimeNote
|
||||||
|
.getConnections()
|
||||||
|
.map((connection) => connection.getRealtimeUserStateAdapter()),
|
||||||
|
transporter,
|
||||||
|
() =>
|
||||||
|
this.includeRealtimeUserStatus ===
|
||||||
|
RealtimeUserState.WITH_READWRITE,
|
||||||
|
);
|
||||||
|
|
||||||
const mockUser =
|
const mockUser =
|
||||||
this.username === null
|
this.username === null
|
||||||
|
@ -107,16 +120,6 @@ export class MockConnectionBuilder {
|
||||||
this.realtimeNote.removeClient(connection),
|
this.realtimeNote.removeClient(connection),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.includeRealtimeUserStatus !== RealtimeUserState.WITHOUT) {
|
|
||||||
realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
|
|
||||||
this.username ?? null,
|
|
||||||
displayName,
|
|
||||||
connection,
|
|
||||||
() =>
|
|
||||||
this.includeRealtimeUserStatus === RealtimeUserState.WITH_READWRITE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.realtimeNote.addClient(connection);
|
this.realtimeNote.addClient(connection);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue