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