@tagea/capacitor-matrix 1.1.1 → 1.2.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
@@ -370,6 +370,8 @@ The full API reference is auto-generated below from the TypeScript definitions.
370
370
  * [`addListener('typingChanged', ...)`](#addlistenertypingchanged-)
371
371
  * [`addListener('receiptReceived', ...)`](#addlistenerreceiptreceived-)
372
372
  * [`addListener('presenceChanged', ...)`](#addlistenerpresencechanged-)
373
+ * [`updateAccessToken(...)`](#updateaccesstoken)
374
+ * [`addListener('tokenRefreshRequired', ...)`](#addlistenertokenrefreshrequired-)
373
375
  * [`removeAllListeners()`](#removealllisteners)
374
376
  * [Interfaces](#interfaces)
375
377
  * [Type Aliases](#type-aliases)
@@ -1155,6 +1157,35 @@ addListener(event: 'presenceChanged', listenerFunc: (data: PresenceChangedEvent)
1155
1157
  --------------------
1156
1158
 
1157
1159
 
1160
+ ### updateAccessToken(...)
1161
+
1162
+ ```typescript
1163
+ updateAccessToken(options: { accessToken: string; }) => Promise<void>
1164
+ ```
1165
+
1166
+ | Param | Type |
1167
+ | ------------- | ------------------------------------- |
1168
+ | **`options`** | <code>{ accessToken: string; }</code> |
1169
+
1170
+ --------------------
1171
+
1172
+
1173
+ ### addListener('tokenRefreshRequired', ...)
1174
+
1175
+ ```typescript
1176
+ addListener(event: 'tokenRefreshRequired', listenerFunc: () => void) => Promise<PluginListenerHandle>
1177
+ ```
1178
+
1179
+ | Param | Type |
1180
+ | ------------------ | ----------------------------------- |
1181
+ | **`event`** | <code>'tokenRefreshRequired'</code> |
1182
+ | **`listenerFunc`** | <code>() =&gt; void</code> |
1183
+
1184
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
1185
+
1186
+ --------------------
1187
+
1188
+
1158
1189
  ### removeAllListeners()
1159
1190
 
1160
1191
  ```typescript
@@ -186,6 +186,53 @@ class MatrixSDKBridge(private val context: Context) {
186
186
  return sessionStore.load()
187
187
  }
188
188
 
189
+ suspend fun updateAccessToken(accessToken: String) {
190
+ requireClient()
191
+
192
+ // Stop sync service and clean up references
193
+ syncService?.stop()
194
+ syncService = null
195
+ receiptSyncJob?.cancel()
196
+ receiptSyncJob = null
197
+ receiptCache.clear()
198
+ timelineListenerHandles.clear()
199
+ roomTimelines.clear()
200
+ subscribedRoomIds.clear()
201
+
202
+ val oldSession = sessionStore.load()
203
+ ?: throw IllegalStateException("No persisted session to update")
204
+
205
+ // Build a new client pointing to the same data directory (preserves crypto store).
206
+ // The Rust SDK's restoreSession() can only be called once per Client instance.
207
+ val safeUserId = oldSession.userId.replace(Regex("[^a-zA-Z0-9_.-]"), "_")
208
+ val dataDir = context.filesDir.resolve("matrix_sdk/$safeUserId")
209
+ dataDir.mkdirs()
210
+ val dataDirPath = dataDir.absolutePath
211
+
212
+ val newClient = ClientBuilder()
213
+ .homeserverUrl(oldSession.homeserverUrl)
214
+ .slidingSyncVersionBuilder(SlidingSyncVersionBuilder.NATIVE)
215
+ .autoEnableCrossSigning(true)
216
+ .sqliteStore(SqliteStoreBuilder(dataDirPath, dataDirPath))
217
+ .build()
218
+
219
+ val newSession = Session(
220
+ accessToken = accessToken,
221
+ refreshToken = null,
222
+ userId = oldSession.userId,
223
+ deviceId = oldSession.deviceId,
224
+ homeserverUrl = oldSession.homeserverUrl,
225
+ oidcData = null,
226
+ slidingSyncVersion = SlidingSyncVersion.NATIVE,
227
+ )
228
+
229
+ newClient.restoreSession(newSession)
230
+ client = newClient
231
+
232
+ val updatedInfo = oldSession.copy(accessToken = accessToken)
233
+ sessionStore.save(updatedInfo)
234
+ }
235
+
189
236
  // ── Sync ──────────────────────────────────────────────
190
237
 
191
238
  suspend fun startSync(
@@ -997,6 +1044,101 @@ class MatrixSDKBridge(private val context: Context) {
997
1044
  )
998
1045
  }
999
1046
 
1047
+ // ── Presence ─────────────────────────────────────────
1048
+
1049
+ suspend fun setPresence(presence: String, statusMsg: String?) {
1050
+ val session = sessionStore.load() ?: throw IllegalStateException("Not logged in")
1051
+ val baseUrl = session.homeserverUrl.trimEnd('/')
1052
+ val encodedUserId = URLEncoder.encode(session.userId, "UTF-8")
1053
+ val url = URL("$baseUrl/_matrix/client/v3/presence/$encodedUserId/status")
1054
+ val connection = url.openConnection() as HttpURLConnection
1055
+ connection.requestMethod = "PUT"
1056
+ connection.setRequestProperty("Authorization", "Bearer ${session.accessToken}")
1057
+ connection.setRequestProperty("Content-Type", "application/json")
1058
+ connection.doOutput = true
1059
+
1060
+ val body = org.json.JSONObject()
1061
+ body.put("presence", presence)
1062
+ if (statusMsg != null) body.put("status_msg", statusMsg)
1063
+ val writer = OutputStreamWriter(connection.outputStream)
1064
+ writer.write(body.toString())
1065
+ writer.flush()
1066
+ writer.close()
1067
+
1068
+ val responseCode = connection.responseCode
1069
+ if (responseCode !in 200..299) {
1070
+ throw Exception("setPresence failed with status $responseCode")
1071
+ }
1072
+ }
1073
+
1074
+ suspend fun getPresence(userId: String): Map<String, Any?> {
1075
+ val session = sessionStore.load() ?: throw IllegalStateException("Not logged in")
1076
+ val baseUrl = session.homeserverUrl.trimEnd('/')
1077
+ val encodedUserId = URLEncoder.encode(userId, "UTF-8")
1078
+ val url = URL("$baseUrl/_matrix/client/v3/presence/$encodedUserId/status")
1079
+ val connection = url.openConnection() as HttpURLConnection
1080
+ connection.requestMethod = "GET"
1081
+ connection.setRequestProperty("Authorization", "Bearer ${session.accessToken}")
1082
+
1083
+ val responseCode = connection.responseCode
1084
+ if (responseCode !in 200..299) {
1085
+ throw Exception("getPresence failed with status $responseCode")
1086
+ }
1087
+
1088
+ val responseBody = connection.inputStream.bufferedReader().readText()
1089
+ val json = org.json.JSONObject(responseBody)
1090
+ return mapOf(
1091
+ "presence" to json.optString("presence", "offline"),
1092
+ "statusMsg" to if (json.has("status_msg")) json.getString("status_msg") else null,
1093
+ "lastActiveAgo" to if (json.has("last_active_ago")) json.getLong("last_active_ago") else null,
1094
+ )
1095
+ }
1096
+
1097
+ // ── Pushers ───────────────────────────────────────────
1098
+
1099
+ suspend fun setPusher(
1100
+ pushkey: String,
1101
+ kind: String?,
1102
+ appId: String,
1103
+ appDisplayName: String,
1104
+ deviceDisplayName: String,
1105
+ lang: String,
1106
+ dataUrl: String,
1107
+ dataFormat: String?,
1108
+ ) {
1109
+ val session = sessionStore.load() ?: throw IllegalStateException("Not logged in")
1110
+ val baseUrl = session.homeserverUrl.trimEnd('/')
1111
+ val url = URL("$baseUrl/_matrix/client/v3/pushers/set")
1112
+ val connection = url.openConnection() as HttpURLConnection
1113
+ connection.requestMethod = "POST"
1114
+ connection.setRequestProperty("Authorization", "Bearer ${session.accessToken}")
1115
+ connection.setRequestProperty("Content-Type", "application/json")
1116
+ connection.doOutput = true
1117
+
1118
+ val dataObj = org.json.JSONObject()
1119
+ dataObj.put("url", dataUrl)
1120
+ if (dataFormat != null) dataObj.put("format", dataFormat)
1121
+
1122
+ val body = org.json.JSONObject()
1123
+ body.put("pushkey", pushkey)
1124
+ body.put("kind", if (kind != null) kind else org.json.JSONObject.NULL)
1125
+ body.put("app_id", appId)
1126
+ body.put("app_display_name", appDisplayName)
1127
+ body.put("device_display_name", deviceDisplayName)
1128
+ body.put("lang", lang)
1129
+ body.put("data", dataObj)
1130
+
1131
+ val writer = OutputStreamWriter(connection.outputStream)
1132
+ writer.write(body.toString())
1133
+ writer.flush()
1134
+ writer.close()
1135
+
1136
+ val responseCode = connection.responseCode
1137
+ if (responseCode !in 200..299) {
1138
+ throw Exception("setPusher failed with status $responseCode")
1139
+ }
1140
+ }
1141
+
1000
1142
  // ── Helpers ───────────────────────────────────────────
1001
1143
 
1002
1144
  private fun requireClient(): Client {
@@ -54,6 +54,20 @@ class MatrixPlugin : Plugin() {
54
54
  }
55
55
  }
56
56
 
57
+ @PluginMethod
58
+ fun updateAccessToken(call: PluginCall) {
59
+ val accessToken = call.getString("accessToken") ?: return call.reject("Missing accessToken")
60
+
61
+ scope.launch {
62
+ try {
63
+ bridge.updateAccessToken(accessToken)
64
+ call.resolve()
65
+ } catch (e: Exception) {
66
+ call.reject(e.message ?: "updateAccessToken failed", e)
67
+ }
68
+ }
69
+ }
70
+
57
71
  @PluginMethod
58
72
  fun logout(call: PluginCall) {
59
73
  scope.launch {
@@ -530,7 +544,24 @@ class MatrixPlugin : Plugin() {
530
544
 
531
545
  @PluginMethod
532
546
  fun setPusher(call: PluginCall) {
533
- call.reject("setPusher is not yet supported on this platform")
547
+ val pushkey = call.getString("pushkey") ?: return call.reject("Missing pushkey")
548
+ val kind = call.getString("kind")
549
+ val appId = call.getString("appId") ?: return call.reject("Missing appId")
550
+ val appDisplayName = call.getString("appDisplayName") ?: return call.reject("Missing appDisplayName")
551
+ val deviceDisplayName = call.getString("deviceDisplayName") ?: return call.reject("Missing deviceDisplayName")
552
+ val lang = call.getString("lang") ?: return call.reject("Missing lang")
553
+ val dataObj = call.getObject("data") ?: return call.reject("Missing data")
554
+ val dataUrl = dataObj.getString("url") ?: return call.reject("Missing data.url")
555
+ val dataFormat = dataObj.optString("format", null)
556
+
557
+ scope.launch {
558
+ try {
559
+ bridge.setPusher(pushkey, kind, appId, appDisplayName, deviceDisplayName, lang, dataUrl, dataFormat)
560
+ call.resolve()
561
+ } catch (e: Exception) {
562
+ call.reject(e.message ?: "setPusher failed", e)
563
+ }
564
+ }
534
565
  }
535
566
 
536
567
  @PluginMethod
@@ -625,12 +656,31 @@ class MatrixPlugin : Plugin() {
625
656
 
626
657
  @PluginMethod
627
658
  fun setPresence(call: PluginCall) {
628
- call.reject("setPresence is not supported on this platform")
659
+ val presence = call.getString("presence") ?: return call.reject("Missing presence")
660
+ val statusMsg = call.getString("statusMsg")
661
+
662
+ scope.launch {
663
+ try {
664
+ bridge.setPresence(presence, statusMsg)
665
+ call.resolve()
666
+ } catch (e: Exception) {
667
+ call.reject(e.message ?: "setPresence failed", e)
668
+ }
669
+ }
629
670
  }
630
671
 
631
672
  @PluginMethod
632
673
  fun getPresence(call: PluginCall) {
633
- call.reject("getPresence is not supported on this platform")
674
+ val userId = call.getString("userId") ?: return call.reject("Missing userId")
675
+
676
+ scope.launch {
677
+ try {
678
+ val result = bridge.getPresence(userId)
679
+ call.resolve(mapToJSObject(result))
680
+ } catch (e: Exception) {
681
+ call.reject(e.message ?: "getPresence failed", e)
682
+ }
683
+ }
634
684
  }
635
685
 
636
686
  @PluginMethod
package/dist/docs.json CHANGED
@@ -946,6 +946,45 @@
946
946
  ],
947
947
  "slug": "addlistenerpresencechanged-"
948
948
  },
949
+ {
950
+ "name": "updateAccessToken",
951
+ "signature": "(options: { accessToken: string; }) => Promise<void>",
952
+ "parameters": [
953
+ {
954
+ "name": "options",
955
+ "docs": "",
956
+ "type": "{ accessToken: string; }"
957
+ }
958
+ ],
959
+ "returns": "Promise<void>",
960
+ "tags": [],
961
+ "docs": "",
962
+ "complexTypes": [],
963
+ "slug": "updateaccesstoken"
964
+ },
965
+ {
966
+ "name": "addListener",
967
+ "signature": "(event: 'tokenRefreshRequired', listenerFunc: () => void) => Promise<PluginListenerHandle>",
968
+ "parameters": [
969
+ {
970
+ "name": "event",
971
+ "docs": "",
972
+ "type": "'tokenRefreshRequired'"
973
+ },
974
+ {
975
+ "name": "listenerFunc",
976
+ "docs": "",
977
+ "type": "() => void"
978
+ }
979
+ ],
980
+ "returns": "Promise<PluginListenerHandle>",
981
+ "tags": [],
982
+ "docs": "",
983
+ "complexTypes": [
984
+ "PluginListenerHandle"
985
+ ],
986
+ "slug": "addlistenertokenrefreshrequired-"
987
+ },
949
988
  {
950
989
  "name": "removeAllListeners",
951
990
  "signature": "() => Promise<void>",
@@ -388,5 +388,9 @@ export interface MatrixPlugin {
388
388
  addListener(event: 'typingChanged', listenerFunc: (data: TypingEvent) => void): Promise<PluginListenerHandle>;
389
389
  addListener(event: 'receiptReceived', listenerFunc: (data: ReceiptReceivedEvent) => void): Promise<PluginListenerHandle>;
390
390
  addListener(event: 'presenceChanged', listenerFunc: (data: PresenceChangedEvent) => void): Promise<PluginListenerHandle>;
391
+ updateAccessToken(options: {
392
+ accessToken: string;
393
+ }): Promise<void>;
394
+ addListener(event: 'tokenRefreshRequired', listenerFunc: () => void): Promise<PluginListenerHandle>;
391
395
  removeAllListeners(): Promise<void>;
392
396
  }
@@ -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 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 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"]}
package/dist/esm/web.d.ts CHANGED
@@ -6,12 +6,17 @@ export declare class MatrixWeb extends WebPlugin implements MatrixPlugin {
6
6
  private secretStorageKeyId?;
7
7
  private recoveryPassphrase?;
8
8
  private fallbackPassphrase?;
9
+ private _tokenRefreshResolve?;
10
+ private _tokenRefreshTimeout?;
9
11
  private readonly _cryptoCallbacks;
10
12
  login(options: LoginOptions): Promise<SessionInfo>;
11
13
  loginWithToken(options: LoginWithTokenOptions): Promise<SessionInfo>;
12
14
  logout(): Promise<void>;
13
15
  clearAllData(): Promise<void>;
14
16
  getSession(): Promise<SessionInfo | null>;
17
+ updateAccessToken(options: {
18
+ accessToken: string;
19
+ }): Promise<void>;
15
20
  startSync(): Promise<void>;
16
21
  stopSync(): Promise<void>;
17
22
  getSyncState(): Promise<{
@@ -188,6 +193,7 @@ export declare class MatrixWeb extends WebPlugin implements MatrixPlugin {
188
193
  private requireCrypto;
189
194
  private ensureCrypto;
190
195
  private persistSession;
196
+ private createTokenRefreshFunction;
191
197
  private serializeEvent;
192
198
  private serializeRoom;
193
199
  searchUsers(options: {
package/dist/esm/web.js CHANGED
@@ -57,6 +57,8 @@ export class MatrixWeb extends WebPlugin {
57
57
  userId: res.user_id,
58
58
  deviceId: res.device_id,
59
59
  cryptoCallbacks: this._cryptoCallbacks,
60
+ refreshToken: 'jwt-placeholder',
61
+ tokenRefreshFunction: this.createTokenRefreshFunction(),
60
62
  });
61
63
  const session = {
62
64
  accessToken: res.access_token,
@@ -80,6 +82,8 @@ export class MatrixWeb extends WebPlugin {
80
82
  userId: options.userId,
81
83
  deviceId: options.deviceId,
82
84
  cryptoCallbacks: this._cryptoCallbacks,
85
+ refreshToken: 'jwt-placeholder',
86
+ tokenRefreshFunction: this.createTokenRefreshFunction(),
83
87
  });
84
88
  const session = {
85
89
  accessToken: options.accessToken,
@@ -127,6 +131,27 @@ export class MatrixWeb extends WebPlugin {
127
131
  return null;
128
132
  }
129
133
  }
134
+ async updateAccessToken(options) {
135
+ this.requireClient();
136
+ this.client.setAccessToken(options.accessToken);
137
+ // Update persisted session
138
+ const raw = localStorage.getItem(SESSION_KEY);
139
+ if (raw) {
140
+ const session = JSON.parse(raw);
141
+ session.accessToken = options.accessToken;
142
+ this.persistSession(session);
143
+ }
144
+ // Resolve pending tokenRefreshFunction promise (if SDK is waiting)
145
+ if (this._tokenRefreshResolve) {
146
+ const resolve = this._tokenRefreshResolve;
147
+ this._tokenRefreshResolve = undefined;
148
+ if (this._tokenRefreshTimeout) {
149
+ clearTimeout(this._tokenRefreshTimeout);
150
+ this._tokenRefreshTimeout = undefined;
151
+ }
152
+ resolve({ accessToken: options.accessToken, refreshToken: 'jwt-placeholder' });
153
+ }
154
+ }
130
155
  // ── Sync ──────────────────────────────────────────────
131
156
  async startSync() {
132
157
  this.requireClient();
@@ -957,6 +982,20 @@ export class MatrixWeb extends WebPlugin {
957
982
  persistSession(session) {
958
983
  localStorage.setItem(SESSION_KEY, JSON.stringify(session));
959
984
  }
985
+ createTokenRefreshFunction() {
986
+ return (_refreshToken) => {
987
+ this.notifyListeners('tokenRefreshRequired', {});
988
+ return new Promise((resolve, reject) => {
989
+ if (this._tokenRefreshTimeout)
990
+ clearTimeout(this._tokenRefreshTimeout);
991
+ this._tokenRefreshResolve = resolve;
992
+ this._tokenRefreshTimeout = setTimeout(() => {
993
+ this._tokenRefreshResolve = undefined;
994
+ reject(new Error('Token refresh timed out'));
995
+ }, 30000);
996
+ });
997
+ };
998
+ }
960
999
  serializeEvent(event, fallbackRoomId) {
961
1000
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
962
1001
  const roomId = (_b = (_a = event.getRoomId()) !== null && _a !== void 0 ? _a : fallbackRoomId) !== null && _b !== void 0 ? _b : '';