fix(presence): your own status shows at once (and clears) in the member list #475
@@ -473,6 +473,37 @@ test.describe('local homeserver regression', () => {
|
||||
await expect(page.getByText(/Status set\. Sharing it with others/)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('own status shows in the members drawer without switching rooms, and clears', async ({
|
||||
page,
|
||||
}) => {
|
||||
// The SDK creates our own User with `new User()` (no event re-emit), so the
|
||||
// shared presence store missed our own presence until a remount. Reported:
|
||||
// a new status only appeared after switching rooms.
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
const me = await ensureUser(uniq('own'));
|
||||
const room = await createRoom(me, 'Own Status Room');
|
||||
await sendText(me, room, 'hello');
|
||||
await loginUI(page, me);
|
||||
await openRoom(page, room);
|
||||
await page.getByRole('button', { name: 'User Settings' }).click();
|
||||
await page.getByRole('dialog').first().getByText('Account', { exact: true }).first().click();
|
||||
await page
|
||||
.getByRole('group', { name: 'Quick statuses' })
|
||||
.getByRole('button', { name: /Gaming/ })
|
||||
.click();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByText('🎮 Gaming')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
await page.getByRole('button', { name: 'User Settings' }).click();
|
||||
await page.getByRole('dialog').first().getByText('Account', { exact: true }).first().click();
|
||||
await page
|
||||
.getByRole('button', { name: /^Clear/ })
|
||||
.first()
|
||||
.click();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByText('🎮 Gaming')).toHaveCount(0, { timeout: 15_000 });
|
||||
});
|
||||
|
||||
test('touch: long-press opens the message action sheet (#166)', async ({ browser }) => {
|
||||
const ctx = await browser.newContext({ ...devices['Pixel 7'] });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { MatrixClient, User, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||
import {
|
||||
ClientEvent,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
User,
|
||||
UserEvent,
|
||||
UserEventHandlerMap,
|
||||
} from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
|
||||
export enum Presence {
|
||||
@@ -19,7 +26,12 @@ export type UserPresence = {
|
||||
|
||||
const getUserPresence = (user: User): UserPresence => ({
|
||||
presence: user.presence as Presence,
|
||||
status: user.presenceStatusMsg,
|
||||
// From the latest presence event rather than user.presenceStatusMsg: the SDK
|
||||
// only overwrites that field with a non-empty status_msg, so a cleared status
|
||||
// would otherwise linger until reload.
|
||||
status: user.events.presence
|
||||
? (user.events.presence.getContent().status_msg as string | undefined) || undefined
|
||||
: user.presenceStatusMsg,
|
||||
active: user.currentlyActive,
|
||||
lastActiveTs: user.getLastActiveTs(),
|
||||
});
|
||||
@@ -55,6 +67,19 @@ class PresenceStore {
|
||||
subs.forEach((cb) => cb());
|
||||
};
|
||||
|
||||
// Our own User object is created by the SDK with `new User()` (not
|
||||
// User.createUser), so its User.* events are never re-emitted on the client
|
||||
// and the listeners above miss our own presence changes. The client does
|
||||
// emit every sync presence event as ClientEvent.Event (after updating the
|
||||
// User), so refresh from that too.
|
||||
private handleClientEvent = (event: MatrixEvent): void => {
|
||||
if (event.getType() !== 'm.presence' || !this.mx) return;
|
||||
const userId = event.getSender();
|
||||
if (!userId) return;
|
||||
const user = this.mx.getUser(userId);
|
||||
if (user) this.handleEvent(event, user);
|
||||
};
|
||||
|
||||
private start(mx: MatrixClient): void {
|
||||
if (this.started && this.mx === mx) return;
|
||||
// A different client (re-login without reload) — drop the old listeners.
|
||||
@@ -62,12 +87,14 @@ class PresenceStore {
|
||||
this.mx.removeListener(UserEvent.Presence, this.handleEvent);
|
||||
this.mx.removeListener(UserEvent.CurrentlyActive, this.handleEvent);
|
||||
this.mx.removeListener(UserEvent.LastPresenceTs, this.handleEvent);
|
||||
this.mx.removeListener(ClientEvent.Event, this.handleClientEvent);
|
||||
this.cache.clear();
|
||||
}
|
||||
this.mx = mx;
|
||||
mx.on(UserEvent.Presence, this.handleEvent);
|
||||
mx.on(UserEvent.CurrentlyActive, this.handleEvent);
|
||||
mx.on(UserEvent.LastPresenceTs, this.handleEvent);
|
||||
mx.on(ClientEvent.Event, this.handleClientEvent);
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user