diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 61ee94c1c..742f81a4b 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -23,9 +23,9 @@ message Envelope { } optional Type type = 1; - optional string sourceUuid = 11; + optional string sourceServiceId = 11; optional uint32 sourceDevice = 7; - optional string destinationUuid = 13; + optional string destinationServiceId = 13; // reserved 3; // formerly optional string relay = 3; optional uint64 timestamp = 5; // reserved 6; // formerly optional bytes legacyMessage = 6; @@ -204,7 +204,7 @@ message DataMessage { optional uint64 id = 1; reserved /* author */ 2; // removed - optional string authorUuid = 5; + optional string authorAci = 5; optional string text = 3; repeated QuotedAttachment attachments = 4; repeated BodyRange bodyRanges = 6; @@ -298,7 +298,7 @@ message DataMessage { optional string emoji = 1; optional bool remove = 2; reserved /* targetAuthorE164 */ 3; // removed - optional string targetAuthorUuid = 4; + optional string targetAuthorAci = 4; optional uint64 targetTimestamp = 5; } @@ -320,7 +320,7 @@ message DataMessage { optional uint32 length = 2; oneof associatedValue { - string mentionUuid = 3; + string mentionAci = 3; Style style = 4; } } @@ -330,7 +330,7 @@ message DataMessage { } message StoryContext { - optional string authorUuid = 1; + optional string authorAci = 1; optional uint64 sentTimestamp = 2; } @@ -447,7 +447,7 @@ message Verified { } optional string destination = 1; - optional string destinationUuid = 5; + optional string destinationAci = 5; optional bytes identityKey = 2; optional State state = 3; optional bytes nullMessage = 4; @@ -457,27 +457,18 @@ message SyncMessage { message Sent { message UnidentifiedDeliveryStatus { optional string destination = 1; - oneof destinationServiceId { - string destinationAci = 3; - string destinationPni = 4; - } + optional string destinationServiceId = 3; optional bool unidentified = 2; } message StoryMessageRecipient { - oneof destinationServiceId { - string destinationAci = 1; - string destinationPni = 4; - } + optional string destinationServiceId = 1; repeated string distributionListIds = 2; optional bool isAllowedToReply = 3; } optional string destination = 1; - oneof destinationServiceId { - string destinationAci = 7; - string destinationPni = 11; - } + optional string destinationServiceId = 7; optional uint64 timestamp = 2; optional DataMessage message = 3; optional uint64 expirationStartTimestamp = 4; @@ -495,7 +486,7 @@ message SyncMessage { message Blocked { repeated string numbers = 1; - repeated string uuids = 3; + repeated string acis = 3; repeated bytes groupIds = 2; } @@ -519,13 +510,13 @@ message SyncMessage { message Read { optional string sender = 1; - optional string senderUuid = 3; + optional string senderAci = 3; optional uint64 timestamp = 2; } message Viewed { optional string senderE164 = 1; - optional string senderUuid = 3; + optional string senderAci = 3; optional uint64 timestamp = 2; } @@ -551,7 +542,7 @@ message SyncMessage { message ViewOnceOpen { optional string sender = 1; - optional string senderUuid = 3; + optional string senderAci = 3; optional uint64 timestamp = 2; } @@ -565,7 +556,7 @@ message SyncMessage { } optional string threadE164 = 1; - optional string threadUuid = 2; + optional string threadAci = 2; optional bytes groupId = 3; optional Type type = 4; } @@ -694,7 +685,7 @@ message ContactDetails { } optional string number = 1; - optional string uuid = 9; + optional string aci = 9; optional string name = 2; optional Avatar avatar = 3; optional string color = 4; diff --git a/protos/SignalStorage.proto b/protos/SignalStorage.proto index e96e2cd8b..155e0e108 100644 --- a/protos/SignalStorage.proto +++ b/protos/SignalStorage.proto @@ -77,7 +77,7 @@ message ContactRecord { UNVERIFIED = 2; } - optional string serviceUuid = 1; + optional string aci = 1; optional string serviceE164 = 2; optional string pni = 15; optional bytes profileKey = 3; @@ -203,7 +203,7 @@ message AccountRecord { message StoryDistributionListRecord { optional bytes identifier = 1; optional string name = 2; - repeated string recipientUuids = 3; + repeated string recipientServiceIds = 3; optional uint64 deletedAtTimestamp = 4; optional bool allowsReplies = 5; optional bool isBlockList = 6; diff --git a/ts/CI/benchmarkConversationOpen.ts b/ts/CI/benchmarkConversationOpen.ts index 4eb904ca1..b620e62d9 100644 --- a/ts/CI/benchmarkConversationOpen.ts +++ b/ts/CI/benchmarkConversationOpen.ts @@ -5,7 +5,6 @@ import { v4 as uuid } from 'uuid'; import { incrementMessageCounter } from '../util/incrementMessageCounter'; import { ReadStatus } from '../messages/MessageReadStatus'; -import { UUID } from '../types/UUID'; import { SendStatus } from '../messages/MessageSendState'; import { BodyRange } from '../types/BodyRange'; import { strictAssert } from '../util/assert'; @@ -42,7 +41,7 @@ export async function populateConversationWithMessages({ const logId = 'benchmarkConversationOpen/populateConversationWithMessages'; log.info(`${logId}: populating conversation`); - const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString(); + const ourAci = window.textsecure.storage.user.getCheckedAci(); const conversation = window.ConversationController.get(conversationId); strictAssert( @@ -71,7 +70,7 @@ export async function populateConversationWithMessages({ schemaVersion: window.Signal.Types.Message.CURRENT_SCHEMA_VERSION, received_at: incrementMessageCounter(), readStatus: isUnread ? ReadStatus.Unread : ReadStatus.Read, - sourceUuid: new UUID(isIncoming ? conversationId : ourUuid).toString(), + sourceUuid: isIncoming ? conversation.getCheckedServiceId('CI') : ourAci, ...(isIncoming ? {} : { @@ -87,7 +86,7 @@ export async function populateConversationWithMessages({ await window.Signal.Data.saveMessages(messages, { forceSave: true, - ourUuid, + ourAci, }); conversation.set('active_at', Date.now()); diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index eba52d0de..314ce8a36 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -3,6 +3,7 @@ import { debounce, pick, uniq, without } from 'lodash'; import PQueue from 'p-queue'; +import { v4 as generateUuid } from 'uuid'; import type { ConversationModelCollectionType, @@ -12,7 +13,6 @@ import type { } from './model-types.d'; import type { ConversationModel } from './models/conversations'; import type { MessageModel } from './models/messages'; -import type { UUIDStringType } from './types/UUID'; import dataInterface from './sql/Client'; import * as log from './logging/log'; @@ -23,11 +23,16 @@ import { assertDev, strictAssert } from './util/assert'; import { drop } from './util/drop'; import { isGroupV1, isGroupV2 } from './util/whatTypeOfConversation'; import { getConversationUnreadCountForAppBadge } from './util/getConversationUnreadCountForAppBadge'; -import { UUID, isValidUuid, UUIDKind } from './types/UUID'; +import type { ServiceIdString } from './types/ServiceId'; +import { + isServiceIdString, + normalizeAci, + normalizePni, +} from './types/ServiceId'; import { sleep } from './util/sleep'; import { isNotNil } from './util/isNotNil'; import { MINUTE, SECOND } from './util/durations'; -import { getUuidsForE164s } from './util/getUuidsForE164s'; +import { getServiceIdsForE164s } from './util/getServiceIdsForE164s'; import { SIGNAL_ACI, SIGNAL_AVATAR_PATH } from './types/SignalConversation'; import { getTitleNoDefault } from './util/getTitle'; import * as StorageService from './services/storage'; @@ -35,7 +40,7 @@ import * as StorageService from './services/storage'; type ConvoMatchType = | { key: 'uuid' | 'pni'; - value: UUIDStringType | undefined; + value: ServiceIdString | undefined; match: ConversationModel | undefined; } | { @@ -118,7 +123,7 @@ const MAX_MESSAGE_BODY_LENGTH = 64 * 1024; const { getAllConversations, - getAllGroupsInvolvingUuid, + getAllGroupsInvolvingServiceId, getMessagesBySentAt, migrateConversationMessages, removeConversation, @@ -261,7 +266,7 @@ export class ConversationController { return conversation; } - const id = UUID.generate().toString(); + const id = generateUuid(); if (type === 'group') { conversation = this._conversations.add({ @@ -273,7 +278,7 @@ export class ConversationController { version: 2, ...additionalInitialProps, }); - } else if (isValidUuid(identifier)) { + } else if (isServiceIdString(identifier)) { conversation = this._conversations.add({ id, uuid: identifier, @@ -364,12 +369,8 @@ export class ConversationController { getOurConversationId(): string | undefined { const e164 = window.textsecure.storage.user.getNumber(); - const aci = window.textsecure.storage.user - .getUuid(UUIDKind.ACI) - ?.toString(); - const pni = window.textsecure.storage.user - .getUuid(UUIDKind.PNI) - ?.toString(); + const aci = window.textsecure.storage.user.getAci(); + const pni = window.textsecure.storage.user.getPni(); if (!e164 && !aci && !pni) { return undefined; @@ -483,9 +484,11 @@ export class ConversationController { const aci = providedAci && providedAci !== providedPni - ? UUID.cast(providedAci) + ? normalizeAci(providedAci, 'maybeMergeContacts.aci') : undefined; - const pni = providedPni ? UUID.cast(providedPni) : undefined; + const pni = providedPni + ? normalizePni(providedPni, 'maybeMergeContacts.pni') + : undefined; const mergePromises: Array> = []; if (!aci && !e164 && !pni) { @@ -1037,10 +1040,10 @@ export class ConversationController { } const obsoleteId = obsolete.get('id'); - const obsoleteUuid = obsolete.getUuid(); + const obsoleteServiceId = obsolete.getServiceId(); const currentId = current.get('id'); - if (conversationType === 'private' && obsoleteUuid) { + if (conversationType === 'private' && obsoleteServiceId) { if (!current.get('profileKey') && obsolete.get('profileKey')) { log.warn(`${logId}: Copying profile key from old to new contact`); @@ -1060,16 +1063,18 @@ export class ConversationController { log.warn( `${logId}: Delete all identity information tied to old conversationId` ); - if (obsoleteUuid) { + if (obsoleteServiceId) { await window.textsecure.storage.protocol.removeIdentityKey( - obsoleteUuid + obsoleteServiceId ); } log.warn( `${logId}: Ensure that all V1 groups have new conversationId instead of old` ); - const groups = await this.getAllGroupsInvolvingUuid(obsoleteUuid); + const groups = await this.getAllGroupsInvolvingServiceId( + obsoleteServiceId + ); groups.forEach(group => { const members = group.get('members'); const withoutObsolete = without(members, obsoleteId); @@ -1178,10 +1183,10 @@ export class ConversationController { return null; } - async getAllGroupsInvolvingUuid( - uuid: UUID + async getAllGroupsInvolvingServiceId( + serviceId: ServiceIdString ): Promise> { - const groups = await getAllGroupsInvolvingUuid(uuid.toString()); + const groups = await getAllGroupsInvolvingServiceId(serviceId); return groups.map(group => { const existing = this.get(group.id); if (existing) { @@ -1278,7 +1283,7 @@ export class ConversationController { async _forgetE164(e164: string): Promise { const { server } = window.textsecure; strictAssert(server, 'Server must be initialized'); - const uuidMap = await getUuidsForE164s(server, [e164]); + const uuidMap = await getServiceIdsForE164s(server, [e164]); const pni = uuidMap.get(e164)?.pni; @@ -1362,7 +1367,7 @@ export class ConversationController { // Clean up the conversations that have UUID as their e164. const e164 = conversation.get('e164'); const uuid = conversation.get('uuid'); - if (isValidUuid(e164) && uuid) { + if (e164 && isServiceIdString(e164) && uuid) { conversation.set({ e164: undefined }); updateConversation(conversation.attributes); diff --git a/ts/Crypto.ts b/ts/Crypto.ts index ceb3273b7..09cee51db 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -13,6 +13,8 @@ import { getBytesSubarray } from './util/uuidToBytes'; export { HashType, CipherType }; +export const UUID_BYTE_SIZE = 16; + const PROFILE_IV_LENGTH = 12; // bytes const PROFILE_KEY_LENGTH = 32; // bytes diff --git a/ts/LibSignalStores.ts b/ts/LibSignalStores.ts index 1cb8ed5cd..375841d2e 100644 --- a/ts/LibSignalStores.ts +++ b/ts/LibSignalStores.ts @@ -27,37 +27,38 @@ import { } from '@signalapp/libsignal-client'; import { Address } from './types/Address'; import { QualifiedAddress } from './types/QualifiedAddress'; -import type { UUID } from './types/UUID'; +import type { ServiceIdString } from './types/ServiceId'; +import { normalizeServiceId } from './types/ServiceId'; import type { Zone } from './util/Zone'; function encodeAddress(address: ProtocolAddress): Address { const name = address.name(); const deviceId = address.deviceId(); - return Address.create(name, deviceId); + return Address.create(normalizeServiceId(name, 'encodeAddress'), deviceId); } function toQualifiedAddress( - ourUuid: UUID, + ourServiceId: ServiceIdString, address: ProtocolAddress ): QualifiedAddress { - return new QualifiedAddress(ourUuid, encodeAddress(address)); + return new QualifiedAddress(ourServiceId, encodeAddress(address)); } export type SessionsOptions = Readonly<{ - ourUuid: UUID; + ourServiceId: ServiceIdString; zone?: Zone; }>; export class Sessions extends SessionStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; private readonly zone: Zone | undefined; - constructor({ ourUuid, zone }: SessionsOptions) { + constructor({ ourServiceId, zone }: SessionsOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; this.zone = zone; } @@ -66,14 +67,14 @@ export class Sessions extends SessionStore { record: SessionRecord ): Promise { await window.textsecure.storage.protocol.storeSession( - toQualifiedAddress(this.ourUuid, address), + toQualifiedAddress(this.ourServiceId, address), record, { zone: this.zone } ); } async getSession(name: ProtocolAddress): Promise { - const encodedAddress = toQualifiedAddress(this.ourUuid, name); + const encodedAddress = toQualifiedAddress(this.ourServiceId, name); const record = await window.textsecure.storage.protocol.loadSession( encodedAddress, { zone: this.zone } @@ -86,7 +87,7 @@ export class Sessions extends SessionStore { addresses: Array ): Promise> { const encodedAddresses = addresses.map(addr => - toQualifiedAddress(this.ourUuid, addr) + toQualifiedAddress(this.ourServiceId, addr) ); return window.textsecure.storage.protocol.loadSessions(encodedAddresses, { zone: this.zone, @@ -95,25 +96,25 @@ export class Sessions extends SessionStore { } export type IdentityKeysOptions = Readonly<{ - ourUuid: UUID; + ourServiceId: ServiceIdString; zone?: Zone; }>; export class IdentityKeys extends IdentityKeyStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; private readonly zone: Zone | undefined; - constructor({ ourUuid, zone }: IdentityKeysOptions) { + constructor({ ourServiceId, zone }: IdentityKeysOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; this.zone = zone; } async getIdentityKey(): Promise { const keyPair = window.textsecure.storage.protocol.getIdentityKeyPair( - this.ourUuid + this.ourServiceId ); if (!keyPair) { throw new Error('IdentityKeyStore/getIdentityKey: No identity key!'); @@ -123,7 +124,7 @@ export class IdentityKeys extends IdentityKeyStore { async getLocalRegistrationId(): Promise { const id = await window.textsecure.storage.protocol.getLocalRegistrationId( - this.ourUuid + this.ourServiceId ); if (!isNumber(id)) { throw new Error( @@ -136,7 +137,7 @@ export class IdentityKeys extends IdentityKeyStore { async getIdentity(address: ProtocolAddress): Promise { const encodedAddress = encodeAddress(address); const key = await window.textsecure.storage.protocol.loadIdentityKey( - encodedAddress.uuid + encodedAddress.serviceId ); if (!key) { @@ -177,15 +178,15 @@ export class IdentityKeys extends IdentityKeyStore { } export type PreKeysOptions = Readonly<{ - ourUuid: UUID; + ourServiceId: ServiceIdString; }>; export class PreKeys extends PreKeyStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; - constructor({ ourUuid }: PreKeysOptions) { + constructor({ ourServiceId }: PreKeysOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; } async savePreKey(): Promise { @@ -194,7 +195,7 @@ export class PreKeys extends PreKeyStore { async getPreKey(id: number): Promise { const preKey = await window.textsecure.storage.protocol.loadPreKey( - this.ourUuid, + this.ourServiceId, id ); @@ -206,16 +207,18 @@ export class PreKeys extends PreKeyStore { } async removePreKey(id: number): Promise { - await window.textsecure.storage.protocol.removePreKeys(this.ourUuid, [id]); + await window.textsecure.storage.protocol.removePreKeys(this.ourServiceId, [ + id, + ]); } } export class KyberPreKeys extends KyberPreKeyStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; - constructor({ ourUuid }: PreKeysOptions) { + constructor({ ourServiceId }: PreKeysOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; } async saveKyberPreKey(): Promise { @@ -225,7 +228,7 @@ export class KyberPreKeys extends KyberPreKeyStore { async getKyberPreKey(id: number): Promise { const kyberPreKey = await window.textsecure.storage.protocol.loadKyberPreKey( - this.ourUuid, + this.ourServiceId, id ); @@ -238,25 +241,25 @@ export class KyberPreKeys extends KyberPreKeyStore { async markKyberPreKeyUsed(id: number): Promise { await window.textsecure.storage.protocol.maybeRemoveKyberPreKey( - this.ourUuid, + this.ourServiceId, id ); } } export type SenderKeysOptions = Readonly<{ - readonly ourUuid: UUID; + readonly ourServiceId: ServiceIdString; readonly zone: Zone | undefined; }>; export class SenderKeys extends SenderKeyStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; readonly zone: Zone | undefined; - constructor({ ourUuid, zone }: SenderKeysOptions) { + constructor({ ourServiceId, zone }: SenderKeysOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; this.zone = zone; } @@ -265,7 +268,7 @@ export class SenderKeys extends SenderKeyStore { distributionId: Uuid, record: SenderKeyRecord ): Promise { - const encodedAddress = toQualifiedAddress(this.ourUuid, sender); + const encodedAddress = toQualifiedAddress(this.ourServiceId, sender); await window.textsecure.storage.protocol.saveSenderKey( encodedAddress, @@ -279,7 +282,7 @@ export class SenderKeys extends SenderKeyStore { sender: ProtocolAddress, distributionId: Uuid ): Promise { - const encodedAddress = toQualifiedAddress(this.ourUuid, sender); + const encodedAddress = toQualifiedAddress(this.ourServiceId, sender); const senderKey = await window.textsecure.storage.protocol.getSenderKey( encodedAddress, @@ -292,15 +295,15 @@ export class SenderKeys extends SenderKeyStore { } export type SignedPreKeysOptions = Readonly<{ - ourUuid: UUID; + ourServiceId: ServiceIdString; }>; export class SignedPreKeys extends SignedPreKeyStore { - private readonly ourUuid: UUID; + private readonly ourServiceId: ServiceIdString; - constructor({ ourUuid }: SignedPreKeysOptions) { + constructor({ ourServiceId }: SignedPreKeysOptions) { super(); - this.ourUuid = ourUuid; + this.ourServiceId = ourServiceId; } async saveSignedPreKey(): Promise { @@ -310,7 +313,7 @@ export class SignedPreKeys extends SignedPreKeyStore { async getSignedPreKey(id: number): Promise { const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey( - this.ourUuid, + this.ourServiceId, id ); diff --git a/ts/RemoteConfig.ts b/ts/RemoteConfig.ts index 474725419..dad585e54 100644 --- a/ts/RemoteConfig.ts +++ b/ts/RemoteConfig.ts @@ -5,7 +5,7 @@ import { get, throttle } from 'lodash'; import type { WebAPIType } from './textsecure/WebAPI'; import * as log from './logging/log'; -import type { UUIDStringType } from './types/UUID'; +import type { AciString } from './types/ServiceId'; import { parseIntOrThrow } from './util/parseIntOrThrow'; import { SECOND, HOUR } from './util/durations'; import * as Bytes from './Bytes'; @@ -162,18 +162,18 @@ export function getValue(name: ConfigKeyType): string | undefined { export function isBucketValueEnabled( name: ConfigKeyType, e164: string | undefined, - uuid: UUIDStringType | undefined + aci: AciString | undefined ): boolean { - return innerIsBucketValueEnabled(name, getValue(name), e164, uuid); + return innerIsBucketValueEnabled(name, getValue(name), e164, aci); } export function innerIsBucketValueEnabled( name: ConfigKeyType, flagValue: unknown, e164: string | undefined, - uuid: UUIDStringType | undefined + aci: AciString | undefined ): boolean { - if (e164 == null || uuid == null) { + if (e164 == null || aci == null) { return false; } @@ -191,7 +191,7 @@ export function innerIsBucketValueEnabled( return false; } - const bucketValue = getBucketValue(uuid, name); + const bucketValue = getBucketValue(aci, name); return bucketValue < remoteConfigValue; } @@ -230,10 +230,10 @@ export function getCountryCodeValue( return wildcard; } -export function getBucketValue(uuid: UUIDStringType, flagName: string): number { +export function getBucketValue(aci: AciString, flagName: string): number { const hashInput = Bytes.concatenate([ Bytes.fromString(`${flagName}.`), - uuidToBytes(uuid), + uuidToBytes(aci), ]); const hashResult = window.SignalContext.crypto.hash( HashType.size256, diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index bd2caddfe..ad28044c8 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -51,8 +51,8 @@ import type { CompatPreKeyType, } from './textsecure/Types.d'; import type { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; -import type { UUIDStringType } from './types/UUID'; -import { UUID, UUIDKind } from './types/UUID'; +import type { ServiceIdString, PniString, AciString } from './types/ServiceId'; +import { isServiceIdString, ServiceIdKind } from './types/ServiceId'; import type { Address } from './types/Address'; import type { QualifiedAddressStringType } from './types/QualifiedAddress'; import { QualifiedAddress } from './types/QualifiedAddress'; @@ -123,8 +123,8 @@ export type SessionTransactionOptions = Readonly<{ }>; export type VerifyAlternateIdentityOptionsType = Readonly<{ - aci: UUID; - pni: UUID; + aci: AciString; + pni: PniString; signature: Uint8Array; }>; @@ -224,9 +224,9 @@ export class SignalProtocolStore extends EventEmitter { // Cached values - private ourIdentityKeys = new Map(); + private ourIdentityKeys = new Map(); - private ourRegistrationIds = new Map(); + private ourRegistrationIds = new Map(); private cachedPniSignatureMessage: PniSignatureMessageType | undefined; @@ -257,7 +257,7 @@ export class SignalProtocolStore extends EventEmitter { sessionQueueJobCounter = 0; - private readonly identityQueues = new Map(); + private readonly identityQueues = new Map(); private currentZone?: Zone; @@ -280,9 +280,13 @@ export class SignalProtocolStore extends EventEmitter { return; } - for (const key of Object.keys(map.value)) { - const { privKey, pubKey } = map.value[key]; - this.ourIdentityKeys.set(new UUID(key).toString(), { + for (const serviceId of Object.keys(map.value)) { + strictAssert( + isServiceIdString(serviceId), + 'Invalid identity key serviceId' + ); + const { privKey, pubKey } = map.value[serviceId]; + this.ourIdentityKeys.set(serviceId, { privKey, pubKey, }); @@ -295,8 +299,12 @@ export class SignalProtocolStore extends EventEmitter { return; } - for (const key of Object.keys(map.value)) { - this.ourRegistrationIds.set(new UUID(key).toString(), map.value[key]); + for (const serviceId of Object.keys(map.value)) { + strictAssert( + isServiceIdString(serviceId), + 'Invalid registration id serviceId' + ); + this.ourRegistrationIds.set(serviceId, map.value[serviceId]); } })(), _fillCaches( @@ -332,16 +340,21 @@ export class SignalProtocolStore extends EventEmitter { ]); } - getIdentityKeyPair(ourUuid: UUID): KeyPairType | undefined { - return this.ourIdentityKeys.get(ourUuid.toString()); + getIdentityKeyPair(ourServiceId: ServiceIdString): KeyPairType | undefined { + return this.ourIdentityKeys.get(ourServiceId); } - async getLocalRegistrationId(ourUuid: UUID): Promise { - return this.ourRegistrationIds.get(ourUuid.toString()); + async getLocalRegistrationId( + ourServiceId: ServiceIdString + ): Promise { + return this.ourRegistrationIds.get(ourServiceId); } - private _getKeyId(ourUuid: UUID, keyId: number): PreKeyIdType { - return `${ourUuid.toString()}:${keyId}`; + private _getKeyId( + ourServiceId: ServiceIdString, + keyId: number + ): PreKeyIdType { + return `${ourServiceId}:${keyId}`; } // KyberPreKeys @@ -384,17 +397,17 @@ export class SignalProtocolStore extends EventEmitter { } async loadKyberPreKey( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyId: number ): Promise { - const id: PreKeyIdType = this._getKeyId(ourUuid, keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); const entry = this._getKyberPreKeyEntry(id, 'loadKyberPreKey'); return entry?.item; } loadKyberPreKeys( - ourUuid: UUID, + ourServiceId: ServiceIdString, { isLastResort }: { isLastResort: boolean } ): Array { if (!this.kyberPreKeys) { @@ -410,18 +423,20 @@ export class SignalProtocolStore extends EventEmitter { .map(item => item.fromDB) .filter( item => - item.ourUuid === ourUuid.toString() && - item.isLastResort === isLastResort + item.ourUuid === ourServiceId && item.isLastResort === isLastResort ); } - async confirmKyberPreKey(ourUuid: UUID, keyId: number): Promise { + async confirmKyberPreKey( + ourServiceId: ServiceIdString, + keyId: number + ): Promise { const kyberPreKeyCache = this.kyberPreKeys; if (!kyberPreKeyCache) { throw new Error('storeKyberPreKey: this.kyberPreKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourUuid, keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); const item = kyberPreKeyCache.get(id); if (!item) { throw new Error(`confirmKyberPreKey: missing kyber prekey ${id}!`); @@ -440,7 +455,7 @@ export class SignalProtocolStore extends EventEmitter { } async storeKyberPreKeys( - ourUuid: UUID, + ourServiceId: ServiceIdString, keys: Array> ): Promise { const kyberPreKeyCache = this.kyberPreKeys; @@ -451,7 +466,7 @@ export class SignalProtocolStore extends EventEmitter { const toSave: Array = []; keys.forEach(key => { - const id: PreKeyIdType = this._getKeyId(ourUuid, key.keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, key.keyId); if (kyberPreKeyCache.has(id)) { throw new Error(`storeKyberPreKey: kyber prekey ${id} already exists!`); } @@ -464,7 +479,7 @@ export class SignalProtocolStore extends EventEmitter { isConfirmed: key.isConfirmed, isLastResort: key.isLastResort, keyId: key.keyId, - ourUuid: ourUuid.toString(), + ourUuid: ourServiceId, }; toSave.push(kyberPreKey); @@ -479,8 +494,11 @@ export class SignalProtocolStore extends EventEmitter { }); } - async maybeRemoveKyberPreKey(ourUuid: UUID, keyId: number): Promise { - const id: PreKeyIdType = this._getKeyId(ourUuid, keyId); + async maybeRemoveKyberPreKey( + ourServiceId: ServiceIdString, + keyId: number + ): Promise { + const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); const entry = this._getKyberPreKeyEntry(id, 'maybeRemoveKyberPreKey'); if (!entry) { @@ -493,11 +511,11 @@ export class SignalProtocolStore extends EventEmitter { return; } - await this.removeKyberPreKeys(ourUuid, [keyId]); + await this.removeKyberPreKeys(ourServiceId, [keyId]); } async removeKyberPreKeys( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyIds: Array ): Promise { const kyberPreKeyCache = this.kyberPreKeys; @@ -505,7 +523,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('removeKyberPreKeys: this.kyberPreKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourUuid, keyId)); + const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); await window.Signal.Data.removeKyberPreKeyById(ids); ids.forEach(id => { @@ -513,7 +531,10 @@ export class SignalProtocolStore extends EventEmitter { }); if (kyberPreKeyCache.size < LOW_KEYS_THRESHOLD) { - this.emitLowKeys(ourUuid, `removeKyberPreKeys@${kyberPreKeyCache.size}`); + this.emitLowKeys( + ourServiceId, + `removeKyberPreKeys@${kyberPreKeyCache.size}` + ); } } @@ -527,14 +548,14 @@ export class SignalProtocolStore extends EventEmitter { // PreKeys async loadPreKey( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyId: number ): Promise { if (!this.preKeys) { throw new Error('loadPreKey: this.preKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourUuid, keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); const entry = this.preKeys.get(id); if (!entry) { log.error('Failed to fetch prekey:', id); @@ -556,7 +577,7 @@ export class SignalProtocolStore extends EventEmitter { return item; } - loadPreKeys(ourUuid: UUID): Array { + loadPreKeys(ourServiceId: ServiceIdString): Array { if (!this.preKeys) { throw new Error('loadPreKeys: this.preKeys not yet cached!'); } @@ -568,11 +589,11 @@ export class SignalProtocolStore extends EventEmitter { const entries = Array.from(this.preKeys.values()); return entries .map(item => item.fromDB) - .filter(item => item.ourUuid === ourUuid.toString()); + .filter(item => item.ourUuid === ourServiceId); } async storePreKeys( - ourUuid: UUID, + ourServiceId: ServiceIdString, keys: Array ): Promise { const preKeyCache = this.preKeys; @@ -583,7 +604,7 @@ export class SignalProtocolStore extends EventEmitter { const now = Date.now(); const toSave: Array = []; keys.forEach(key => { - const id: PreKeyIdType = this._getKeyId(ourUuid, key.keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, key.keyId); if (preKeyCache.has(id)) { throw new Error(`storePreKeys: prekey ${id} already exists!`); @@ -592,7 +613,7 @@ export class SignalProtocolStore extends EventEmitter { const preKey = { id, keyId: key.keyId, - ourUuid: ourUuid.toString(), + ourUuid: ourServiceId, publicKey: key.keyPair.pubKey, privateKey: key.keyPair.privKey, createdAt: now, @@ -610,13 +631,16 @@ export class SignalProtocolStore extends EventEmitter { }); } - async removePreKeys(ourUuid: UUID, keyIds: Array): Promise { + async removePreKeys( + ourServiceId: ServiceIdString, + keyIds: Array + ): Promise { const preKeyCache = this.preKeys; if (!preKeyCache) { throw new Error('removePreKeys: this.preKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourUuid, keyId)); + const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); await window.Signal.Data.removePreKeyById(ids); ids.forEach(id => { @@ -624,7 +648,7 @@ export class SignalProtocolStore extends EventEmitter { }); if (preKeyCache.size < LOW_KEYS_THRESHOLD) { - this.emitLowKeys(ourUuid, `removePreKeys@${preKeyCache.size}`); + this.emitLowKeys(ourServiceId, `removePreKeys@${preKeyCache.size}`); } } @@ -638,14 +662,14 @@ export class SignalProtocolStore extends EventEmitter { // Signed PreKeys async loadSignedPreKey( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyId: number ): Promise { if (!this.signedPreKeys) { throw new Error('loadSignedPreKey: this.signedPreKeys not yet cached!'); } - const id: SignedPreKeyIdType = `${ourUuid.toString()}:${keyId}`; + const id: SignedPreKeyIdType = `${ourServiceId}:${keyId}`; const entry = this.signedPreKeys.get(id); if (!entry) { @@ -668,7 +692,9 @@ export class SignalProtocolStore extends EventEmitter { return item; } - loadSignedPreKeys(ourUuid: UUID): Array { + loadSignedPreKeys( + ourServiceId: ServiceIdString + ): Array { if (!this.signedPreKeys) { throw new Error('loadSignedPreKeys: this.signedPreKeys not yet cached!'); } @@ -679,7 +705,7 @@ export class SignalProtocolStore extends EventEmitter { const entries = Array.from(this.signedPreKeys.values()); return entries - .filter(({ fromDB }) => fromDB.ourUuid === ourUuid.toString()) + .filter(({ fromDB }) => fromDB.ourUuid === ourServiceId) .map(entry => { const preKey = entry.fromDB; return { @@ -692,13 +718,16 @@ export class SignalProtocolStore extends EventEmitter { }); } - async confirmSignedPreKey(ourUuid: UUID, keyId: number): Promise { + async confirmSignedPreKey( + ourServiceId: ServiceIdString, + keyId: number + ): Promise { const signedPreKeyCache = this.signedPreKeys; if (!signedPreKeyCache) { throw new Error('storeKyberPreKey: this.signedPreKeys not yet cached!'); } - const id: PreKeyIdType = this._getKeyId(ourUuid, keyId); + const id: PreKeyIdType = this._getKeyId(ourServiceId, keyId); const item = signedPreKeyCache.get(id); if (!item) { throw new Error(`confirmSignedPreKey: missing prekey ${id}!`); @@ -717,7 +746,7 @@ export class SignalProtocolStore extends EventEmitter { } async storeSignedPreKey( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyId: number, keyPair: KeyPairType, confirmed?: boolean, @@ -727,11 +756,11 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!'); } - const id: SignedPreKeyIdType = this._getKeyId(ourUuid, keyId); + const id: SignedPreKeyIdType = this._getKeyId(ourServiceId, keyId); const fromDB = { id, - ourUuid: ourUuid.toString(), + ourUuid: ourServiceId, keyId, publicKey: keyPair.pubKey, privateKey: keyPair.privKey, @@ -747,7 +776,7 @@ export class SignalProtocolStore extends EventEmitter { } async removeSignedPreKeys( - ourUuid: UUID, + ourServiceId: ServiceIdString, keyIds: Array ): Promise { const signedPreKeyCache = this.signedPreKeys; @@ -755,7 +784,7 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!'); } - const ids = keyIds.map(keyId => this._getKeyId(ourUuid, keyId)); + const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); await window.Signal.Data.removeSignedPreKeyById(ids); ids.forEach(id => { @@ -1014,14 +1043,14 @@ export class SignalProtocolStore extends EventEmitter { }); } - private _getIdentityQueue(uuid: UUID): PQueue { - const cachedQueue = this.identityQueues.get(uuid.toString()); + private _getIdentityQueue(serviceId: ServiceIdString): PQueue { + const cachedQueue = this.identityQueues.get(serviceId); if (cachedQueue) { return cachedQueue; } const freshQueue = this._createIdentityQueue(); - this.identityQueues.set(uuid.toString(), freshQueue); + this.identityQueues.set(serviceId, freshQueue); return freshQueue; } @@ -1306,14 +1335,14 @@ export class SignalProtocolStore extends EventEmitter { throw new Error('_maybeMigrateSession: Unknown session version type!'); } - const ourUuid = new UUID(session.ourUuid); + const ourServiceId = session.ourUuid; - const keyPair = this.getIdentityKeyPair(ourUuid); + const keyPair = this.getIdentityKeyPair(ourServiceId); if (!keyPair) { throw new Error('_maybeMigrateSession: No identity key for ourself!'); } - const localRegistrationId = await this.getLocalRegistrationId(ourUuid); + const localRegistrationId = await this.getLocalRegistrationId(ourServiceId); if (!isNumber(localRegistrationId)) { throw new Error('_maybeMigrateSession: No registration id for ourself!'); } @@ -1352,10 +1381,10 @@ export class SignalProtocolStore extends EventEmitter { if (qualifiedAddress == null) { throw new Error('storeSession: qualifiedAddress was undefined/null'); } - const { uuid, deviceId } = qualifiedAddress; + const { serviceId, deviceId } = qualifiedAddress; const conversation = window.ConversationController.lookupOrCreate({ - uuid: uuid.toString(), + uuid: serviceId, reason: 'SignalProtocolStore.storeSession', }); strictAssert( @@ -1368,9 +1397,9 @@ export class SignalProtocolStore extends EventEmitter { const fromDB = { id, version: 2, - ourUuid: qualifiedAddress.ourUuid.toString(), + ourUuid: qualifiedAddress.ourServiceId, conversationId: conversation.id, - uuid: uuid.toString(), + uuid: serviceId, deviceId, record: record.serialize().toString('base64'), }; @@ -1398,33 +1427,28 @@ export class SignalProtocolStore extends EventEmitter { } async getOpenDevices( - ourUuid: UUID, - identifiers: ReadonlyArray, + ourServiceId: ServiceIdString, + serviceIds: ReadonlyArray, { zone = GLOBAL_ZONE }: SessionTransactionOptions = {} ): Promise<{ devices: Array; - emptyIdentifiers: Array; + emptyServiceIds: Array; }> { return this.withZone(zone, 'getOpenDevices', async () => { if (!this.sessions) { throw new Error('getOpenDevices: this.sessions not yet cached!'); } - if (identifiers.length === 0) { - return { devices: [], emptyIdentifiers: [] }; + if (serviceIds.length === 0) { + return { devices: [], emptyServiceIds: [] }; } try { - const uuidsOrIdentifiers = new Set( - identifiers.map( - identifier => UUID.lookup(identifier)?.toString() || identifier - ) - ); + const serviceIdSet = new Set(serviceIds); const allSessions = this._getAllSessions(); const entries = allSessions.filter( ({ fromDB }) => - fromDB.ourUuid === ourUuid.toString() && - uuidsOrIdentifiers.has(fromDB.uuid) + fromDB.ourUuid === ourServiceId && serviceIdSet.has(fromDB.uuid) ); const openEntries: Array< | undefined @@ -1462,24 +1486,24 @@ export class SignalProtocolStore extends EventEmitter { const { entry, record } = item; const { uuid } = entry.fromDB; - uuidsOrIdentifiers.delete(uuid); + serviceIdSet.delete(uuid); const id = entry.fromDB.deviceId; const registrationId = record.remoteRegistrationId(); return { - identifier: uuid, + serviceId: uuid, id, registrationId, }; }) .filter(isNotNil); - const emptyIdentifiers = Array.from(uuidsOrIdentifiers.values()); + const emptyServiceIds = Array.from(serviceIdSet.values()); return { devices, - emptyIdentifiers, + emptyServiceIds, }; } catch (error) { log.error( @@ -1492,13 +1516,13 @@ export class SignalProtocolStore extends EventEmitter { } async getDeviceIds({ - ourUuid, - identifier, + ourServiceId, + serviceId, }: Readonly<{ - ourUuid: UUID; - identifier: string; + ourServiceId: ServiceIdString; + serviceId: ServiceIdString; }>): Promise> { - const { devices } = await this.getOpenDevices(ourUuid, [identifier]); + const { devices } = await this.getOpenDevices(ourServiceId, [serviceId]); return devices.map((device: DeviceType) => device.id); } @@ -1563,25 +1587,27 @@ export class SignalProtocolStore extends EventEmitter { ); } - async removeSessionsByUUID(uuid: UUIDStringType): Promise { - return this.withZone(GLOBAL_ZONE, 'removeSessionsByUUID', async () => { + async removeSessionsByServiceId(serviceId: ServiceIdString): Promise { + return this.withZone(GLOBAL_ZONE, 'removeSessionsByServiceId', async () => { if (!this.sessions) { - throw new Error('removeSessionsByUUID: this.sessions not yet cached!'); + throw new Error( + 'removeSessionsByServiceId: this.sessions not yet cached!' + ); } - log.info('removeSessionsByUUID: deleting sessions for', uuid); + log.info('removeSessionsByServiceId: deleting sessions for', serviceId); const entries = Array.from(this.sessions.values()); for (let i = 0, max = entries.length; i < max; i += 1) { const entry = entries[i]; - if (entry.fromDB.uuid === uuid) { + if (entry.fromDB.uuid === serviceId) { this.sessions.delete(entry.fromDB.id); this.pendingSessions.delete(entry.fromDB.id); } } - await window.Signal.Data.removeSessionsByUUID(uuid); + await window.Signal.Data.removeSessionsByServiceId(serviceId); }); } @@ -1644,13 +1670,12 @@ export class SignalProtocolStore extends EventEmitter { encodedAddress.toString() ); - const { uuid, deviceId } = encodedAddress; + const { serviceId, deviceId } = encodedAddress; const allEntries = this._getAllSessions(); const entries = allEntries.filter( entry => - entry.fromDB.uuid === uuid.toString() && - entry.fromDB.deviceId !== deviceId + entry.fromDB.uuid === serviceId && entry.fromDB.deviceId !== deviceId ); await Promise.all( @@ -1661,20 +1686,17 @@ export class SignalProtocolStore extends EventEmitter { }); } - async archiveAllSessions(uuid: UUID): Promise { + async archiveAllSessions(serviceId: ServiceIdString): Promise { return this.withZone(GLOBAL_ZONE, 'archiveAllSessions', async () => { if (!this.sessions) { throw new Error('archiveAllSessions: this.sessions not yet cached!'); } - log.info( - 'archiveAllSessions: archiving all sessions for', - uuid.toString() - ); + log.info('archiveAllSessions: archiving all sessions for', serviceId); const allEntries = this._getAllSessions(); const entries = allEntries.filter( - entry => entry.fromDB.uuid === uuid.toString() + entry => entry.fromDB.uuid === serviceId ); await Promise.all( @@ -1717,11 +1739,11 @@ export class SignalProtocolStore extends EventEmitter { await window.storage.put('sessionResets', sessionResets); try { - const { uuid } = qualifiedAddress; + const { serviceId } = qualifiedAddress; // First, fetch this conversation const conversation = window.ConversationController.lookupOrCreate({ - uuid: uuid.toString(), + uuid: serviceId, reason: 'SignalProtocolStore.lightSessionReset', }); assertDev(conversation, `lightSessionReset/${id}: missing conversation`); @@ -1752,15 +1774,13 @@ export class SignalProtocolStore extends EventEmitter { // Identity Keys - getIdentityRecord(uuid: UUID): IdentityKeyType | undefined { + getIdentityRecord(serviceId: ServiceIdString): IdentityKeyType | undefined { if (!this.identityKeys) { throw new Error('getIdentityRecord: this.identityKeys not yet cached!'); } - const id = uuid.toString(); - try { - const entry = this.identityKeys.get(id); + const entry = this.identityKeys.get(serviceId); if (!entry) { return undefined; } @@ -1768,14 +1788,14 @@ export class SignalProtocolStore extends EventEmitter { return entry.fromDB; } catch (e) { log.error( - `getIdentityRecord: Failed to get identity record for identifier ${id}` + `getIdentityRecord: Failed to get identity record for serviceId ${serviceId}` ); return undefined; } } async getOrMigrateIdentityRecord( - uuid: UUID + serviceId: ServiceIdString ): Promise { if (!this.identityKeys) { throw new Error( @@ -1783,12 +1803,12 @@ export class SignalProtocolStore extends EventEmitter { ); } - const result = this.getIdentityRecord(uuid); + const result = this.getIdentityRecord(serviceId); if (result) { return result; } - const newId = uuid.toString(); + const newId = serviceId; const conversation = window.ConversationController.get(newId); if (!conversation) { return undefined; @@ -1831,12 +1851,12 @@ export class SignalProtocolStore extends EventEmitter { if (encodedAddress == null) { throw new Error('isTrustedIdentity: encodedAddress was undefined/null'); } - const isOurIdentifier = window.textsecure.storage.user.isOurUuid( - encodedAddress.uuid + const isOurIdentifier = window.textsecure.storage.user.isOurServiceId( + encodedAddress.serviceId ); const identityRecord = await this.getOrMigrateIdentityRecord( - encodedAddress.uuid + encodedAddress.serviceId ); if (isOurIdentifier) { @@ -1852,7 +1872,7 @@ export class SignalProtocolStore extends EventEmitter { switch (direction) { case Direction.Sending: return this.isTrustedForSending( - encodedAddress.uuid, + encodedAddress.serviceId, publicKey, identityRecord ); @@ -1865,14 +1885,14 @@ export class SignalProtocolStore extends EventEmitter { // https://github.com/signalapp/Signal-Android/blob/fc3db538bcaa38dc149712a483d3032c9c1f3998/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java#L233 isTrustedForSending( - uuid: UUID, + serviceId: ServiceIdString, publicKey: Uint8Array, identityRecord?: IdentityKeyType ): boolean { if (!identityRecord) { // To track key changes across session switches, we save an old identity key on the // conversation. - const conversation = window.ConversationController.get(uuid.toString()); + const conversation = window.ConversationController.get(serviceId); const previousIdentityKeyBase64 = conversation?.get( 'previousIdentityKey' ); @@ -1915,11 +1935,13 @@ export class SignalProtocolStore extends EventEmitter { return true; } - async loadIdentityKey(uuid: UUID): Promise { - if (uuid == null) { - throw new Error('loadIdentityKey: uuid was undefined/null'); + async loadIdentityKey( + serviceId: ServiceIdString + ): Promise { + if (serviceId == null) { + throw new Error('loadIdentityKey: serviceId was undefined/null'); } - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); if (identityRecord) { return identityRecord.publicKey; @@ -1928,12 +1950,14 @@ export class SignalProtocolStore extends EventEmitter { return undefined; } - async getFingerprint(uuid: UUID): Promise { - if (uuid == null) { - throw new Error('loadIdentityKey: uuid was undefined/null'); + async getFingerprint( + serviceId: ServiceIdString + ): Promise { + if (serviceId == null) { + throw new Error('loadIdentityKey: serviceId was undefined/null'); } - const pubKey = await this.loadIdentityKey(uuid); + const pubKey = await this.loadIdentityKey(serviceId); if (!pubKey) { return; @@ -1982,12 +2006,12 @@ export class SignalProtocolStore extends EventEmitter { nonblockingApproval = false; } - return this._getIdentityQueue(encodedAddress.uuid).add(async () => { + return this._getIdentityQueue(encodedAddress.serviceId).add(async () => { const identityRecord = await this.getOrMigrateIdentityRecord( - encodedAddress.uuid + encodedAddress.serviceId ); - const id = encodedAddress.uuid.toString(); + const id = encodedAddress.serviceId; const logId = `saveIdentity(${id})`; if (!identityRecord || !identityRecord.publicKey) { @@ -2002,7 +2026,11 @@ export class SignalProtocolStore extends EventEmitter { nonblockingApproval, }); - this.checkPreviousKey(encodedAddress.uuid, publicKey, 'saveIdentity'); + this.checkPreviousKey( + encodedAddress.serviceId, + publicKey, + 'saveIdentity' + ); return false; } @@ -2013,8 +2041,8 @@ export class SignalProtocolStore extends EventEmitter { ); if (identityKeyChanged) { - const isOurIdentifier = window.textsecure.storage.user.isOurUuid( - encodedAddress.uuid + const isOurIdentifier = window.textsecure.storage.user.isOurServiceId( + encodedAddress.serviceId ); if (isOurIdentifier && identityKeyChanged) { @@ -2046,7 +2074,11 @@ export class SignalProtocolStore extends EventEmitter { // See `addKeyChange` in `ts/models/conversations.ts` for sender key info // update caused by this. try { - this.emit('keychange', encodedAddress.uuid, 'saveIdentity - change'); + this.emit( + 'keychange', + encodedAddress.serviceId, + 'saveIdentity - change' + ); } catch (error) { log.error( `${logId}: error triggering keychange:`, @@ -2087,28 +2119,31 @@ export class SignalProtocolStore extends EventEmitter { } async saveIdentityWithAttributes( - uuid: UUID, + serviceId: ServiceIdString, attributes: Partial ): Promise { - return this._getIdentityQueue(uuid).add(async () => { - return this.saveIdentityWithAttributesOnQueue(uuid, attributes); + return this._getIdentityQueue(serviceId).add(async () => { + return this.saveIdentityWithAttributesOnQueue(serviceId, attributes); }); } private async saveIdentityWithAttributesOnQueue( - uuid: UUID, + serviceId: ServiceIdString, attributes: Partial ): Promise { - if (uuid == null) { - throw new Error('saveIdentityWithAttributes: uuid was undefined/null'); + if (serviceId == null) { + throw new Error( + 'saveIdentityWithAttributes: serviceId was undefined/null' + ); } - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); - const id = uuid.toString(); + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); + const id = serviceId; // When saving a PNI identity - don't create a separate conversation - const uuidKind = window.textsecure.storage.user.getOurUuidKind(uuid); - if (uuidKind !== UUIDKind.PNI) { + const serviceIdKind = + window.textsecure.storage.user.getOurServiceIdKind(serviceId); + if (serviceIdKind !== ServiceIdKind.PNI) { window.ConversationController.getOrCreate(id, 'private'); } @@ -2123,19 +2158,22 @@ export class SignalProtocolStore extends EventEmitter { } } - async setApproval(uuid: UUID, nonblockingApproval: boolean): Promise { - if (uuid == null) { - throw new Error('setApproval: uuid was undefined/null'); + async setApproval( + serviceId: ServiceIdString, + nonblockingApproval: boolean + ): Promise { + if (serviceId == null) { + throw new Error('setApproval: serviceId was undefined/null'); } if (typeof nonblockingApproval !== 'boolean') { throw new Error('setApproval: Invalid approval status'); } - return this._getIdentityQueue(uuid).add(async () => { - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); + return this._getIdentityQueue(serviceId).add(async () => { + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); if (!identityRecord) { - throw new Error(`setApproval: No identity record for ${uuid}`); + throw new Error(`setApproval: No identity record for ${serviceId}`); } identityRecord.nonblockingApproval = nonblockingApproval; @@ -2146,24 +2184,22 @@ export class SignalProtocolStore extends EventEmitter { // https://github.com/signalapp/Signal-Android/blob/fc3db538bcaa38dc149712a483d3032c9c1f3998/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalBaseIdentityKeyStore.java#L215 // and https://github.com/signalapp/Signal-Android/blob/fc3db538bcaa38dc149712a483d3032c9c1f3998/app/src/main/java/org/thoughtcrime/securesms/verify/VerifyDisplayFragment.java#L544 async setVerified( - uuid: UUID, + serviceId: ServiceIdString, verifiedStatus: number, extra: SetVerifiedExtra = {} ): Promise { - if (uuid == null) { - throw new Error('setVerified: uuid was undefined/null'); + if (serviceId == null) { + throw new Error('setVerified: serviceId was undefined/null'); } if (!validateVerifiedStatus(verifiedStatus)) { throw new Error('setVerified: Invalid verified status'); } - return this._getIdentityQueue(uuid).add(async () => { - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); + return this._getIdentityQueue(serviceId).add(async () => { + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); if (!identityRecord) { - throw new Error( - `setVerified: No identity record for ${uuid.toString()}` - ); + throw new Error(`setVerified: No identity record for ${serviceId}`); } if (validateIdentityKey(identityRecord)) { @@ -2176,14 +2212,14 @@ export class SignalProtocolStore extends EventEmitter { }); } - async getVerified(uuid: UUID): Promise { - if (uuid == null) { - throw new Error('getVerified: uuid was undefined/null'); + async getVerified(serviceId: ServiceIdString): Promise { + if (serviceId == null) { + throw new Error('getVerified: serviceId was undefined/null'); } - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); if (!identityRecord) { - throw new Error(`getVerified: No identity record for ${uuid}`); + throw new Error(`getVerified: No identity record for ${serviceId}`); } const verifiedStatus = identityRecord.verified; @@ -2198,8 +2234,12 @@ export class SignalProtocolStore extends EventEmitter { // conversation. Whenever we get a new identity key for that contact, we need to // check it against that saved key - no need to pop a key change warning if it is // the same! - checkPreviousKey(uuid: UUID, publicKey: Uint8Array, context: string): void { - const conversation = window.ConversationController.get(uuid.toString()); + checkPreviousKey( + serviceId: ServiceIdString, + publicKey: Uint8Array, + context: string + ): void { + const conversation = window.ConversationController.get(serviceId); const previousIdentityKeyBase64 = conversation?.get('previousIdentityKey'); if (conversation && previousIdentityKeyBase64) { const previousIdentityKey = Bytes.fromBase64(previousIdentityKeyBase64); @@ -2208,7 +2248,7 @@ export class SignalProtocolStore extends EventEmitter { if (!constantTimeEqual(previousIdentityKey, publicKey)) { this.emit( 'keychange', - uuid, + serviceId, `${context} - previousIdentityKey check` ); } @@ -2227,7 +2267,7 @@ export class SignalProtocolStore extends EventEmitter { // See https://github.com/signalapp/Signal-Android/blob/fc3db538bcaa38dc149712a483d3032c9c1f3998/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java#L184 async updateIdentityAfterSync( - uuid: UUID, + serviceId: ServiceIdString, verifiedStatus: number, publicKey: Uint8Array ): Promise { @@ -2236,8 +2276,8 @@ export class SignalProtocolStore extends EventEmitter { `Invalid verified status: ${verifiedStatus}` ); - return this._getIdentityQueue(uuid).add(async () => { - const identityRecord = await this.getOrMigrateIdentityRecord(uuid); + return this._getIdentityQueue(serviceId).add(async () => { + const identityRecord = await this.getOrMigrateIdentityRecord(serviceId); const hadEntry = identityRecord !== undefined; const keyMatches = Boolean( identityRecord?.publicKey && @@ -2247,7 +2287,7 @@ export class SignalProtocolStore extends EventEmitter { keyMatches && verifiedStatus === identityRecord?.verified; if (!keyMatches || !statusMatches) { - await this.saveIdentityWithAttributesOnQueue(uuid, { + await this.saveIdentityWithAttributesOnQueue(serviceId, { publicKey, verified: verifiedStatus, firstUse: !hadEntry, @@ -2256,10 +2296,10 @@ export class SignalProtocolStore extends EventEmitter { }); } if (!hadEntry) { - this.checkPreviousKey(uuid, publicKey, 'updateIdentityAfterSync'); + this.checkPreviousKey(serviceId, publicKey, 'updateIdentityAfterSync'); } else if (hadEntry && !keyMatches) { try { - this.emit('keychange', uuid, 'updateIdentityAfterSync - change'); + this.emit('keychange', serviceId, 'updateIdentityAfterSync - change'); } catch (error) { log.error( 'updateIdentityAfterSync: error triggering keychange:', @@ -2288,14 +2328,17 @@ export class SignalProtocolStore extends EventEmitter { }); } - isUntrusted(uuid: UUID, timestampThreshold = TIMESTAMP_THRESHOLD): boolean { - if (uuid == null) { - throw new Error('isUntrusted: uuid was undefined/null'); + isUntrusted( + serviceId: ServiceIdString, + timestampThreshold = TIMESTAMP_THRESHOLD + ): boolean { + if (serviceId == null) { + throw new Error('isUntrusted: serviceId was undefined/null'); } - const identityRecord = this.getIdentityRecord(uuid); + const identityRecord = this.getIdentityRecord(serviceId); if (!identityRecord) { - throw new Error(`isUntrusted: No identity record for ${uuid.toString()}`); + throw new Error(`isUntrusted: No identity record for ${serviceId}`); } if ( @@ -2309,15 +2352,15 @@ export class SignalProtocolStore extends EventEmitter { return false; } - async removeIdentityKey(uuid: UUID): Promise { + async removeIdentityKey(serviceId: ServiceIdString): Promise { if (!this.identityKeys) { throw new Error('removeIdentityKey: this.identityKeys not yet cached!'); } - const id = uuid.toString(); + const id = serviceId; this.identityKeys.delete(id); - await window.Signal.Data.removeIdentityKeyById(id); - await this.removeSessionsByUUID(id); + await window.Signal.Data.removeIdentityKeyById(serviceId); + await this.removeSessionsByServiceId(serviceId); } // Not yet processed messages - for resiliency @@ -2415,16 +2458,16 @@ export class SignalProtocolStore extends EventEmitter { }); } - async removeOurOldPni(oldPni: UUID): Promise { + async removeOurOldPni(oldPni: PniString): Promise { const { storage } = window; log.info(`SignalProtocolStore.removeOurOldPni(${oldPni})`); // Update caches - this.ourIdentityKeys.delete(oldPni.toString()); - this.ourRegistrationIds.delete(oldPni.toString()); + this.ourIdentityKeys.delete(oldPni); + this.ourRegistrationIds.delete(oldPni); - const preKeyPrefix = `${oldPni.toString()}:`; + const preKeyPrefix = `${oldPni}:`; if (this.preKeys) { for (const key of this.preKeys.keys()) { if (key.startsWith(preKeyPrefix)) { @@ -2451,20 +2494,20 @@ export class SignalProtocolStore extends EventEmitter { await Promise.all([ storage.put( 'identityKeyMap', - omit(storage.get('identityKeyMap') || {}, oldPni.toString()) + omit(storage.get('identityKeyMap') || {}, oldPni) ), storage.put( 'registrationIdMap', - omit(storage.get('registrationIdMap') || {}, oldPni.toString()) + omit(storage.get('registrationIdMap') || {}, oldPni) ), - window.Signal.Data.removePreKeysByUuid(oldPni.toString()), - window.Signal.Data.removeSignedPreKeysByUuid(oldPni.toString()), - window.Signal.Data.removeKyberPreKeysByUuid(oldPni.toString()), + window.Signal.Data.removePreKeysByServiceId(oldPni), + window.Signal.Data.removeSignedPreKeysByServiceId(oldPni), + window.Signal.Data.removeKyberPreKeysByServiceId(oldPni), ]); } async updateOurPniKeyMaterial( - pni: UUID, + pni: PniString, { identityKeyPair: identityBytes, lastResortKyberPreKey: lastResortKyberPreKeyBytes, @@ -2491,29 +2534,29 @@ export class SignalProtocolStore extends EventEmitter { const pniPrivateKey = identityKeyPair.privateKey.serialize(); // Update caches - this.ourIdentityKeys.set(pni.toString(), { + this.ourIdentityKeys.set(pni, { pubKey: pniPublicKey, privKey: pniPrivateKey, }); - this.ourRegistrationIds.set(pni.toString(), registrationId); + this.ourRegistrationIds.set(pni, registrationId); // Update database await Promise.all([ storage.put('identityKeyMap', { ...(storage.get('identityKeyMap') || {}), - [pni.toString()]: { + [pni]: { pubKey: pniPublicKey, privKey: pniPrivateKey, }, }), storage.put('registrationIdMap', { ...(storage.get('registrationIdMap') || {}), - [pni.toString()]: registrationId, + [pni]: registrationId, }), async () => { const newId = signedPreKey.id() + 1; log.warn(`${logId}: Updating next signed pre key id to ${newId}`); - await storage.put(SIGNED_PRE_KEY_ID_KEY[UUIDKind.PNI], newId); + await storage.put(SIGNED_PRE_KEY_ID_KEY[ServiceIdKind.PNI], newId); }, this.storeSignedPreKey( pni, @@ -2531,7 +2574,7 @@ export class SignalProtocolStore extends EventEmitter { } const newId = lastResortKyberPreKey.id() + 1; log.warn(`${logId}: Updating next kyber pre key id to ${newId}`); - await storage.put(KYBER_KEY_ID_KEY[UUIDKind.PNI], newId); + await storage.put(KYBER_KEY_ID_KEY[ServiceIdKind.PNI], newId); }, lastResortKyberPreKeyBytes && lastResortKyberPreKey ? this.storeKyberPreKeys(pni, [ @@ -2541,7 +2584,7 @@ export class SignalProtocolStore extends EventEmitter { isConfirmed: true, isLastResort: true, keyId: lastResortKyberPreKey.id(), - ourUuid: pni.toString(), + ourUuid: pni, }, ]) : undefined, @@ -2570,19 +2613,19 @@ export class SignalProtocolStore extends EventEmitter { } signAlternateIdentity(): PniSignatureMessageType | undefined { - const ourACI = window.textsecure.storage.user.getCheckedUuid(UUIDKind.ACI); - const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI); - if (!ourPNI) { + const ourAci = window.textsecure.storage.user.getCheckedAci(); + const ourPni = window.textsecure.storage.user.getPni(); + if (!ourPni) { log.error('signAlternateIdentity: No local pni'); return undefined; } - if (this.cachedPniSignatureMessage?.pni === ourPNI.toString()) { + if (this.cachedPniSignatureMessage?.pni === ourPni) { return this.cachedPniSignatureMessage; } - const aciKeyPair = this.getIdentityKeyPair(ourACI); - const pniKeyPair = this.getIdentityKeyPair(ourPNI); + const aciKeyPair = this.getIdentityKeyPair(ourAci); + const pniKeyPair = this.getIdentityKeyPair(ourPni); if (!aciKeyPair) { log.error('signAlternateIdentity: No local ACI key pair'); return undefined; @@ -2598,7 +2641,7 @@ export class SignalProtocolStore extends EventEmitter { ); const aciPubKey = PublicKey.deserialize(Buffer.from(aciKeyPair.pubKey)); this.cachedPniSignatureMessage = { - pni: ourPNI.toString(), + pni: ourPni, signature: pniIdentity.signAlternateIdentity(aciPubKey), }; @@ -2645,11 +2688,11 @@ export class SignalProtocolStore extends EventEmitter { return Array.from(union.values()); } - private emitLowKeys(ourUuid: UUID, source: string) { + private emitLowKeys(ourServiceId: ServiceIdString, source: string) { const logId = `SignalProtocolStore.emitLowKeys/${source}:`; try { log.info(`${logId}: Emitting event`); - this.emit('lowKeys', ourUuid); + this.emit('lowKeys', ourServiceId); } catch (error) { log.error(`${logId}: Error thrown from emit`, Errors.toLogFormat(error)); } @@ -2661,12 +2704,12 @@ export class SignalProtocolStore extends EventEmitter { public override on( name: 'lowKeys', - handler: (ourUuid: UUID) => unknown + handler: (ourServiceId: ServiceIdString) => unknown ): this; public override on( name: 'keychange', - handler: (theirUuid: UUID, reason: string) => unknown + handler: (theirServiceId: ServiceIdString, reason: string) => unknown ): this; public override on(name: 'removeAllData', handler: () => unknown): this; @@ -2679,11 +2722,11 @@ export class SignalProtocolStore extends EventEmitter { return super.on(eventName, listener); } - public override emit(name: 'lowKeys', ourUuid: UUID): boolean; + public override emit(name: 'lowKeys', ourServiceid: ServiceIdString): boolean; public override emit( name: 'keychange', - theirUuid: UUID, + theirServiceId: ServiceIdString, reason: string ): boolean; diff --git a/ts/background.ts b/ts/background.ts index 7640926c4..137118f0a 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -7,6 +7,7 @@ import { bindActionCreators } from 'redux'; import { render } from 'react-dom'; import { batch as batchDispatch } from 'react-redux'; import PQueue from 'p-queue'; +import { v4 as generateUuid } from 'uuid'; import * as Registration from './util/registration'; import MessageReceiver from './textsecure/MessageReceiver'; @@ -39,7 +40,6 @@ import { drop } from './util/drop'; import { explodePromise } from './util/explodePromise'; import { isWindowDragElement } from './util/isWindowDragElement'; import { assertDev, strictAssert } from './util/assert'; -import { normalizeUuid } from './util/normalizeUuid'; import { filter } from './util/iterables'; import { isNotNil } from './util/isNotNil'; import { isPnpEnabled } from './util/isPnpEnabled'; @@ -146,8 +146,13 @@ import { import { themeChanged } from './shims/themeChanged'; import { createIPCEvents } from './util/createIPCEvents'; import { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; -import { isValidUuid, UUIDKind, UUID } from './types/UUID'; -import type { TaggedUUIDStringType } from './types/UUID'; +import type { ServiceIdString } from './types/ServiceId'; +import { + ServiceIdKind, + isAciString, + isServiceIdString, + normalizeAci, +} from './types/ServiceId'; import * as log from './logging/log'; import { loadRecentEmojis } from './util/loadRecentEmojis'; import { deleteAllLogs } from './util/deleteAllLogs'; @@ -594,9 +599,10 @@ export async function startApp(): Promise { window.textsecure.storage.protocol.on( 'lowKeys', throttle( - (ourUuid: UUID) => { - const uuidKind = window.textsecure.storage.user.getOurUuidKind(ourUuid); - drop(window.getAccountManager().maybeUpdateKeys(uuidKind)); + (ourServiceId: ServiceIdString) => { + const serviceIdKind = + window.textsecure.storage.user.getOurServiceIdKind(ourServiceId); + drop(window.getAccountManager().maybeUpdateKeys(serviceIdKind)); }, durations.MINUTE, { trailing: true, leading: false } @@ -1322,12 +1328,8 @@ export async function startApp(): Promise { window.Whisper.events.on('userChanged', (reconnect = false) => { const newDeviceId = window.textsecure.storage.user.getDeviceId(); const newNumber = window.textsecure.storage.user.getNumber(); - const newACI = window.textsecure.storage.user - .getUuid(UUIDKind.ACI) - ?.toString(); - const newPNI = window.textsecure.storage.user - .getUuid(UUIDKind.PNI) - ?.toString(); + const newACI = window.textsecure.storage.user.getAci(); + const newPNI = window.textsecure.storage.user.getPni(); const ourConversation = window.ConversationController.getOurConversation(); @@ -1339,8 +1341,8 @@ export async function startApp(): Promise { ourConversationId: ourConversation?.get('id'), ourDeviceId: newDeviceId, ourNumber: newNumber, - ourACI: newACI, - ourPNI: newPNI, + ourAci: newACI, + ourPni: newPNI, regionCode: window.storage.get('regionCode'), }); @@ -1464,7 +1466,7 @@ export async function startApp(): Promise { log.info( `Expiration start timestamp cleanup: Found ${messagesUnexpectedlyMissingExpirationStartTimestamp.length} messages for cleanup` ); - if (!window.textsecure.storage.user.getUuid()) { + if (!window.textsecure.storage.user.getAci()) { log.info( "Expiration start timestamp cleanup: Cancelling update; we don't have our own UUID" ); @@ -1495,7 +1497,7 @@ export async function startApp(): Promise { }); await window.Signal.Data.saveMessages(newMessageAttributes, { - ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(), + ourAci: window.textsecure.storage.user.getCheckedAci(), }); } log.info('Expiration start timestamp cleanup: complete'); @@ -1537,7 +1539,7 @@ export async function startApp(): Promise { }); const isCoreDataValid = Boolean( - window.textsecure.storage.user.getUuid() && + window.textsecure.storage.user.getAci() && window.ConversationController.getOurConversation() ); @@ -1855,7 +1857,7 @@ export async function startApp(): Promise { const deviceId = window.textsecure.storage.user.getDeviceId(); - if (!window.textsecure.storage.user.getUuid()) { + if (!window.textsecure.storage.user.getAci()) { log.error('UUID not captured during registration, unlinking'); return unlinkAndDisconnect(RemoveAllConfiguration.Full); } @@ -1875,7 +1877,7 @@ export async function startApp(): Promise { } } - if (!window.textsecure.storage.user.getUuid(UUIDKind.PNI)) { + if (!window.textsecure.storage.user.getPni()) { log.error('PNI not captured during registration, unlinking softly'); return unlinkAndDisconnect(RemoveAllConfiguration.Soft); } @@ -2170,7 +2172,7 @@ export async function startApp(): Promise { function onTyping(ev: TypingEvent): void { // Note: this type of message is automatically removed from cache in MessageReceiver - const { typing, sender, senderUuid, senderDevice } = ev; + const { typing, sender, senderAci, senderDevice } = ev; const { groupV2Id, started } = typing || {}; // We don't do anything with incoming typing messages if the setting is disabled @@ -2183,7 +2185,7 @@ export async function startApp(): Promise { const { conversation: senderConversation } = window.ConversationController.maybeMergeContacts({ e164: sender, - aci: senderUuid, + aci: senderAci, reason: `onTyping(${typing.timestamp})`, }); @@ -2202,19 +2204,19 @@ export async function startApp(): Promise { } if (!conversation) { log.warn( - `onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), ${sender}, ${senderUuid})` + `onTyping: Did not find conversation for typing indicator (groupv2(${groupV2Id}), ${sender}, ${senderAci})` ); return; } - const ourACI = window.textsecure.storage.user.getUuid(UUIDKind.ACI); - const ourPNI = window.textsecure.storage.user.getUuid(UUIDKind.PNI); + const ourAci = window.textsecure.storage.user.getAci(); + const ourPni = window.textsecure.storage.user.getPni(); // We drop typing notifications in groups we're not a part of if ( !isDirectConversation(conversation.attributes) && - !(ourACI && conversation.hasMember(ourACI)) && - !(ourPNI && conversation.hasMember(ourPNI)) + !(ourAci && conversation.hasMember(ourAci)) && + !(ourPni && conversation.hasMember(ourPni)) ) { log.warn( `Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.` @@ -2353,12 +2355,15 @@ export async function startApp(): Promise { }: EnvelopeUnsealedEvent): Promise { throttledSetInboxEnvelopeTimestamp(envelope.serverTimestamp); - const ourUuid = window.textsecure.storage.user.getUuid()?.toString(); - if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) { + const ourAci = window.textsecure.storage.user.getAci(); + if ( + envelope.sourceServiceId !== ourAci && + isAciString(envelope.sourceServiceId) + ) { const { mergePromises, conversation } = window.ConversationController.maybeMergeContacts({ e164: envelope.source, - aci: envelope.sourceUuid, + aci: envelope.sourceServiceId, reason: `onEnvelopeUnsealed(${envelope.timestamp})`, }); @@ -2382,9 +2387,7 @@ export async function startApp(): Promise { message: data.message, // 'message' event: for 1:1 converations, the conversation is same as sender destination: data.source, - destinationUuid: { - aci: data.sourceUuid, - }, + destinationServiceId: data.sourceAci, }); const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags; @@ -2404,11 +2407,14 @@ export async function startApp(): Promise { const sender = getContact(message.attributes); strictAssert(sender, 'MessageModel has no sender'); - const uuidKind = window.textsecure.storage.user.getOurUuidKind( - new UUID(data.destinationUuid) + const serviceIdKind = window.textsecure.storage.user.getOurServiceIdKind( + data.destinationServiceId ); - if (uuidKind === UUIDKind.PNI && !sender.get('shareMyPhoneNumber')) { + if ( + serviceIdKind === ServiceIdKind.PNI && + !sender.get('shareMyPhoneNumber') + ) { log.info( 'onMessageReceived: setting shareMyPhoneNumber ' + `for ${sender.idForLogging()}` @@ -2428,12 +2434,12 @@ export async function startApp(): Promise { if (data.message.reaction) { strictAssert( - data.message.reaction.targetAuthorUuid, - 'Reaction without targetAuthorUuid' + data.message.reaction.targetAuthorAci, + 'Reaction without targetAuthorAci' ); - const targetAuthorUuid = normalizeUuid( - data.message.reaction.targetAuthorUuid, - 'DataMessage.Reaction.targetAuthorUuid' + const targetAuthorAci = normalizeAci( + data.message.reaction.targetAuthorAci, + 'DataMessage.Reaction.targetAuthorAci' ); const { reaction, timestamp } = data.message; @@ -2448,11 +2454,12 @@ export async function startApp(): Promise { reaction.targetTimestamp, 'Reaction without targetTimestamp' ); - const fromConversation = window.ConversationController.lookupOrCreate({ - e164: data.source, - uuid: data.sourceUuid, - reason: 'onMessageReceived:reaction', - }); + const { conversation: fromConversation } = + window.ConversationController.maybeMergeContacts({ + e164: data.source, + aci: data.sourceAci, + reason: 'onMessageReceived:reaction', + }); strictAssert(fromConversation, 'Reaction without fromConversation'); log.info('Queuing incoming reaction for', reaction.targetTimestamp); @@ -2462,7 +2469,7 @@ export async function startApp(): Promise { remove: reaction.remove, source: ReactionSource.FromSomeoneElse, storyReactionMessage: message, - targetAuthorUuid, + targetAuthorUuid: targetAuthorAci, targetTimestamp: reaction.targetTimestamp, timestamp, }; @@ -2483,11 +2490,12 @@ export async function startApp(): Promise { 'Delete missing targetSentTimestamp' ); strictAssert(data.serverTimestamp, 'Delete missing serverTimestamp'); - const fromConversation = window.ConversationController.lookupOrCreate({ - e164: data.source, - uuid: data.sourceUuid, - reason: 'onMessageReceived:delete', - }); + const { conversation: fromConversation } = + window.ConversationController.maybeMergeContacts({ + e164: data.source, + aci: data.sourceAci, + reason: 'onMessageReceived:delete', + }); strictAssert(fromConversation, 'Delete missing fromConversation'); const attributes: DeleteAttributesType = { @@ -2506,11 +2514,12 @@ export async function startApp(): Promise { const { editedMessageTimestamp } = data.message; strictAssert(editedMessageTimestamp, 'Edit missing targetSentTimestamp'); - const fromConversation = window.ConversationController.lookupOrCreate({ - e164: data.source, - uuid: data.sourceUuid, - reason: 'onMessageReceived:edit', - }); + const { conversation: fromConversation } = + window.ConversationController.maybeMergeContacts({ + aci: data.sourceAci, + e164: data.source, + reason: 'onMessageReceived:edit', + }); strictAssert(fromConversation, 'Edit missing fromConversation'); log.info('Queuing incoming edit for', { @@ -2547,7 +2556,7 @@ export async function startApp(): Promise { confirm, }: ProfileKeyUpdateEvent): Promise { const { conversation } = window.ConversationController.maybeMergeContacts({ - aci: data.sourceUuid, + aci: data.sourceAci, e164: data.source, reason: 'onProfileKeyUpdate', }); @@ -2560,7 +2569,7 @@ export async function startApp(): Promise { log.info( 'onProfileKeyUpdate: updating profileKey for', - data.sourceUuid, + data.sourceAci, data.source ); @@ -2617,10 +2626,10 @@ export async function startApp(): Promise { unidentifiedStatus.reduce( ( result: SendStateByConversationId, - { destinationUuid, destination, isAllowedToReplyToStory } + { destinationServiceId, destination, isAllowedToReplyToStory } ) => { const conversation = window.ConversationController.get( - destinationUuid?.aci || destinationUuid?.pni || destination + destinationServiceId || destination ); if (!conversation || conversation.id === ourId) { return result; @@ -2647,17 +2656,12 @@ export async function startApp(): Promise { if (unidentifiedStatus.length) { unidentifiedDeliveries = unidentifiedStatus .filter(item => Boolean(item.unidentified)) - .map( - item => - item.destinationUuid?.aci || - item.destinationUuid?.pni || - item.destination - ) + .map(item => item.destinationServiceId || item.destination) .filter(isNotNil); } const partialMessage: MessageAttributesType = { - id: UUID.generate().toString(), + id: generateUuid(), canReplyToStory: data.message.isStory ? data.message.canReplyToStory : undefined, @@ -2675,7 +2679,7 @@ export async function startApp(): Promise { serverTimestamp: data.serverTimestamp, source: window.textsecure.storage.user.getNumber(), sourceDevice: data.device, - sourceUuid: window.textsecure.storage.user.getUuid()?.toString(), + sourceUuid: window.textsecure.storage.user.getAci(), timestamp, type: data.message.isStory ? 'story' : 'outgoing', storyDistributionListId: data.storyDistributionListId, @@ -2689,11 +2693,11 @@ export async function startApp(): Promise { const getMessageDescriptor = ({ message, destination, - destinationUuid, + destinationServiceId, }: { message: ProcessedDataMessage; destination?: string; - destinationUuid?: TaggedUUIDStringType; + destinationServiceId?: ServiceIdString; }): MessageDescriptor => { if (message.groupV2) { const { id } = message.groupV2; @@ -2734,7 +2738,7 @@ export async function startApp(): Promise { } const conversation = window.ConversationController.get( - destinationUuid?.aci || destinationUuid?.pni || destination + destinationServiceId || destination ); strictAssert(conversation, 'Destination conversation cannot be created'); @@ -2751,7 +2755,7 @@ export async function startApp(): Promise { const { data, confirm } = event; const source = window.textsecure.storage.user.getNumber(); - const sourceUuid = window.textsecure.storage.user.getUuid()?.toString(); + const sourceUuid = window.textsecure.storage.user.getAci(); strictAssert(source && sourceUuid, 'Missing user number and uuid'); const messageDescriptor = getMessageDescriptor({ @@ -2773,18 +2777,18 @@ export async function startApp(): Promise { if (data.message.reaction) { strictAssert( - data.message.reaction.targetAuthorUuid, - 'Reaction without targetAuthorUuid' + data.message.reaction.targetAuthorAci, + 'Reaction without targetAuthorAci' ); - const targetAuthorUuid = normalizeUuid( - data.message.reaction.targetAuthorUuid, - 'DataMessage.Reaction.targetAuthorUuid' + const targetAuthorAci = normalizeAci( + data.message.reaction.targetAuthorAci, + 'DataMessage.Reaction.targetAuthorAci' ); const { reaction, timestamp } = data.message; strictAssert( reaction.targetTimestamp, - 'Reaction without targetAuthorUuid' + 'Reaction without targetAuthorAci' ); if (!isValidReactionEmoji(reaction.emoji)) { @@ -2800,7 +2804,7 @@ export async function startApp(): Promise { remove: reaction.remove, source: ReactionSource.FromSync, storyReactionMessage: message, - targetAuthorUuid, + targetAuthorUuid: targetAuthorAci, targetTimestamp: reaction.targetTimestamp, timestamp, }; @@ -2882,7 +2886,7 @@ export async function startApp(): Promise { `Did not receive receivedAtCounter for message: ${data.timestamp}` ); const partialMessage: MessageAttributesType = { - id: UUID.generate().toString(), + id: generateUuid(), canReplyToStory: data.message.isStory ? data.message.canReplyToStory : undefined, @@ -2896,7 +2900,7 @@ export async function startApp(): Promise { serverTimestamp: data.serverTimestamp, source: data.source, sourceDevice: data.sourceDevice, - sourceUuid: data.sourceUuid ? UUID.cast(data.sourceUuid) : undefined, + sourceUuid: data.sourceAci, timestamp: data.timestamp, type: data.message.isStory ? 'story' : 'incoming', unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived, @@ -3033,14 +3037,14 @@ export async function startApp(): Promise { function onViewOnceOpenSync(ev: ViewOnceOpenSyncEvent): void { ev.confirm(); - const { source, sourceUuid, timestamp } = ev; + const { source, sourceAci, timestamp } = ev; log.info(`view once open sync ${source} ${timestamp}`); - strictAssert(sourceUuid, 'ViewOnceOpen without sourceUuid'); + strictAssert(sourceAci, 'ViewOnceOpen without sourceAci'); strictAssert(timestamp, 'ViewOnceOpen without timestamp'); const attributes: ViewOnceOpenSyncAttributesType = { source, - sourceUuid, + sourceAci, timestamp, }; const sync = ViewOnceOpenSyncs.getSingleton().add(attributes); @@ -3058,9 +3062,9 @@ export async function startApp(): Promise { switch (eventType) { case FETCH_LATEST_ENUM.LOCAL_PROFILE: { log.info('onFetchLatestSync: fetching latest local profile'); - const ourUuid = window.textsecure.storage.user.getUuid()?.toString(); + const ourAci = window.textsecure.storage.user.getAci(); const ourE164 = window.textsecure.storage.user.getNumber(); - await getProfile(ourUuid, ourE164); + await getProfile(ourAci, ourE164); break; } case FETCH_LATEST_ENUM.STORAGE_MANIFEST: @@ -3111,12 +3115,11 @@ export async function startApp(): Promise { function onMessageRequestResponse(ev: MessageRequestResponseEvent): void { ev.confirm(); - const { threadE164, threadUuid, groupV2Id, messageRequestResponseType } = - ev; + const { threadE164, threadAci, groupV2Id, messageRequestResponseType } = ev; log.info('onMessageRequestResponse', { threadE164, - threadUuid, + threadAci, groupV2Id: `groupv2(${groupV2Id})`, messageRequestResponseType, }); @@ -3128,7 +3131,7 @@ export async function startApp(): Promise { const attributes: MessageRequestAttributesType = { threadE164, - threadUuid, + threadAci, groupV2Id, type: messageRequestResponseType, }; @@ -3166,19 +3169,19 @@ export async function startApp(): Promise { envelopeTimestamp, timestamp, source, - sourceUuid, + sourceServiceId, sourceDevice, wasSentEncrypted, } = event.receipt; - const { conversation: sourceConversation } = - window.ConversationController.maybeMergeContacts({ - aci: sourceUuid, - e164: source, - reason: `onReadOrViewReceipt(${envelopeTimestamp})`, - }); + const sourceConversation = window.ConversationController.lookupOrCreate({ + uuid: sourceServiceId, + e164: source, + reason: `onReadOrViewReceipt(${envelopeTimestamp})`, + }); + strictAssert(sourceConversation, 'Failed to create conversation'); log.info( logTitle, - `${sourceUuid || source}.${sourceDevice}`, + `${sourceServiceId || source}.${sourceDevice}`, envelopeTimestamp, 'for sent message', timestamp @@ -3187,8 +3190,8 @@ export async function startApp(): Promise { event.confirm(); strictAssert( - isValidUuid(sourceUuid), - 'onReadOrViewReceipt: Missing sourceUuid' + isServiceIdString(sourceServiceId), + 'onReadOrViewReceipt: Missing sourceServiceId' ); strictAssert(sourceDevice, 'onReadOrViewReceipt: Missing sourceDevice'); @@ -3196,7 +3199,7 @@ export async function startApp(): Promise { messageSentAt: timestamp, receiptTimestamp: envelopeTimestamp, sourceConversationId: sourceConversation.id, - sourceUuid, + sourceServiceId, sourceDevice, type, wasSentEncrypted, @@ -3208,19 +3211,20 @@ export async function startApp(): Promise { } function onReadSync(ev: ReadSyncEvent): Promise { - const { envelopeTimestamp, sender, senderUuid, timestamp } = ev.read; + const { envelopeTimestamp, sender, senderAci, timestamp } = ev.read; const readAt = envelopeTimestamp; - const senderConversation = window.ConversationController.lookupOrCreate({ - e164: sender, - uuid: senderUuid, - reason: 'onReadSync', - }); + const { conversation: senderConversation } = + window.ConversationController.maybeMergeContacts({ + aci: senderAci, + e164: sender, + reason: 'onReadSync', + }); const senderId = senderConversation?.id; log.info( 'read sync', sender, - senderUuid, + senderAci, envelopeTimestamp, senderId, 'for message', @@ -3228,13 +3232,13 @@ export async function startApp(): Promise { ); strictAssert(senderId, 'onReadSync missing senderId'); - strictAssert(senderUuid, 'onReadSync missing senderUuid'); + strictAssert(senderAci, 'onReadSync missing senderAci'); strictAssert(timestamp, 'onReadSync missing timestamp'); const attributes: ReadSyncAttributesType = { senderId, sender, - senderUuid, + senderAci, timestamp, readAt, }; @@ -3248,18 +3252,19 @@ export async function startApp(): Promise { } function onViewSync(ev: ViewSyncEvent): Promise { - const { envelopeTimestamp, senderE164, senderUuid, timestamp } = ev.view; - const senderConversation = window.ConversationController.lookupOrCreate({ - e164: senderE164, - uuid: senderUuid, - reason: 'onViewSync', - }); + const { envelopeTimestamp, senderE164, senderAci, timestamp } = ev.view; + const { conversation: senderConversation } = + window.ConversationController.maybeMergeContacts({ + e164: senderE164, + aci: senderAci, + reason: 'onViewSync', + }); const senderId = senderConversation?.id; log.info( 'view sync', senderE164, - senderUuid, + senderAci, envelopeTimestamp, senderId, 'for message', @@ -3267,13 +3272,13 @@ export async function startApp(): Promise { ); strictAssert(senderId, 'onViewSync missing senderId'); - strictAssert(senderUuid, 'onViewSync missing senderUuid'); + strictAssert(senderAci, 'onViewSync missing senderAci'); strictAssert(timestamp, 'onViewSync missing timestamp'); const attributes: ViewSyncAttributesType = { senderId, senderE164, - senderUuid, + senderAci, timestamp, viewedAt: envelopeTimestamp, }; @@ -3290,7 +3295,7 @@ export async function startApp(): Promise { const { deliveryReceipt } = ev; const { envelopeTimestamp, - sourceUuid, + sourceServiceId, source, sourceDevice, timestamp, @@ -3299,16 +3304,15 @@ export async function startApp(): Promise { ev.confirm(); - const { conversation: sourceConversation } = - window.ConversationController.maybeMergeContacts({ - aci: sourceUuid, - e164: source, - reason: `onDeliveryReceipt(${envelopeTimestamp})`, - }); + const sourceConversation = window.ConversationController.lookupOrCreate({ + uuid: sourceServiceId, + e164: source, + reason: `onDeliveryReceipt(${envelopeTimestamp})`, + }); log.info( 'delivery receipt from', - `${sourceUuid || source}.${sourceDevice}`, + `${sourceServiceId || source}.${sourceDevice}`, envelopeTimestamp, 'for sent message', timestamp, @@ -3320,8 +3324,8 @@ export async function startApp(): Promise { 'onDeliveryReceipt: missing envelopeTimestamp' ); strictAssert( - isValidUuid(sourceUuid), - 'onDeliveryReceipt: missing valid sourceUuid' + isServiceIdString(sourceServiceId), + 'onDeliveryReceipt: missing valid sourceServiceId' ); strictAssert(sourceDevice, 'onDeliveryReceipt: missing sourceDevice'); @@ -3329,7 +3333,7 @@ export async function startApp(): Promise { messageSentAt: timestamp, receiptTimestamp: envelopeTimestamp, sourceConversationId: sourceConversation?.id, - sourceUuid, + sourceServiceId, sourceDevice, type: MessageReceiptType.Delivery, wasSentEncrypted, diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 6cef11e1c..d7a77e5ca 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -18,6 +18,7 @@ import { import type { ConversationTypeType } from '../state/ducks/conversations'; import type { AvatarColorType } from '../types/Colors'; import { AvatarColors } from '../types/Colors'; +import { generateAci } from '../types/ServiceId'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource'; import { setupI18n } from '../util/setupI18n'; @@ -89,7 +90,7 @@ const createProps = (storyProps: Partial = {}): PropsType => ({ ), title: text('Caller Title', 'Morty Smith'), }), - uuid: 'cb0dd0c8-7393-41e9-a0aa-d631c4109541', + uuid: generateAci(), }, notifyForCall: action('notify-for-call'), openSystemPreferencesAction: action('open-system-preferences-action'), diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index 247c85fb5..c9a8fed3c 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -14,6 +14,7 @@ import { GroupCallConnectionState, GroupCallJoinState, } from '../types/Calling'; +import { generateAci } from '../types/ServiceId'; import type { ConversationType } from '../state/ducks/conversations'; import { AvatarColors } from '../types/Colors'; import type { PropsType } from './CallScreen'; @@ -174,7 +175,7 @@ const createProps = ( name: 'Morty Smith', profileName: 'Morty Smith', title: 'Morty Smith', - uuid: '3c134598-eecb-42ab-9ad3-2b0873f771b2', + uuid: generateAci(), }), openSystemPreferencesAction: action('open-system-preferences-action'), setGroupCallVideoRequest: action('set-group-call-video-request'), @@ -311,7 +312,7 @@ export function GroupCall1(): JSX.Element { videoAspectRatio: 1.3, ...getDefaultConversation({ isBlocked: false, - uuid: '72fa60e5-25fb-472d-8a56-e56867c57dda', + uuid: generateAci(), title: 'Tyler', }), }, @@ -379,7 +380,7 @@ export function GroupCallReconnecting(): JSX.Element { ...getDefaultConversation({ isBlocked: false, title: 'Tyler', - uuid: '33871c64-0c22-45ce-8aa4-0ec237ac4a31', + uuid: generateAci(), }), }, ], diff --git a/ts/components/CallingLobby.stories.tsx b/ts/components/CallingLobby.stories.tsx index 46d2f73a3..f844627bc 100644 --- a/ts/components/CallingLobby.stories.tsx +++ b/ts/components/CallingLobby.stories.tsx @@ -5,13 +5,14 @@ import * as React from 'react'; import { times } from 'lodash'; import { boolean } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; +import { v4 as generateUuid } from 'uuid'; import { AvatarColors } from '../types/Colors'; import type { ConversationType } from '../state/ducks/conversations'; import type { PropsType } from './CallingLobby'; import { CallingLobby } from './CallingLobby'; import { setupI18n } from '../util/setupI18n'; -import { UUID } from '../types/UUID'; +import { generateAci } from '../types/ServiceId'; import enMessages from '../../_locales/en/messages.json'; import { getDefaultConversation, @@ -65,8 +66,8 @@ const createProps = (overrideProps: Partial = {}): PropsType => { overrideProps.me || getDefaultConversation({ color: AvatarColors[0], - id: UUID.generate().toString(), - uuid: UUID.generate().toString(), + id: generateUuid(), + uuid: generateAci(), }), onCallCanceled: action('on-call-canceled'), onJoinCall: action('on-join-call'), @@ -116,8 +117,8 @@ export function NoCameraLocalAvatar(): JSX.Element { me: getDefaultConversation({ avatarPath: '/fixtures/kitten-4-112-112.jpg', color: AvatarColors[0], - id: UUID.generate().toString(), - uuid: UUID.generate().toString(), + id: generateUuid(), + uuid: generateAci(), }), }); return ; @@ -167,11 +168,11 @@ GroupCall1PeekedParticipant.story = { }; export function GroupCall1PeekedParticipantSelf(): JSX.Element { - const uuid = UUID.generate().toString(); + const uuid = generateAci(); const props = createProps({ isGroupCall: true, me: getDefaultConversation({ - id: UUID.generate().toString(), + id: generateUuid(), uuid, }), peekedParticipants: [fakePeekedParticipant({ title: 'Ash', uuid })], diff --git a/ts/components/CompositionInput.stories.tsx b/ts/components/CompositionInput.stories.tsx index 48834ff2a..9833cbfa3 100644 --- a/ts/components/CompositionInput.stories.tsx +++ b/ts/components/CompositionInput.stories.tsx @@ -11,6 +11,7 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat import type { Props } from './CompositionInput'; import { CompositionInput } from './CompositionInput'; import { setupI18n } from '../util/setupI18n'; +import { generateAci } from '../types/ServiceId'; import enMessages from '../../_locales/en/messages.json'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; @@ -137,7 +138,7 @@ export function Mentions(): JSX.Element { { start: 5, length: 1, - mentionUuid: '0', + mentionUuid: generateAci(), conversationID: 'k', replacementText: 'Kate Beaton', }, diff --git a/ts/components/CompositionInput.tsx b/ts/components/CompositionInput.tsx index 40caed661..03cec3fc7 100644 --- a/ts/components/CompositionInput.tsx +++ b/ts/components/CompositionInput.tsx @@ -26,7 +26,7 @@ import { BodyRange, collapseRangeTree, insertRange } from '../types/BodyRange'; import type { LocalizerType, ThemeType } from '../types/Util'; import type { ConversationType } from '../state/ducks/conversations'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; -import { isValidUuid } from '../types/UUID'; +import { isServiceIdString } from '../types/ServiceId'; import { MentionBlot } from '../quill/mentions/blot'; import { matchEmojiImage, @@ -46,6 +46,7 @@ import { import { SignalClipboard } from '../quill/signal-clipboard'; import { DirectionalBlot } from '../quill/block/blot'; import { getClassNamesFor } from '../util/getClassNamesFor'; +import { isNotNil } from '../util/isNotNil'; import * as log from '../logging/log'; import * as Errors from '../types/errors'; import { useRefMerger } from '../hooks/useRefMerger'; @@ -677,11 +678,15 @@ export function CompositionInput(props: Props): React.ReactElement { return; } - const currentMemberUuids = currentMembers + const currentMemberServiceIds = currentMembers .map(m => m.uuid) - .filter(isValidUuid); + .filter(isNotNil) + .filter(isServiceIdString); - const newDelta = getDeltaToRemoveStaleMentions(ops, currentMemberUuids); + const newDelta = getDeltaToRemoveStaleMentions( + ops, + currentMemberServiceIds + ); // eslint-disable-next-line @typescript-eslint/no-explicit-any quill.updateContents(newDelta as any); diff --git a/ts/components/ConversationList.stories.tsx b/ts/components/ConversationList.stories.tsx index c52db15d0..db84f0000 100644 --- a/ts/components/ConversationList.stories.tsx +++ b/ts/components/ConversationList.stories.tsx @@ -3,6 +3,7 @@ import React, { useContext } from 'react'; import { times, omit } from 'lodash'; +import { v4 as generateUuid } from 'uuid'; import { action } from '@storybook/addon-actions'; import { boolean, date, select, text } from '@storybook/addon-knobs'; @@ -18,8 +19,7 @@ import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; import { ThemeType } from '../types/Util'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; -import { UUID } from '../types/UUID'; -import { makeFakeLookupConversationWithoutUuid } from '../test-both/helpers/fakeLookupConversationWithoutUuid'; +import { makeFakeLookupConversationWithoutServiceId } from '../test-both/helpers/fakeLookupConversationWithoutServiceId'; const i18n = setupI18n('en', enMessages); @@ -95,7 +95,7 @@ function Wrapper({ /> )} scrollable={scrollable} - lookupConversationWithoutUuid={makeFakeLookupConversationWithoutUuid()} + lookupConversationWithoutServiceId={makeFakeLookupConversationWithoutServiceId()} showChooseGroupMembers={action('showChooseGroupMembers')} showUserNotFoundModal={action('showUserNotFoundModal')} setIsFetchingUUID={action('setIsFetchingUUID')} @@ -381,7 +381,7 @@ ConversationsMessageStatuses.story = { export const ConversationTypingStatus = (): JSX.Element => renderConversation({ - typingContactId: UUID.generate().toString(), + typingContactId: generateUuid(), }); ConversationTypingStatus.story = { diff --git a/ts/components/ConversationList.tsx b/ts/components/ConversationList.tsx index 25b1fc994..29e657548 100644 --- a/ts/components/ConversationList.tsx +++ b/ts/components/ConversationList.tsx @@ -14,7 +14,7 @@ import type { LocalizerType, ThemeType } from '../types/Util'; import { ScrollBehavior } from '../types/Util'; import { getNavSidebarWidthBreakpoint } from './_util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; -import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid'; +import type { LookupConversationWithoutServiceIdActionsType } from '../util/lookupConversationWithoutServiceId'; import type { ShowConversationType } from '../state/ducks/conversations'; import type { PropsData as ConversationListItemPropsType } from './conversationList/ConversationListItem'; @@ -189,7 +189,7 @@ export type PropsType = { renderMessageSearchResult?: (id: string) => JSX.Element; showChooseGroupMembers: () => void; showConversation: ShowConversationType; -} & LookupConversationWithoutUuidActionsType; +} & LookupConversationWithoutServiceIdActionsType; const NORMAL_ROW_HEIGHT = 76; const SELECT_ROW_HEIGHT = 52; @@ -214,7 +214,7 @@ export function ConversationList({ scrollable = true, shouldRecomputeRowHeights, showChooseGroupMembers, - lookupConversationWithoutUuid, + lookupConversationWithoutServiceId, showUserNotFoundModal, setIsFetchingUUID, showConversation, @@ -313,7 +313,9 @@ export function ConversationList({ result = ( @@ -330,7 +332,9 @@ export function ConversationList({ result = ( @@ -436,7 +440,9 @@ export function ConversationList({ i18n={i18n} phoneNumber={row.phoneNumber} isFetching={row.isFetching} - lookupConversationWithoutUuid={lookupConversationWithoutUuid} + lookupConversationWithoutServiceId={ + lookupConversationWithoutServiceId + } showUserNotFoundModal={showUserNotFoundModal} setIsFetchingUUID={setIsFetchingUUID} showConversation={showConversation} @@ -449,7 +455,9 @@ export function ConversationList({ i18n={i18n} username={row.username} isFetchingUsername={row.isFetchingUsername} - lookupConversationWithoutUuid={lookupConversationWithoutUuid} + lookupConversationWithoutServiceId={ + lookupConversationWithoutServiceId + } showUserNotFoundModal={showUserNotFoundModal} setIsFetchingUUID={setIsFetchingUUID} showConversation={showConversation} @@ -473,7 +481,7 @@ export function ConversationList({ getPreferredBadge, getRow, i18n, - lookupConversationWithoutUuid, + lookupConversationWithoutServiceId, onClickArchiveButton, onClickContactCheckbox, onOutgoingAudioCallInConversation, diff --git a/ts/components/ForwardMessagesModal.tsx b/ts/components/ForwardMessagesModal.tsx index bb3babb71..2675014b5 100644 --- a/ts/components/ForwardMessagesModal.tsx +++ b/ts/components/ForwardMessagesModal.tsx @@ -358,7 +358,9 @@ export function ForwardMessagesModal({ toggleSelectedConversation(conversationId); } }} - lookupConversationWithoutUuid={asyncShouldNeverBeCalled} + lookupConversationWithoutServiceId={ + asyncShouldNeverBeCalled + } showConversation={shouldNeverBeCalled} showUserNotFoundModal={shouldNeverBeCalled} setIsFetchingUUID={shouldNeverBeCalled} diff --git a/ts/components/GroupCallRemoteParticipant.stories.tsx b/ts/components/GroupCallRemoteParticipant.stories.tsx index 92bcf3735..7114f28d6 100644 --- a/ts/components/GroupCallRemoteParticipant.stories.tsx +++ b/ts/components/GroupCallRemoteParticipant.stories.tsx @@ -10,6 +10,7 @@ import { GroupCallRemoteParticipant } from './GroupCallRemoteParticipant'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { FRAME_BUFFER_SIZE } from '../calling/constants'; import { setupI18n } from '../util/setupI18n'; +import { generateAci } from '../types/ServiceId'; import enMessages from '../../_locales/en/messages.json'; const i18n = setupI18n('en', enMessages); @@ -60,7 +61,7 @@ const createProps = ( isBlocked: Boolean(isBlocked), title: 'Pablo Diego José Francisco de Paula Juan Nepomuceno María de los Remedios Cipriano de la Santísima Trinidad Ruiz y Picasso', - uuid: '992ed3b9-fc9b-47a9-bdb4-e0c7cbb0fda5', + uuid: generateAci(), }), }, remoteParticipantsCount: 1, diff --git a/ts/components/LeftPane.stories.tsx b/ts/components/LeftPane.stories.tsx index 38d1807be..4654be271 100644 --- a/ts/components/LeftPane.stories.tsx +++ b/ts/components/LeftPane.stories.tsx @@ -31,9 +31,9 @@ import { DialogType } from '../types/Dialogs'; import { SocketStatus } from '../types/SocketStatus'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; import { - makeFakeLookupConversationWithoutUuid, + makeFakeLookupConversationWithoutServiceId, useUuidFetchState, -} from '../test-both/helpers/fakeLookupConversationWithoutUuid'; +} from '../test-both/helpers/fakeLookupConversationWithoutServiceId'; import type { GroupListItemConversationType } from './conversationList/GroupListItem'; const i18n = setupI18n('en', enMessages); @@ -168,7 +168,8 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => { navTabsCollapsed: boolean('navTabsCollapsed', false), setChallengeStatus: action('setChallengeStatus'), - lookupConversationWithoutUuid: makeFakeLookupConversationWithoutUuid(), + lookupConversationWithoutServiceId: + makeFakeLookupConversationWithoutServiceId(), showUserNotFoundModal: action('showUserNotFoundModal'), setIsFetchingUUID, showConversation: action('showConversation'), diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index e1e6f03cf..69cc1fbfa 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -29,7 +29,7 @@ import type { DurationInSeconds } from '../util/durations'; import type { WidthBreakpoint } from './_util'; import { getNavSidebarWidthBreakpoint } from './_util'; import * as KeyboardLayout from '../services/keyboardLayout'; -import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid'; +import type { LookupConversationWithoutServiceIdActionsType } from '../util/lookupConversationWithoutServiceId'; import type { ShowConversationType } from '../state/ducks/conversations'; import type { PropsType as UnsupportedOSDialogPropsType } from '../state/smart/UnsupportedOSDialog'; @@ -152,7 +152,7 @@ export type PropsType = { renderCaptchaDialog: (props: { onSkip(): void }) => JSX.Element; renderCrashReportDialog: () => JSX.Element; renderExpiredBuildDialog: (_: DialogExpiredBuildPropsType) => JSX.Element; -} & LookupConversationWithoutUuidActionsType; +} & LookupConversationWithoutServiceIdActionsType; export function LeftPane({ blockConversation, @@ -173,7 +173,7 @@ export function LeftPane({ hasRelinkDialog, hasUpdateDialog, i18n, - lookupConversationWithoutUuid, + lookupConversationWithoutServiceId, isMacOS, isUpdateDownloaded, isContactManagementEnabled, @@ -657,8 +657,8 @@ export function LeftPane({ }} showUserNotFoundModal={showUserNotFoundModal} setIsFetchingUUID={setIsFetchingUUID} - lookupConversationWithoutUuid={ - lookupConversationWithoutUuid + lookupConversationWithoutServiceId={ + lookupConversationWithoutServiceId } showConversation={showConversation} blockConversation={blockConversation} diff --git a/ts/components/ProfileEditor.stories.tsx b/ts/components/ProfileEditor.stories.tsx index 7eace186b..32c388e2d 100644 --- a/ts/components/ProfileEditor.stories.tsx +++ b/ts/components/ProfileEditor.stories.tsx @@ -5,6 +5,7 @@ import type { Meta, Story } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React, { useState } from 'react'; import casual from 'casual'; +import { v4 as generateUuid } from 'uuid'; import type { PropsType } from './ProfileEditor'; import enMessages from '../../_locales/en/messages.json'; @@ -15,7 +16,6 @@ import { UsernameLinkState, UsernameReservationState, } from '../state/ducks/usernameEnums'; -import { UUID } from '../types/UUID'; import { getRandomColor } from '../test-both/helpers/getRandomColor'; import { setupI18n } from '../util/setupI18n'; @@ -35,7 +35,7 @@ export default { defaultValue: undefined, }, conversationId: { - defaultValue: UUID.generate().toString(), + defaultValue: generateUuid(), }, color: { defaultValue: getRandomColor(), diff --git a/ts/components/SafetyNumberChangeDialog.stories.tsx b/ts/components/SafetyNumberChangeDialog.stories.tsx index 36d84d927..b9f5a3ce3 100644 --- a/ts/components/SafetyNumberChangeDialog.stories.tsx +++ b/ts/components/SafetyNumberChangeDialog.stories.tsx @@ -11,6 +11,7 @@ import enMessages from '../../_locales/en/messages.json'; import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext'; import { getFakeBadge } from '../test-both/helpers/getFakeBadge'; import { MY_STORY_ID } from '../types/Stories'; +import { generateStoryDistributionId } from '../types/StoryDistributionId'; const i18n = setupI18n('en', enMessages); @@ -254,7 +255,7 @@ export function NoContacts(): JSX.Element { story: { name: 'Custom List A', conversationId: 'our-conversation-id', - distributionId: 'some-other-distribution-id', + distributionId: generateStoryDistributionId(), }, contacts: [], }, @@ -290,7 +291,7 @@ export function InMultipleStories(): JSX.Element { story: { name: 'Custom List A', conversationId: 'our-conversation-id', - distributionId: 'some-other-distribution-id', + distributionId: generateStoryDistributionId(), }, contacts: [ contactWithAllData, diff --git a/ts/components/SafetyNumberChangeDialog.tsx b/ts/components/SafetyNumberChangeDialog.tsx index 50713cc75..7df12edad 100644 --- a/ts/components/SafetyNumberChangeDialog.tsx +++ b/ts/components/SafetyNumberChangeDialog.tsx @@ -21,7 +21,8 @@ import { ContextMenu } from './ContextMenu'; import { Theme } from '../util/theme'; import { isNotNil } from '../util/isNotNil'; import { MY_STORY_ID } from '../types/Stories'; -import type { UUIDStringType } from '../types/UUID'; +import type { ServiceIdString } from '../types/ServiceId'; +import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import { UserText } from './UserText'; export enum SafetyNumberChangeSource { @@ -48,7 +49,7 @@ type StoryContacts = { // For My Story or custom distribution lists, conversationId will be our own conversationId: string; // For Group stories, distributionId will not be provided - distributionId?: string; + distributionId?: StoryDistributionIdString; }; contacts: Array; }; @@ -62,8 +63,8 @@ export type Props = Readonly<{ onCancel: () => void; onConfirm: () => void; removeFromStory?: ( - distributionId: string, - uuids: Array + distributionId: StoryDistributionIdString, + serviceIds: Array ) => unknown; renderSafetyNumber: (props: SafetyNumberProps) => JSX.Element; theme: ThemeType; @@ -275,8 +276,8 @@ function ContactSection({ getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; removeFromStory?: ( - distributionId: string, - uuids: Array + distributionId: StoryDistributionIdString, + serviceIds: Array ) => unknown; setSelectedContact: (contact: ConversationType) => void; theme: ThemeType; @@ -431,12 +432,12 @@ function ContactRow({ theme, }: Readonly<{ contact: ConversationType; - distributionId?: string; + distributionId?: StoryDistributionIdString; getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; removeFromStory?: ( - distributionId: string, - uuids: Array + distributionId: StoryDistributionIdString, + serviceIds: Array ) => unknown; setSelectedContact: (contact: ConversationType) => void; shouldShowNumber: boolean; diff --git a/ts/components/SendStoryModal.tsx b/ts/components/SendStoryModal.tsx index bc32b6499..73a12eb16 100644 --- a/ts/components/SendStoryModal.tsx +++ b/ts/components/SendStoryModal.tsx @@ -21,7 +21,8 @@ import { Page as StoriesSettingsPage, } from './StoriesSettingsModal'; import type { StoryDistributionListWithMembersDataType } from '../types/Stories'; -import type { UUIDStringType } from '../types/UUID'; +import type { StoryDistributionIdString } from '../types/StoryDistributionId'; +import type { ServiceIdString } from '../types/ServiceId'; import { Alert } from './Alert'; import { Avatar, AvatarSize } from './Avatar'; import { Button, ButtonSize, ButtonVariant } from './Button'; @@ -54,18 +55,18 @@ export type PropsType = { i18n: LocalizerType; me: ConversationType; onClose: () => unknown; - onDeleteList: (listId: string) => unknown; + onDeleteList: (listId: StoryDistributionIdString) => unknown; onDistributionListCreated: ( name: string, - viewerUuids: Array - ) => Promise; + viewerUuids: Array + ) => Promise; onSelectedStoryList: (options: { conversationId: string; - distributionId: string | undefined; - uuids: Array; + distributionId: StoryDistributionIdString | undefined; + serviceIds: Array; }) => unknown; onSend: ( - listIds: Array, + listIds: Array, conversationIds: Array ) => unknown; signalConnections: Array; @@ -99,21 +100,23 @@ const Page = { type PageType = SendStoryPage | StoriesSettingsPage; -function getListMemberUuids( +function getListMemberServiceIds( list: StoryDistributionListWithMembersDataType, signalConnections: Array -): Array { - const memberUuids = list.members.map(({ uuid }) => uuid).filter(isNotNil); +): Array { + const memberServiceIds = list.members + .map(({ uuid }) => uuid) + .filter(isNotNil); if (list.id === MY_STORY_ID && list.isBlockList) { - const excludeUuids = new Set(memberUuids); + const excludeUuids = new Set(memberServiceIds); return signalConnections .map(conversation => conversation.uuid) .filter(isNotNil) .filter(uuid => !excludeUuids.has(uuid)); } - return memberUuids; + return memberServiceIds; } export function SendStoryModal({ @@ -147,9 +150,9 @@ export function SendStoryModal({ const [confirmDiscardModal, confirmDiscardIf] = useConfirmDiscard(i18n); - const [selectedListIds, setSelectedListIds] = useState>( - new Set() - ); + const [selectedListIds, setSelectedListIds] = useState< + Set + >(new Set()); const [selectedGroupIds, setSelectedGroupIds] = useState>( new Set() ); @@ -215,7 +218,7 @@ export function SendStoryModal({ string | undefined >(); const [confirmDeleteList, setConfirmDeleteList] = useState< - { id: string; name: string } | undefined + { id: StoryDistributionIdString; name: string } | undefined >(); const [listIdToEdit, setListIdToEdit] = useState(); @@ -263,7 +266,7 @@ export function SendStoryModal({ selectedNames = chosenGroupNames.join(', '); } else { selectedNames = selectedStoryNames - .map(listName => getStoryDistributionListName(i18n, listName, listName)) + .map(listName => getStoryDistributionListName(i18n, undefined, listName)) .join(', '); } @@ -661,7 +664,7 @@ export function SendStoryModal({ onSelectedStoryList({ conversationId: ourConversationId, distributionId: list.id, - uuids: getListMemberUuids(list, signalConnections), + serviceIds: getListMemberServiceIds(list, signalConnections), }); } }} @@ -792,7 +795,7 @@ export function SendStoryModal({ onSelectedStoryList({ conversationId: group.id, distributionId: undefined, - uuids: group.memberships.map(({ uuid }) => uuid), + serviceIds: group.memberships.map(({ uuid }) => uuid), }); } }} diff --git a/ts/components/StoriesSettingsModal.stories.tsx b/ts/components/StoriesSettingsModal.stories.tsx index ee7dd0cb5..1d8cf3310 100644 --- a/ts/components/StoriesSettingsModal.stories.tsx +++ b/ts/components/StoriesSettingsModal.stories.tsx @@ -115,7 +115,9 @@ export const SingleList = Template.bind({}); }, { ...fakeDistroList, - members: fakeDistroList.memberUuids.map(() => getDefaultConversation()), + members: fakeDistroList.memberServiceIds.map(() => + getDefaultConversation() + ), }, ], }; diff --git a/ts/components/StoriesSettingsModal.tsx b/ts/components/StoriesSettingsModal.tsx index e84afa4a6..b57229569 100644 --- a/ts/components/StoriesSettingsModal.tsx +++ b/ts/components/StoriesSettingsModal.tsx @@ -11,7 +11,8 @@ import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { Row } from './ConversationList'; import type { StoryDistributionListWithMembersDataType } from '../types/Stories'; -import type { UUIDStringType } from '../types/UUID'; +import type { StoryDistributionIdString } from '../types/StoryDistributionId'; +import type { ServiceIdString } from '../types/ServiceId'; import type { RenderModalPage, ModalPropsType } from './Modal'; import { Avatar, AvatarSize } from './Avatar'; import { Button, ButtonVariant } from './Button'; @@ -28,7 +29,6 @@ import { SearchInput } from './SearchInput'; import { StoryDistributionListName } from './StoryDistributionListName'; import { Theme } from '../util/theme'; import { ThemeType } from '../types/Util'; -import { UUID } from '../types/UUID'; import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations'; import { isNotNil } from '../util/isNotNil'; import { @@ -54,23 +54,25 @@ export type PropsType = { toggleGroupsForStorySend: (groupIds: Array) => unknown; onDistributionListCreated: ( name: string, - viewerUuids: Array + viewerUuids: Array ) => Promise; - onHideMyStoriesFrom: (viewerUuids: Array) => unknown; - onRemoveMembers: (listId: string, uuids: Array) => unknown; + onHideMyStoriesFrom: (viewerUuids: Array) => unknown; + onRemoveMembers: (listId: string, uuids: Array) => unknown; onRepliesNReactionsChanged: ( listId: string, allowsReplies: boolean ) => unknown; onViewersUpdated: ( listId: string, - viewerUuids: Array + viewerUuids: Array ) => unknown; setMyStoriesToAllSignalConnections: () => unknown; storyViewReceiptsEnabled: boolean; toggleSignalConnectionsModal: () => unknown; setStoriesDisabled: (value: boolean) => void; - getConversationByUuid: (uuid: UUIDStringType) => ConversationType | undefined; + getConversationByUuid: ( + uuid: ServiceIdString + ) => ConversationType | undefined; }; export enum Page { @@ -134,7 +136,7 @@ type DistributionListItemProps = { distributionList: StoryDistributionListWithMembersDataType; me: ConversationType; signalConnections: Array; - onSelectItemToEdit(id: UUIDStringType): void; + onSelectItemToEdit(id: StoryDistributionIdString): void; }; function DistributionListItem({ @@ -543,7 +545,10 @@ type DistributionListSettingsModalPropsType = { i18n: LocalizerType; listToEdit: StoryDistributionListWithMembersDataType; signalConnectionsCount: number; - setConfirmDeleteList: (_: { id: string; name: string }) => unknown; + setConfirmDeleteList: (_: { + id: StoryDistributionIdString; + name: string; + }) => unknown; setPage: (page: Page) => unknown; setSelectedContacts: (contacts: Array) => unknown; onBackButtonClick: (() => void) | undefined; @@ -577,7 +582,7 @@ export function DistributionListSettingsModal({ | { listId: string; title: string; - uuid: UUIDStringType; + uuid: ServiceIdString; } >(); @@ -945,8 +950,8 @@ export function EditMyStoryPrivacy({ } type EditDistributionListModalPropsType = { - onCreateList: (name: string, viewerUuids: Array) => unknown; - onViewersUpdated: (viewerUuids: Array) => unknown; + onCreateList: (name: string, viewerUuids: Array) => unknown; + onViewersUpdated: (viewerUuids: Array) => unknown; page: | Page.AddViewer | Page.ChooseViewers @@ -998,7 +1003,7 @@ export function EditDistributionListModal({ return map; }, [candidateConversations]); - const selectedConversationUuids: Set = useMemo( + const selectConversationServiceIds: Set = useMemo( () => new Set(selectedContacts.map(contact => contact.uuid).filter(isNotNil)), [selectedContacts] @@ -1034,7 +1039,7 @@ export function EditDistributionListModal({