diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index 87ae81a..5e850f3 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -17,13 +17,13 @@ import { CachedEventDispatcher, EventDispatcher } from "../util/eventdispatcher. import { CancellablePromise } from "../util/promise.ts" import type { ClientWellKnown, - DBEvent, EventID, EventRowID, EventType, PaginationResponse, RPCCommand, RPCEvent, + RawDBEvent, RoomID, TimelineRowID, UserID, @@ -105,7 +105,7 @@ export default abstract class RPCClient { }, this.cancelRequest.bind(this, request_id)) } - sendMessage(room_id: RoomID, type: EventType, content: Record): Promise { + sendMessage(room_id: RoomID, type: EventType, content: Record): Promise { return this.request("send_message", { room_id, type, content }) } @@ -113,15 +113,15 @@ export default abstract class RPCClient { return this.request("ensure_group_session_shared", { room_id }) } - getRoomState(room_id: RoomID, fetch_members = false, refetch = false): Promise { + getRoomState(room_id: RoomID, fetch_members = false, refetch = false): Promise { return this.request("get_room_state", { room_id, fetch_members, refetch }) } - getEvent(room_id: RoomID, event_id: EventID): Promise { + getEvent(room_id: RoomID, event_id: EventID): Promise { return this.request("get_event", { room_id, event_id }) } - getEventsByRowIDs(row_ids: EventRowID[]): Promise { + getEventsByRowIDs(row_ids: EventRowID[]): Promise { return this.request("get_events_by_row_ids", { row_ids }) } diff --git a/web/src/api/statestore.ts b/web/src/api/statestore.ts index 6e93b93..7895563 100644 --- a/web/src/api/statestore.ts +++ b/web/src/api/statestore.ts @@ -16,7 +16,6 @@ import { NonNullCachedEventDispatcher } from "../util/eventdispatcher.ts" import type { ContentURI, - DBEvent, DBRoom, EncryptedEventContent, EventID, @@ -24,6 +23,8 @@ import type { EventType, EventsDecryptedData, LazyLoadSummary, + MemDBEvent, + RawDBEvent, RoomID, SyncCompleteData, SyncRoom, @@ -67,15 +68,15 @@ export class RoomStateStore { readonly timeline = new NonNullCachedEventDispatcher([]) state: Map> = new Map() stateLoaded = false - readonly eventsByRowID: Map = new Map() - readonly eventsByID: Map = new Map() + readonly eventsByRowID: Map = new Map() + readonly eventsByID: Map = new Map() constructor(meta: DBRoom) { this.roomID = meta.room_id this.meta = new NonNullCachedEventDispatcher(meta) } - getStateEvent(type: EventType, stateKey: string): DBEvent | undefined { + getStateEvent(type: EventType, stateKey: string): MemDBEvent | undefined { const rowID = this.state.get(type)?.get(stateKey) if (!rowID) { return @@ -83,7 +84,7 @@ export class RoomStateStore { return this.eventsByRowID.get(rowID) } - applyPagination(history: DBEvent[]) { + applyPagination(history: RawDBEvent[]) { // Pagination comes in newest to oldest, timeline is in the opposite order history.reverse() const newTimeline = history.map(evt => { @@ -93,14 +94,18 @@ export class RoomStateStore { this.timeline.emit([...newTimeline, ...this.timeline.current]) } - applyEvent(evt: DBEvent) { + applyEvent(evt: RawDBEvent) { + const memEvt = evt as MemDBEvent + memEvt.mem = true if (evt.type === "m.room.encrypted" && evt.decrypted && evt.decrypted_type) { - evt.type = evt.decrypted_type - evt.encrypted = evt.content as EncryptedEventContent - evt.content = evt.decrypted + memEvt.type = evt.decrypted_type + memEvt.encrypted = evt.content as EncryptedEventContent + memEvt.content = evt.decrypted } - this.eventsByRowID.set(evt.rowid, evt) - this.eventsByID.set(evt.event_id, evt) + delete evt.decrypted + delete evt.decrypted_type + this.eventsByRowID.set(memEvt.rowid, memEvt) + this.eventsByID.set(memEvt.event_id, memEvt) } applySync(sync: SyncRoom) { @@ -133,8 +138,7 @@ export class RoomStateStore { let timelineChanged = false for (const evt of decrypted.events) { timelineChanged = timelineChanged || !!this.timeline.current.find(rt => rt.event_rowid === evt.rowid) - this.eventsByRowID.set(evt.rowid, evt) - this.eventsByID.set(evt.event_id, evt) + this.applyEvent(evt) } if (timelineChanged) { this.timeline.emit([...this.timeline.current]) @@ -148,7 +152,7 @@ export class RoomStateStore { export interface RoomListEntry { room_id: RoomID sorting_timestamp: number - preview_event?: DBEvent + preview_event?: MemDBEvent name: string avatar?: ContentURI } diff --git a/web/src/api/types/hievents.ts b/web/src/api/types/hievents.ts index b80c08c..c208e67 100644 --- a/web/src/api/types/hievents.ts +++ b/web/src/api/types/hievents.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { - DBEvent, DBRoom, EventRowID, + RawDBEvent, TimelineRowTuple, } from "./hitypes.ts" import { @@ -42,7 +42,7 @@ export interface TypingEvent extends RPCCommand { } export interface SendCompleteData { - event: DBEvent + event: RawDBEvent error: string | null } @@ -53,7 +53,7 @@ export interface SendCompleteEvent extends RPCCommand { export interface EventsDecryptedData { room_id: RoomID preview_event_rowid?: EventRowID - events: DBEvent[] + events: RawDBEvent[] } export interface EventsDecryptedEvent extends RPCCommand { @@ -63,7 +63,7 @@ export interface EventsDecryptedEvent extends RPCCommand { export interface SyncRoom { meta: DBRoom timeline: TimelineRowTuple[] - events: DBEvent[] + events: RawDBEvent[] state: Record> reset: boolean } diff --git a/web/src/api/types/hitypes.ts b/web/src/api/types/hitypes.ts index ad41227..1bd9c70 100644 --- a/web/src/api/types/hitypes.ts +++ b/web/src/api/types/hitypes.ts @@ -62,7 +62,7 @@ export interface DBRoom { prev_batch: string } -export interface DBEvent { +export interface BaseDBEvent { rowid: EventRowID timeline_rowid: TimelineRowID @@ -73,10 +73,8 @@ export interface DBEvent { state_key?: string timestamp: number - content: unknown - decrypted?: unknown - decrypted_type?: EventType - encrypted?: EncryptedEventContent + //eslint-disable-next-line @typescript-eslint/no-explicit-any + content: Record unsigned: EventUnsigned transaction_id?: string @@ -91,6 +89,17 @@ export interface DBEvent { last_edit_rowid?: EventRowID } +export interface RawDBEvent extends BaseDBEvent { + //eslint-disable-next-line @typescript-eslint/no-explicit-any + decrypted?: Record + decrypted_type?: EventType +} + +export interface MemDBEvent extends BaseDBEvent { + mem: true + encrypted?: EncryptedEventContent +} + export interface DBAccountData { user_id: UserID room_id?: RoomID @@ -99,7 +108,7 @@ export interface DBAccountData { } export interface PaginationResponse { - events: DBEvent[] + events: RawDBEvent[] has_more: boolean } diff --git a/web/src/ui/roomlist/Entry.tsx b/web/src/ui/roomlist/Entry.tsx index 506702d..f01a5b1 100644 --- a/web/src/ui/roomlist/Entry.tsx +++ b/web/src/ui/roomlist/Entry.tsx @@ -15,19 +15,18 @@ // along with this program. If not, see . import { getMediaURL } from "../../api/media.ts" import type { RoomListEntry } from "../../api/statestore.ts" -import type { DBEvent } from "../../api/types/hitypes.ts" +import type { MemDBEvent } from "../../api/types" export interface RoomListEntryProps { room: RoomListEntry setActiveRoom: (evt: React.MouseEvent) => void } -function makePreviewText(evt?: DBEvent): string { +function makePreviewText(evt?: MemDBEvent): string { if (!evt) { return "" } - if (evt.type === "m.room.message" || evt.type === "m.sticker") { - // @ts-expect-error TODO add content types + if ((evt.type === "m.room.message" || evt.type === "m.sticker") && typeof evt.content.body === "string") { return evt.content.body } return "" diff --git a/web/src/ui/timeline/TimelineEvent.tsx b/web/src/ui/timeline/TimelineEvent.tsx index 02484b3..7fc4bf1 100644 --- a/web/src/ui/timeline/TimelineEvent.tsx +++ b/web/src/ui/timeline/TimelineEvent.tsx @@ -16,7 +16,7 @@ import React from "react" import { getMediaURL } from "../../api/media.ts" import { RoomStateStore } from "../../api/statestore.ts" -import { DBEvent, MemberEventContent } from "../../api/types" +import { MemDBEvent, MemberEventContent } from "../../api/types" import HiddenEvent from "./content/HiddenEvent.tsx" import MessageBody from "./content/MessageBody.tsx" import { EventContentProps } from "./content/props.ts" @@ -27,7 +27,10 @@ export interface TimelineEventProps { eventRowID: number } -function getBodyType(evt: DBEvent): React.FunctionComponent { +function getBodyType(evt: MemDBEvent): React.FunctionComponent { + if (evt.content["m.relates_to"]?.relation_type === "m.replace") { + return HiddenEvent + } switch (evt.type) { case "m.room.message": case "m.sticker": diff --git a/web/src/ui/timeline/content/props.ts b/web/src/ui/timeline/content/props.ts index caf571d..91c394f 100644 --- a/web/src/ui/timeline/content/props.ts +++ b/web/src/ui/timeline/content/props.ts @@ -14,9 +14,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { RoomStateStore } from "../../../api/statestore.ts" -import { DBEvent } from "../../../api/types/hitypes.ts" +import { MemDBEvent } from "../../../api/types" export interface EventContentProps { room: RoomStateStore - event: DBEvent + event: MemDBEvent }