fix(realtime): allow realtime user status updates from users that have read-only access

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-05-08 17:23:28 +02:00
parent 46330563fa
commit d29e840bc6
4 changed files with 276 additions and 56 deletions

View file

@ -119,6 +119,13 @@ describe('realtime user status adapter', () => {
username: null, username: null,
displayName: guestDisplayName, displayName: guestDisplayName,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
], ],
}, },
}; };
@ -180,6 +187,13 @@ describe('realtime user status adapter', () => {
username: null, username: null,
displayName: guestDisplayName, displayName: guestDisplayName,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
], ],
}, },
}; };
@ -212,6 +226,55 @@ describe('realtime user status adapter', () => {
username: clientLoggedIn2Username, username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username, displayName: clientLoggedIn2Username,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
],
},
};
const expectedMessage5: Message<MessageType.REALTIME_USER_STATE_SET> = {
type: MessageType.REALTIME_USER_STATE_SET,
payload: {
ownUser: {
displayName: clientDeclineUsername,
styleIndex: 4,
},
users: [
{
active: true,
cursor: {
from: newFrom,
to: newTo,
},
styleIndex: 0,
username: clientLoggedIn1Username,
displayName: clientLoggedIn1Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
styleIndex: 1,
username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
displayName: guestDisplayName,
styleIndex: 2,
username: null,
},
], ],
}, },
}; };
@ -226,7 +289,10 @@ describe('realtime user status adapter', () => {
expectedMessage3, expectedMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
1,
expectedMessage5,
);
}); });
it('will inform other clients about removed client', () => { it('will inform other clients about removed client', () => {
@ -256,6 +322,13 @@ describe('realtime user status adapter', () => {
username: null, username: null,
displayName: guestDisplayName, displayName: guestDisplayName,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
], ],
}, },
}; };
@ -278,6 +351,45 @@ describe('realtime user status adapter', () => {
username: clientLoggedIn1Username, username: clientLoggedIn1Username,
displayName: clientLoggedIn1Username, displayName: clientLoggedIn1Username,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
],
},
};
const expectedMessage5: Message<MessageType.REALTIME_USER_STATE_SET> = {
type: MessageType.REALTIME_USER_STATE_SET,
payload: {
ownUser: {
displayName: clientDeclineUsername,
styleIndex: 4,
},
users: [
{
active: true,
cursor: {
from: 0,
to: 0,
},
styleIndex: 0,
username: clientLoggedIn1Username,
displayName: clientLoggedIn1Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
displayName: guestDisplayName,
styleIndex: 2,
username: null,
},
], ],
}, },
}; };
@ -292,7 +404,10 @@ describe('realtime user status adapter', () => {
expectedMessage3, expectedMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
1,
expectedMessage5,
);
}); });
it('will inform other clients about inactivity and reactivity', () => { it('will inform other clients about inactivity and reactivity', () => {
@ -338,7 +453,14 @@ describe('realtime user status adapter', () => {
}, },
styleIndex: 2, styleIndex: 2,
username: null, username: null,
displayName: 'Virtuous Mockingbird', displayName: guestDisplayName,
},
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
}, },
], ],
}, },
@ -350,7 +472,7 @@ describe('realtime user status adapter', () => {
payload: { payload: {
ownUser: { ownUser: {
styleIndex: 2, styleIndex: 2,
displayName: 'Virtuous Mockingbird', displayName: guestDisplayName,
}, },
users: [ users: [
{ {
@ -373,6 +495,56 @@ describe('realtime user status adapter', () => {
username: clientLoggedIn2Username, username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username, displayName: clientLoggedIn2Username,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
],
},
};
const expectedInactivityMessage5: Message<MessageType.REALTIME_USER_STATE_SET> =
{
type: MessageType.REALTIME_USER_STATE_SET,
payload: {
ownUser: {
styleIndex: 4,
displayName: clientDeclineUsername,
},
users: [
{
active: false,
cursor: {
from: 0,
to: 0,
},
styleIndex: 0,
username: clientLoggedIn1Username,
displayName: clientLoggedIn1Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
styleIndex: 1,
username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
displayName: guestDisplayName,
styleIndex: 2,
username: null,
},
], ],
}, },
}; };
@ -387,7 +559,10 @@ describe('realtime user status adapter', () => {
expectedInactivityMessage3, expectedInactivityMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
1,
expectedInactivityMessage5,
);
clientLoggedIn1 clientLoggedIn1
.getTransporter() .getTransporter()
@ -408,7 +583,10 @@ describe('realtime user status adapter', () => {
expectedInactivityMessage3, expectedInactivityMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(1); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
1,
expectedInactivityMessage5,
);
clientLoggedIn1 clientLoggedIn1
.getTransporter() .getTransporter()
@ -446,7 +624,14 @@ describe('realtime user status adapter', () => {
}, },
styleIndex: 2, styleIndex: 2,
username: null, username: null,
displayName: 'Virtuous Mockingbird', displayName: guestDisplayName,
},
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
}, },
], ],
}, },
@ -458,7 +643,7 @@ describe('realtime user status adapter', () => {
payload: { payload: {
ownUser: { ownUser: {
styleIndex: 2, styleIndex: 2,
displayName: 'Virtuous Mockingbird', displayName: guestDisplayName,
}, },
users: [ users: [
{ {
@ -481,6 +666,56 @@ describe('realtime user status adapter', () => {
username: clientLoggedIn2Username, username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username, displayName: clientLoggedIn2Username,
}, },
{
active: true,
cursor: null,
displayName: clientDeclineUsername,
styleIndex: 4,
username: clientDeclineUsername,
},
],
},
};
const expectedReactivityMessage5: Message<MessageType.REALTIME_USER_STATE_SET> =
{
type: MessageType.REALTIME_USER_STATE_SET,
payload: {
ownUser: {
styleIndex: 4,
displayName: clientDeclineUsername,
},
users: [
{
active: true,
cursor: {
from: 0,
to: 0,
},
styleIndex: 0,
username: clientLoggedIn1Username,
displayName: clientLoggedIn1Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
styleIndex: 1,
username: clientLoggedIn2Username,
displayName: clientLoggedIn2Username,
},
{
active: true,
cursor: {
from: 0,
to: 0,
},
displayName: guestDisplayName,
styleIndex: 2,
username: null,
},
], ],
}, },
}; };
@ -495,7 +730,10 @@ describe('realtime user status adapter', () => {
expectedReactivityMessage3, expectedReactivityMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(2); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
1,
expectedReactivityMessage5,
);
clientLoggedIn1 clientLoggedIn1
.getTransporter() .getTransporter()
@ -516,30 +754,9 @@ describe('realtime user status adapter', () => {
expectedReactivityMessage3, expectedReactivityMessage3,
); );
expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0); expect(clientNotReadySendMessageSpy).toHaveBeenCalledTimes(0);
expect(clientDeclineSendMessageSpy).toHaveBeenCalledTimes(2); expect(clientDeclineSendMessageSpy).toHaveBeenNthCalledWith(
}); 1,
expectedReactivityMessage5,
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);
}); });
}); });

View file

@ -41,7 +41,9 @@ export class RealtimeUserStatusAdapter {
styleIndex: this.findLeastUsedStyleIndex( styleIndex: this.findLeastUsedStyleIndex(
this.createStyleIndexToCountMap(realtimeNote), this.createStyleIndexToCountMap(realtimeNote),
), ),
cursor: { cursor: !this.acceptCursorUpdateProvider()
? null
: {
from: 0, from: 0,
to: 0, to: 0,
}, },
@ -53,10 +55,10 @@ export class RealtimeUserStatusAdapter {
const transporterMessagesListener = connection.getTransporter().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>) => {
if (this.acceptCursorUpdateProvider()) { this.realtimeUser.cursor = this.acceptCursorUpdateProvider()
this.realtimeUser.cursor = message.payload; ? message.payload
: null;
this.sendRealtimeUserStatusUpdateEvent(connection); this.sendRealtimeUserStatusUpdateEvent(connection);
}
}, },
{ objectify: true }, { objectify: true },
) as Listener; ) as Listener;
@ -83,10 +85,7 @@ 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<MessageType.REALTIME_USER_SET_ACTIVITY>) => { (message: Message<MessageType.REALTIME_USER_SET_ACTIVITY>) => {
if ( if (this.realtimeUser.active === message.payload.active) {
!this.acceptCursorUpdateProvider() ||
this.realtimeUser.active === message.payload.active
) {
return; return;
} }
this.realtimeUser.active = message.payload.active; this.realtimeUser.active = message.payload.active;
@ -115,9 +114,6 @@ export class RealtimeUserStatusAdapter {
const realtimeUser = const realtimeUser =
receivingClient.getRealtimeUserStateAdapter().realtimeUser; receivingClient.getRealtimeUserStateAdapter().realtimeUser;
const realtimeUsers = this.collectAllConnectionsExcept(receivingClient) const realtimeUsers = this.collectAllConnectionsExcept(receivingClient)
.filter((client) =>
client.getRealtimeUserStateAdapter().acceptCursorUpdateProvider(),
)
.map((client) => client.getRealtimeUserStateAdapter().realtimeUser) .map((client) => client.getRealtimeUserStateAdapter().realtimeUser)
.filter((realtimeUser) => realtimeUser !== null); .filter((realtimeUser) => realtimeUser !== null);

View file

@ -9,7 +9,7 @@ export interface RealtimeUser {
username: string | null username: string | null
active: boolean active: boolean
styleIndex: number styleIndex: number
cursor: RemoteCursor cursor: RemoteCursor | null
} }
export interface RemoteCursor { export interface RemoteCursor {

View file

@ -20,12 +20,19 @@ export class ReceiveRemoteCursorViewPlugin implements PluginValue {
this.listener = messageTransporter.on( this.listener = messageTransporter.on(
MessageType.REALTIME_USER_STATE_SET, MessageType.REALTIME_USER_STATE_SET,
({ payload }) => { ({ payload }) => {
const cursors: RemoteCursor[] = payload.users.map((user) => ({ const cursors = payload.users
.map((user) => {
if (!user.cursor) {
return undefined
}
return {
from: user.cursor.from, from: user.cursor.from,
to: user.cursor.to, to: user.cursor.to,
displayName: user.displayName, displayName: user.displayName,
styleIndex: user.styleIndex styleIndex: user.styleIndex
})) }
})
.filter((value) => value !== undefined) as RemoteCursor[]
view.dispatch({ view.dispatch({
effects: [remoteCursorUpdateEffect.of(cursors)] effects: [remoteCursorUpdateEffect.of(cursors)]
}) })