@tagea/capacitor-matrix 1.2.1 → 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 +1 -0
- package/android/src/main/kotlin/de/tremaze/capacitor/matrix/CapMatrix.kt +168 -9
- package/dist/docs.json +7 -0
- package/dist/esm/definitions.d.ts +1 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +17 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +17 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +17 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapMatrixPlugin/CapMatrix.swift +76 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1238,6 +1238,7 @@ removeAllListeners() => Promise<void>
|
|
|
1238
1238
|
| **`isEncrypted`** | <code>boolean</code> |
|
|
1239
1239
|
| **`unreadCount`** | <code>number</code> |
|
|
1240
1240
|
| **`lastEventTs`** | <code>number</code> |
|
|
1241
|
+
| **`createdAt`** | <code>number</code> |
|
|
1241
1242
|
| **`membership`** | <code>'join' \| 'invite' \| 'leave' \| 'ban'</code> |
|
|
1242
1243
|
| **`avatarUrl`** | <code>string</code> |
|
|
1243
1244
|
| **`isDirect`** | <code>boolean</code> |
|
|
@@ -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
|
|
287
|
-
room
|
|
288
|
-
} catch (_: Exception) {
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ->
|
|
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
|
-
|
|
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": [],
|
|
@@ -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 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 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"]}
|
|
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, _g, _h, _j;
|
|
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 (
|
|
1124
|
+
catch (_m) {
|
|
1111
1125
|
// ignore
|
|
1112
1126
|
}
|
|
1113
1127
|
// Get avatar URL
|
|
@@ -1150,6 +1164,7 @@ export class MatrixWeb extends WebPlugin {
|
|
|
1150
1164
|
isEncrypted: room.hasEncryptionStateEvent(),
|
|
1151
1165
|
unreadCount: (_j = room.getUnreadNotificationCount()) !== null && _j !== void 0 ? _j : 0,
|
|
1152
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,
|
|
1153
1168
|
membership: room.getMyMembership(),
|
|
1154
1169
|
avatarUrl,
|
|
1155
1170
|
isDirect,
|