Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.session.libsession.messaging.messages.signal

import kotlinx.serialization.json.Json
import network.loki.messenger.libsession_util.protocol.ProFeature
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
Expand Down Expand Up @@ -83,6 +84,7 @@ data class IncomingTextMessage(

companion object {
fun fromOpenGroupInvitation(
json: Json,
invitation: OpenGroupInvitation,
sender: Address,
sentTimestampMillis: Long,
Expand All @@ -92,7 +94,7 @@ data class IncomingTextMessage(
val body = UpdateMessageData.buildOpenGroupInvitation(
url = invitation.url ?: return null,
name = invitation.name ?: return null,
).toJSON()
).toJSON(json)

return IncomingTextMessage(
message = body,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.session.libsession.messaging.messages.signal

import kotlinx.serialization.json.Json
import network.loki.messenger.libsession_util.protocol.ProFeature
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
import org.session.libsession.messaging.messages.visible.VisibleMessage
Expand Down Expand Up @@ -32,6 +33,7 @@ data class OutgoingTextMessage private constructor(

companion object {
fun fromOpenGroupInvitation(
json: Json,
invitation: OpenGroupInvitation,
recipient: Address,
sentTimestampMillis: Long,
Expand All @@ -44,7 +46,7 @@ data class OutgoingTextMessage private constructor(
message = UpdateMessageData.buildOpenGroupInvitation(
url = invitation.url ?: return null,
name = invitation.name ?: return null,
).toJSON(),
).toJSON(json),
expiresInMillis = expiresInMillis,
expireStartedAtMillis = expireStartedAtMillis,
sentTimestampMillis = sentTimestampMillis,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,95 +1,111 @@
package org.session.libsession.messaging.utilities

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.core.JsonParseException
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonClassDiscriminator
import org.session.libsession.messaging.messages.control.GroupUpdated
import org.session.libsignal.utilities.Log
import org.session.protos.SessionProtos.GroupUpdateInfoChangeMessage
import org.session.protos.SessionProtos.GroupUpdateMemberChangeMessage.Type
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log
import java.util.Collections

// class used to save update messages details
class UpdateMessageData () {

var kind: Kind? = null

//the annotations below are required for serialization. Any new Kind class MUST be declared as JsonSubTypes as well
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes(
JsonSubTypes.Type(Kind.GroupCreation::class, name = "GroupCreation"),
JsonSubTypes.Type(Kind.GroupNameChange::class, name = "GroupNameChange"),
JsonSubTypes.Type(Kind.GroupMemberAdded::class, name = "GroupMemberAdded"),
JsonSubTypes.Type(Kind.GroupMemberRemoved::class, name = "GroupMemberRemoved"),
JsonSubTypes.Type(Kind.GroupMemberLeft::class, name = "GroupMemberLeft"),
JsonSubTypes.Type(Kind.OpenGroupInvitation::class, name = "OpenGroupInvitation"),
JsonSubTypes.Type(Kind.GroupAvatarUpdated::class, name = "GroupAvatarUpdated"),
JsonSubTypes.Type(Kind.GroupMemberUpdated::class, name = "GroupMemberUpdated"),
JsonSubTypes.Type(Kind.GroupExpirationUpdated::class, name = "GroupExpirationUpdated"),
JsonSubTypes.Type(Kind.GroupInvitation::class, name = "GroupInvitation"),
JsonSubTypes.Type(Kind.GroupLeaving::class, name = "GroupLeaving"),
JsonSubTypes.Type(Kind.GroupErrorQuit::class, name = "GroupErrorQuit"),
)
sealed class Kind {
data object GroupCreation: Kind()
class GroupNameChange(val name: String): Kind() {
constructor(): this("") //default constructor required for json serialization
}
class GroupMemberAdded(val updatedMembers: Collection<String>, val groupName: String): Kind() {
constructor(): this(Collections.emptyList(), "")
}
class GroupMemberRemoved(val updatedMembers: Collection<String>, val groupName:String): Kind() {
constructor(): this(Collections.emptyList(), "")
}
class GroupMemberLeft(val updatedMembers: Collection<String>, val groupName:String): Kind() {
constructor(): this(Collections.emptyList(), "")
}

/**
* Represents certain type of message.
*
* This class is an afterthought to save "rich message" into a message's body as JSON text.
* We've since moved away from this setup, a dedicated
* [org.thoughtcrime.securesms.database.model.content.MessageContent] is now used for rich
* message types.
*
* If you want to store a new message type, you should use the new setup instead.
*
* We'll look into migrating this class into the new setup in the future.
*/
@Serializable
class UpdateMessageData(val kind: Kind) {

@OptIn(ExperimentalSerializationApi::class)
@Serializable
@JsonClassDiscriminator("@type")
sealed interface Kind {
@Serializable
@SerialName("GroupCreation")
data object GroupCreation: Kind

@Serializable
@SerialName("GroupNameChange")
class GroupNameChange(val name: String): Kind

@Serializable
@SerialName("GroupMemberAdded")
class GroupMemberAdded(val updatedMembers: Collection<String>, val groupName: String): Kind

@Serializable
@SerialName("GroupMemberRemoved")
class GroupMemberRemoved(val updatedMembers: Collection<String>, val groupName:String): Kind

@Serializable
@SerialName("GroupMemberLeft")
class GroupMemberLeft(val updatedMembers: Collection<String>, val groupName:String): Kind

@Serializable
@SerialName("GroupMemberUpdated")
class GroupMemberUpdated(
val sessionIds: List<String>,
val type: MemberUpdateType?,
val groupName: String,
val historyShared: Boolean
): Kind() {
constructor(): this(emptyList(), null, "", false)
}
data object GroupAvatarUpdated: Kind()
class GroupExpirationUpdated(val updatedExpiration: Long, val updatingAdmin: String): Kind() {
constructor(): this(0L, "")
}
class OpenGroupInvitation(val groupUrl: String, val groupName: String): Kind() {
constructor(): this("", "")
}
data object GroupLeaving: Kind()
data class GroupErrorQuit(val groupName: String): Kind() {
constructor(): this("")
}
): Kind

@Serializable
@SerialName("GroupAvatarUpdated")
data object GroupAvatarUpdated: Kind

@Serializable
@SerialName("GroupExpirationUpdated")
class GroupExpirationUpdated(val updatedExpiration: Long, val updatingAdmin: String): Kind

@Serializable
@SerialName("OpenGroupInvitation")
class OpenGroupInvitation(val groupUrl: String, val groupName: String): Kind

@Serializable
@SerialName("GroupLeaving")
data object GroupLeaving: Kind

@Serializable
@SerialName("GroupErrorQuit")
data class GroupErrorQuit(val groupName: String): Kind

@Serializable
@SerialName("GroupInvitation")
class GroupInvitation(
val groupAccountId: String,
val invitingAdminId: String,
val invitingAdminName: String?,
val groupName: String
) : Kind() {
constructor(): this("", "", null, "")
}
) : Kind
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes(
JsonSubTypes.Type(MemberUpdateType.ADDED::class, name = "ADDED"),
JsonSubTypes.Type(MemberUpdateType.REMOVED::class, name = "REMOVED"),
JsonSubTypes.Type(MemberUpdateType.PROMOTED::class, name = "PROMOTED"),
)
sealed class MemberUpdateType {
data object ADDED: MemberUpdateType()
data object REMOVED: MemberUpdateType()
data object PROMOTED: MemberUpdateType()
}
@Serializable
@JsonClassDiscriminator("@type")
sealed interface MemberUpdateType {
@Serializable
@SerialName("ADDED")
data object ADDED: MemberUpdateType

@Serializable
@SerialName("REMOVED")
data object REMOVED: MemberUpdateType

@Serializable
@SerialName("PROMOTED")
data object PROMOTED: MemberUpdateType

constructor(kind: Kind): this() {
this.kind = kind
}


companion object {
val TAG = UpdateMessageData::class.simpleName

Expand Down Expand Up @@ -137,19 +153,17 @@ class UpdateMessageData () {
}

@JvmStatic
fun fromJSON(json: String): UpdateMessageData? {
return try {
JsonUtil.fromJson(json, UpdateMessageData::class.java)
} catch (e: JsonParseException) {
Log.e(TAG, "${e.message}")
null
}
fun fromJSON(json: Json, value: String): UpdateMessageData? {
return runCatching {
json.decodeFromString<UpdateMessageData>(value)
}.onFailure { Log.e(TAG, "Error decoding updateMessageData", it) }
.getOrNull()
}
Comment on lines +156 to 161
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The migration from Jackson to kotlinx.serialization changes the JSON serialization implementation. While both use "@type" as the discriminator field, there may be subtle differences in formatting or behavior that could affect backward compatibility with existing messages in the database. Consider adding tests to verify that messages serialized with the old Jackson format can still be deserialized correctly with the new kotlinx.serialization implementation, or document if this has been verified.

Copilot uses AI. Check for mistakes.

}

fun toJSON(): String {
return JsonUtil.toJson(this)
fun toJSON(json: Json): String {
return json.encodeToString(this)
}

fun isGroupLeavingKind(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewOpenGroupInvitationBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.OpenGroupUrlParser
import org.thoughtcrime.securesms.database.model.MessageRecord
Expand All @@ -23,7 +24,7 @@ class OpenGroupInvitationView : LinearLayout {

fun bind(message: MessageRecord, @ColorInt textColor: Int) {
// FIXME: This is a really weird approach...
val umd = UpdateMessageData.fromJSON(message.body)!!
val umd = UpdateMessageData.fromJSON(MessagingModuleConfiguration.shared.json, message.body)!!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should be able to inject in the view, though it comes with being careful at the AndroidentryPoints along the way

val data = umd.kind as UpdateMessageData.Kind.OpenGroupInvitation
this.data = data
val iconID = if (message.isOutgoing) R.drawable.ic_globe else R.drawable.ic_plus
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.conversation.v2.utilities

import kotlinx.serialization.json.Json
import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.visible.LinkPreview
Expand All @@ -17,6 +18,7 @@ import javax.inject.Inject
class ResendMessageUtilities @Inject constructor(
private val messageSender: MessageSender,
private val storage: StorageProtocol,
private val json: Json,
) {

suspend fun resend(accountId: String?, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) {
Expand All @@ -25,7 +27,7 @@ class ResendMessageUtilities @Inject constructor(
message.id = messageRecord.messageId
if (messageRecord.isOpenGroupInvitation) {
val openGroupInvitation = OpenGroupInvitation()
UpdateMessageData.fromJSON(messageRecord.body)?.let { updateMessageData ->
UpdateMessageData.fromJSON(json, messageRecord.body)?.let { updateMessageData ->
val kind = updateMessageData.kind
if (kind is UpdateMessageData.Kind.OpenGroupInvitation) {
openGroupInvitation.name = kind.groupName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import network.loki.messenger.libsession_util.MutableConversationVolatileConfig
import network.loki.messenger.libsession_util.PRIORITY_PINNED
import network.loki.messenger.libsession_util.PRIORITY_VISIBLE
Expand Down Expand Up @@ -108,6 +109,7 @@ open class Storage @Inject constructor(
private val openGroupManager: Lazy<OpenGroupManager>,
private val recipientRepository: RecipientRepository,
private val loginStateRepository: LoginStateRepository,
private val json: Json,
) : Database(context, helper), StorageProtocol {

override fun getUserPublicKey(): String? { return loginStateRepository.peekLoginState()?.accountId?.hexString }
Expand Down Expand Up @@ -368,6 +370,7 @@ open class Storage @Inject constructor(

val insertResult = if (isUserSender || isUserBlindedSender) {
val textMessage = if (isOpenGroupInvitation) OutgoingTextMessage.fromOpenGroupInvitation(
json = json,
invitation = message.openGroupInvitation!!,
recipient = targetAddress,
sentTimestampMillis = message.sentTimestamp!!,
Expand All @@ -385,6 +388,7 @@ open class Storage @Inject constructor(
smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!, runThreadUpdate)
} else {
val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(
json = json,
invitation = message.openGroupInvitation!!,
sender = senderAddress,
sentTimestampMillis = message.sentTimestamp!!,
Expand Down Expand Up @@ -797,7 +801,7 @@ open class Storage @Inject constructor(
val expiryMode = recipient.expiryMode
val expiresInMillis = expiryMode.expiryMillis
val expireStartedAt = if (expiryMode is ExpiryMode.AfterSend) sentTimestamp else 0
val inviteJson = updateData.toJSON()
val inviteJson = updateData.toJSON(json)

if (senderPublicKey == null || senderPublicKey == userPublicKey) {
val infoMessage = OutgoingMediaMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.utilities.UpdateMessageData;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.database.model.content.MessageContent;
Expand Down Expand Up @@ -106,19 +107,19 @@ public boolean isMediaPending() {
@Nullable
public UpdateMessageData getGroupUpdateMessage() {
if (isGroupUpdateMessage()) {
groupUpdateMessage = UpdateMessageData.Companion.fromJSON(getBody());
groupUpdateMessage = UpdateMessageData.Companion.fromJSON(
MessagingModuleConfiguration.getShared().getJson(),
getBody()
);
}

return groupUpdateMessage;
}

public boolean isGroupExpirationTimerUpdate() {
if (!isGroupUpdateMessage()) {
return false;
}

UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody());
return updateMessageData != null && updateMessageData.getKind() instanceof UpdateMessageData.Kind.GroupExpirationUpdated;
UpdateMessageData message = getGroupUpdateMessage();
return message != null &&
message.getKind() instanceof UpdateMessageData.Kind.GroupExpirationUpdated;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import network.loki.messenger.libsession_util.util.ExpiryMode
import network.loki.messenger.libsession_util.util.GroupInfo
import org.session.libsession.database.MessageDataProvider
Expand Down Expand Up @@ -95,6 +96,7 @@ class DefaultConversationRepository @Inject constructor(
private val banUserApiFactory: BanUserApi.Factory,
private val unbanUserApiFactory: UnbanUserApi.Factory,
private val deleteUserMessageApiFactory: DeleteUserMessagesApi.Factory,
private val json: Json,
) : ConversationRepository {

override val conversationListAddressesFlow get() = loginStateRepository.flowWithLoggedInState {
Expand Down Expand Up @@ -227,6 +229,7 @@ class DefaultConversationRepository @Inject constructor(
val expirationConfig = recipientRepository.getRecipientSync(contact).expiryMode
val expireStartedAt = if (expirationConfig is ExpiryMode.AfterSend) message.sentTimestamp!! else 0
val outgoingTextMessage = OutgoingTextMessage.Companion.fromOpenGroupInvitation(
json,
openGroupInvitation,
contact,
message.sentTimestamp!!,
Expand Down