Migrate message ids to UUIDv7
This commit is contained in:
parent
c1b5811c39
commit
60d7cbff3e
24 changed files with 203 additions and 147 deletions
|
@ -6,7 +6,7 @@ import { render } from 'react-dom';
|
|||
import { batch as batchDispatch } from 'react-redux';
|
||||
import PQueue from 'p-queue';
|
||||
import pMap from 'p-map';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { v7 as generateUuid } from 'uuid';
|
||||
|
||||
import * as Registration from './util/registration';
|
||||
import MessageReceiver from './textsecure/MessageReceiver';
|
||||
|
@ -169,6 +169,7 @@ import {
|
|||
incrementMessageCounter,
|
||||
initializeMessageCounter,
|
||||
} from './util/incrementMessageCounter';
|
||||
import { generateMessageId } from './util/generateMessageId';
|
||||
import { RetryPlaceholders } from './util/retryPlaceholders';
|
||||
import { setBatchingStrategy } from './util/messageBatcher';
|
||||
import { parseRemoteClientExpiration } from './util/parseRemoteClientExpiration';
|
||||
|
@ -2694,7 +2695,8 @@ export async function startApp(): Promise<void> {
|
|||
}
|
||||
|
||||
const partialMessage: MessageAttributesType = {
|
||||
id: generateUuid(),
|
||||
...generateMessageId(data.receivedAtCounter),
|
||||
|
||||
canReplyToStory: data.message.isStory
|
||||
? data.message.canReplyToStory
|
||||
: undefined,
|
||||
|
@ -2705,7 +2707,6 @@ export async function startApp(): Promise<void> {
|
|||
),
|
||||
readStatus: ReadStatus.Read,
|
||||
received_at_ms: data.receivedAtDate,
|
||||
received_at: data.receivedAtCounter,
|
||||
seenStatus: SeenStatus.NotApplicable,
|
||||
sendStateByConversationId,
|
||||
sent_at: timestamp,
|
||||
|
@ -2957,13 +2958,13 @@ export async function startApp(): Promise<void> {
|
|||
`Did not receive receivedAtCounter for message: ${data.timestamp}`
|
||||
);
|
||||
const partialMessage: MessageAttributesType = {
|
||||
id: generateUuid(),
|
||||
...generateMessageId(data.receivedAtCounter),
|
||||
|
||||
canReplyToStory: data.message.isStory
|
||||
? data.message.canReplyToStory
|
||||
: undefined,
|
||||
conversationId: descriptor.id,
|
||||
readStatus: ReadStatus.Unread,
|
||||
received_at: data.receivedAtCounter,
|
||||
received_at_ms: data.receivedAtDate,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
sent_at: data.timestamp,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import type { FormEvent } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import { Modal } from './Modal';
|
||||
import type { LocalizerType } from '../types/I18N';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getClassNamesFor } from '../util/getClassNamesFor';
|
||||
import { CircleCheckbox } from './CircleCheckbox';
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import React, {
|
|||
} from 'react';
|
||||
import { noop, partition } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||
|
||||
import type { MediaDeviceSettings } from '../types/Calling';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import type { UIEvent } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { LocalizerType } from '../types/I18N';
|
||||
import { Modal } from './Modal';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import type { LocalizerType } from '../../../types/Util';
|
||||
import { getMuteOptions } from '../../../util/getMuteOptions';
|
||||
|
|
35
ts/groups.ts
35
ts/groups.ts
|
@ -12,7 +12,6 @@ import {
|
|||
} from 'lodash';
|
||||
import Long from 'long';
|
||||
import type { ClientZkGroupCipher } from '@signalapp/libsignal-client/zkgroup';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
import LRU from 'lru-cache';
|
||||
import * as log from './logging/log';
|
||||
import {
|
||||
|
@ -101,6 +100,7 @@ import {
|
|||
decodeGroupSendEndorsementResponse,
|
||||
isValidGroupSendEndorsementsExpiration,
|
||||
} from './util/groupSendEndorsements';
|
||||
import { generateMessageId } from './util/generateMessageId';
|
||||
|
||||
type AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
||||
|
||||
|
@ -293,7 +293,7 @@ type UploadedAvatarType = {
|
|||
|
||||
type BasicMessageType = Pick<
|
||||
MessageAttributesType,
|
||||
'id' | 'schemaVersion' | 'readStatus' | 'seenStatus'
|
||||
'readStatus' | 'seenStatus'
|
||||
>;
|
||||
|
||||
type GroupV2ChangeMessageType = {
|
||||
|
@ -335,14 +335,6 @@ const SUPPORTED_CHANGE_EPOCH = 5;
|
|||
export const LINK_VERSION_ERROR = 'LINK_VERSION_ERROR';
|
||||
const GROUP_INVITE_LINK_PASSWORD_LENGTH = 16;
|
||||
|
||||
function generateBasicMessage(): BasicMessageType {
|
||||
return {
|
||||
id: getGuid(),
|
||||
schemaVersion: MAX_MESSAGE_SCHEMA,
|
||||
// this is missing most properties to fulfill this type
|
||||
};
|
||||
}
|
||||
|
||||
// Group Links
|
||||
|
||||
export function generateGroupInviteLinkPassword(): Uint8Array {
|
||||
|
@ -2024,12 +2016,13 @@ export async function createGroupV2(
|
|||
});
|
||||
|
||||
const createdTheGroupMessage: MessageAttributesType = {
|
||||
...generateBasicMessage(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
|
||||
schemaVersion: MAX_MESSAGE_SCHEMA,
|
||||
type: 'group-v2-change',
|
||||
sourceServiceId: ourAci,
|
||||
conversationId: conversation.id,
|
||||
readStatus: ReadStatus.Read,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
timestamp,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
|
@ -2468,7 +2461,6 @@ export async function initiateMigrationToGroupV2(
|
|||
|
||||
const groupChangeMessages: Array<GroupChangeMessageType> = [];
|
||||
groupChangeMessages.push({
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v1-migration',
|
||||
groupMigration: {
|
||||
areWeInvited: false,
|
||||
|
@ -2609,7 +2601,6 @@ export function buildMigrationBubble(
|
|||
);
|
||||
|
||||
return {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v1-migration',
|
||||
groupMigration: {
|
||||
areWeInvited,
|
||||
|
@ -2623,7 +2614,6 @@ export function buildMigrationBubble(
|
|||
|
||||
export function getBasicMigrationBubble(): GroupChangeMessageType {
|
||||
return {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v1-migration',
|
||||
groupMigration: {
|
||||
areWeInvited: false,
|
||||
|
@ -2697,7 +2687,6 @@ export async function joinGroupV2ViaLinkAndMigrate({
|
|||
};
|
||||
const groupChangeMessages: Array<GroupChangeMessageType> = [
|
||||
{
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v1-migration',
|
||||
groupMigration: {
|
||||
areWeInvited: false,
|
||||
|
@ -2865,7 +2854,6 @@ export async function respondToGroupV2Migration({
|
|||
seenStatus: SeenStatus.Seen,
|
||||
},
|
||||
{
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
details: [
|
||||
|
@ -2946,7 +2934,6 @@ export async function respondToGroupV2Migration({
|
|||
if (!areWeInvited && !areWeMember) {
|
||||
// Add a message to the timeline saying the user was removed. This shouldn't happen.
|
||||
groupChangeMessages.push({
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
details: [
|
||||
|
@ -3184,8 +3171,9 @@ async function updateGroup(
|
|||
|
||||
return {
|
||||
...changeMessage,
|
||||
...generateMessageId(finalReceivedAt),
|
||||
schemaVersion: MAX_MESSAGE_SCHEMA,
|
||||
conversationId: conversation.id,
|
||||
received_at: finalReceivedAt,
|
||||
received_at_ms: syntheticSentAt,
|
||||
sent_at: syntheticSentAt,
|
||||
timestamp,
|
||||
|
@ -3895,7 +3883,6 @@ async function updateGroupViaSingleChange({
|
|||
catchupMessages.length > 0
|
||||
) {
|
||||
groupChangeMessages.push({
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
details: [
|
||||
|
@ -4901,7 +4888,6 @@ function extractDiffs({
|
|||
if (firstUpdate && serviceIdKindInvitedToGroup !== undefined) {
|
||||
// Note, we will add 'you were invited' to group even if dropInitialJoinMessage = true
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
from: whoInvitedUsUserId || from,
|
||||
|
@ -4919,7 +4905,6 @@ function extractDiffs({
|
|||
};
|
||||
} else if (firstUpdate && areWePendingApproval) {
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
from: ourAci,
|
||||
|
@ -4940,7 +4925,6 @@ function extractDiffs({
|
|||
sourceServiceId === ourAci
|
||||
) {
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
from,
|
||||
|
@ -4962,7 +4946,6 @@ function extractDiffs({
|
|||
);
|
||||
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
from,
|
||||
|
@ -4973,7 +4956,6 @@ function extractDiffs({
|
|||
};
|
||||
} else if (firstUpdate && current.revision === 0) {
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
groupV2Change: {
|
||||
from,
|
||||
|
@ -5002,7 +4984,6 @@ function extractDiffs({
|
|||
}
|
||||
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
sourceServiceId,
|
||||
groupV2Change: {
|
||||
|
@ -5014,7 +4995,6 @@ function extractDiffs({
|
|||
};
|
||||
} else if (details.length > 0) {
|
||||
message = {
|
||||
...generateBasicMessage(),
|
||||
type: 'group-v2-change',
|
||||
sourceServiceId,
|
||||
groupV2Change: {
|
||||
|
@ -5041,7 +5021,6 @@ function extractDiffs({
|
|||
`extractDiffs/${logId}: generating change notification for new ${expireTimer} timer`
|
||||
);
|
||||
timerNotification = {
|
||||
...generateBasicMessage(),
|
||||
type: 'timer-notification',
|
||||
sourceServiceId: isReJoin ? undefined : sourceServiceId,
|
||||
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import PQueue from 'p-queue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { v7 as uuid } from 'uuid';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { Job } from './Job';
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { isNumber } from 'lodash';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import * as Errors from '../../types/errors';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
|
@ -32,6 +31,7 @@ import type { AciString, ServiceIdString } from '../../types/ServiceId';
|
|||
import { isAciString } from '../../util/isAciString';
|
||||
import { handleMultipleSendErrors } from './handleMultipleSendErrors';
|
||||
import { incrementMessageCounter } from '../../util/incrementMessageCounter';
|
||||
import { generateMessageId } from '../../util/generateMessageId';
|
||||
|
||||
import type {
|
||||
ConversationQueueJobBundle,
|
||||
|
@ -159,11 +159,10 @@ export async function sendReaction(
|
|||
remove: !emoji,
|
||||
};
|
||||
const ephemeralMessageForReactionSend = new window.Whisper.Message({
|
||||
id: generateUuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
type: 'outgoing',
|
||||
conversationId: conversation.get('id'),
|
||||
sent_at: pendingReaction.timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: pendingReaction.timestamp,
|
||||
timestamp: pendingReaction.timestamp,
|
||||
sendStateByConversationId: zipObject(
|
||||
|
|
|
@ -174,6 +174,7 @@ import { ReceiptType } from '../types/Receipt';
|
|||
import { getQuoteAttachment } from '../util/makeQuote';
|
||||
import { deriveProfileKeyVersion } from '../util/zkgroup';
|
||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||
import { generateMessageId } from '../util/generateMessageId';
|
||||
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||
import { downscaleOutgoingAttachment } from '../util/attachments';
|
||||
import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent';
|
||||
|
@ -2328,11 +2329,10 @@ export class ConversationModel extends window.Backbone
|
|||
: lastMessageTimestamp;
|
||||
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type: 'message-request-response-event',
|
||||
sent_at: maybeLastMessageTimestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: maybeLastMessageTimestamp,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.NotApplicable,
|
||||
|
@ -3085,12 +3085,11 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(receivedAtCounter),
|
||||
conversationId: this.id,
|
||||
type: 'chat-session-refreshed',
|
||||
timestamp: receivedAt,
|
||||
sent_at: receivedAt,
|
||||
received_at: receivedAtCounter,
|
||||
received_at_ms: receivedAt,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
|
@ -3133,12 +3132,11 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(receivedAtCounter),
|
||||
conversationId: this.id,
|
||||
type: 'delivery-issue',
|
||||
sourceServiceId: senderAci,
|
||||
sent_at: receivedAt,
|
||||
received_at: receivedAtCounter,
|
||||
received_at_ms: receivedAt,
|
||||
timestamp: receivedAt,
|
||||
readStatus: ReadStatus.Unread,
|
||||
|
@ -3183,12 +3181,11 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const timestamp = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type: 'keychange',
|
||||
sent_at: timestamp,
|
||||
timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
key_changed: keyChangedId,
|
||||
readStatus: ReadStatus.Read,
|
||||
|
@ -3245,12 +3242,11 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const timestamp = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type: 'conversation-merge',
|
||||
sent_at: timestamp,
|
||||
timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
conversationMerge: {
|
||||
renderInfo,
|
||||
|
@ -3294,12 +3290,11 @@ export class ConversationModel extends window.Backbone
|
|||
log.info(`${logId}: adding notification`);
|
||||
const timestamp = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type: 'phone-number-discovery',
|
||||
sent_at: timestamp,
|
||||
timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
phoneNumberDiscovery: {
|
||||
e164,
|
||||
|
@ -3343,12 +3338,11 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const timestamp = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
local: Boolean(options.local),
|
||||
readStatus: ReadStatus.Read,
|
||||
received_at_ms: timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
seenStatus: options.local ? SeenStatus.Seen : SeenStatus.Unseen,
|
||||
sent_at: lastMessage,
|
||||
timestamp,
|
||||
|
@ -3388,11 +3382,10 @@ export class ConversationModel extends window.Backbone
|
|||
): Promise<void> {
|
||||
const now = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type: 'profile-change',
|
||||
sent_at: now,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: now,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.NotApplicable,
|
||||
|
@ -3432,11 +3425,10 @@ export class ConversationModel extends window.Backbone
|
|||
): Promise<string> {
|
||||
const now = Date.now();
|
||||
const message: MessageAttributesType = {
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: this.id,
|
||||
type,
|
||||
sent_at: now,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: now,
|
||||
timestamp: now,
|
||||
|
||||
|
@ -4102,7 +4094,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
// Here we move attachments to disk
|
||||
const attributes = await upgradeMessageSchema({
|
||||
id: generateGuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
timestamp: now,
|
||||
type: 'outgoing',
|
||||
body,
|
||||
|
@ -4112,7 +4104,6 @@ export class ConversationModel extends window.Backbone
|
|||
preview,
|
||||
attachments: attachmentsToSend,
|
||||
sent_at: now,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: now,
|
||||
expirationStartTimestamp,
|
||||
expireTimer,
|
||||
|
@ -4734,9 +4725,9 @@ export class ConversationModel extends window.Backbone
|
|||
const shouldBeRead =
|
||||
(isInitialSync && isFromSyncOperation) || isFromMe || isNoteToSelf;
|
||||
|
||||
const id = generateGuid();
|
||||
const counter = receivedAt ?? incrementMessageCounter();
|
||||
const attributes = {
|
||||
id,
|
||||
...generateMessageId(counter),
|
||||
conversationId: this.id,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer,
|
||||
|
@ -4747,7 +4738,6 @@ export class ConversationModel extends window.Backbone
|
|||
flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||
readStatus: shouldBeRead ? ReadStatus.Read : ReadStatus.Unread,
|
||||
received_at_ms: receivedAtMS,
|
||||
received_at: receivedAt ?? incrementMessageCounter(),
|
||||
seenStatus: shouldBeRead ? SeenStatus.Seen : SeenStatus.Unseen,
|
||||
sent_at: sentAt,
|
||||
timestamp: sentAt,
|
||||
|
@ -4760,7 +4750,7 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
|
||||
window.MessageCache.__DEPRECATED$register(
|
||||
id,
|
||||
attributes.id,
|
||||
attributes,
|
||||
'updateExpirationTimer'
|
||||
);
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
pick,
|
||||
union,
|
||||
} from 'lodash';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import type {
|
||||
CustomError,
|
||||
|
@ -26,6 +25,7 @@ import { isNotNil } from '../util/isNotNil';
|
|||
import { isNormalNumber } from '../util/isNormalNumber';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { hydrateStoryContext } from '../util/hydrateStoryContext';
|
||||
import { generateMessageId } from '../util/generateMessageId';
|
||||
import { drop } from '../util/drop';
|
||||
import type { ConversationModel } from './conversations';
|
||||
import type {
|
||||
|
@ -1642,7 +1642,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
}
|
||||
|
||||
const messageId = message.get('id') || generateUuid();
|
||||
const messageId =
|
||||
message.get('id') || generateMessageId(this.get('received_at')).id;
|
||||
|
||||
// Send delivery receipts, but only for non-story sealed sender messages
|
||||
// and not for messages from unaccepted conversations
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import noop from 'lodash/noop';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { v7 as generateUuid } from 'uuid';
|
||||
|
||||
import { DataWriter } from '../sql/Client';
|
||||
import type { MessageModel } from '../models/messages';
|
||||
import type { ReactionAttributesType } from '../messageModifiers/Reactions';
|
||||
import { ReactionSource } from './ReactionSource';
|
||||
import { __DEPRECATED$getMessageById } from '../messages/getMessageById';
|
||||
|
@ -12,6 +13,7 @@ import { getSourceServiceId, isStory } from '../messages/helpers';
|
|||
import { strictAssert } from '../util/assert';
|
||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||
import { generateMessageId } from '../util/generateMessageId';
|
||||
import { repeat, zipObject } from '../util/iterables';
|
||||
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||
import { isAciString } from '../util/isAciString';
|
||||
|
@ -87,31 +89,31 @@ export async function enqueueReactionForSend({
|
|||
: undefined;
|
||||
|
||||
// Only used in story scenarios, where we use a whole message to represent the reaction
|
||||
const storyReactionMessage = storyMessage
|
||||
? new window.Whisper.Message({
|
||||
id: generateUuid(),
|
||||
type: 'outgoing',
|
||||
conversationId: targetConversation.id,
|
||||
sent_at: timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
received_at_ms: timestamp,
|
||||
timestamp,
|
||||
expireTimer,
|
||||
sendStateByConversationId: zipObject(
|
||||
targetConversation.getMemberConversationIds(),
|
||||
repeat({
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
),
|
||||
storyId: message.id,
|
||||
storyReaction: {
|
||||
emoji,
|
||||
targetAuthorAci,
|
||||
targetTimestamp,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
let storyReactionMessage: MessageModel | undefined;
|
||||
if (storyMessage) {
|
||||
storyReactionMessage = new window.Whisper.Message({
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
type: 'outgoing',
|
||||
conversationId: targetConversation.id,
|
||||
sent_at: timestamp,
|
||||
received_at_ms: timestamp,
|
||||
timestamp,
|
||||
expireTimer,
|
||||
sendStateByConversationId: zipObject(
|
||||
targetConversation.getMemberConversationIds(),
|
||||
repeat({
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
),
|
||||
storyId: message.id,
|
||||
storyReaction: {
|
||||
emoji,
|
||||
targetAuthorAci,
|
||||
targetTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const reaction: ReactionAttributesType = {
|
||||
envelopeId: generateUuid(),
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { Aci, Pni, ServiceId } from '@signalapp/libsignal-client';
|
||||
import { ReceiptCredentialPresentation } from '@signalapp/libsignal-client/zkgroup';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { v7 as generateUuid } from 'uuid';
|
||||
import pMap from 'p-map';
|
||||
import { Writable } from 'stream';
|
||||
import { isNumber } from 'lodash';
|
||||
|
@ -63,6 +63,7 @@ import {
|
|||
deriveGroupPublicParams,
|
||||
} from '../../util/zkgroup';
|
||||
import { incrementMessageCounter } from '../../util/incrementMessageCounter';
|
||||
import { generateMessageId } from '../../util/generateMessageId';
|
||||
import { isAciString } from '../../util/isAciString';
|
||||
import { PhoneNumberDiscoverability } from '../../util/phoneNumberDiscoverability';
|
||||
import { PhoneNumberSharingMode } from '../../util/phoneNumberSharingMode';
|
||||
|
@ -487,6 +488,26 @@ export class BackupImportStream extends Writable {
|
|||
const batch = Array.from(this.saveMessageBatch);
|
||||
this.saveMessageBatch.clear();
|
||||
|
||||
// There are a few indexes that start with message id, and many more that
|
||||
// start with conversationId. Sort messages by both to make sure that we
|
||||
// are not doing random insertions into the database file.
|
||||
// This improves bulk insert performance >2x.
|
||||
batch.sort((a, b) => {
|
||||
if (a.conversationId > b.conversationId) {
|
||||
return -1;
|
||||
}
|
||||
if (a.conversationId < b.conversationId) {
|
||||
return 1;
|
||||
}
|
||||
if (a.id < b.id) {
|
||||
return -1;
|
||||
}
|
||||
if (a.id > b.id) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
await DataWriter.saveMessages(batch, {
|
||||
forceSave: true,
|
||||
ourAci,
|
||||
|
@ -1235,9 +1256,8 @@ export class BackupImportStream extends Writable {
|
|||
}
|
||||
|
||||
let attributes: MessageAttributesType = {
|
||||
id: generateUuid(),
|
||||
...generateMessageId(incrementMessageCounter()),
|
||||
conversationId: chatConvo.id,
|
||||
received_at: incrementMessageCounter(),
|
||||
sent_at: timestamp,
|
||||
source: authorConvo?.e164,
|
||||
sourceServiceId: authorConvo?.serviceId,
|
||||
|
|
|
@ -18,7 +18,7 @@ import { cleanDataForIpc } from './cleanDataForIpc';
|
|||
import type { AciString, ServiceIdString } from '../types/ServiceId';
|
||||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
||||
import * as log from '../logging/log';
|
||||
import { isValidUuid } from '../util/isValidUuid';
|
||||
import { isValidUuidV7 } from '../util/isValidUuid';
|
||||
import * as Errors from '../types/errors';
|
||||
|
||||
import type { StoredJob } from '../jobs/types';
|
||||
|
@ -603,7 +603,7 @@ async function saveMessage(
|
|||
jobToInsert: options.jobToInsert && formatJobForInsert(options.jobToInsert),
|
||||
});
|
||||
|
||||
softAssert(isValidUuid(id), 'saveMessage: messageId is not a UUID');
|
||||
softAssert(isValidUuidV7(id), 'saveMessage: messageId is not a UUID');
|
||||
|
||||
void updateExpiringMessagesService();
|
||||
void tapToViewMessagesDeletionService.update();
|
||||
|
|
|
@ -9,7 +9,6 @@ import rimraf from 'rimraf';
|
|||
import { randomBytes } from 'crypto';
|
||||
import type { Database, Statement } from '@signalapp/better-sqlite3';
|
||||
import SQL from '@signalapp/better-sqlite3';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
|
||||
|
@ -49,6 +48,7 @@ import { isNormalNumber } from '../util/isNormalNumber';
|
|||
import { isNotNil } from '../util/isNotNil';
|
||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import * as durations from '../util/durations';
|
||||
import { generateMessageId } from '../util/generateMessageId';
|
||||
import { formatCountForLogging } from '../logging/formatCountForLogging';
|
||||
import type { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||
import type { BadgeType, BadgeImageType } from '../badges/types';
|
||||
|
@ -2328,7 +2328,7 @@ export function saveMessage(
|
|||
|
||||
const toCreate = {
|
||||
...data,
|
||||
id: id || generateUuid(),
|
||||
id: id || generateMessageId(data.received_at).id,
|
||||
};
|
||||
|
||||
prepare(
|
||||
|
@ -6813,23 +6813,30 @@ function pageMessages(
|
|||
writable.exec(
|
||||
`
|
||||
CREATE TEMP TABLE tmp_${runId}_updated_messages
|
||||
(rowid INTEGER PRIMARY KEY ASC);
|
||||
(rowid INTEGER PRIMARY KEY, received_at INTEGER, sent_at INTEGER);
|
||||
|
||||
INSERT INTO tmp_${runId}_updated_messages (rowid)
|
||||
SELECT rowid FROM messages;
|
||||
CREATE INDEX tmp_${runId}_updated_messages_received_at
|
||||
ON tmp_${runId}_updated_messages (received_at ASC, sent_at ASC);
|
||||
|
||||
INSERT INTO tmp_${runId}_updated_messages
|
||||
(rowid, received_at, sent_at)
|
||||
SELECT rowid, received_at, sent_at FROM messages
|
||||
ORDER BY received_at ASC, sent_at ASC;
|
||||
|
||||
CREATE TEMP TRIGGER tmp_${runId}_message_updates
|
||||
UPDATE OF json ON messages
|
||||
BEGIN
|
||||
INSERT OR IGNORE INTO tmp_${runId}_updated_messages (rowid)
|
||||
VALUES (NEW.rowid);
|
||||
INSERT OR IGNORE INTO tmp_${runId}_updated_messages
|
||||
(rowid, received_at, sent_at)
|
||||
VALUES (NEW.rowid, NEW.received_at, NEW.sent_at);
|
||||
END;
|
||||
|
||||
CREATE TEMP TRIGGER tmp_${runId}_message_inserts
|
||||
AFTER INSERT ON messages
|
||||
BEGIN
|
||||
INSERT OR IGNORE INTO tmp_${runId}_updated_messages (rowid)
|
||||
VALUES (NEW.rowid);
|
||||
INSERT OR IGNORE INTO tmp_${runId}_updated_messages
|
||||
(rowid, received_at, sent_at)
|
||||
VALUES (NEW.rowid, NEW.received_at, NEW.sent_at);
|
||||
END;
|
||||
`
|
||||
);
|
||||
|
@ -6840,10 +6847,11 @@ function pageMessages(
|
|||
const rowids: Array<number> = writable
|
||||
.prepare<Query>(
|
||||
`
|
||||
DELETE FROM tmp_${runId}_updated_messages
|
||||
RETURNING rowid
|
||||
LIMIT $chunkSize;
|
||||
`
|
||||
DELETE FROM tmp_${runId}_updated_messages
|
||||
RETURNING rowid
|
||||
ORDER BY received_at ASC, sent_at ASC
|
||||
LIMIT $chunkSize;
|
||||
`
|
||||
)
|
||||
.pluck()
|
||||
.all({ chunkSize });
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { type AciString, generateAci } from '../types/ServiceId';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import type { PeekInfo } from '@signalapp/ringrtc';
|
||||
import uuid from 'uuid';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import {
|
||||
getPeerIdFromConversation,
|
||||
getCallIdFromEra,
|
||||
|
|
47
ts/util/generateMessageId.ts
Normal file
47
ts/util/generateMessageId.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { stringify } from 'uuid';
|
||||
|
||||
import { getRandomBytes } from '../Crypto';
|
||||
|
||||
export type GeneratedMessageIdType = Readonly<{
|
||||
id: string;
|
||||
received_at: number;
|
||||
}>;
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.7
|
||||
export function generateMessageId(counter: number): GeneratedMessageIdType {
|
||||
const uuid = getRandomBytes(16);
|
||||
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
// We compose uuid out of 48 bits (6 bytes of) timestamp-like counter:
|
||||
// `incrementMessageCounter`. Note big-endian encoding (which ensures proper
|
||||
// lexicographical order), and floating point divisions (because `&` operator
|
||||
// coerces to 32bit integers)
|
||||
|
||||
uuid[0] = (counter / 0x10000000000) & 0xff;
|
||||
uuid[1] = (counter / 0x00100000000) & 0xff;
|
||||
uuid[2] = (counter / 0x00001000000) & 0xff;
|
||||
uuid[3] = (counter / 0x00000010000) & 0xff;
|
||||
uuid[4] = (counter / 0x00000000100) & 0xff;
|
||||
uuid[5] = (counter / 0x00000000001) & 0xff;
|
||||
|
||||
// Mask out 4 bits of version number
|
||||
uuid[6] &= 0x0f;
|
||||
// And set the version to 7
|
||||
uuid[6] |= 0x70;
|
||||
|
||||
// Mask out 2 bits of variant
|
||||
uuid[8] &= 0x3f;
|
||||
// And set it to "2"
|
||||
uuid[8] |= 0x80;
|
||||
|
||||
/* eslint-enable no-bitwise */
|
||||
|
||||
return {
|
||||
id: stringify(uuid),
|
||||
received_at: counter,
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
import { debounce, isNumber } from 'lodash';
|
||||
|
||||
import { strictAssert } from './assert';
|
||||
import { safeParseInteger } from './numbers';
|
||||
import { DataReader } from '../sql/Client';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
|
@ -15,7 +16,9 @@ export async function initializeMessageCounter(): Promise<void> {
|
|||
'incrementMessageCounter: already initialized'
|
||||
);
|
||||
|
||||
const storedCounter = Number(localStorage.getItem('lastReceivedAtCounter'));
|
||||
const storedCounter = safeParseInteger(
|
||||
localStorage.getItem('lastReceivedAtCounter') ?? ''
|
||||
);
|
||||
const dbCounter = await DataReader.getMaxMessageCounter();
|
||||
|
||||
if (isNumber(dbCounter) && isNumber(storedCounter)) {
|
||||
|
|
|
@ -16,3 +16,19 @@ export const isValidUuid = (value: unknown): value is string => {
|
|||
|
||||
return UUID_REGEXP.test(value);
|
||||
};
|
||||
|
||||
const UUID_V7_REGEXP =
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i;
|
||||
|
||||
export const isValidUuidV7 = (value: unknown): value is string => {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Zero UUID is a valid uuid.
|
||||
if (value === '00000000-0000-0000-0000-000000000000') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return UUID_V7_REGEXP.test(value);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ipcRenderer } from 'electron';
|
|||
import { isString, isTypedArray } from 'lodash';
|
||||
import { join, normalize, basename } from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import getGuid from 'uuid/v4';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import { isPathInside } from '../util/isPathInside';
|
||||
import { writeWindowsZoneIdentifier } from '../util/windowsZoneIdentifier';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue