-
-
Notifications
You must be signed in to change notification settings - Fork 606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Distinguish room state and timeline events in embedded clients #4574
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ import { | |
WidgetApiAction, | ||
IWidgetApiResponse, | ||
IWidgetApiResponseData, | ||
IUpdateStateToWidgetActionRequest, | ||
} from "matrix-widget-api"; | ||
|
||
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event.ts"; | ||
|
@@ -136,6 +137,7 @@ export type EventHandlerMap = { [RoomWidgetClientEvent.PendingEventsChanged]: () | |
export class RoomWidgetClient extends MatrixClient { | ||
private room?: Room; | ||
private readonly widgetApiReady: Promise<void>; | ||
private readonly roomStateSynced: Promise<void>; | ||
private lifecycle?: AbortController; | ||
private syncState: SyncState | null = null; | ||
|
||
|
@@ -189,6 +191,11 @@ export class RoomWidgetClient extends MatrixClient { | |
}; | ||
|
||
this.widgetApiReady = new Promise<void>((resolve) => this.widgetApi.once("ready", resolve)); | ||
this.roomStateSynced = capabilities.receiveState?.length | ||
? new Promise<void>((resolve) => | ||
this.widgetApi.once(`action:${WidgetApiToWidgetAction.UpdateState}`, resolve), | ||
) | ||
: Promise.resolve(); | ||
|
||
// Request capabilities for the functionality this client needs to support | ||
if ( | ||
|
@@ -241,6 +248,7 @@ export class RoomWidgetClient extends MatrixClient { | |
|
||
widgetApi.on(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent); | ||
widgetApi.on(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice); | ||
widgetApi.on(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate); | ||
|
||
// Open communication with the host | ||
widgetApi.start(); | ||
|
@@ -276,37 +284,16 @@ export class RoomWidgetClient extends MatrixClient { | |
|
||
await this.widgetApiReady; | ||
|
||
// Backfill the requested events | ||
// We only get the most recent event for every type + state key combo, | ||
// so it doesn't really matter what order we inject them in | ||
await Promise.all( | ||
this.capabilities.receiveState?.map(async ({ eventType, stateKey }) => { | ||
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]); | ||
const events = rawEvents.map((rawEvent) => new MatrixEvent(rawEvent as Partial<IEvent>)); | ||
|
||
if (this.syncApi instanceof SyncApi) { | ||
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode | ||
// -> state events in `timelineEventList` will update the state. | ||
await this.syncApi.injectRoomEvents(this.room!, undefined, events); | ||
} else { | ||
await this.syncApi!.injectRoomEvents(this.room!, events); // Sliding Sync | ||
} | ||
events.forEach((event) => { | ||
this.emit(ClientEvent.Event, event); | ||
logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`); | ||
}); | ||
}) ?? [], | ||
); | ||
|
||
if (opts.clientWellKnownPollPeriod !== undefined) { | ||
this.clientWellKnownIntervalID = setInterval(() => { | ||
this.fetchClientWellKnown(); | ||
}, 1000 * opts.clientWellKnownPollPeriod); | ||
this.fetchClientWellKnown(); | ||
} | ||
|
||
await this.roomStateSynced; | ||
this.setSyncState(SyncState.Syncing); | ||
logger.info("Finished backfilling events"); | ||
logger.info("Finished initial sync"); | ||
|
||
this.matrixRTC.start(); | ||
|
||
|
@@ -317,6 +304,7 @@ export class RoomWidgetClient extends MatrixClient { | |
public stopClient(): void { | ||
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent); | ||
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice); | ||
this.widgetApi.off(`action:${WidgetApiToWidgetAction.UpdateState}`, this.onStateUpdate); | ||
|
||
super.stopClient(); | ||
this.lifecycle!.abort(); // Signal to other async tasks that the client has stopped | ||
|
@@ -574,36 +562,15 @@ export class RoomWidgetClient extends MatrixClient { | |
// Only inject once we have update the txId | ||
await this.updateTxId(event); | ||
|
||
// The widget API does not tell us whether a state event came from `state_after` or not so we assume legacy behaviour for now. | ||
if (this.syncApi instanceof SyncApi) { | ||
// The code will want to be something like: | ||
// ``` | ||
// if (!params.addToTimeline && !params.addToState) { | ||
// // Passing undefined for `stateAfterEventList` makes `injectRoomEvents` run in "legacy mode" | ||
// // -> state events part of the `timelineEventList` parameter will update the state. | ||
// this.injectRoomEvents(this.room!, [], undefined, [event]); | ||
// } else { | ||
// this.injectRoomEvents(this.room!, undefined, params.addToState ? [event] : [], params.addToTimeline ? [event] : []); | ||
// } | ||
// ``` | ||
|
||
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode | ||
// -> state events in `timelineEventList` will update the state. | ||
await this.syncApi.injectRoomEvents(this.room!, [], undefined, [event]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the |
||
await this.syncApi.injectRoomEvents(this.room!, undefined, [], [event]); | ||
} else { | ||
// The code will want to be something like: | ||
// ``` | ||
// if (!params.addToTimeline && !params.addToState) { | ||
// this.injectRoomEvents(this.room!, [], [event]); | ||
// } else { | ||
// this.injectRoomEvents(this.room!, params.addToState ? [event] : [], params.addToTimeline ? [event] : []); | ||
// } | ||
// ``` | ||
await this.syncApi!.injectRoomEvents(this.room!, [], [event]); // Sliding Sync | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also use this if the version is not supported. |
||
// Sliding Sync | ||
await this.syncApi!.injectRoomEvents(this.room!, [], [event]); | ||
} | ||
this.emit(ClientEvent.Event, event); | ||
this.setSyncState(SyncState.Syncing); | ||
logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`); | ||
logger.info(`Received event ${event.getId()} ${event.getType()}`); | ||
} else { | ||
const { event_id: eventId, room_id: roomId } = ev.detail.data; | ||
logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`); | ||
|
@@ -628,6 +595,32 @@ export class RoomWidgetClient extends MatrixClient { | |
await this.ack(ev); | ||
}; | ||
|
||
private onStateUpdate = async (ev: CustomEvent<IUpdateStateToWidgetActionRequest>): Promise<void> => { | ||
ev.preventDefault(); | ||
|
||
for (const rawEvent of ev.detail.data.state) { | ||
// Verify the room ID matches, since it's possible for the client to | ||
// send us state updates from other rooms if this widget is always | ||
// on screen | ||
if (rawEvent.room_id === this.roomId) { | ||
const event = new MatrixEvent(rawEvent as Partial<IEvent>); | ||
|
||
if (this.syncApi instanceof SyncApi) { | ||
await this.syncApi.injectRoomEvents(this.room!, undefined, [event]); | ||
} else { | ||
// Sliding Sync | ||
await this.syncApi!.injectRoomEvents(this.room!, [event]); | ||
} | ||
logger.info(`Updated state entry ${event.getType()} ${event.getStateKey()} to ${event.getId()}`); | ||
} else { | ||
const { event_id: eventId, room_id: roomId } = ev.detail.data; | ||
logger.info(`Received state entry ${eventId} for a different room ${roomId}; discarding`); | ||
} | ||
} | ||
|
||
await this.ack(ev); | ||
}; | ||
|
||
private async watchTurnServers(): Promise<void> { | ||
const servers = this.widgetApi.getTurnServers(); | ||
const onClientStopped = (): void => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could add this code back in case the driver does not support the
update_state
(orstate_after
) version.