From 44404b6e2403e16921e41e606ae2dcdcf2d1ff0c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 13 Oct 2025 11:25:33 +0100 Subject: [PATCH] Start using Namespaced value everywhere. --- src/@types/event.ts | 22 ++++++++++++++++++++++ src/client.ts | 3 ++- src/matrixrtc/CallMembership.ts | 4 ++-- src/models/event.ts | 13 ++++++++----- src/models/room-sticky-events.ts | 7 ++++--- src/sync-accumulator.ts | 23 ++++++++++++++--------- src/sync.ts | 5 +++-- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/@types/event.ts b/src/@types/event.ts index 1364d9ca75..df2892fc7b 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -315,6 +315,28 @@ export const UNSIGNED_THREAD_ID_FIELD = new UnstableValue("thread_id", "org.matr */ export const UNSIGNED_MEMBERSHIP_FIELD = new NamespacedValue("membership", "io.element.msc4115.membership"); + +/** + * https://github.com/matrix-org/matrix-spec-proposals/pull/4354 + * + * @experimental + */ +export const STICKY_EVENT_FIELD = new NamespacedValue(null, "msc4354_sticky"); + +/** + * https://github.com/matrix-org/matrix-spec-proposals/pull/4354 + * + * @experimental + */ +export const STICKY_EVENT_KEY_FIELD = new NamespacedValue(null, "msc4354_sticky_key"); + +/** + * https://github.com/matrix-org/matrix-spec-proposals/pull/4354 + * + * @experimental + */ +export const STICKY_EVENT_DURATION_TTL_MS = new NamespacedValue(null, "msc4354_sticky_duration_ttl_ms") + /** * Mapped type from event type to content type for all specified non-state room events. */ diff --git a/src/client.ts b/src/client.ts index bd1f836260..9867fca6ae 100644 --- a/src/client.ts +++ b/src/client.ts @@ -143,6 +143,7 @@ import { RoomCreateTypeField, RoomType, type StateEvents, + STICKY_EVENT_KEY_FIELD, type TimelineEvents, UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, @@ -3443,7 +3444,7 @@ export class MatrixClient extends TypedEventEmitter { if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) { diff --git a/src/matrixrtc/CallMembership.ts b/src/matrixrtc/CallMembership.ts index 59ff3778e7..f029407614 100644 --- a/src/matrixrtc/CallMembership.ts +++ b/src/matrixrtc/CallMembership.ts @@ -20,7 +20,7 @@ import { type LivekitFocusSelection } from "./LivekitTransport.ts"; import { slotDescriptionToId, slotIdToDescription, type SlotDescription } from "./MatrixRTCSession.ts"; import type { RTCCallIntent, Transport } from "./types.ts"; import { type IContent, type MatrixEvent } from "../models/event.ts"; -import { type RelationType } from "../@types/event.ts"; +import { STICKY_EVENT_KEY_FIELD, type RelationType } from "../@types/event.ts"; import { logger } from "../logger.ts"; /** @@ -47,7 +47,7 @@ export interface RtcMembershipData { }; "rtc_transports": Transport[]; "versions": string[]; - "msc4354_sticky_key"?: string; + [STICKY_EVENT_KEY_FIELD.name]?: string; "sticky_key"?: string; } diff --git a/src/models/event.ts b/src/models/event.ts index bc175ef0df..3f69167cfd 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -31,6 +31,8 @@ import { ToDeviceMessageId, UNSIGNED_THREAD_ID_FIELD, UNSIGNED_MEMBERSHIP_FIELD, + STICKY_EVENT_DURATION_TTL_MS, + STICKY_EVENT_FIELD, } from "../@types/event.ts"; import { deepSortedObjectEntries, internaliseString } from "../utils.ts"; import { type RoomMember } from "./room-member.ts"; @@ -75,7 +77,7 @@ export interface IUnsigned { "transaction_id"?: string; "invite_room_state"?: StrippedState[]; "m.relations"?: Record; // No common pattern for aggregated relations - "msc4354_sticky_duration_ttl_ms"?: number; + [STICKY_EVENT_DURATION_TTL_MS.name]?: number; [UNSIGNED_THREAD_ID_FIELD.name]?: string; } @@ -97,7 +99,7 @@ export interface IEvent { membership?: Membership; unsigned: IUnsigned; redacts?: string; - msc4354_sticky?: { duration_ms: number }; + [STICKY_EVENT_FIELD.name]?: { duration_ms: number }; } export interface IAggregatedRelation { @@ -1771,13 +1773,14 @@ export class MatrixEvent extends TypedEventEmitter { + stickyEventData.events.map((event) => { // If `duration_ms` exceeds the spec limit of a hour, we cap it. - const cappedDuration = Math.min(event.msc4354_sticky.duration_ms, MAX_STICKY_DURATION_MS); + const duration = event['sticky_event']?.duration_ms ?? event['msc4354_sticky']?.duration_ms; + if (typeof duration !== "number") { + return null; + } + const cappedDuration = Math.min(duration, MAX_STICKY_DURATION_MS); // If `origin_server_ts` claims to have been from the future, we still bound it to now. const createdTs = Math.min(event.origin_server_ts, now); return { event, expiresTs: cappedDuration + createdTs, }; - }), + }).filter(e => e !== null), ); } @@ -659,7 +664,7 @@ export class SyncAccumulator { "msc4354_sticky": roomData._stickyEvents?.length ? { events: roomData._stickyEvents.map((e) => e.event), - } + } satisfies ISticky : undefined, }; // Add account data diff --git a/src/sync.ts b/src/sync.ts index a191fa8776..f11bbddffe 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -40,6 +40,7 @@ import { type ResetTimelineCallback, } from "./client.ts"; import { + ISticky, type IEphemeral, type IInvitedRoom, type IInviteState, @@ -58,7 +59,7 @@ import { import { MatrixEvent } from "./models/event.ts"; import { type MatrixError, Method } from "./http-api/index.ts"; import { type ISavedSync } from "./store/index.ts"; -import { EventType } from "./@types/event.ts"; +import { EventType, STICKY_EVENT_FIELD } from "./@types/event.ts"; import { type IPushRules } from "./@types/PushRules.ts"; import { type IMarkerFoundOptions, RoomStateEvent } from "./models/room-state.ts"; import { RoomMemberEvent } from "./models/room-member.ts"; @@ -1221,7 +1222,7 @@ export class SyncApi { const timelineEvents = this.mapSyncEventsFormat(joinObj.timeline, room, false); const ephemeralEvents = this.mapSyncEventsFormat(joinObj.ephemeral); const accountDataEvents = this.mapSyncEventsFormat(joinObj.account_data); - const stickyEvents = this.mapSyncEventsFormat(joinObj.msc4354_sticky); + const stickyEvents = this.mapSyncEventsFormat((joinObj[STICKY_EVENT_FIELD.name] ?? joinObj[STICKY_EVENT_FIELD.unstable!]) as ISticky); // If state_after is present, this is the events that form the state at the end of the timeline block and // regular timeline events do *not* count towards state. If it's not present, then the state is formed by