Fix message disappearance after send

This commit is contained in:
charles 2022-12-20 13:14:36 +11:00
parent 3c6b93b2f8
commit 42923b5c2b
16 changed files with 92 additions and 41 deletions

View File

@ -108,9 +108,13 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
lifecycleScope.launchWhenStarted {
launch {
viewModel.uiState.collect { uiState ->
if (uiState.settingsSaved == true) {
Toast.makeText(this@ExpirationSettingsActivity, getString(R.string.ExpirationSettingsActivity_settings_saved), Toast.LENGTH_SHORT).show()
finish()
when (uiState.settingsSaved) {
true -> {
showToast(getString(R.string.ExpirationSettingsActivity_settings_updated))
finish()
}
false -> showToast(getString(R.string.ExpirationSettingsActivity_settings_not_updated))
else -> {}
}
}
}
@ -145,6 +149,10 @@ class ExpirationSettingsActivity: PassphraseRequiredActionBarActivity() {
}
private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
private fun getDeleteOptions(): List<RadioOption> {
if (!viewModel.uiState.value.showExpirationTypeSelector) return emptyList()

View File

@ -88,6 +88,7 @@ class ExpirationSettingsViewModel(
fun onExpirationTypeSelected(option: RadioOption) {
_selectedExpirationType.value = option.value.toIntOrNull() ?: -1
_selectedExpirationTimer.value = _expirationTimerOptions.value.firstOrNull()
}
fun onExpirationTimerSelected(option: RadioOption) {
@ -96,16 +97,22 @@ class ExpirationSettingsViewModel(
fun onSetClick() = viewModelScope.launch {
val expiryType = _selectedExpirationType.value
val firstOption = _expirationTimerOptions.value.firstOrNull()?.value?.toIntOrNull() ?: 0
val expiresIn = _selectedExpirationTimer.value?.value?.toIntOrNull() ?: if (expiryType >= 0) firstOption else 0
val expiryChangeTimestampMs = System.currentTimeMillis()
storage.setExpirationConfiguration(ExpirationConfiguration(threadId, expiresIn, expiryType, expiryChangeTimestampMs))
val expirationTimer = _selectedExpirationTimer.value?.value?.toIntOrNull() ?: 0
val address = recipient.value?.address
if (address == null || (expirationConfig?.expirationTypeValue == expiryType && expirationConfig?.durationSeconds == expirationTimer)) {
_uiState.update {
it.copy(settingsSaved = false)
}
return@launch
}
val message = ExpirationTimerUpdate(expiresIn)
val address = recipient.value?.address ?: return@launch
val expiryChangeTimestampMs = System.currentTimeMillis()
storage.setExpirationConfiguration(ExpirationConfiguration(threadId, expirationTimer, expiryType, expiryChangeTimestampMs))
val message = ExpirationTimerUpdate(expirationTimer)
message.recipient = address.serialize()
message.sentTimestamp = System.currentTimeMillis()
messageExpirationManager.setExpirationTimer(message)
message.sentTimestamp = expiryChangeTimestampMs
messageExpirationManager.setExpirationTimer(message, expiryType)
MessageSender.send(message, address)
_uiState.update {

View File

@ -65,6 +65,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.Recipient.DisappearingState
import org.session.libsession.utilities.recipients.RecipientModifiedListener
import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.ListenableFuture
import org.session.libsignal.utilities.Log
@ -1356,8 +1357,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val recipient = viewModel.recipient ?: return
processMessageRequestApproval()
// Create the message
val sentTimestampMs = System.currentTimeMillis()
val message = VisibleMessage()
message.sentTimestamp = System.currentTimeMillis()
message.sentTimestamp = sentTimestampMs
message.text = body
val quote = quotedMessage?.let {
val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf()
@ -1372,8 +1374,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
else it.individualRecipient.address
quote?.copy(author = sender)
}
val expiresInMillis = (viewModel.expirationConfiguration?.durationSeconds ?: 0) * 1000L
val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, localQuote, linkPreview, expiresInMillis)
val expiresInMs = (viewModel.expirationConfiguration?.durationSeconds ?: 0) * 1000L
val expireStartedAtMs = if (viewModel.expirationConfiguration?.expirationType == ExpirationType.DELETE_AFTER_SEND) {
sentTimestampMs + expiresInMs
} else 0
val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, localQuote, linkPreview, expiresInMs, expireStartedAtMs)
// Clear the input bar
binding?.inputBar?.text = ""
binding?.inputBar?.cancelQuoteDraft()

View File

@ -467,6 +467,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT))
val subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID))
val expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN))
val expireStartedAt = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED))
val address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS))
val threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID))
val distributionType = get(context).threadDatabase().getDistributionType(threadId)
@ -535,6 +536,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
timestamp,
subscriptionId,
expiresIn,
expireStartedAt,
distributionType,
quote,
contacts,
@ -663,6 +665,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
contentValues.put(PART_COUNT, retrieved.attachments.size)
contentValues.put(SUBSCRIPTION_ID, retrieved.subscriptionId)
contentValues.put(EXPIRES_IN, retrieved.expiresIn)
contentValues.put(EXPIRE_STARTED, retrieved.expireStartedAt)
contentValues.put(READ, if (retrieved.isExpirationUpdate) 1 else 0)
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified)
contentValues.put(MESSAGE_REQUEST_RESPONSE, retrieved.isMessageRequestResponse)
@ -805,6 +808,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
contentValues.put(DATE_RECEIVED, receivedTimestamp)
contentValues.put(SUBSCRIPTION_ID, message.subscriptionId)
contentValues.put(EXPIRES_IN, message.expiresIn)
contentValues.put(EXPIRE_STARTED, message.expireStartedAt)
contentValues.put(ADDRESS, message.recipient.address.serialize())
contentValues.put(
DELIVERY_RECEIPT_COUNT,

View File

@ -158,7 +158,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
recipientDb.setApprovedMe(targetRecipient, true)
}
}
val expiresIn = getExpirationConfiguration(message.threadID ?: -1)?.durationSeconds ?: 0
val expirationConfig = getExpirationConfiguration(message.threadID ?: -1)
val expiresIn = expirationConfig?.durationSeconds ?: 0
val expireStartedAt = if (expirationConfig?.expirationType == ExpirationType.DELETE_AFTER_SEND) {
message.sentTimestamp!! + (expirationConfig.durationSeconds * 1000L)
} else 0
if (message.isMediaMessage() || attachments.isNotEmpty()) {
val quote: Optional<QuoteModel> = if (quotes != null) Optional.of(quotes) else Optional.absent()
val linkPreviews: Optional<List<LinkPreview>> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! })
@ -170,7 +174,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
pointers,
quote.orNull(),
linkPreviews.orNull()?.firstOrNull(),
expiresIn * 1000L
expiresIn * 1000L,
expireStartedAt
)
mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!, runThreadUpdate)
} else {
@ -178,7 +183,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val signalServiceAttachments = attachments.mapNotNull {
it.toSignalPointer()
}
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, expiresIn * 1000L, group, signalServiceAttachments, quote, linkPreviews)
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, expiresIn * 1000L, expireStartedAt, group, signalServiceAttachments, quote, linkPreviews)
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runIncrement, runThreadUpdate)
}
if (insertResult.isPresent) {
@ -485,7 +490,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val recipient = Recipient.from(context, fromSerialized(groupID), false)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, 0, true, null, listOf(), listOf())
val mmsDB = DatabaseComponent.get(context).mmsDatabase()
val mmsSmsDB = DatabaseComponent.get(context).mmsSmsDatabase()
if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return
@ -736,6 +741,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
sentTimestamp,
-1,
0,
0,
false,
false,
false,
@ -810,6 +816,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
response.sentTimestamp!!,
-1,
0,
0,
false,
false,
true,

View File

@ -90,7 +90,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver {
if (recipient.isGroupRecipient()) {
Log.w("AndroidAutoReplyReceiver", "GroupRecipient, Sending media message");
OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis);
OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis, 0);
try {
DatabaseComponent.get(context).mmsDatabase().insertMessageOutbox(reply, replyThreadId, false, null, true);
} catch (MmsException e) {

View File

@ -83,7 +83,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
long expiresInMillis = config == null ? 0 : config.getDurationSeconds() * 1000L;
switch (replyMethod) {
case GroupMessage: {
OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis);
OutgoingMediaMessage reply = OutgoingMediaMessage.from(message, recipient, Collections.emptyList(), null, null, expiresInMillis, 0);
try {
DatabaseComponent.get(context).mmsDatabase().insertMessageOutbox(reply, threadId, false, null, true);
MessageSender.send(message, address);

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.service;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.session.libsession.messaging.messages.ExpirationConfiguration;
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage;
@ -14,11 +13,11 @@ import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.messages.SignalServiceGroup;
import org.session.libsignal.protos.SignalServiceProtos.Content.ExpirationType;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.mms.MmsException;
@ -69,16 +68,18 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
}
@Override
public void setExpirationTimer(@NotNull ExpirationTimerUpdate message) {
public void setExpirationTimer(@NotNull ExpirationTimerUpdate message, int expiryType) {
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
String senderPublicKey = message.getSender();
long expireStartedAt = ExpirationType.DELETE_AFTER_SEND_VALUE != expiryType
? 0 : message.getSentTimestamp() + (message.getDuration() * 1000L);
// Notify the user
if (senderPublicKey == null || userPublicKey.equals(senderPublicKey)) {
// sender is self or a linked device
insertOutgoingExpirationTimerMessage(message);
insertOutgoingExpirationTimerMessage(message, expireStartedAt);
} else {
insertIncomingExpirationTimerMessage(message);
insertIncomingExpirationTimerMessage(message, expireStartedAt);
}
if (message.getId() != null) {
@ -86,7 +87,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
}
}
private void insertIncomingExpirationTimerMessage(ExpirationTimerUpdate message) {
private void insertIncomingExpirationTimerMessage(ExpirationTimerUpdate message, long expireStartedAt) {
MmsDatabase database = DatabaseComponent.get(context).mmsDatabase();
String senderPublicKey = message.getSender();
@ -111,7 +112,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
}
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(address, sentTimestamp, -1,
duration * 1000L, true,
duration * 1000L, expireStartedAt, true,
false,
false,
Optional.absent(),
@ -128,7 +129,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
}
}
private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) {
private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message, long expireStartedAt) {
MmsDatabase database = DatabaseComponent.get(context).mmsDatabase();
Long sentTimestamp = message.getSentTimestamp();
@ -139,7 +140,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
Recipient recipient = Recipient.from(context, address, false);
try {
OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId);
OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, expireStartedAt, groupId);
database.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp, true);
} catch (MmsException e) {
Log.e("Loki", "Failed to insert expiration update message.");

View File

@ -885,5 +885,6 @@
<string name="activity_expiration_settings_timer">Timer</string>
<string name="activity_expiration_settings_group_footer"><![CDATA[This setting applies to everyone in this conversation.<br/>Only group admins can change this setting.]]></string>
<string name="activity_conversation_outdated_client_banner_text">%s is using an outdated client. Disappearing messages may not work as expected.</string>
<string name="ExpirationSettingsActivity_settings_saved">Settings saved</string>
<string name="ExpirationSettingsActivity_settings_updated">Settings updated</string>
<string name="ExpirationSettingsActivity_settings_not_updated">Settings not updated and please try again</string>
</resources>

View File

@ -26,6 +26,7 @@ public class IncomingMediaMessage {
private final long sentTimeMillis;
private final int subscriptionId;
private final long expiresIn;
private final long expireStartedAt;
private final boolean expirationUpdate;
private final boolean unidentified;
private final boolean messageRequestResponse;
@ -41,6 +42,7 @@ public class IncomingMediaMessage {
long sentTimeMillis,
int subscriptionId,
long expiresIn,
long expireStartedAt,
boolean expirationUpdate,
boolean unidentified,
boolean messageRequestResponse,
@ -58,6 +60,7 @@ public class IncomingMediaMessage {
this.body = body.orNull();
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expireStartedAt = expireStartedAt;
this.expirationUpdate = expirationUpdate;
this.dataExtractionNotification = dataExtractionNotification.orNull();
this.quote = quote.orNull();
@ -75,12 +78,13 @@ public class IncomingMediaMessage {
public static IncomingMediaMessage from(VisibleMessage message,
Address from,
long expiresIn,
long expireStartedAt,
Optional<SignalServiceGroup> group,
List<SignalServiceAttachment> attachments,
Optional<QuoteModel> quote,
Optional<List<LinkPreview>> linkPreviews)
{
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false,
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, expireStartedAt, false,
false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
}
@ -120,6 +124,10 @@ public class IncomingMediaMessage {
return expiresIn;
}
public long getExpireStartedAt() {
return expireStartedAt;
}
public boolean isGroupMessage() {
return groupId != null;
}

View File

@ -11,9 +11,9 @@ public class OutgoingExpirationUpdateMessage extends OutgoingSecureMediaMessage
private final String groupId;
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, String groupId) {
public OutgoingExpirationUpdateMessage(Recipient recipient, long sentTimeMillis, long expiresIn, long expireStartedAt, String groupId) {
super(recipient, "", new LinkedList<Attachment>(), sentTimeMillis,
DistributionTypes.CONVERSATION, expiresIn, null, Collections.emptyList(),
DistributionTypes.CONVERSATION, expiresIn, expireStartedAt, null, Collections.emptyList(),
Collections.emptyList());
this.groupId = groupId;
}

View File

@ -24,6 +24,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
@Nullable final Attachment avatar,
long sentTime,
long expireIn,
long expireStartedAt,
boolean updateMessage,
@Nullable QuoteModel quote,
@NonNull List<Contact> contacts,
@ -32,7 +33,7 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
super(recipient, body,
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
sentTime,
DistributionTypes.CONVERSATION, expireIn, quote, contacts, previews);
DistributionTypes.CONVERSATION, expireIn, expireStartedAt, quote, contacts, previews);
this.groupID = groupId;
this.isUpdateMessage = updateMessage;

View File

@ -26,6 +26,7 @@ public class OutgoingMediaMessage {
private final int distributionType;
private final int subscriptionId;
private final long expiresIn;
private final long expireStartedAt;
private final QuoteModel outgoingQuote;
private final List<NetworkFailure> networkFailures = new LinkedList<>();
@ -35,7 +36,7 @@ public class OutgoingMediaMessage {
public OutgoingMediaMessage(Recipient recipient, String message,
List<Attachment> attachments, long sentTimeMillis,
int subscriptionId, long expiresIn,
int subscriptionId, long expiresIn, long expireStartedAt,
int distributionType,
@Nullable QuoteModel outgoingQuote,
@NonNull List<Contact> contacts,
@ -50,6 +51,7 @@ public class OutgoingMediaMessage {
this.attachments = attachments;
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expireStartedAt = expireStartedAt;
this.outgoingQuote = outgoingQuote;
this.contacts.addAll(contacts);
@ -66,6 +68,7 @@ public class OutgoingMediaMessage {
this.sentTimeMillis = that.sentTimeMillis;
this.subscriptionId = that.subscriptionId;
this.expiresIn = that.expiresIn;
this.expireStartedAt = that.expireStartedAt;
this.outgoingQuote = that.outgoingQuote;
this.identityKeyMismatches.addAll(that.identityKeyMismatches);
@ -79,14 +82,15 @@ public class OutgoingMediaMessage {
List<Attachment> attachments,
@Nullable QuoteModel outgoingQuote,
@Nullable LinkPreview linkPreview,
long expiresInMillis)
long expiresInMillis,
long expireStartedAt)
{
List<LinkPreview> previews = Collections.emptyList();
if (linkPreview != null) {
previews = Collections.singletonList(linkPreview);
}
return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1,
expiresInMillis, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(),
expiresInMillis, expireStartedAt, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(),
previews, Collections.emptyList(), Collections.emptyList());
}
@ -124,6 +128,10 @@ public class OutgoingMediaMessage {
return expiresIn;
}
public long getExpireStartedAt() {
return expireStartedAt;
}
public @Nullable QuoteModel getOutgoingQuote() {
return outgoingQuote;
}

View File

@ -19,11 +19,12 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
long sentTimeMillis,
int distributionType,
long expiresIn,
long expireStartedAt,
@Nullable QuoteModel quote,
@NonNull List<Contact> contacts,
@NonNull List<LinkPreview> previews)
{
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
super(recipient, body, attachments, sentTimeMillis, -1, expiresIn, expireStartedAt, distributionType, quote, contacts, previews, Collections.emptyList(), Collections.emptyList());
}
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {

View File

@ -105,7 +105,7 @@ fun updateExpirationConfigurationIfNeeded(message: Message, proto: SignalService
)
storage.setExpirationConfiguration(remoteConfig)
if (message is ExpirationTimerUpdate) {
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message)
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message, type?.number ?: -1)
}
}
@ -181,7 +181,7 @@ private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimer
} catch (e: Exception) {
Log.e("Loki", "Failed to update expiration configuration.")
}
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message)
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(message, type?.number ?: -1)
}
private fun MessageReceiver.handleDataExtractionNotification(message: DataExtractionNotification) {

View File

@ -36,7 +36,7 @@ class SSKEnvironment(
}
interface MessageExpirationManagerProtocol {
fun setExpirationTimer(message: ExpirationTimerUpdate)
fun setExpirationTimer(message: ExpirationTimerUpdate, expiryType: Int)
fun startAnyExpiration(timestamp: Long, author: String)
}