mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-02-22 06:57:44 +01:00
fixing images causing decryption errors
- introduces a dedicated encrypted room event type
This commit is contained in:
parent
14a8b94496
commit
f47a44063b
@ -105,6 +105,19 @@ sealed class RoomEvent {
|
||||
abstract val author: RoomMember
|
||||
abstract val meta: MessageMeta
|
||||
|
||||
data class Encrypted(
|
||||
override val eventId: EventId,
|
||||
override val utcTimestamp: Long,
|
||||
override val author: RoomMember,
|
||||
override val meta: MessageMeta,
|
||||
) : RoomEvent() {
|
||||
|
||||
val time: String by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
val instant = Instant.ofEpochMilli(utcTimestamp)
|
||||
ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT)
|
||||
}
|
||||
}
|
||||
|
||||
data class Message(
|
||||
override val eventId: EventId,
|
||||
override val utcTimestamp: Long,
|
||||
|
@ -198,6 +198,7 @@ private fun ColumnScope.RoomContent(self: UserId, state: RoomState, replyActions
|
||||
is RoomEvent.Image -> MessageImage(it as BubbleContent<RoomEvent.Image>)
|
||||
is RoomEvent.Message -> TextBubbleContent(it as BubbleContent<RoomEvent.Message>)
|
||||
is RoomEvent.Reply -> ReplyBubbleContent(it as BubbleContent<RoomEvent.Reply>)
|
||||
is RoomEvent.Encrypted -> EncryptedBubbleContent(it as BubbleContent<RoomEvent.Encrypted>)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -445,6 +446,54 @@ private fun TextBubbleContent(content: BubbleContent<RoomEvent.Message>) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EncryptedBubbleContent(content: BubbleContent<RoomEvent.Encrypted>) {
|
||||
Box(modifier = Modifier.padding(start = 6.dp)) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(4.dp)
|
||||
.clip(content.shape)
|
||||
.background(content.background)
|
||||
.height(IntrinsicSize.Max),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(8.dp)
|
||||
.width(IntrinsicSize.Max)
|
||||
.defaultMinSize(minWidth = 50.dp)
|
||||
) {
|
||||
if (content.isNotSelf) {
|
||||
Text(
|
||||
fontSize = 11.sp,
|
||||
text = content.message.author.displayName ?: content.message.author.id.value,
|
||||
maxLines = 1,
|
||||
color = content.textColor()
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Encrypted message",
|
||||
color = content.textColor(),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
fontSize = 9.sp,
|
||||
text = "${content.message.time}",
|
||||
textAlign = TextAlign.End,
|
||||
color = content.textColor(),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
SendStatus(content.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
||||
Box(modifier = Modifier.padding(start = 6.dp)) {
|
||||
@ -511,6 +560,16 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
||||
is RoomEvent.Reply -> {
|
||||
// TODO - a reply to a reply
|
||||
}
|
||||
|
||||
is RoomEvent.Encrypted -> {
|
||||
Text(
|
||||
text = "Encrypted message",
|
||||
color = content.textColor().copy(alpha = 0.8f),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,6 +613,16 @@ private fun ReplyBubbleContent(content: BubbleContent<RoomEvent.Reply>) {
|
||||
is RoomEvent.Reply -> {
|
||||
// TODO - a reply to a reply
|
||||
}
|
||||
|
||||
is RoomEvent.Encrypted -> {
|
||||
Text(
|
||||
text = "Encrypted message",
|
||||
color = content.textColor(),
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
|
@ -97,6 +97,7 @@ internal class MessengerViewModel(
|
||||
is RoomEvent.Image -> TODO()
|
||||
is RoomEvent.Reply -> TODO()
|
||||
is RoomEvent.Message -> it.content
|
||||
is RoomEvent.Encrypted -> error("Should never happen")
|
||||
},
|
||||
eventId = it.eventId,
|
||||
timestampUtc = it.utcTimestamp,
|
||||
|
@ -6,19 +6,14 @@ import app.dapk.st.matrix.common.RoomMember
|
||||
class RoomEventsToNotifiableMapper {
|
||||
|
||||
fun map(events: List<RoomEvent>): List<Notifiable> {
|
||||
return events.map {
|
||||
when (it) {
|
||||
is RoomEvent.Image -> Notifiable(content = it.toNotifiableContent(), it.utcTimestamp, it.author)
|
||||
is RoomEvent.Message -> Notifiable(content = it.toNotifiableContent(), it.utcTimestamp, it.author)
|
||||
is RoomEvent.Reply -> Notifiable(content = it.toNotifiableContent(), it.utcTimestamp, it.author)
|
||||
}
|
||||
}
|
||||
return events.map { Notifiable(content = it.toNotifiableContent(), it.utcTimestamp, it.author) }
|
||||
}
|
||||
|
||||
private fun RoomEvent.toNotifiableContent(): String = when (this) {
|
||||
is RoomEvent.Image -> "\uD83D\uDCF7"
|
||||
is RoomEvent.Message -> this.content
|
||||
is RoomEvent.Reply -> this.message.toNotifiableContent()
|
||||
is RoomEvent.Encrypted -> "Encrypted message"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ internal class LocalEchoMapper(private val metaMapper: MetaMapper) {
|
||||
is RoomEvent.Message -> this.copy(meta = metaMapper.toMeta(echo))
|
||||
is RoomEvent.Reply -> this.copy(message = this.message.mergeWith(echo))
|
||||
is RoomEvent.Image -> this.copy(meta = metaMapper.toMeta(echo))
|
||||
is RoomEvent.Encrypted -> this.copy(meta = metaMapper.toMeta(echo))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ fun MatrixRoomEvent.engine(): RoomEvent = when (this) {
|
||||
is MatrixRoomEvent.Image -> RoomEvent.Image(this.eventId, this.utcTimestamp, this.imageMeta.engine(), this.author, this.meta.engine(), this.edited)
|
||||
is MatrixRoomEvent.Message -> RoomEvent.Message(this.eventId, this.utcTimestamp, this.content, this.author, this.meta.engine(), this.edited, this.redacted)
|
||||
is MatrixRoomEvent.Reply -> RoomEvent.Reply(this.message.engine(), this.replyingTo.engine())
|
||||
is MatrixRoomEvent.Encrypted -> RoomEvent.Encrypted(this.eventId, this.utcTimestamp, this.author, this.meta.engine())
|
||||
}
|
||||
|
||||
fun MatrixRoomEvent.Image.ImageMeta.engine() = RoomEvent.Image.ImageMeta(
|
||||
|
@ -77,13 +77,12 @@ interface MessageService : MatrixService {
|
||||
|
||||
@Serializable
|
||||
data class Meta(
|
||||
val height: Int,
|
||||
val width: Int,
|
||||
val size: Long,
|
||||
val fileName: String,
|
||||
val mimeType: String,
|
||||
@SerialName("height") val height: Int,
|
||||
@SerialName("width") val width: Int,
|
||||
@SerialName("size") val size: Long,
|
||||
@SerialName("file_name") val fileName: String,
|
||||
@SerialName("mime_type") val mimeType: String,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,18 +24,19 @@ sealed class RoomEvent {
|
||||
abstract val utcTimestamp: Long
|
||||
abstract val author: RoomMember
|
||||
abstract val meta: MessageMeta
|
||||
abstract val redacted: Boolean
|
||||
|
||||
@Serializable
|
||||
@SerialName("message")
|
||||
data class Message(
|
||||
@SerialName("encrypted")
|
||||
data class Encrypted(
|
||||
@SerialName("event_id") override val eventId: EventId,
|
||||
@SerialName("timestamp") override val utcTimestamp: Long,
|
||||
@SerialName("content") val content: String,
|
||||
@SerialName("author") override val author: RoomMember,
|
||||
@SerialName("meta") override val meta: MessageMeta,
|
||||
@SerialName("encrypted_content") val encryptedContent: MegOlmV1? = null,
|
||||
@SerialName("encrypted_content") val encryptedContent: MegOlmV1,
|
||||
@SerialName("edited") val edited: Boolean = false,
|
||||
@SerialName("redacted") val redacted: Boolean = false,
|
||||
@SerialName("redacted") override val redacted: Boolean = false,
|
||||
) : RoomEvent() {
|
||||
|
||||
@Serializable
|
||||
@ -52,6 +53,24 @@ sealed class RoomEvent {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("message")
|
||||
data class Message(
|
||||
@SerialName("event_id") override val eventId: EventId,
|
||||
@SerialName("timestamp") override val utcTimestamp: Long,
|
||||
@SerialName("content") val content: String,
|
||||
@SerialName("author") override val author: RoomMember,
|
||||
@SerialName("meta") override val meta: MessageMeta,
|
||||
@SerialName("edited") val edited: Boolean = false,
|
||||
@SerialName("redacted") override val redacted: Boolean = false,
|
||||
) : RoomEvent() {
|
||||
|
||||
val time: String by unsafeLazy {
|
||||
val instant = Instant.ofEpochMilli(utcTimestamp)
|
||||
ZonedDateTime.ofInstant(instant, DEFAULT_ZONE).toLocalTime().format(MESSAGE_TIME_FORMAT)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("reply")
|
||||
data class Reply(
|
||||
@ -63,6 +82,7 @@ sealed class RoomEvent {
|
||||
override val utcTimestamp: Long = message.utcTimestamp
|
||||
override val author: RoomMember = message.author
|
||||
override val meta: MessageMeta = message.meta
|
||||
override val redacted: Boolean = message.redacted
|
||||
|
||||
val replyingToSelf = replyingTo.author == message.author
|
||||
|
||||
@ -80,8 +100,8 @@ sealed class RoomEvent {
|
||||
@SerialName("image_meta") val imageMeta: ImageMeta,
|
||||
@SerialName("author") override val author: RoomMember,
|
||||
@SerialName("meta") override val meta: MessageMeta,
|
||||
@SerialName("encrypted_content") val encryptedContent: Message.MegOlmV1? = null,
|
||||
@SerialName("edited") val edited: Boolean = false,
|
||||
@SerialName("redacted") override val redacted: Boolean = false,
|
||||
) : RoomEvent() {
|
||||
|
||||
val time: String by unsafeLazy {
|
||||
|
@ -3,6 +3,8 @@ package app.dapk.st.matrix.sync.internal.room
|
||||
import app.dapk.st.matrix.common.*
|
||||
import app.dapk.st.matrix.sync.RoomEvent
|
||||
import app.dapk.st.matrix.sync.internal.request.ApiTimelineEvent
|
||||
import app.dapk.st.matrix.sync.internal.request.ApiTimelineEvent.TimelineMessage.Content.Image
|
||||
import app.dapk.st.matrix.sync.internal.request.ApiTimelineEvent.TimelineMessage.Content.Text
|
||||
import app.dapk.st.matrix.sync.internal.request.DecryptedContent
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@ -17,56 +19,60 @@ internal class RoomEventsDecrypter(
|
||||
}
|
||||
|
||||
private suspend fun decryptEvent(event: RoomEvent, userCredentials: UserCredentials): RoomEvent = when (event) {
|
||||
is RoomEvent.Message -> event.decrypt()
|
||||
is RoomEvent.Encrypted -> event.decrypt(userCredentials)
|
||||
is RoomEvent.Message -> event
|
||||
is RoomEvent.Reply -> RoomEvent.Reply(
|
||||
message = decryptEvent(event.message, userCredentials),
|
||||
replyingTo = decryptEvent(event.replyingTo, userCredentials),
|
||||
)
|
||||
is RoomEvent.Image -> event.decrypt(userCredentials)
|
||||
|
||||
is RoomEvent.Image -> event
|
||||
}
|
||||
|
||||
private suspend fun RoomEvent.Image.decrypt(userCredentials: UserCredentials) = when (this.encryptedContent) {
|
||||
null -> this
|
||||
else -> when (val result = messageDecrypter.decrypt(this.encryptedContent.toModel())) {
|
||||
is DecryptionResult.Failed -> this.also { logger.crypto("Failed to decrypt ${it.eventId}") }
|
||||
is DecryptionResult.Success -> when (val model = result.payload.toModel()) {
|
||||
DecryptedContent.Ignored -> this
|
||||
is DecryptedContent.TimelineText -> {
|
||||
val content = model.content as ApiTimelineEvent.TimelineMessage.Content.Image
|
||||
this.copy(
|
||||
imageMeta = RoomEvent.Image.ImageMeta(
|
||||
width = content.info?.width,
|
||||
height = content.info?.height,
|
||||
url = content.file?.url?.convertMxUrToUrl(userCredentials.homeServer) ?: content.url!!.convertMxUrToUrl(userCredentials.homeServer),
|
||||
keys = content.file?.let { RoomEvent.Image.ImageMeta.Keys(it.key.k, it.iv, it.v, it.hashes) }
|
||||
),
|
||||
encryptedContent = null,
|
||||
)
|
||||
}
|
||||
private suspend fun RoomEvent.Encrypted.decrypt(userCredentials: UserCredentials) = when (val result = this.decryptContent()) {
|
||||
is DecryptionResult.Failed -> this.also { logger.crypto("Failed to decrypt ${it.eventId}") }
|
||||
is DecryptionResult.Success -> when (val model = result.payload.toModel()) {
|
||||
DecryptedContent.Ignored -> this
|
||||
is DecryptedContent.TimelineText -> when (val content = model.content) {
|
||||
ApiTimelineEvent.TimelineMessage.Content.Ignored -> this
|
||||
is Image -> createImageEvent(content, userCredentials)
|
||||
is Text -> createMessageEvent(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun RoomEvent.Message.decrypt() = when (this.encryptedContent) {
|
||||
null -> this
|
||||
else -> when (val result = messageDecrypter.decrypt(this.encryptedContent.toModel())) {
|
||||
is DecryptionResult.Failed -> this.also { logger.crypto("Failed to decrypt ${it.eventId}") }
|
||||
is DecryptionResult.Success -> when (val model = result.payload.toModel()) {
|
||||
DecryptedContent.Ignored -> this
|
||||
is DecryptedContent.TimelineText -> this.copyWithDecryptedContent(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
private suspend fun RoomEvent.Encrypted.decryptContent() = messageDecrypter.decrypt(this.encryptedContent.toModel())
|
||||
|
||||
private fun RoomEvent.Encrypted.createMessageEvent(content: Text) = RoomEvent.Message(
|
||||
eventId = this.eventId,
|
||||
utcTimestamp = this.utcTimestamp,
|
||||
author = this.author,
|
||||
meta = this.meta,
|
||||
edited = this.edited,
|
||||
redacted = this.redacted,
|
||||
content = content.body ?: ""
|
||||
)
|
||||
|
||||
private fun RoomEvent.Encrypted.createImageEvent(content: Image, userCredentials: UserCredentials) = RoomEvent.Image(
|
||||
eventId = this.eventId,
|
||||
utcTimestamp = this.utcTimestamp,
|
||||
author = this.author,
|
||||
meta = this.meta,
|
||||
edited = this.edited,
|
||||
redacted = this.redacted,
|
||||
imageMeta = RoomEvent.Image.ImageMeta(
|
||||
width = content.info?.width,
|
||||
height = content.info?.height,
|
||||
url = content.file?.url?.convertMxUrToUrl(userCredentials.homeServer) ?: content.url!!.convertMxUrToUrl(userCredentials.homeServer),
|
||||
keys = content.file?.let { RoomEvent.Image.ImageMeta.Keys(it.key.k, it.iv, it.v, it.hashes) }
|
||||
),
|
||||
)
|
||||
|
||||
private fun JsonString.toModel() = json.decodeFromString(DecryptedContent.serializer(), this.value)
|
||||
|
||||
private fun RoomEvent.Message.copyWithDecryptedContent(decryptedContent: DecryptedContent.TimelineText) = this.copy(
|
||||
content = (decryptedContent.content as ApiTimelineEvent.TimelineMessage.Content.Text).body ?: "",
|
||||
encryptedContent = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun RoomEvent.Message.MegOlmV1.toModel() = EncryptedMessageContent.MegOlmV1(
|
||||
private fun RoomEvent.Encrypted.MegOlmV1.toModel() = EncryptedMessageContent.MegOlmV1(
|
||||
this.cipherText,
|
||||
this.deviceId,
|
||||
this.senderKey,
|
||||
|
@ -51,11 +51,7 @@ class RoomDataSource(
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomEvent.redact() = when (this) {
|
||||
is RoomEvent.Image -> RoomEvent.Message(this.eventId, this.utcTimestamp, "Redacted", this.author, this.meta, redacted = true)
|
||||
is RoomEvent.Message -> RoomEvent.Message(this.eventId, this.utcTimestamp, "Redacted", this.author, this.meta, redacted = true)
|
||||
is RoomEvent.Reply -> RoomEvent.Message(this.eventId, this.utcTimestamp, "Redacted", this.author, this.meta, redacted = true)
|
||||
}
|
||||
private fun RoomEvent.redact() = RoomEvent.Message(this.eventId, this.utcTimestamp, "Redacted", this.author, this.meta, redacted = true)
|
||||
|
||||
private fun RoomState.replaceEvent(old: RoomEvent, new: RoomEvent): RoomState {
|
||||
val updatedEvents = this.events.toMutableList().apply {
|
||||
|
@ -24,13 +24,13 @@ internal class RoomEventCreator(
|
||||
suspend fun ApiTimelineEvent.Encrypted.toRoomEvent(roomId: RoomId): RoomEvent? {
|
||||
return when (this.encryptedContent) {
|
||||
is ApiEncryptedContent.MegOlmV1 -> {
|
||||
RoomEvent.Message(
|
||||
RoomEvent.Encrypted(
|
||||
eventId = this.eventId,
|
||||
author = roomMembersService.find(roomId, this.senderId)!!,
|
||||
utcTimestamp = this.utcTimestamp,
|
||||
meta = MessageMeta.FromServer,
|
||||
content = "Encrypted message",
|
||||
encryptedContent = RoomEvent.Message.MegOlmV1(
|
||||
encryptedContent = RoomEvent.Encrypted.MegOlmV1(
|
||||
this.encryptedContent.cipherText,
|
||||
this.encryptedContent.deviceId,
|
||||
this.encryptedContent.senderKey,
|
||||
@ -38,6 +38,7 @@ internal class RoomEventCreator(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is ApiEncryptedContent.OlmV1 -> errorTracker.nullAndTrack(IllegalStateException("unexpected encryption, got OlmV1 for a room event"))
|
||||
ApiEncryptedContent.Unknown -> errorTracker.nullAndTrack(IllegalStateException("unknown room event encryption"))
|
||||
}
|
||||
@ -79,6 +80,7 @@ internal class TimelineEventMapper(
|
||||
is RoomEvent.Message -> relationEvent
|
||||
is RoomEvent.Reply -> relationEvent.message
|
||||
is RoomEvent.Image -> relationEvent
|
||||
is RoomEvent.Encrypted -> relationEvent
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -110,12 +112,19 @@ internal class TimelineEventMapper(
|
||||
is RoomEvent.Image -> original.message
|
||||
is RoomEvent.Message -> original.message.edited(incomingEdit)
|
||||
is RoomEvent.Reply -> original.message
|
||||
is RoomEvent.Encrypted -> original.message
|
||||
}
|
||||
)
|
||||
|
||||
is RoomEvent.Image -> {
|
||||
// can't edit images
|
||||
null
|
||||
}
|
||||
|
||||
is RoomEvent.Encrypted -> {
|
||||
// can't edit encrypted messages
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,11 +136,13 @@ internal class TimelineEventMapper(
|
||||
utcTimestamp = incomingEdit.utcTimestamp,
|
||||
edited = true,
|
||||
)
|
||||
|
||||
is ApiTimelineEvent.TimelineMessage.Content.Text -> original.toTextMessage(
|
||||
utcTimestamp = incomingEdit.utcTimestamp,
|
||||
content = incomingEdit.asTextContent().body?.removePrefix(" * ")?.trim() ?: "redacted",
|
||||
edited = true,
|
||||
)
|
||||
|
||||
ApiTimelineEvent.TimelineMessage.Content.Ignored -> null
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,5 @@ private fun RoomEvent.toTextContent(): String = when (this) {
|
||||
is RoomEvent.Image -> "\uD83D\uDCF7"
|
||||
is RoomEvent.Message -> this.content
|
||||
is RoomEvent.Reply -> this.message.toTextContent()
|
||||
is RoomEvent.Encrypted -> "Encrypted message"
|
||||
}
|
@ -43,6 +43,7 @@ internal class UnreadEventsProcessor(
|
||||
is RoomEvent.Message -> it.author.id == selfId
|
||||
is RoomEvent.Reply -> it.message.author.id == selfId
|
||||
is RoomEvent.Image -> it.author.id == selfId
|
||||
is RoomEvent.Encrypted -> it.author.id == selfId
|
||||
}
|
||||
}.map { it.eventId }
|
||||
roomStore.insertUnread(overview.roomId, eventsFromOthers)
|
||||
|
Loading…
x
Reference in New Issue
Block a user