Use UUIDs in group database schema
This commit is contained in:
parent
74fde10ff5
commit
63fcdbe787
79 changed files with 4530 additions and 3664 deletions
|
@ -378,14 +378,12 @@ try {
|
|||
});
|
||||
|
||||
const { imageToBlurHash } = require('./ts/util/imageToBlurHash');
|
||||
const { isValidGuid } = require('./ts/util/isValidGuid');
|
||||
const { ActiveWindowService } = require('./ts/services/ActiveWindowService');
|
||||
|
||||
window.imageToBlurHash = imageToBlurHash;
|
||||
window.emojiData = require('emoji-datasource');
|
||||
window.libphonenumber = require('google-libphonenumber').PhoneNumberUtil.getInstance();
|
||||
window.libphonenumber.PhoneNumberFormat = require('google-libphonenumber').PhoneNumberFormat;
|
||||
window.getGuid = require('uuid/v4');
|
||||
|
||||
const activeWindowService = new ActiveWindowService();
|
||||
activeWindowService.initialize(window.document, ipc);
|
||||
|
@ -401,8 +399,6 @@ try {
|
|||
reducedMotionSetting: Boolean(config.reducedMotionSetting),
|
||||
};
|
||||
|
||||
window.isValidGuid = isValidGuid;
|
||||
|
||||
window.React = require('react');
|
||||
window.ReactDOM = require('react-dom');
|
||||
window.moment = require('moment');
|
||||
|
|
|
@ -34,7 +34,6 @@ const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
|
|||
window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/';
|
||||
window.getEnvironment = getEnvironment;
|
||||
window.getVersion = () => config.version;
|
||||
window.getGuid = require('uuid/v4');
|
||||
window.PQueue = require('p-queue').default;
|
||||
window.Backbone = require('backbone');
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ const chaiAsPromised = require('chai-as-promised');
|
|||
|
||||
const { Crypto } = require('../ts/context/Crypto');
|
||||
const { setEnvironment, Environment } = require('../ts/environment');
|
||||
const { isValidGuid } = require('../ts/util/isValidGuid');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
|
@ -31,7 +30,6 @@ global.window = {
|
|||
get: key => storageMap.get(key),
|
||||
put: async (key, value) => storageMap.set(key, value),
|
||||
},
|
||||
isValidGuid,
|
||||
};
|
||||
|
||||
// For ducks/network.getEmptyState()
|
||||
|
|
|
@ -13,10 +13,9 @@ import type {
|
|||
import type { ConversationModel } from './models/conversations';
|
||||
import { maybeDeriveGroupV2Id } from './groups';
|
||||
import { assert } from './util/assert';
|
||||
import { isValidGuid } from './util/isValidGuid';
|
||||
import { map, reduce } from './util/iterables';
|
||||
import { isGroupV1, isGroupV2 } from './util/whatTypeOfConversation';
|
||||
import { UUID } from './types/UUID';
|
||||
import { UUID, isValidUuid } from './types/UUID';
|
||||
import { Address } from './types/Address';
|
||||
import { QualifiedAddress } from './types/QualifiedAddress';
|
||||
import * as log from './logging/log';
|
||||
|
@ -25,7 +24,7 @@ const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
|||
|
||||
const {
|
||||
getAllConversations,
|
||||
getAllGroupsInvolvingId,
|
||||
getAllGroupsInvolvingUuid,
|
||||
getMessagesBySentAt,
|
||||
migrateConversationMessages,
|
||||
removeConversation,
|
||||
|
@ -181,7 +180,7 @@ export class ConversationController {
|
|||
return conversation;
|
||||
}
|
||||
|
||||
const id = window.getGuid();
|
||||
const id = UUID.generate().toString();
|
||||
|
||||
if (type === 'group') {
|
||||
conversation = this._conversations.add({
|
||||
|
@ -193,7 +192,7 @@ export class ConversationController {
|
|||
version: 2,
|
||||
...additionalInitialProps,
|
||||
});
|
||||
} else if (window.isValidGuid(identifier)) {
|
||||
} else if (isValidUuid(identifier)) {
|
||||
conversation = this._conversations.add({
|
||||
id,
|
||||
uuid: identifier,
|
||||
|
@ -617,7 +616,7 @@ export class ConversationController {
|
|||
}
|
||||
|
||||
const obsoleteId = obsolete.get('id');
|
||||
const obsoleteUuid = obsolete.get('uuid');
|
||||
const obsoleteUuid = obsolete.getUuid();
|
||||
const currentId = current.get('id');
|
||||
log.warn('combineConversations: Combining two conversations', {
|
||||
obsolete: obsoleteId,
|
||||
|
@ -643,13 +642,13 @@ export class ConversationController {
|
|||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier: obsoleteUuid,
|
||||
identifier: obsoleteUuid.toString(),
|
||||
});
|
||||
await Promise.all(
|
||||
deviceIds.map(async deviceId => {
|
||||
const addr = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(obsoleteUuid, deviceId)
|
||||
new Address(obsoleteUuid, deviceId)
|
||||
);
|
||||
await window.textsecure.storage.protocol.removeSession(addr);
|
||||
})
|
||||
|
@ -661,14 +660,14 @@ export class ConversationController {
|
|||
|
||||
if (obsoleteUuid) {
|
||||
await window.textsecure.storage.protocol.removeIdentityKey(
|
||||
new UUID(obsoleteUuid)
|
||||
obsoleteUuid
|
||||
);
|
||||
}
|
||||
|
||||
log.warn(
|
||||
'combineConversations: Ensure that all V1 groups have new conversationId instead of old'
|
||||
);
|
||||
const groups = await this.getAllGroupsInvolvingId(obsoleteId);
|
||||
const groups = await this.getAllGroupsInvolvingUuid(obsoleteUuid);
|
||||
groups.forEach(group => {
|
||||
const members = group.get('members');
|
||||
const withoutObsolete = without(members, obsoleteId);
|
||||
|
@ -737,10 +736,10 @@ export class ConversationController {
|
|||
return null;
|
||||
}
|
||||
|
||||
async getAllGroupsInvolvingId(
|
||||
conversationId: string
|
||||
async getAllGroupsInvolvingUuid(
|
||||
uuid: UUID
|
||||
): Promise<Array<ConversationModel>> {
|
||||
const groups = await getAllGroupsInvolvingId(conversationId, {
|
||||
const groups = await getAllGroupsInvolvingUuid(uuid.toString(), {
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
});
|
||||
return groups.map(group => {
|
||||
|
@ -836,7 +835,7 @@ export class ConversationController {
|
|||
// Clean up the conversations that have UUID as their e164.
|
||||
const e164 = conversation.get('e164');
|
||||
const uuid = conversation.get('uuid');
|
||||
if (isValidGuid(e164) && uuid) {
|
||||
if (isValidUuid(e164) && uuid) {
|
||||
conversation.set({ e164: undefined });
|
||||
updateConversation(conversation.attributes);
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import { calculateAgreement, generateKeyPair } from './Curve';
|
|||
import * as log from './logging/log';
|
||||
import { HashType, CipherType } from './types/Crypto';
|
||||
import { ProfileDecryptError } from './types/errors';
|
||||
import { UUID } from './types/UUID';
|
||||
import type { UUIDStringType } from './types/UUID';
|
||||
|
||||
export { HashType, CipherType };
|
||||
|
||||
|
@ -457,7 +459,7 @@ export function uuidToBytes(uuid: string): Uint8Array {
|
|||
);
|
||||
}
|
||||
|
||||
export function bytesToUuid(bytes: Uint8Array): undefined | string {
|
||||
export function bytesToUuid(bytes: Uint8Array): undefined | UUIDStringType {
|
||||
if (bytes.byteLength !== 16) {
|
||||
log.warn(
|
||||
'bytesToUuid: received an Uint8Array of invalid length. ' +
|
||||
|
@ -473,7 +475,7 @@ export function bytesToUuid(bytes: Uint8Array): undefined | string {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
export function splitUuids(buffer: Uint8Array): Array<string | null> {
|
||||
export function splitUuids(buffer: Uint8Array): Array<UUIDStringType | null> {
|
||||
const uuids = [];
|
||||
for (let i = 0; i < buffer.byteLength; i += 16) {
|
||||
const bytes = getBytes(buffer, i, 16);
|
||||
|
@ -487,7 +489,7 @@ export function splitUuids(buffer: Uint8Array): Array<string | null> {
|
|||
];
|
||||
const uuid = chunks.join('-');
|
||||
if (uuid !== '00000000-0000-0000-0000-000000000000') {
|
||||
uuids.push(uuid);
|
||||
uuids.push(UUID.cast(uuid));
|
||||
} else {
|
||||
uuids.push(null);
|
||||
}
|
||||
|
|
|
@ -994,7 +994,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
id,
|
||||
version: 2,
|
||||
ourUuid: qualifiedAddress.ourUuid.toString(),
|
||||
conversationId: new UUID(conversationId).toString(),
|
||||
conversationId,
|
||||
uuid: uuid.toString(),
|
||||
deviceId,
|
||||
record: record.serialize().toString('base64'),
|
||||
|
@ -1394,7 +1394,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const conversationId = new UUID(conversation.id).toString();
|
||||
const conversationId = conversation.id;
|
||||
const record = this.identityKeys.get(`conversation:${conversationId}`);
|
||||
if (!record) {
|
||||
return undefined;
|
||||
|
|
|
@ -3608,6 +3608,7 @@ export async function startApp(): Promise<void> {
|
|||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
type,
|
||||
});
|
||||
|
@ -3791,6 +3792,7 @@ export async function startApp(): Promise<void> {
|
|||
messageSentAt: timestamp,
|
||||
receiptTimestamp: envelopeTimestamp,
|
||||
sourceConversationId,
|
||||
sourceUuid,
|
||||
sourceDevice,
|
||||
type: MessageReceiptType.Delivery,
|
||||
});
|
||||
|
|
|
@ -40,12 +40,13 @@ import type {
|
|||
StartCallType,
|
||||
} from '../state/ducks/calling';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
|
||||
const GROUP_CALL_RING_DURATION = 60 * 1000;
|
||||
|
||||
type MeType = ConversationType & {
|
||||
uuid: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
|
||||
export type PropsType = {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { times } from 'lodash';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { boolean, select, number } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
@ -21,7 +20,10 @@ import type { PropsType } from './CallScreen';
|
|||
import { CallScreen } from './CallScreen';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultConversationWithUuid,
|
||||
} from '../test-both/helpers/getDefaultConversation';
|
||||
import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
|
||||
|
@ -286,10 +288,9 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
|
|||
presenting: false,
|
||||
sharingScreen: false,
|
||||
videoAspectRatio: 1.3,
|
||||
...getDefaultConversation({
|
||||
...getDefaultConversationWithUuid({
|
||||
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
|
||||
title: `Participant ${index + 1}`,
|
||||
uuid: generateUuid(),
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import { GroupCallRemoteParticipants } from './GroupCallRemoteParticipants';
|
|||
import type { LocalizerType } from '../types/Util';
|
||||
import { NeedsScreenRecordingPermissionsModal } from './NeedsScreenRecordingPermissionsModal';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||
import { useActivateSpeakerViewOnPresenting } from '../hooks/useActivateSpeakerViewOnPresenting';
|
||||
|
||||
|
@ -57,7 +58,7 @@ export type PropsType = {
|
|||
phoneNumber?: string;
|
||||
profileName?: string;
|
||||
title: string;
|
||||
uuid: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
openSystemPreferencesAction: () => unknown;
|
||||
setGroupCallVideoRequest: (_: Array<GroupCallVideoRequest>) => void;
|
||||
|
|
|
@ -6,15 +6,18 @@ import { times } from 'lodash';
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import { AvatarColors } from '../types/Colors';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type { PropsType } from './CallingLobby';
|
||||
import { CallingLobby } from './CallingLobby';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { UUID } from '../types/UUID';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultConversationWithUuid,
|
||||
} from '../test-both/helpers/getDefaultConversation';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -60,8 +63,8 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
isCallFull: boolean('isCallFull', overrideProps.isCallFull || false),
|
||||
me: overrideProps.me || {
|
||||
color: AvatarColors[0],
|
||||
id: generateUuid(),
|
||||
uuid: generateUuid(),
|
||||
id: UUID.generate().toString(),
|
||||
uuid: UUID.generate().toString(),
|
||||
},
|
||||
onCallCanceled: action('on-call-canceled'),
|
||||
onJoinCall: action('on-join-call'),
|
||||
|
@ -81,8 +84,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
};
|
||||
|
||||
const fakePeekedParticipant = (conversationProps: Partial<ConversationType>) =>
|
||||
getDefaultConversation({
|
||||
uuid: generateUuid(),
|
||||
getDefaultConversationWithUuid({
|
||||
...conversationProps,
|
||||
});
|
||||
|
||||
|
@ -106,8 +108,8 @@ story.add('No Camera, local avatar', () => {
|
|||
me: {
|
||||
avatarPath: '/fixtures/kitten-4-112-112.jpg',
|
||||
color: AvatarColors[0],
|
||||
id: generateUuid(),
|
||||
uuid: generateUuid(),
|
||||
id: UUID.generate().toString(),
|
||||
uuid: UUID.generate().toString(),
|
||||
},
|
||||
});
|
||||
return <CallingLobby {...props} />;
|
||||
|
@ -141,11 +143,11 @@ story.add('Group Call - 1 peeked participant', () => {
|
|||
});
|
||||
|
||||
story.add('Group Call - 1 peeked participant (self)', () => {
|
||||
const uuid = generateUuid();
|
||||
const uuid = UUID.generate().toString();
|
||||
const props = createProps({
|
||||
isGroupCall: true,
|
||||
me: {
|
||||
id: generateUuid(),
|
||||
id: UUID.generate().toString(),
|
||||
uuid,
|
||||
},
|
||||
peekedParticipants: [fakePeekedParticipant({ title: 'Ash', uuid })],
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from './CallingLobbyJoinButton';
|
||||
import type { AvatarColorType } from '../types/Colors';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { useIsOnline } from '../hooks/useIsOnline';
|
||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
|
@ -52,7 +53,7 @@ export type PropsType = {
|
|||
avatarPath?: string;
|
||||
id: string;
|
||||
color?: AvatarColorType;
|
||||
uuid: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
onCallCanceled: () => void;
|
||||
onJoinCall: () => void;
|
||||
|
|
|
@ -5,13 +5,12 @@ import * as React from 'react';
|
|||
import { sample } from 'lodash';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import type { PropsType } from './CallingParticipantsList';
|
||||
import { CallingParticipantsList } from './CallingParticipantsList';
|
||||
import { AvatarColors } from '../types/Colors';
|
||||
import type { GroupCallRemoteParticipantType } from '../types/Calling';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultConversationWithUuid } from '../test-both/helpers/getDefaultConversation';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
|
||||
|
@ -27,14 +26,13 @@ function createParticipant(
|
|||
presenting: Boolean(participantProps.presenting),
|
||||
sharingScreen: Boolean(participantProps.sharingScreen),
|
||||
videoAspectRatio: 1.3,
|
||||
...getDefaultConversation({
|
||||
...getDefaultConversationWithUuid({
|
||||
avatarPath: participantProps.avatarPath,
|
||||
color: sample(AvatarColors),
|
||||
isBlocked: Boolean(participantProps.isBlocked),
|
||||
name: participantProps.name,
|
||||
profileName: participantProps.title,
|
||||
title: String(participantProps.title),
|
||||
uuid: generateUuid(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import type { EmojiPickDataType } from './emoji/EmojiPicker';
|
|||
import { convertShortName } from './emoji/lib';
|
||||
import type { LocalizerType, BodyRangeType } from '../types/Util';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { isValidUuid } from '../types/UUID';
|
||||
import { MentionBlot } from '../quill/mentions/blot';
|
||||
import {
|
||||
matchEmojiImage,
|
||||
|
@ -465,7 +466,7 @@ export function CompositionInput(props: Props): React.ReactElement {
|
|||
|
||||
const currentMemberUuids = currentMembers
|
||||
.map(m => m.uuid)
|
||||
.filter((uuid): uuid is string => uuid !== undefined);
|
||||
.filter(isValidUuid);
|
||||
|
||||
const newDelta = getDeltaToRemoveStaleMentions(ops, currentMemberUuids);
|
||||
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { memoize, times } from 'lodash';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number } from '@storybook/addon-knobs';
|
||||
|
||||
import { GroupCallOverflowArea } from './GroupCallOverflowArea';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultConversationWithUuid } from '../test-both/helpers/getDefaultConversation';
|
||||
import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource';
|
||||
import { FRAME_BUFFER_SIZE } from '../calling/constants';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
|
@ -26,10 +25,9 @@ const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({
|
|||
presenting: false,
|
||||
sharingScreen: false,
|
||||
videoAspectRatio: 1.3,
|
||||
...getDefaultConversation({
|
||||
...getDefaultConversationWithUuid({
|
||||
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
|
||||
title: `Participant ${index + 1}`,
|
||||
uuid: generateUuid(),
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as React from 'react';
|
|||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import type { GroupV2ChangeType } from '../../groups';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
|
@ -14,12 +15,12 @@ import { GroupV2Change } from './GroupV2Change';
|
|||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const OUR_ID = 'OUR_ID';
|
||||
const CONTACT_A = 'CONTACT_A';
|
||||
const CONTACT_B = 'CONTACT_B';
|
||||
const CONTACT_C = 'CONTACT_C';
|
||||
const ADMIN_A = 'ADMIN_A';
|
||||
const INVITEE_A = 'INVITEE_A';
|
||||
const OUR_ID = UUID.generate().toString();
|
||||
const CONTACT_A = UUID.generate().toString();
|
||||
const CONTACT_B = UUID.generate().toString();
|
||||
const CONTACT_C = UUID.generate().toString();
|
||||
const ADMIN_A = UUID.generate().toString();
|
||||
const INVITEE_A = UUID.generate().toString();
|
||||
|
||||
const AccessControlEnum = Proto.AccessControl.AccessRequired;
|
||||
const RoleEnum = Proto.Member.Role;
|
||||
|
@ -35,7 +36,7 @@ const renderChange = (change: GroupV2ChangeType, groupName?: string) => (
|
|||
change={change}
|
||||
groupName={groupName}
|
||||
i18n={i18n}
|
||||
ourConversationId={OUR_ID}
|
||||
ourUuid={OUR_ID}
|
||||
renderContact={renderContact}
|
||||
/>
|
||||
);
|
||||
|
@ -62,7 +63,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
},
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
{
|
||||
type: 'description',
|
||||
|
@ -70,7 +71,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
},
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -402,7 +403,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -411,7 +412,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -419,7 +420,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -428,7 +429,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -437,7 +438,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -445,7 +446,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -461,7 +462,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -470,7 +471,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
inviter: CONTACT_A,
|
||||
},
|
||||
],
|
||||
|
@ -481,7 +482,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -491,7 +492,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_B,
|
||||
uuid: CONTACT_B,
|
||||
inviter: CONTACT_C,
|
||||
},
|
||||
],
|
||||
|
@ -500,7 +501,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -511,7 +512,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
inviter: CONTACT_A,
|
||||
},
|
||||
],
|
||||
|
@ -521,7 +522,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -530,7 +531,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
inviter: OUR_ID,
|
||||
},
|
||||
],
|
||||
|
@ -540,7 +541,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -550,7 +551,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-invite',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -565,7 +566,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-link',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -574,7 +575,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-link',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -582,7 +583,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-link',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -597,7 +598,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-admin-approval',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -605,7 +606,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-admin-approval',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -614,7 +615,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-admin-approval',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -623,7 +624,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-admin-approval',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -631,7 +632,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-add-from-admin-approval',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -646,7 +647,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -655,7 +656,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -663,7 +664,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -672,7 +673,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -681,7 +682,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -690,7 +691,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -698,7 +699,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-remove',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -713,7 +714,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -722,7 +723,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -732,7 +733,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -742,7 +743,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -751,7 +752,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.ADMINISTRATOR,
|
||||
},
|
||||
],
|
||||
|
@ -761,7 +762,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
newPrivilege: RoleEnum.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
@ -770,7 +771,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
newPrivilege: RoleEnum.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
@ -780,7 +781,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
@ -790,7 +791,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
@ -799,7 +800,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'member-privilege',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
newPrivilege: RoleEnum.DEFAULT,
|
||||
},
|
||||
],
|
||||
|
@ -815,7 +816,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-add-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -823,7 +824,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-add-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -832,7 +833,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-add-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -841,7 +842,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-add-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -849,7 +850,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-add-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -896,7 +897,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: OUR_ID,
|
||||
},
|
||||
],
|
||||
|
@ -906,7 +907,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: OUR_ID,
|
||||
},
|
||||
],
|
||||
|
@ -916,7 +917,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: OUR_ID,
|
||||
},
|
||||
],
|
||||
|
@ -925,7 +926,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: OUR_ID,
|
||||
},
|
||||
],
|
||||
|
@ -935,7 +936,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -944,7 +945,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -955,7 +956,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -965,7 +966,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: CONTACT_B,
|
||||
uuid: CONTACT_B,
|
||||
inviter: CONTACT_A,
|
||||
},
|
||||
],
|
||||
|
@ -976,7 +977,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -986,7 +987,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -995,7 +996,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
inviter: CONTACT_B,
|
||||
},
|
||||
],
|
||||
|
@ -1006,7 +1007,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1015,7 +1016,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1023,7 +1024,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'pending-remove-one',
|
||||
conversationId: INVITEE_A,
|
||||
uuid: INVITEE_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1128,7 +1129,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-add-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1136,7 +1137,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-add-one',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1151,7 +1152,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1159,7 +1160,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: OUR_ID,
|
||||
uuid: OUR_ID,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1168,7 +1169,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1177,7 +1178,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1186,7 +1187,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
@ -1194,7 +1195,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
|||
details: [
|
||||
{
|
||||
type: 'admin-approval-remove-one',
|
||||
conversationId: CONTACT_A,
|
||||
uuid: CONTACT_A,
|
||||
},
|
||||
],
|
||||
})}
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { ReplacementValuesType } from '../../types/I18N';
|
|||
import type { FullJSXType } from '../Intl';
|
||||
import { Intl } from '../Intl';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { GroupDescriptionText } from '../GroupDescriptionText';
|
||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||
import { SystemMessage } from './SystemMessage';
|
||||
|
@ -21,7 +22,7 @@ import { Modal } from '../Modal';
|
|||
|
||||
export type PropsDataType = {
|
||||
groupName?: string;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
change: GroupV2ChangeType;
|
||||
};
|
||||
|
||||
|
@ -78,11 +79,11 @@ const changeToIconMap = new Map<string, GroupIconType>([
|
|||
|
||||
function getIcon(
|
||||
detail: GroupV2ChangeDetailType,
|
||||
fromId?: string
|
||||
fromId?: UUIDStringType
|
||||
): GroupIconType {
|
||||
const changeType = detail.type;
|
||||
let possibleIcon = changeToIconMap.get(changeType);
|
||||
const isSameId = fromId === get(detail, 'conversationId', null);
|
||||
const isSameId = fromId === get(detail, 'uuid', null);
|
||||
if (isSameId) {
|
||||
if (changeType === 'member-remove') {
|
||||
possibleIcon = 'group-leave';
|
||||
|
@ -103,7 +104,7 @@ function GroupV2Detail({
|
|||
}: {
|
||||
detail: GroupV2ChangeDetailType;
|
||||
i18n: LocalizerType;
|
||||
fromId?: string;
|
||||
fromId?: UUIDStringType;
|
||||
onButtonClick: (x: string) => unknown;
|
||||
text: FullJSXType;
|
||||
}): JSX.Element {
|
||||
|
@ -132,7 +133,7 @@ function GroupV2Detail({
|
|||
}
|
||||
|
||||
export function GroupV2Change(props: PropsType): ReactElement {
|
||||
const { change, groupName, i18n, ourConversationId, renderContact } = props;
|
||||
const { change, groupName, i18n, ourUuid, renderContact } = props;
|
||||
|
||||
const [groupDescription, setGroupDescription] = useState<
|
||||
string | undefined
|
||||
|
@ -142,7 +143,7 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
|||
<>
|
||||
{renderChange(change, {
|
||||
i18n,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
renderContact,
|
||||
renderString: renderStringToIntl,
|
||||
}).map((text: FullJSXType, index: number) => (
|
||||
|
|
|
@ -301,8 +301,8 @@ const actions = () => ({
|
|||
),
|
||||
checkForAccount: action('checkForAccount'),
|
||||
clearChangedMessages: action('clearChangedMessages'),
|
||||
clearInvitedConversationsForNewlyCreatedGroup: action(
|
||||
'clearInvitedConversationsForNewlyCreatedGroup'
|
||||
clearInvitedUuidsForNewlyCreatedGroup: action(
|
||||
'clearInvitedUuidsForNewlyCreatedGroup'
|
||||
),
|
||||
setLoadCountdownStart: action('setLoadCountdownStart'),
|
||||
setIsNearBottom: action('setIsNearBottom'),
|
||||
|
|
|
@ -130,7 +130,7 @@ export type PropsActionsType = {
|
|||
groupNameCollisions: Readonly<GroupNameCollisionsWithIdsByTitle>
|
||||
) => void;
|
||||
clearChangedMessages: (conversationId: string) => unknown;
|
||||
clearInvitedConversationsForNewlyCreatedGroup: () => void;
|
||||
clearInvitedUuidsForNewlyCreatedGroup: () => void;
|
||||
closeContactSpoofingReview: () => void;
|
||||
setLoadCountdownStart: (
|
||||
conversationId: string,
|
||||
|
@ -231,7 +231,7 @@ const getActions = createSelector(
|
|||
const unsafe = pick(props, [
|
||||
'acknowledgeGroupMemberNameCollisions',
|
||||
'clearChangedMessages',
|
||||
'clearInvitedConversationsForNewlyCreatedGroup',
|
||||
'clearInvitedUuidsForNewlyCreatedGroup',
|
||||
'closeContactSpoofingReview',
|
||||
'setLoadCountdownStart',
|
||||
'setIsNearBottom',
|
||||
|
@ -1313,7 +1313,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
|||
const {
|
||||
acknowledgeGroupMemberNameCollisions,
|
||||
areWeAdmin,
|
||||
clearInvitedConversationsForNewlyCreatedGroup,
|
||||
clearInvitedUuidsForNewlyCreatedGroup,
|
||||
closeContactSpoofingReview,
|
||||
contactSpoofingReview,
|
||||
i18n,
|
||||
|
@ -1566,7 +1566,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
|||
<NewlyCreatedGroupInvitedContactsDialog
|
||||
contacts={invitedContactsForNewlyCreatedGroup}
|
||||
i18n={i18n}
|
||||
onClose={clearInvitedConversationsForNewlyCreatedGroup}
|
||||
onClose={clearInvitedUuidsForNewlyCreatedGroup}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { times } from 'lodash';
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { UUID } from '../../../types/UUID';
|
||||
import { setupI18n } from '../../../util/setupI18n';
|
||||
import enMessages from '../../../../_locales/en/messages.json';
|
||||
import type { PropsType } from './PendingInvites';
|
||||
|
@ -40,11 +41,13 @@ const conversation: ConversationType = {
|
|||
sharedGroupNames: [],
|
||||
};
|
||||
|
||||
const OUR_UUID = UUID.generate().toString();
|
||||
|
||||
const createProps = (): PropsType => ({
|
||||
approvePendingMembership: action('approvePendingMembership'),
|
||||
conversation,
|
||||
i18n,
|
||||
ourConversationId: 'abc123',
|
||||
ourUuid: OUR_UUID,
|
||||
pendingApprovalMemberships: times(5, () => ({
|
||||
member: getDefaultConversation(),
|
||||
})),
|
||||
|
@ -52,13 +55,13 @@ const createProps = (): PropsType => ({
|
|||
...times(4, () => ({
|
||||
member: getDefaultConversation(),
|
||||
metadata: {
|
||||
addedByUserId: 'abc123',
|
||||
addedByUserId: OUR_UUID,
|
||||
},
|
||||
})),
|
||||
...times(8, () => ({
|
||||
member: getDefaultConversation(),
|
||||
metadata: {
|
||||
addedByUserId: 'def456',
|
||||
addedByUserId: UUID.generate().toString(),
|
||||
},
|
||||
})),
|
||||
],
|
||||
|
|
|
@ -7,6 +7,7 @@ import _ from 'lodash';
|
|||
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import type { LocalizerType } from '../../../types/Util';
|
||||
import type { UUIDStringType } from '../../../types/UUID';
|
||||
import { Avatar } from '../../Avatar';
|
||||
import { ConfirmationDialog } from '../../ConfirmationDialog';
|
||||
import { PanelSection } from './PanelSection';
|
||||
|
@ -16,7 +17,7 @@ import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon';
|
|||
export type PropsType = {
|
||||
readonly conversation?: ConversationType;
|
||||
readonly i18n: LocalizerType;
|
||||
readonly ourConversationId?: string;
|
||||
readonly ourUuid?: UUIDStringType;
|
||||
readonly pendingApprovalMemberships: ReadonlyArray<GroupV2RequestingMembership>;
|
||||
readonly pendingMemberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||
readonly approvePendingMembership: (conversationId: string) => void;
|
||||
|
@ -25,7 +26,7 @@ export type PropsType = {
|
|||
|
||||
export type GroupV2PendingMembership = {
|
||||
metadata: {
|
||||
addedByUserId?: string;
|
||||
addedByUserId?: UUIDStringType;
|
||||
};
|
||||
member: ConversationType;
|
||||
};
|
||||
|
@ -54,14 +55,14 @@ export const PendingInvites: React.ComponentType<PropsType> = ({
|
|||
approvePendingMembership,
|
||||
conversation,
|
||||
i18n,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
pendingMemberships,
|
||||
pendingApprovalMemberships,
|
||||
revokePendingMemberships,
|
||||
}) => {
|
||||
if (!conversation || !ourConversationId) {
|
||||
if (!conversation || !ourUuid) {
|
||||
throw new Error(
|
||||
'PendingInvites rendered without a conversation or ourConversationId'
|
||||
'PendingInvites rendered without a conversation or ourUuid'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -131,7 +132,7 @@ export const PendingInvites: React.ComponentType<PropsType> = ({
|
|||
i18n={i18n}
|
||||
members={conversation.sortedGroupMembers || []}
|
||||
memberships={pendingMemberships}
|
||||
ourConversationId={ourConversationId}
|
||||
ourUuid={ourUuid}
|
||||
setStagedMemberships={setStagedMemberships}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -142,7 +143,7 @@ export const PendingInvites: React.ComponentType<PropsType> = ({
|
|||
i18n={i18n}
|
||||
members={conversation.sortedGroupMembers || []}
|
||||
onClose={() => setStagedMemberships(null)}
|
||||
ourConversationId={ourConversationId}
|
||||
ourUuid={ourUuid}
|
||||
revokePendingMemberships={revokePendingMemberships}
|
||||
stagedMemberships={stagedMemberships}
|
||||
/>
|
||||
|
@ -156,7 +157,7 @@ function MembershipActionConfirmation({
|
|||
i18n,
|
||||
members,
|
||||
onClose,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
revokePendingMemberships,
|
||||
stagedMemberships,
|
||||
}: {
|
||||
|
@ -164,7 +165,7 @@ function MembershipActionConfirmation({
|
|||
i18n: LocalizerType;
|
||||
members: Array<ConversationType>;
|
||||
onClose: () => void;
|
||||
ourConversationId: string;
|
||||
ourUuid: string;
|
||||
revokePendingMemberships: (conversationIds: Array<string>) => void;
|
||||
stagedMemberships: Array<StagedMembershipType>;
|
||||
}) {
|
||||
|
@ -216,7 +217,7 @@ function MembershipActionConfirmation({
|
|||
{getConfirmationMessage({
|
||||
i18n,
|
||||
members,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
stagedMemberships,
|
||||
})}
|
||||
</ConfirmationDialog>
|
||||
|
@ -226,12 +227,12 @@ function MembershipActionConfirmation({
|
|||
function getConfirmationMessage({
|
||||
i18n,
|
||||
members,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
stagedMemberships,
|
||||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
members: ReadonlyArray<ConversationType>;
|
||||
ourConversationId: string;
|
||||
ourUuid: string;
|
||||
stagedMemberships: ReadonlyArray<StagedMembershipType>;
|
||||
}>): string {
|
||||
if (!stagedMemberships || !stagedMemberships.length) {
|
||||
|
@ -261,8 +262,7 @@ function getConfirmationMessage({
|
|||
const firstPendingMembership = firstMembership as GroupV2PendingMembership;
|
||||
|
||||
// Pending invite
|
||||
const invitedByUs =
|
||||
firstPendingMembership.metadata.addedByUserId === ourConversationId;
|
||||
const invitedByUs = firstPendingMembership.metadata.addedByUserId === ourUuid;
|
||||
|
||||
if (invitedByUs) {
|
||||
return i18n('PendingInvites--revoke-for', {
|
||||
|
@ -364,14 +364,14 @@ function MembersPendingProfileKey({
|
|||
i18n,
|
||||
members,
|
||||
memberships,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
setStagedMemberships,
|
||||
}: Readonly<{
|
||||
conversation: ConversationType;
|
||||
i18n: LocalizerType;
|
||||
members: Array<ConversationType>;
|
||||
memberships: ReadonlyArray<GroupV2PendingMembership>;
|
||||
ourConversationId: string;
|
||||
ourUuid: string;
|
||||
setStagedMemberships: (stagedMembership: Array<StagedMembershipType>) => void;
|
||||
}>) {
|
||||
const groupedPendingMemberships = _.groupBy(
|
||||
|
@ -380,7 +380,7 @@ function MembersPendingProfileKey({
|
|||
);
|
||||
|
||||
const {
|
||||
[ourConversationId]: ourPendingMemberships,
|
||||
[ourUuid]: ourPendingMemberships,
|
||||
...otherPendingMembershipGroups
|
||||
} = groupedPendingMemberships;
|
||||
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
import type { FullJSXType } from './components/Intl';
|
||||
import type { LocalizerType } from './types/Util';
|
||||
import type { ReplacementValuesType } from './types/I18N';
|
||||
import type { UUIDStringType } from './types/UUID';
|
||||
import { missingCaseError } from './util/missingCaseError';
|
||||
|
||||
import type { GroupV2ChangeDetailType, GroupV2ChangeType } from './groups';
|
||||
import { SignalService as Proto } from './protobuf';
|
||||
import * as log from './logging/log';
|
||||
|
||||
export type SmartContactRendererType = (conversationId: string) => FullJSXType;
|
||||
export type SmartContactRendererType = (uuid: UUIDStringType) => FullJSXType;
|
||||
export type StringRendererType = (
|
||||
id: string,
|
||||
i18n: LocalizerType,
|
||||
|
@ -18,9 +19,9 @@ export type StringRendererType = (
|
|||
) => FullJSXType;
|
||||
|
||||
export type RenderOptionsType = {
|
||||
from?: string;
|
||||
from?: UUIDStringType;
|
||||
i18n: LocalizerType;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
renderContact: SmartContactRendererType;
|
||||
renderString: StringRendererType;
|
||||
};
|
||||
|
@ -46,14 +47,8 @@ export function renderChangeDetail(
|
|||
detail: GroupV2ChangeDetailType,
|
||||
options: RenderOptionsType
|
||||
): FullJSXType {
|
||||
const {
|
||||
from,
|
||||
i18n,
|
||||
ourConversationId,
|
||||
renderContact,
|
||||
renderString,
|
||||
} = options;
|
||||
const fromYou = Boolean(from && from === ourConversationId);
|
||||
const { from, i18n, ourUuid, renderContact, renderString } = options;
|
||||
const fromYou = Boolean(from && from === ourUuid);
|
||||
|
||||
if (detail.type === 'create') {
|
||||
if (fromYou) {
|
||||
|
@ -214,8 +209,8 @@ export function renderChangeDetail(
|
|||
return '';
|
||||
}
|
||||
if (detail.type === 'member-add') {
|
||||
const { conversationId } = detail;
|
||||
const weAreJoiner = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreJoiner = uuid === ourUuid;
|
||||
|
||||
if (weAreJoiner) {
|
||||
if (fromYou) {
|
||||
|
@ -230,25 +225,25 @@ export function renderChangeDetail(
|
|||
}
|
||||
if (fromYou) {
|
||||
return renderString('GroupV2--member-add--other--you', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (from) {
|
||||
return renderString('GroupV2--member-add--other--other', i18n, {
|
||||
adderName: renderContact(from),
|
||||
addeeName: renderContact(conversationId),
|
||||
addeeName: renderContact(uuid),
|
||||
});
|
||||
}
|
||||
return renderString('GroupV2--member-add--other--unknown', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (detail.type === 'member-add-from-invite') {
|
||||
const { conversationId, inviter } = detail;
|
||||
const weAreJoiner = conversationId === ourConversationId;
|
||||
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
|
||||
const { uuid, inviter } = detail;
|
||||
const weAreJoiner = uuid === ourUuid;
|
||||
const weAreInviter = Boolean(inviter && inviter === ourUuid);
|
||||
|
||||
if (!from || from !== conversationId) {
|
||||
if (!from || from !== uuid) {
|
||||
if (weAreJoiner) {
|
||||
// They can't be the same, no fromYou check here
|
||||
if (from) {
|
||||
|
@ -261,17 +256,17 @@ export function renderChangeDetail(
|
|||
|
||||
if (fromYou) {
|
||||
return renderString('GroupV2--member-add--invited--you', i18n, {
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
});
|
||||
}
|
||||
if (from) {
|
||||
return renderString('GroupV2--member-add--invited--other', i18n, {
|
||||
memberName: renderContact(from),
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
});
|
||||
}
|
||||
return renderString('GroupV2--member-add--invited--unknown', i18n, {
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -288,12 +283,12 @@ export function renderChangeDetail(
|
|||
}
|
||||
if (weAreInviter) {
|
||||
return renderString('GroupV2--member-add--from-invite--from-you', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (inviter) {
|
||||
return renderString('GroupV2--member-add--from-invite--other', i18n, {
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
inviterName: renderContact(inviter),
|
||||
});
|
||||
}
|
||||
|
@ -301,17 +296,17 @@ export function renderChangeDetail(
|
|||
'GroupV2--member-add--from-invite--other-no-from',
|
||||
i18n,
|
||||
{
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
if (detail.type === 'member-add-from-link') {
|
||||
const { conversationId } = detail;
|
||||
const { uuid } = detail;
|
||||
|
||||
if (fromYou && conversationId === ourConversationId) {
|
||||
if (fromYou && uuid === ourUuid) {
|
||||
return renderString('GroupV2--member-add-from-link--you--you', i18n);
|
||||
}
|
||||
if (from && conversationId === from) {
|
||||
if (from && uuid === from) {
|
||||
return renderString('GroupV2--member-add-from-link--other', i18n, [
|
||||
renderContact(from),
|
||||
]);
|
||||
|
@ -321,12 +316,12 @@ export function renderChangeDetail(
|
|||
// from group change events, which always have a sender.
|
||||
log.warn('member-add-from-link change type; we have no from!');
|
||||
return renderString('GroupV2--member-add--other--unknown', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (detail.type === 'member-add-from-admin-approval') {
|
||||
const { conversationId } = detail;
|
||||
const weAreJoiner = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreJoiner = uuid === ourUuid;
|
||||
|
||||
if (weAreJoiner) {
|
||||
if (from) {
|
||||
|
@ -352,7 +347,7 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--member-add-from-admin-approval--other--you',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -361,7 +356,7 @@ export function renderChangeDetail(
|
|||
i18n,
|
||||
{
|
||||
adminName: renderContact(from),
|
||||
joinerName: renderContact(conversationId),
|
||||
joinerName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -372,12 +367,12 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--member-add-from-admin-approval--other--unknown',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (detail.type === 'member-remove') {
|
||||
const { conversationId } = detail;
|
||||
const weAreLeaver = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreLeaver = uuid === ourUuid;
|
||||
|
||||
if (weAreLeaver) {
|
||||
if (fromYou) {
|
||||
|
@ -393,10 +388,10 @@ export function renderChangeDetail(
|
|||
|
||||
if (fromYou) {
|
||||
return renderString('GroupV2--member-remove--other--you', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (from && from === conversationId) {
|
||||
if (from && from === uuid) {
|
||||
return renderString('GroupV2--member-remove--other--self', i18n, [
|
||||
renderContact(from),
|
||||
]);
|
||||
|
@ -404,16 +399,16 @@ export function renderChangeDetail(
|
|||
if (from) {
|
||||
return renderString('GroupV2--member-remove--other--other', i18n, {
|
||||
adminName: renderContact(from),
|
||||
memberName: renderContact(conversationId),
|
||||
memberName: renderContact(uuid),
|
||||
});
|
||||
}
|
||||
return renderString('GroupV2--member-remove--other--unknown', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (detail.type === 'member-privilege') {
|
||||
const { conversationId, newPrivilege } = detail;
|
||||
const weAreMember = conversationId === ourConversationId;
|
||||
const { uuid, newPrivilege } = detail;
|
||||
const weAreMember = uuid === ourUuid;
|
||||
|
||||
if (newPrivilege === RoleEnum.ADMINISTRATOR) {
|
||||
if (weAreMember) {
|
||||
|
@ -435,7 +430,7 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--member-privilege--promote--other--you',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -444,14 +439,14 @@ export function renderChangeDetail(
|
|||
i18n,
|
||||
{
|
||||
adminName: renderContact(from),
|
||||
memberName: renderContact(conversationId),
|
||||
memberName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
return renderString(
|
||||
'GroupV2--member-privilege--promote--other--unknown',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (newPrivilege === RoleEnum.DEFAULT) {
|
||||
|
@ -473,7 +468,7 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--member-privilege--demote--other--you',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -482,14 +477,14 @@ export function renderChangeDetail(
|
|||
i18n,
|
||||
{
|
||||
adminName: renderContact(from),
|
||||
memberName: renderContact(conversationId),
|
||||
memberName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
return renderString(
|
||||
'GroupV2--member-privilege--demote--other--unknown',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
log.warn(
|
||||
|
@ -498,8 +493,8 @@ export function renderChangeDetail(
|
|||
return '';
|
||||
}
|
||||
if (detail.type === 'pending-add-one') {
|
||||
const { conversationId } = detail;
|
||||
const weAreInvited = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreInvited = uuid === ourUuid;
|
||||
if (weAreInvited) {
|
||||
if (from) {
|
||||
return renderString('GroupV2--pending-add--one--you--other', i18n, [
|
||||
|
@ -510,7 +505,7 @@ export function renderChangeDetail(
|
|||
}
|
||||
if (fromYou) {
|
||||
return renderString('GroupV2--pending-add--one--other--you', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -539,23 +534,23 @@ export function renderChangeDetail(
|
|||
]);
|
||||
}
|
||||
if (detail.type === 'pending-remove-one') {
|
||||
const { inviter, conversationId } = detail;
|
||||
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
|
||||
const weAreInvited = conversationId === ourConversationId;
|
||||
const sentByInvited = Boolean(from && from === conversationId);
|
||||
const { inviter, uuid } = detail;
|
||||
const weAreInviter = Boolean(inviter && inviter === ourUuid);
|
||||
const weAreInvited = uuid === ourUuid;
|
||||
const sentByInvited = Boolean(from && from === uuid);
|
||||
const sentByInviter = Boolean(from && inviter && from === inviter);
|
||||
|
||||
if (weAreInviter) {
|
||||
if (sentByInvited) {
|
||||
return renderString('GroupV2--pending-remove--decline--you', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (fromYou) {
|
||||
return renderString(
|
||||
'GroupV2--pending-remove--revoke-invite-from-you--one--you',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -564,14 +559,14 @@ export function renderChangeDetail(
|
|||
i18n,
|
||||
{
|
||||
adminName: renderContact(from),
|
||||
inviteeName: renderContact(conversationId),
|
||||
inviteeName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
return renderString(
|
||||
'GroupV2--pending-remove--revoke-invite-from-you--one--unknown',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (sentByInvited) {
|
||||
|
@ -635,7 +630,7 @@ export function renderChangeDetail(
|
|||
}
|
||||
if (detail.type === 'pending-remove-many') {
|
||||
const { count, inviter } = detail;
|
||||
const weAreInviter = Boolean(inviter && inviter === ourConversationId);
|
||||
const weAreInviter = Boolean(inviter && inviter === ourUuid);
|
||||
|
||||
if (weAreInviter) {
|
||||
if (fromYou) {
|
||||
|
@ -714,19 +709,19 @@ export function renderChangeDetail(
|
|||
);
|
||||
}
|
||||
if (detail.type === 'admin-approval-add-one') {
|
||||
const { conversationId } = detail;
|
||||
const weAreJoiner = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreJoiner = uuid === ourUuid;
|
||||
|
||||
if (weAreJoiner) {
|
||||
return renderString('GroupV2--admin-approval-add-one--you', i18n);
|
||||
}
|
||||
return renderString('GroupV2--admin-approval-add-one--other', i18n, [
|
||||
renderContact(conversationId),
|
||||
renderContact(uuid),
|
||||
]);
|
||||
}
|
||||
if (detail.type === 'admin-approval-remove-one') {
|
||||
const { conversationId } = detail;
|
||||
const weAreJoiner = conversationId === ourConversationId;
|
||||
const { uuid } = detail;
|
||||
const weAreJoiner = uuid === ourUuid;
|
||||
|
||||
if (weAreJoiner) {
|
||||
if (fromYou) {
|
||||
|
@ -745,14 +740,14 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--admin-approval-remove-one--other--you',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from && from === conversationId) {
|
||||
if (from && from === uuid) {
|
||||
return renderString(
|
||||
'GroupV2--admin-approval-remove-one--other--own',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (from) {
|
||||
|
@ -761,7 +756,7 @@ export function renderChangeDetail(
|
|||
i18n,
|
||||
{
|
||||
adminName: renderContact(from),
|
||||
joinerName: renderContact(conversationId),
|
||||
joinerName: renderContact(uuid),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -771,7 +766,7 @@ export function renderChangeDetail(
|
|||
return renderString(
|
||||
'GroupV2--admin-approval-remove-one--other--own',
|
||||
i18n,
|
||||
[renderContact(conversationId)]
|
||||
[renderContact(uuid)]
|
||||
);
|
||||
}
|
||||
if (detail.type === 'group-link-add') {
|
||||
|
|
607
ts/groups.ts
607
ts/groups.ts
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@ import { isDirectConversation } from '../util/whatTypeOfConversation';
|
|||
import { getOwn } from '../util/getOwn';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import { createWaitBatcher } from '../util/waitBatcher';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import {
|
||||
SendActionType,
|
||||
SendStatus,
|
||||
|
@ -34,6 +35,7 @@ export enum MessageReceiptType {
|
|||
type MessageReceiptAttributesType = {
|
||||
messageSentAt: number;
|
||||
receiptTimestamp: number;
|
||||
sourceUuid: UUIDStringType;
|
||||
sourceConversationId: string;
|
||||
sourceDevice: number;
|
||||
type: MessageReceiptType;
|
||||
|
@ -57,6 +59,7 @@ const deleteSentProtoBatcher = createWaitBatcher({
|
|||
|
||||
async function getTargetMessage(
|
||||
sourceId: string,
|
||||
sourceUuid: UUIDStringType,
|
||||
messages: MessageModelCollectionType
|
||||
): Promise<MessageModel | null> {
|
||||
if (messages.length === 0) {
|
||||
|
@ -70,9 +73,12 @@ async function getTargetMessage(
|
|||
return window.MessageController.register(message.id, message);
|
||||
}
|
||||
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingId(sourceId, {
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
});
|
||||
const groups = await window.Signal.Data.getAllGroupsInvolvingUuid(
|
||||
sourceUuid,
|
||||
{
|
||||
ConversationCollection: window.Whisper.ConversationCollection,
|
||||
}
|
||||
);
|
||||
|
||||
const ids = groups.pluck('id');
|
||||
ids.push(sourceId);
|
||||
|
@ -136,6 +142,7 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
|
|||
const type = receipt.get('type');
|
||||
const messageSentAt = receipt.get('messageSentAt');
|
||||
const sourceConversationId = receipt.get('sourceConversationId');
|
||||
const sourceUuid = receipt.get('sourceUuid');
|
||||
|
||||
try {
|
||||
const messages = await window.Signal.Data.getMessagesBySentAt(
|
||||
|
@ -145,7 +152,11 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
|
|||
}
|
||||
);
|
||||
|
||||
const message = await getTargetMessage(sourceConversationId, messages);
|
||||
const message = await getTargetMessage(
|
||||
sourceConversationId,
|
||||
sourceUuid,
|
||||
messages
|
||||
);
|
||||
if (!message) {
|
||||
log.info(
|
||||
'No message for receipt',
|
||||
|
|
13
ts/model-types.d.ts
vendored
13
ts/model-types.d.ts
vendored
|
@ -26,6 +26,7 @@ import { AttachmentType, ThumbnailType } from './types/Attachment';
|
|||
import { EmbeddedContactType } from './types/EmbeddedContact';
|
||||
import { SignalService as Proto } from './protobuf';
|
||||
import { AvatarDataType } from './types/Avatar';
|
||||
import { UUIDStringType } from './types/UUID';
|
||||
|
||||
import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
|
||||
import MemberRoleEnum = Proto.Member.Role;
|
||||
|
@ -181,7 +182,7 @@ export type MessageAttributesType = {
|
|||
serverGuid?: string;
|
||||
serverTimestamp?: number;
|
||||
source?: string;
|
||||
sourceUuid?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
|
||||
timestamp: number;
|
||||
|
||||
|
@ -253,7 +254,7 @@ export type ConversationAttributesType = {
|
|||
version: number;
|
||||
|
||||
// Private core info
|
||||
uuid?: string;
|
||||
uuid?: UUIDStringType;
|
||||
e164?: string;
|
||||
|
||||
// Private other fields
|
||||
|
@ -327,7 +328,7 @@ export type ConversationAttributesType = {
|
|||
};
|
||||
|
||||
export type GroupV2MemberType = {
|
||||
conversationId: string;
|
||||
uuid: UUIDStringType;
|
||||
role: MemberRoleEnum;
|
||||
joinedAtVersion: number;
|
||||
|
||||
|
@ -339,14 +340,14 @@ export type GroupV2MemberType = {
|
|||
};
|
||||
|
||||
export type GroupV2PendingMemberType = {
|
||||
addedByUserId?: string;
|
||||
conversationId: string;
|
||||
addedByUserId?: UUIDStringType;
|
||||
uuid: UUIDStringType;
|
||||
timestamp: number;
|
||||
role: MemberRoleEnum;
|
||||
};
|
||||
|
||||
export type GroupV2PendingAdminApprovalType = {
|
||||
conversationId: string;
|
||||
uuid: UUIDStringType;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
|
|
|
@ -46,7 +46,8 @@ import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
|||
import { isValidE164 } from '../util/isValidE164';
|
||||
import type { MIMEType } from '../types/MIME';
|
||||
import { IMAGE_JPEG, IMAGE_GIF, IMAGE_WEBP } from '../types/MIME';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { UUID, isValidUuid } from '../types/UUID';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { deriveAccessKey, decryptProfileName, decryptProfile } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import type { BodyRangesType } from '../types/Util';
|
||||
|
@ -242,7 +243,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
initialize(attributes: Partial<ConversationAttributesType> = {}): void {
|
||||
if (isValidE164(attributes.id, false)) {
|
||||
this.set({ id: window.getGuid(), e164: attributes.id });
|
||||
this.set({ id: UUID.generate().toString(), e164: attributes.id });
|
||||
}
|
||||
|
||||
this.storeName = 'conversations';
|
||||
|
@ -340,7 +341,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
}
|
||||
|
||||
isMemberRequestingToJoin(conversationId: string): boolean {
|
||||
isMemberRequestingToJoin(id: string): boolean {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -350,12 +351,11 @@ export class ConversationModel extends window.Backbone
|
|||
return false;
|
||||
}
|
||||
|
||||
return pendingAdminApprovalV2.some(
|
||||
item => item.conversationId === conversationId
|
||||
);
|
||||
const uuid = UUID.checkedLookup(id).toString();
|
||||
return pendingAdminApprovalV2.some(item => item.uuid === uuid);
|
||||
}
|
||||
|
||||
isMemberPending(conversationId: string): boolean {
|
||||
isMemberPending(id: string): boolean {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -365,13 +365,11 @@ export class ConversationModel extends window.Backbone
|
|||
return false;
|
||||
}
|
||||
|
||||
return window._.any(
|
||||
pendingMembersV2,
|
||||
item => item.conversationId === conversationId
|
||||
);
|
||||
const uuid = UUID.checkedLookup(id).toString();
|
||||
return window._.any(pendingMembersV2, item => item.uuid === uuid);
|
||||
}
|
||||
|
||||
isMemberAwaitingApproval(conversationId: string): boolean {
|
||||
isMemberAwaitingApproval(id: string): boolean {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -381,13 +379,11 @@ export class ConversationModel extends window.Backbone
|
|||
return false;
|
||||
}
|
||||
|
||||
return window._.any(
|
||||
pendingAdminApprovalV2,
|
||||
item => item.conversationId === conversationId
|
||||
);
|
||||
const uuid = UUID.checkedLookup(id).toString();
|
||||
return window._.any(pendingAdminApprovalV2, item => item.uuid === uuid);
|
||||
}
|
||||
|
||||
isMember(conversationId: string): boolean {
|
||||
isMember(id: string): boolean {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
throw new Error(
|
||||
`isMember: Called for non-GroupV2 conversation ${this.idForLogging()}`
|
||||
|
@ -398,11 +394,9 @@ export class ConversationModel extends window.Backbone
|
|||
if (!membersV2 || !membersV2.length) {
|
||||
return false;
|
||||
}
|
||||
const uuid = UUID.checkedLookup(id).toString();
|
||||
|
||||
return window._.any(
|
||||
membersV2,
|
||||
item => item.conversationId === conversationId
|
||||
);
|
||||
return window._.any(membersV2, item => item.uuid === uuid);
|
||||
}
|
||||
|
||||
async updateExpirationTimerInGroupV2(
|
||||
|
@ -678,7 +672,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
return uuid;
|
||||
})
|
||||
.filter((uuid): uuid is string => Boolean(uuid));
|
||||
.filter(isNotNil);
|
||||
|
||||
if (!uuids.length) {
|
||||
return undefined;
|
||||
|
@ -1565,7 +1559,7 @@ export class ConversationModel extends window.Backbone
|
|||
updateUuid(uuid?: string): void {
|
||||
const oldValue = this.get('uuid');
|
||||
if (uuid && uuid !== oldValue) {
|
||||
this.set('uuid', uuid.toLowerCase());
|
||||
this.set('uuid', UUID.cast(uuid.toLowerCase()));
|
||||
window.Signal.Data.updateConversation(this.attributes);
|
||||
this.trigger('idUpdated', this, 'uuid', oldValue);
|
||||
}
|
||||
|
@ -1840,6 +1834,7 @@ export class ConversationModel extends window.Backbone
|
|||
approvalRequired: boolean;
|
||||
}): Promise<void> {
|
||||
const ourConversationId = window.ConversationController.getOurConversationIdOrThrow();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
|
||||
try {
|
||||
if (approvalRequired) {
|
||||
await this.modifyGroupV2({
|
||||
|
@ -1870,7 +1865,7 @@ export class ConversationModel extends window.Backbone
|
|||
this.set({
|
||||
pendingAdminApprovalV2: [
|
||||
{
|
||||
conversationId: ourConversationId,
|
||||
uuid: ourUuid,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
|
@ -2143,14 +2138,12 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
async safeGetVerified(): Promise<number> {
|
||||
const uuid = this.get('uuid');
|
||||
const uuid = this.getUuid();
|
||||
if (!uuid) {
|
||||
return window.textsecure.storage.protocol.VerifiedStatus.DEFAULT;
|
||||
}
|
||||
|
||||
const promise = window.textsecure.storage.protocol.getVerified(
|
||||
new UUID(uuid)
|
||||
);
|
||||
const promise = window.textsecure.storage.protocol.getVerified(uuid);
|
||||
return promise.catch(
|
||||
() => window.textsecure.storage.protocol.VerifiedStatus.DEFAULT
|
||||
);
|
||||
|
@ -2227,7 +2220,7 @@ export class ConversationModel extends window.Backbone
|
|||
);
|
||||
}
|
||||
|
||||
const uuid = this.get('uuid');
|
||||
const uuid = this.getUuid();
|
||||
const beginningVerified = this.get('verified');
|
||||
let keyChange;
|
||||
if (options.viaSyncMessage) {
|
||||
|
@ -2239,13 +2232,13 @@ export class ConversationModel extends window.Backbone
|
|||
// handle the incoming key from the sync messages - need different
|
||||
// behavior if that key doesn't match the current key
|
||||
keyChange = await window.textsecure.storage.protocol.processVerifiedMessage(
|
||||
new UUID(uuid),
|
||||
uuid,
|
||||
verified,
|
||||
options.key || undefined
|
||||
);
|
||||
} else if (uuid) {
|
||||
keyChange = await window.textsecure.storage.protocol.setVerified(
|
||||
new UUID(uuid),
|
||||
uuid,
|
||||
verified
|
||||
);
|
||||
} else {
|
||||
|
@ -2289,10 +2282,10 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
async sendVerifySyncMessage(
|
||||
e164: string | undefined,
|
||||
uuid: string,
|
||||
uuid: UUID,
|
||||
state: number
|
||||
): Promise<CallbackResultType | void> {
|
||||
const identifier = uuid || e164;
|
||||
const identifier = uuid ? uuid.toString() : e164;
|
||||
if (!identifier) {
|
||||
throw new Error(
|
||||
'sendVerifySyncMessage: Neither e164 nor UUID were provided'
|
||||
|
@ -2328,7 +2321,7 @@ export class ConversationModel extends window.Backbone
|
|||
await handleMessageSend(
|
||||
window.textsecure.messaging.syncVerification(
|
||||
e164,
|
||||
uuid,
|
||||
uuid.toString(),
|
||||
state,
|
||||
key,
|
||||
options
|
||||
|
@ -2402,20 +2395,20 @@ export class ConversationModel extends window.Backbone
|
|||
);
|
||||
}
|
||||
|
||||
const uuid = this.get('uuid');
|
||||
const uuid = this.getUuid();
|
||||
if (!uuid) {
|
||||
log.warn(`setApproved(${this.id}): no uuid, ignoring`);
|
||||
return;
|
||||
}
|
||||
|
||||
return window.textsecure.storage.protocol.setApproval(new UUID(uuid), true);
|
||||
return window.textsecure.storage.protocol.setApproval(uuid, true);
|
||||
}
|
||||
|
||||
safeIsUntrusted(): boolean {
|
||||
const uuid = this.get('uuid');
|
||||
try {
|
||||
const uuid = this.getUuid();
|
||||
strictAssert(uuid, `No uuid for conversation: ${this.id}`);
|
||||
return window.textsecure.storage.protocol.isUntrusted(new UUID(uuid));
|
||||
return window.textsecure.storage.protocol.isUntrusted(uuid);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2671,8 +2664,9 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
this.trigger('newmessage', model);
|
||||
|
||||
if (isDirectConversation(this.attributes)) {
|
||||
window.ConversationController.getAllGroupsInvolvingId(this.id).then(
|
||||
const uuid = this.getUuid();
|
||||
if (isDirectConversation(this.attributes) && uuid) {
|
||||
window.ConversationController.getAllGroupsInvolvingUuid(uuid).then(
|
||||
groups => {
|
||||
window._.forEach(groups, group => {
|
||||
group.addVerifiedChange(this.id, verified, options);
|
||||
|
@ -2790,8 +2784,9 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
this.trigger('newmessage', model);
|
||||
|
||||
if (isDirectConversation(this.attributes)) {
|
||||
window.ConversationController.getAllGroupsInvolvingId(this.id).then(
|
||||
const uuid = this.getUuid();
|
||||
if (isDirectConversation(this.attributes) && uuid) {
|
||||
window.ConversationController.getAllGroupsInvolvingUuid(uuid).then(
|
||||
groups => {
|
||||
window._.forEach(groups, group => {
|
||||
group.addProfileChange(profileChange, this.id);
|
||||
|
@ -2899,17 +2894,21 @@ export class ConversationModel extends window.Backbone
|
|||
`Conversation ${this.idForLogging()}: adding change number notification`
|
||||
);
|
||||
|
||||
const sourceUuid = this.getCheckedUuid(
|
||||
'Change number notification without uuid'
|
||||
);
|
||||
|
||||
const convos = [
|
||||
this,
|
||||
...(await window.ConversationController.getAllGroupsInvolvingId(this.id)),
|
||||
...(await window.ConversationController.getAllGroupsInvolvingUuid(
|
||||
sourceUuid
|
||||
)),
|
||||
];
|
||||
|
||||
const sourceUuid = this.get('uuid');
|
||||
|
||||
await Promise.all(
|
||||
convos.map(convo => {
|
||||
return convo.addNotification('change-number-notification', {
|
||||
sourceUuid,
|
||||
sourceUuid: sourceUuid.toString(),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
@ -2998,7 +2997,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
validateUuid(): string | null {
|
||||
if (isDirectConversation(this.attributes) && this.get('uuid')) {
|
||||
if (window.isValidGuid(this.get('uuid'))) {
|
||||
if (isValidUuid(this.get('uuid'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -3037,13 +3036,14 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
}
|
||||
|
||||
isAdmin(conversationId: string): boolean {
|
||||
isAdmin(id: string): boolean {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uuid = UUID.checkedLookup(id).toString();
|
||||
const members = this.get('membersV2') || [];
|
||||
const member = members.find(x => x.conversationId === conversationId);
|
||||
const member = members.find(x => x.uuid === uuid);
|
||||
if (!member) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3053,8 +3053,19 @@ export class ConversationModel extends window.Backbone
|
|||
return member.role === MEMBER_ROLES.ADMINISTRATOR;
|
||||
}
|
||||
|
||||
getUuid(): UUID | undefined {
|
||||
const value = this.get('uuid');
|
||||
return value && new UUID(value);
|
||||
}
|
||||
|
||||
getCheckedUuid(reason: string): UUID {
|
||||
const result = this.getUuid();
|
||||
strictAssert(result !== undefined, reason);
|
||||
return result;
|
||||
}
|
||||
|
||||
private getMemberships(): Array<{
|
||||
conversationId: string;
|
||||
uuid: UUIDStringType;
|
||||
isAdmin: boolean;
|
||||
}> {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
|
@ -3064,7 +3075,7 @@ export class ConversationModel extends window.Backbone
|
|||
const members = this.get('membersV2') || [];
|
||||
return members.map(member => ({
|
||||
isAdmin: member.role === Proto.Member.Role.ADMINISTRATOR,
|
||||
conversationId: member.conversationId,
|
||||
uuid: member.uuid,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -3081,8 +3092,8 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
private getPendingMemberships(): Array<{
|
||||
addedByUserId?: string;
|
||||
conversationId: string;
|
||||
addedByUserId?: UUIDStringType;
|
||||
uuid: UUIDStringType;
|
||||
}> {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return [];
|
||||
|
@ -3091,18 +3102,18 @@ export class ConversationModel extends window.Backbone
|
|||
const members = this.get('pendingMembersV2') || [];
|
||||
return members.map(member => ({
|
||||
addedByUserId: member.addedByUserId,
|
||||
conversationId: member.conversationId,
|
||||
uuid: member.uuid,
|
||||
}));
|
||||
}
|
||||
|
||||
private getPendingApprovalMemberships(): Array<{ conversationId: string }> {
|
||||
private getPendingApprovalMemberships(): Array<{ uuid: UUIDStringType }> {
|
||||
if (!isGroupV2(this.attributes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const members = this.get('pendingAdminApprovalV2') || [];
|
||||
return members.map(member => ({
|
||||
conversationId: member.conversationId,
|
||||
uuid: member.uuid,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -3136,6 +3147,13 @@ export class ConversationModel extends window.Backbone
|
|||
return members.map(member => member.id);
|
||||
}
|
||||
|
||||
getMemberUuids(): Array<UUID> {
|
||||
const members = this.getMembers();
|
||||
return members.map(member => {
|
||||
return member.getCheckedUuid('Group member without uuid');
|
||||
});
|
||||
}
|
||||
|
||||
getRecipients({
|
||||
includePendingMembers,
|
||||
extraConversationsForSend,
|
||||
|
@ -3356,7 +3374,7 @@ export class ConversationModel extends window.Backbone
|
|||
// We are only creating this model so we can use its sync message
|
||||
// sending functionality. It will not be saved to the database.
|
||||
const message = new window.Whisper.Message({
|
||||
id: window.getGuid(),
|
||||
id: UUID.generate().toString(),
|
||||
type: 'outgoing',
|
||||
conversationId: this.get('id'),
|
||||
sent_at: timestamp,
|
||||
|
@ -3489,7 +3507,7 @@ export class ConversationModel extends window.Backbone
|
|||
// We are only creating this model so we can use its sync message
|
||||
// sending functionality. It will not be saved to the database.
|
||||
const message = new window.Whisper.Message({
|
||||
id: window.getGuid(),
|
||||
id: UUID.generate.toString(),
|
||||
type: 'outgoing',
|
||||
conversationId: this.get('id'),
|
||||
sent_at: timestamp,
|
||||
|
@ -3731,7 +3749,7 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
const attributes: MessageAttributesType = {
|
||||
...messageWithSchema,
|
||||
id: window.getGuid(),
|
||||
id: UUID.generate().toString(),
|
||||
};
|
||||
|
||||
const model = new window.Whisper.Message(attributes);
|
||||
|
@ -3840,9 +3858,10 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const conversationId = this.id;
|
||||
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
|
||||
const lastMessages = await window.Signal.Data.getLastConversationMessages({
|
||||
conversationId,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
Message: window.Whisper.Message,
|
||||
});
|
||||
|
||||
|
@ -4361,12 +4380,16 @@ export class ConversationModel extends window.Backbone
|
|||
return;
|
||||
}
|
||||
|
||||
const ourGroups = await window.ConversationController.getAllGroupsInvolvingId(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
window.ConversationController.getOurConversationId()!
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourGroups = await window.ConversationController.getAllGroupsInvolvingUuid(
|
||||
ourUuid
|
||||
);
|
||||
const theirGroups = await window.ConversationController.getAllGroupsInvolvingId(
|
||||
this.id
|
||||
const theirUuid = this.getUuid();
|
||||
if (!theirUuid) {
|
||||
return;
|
||||
}
|
||||
const theirGroups = await window.ConversationController.getAllGroupsInvolvingUuid(
|
||||
theirUuid
|
||||
);
|
||||
|
||||
const sharedGroups = window._.intersection(ourGroups, theirGroups);
|
||||
|
@ -4734,8 +4757,8 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
const memberEnum = Proto.Member.Role;
|
||||
const members = this.get('membersV2') || [];
|
||||
const myId = window.ConversationController.getOurConversationId();
|
||||
const me = members.find(item => item.conversationId === myId);
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
|
||||
const me = members.find(item => item.uuid === ourUuid);
|
||||
if (!me) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import { SendMessageProtoError } from '../textsecure/Errors';
|
|||
import * as expirationTimer from '../util/expirationTimer';
|
||||
|
||||
import type { ReactionType } from '../types/Reactions';
|
||||
import { UUID } from '../types/UUID';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import {
|
||||
copyStickerToAttachments,
|
||||
deletePackReference,
|
||||
|
@ -181,8 +183,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
INITIAL_PROTOCOL_VERSION?: number;
|
||||
|
||||
OUR_UUID?: string;
|
||||
|
||||
isSelected?: boolean;
|
||||
|
||||
private pendingMarkRead?: number;
|
||||
|
@ -223,7 +223,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
this.CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT;
|
||||
this.INITIAL_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.INITIAL;
|
||||
this.OUR_UUID = window.textsecure.storage.user.getUuid()?.toString();
|
||||
|
||||
this.on('change', this.notifyRedux);
|
||||
}
|
||||
|
@ -385,7 +384,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
conversationSelector: findAndFormatContact,
|
||||
ourConversationId,
|
||||
ourNumber: window.textsecure.storage.user.getNumber(),
|
||||
ourUuid: this.OUR_UUID,
|
||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||
regionCode: window.storage.get('regionCode', 'ZZ'),
|
||||
accountSelector: (identifier?: string) => {
|
||||
const state = window.reduxStore.getState();
|
||||
|
@ -1104,7 +1103,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return sourceDevice || window.textsecure.storage.user.getDeviceId();
|
||||
}
|
||||
|
||||
getSourceUuid(): string | undefined {
|
||||
getSourceUuid(): UUIDStringType | undefined {
|
||||
if (isIncoming(this.attributes)) {
|
||||
return this.get('sourceUuid');
|
||||
}
|
||||
|
@ -1114,7 +1113,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
);
|
||||
}
|
||||
|
||||
return this.OUR_UUID;
|
||||
return window.textsecure.storage.user.getUuid()?.toString();
|
||||
}
|
||||
|
||||
getContactId(): string | undefined {
|
||||
|
@ -2510,7 +2509,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
return;
|
||||
}
|
||||
|
||||
const messageId = window.getGuid();
|
||||
const messageId = UUID.generate().toString();
|
||||
|
||||
// Send delivery receipts, but only for incoming sealed sender messages
|
||||
// and not for messages from unaccepted conversations
|
||||
|
|
|
@ -36,8 +36,6 @@ import {
|
|||
import { ourProfileKeyService } from './ourProfileKey';
|
||||
import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation';
|
||||
import * as preferredReactionEmoji from '../reactions/preferredReactionEmoji';
|
||||
import { UUID } from '../types/UUID';
|
||||
import * as Errors from '../types/errors';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
|
@ -107,9 +105,9 @@ export async function toContactRecord(
|
|||
conversation: ConversationModel
|
||||
): Promise<Proto.ContactRecord> {
|
||||
const contactRecord = new Proto.ContactRecord();
|
||||
const uuid = conversation.get('uuid');
|
||||
const uuid = conversation.getUuid();
|
||||
if (uuid) {
|
||||
contactRecord.serviceUuid = uuid;
|
||||
contactRecord.serviceUuid = uuid.toString();
|
||||
}
|
||||
const e164 = conversation.get('e164');
|
||||
if (e164) {
|
||||
|
@ -120,15 +118,8 @@ export async function toContactRecord(
|
|||
contactRecord.profileKey = Bytes.fromBase64(String(profileKey));
|
||||
}
|
||||
|
||||
let maybeUuid: UUID | undefined;
|
||||
try {
|
||||
maybeUuid = uuid ? new UUID(uuid) : undefined;
|
||||
} catch (error) {
|
||||
log.warn(`Invalid uuid in contact record: ${Errors.toLogFormat(error)}`);
|
||||
}
|
||||
|
||||
const identityKey = maybeUuid
|
||||
? await window.textsecure.storage.protocol.loadIdentityKey(maybeUuid)
|
||||
const identityKey = uuid
|
||||
? await window.textsecure.storage.protocol.loadIdentityKey(uuid)
|
||||
: undefined;
|
||||
if (identityKey) {
|
||||
contactRecord.identityKey = identityKey;
|
||||
|
|
|
@ -33,6 +33,7 @@ import { assert, strictAssert } from '../util/assert';
|
|||
import { cleanDataForIpc } from './cleanDataForIpc';
|
||||
import type { ReactionType } from '../types/Reactions';
|
||||
import type { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
|
||||
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
||||
import createTaskWithTimeout from '../textsecure/TaskWithTimeout';
|
||||
|
@ -204,7 +205,7 @@ const dataInterface: ClientInterface = {
|
|||
getAllConversations,
|
||||
getAllConversationIds,
|
||||
getAllPrivateConversations,
|
||||
getAllGroupsInvolvingId,
|
||||
getAllGroupsInvolvingUuid,
|
||||
|
||||
searchConversations,
|
||||
searchMessages,
|
||||
|
@ -1044,15 +1045,15 @@ async function getAllPrivateConversations({
|
|||
return collection;
|
||||
}
|
||||
|
||||
async function getAllGroupsInvolvingId(
|
||||
id: string,
|
||||
async function getAllGroupsInvolvingUuid(
|
||||
uuid: UUIDStringType,
|
||||
{
|
||||
ConversationCollection,
|
||||
}: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}
|
||||
) {
|
||||
const conversations = await channels.getAllGroupsInvolvingId(id);
|
||||
const conversations = await channels.getAllGroupsInvolvingUuid(uuid);
|
||||
|
||||
const collection = new ConversationCollection();
|
||||
collection.add(conversations);
|
||||
|
@ -1325,11 +1326,11 @@ async function getNewerMessagesByConversation(
|
|||
}
|
||||
async function getLastConversationMessages({
|
||||
conversationId,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
Message,
|
||||
}: {
|
||||
conversationId: string;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
Message: typeof MessageModel;
|
||||
}): Promise<LastConversationMessagesType> {
|
||||
const {
|
||||
|
@ -1338,7 +1339,7 @@ async function getLastConversationMessages({
|
|||
hasUserInitiatedMessages,
|
||||
} = await channels.getLastConversationMessages({
|
||||
conversationId,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -63,7 +63,7 @@ export type EmojiType = {
|
|||
|
||||
export type IdentityKeyType = {
|
||||
firstUse: boolean;
|
||||
id: UUIDStringType | `conversation:${UUIDStringType}`;
|
||||
id: UUIDStringType | `conversation:${string}`;
|
||||
nonblockingApproval: boolean;
|
||||
publicKey: Uint8Array;
|
||||
timestamp: number;
|
||||
|
@ -501,7 +501,9 @@ export type DataInterface = {
|
|||
|
||||
export type ServerInterface = DataInterface & {
|
||||
getAllConversations: () => Promise<Array<ConversationType>>;
|
||||
getAllGroupsInvolvingId: (id: string) => Promise<Array<ConversationType>>;
|
||||
getAllGroupsInvolvingUuid: (
|
||||
id: UUIDStringType
|
||||
) => Promise<Array<ConversationType>>;
|
||||
getAllPrivateConversations: () => Promise<Array<ConversationType>>;
|
||||
getConversationById: (id: string) => Promise<ConversationType | undefined>;
|
||||
getExpiredMessages: () => Promise<Array<MessageType>>;
|
||||
|
@ -528,7 +530,7 @@ export type ServerInterface = DataInterface & {
|
|||
) => Promise<Array<MessageTypeUnhydrated>>;
|
||||
getLastConversationMessages: (options: {
|
||||
conversationId: string;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
}) => Promise<LastConversationMessagesServerType>;
|
||||
getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>;
|
||||
removeConversation: (id: Array<string> | string) => Promise<void>;
|
||||
|
@ -576,8 +578,8 @@ export type ClientInterface = DataInterface & {
|
|||
getAllConversations: (options: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}) => Promise<ConversationModelCollectionType>;
|
||||
getAllGroupsInvolvingId: (
|
||||
id: string,
|
||||
getAllGroupsInvolvingUuid: (
|
||||
id: UUIDStringType,
|
||||
options: {
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
}
|
||||
|
@ -630,7 +632,7 @@ export type ClientInterface = DataInterface & {
|
|||
) => Promise<MessageModelCollectionType>;
|
||||
getLastConversationMessages: (options: {
|
||||
conversationId: string;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
Message: typeof MessageModel;
|
||||
}) => Promise<LastConversationMessagesType>;
|
||||
getTapToViewMessagesNeedingErase: (options: {
|
||||
|
|
2778
ts/sql/Server.ts
2778
ts/sql/Server.ts
File diff suppressed because it is too large
Load diff
448
ts/sql/migrations/41-uuid-keys.ts
Normal file
448
ts/sql/migrations/41-uuid-keys.ts
Normal file
|
@ -0,0 +1,448 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from 'better-sqlite3';
|
||||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { isValidUuid } from '../../types/UUID';
|
||||
import { assertSync } from '../../util/assert';
|
||||
import Helpers from '../../textsecure/Helpers';
|
||||
import { createOrUpdate, getById, removeById } from '../util';
|
||||
import type { EmptyQuery, Query } from '../util';
|
||||
import type { ItemKeyType } from '../Interface';
|
||||
|
||||
function getOurUuid(db: Database): string | undefined {
|
||||
const UUID_ID: ItemKeyType = 'uuid_id';
|
||||
|
||||
const row: { json: string } | undefined = db
|
||||
.prepare<Query>('SELECT json FROM items WHERE id = $id;')
|
||||
.get({ id: UUID_ID });
|
||||
|
||||
if (!row) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { value } = JSON.parse(row.json);
|
||||
|
||||
const [ourUuid] = Helpers.unencodeNumber(String(value).toLowerCase());
|
||||
return ourUuid;
|
||||
}
|
||||
|
||||
export default function updateToSchemaVersion41(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 41) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getConversationUuid = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
SELECT uuid
|
||||
FROM
|
||||
conversations
|
||||
WHERE
|
||||
id = $conversationId
|
||||
`
|
||||
)
|
||||
.pluck();
|
||||
|
||||
const getConversationStats = db.prepare<Query>(
|
||||
`
|
||||
SELECT uuid, e164, active_at
|
||||
FROM
|
||||
conversations
|
||||
WHERE
|
||||
id = $conversationId
|
||||
`
|
||||
);
|
||||
|
||||
const compareConvoRecency = (a: string, b: string): number => {
|
||||
const aStats = getConversationStats.get({ conversationId: a });
|
||||
const bStats = getConversationStats.get({ conversationId: b });
|
||||
|
||||
const isAComplete = Boolean(aStats?.uuid && aStats?.e164);
|
||||
const isBComplete = Boolean(bStats?.uuid && bStats?.e164);
|
||||
|
||||
if (!isAComplete && !isBComplete) {
|
||||
return 0;
|
||||
}
|
||||
if (!isAComplete) {
|
||||
return -1;
|
||||
}
|
||||
if (!isBComplete) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return aStats.active_at - bStats.active_at;
|
||||
};
|
||||
|
||||
const clearSessionsAndKeys = () => {
|
||||
// ts/background.ts will ask user to relink so all that matters here is
|
||||
// to maintain an invariant:
|
||||
//
|
||||
// After this migration all sessions and keys are prefixed by
|
||||
// "uuid:".
|
||||
db.exec(
|
||||
`
|
||||
DELETE FROM senderKeys;
|
||||
DELETE FROM sessions;
|
||||
DELETE FROM signedPreKeys;
|
||||
DELETE FROM preKeys;
|
||||
`
|
||||
);
|
||||
|
||||
assertSync(removeById<string>(db, 'items', 'identityKey'));
|
||||
assertSync(removeById<string>(db, 'items', 'registrationId'));
|
||||
};
|
||||
|
||||
const moveIdentityKeyToMap = (ourUuid: string) => {
|
||||
type IdentityKeyType = {
|
||||
privKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
|
||||
const identityKey = assertSync(
|
||||
getById<string, { value: IdentityKeyType }>(db, 'items', 'identityKey')
|
||||
);
|
||||
|
||||
type RegistrationId = number;
|
||||
|
||||
const registrationId = assertSync(
|
||||
getById<string, { value: RegistrationId }>(db, 'items', 'registrationId')
|
||||
);
|
||||
|
||||
if (identityKey) {
|
||||
assertSync(
|
||||
createOrUpdate<ItemKeyType>(db, 'items', {
|
||||
id: 'identityKeyMap',
|
||||
value: {
|
||||
[ourUuid]: identityKey.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (registrationId) {
|
||||
assertSync(
|
||||
createOrUpdate<ItemKeyType>(db, 'items', {
|
||||
id: 'registrationIdMap',
|
||||
value: {
|
||||
[ourUuid]: registrationId.value,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
db.exec(
|
||||
`
|
||||
DELETE FROM items WHERE id = "identityKey" OR id = "registrationId";
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
const prefixKeys = (ourUuid: string) => {
|
||||
for (const table of ['signedPreKeys', 'preKeys']) {
|
||||
// Update id to include suffix, add `ourUuid` and `keyId` fields.
|
||||
db.prepare<Query>(
|
||||
`
|
||||
UPDATE ${table}
|
||||
SET
|
||||
id = $ourUuid || ':' || id,
|
||||
json = json_set(
|
||||
json,
|
||||
'$.id',
|
||||
$ourUuid || ':' || json_extract(json, '$.id'),
|
||||
'$.keyId',
|
||||
json_extract(json, '$.id'),
|
||||
'$.ourUuid',
|
||||
$ourUuid
|
||||
)
|
||||
`
|
||||
).run({ ourUuid });
|
||||
}
|
||||
};
|
||||
|
||||
const updateSenderKeys = (ourUuid: string) => {
|
||||
const senderKeys: ReadonlyArray<{
|
||||
id: string;
|
||||
senderId: string;
|
||||
lastUpdatedDate: number;
|
||||
}> = db
|
||||
.prepare<EmptyQuery>(
|
||||
'SELECT id, senderId, lastUpdatedDate FROM senderKeys'
|
||||
)
|
||||
.all();
|
||||
|
||||
logger.info(`Updating ${senderKeys.length} sender keys`);
|
||||
|
||||
const updateSenderKey = db.prepare<Query>(
|
||||
`
|
||||
UPDATE senderKeys
|
||||
SET
|
||||
id = $newId,
|
||||
senderId = $newSenderId
|
||||
WHERE
|
||||
id = $id
|
||||
`
|
||||
);
|
||||
|
||||
const deleteSenderKey = db.prepare<Query>(
|
||||
'DELETE FROM senderKeys WHERE id = $id'
|
||||
);
|
||||
|
||||
const pastKeys = new Map<
|
||||
string,
|
||||
{
|
||||
conversationId: string;
|
||||
lastUpdatedDate: number;
|
||||
}
|
||||
>();
|
||||
|
||||
let updated = 0;
|
||||
let deleted = 0;
|
||||
let skipped = 0;
|
||||
for (const { id, senderId, lastUpdatedDate } of senderKeys) {
|
||||
const [conversationId] = Helpers.unencodeNumber(senderId);
|
||||
const uuid = getConversationUuid.get({ conversationId });
|
||||
|
||||
if (!uuid) {
|
||||
deleted += 1;
|
||||
deleteSenderKey.run({ id });
|
||||
continue;
|
||||
}
|
||||
|
||||
const newId = `${ourUuid}:${id.replace(conversationId, uuid)}`;
|
||||
|
||||
const existing = pastKeys.get(newId);
|
||||
|
||||
// We are going to delete on of the keys anyway
|
||||
if (existing) {
|
||||
skipped += 1;
|
||||
} else {
|
||||
updated += 1;
|
||||
}
|
||||
|
||||
const isOlder =
|
||||
existing &&
|
||||
(lastUpdatedDate < existing.lastUpdatedDate ||
|
||||
compareConvoRecency(conversationId, existing.conversationId) < 0);
|
||||
if (isOlder) {
|
||||
deleteSenderKey.run({ id });
|
||||
continue;
|
||||
} else if (existing) {
|
||||
deleteSenderKey.run({ id: newId });
|
||||
}
|
||||
|
||||
pastKeys.set(newId, { conversationId, lastUpdatedDate });
|
||||
|
||||
updateSenderKey.run({
|
||||
id,
|
||||
newId,
|
||||
newSenderId: `${senderId.replace(conversationId, uuid)}`,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Updated ${senderKeys.length} sender keys: ` +
|
||||
`updated: ${updated}, deleted: ${deleted}, skipped: ${skipped}`
|
||||
);
|
||||
};
|
||||
|
||||
const updateSessions = (ourUuid: string) => {
|
||||
// Use uuid instead of conversation id in existing sesions and prefix id
|
||||
// with ourUuid.
|
||||
//
|
||||
// Set ourUuid column and field in json
|
||||
const allSessions = db
|
||||
.prepare<EmptyQuery>('SELECT id, conversationId FROM SESSIONS')
|
||||
.all();
|
||||
|
||||
logger.info(`Updating ${allSessions.length} sessions`);
|
||||
|
||||
const updateSession = db.prepare<Query>(
|
||||
`
|
||||
UPDATE sessions
|
||||
SET
|
||||
id = $newId,
|
||||
ourUuid = $ourUuid,
|
||||
uuid = $uuid,
|
||||
json = json_set(
|
||||
sessions.json,
|
||||
'$.id',
|
||||
$newId,
|
||||
'$.uuid',
|
||||
$uuid,
|
||||
'$.ourUuid',
|
||||
$ourUuid
|
||||
)
|
||||
WHERE
|
||||
id = $id
|
||||
`
|
||||
);
|
||||
|
||||
const deleteSession = db.prepare<Query>(
|
||||
'DELETE FROM sessions WHERE id = $id'
|
||||
);
|
||||
|
||||
const pastSessions = new Map<
|
||||
string,
|
||||
{
|
||||
conversationId: string;
|
||||
}
|
||||
>();
|
||||
|
||||
let updated = 0;
|
||||
let deleted = 0;
|
||||
let skipped = 0;
|
||||
for (const { id, conversationId } of allSessions) {
|
||||
const uuid = getConversationUuid.get({ conversationId });
|
||||
if (!uuid) {
|
||||
deleted += 1;
|
||||
deleteSession.run({ id });
|
||||
continue;
|
||||
}
|
||||
|
||||
const newId = `${ourUuid}:${id.replace(conversationId, uuid)}`;
|
||||
|
||||
const existing = pastSessions.get(newId);
|
||||
|
||||
// We are going to delete on of the keys anyway
|
||||
if (existing) {
|
||||
skipped += 1;
|
||||
} else {
|
||||
updated += 1;
|
||||
}
|
||||
|
||||
const isOlder =
|
||||
existing &&
|
||||
compareConvoRecency(conversationId, existing.conversationId) < 0;
|
||||
if (isOlder) {
|
||||
deleteSession.run({ id });
|
||||
continue;
|
||||
} else if (existing) {
|
||||
deleteSession.run({ id: newId });
|
||||
}
|
||||
|
||||
pastSessions.set(newId, { conversationId });
|
||||
|
||||
updateSession.run({
|
||||
id,
|
||||
newId,
|
||||
uuid,
|
||||
ourUuid,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Updated ${allSessions.length} sessions: ` +
|
||||
`updated: ${updated}, deleted: ${deleted}, skipped: ${skipped}`
|
||||
);
|
||||
};
|
||||
|
||||
const updateIdentityKeys = () => {
|
||||
const identityKeys: ReadonlyArray<{
|
||||
id: string;
|
||||
}> = db.prepare<EmptyQuery>('SELECT id FROM identityKeys').all();
|
||||
|
||||
logger.info(`Updating ${identityKeys.length} identity keys`);
|
||||
|
||||
const updateIdentityKey = db.prepare<Query>(
|
||||
`
|
||||
UPDATE identityKeys
|
||||
SET
|
||||
id = $newId,
|
||||
json = json_set(
|
||||
identityKeys.json,
|
||||
'$.id',
|
||||
$newId
|
||||
)
|
||||
WHERE
|
||||
id = $id
|
||||
`
|
||||
);
|
||||
|
||||
let migrated = 0;
|
||||
for (const { id } of identityKeys) {
|
||||
const uuid = getConversationUuid.get({ conversationId: id });
|
||||
|
||||
let newId: string;
|
||||
if (uuid) {
|
||||
migrated += 1;
|
||||
newId = uuid;
|
||||
} else {
|
||||
newId = `conversation:${id}`;
|
||||
}
|
||||
|
||||
updateIdentityKey.run({ id, newId });
|
||||
}
|
||||
|
||||
logger.info(`Migrated ${migrated} identity keys`);
|
||||
};
|
||||
|
||||
db.transaction(() => {
|
||||
db.exec(
|
||||
`
|
||||
-- Change type of 'id' column from INTEGER to STRING
|
||||
|
||||
ALTER TABLE preKeys
|
||||
RENAME TO old_preKeys;
|
||||
|
||||
ALTER TABLE signedPreKeys
|
||||
RENAME TO old_signedPreKeys;
|
||||
|
||||
CREATE TABLE preKeys(
|
||||
id STRING PRIMARY KEY ASC,
|
||||
json TEXT
|
||||
);
|
||||
CREATE TABLE signedPreKeys(
|
||||
id STRING PRIMARY KEY ASC,
|
||||
json TEXT
|
||||
);
|
||||
|
||||
-- sqlite handles the type conversion
|
||||
INSERT INTO preKeys SELECT * FROM old_preKeys;
|
||||
INSERT INTO signedPreKeys SELECT * FROM old_signedPreKeys;
|
||||
|
||||
DROP TABLE old_preKeys;
|
||||
DROP TABLE old_signedPreKeys;
|
||||
|
||||
-- Alter sessions
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD COLUMN ourUuid STRING;
|
||||
|
||||
ALTER TABLE sessions
|
||||
ADD COLUMN uuid STRING;
|
||||
`
|
||||
);
|
||||
|
||||
const ourUuid = getOurUuid(db);
|
||||
|
||||
if (!isValidUuid(ourUuid)) {
|
||||
logger.error(
|
||||
'updateToSchemaVersion41: no uuid is available clearing sessions'
|
||||
);
|
||||
|
||||
clearSessionsAndKeys();
|
||||
|
||||
db.pragma('user_version = 41');
|
||||
return;
|
||||
}
|
||||
|
||||
prefixKeys(ourUuid);
|
||||
|
||||
updateSenderKeys(ourUuid);
|
||||
|
||||
updateSessions(ourUuid);
|
||||
|
||||
moveIdentityKeyToMap(ourUuid);
|
||||
|
||||
updateIdentityKeys();
|
||||
|
||||
db.pragma('user_version = 41');
|
||||
})();
|
||||
logger.info('updateToSchemaVersion41: success!');
|
||||
}
|
77
ts/sql/migrations/42-stale-reactions.ts
Normal file
77
ts/sql/migrations/42-stale-reactions.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from 'better-sqlite3';
|
||||
|
||||
import { batchMultiVarQuery } from '../util';
|
||||
import type { ArrayQuery } from '../util';
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
|
||||
export default function updateToSchemaVersion42(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 42) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction(() => {
|
||||
// First, recreate messages table delete trigger with reaction support
|
||||
|
||||
db.exec(`
|
||||
DROP TRIGGER messages_on_delete;
|
||||
|
||||
CREATE TRIGGER messages_on_delete AFTER DELETE ON messages BEGIN
|
||||
DELETE FROM messages_fts WHERE rowid = old.rowid;
|
||||
DELETE FROM sendLogPayloads WHERE id IN (
|
||||
SELECT payloadId FROM sendLogMessageIds
|
||||
WHERE messageId = old.id
|
||||
);
|
||||
DELETE FROM reactions WHERE rowid IN (
|
||||
SELECT rowid FROM reactions
|
||||
WHERE messageId = old.id
|
||||
);
|
||||
END;
|
||||
`);
|
||||
|
||||
// Then, delete previously-orphaned reactions
|
||||
|
||||
// Note: we use `pluck` here to fetch only the first column of
|
||||
// returned row.
|
||||
const messageIdList: Array<string> = db
|
||||
.prepare('SELECT id FROM messages ORDER BY id ASC;')
|
||||
.pluck()
|
||||
.all();
|
||||
const allReactions: Array<{
|
||||
rowid: number;
|
||||
messageId: string;
|
||||
}> = db.prepare('SELECT rowid, messageId FROM reactions;').all();
|
||||
|
||||
const messageIds = new Set(messageIdList);
|
||||
const reactionsToDelete: Array<number> = [];
|
||||
|
||||
allReactions.forEach(reaction => {
|
||||
if (!messageIds.has(reaction.messageId)) {
|
||||
reactionsToDelete.push(reaction.rowid);
|
||||
}
|
||||
});
|
||||
|
||||
function deleteReactions(rowids: Array<number>) {
|
||||
db.prepare<ArrayQuery>(
|
||||
`
|
||||
DELETE FROM reactions
|
||||
WHERE rowid IN ( ${rowids.map(() => '?').join(', ')} );
|
||||
`
|
||||
).run(rowids);
|
||||
}
|
||||
|
||||
if (reactionsToDelete.length > 0) {
|
||||
logger.info(`Deleting ${reactionsToDelete.length} orphaned reactions`);
|
||||
batchMultiVarQuery(db, reactionsToDelete, deleteReactions);
|
||||
}
|
||||
|
||||
db.pragma('user_version = 42');
|
||||
})();
|
||||
logger.info('updateToSchemaVersion42: success!');
|
||||
}
|
417
ts/sql/migrations/43-gv2-uuid.ts
Normal file
417
ts/sql/migrations/43-gv2-uuid.ts
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import { assert } from '../../util/assert';
|
||||
import {
|
||||
TableIterator,
|
||||
getCountFromTable,
|
||||
jsonToObject,
|
||||
objectToJSON,
|
||||
} from '../util';
|
||||
import type { EmptyQuery, Query } from '../util';
|
||||
import type { MessageType, ConversationType } from '../Interface';
|
||||
|
||||
export default function updateToSchemaVersion43(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 43) {
|
||||
return;
|
||||
}
|
||||
|
||||
type LegacyPendingMemberType = {
|
||||
addedByUserId?: string;
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
type LegacyAdminApprovalType = {
|
||||
conversationId: string;
|
||||
};
|
||||
|
||||
type LegacyConversationType = {
|
||||
id: string;
|
||||
membersV2?: Array<{
|
||||
conversationId: string;
|
||||
}>;
|
||||
pendingMembersV2?: Array<LegacyPendingMemberType>;
|
||||
pendingAdminApprovalV2?: Array<LegacyAdminApprovalType>;
|
||||
};
|
||||
|
||||
const getConversationUuid = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
SELECT uuid
|
||||
FROM
|
||||
conversations
|
||||
WHERE
|
||||
id = $conversationId
|
||||
`
|
||||
)
|
||||
.pluck();
|
||||
|
||||
const updateConversationStmt = db.prepare(
|
||||
`
|
||||
UPDATE conversations SET
|
||||
json = $json,
|
||||
members = $members
|
||||
WHERE id = $id;
|
||||
`
|
||||
);
|
||||
|
||||
const updateMessageStmt = db.prepare(
|
||||
`
|
||||
UPDATE messages SET
|
||||
json = $json,
|
||||
sourceUuid = $sourceUuid
|
||||
WHERE id = $id;
|
||||
`
|
||||
);
|
||||
|
||||
const upgradeConversation = (convo: ConversationType) => {
|
||||
const legacy = (convo as unknown) as LegacyConversationType;
|
||||
let result = convo;
|
||||
|
||||
const memberKeys: Array<keyof LegacyConversationType> = [
|
||||
'membersV2',
|
||||
'pendingMembersV2',
|
||||
'pendingAdminApprovalV2',
|
||||
];
|
||||
for (const key of memberKeys) {
|
||||
const oldValue = legacy[key];
|
||||
if (!Array.isArray(oldValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let addedByCount = 0;
|
||||
|
||||
const newValue = oldValue
|
||||
.map(member => {
|
||||
const uuid: UUIDStringType = getConversationUuid.get({
|
||||
conversationId: member.conversationId,
|
||||
});
|
||||
if (!uuid) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${legacy.id}.${key} UUID not found ` +
|
||||
`for ${member.conversationId}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const updated = {
|
||||
...omit(member, 'conversationId'),
|
||||
uuid,
|
||||
};
|
||||
|
||||
// We previously stored our conversation
|
||||
if (!('addedByUserId' in member) || !member.addedByUserId) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
const addedByUserId:
|
||||
| UUIDStringType
|
||||
| undefined = getConversationUuid.get({
|
||||
conversationId: member.addedByUserId,
|
||||
});
|
||||
|
||||
if (!addedByUserId) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
addedByCount += 1;
|
||||
|
||||
return {
|
||||
...updated,
|
||||
addedByUserId,
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
[key]: newValue,
|
||||
};
|
||||
|
||||
if (oldValue.length !== 0) {
|
||||
logger.info(
|
||||
`updateToSchemaVersion43: migrated ${oldValue.length} ${key} ` +
|
||||
`entries to ${newValue.length} for ${legacy.id}`
|
||||
);
|
||||
}
|
||||
|
||||
if (addedByCount > 0) {
|
||||
logger.info(
|
||||
`updateToSchemaVersion43: migrated ${addedByCount} addedByUserId ` +
|
||||
`in ${key} for ${legacy.id}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (result === convo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dbMembers: string | null;
|
||||
if (result.membersV2) {
|
||||
dbMembers = result.membersV2.map(item => item.uuid).join(' ');
|
||||
} else if (result.members) {
|
||||
dbMembers = result.members.join(' ');
|
||||
} else {
|
||||
dbMembers = null;
|
||||
}
|
||||
|
||||
updateConversationStmt.run({
|
||||
id: result.id,
|
||||
json: objectToJSON(result),
|
||||
members: dbMembers,
|
||||
});
|
||||
};
|
||||
|
||||
type LegacyMessageType = {
|
||||
id: string;
|
||||
groupV2Change?: {
|
||||
from: string;
|
||||
details: Array<
|
||||
(
|
||||
| {
|
||||
type:
|
||||
| 'member-add'
|
||||
| 'member-add-from-invite'
|
||||
| 'member-add-from-link'
|
||||
| 'member-add-from-admin-approval'
|
||||
| 'member-privilege'
|
||||
| 'member-remove'
|
||||
| 'pending-add-one'
|
||||
| 'pending-remove-one'
|
||||
| 'admin-approval-add-one'
|
||||
| 'admin-approval-remove-one';
|
||||
conversationId: string;
|
||||
}
|
||||
| {
|
||||
type: unknown;
|
||||
conversationId?: undefined;
|
||||
}
|
||||
) &
|
||||
(
|
||||
| {
|
||||
type:
|
||||
| 'member-add-from-invite'
|
||||
| 'pending-remove-one'
|
||||
| 'pending-remove-many'
|
||||
| 'admin-approval-remove-one';
|
||||
inviter: string;
|
||||
}
|
||||
| {
|
||||
inviter?: undefined;
|
||||
}
|
||||
)
|
||||
>;
|
||||
};
|
||||
sourceUuid: string;
|
||||
invitedGV2Members?: Array<LegacyPendingMemberType>;
|
||||
};
|
||||
|
||||
const upgradeMessage = (message: MessageType): boolean => {
|
||||
const {
|
||||
id,
|
||||
groupV2Change,
|
||||
sourceUuid,
|
||||
invitedGV2Members,
|
||||
} = (message as unknown) as LegacyMessageType;
|
||||
let result = message;
|
||||
|
||||
if (groupV2Change) {
|
||||
assert(result.groupV2Change, 'Pacify typescript');
|
||||
|
||||
const from: UUIDStringType | undefined = getConversationUuid.get({
|
||||
conversationId: groupV2Change.from,
|
||||
});
|
||||
|
||||
if (from) {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: {
|
||||
...result.groupV2Change,
|
||||
from,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: omit(result.groupV2Change, ['from']),
|
||||
};
|
||||
}
|
||||
|
||||
let changedDetails = false;
|
||||
const details = groupV2Change.details
|
||||
.map((legacyDetail, i) => {
|
||||
const oldDetail = result.groupV2Change?.details[i];
|
||||
assert(oldDetail, 'Pacify typescript');
|
||||
let newDetail = oldDetail;
|
||||
|
||||
for (const key of ['conversationId' as const, 'inviter' as const]) {
|
||||
const oldValue = legacyDetail[key];
|
||||
const newKey = key === 'conversationId' ? 'uuid' : key;
|
||||
|
||||
if (oldValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
changedDetails = true;
|
||||
|
||||
let newValue: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId: oldValue,
|
||||
});
|
||||
if (key === 'inviter') {
|
||||
newValue = newValue ?? UUID.cast(oldValue);
|
||||
}
|
||||
if (!newValue) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${id}.groupV2Change.details.${key} ` +
|
||||
`UUID not found for ${oldValue}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
assert(newDetail.type === legacyDetail.type, 'Pacify typescript');
|
||||
newDetail = {
|
||||
...omit(newDetail, key),
|
||||
[newKey]: newValue,
|
||||
};
|
||||
}
|
||||
|
||||
return newDetail;
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
if (changedDetails) {
|
||||
result = {
|
||||
...result,
|
||||
groupV2Change: {
|
||||
...result.groupV2Change,
|
||||
details,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceUuid) {
|
||||
const newValue: UUIDStringType =
|
||||
getConversationUuid.get({
|
||||
conversationId: sourceUuid,
|
||||
}) ?? UUID.cast(sourceUuid);
|
||||
|
||||
if (newValue !== sourceUuid) {
|
||||
result = {
|
||||
...result,
|
||||
sourceUuid: newValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (invitedGV2Members) {
|
||||
const newMembers = invitedGV2Members
|
||||
.map(({ addedByUserId, conversationId }, i) => {
|
||||
const uuid: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId,
|
||||
});
|
||||
const oldMember =
|
||||
result.invitedGV2Members && result.invitedGV2Members[i];
|
||||
assert(oldMember !== undefined, 'Pacify typescript');
|
||||
|
||||
if (!uuid) {
|
||||
logger.warn(
|
||||
`updateToSchemaVersion43: ${id}.invitedGV2Members UUID ` +
|
||||
`not found for ${conversationId}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newMember = {
|
||||
...omit(oldMember, ['conversationId']),
|
||||
uuid,
|
||||
};
|
||||
|
||||
if (!addedByUserId) {
|
||||
return newMember;
|
||||
}
|
||||
|
||||
const newAddedBy: UUIDStringType | null = getConversationUuid.get({
|
||||
conversationId: addedByUserId,
|
||||
});
|
||||
if (!newAddedBy) {
|
||||
return newMember;
|
||||
}
|
||||
|
||||
return {
|
||||
...newMember,
|
||||
addedByUserId: newAddedBy,
|
||||
};
|
||||
})
|
||||
.filter(isNotNil);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
invitedGV2Members: newMembers,
|
||||
};
|
||||
}
|
||||
|
||||
if (result === message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateMessageStmt.run({
|
||||
id: result.id,
|
||||
json: JSON.stringify(result),
|
||||
sourceUuid: result.sourceUuid ?? null,
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
db.transaction(() => {
|
||||
const allConversations = db
|
||||
.prepare<EmptyQuery>(
|
||||
`
|
||||
SELECT json, profileLastFetchedAt
|
||||
FROM conversations
|
||||
ORDER BY id ASC;
|
||||
`
|
||||
)
|
||||
.all()
|
||||
.map(({ json }) => jsonToObject<ConversationType>(json));
|
||||
|
||||
logger.info(
|
||||
'updateToSchemaVersion43: About to iterate through ' +
|
||||
`${allConversations.length} conversations`
|
||||
);
|
||||
|
||||
for (const convo of allConversations) {
|
||||
upgradeConversation(convo);
|
||||
}
|
||||
|
||||
const messageCount = getCountFromTable(db, 'messages');
|
||||
logger.info(
|
||||
'updateToSchemaVersion43: About to iterate through ' +
|
||||
`${messageCount} messages`
|
||||
);
|
||||
|
||||
let updatedCount = 0;
|
||||
for (const message of new TableIterator<MessageType>(db, 'messages')) {
|
||||
if (upgradeMessage(message)) {
|
||||
updatedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`updateToSchemaVersion43: Updated ${updatedCount} messages`);
|
||||
|
||||
db.pragma('user_version = 43');
|
||||
})();
|
||||
logger.info('updateToSchemaVersion43: success!');
|
||||
}
|
1934
ts/sql/migrations/index.ts
Normal file
1934
ts/sql/migrations/index.ts
Normal file
File diff suppressed because it is too large
Load diff
260
ts/sql/util.ts
Normal file
260
ts/sql/util.ts
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { isNumber, last } from 'lodash';
|
||||
|
||||
export type EmptyQuery = [];
|
||||
export type ArrayQuery = Array<Array<null | number | bigint | string>>;
|
||||
export type Query = { [key: string]: null | number | bigint | string | Buffer };
|
||||
export type JSONRows = Array<{ readonly json: string }>;
|
||||
|
||||
export type TableType =
|
||||
| 'attachment_downloads'
|
||||
| 'conversations'
|
||||
| 'identityKeys'
|
||||
| 'items'
|
||||
| 'messages'
|
||||
| 'preKeys'
|
||||
| 'senderKeys'
|
||||
| 'sessions'
|
||||
| 'signedPreKeys'
|
||||
| 'stickers'
|
||||
| 'unprocessed';
|
||||
|
||||
// This value needs to be below SQLITE_MAX_VARIABLE_NUMBER.
|
||||
const MAX_VARIABLE_COUNT = 100;
|
||||
|
||||
export function objectToJSON<T>(data: T): string {
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
export function jsonToObject<T>(json: string): T {
|
||||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
//
|
||||
// Database helpers
|
||||
//
|
||||
|
||||
export function getSQLiteVersion(db: Database): string {
|
||||
const { sqlite_version: version } = db
|
||||
.prepare<EmptyQuery>('select sqlite_version() AS sqlite_version')
|
||||
.get();
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
export function getSchemaVersion(db: Database): number {
|
||||
return db.pragma('schema_version', { simple: true });
|
||||
}
|
||||
|
||||
export function setUserVersion(db: Database, version: number): void {
|
||||
if (!isNumber(version)) {
|
||||
throw new Error(`setUserVersion: version ${version} is not a number`);
|
||||
}
|
||||
db.pragma(`user_version = ${version}`);
|
||||
}
|
||||
|
||||
export function getUserVersion(db: Database): number {
|
||||
return db.pragma('user_version', { simple: true });
|
||||
}
|
||||
|
||||
export function getSQLCipherVersion(db: Database): string | undefined {
|
||||
return db.pragma('cipher_version', { simple: true });
|
||||
}
|
||||
|
||||
//
|
||||
// Various table helpers
|
||||
//
|
||||
|
||||
export function batchMultiVarQuery<ValueT>(
|
||||
db: Database,
|
||||
values: Array<ValueT>,
|
||||
query: (batch: Array<ValueT>) => void
|
||||
): [];
|
||||
export function batchMultiVarQuery<ValueT, ResultT>(
|
||||
db: Database,
|
||||
values: Array<ValueT>,
|
||||
query: (batch: Array<ValueT>) => Array<ResultT>
|
||||
): Array<ResultT>;
|
||||
|
||||
export function batchMultiVarQuery<ValueT, ResultT>(
|
||||
db: Database,
|
||||
values: Array<ValueT>,
|
||||
query:
|
||||
| ((batch: Array<ValueT>) => void)
|
||||
| ((batch: Array<ValueT>) => Array<ResultT>)
|
||||
): Array<ResultT> {
|
||||
if (values.length > MAX_VARIABLE_COUNT) {
|
||||
const result: Array<ResultT> = [];
|
||||
db.transaction(() => {
|
||||
for (let i = 0; i < values.length; i += MAX_VARIABLE_COUNT) {
|
||||
const batch = values.slice(i, i + MAX_VARIABLE_COUNT);
|
||||
const batchResult = query(batch);
|
||||
if (Array.isArray(batchResult)) {
|
||||
result.push(...batchResult);
|
||||
}
|
||||
}
|
||||
})();
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = query(values);
|
||||
return Array.isArray(result) ? result : [];
|
||||
}
|
||||
|
||||
export function createOrUpdate<Key extends string | number>(
|
||||
db: Database,
|
||||
table: TableType,
|
||||
data: Record<string, unknown> & { id: Key }
|
||||
): void {
|
||||
const { id } = data;
|
||||
if (!id) {
|
||||
throw new Error('createOrUpdate: Provided data did not have a truthy id');
|
||||
}
|
||||
|
||||
db.prepare<Query>(
|
||||
`
|
||||
INSERT OR REPLACE INTO ${table} (
|
||||
id,
|
||||
json
|
||||
) values (
|
||||
$id,
|
||||
$json
|
||||
)
|
||||
`
|
||||
).run({
|
||||
id,
|
||||
json: objectToJSON(data),
|
||||
});
|
||||
}
|
||||
|
||||
export function bulkAdd(
|
||||
db: Database,
|
||||
table: TableType,
|
||||
array: Array<Record<string, unknown> & { id: string | number }>
|
||||
): void {
|
||||
db.transaction(() => {
|
||||
for (const data of array) {
|
||||
createOrUpdate(db, table, data);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
export function getById<Key extends string | number, Result = unknown>(
|
||||
db: Database,
|
||||
table: TableType,
|
||||
id: Key
|
||||
): Result | undefined {
|
||||
const row = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
SELECT *
|
||||
FROM ${table}
|
||||
WHERE id = $id;
|
||||
`
|
||||
)
|
||||
.get({
|
||||
id,
|
||||
});
|
||||
|
||||
if (!row) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return jsonToObject(row.json);
|
||||
}
|
||||
|
||||
export function removeById<Key extends string | number>(
|
||||
db: Database,
|
||||
table: TableType,
|
||||
id: Key | Array<Key>
|
||||
): void {
|
||||
if (!Array.isArray(id)) {
|
||||
db.prepare<Query>(
|
||||
`
|
||||
DELETE FROM ${table}
|
||||
WHERE id = $id;
|
||||
`
|
||||
).run({ id });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!id.length) {
|
||||
throw new Error('removeById: No ids to delete!');
|
||||
}
|
||||
|
||||
const removeByIdsSync = (ids: Array<string | number>): void => {
|
||||
db.prepare<ArrayQuery>(
|
||||
`
|
||||
DELETE FROM ${table}
|
||||
WHERE id IN ( ${id.map(() => '?').join(', ')} );
|
||||
`
|
||||
).run(ids);
|
||||
};
|
||||
|
||||
batchMultiVarQuery(db, id, removeByIdsSync);
|
||||
}
|
||||
|
||||
export function removeAllFromTable(db: Database, table: TableType): void {
|
||||
db.prepare<EmptyQuery>(`DELETE FROM ${table};`).run();
|
||||
}
|
||||
|
||||
export function getAllFromTable<T>(db: Database, table: TableType): Array<T> {
|
||||
const rows: JSONRows = db
|
||||
.prepare<EmptyQuery>(`SELECT json FROM ${table};`)
|
||||
.all();
|
||||
|
||||
return rows.map(row => jsonToObject(row.json));
|
||||
}
|
||||
|
||||
export function getCountFromTable(db: Database, table: TableType): number {
|
||||
const result: null | number = db
|
||||
.prepare<EmptyQuery>(`SELECT count(*) from ${table};`)
|
||||
.pluck(true)
|
||||
.get();
|
||||
if (isNumber(result)) {
|
||||
return result;
|
||||
}
|
||||
throw new Error(`getCountFromTable: Unable to get count from table ${table}`);
|
||||
}
|
||||
|
||||
export class TableIterator<ObjectType extends { id: string }> {
|
||||
constructor(
|
||||
private readonly db: Database,
|
||||
private readonly table: TableType,
|
||||
private readonly pageSize = 500
|
||||
) {}
|
||||
|
||||
*[Symbol.iterator](): Iterator<ObjectType> {
|
||||
const fetchObject = this.db.prepare<Query>(
|
||||
`
|
||||
SELECT json FROM ${this.table}
|
||||
WHERE id > $id
|
||||
ORDER BY id ASC
|
||||
LIMIT $pageSize;
|
||||
`
|
||||
);
|
||||
|
||||
let complete = false;
|
||||
let id = '';
|
||||
while (!complete) {
|
||||
const rows: JSONRows = fetchObject.all({
|
||||
id,
|
||||
pageSize: this.pageSize,
|
||||
});
|
||||
|
||||
const messages: Array<ObjectType> = rows.map(row =>
|
||||
jsonToObject(row.json)
|
||||
);
|
||||
yield* messages;
|
||||
|
||||
const lastMessage: ObjectType | undefined = last(messages);
|
||||
if (lastMessage) {
|
||||
({ id } = lastMessage);
|
||||
}
|
||||
complete = messages.length < this.pageSize;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,21 +35,22 @@ import { requestCameraPermissions } from '../../util/callingPermissions';
|
|||
import { isGroupCallOutboundRingEnabled } from '../../util/isGroupCallOutboundRingEnabled';
|
||||
import { sleep } from '../../util/sleep';
|
||||
import { LatestQueue } from '../../util/LatestQueue';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import type { ConversationChangedActionType } from './conversations';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
// State
|
||||
|
||||
export type GroupCallPeekInfoType = {
|
||||
uuids: Array<string>;
|
||||
creatorUuid?: string;
|
||||
uuids: Array<UUIDStringType>;
|
||||
creatorUuid?: UUIDStringType;
|
||||
eraId?: string;
|
||||
maxDevices: number;
|
||||
deviceCount: number;
|
||||
};
|
||||
|
||||
export type GroupCallParticipantInfoType = {
|
||||
uuid: string;
|
||||
uuid: UUIDStringType;
|
||||
demuxId: number;
|
||||
hasRemoteAudio: boolean;
|
||||
hasRemoteVideo: boolean;
|
||||
|
@ -77,7 +78,7 @@ type GroupCallRingStateType =
|
|||
}
|
||||
| {
|
||||
ringId: bigint;
|
||||
ringerUuid: string;
|
||||
ringerUuid: UUIDStringType;
|
||||
};
|
||||
|
||||
export type GroupCallStateType = {
|
||||
|
@ -99,7 +100,7 @@ export type ActiveCallStateType = {
|
|||
pip: boolean;
|
||||
presentingSource?: PresentedSource;
|
||||
presentingSourcesAvailable?: Array<PresentableSource>;
|
||||
safetyNumberChangedUuids: Array<string>;
|
||||
safetyNumberChangedUuids: Array<UUIDStringType>;
|
||||
settingsDialogOpen: boolean;
|
||||
showNeedsScreenRecordingPermissionsWarning?: boolean;
|
||||
showParticipantsList: boolean;
|
||||
|
@ -153,7 +154,7 @@ type GroupCallStateChangeArgumentType = {
|
|||
};
|
||||
|
||||
type GroupCallStateChangeActionPayloadType = GroupCallStateChangeArgumentType & {
|
||||
ourUuid: string;
|
||||
ourUuid: UUIDStringType;
|
||||
};
|
||||
|
||||
export type HangUpType = {
|
||||
|
@ -161,7 +162,7 @@ export type HangUpType = {
|
|||
};
|
||||
|
||||
type KeyChangedType = {
|
||||
uuid: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
|
||||
export type KeyChangeOkType = {
|
||||
|
@ -176,7 +177,7 @@ export type IncomingDirectCallType = {
|
|||
type IncomingGroupCallType = {
|
||||
conversationId: string;
|
||||
ringId: bigint;
|
||||
ringerUuid: string;
|
||||
ringerUuid: UUIDStringType;
|
||||
};
|
||||
|
||||
type PeekNotConnectedGroupCallType = {
|
||||
|
@ -262,7 +263,7 @@ export const getActiveCall = ({
|
|||
// support it for direct calls.
|
||||
export const getIncomingCall = (
|
||||
callsByConversation: Readonly<CallsByConversationType>,
|
||||
ourUuid: string
|
||||
ourUuid: UUIDStringType
|
||||
): undefined | DirectCallStateType | GroupCallStateType =>
|
||||
Object.values(callsByConversation).find(call => {
|
||||
switch (call.callMode) {
|
||||
|
@ -281,7 +282,7 @@ export const getIncomingCall = (
|
|||
|
||||
export const isAnybodyElseInGroupCall = (
|
||||
{ uuids }: Readonly<GroupCallPeekInfoType>,
|
||||
ourUuid: string
|
||||
ourUuid: UUIDStringType
|
||||
): boolean => uuids.some(id => id !== ourUuid);
|
||||
|
||||
const getGroupCallRingState = (
|
||||
|
@ -390,7 +391,7 @@ type IncomingGroupCallActionType = {
|
|||
type KeyChangedActionType = {
|
||||
type: 'calling/MARK_CALL_UNTRUSTED';
|
||||
payload: {
|
||||
safetyNumberChangedUuids: Array<string>;
|
||||
safetyNumberChangedUuids: Array<UUIDStringType>;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -409,7 +410,7 @@ export type PeekNotConnectedGroupCallFulfilledActionType = {
|
|||
payload: {
|
||||
conversationId: string;
|
||||
peekInfo: GroupCallPeekInfoType;
|
||||
ourConversationId: string;
|
||||
ourUuid: UUIDStringType;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -895,6 +896,8 @@ function peekNotConnectedGroupCall(
|
|||
return;
|
||||
}
|
||||
|
||||
const { ourUuid } = state.user;
|
||||
|
||||
await calling.updateCallHistoryForGroupCall(conversationId, peekInfo);
|
||||
|
||||
const formattedPeekInfo = calling.formatGroupCallPeekInfoForRedux(
|
||||
|
@ -906,7 +909,7 @@ function peekNotConnectedGroupCall(
|
|||
payload: {
|
||||
conversationId,
|
||||
peekInfo: formattedPeekInfo,
|
||||
ourConversationId: state.user.ourConversationId,
|
||||
ourUuid,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1661,7 +1664,7 @@ export function reducer(
|
|||
}
|
||||
|
||||
if (action.type === PEEK_NOT_CONNECTED_GROUP_CALL_FULFILLED) {
|
||||
const { conversationId, peekInfo, ourConversationId } = action.payload;
|
||||
const { conversationId, peekInfo, ourUuid } = action.payload;
|
||||
|
||||
const existingCall: GroupCallStateType = getGroupCall(
|
||||
conversationId,
|
||||
|
@ -1693,7 +1696,7 @@ export function reducer(
|
|||
}
|
||||
|
||||
if (
|
||||
!isAnybodyElseInGroupCall(peekInfo, ourConversationId) &&
|
||||
!isAnybodyElseInGroupCall(peekInfo, ourUuid) &&
|
||||
!existingCall.ringerUuid
|
||||
) {
|
||||
return removeConversationFromState(state, conversationId);
|
||||
|
|
|
@ -39,6 +39,7 @@ import type {
|
|||
import type { BodyRangeType } from '../../types/Util';
|
||||
import { CallMode } from '../../types/Calling';
|
||||
import type { MediaItemType } from '../../types/MediaItem';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import {
|
||||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
|
@ -82,7 +83,7 @@ export type ConversationTypeType = typeof ConversationTypes[number];
|
|||
|
||||
export type ConversationType = {
|
||||
id: string;
|
||||
uuid?: string;
|
||||
uuid?: UUIDStringType;
|
||||
e164?: string;
|
||||
name?: string;
|
||||
familyName?: string;
|
||||
|
@ -134,15 +135,15 @@ export type ConversationType = {
|
|||
announcementsOnlyReady?: boolean;
|
||||
expireTimer?: number;
|
||||
memberships?: Array<{
|
||||
conversationId: string;
|
||||
uuid: UUIDStringType;
|
||||
isAdmin: boolean;
|
||||
}>;
|
||||
pendingMemberships?: Array<{
|
||||
conversationId: string;
|
||||
addedByUserId?: string;
|
||||
uuid: UUIDStringType;
|
||||
addedByUserId?: UUIDStringType;
|
||||
}>;
|
||||
pendingApprovalMemberships?: Array<{
|
||||
conversationId: string;
|
||||
uuid: UUIDStringType;
|
||||
}>;
|
||||
muteExpiresAt?: number;
|
||||
dontNotifyForMentionsIfMuted?: boolean;
|
||||
|
@ -294,7 +295,7 @@ type ContactSpoofingReviewStateType =
|
|||
|
||||
export type ConversationsStateType = {
|
||||
preJoinConversation?: PreJoinConversationType;
|
||||
invitedConversationIdsForNewlyCreatedGroup?: Array<string>;
|
||||
invitedUuidsForNewlyCreatedGroup?: Array<string>;
|
||||
conversationLookup: ConversationLookupType;
|
||||
conversationsByE164: ConversationLookupType;
|
||||
conversationsByUuid: ConversationLookupType;
|
||||
|
@ -373,8 +374,8 @@ type CantAddContactToGroupActionType = {
|
|||
};
|
||||
};
|
||||
type ClearGroupCreationErrorActionType = { type: 'CLEAR_GROUP_CREATION_ERROR' };
|
||||
type ClearInvitedConversationsForNewlyCreatedGroupActionType = {
|
||||
type: 'CLEAR_INVITED_CONVERSATIONS_FOR_NEWLY_CREATED_GROUP';
|
||||
type ClearInvitedUuidsForNewlyCreatedGroupActionType = {
|
||||
type: 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP';
|
||||
};
|
||||
type CloseCantAddContactToGroupModalActionType = {
|
||||
type: 'CLOSE_CANT_ADD_CONTACT_TO_GROUP_MODAL';
|
||||
|
@ -470,7 +471,7 @@ type CreateGroupPendingActionType = {
|
|||
type CreateGroupFulfilledActionType = {
|
||||
type: 'CREATE_GROUP_FULFILLED';
|
||||
payload: {
|
||||
invitedConversationIds: Array<string>;
|
||||
invitedUuids: Array<UUIDStringType>;
|
||||
};
|
||||
};
|
||||
type CreateGroupRejectedActionType = {
|
||||
|
@ -689,7 +690,7 @@ export type ConversationActionType =
|
|||
| CantAddContactToGroupActionType
|
||||
| ClearChangedMessagesActionType
|
||||
| ClearGroupCreationErrorActionType
|
||||
| ClearInvitedConversationsForNewlyCreatedGroupActionType
|
||||
| ClearInvitedUuidsForNewlyCreatedGroupActionType
|
||||
| ClearSelectedMessageActionType
|
||||
| ClearUnreadMetricsActionType
|
||||
| CloseCantAddContactToGroupModalActionType
|
||||
|
@ -751,7 +752,7 @@ export const actions = {
|
|||
cantAddContactToGroup,
|
||||
clearChangedMessages,
|
||||
clearGroupCreationError,
|
||||
clearInvitedConversationsForNewlyCreatedGroup,
|
||||
clearInvitedUuidsForNewlyCreatedGroup,
|
||||
clearSelectedMessage,
|
||||
clearUnreadMetrics,
|
||||
closeCantAddContactToGroupModal,
|
||||
|
@ -1320,9 +1321,9 @@ function createGroup(): ThunkAction<
|
|||
dispatch({
|
||||
type: 'CREATE_GROUP_FULFILLED',
|
||||
payload: {
|
||||
invitedConversationIds: (
|
||||
conversation.get('pendingMembersV2') || []
|
||||
).map(member => member.conversationId),
|
||||
invitedUuids: (conversation.get('pendingMembersV2') || []).map(
|
||||
member => member.uuid
|
||||
),
|
||||
},
|
||||
});
|
||||
openConversationInternal({
|
||||
|
@ -1551,8 +1552,8 @@ function clearChangedMessages(
|
|||
},
|
||||
};
|
||||
}
|
||||
function clearInvitedConversationsForNewlyCreatedGroup(): ClearInvitedConversationsForNewlyCreatedGroupActionType {
|
||||
return { type: 'CLEAR_INVITED_CONVERSATIONS_FOR_NEWLY_CREATED_GROUP' };
|
||||
function clearInvitedUuidsForNewlyCreatedGroup(): ClearInvitedUuidsForNewlyCreatedGroupActionType {
|
||||
return { type: 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP' };
|
||||
}
|
||||
function clearGroupCreationError(): ClearGroupCreationErrorActionType {
|
||||
return { type: 'CLEAR_GROUP_CREATION_ERROR' };
|
||||
|
@ -1979,8 +1980,8 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === 'CLEAR_INVITED_CONVERSATIONS_FOR_NEWLY_CREATED_GROUP') {
|
||||
return omit(state, 'invitedConversationIdsForNewlyCreatedGroup');
|
||||
if (action.type === 'CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP') {
|
||||
return omit(state, 'invitedUuidsForNewlyCreatedGroup');
|
||||
}
|
||||
|
||||
if (action.type === 'CLEAR_GROUP_CREATION_ERROR') {
|
||||
|
@ -2169,8 +2170,7 @@ export function reducer(
|
|||
// the work.
|
||||
return {
|
||||
...state,
|
||||
invitedConversationIdsForNewlyCreatedGroup:
|
||||
action.payload.invitedConversationIds,
|
||||
invitedUuidsForNewlyCreatedGroup: action.payload.invitedUuids,
|
||||
};
|
||||
}
|
||||
if (action.type === 'CREATE_GROUP_REJECTED') {
|
||||
|
|
|
@ -6,6 +6,7 @@ import { trigger } from '../../shims/events';
|
|||
import type { NoopActionType } from './noop';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import { ThemeType } from '../../types/Util';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -15,7 +16,7 @@ export type UserStateType = {
|
|||
tempPath: string;
|
||||
ourConversationId: string;
|
||||
ourDeviceId: number;
|
||||
ourUuid: string;
|
||||
ourUuid: UUIDStringType;
|
||||
ourNumber: string;
|
||||
platform: string;
|
||||
regionCode: string;
|
||||
|
@ -31,7 +32,7 @@ type UserChangedActionType = {
|
|||
payload: {
|
||||
ourConversationId?: string;
|
||||
ourDeviceId?: number;
|
||||
ourUuid?: string;
|
||||
ourUuid?: UUIDStringType;
|
||||
ourNumber?: string;
|
||||
regionCode?: string;
|
||||
interactionMode?: 'mouse' | 'keyboard';
|
||||
|
@ -53,7 +54,7 @@ function userChanged(attributes: {
|
|||
ourConversationId?: string;
|
||||
ourDeviceId?: number;
|
||||
ourNumber?: string;
|
||||
ourUuid?: string;
|
||||
ourUuid?: UUIDStringType;
|
||||
regionCode?: string;
|
||||
theme?: ThemeType;
|
||||
}): UserChangedActionType {
|
||||
|
@ -81,7 +82,7 @@ export function getEmptyState(): UserStateType {
|
|||
tempPath: 'missing',
|
||||
ourConversationId: 'missing',
|
||||
ourDeviceId: 0,
|
||||
ourUuid: 'missing',
|
||||
ourUuid: '00000000-0000-4000-8000-000000000000',
|
||||
ourNumber: 'missing',
|
||||
regionCode: 'missing',
|
||||
platform: 'missing',
|
||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
|||
import { getIncomingCall as getIncomingCallHelper } from '../ducks/calling';
|
||||
import { getUserUuid } from './user';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
export type CallStateType = DirectCallStateType | GroupCallStateType;
|
||||
|
||||
|
@ -61,7 +62,7 @@ export const getIncomingCall = createSelector(
|
|||
getUserUuid,
|
||||
(
|
||||
callsByConversation: CallsByConversationType,
|
||||
ourUuid: string
|
||||
ourUuid: UUIDStringType
|
||||
): undefined | DirectCallStateType | GroupCallStateType =>
|
||||
getIncomingCallHelper(callsByConversation, ourUuid)
|
||||
);
|
||||
|
|
|
@ -27,6 +27,7 @@ import { filterAndSortConversationsByTitle } from '../../util/filterAndSortConve
|
|||
import type { ContactNameColorType } from '../../types/Colors';
|
||||
import { ContactNameColors } from '../../types/Colors';
|
||||
import type { AvatarDataType } from '../../types/Avatar';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { isInSystemContacts } from '../../util/isInSystemContacts';
|
||||
import { sortByTitle } from '../../util/sortByTitle';
|
||||
import {
|
||||
|
@ -668,6 +669,12 @@ export const getConversationByIdSelector = createSelector(
|
|||
getOwn(conversationLookup, id)
|
||||
);
|
||||
|
||||
export const getConversationByUuidSelector = createSelector(
|
||||
getConversationsByUuid,
|
||||
conversationsByUuid => (uuid: UUIDStringType): undefined | ConversationType =>
|
||||
getOwn(conversationsByUuid, uuid)
|
||||
);
|
||||
|
||||
// A little optimization to reset our selector cache whenever high-level application data
|
||||
// changes: regionCode and userNumber.
|
||||
export const getCachedSelectorForMessage = createSelector(
|
||||
|
@ -766,7 +773,7 @@ export const getMessageSelector = createSelector(
|
|||
conversationSelector: GetConversationByIdType,
|
||||
regionCode: string,
|
||||
ourNumber: string,
|
||||
ourUuid: string,
|
||||
ourUuid: UUIDStringType,
|
||||
ourConversationId: string,
|
||||
callSelector: CallSelectorType,
|
||||
activeCall: undefined | CallStateType,
|
||||
|
@ -903,16 +910,13 @@ export const getConversationMessagesSelector = createSelector(
|
|||
);
|
||||
|
||||
export const getInvitedContactsForNewlyCreatedGroup = createSelector(
|
||||
getConversationLookup,
|
||||
getConversationsByUuid,
|
||||
getConversations,
|
||||
(
|
||||
conversationLookup,
|
||||
{ invitedConversationIdsForNewlyCreatedGroup = [] }
|
||||
{ invitedUuidsForNewlyCreatedGroup = [] }
|
||||
): Array<ConversationType> =>
|
||||
deconstructLookup(
|
||||
conversationLookup,
|
||||
invitedConversationIdsForNewlyCreatedGroup
|
||||
)
|
||||
deconstructLookup(conversationLookup, invitedUuidsForNewlyCreatedGroup)
|
||||
);
|
||||
|
||||
export const getConversationsWithCustomColorSelector = createSelector(
|
||||
|
@ -962,7 +966,7 @@ export const getGroupAdminsSelector = createSelector(
|
|||
const admins: Array<ConversationType> = [];
|
||||
memberships.forEach(membership => {
|
||||
if (membership.isAdmin) {
|
||||
const admin = conversationSelector(membership.conversationId);
|
||||
const admin = conversationSelector(membership.uuid);
|
||||
admins.push(admin);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ import type { PropsType as ProfileChangeNotificationPropsType } from '../../comp
|
|||
import type { QuotedAttachmentType } from '../../components/conversation/Quote';
|
||||
|
||||
import { getDomain, isStickerPack } from '../../types/LinkPreview';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
|
||||
import { embeddedContactSelector } from '../../types/EmbeddedContact';
|
||||
|
@ -99,7 +100,7 @@ export type GetPropsForBubbleOptions = Readonly<{
|
|||
conversationSelector: GetConversationByIdType;
|
||||
ourConversationId: string;
|
||||
ourNumber?: string;
|
||||
ourUuid?: string;
|
||||
ourUuid: UUIDStringType;
|
||||
selectedMessageId?: string;
|
||||
selectedMessageCounter?: number;
|
||||
regionCode: string;
|
||||
|
@ -772,7 +773,7 @@ export function isGroupV2Change(message: MessageAttributesType): boolean {
|
|||
|
||||
function getPropsForGroupV2Change(
|
||||
message: MessageAttributesType,
|
||||
{ conversationSelector, ourConversationId }: GetPropsForBubbleOptions
|
||||
{ conversationSelector, ourUuid }: GetPropsForBubbleOptions
|
||||
): GroupsV2Props {
|
||||
const change = message.groupV2Change;
|
||||
|
||||
|
@ -784,7 +785,7 @@ function getPropsForGroupV2Change(
|
|||
|
||||
return {
|
||||
groupName: conversation?.type === 'group' ? conversation?.name : undefined,
|
||||
ourConversationId,
|
||||
ourUuid,
|
||||
change,
|
||||
};
|
||||
}
|
||||
|
@ -806,7 +807,7 @@ function getPropsForGroupV1Migration(
|
|||
const droppedGV2MemberIds = message.droppedGV2MemberIds || [];
|
||||
|
||||
const invitedMembers = invitedGV2Members.map(item =>
|
||||
conversationSelector(item.conversationId)
|
||||
conversationSelector(item.uuid)
|
||||
);
|
||||
const droppedMembers = droppedGV2MemberIds.map(conversationId =>
|
||||
conversationSelector(conversationId)
|
||||
|
@ -825,7 +826,7 @@ function getPropsForGroupV1Migration(
|
|||
invitedMembers: rawInvitedMembers,
|
||||
} = migration;
|
||||
const invitedMembers = rawInvitedMembers.map(item =>
|
||||
conversationSelector(item.conversationId)
|
||||
conversationSelector(item.uuid)
|
||||
);
|
||||
const droppedMembers = droppedMemberIds.map(conversationId =>
|
||||
conversationSelector(conversationId)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import type { UserStateType } from '../ducks/user';
|
||||
|
@ -32,7 +33,7 @@ export const getUserConversationId = createSelector(
|
|||
|
||||
export const getUserUuid = createSelector(
|
||||
getUser,
|
||||
(state: UserStateType): string => state.ourUuid
|
||||
(state: UserStateType): UUIDStringType => state.ourUuid
|
||||
);
|
||||
|
||||
export const getIntl = createSelector(
|
||||
|
|
|
@ -17,6 +17,7 @@ import type {
|
|||
ActiveCallType,
|
||||
GroupCallRemoteParticipantType,
|
||||
} from '../../types/Calling';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { CallMode, CallState } from '../../types/Calling';
|
||||
import type { StateType } from '../reducer';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
|
@ -117,7 +118,7 @@ const mapStateToActiveCallProp = (
|
|||
}
|
||||
|
||||
const conversationSelectorByUuid = memoize<
|
||||
(uuid: string) => undefined | ConversationType
|
||||
(uuid: UUIDStringType) => undefined | ConversationType
|
||||
>(uuid => {
|
||||
const conversationId = window.ConversationController.ensureContactIds({
|
||||
uuid,
|
||||
|
@ -175,9 +176,9 @@ const mapStateToActiveCallProp = (
|
|||
|
||||
const { memberships = [] } = conversation;
|
||||
for (let i = 0; i < memberships.length; i += 1) {
|
||||
const { conversationId } = memberships[i];
|
||||
const member = conversationSelectorByUuid(conversationId);
|
||||
const { uuid } = memberships[i];
|
||||
|
||||
const member = conversationSelector(uuid);
|
||||
if (!member) {
|
||||
log.error('Group member has no corresponding conversation');
|
||||
continue;
|
||||
|
|
|
@ -26,7 +26,7 @@ const mapStateToProps = (state: StateType): PropsDataType => {
|
|||
let isAdmin = false;
|
||||
if (contact && currentConversation && currentConversation.memberships) {
|
||||
currentConversation.memberships.forEach(membership => {
|
||||
if (membership.conversationId === contact.id) {
|
||||
if (membership.uuid === contact.uuid) {
|
||||
isMember = true;
|
||||
isAdmin = membership.isAdmin;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ConversationDetails } from '../../components/conversation/conversation-
|
|||
import {
|
||||
getCandidateContactsForNewGroup,
|
||||
getConversationByIdSelector,
|
||||
getConversationByUuidSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
@ -67,6 +68,7 @@ const mapStateToProps = (
|
|||
Boolean(conversation.groupLink) &&
|
||||
conversation.accessControlAddFromInviteLink !== ACCESS_ENUM.UNSATISFIABLE;
|
||||
|
||||
const conversationByUuidSelector = getConversationByUuidSelector(state);
|
||||
return {
|
||||
...props,
|
||||
canEditGroupInfo,
|
||||
|
@ -74,7 +76,7 @@ const mapStateToProps = (
|
|||
conversation,
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
...getGroupMemberships(conversation, conversationSelector),
|
||||
...getGroupMemberships(conversation, conversationByUuidSelector),
|
||||
userAvatarData: conversation.avatars || [],
|
||||
hasGroupLink,
|
||||
isGroup: conversation.type === 'group',
|
||||
|
|
|
@ -8,7 +8,10 @@ import { PendingInvites } from '../../components/conversation/conversation-detai
|
|||
import type { StateType } from '../reducer';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||
import {
|
||||
getConversationByIdSelector,
|
||||
getConversationByUuidSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
import { assert } from '../../util/assert';
|
||||
|
||||
|
@ -24,6 +27,7 @@ const mapStateToProps = (
|
|||
props: SmartPendingInvitesProps
|
||||
): PropsType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
const conversationByUuidSelector = getConversationByUuidSelector(state);
|
||||
|
||||
const conversation = conversationSelector(props.conversationId);
|
||||
assert(
|
||||
|
@ -33,7 +37,7 @@ const mapStateToProps = (
|
|||
|
||||
return {
|
||||
...props,
|
||||
...getGroupMemberships(conversation, conversationSelector),
|
||||
...getGroupMemberships(conversation, conversationByUuidSelector),
|
||||
conversation,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { ConversationType } from '../ducks/conversations';
|
|||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getConversationByIdSelector,
|
||||
getConversationByUuidSelector,
|
||||
getConversationMessagesSelector,
|
||||
getConversationSelector,
|
||||
getConversationsByTitleSelector,
|
||||
|
@ -208,11 +208,11 @@ const getWarning = (
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const getConversationById = getConversationByIdSelector(state);
|
||||
const getConversationByUuid = getConversationByUuidSelector(state);
|
||||
|
||||
const { memberships } = getGroupMemberships(
|
||||
conversation,
|
||||
getConversationById
|
||||
getConversationByUuid
|
||||
);
|
||||
const groupNameCollisions = getCollisionsFromMemberships(memberships);
|
||||
const hasGroupMembersWithSameName = !isEmpty(groupNameCollisions);
|
||||
|
@ -244,7 +244,7 @@ const getContactSpoofingReview = (
|
|||
}
|
||||
|
||||
const conversationSelector = getConversationSelector(state);
|
||||
const getConversationById = getConversationByIdSelector(state);
|
||||
const getConversationByUuid = getConversationByUuidSelector(state);
|
||||
|
||||
const currentConversation = conversationSelector(selectedConversationId);
|
||||
|
||||
|
@ -260,7 +260,7 @@ const getContactSpoofingReview = (
|
|||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||
const { memberships } = getGroupMemberships(
|
||||
currentConversation,
|
||||
getConversationById
|
||||
getConversationByUuid
|
||||
);
|
||||
const groupNameCollisions = getCollisionsFromMemberships(memberships);
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
import { assert } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { times } from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as remoteConfig from '../../RemoteConfig';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
import { isConversationTooBigToRing } from '../../conversations/isConversationTooBigToRing';
|
||||
|
||||
|
@ -23,7 +23,7 @@ describe('isConversationTooBigToRing', () => {
|
|||
});
|
||||
|
||||
const fakeMemberships = (count: number) =>
|
||||
times(count, () => ({ conversationId: uuid(), isAdmin: false }));
|
||||
times(count, () => ({ uuid: UUID.generate().toString(), isAdmin: false }));
|
||||
|
||||
afterEach(() => {
|
||||
sinonSandbox.restore();
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
import { v4 as generateUuid } from 'uuid';
|
||||
import { sample } from 'lodash';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { getRandomColor } from './getRandomColor';
|
||||
|
||||
const FIRST_NAMES = [
|
||||
|
@ -334,7 +336,17 @@ export function getDefaultConversation(
|
|||
sharedGroupNames: [],
|
||||
title: `${firstName} ${lastName}`,
|
||||
type: 'direct' as const,
|
||||
uuid: generateUuid(),
|
||||
uuid: UUID.generate().toString(),
|
||||
...overrideProps,
|
||||
};
|
||||
}
|
||||
|
||||
export function getDefaultConversationWithUuid(
|
||||
overrideProps: Partial<ConversationType> = {},
|
||||
uuid: UUIDStringType = UUID.generate().toString()
|
||||
): ConversationType & { uuid: UUIDStringType } {
|
||||
return {
|
||||
...getDefaultConversation(overrideProps),
|
||||
uuid,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -48,8 +48,13 @@ import { noopAction } from '../../../state/ducks/noop';
|
|||
import type { StateType } from '../../../state/reducer';
|
||||
import { reducer as rootReducer } from '../../../state/reducer';
|
||||
import { setupI18n } from '../../../util/setupI18n';
|
||||
import { UUID } from '../../../types/UUID';
|
||||
import type { UUIDStringType } from '../../../types/UUID';
|
||||
import enMessages from '../../../../_locales/en/messages.json';
|
||||
import { getDefaultConversation } from '../../helpers/getDefaultConversation';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultConversationWithUuid,
|
||||
} from '../../helpers/getDefaultConversation';
|
||||
import {
|
||||
defaultStartDirectConversationComposerState,
|
||||
defaultChooseGroupMembersComposerState,
|
||||
|
@ -69,6 +74,19 @@ describe('both/state/selectors/conversations', () => {
|
|||
});
|
||||
}
|
||||
|
||||
function makeConversationWithUuid(
|
||||
id: string
|
||||
): ConversationType & { uuid: UUIDStringType } {
|
||||
return getDefaultConversationWithUuid(
|
||||
{
|
||||
id,
|
||||
searchableTitle: `${id} title`,
|
||||
title: `${id} title`,
|
||||
},
|
||||
UUID.fromPrefix(id).toString()
|
||||
);
|
||||
}
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
describe('#getConversationByIdSelector', () => {
|
||||
|
@ -374,15 +392,17 @@ describe('both/state/selectors/conversations', () => {
|
|||
});
|
||||
|
||||
it('returns "hydrated" invited contacts', () => {
|
||||
const abc = makeConversationWithUuid('abc');
|
||||
const def = makeConversationWithUuid('def');
|
||||
const state = {
|
||||
...getEmptyRootState(),
|
||||
conversations: {
|
||||
...getEmptyState(),
|
||||
conversationLookup: {
|
||||
abc: makeConversation('abc'),
|
||||
def: makeConversation('def'),
|
||||
conversationsByUuid: {
|
||||
[abc.uuid]: abc,
|
||||
[def.uuid]: def,
|
||||
},
|
||||
invitedConversationIdsForNewlyCreatedGroup: ['def', 'abc'],
|
||||
invitedUuidsForNewlyCreatedGroup: [def.uuid, abc.uuid],
|
||||
},
|
||||
};
|
||||
const result = getInvitedContactsForNewlyCreatedGroup(state);
|
||||
|
@ -1826,23 +1846,17 @@ describe('both/state/selectors/conversations', () => {
|
|||
});
|
||||
|
||||
describe('#getContactNameColorSelector', () => {
|
||||
function makeConversationWithUuid(id: string): ConversationType {
|
||||
const convo = makeConversation(id);
|
||||
convo.uuid = id;
|
||||
return convo;
|
||||
}
|
||||
|
||||
it('returns the right color order sorted by UUID ASC', () => {
|
||||
const group = makeConversation('group');
|
||||
group.type = 'group';
|
||||
group.sortedGroupMembers = [
|
||||
makeConversationWithUuid('zyx'),
|
||||
makeConversationWithUuid('vut'),
|
||||
makeConversationWithUuid('srq'),
|
||||
makeConversationWithUuid('pon'),
|
||||
makeConversationWithUuid('mlk'),
|
||||
makeConversationWithUuid('jih'),
|
||||
makeConversationWithUuid('gfe'),
|
||||
makeConversationWithUuid('fff'),
|
||||
makeConversationWithUuid('f00'),
|
||||
makeConversationWithUuid('e00'),
|
||||
makeConversationWithUuid('d00'),
|
||||
makeConversationWithUuid('c00'),
|
||||
makeConversationWithUuid('b00'),
|
||||
makeConversationWithUuid('a00'),
|
||||
];
|
||||
const state = {
|
||||
...getEmptyRootState(),
|
||||
|
@ -1856,13 +1870,13 @@ describe('both/state/selectors/conversations', () => {
|
|||
|
||||
const contactNameColorSelector = getContactNameColorSelector(state);
|
||||
|
||||
assert.equal(contactNameColorSelector('group', 'gfe'), '200');
|
||||
assert.equal(contactNameColorSelector('group', 'jih'), '120');
|
||||
assert.equal(contactNameColorSelector('group', 'mlk'), '300');
|
||||
assert.equal(contactNameColorSelector('group', 'pon'), '010');
|
||||
assert.equal(contactNameColorSelector('group', 'srq'), '210');
|
||||
assert.equal(contactNameColorSelector('group', 'vut'), '330');
|
||||
assert.equal(contactNameColorSelector('group', 'zyx'), '230');
|
||||
assert.equal(contactNameColorSelector('group', 'a00'), '200');
|
||||
assert.equal(contactNameColorSelector('group', 'b00'), '120');
|
||||
assert.equal(contactNameColorSelector('group', 'c00'), '300');
|
||||
assert.equal(contactNameColorSelector('group', 'd00'), '010');
|
||||
assert.equal(contactNameColorSelector('group', 'e00'), '210');
|
||||
assert.equal(contactNameColorSelector('group', 'f00'), '330');
|
||||
assert.equal(contactNameColorSelector('group', 'fff'), '230');
|
||||
});
|
||||
|
||||
it('returns the right colors for direct conversation', () => {
|
||||
|
|
|
@ -19,7 +19,11 @@ import {
|
|||
getSearchResults,
|
||||
} from '../../../state/selectors/search';
|
||||
import { makeLookup } from '../../../util/makeLookup';
|
||||
import { getDefaultConversation } from '../../helpers/getDefaultConversation';
|
||||
import { UUID } from '../../../types/UUID';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultConversationWithUuid,
|
||||
} from '../../helpers/getDefaultConversation';
|
||||
import { ReadStatus } from '../../../messages/MessageReadStatus';
|
||||
|
||||
import type { StateType } from '../../../state/reducer';
|
||||
|
@ -52,7 +56,7 @@ describe('both/state/selectors/search', () => {
|
|||
received_at: NOW,
|
||||
sent_at: NOW,
|
||||
source: 'source',
|
||||
sourceUuid: 'sourceUuid',
|
||||
sourceUuid: UUID.generate().toString(),
|
||||
timestamp: NOW,
|
||||
type: 'incoming' as const,
|
||||
readStatus: ReadStatus.Read,
|
||||
|
@ -125,10 +129,9 @@ describe('both/state/selectors/search', () => {
|
|||
|
||||
it('returns incoming message', () => {
|
||||
const searchId = 'search-id';
|
||||
const fromId = 'from-id';
|
||||
const toId = 'to-id';
|
||||
|
||||
const from = getDefaultConversation({ id: fromId });
|
||||
const from = getDefaultConversationWithUuid();
|
||||
const to = getDefaultConversation({ id: toId });
|
||||
|
||||
const state = {
|
||||
|
@ -136,9 +139,12 @@ describe('both/state/selectors/search', () => {
|
|||
conversations: {
|
||||
...getEmptyConversationState(),
|
||||
conversationLookup: {
|
||||
[fromId]: from,
|
||||
[from.id]: from,
|
||||
[toId]: to,
|
||||
},
|
||||
conversationsByUuid: {
|
||||
[from.uuid]: from,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
...getEmptySearchState(),
|
||||
|
@ -146,7 +152,7 @@ describe('both/state/selectors/search', () => {
|
|||
[searchId]: {
|
||||
...getDefaultMessage(searchId),
|
||||
type: 'incoming' as const,
|
||||
sourceUuid: fromId,
|
||||
sourceUuid: from.uuid,
|
||||
conversationId: toId,
|
||||
snippet: 'snippet',
|
||||
body: 'snippet',
|
||||
|
@ -178,11 +184,10 @@ describe('both/state/selectors/search', () => {
|
|||
|
||||
it('returns the correct "from" and "to" when sent to me', () => {
|
||||
const searchId = 'search-id';
|
||||
const fromId = 'from-id';
|
||||
const toId = fromId;
|
||||
const myId = 'my-id';
|
||||
|
||||
const from = getDefaultConversation({ id: fromId });
|
||||
const from = getDefaultConversationWithUuid();
|
||||
const toId = from.uuid;
|
||||
const meAsRecipient = getDefaultConversation({ id: myId });
|
||||
|
||||
const state = {
|
||||
|
@ -190,9 +195,12 @@ describe('both/state/selectors/search', () => {
|
|||
conversations: {
|
||||
...getEmptyConversationState(),
|
||||
conversationLookup: {
|
||||
[fromId]: from,
|
||||
[from.id]: from,
|
||||
[myId]: meAsRecipient,
|
||||
},
|
||||
conversationsByUuid: {
|
||||
[from.uuid]: from,
|
||||
},
|
||||
},
|
||||
ourConversationId: myId,
|
||||
search: {
|
||||
|
@ -201,7 +209,7 @@ describe('both/state/selectors/search', () => {
|
|||
[searchId]: {
|
||||
...getDefaultMessage(searchId),
|
||||
type: 'incoming' as const,
|
||||
sourceUuid: fromId,
|
||||
sourceUuid: from.uuid,
|
||||
conversationId: toId,
|
||||
snippet: 'snippet',
|
||||
body: 'snippet',
|
||||
|
@ -223,24 +231,26 @@ describe('both/state/selectors/search', () => {
|
|||
|
||||
it('returns outgoing message and caches appropriately', () => {
|
||||
const searchId = 'search-id';
|
||||
const fromId = 'from-id';
|
||||
const toId = 'to-id';
|
||||
|
||||
const from = getDefaultConversation({ id: fromId });
|
||||
const from = getDefaultConversationWithUuid();
|
||||
const to = getDefaultConversation({ id: toId });
|
||||
|
||||
const state = {
|
||||
...getEmptyRootState(),
|
||||
user: {
|
||||
...getEmptyUserState(),
|
||||
ourConversationId: fromId,
|
||||
ourConversationId: from.id,
|
||||
},
|
||||
conversations: {
|
||||
...getEmptyConversationState(),
|
||||
conversationLookup: {
|
||||
[fromId]: from,
|
||||
[from.id]: from,
|
||||
[toId]: to,
|
||||
},
|
||||
conversationsByUuid: {
|
||||
[from.uuid]: from,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
...getEmptySearchState(),
|
||||
|
@ -293,9 +303,9 @@ describe('both/state/selectors/search', () => {
|
|||
...state,
|
||||
conversations: {
|
||||
...state.conversations,
|
||||
conversationLookup: {
|
||||
...state.conversations.conversationLookup,
|
||||
[fromId]: {
|
||||
conversationsByUuid: {
|
||||
...state.conversations.conversationsByUuid,
|
||||
[from.uuid]: {
|
||||
...from,
|
||||
name: 'new-name',
|
||||
},
|
||||
|
|
33
ts/test-both/types/UUID_test.ts
Normal file
33
ts/test-both/types/UUID_test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isValidUuid } from '../../types/UUID';
|
||||
|
||||
describe('isValidUuid', () => {
|
||||
const LOWERCASE_V4_UUID = '9cb737ce-2bb3-4c21-9fe0-d286caa0ca68';
|
||||
|
||||
it('returns false for non-strings', () => {
|
||||
assert.isFalse(isValidUuid(undefined));
|
||||
assert.isFalse(isValidUuid(null));
|
||||
assert.isFalse(isValidUuid(1234));
|
||||
});
|
||||
|
||||
it('returns false for non-UUID strings', () => {
|
||||
assert.isFalse(isValidUuid(''));
|
||||
assert.isFalse(isValidUuid('hello world'));
|
||||
assert.isFalse(isValidUuid(` ${LOWERCASE_V4_UUID}`));
|
||||
assert.isFalse(isValidUuid(`${LOWERCASE_V4_UUID} `));
|
||||
});
|
||||
|
||||
it("returns false for UUIDs that aren't version 4", () => {
|
||||
assert.isFalse(isValidUuid('a200a6e0-d2d9-11eb-bda7-dd5936a30ddf'));
|
||||
assert.isFalse(isValidUuid('2adb8b83-4f2c-55ca-a481-7f98b716e615'));
|
||||
});
|
||||
|
||||
it('returns true for v4 UUIDs', () => {
|
||||
assert.isTrue(isValidUuid(LOWERCASE_V4_UUID));
|
||||
assert.isTrue(isValidUuid(LOWERCASE_V4_UUID.toUpperCase()));
|
||||
});
|
||||
});
|
|
@ -3,30 +3,34 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { getDefaultConversation } from '../helpers/getDefaultConversation';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { getDefaultConversationWithUuid } from '../helpers/getDefaultConversation';
|
||||
|
||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||
|
||||
describe('getGroupMemberships', () => {
|
||||
const normalConversation1 = getDefaultConversation();
|
||||
const normalConversation2 = getDefaultConversation();
|
||||
const unregisteredConversation = getDefaultConversation({
|
||||
const normalConversation1 = getDefaultConversationWithUuid();
|
||||
const normalConversation2 = getDefaultConversationWithUuid();
|
||||
const unregisteredConversation = getDefaultConversationWithUuid({
|
||||
discoveredUnregisteredAt: Date.now(),
|
||||
});
|
||||
|
||||
function getConversationById(id: string): undefined | ConversationType {
|
||||
function getConversationByUuid(
|
||||
uuid: UUIDStringType
|
||||
): undefined | ConversationType {
|
||||
return [
|
||||
normalConversation1,
|
||||
normalConversation2,
|
||||
unregisteredConversation,
|
||||
].find(conversation => conversation.id === id);
|
||||
].find(conversation => conversation.uuid === uuid);
|
||||
}
|
||||
|
||||
describe('memberships', () => {
|
||||
it('returns an empty array if passed undefined', () => {
|
||||
const conversation = {};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.memberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -35,7 +39,7 @@ describe('getGroupMemberships', () => {
|
|||
it('returns an empty array if passed an empty array', () => {
|
||||
const conversation = { memberships: [] };
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.memberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -45,13 +49,13 @@ describe('getGroupMemberships', () => {
|
|||
const conversation = {
|
||||
memberships: [
|
||||
{
|
||||
conversationId: 'garbage',
|
||||
uuid: UUID.generate().toString(),
|
||||
isAdmin: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.memberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -61,13 +65,13 @@ describe('getGroupMemberships', () => {
|
|||
const conversation = {
|
||||
memberships: [
|
||||
{
|
||||
conversationId: unregisteredConversation.id,
|
||||
uuid: unregisteredConversation.uuid,
|
||||
isAdmin: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.memberships;
|
||||
|
||||
assert.lengthOf(result, 1);
|
||||
|
@ -81,17 +85,17 @@ describe('getGroupMemberships', () => {
|
|||
const conversation = {
|
||||
memberships: [
|
||||
{
|
||||
conversationId: normalConversation2.id,
|
||||
uuid: normalConversation2.uuid,
|
||||
isAdmin: false,
|
||||
},
|
||||
{
|
||||
conversationId: normalConversation1.id,
|
||||
uuid: normalConversation1.uuid,
|
||||
isAdmin: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.memberships;
|
||||
|
||||
assert.lengthOf(result, 2);
|
||||
|
@ -110,7 +114,7 @@ describe('getGroupMemberships', () => {
|
|||
it('returns an empty array if passed undefined', () => {
|
||||
const conversation = {};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingApprovalMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -119,7 +123,7 @@ describe('getGroupMemberships', () => {
|
|||
it('returns an empty array if passed an empty array', () => {
|
||||
const conversation = { pendingApprovalMemberships: [] };
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingApprovalMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -127,10 +131,10 @@ describe('getGroupMemberships', () => {
|
|||
|
||||
it("filters out conversation IDs that don't exist", () => {
|
||||
const conversation = {
|
||||
pendingApprovalMemberships: [{ conversationId: 'garbage' }],
|
||||
pendingApprovalMemberships: [{ uuid: UUID.generate().toString() }],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingApprovalMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -138,12 +142,10 @@ describe('getGroupMemberships', () => {
|
|||
|
||||
it('filters out unregistered conversations', () => {
|
||||
const conversation = {
|
||||
pendingApprovalMemberships: [
|
||||
{ conversationId: unregisteredConversation.id },
|
||||
],
|
||||
pendingApprovalMemberships: [{ uuid: unregisteredConversation.uuid }],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingApprovalMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -152,12 +154,12 @@ describe('getGroupMemberships', () => {
|
|||
it('hydrates pending-approval memberships', () => {
|
||||
const conversation = {
|
||||
pendingApprovalMemberships: [
|
||||
{ conversationId: normalConversation2.id },
|
||||
{ conversationId: normalConversation1.id },
|
||||
{ uuid: normalConversation2.uuid },
|
||||
{ uuid: normalConversation1.uuid },
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingApprovalMemberships;
|
||||
|
||||
assert.lengthOf(result, 2);
|
||||
|
@ -170,7 +172,7 @@ describe('getGroupMemberships', () => {
|
|||
it('returns an empty array if passed undefined', () => {
|
||||
const conversation = {};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -179,7 +181,7 @@ describe('getGroupMemberships', () => {
|
|||
it('returns an empty array if passed an empty array', () => {
|
||||
const conversation = { pendingMemberships: [] };
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -188,11 +190,14 @@ describe('getGroupMemberships', () => {
|
|||
it("filters out conversation IDs that don't exist", () => {
|
||||
const conversation = {
|
||||
pendingMemberships: [
|
||||
{ conversationId: 'garbage', addedByUserId: normalConversation1.id },
|
||||
{
|
||||
uuid: UUID.generate().toString(),
|
||||
addedByUserId: normalConversation1.uuid,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
|
@ -202,37 +207,40 @@ describe('getGroupMemberships', () => {
|
|||
const conversation = {
|
||||
pendingMemberships: [
|
||||
{
|
||||
conversationId: unregisteredConversation.id,
|
||||
addedByUserId: normalConversation1.id,
|
||||
uuid: unregisteredConversation.uuid,
|
||||
addedByUserId: normalConversation1.uuid,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingMemberships;
|
||||
|
||||
assert.isEmpty(result);
|
||||
});
|
||||
|
||||
it('hydrates pending memberships', () => {
|
||||
const abc = UUID.generate().toString();
|
||||
const xyz = UUID.generate().toString();
|
||||
|
||||
const conversation = {
|
||||
pendingMemberships: [
|
||||
{ conversationId: normalConversation2.id, addedByUserId: 'abc' },
|
||||
{ conversationId: normalConversation1.id, addedByUserId: 'xyz' },
|
||||
{ uuid: normalConversation2.uuid, addedByUserId: abc },
|
||||
{ uuid: normalConversation1.uuid, addedByUserId: xyz },
|
||||
],
|
||||
};
|
||||
|
||||
const result = getGroupMemberships(conversation, getConversationById)
|
||||
const result = getGroupMemberships(conversation, getConversationByUuid)
|
||||
.pendingMemberships;
|
||||
|
||||
assert.lengthOf(result, 2);
|
||||
assert.deepEqual(result[0], {
|
||||
member: normalConversation2,
|
||||
metadata: { addedByUserId: 'abc' },
|
||||
metadata: { addedByUserId: abc },
|
||||
});
|
||||
assert.deepEqual(result[1], {
|
||||
member: normalConversation1,
|
||||
metadata: { addedByUserId: 'xyz' },
|
||||
metadata: { addedByUserId: xyz },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isValidGuid } from '../../util/isValidGuid';
|
||||
|
||||
describe('isValidGuid', () => {
|
||||
const LOWERCASE_V4_UUID = '9cb737ce-2bb3-4c21-9fe0-d286caa0ca68';
|
||||
|
||||
it('returns false for non-strings', () => {
|
||||
assert.isFalse(isValidGuid(undefined));
|
||||
assert.isFalse(isValidGuid(null));
|
||||
assert.isFalse(isValidGuid(1234));
|
||||
});
|
||||
|
||||
it('returns false for non-UUID strings', () => {
|
||||
assert.isFalse(isValidGuid(''));
|
||||
assert.isFalse(isValidGuid('hello world'));
|
||||
assert.isFalse(isValidGuid(` ${LOWERCASE_V4_UUID}`));
|
||||
assert.isFalse(isValidGuid(`${LOWERCASE_V4_UUID} `));
|
||||
});
|
||||
|
||||
it("returns false for UUIDs that aren't version 4", () => {
|
||||
assert.isFalse(isValidGuid('a200a6e0-d2d9-11eb-bda7-dd5936a30ddf'));
|
||||
assert.isFalse(isValidGuid('2adb8b83-4f2c-55ca-a481-7f98b716e615'));
|
||||
});
|
||||
|
||||
it('returns true for v4 UUIDs', () => {
|
||||
assert.isTrue(isValidGuid(LOWERCASE_V4_UUID));
|
||||
assert.isTrue(isValidGuid(LOWERCASE_V4_UUID.toUpperCase()));
|
||||
});
|
||||
});
|
|
@ -10,7 +10,6 @@ import {
|
|||
SenderKeyRecord,
|
||||
SessionRecord,
|
||||
} from '@signalapp/signal-client';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import { signal } from '../protobuf/compiled';
|
||||
import { sessionStructureToBytes } from '../util/sessionTranslation';
|
||||
|
@ -36,8 +35,8 @@ const {
|
|||
} = signal.proto.storage;
|
||||
|
||||
describe('SignalProtocolStore', () => {
|
||||
const ourUuid = new UUID(getGuid());
|
||||
const theirUuid = new UUID(getGuid());
|
||||
const ourUuid = UUID.generate();
|
||||
const theirUuid = UUID.generate();
|
||||
let store: SignalProtocolStore;
|
||||
let identityKey: KeyPairType;
|
||||
let testKey: KeyPairType;
|
||||
|
@ -170,7 +169,7 @@ describe('SignalProtocolStore', () => {
|
|||
|
||||
describe('senderKeys', () => {
|
||||
it('roundtrips in memory', async () => {
|
||||
const distributionId = window.getGuid();
|
||||
const distributionId = UUID.generate().toString();
|
||||
const expected = getSenderKeyRecord();
|
||||
|
||||
const deviceId = 1;
|
||||
|
@ -200,7 +199,7 @@ describe('SignalProtocolStore', () => {
|
|||
});
|
||||
|
||||
it('roundtrips through database', async () => {
|
||||
const distributionId = window.getGuid();
|
||||
const distributionId = UUID.generate().toString();
|
||||
const expected = getSenderKeyRecord();
|
||||
|
||||
const deviceId = 1;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
describe('Conversations', () => {
|
||||
async function resetConversationController(): Promise<void> {
|
||||
|
@ -16,14 +17,14 @@ describe('Conversations', () => {
|
|||
|
||||
it('updates lastMessage even in race conditions with db', async () => {
|
||||
const ourNumber = '+15550000000';
|
||||
const ourUuid = window.getGuid();
|
||||
const ourUuid = UUID.generate().toString();
|
||||
|
||||
// Creating a fake conversation
|
||||
const conversation = new window.Whisper.Conversation({
|
||||
avatars: [],
|
||||
id: window.getGuid(),
|
||||
id: UUID.generate().toString(),
|
||||
e164: '+15551234567',
|
||||
uuid: window.getGuid(),
|
||||
uuid: UUID.generate().toString(),
|
||||
type: 'private',
|
||||
inbox_position: 0,
|
||||
isPinned: false,
|
||||
|
@ -56,7 +57,7 @@ describe('Conversations', () => {
|
|||
hasAttachments: false,
|
||||
hasFileAttachments: false,
|
||||
hasVisualMediaAttachments: false,
|
||||
id: window.getGuid(),
|
||||
id: UUID.generate().toString(),
|
||||
received_at: now,
|
||||
sent_at: now,
|
||||
timestamp: now,
|
||||
|
|
|
@ -10,6 +10,7 @@ import MessageSender from '../../textsecure/SendMessage';
|
|||
import type { WebAPIType } from '../../textsecure/WebAPI';
|
||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||
import type { StorageAccessType } from '../../types/Storage.d';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
|
||||
describe('Message', () => {
|
||||
|
@ -32,7 +33,7 @@ describe('Message', () => {
|
|||
|
||||
const source = '+1 415-555-5555';
|
||||
const me = '+14155555556';
|
||||
const ourUuid = window.getGuid();
|
||||
const ourUuid = UUID.generate().toString();
|
||||
|
||||
function createMessage(attrs: { [key: string]: unknown }) {
|
||||
const messages = new window.Whisper.MessageCollection();
|
||||
|
@ -138,7 +139,7 @@ describe('Message', () => {
|
|||
|
||||
const fakeDataMessage = new Uint8Array(0);
|
||||
const conversation1Uuid = conversation1.get('uuid');
|
||||
const ignoredUuid = window.getGuid();
|
||||
const ignoredUuid = UUID.generate().toString();
|
||||
|
||||
if (!conversation1Uuid) {
|
||||
throw new Error('Test setup failed: conversation1 should have a UUID');
|
||||
|
|
|
@ -12,11 +12,10 @@ import type { MentionCompletionOptions } from '../../../quill/mentions/completio
|
|||
import { MentionCompletion } from '../../../quill/mentions/completion';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import { MemberRepository } from '../../../quill/memberRepository';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultConversationWithUuid } from '../../../test-both/helpers/getDefaultConversation';
|
||||
|
||||
const me: ConversationType = getDefaultConversation({
|
||||
const me: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '666777',
|
||||
uuid: 'pqrstuv',
|
||||
title: 'Fred Savage',
|
||||
firstName: 'Fred',
|
||||
profileName: 'Fred S.',
|
||||
|
@ -28,9 +27,8 @@ const me: ConversationType = getDefaultConversation({
|
|||
});
|
||||
|
||||
const members: Array<ConversationType> = [
|
||||
getDefaultConversation({
|
||||
getDefaultConversationWithUuid({
|
||||
id: '555444',
|
||||
uuid: 'abcdefg',
|
||||
title: 'Mahershala Ali',
|
||||
firstName: 'Mahershala',
|
||||
profileName: 'Mahershala A.',
|
||||
|
@ -39,9 +37,8 @@ const members: Array<ConversationType> = [
|
|||
markedUnread: false,
|
||||
areWeAdmin: false,
|
||||
}),
|
||||
getDefaultConversation({
|
||||
getDefaultConversationWithUuid({
|
||||
id: '333222',
|
||||
uuid: 'hijklmno',
|
||||
title: 'Shia LaBeouf',
|
||||
firstName: 'Shia',
|
||||
profileName: 'Shia L.',
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { times } from 'lodash';
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
import type { ConversationAttributesType } from '../model-types.d';
|
||||
import { UUID } from '../types/UUID';
|
||||
|
||||
import { routineProfileRefresh } from '../routineProfileRefresh';
|
||||
import * as getProfileStub from '../util/getProfile';
|
||||
|
@ -26,12 +26,12 @@ describe('routineProfileRefresh', () => {
|
|||
overrideAttributes: Partial<ConversationAttributesType> = {}
|
||||
): ConversationModel {
|
||||
const result = new ConversationModel({
|
||||
accessKey: uuid(),
|
||||
accessKey: UUID.generate().toString(),
|
||||
active_at: Date.now(),
|
||||
draftAttachments: [],
|
||||
draftBodyRanges: [],
|
||||
draftTimestamp: null,
|
||||
id: uuid(),
|
||||
id: UUID.generate().toString(),
|
||||
inbox_position: 0,
|
||||
isPinned: false,
|
||||
lastMessageDeletedForEveryone: false,
|
||||
|
@ -43,7 +43,7 @@ describe('routineProfileRefresh', () => {
|
|||
messageRequestResponseType: 0,
|
||||
muteExpiresAt: 0,
|
||||
profileAvatar: undefined,
|
||||
profileKeyCredential: uuid(),
|
||||
profileKeyCredential: UUID.generate().toString(),
|
||||
profileKeyVersion: '',
|
||||
profileSharing: true,
|
||||
quotedMessageId: null,
|
||||
|
@ -52,7 +52,7 @@ describe('routineProfileRefresh', () => {
|
|||
sharedGroupNames: [],
|
||||
timestamp: Date.now(),
|
||||
type: 'private',
|
||||
uuid: uuid(),
|
||||
uuid: UUID.generate().toString(),
|
||||
version: 2,
|
||||
...overrideAttributes,
|
||||
});
|
||||
|
@ -85,7 +85,7 @@ describe('routineProfileRefresh', () => {
|
|||
|
||||
await routineProfileRefresh({
|
||||
allConversations: [conversation1, conversation2],
|
||||
ourConversationId: uuid(),
|
||||
ourConversationId: UUID.generate().toString(),
|
||||
storage,
|
||||
});
|
||||
|
||||
|
@ -99,7 +99,7 @@ describe('routineProfileRefresh', () => {
|
|||
|
||||
await routineProfileRefresh({
|
||||
allConversations: [conversation1, conversation2],
|
||||
ourConversationId: uuid(),
|
||||
ourConversationId: UUID.generate().toString(),
|
||||
storage: makeStorage(),
|
||||
});
|
||||
|
||||
|
@ -124,7 +124,7 @@ describe('routineProfileRefresh', () => {
|
|||
|
||||
await routineProfileRefresh({
|
||||
allConversations: [recentlyActive, inactive, neverActive],
|
||||
ourConversationId: uuid(),
|
||||
ourConversationId: UUID.generate().toString(),
|
||||
storage: makeStorage(),
|
||||
});
|
||||
|
||||
|
@ -176,7 +176,7 @@ describe('routineProfileRefresh', () => {
|
|||
|
||||
await routineProfileRefresh({
|
||||
allConversations: [neverRefreshed, recentlyFetched],
|
||||
ourConversationId: uuid(),
|
||||
ourConversationId: UUID.generate().toString(),
|
||||
storage: makeStorage(),
|
||||
});
|
||||
|
||||
|
@ -218,7 +218,7 @@ describe('routineProfileRefresh', () => {
|
|||
memberWhoHasRecentlyRefreshed,
|
||||
groupConversation,
|
||||
],
|
||||
ourConversationId: uuid(),
|
||||
ourConversationId: UUID.generate().toString(),
|
||||
storage: makeStorage(),
|
||||
});
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import {
|
|||
GroupCallConnectionState,
|
||||
GroupCallJoinState,
|
||||
} from '../../../types/Calling';
|
||||
import { UUID } from '../../../types/UUID';
|
||||
import type { UUIDStringType } from '../../../types/UUID';
|
||||
|
||||
describe('calling duck', () => {
|
||||
const stateWithDirectCall: CallingStateType = {
|
||||
|
@ -68,6 +70,11 @@ describe('calling duck', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const creatorUuid = UUID.generate().toString();
|
||||
const differentCreatorUuid = UUID.generate().toString();
|
||||
const remoteUuid = UUID.generate().toString();
|
||||
const ringerUuid = UUID.generate().toString();
|
||||
|
||||
const stateWithGroupCall = {
|
||||
...getEmptyState(),
|
||||
callsByConversation: {
|
||||
|
@ -77,15 +84,15 @@ describe('calling duck', () => {
|
|||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.NotJoined,
|
||||
peekInfo: {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -107,7 +114,7 @@ describe('calling duck', () => {
|
|||
'fake-group-call-conversation-id'
|
||||
],
|
||||
ringId: BigInt(123),
|
||||
ringerUuid: '789',
|
||||
ringerUuid: UUID.generate().toString(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -127,7 +134,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const ourUuid = 'ebf5fd79-9344-4ec1-b5c9-af463572caf5';
|
||||
const ourUuid = UUID.generate().toString();
|
||||
|
||||
const getEmptyRootState = () => {
|
||||
const rootState = rootReducer(undefined, noopAction());
|
||||
|
@ -607,7 +614,7 @@ describe('calling duck', () => {
|
|||
],
|
||||
connectionState: GroupCallConnectionState.NotConnected,
|
||||
ringId: BigInt(123),
|
||||
ringerUuid: '789',
|
||||
ringerUuid: UUID.generate().toString(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -899,15 +906,15 @@ describe('calling duck', () => {
|
|||
hasLocalAudio: true,
|
||||
hasLocalVideo: false,
|
||||
peekInfo: {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -927,15 +934,15 @@ describe('calling duck', () => {
|
|||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.Joining,
|
||||
peekInfo: {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1022,7 +1029,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1048,7 +1055,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1071,7 +1078,7 @@ describe('calling duck', () => {
|
|||
'fake-group-call-conversation-id'
|
||||
],
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
|
||||
ringerUuid,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1090,7 +1097,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1107,7 +1114,7 @@ describe('calling duck', () => {
|
|||
{
|
||||
callMode: CallMode.Group,
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
|
||||
ringerUuid,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1122,7 +1129,7 @@ describe('calling duck', () => {
|
|||
'fake-group-call-conversation-id'
|
||||
],
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '55addfd8-09ed-4f5b-b42e-01058898d13b',
|
||||
ringerUuid,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1141,7 +1148,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1179,7 +1186,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1210,7 +1217,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1251,7 +1258,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 456,
|
||||
hasRemoteAudio: false,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1423,7 +1430,7 @@ describe('calling duck', () => {
|
|||
const action = receiveIncomingGroupCall({
|
||||
conversationId: 'fake-group-call-conversation-id',
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
});
|
||||
const result = reducer(stateWithIncomingGroupCall, action);
|
||||
|
||||
|
@ -1446,7 +1453,7 @@ describe('calling duck', () => {
|
|||
const action = receiveIncomingGroupCall({
|
||||
conversationId: 'fake-group-call-conversation-id',
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
});
|
||||
const result = reducer(state, action);
|
||||
|
||||
|
@ -1457,7 +1464,7 @@ describe('calling duck', () => {
|
|||
const action = receiveIncomingGroupCall({
|
||||
conversationId: 'fake-group-call-conversation-id',
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
});
|
||||
const result = reducer(getEmptyState(), action);
|
||||
|
||||
|
@ -1475,7 +1482,7 @@ describe('calling duck', () => {
|
|||
},
|
||||
remoteParticipants: [],
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1484,7 +1491,7 @@ describe('calling duck', () => {
|
|||
const action = receiveIncomingGroupCall({
|
||||
conversationId: 'fake-group-call-conversation-id',
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
});
|
||||
const result = reducer(stateWithGroupCall, action);
|
||||
|
||||
|
@ -1492,7 +1499,7 @@ describe('calling duck', () => {
|
|||
result.callsByConversation['fake-group-call-conversation-id'],
|
||||
{
|
||||
ringId: BigInt(456),
|
||||
ringerUuid: '208b8ce6-3a73-48ee-9c8a-32e6196f6e96',
|
||||
ringerUuid,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1644,15 +1651,15 @@ describe('calling duck', () => {
|
|||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.NotJoined,
|
||||
peekInfo: {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1670,15 +1677,15 @@ describe('calling duck', () => {
|
|||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.NotJoined,
|
||||
peekInfo: {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1734,7 +1741,7 @@ describe('calling duck', () => {
|
|||
peekInfo: undefined,
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1749,8 +1756,8 @@ describe('calling duck', () => {
|
|||
const call =
|
||||
result.callsByConversation['fake-group-call-conversation-id'];
|
||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||
uuids: ['456'],
|
||||
creatorUuid: '456',
|
||||
uuids: [creatorUuid],
|
||||
creatorUuid,
|
||||
eraId: 'xyz',
|
||||
maxDevices: 16,
|
||||
deviceCount: 1,
|
||||
|
@ -1769,15 +1776,15 @@ describe('calling duck', () => {
|
|||
connectionState: GroupCallConnectionState.Connected,
|
||||
joinState: GroupCallJoinState.NotJoined,
|
||||
peekInfo: {
|
||||
uuids: ['999'],
|
||||
creatorUuid: '999',
|
||||
uuids: [differentCreatorUuid],
|
||||
creatorUuid: differentCreatorUuid,
|
||||
eraId: 'abc',
|
||||
maxDevices: 5,
|
||||
deviceCount: 1,
|
||||
},
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1792,8 +1799,8 @@ describe('calling duck', () => {
|
|||
const call =
|
||||
result.callsByConversation['fake-group-call-conversation-id'];
|
||||
assert.deepEqual(call?.callMode === CallMode.Group && call.peekInfo, {
|
||||
uuids: ['999'],
|
||||
creatorUuid: '999',
|
||||
uuids: [differentCreatorUuid],
|
||||
creatorUuid: differentCreatorUuid,
|
||||
eraId: 'abc',
|
||||
maxDevices: 5,
|
||||
deviceCount: 1,
|
||||
|
@ -1810,7 +1817,7 @@ describe('calling duck', () => {
|
|||
'fake-group-call-conversation-id'
|
||||
],
|
||||
ringId: BigInt(987),
|
||||
ringerUuid: 'd59f05f7-3be8-4d44-a1e8-0d7cb5677ed8',
|
||||
ringerUuid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1825,7 +1832,7 @@ describe('calling duck', () => {
|
|||
peekInfo: undefined,
|
||||
remoteParticipants: [
|
||||
{
|
||||
uuid: '123',
|
||||
uuid: remoteUuid,
|
||||
demuxId: 123,
|
||||
hasRemoteAudio: true,
|
||||
hasRemoteVideo: true,
|
||||
|
@ -1844,10 +1851,7 @@ describe('calling duck', () => {
|
|||
}
|
||||
|
||||
assert.strictEqual(call.ringId, BigInt(987));
|
||||
assert.strictEqual(
|
||||
call.ringerUuid,
|
||||
'd59f05f7-3be8-4d44-a1e8-0d7cb5677ed8'
|
||||
);
|
||||
assert.strictEqual(call.ringerUuid, ringerUuid);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2036,47 +2040,33 @@ describe('calling duck', () => {
|
|||
});
|
||||
|
||||
describe('isAnybodyElseInGroupCall', () => {
|
||||
const fakePeekInfo = (uuids: Array<string>) => ({
|
||||
const fakePeekInfo = (uuids: Array<UUIDStringType>) => ({
|
||||
uuids,
|
||||
maxDevices: 5,
|
||||
deviceCount: uuids.length,
|
||||
});
|
||||
|
||||
it('returns false if the peek info has no participants', () => {
|
||||
assert.isFalse(
|
||||
isAnybodyElseInGroupCall(
|
||||
fakePeekInfo([]),
|
||||
'2cd7b14c-3433-4b3c-9685-1ef1e2d26db2'
|
||||
)
|
||||
);
|
||||
assert.isFalse(isAnybodyElseInGroupCall(fakePeekInfo([]), remoteUuid));
|
||||
});
|
||||
|
||||
it('returns false if the peek info has one participant, you', () => {
|
||||
assert.isFalse(
|
||||
isAnybodyElseInGroupCall(
|
||||
fakePeekInfo(['2cd7b14c-3433-4b3c-9685-1ef1e2d26db2']),
|
||||
'2cd7b14c-3433-4b3c-9685-1ef1e2d26db2'
|
||||
)
|
||||
isAnybodyElseInGroupCall(fakePeekInfo([creatorUuid]), creatorUuid)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if the peek info has one participant, someone else', () => {
|
||||
assert.isTrue(
|
||||
isAnybodyElseInGroupCall(
|
||||
fakePeekInfo(['ca0ae16c-2936-4c68-86b1-a6f82e8fe67f']),
|
||||
'2cd7b14c-3433-4b3c-9685-1ef1e2d26db2'
|
||||
)
|
||||
isAnybodyElseInGroupCall(fakePeekInfo([creatorUuid]), remoteUuid)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if the peek info has two participants, you and someone else', () => {
|
||||
assert.isTrue(
|
||||
isAnybodyElseInGroupCall(
|
||||
fakePeekInfo([
|
||||
'ca0ae16c-2936-4c68-86b1-a6f82e8fe67f',
|
||||
'2cd7b14c-3433-4b3c-9685-1ef1e2d26db2',
|
||||
]),
|
||||
'2cd7b14c-3433-4b3c-9685-1ef1e2d26db2'
|
||||
fakePeekInfo([creatorUuid, remoteUuid]),
|
||||
remoteUuid
|
||||
)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -28,8 +28,12 @@ import {
|
|||
import { ReadStatus } from '../../../messages/MessageReadStatus';
|
||||
import { ContactSpoofingType } from '../../../util/contactSpoofing';
|
||||
import { CallMode } from '../../../types/Calling';
|
||||
import { UUID } from '../../../types/UUID';
|
||||
import * as groups from '../../../groups';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import {
|
||||
getDefaultConversation,
|
||||
getDefaultConversationWithUuid,
|
||||
} from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultAvatars } from '../../../types/Avatar';
|
||||
import {
|
||||
defaultStartDirectConversationComposerState,
|
||||
|
@ -40,7 +44,7 @@ import {
|
|||
const {
|
||||
cantAddContactToGroup,
|
||||
clearGroupCreationError,
|
||||
clearInvitedConversationsForNewlyCreatedGroup,
|
||||
clearInvitedUuidsForNewlyCreatedGroup,
|
||||
closeCantAddContactToGroupModal,
|
||||
closeContactSpoofingReview,
|
||||
closeMaximumGroupSizeModal,
|
||||
|
@ -225,26 +229,24 @@ describe('both/state/ducks/conversations', () => {
|
|||
});
|
||||
|
||||
it('adds and removes uuid-only contact', () => {
|
||||
const removed = getDefaultConversation({
|
||||
const removed = getDefaultConversationWithUuid({
|
||||
id: 'id-removed',
|
||||
uuid: 'uuid-removed',
|
||||
e164: undefined,
|
||||
});
|
||||
|
||||
const state = {
|
||||
...getEmptyState(),
|
||||
conversationsByuuid: {
|
||||
'uuid-removed': removed,
|
||||
[removed.uuid]: removed,
|
||||
},
|
||||
};
|
||||
const added = getDefaultConversation({
|
||||
const added = getDefaultConversationWithUuid({
|
||||
id: 'id-added',
|
||||
uuid: 'uuid-added',
|
||||
e164: undefined,
|
||||
});
|
||||
|
||||
const expected = {
|
||||
'uuid-added': added,
|
||||
[added.uuid]: added,
|
||||
};
|
||||
|
||||
const actual = updateConversationLookups(added, removed, state);
|
||||
|
@ -307,6 +309,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
const messageId = 'message-guid-1';
|
||||
const messageIdTwo = 'message-guid-2';
|
||||
const messageIdThree = 'message-guid-3';
|
||||
const sourceUuid = UUID.generate().toString();
|
||||
|
||||
function getDefaultMessage(id: string): MessageType {
|
||||
return {
|
||||
|
@ -316,7 +319,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
received_at: previousTime,
|
||||
sent_at: previousTime,
|
||||
source: 'source',
|
||||
sourceUuid: 'sourceUuid',
|
||||
sourceUuid,
|
||||
timestamp: previousTime,
|
||||
type: 'incoming' as const,
|
||||
readStatus: ReadStatus.Read,
|
||||
|
@ -491,16 +494,19 @@ describe('both/state/ducks/conversations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('CLEAR_INVITED_CONVERSATIONS_FOR_NEWLY_CREATED_GROUP', () => {
|
||||
it('clears the list of invited conversation IDs', () => {
|
||||
describe('CLEAR_INVITED_UUIDS_FOR_NEWLY_CREATED_GROUP', () => {
|
||||
it('clears the list of invited conversation UUIDs', () => {
|
||||
const state = {
|
||||
...getEmptyState(),
|
||||
invitedConversationIdsForNewlyCreatedGroup: ['abc123', 'def456'],
|
||||
invitedUuidsForNewlyCreatedGroup: [
|
||||
UUID.generate().toString(),
|
||||
UUID.generate().toString(),
|
||||
],
|
||||
};
|
||||
const action = clearInvitedConversationsForNewlyCreatedGroup();
|
||||
const action = clearInvitedUuidsForNewlyCreatedGroup();
|
||||
const result = reducer(state, action);
|
||||
|
||||
assert.isUndefined(result.invitedConversationIdsForNewlyCreatedGroup);
|
||||
assert.isUndefined(result.invitedUuidsForNewlyCreatedGroup);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -775,13 +781,14 @@ describe('both/state/ducks/conversations', () => {
|
|||
});
|
||||
|
||||
it('dispatches a CREATE_GROUP_FULFILLED event (which updates the newly-created conversation IDs), triggers a showConversation event and switches to the associated conversation on success', async () => {
|
||||
const abc = UUID.fromPrefix('abc').toString();
|
||||
createGroupStub.resolves({
|
||||
id: '9876',
|
||||
get: (key: string) => {
|
||||
if (key !== 'pendingMembersV2') {
|
||||
throw new Error('This getter is not set up for this test');
|
||||
}
|
||||
return [{ conversationId: 'xyz999' }];
|
||||
return [{ uuid: abc }];
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -805,14 +812,12 @@ describe('both/state/ducks/conversations', () => {
|
|||
|
||||
sinon.assert.calledWith(dispatch, {
|
||||
type: 'CREATE_GROUP_FULFILLED',
|
||||
payload: { invitedConversationIds: ['xyz999'] },
|
||||
payload: { invitedUuids: [abc] },
|
||||
});
|
||||
|
||||
const fulfilledAction = dispatch.getCall(1).args[0];
|
||||
const result = reducer(conversationsState, fulfilledAction);
|
||||
assert.deepEqual(result.invitedConversationIdsForNewlyCreatedGroup, [
|
||||
'xyz999',
|
||||
]);
|
||||
assert.deepEqual(result.invitedUuidsForNewlyCreatedGroup, [abc]);
|
||||
|
||||
sinon.assert.calledWith(dispatch, {
|
||||
type: 'SWITCH_TO_ASSOCIATED_VIEW',
|
||||
|
@ -1765,14 +1770,12 @@ describe('both/state/ducks/conversations', () => {
|
|||
});
|
||||
|
||||
describe('COLORS_CHANGED', () => {
|
||||
const abc = getDefaultConversation({
|
||||
const abc = getDefaultConversationWithUuid({
|
||||
id: 'abc',
|
||||
uuid: 'abc',
|
||||
conversationColor: 'wintergreen',
|
||||
});
|
||||
const def = getDefaultConversation({
|
||||
const def = getDefaultConversationWithUuid({
|
||||
id: 'def',
|
||||
uuid: 'def',
|
||||
conversationColor: 'infrared',
|
||||
});
|
||||
const ghi = getDefaultConversation({
|
||||
|
@ -1820,8 +1823,12 @@ describe('both/state/ducks/conversations', () => {
|
|||
assert.isUndefined(nextState.conversationLookup.def.conversationColor);
|
||||
assert.isUndefined(nextState.conversationLookup.ghi.conversationColor);
|
||||
assert.isUndefined(nextState.conversationLookup.jkl.conversationColor);
|
||||
assert.isUndefined(nextState.conversationsByUuid.abc.conversationColor);
|
||||
assert.isUndefined(nextState.conversationsByUuid.def.conversationColor);
|
||||
assert.isUndefined(
|
||||
nextState.conversationsByUuid[abc.uuid].conversationColor
|
||||
);
|
||||
assert.isUndefined(
|
||||
nextState.conversationsByUuid[def.uuid].conversationColor
|
||||
);
|
||||
assert.isUndefined(nextState.conversationsByE164.ghi.conversationColor);
|
||||
assert.isUndefined(
|
||||
nextState.conversationsByGroupId.jkl.conversationColor
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import sinon from 'sinon';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
import type { ConversationAttributesType } from '../model-types.d';
|
||||
import type SendMessage from '../textsecure/SendMessage';
|
||||
import { UUID } from '../types/UUID';
|
||||
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
|
||||
|
@ -41,7 +41,7 @@ describe('updateConversationsWithUuidLookup', () => {
|
|||
'FakeConversationController is not set up for this case (E164 must be provided)'
|
||||
);
|
||||
assert(
|
||||
uuid,
|
||||
uuidFromServer,
|
||||
'FakeConversationController is not set up for this case (UUID must be provided)'
|
||||
);
|
||||
assert(
|
||||
|
@ -81,7 +81,7 @@ describe('updateConversationsWithUuidLookup', () => {
|
|||
attributes: Readonly<Partial<ConversationAttributesType>> = {}
|
||||
): ConversationModel {
|
||||
return new ConversationModel({
|
||||
id: uuid(),
|
||||
id: UUID.generate().toString(),
|
||||
inbox_position: 0,
|
||||
isPinned: false,
|
||||
lastMessageDeletedForEveryone: false,
|
||||
|
@ -128,7 +128,7 @@ describe('updateConversationsWithUuidLookup', () => {
|
|||
conversationController: new FakeConversationController(),
|
||||
conversations: [
|
||||
createConversation(),
|
||||
createConversation({ uuid: uuid() }),
|
||||
createConversation({ uuid: UUID.generate().toString() }),
|
||||
],
|
||||
messaging: fakeMessaging,
|
||||
});
|
||||
|
@ -140,11 +140,11 @@ describe('updateConversationsWithUuidLookup', () => {
|
|||
const conversation1 = createConversation({ e164: '+13215559876' });
|
||||
const conversation2 = createConversation({
|
||||
e164: '+16545559876',
|
||||
uuid: 'should be overwritten',
|
||||
uuid: UUID.generate().toString(), // should be overwritten
|
||||
});
|
||||
|
||||
const uuid1 = uuid();
|
||||
const uuid2 = uuid();
|
||||
const uuid1 = UUID.generate().toString();
|
||||
const uuid2 = UUID.generate().toString();
|
||||
|
||||
fakeGetUuidsForE164s.resolves({
|
||||
'+13215559876': uuid1,
|
||||
|
@ -187,7 +187,7 @@ describe('updateConversationsWithUuidLookup', () => {
|
|||
});
|
||||
|
||||
it("doesn't mark conversations unregistered if we already had a UUID for them, even if the server doesn't return one", async () => {
|
||||
const existingUuid = uuid();
|
||||
const existingUuid = UUID.generate().toString();
|
||||
const conversation = createConversation({
|
||||
e164: '+13215559876',
|
||||
uuid: existingUuid,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import * as Bytes from '../../Bytes';
|
||||
import {
|
||||
|
@ -11,6 +10,7 @@ import {
|
|||
decryptProfileName,
|
||||
decryptProfile,
|
||||
} from '../../Crypto';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import { encryptProfileData } from '../../util/encryptProfileData';
|
||||
|
||||
describe('encryptProfileData', () => {
|
||||
|
@ -22,7 +22,7 @@ describe('encryptProfileData', () => {
|
|||
familyName: 'Kid',
|
||||
firstName: 'Zombie',
|
||||
profileKey: Bytes.toBase64(keyBuffer),
|
||||
uuid: uuid(),
|
||||
uuid: UUID.generate().toString(),
|
||||
|
||||
// To satisfy TS
|
||||
acceptedMessageRequest: true,
|
||||
|
|
|
@ -5,11 +5,10 @@ import { assert } from 'chai';
|
|||
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import { MemberRepository } from '../../quill/memberRepository';
|
||||
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultConversationWithUuid } from '../../test-both/helpers/getDefaultConversation';
|
||||
|
||||
const memberMahershala: ConversationType = getDefaultConversation({
|
||||
const memberMahershala: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '555444',
|
||||
uuid: 'abcdefg',
|
||||
title: 'Pal',
|
||||
firstName: 'Mahershala',
|
||||
profileName: 'Mr Ali',
|
||||
|
@ -20,9 +19,8 @@ const memberMahershala: ConversationType = getDefaultConversation({
|
|||
areWeAdmin: false,
|
||||
});
|
||||
|
||||
const memberShia: ConversationType = getDefaultConversation({
|
||||
const memberShia: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '333222',
|
||||
uuid: 'hijklmno',
|
||||
title: 'Buddy',
|
||||
firstName: 'Shia',
|
||||
profileName: 'Sr LaBeouf',
|
||||
|
@ -35,9 +33,8 @@ const memberShia: ConversationType = getDefaultConversation({
|
|||
|
||||
const members: Array<ConversationType> = [memberMahershala, memberShia];
|
||||
|
||||
const singleMember: ConversationType = getDefaultConversation({
|
||||
const singleMember: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '666777',
|
||||
uuid: 'pqrstuv',
|
||||
title: 'The Guy',
|
||||
firstName: 'Jeff',
|
||||
profileName: 'Jr Klaus',
|
||||
|
@ -85,7 +82,7 @@ describe('MemberRepository', () => {
|
|||
|
||||
it('returns a matched member', () => {
|
||||
const memberRepository = new MemberRepository(members);
|
||||
assert.isDefined(memberRepository.getMemberByUuid('abcdefg'));
|
||||
assert.isDefined(memberRepository.getMemberByUuid(memberMahershala.uuid));
|
||||
});
|
||||
|
||||
it('returns undefined when it does not have the member', () => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import Delta from 'quill-delta';
|
|||
import { matchMention } from '../../../quill/mentions/matchers';
|
||||
import { MemberRepository } from '../../../quill/memberRepository';
|
||||
import type { ConversationType } from '../../../state/ducks/conversations';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { getDefaultConversationWithUuid } from '../../../test-both/helpers/getDefaultConversation';
|
||||
|
||||
class FakeTokenList<T> extends Array<T> {
|
||||
constructor(elements: Array<T>) {
|
||||
|
@ -38,9 +38,8 @@ const createMockMentionBlotElement = (
|
|||
dataset: Record<string, string>
|
||||
): HTMLElement => createMockElement('mention-blot', dataset);
|
||||
|
||||
const memberMahershala: ConversationType = getDefaultConversation({
|
||||
const memberMahershala: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '555444',
|
||||
uuid: 'abcdefg',
|
||||
title: 'Mahershala Ali',
|
||||
firstName: 'Mahershala',
|
||||
profileName: 'Mahershala A.',
|
||||
|
@ -50,9 +49,8 @@ const memberMahershala: ConversationType = getDefaultConversation({
|
|||
areWeAdmin: false,
|
||||
});
|
||||
|
||||
const memberShia: ConversationType = getDefaultConversation({
|
||||
const memberShia: ConversationType = getDefaultConversationWithUuid({
|
||||
id: '333222',
|
||||
uuid: 'hijklmno',
|
||||
title: 'Shia LaBeouf',
|
||||
firstName: 'Shia',
|
||||
profileName: 'Shia L.',
|
||||
|
|
|
@ -6,7 +6,8 @@ import type { Database } from 'better-sqlite3';
|
|||
import SQL from 'better-sqlite3';
|
||||
import { v4 as generateGuid } from 'uuid';
|
||||
|
||||
import { SCHEMA_VERSIONS } from '../sql/Server';
|
||||
import { SCHEMA_VERSIONS } from '../sql/migrations';
|
||||
import { consoleLogger } from '../util/consoleLogger';
|
||||
|
||||
const OUR_UUID = generateGuid();
|
||||
|
||||
|
@ -17,7 +18,7 @@ describe('SQL migrations test', () => {
|
|||
const startVersion = db.pragma('user_version', { simple: true });
|
||||
|
||||
for (const run of SCHEMA_VERSIONS) {
|
||||
run(startVersion, db);
|
||||
run(startVersion, db, consoleLogger);
|
||||
|
||||
const currentVersion = db.pragma('user_version', { simple: true });
|
||||
|
||||
|
@ -616,4 +617,157 @@ describe('SQL migrations test', () => {
|
|||
assert.sameDeepMembers(reactionMessageIds, [MESSAGE_ID_2, MESSAGE_ID_3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateToSchemaVersion43', () => {
|
||||
it('remaps conversation ids to UUIDs in groups and messages', () => {
|
||||
updateToVersion(42);
|
||||
|
||||
const UUID_A = generateGuid();
|
||||
const UUID_B = generateGuid();
|
||||
const UUID_C = generateGuid();
|
||||
|
||||
const rawConvoC = {
|
||||
id: 'c',
|
||||
uuid: UUID_C,
|
||||
membersV2: [
|
||||
{ conversationId: 'a', joinedAtVersion: 1 },
|
||||
{ conversationId: 'b', joinedAtVersion: 2 },
|
||||
{ conversationId: 'z', joinedAtVersion: 3 },
|
||||
],
|
||||
pendingMembersV2: [
|
||||
{ conversationId: 'a', addedByUserId: 'b', timestamp: 4 },
|
||||
{ conversationId: 'b', addedByUserId: UUID_A, timestamp: 5 },
|
||||
{ conversationId: 'z', timestamp: 6 },
|
||||
],
|
||||
pendingAdminApprovalV2: [
|
||||
{ conversationId: 'a', timestamp: 6 },
|
||||
{ conversationId: 'b', timestamp: 7 },
|
||||
{ conversationId: 'z', timestamp: 8 },
|
||||
],
|
||||
};
|
||||
|
||||
const CHANGE_TYPES = [
|
||||
'member-add',
|
||||
'member-add-from-link',
|
||||
'member-add-from-admin-approval',
|
||||
'member-privilege',
|
||||
'member-remove',
|
||||
'pending-add-one',
|
||||
'admin-approval-add-one',
|
||||
];
|
||||
|
||||
const CHANGE_TYPES_WITH_INVITER = [
|
||||
'member-add-from-invite',
|
||||
'pending-remove-one',
|
||||
'pending-remove-many',
|
||||
'admin-approval-remove-one',
|
||||
];
|
||||
|
||||
db.exec(
|
||||
`
|
||||
INSERT INTO conversations
|
||||
(id, uuid, json)
|
||||
VALUES
|
||||
('a', '${UUID_A}', '${JSON.stringify({ id: 'a', uuid: UUID_A })}'),
|
||||
('b', '${UUID_B}', '${JSON.stringify({ id: 'b', uuid: UUID_B })}'),
|
||||
('c', '${UUID_C}', '${JSON.stringify(rawConvoC)}');
|
||||
|
||||
INSERT INTO messages
|
||||
(id, json)
|
||||
VALUES
|
||||
('m', '${JSON.stringify({
|
||||
id: 'm',
|
||||
groupV2Change: {
|
||||
from: 'a',
|
||||
details: [
|
||||
...CHANGE_TYPES.map(type => ({ type, conversationId: 'b' })),
|
||||
...CHANGE_TYPES_WITH_INVITER.map(type => {
|
||||
return { type, conversationId: 'c', inviter: 'a' };
|
||||
}),
|
||||
],
|
||||
},
|
||||
sourceUuid: 'a',
|
||||
invitedGV2Members: [
|
||||
{
|
||||
conversationId: 'b',
|
||||
addedByUserId: 'c',
|
||||
},
|
||||
],
|
||||
})}'),
|
||||
('n', '${JSON.stringify({
|
||||
id: 'n',
|
||||
groupV2Change: {
|
||||
from: 'not-found',
|
||||
details: [],
|
||||
},
|
||||
sourceUuid: 'a',
|
||||
})}');
|
||||
`
|
||||
);
|
||||
|
||||
updateToVersion(43);
|
||||
|
||||
const { members, json: convoJSON } = db
|
||||
.prepare('SELECT members, json FROM conversations WHERE id = "c"')
|
||||
.get();
|
||||
|
||||
assert.strictEqual(members, `${UUID_A} ${UUID_B}`);
|
||||
assert.deepStrictEqual(JSON.parse(convoJSON), {
|
||||
id: 'c',
|
||||
uuid: UUID_C,
|
||||
membersV2: [
|
||||
{ uuid: UUID_A, joinedAtVersion: 1 },
|
||||
{ uuid: UUID_B, joinedAtVersion: 2 },
|
||||
],
|
||||
pendingMembersV2: [
|
||||
{ uuid: UUID_A, addedByUserId: UUID_B, timestamp: 4 },
|
||||
{ uuid: UUID_B, addedByUserId: UUID_A, timestamp: 5 },
|
||||
],
|
||||
pendingAdminApprovalV2: [
|
||||
{ uuid: UUID_A, timestamp: 6 },
|
||||
{ uuid: UUID_B, timestamp: 7 },
|
||||
],
|
||||
});
|
||||
|
||||
const { json: messageMJSON } = db
|
||||
.prepare('SELECT json FROM messages WHERE id = "m"')
|
||||
.get();
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(messageMJSON), {
|
||||
id: 'm',
|
||||
groupV2Change: {
|
||||
from: UUID_A,
|
||||
details: [
|
||||
...CHANGE_TYPES.map(type => ({ type, uuid: UUID_B })),
|
||||
...CHANGE_TYPES_WITH_INVITER.map(type => {
|
||||
return {
|
||||
type,
|
||||
uuid: UUID_C,
|
||||
inviter: UUID_A,
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
sourceUuid: UUID_A,
|
||||
invitedGV2Members: [
|
||||
{
|
||||
uuid: UUID_B,
|
||||
addedByUserId: UUID_C,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { json: messageNJSON } = db
|
||||
.prepare('SELECT json FROM messages WHERE id = "n"')
|
||||
.get();
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(messageNJSON), {
|
||||
id: 'n',
|
||||
groupV2Change: {
|
||||
details: [],
|
||||
},
|
||||
sourceUuid: UUID_A,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,8 +14,8 @@ export function init(signalProtocolStore: SignalProtocolStore): void {
|
|||
);
|
||||
conversation.addKeyChange(uuid);
|
||||
|
||||
const groups = await window.ConversationController.getAllGroupsInvolvingId(
|
||||
conversation.id
|
||||
const groups = await window.ConversationController.getAllGroupsInvolvingUuid(
|
||||
uuid
|
||||
);
|
||||
for (const group of groups) {
|
||||
group.addKeyChange(uuid);
|
||||
|
|
|
@ -37,7 +37,7 @@ import type { CallbackResultType, CustomError } from './Types.d';
|
|||
import { isValidNumber } from '../types/PhoneNumber';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { UUID, isValidUuid } from '../types/UUID';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||
|
@ -656,7 +656,7 @@ export default class OutgoingMessage {
|
|||
async sendToIdentifier(providedIdentifier: string): Promise<void> {
|
||||
let identifier = providedIdentifier;
|
||||
try {
|
||||
if (window.isValidGuid(identifier)) {
|
||||
if (isValidUuid(identifier)) {
|
||||
// We're good!
|
||||
} else if (isValidNumber(identifier)) {
|
||||
if (!window.textsecure.messaging) {
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { isValidGuid } from '../util/isValidGuid';
|
||||
|
||||
export type UUIDStringType = `${string}-${string}-${string}-${string}-${string}`;
|
||||
|
||||
export const isValidUuid = (value: unknown): value is UUIDStringType =>
|
||||
typeof value === 'string' &&
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(
|
||||
value
|
||||
);
|
||||
|
||||
export class UUID {
|
||||
constructor(protected readonly value: string) {
|
||||
strictAssert(isValidGuid(value), `Invalid UUID: ${value}`);
|
||||
strictAssert(isValidUuid(value), `Invalid UUID: ${value}`);
|
||||
}
|
||||
|
||||
public toString(): UUIDStringType {
|
||||
|
@ -41,4 +48,24 @@ export class UUID {
|
|||
);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public static generate(): UUID {
|
||||
return new UUID(generateUUID());
|
||||
}
|
||||
|
||||
public static cast(value: UUIDStringType): never;
|
||||
public static cast(value: string): UUIDStringType;
|
||||
|
||||
public static cast(value: string): UUIDStringType {
|
||||
return new UUID(value.toLowerCase()).toString();
|
||||
}
|
||||
|
||||
// For testing
|
||||
public static fromPrefix(value: string): UUID {
|
||||
let padded = value;
|
||||
while (padded.length < 8) {
|
||||
padded += '0';
|
||||
}
|
||||
return new UUID(`${padded}-0000-4000-8000-${'0'.repeat(12)}`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,11 @@ export function strictAssert(
|
|||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the type of value is not a promise.
|
||||
* (Useful for database modules)
|
||||
*/
|
||||
export function assertSync<T, X>(value: T extends Promise<X> ? never : T): T {
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { compact } from 'lodash';
|
||||
import type { ConversationAttributesType } from '../model-types.d';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { isDirectConversation } from './whatTypeOfConversation';
|
||||
|
||||
export function getConversationMembers(
|
||||
|
@ -15,7 +16,7 @@ export function getConversationMembers(
|
|||
|
||||
if (conversationAttrs.membersV2) {
|
||||
const { includePendingMembers } = options;
|
||||
const members: Array<{ conversationId: string }> = includePendingMembers
|
||||
const members: Array<{ uuid: UUIDStringType }> = includePendingMembers
|
||||
? [
|
||||
...(conversationAttrs.membersV2 || []),
|
||||
...(conversationAttrs.pendingMembersV2 || []),
|
||||
|
@ -24,9 +25,7 @@ export function getConversationMembers(
|
|||
|
||||
return compact(
|
||||
members.map(member => {
|
||||
const conversation = window.ConversationController.get(
|
||||
member.conversationId
|
||||
);
|
||||
const conversation = window.ConversationController.get(member.uuid);
|
||||
|
||||
// In groups we won't sent to contacts we believe are unregistered
|
||||
if (conversation && conversation.isUnregistered()) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import type {
|
|||
GroupV2RequestingMembership,
|
||||
} from '../components/conversation/conversation-details/PendingInvites';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
import { isConversationUnregistered } from './isConversationUnregistered';
|
||||
|
||||
export const getGroupMemberships = (
|
||||
|
@ -20,7 +21,7 @@ export const getGroupMemberships = (
|
|||
'memberships' | 'pendingApprovalMemberships' | 'pendingMemberships'
|
||||
>
|
||||
>,
|
||||
getConversationById: (conversationId: string) => undefined | ConversationType
|
||||
getConversationByUuid: (uuid: UUIDStringType) => undefined | ConversationType
|
||||
): {
|
||||
memberships: Array<GroupV2Membership>;
|
||||
pendingApprovalMemberships: Array<GroupV2RequestingMembership>;
|
||||
|
@ -28,7 +29,7 @@ export const getGroupMemberships = (
|
|||
} => ({
|
||||
memberships: memberships.reduce(
|
||||
(result: Array<GroupV2Membership>, membership) => {
|
||||
const member = getConversationById(membership.conversationId);
|
||||
const member = getConversationByUuid(membership.uuid);
|
||||
if (!member) {
|
||||
return result;
|
||||
}
|
||||
|
@ -38,7 +39,7 @@ export const getGroupMemberships = (
|
|||
),
|
||||
pendingApprovalMemberships: pendingApprovalMemberships.reduce(
|
||||
(result: Array<GroupV2RequestingMembership>, membership) => {
|
||||
const member = getConversationById(membership.conversationId);
|
||||
const member = getConversationByUuid(membership.uuid);
|
||||
if (!member || isConversationUnregistered(member)) {
|
||||
return result;
|
||||
}
|
||||
|
@ -48,7 +49,7 @@ export const getGroupMemberships = (
|
|||
),
|
||||
pendingMemberships: pendingMemberships.reduce(
|
||||
(result: Array<GroupV2PendingMembership>, membership) => {
|
||||
const member = getConversationById(membership.conversationId);
|
||||
const member = getConversationByUuid(membership.uuid);
|
||||
if (!member || isConversationUnregistered(member)) {
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright 2017-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export const isValidGuid = (value: unknown): value is string =>
|
||||
typeof value === 'string' &&
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(
|
||||
value
|
||||
);
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { isValidUuid } from '../types/UUID';
|
||||
import { assert } from './assert';
|
||||
import { isValidGuid } from './isValidGuid';
|
||||
|
||||
export function normalizeUuid(uuid: string, context: string): string {
|
||||
assert(
|
||||
isValidGuid(uuid),
|
||||
isValidUuid(uuid),
|
||||
`Normalizing invalid uuid: ${uuid} in context "${context}"`
|
||||
);
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ export async function sendToGroupViaSenderKey(options: {
|
|||
conversation.set({
|
||||
senderKeyInfo: {
|
||||
createdAtDate: Date.now(),
|
||||
distributionId: window.getGuid(),
|
||||
distributionId: UUID.generate().toString(),
|
||||
memberDevices: [],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
UuidCiphertext,
|
||||
} from 'zkgroup';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { UUID } from '../types/UUID';
|
||||
import type { UUIDStringType } from '../types/UUID';
|
||||
|
||||
export * from 'zkgroup';
|
||||
|
||||
|
@ -63,7 +65,7 @@ export function decryptGroupBlob(
|
|||
export function decryptProfileKeyCredentialPresentation(
|
||||
clientZkGroupCipher: ClientZkGroupCipher,
|
||||
presentationBuffer: Uint8Array
|
||||
): { profileKey: Uint8Array; uuid: string } {
|
||||
): { profileKey: Uint8Array; uuid: UUIDStringType } {
|
||||
const presentation = new ProfileKeyCredentialPresentation(
|
||||
uint8ArrayToCompatArray(presentationBuffer)
|
||||
);
|
||||
|
@ -79,14 +81,14 @@ export function decryptProfileKeyCredentialPresentation(
|
|||
|
||||
return {
|
||||
profileKey: compatArrayToUint8Array(profileKey.serialize()),
|
||||
uuid,
|
||||
uuid: UUID.cast(uuid),
|
||||
};
|
||||
}
|
||||
|
||||
export function decryptProfileKey(
|
||||
clientZkGroupCipher: ClientZkGroupCipher,
|
||||
profileKeyCiphertextBuffer: Uint8Array,
|
||||
uuid: string
|
||||
uuid: UUIDStringType
|
||||
): Uint8Array {
|
||||
const profileKeyCiphertext = new ProfileKeyCiphertext(
|
||||
uint8ArrayToCompatArray(profileKeyCiphertextBuffer)
|
||||
|
@ -113,7 +115,7 @@ export function decryptUuid(
|
|||
|
||||
export function deriveProfileKeyVersion(
|
||||
profileKeyBase64: string,
|
||||
uuid: string
|
||||
uuid: UUIDStringType
|
||||
): string {
|
||||
const profileKeyArray = base64ToCompatArray(profileKeyBase64);
|
||||
const profileKey = new ProfileKey(profileKeyArray);
|
||||
|
@ -167,7 +169,7 @@ export function encryptGroupBlob(
|
|||
|
||||
export function encryptUuid(
|
||||
clientZkGroupCipher: ClientZkGroupCipher,
|
||||
uuidPlaintext: string
|
||||
uuidPlaintext: UUIDStringType
|
||||
): Uint8Array {
|
||||
const uuidCiphertext = clientZkGroupCipher.encryptUuid(uuidPlaintext);
|
||||
|
||||
|
@ -176,7 +178,7 @@ export function encryptUuid(
|
|||
|
||||
export function generateProfileKeyCredentialRequest(
|
||||
clientZkProfileCipher: ClientZkProfileOperations,
|
||||
uuid: string,
|
||||
uuid: UUIDStringType,
|
||||
profileKeyBase64: string
|
||||
): { context: ProfileKeyCredentialRequestContext; requestHex: string } {
|
||||
const profileKeyArray = base64ToCompatArray(profileKeyBase64);
|
||||
|
@ -287,7 +289,7 @@ export function handleProfileKeyCredential(
|
|||
|
||||
export function deriveProfileKeyCommitment(
|
||||
profileKeyBase64: string,
|
||||
uuid: string
|
||||
uuid: UUIDStringType
|
||||
): string {
|
||||
const profileKeyArray = base64ToCompatArray(profileKeyBase64);
|
||||
const profileKey = new ProfileKey(profileKeyArray);
|
||||
|
|
|
@ -1258,7 +1258,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
});
|
||||
|
||||
const invitedMemberIds = pendingMembersV2.map(
|
||||
(item: GroupV2PendingMemberType) => item.conversationId
|
||||
(item: GroupV2PendingMemberType) => item.uuid
|
||||
);
|
||||
|
||||
this.migrationDialog = new Whisper.ReactWrapperView({
|
||||
|
|
3
ts/window.d.ts
vendored
3
ts/window.d.ts
vendored
|
@ -102,7 +102,6 @@ import { SocketStatus } from './types/SocketStatus';
|
|||
import SyncRequest from './textsecure/SyncRequest';
|
||||
import { ConversationColorType, CustomColorType } from './types/Colors';
|
||||
import { MessageController } from './util/MessageController';
|
||||
import { isValidGuid } from './util/isValidGuid';
|
||||
import { StateType } from './state/reducer';
|
||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||
import { UUID } from './types/UUID';
|
||||
|
@ -199,7 +198,6 @@ declare global {
|
|||
getBuildCreation: () => number;
|
||||
getEnvironment: typeof getEnvironment;
|
||||
getExpiration: () => string;
|
||||
getGuid: () => string;
|
||||
getHostName: () => string;
|
||||
getInboxCollection: () => ConversationModelCollectionType;
|
||||
getInteractionMode: () => 'mouse' | 'keyboard';
|
||||
|
@ -219,7 +217,6 @@ declare global {
|
|||
isAfterVersion: (version: string, anotherVersion: string) => boolean;
|
||||
isBeforeVersion: (version: string, anotherVersion: string) => boolean;
|
||||
isFullScreen: () => boolean;
|
||||
isValidGuid: typeof isValidGuid;
|
||||
libphonenumber: {
|
||||
util: {
|
||||
getRegionCodeForNumber: (number: string) => string;
|
||||
|
|
Loading…
Reference in a new issue