mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Update expiry configuration
This commit is contained in:
parent
b529d6d341
commit
6eba3ac8af
@ -12,6 +12,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import network.loki.messenger.BuildConfig
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ActivityExpirationSettingsBinding
|
import network.loki.messenger.databinding.ActivityExpirationSettingsBinding
|
||||||
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
|
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
|
||||||
@ -41,7 +42,15 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
|
|||||||
.zip(resources.getStringArray(R.array.read_expiration_time_names)) { value, name -> RadioOption(value, name)}
|
.zip(resources.getStringArray(R.array.read_expiration_time_names)) { value, name -> RadioOption(value, name)}
|
||||||
val afterSendOptions = resources.getIntArray(R.array.send_expiration_time_values).map(Int::toString)
|
val afterSendOptions = resources.getIntArray(R.array.send_expiration_time_values).map(Int::toString)
|
||||||
.zip(resources.getStringArray(R.array.send_expiration_time_names)) { value, name -> RadioOption(value, name)}
|
.zip(resources.getStringArray(R.array.send_expiration_time_names)) { value, name -> RadioOption(value, name)}
|
||||||
viewModelFactory.create(threadId, afterReadOptions, afterSendOptions)
|
viewModelFactory.create(threadId, mayAddTestExpiryOption(afterReadOptions), mayAddTestExpiryOption(afterSendOptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mayAddTestExpiryOption(expiryOptions: List<RadioOption>): List<RadioOption> {
|
||||||
|
return if (BuildConfig.DEBUG) {
|
||||||
|
val options = expiryOptions.toMutableList()
|
||||||
|
options.add(1, RadioOption("60", "1 Minute"))
|
||||||
|
options
|
||||||
|
} else expiryOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -12,6 +12,7 @@ import org.session.libsession.messaging.jobs.Job
|
|||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||||
|
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
@ -961,4 +962,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return recipientDb.blockedContacts
|
return recipientDb.blockedContacts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getExpirationSettingsConfiguration(threadId: Long): ExpirationSettingsConfiguration? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addExpirationSettingsConfiguration(config: ExpirationSettingsConfiguration) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -6,7 +6,6 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/menu_overflow"
|
android:id="@+id/menu_overflow"
|
||||||
android:icon="@drawable/ic_outline_settings_24"
|
android:icon="@drawable/ic_outline_settings_24"
|
||||||
android:title="@string/conversation_context__menu_call"
|
|
||||||
app:showAsAction="always">
|
app:showAsAction="always">
|
||||||
|
|
||||||
<menu>
|
<menu>
|
||||||
|
@ -8,6 +8,7 @@ import org.session.libsession.messaging.contacts.Contact
|
|||||||
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||||
import org.session.libsession.messaging.jobs.Job
|
import org.session.libsession.messaging.jobs.Job
|
||||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||||
|
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
@ -144,7 +145,7 @@ interface StorageProtocol {
|
|||||||
|
|
||||||
// Thread
|
// Thread
|
||||||
fun getOrCreateThreadIdFor(address: Address): Long
|
fun getOrCreateThreadIdFor(address: Address): Long
|
||||||
fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long
|
fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String? = null, openGroupID: String? = null): Long
|
||||||
fun getThreadId(publicKeyOrOpenGroupID: String): Long?
|
fun getThreadId(publicKeyOrOpenGroupID: String): Long?
|
||||||
fun getThreadId(address: Address): Long?
|
fun getThreadId(address: Address): Long?
|
||||||
fun getThreadId(recipient: Recipient): Long?
|
fun getThreadId(recipient: Recipient): Long?
|
||||||
@ -198,4 +199,7 @@ interface StorageProtocol {
|
|||||||
fun deleteReactions(messageId: Long, mms: Boolean)
|
fun deleteReactions(messageId: Long, mms: Boolean)
|
||||||
fun unblock(toUnblock: List<Recipient>)
|
fun unblock(toUnblock: List<Recipient>)
|
||||||
fun blockedContacts(): List<Recipient>
|
fun blockedContacts(): List<Recipient>
|
||||||
|
fun getExpirationSettingsConfiguration(threadId: Long): ExpirationSettingsConfiguration?
|
||||||
|
fun addExpirationSettingsConfiguration(config: ExpirationSettingsConfiguration)
|
||||||
|
fun getExpiringMessages(messageIds: LongArray): List<Pair<String, Int>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.session.libsession.messaging.jobs
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.messaging.messages.control.SyncedExpiriesMessage
|
||||||
|
import org.session.libsession.messaging.messages.control.SyncedExpiry
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
|
||||||
|
class DisappearingMessagesJob(val messageIds: LongArray, val startedAtMs: Long): Job {
|
||||||
|
|
||||||
|
override var delegate: JobDelegate? = null
|
||||||
|
override var id: String? = null
|
||||||
|
override var failureCount: Int = 0
|
||||||
|
override val maxFailureCount: Int = 1
|
||||||
|
|
||||||
|
override fun execute() {
|
||||||
|
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: return
|
||||||
|
val module = MessagingModuleConfiguration.shared
|
||||||
|
try {
|
||||||
|
module.storage.getExpiringMessages(messageIds).groupBy { it.second }.forEach { (expiresInSeconds, messages) ->
|
||||||
|
val serverHashes = messages.map { it.first }
|
||||||
|
if (serverHashes.isEmpty()) return
|
||||||
|
val expirationTimestamp = startedAtMs + expiresInSeconds * 1000
|
||||||
|
val syncTarget = ""
|
||||||
|
val syncedExpiriesMessage = SyncedExpiriesMessage()
|
||||||
|
syncedExpiriesMessage.conversationExpiries = mapOf(
|
||||||
|
syncTarget to serverHashes.map { serverHash -> SyncedExpiry(serverHash, expirationTimestamp) }
|
||||||
|
)
|
||||||
|
MessageSender.send(syncedExpiriesMessage, Address.fromSerialized(userPublicKey))
|
||||||
|
SnodeAPI.updateExpiry(expirationTimestamp, serverHashes)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
delegate?.handleJobFailed(this, e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delegate?.handleJobSucceeded(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(): Data = Data.Builder()
|
||||||
|
.putLongArray(MESSAGE_IDS, messageIds)
|
||||||
|
.putLong(STARTED_AT_MS, startedAtMs)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String = KEY
|
||||||
|
|
||||||
|
class Factory : Job.Factory<DisappearingMessagesJob> {
|
||||||
|
override fun create(data: Data): DisappearingMessagesJob {
|
||||||
|
return DisappearingMessagesJob(
|
||||||
|
data.getLongArray(MESSAGE_IDS),
|
||||||
|
data.getLong(STARTED_AT_MS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY = "DisappearingMessagesJob"
|
||||||
|
|
||||||
|
private const val MESSAGE_IDS = "messageIds"
|
||||||
|
private const val STARTED_AT_MS = "startedAtMs"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.session.libsession.messaging.messages
|
||||||
|
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
|
||||||
|
|
||||||
|
class ExpirationSettingsConfiguration(
|
||||||
|
val threadId: Long = -1,
|
||||||
|
val isEnabled: Boolean = false,
|
||||||
|
val durationSeconds: Int = 0,
|
||||||
|
val expirationType: ExpirationType? = null,
|
||||||
|
val lastChangeTimestampMs: Long = 0
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
package org.session.libsession.messaging.messages
|
package org.session.libsession.messaging.messages
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
|
|
||||||
@ -14,8 +15,10 @@ abstract class Message {
|
|||||||
var groupPublicKey: String? = null
|
var groupPublicKey: String? = null
|
||||||
var openGroupServerMessageID: Long? = null
|
var openGroupServerMessageID: Long? = null
|
||||||
var serverHash: String? = null
|
var serverHash: String? = null
|
||||||
|
var specifiedTtl: Long? = null
|
||||||
|
|
||||||
open val ttl: Long = 14 * 24 * 60 * 60 * 1000
|
open val defaultTtl: Long = 14 * 24 * 60 * 60 * 1000
|
||||||
|
val ttl: Long get() = specifiedTtl ?: defaultTtl
|
||||||
open val isSelfSendValid: Boolean = false
|
open val isSelfSendValid: Boolean = false
|
||||||
|
|
||||||
open fun isValid(): Boolean {
|
open fun isValid(): Boolean {
|
||||||
@ -36,4 +39,13 @@ abstract class Message {
|
|||||||
dataMessage.group = groupProto.build()
|
dataMessage.group = groupProto.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setExpirationSettingsConfigIfNeeded(builder: SignalServiceProtos.Content.Builder) {
|
||||||
|
val threadId = threadID ?: return
|
||||||
|
val config = MessagingModuleConfiguration.shared.storage.getExpirationSettingsConfiguration(threadId) ?: return
|
||||||
|
builder.expirationTimer = config.durationSeconds
|
||||||
|
if (config.isEnabled) {
|
||||||
|
builder.expirationType = config.expirationType
|
||||||
|
builder.lastDisappearingMessageChangeTimestamp = config.lastChangeTimestampMs
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,10 +14,10 @@ class CallMessage(): ControlMessage() {
|
|||||||
|
|
||||||
override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL)
|
override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL)
|
||||||
|
|
||||||
override val ttl: Long = 300000L // 5m
|
override val defaultTtl: Long = 300000L // 5m
|
||||||
|
|
||||||
override fun isValid(): Boolean = super.isValid() && type != null && callId != null
|
override fun isValid(): Boolean = super.isValid() && type != null && callId != null
|
||||||
&& (!sdps.isNullOrEmpty() || type in listOf(END_CALL, PRE_OFFER))
|
&& (sdps.isNotEmpty() || type in listOf(END_CALL, PRE_OFFER))
|
||||||
|
|
||||||
constructor(type: SignalServiceProtos.CallMessage.Type,
|
constructor(type: SignalServiceProtos.CallMessage.Type,
|
||||||
sdps: List<String>,
|
sdps: List<String>,
|
||||||
@ -81,10 +81,11 @@ class CallMessage(): ControlMessage() {
|
|||||||
.addAllSdpMids(sdpMids)
|
.addAllSdpMids(sdpMids)
|
||||||
.setUuid(callId!!.toString())
|
.setUuid(callId!!.toString())
|
||||||
|
|
||||||
return SignalServiceProtos.Content.newBuilder()
|
val content = SignalServiceProtos.Content.newBuilder()
|
||||||
.setCallMessage(
|
setExpirationSettingsConfigIfNeeded(content)
|
||||||
callMessage
|
|
||||||
)
|
return content
|
||||||
|
.setCallMessage(callMessage)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import org.session.libsignal.utilities.Log
|
|||||||
class ClosedGroupControlMessage() : ControlMessage() {
|
class ClosedGroupControlMessage() : ControlMessage() {
|
||||||
var kind: Kind? = null
|
var kind: Kind? = null
|
||||||
|
|
||||||
override val ttl: Long get() {
|
override val defaultTtl: Long get() {
|
||||||
return when (kind) {
|
return when (kind) {
|
||||||
is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000
|
is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000
|
||||||
else -> 14 * 24 * 60 * 60 * 1000
|
else -> 14 * 24 * 60 * 60 * 1000
|
||||||
@ -167,6 +167,8 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
|||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
val dataMessageProto = DataMessage.newBuilder()
|
val dataMessageProto = DataMessage.newBuilder()
|
||||||
dataMessageProto.closedGroupControlMessage = closedGroupControlMessage.build()
|
dataMessageProto.closedGroupControlMessage = closedGroupControlMessage.build()
|
||||||
|
// Expiration timer
|
||||||
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
// Group context
|
// Group context
|
||||||
setGroupContext(dataMessageProto)
|
setGroupContext(dataMessageProto)
|
||||||
contentProto.dataMessage = dataMessageProto.build()
|
contentProto.dataMessage = dataMessageProto.build()
|
||||||
|
@ -64,6 +64,7 @@ class DataExtractionNotification() : ControlMessage() {
|
|||||||
}
|
}
|
||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
contentProto.dataExtractionNotification = dataExtractionNotification.build()
|
contentProto.dataExtractionNotification = dataExtractionNotification.build()
|
||||||
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
return contentProto.build()
|
return contentProto.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Couldn't construct data extraction notification proto from: $this")
|
Log.w(TAG, "Couldn't construct data extraction notification proto from: $this")
|
||||||
|
@ -10,10 +10,11 @@ class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() {
|
|||||||
override fun toProto(): SignalServiceProtos.Content? {
|
override fun toProto(): SignalServiceProtos.Content? {
|
||||||
val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder()
|
val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder()
|
||||||
.setIsApproved(isApproved)
|
.setIsApproved(isApproved)
|
||||||
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
return try {
|
return try {
|
||||||
SignalServiceProtos.Content.newBuilder()
|
contentProto.messageRequestResponse = messageRequestResponseProto.build()
|
||||||
.setMessageRequestResponse(messageRequestResponseProto.build())
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
.build()
|
contentProto.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Couldn't construct message request response proto from: $this")
|
Log.w(TAG, "Couldn't construct message request response proto from: $this")
|
||||||
null
|
null
|
||||||
|
@ -39,12 +39,13 @@ class ReadReceipt() : ControlMessage() {
|
|||||||
receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ
|
receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ
|
||||||
receiptProto.addAllTimestamp(timestamps.asIterable())
|
receiptProto.addAllTimestamp(timestamps.asIterable())
|
||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
try {
|
return try {
|
||||||
contentProto.receiptMessage = receiptProto.build()
|
contentProto.receiptMessage = receiptProto.build()
|
||||||
return contentProto.build()
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
|
contentProto.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Couldn't construct read receipt proto from: $this")
|
Log.w(TAG, "Couldn't construct read receipt proto from: $this")
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.session.libsession.messaging.messages.control
|
||||||
|
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.SyncedExpiries.SyncedConversationExpiries
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
|
class SyncedExpiriesMessage(): ControlMessage() {
|
||||||
|
var conversationExpiries: Map<String, List<SyncedExpiry>> = emptyMap()
|
||||||
|
|
||||||
|
override val isSelfSendValid: Boolean = true
|
||||||
|
|
||||||
|
// region Validation
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (!super.isValid()) return false
|
||||||
|
return conversationExpiries.isNotEmpty()
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "SyncedExpiriesMessage"
|
||||||
|
|
||||||
|
fun fromProto(proto: SignalServiceProtos.Content): SyncedExpiriesMessage? {
|
||||||
|
val syncedExpiriesProto = if (proto.hasSyncedExpiries()) proto.syncedExpiries else return null
|
||||||
|
val conversationExpiries = syncedExpiriesProto.conversationExpiriesList.associate {
|
||||||
|
it.syncTarget to it.expiriesList.map { syncedExpiry -> SyncedExpiry.fromProto(syncedExpiry) }
|
||||||
|
}
|
||||||
|
return SyncedExpiriesMessage(conversationExpiries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(conversationExpiries: Map<String, List<SyncedExpiry>>) : this() {
|
||||||
|
this.conversationExpiries = conversationExpiries
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toProto(): SignalServiceProtos.Content? {
|
||||||
|
val conversationExpiries = conversationExpiries
|
||||||
|
if (conversationExpiries.isEmpty()) {
|
||||||
|
Log.w(TAG, "Couldn't construct synced expiries proto from: $this")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val conversationExpiriesProto = conversationExpiries.map { (syncTarget, syncedExpiries) ->
|
||||||
|
val expiriesProto = syncedExpiries.map(SyncedExpiry::toProto)
|
||||||
|
val syncedConversationExpiriesProto = SyncedConversationExpiries.newBuilder()
|
||||||
|
syncedConversationExpiriesProto.syncTarget = syncTarget
|
||||||
|
syncedConversationExpiriesProto.addAllExpiries(expiriesProto)
|
||||||
|
syncedConversationExpiriesProto.build()
|
||||||
|
}
|
||||||
|
val syncedExpiriesProto = SignalServiceProtos.SyncedExpiries.newBuilder()
|
||||||
|
syncedExpiriesProto.addAllConversationExpiries(conversationExpiriesProto)
|
||||||
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
|
return try {
|
||||||
|
contentProto.syncedExpiries = syncedExpiriesProto.build()
|
||||||
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
|
contentProto.build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Couldn't construct synced expiries proto from: $this")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.session.libsession.messaging.messages.control
|
||||||
|
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.SyncedExpiries
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
|
class SyncedExpiry(
|
||||||
|
var serverHash: String? = null,
|
||||||
|
var expirationTimestamp: Long? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun toProto(): SyncedExpiries.SyncedConversationExpiries.SyncedExpiry? {
|
||||||
|
val syncedExpiryProto = SyncedExpiries.SyncedConversationExpiries.SyncedExpiry.newBuilder()
|
||||||
|
serverHash?.let { syncedExpiryProto.serverHash = it }
|
||||||
|
expirationTimestamp?.let { syncedExpiryProto.expirationTimestamp = it }
|
||||||
|
return try {
|
||||||
|
syncedExpiryProto.build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Couldn't construct synced expiry proto from: $this")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "SyncedExpiry"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromProto(proto: SyncedExpiries.SyncedConversationExpiries.SyncedExpiry): SyncedExpiry {
|
||||||
|
val result = SyncedExpiry()
|
||||||
|
result.serverHash = if (proto.hasServerHash()) proto.serverHash else null
|
||||||
|
result.expirationTimestamp = if (proto.hasServerHash()) proto.expirationTimestamp else null
|
||||||
|
return SyncedExpiry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,7 +6,7 @@ import org.session.libsignal.utilities.Log
|
|||||||
class TypingIndicator() : ControlMessage() {
|
class TypingIndicator() : ControlMessage() {
|
||||||
var kind: Kind? = null
|
var kind: Kind? = null
|
||||||
|
|
||||||
override val ttl: Long = 20 * 1000
|
override val defaultTtl: Long = 20 * 1000
|
||||||
|
|
||||||
override fun isValid(): Boolean {
|
override fun isValid(): Boolean {
|
||||||
if (!super.isValid()) return false
|
if (!super.isValid()) return false
|
||||||
@ -58,12 +58,13 @@ class TypingIndicator() : ControlMessage() {
|
|||||||
typingIndicatorProto.timestamp = timestamp
|
typingIndicatorProto.timestamp = timestamp
|
||||||
typingIndicatorProto.action = kind.toProto()
|
typingIndicatorProto.action = kind.toProto()
|
||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
try {
|
return try {
|
||||||
contentProto.typingMessage = typingIndicatorProto.build()
|
contentProto.typingMessage = typingIndicatorProto.build()
|
||||||
return contentProto.build()
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
|
contentProto.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
|
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,12 +43,13 @@ class UnsendRequest(): ControlMessage() {
|
|||||||
unsendRequestProto.timestamp = timestamp
|
unsendRequestProto.timestamp = timestamp
|
||||||
unsendRequestProto.author = author
|
unsendRequestProto.author = author
|
||||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||||
try {
|
return try {
|
||||||
contentProto.unsendRequest = unsendRequestProto.build()
|
contentProto.unsendRequest = unsendRequestProto.build()
|
||||||
return contentProto.build()
|
setExpirationSettingsConfigIfNeeded(contentProto)
|
||||||
|
contentProto.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(TAG, "Couldn't construct unsend request proto from: $this")
|
Log.w(TAG, "Couldn't construct unsend request proto from: $this")
|
||||||
return null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,6 @@ import com.goterl.lazysodium.BuildConfig
|
|||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
||||||
@ -119,17 +116,9 @@ class VisibleMessage : Message() {
|
|||||||
dataMessage.addAllAttachments(pointers)
|
dataMessage.addAllAttachments(pointers)
|
||||||
// TODO: Contact
|
// TODO: Contact
|
||||||
// Expiration timer
|
// Expiration timer
|
||||||
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
|
setExpirationSettingsConfigIfNeeded(proto)
|
||||||
// if it receives a message without the current expiration timer value attached to it...
|
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
|
||||||
val expiration = if (storage.isClosedGroup(recipient!!)) {
|
|
||||||
Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
|
|
||||||
} else {
|
|
||||||
Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages
|
|
||||||
}
|
|
||||||
dataMessage.expireTimer = expiration
|
|
||||||
// Group context
|
// Group context
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
if (storage.isClosedGroup(recipient!!)) {
|
if (storage.isClosedGroup(recipient!!)) {
|
||||||
try {
|
try {
|
||||||
setGroupContext(dataMessage)
|
setGroupContext(dataMessage)
|
||||||
|
@ -9,6 +9,7 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat
|
|||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||||
|
import org.session.libsession.messaging.messages.control.SyncedExpiriesMessage
|
||||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
@ -138,6 +139,7 @@ object MessageReceiver {
|
|||||||
UnsendRequest.fromProto(proto) ?:
|
UnsendRequest.fromProto(proto) ?:
|
||||||
MessageRequestResponse.fromProto(proto) ?:
|
MessageRequestResponse.fromProto(proto) ?:
|
||||||
CallMessage.fromProto(proto) ?:
|
CallMessage.fromProto(proto) ?:
|
||||||
|
SyncedExpiriesMessage.fromProto(proto) ?:
|
||||||
VisibleMessage.fromProto(proto) ?: run {
|
VisibleMessage.fromProto(proto) ?: run {
|
||||||
throw Error.UnknownMessage
|
throw Error.UnknownMessage
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import org.session.libsession.utilities.GroupUtil
|
|||||||
import org.session.libsession.utilities.SSKEnvironment
|
import org.session.libsession.utilities.SSKEnvironment
|
||||||
import org.session.libsignal.crypto.PushTransportDetails
|
import org.session.libsignal.crypto.PushTransportDetails
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
|
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.IdPrefix
|
import org.session.libsignal.utilities.IdPrefix
|
||||||
import org.session.libsignal.utilities.Namespace
|
import org.session.libsignal.utilities.Namespace
|
||||||
@ -171,7 +172,12 @@ object MessageSender {
|
|||||||
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
|
val base64EncodedData = Base64.encodeBytes(wrappedMessage)
|
||||||
// Send the result
|
// Send the result
|
||||||
val timestamp = messageSendTime + SnodeAPI.clockOffset
|
val timestamp = messageSendTime + SnodeAPI.clockOffset
|
||||||
val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, timestamp)
|
val snodeMessage = SnodeMessage(
|
||||||
|
recipient = message.recipient!!,
|
||||||
|
data = base64EncodedData,
|
||||||
|
ttl = getSpecifiedTtl(message, isSyncMessage) ?: message.ttl,
|
||||||
|
timestamp = timestamp
|
||||||
|
)
|
||||||
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
|
||||||
SnodeModule.shared.broadcaster.broadcast("sendingMessage", messageSendTime)
|
SnodeModule.shared.broadcaster.broadcast("sendingMessage", messageSendTime)
|
||||||
}
|
}
|
||||||
@ -214,6 +220,19 @@ object MessageSender {
|
|||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSpecifiedTtl(message: Message, isSyncMessage: Boolean): Long? {
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
val threadId = message.threadID
|
||||||
|
?: run {
|
||||||
|
val address = if (isSyncMessage && message is VisibleMessage) message.syncTarget else message.recipient
|
||||||
|
storage.getOrCreateThreadIdFor(address!!)
|
||||||
|
}
|
||||||
|
val config = storage.getExpirationSettingsConfiguration(threadId) ?: return null
|
||||||
|
return if (config.isEnabled && (config.expirationType == ExpirationType.DELETE_AFTER_SEND || isSyncMessage)) {
|
||||||
|
config.durationSeconds * 1000L
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
// Open Groups
|
// Open Groups
|
||||||
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
||||||
val deferred = deferred<Unit, Exception>()
|
val deferred = deferred<Unit, Exception>()
|
||||||
|
@ -5,6 +5,7 @@ import org.session.libsession.avatars.AvatarHelper
|
|||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
|
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
import org.session.libsession.messaging.messages.ExpirationSettingsConfiguration
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.CallMessage
|
import org.session.libsession.messaging.messages.control.CallMessage
|
||||||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
||||||
@ -13,6 +14,7 @@ import org.session.libsession.messaging.messages.control.DataExtractionNotificat
|
|||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||||
|
import org.session.libsession.messaging.messages.control.SyncedExpiriesMessage
|
||||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
@ -58,6 +60,7 @@ internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||||
|
updateExpirationSettingsConfigIfNeeded(message, proto, openGroupID)
|
||||||
when (message) {
|
when (message) {
|
||||||
is ReadReceipt -> handleReadReceipt(message)
|
is ReadReceipt -> handleReadReceipt(message)
|
||||||
is TypingIndicator -> handleTypingIndicator(message)
|
is TypingIndicator -> handleTypingIndicator(message)
|
||||||
@ -73,9 +76,30 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
|||||||
runProfileUpdate = true
|
runProfileUpdate = true
|
||||||
)
|
)
|
||||||
is CallMessage -> handleCallMessage(message)
|
is CallMessage -> handleCallMessage(message)
|
||||||
|
is SyncedExpiriesMessage -> handleSyncedExpiriesMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateExpirationSettingsConfigIfNeeded(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
|
||||||
|
if (!proto.hasLastDisappearingMessageChangeTimestamp()) return
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
val threadID = storage.getOrCreateThreadIdFor(message.sender!!, message.groupPublicKey, openGroupID)
|
||||||
|
if (threadID <= 0) return
|
||||||
|
val localConfig = storage.getExpirationSettingsConfiguration(threadID)
|
||||||
|
if (localConfig == null || localConfig.lastChangeTimestampMs < proto.lastDisappearingMessageChangeTimestamp) return
|
||||||
|
val durationSeconds = if (proto.hasExpirationTimer()) proto.expirationTimer else 0
|
||||||
|
val isEnabled = durationSeconds != 0
|
||||||
|
val type = if (proto.hasExpirationType()) proto.expirationType else null
|
||||||
|
val remoteConfig = ExpirationSettingsConfiguration(
|
||||||
|
threadID,
|
||||||
|
isEnabled,
|
||||||
|
durationSeconds,
|
||||||
|
type,
|
||||||
|
proto.lastDisappearingMessageChangeTimestamp
|
||||||
|
)
|
||||||
|
storage.addExpirationSettingsConfiguration(remoteConfig)
|
||||||
|
}
|
||||||
|
|
||||||
// region Control Messages
|
// region Control Messages
|
||||||
private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
|
private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
val context = MessagingModuleConfiguration.shared.context
|
||||||
@ -87,6 +111,19 @@ private fun MessageReceiver.handleCallMessage(message: CallMessage) {
|
|||||||
WebRtcUtils.SIGNAL_QUEUE.trySend(message)
|
WebRtcUtils.SIGNAL_QUEUE.trySend(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MessageReceiver.handleSyncedExpiriesMessage(message: SyncedExpiriesMessage) {
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
val userPublicKey = storage.getUserPublicKey() ?: return
|
||||||
|
if (userPublicKey != message.sender) return
|
||||||
|
message.conversationExpiries.forEach { (syncTarget, syncedExpiries) ->
|
||||||
|
val config = storage.getExpirationSettingsConfiguration(storage.getOrCreateThreadIdFor(syncTarget)) ?: return@forEach
|
||||||
|
syncedExpiries.forEach { syncedExpiry ->
|
||||||
|
val startedAtMs = syncedExpiry.expirationTimestamp!! - config.durationSeconds * 1000
|
||||||
|
SSKEnvironment.shared.messageExpirationManager.startAnyExpiration(startedAtMs, syncTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
|
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
|
||||||
when (message.kind!!) {
|
when (message.kind!!) {
|
||||||
TypingIndicator.Kind.STARTED -> showTypingIndicatorIfNeeded(message.sender!!)
|
TypingIndicator.Kind.STARTED -> showTypingIndicatorIfNeeded(message.sender!!)
|
||||||
|
@ -493,6 +493,62 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateExpiry(updatedExpiryMs: Long, serverHashes: List<String>): Promise<Map<String, Pair<List<String>, Long>>, Exception> {
|
||||||
|
return retryIfNeeded(maxRetryCount) {
|
||||||
|
val module = MessagingModuleConfiguration.shared
|
||||||
|
val userED25519KeyPair = module.getUserED25519KeyPair() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair)
|
||||||
|
val userPublicKey = module.storage.getUserPublicKey() ?: return@retryIfNeeded Promise.ofFail(Error.NoKeyPair)
|
||||||
|
val updatedExpiryMsWithNetworkOffset = updatedExpiryMs + clockOffset
|
||||||
|
getSingleTargetSnode(userPublicKey).bind { snode ->
|
||||||
|
retryIfNeeded(maxRetryCount) {
|
||||||
|
// "expire" || expiry || messages[0] || ... || messages[N]
|
||||||
|
val verificationData =
|
||||||
|
(Snode.Method.Expire.rawValue + updatedExpiryMsWithNetworkOffset + serverHashes.fold("") { a, v -> a + v }).toByteArray()
|
||||||
|
val signature = ByteArray(Sign.BYTES)
|
||||||
|
sodium.cryptoSignDetached(
|
||||||
|
signature,
|
||||||
|
verificationData,
|
||||||
|
verificationData.size.toLong(),
|
||||||
|
userED25519KeyPair.secretKey.asBytes
|
||||||
|
)
|
||||||
|
val params = mapOf(
|
||||||
|
"pubkey" to userPublicKey,
|
||||||
|
"pubkey_ed25519" to userED25519KeyPair.publicKey.asHexString,
|
||||||
|
"expiry" to updatedExpiryMs,
|
||||||
|
"messages" to serverHashes,
|
||||||
|
"signature" to Base64.encodeBytes(signature)
|
||||||
|
)
|
||||||
|
invoke(Snode.Method.Expire, snode, params, userPublicKey).map { rawResponse ->
|
||||||
|
val swarms = rawResponse["swarm"] as? Map<String, Any> ?: return@map mapOf()
|
||||||
|
val result = swarms.mapNotNull { (hexSnodePublicKey, rawJSON) ->
|
||||||
|
val json = rawJSON as? Map<String, Any> ?: return@mapNotNull null
|
||||||
|
val isFailed = json["failed"] as? Boolean ?: false
|
||||||
|
val statusCode = json["code"] as? String
|
||||||
|
val reason = json["reason"] as? String
|
||||||
|
hexSnodePublicKey to if (isFailed) {
|
||||||
|
Log.e("Loki", "Failed to update expiry for: $hexSnodePublicKey due to error: $reason ($statusCode).")
|
||||||
|
listOf<String>() to 0L
|
||||||
|
} else {
|
||||||
|
val hashes = json["updated"] as List<String>
|
||||||
|
val expiryApplied = json["expiry"] as Long
|
||||||
|
val signature = json["signature"] as String
|
||||||
|
val snodePublicKey = Key.fromHexString(hexSnodePublicKey)
|
||||||
|
// The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )
|
||||||
|
val message = (userPublicKey + serverHashes.fold("") { a, v -> a + v } + hashes.fold("") { a, v -> a + v }).toByteArray()
|
||||||
|
if (sodium.cryptoSignVerifyDetached(Base64.decode(signature), message, message.size, snodePublicKey.asBytes)) {
|
||||||
|
hashes to expiryApplied
|
||||||
|
} else listOf<String>() to 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@map result.toMap()
|
||||||
|
}.fail { e ->
|
||||||
|
Log.e("Loki", "Failed to update expiry", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0): List<Pair<SignalServiceProtos.Envelope, String?>> {
|
fun parseRawMessagesResponse(rawResponse: RawResponse, snode: Snode, publicKey: String, namespace: Int = 0): List<Pair<SignalServiceProtos.Envelope, String?>> {
|
||||||
val messages = rawResponse["messages"] as? List<*>
|
val messages = rawResponse["messages"] as? List<*>
|
||||||
return if (messages != null) {
|
return if (messages != null) {
|
||||||
|
@ -59,11 +59,26 @@ message Content {
|
|||||||
optional ExpirationType expirationType = 11;
|
optional ExpirationType expirationType = 11;
|
||||||
optional uint32 expirationTimer = 12;
|
optional uint32 expirationTimer = 12;
|
||||||
optional uint64 lastDisappearingMessageChangeTimestamp = 13;
|
optional uint64 lastDisappearingMessageChangeTimestamp = 13;
|
||||||
|
optional SyncedExpiries syncedExpiries = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SyncedExpiry {
|
message SyncedExpiries {
|
||||||
required string serverHash = 1;
|
|
||||||
required uint64 expirationTimestamp = 2;
|
message SyncedConversationExpiries {
|
||||||
|
|
||||||
|
message SyncedExpiry {
|
||||||
|
// @required
|
||||||
|
required string serverHash = 1; // messageHash for desktop and serverHash for mobile
|
||||||
|
// @required
|
||||||
|
required uint64 expirationTimestamp = 2; // this is only used for deleteAfterRead
|
||||||
|
}
|
||||||
|
|
||||||
|
// @required
|
||||||
|
required string syncTarget = 1; // the conversationID those expiries are related to
|
||||||
|
repeated SyncedExpiry expiries = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
repeated SyncedConversationExpiries conversationExpiries = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message KeyPair {
|
message KeyPair {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
|||||||
DeleteMessage("delete"),
|
DeleteMessage("delete"),
|
||||||
OxenDaemonRPCCall("oxend_request"),
|
OxenDaemonRPCCall("oxend_request"),
|
||||||
Info("info"),
|
Info("info"),
|
||||||
DeleteAll("delete_all")
|
DeleteAll("delete_all"),
|
||||||
|
Expire("expire")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
||||||
|
Loading…
Reference in New Issue
Block a user