feat: add cleanup of old expire update in history
we only keep one from each sender
This commit is contained in:
parent
543c80bbe3
commit
19e9e0311e
|
@ -192,10 +192,10 @@
|
|||
"followSetting": "Follow Setting",
|
||||
"followSettingDisabled": "Messages you send will no longer disappear. Are you sure you want to turn off disappearing messages?",
|
||||
"followSettingTimeAndType": "Set your messages to disappear $time$ after they have been $type$?",
|
||||
"youChangedTheTimer": "You have set messages to disappear $time$ after they have been $mode$",
|
||||
"youChangedTheTimerLegacy": "You set the disappearing message timer to $time$",
|
||||
"theyChangedTheTimer": "$name$ has set messages to disappear $time$ after they have been $mode$",
|
||||
"theyChangedTheTimerLegacy": "$name$ set the disappearing message timer to $time$",
|
||||
"youChangedTheTimer": "<b>You</b> have set messages to disappear <b>$time$</b> after they have been <b>$mode$</b>",
|
||||
"youChangedTheTimerLegacy": "<b>You<b> set the disappearing message timer to <b>$time$</b>",
|
||||
"theyChangedTheTimer": "<b>$name$</b> has set messages to disappear <b>$time$</b> after they have been <b>$mode$</b>",
|
||||
"theyChangedTheTimerLegacy": "<b>$name$</b> set the disappearing message timer to <b>$time$</b>",
|
||||
"timerOption_0_seconds": "Off",
|
||||
"timerOption_5_seconds": "5 seconds",
|
||||
"timerOption_10_seconds": "10 seconds",
|
||||
|
@ -233,12 +233,12 @@
|
|||
"disappearingMessagesModeLegacy": "Legacy",
|
||||
"disappearingMessagesModeLegacySubtitle": "Original version of disappearing messages.",
|
||||
"disappearingMessagesDisabled": "Disappearing messages disabled",
|
||||
"disabledDisappearingMessages": "$name$ has turned off disappearing messages.",
|
||||
"youDisabledDisappearingMessages": "You have turned off disappearing messages.",
|
||||
"youDisabledYourDisappearingMessages": "You turned off disappearing messages. Messages you send will no longer disappear.",
|
||||
"youSetYourDisappearingMessages": "You set your messages to disappear $time$ after they have been $type$.",
|
||||
"theyDisabledTheirDisappearingMessages": "$name$ has turned off disappearing messages. Messages they send will no longer disappear.",
|
||||
"theySetTheirDisappearingMessages": "$name$ has set their messages to disappear $time$ after they have been $type$.",
|
||||
"disabledDisappearingMessages": "<b>$name$</b> has turned <b>off</b> disappearing messages.",
|
||||
"youDisabledDisappearingMessages": "<b>You</b> have turned <b>off</b> disappearing messages.",
|
||||
"youDisabledYourDisappearingMessages": "<b>You</b> turned <b>off</b> disappearing messages. Messages you send will no longer disappear.",
|
||||
"youSetYourDisappearingMessages": "<b>You</b> set your messages to disappear <b>$time$</b> after they have been <b>$type$</b>.",
|
||||
"theyDisabledTheirDisappearingMessages": "<b>$name$</b> has turned <b>off</b> disappearing messages. Messages they send will no longer disappear.",
|
||||
"theySetTheirDisappearingMessages": "<b>$name$</b> has set their messages to disappear <b>$time$</b> after they have been <b>$type$</b>.",
|
||||
"timerSetTo": "Disappearing message time set to $time$",
|
||||
"set": "Set",
|
||||
"changeNickname": "Change Nickname",
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
"glob": "7.1.2",
|
||||
"image-type": "^4.1.0",
|
||||
"ip2country": "1.0.1",
|
||||
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.0/libsession_util_nodejs-v0.3.0.tar.gz",
|
||||
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.1/libsession_util_nodejs-v0.3.1.tar.gz",
|
||||
"libsodium-wrappers-sumo": "^0.7.9",
|
||||
"linkify-it": "^4.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -10,7 +10,7 @@ type TextProps = {
|
|||
ellipsisOverflow?: boolean;
|
||||
};
|
||||
|
||||
const StyledDefaultText = styled.div<TextProps>`
|
||||
const StyledDefaultText = styled.div<Omit<TextProps, 'text'>>`
|
||||
transition: var(--default-duration);
|
||||
max-width: ${props => (props.maxWidth ? props.maxWidth : '')};
|
||||
padding: ${props => (props.padding ? props.padding : '')};
|
||||
|
@ -26,6 +26,12 @@ export const Text = (props: TextProps) => {
|
|||
return <StyledDefaultText {...props}>{props.text}</StyledDefaultText>;
|
||||
};
|
||||
|
||||
export const TextWithChildren = (
|
||||
props: Omit<TextProps, 'text'> & { children: React.ReactNode }
|
||||
) => {
|
||||
return <StyledDefaultText {...props}>{props.children}</StyledDefaultText>;
|
||||
};
|
||||
|
||||
type SpacerProps = {
|
||||
size: 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
||||
style?: CSSProperties;
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../state/selectors/selectedConversation';
|
||||
import { ReleasedFeatures } from '../../util/releaseFeature';
|
||||
import { Flex } from '../basic/Flex';
|
||||
import { Text } from '../basic/Text';
|
||||
import { TextWithChildren } from '../basic/Text';
|
||||
import { ExpirableReadableMessage } from './message/message-item/ExpirableReadableMessage';
|
||||
// eslint-disable-next-line import/order
|
||||
import { pick } from 'lodash';
|
||||
|
@ -25,6 +25,7 @@ import { ConversationInteraction } from '../../interactions';
|
|||
import { getConversationController } from '../../session/conversations';
|
||||
import { updateConfirmModal } from '../../state/ducks/modalDialog';
|
||||
import { SessionButtonColor } from '../basic/SessionButton';
|
||||
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
|
||||
|
||||
const FollowSettingButton = styled.button`
|
||||
color: var(--primary-color);
|
||||
|
@ -208,7 +209,9 @@ export const TimerNotification = (props: PropsForExpirationTimer) => {
|
|||
padding="5px 10px"
|
||||
style={{ textAlign: 'center' }}
|
||||
>
|
||||
<Text text={textToRender} subtle={true} />
|
||||
<TextWithChildren subtle={true}>
|
||||
<SessionHtmlRenderer html={textToRender} />
|
||||
</TextWithChildren>
|
||||
<FollowSettingsButton {...props} />
|
||||
</Flex>
|
||||
</ExpirableReadableMessage>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useMessageExpirationPropsById } from '../../../../hooks/useParamSelecto
|
|||
import { useMessageStatus } from '../../../../state/selectors';
|
||||
|
||||
import { getMostRecentMessageId } from '../../../../state/selectors/conversations';
|
||||
import { useSelectedIsGroup } from '../../../../state/selectors/selectedConversation';
|
||||
import { SpacerXS } from '../../../basic/Text';
|
||||
import { SessionIcon, SessionIconType } from '../../../icon';
|
||||
import { ExpireTimer } from '../../ExpireTimer';
|
||||
|
@ -60,7 +61,7 @@ export const MessageStatus = (props: Props) => {
|
|||
}
|
||||
};
|
||||
|
||||
const MessageStatusContainer = styled.div<{ isIncoming: boolean }>`
|
||||
const MessageStatusContainer = styled.div<{ isIncoming: boolean; isGroup: boolean }>`
|
||||
display: inline-block;
|
||||
align-self: ${props => (props.isIncoming ? 'flex-start' : 'flex-end')};
|
||||
flex-direction: ${props =>
|
||||
|
@ -73,6 +74,8 @@ const MessageStatusContainer = styled.div<{ isIncoming: boolean }>`
|
|||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-inline-start: ${props =>
|
||||
props.isGroup || !props.isIncoming ? 'var(--width-avatar-group-msg-list)' : 0};
|
||||
`;
|
||||
|
||||
const StyledStatusText = styled.div`
|
||||
|
@ -144,7 +147,12 @@ function MessageStatusExpireTimer(props: Props) {
|
|||
const MessageStatusSending = ({ dataTestId }: Props) => {
|
||||
// while sending, we do not display the expire timer at all.
|
||||
return (
|
||||
<MessageStatusContainer data-testid={dataTestId} data-testtype="sending" isIncoming={false}>
|
||||
<MessageStatusContainer
|
||||
data-testid={dataTestId}
|
||||
data-testtype="sending"
|
||||
isIncoming={false}
|
||||
isGroup={false}
|
||||
>
|
||||
<TextDetails text={window.i18n('sending')} />
|
||||
<IconNormal rotateDuration={2} iconType="sending" />
|
||||
</MessageStatusContainer>
|
||||
|
@ -171,13 +179,19 @@ function IconForExpiringMessageId({
|
|||
const MessageStatusSent = ({ dataTestId, messageId }: Props) => {
|
||||
const isExpiring = useIsExpiring(messageId);
|
||||
const isMostRecentMessage = useIsMostRecentMessage(messageId);
|
||||
const isGroup = useSelectedIsGroup();
|
||||
|
||||
// we hide a "sent" message status which is not expiring except for the most recent message
|
||||
if (!isExpiring && !isMostRecentMessage) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MessageStatusContainer data-testid={dataTestId} data-testtype="sent" isIncoming={false}>
|
||||
<MessageStatusContainer
|
||||
data-testid={dataTestId}
|
||||
data-testtype="sent"
|
||||
isIncoming={false}
|
||||
isGroup={isGroup}
|
||||
>
|
||||
<TextDetails text={window.i18n('sent')} />
|
||||
<IconForExpiringMessageId messageId={messageId} iconType="circleCheck" />
|
||||
</MessageStatusContainer>
|
||||
|
@ -190,6 +204,7 @@ const MessageStatusRead = ({
|
|||
isIncoming,
|
||||
}: Props & { isIncoming: boolean }) => {
|
||||
const isExpiring = useIsExpiring(messageId);
|
||||
const isGroup = useSelectedIsGroup();
|
||||
|
||||
const isMostRecentMessage = useIsMostRecentMessage(messageId);
|
||||
|
||||
|
@ -199,7 +214,12 @@ const MessageStatusRead = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<MessageStatusContainer data-testid={dataTestId} data-testtype="read" isIncoming={isIncoming}>
|
||||
<MessageStatusContainer
|
||||
data-testid={dataTestId}
|
||||
data-testtype="read"
|
||||
isIncoming={isIncoming}
|
||||
isGroup={isGroup}
|
||||
>
|
||||
<TextDetails text={window.i18n('read')} />
|
||||
<IconForExpiringMessageId messageId={messageId} iconType="doubleCheckCircleFilled" />
|
||||
</MessageStatusContainer>
|
||||
|
@ -211,6 +231,7 @@ const MessageStatusError = ({ dataTestId }: Props) => {
|
|||
ipcRenderer.send('show-debug-log');
|
||||
}, []);
|
||||
// when on error, we do not display the expire timer at all.
|
||||
const isGroup = useSelectedIsGroup();
|
||||
|
||||
return (
|
||||
<MessageStatusContainer
|
||||
|
@ -219,6 +240,7 @@ const MessageStatusError = ({ dataTestId }: Props) => {
|
|||
onClick={showDebugLog}
|
||||
title={window.i18n('sendFailed')}
|
||||
isIncoming={false}
|
||||
isGroup={isGroup}
|
||||
>
|
||||
<TextDetails text={window.i18n('failed')} />
|
||||
<IconDanger iconType="error" />
|
||||
|
|
|
@ -252,6 +252,19 @@ async function saveMessages(arrayOfMessages: Array<MessageAttributes>): Promise<
|
|||
await channels.saveMessages(cleanData(arrayOfMessages));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param conversationId the conversation from which to remove all but the most recent disappear timer update
|
||||
* @param isPrivate if that conversation is private, we keep a expiration timer update for each sender
|
||||
* @returns the array of messageIds removed, or [] if none were removed
|
||||
*/
|
||||
async function cleanUpExpirationTimerUpdateHistory(
|
||||
conversationId: string,
|
||||
isPrivate: boolean
|
||||
): Promise<Array<string>> {
|
||||
return channels.cleanUpExpirationTimerUpdateHistory(conversationId, isPrivate);
|
||||
}
|
||||
|
||||
async function removeMessage(id: string): Promise<void> {
|
||||
const message = await getMessageById(id, true);
|
||||
|
||||
|
@ -800,6 +813,7 @@ export const Data = {
|
|||
saveMessages,
|
||||
removeMessage,
|
||||
removeMessagesByIds,
|
||||
cleanUpExpirationTimerUpdateHistory,
|
||||
getMessageIdsFromServerIds,
|
||||
getMessageById,
|
||||
getMessagesBySenderAndSentAt,
|
||||
|
|
|
@ -41,6 +41,7 @@ const channelsToMake = new Set([
|
|||
'saveMessages',
|
||||
'removeMessage',
|
||||
'removeMessagesByIds',
|
||||
'cleanUpExpirationTimerUpdateHistory',
|
||||
'getUnreadByConversation',
|
||||
'getUnreadDisappearingByConversation',
|
||||
'markAllAsReadByConversationNoExpiration',
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
conversationsChanged,
|
||||
markConversationFullyRead,
|
||||
MessageModelPropsWithoutConvoProps,
|
||||
messagesDeleted,
|
||||
ReduxConversationType,
|
||||
} from '../state/ducks/conversations';
|
||||
|
||||
|
@ -823,7 +824,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
fromSync, // if the update comes from a config or sync message
|
||||
fromCurrentDevice,
|
||||
shouldCommitConvo = true,
|
||||
shouldCommitMessage = true,
|
||||
existingMessage,
|
||||
}: {
|
||||
providedDisappearingMode?: DisappearingMessageConversationModeType;
|
||||
|
@ -833,7 +833,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
fromSync: boolean;
|
||||
fromCurrentDevice: boolean;
|
||||
shouldCommitConvo?: boolean;
|
||||
shouldCommitMessage?: boolean;
|
||||
existingMessage?: MessageModel;
|
||||
}): Promise<boolean> {
|
||||
const isRemoteChange = Boolean((receivedAt || fromSync) && !fromCurrentDevice);
|
||||
|
@ -864,7 +863,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
);
|
||||
|
||||
const isV2DisappearReleased = ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached();
|
||||
|
||||
// when the v2 disappear is released, the changes we make are only for our outgoing messages, not shared with a contact anymore
|
||||
if (isV2DisappearReleased) {
|
||||
if (!this.isPrivate()) {
|
||||
|
@ -873,6 +871,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
expireTimer,
|
||||
});
|
||||
} else if (fromSync || fromCurrentDevice) {
|
||||
if (expirationMode === 'legacy') {
|
||||
// TODO legacy messages support will be removed in a future release
|
||||
return false;
|
||||
}
|
||||
// v2 is live, this is a private chat and a change we made, set the setting to what was given, otherwise discard it
|
||||
this.set({
|
||||
expirationMode,
|
||||
|
@ -960,16 +962,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
message.get('id')
|
||||
),
|
||||
});
|
||||
if (shouldCommitMessage) {
|
||||
await message.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
await message.commit();
|
||||
|
||||
await cleanUpExpireHistoryFromConvo(this.id, this.isPrivate());
|
||||
return true;
|
||||
}
|
||||
if (shouldCommitMessage) {
|
||||
await message.commit();
|
||||
}
|
||||
await message.commit();
|
||||
|
||||
await cleanUpExpireHistoryFromConvo(this.id, this.isPrivate());
|
||||
//
|
||||
// Below is the "sending the update to the conversation" part.
|
||||
// We would have returned if that message sending part was not needed
|
||||
|
@ -1158,18 +1160,18 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
|
|||
const sendReceipt =
|
||||
settingsReadReceiptEnabled && !this.isBlocked() && !this.isIncomingRequest();
|
||||
|
||||
if (sendReceipt) {
|
||||
window?.log?.info(`Sending ${timestamps.length} read receipts.`);
|
||||
// we should probably stack read receipts and send them every 5 seconds for instance per conversation
|
||||
|
||||
const receiptMessage = new ReadReceiptMessage({
|
||||
timestamp: Date.now(),
|
||||
timestamps,
|
||||
});
|
||||
|
||||
const device = new PubKey(this.id);
|
||||
await getMessageQueue().sendToPubKey(device, receiptMessage, SnodeNamespaces.UserMessages);
|
||||
if (!sendReceipt) {
|
||||
return;
|
||||
}
|
||||
window?.log?.info(`Sending ${timestamps.length} read receipts.`);
|
||||
|
||||
const receiptMessage = new ReadReceiptMessage({
|
||||
timestamp: Date.now(),
|
||||
timestamps,
|
||||
});
|
||||
|
||||
const device = new PubKey(this.id);
|
||||
await getMessageQueue().sendToPubKey(device, receiptMessage, SnodeNamespaces.UserMessages);
|
||||
}
|
||||
|
||||
public async setNickname(nickname: string | null, shouldCommit = false) {
|
||||
|
@ -2519,3 +2521,14 @@ export function hasValidIncomingRequestValues({
|
|||
const isActive = activeAt && isFinite(activeAt) && activeAt > 0;
|
||||
return Boolean(isPrivate && !isMe && !isApproved && !isBlocked && isActive && didApproveMe);
|
||||
}
|
||||
|
||||
async function cleanUpExpireHistoryFromConvo(conversationId: string, isPrivate: boolean) {
|
||||
const updateIdsRemoved = await Data.cleanUpExpirationTimerUpdateHistory(
|
||||
conversationId,
|
||||
isPrivate
|
||||
);
|
||||
|
||||
window.inboxStore.dispatch(
|
||||
messagesDeleted(updateIdsRemoved.map(m => ({ conversationKey: conversationId, messageId: m })))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1677,6 +1677,8 @@ function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite
|
|||
|
||||
// Message changes
|
||||
db.prepare(`ALTER TABLE ${MESSAGES_TABLE} ADD COLUMN expirationType TEXT;`).run();
|
||||
db.prepare(`ALTER TABLE ${MESSAGES_TABLE} ADD COLUMN flags INTEGER;`).run();
|
||||
db.prepare(`UPDATE ${MESSAGES_TABLE} SET flags = json_extract(json, '$.flags');`);
|
||||
|
||||
// #endregion
|
||||
|
||||
|
|
|
@ -61,6 +61,8 @@ import {
|
|||
} from '../types/sqlSharedTypes';
|
||||
|
||||
import { KNOWN_BLINDED_KEYS_ITEM, SettingsKey } from '../data/settings-key';
|
||||
import { MessageAttributes } from '../models/messageType';
|
||||
import { SignalService } from '../protobuf';
|
||||
import { Quote } from '../receiver/types';
|
||||
import {
|
||||
getSQLCipherIntegrityCheck,
|
||||
|
@ -780,11 +782,10 @@ function getMessageCount() {
|
|||
return row['count(*)'];
|
||||
}
|
||||
|
||||
function saveMessage(data: any) {
|
||||
function saveMessage(data: MessageAttributes) {
|
||||
const {
|
||||
body,
|
||||
conversationId,
|
||||
// eslint-disable-next-line camelcase
|
||||
expires_at,
|
||||
hasAttachments,
|
||||
hasFileAttachments,
|
||||
|
@ -792,10 +793,8 @@ function saveMessage(data: any) {
|
|||
id,
|
||||
serverId,
|
||||
serverTimestamp,
|
||||
// eslint-disable-next-line camelcase
|
||||
received_at,
|
||||
sent,
|
||||
// eslint-disable-next-line camelcase
|
||||
sent_at,
|
||||
source,
|
||||
type,
|
||||
|
@ -803,6 +802,7 @@ function saveMessage(data: any) {
|
|||
expirationType,
|
||||
expireTimer,
|
||||
expirationStartTimestamp,
|
||||
flags,
|
||||
} = data;
|
||||
|
||||
if (!id) {
|
||||
|
@ -834,6 +834,7 @@ function saveMessage(data: any) {
|
|||
source,
|
||||
type: type || '',
|
||||
unread,
|
||||
flags: flags ?? 0,
|
||||
};
|
||||
|
||||
assertGlobalInstance()
|
||||
|
@ -857,7 +858,8 @@ function saveMessage(data: any) {
|
|||
sent_at,
|
||||
source,
|
||||
type,
|
||||
unread
|
||||
unread,
|
||||
flags
|
||||
) values (
|
||||
$id,
|
||||
$json,
|
||||
|
@ -877,7 +879,8 @@ function saveMessage(data: any) {
|
|||
$sent_at,
|
||||
$source,
|
||||
$type,
|
||||
$unread
|
||||
$unread,
|
||||
$flags
|
||||
);`
|
||||
)
|
||||
.run(payload);
|
||||
|
@ -959,8 +962,8 @@ function cleanSeenMessages() {
|
|||
});
|
||||
}
|
||||
|
||||
function saveMessages(arrayOfMessages: Array<any>) {
|
||||
console.info('saveMessages of length: ', arrayOfMessages.length);
|
||||
function saveMessages(arrayOfMessages: Array<MessageAttributes>) {
|
||||
console.info('saveMessages count: ', arrayOfMessages.length);
|
||||
assertGlobalInstance().transaction(() => {
|
||||
map(arrayOfMessages, saveMessage);
|
||||
})();
|
||||
|
@ -969,8 +972,6 @@ function saveMessages(arrayOfMessages: Array<any>) {
|
|||
function removeMessage(id: string, instance?: BetterSqlite3.Database) {
|
||||
if (!isString(id)) {
|
||||
throw new Error('removeMessage: only takes single message to delete!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assertGlobalInstanceOrInstance(instance)
|
||||
|
@ -1008,6 +1009,48 @@ function removeAllMessagesInConversation(
|
|||
.run({ conversationId });
|
||||
}
|
||||
|
||||
function cleanUpExpirationTimerUpdateHistory(
|
||||
conversationId: string,
|
||||
isPrivate: boolean,
|
||||
db?: BetterSqlite3.Database
|
||||
) {
|
||||
if (isEmpty(conversationId)) {
|
||||
return [];
|
||||
}
|
||||
const rows = assertGlobalInstanceOrInstance(db)
|
||||
.prepare(
|
||||
`SELECT id, source FROM ${MESSAGES_TABLE} WHERE conversationId = $conversationId and flags = ${SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE} ${orderByClause}`
|
||||
)
|
||||
.all({ conversationId });
|
||||
|
||||
if (rows.length <= 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// we want to allow 1 message at most per sender for private chats only
|
||||
const bySender: Record<string, Array<string>> = {};
|
||||
// we keep the order, so the first message of each array should be kept, the other ones discarded
|
||||
rows.forEach(r => {
|
||||
const groupedById = isPrivate ? r.source : conversationId;
|
||||
if (!bySender[groupedById]) {
|
||||
bySender[groupedById] = [];
|
||||
}
|
||||
bySender[groupedById].push(r.id);
|
||||
});
|
||||
|
||||
const allMsgIdsRemoved: Array<string> = [];
|
||||
Object.keys(bySender).forEach(k => {
|
||||
const idsToRemove = bySender[k].slice(1); // we keep the first one
|
||||
if (isEmpty(idsToRemove)) {
|
||||
return;
|
||||
}
|
||||
removeMessagesByIds(idsToRemove, db);
|
||||
allMsgIdsRemoved.push(...idsToRemove);
|
||||
});
|
||||
|
||||
return allMsgIdsRemoved;
|
||||
}
|
||||
|
||||
function getMessageIdsFromServerIds(serverIds: Array<string | number>, conversationId: string) {
|
||||
if (!Array.isArray(serverIds)) {
|
||||
return [];
|
||||
|
@ -2414,6 +2457,7 @@ export const sqlNode = {
|
|||
saveMessages,
|
||||
removeMessage,
|
||||
removeMessagesByIds,
|
||||
cleanUpExpirationTimerUpdateHistory,
|
||||
removeAllMessagesInConversation,
|
||||
getUnreadByConversation,
|
||||
getUnreadDisappearingByConversation,
|
||||
|
|
|
@ -279,7 +279,6 @@ export async function handleNewClosedGroup(
|
|||
const envelopeTimestamp = toNumber(envelope.timestamp);
|
||||
// a type new is sent and received on one to one so do not use envelope.senderIdentity here
|
||||
const sender = envelope.source;
|
||||
|
||||
if (
|
||||
(await sentAtMoreRecentThanWrapper(envelopeTimestamp, 'UserGroupsConfig')) ===
|
||||
'wrapper_more_recent'
|
||||
|
|
|
@ -230,6 +230,7 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise<Inco
|
|||
let changes = false;
|
||||
|
||||
const expireTimer = ourConvo.getExpireTimer();
|
||||
|
||||
const wrapperNoteToSelfExpirySeconds = await UserConfigWrapperActions.getNoteToSelfExpiry();
|
||||
|
||||
if (wrapperNoteToSelfExpirySeconds !== expireTimer) {
|
||||
|
|
|
@ -474,7 +474,6 @@ export async function innerHandleSwarmContentMessage(
|
|||
content,
|
||||
conversationModelForUIUpdate
|
||||
);
|
||||
|
||||
// TODO legacy messages support will be removed in a future release
|
||||
if (expireUpdate?.isDisappearingMessagesV2Released) {
|
||||
await DisappearingMessages.checkHasOutdatedDisappearingMessageClient(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { throttle, uniq } from 'lodash';
|
||||
import { isNumber, throttle, uniq } from 'lodash';
|
||||
import { messagesExpired } from '../../state/ducks/conversations';
|
||||
import { initWallClockListener } from '../../util/wallClockListener';
|
||||
|
||||
|
@ -102,7 +102,7 @@ async function checkExpiringMessages() {
|
|||
}
|
||||
|
||||
const expiresAt = next.getExpiresAt();
|
||||
if (!expiresAt) {
|
||||
if (!expiresAt || !isNumber(expiresAt)) {
|
||||
return;
|
||||
}
|
||||
window.log.info('next message expires', new Date(expiresAt).toISOString());
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// TODO legacy messages support will be removed in a future release
|
||||
import { isNumber } from 'lodash';
|
||||
import { ProtobufUtils, SignalService } from '../../protobuf';
|
||||
import { ReleasedFeatures } from '../../util/releaseFeature';
|
||||
import { DisappearingMessageConversationModeType } from './types';
|
||||
|
@ -26,16 +25,10 @@ export function checkIsLegacyDisappearingDataMessage(
|
|||
}
|
||||
|
||||
function contentHasTimerProp(contentMessage: SignalService.Content) {
|
||||
return (
|
||||
ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationTimer') ||
|
||||
isNumber(contentMessage.expirationTimer)
|
||||
);
|
||||
return ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationTimer');
|
||||
}
|
||||
function contentHasTypeProp(contentMessage: SignalService.Content) {
|
||||
return (
|
||||
ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationType') ||
|
||||
isNumber(contentMessage.expirationType)
|
||||
);
|
||||
return ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationType');
|
||||
}
|
||||
|
||||
/** Use this to check for legacy disappearing messages where the expirationType and expireTimer should be undefined on the ContentMessage */
|
||||
|
@ -44,8 +37,8 @@ export function couldBeLegacyDisappearingMessageContent(
|
|||
): boolean {
|
||||
const couldBe =
|
||||
(contentMessage.expirationType === SignalService.Content.ExpirationType.UNKNOWN ||
|
||||
(ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached() &&
|
||||
!contentHasTypeProp(contentMessage))) &&
|
||||
ReleasedFeatures.isDisappearMessageV2FeatureReleasedCached()) &&
|
||||
!contentHasTypeProp(contentMessage) &&
|
||||
!contentHasTimerProp(contentMessage);
|
||||
|
||||
return couldBe;
|
||||
|
|
|
@ -148,10 +148,7 @@ async function send(
|
|||
const storedAt = batchResult?.[0]?.body?.t;
|
||||
const storedHash = batchResult?.[0]?.body?.hash;
|
||||
|
||||
// If message also has a sync message, save that hash. Otherwise save the hash from the regular message send i.e. only closed groups in this case.
|
||||
if (
|
||||
encryptedAndWrapped.identifier &&
|
||||
(encryptedAndWrapped.isSyncMessage || isDestinationClosedGroup) &&
|
||||
batchResult &&
|
||||
!isEmpty(batchResult) &&
|
||||
batchResult[0].code === 200 &&
|
||||
|
@ -159,39 +156,48 @@ async function send(
|
|||
isString(storedHash) &&
|
||||
isNumber(storedAt)
|
||||
) {
|
||||
// TODO: the expiration is due to be returned by the storage server on "store" soon, we will then be able to use it instead of doing the storedAt + ttl logic below
|
||||
// if we have a hash and a storedAt, mark it as seen so we don't reprocess it on the next retrieve
|
||||
await Data.saveSeenMessageHashes([{ expiresAt: storedAt + ttl, hash: storedHash }]);
|
||||
const foundMessage = await Data.getMessageById(encryptedAndWrapped.identifier);
|
||||
if (foundMessage) {
|
||||
await foundMessage.updateMessageHash(storedHash);
|
||||
const convo = foundMessage.getConversation();
|
||||
const expireTimer = foundMessage.getExpireTimer();
|
||||
const expirationType = foundMessage.getExpirationType();
|
||||
// If message also has a sync message, save that hash. Otherwise save the hash from the regular message send i.e. only closed groups in this case.
|
||||
|
||||
if (
|
||||
convo &&
|
||||
expirationType &&
|
||||
expireTimer > 0 &&
|
||||
// a message has started to disappear
|
||||
foundMessage.getExpirationStartTimestamp()
|
||||
) {
|
||||
const expirationMode = DisappearingMessages.changeToDisappearingConversationMode(
|
||||
convo,
|
||||
expirationType,
|
||||
expireTimer
|
||||
);
|
||||
if (
|
||||
encryptedAndWrapped.identifier &&
|
||||
(encryptedAndWrapped.isSyncMessage || isDestinationClosedGroup)
|
||||
) {
|
||||
const foundMessage = await Data.getMessageById(encryptedAndWrapped.identifier);
|
||||
if (foundMessage) {
|
||||
await foundMessage.updateMessageHash(storedHash);
|
||||
const convo = foundMessage.getConversation();
|
||||
const expireTimer = foundMessage.getExpireTimer();
|
||||
const expirationType = foundMessage.getExpirationType();
|
||||
|
||||
const canBeDeleteAfterRead = convo && !convo.isMe() && convo.isPrivate();
|
||||
|
||||
// TODO legacy messages support will be removed in a future release
|
||||
if (
|
||||
canBeDeleteAfterRead &&
|
||||
(expirationMode === 'legacy' || expirationMode === 'deleteAfterRead')
|
||||
convo &&
|
||||
expirationType &&
|
||||
expireTimer > 0 &&
|
||||
// a message has started to disappear
|
||||
foundMessage.getExpirationStartTimestamp()
|
||||
) {
|
||||
await DisappearingMessages.updateMessageExpiryOnSwarm(foundMessage, 'send()');
|
||||
}
|
||||
}
|
||||
const expirationMode = DisappearingMessages.changeToDisappearingConversationMode(
|
||||
convo,
|
||||
expirationType,
|
||||
expireTimer
|
||||
);
|
||||
|
||||
await foundMessage.commit();
|
||||
const canBeDeleteAfterRead = convo && !convo.isMe() && convo.isPrivate();
|
||||
|
||||
// TODO legacy messages support will be removed in a future release
|
||||
if (
|
||||
canBeDeleteAfterRead &&
|
||||
(expirationMode === 'legacy' || expirationMode === 'deleteAfterRead')
|
||||
) {
|
||||
await DisappearingMessages.updateMessageExpiryOnSwarm(foundMessage, 'send()');
|
||||
}
|
||||
}
|
||||
|
||||
await foundMessage.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ async function insertUserProfileIntoWrapper(convoId: string) {
|
|||
const areBlindedMsgRequestEnabled = !!Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled);
|
||||
|
||||
const expirySeconds = ourConvo.getExpireTimer() || 0;
|
||||
|
||||
window.log.debug(
|
||||
`inserting into userprofile wrapper: username:"${dbName}", priority:${priority} image:${JSON.stringify(
|
||||
{
|
||||
|
|
|
@ -600,7 +600,6 @@ describe('DisappearingMessage', () => {
|
|||
receivedAt: GetNetworkTime.getNowWithNetworkOffset(),
|
||||
fromSync: true,
|
||||
shouldCommitConvo: false,
|
||||
shouldCommitMessage: false,
|
||||
existingMessage: undefined,
|
||||
fromCurrentDevice: false,
|
||||
});
|
||||
|
|
30
yarn.lock
30
yarn.lock
|
@ -1039,7 +1039,15 @@
|
|||
"@types/prop-types" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^17", "@types/react@^17.0.2":
|
||||
"@types/react@*", "@types/react@17.0.2", "@types/react@^17":
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8"
|
||||
integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@^17.0.2":
|
||||
version "17.0.65"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.65.tgz#95f6a2ab61145ffb69129d07982d047f9e0870cd"
|
||||
integrity sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==
|
||||
|
@ -1048,14 +1056,6 @@
|
|||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@17.0.2":
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8"
|
||||
integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/redux-logger@3.0.7":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.7.tgz#163f6f6865c69c21d56f9356dc8d741718ec0db0"
|
||||
|
@ -1817,9 +1817,9 @@ available-typed-arrays@^1.0.5:
|
|||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||
|
||||
axios@^1.3.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.1.tgz#11fbaa11fc35f431193a9564109c88c1f27b585f"
|
||||
integrity sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2"
|
||||
integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
|
@ -4896,9 +4896,9 @@ levn@~0.3.0:
|
|||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.0/libsession_util_nodejs-v0.3.0.tar.gz":
|
||||
version "0.3.0"
|
||||
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.0/libsession_util_nodejs-v0.3.0.tar.gz#83b733c8fdede577651881de239a8fd2843c929f"
|
||||
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.1/libsession_util_nodejs-v0.3.1.tar.gz":
|
||||
version "0.3.1"
|
||||
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.1/libsession_util_nodejs-v0.3.1.tar.gz#d2c94bfaae6e3ef594609abb08cf8be485fa5d39"
|
||||
dependencies:
|
||||
cmake-js "^7.2.1"
|
||||
node-addon-api "^6.1.0"
|
||||
|
|
Loading…
Reference in New Issue