web/statestore: allow sync event fields to be null

This commit is contained in:
Tulir Asokan 2024-12-28 18:58:49 +02:00
parent 2b206bb32f
commit 2ea80dac6f
7 changed files with 45 additions and 57 deletions

View file

@ -16,10 +16,7 @@ func (h *HiClient) getInitialSyncRoom(ctx context.Context, room *database.Room)
syncRoom := &SyncRoom{ syncRoom := &SyncRoom{
Meta: room, Meta: room,
Events: make([]*database.Event, 0, 2), Events: make([]*database.Event, 0, 2),
Timeline: make([]database.TimelineRowTuple, 0),
State: map[event.Type]map[string]database.EventRowID{}, State: map[event.Type]map[string]database.EventRowID{},
Notifications: make([]SyncNotification, 0),
Receipts: make(map[id.EventID][]*database.Receipt),
} }
ad, err := h.DB.AccountData.GetAllRoom(ctx, h.Account.UserID, room.ID) ad, err := h.DB.AccountData.GetAllRoom(ctx, h.Account.UserID, room.ID)
if err != nil { if err != nil {
@ -27,7 +24,6 @@ func (h *HiClient) getInitialSyncRoom(ctx context.Context, room *database.Room)
if ctx.Err() != nil { if ctx.Err() != nil {
return nil return nil
} }
syncRoom.AccountData = make(map[event.Type]*database.AccountData)
} else { } else {
syncRoom.AccountData = make(map[event.Type]*database.AccountData, len(ad)) syncRoom.AccountData = make(map[event.Type]*database.AccountData, len(ad))
for _, data := range ad { for _, data := range ad {
@ -79,9 +75,7 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[*
return return
} }
payload := SyncComplete{ payload := SyncComplete{
Rooms: make(map[id.RoomID]*SyncRoom, len(rooms)-1), Rooms: make(map[id.RoomID]*SyncRoom, len(rooms)),
LeftRooms: make([]id.RoomID, 0),
AccountData: make(map[event.Type]*database.AccountData),
} }
if i == 0 { if i == 0 {
payload.InvitedRooms, err = h.DB.InvitedRoom.GetAll(ctx) payload.InvitedRooms, err = h.DB.InvitedRoom.GetAll(ctx)
@ -91,6 +85,7 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[*
} }
return return
} }
// TODO include space rooms in first batch too?
payload.SpaceEdges, err = h.DB.SpaceEdge.GetAll(ctx, "") payload.SpaceEdges, err = h.DB.SpaceEdge.GetAll(ctx, "")
if err != nil { if err != nil {
if ctx.Err() == nil { if ctx.Err() == nil {
@ -100,12 +95,6 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[*
} }
payload.ClearState = true payload.ClearState = true
} }
if payload.InvitedRooms == nil {
payload.InvitedRooms = make([]*database.InvitedRoom, 0)
}
if payload.SpaceEdges == nil {
payload.SpaceEdges = make(map[id.RoomID][]*database.SpaceEdge)
}
for _, room := range rooms { for _, room := range rooms {
if room.SortingTimestamp == rooms[len(rooms)-1].SortingTimestamp { if room.SortingTimestamp == rooms[len(rooms)-1].SortingTimestamp {
break break
@ -127,9 +116,6 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[*
return return
} }
payload := SyncComplete{ payload := SyncComplete{
Rooms: make(map[id.RoomID]*SyncRoom),
InvitedRooms: make([]*database.InvitedRoom, 0),
LeftRooms: make([]id.RoomID, 0),
AccountData: make(map[event.Type]*database.AccountData, len(ad)), AccountData: make(map[event.Type]*database.AccountData, len(ad)),
} }
for _, data := range ad { for _, data := range ad {

View file

@ -147,6 +147,7 @@ func (h *HiClient) processGetRoomState(ctx context.Context, roomID id.RoomID, fe
return fmt.Errorf("failed to save current state entries: %w", err) return fmt.Errorf("failed to save current state entries: %w", err)
} }
roomChanged := updatedRoom.CheckChangesAndCopyInto(room) roomChanged := updatedRoom.CheckChangesAndCopyInto(room)
// TODO dispatch space edge changes if something changed? (fairly unlikely though)
err = sdc.Apply(ctx, room, h.DB.SpaceEdge) err = sdc.Apply(ctx, room, h.DB.SpaceEdge)
if err != nil { if err != nil {
return err return err
@ -161,20 +162,8 @@ func (h *HiClient) processGetRoomState(ctx context.Context, roomID id.RoomID, fe
Rooms: map[id.RoomID]*SyncRoom{ Rooms: map[id.RoomID]*SyncRoom{
roomID: { roomID: {
Meta: room, Meta: room,
Timeline: make([]database.TimelineRowTuple, 0),
State: make(map[event.Type]map[string]database.EventRowID),
AccountData: make(map[event.Type]*database.AccountData),
Events: make([]*database.Event, 0),
Reset: false,
Notifications: make([]SyncNotification, 0),
Receipts: make(map[id.EventID][]*database.Receipt),
}, },
}, },
InvitedRooms: make([]*database.InvitedRoom, 0),
AccountData: make(map[event.Type]*database.AccountData),
LeftRooms: make([]id.RoomID, 0),
// TODO dispatch space edge changes if something changed? (fairly unlikely though)
SpaceEdges: make(map[id.RoomID][]*database.SpaceEdge),
}) })
} }
} }

View file

@ -38,7 +38,6 @@ func (h *hiSyncer) ProcessResponse(ctx context.Context, resp *mautrix.RespSync,
Rooms: make(map[id.RoomID]*SyncRoom, len(resp.Rooms.Join)), Rooms: make(map[id.RoomID]*SyncRoom, len(resp.Rooms.Join)),
InvitedRooms: make([]*database.InvitedRoom, 0, len(resp.Rooms.Invite)), InvitedRooms: make([]*database.InvitedRoom, 0, len(resp.Rooms.Invite)),
LeftRooms: make([]id.RoomID, 0, len(resp.Rooms.Leave)), LeftRooms: make([]id.RoomID, 0, len(resp.Rooms.Leave)),
SpaceEdges: make(map[id.RoomID][]*database.SpaceEdge),
}}) }})
err := c.preProcessSyncResponse(ctx, resp, since) err := c.preProcessSyncResponse(ctx, resp, since)
if err != nil { if err != nil {

View file

@ -122,7 +122,7 @@ export class StateStore {
entry.meta.unread_highlights !== oldEntry.meta.current.unread_highlights || entry.meta.unread_highlights !== oldEntry.meta.current.unread_highlights ||
entry.meta.marked_unread !== oldEntry.meta.current.marked_unread || entry.meta.marked_unread !== oldEntry.meta.current.marked_unread ||
entry.meta.preview_event_rowid !== oldEntry.meta.current.preview_event_rowid || entry.meta.preview_event_rowid !== oldEntry.meta.current.preview_event_rowid ||
entry.events.findIndex(evt => evt.rowid === entry.meta.preview_event_rowid) !== -1 (entry.events ?? []).findIndex(evt => evt.rowid === entry.meta.preview_event_rowid) !== -1
} }
#makeRoomListEntry(entry: SyncRoom, room?: RoomStateStore): RoomListEntry | null { #makeRoomListEntry(entry: SyncRoom, room?: RoomStateStore): RoomListEntry | null {
@ -165,7 +165,7 @@ export class StateStore {
} }
const resyncRoomList = this.roomList.current.length === 0 const resyncRoomList = this.roomList.current.length === 0
const changedRoomListEntries = new Map<RoomID, RoomListEntry | null>() const changedRoomListEntries = new Map<RoomID, RoomListEntry | null>()
for (const data of sync.invited_rooms) { for (const data of sync.invited_rooms ?? []) {
const room = new InvitedRoomStore(data, this) const room = new InvitedRoomStore(data, this)
this.inviteRooms.set(room.room_id, room) this.inviteRooms.set(room.room_id, room)
if (!resyncRoomList) { if (!resyncRoomList) {
@ -176,7 +176,7 @@ export class StateStore {
} }
} }
const hasInvites = this.inviteRooms.size > 0 const hasInvites = this.inviteRooms.size > 0
for (const [roomID, data] of Object.entries(sync.rooms)) { for (const [roomID, data] of Object.entries(sync.rooms ?? {})) {
let isNewRoom = false let isNewRoom = false
let room = this.rooms.get(roomID) let room = this.rooms.get(roomID)
if (!room) { if (!room) {
@ -203,7 +203,7 @@ export class StateStore {
} }
} }
if (window.Notification?.permission === "granted" && !focused.current) { if (window.Notification?.permission === "granted" && !focused.current && data.notifications) {
for (const notification of data.notifications) { for (const notification of data.notifications) {
this.showNotification(room, notification.event_rowid, notification.sound) this.showNotification(room, notification.event_rowid, notification.sound)
} }
@ -212,7 +212,7 @@ export class StateStore {
this.switchRoom?.(roomID) this.switchRoom?.(roomID)
} }
} }
for (const ad of Object.values(sync.account_data)) { for (const ad of Object.values(sync.account_data ?? {})) {
if (ad.type === "io.element.recent_emoji") { if (ad.type === "io.element.recent_emoji") {
this.#frequentlyUsedEmoji = null this.#frequentlyUsedEmoji = null
} else if (ad.type === "fi.mau.gomuks.preferences") { } else if (ad.type === "fi.mau.gomuks.preferences") {
@ -222,7 +222,7 @@ export class StateStore {
this.accountData.set(ad.type, ad.content) this.accountData.set(ad.type, ad.content)
this.accountDataSubs.notify(ad.type) this.accountDataSubs.notify(ad.type)
} }
for (const roomID of sync.left_rooms) { for (const roomID of sync.left_rooms ?? []) {
if (this.activeRoomID === roomID) { if (this.activeRoomID === roomID) {
this.switchRoom?.(null) this.switchRoom?.(null)
} }
@ -233,7 +233,7 @@ export class StateStore {
let updatedRoomList: RoomListEntry[] | undefined let updatedRoomList: RoomListEntry[] | undefined
if (resyncRoomList) { if (resyncRoomList) {
updatedRoomList = this.inviteRooms.values().toArray() updatedRoomList = this.inviteRooms.values().toArray()
updatedRoomList = updatedRoomList.concat(Object.values(sync.rooms) updatedRoomList = updatedRoomList.concat(Object.values(sync.rooms ?? {})
.map(entry => this.#makeRoomListEntry(entry)) .map(entry => this.#makeRoomListEntry(entry))
.filter(entry => entry !== null)) .filter(entry => entry !== null))
updatedRoomList.sort((r1, r2) => r1.sorting_timestamp - r2.sorting_timestamp) updatedRoomList.sort((r1, r2) => r1.sorting_timestamp - r2.sorting_timestamp)

View file

@ -390,7 +390,7 @@ export class RoomStateStore {
} else { } else {
this.meta.emit(sync.meta) this.meta.emit(sync.meta)
} }
for (const ad of Object.values(sync.account_data)) { for (const ad of Object.values(sync.account_data ?? {})) {
if (ad.type === "fi.mau.gomuks.preferences") { if (ad.type === "fi.mau.gomuks.preferences") {
this.serverPreferenceCache = ad.content this.serverPreferenceCache = ad.content
this.preferenceSub.notify() this.preferenceSub.notify()
@ -398,10 +398,10 @@ export class RoomStateStore {
this.accountData.set(ad.type, ad.content) this.accountData.set(ad.type, ad.content)
this.accountDataSubs.notify(ad.type) this.accountDataSubs.notify(ad.type)
} }
for (const evt of sync.events) { for (const evt of sync.events ?? []) {
this.applyEvent(evt) this.applyEvent(evt)
} }
for (const [evtType, changedEvts] of Object.entries(sync.state)) { for (const [evtType, changedEvts] of Object.entries(sync.state ?? {})) {
let stateMap = this.state.get(evtType) let stateMap = this.state.get(evtType)
if (!stateMap) { if (!stateMap) {
stateMap = new Map() stateMap = new Map()
@ -414,9 +414,9 @@ export class RoomStateStore {
this.stateSubs.notify(evtType) this.stateSubs.notify(evtType)
} }
if (sync.reset) { if (sync.reset) {
this.timeline = sync.timeline this.timeline = sync.timeline ?? []
this.pendingEvents.splice(0, this.pendingEvents.length) this.pendingEvents.splice(0, this.pendingEvents.length)
} else { } else if (sync.timeline) {
this.timeline.push(...sync.timeline) this.timeline.push(...sync.timeline)
} }
if (sync.meta.unread_notifications === 0 && sync.meta.unread_highlights === 0) { if (sync.meta.unread_notifications === 0 && sync.meta.unread_highlights === 0) {
@ -426,7 +426,7 @@ export class RoomStateStore {
this.openNotifications.clear() this.openNotifications.clear()
} }
this.notifyTimelineSubscribers() this.notifyTimelineSubscribers()
for (const [evtID, receipts] of Object.entries(sync.receipts)) { for (const [evtID, receipts] of Object.entries(sync.receipts ?? {})) {
this.applyReceipts(receipts, evtID, false) this.applyReceipts(receipts, evtID, false)
} }
} }

View file

@ -19,6 +19,7 @@ import {
DBReceipt, DBReceipt,
DBRoom, DBRoom,
DBRoomAccountData, DBRoomAccountData,
DBSpaceEdge,
EventRowID, EventRowID,
RawDBEvent, RawDBEvent,
TimelineRowTuple, TimelineRowTuple,
@ -71,13 +72,13 @@ export interface ImageAuthTokenEvent extends BaseRPCCommand<string> {
export interface SyncRoom { export interface SyncRoom {
meta: DBRoom meta: DBRoom
timeline: TimelineRowTuple[] timeline: TimelineRowTuple[] | null
events: RawDBEvent[] events: RawDBEvent[] | null
state: Record<EventType, Record<string, EventRowID>> state: Record<EventType, Record<string, EventRowID>> | null
reset: boolean reset: boolean
notifications: SyncNotification[] notifications: SyncNotification[] | null
account_data: Record<EventType, DBRoomAccountData> account_data: Record<EventType, DBRoomAccountData> | null
receipts: Record<EventID, DBReceipt[]> receipts: Record<EventID, DBReceipt[]> | null
} }
export interface SyncNotification { export interface SyncNotification {
@ -86,10 +87,11 @@ export interface SyncNotification {
} }
export interface SyncCompleteData { export interface SyncCompleteData {
rooms: Record<RoomID, SyncRoom> rooms: Record<RoomID, SyncRoom> | null
invited_rooms: DBInvitedRoom[] invited_rooms: DBInvitedRoom[] | null
left_rooms: RoomID[] left_rooms: RoomID[] | null
account_data: Record<EventType, DBAccountData> account_data: Record<EventType, DBAccountData> | null
space_edges: Record<RoomID, Omit<DBSpaceEdge, "space_id">[]> | null
since?: string since?: string
clear_state?: boolean clear_state?: boolean
} }

View file

@ -71,6 +71,18 @@ export interface DBRoom {
prev_batch: string prev_batch: string
} }
export interface DBSpaceEdge {
space_id: RoomID
child_id: RoomID
child_event_rowid?: EventRowID
order?: string
suggested?: true
parent_event_rowid?: EventRowID
canonical?: true
}
//eslint-disable-next-line @typescript-eslint/no-explicit-any //eslint-disable-next-line @typescript-eslint/no-explicit-any
export type UnknownEventContent = Record<string, any> export type UnknownEventContent = Record<string, any>