fix: store envelopetimestamp in cache, and make sure we use network one

everywhere
pull/2963/head
Audric Ackermann 1 year ago
parent 93d87d82ae
commit 08c5f76a15

@ -1,19 +1,19 @@
import { compact } from 'lodash';
import { SessionButtonColor } from '../../components/basic/SessionButton';
import { Data } from '../../data/data';
import { ConversationModel } from '../../models/conversation';
import { MessageModel } from '../../models/message';
import { getMessageQueue } from '../../session';
import { deleteSogsMessageByServerIds } from '../../session/apis/open_group_api/sogsv3/sogsV3DeleteMessages';
import { SnodeAPI } from '../../session/apis/snode_api/SNodeAPI';
import { SnodeNamespaces } from '../../session/apis/snode_api/namespaces';
import { ConvoHub } from '../../session/conversations';
import { UnsendMessage } from '../../session/messages/outgoing/controlMessage/UnsendMessage';
import { ed25519Str } from '../../session/onions/onionPath';
import { SnodeAPI } from '../../session/apis/snode_api/SNodeAPI';
import { PubKey } from '../../session/types';
import { ToastUtils, UserUtils } from '../../session/utils';
import { resetSelectedMessageIds } from '../../state/ducks/conversations';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SessionButtonColor } from '../../components/basic/SessionButton';
import { deleteSogsMessageByServerIds } from '../../session/apis/open_group_api/sogsv3/sogsV3DeleteMessages';
import { SnodeNamespaces } from '../../session/apis/snode_api/namespaces';
/**
* Deletes messages for everyone in a 1-1 or everyone in a closed group conversation.
@ -84,7 +84,7 @@ function getUnsendMessagesObjects(messages: Array<MessageModel>) {
}
const unsendParams = {
timestamp,
createAtNetworkTimestamp: timestamp,
author,
};

@ -553,7 +553,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const chatMessageParams: VisibleMessageParams = {
body: '',
// we need to use a new timestamp here, otherwise android&iOS will consider this message as a duplicate and drop the synced reaction
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
reaction,
lokiProfile: UserUtils.getOurProfile(),
};
@ -726,10 +726,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return;
}
const timestamp = Date.now();
const messageRequestResponseParams: MessageRequestResponseParams = {
timestamp,
createAtNetworkTimestamp: GetNetworkTime.now(),
lokiProfile: UserUtils.getOurProfile(),
};
@ -758,7 +756,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
quote: isEmpty(quote) ? undefined : quote,
preview,
attachments,
sent_at: networkTimestamp,
expireTimer,
serverTimestamp: this.isPublic() ? networkTimestamp : undefined,
groupInvitation,
@ -832,7 +829,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// When we add a disappearing messages notification to the conversation, we want it
// to be above the message that initiated that change, hence the subtraction.
const timestamp = (receivedAt || Date.now()) - 1;
const createAtNetworkTimestamp = (receivedAt || GetNetworkTime.now()) - 1;
this.set({ expireTimer });
@ -851,7 +848,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (isOutgoing) {
message = await this.addSingleOutgoingMessage({
...commonAttributes,
sent_at: timestamp,
sent_at: createAtNetworkTimestamp,
});
} else {
message = await this.addSingleIncomingMessage({
@ -860,13 +857,13 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// indicator above it. We set it to 'unread' to trigger that placement.
unread: READ_MESSAGE_STATE.unread,
source,
sent_at: timestamp,
received_at: timestamp,
sent_at: createAtNetworkTimestamp,
received_at: createAtNetworkTimestamp,
});
}
if (this.isActive()) {
this.set('active_at', timestamp);
this.set('active_at', createAtNetworkTimestamp);
}
if (shouldCommit) {
@ -880,7 +877,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const expireUpdate = {
identifier: message.id,
timestamp,
createAtNetworkTimestamp,
expireTimer: expireTimer || (null as number | null),
};
@ -1046,7 +1043,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// we should probably stack read receipts and send them every 5 seconds for instance per conversation
const receiptMessage = new ReadReceiptMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
timestamps,
});
@ -1785,9 +1782,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const destination = this.id as string;
const sentAt = message.get('sent_at');
if (!sentAt) {
throw new Error('sendMessageJob() sent_at must be set.');
if (sentAt) {
throw new Error('sendMessageJob() sent_at is already set.');
}
const networkTimestamp = GetNetworkTime.now();
// we are trying to send a message to someone. Make sure this convo is not hidden
await this.unhideIfNeeded(true);
@ -1797,7 +1795,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const chatMessageParams: VisibleMessageParams = {
body,
identifier: id,
timestamp: sentAt,
createAtNetworkTimestamp: networkTimestamp,
attachments,
expireTimer,
preview: preview ? [preview] : [],
@ -1866,7 +1864,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const groupInvitation = message.get('groupInvitation');
const groupInvitMessage = new GroupInvitationMessage({
identifier: id,
timestamp: sentAt,
createAtNetworkTimestamp: networkTimestamp,
name: groupInvitation.name,
url: groupInvitation.url,
expireTimer: this.get('expireTimer'),
@ -2219,15 +2217,19 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
const typingParams = {
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
isTyping,
typingTimestamp: GetNetworkTime.now(),
};
const typingMessage = new TypingMessage(typingParams);
const device = new PubKey(recipientId);
const pubkey = new PubKey(recipientId);
void getMessageQueue()
.sendToPubKey(device, typingMessage, SnodeNamespaces.Default)
.sendToPubKeyNonDurably({
pubkey,
message: typingMessage,
namespace: SnodeNamespaces.Default,
})
.catch(window?.log?.error);
}

@ -791,7 +791,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
if (conversation.isPublic()) {
const openGroupParams: VisibleMessageParams = {
identifier: this.id,
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
lokiProfile: UserUtils.getOurProfile(),
body,
attachments,
@ -817,7 +817,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const chatParams = {
identifier: this.id,
body,
timestamp: Date.now(), // force a new timestamp to handle user fixed his clock
createAtNetworkTimestamp: GetNetworkTime.now(),
expireTimer: this.get('expireTimer'),
attachments,
preview: preview ? [preview] : [],
@ -997,7 +997,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
if (!this.id) {
throw new Error('A message always needs an id');
}
console.warn('this.attributes', JSON.stringify(this.attributes));
// because the saving to db calls _cleanData which mutates the field for cleaning, we need to save a copy
const id = await Data.saveMessage(cloneDeep(this.attributes));
if (triggerUIUpdate) {

@ -20,6 +20,7 @@ import { perfEnd, perfStart } from '../session/utils/Performance';
import { ReleasedFeatures } from '../util/releaseFeature';
import { Storage } from '../util/storage';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { ClosedGroup, GroupDiff, GroupInfo } from '../session/group/closed-group';
import { ConfigWrapperUser } from '../webworker/workers/browser/libsession_worker_functions';
import { IncomingMessageCache } from './cache';
@ -915,7 +916,7 @@ async function sendLatestKeyPairToUsers(
const keypairsMessage = new ClosedGroupEncryptionPairReplyMessage({
groupId: groupPubKey,
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
encryptedKeyPairs: wrappers,
});

@ -59,6 +59,7 @@ async function handleGroupInviteMessage({
);
return;
}
debugger;
const sigValid = await verifySig({
pubKey: HexString.fromHexStringNoPrefix(inviteMessage.groupSessionId),
signature: inviteMessage.adminSignature,
@ -119,12 +120,13 @@ async function handleGroupInviteMessage({
await UserSync.queueNewJobIfNeeded();
// TODO currently sending auto-accept of invite. needs to be removed once we get the Group message request logic
debugger;
console.warn('currently sending auto accept invite response');
await getMessageQueue().sendToGroupV2({
message: new GroupUpdateInviteResponseMessage({
groupPk: inviteMessage.groupSessionId,
isApproved: true,
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
}),
});

@ -33,7 +33,7 @@ async function getGroupInviteMessage({
groupPk: GroupPubkeyType;
}) {
const sodium = await getSodiumRenderer();
const timestamp = GetNetworkTime.now();
const createAtNetworkTimestamp = GetNetworkTime.now();
if (UserUtils.isUsFromCache(member)) {
throw new Error('getGroupInviteMessage: we cannot invite ourselves');
@ -42,7 +42,7 @@ async function getGroupInviteMessage({
// Note: as the signature is built with the timestamp here, we cannot override the timestamp later on the sending pipeline
const adminSignature = sodium.crypto_sign_detached(
stringToUint8Array(`INVITE${member}${timestamp}`),
stringToUint8Array(`INVITE${member}${createAtNetworkTimestamp}`),
secretKey
);
const memberAuthData = await MetaGroupWrapperActions.makeSwarmSubAccount(groupPk, member);
@ -50,7 +50,7 @@ async function getGroupInviteMessage({
const invite = new GroupUpdateInviteMessage({
groupName,
groupPk,
timestamp,
createAtNetworkTimestamp,
adminSignature,
memberAuthData,
});

@ -473,8 +473,6 @@ async function leaveClosedGroup(groupId: string, fromSyncMessage: boolean) {
await convo.updateGroupAdmins(admins, false);
await convo.commit();
const networkTimestamp = GetNetworkTime.now();
getSwarmPollingInstance().removePubkey(groupId, 'leaveClosedGroup');
if (fromSyncMessage) {
@ -491,7 +489,7 @@ async function leaveClosedGroup(groupId: string, fromSyncMessage: boolean) {
// Send the update to the group
const ourLeavingMessage = new ClosedGroupMemberLeftMessage({
timestamp: networkTimestamp,
createAtNetworkTimestamp: GetNetworkTime.now(),
groupId,
});

@ -6,6 +6,7 @@ import { ECKeyPair } from '../../receiver/keypairs';
import { openConversationWithMessages } from '../../state/ducks/conversations';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { getSwarmPollingInstance } from '../apis/snode_api';
import { GetNetworkTime } from '../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { generateClosedGroupPublicKey, generateCurve25519KeyPairWithoutPrefix } from '../crypto';
import { ClosedGroup, GroupInfo } from '../group/closed-group';
@ -191,6 +192,8 @@ function createInvitePromises(
encryptionKeyPair: ECKeyPair,
existingExpireTimer: number
) {
const createAtNetworkTimestamp = GetNetworkTime.now();
return listOfMembers.map(async m => {
const messageParams: ClosedGroupNewMessageParams = {
groupId: groupPublicKey,
@ -198,7 +201,7 @@ function createInvitePromises(
members: listOfMembers,
admins,
keypair: encryptionKeyPair,
timestamp: Date.now(),
createAtNetworkTimestamp,
expireTimer: existingExpireTimer,
};
const message = new ClosedGroupNewMessage(messageParams);

@ -259,7 +259,7 @@ async function sendNewName(convo: ConversationModel, name: string, messageId: st
// Send the update to the group
const nameChangeMessage = new ClosedGroupNameChangeMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
groupId,
identifier: messageId,
name,
@ -294,7 +294,7 @@ async function sendAddedMembers(
const existingExpireTimer = convo.get('expireTimer') || 0;
// Send the Added Members message to the group (only members already in the group will get it)
const closedGroupControlMessage = new ClosedGroupAddedMembersMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
groupId,
addedMembers,
identifier: messageId,
@ -306,7 +306,7 @@ async function sendAddedMembers(
// Send closed group update messages to any new members individually
const newClosedGroupUpdate = new ClosedGroupNewMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
name: groupName,
groupId,
admins,
@ -352,7 +352,7 @@ async function sendRemovedMembers(
}
// Send the update to the group and generate + distribute a new encryption key pair if needed
const mainClosedGroupControlMessage = new ClosedGroupRemovedMembersMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
groupId,
removedMembers,
identifier: messageId,
@ -414,7 +414,7 @@ async function generateAndSendNewEncryptionKeyPair(
const keypairsMessage = new ClosedGroupEncryptionPairMessage({
groupId: toHex(groupId),
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
encryptedKeyPairs: wrappers,
});

@ -1,22 +1,27 @@
import { v4 as uuid } from 'uuid';
export interface MessageParams {
timestamp: number;
createAtNetworkTimestamp: number;
identifier?: string;
}
export abstract class Message {
public readonly timestamp: number;
/**
* This is the network timestamp when this message was created (and so, potentially signed).
* This must be used as the envelope timestamp, as other devices are going to use it to verify messages.
* There is also the stored_at/effectiveTimestamp which we get back once we sent a message to the recipient's swarm, but that's not included here.
*/
public readonly createAtNetworkTimestamp: number;
public readonly identifier: string;
constructor({ timestamp, identifier }: MessageParams) {
this.timestamp = timestamp;
constructor({ createAtNetworkTimestamp, identifier }: MessageParams) {
this.createAtNetworkTimestamp = createAtNetworkTimestamp;
if (identifier && identifier.length === 0) {
throw new Error('Cannot set empty identifier');
}
if (!timestamp || timestamp <= 0) {
throw new Error('Cannot set undefined timestamp or <=0');
if (!createAtNetworkTimestamp || createAtNetworkTimestamp <= 0) {
throw new Error('Cannot set undefined createAtNetworkTimestamp or <=0');
}
this.identifier = identifier || uuid();
}

@ -1,8 +1,8 @@
import { SignalService } from '../../../../protobuf';
import { MessageParams } from '../Message';
import { ContentMessage } from '..';
import { SignalService } from '../../../../protobuf';
import { signalservice } from '../../../../protobuf/compiled';
import { TTL_DEFAULT } from '../../../constants';
import { MessageParams } from '../Message';
interface CallMessageParams extends MessageParams {
type: SignalService.CallMessage.Type;
@ -20,7 +20,10 @@ export class CallMessage extends ContentMessage {
public readonly uuid: string;
constructor(params: CallMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.type = params.type;
this.sdpMLineIndexes = params.sdpMLineIndexes;
this.sdpMids = params.sdpMids;

@ -1,15 +1,16 @@
import { v4 as uuid } from 'uuid';
import { SignalService } from '../../../../protobuf';
import { MessageParams } from '../Message';
import { ContentMessage } from '..';
import { PubKey } from '../../../types';
import { getMessageQueue } from '../../..';
import { ConvoHub } from '../../../conversations';
import { UserUtils } from '../../../utils';
import { SettingsKey } from '../../../../data/settings-key';
import { SignalService } from '../../../../protobuf';
import { Storage } from '../../../../util/storage';
import { GetNetworkTime } from '../../../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../../../apis/snode_api/namespaces';
import { ConvoHub } from '../../../conversations';
import { PubKey } from '../../../types';
import { UserUtils } from '../../../utils';
import { MessageParams } from '../Message';
interface DataExtractionNotificationMessageParams extends MessageParams {
referencedAttachmentTimestamp: number;
@ -19,7 +20,10 @@ export class DataExtractionNotificationMessage extends ContentMessage {
public readonly referencedAttachmentTimestamp: number;
constructor(params: DataExtractionNotificationMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.referencedAttachmentTimestamp = params.referencedAttachmentTimestamp;
// this does not make any sense
if (!this.referencedAttachmentTimestamp) {
@ -68,7 +72,7 @@ export const sendDataExtractionNotification = async (
const dataExtractionNotificationMessage = new DataExtractionNotificationMessage({
referencedAttachmentTimestamp,
identifier: uuid(),
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
});
const pubkey = PubKey.cast(conversationId);
window.log.info(
@ -76,11 +80,11 @@ export const sendDataExtractionNotification = async (
);
try {
await getMessageQueue().sendToPubKey(
await getMessageQueue().sendToPubKeyNonDurably({
pubkey,
dataExtractionNotificationMessage,
SnodeNamespaces.Default
);
message: dataExtractionNotificationMessage,
namespace: SnodeNamespaces.Default,
});
} catch (e) {
window.log.warn('failed to send data extraction notification', e);
}

@ -16,7 +16,10 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
public readonly expireTimer: number | null;
constructor(params: ExpirationTimerUpdateMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.expireTimer = params.expireTimer;
const { groupId, syncTarget } = params;

@ -16,7 +16,7 @@ export class MessageRequestResponse extends ContentMessage {
constructor(params: MessageRequestResponseParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
} as MessageRequestResponseParams);
const profile = buildProfileForOutgoingMessage(params);

@ -1,7 +1,7 @@
import { ContentMessage } from '..';
import { Constants } from '../../..';
import { SignalService } from '../../../../protobuf';
import { MessageParams } from '../Message';
import { Constants } from '../../..';
import { ContentMessage } from '..';
interface TypingMessageParams extends MessageParams {
isTyping: boolean;
@ -10,12 +10,13 @@ interface TypingMessageParams extends MessageParams {
export class TypingMessage extends ContentMessage {
public readonly isTyping: boolean;
public readonly typingTimestamp?: number;
constructor(params: TypingMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.isTyping = params.isTyping;
this.typingTimestamp = params.typingTimestamp;
}
public ttl(): number {
@ -29,14 +30,13 @@ export class TypingMessage extends ContentMessage {
}
protected typingProto(): SignalService.TypingMessage {
const ACTION_ENUM = SignalService.TypingMessage.Action;
const action = this.isTyping ? ACTION_ENUM.STARTED : ACTION_ENUM.STOPPED;
const finalTimestamp = this.typingTimestamp || Date.now();
const action = this.isTyping
? SignalService.TypingMessage.Action.STARTED
: SignalService.TypingMessage.Action.STOPPED;
const typingMessage = new SignalService.TypingMessage();
typingMessage.action = action;
typingMessage.timestamp = finalTimestamp;
typingMessage.timestamp = this.createAtNetworkTimestamp;
return typingMessage;
}

@ -3,7 +3,6 @@ import { ContentMessage } from '../ContentMessage';
import { MessageParams } from '../Message';
interface UnsendMessageParams extends MessageParams {
timestamp: number;
author: string;
}
@ -11,7 +10,10 @@ export class UnsendMessage extends ContentMessage {
private readonly author: string;
constructor(params: UnsendMessageParams) {
super({ timestamp: params.timestamp, author: params.author } as MessageParams);
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
author: params.author,
} as MessageParams);
this.author = params.author;
}
@ -23,7 +25,7 @@ export class UnsendMessage extends ContentMessage {
public unsendProto(): SignalService.Unsend {
return new SignalService.Unsend({
timestamp: this.timestamp,
timestamp: this.createAtNetworkTimestamp,
author: this.author,
});
}

@ -11,7 +11,7 @@ export class ClosedGroupAddedMembersMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupAddedMembersMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
groupId: params.groupId,
});

@ -10,7 +10,7 @@ export class ClosedGroupEncryptionPairMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupEncryptionPairMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
groupId: params.groupId,
});

@ -12,7 +12,7 @@ export abstract class ClosedGroupMessage extends DataMessage {
constructor(params: ClosedGroupMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});

@ -10,7 +10,7 @@ export class ClosedGroupNameChangeMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupNameChangeMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
groupId: params.groupId,
});

@ -1,7 +1,7 @@
import { SignalService } from '../../../../../protobuf';
import { ClosedGroupMessage, ClosedGroupMessageParams } from './ClosedGroupMessage';
import { fromHexToArray } from '../../../../utils/String';
import { ECKeyPair } from '../../../../../receiver/keypairs';
import { fromHexToArray } from '../../../../utils/String';
import { ClosedGroupMessage, ClosedGroupMessageParams } from './ClosedGroupMessage';
export interface ClosedGroupNewMessageParams extends ClosedGroupMessageParams {
name: string;
@ -20,7 +20,7 @@ export class ClosedGroupNewMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupNewMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
groupId: params.groupId,
});

@ -11,7 +11,7 @@ export class ClosedGroupRemovedMembersMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupRemovedMembersMessageParams) {
super({
timestamp: params.timestamp,
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
groupId: params.groupId,
});

@ -74,7 +74,7 @@ export class GroupUpdateInfoChangeMessage extends GroupUpdateMessage {
const infoChangeMessage = new SignalService.GroupUpdateInfoChangeMessage({
type: this.typeOfChange,
adminSignature: this.sodium.crypto_sign_detached(
stringToUint8Array(`INFO_CHANGE${this.typeOfChange}${this.timestamp}`),
stringToUint8Array(`INFO_CHANGE${this.typeOfChange}${this.createAtNetworkTimestamp}`),
this.secretKey
),
});

@ -84,7 +84,7 @@ export class GroupUpdateMemberChangeMessage extends GroupUpdateMessage {
type: this.typeOfChange,
memberSessionIds: this.memberSessionIds,
adminSignature: this.sodium.crypto_sign_detached(
stringToUint8Array(`MEMBER_CHANGE${this.typeOfChange}${this.timestamp}`),
stringToUint8Array(`MEMBER_CHANGE${this.typeOfChange}${this.createAtNetworkTimestamp}`),
this.secretKey
),
});

@ -8,8 +8,8 @@ interface ReadReceiptMessageParams extends MessageParams {
export class ReadReceiptMessage extends ContentMessage {
public readonly timestamps: Array<number>;
constructor({ timestamp, identifier, timestamps }: ReadReceiptMessageParams) {
super({ timestamp, identifier });
constructor({ createAtNetworkTimestamp, identifier, timestamps }: ReadReceiptMessageParams) {
super({ createAtNetworkTimestamp, identifier });
this.timestamps = timestamps;
}

@ -18,7 +18,7 @@ export class ClosedGroupVisibleMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupVisibleMessageParams) {
super({
timestamp: params.chatMessage.timestamp,
createAtNetworkTimestamp: params.chatMessage.createAtNetworkTimestamp,
identifier: params.identifier ?? params.chatMessage.identifier,
groupId: params.groupId,
});
@ -64,7 +64,7 @@ export class ClosedGroupV2VisibleMessage extends DataMessage {
WithGroupMessageNamespace
) {
super({
timestamp: params.chatMessage.timestamp,
createAtNetworkTimestamp: params.chatMessage.createAtNetworkTimestamp,
identifier: params.identifier ?? params.chatMessage.identifier,
});
this.chatMessage = params.chatMessage;

@ -16,7 +16,10 @@ export class GroupInvitationMessage extends DataMessage {
private readonly expireTimer?: number;
constructor(params: GroupInvitationMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.url = params.url;
this.name = params.name;
this.expireTimer = params.expireTimer;

@ -89,7 +89,10 @@ export class VisibleMessage extends DataMessage {
private readonly syncTarget?: string;
constructor(params: VisibleMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
super({
createAtNetworkTimestamp: params.createAtNetworkTimestamp,
identifier: params.identifier,
});
this.attachments = params.attachments;
this.body = params.body;
this.quote = params.quote;
@ -179,7 +182,10 @@ export class VisibleMessage extends DataMessage {
}
public isEqual(comparator: VisibleMessage): boolean {
return this.identifier === comparator.identifier && this.timestamp === comparator.timestamp;
return (
this.identifier === comparator.identifier &&
this.createAtNetworkTimestamp === comparator.createAtNetworkTimestamp
);
}
}

@ -3,7 +3,7 @@ import { AbortController } from 'abort-controller';
import { MessageSender } from '.';
import { ClosedGroupMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { ClosedGroupNameChangeMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNameChangeMessage';
import { PubKey, RawMessage } from '../types';
import { OutgoingRawMessage, PubKey } from '../types';
import { JobQueue, MessageUtils, UserUtils } from '../utils';
import { PendingMessageCache } from './PendingMessageCache';
@ -30,6 +30,8 @@ import {
SnodeNamespacesUser,
} from '../apis/snode_api/namespaces';
import { CallMessage } from '../messages/outgoing/controlMessage/CallMessage';
import { DataExtractionNotificationMessage } from '../messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { TypingMessage } from '../messages/outgoing/controlMessage/TypingMessage';
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
import { GroupUpdateDeleteMemberContentMessage } from '../messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateDeleteMemberContentMessage';
import { GroupUpdateInfoChangeMessage } from '../messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInfoChangeMessage';
@ -64,7 +66,7 @@ export class MessageQueue {
destinationPubKey: PubKey,
message: ContentMessage,
namespace: SnodeNamespaces,
sentCb?: (message: RawMessage) => Promise<void>,
sentCb?: (message: OutgoingRawMessage) => Promise<void>,
isGroup = false
): Promise<void> {
if ((message as any).syncTarget) {
@ -187,7 +189,7 @@ export class MessageQueue {
}: {
message: ClosedGroupMessageType;
namespace: SnodeNamespacesLegacyGroup;
sentCb?: (message: RawMessage) => Promise<void>;
sentCb?: (message: OutgoingRawMessage) => Promise<void>;
groupPubKey?: PubKey;
}): Promise<void> {
let destinationPubKey: PubKey | undefined = groupPubKey;
@ -213,7 +215,7 @@ export class MessageQueue {
| GroupUpdateInfoChangeMessage
| GroupUpdateDeleteMemberContentMessage
| GroupUpdateMemberLeftMessage;
sentCb?: (message: RawMessage) => Promise<void>;
sentCb?: (message: OutgoingRawMessage) => Promise<void>;
}): Promise<void> {
if (!message.destination) {
throw new Error('Invalid group message passed in sendToGroupV2.');
@ -235,7 +237,7 @@ export class MessageQueue {
}: {
namespace: SnodeNamespacesUser;
message?: SyncMessageType;
sentCb?: (message: RawMessage) => Promise<void>;
sentCb?: (message: OutgoingRawMessage) => Promise<void>;
}): Promise<void> {
if (!message) {
return;
@ -262,6 +264,8 @@ export class MessageQueue {
pubkey: PubKey;
message:
| ClosedGroupNewMessage
| TypingMessage // no point of caching the typing message, they are very short lived
| DataExtractionNotificationMessage
| CallMessage
| ClosedGroupMemberLeftMessage
| GroupUpdateInviteMessage
@ -351,7 +355,7 @@ export class MessageQueue {
destinationPk: PubKey,
message: ContentMessage,
namespace: SnodeNamespaces,
sentCb?: (message: RawMessage) => Promise<void>,
sentCb?: (message: OutgoingRawMessage) => Promise<void>,
isGroup = false
): Promise<void> {
// Don't send to ourselves

@ -3,7 +3,7 @@
import { AbortController } from 'abort-controller';
import ByteBuffer from 'bytebuffer';
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { isEmpty, sample, toNumber } from 'lodash';
import { isEmpty, sample } from 'lodash';
import pRetry from 'p-retry';
import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
@ -40,53 +40,13 @@ import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { ed25519Str } from '../onions/onionPath';
import { PubKey } from '../types';
import { RawMessage } from '../types/RawMessage';
import { OutgoingRawMessage } from '../types/RawMessage';
import { UserUtils } from '../utils';
import { fromUInt8ArrayToBase64 } from '../utils/String';
import { EmptySwarmError } from '../utils/errors';
// ================ SNODE STORE ================
function overwriteOutgoingTimestampWithNetworkTimestamp(message: { plainTextBuffer: Uint8Array }) {
const networkTimestamp = GetNetworkTime.now();
const { plainTextBuffer } = message;
const contentDecoded = SignalService.Content.decode(plainTextBuffer);
const { dataMessage, dataExtractionNotification, typingMessage } = contentDecoded;
if (dataMessage && dataMessage.timestamp && toNumber(dataMessage.timestamp) > 0) {
// for a few message types, we cannot override the timestamp when sending it.
// - for a sync message
// - groupv2InviteMessage, groupUpdateDeleteMemberContentMessage, groupUpdateDeleteMessage as the embedded signature depends on the timestamp inside
if (
dataMessage.syncTarget ||
dataMessage.groupUpdateMessage?.inviteMessage ||
dataMessage.groupUpdateMessage?.infoChangeMessage ||
dataMessage.groupUpdateMessage?.deleteMemberContent ||
dataMessage.groupUpdateMessage?.memberChangeMessage ||
dataMessage.groupUpdateMessage?.deleteMessage
) {
return {
overRiddenTimestampBuffer: plainTextBuffer,
networkTimestamp: toNumber(dataMessage.timestamp),
};
}
dataMessage.timestamp = networkTimestamp;
}
if (
dataExtractionNotification &&
dataExtractionNotification.timestamp &&
toNumber(dataExtractionNotification.timestamp) > 0
) {
dataExtractionNotification.timestamp = networkTimestamp;
}
if (typingMessage && typingMessage.timestamp && toNumber(typingMessage.timestamp) > 0) {
typingMessage.timestamp = networkTimestamp;
}
const overRiddenTimestampBuffer = SignalService.Content.encode(contentDecoded).finish();
return { overRiddenTimestampBuffer, networkTimestamp };
}
function getMinRetryTimeout() {
return 1000;
}
@ -109,7 +69,7 @@ function isSyncMessage(message: ContentMessage) {
* @param attempts The amount of times to attempt sending. Minimum value is 1.
*/
async function send(
message: RawMessage,
message: OutgoingRawMessage,
attempts: number = 3,
retryMinTimeout?: number, // in ms
isASyncMessage?: boolean
@ -131,7 +91,8 @@ async function send(
namespace: message.namespace,
ttl,
identifier: message.identifier,
isSyncMessage: Boolean(isASyncMessage),
isSyncMessage: !!isASyncMessage,
networkTimestamp: message.networkTimestampCreated,
},
]);
@ -332,6 +293,7 @@ type EncryptAndWrapMessage = {
plainTextBuffer: Uint8Array;
destination: string;
namespace: number;
networkTimestamp: number;
} & SharedEncryptAndWrap;
type EncryptAndWrapMessageResults = {
@ -353,15 +315,14 @@ async function encryptForGroupV2(
namespace,
plainTextBuffer,
ttl,
networkTimestamp,
} = params;
const { overRiddenTimestampBuffer, networkTimestamp } =
overwriteOutgoingTimestampWithNetworkTimestamp({ plainTextBuffer });
const envelope = await buildEnvelope(
SignalService.Envelope.Type.CLOSED_GROUP_MESSAGE,
destination,
networkTimestamp,
overRiddenTimestampBuffer
plainTextBuffer
);
const recipient = PubKey.cast(destination);
@ -395,19 +356,18 @@ async function encryptMessageAndWrap(
namespace,
plainTextBuffer,
ttl,
networkTimestamp,
} = params;
if (PubKey.is03Pubkey(destination)) {
return encryptForGroupV2(params);
}
const { overRiddenTimestampBuffer, networkTimestamp } =
overwriteOutgoingTimestampWithNetworkTimestamp({ plainTextBuffer });
const recipient = PubKey.cast(destination);
const { envelopeType, cipherText } = await MessageEncrypter.encrypt(
recipient,
overRiddenTimestampBuffer,
plainTextBuffer,
encryptionBasedOnConversation(recipient)
);

@ -3,7 +3,7 @@ import { Data } from '../../data/data';
import { SignalService } from '../../protobuf';
import { PnServer } from '../apis/push_notification_api';
import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { RawMessage } from '../types';
import { OutgoingRawMessage } from '../types';
import { UserUtils } from '../utils';
async function handlePublicMessageSentSuccess(
@ -41,7 +41,7 @@ async function handlePublicMessageSentSuccess(
}
async function handleMessageSentSuccess(
sentMessage: RawMessage,
sentMessage: OutgoingRawMessage,
effectiveTimestamp: number,
wrappedEnvelope?: Uint8Array
) {
@ -135,7 +135,7 @@ async function handleMessageSentSuccess(
}
async function handleMessageSentFailure(
sentMessage: RawMessage | OpenGroupVisibleMessage,
sentMessage: OutgoingRawMessage | OpenGroupVisibleMessage,
error: any
) {
const fetchedMessage = await fetchHandleMessageSentData(sentMessage.identifier);

@ -1,10 +1,11 @@
import _ from 'lodash';
import { from_hex, to_hex } from 'libsodium-wrappers-sumo';
import _, { compact, isNumber } from 'lodash';
import { Data } from '../../data/data';
import { Storage } from '../../util/storage';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { ContentMessage } from '../messages/outgoing';
import { PubKey } from '../types';
import { PartialRawMessage, RawMessage } from '../types/RawMessage';
import { OutgoingRawMessage, StoredRawMessage } from '../types/RawMessage';
import { MessageUtils } from '../utils';
// This is an abstraction for storing pending messages.
@ -15,18 +16,18 @@ import { MessageUtils } from '../utils';
// memory and sync its state with the database on modification (add or remove).
export class PendingMessageCache {
public callbacks: Map<string, (message: RawMessage) => Promise<void>> = new Map();
public callbacks: Map<string, (message: OutgoingRawMessage) => Promise<void>> = new Map();
protected loadPromise: Promise<void> | undefined;
protected cache: Array<RawMessage> = [];
protected cache: Array<OutgoingRawMessage> = [];
public async getAllPending(): Promise<Array<RawMessage>> {
public async getAllPending(): Promise<Array<OutgoingRawMessage>> {
await this.loadFromDBIfNeeded();
// Get all pending from cache
return [...this.cache];
}
public async getForDevice(device: PubKey): Promise<Array<RawMessage>> {
public async getForDevice(device: PubKey): Promise<Array<OutgoingRawMessage>> {
const pending = await this.getAllPending();
return pending.filter(m => m.device === device.key);
}
@ -46,7 +47,7 @@ export class PendingMessageCache {
namespace: SnodeNamespaces,
sentCb?: (message: any) => Promise<void>,
isGroup = false
): Promise<RawMessage> {
): Promise<OutgoingRawMessage> {
await this.loadFromDBIfNeeded();
const rawMessage = await MessageUtils.toRawMessage(
destinationPubKey,
@ -69,7 +70,7 @@ export class PendingMessageCache {
return rawMessage;
}
public async remove(message: RawMessage): Promise<Array<RawMessage> | undefined> {
public async remove(message: OutgoingRawMessage): Promise<Array<OutgoingRawMessage> | undefined> {
await this.loadFromDBIfNeeded();
// Should only be called after message is processed
@ -89,7 +90,7 @@ export class PendingMessageCache {
return updatedCache;
}
public find(message: RawMessage): RawMessage | undefined {
public find(message: OutgoingRawMessage): OutgoingRawMessage | undefined {
// Find a message in the cache
return this.cache.find(m => m.device === message.device && m.identifier === message.identifier);
}
@ -114,33 +115,62 @@ export class PendingMessageCache {
this.cache = messages;
}
protected async getFromStorage(): Promise<Array<RawMessage>> {
protected async getFromStorage(): Promise<Array<OutgoingRawMessage>> {
const data = await Data.getItemById('pendingMessages');
if (!data || !data.value) {
return [];
}
const barePending = JSON.parse(String(data.value)) as Array<PartialRawMessage>;
// Rebuild plainTextBuffer
return barePending.map((message: PartialRawMessage) => {
return {
...message,
plainTextBuffer: new Uint8Array(message.plainTextBuffer),
} as RawMessage;
});
try {
// let's do some cleanup, read what we have in DB, remove what is invalid, write to DB, and return filtered data.
// this is because we've added some mandatory fields recently, and the current stored messages won't have them.
const barePending = JSON.parse(String(data.value)) as Array<StoredRawMessage>;
const filtered = compact(
barePending.map((message: StoredRawMessage) => {
try {
// let's skip outgoing messages which have no networkTimestamp associated with them, as we need one to send a message (mapped to the envelope one)
if (
!message.networkTimestampCreated ||
!isNumber(message.networkTimestampCreated) ||
message.networkTimestampCreated <= 0
) {
throw new Error('networkTimestampCreated is emptyo <=0');
}
const plainTextBuffer = from_hex(message.plainTextBufferHex); // if a plaintextBufferHex is unset or not hex, this throws and we remove that message entirely
return {
...message,
plainTextBuffer,
} as OutgoingRawMessage;
} catch (e) {
window.log.warn('failed to decode from message cache:', e.message);
return null;
}
// let's also remove that logic with the plaintextbuffer stored as array of numbers, and use base64 strings instead
})
);
await this.saveToDBWithData(filtered);
return filtered;
} catch (e) {
window.log.warn('getFromStorage failed with', e.message);
return [];
}
}
protected async saveToDB() {
// For each plainTextBuffer in cache, save in as a simple Array<number> to avoid
// Node issues with JSON stringifying Buffer without strict typing
const encodedCache = [...this.cache].map(item => {
const plainTextBuffer = Array.from(item.plainTextBuffer);
return { ...item, plainTextBuffer };
private async saveToDBWithData(msg: Array<OutgoingRawMessage>) {
// For each plainTextBuffer in cache, save it as hex (because Uint8Array are not serializable as is)
const encodedCache = msg.map(item => {
return { ...item, plainTextBufferHex: to_hex(item.plainTextBuffer) };
});
const encodedPendingMessages = JSON.stringify(encodedCache) || '[]';
await Storage.put('pendingMessages', encodedPendingMessages);
}
protected async saveToDB() {
await this.saveToDBWithData(this.cache);
}
}

@ -1,20 +1,21 @@
import { SignalService } from '../../protobuf';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
export type RawMessage = {
export type OutgoingRawMessage = {
identifier: string;
plainTextBuffer: Uint8Array;
device: string;
ttl: number;
networkTimestampCreated: number;
encryption: SignalService.Envelope.Type;
namespace: SnodeNamespaces;
};
// For building RawMessages from JSON
export interface PartialRawMessage {
identifier: string;
plainTextBuffer: any;
device: string;
ttl: number;
encryption: number;
}
export type StoredRawMessage = Pick<
OutgoingRawMessage,
'identifier' | 'device' | 'ttl' | 'networkTimestampCreated'
> & {
plainTextBufferHex: string;
encryption: number; // read it as number, we need to check that it is indeed a valid encryption once loaded
namespace: number; // read it as number, we need to check that it is indeed a valid namespace once loaded
};

@ -1,13 +1,13 @@
import { RawMessage } from '../types/RawMessage';
import { OutgoingRawMessage } from '../types/RawMessage';
import { PubKey } from '../types';
import { ClosedGroupMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
import { ClosedGroupEncryptionPairReplyMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
import { ContentMessage } from '../messages/outgoing';
import { ExpirationTimerUpdateMessage } from '../messages/outgoing/controlMessage/ExpirationTimerUpdateMessage';
import { SignalService } from '../../protobuf';
import { SnodeNamespaces } from '../apis/snode_api/namespaces';
import { ContentMessage } from '../messages/outgoing';
import { ExpirationTimerUpdateMessage } from '../messages/outgoing/controlMessage/ExpirationTimerUpdateMessage';
import { ClosedGroupEncryptionPairReplyMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
import { ClosedGroupMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { ClosedGroupNewMessage } from '../messages/outgoing/controlMessage/group/ClosedGroupNewMessage';
import { PubKey } from '../types';
function getEncryptionTypeFromMessageType(
message: ContentMessage,
@ -38,19 +38,20 @@ export async function toRawMessage(
message: ContentMessage,
namespace: SnodeNamespaces,
isGroup = false
): Promise<RawMessage> {
): Promise<OutgoingRawMessage> {
const ttl = message.ttl();
const plainTextBuffer = message.plainTextBuffer();
const encryption = getEncryptionTypeFromMessageType(message, isGroup);
const rawMessage: RawMessage = {
const rawMessage: OutgoingRawMessage = {
identifier: message.identifier,
plainTextBuffer,
device: destinationPubKey.key,
ttl,
encryption,
namespace,
networkTimestampCreated: message.createAtNetworkTimestamp,
};
return rawMessage;

@ -412,7 +412,7 @@ async function createOfferAndSendIt(recipient: string) {
);
const offerMessage = new CallMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
type: SignalService.CallMessage.Type.OFFER,
sdps: [overridenSdps],
uuid: currentCallUUID,
@ -499,7 +499,7 @@ export async function USER_callRecipient(recipient: string) {
peerConnection = createOrGetPeerConnection(recipient);
// send a pre offer just to wake up the device on the remote side
const preOfferMsg = new CallMessage({
timestamp: now,
createAtNetworkTimestamp: GetNetworkTime.now(),
type: SignalService.CallMessage.Type.PRE_OFFER,
uuid: currentCallUUID,
});
@ -572,7 +572,7 @@ const iceSenderDebouncer = _.debounce(async (recipient: string) => {
return;
}
const callIceCandicates = new CallMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
type: SignalService.CallMessage.Type.ICE_CANDIDATES,
sdpMLineIndexes: validCandidates.map(c => c.sdpMLineIndex),
sdpMids: validCandidates.map(c => c.sdpMid),
@ -881,7 +881,7 @@ export async function rejectCallAlreadyAnotherCall(fromSender: string, forcedUUI
rejectedCallUUIDS.add(forcedUUID);
const rejectCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
uuid: forcedUUID,
});
await sendCallMessageAndSync(rejectCallMessage, fromSender);
@ -904,7 +904,7 @@ export async function USER_rejectIncomingCallRequest(fromSender: string) {
rejectedCallUUIDS.add(aboutCallUUID);
const endCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
uuid: aboutCallUUID,
});
// sync the reject event so our other devices remove the popup too
@ -946,7 +946,7 @@ export async function USER_hangup(fromSender: string) {
rejectedCallUUIDS.add(currentCallUUID);
const endCallMessage = new CallMessage({
type: SignalService.CallMessage.Type.END_CALL,
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
uuid: currentCallUUID,
});
void getMessageQueue().sendToPubKeyNonDurably({
@ -1020,7 +1020,7 @@ async function buildAnswerAndSendIt(sender: string) {
}
const answerSdp = answer.sdp;
const callAnswerMessage = new CallMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: GetNetworkTime.now(),
type: SignalService.CallMessage.Type.ANSWER,
sdps: [answerSdp],
uuid: currentCallUUID,

@ -43,7 +43,7 @@ export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = fal
const buildSyncVisibleMessage = (
identifier: string,
dataMessage: SignalService.DataMessage,
timestamp: number,
createAtNetworkTimestamp: number,
syncTarget: string
) => {
const body = dataMessage.body || undefined;
@ -74,7 +74,7 @@ const buildSyncVisibleMessage = (
return new VisibleMessage({
identifier,
timestamp,
createAtNetworkTimestamp,
attachments,
body,
quote,
@ -87,14 +87,14 @@ const buildSyncVisibleMessage = (
const buildSyncExpireTimerMessage = (
identifier: string,
dataMessage: SignalService.DataMessage,
timestamp: number,
createAtNetworkTimestamp: number,
syncTarget: string
) => {
const expireTimer = dataMessage.expireTimer;
return new ExpirationTimerUpdateMessage({
identifier,
timestamp,
createAtNetworkTimestamp,
expireTimer,
syncTarget,
});

@ -473,16 +473,16 @@ async function handleRemoveMembers({
}
await MetaGroupWrapperActions.memberEraseAndRekey(groupPk, removed);
const timestamp = GetNetworkTime.now();
const createAtNetworkTimestamp = GetNetworkTime.now();
await Promise.all(
removed.map(async m => {
const adminSignature = await SnodeGroupSignature.signDataWithAdminSecret(
`DELETE${m}${timestamp}`,
`DELETE${m}${createAtNetworkTimestamp}`,
{ secretKey }
);
const deleteMessage = new GroupUpdateDeleteMessage({
groupPk,
timestamp,
createAtNetworkTimestamp,
adminSignature: from_base64(adminSignature.signature, base64_variants.ORIGINAL),
});
console.warn(
@ -600,13 +600,13 @@ async function handleMemberChangeFromUIOrNot({
const sodium = await getSodiumRenderer();
const allAdded = [...withHistory, ...withoutHistory]; // those are already enforced to be unique (and without intersection) in `validateMemberChange()`
const timestamp = Date.now();
const createAtNetworkTimestamp = GetNetworkTime.now();
if (fromCurrentDevice && allAdded.length) {
const msg = await ClosedGroup.addUpdateMessage(
convo,
{ joiningMembers: allAdded },
us,
timestamp
createAtNetworkTimestamp
);
await getMessageQueue().sendToGroupV2({
message: new GroupUpdateMemberChangeMessage({
@ -614,7 +614,7 @@ async function handleMemberChangeFromUIOrNot({
groupPk,
typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.ADDED,
identifier: msg.id,
timestamp,
createAtNetworkTimestamp,
secretKey: group.secretKey,
sodium,
}),
@ -625,7 +625,7 @@ async function handleMemberChangeFromUIOrNot({
convo,
{ kickedMembers: removed },
us,
timestamp
createAtNetworkTimestamp
);
await getMessageQueue().sendToGroupV2({
message: new GroupUpdateMemberChangeMessage({
@ -633,7 +633,7 @@ async function handleMemberChangeFromUIOrNot({
groupPk,
typeOfChange: SignalService.GroupUpdateMemberChangeMessage.Type.REMOVED,
identifier: msg.id,
timestamp: GetNetworkTime.now(),
createAtNetworkTimestamp,
secretKey: group.secretKey,
sodium,
}),
@ -641,7 +641,7 @@ async function handleMemberChangeFromUIOrNot({
}
convo.set({
active_at: timestamp,
active_at: createAtNetworkTimestamp,
});
await convo.commit();
}
@ -686,17 +686,22 @@ async function handleNameChangeFromUIOrNot({
await UserSync.queueNewJobIfNeeded();
const timestamp = Date.now();
const createAtNetworkTimestamp = GetNetworkTime.now();
if (fromCurrentDevice) {
const msg = await ClosedGroup.addUpdateMessage(convo, { newName }, us, timestamp);
const msg = await ClosedGroup.addUpdateMessage(
convo,
{ newName },
us,
createAtNetworkTimestamp
);
await getMessageQueue().sendToGroupV2({
message: new GroupUpdateInfoChangeMessage({
groupPk,
typeOfChange: SignalService.GroupUpdateInfoChangeMessage.Type.NAME,
updatedName: newName,
identifier: msg.id,
timestamp: Date.now(),
createAtNetworkTimestamp,
secretKey: group.secretKey,
sodium: await getSodiumRenderer(),
}),
@ -704,7 +709,7 @@ async function handleNameChangeFromUIOrNot({
}
convo.set({
active_at: timestamp,
active_at: createAtNetworkTimestamp,
});
await convo.commit();
}

@ -1,5 +1,5 @@
import { TextEncoder } from 'util';
import { expect } from 'chai';
import { TextEncoder } from 'util';
import { toNumber } from 'lodash';
import { SignalService } from '../../../../protobuf';
@ -14,7 +14,7 @@ import {
describe('VisibleMessage', () => {
it('can create empty message with just a timestamp', () => {
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
const plainText = message.plainTextBuffer();
const decoded = SignalService.Content.decode(plainText);
@ -24,7 +24,7 @@ describe('VisibleMessage', () => {
it('can create message with a body', () => {
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
body: 'body',
});
const plainText = message.plainTextBuffer();
@ -34,7 +34,7 @@ describe('VisibleMessage', () => {
it('can create message with a expire timer', () => {
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
expireTimer: 3600,
});
const plainText = message.plainTextBuffer();
@ -51,7 +51,7 @@ describe('VisibleMessage', () => {
profileKey,
};
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile,
});
const plainText = message.plainTextBuffer();
@ -70,7 +70,7 @@ describe('VisibleMessage', () => {
it('can create message with a quote without attachments', () => {
const quote: Quote = { id: 1234, author: 'author', text: 'text' };
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
quote,
});
const plainText = message.plainTextBuffer();
@ -87,7 +87,7 @@ describe('VisibleMessage', () => {
previews.push(preview);
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
preview: previews,
});
const plainText = message.plainTextBuffer();
@ -109,7 +109,7 @@ describe('VisibleMessage', () => {
attachments.push(attachment);
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
attachments,
});
const plainText = message.plainTextBuffer();
@ -124,14 +124,14 @@ describe('VisibleMessage', () => {
it('correct ttl', () => {
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TTL_MAX);
});
it('has an identifier', () => {
const message = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
expect(message.identifier).to.not.equal(undefined, 'identifier cannot be undefined');

@ -7,13 +7,13 @@ import { GroupInvitationMessage } from '../../../../session/messages/outgoing/vi
describe('GroupInvitationMessage', () => {
let message: GroupInvitationMessage;
const timestamp = Date.now();
const createAtNetworkTimestamp = Date.now();
const url = 'http://localhost';
const name = 'test';
beforeEach(() => {
message = new GroupInvitationMessage({
timestamp,
createAtNetworkTimestamp,
url,
name,
});

@ -9,7 +9,7 @@ describe('MessageRequestResponse', () => {
let message: MessageRequestResponse | undefined;
it('correct ttl', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TTL_MAX);
@ -17,7 +17,7 @@ describe('MessageRequestResponse', () => {
it('has an identifier', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
@ -27,7 +27,7 @@ describe('MessageRequestResponse', () => {
it('has an identifier matching if given', () => {
const identifier = v4();
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
identifier,
});
@ -36,7 +36,7 @@ describe('MessageRequestResponse', () => {
it('isApproved is always true', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
const plainText = message.plainTextBuffer();
const decoded = SignalService.Content.decode(plainText);
@ -47,7 +47,7 @@ describe('MessageRequestResponse', () => {
it('can create response without lokiProfile', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
const plainText = message.plainTextBuffer();
const decoded = SignalService.Content.decode(plainText);
@ -58,7 +58,7 @@ describe('MessageRequestResponse', () => {
it('can create response with display name only', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile: { displayName: 'Jane', profileKey: null },
});
const plainText = message.plainTextBuffer();
@ -71,7 +71,7 @@ describe('MessageRequestResponse', () => {
it('empty profileKey does not get included', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile: { displayName: 'Jane', profileKey: new Uint8Array(0) },
});
const plainText = message.plainTextBuffer();
@ -85,7 +85,7 @@ describe('MessageRequestResponse', () => {
it('can create response with display name and profileKey and profileImage', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile: {
displayName: 'Jane',
profileKey: new Uint8Array([1, 2, 3, 4, 5, 6]),
@ -117,7 +117,7 @@ describe('MessageRequestResponse', () => {
it('profileKey not included if profileUrl not set', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile: { displayName: 'Jane', profileKey: new Uint8Array([1, 2, 3, 4, 5, 6]) },
});
const plainText = message.plainTextBuffer();
@ -135,7 +135,7 @@ describe('MessageRequestResponse', () => {
it('url not included if profileKey not set', () => {
message = new MessageRequestResponse({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
lokiProfile: {
displayName: 'Jane',
profileKey: null,

@ -1,6 +1,6 @@
import { expect } from 'chai';
import { beforeEach } from 'mocha';
import { toNumber } from 'lodash';
import { beforeEach } from 'mocha';
import { SignalService } from '../../../../protobuf';
import { Constants } from '../../../../session';
@ -12,8 +12,8 @@ describe('ReceiptMessage', () => {
beforeEach(() => {
timestamps = [987654321, 123456789];
const timestamp = Date.now();
readMessage = new ReadReceiptMessage({ timestamp, timestamps });
const createAtNetworkTimestamp = Date.now();
readMessage = new ReadReceiptMessage({ createAtNetworkTimestamp, timestamps });
});
it('content of a read receipt is correct', () => {

@ -1,7 +1,7 @@
import { expect } from 'chai';
import Long from 'long';
import { toNumber } from 'lodash';
import Long from 'long';
import { SignalService } from '../../../../protobuf';
import { Constants } from '../../../../session';
import { TypingMessage } from '../../../../session/messages/outgoing/controlMessage/TypingMessage';
@ -9,7 +9,7 @@ import { TypingMessage } from '../../../../session/messages/outgoing/controlMess
describe('TypingMessage', () => {
it('has Action.STARTED if isTyping = true', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: true,
});
const plainText = message.plainTextBuffer();
@ -22,7 +22,7 @@ describe('TypingMessage', () => {
it('has Action.STOPPED if isTyping = false', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: false,
});
const plainText = message.plainTextBuffer();
@ -35,7 +35,7 @@ describe('TypingMessage', () => {
it('has typingTimestamp set if value passed', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: true,
typingTimestamp: 111111111,
});
@ -47,7 +47,7 @@ describe('TypingMessage', () => {
it('has typingTimestamp set with Date.now() if value not passed', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: true,
});
const plainText = message.plainTextBuffer();
@ -61,7 +61,7 @@ describe('TypingMessage', () => {
it('correct ttl', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: true,
});
expect(message.ttl()).to.equal(Constants.TTL_DEFAULT.TYPING_MESSAGE);
@ -69,7 +69,7 @@ describe('TypingMessage', () => {
it('has an identifier', () => {
const message = new TypingMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
isTyping: true,
});
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');

@ -1,12 +1,12 @@
import { expect } from 'chai';
import { SignalService } from '../../../../../protobuf';
import { TestUtils } from '../../../../test-utils';
import { StringUtils } from '../../../../../session/utils';
import { PubKey } from '../../../../../session/types';
import { Constants } from '../../../../../session';
import { ClosedGroupVisibleMessage } from '../../../../../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import { VisibleMessage } from '../../../../../session/messages/outgoing/visibleMessage/VisibleMessage';
import { PubKey } from '../../../../../session/types';
import { StringUtils } from '../../../../../session/utils';
import { TestUtils } from '../../../../test-utils';
describe('ClosedGroupVisibleMessage', () => {
let groupId: string;
@ -15,7 +15,7 @@ describe('ClosedGroupVisibleMessage', () => {
});
it('can create empty message with timestamp, groupId and chatMessage', () => {
const chatMessage = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
body: 'body',
});
const message = new ClosedGroupVisibleMessage({
@ -37,12 +37,12 @@ describe('ClosedGroupVisibleMessage', () => {
expect(decoded.dataMessage).to.have.deep.property('body', 'body');
// we use the timestamp of the chatMessage as parent timestamp
expect(message).to.have.property('timestamp').to.be.equal(chatMessage.timestamp);
expect(message).to.have.property('timestamp').to.be.equal(chatMessage.createAtNetworkTimestamp);
});
it('correct ttl', () => {
const chatMessage = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
const message = new ClosedGroupVisibleMessage({
groupId,
@ -53,7 +53,7 @@ describe('ClosedGroupVisibleMessage', () => {
it('has an identifier', () => {
const chatMessage = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
const message = new ClosedGroupVisibleMessage({
groupId,
@ -65,7 +65,7 @@ describe('ClosedGroupVisibleMessage', () => {
it('should use the identifier passed into it over the one set in chatMessage', () => {
const chatMessage = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
body: 'body',
identifier: 'chatMessage',
});
@ -79,7 +79,7 @@ describe('ClosedGroupVisibleMessage', () => {
it('should use the identifier of the chatMessage if one is not specified on the closed group message', () => {
const chatMessage = new VisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
body: 'body',
identifier: 'chatMessage',
});

@ -18,7 +18,7 @@ import { ContentMessage } from '../../../../session/messages/outgoing';
import { ClosedGroupMessage } from '../../../../session/messages/outgoing/controlMessage/group/ClosedGroupMessage';
import { MessageSender } from '../../../../session/sending';
import { MessageQueue } from '../../../../session/sending/MessageQueue';
import { PubKey, RawMessage } from '../../../../session/types';
import { OutgoingRawMessage, PubKey } from '../../../../session/types';
import { PromiseUtils, UserUtils } from '../../../../session/utils';
import { TestUtils } from '../../../test-utils';
import { PendingMessageCacheStub } from '../../../test-utils/stubs';
@ -46,7 +46,7 @@ describe('MessageQueue', () => {
// Message Sender Stubs
let sendStub: sinon.SinonStub<
[RawMessage, (number | undefined)?, (number | undefined)?, (boolean | undefined)?]
[OutgoingRawMessage, (number | undefined)?, (number | undefined)?, (boolean | undefined)?]
>;
beforeEach(() => {

@ -15,7 +15,7 @@ import { MessageEncrypter } from '../../../../session/crypto';
import { OnionSending } from '../../../../session/onions/onionSend';
import { OnionV4 } from '../../../../session/onions/onionv4';
import { MessageSender } from '../../../../session/sending';
import { PubKey, RawMessage } from '../../../../session/types';
import { OutgoingRawMessage, PubKey } from '../../../../session/types';
import { MessageUtils, UserUtils } from '../../../../session/utils';
import { fromBase64ToArrayBuffer } from '../../../../session/utils/String';
import { TestUtils } from '../../../test-utils';
@ -57,7 +57,7 @@ describe('MessageSender', () => {
});
describe('retry', () => {
let rawMessage: RawMessage;
let rawMessage: OutgoingRawMessage;
beforeEach(async () => {
rawMessage = await MessageUtils.toRawMessage(

@ -110,7 +110,7 @@ describe('Message Utils', () => {
const member = TestUtils.generateFakePubKey().key;
const msg = new ClosedGroupNewMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
name: 'df',
members: [member],
admins: [member],
@ -126,7 +126,7 @@ describe('Message Utils', () => {
const device = TestUtils.generateFakePubKey();
const msg = new ClosedGroupNameChangeMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
name: 'df',
groupId: TestUtils.generateFakePubKey().key,
});
@ -138,7 +138,7 @@ describe('Message Utils', () => {
const device = TestUtils.generateFakePubKey();
const msg = new ClosedGroupAddedMembersMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
addedMembers: [TestUtils.generateFakePubKey().key],
groupId: TestUtils.generateFakePubKey().key,
});
@ -150,7 +150,7 @@ describe('Message Utils', () => {
const device = TestUtils.generateFakePubKey();
const msg = new ClosedGroupRemovedMembersMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
removedMembers: [TestUtils.generateFakePubKey().key],
groupId: TestUtils.generateFakePubKey().key,
});
@ -170,7 +170,7 @@ describe('Message Utils', () => {
})
);
const msg = new ClosedGroupEncryptionPairMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
groupId: TestUtils.generateFakePubKey().key,
encryptedKeyPairs: fakeWrappers,
});
@ -190,7 +190,7 @@ describe('Message Utils', () => {
})
);
const msg = new ClosedGroupEncryptionPairReplyMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
groupId: TestUtils.generateFakePubKey().key,
encryptedKeyPairs: fakeWrappers,
});

@ -1,14 +1,14 @@
import { PendingMessageCache } from '../../../../session/sending';
import { RawMessage } from '../../../../session/types';
import { OutgoingRawMessage } from '../../../../session/types';
export class PendingMessageCacheStub extends PendingMessageCache {
public dbData: Array<RawMessage>;
constructor(dbData: Array<RawMessage> = []) {
public dbData: Array<OutgoingRawMessage>;
constructor(dbData: Array<OutgoingRawMessage> = []) {
super();
this.dbData = dbData;
}
public getCache(): Readonly<Array<RawMessage>> {
public getCache(): Readonly<Array<OutgoingRawMessage>> {
return this.cache;
}

@ -1,17 +1,17 @@
import { v4 as uuid } from 'uuid';
import { generateFakePubKey } from './pubkey';
import { ClosedGroupVisibleMessage } from '../../../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import { VisibleMessage } from '../../../session/messages/outgoing/visibleMessage/VisibleMessage';
import { OpenGroupMessageV2 } from '../../../session/apis/open_group_api/opengroupV2/OpenGroupMessageV2';
import { TestUtils } from '..';
import { OpenGroupRequestCommonType } from '../../../session/apis/open_group_api/opengroupV2/ApiUtil';
import { OpenGroupVisibleMessage } from '../../../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { MessageModel } from '../../../models/message';
import { OpenGroupRequestCommonType } from '../../../session/apis/open_group_api/opengroupV2/ApiUtil';
import { OpenGroupMessageV2 } from '../../../session/apis/open_group_api/opengroupV2/OpenGroupMessageV2';
import {
OpenGroupMessageV4,
OpenGroupReactionMessageV4,
} from '../../../session/apis/open_group_api/opengroupV2/OpenGroupServerPoller';
import { ClosedGroupVisibleMessage } from '../../../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
import { OpenGroupVisibleMessage } from '../../../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { VisibleMessage } from '../../../session/messages/outgoing/visibleMessage/VisibleMessage';
import { OpenGroupReaction } from '../../../types/Reaction';
import { generateFakePubKey } from './pubkey';
export function generateVisibleMessage({
identifier,
@ -23,7 +23,7 @@ export function generateVisibleMessage({
return new VisibleMessage({
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
identifier: identifier ?? uuid(),
timestamp: timestamp || Date.now(),
createAtNetworkTimestamp: timestamp || Date.now(),
attachments: undefined,
quote: undefined,
expireTimer: undefined,
@ -70,7 +70,7 @@ export function generateOpenGroupMessageV2WithServerId(
export function generateOpenGroupVisibleMessage(): OpenGroupVisibleMessage {
return new OpenGroupVisibleMessage({
timestamp: Date.now(),
createAtNetworkTimestamp: Date.now(),
});
}

Loading…
Cancel
Save