@tagea/capacitor-matrix 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1229,18 +1229,32 @@ removeAllListeners() => Promise<void>
1229
1229
 
1230
1230
  #### RoomSummary
1231
1231
 
1232
- | Prop | Type |
1233
- | ----------------- | --------------------------------------------------- |
1234
- | **`roomId`** | <code>string</code> |
1235
- | **`name`** | <code>string</code> |
1236
- | **`topic`** | <code>string</code> |
1237
- | **`memberCount`** | <code>number</code> |
1238
- | **`isEncrypted`** | <code>boolean</code> |
1239
- | **`unreadCount`** | <code>number</code> |
1240
- | **`lastEventTs`** | <code>number</code> |
1241
- | **`membership`** | <code>'join' \| 'invite' \| 'leave' \| 'ban'</code> |
1242
- | **`avatarUrl`** | <code>string</code> |
1243
- | **`isDirect`** | <code>boolean</code> |
1232
+ | Prop | Type |
1233
+ | ----------------- | ----------------------------------------------------------------- |
1234
+ | **`roomId`** | <code>string</code> |
1235
+ | **`name`** | <code>string</code> |
1236
+ | **`topic`** | <code>string</code> |
1237
+ | **`memberCount`** | <code>number</code> |
1238
+ | **`isEncrypted`** | <code>boolean</code> |
1239
+ | **`unreadCount`** | <code>number</code> |
1240
+ | **`lastEventTs`** | <code>number</code> |
1241
+ | **`createdAt`** | <code>number</code> |
1242
+ | **`membership`** | <code>'join' \| 'invite' \| 'leave' \| 'ban'</code> |
1243
+ | **`avatarUrl`** | <code>string</code> |
1244
+ | **`isDirect`** | <code>boolean</code> |
1245
+ | **`latestEvent`** | <code><a href="#latesteventpreview">LatestEventPreview</a></code> |
1246
+
1247
+
1248
+ #### LatestEventPreview
1249
+
1250
+ | Prop | Type |
1251
+ | ----------------------- | ---------------------------------------------------------------- |
1252
+ | **`roomId`** | <code>string</code> |
1253
+ | **`senderId`** | <code>string</code> |
1254
+ | **`type`** | <code>string</code> |
1255
+ | **`content`** | <code><a href="#record">Record</a>&lt;string, unknown&gt;</code> |
1256
+ | **`originServerTs`** | <code>number</code> |
1257
+ | **`senderDisplayName`** | <code>string</code> |
1244
1258
 
1245
1259
 
1246
1260
  #### RoomMember
@@ -16,8 +16,12 @@ import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener
16
16
  import org.matrix.rustcomponents.sdk.EventOrTransactionId
17
17
  import org.matrix.rustcomponents.sdk.EventSendState
18
18
  import org.matrix.rustcomponents.sdk.EventTimelineItem
19
+ import org.matrix.rustcomponents.sdk.LatestEventValue
20
+ import org.matrix.rustcomponents.sdk.ProfileDetails
21
+ import org.matrix.rustcomponents.sdk.MembershipChange
19
22
  import org.matrix.rustcomponents.sdk.MembershipState
20
23
  import org.matrix.rustcomponents.sdk.MessageType
24
+ import org.matrix.rustcomponents.sdk.OtherState
21
25
  import org.matrix.rustcomponents.sdk.MsgLikeKind
22
26
  import org.matrix.rustcomponents.sdk.ReceiptType
23
27
  import org.matrix.rustcomponents.sdk.RecoveryException
@@ -67,6 +71,7 @@ class MatrixSDKBridge(private val context: Context) {
67
71
  private val paginatingRooms = Collections.synchronizedSet(mutableSetOf<String>())
68
72
  // Per-room tracking of the oldest event ID returned to JS, used for pagination cursor
69
73
  private val oldestReturnedEventId = mutableMapOf<String, String>()
74
+ private val roomCreatedAtCache = mutableMapOf<String, Long>()
70
75
  private var receiptSyncJob: Job? = null
71
76
  // Receipt cache: roomId → (eventId → set of userIds who sent a read receipt)
72
77
  // Populated by the parallel v2 receipt sync since sliding sync doesn't deliver
@@ -283,10 +288,12 @@ class MatrixSDKBridge(private val context: Context) {
283
288
  val handle = timeline.addListener(object : TimelineListener {
284
289
  private fun emitRoomUpdate() {
285
290
  scope.launch {
286
- val unreadCount = try {
287
- room.roomInfo().numUnreadMessages?.toInt() ?: 0
288
- } catch (_: Exception) { 0 }
289
- onRoomUpdate(roomId, mapOf("roomId" to roomId, "unreadCount" to unreadCount))
291
+ val summary = try {
292
+ serializeRoom(room)
293
+ } catch (_: Exception) {
294
+ mapOf<String, Any?>("roomId" to roomId)
295
+ }
296
+ onRoomUpdate(roomId, summary)
290
297
  }
291
298
  }
292
299
 
@@ -362,11 +369,45 @@ class MatrixSDKBridge(private val context: Context) {
362
369
  val c = requireClient()
363
370
  val result = mutableListOf<Map<String, Any?>>()
364
371
  for (room in c.rooms()) {
365
- result.add(serializeRoom(room))
372
+ val dict = serializeRoom(room).toMutableMap()
373
+ if (dict["lastEventTs"] == null) {
374
+ fetchRoomCreatedAt(room.id())?.let { dict["createdAt"] = it }
375
+ }
376
+ result.add(dict)
366
377
  }
367
378
  return result
368
379
  }
369
380
 
381
+ private fun fetchRoomCreatedAt(roomId: String): Long? {
382
+ roomCreatedAtCache[roomId]?.let { return it }
383
+ val session = sessionStore.load() ?: return null
384
+ val baseUrl = session.homeserverUrl.trimEnd('/')
385
+ val encodedRoomId = java.net.URLEncoder.encode(roomId, "UTF-8")
386
+ val url = URL("$baseUrl/_matrix/client/v3/rooms/$encodedRoomId/state")
387
+ return try {
388
+ val connection = url.openConnection() as HttpURLConnection
389
+ connection.requestMethod = "GET"
390
+ connection.setRequestProperty("Authorization", "Bearer ${session.accessToken}")
391
+ val responseCode = connection.responseCode
392
+ if (responseCode !in 200..299) return null
393
+ val body = connection.inputStream.bufferedReader().readText()
394
+ val events = org.json.JSONArray(body)
395
+ for (i in 0 until events.length()) {
396
+ val event = events.getJSONObject(i)
397
+ if (event.optString("type") == "m.room.create") {
398
+ val ts = event.optLong("origin_server_ts", -1)
399
+ if (ts > 0) {
400
+ roomCreatedAtCache[roomId] = ts
401
+ return ts
402
+ }
403
+ }
404
+ }
405
+ null
406
+ } catch (_: Exception) {
407
+ null
408
+ }
409
+ }
410
+
370
411
  suspend fun getRoomMembers(roomId: String): List<Map<String, Any?>> {
371
412
  val c = requireClient()
372
413
  val room = c.getRoom(roomId) ?: throw IllegalArgumentException("Room $roomId not found")
@@ -1167,18 +1208,99 @@ class MatrixSDKBridge(private val context: Context) {
1167
1208
  val avatarUrl = info.rawName?.let { null } // Rust SDK doesn't expose avatar URL via RoomInfo
1168
1209
  // TODO: Expose room avatar from Rust SDK when available
1169
1210
 
1170
- return mapOf(
1211
+ val latestEvent = serializeLatestEvent(room.latestEvent(), room.id())
1212
+
1213
+ val result = mutableMapOf<String, Any?>(
1171
1214
  "roomId" to room.id(),
1172
1215
  "name" to (info.displayName ?: ""),
1173
1216
  "topic" to info.topic,
1174
1217
  "memberCount" to info.joinedMembersCount.toInt(),
1175
1218
  "isEncrypted" to (info.encryptionState != EncryptionState.NOT_ENCRYPTED),
1176
1219
  "unreadCount" to (info.numUnreadMessages?.toInt() ?: 0),
1177
- "lastEventTs" to null,
1220
+ "lastEventTs" to latestEvent?.get("originServerTs"),
1178
1221
  "membership" to membership,
1179
1222
  "avatarUrl" to avatarUrl,
1180
1223
  "isDirect" to isDirect,
1181
1224
  )
1225
+ if (latestEvent != null) {
1226
+ result["latestEvent"] = latestEvent
1227
+ }
1228
+ return result
1229
+ }
1230
+
1231
+ private fun serializeLatestEvent(value: LatestEventValue, roomId: String): Map<String, Any?>? {
1232
+ val timestamp: Long
1233
+ val sender: String
1234
+ val profile: ProfileDetails
1235
+ val content: TimelineItemContent
1236
+
1237
+ when (value) {
1238
+ is LatestEventValue.None -> return null
1239
+ is LatestEventValue.Remote -> {
1240
+ timestamp = value.timestamp.toLong()
1241
+ sender = value.sender
1242
+ profile = value.profile
1243
+ content = value.content
1244
+ }
1245
+ is LatestEventValue.Local -> {
1246
+ timestamp = value.timestamp.toLong()
1247
+ sender = value.sender
1248
+ profile = value.profile
1249
+ content = value.content
1250
+ }
1251
+ else -> return null
1252
+ }
1253
+
1254
+ val contentMap = mutableMapOf<String, Any?>()
1255
+ var eventType = "m.room.message"
1256
+
1257
+ when (content) {
1258
+ is TimelineItemContent.MsgLike -> {
1259
+ when (val kind = content.content.kind) {
1260
+ is MsgLikeKind.Message -> {
1261
+ contentMap["body"] = kind.content.body
1262
+ when (kind.content.msgType) {
1263
+ is MessageType.Text -> contentMap["msgtype"] = "m.text"
1264
+ is MessageType.Image -> contentMap["msgtype"] = "m.image"
1265
+ is MessageType.File -> contentMap["msgtype"] = "m.file"
1266
+ is MessageType.Audio -> contentMap["msgtype"] = "m.audio"
1267
+ is MessageType.Video -> contentMap["msgtype"] = "m.video"
1268
+ is MessageType.Emote -> contentMap["msgtype"] = "m.emote"
1269
+ is MessageType.Notice -> contentMap["msgtype"] = "m.notice"
1270
+ else -> contentMap["msgtype"] = "m.text"
1271
+ }
1272
+ }
1273
+ is MsgLikeKind.UnableToDecrypt -> {
1274
+ contentMap["body"] = "Unable to decrypt message"
1275
+ contentMap["msgtype"] = "m.text"
1276
+ contentMap["encrypted"] = true
1277
+ }
1278
+ is MsgLikeKind.Redacted -> {
1279
+ eventType = "m.room.redaction"
1280
+ contentMap["body"] = "Message deleted"
1281
+ }
1282
+ else -> eventType = "m.room.unknown"
1283
+ }
1284
+ }
1285
+ else -> eventType = "m.room.unknown"
1286
+ }
1287
+
1288
+ var senderDisplayName: String? = null
1289
+ if (profile is ProfileDetails.Ready) {
1290
+ senderDisplayName = profile.displayName
1291
+ }
1292
+
1293
+ val result = mutableMapOf<String, Any?>(
1294
+ "roomId" to roomId,
1295
+ "senderId" to sender,
1296
+ "type" to eventType,
1297
+ "content" to contentMap,
1298
+ "originServerTs" to timestamp,
1299
+ )
1300
+ if (senderDisplayName != null) {
1301
+ result["senderDisplayName"] = senderDisplayName
1302
+ }
1303
+ return result
1182
1304
  }
1183
1305
 
1184
1306
  /**
@@ -1299,6 +1421,7 @@ class MatrixSDKBridge(private val context: Context) {
1299
1421
 
1300
1422
  val contentMap = mutableMapOf<String, Any?>()
1301
1423
  var eventType = "m.room.message"
1424
+ var stateKey: String? = null
1302
1425
 
1303
1426
  try {
1304
1427
  val content = eventItem.content
@@ -1369,7 +1492,30 @@ class MatrixSDKBridge(private val context: Context) {
1369
1492
  }
1370
1493
  }
1371
1494
  }
1372
- is TimelineItemContent.RoomMembership -> eventType = "m.room.member"
1495
+ is TimelineItemContent.RoomMembership -> {
1496
+ eventType = "m.room.member"
1497
+ stateKey = content.userId
1498
+ val membership = when (content.change) {
1499
+ MembershipChange.JOINED, MembershipChange.INVITATION_ACCEPTED -> "join"
1500
+ MembershipChange.LEFT -> "leave"
1501
+ MembershipChange.BANNED, MembershipChange.KICKED_AND_BANNED -> "ban"
1502
+ MembershipChange.INVITED -> "invite"
1503
+ MembershipChange.KICKED -> "leave"
1504
+ MembershipChange.UNBANNED -> "leave"
1505
+ else -> "join"
1506
+ }
1507
+ contentMap["membership"] = membership
1508
+ contentMap["displayname"] = content.userDisplayName ?: content.userId
1509
+ }
1510
+ is TimelineItemContent.State -> {
1511
+ stateKey = content.stateKey
1512
+ when (content.content) {
1513
+ is OtherState.RoomCreate -> eventType = "m.room.create"
1514
+ is OtherState.RoomName -> eventType = "m.room.name"
1515
+ is OtherState.RoomTopic -> eventType = "m.room.topic"
1516
+ else -> eventType = "m.room.unknown"
1517
+ }
1518
+ }
1373
1519
  else -> {}
1374
1520
  }
1375
1521
  } catch (e: Exception) {
@@ -1408,7 +1554,13 @@ class MatrixSDKBridge(private val context: Context) {
1408
1554
  status = "sent"
1409
1555
  }
1410
1556
 
1411
- return mapOf(
1557
+ // Build unsigned dict — include transaction_id when available
1558
+ val unsigned: Map<String, Any?>? = when (val id = eventItem.eventOrTransactionId) {
1559
+ is EventOrTransactionId.TransactionId -> mapOf("transaction_id" to id.transactionId)
1560
+ else -> null
1561
+ }
1562
+
1563
+ val result = mutableMapOf<String, Any?>(
1412
1564
  "eventId" to eventId,
1413
1565
  "roomId" to roomId,
1414
1566
  "senderId" to eventItem.sender,
@@ -1418,6 +1570,13 @@ class MatrixSDKBridge(private val context: Context) {
1418
1570
  "status" to status,
1419
1571
  "readBy" to readBy,
1420
1572
  )
1573
+ if (stateKey != null) {
1574
+ result["stateKey"] = stateKey
1575
+ }
1576
+ if (unsigned != null) {
1577
+ result["unsigned"] = unsigned
1578
+ }
1579
+ return result
1421
1580
  }
1422
1581
 
1423
1582
  // ── Receipt Sync (parallel v2 sync for read receipts) ──────
package/dist/docs.json CHANGED
@@ -1159,6 +1159,13 @@
1159
1159
  "complexTypes": [],
1160
1160
  "type": "number | undefined"
1161
1161
  },
1162
+ {
1163
+ "name": "createdAt",
1164
+ "tags": [],
1165
+ "docs": "",
1166
+ "complexTypes": [],
1167
+ "type": "number | undefined"
1168
+ },
1162
1169
  {
1163
1170
  "name": "membership",
1164
1171
  "tags": [],
@@ -1179,6 +1186,68 @@
1179
1186
  "docs": "",
1180
1187
  "complexTypes": [],
1181
1188
  "type": "boolean | undefined"
1189
+ },
1190
+ {
1191
+ "name": "latestEvent",
1192
+ "tags": [],
1193
+ "docs": "",
1194
+ "complexTypes": [
1195
+ "LatestEventPreview"
1196
+ ],
1197
+ "type": "LatestEventPreview"
1198
+ }
1199
+ ]
1200
+ },
1201
+ {
1202
+ "name": "LatestEventPreview",
1203
+ "slug": "latesteventpreview",
1204
+ "docs": "",
1205
+ "tags": [],
1206
+ "methods": [],
1207
+ "properties": [
1208
+ {
1209
+ "name": "roomId",
1210
+ "tags": [],
1211
+ "docs": "",
1212
+ "complexTypes": [],
1213
+ "type": "string"
1214
+ },
1215
+ {
1216
+ "name": "senderId",
1217
+ "tags": [],
1218
+ "docs": "",
1219
+ "complexTypes": [],
1220
+ "type": "string"
1221
+ },
1222
+ {
1223
+ "name": "type",
1224
+ "tags": [],
1225
+ "docs": "",
1226
+ "complexTypes": [],
1227
+ "type": "string"
1228
+ },
1229
+ {
1230
+ "name": "content",
1231
+ "tags": [],
1232
+ "docs": "",
1233
+ "complexTypes": [
1234
+ "Record"
1235
+ ],
1236
+ "type": "Record<string, unknown>"
1237
+ },
1238
+ {
1239
+ "name": "originServerTs",
1240
+ "tags": [],
1241
+ "docs": "",
1242
+ "complexTypes": [],
1243
+ "type": "number"
1244
+ },
1245
+ {
1246
+ "name": "senderDisplayName",
1247
+ "tags": [],
1248
+ "docs": "",
1249
+ "complexTypes": [],
1250
+ "type": "string | undefined"
1182
1251
  }
1183
1252
  ]
1184
1253
  },
@@ -115,6 +115,14 @@ export interface MatrixEvent {
115
115
  /** Unsigned data (e.g. m.relations for edits, transaction_id for local echo) */
116
116
  unsigned?: Record<string, unknown>;
117
117
  }
118
+ export interface LatestEventPreview {
119
+ roomId: string;
120
+ senderId: string;
121
+ type: string;
122
+ content: Record<string, unknown>;
123
+ originServerTs: number;
124
+ senderDisplayName?: string;
125
+ }
118
126
  export interface RoomSummary {
119
127
  roomId: string;
120
128
  name: string;
@@ -123,9 +131,11 @@ export interface RoomSummary {
123
131
  isEncrypted: boolean;
124
132
  unreadCount: number;
125
133
  lastEventTs?: number;
134
+ createdAt?: number;
126
135
  membership?: 'join' | 'invite' | 'leave' | 'ban';
127
136
  avatarUrl?: string;
128
137
  isDirect?: boolean;
138
+ latestEvent?: LatestEventPreview;
129
139
  }
130
140
  export interface RoomMember {
131
141
  userId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// Auth & Session\n\nexport interface LoginOptions {\n homeserverUrl: string;\n userId: string;\n password: string;\n}\n\nexport interface LoginWithTokenOptions {\n homeserverUrl: string;\n accessToken: string;\n userId: string;\n deviceId: string;\n}\n\nexport interface SessionInfo {\n accessToken: string;\n userId: string;\n deviceId: string;\n homeserverUrl: string;\n}\n\n// Messaging\n\nexport interface SendMessageOptions {\n roomId: string;\n body: string;\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds (sets info.duration per Matrix spec) */\n duration?: number;\n /** Image/video width in pixels (sets info.w per Matrix spec) */\n width?: number;\n /** Image/video height in pixels (sets info.h per Matrix spec) */\n height?: number;\n}\n\n// Presence\n\nexport interface PresenceInfo {\n presence: 'online' | 'offline' | 'unavailable';\n statusMsg?: string;\n lastActiveAgo?: number;\n}\n\n// Typing\n\nexport interface TypingEvent {\n roomId: string;\n userIds: string[];\n}\n\nexport interface ReceiptReceivedEvent {\n roomId: string;\n /** The event that was read */\n eventId: string;\n /** The user who sent the read receipt */\n userId: string;\n}\n\nexport interface PresenceChangedEvent {\n userId: string;\n presence: PresenceInfo;\n}\n\n// Edit & Reply\n\nexport interface EditMessageOptions {\n roomId: string;\n eventId: string;\n newBody: string;\n /** Required when editing a media message; must match the original msgtype */\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n /** New file to replace the media content (optional for caption-only edits) */\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds */\n duration?: number;\n /** Image/video width in pixels */\n width?: number;\n /** Image/video height in pixels */\n height?: number;\n}\n\nexport interface SendReplyOptions {\n roomId: string;\n body: string;\n replyToEventId: string;\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds (sets info.duration per Matrix spec) */\n duration?: number;\n /** Image/video width in pixels (sets info.w per Matrix spec) */\n width?: number;\n /** Image/video height in pixels (sets info.h per Matrix spec) */\n height?: number;\n}\n\n// Upload\n\nexport interface UploadContentOptions {\n fileUri: string;\n fileName: string;\n mimeType: string;\n}\n\nexport interface UploadContentResult {\n contentUri: string;\n}\n\n// Thumbnail\n\nexport interface ThumbnailUrlOptions {\n mxcUrl: string;\n width: number;\n height: number;\n method?: 'scale' | 'crop';\n}\n\nexport interface MatrixEvent {\n eventId: string;\n roomId: string;\n senderId: string;\n type: string;\n /** State key for state events (e.g. target user ID for m.room.member) */\n stateKey?: string;\n content: Record<string, unknown>;\n originServerTs: number;\n /** Delivery/read status for own messages: 'sending' | 'sent' | 'delivered' | 'read' */\n status?: 'sending' | 'sent' | 'delivered' | 'read';\n /** User IDs that have read this event */\n readBy?: string[];\n /** Unsigned data (e.g. m.relations for edits, transaction_id for local echo) */\n unsigned?: Record<string, unknown>;\n}\n\n// Rooms\n\nexport interface RoomSummary {\n roomId: string;\n name: string;\n topic?: string;\n memberCount: number;\n isEncrypted: boolean;\n unreadCount: number;\n lastEventTs?: number;\n membership?: 'join' | 'invite' | 'leave' | 'ban';\n avatarUrl?: string;\n isDirect?: boolean;\n}\n\nexport interface RoomMember {\n userId: string;\n displayName?: string;\n membership: 'join' | 'invite' | 'leave' | 'ban';\n avatarUrl?: string;\n}\n\n// Device Management\n\nexport interface DeviceInfo {\n deviceId: string;\n displayName?: string;\n lastSeenTs?: number;\n lastSeenIp?: string;\n /** Whether this device is verified via cross-signing */\n isCrossSigningVerified?: boolean;\n}\n\n// Pusher\n\nexport interface PusherOptions {\n pushkey: string;\n kind: string | null;\n appId: string;\n appDisplayName: string;\n deviceDisplayName: string;\n lang: string;\n data: { url: string; format?: string };\n}\n\n// User Discovery\n\nexport interface UserProfile {\n userId: string;\n displayName?: string;\n avatarUrl?: string;\n}\n\n// Encryption\n\nexport interface CrossSigningStatus {\n hasMaster: boolean;\n hasSelfSigning: boolean;\n hasUserSigning: boolean;\n isReady: boolean;\n}\n\nexport interface KeyBackupStatus {\n exists: boolean;\n version?: string;\n enabled: boolean;\n}\n\nexport interface RecoveryKeyInfo {\n recoveryKey: string;\n}\n\nexport interface EncryptionStatus {\n isCrossSigningReady: boolean;\n crossSigningStatus: CrossSigningStatus;\n isKeyBackupEnabled: boolean;\n keyBackupVersion?: string;\n isSecretStorageReady: boolean;\n}\n\n// Events & Sync\n\nexport type SyncState = 'INITIAL' | 'SYNCING' | 'ERROR' | 'STOPPED';\n\nexport interface SyncStateChangeEvent {\n state: SyncState;\n error?: string;\n}\n\nexport interface MessageReceivedEvent {\n event: MatrixEvent;\n}\n\nexport interface RoomUpdatedEvent {\n roomId: string;\n summary: RoomSummary;\n}\n\n// Plugin Interface\n\nexport interface MatrixPlugin {\n // Auth\n login(options: LoginOptions): Promise<SessionInfo>;\n loginWithToken(options: LoginWithTokenOptions): Promise<SessionInfo>;\n logout(): Promise<void>;\n getSession(): Promise<SessionInfo | null>;\n\n // Sync\n startSync(): Promise<void>;\n stopSync(): Promise<void>;\n getSyncState(): Promise<{ state: SyncState }>;\n\n // Rooms\n createRoom(options: {\n name?: string;\n topic?: string;\n isEncrypted?: boolean;\n isDirect?: boolean;\n invite?: string[];\n preset?: 'private_chat' | 'trusted_private_chat' | 'public_chat';\n historyVisibility?: 'invited' | 'joined' | 'shared' | 'world_readable';\n }): Promise<{ roomId: string }>;\n getRooms(): Promise<{ rooms: RoomSummary[] }>;\n getRoomMembers(options: { roomId: string }): Promise<{ members: RoomMember[] }>;\n joinRoom(options: { roomIdOrAlias: string }): Promise<{ roomId: string }>;\n leaveRoom(options: { roomId: string }): Promise<void>;\n forgetRoom(options: { roomId: string }): Promise<void>;\n\n // Messaging\n sendMessage(options: SendMessageOptions): Promise<{ eventId: string }>;\n editMessage(options: EditMessageOptions): Promise<{ eventId: string }>;\n sendReply(options: SendReplyOptions): Promise<{ eventId: string }>;\n getRoomMessages(options: {\n roomId: string;\n limit?: number;\n from?: string;\n }): Promise<{ events: MatrixEvent[]; nextBatch?: string }>;\n markRoomAsRead(options: {\n roomId: string;\n eventId: string;\n }): Promise<void>;\n refreshEventStatuses(options: {\n roomId: string;\n eventIds: string[];\n }): Promise<{ events: MatrixEvent[] }>;\n redactEvent(options: {\n roomId: string;\n eventId: string;\n reason?: string;\n }): Promise<void>;\n sendReaction(options: {\n roomId: string;\n eventId: string;\n key: string;\n }): Promise<{ eventId: string }>;\n\n // Room Management\n setRoomName(options: { roomId: string; name: string }): Promise<void>;\n setRoomTopic(options: { roomId: string; topic: string }): Promise<void>;\n setRoomAvatar(options: { roomId: string; mxcUrl: string }): Promise<void>;\n inviteUser(options: { roomId: string; userId: string }): Promise<void>;\n kickUser(options: { roomId: string; userId: string; reason?: string }): Promise<void>;\n banUser(options: { roomId: string; userId: string; reason?: string }): Promise<void>;\n unbanUser(options: { roomId: string; userId: string }): Promise<void>;\n\n // Typing\n sendTyping(options: {\n roomId: string;\n isTyping: boolean;\n timeout?: number;\n }): Promise<void>;\n\n // Media\n getMediaUrl(options: { mxcUrl: string }): Promise<{ httpUrl: string }>;\n getThumbnailUrl(options: ThumbnailUrlOptions): Promise<{ httpUrl: string }>;\n uploadContent(options: UploadContentOptions): Promise<UploadContentResult>;\n\n // User Discovery\n searchUsers(options: {\n searchTerm: string;\n limit?: number;\n }): Promise<{ results: UserProfile[]; limited: boolean }>;\n\n // Presence\n setPresence(options: {\n presence: 'online' | 'offline' | 'unavailable';\n statusMsg?: string;\n }): Promise<void>;\n getPresence(options: { userId: string }): Promise<PresenceInfo>;\n\n // Device Management\n getDevices(): Promise<{ devices: DeviceInfo[] }>;\n deleteDevice(options: { deviceId: string; auth?: Record<string, unknown> }): Promise<void>;\n verifyDevice(options: { deviceId: string }): Promise<void>;\n\n // Push\n setPusher(options: PusherOptions): Promise<void>;\n\n // Encryption\n initializeCrypto(): Promise<void>;\n getEncryptionStatus(): Promise<EncryptionStatus>;\n bootstrapCrossSigning(): Promise<void>;\n setupKeyBackup(): Promise<KeyBackupStatus>;\n getKeyBackupStatus(): Promise<KeyBackupStatus>;\n restoreKeyBackup(options?: {\n recoveryKey?: string;\n }): Promise<{ importedKeys: number }>;\n setupRecovery(options?: {\n passphrase?: string;\n /**\n * Passphrase for the *existing* secret storage key, used by\n * bootstrapSecretStorage to decrypt and migrate the current cross-signing\n * and backup secrets into the newly created SSSS. Only needed on web;\n * native platforms (Rust SDK) handle the migration internally.\n */\n existingPassphrase?: string;\n }): Promise<RecoveryKeyInfo>;\n /** Wipe all local Matrix state (crypto DB, session, caches). */\n clearAllData(): Promise<void>;\n isRecoveryEnabled(): Promise<{ enabled: boolean }>;\n recoverAndSetup(options: {\n recoveryKey?: string;\n passphrase?: string;\n }): Promise<void>;\n resetRecoveryKey(options?: {\n passphrase?: string;\n }): Promise<RecoveryKeyInfo>;\n exportRoomKeys(options: {\n passphrase: string;\n }): Promise<{ data: string }>;\n importRoomKeys(options: {\n data: string;\n passphrase: string;\n }): Promise<{ importedKeys: number }>;\n\n // Listeners\n addListener(\n event: 'syncStateChange',\n listenerFunc: (data: SyncStateChangeEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'messageReceived',\n listenerFunc: (data: MessageReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'roomUpdated',\n listenerFunc: (data: RoomUpdatedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'typingChanged',\n listenerFunc: (data: TypingEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'receiptReceived',\n listenerFunc: (data: ReceiptReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'presenceChanged',\n listenerFunc: (data: PresenceChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n // Token Refresh\n updateAccessToken(options: { accessToken: string }): Promise<void>;\n\n addListener(\n event: 'tokenRefreshRequired',\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n// Auth & Session\n\nexport interface LoginOptions {\n homeserverUrl: string;\n userId: string;\n password: string;\n}\n\nexport interface LoginWithTokenOptions {\n homeserverUrl: string;\n accessToken: string;\n userId: string;\n deviceId: string;\n}\n\nexport interface SessionInfo {\n accessToken: string;\n userId: string;\n deviceId: string;\n homeserverUrl: string;\n}\n\n// Messaging\n\nexport interface SendMessageOptions {\n roomId: string;\n body: string;\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds (sets info.duration per Matrix spec) */\n duration?: number;\n /** Image/video width in pixels (sets info.w per Matrix spec) */\n width?: number;\n /** Image/video height in pixels (sets info.h per Matrix spec) */\n height?: number;\n}\n\n// Presence\n\nexport interface PresenceInfo {\n presence: 'online' | 'offline' | 'unavailable';\n statusMsg?: string;\n lastActiveAgo?: number;\n}\n\n// Typing\n\nexport interface TypingEvent {\n roomId: string;\n userIds: string[];\n}\n\nexport interface ReceiptReceivedEvent {\n roomId: string;\n /** The event that was read */\n eventId: string;\n /** The user who sent the read receipt */\n userId: string;\n}\n\nexport interface PresenceChangedEvent {\n userId: string;\n presence: PresenceInfo;\n}\n\n// Edit & Reply\n\nexport interface EditMessageOptions {\n roomId: string;\n eventId: string;\n newBody: string;\n /** Required when editing a media message; must match the original msgtype */\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n /** New file to replace the media content (optional for caption-only edits) */\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds */\n duration?: number;\n /** Image/video width in pixels */\n width?: number;\n /** Image/video height in pixels */\n height?: number;\n}\n\nexport interface SendReplyOptions {\n roomId: string;\n body: string;\n replyToEventId: string;\n msgtype?: 'm.text' | 'm.notice' | 'm.emote' | 'm.image' | 'm.audio' | 'm.video' | 'm.file';\n fileUri?: string;\n fileName?: string;\n mimeType?: string;\n fileSize?: number;\n /** Audio/video duration in milliseconds (sets info.duration per Matrix spec) */\n duration?: number;\n /** Image/video width in pixels (sets info.w per Matrix spec) */\n width?: number;\n /** Image/video height in pixels (sets info.h per Matrix spec) */\n height?: number;\n}\n\n// Upload\n\nexport interface UploadContentOptions {\n fileUri: string;\n fileName: string;\n mimeType: string;\n}\n\nexport interface UploadContentResult {\n contentUri: string;\n}\n\n// Thumbnail\n\nexport interface ThumbnailUrlOptions {\n mxcUrl: string;\n width: number;\n height: number;\n method?: 'scale' | 'crop';\n}\n\nexport interface MatrixEvent {\n eventId: string;\n roomId: string;\n senderId: string;\n type: string;\n /** State key for state events (e.g. target user ID for m.room.member) */\n stateKey?: string;\n content: Record<string, unknown>;\n originServerTs: number;\n /** Delivery/read status for own messages: 'sending' | 'sent' | 'delivered' | 'read' */\n status?: 'sending' | 'sent' | 'delivered' | 'read';\n /** User IDs that have read this event */\n readBy?: string[];\n /** Unsigned data (e.g. m.relations for edits, transaction_id for local echo) */\n unsigned?: Record<string, unknown>;\n}\n\n// Rooms\n\nexport interface LatestEventPreview {\n roomId: string;\n senderId: string;\n type: string;\n content: Record<string, unknown>;\n originServerTs: number;\n senderDisplayName?: string;\n}\n\nexport interface RoomSummary {\n roomId: string;\n name: string;\n topic?: string;\n memberCount: number;\n isEncrypted: boolean;\n unreadCount: number;\n lastEventTs?: number;\n createdAt?: number;\n membership?: 'join' | 'invite' | 'leave' | 'ban';\n avatarUrl?: string;\n isDirect?: boolean;\n latestEvent?: LatestEventPreview;\n}\n\nexport interface RoomMember {\n userId: string;\n displayName?: string;\n membership: 'join' | 'invite' | 'leave' | 'ban';\n avatarUrl?: string;\n}\n\n// Device Management\n\nexport interface DeviceInfo {\n deviceId: string;\n displayName?: string;\n lastSeenTs?: number;\n lastSeenIp?: string;\n /** Whether this device is verified via cross-signing */\n isCrossSigningVerified?: boolean;\n}\n\n// Pusher\n\nexport interface PusherOptions {\n pushkey: string;\n kind: string | null;\n appId: string;\n appDisplayName: string;\n deviceDisplayName: string;\n lang: string;\n data: { url: string; format?: string };\n}\n\n// User Discovery\n\nexport interface UserProfile {\n userId: string;\n displayName?: string;\n avatarUrl?: string;\n}\n\n// Encryption\n\nexport interface CrossSigningStatus {\n hasMaster: boolean;\n hasSelfSigning: boolean;\n hasUserSigning: boolean;\n isReady: boolean;\n}\n\nexport interface KeyBackupStatus {\n exists: boolean;\n version?: string;\n enabled: boolean;\n}\n\nexport interface RecoveryKeyInfo {\n recoveryKey: string;\n}\n\nexport interface EncryptionStatus {\n isCrossSigningReady: boolean;\n crossSigningStatus: CrossSigningStatus;\n isKeyBackupEnabled: boolean;\n keyBackupVersion?: string;\n isSecretStorageReady: boolean;\n}\n\n// Events & Sync\n\nexport type SyncState = 'INITIAL' | 'SYNCING' | 'ERROR' | 'STOPPED';\n\nexport interface SyncStateChangeEvent {\n state: SyncState;\n error?: string;\n}\n\nexport interface MessageReceivedEvent {\n event: MatrixEvent;\n}\n\nexport interface RoomUpdatedEvent {\n roomId: string;\n summary: RoomSummary;\n}\n\n// Plugin Interface\n\nexport interface MatrixPlugin {\n // Auth\n login(options: LoginOptions): Promise<SessionInfo>;\n loginWithToken(options: LoginWithTokenOptions): Promise<SessionInfo>;\n logout(): Promise<void>;\n getSession(): Promise<SessionInfo | null>;\n\n // Sync\n startSync(): Promise<void>;\n stopSync(): Promise<void>;\n getSyncState(): Promise<{ state: SyncState }>;\n\n // Rooms\n createRoom(options: {\n name?: string;\n topic?: string;\n isEncrypted?: boolean;\n isDirect?: boolean;\n invite?: string[];\n preset?: 'private_chat' | 'trusted_private_chat' | 'public_chat';\n historyVisibility?: 'invited' | 'joined' | 'shared' | 'world_readable';\n }): Promise<{ roomId: string }>;\n getRooms(): Promise<{ rooms: RoomSummary[] }>;\n getRoomMembers(options: { roomId: string }): Promise<{ members: RoomMember[] }>;\n joinRoom(options: { roomIdOrAlias: string }): Promise<{ roomId: string }>;\n leaveRoom(options: { roomId: string }): Promise<void>;\n forgetRoom(options: { roomId: string }): Promise<void>;\n\n // Messaging\n sendMessage(options: SendMessageOptions): Promise<{ eventId: string }>;\n editMessage(options: EditMessageOptions): Promise<{ eventId: string }>;\n sendReply(options: SendReplyOptions): Promise<{ eventId: string }>;\n getRoomMessages(options: {\n roomId: string;\n limit?: number;\n from?: string;\n }): Promise<{ events: MatrixEvent[]; nextBatch?: string }>;\n markRoomAsRead(options: {\n roomId: string;\n eventId: string;\n }): Promise<void>;\n refreshEventStatuses(options: {\n roomId: string;\n eventIds: string[];\n }): Promise<{ events: MatrixEvent[] }>;\n redactEvent(options: {\n roomId: string;\n eventId: string;\n reason?: string;\n }): Promise<void>;\n sendReaction(options: {\n roomId: string;\n eventId: string;\n key: string;\n }): Promise<{ eventId: string }>;\n\n // Room Management\n setRoomName(options: { roomId: string; name: string }): Promise<void>;\n setRoomTopic(options: { roomId: string; topic: string }): Promise<void>;\n setRoomAvatar(options: { roomId: string; mxcUrl: string }): Promise<void>;\n inviteUser(options: { roomId: string; userId: string }): Promise<void>;\n kickUser(options: { roomId: string; userId: string; reason?: string }): Promise<void>;\n banUser(options: { roomId: string; userId: string; reason?: string }): Promise<void>;\n unbanUser(options: { roomId: string; userId: string }): Promise<void>;\n\n // Typing\n sendTyping(options: {\n roomId: string;\n isTyping: boolean;\n timeout?: number;\n }): Promise<void>;\n\n // Media\n getMediaUrl(options: { mxcUrl: string }): Promise<{ httpUrl: string }>;\n getThumbnailUrl(options: ThumbnailUrlOptions): Promise<{ httpUrl: string }>;\n uploadContent(options: UploadContentOptions): Promise<UploadContentResult>;\n\n // User Discovery\n searchUsers(options: {\n searchTerm: string;\n limit?: number;\n }): Promise<{ results: UserProfile[]; limited: boolean }>;\n\n // Presence\n setPresence(options: {\n presence: 'online' | 'offline' | 'unavailable';\n statusMsg?: string;\n }): Promise<void>;\n getPresence(options: { userId: string }): Promise<PresenceInfo>;\n\n // Device Management\n getDevices(): Promise<{ devices: DeviceInfo[] }>;\n deleteDevice(options: { deviceId: string; auth?: Record<string, unknown> }): Promise<void>;\n verifyDevice(options: { deviceId: string }): Promise<void>;\n\n // Push\n setPusher(options: PusherOptions): Promise<void>;\n\n // Encryption\n initializeCrypto(): Promise<void>;\n getEncryptionStatus(): Promise<EncryptionStatus>;\n bootstrapCrossSigning(): Promise<void>;\n setupKeyBackup(): Promise<KeyBackupStatus>;\n getKeyBackupStatus(): Promise<KeyBackupStatus>;\n restoreKeyBackup(options?: {\n recoveryKey?: string;\n }): Promise<{ importedKeys: number }>;\n setupRecovery(options?: {\n passphrase?: string;\n /**\n * Passphrase for the *existing* secret storage key, used by\n * bootstrapSecretStorage to decrypt and migrate the current cross-signing\n * and backup secrets into the newly created SSSS. Only needed on web;\n * native platforms (Rust SDK) handle the migration internally.\n */\n existingPassphrase?: string;\n }): Promise<RecoveryKeyInfo>;\n /** Wipe all local Matrix state (crypto DB, session, caches). */\n clearAllData(): Promise<void>;\n isRecoveryEnabled(): Promise<{ enabled: boolean }>;\n recoverAndSetup(options: {\n recoveryKey?: string;\n passphrase?: string;\n }): Promise<void>;\n resetRecoveryKey(options?: {\n passphrase?: string;\n }): Promise<RecoveryKeyInfo>;\n exportRoomKeys(options: {\n passphrase: string;\n }): Promise<{ data: string }>;\n importRoomKeys(options: {\n data: string;\n passphrase: string;\n }): Promise<{ importedKeys: number }>;\n\n // Listeners\n addListener(\n event: 'syncStateChange',\n listenerFunc: (data: SyncStateChangeEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'messageReceived',\n listenerFunc: (data: MessageReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'roomUpdated',\n listenerFunc: (data: RoomUpdatedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'typingChanged',\n listenerFunc: (data: TypingEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'receiptReceived',\n listenerFunc: (data: ReceiptReceivedEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n event: 'presenceChanged',\n listenerFunc: (data: PresenceChangedEvent) => void,\n ): Promise<PluginListenerHandle>;\n // Token Refresh\n updateAccessToken(options: { accessToken: string }): Promise<void>;\n\n addListener(\n event: 'tokenRefreshRequired',\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n}\n"]}
package/dist/esm/web.d.ts CHANGED
@@ -8,6 +8,7 @@ export declare class MatrixWeb extends WebPlugin implements MatrixPlugin {
8
8
  private fallbackPassphrase?;
9
9
  private _tokenRefreshResolve?;
10
10
  private _tokenRefreshTimeout?;
11
+ private _roomUpdateTimers;
11
12
  private readonly _cryptoCallbacks;
12
13
  login(options: LoginOptions): Promise<SessionInfo>;
13
14
  loginWithToken(options: LoginWithTokenOptions): Promise<SessionInfo>;
package/dist/esm/web.js CHANGED
@@ -7,6 +7,7 @@ const SESSION_KEY = 'matrix_session';
7
7
  export class MatrixWeb extends WebPlugin {
8
8
  constructor() {
9
9
  super(...arguments);
10
+ this._roomUpdateTimers = new Map();
10
11
  this._cryptoCallbacks = {
11
12
  getSecretStorageKey: async (opts) => {
12
13
  var _a, _b;
@@ -195,6 +196,19 @@ export class MatrixWeb extends WebPlugin {
195
196
  }
196
197
  }
197
198
  }
199
+ // Debounced roomUpdated emission so room list stays current (unread count, sort order)
200
+ if (room) {
201
+ const existing = this._roomUpdateTimers.get(room.roomId);
202
+ if (existing)
203
+ clearTimeout(existing);
204
+ this._roomUpdateTimers.set(room.roomId, setTimeout(() => {
205
+ this._roomUpdateTimers.delete(room.roomId);
206
+ this.notifyListeners('roomUpdated', {
207
+ roomId: room.roomId,
208
+ summary: this.serializeRoom(room),
209
+ });
210
+ }, 200));
211
+ }
198
212
  });
199
213
  this.client.on(RoomEvent.Receipt, (event, room) => {
200
214
  var _a, _b;
@@ -1092,7 +1106,7 @@ export class MatrixWeb extends WebPlugin {
1092
1106
  return Object.assign(Object.assign({ eventId: eventId !== null && eventId !== void 0 ? eventId : '', roomId, senderId: sender !== null && sender !== void 0 ? sender : '', type: event.getType() }, (sk !== undefined && { stateKey: sk })), { content, originServerTs: event.getTs(), status, readBy: readBy.length > 0 ? readBy : undefined, unsigned });
1093
1107
  }
1094
1108
  serializeRoom(room) {
1095
- var _a, _b, _c, _d, _e, _f;
1109
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
1096
1110
  // Detect DM: check m.direct account data or guess from room state
1097
1111
  let isDirect = false;
1098
1112
  try {
@@ -1107,7 +1121,7 @@ export class MatrixWeb extends WebPlugin {
1107
1121
  }
1108
1122
  }
1109
1123
  }
1110
- catch (_g) {
1124
+ catch (_m) {
1111
1125
  // ignore
1112
1126
  }
1113
1127
  // Get avatar URL
@@ -1119,17 +1133,42 @@ export class MatrixWeb extends WebPlugin {
1119
1133
  avatarUrl = mxcUrl;
1120
1134
  }
1121
1135
  }
1136
+ // Build latestEvent from the room's last displayable timeline event
1137
+ let latestEvent;
1138
+ const timeline = room.getLiveTimeline().getEvents();
1139
+ for (let i = timeline.length - 1; i >= 0; i--) {
1140
+ const evt = timeline[i];
1141
+ const evtType = evt.getType();
1142
+ if (evtType === EventType.RoomMessage || evtType === EventType.Reaction) {
1143
+ const relatesTo = (_c = evt.getContent()) === null || _c === void 0 ? void 0 : _c['m.relates_to'];
1144
+ if ((relatesTo === null || relatesTo === void 0 ? void 0 : relatesTo['rel_type']) === RelationType.Replace)
1145
+ continue;
1146
+ const sender = (_d = evt.getSender()) !== null && _d !== void 0 ? _d : '';
1147
+ const senderMember = room.getMember(sender);
1148
+ latestEvent = {
1149
+ roomId: room.roomId,
1150
+ senderId: sender,
1151
+ type: evtType,
1152
+ content: evt.getContent(),
1153
+ originServerTs: evt.getTs(),
1154
+ senderDisplayName: (_e = senderMember === null || senderMember === void 0 ? void 0 : senderMember.name) !== null && _e !== void 0 ? _e : undefined,
1155
+ };
1156
+ break;
1157
+ }
1158
+ }
1122
1159
  return {
1123
1160
  roomId: room.roomId,
1124
1161
  name: room.name,
1125
- topic: (_e = (_d = (_c = room.currentState.getStateEvents('m.room.topic', '')) === null || _c === void 0 ? void 0 : _c.getContent()) === null || _d === void 0 ? void 0 : _d.topic) !== null && _e !== void 0 ? _e : undefined,
1162
+ topic: (_h = (_g = (_f = room.currentState.getStateEvents('m.room.topic', '')) === null || _f === void 0 ? void 0 : _f.getContent()) === null || _g === void 0 ? void 0 : _g.topic) !== null && _h !== void 0 ? _h : undefined,
1126
1163
  memberCount: room.getJoinedMemberCount(),
1127
1164
  isEncrypted: room.hasEncryptionStateEvent(),
1128
- unreadCount: (_f = room.getUnreadNotificationCount()) !== null && _f !== void 0 ? _f : 0,
1165
+ unreadCount: (_j = room.getUnreadNotificationCount()) !== null && _j !== void 0 ? _j : 0,
1129
1166
  lastEventTs: room.getLastActiveTimestamp() || undefined,
1167
+ createdAt: (_l = (_k = room.currentState.getStateEvents('m.room.create', '')) === null || _k === void 0 ? void 0 : _k.getTs()) !== null && _l !== void 0 ? _l : undefined,
1130
1168
  membership: room.getMyMembership(),
1131
1169
  avatarUrl,
1132
1170
  isDirect,
1171
+ latestEvent,
1133
1172
  };
1134
1173
  }
1135
1174
  async searchUsers(options) {