mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
feat: don't let read-only users send their cursors or selections
This was done as it may be used to distract or annoy other users either intentionally or unintentionally. Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
7636480d8a
commit
2fc89a7de5
6 changed files with 121 additions and 40 deletions
|
@ -52,6 +52,7 @@ export class RealtimeConnection {
|
||||||
this.user?.username ?? null,
|
this.user?.username ?? null,
|
||||||
this.getDisplayName(),
|
this.getDisplayName(),
|
||||||
this,
|
this,
|
||||||
|
acceptEdits,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,17 +21,20 @@ describe('realtime user status adapter', () => {
|
||||||
let clientLoggedIn2: RealtimeConnection;
|
let clientLoggedIn2: RealtimeConnection;
|
||||||
let clientGuest: RealtimeConnection;
|
let clientGuest: RealtimeConnection;
|
||||||
let clientNotReady: RealtimeConnection;
|
let clientNotReady: RealtimeConnection;
|
||||||
|
let clientDecline: RealtimeConnection;
|
||||||
|
|
||||||
let clientLoggedIn1SendMessageSpy: SendMessageSpy;
|
let clientLoggedIn1SendMessageSpy: SendMessageSpy;
|
||||||
let clientLoggedIn2SendMessageSpy: SendMessageSpy;
|
let clientLoggedIn2SendMessageSpy: SendMessageSpy;
|
||||||
let clientGuestSendMessageSpy: SendMessageSpy;
|
let clientGuestSendMessageSpy: SendMessageSpy;
|
||||||
let clientNotReadySendMessageSpy: SendMessageSpy;
|
let clientNotReadySendMessageSpy: SendMessageSpy;
|
||||||
|
let clientDeclineSendMessageSpy: SendMessageSpy;
|
||||||
|
|
||||||
let realtimeNote: RealtimeNote;
|
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';
|
||||||
|
const clientDeclineUsername = 'read.only';
|
||||||
|
|
||||||
const guestDisplayName = 'Virtuous Mockingbird';
|
const guestDisplayName = 'Virtuous Mockingbird';
|
||||||
|
|
||||||
|
@ -45,30 +48,36 @@ describe('realtime user status adapter', () => {
|
||||||
'mockedContent',
|
'mockedContent',
|
||||||
);
|
);
|
||||||
clientLoggedIn1 = new MockConnectionBuilder(realtimeNote)
|
clientLoggedIn1 = new MockConnectionBuilder(realtimeNote)
|
||||||
.withRealtimeUserStatus()
|
.withAcceptingRealtimeUserStatus()
|
||||||
.withLoggedInUser(clientLoggedIn1Username)
|
.withLoggedInUser(clientLoggedIn1Username)
|
||||||
.build();
|
.build();
|
||||||
clientLoggedIn2 = new MockConnectionBuilder(realtimeNote)
|
clientLoggedIn2 = new MockConnectionBuilder(realtimeNote)
|
||||||
.withRealtimeUserStatus()
|
.withAcceptingRealtimeUserStatus()
|
||||||
.withLoggedInUser(clientLoggedIn2Username)
|
.withLoggedInUser(clientLoggedIn2Username)
|
||||||
.build();
|
.build();
|
||||||
clientGuest = new MockConnectionBuilder(realtimeNote)
|
clientGuest = new MockConnectionBuilder(realtimeNote)
|
||||||
.withRealtimeUserStatus()
|
.withAcceptingRealtimeUserStatus()
|
||||||
.withGuestUser(guestDisplayName)
|
.withGuestUser(guestDisplayName)
|
||||||
.build();
|
.build();
|
||||||
clientNotReady = new MockConnectionBuilder(realtimeNote)
|
clientNotReady = new MockConnectionBuilder(realtimeNote)
|
||||||
.withRealtimeUserStatus()
|
.withAcceptingRealtimeUserStatus()
|
||||||
.withLoggedInUser(clientNotReadyUsername)
|
.withLoggedInUser(clientNotReadyUsername)
|
||||||
.build();
|
.build();
|
||||||
|
clientDecline = new MockConnectionBuilder(realtimeNote)
|
||||||
|
.withDecliningRealtimeUserStatus()
|
||||||
|
.withLoggedInUser(clientDeclineUsername)
|
||||||
|
.build();
|
||||||
|
|
||||||
clientLoggedIn1SendMessageSpy = spyOnSendMessage(clientLoggedIn1);
|
clientLoggedIn1SendMessageSpy = spyOnSendMessage(clientLoggedIn1);
|
||||||
clientLoggedIn2SendMessageSpy = spyOnSendMessage(clientLoggedIn2);
|
clientLoggedIn2SendMessageSpy = spyOnSendMessage(clientLoggedIn2);
|
||||||
clientGuestSendMessageSpy = spyOnSendMessage(clientGuest);
|
clientGuestSendMessageSpy = spyOnSendMessage(clientGuest);
|
||||||
clientNotReadySendMessageSpy = spyOnSendMessage(clientNotReady);
|
clientNotReadySendMessageSpy = spyOnSendMessage(clientNotReady);
|
||||||
|
clientDeclineSendMessageSpy = spyOnSendMessage(clientDecline);
|
||||||
|
|
||||||
clientLoggedIn1.getTransporter().sendReady();
|
clientLoggedIn1.getTransporter().sendReady();
|
||||||
clientLoggedIn2.getTransporter().sendReady();
|
clientLoggedIn2.getTransporter().sendReady();
|
||||||
clientGuest.getTransporter().sendReady();
|
clientGuest.getTransporter().sendReady();
|
||||||
|
clientDecline.getTransporter().sendReady();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can answer a state request', () => {
|
it('can answer a state request', () => {
|
||||||
|
@ -76,6 +85,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn1
|
clientLoggedIn1
|
||||||
.getTransporter()
|
.getTransporter()
|
||||||
|
@ -119,6 +129,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can save an cursor update', () => {
|
it('can save an cursor update', () => {
|
||||||
|
@ -126,6 +137,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
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);
|
||||||
|
@ -214,6 +226,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedMessage3,
|
expectedMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will inform other clients about removed client', () => {
|
it('will inform other clients about removed client', () => {
|
||||||
|
@ -221,6 +234,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn2.getTransporter().disconnect();
|
clientLoggedIn2.getTransporter().disconnect();
|
||||||
|
|
||||||
|
@ -278,6 +292,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedMessage3,
|
expectedMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will inform other clients about inactivity and reactivity', () => {
|
it('will inform other clients about inactivity and reactivity', () => {
|
||||||
|
@ -285,6 +300,7 @@ describe('realtime user status adapter', () => {
|
||||||
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
clientLoggedIn1
|
clientLoggedIn1
|
||||||
.getTransporter()
|
.getTransporter()
|
||||||
|
@ -371,6 +387,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedInactivityMessage3,
|
expectedInactivityMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
clientLoggedIn1
|
clientLoggedIn1
|
||||||
.getTransporter()
|
.getTransporter()
|
||||||
|
@ -391,6 +408,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedInactivityMessage3,
|
expectedInactivityMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
clientLoggedIn1
|
clientLoggedIn1
|
||||||
.getTransporter()
|
.getTransporter()
|
||||||
|
@ -477,6 +495,7 @@ describe('realtime user status adapter', () => {
|
||||||
expectedReactivityMessage3,
|
expectedReactivityMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
clientLoggedIn1
|
clientLoggedIn1
|
||||||
.getTransporter()
|
.getTransporter()
|
||||||
|
@ -497,5 +516,30 @@ describe('realtime user status adapter', () => {
|
||||||
expectedReactivityMessage3,
|
expectedReactivityMessage3,
|
||||||
);
|
);
|
||||||
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('will ignore updates from read only clients', () => {
|
||||||
|
expect(clientLoggedIn1SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
clientDecline
|
||||||
|
.getTransporter()
|
||||||
|
.emit(MessageType.REALTIME_USER_SINGLE_UPDATE, {
|
||||||
|
type: MessageType.REALTIME_USER_SINGLE_UPDATE,
|
||||||
|
payload: {
|
||||||
|
from: 0,
|
||||||
|
to: 1234,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(clientLoggedIn1SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientLoggedIn2SendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientGuestSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
|
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { MessageType, RealtimeUser } from '@hedgedoc/commons';
|
import { Message, MessageType, RealtimeUser } from '@hedgedoc/commons';
|
||||||
import { Listener } from 'eventemitter2';
|
import { Listener } from 'eventemitter2';
|
||||||
|
|
||||||
import { RealtimeConnection } from './realtime-connection';
|
import { RealtimeConnection } from './realtime-connection';
|
||||||
|
@ -19,6 +19,7 @@ export class RealtimeUserStatusAdapter {
|
||||||
username: string | null,
|
username: string | null,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
private connection: RealtimeConnection,
|
private connection: RealtimeConnection,
|
||||||
|
private acceptCursorUpdate: boolean,
|
||||||
) {
|
) {
|
||||||
this.realtimeUser = this.createInitialRealtimeUserState(
|
this.realtimeUser = this.createInitialRealtimeUserState(
|
||||||
username,
|
username,
|
||||||
|
@ -51,13 +52,14 @@ export class RealtimeUserStatusAdapter {
|
||||||
const realtimeNote = connection.getRealtimeNote();
|
const realtimeNote = connection.getRealtimeNote();
|
||||||
const transporterMessagesListener = connection.getTransporter().on(
|
const transporterMessagesListener = connection.getTransporter().on(
|
||||||
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
MessageType.REALTIME_USER_SINGLE_UPDATE,
|
||||||
(message) => {
|
(message: Message<MessageType.REALTIME_USER_SINGLE_UPDATE>) => {
|
||||||
this.realtimeUser.cursor = message.payload;
|
if (this.isAcceptingCursorUpdates()) {
|
||||||
this.sendRealtimeUserStatusUpdateEvent(connection);
|
this.realtimeUser.cursor = message.payload;
|
||||||
|
this.sendRealtimeUserStatusUpdateEvent(connection);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ objectify: true },
|
{ objectify: true },
|
||||||
) as Listener;
|
) as Listener;
|
||||||
|
|
||||||
const transporterRequestMessageListener = connection.getTransporter().on(
|
const transporterRequestMessageListener = connection.getTransporter().on(
|
||||||
MessageType.REALTIME_USER_STATE_REQUEST,
|
MessageType.REALTIME_USER_STATE_REQUEST,
|
||||||
() => {
|
() => {
|
||||||
|
@ -80,8 +82,11 @@ export class RealtimeUserStatusAdapter {
|
||||||
|
|
||||||
const realtimeUserSetActivityListener = connection.getTransporter().on(
|
const realtimeUserSetActivityListener = connection.getTransporter().on(
|
||||||
MessageType.REALTIME_USER_SET_ACTIVITY,
|
MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||||
(message) => {
|
(message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => {
|
||||||
if (this.realtimeUser.active === message.payload.active) {
|
if (
|
||||||
|
!this.isAcceptingCursorUpdates() ||
|
||||||
|
this.realtimeUser.active === message.payload.active
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.realtimeUser.active = message.payload.active;
|
this.realtimeUser.active = message.payload.active;
|
||||||
|
@ -91,7 +96,7 @@ export class RealtimeUserStatusAdapter {
|
||||||
) as Listener;
|
) as Listener;
|
||||||
|
|
||||||
connection.getTransporter().on('disconnected', () => {
|
connection.getTransporter().on('disconnected', () => {
|
||||||
transporterMessagesListener.off();
|
transporterMessagesListener?.off();
|
||||||
transporterRequestMessageListener.off();
|
transporterRequestMessageListener.off();
|
||||||
clientRemoveListener.off();
|
clientRemoveListener.off();
|
||||||
realtimeUserSetActivityListener.off();
|
realtimeUserSetActivityListener.off();
|
||||||
|
@ -106,25 +111,32 @@ export class RealtimeUserStatusAdapter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendCompleteStateToClient(client: RealtimeConnection): void {
|
private sendCompleteStateToClient(receivingClient: RealtimeConnection): void {
|
||||||
const realtimeUsers = this.collectAllConnectionsExcept(client).map(
|
const realtimeUser =
|
||||||
(client) => client.getRealtimeUserStateAdapter().realtimeUser,
|
receivingClient.getRealtimeUserStateAdapter().realtimeUser;
|
||||||
);
|
const realtimeUsers = this.collectAllConnectionsExcept(receivingClient)
|
||||||
|
.filter((client) =>
|
||||||
|
client.getRealtimeUserStateAdapter().isAcceptingCursorUpdates(),
|
||||||
|
)
|
||||||
|
.map((client) => client.getRealtimeUserStateAdapter().realtimeUser)
|
||||||
|
.filter((realtimeUser) => realtimeUser !== null);
|
||||||
|
|
||||||
client.getTransporter().sendMessage({
|
receivingClient.getTransporter().sendMessage({
|
||||||
type: MessageType.REALTIME_USER_STATE_SET,
|
type: MessageType.REALTIME_USER_STATE_SET,
|
||||||
payload: {
|
payload: {
|
||||||
users: realtimeUsers,
|
users: realtimeUsers,
|
||||||
ownUser: {
|
ownUser: {
|
||||||
displayName:
|
displayName: realtimeUser.displayName,
|
||||||
client.getRealtimeUserStateAdapter().realtimeUser.displayName,
|
styleIndex: realtimeUser.styleIndex,
|
||||||
styleIndex:
|
|
||||||
client.getRealtimeUserStateAdapter().realtimeUser.styleIndex,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isAcceptingCursorUpdates(): boolean {
|
||||||
|
return this.acceptCursorUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
private collectAllConnectionsExcept(
|
private collectAllConnectionsExcept(
|
||||||
exceptClient: RealtimeConnection,
|
exceptClient: RealtimeConnection,
|
||||||
): RealtimeConnection[] {
|
): RealtimeConnection[] {
|
||||||
|
@ -157,11 +169,13 @@ export class RealtimeUserStatusAdapter {
|
||||||
.getConnections()
|
.getConnections()
|
||||||
.map(
|
.map(
|
||||||
(connection) =>
|
(connection) =>
|
||||||
connection.getRealtimeUserStateAdapter().realtimeUser.styleIndex,
|
connection.getRealtimeUserStateAdapter().realtimeUser?.styleIndex,
|
||||||
)
|
)
|
||||||
.reduce((map, styleIndex) => {
|
.reduce((map, styleIndex) => {
|
||||||
const count = (map.get(styleIndex) ?? 0) + 1;
|
if (styleIndex !== undefined) {
|
||||||
map.set(styleIndex, count);
|
const count = (map.get(styleIndex) ?? 0) + 1;
|
||||||
|
map.set(styleIndex, count);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}, new Map<number, number>());
|
}, new Map<number, number>());
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,12 @@ import { RealtimeConnection } from '../realtime-connection';
|
||||||
import { RealtimeNote } from '../realtime-note';
|
import { RealtimeNote } from '../realtime-note';
|
||||||
import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter';
|
import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter';
|
||||||
|
|
||||||
|
enum RealtimeUserState {
|
||||||
|
WITHOUT,
|
||||||
|
WITH_READWRITE,
|
||||||
|
WITH_READONLY,
|
||||||
|
}
|
||||||
|
|
||||||
const MOCK_FALLBACK_USERNAME = 'mock';
|
const MOCK_FALLBACK_USERNAME = 'mock';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +28,8 @@ const MOCK_FALLBACK_USERNAME = 'mock';
|
||||||
export class MockConnectionBuilder {
|
export class MockConnectionBuilder {
|
||||||
private username: string | null;
|
private username: string | null;
|
||||||
private displayName: string | undefined;
|
private displayName: string | undefined;
|
||||||
private includeRealtimeUserStatus = false;
|
private includeRealtimeUserStatus: RealtimeUserState =
|
||||||
|
RealtimeUserState.WITHOUT;
|
||||||
|
|
||||||
constructor(private readonly realtimeNote: RealtimeNote) {}
|
constructor(private readonly realtimeNote: RealtimeNote) {}
|
||||||
|
|
||||||
|
@ -50,10 +57,18 @@ export class MockConnectionBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines that the connection should contain a {@link RealtimeUserStatusAdapter}.
|
* Defines that the connection should contain a {@link RealtimeUserStatusAdapter} that is accepting cursor updates.
|
||||||
*/
|
*/
|
||||||
public withRealtimeUserStatus(): this {
|
public withAcceptingRealtimeUserStatus(): this {
|
||||||
this.includeRealtimeUserStatus = true;
|
this.includeRealtimeUserStatus = RealtimeUserState.WITH_READWRITE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines that the connection should contain a {@link RealtimeUserStatusAdapter} that is declining cursor updates.
|
||||||
|
*/
|
||||||
|
public withDecliningRealtimeUserStatus(): this {
|
||||||
|
this.includeRealtimeUserStatus = RealtimeUserState.WITH_READONLY;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,11 +107,12 @@ export class MockConnectionBuilder {
|
||||||
this.realtimeNote.removeClient(connection),
|
this.realtimeNote.removeClient(connection),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.includeRealtimeUserStatus) {
|
if (this.includeRealtimeUserStatus !== RealtimeUserState.WITHOUT) {
|
||||||
realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
|
realtimeUserStateAdapter = new RealtimeUserStatusAdapter(
|
||||||
this.username ?? null,
|
this.username ?? null,
|
||||||
displayName,
|
displayName,
|
||||||
connection,
|
connection,
|
||||||
|
this.includeRealtimeUserStatus === RealtimeUserState.WITH_READWRITE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,16 +14,18 @@ import type { Listener } from 'eventemitter2'
|
||||||
*/
|
*/
|
||||||
export class SendCursorViewPlugin implements PluginValue {
|
export class SendCursorViewPlugin implements PluginValue {
|
||||||
private lastCursor: SelectionRange | undefined
|
private lastCursor: SelectionRange | undefined
|
||||||
private listener: Listener
|
private listener?: Listener
|
||||||
|
|
||||||
constructor(private view: EditorView, private messageTransporter: MessageTransporter) {
|
constructor(private view: EditorView, private messageTransporter: MessageTransporter, private mayEdit: boolean) {
|
||||||
this.listener = messageTransporter.doAsSoonAsReady(() => {
|
if (mayEdit) {
|
||||||
this.sendCursor(this.lastCursor)
|
this.listener = messageTransporter.doAsSoonAsReady(() => {
|
||||||
})
|
this.sendCursor(this.lastCursor)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.listener.off()
|
this.listener?.off()
|
||||||
}
|
}
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
update(update: ViewUpdate) {
|
||||||
|
@ -37,7 +39,8 @@ export class SendCursorViewPlugin implements PluginValue {
|
||||||
if (
|
if (
|
||||||
!this.messageTransporter.isReady() ||
|
!this.messageTransporter.isReady() ||
|
||||||
currentCursor === undefined ||
|
currentCursor === undefined ||
|
||||||
(this.lastCursor?.to === currentCursor?.to && this.lastCursor?.from === currentCursor?.from)
|
(this.lastCursor?.to === currentCursor?.to && this.lastCursor?.from === currentCursor?.from) ||
|
||||||
|
!this.mayEdit
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
import { useMayEdit } from '../../../../../hooks/common/use-may-edit'
|
||||||
import {
|
import {
|
||||||
createCursorLayer,
|
createCursorLayer,
|
||||||
createSelectionLayer,
|
createSelectionLayer,
|
||||||
|
@ -19,14 +20,16 @@ import { useMemo } from 'react'
|
||||||
* Bundles all extensions that are needed for the remote cursor display.
|
* Bundles all extensions that are needed for the remote cursor display.
|
||||||
* @return The created codemirror extensions
|
* @return The created codemirror extensions
|
||||||
*/
|
*/
|
||||||
export const useCodeMirrorRemoteCursorsExtension = (messageTransporter: MessageTransporter): Extension =>
|
export const useCodeMirrorRemoteCursorsExtension = (messageTransporter: MessageTransporter): Extension => {
|
||||||
useMemo(
|
const mayEdit = useMayEdit()
|
||||||
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
remoteCursorStateField.extension,
|
remoteCursorStateField.extension,
|
||||||
createCursorLayer(),
|
createCursorLayer(),
|
||||||
createSelectionLayer(),
|
createSelectionLayer(),
|
||||||
ViewPlugin.define((view) => new ReceiveRemoteCursorViewPlugin(view, messageTransporter)),
|
ViewPlugin.define((view) => new ReceiveRemoteCursorViewPlugin(view, messageTransporter)),
|
||||||
ViewPlugin.define((view) => new SendCursorViewPlugin(view, messageTransporter))
|
ViewPlugin.define((view) => new SendCursorViewPlugin(view, messageTransporter, mayEdit))
|
||||||
],
|
],
|
||||||
[messageTransporter]
|
[mayEdit, messageTransporter]
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue