Introduce Service Id Types
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
414c0a58d3
commit
366b875fd2
269 changed files with 5832 additions and 5550 deletions
|
@ -38,7 +38,8 @@ import {
|
|||
generatePreKey,
|
||||
generateKyberPreKey,
|
||||
} from '../Curve';
|
||||
import { UUID, UUIDKind } from '../types/UUID';
|
||||
import type { ServiceIdString, PniString } from '../types/ServiceId';
|
||||
import { ServiceIdKind, normalizeAci, normalizePni } from '../types/ServiceId';
|
||||
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
||||
import { ourProfileKeyService } from '../services/ourProfileKey';
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
|
@ -49,8 +50,8 @@ import { SignalService as Proto } from '../protobuf';
|
|||
import * as log from '../logging/log';
|
||||
import type { StorageAccessType } from '../types/Storage';
|
||||
|
||||
type StorageKeyByUuidKind = {
|
||||
[kind in UUIDKind]: keyof StorageAccessType;
|
||||
type StorageKeyByServiceIdKind = {
|
||||
[kind in ServiceIdKind]: keyof StorageAccessType;
|
||||
};
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
|
@ -59,44 +60,44 @@ const STARTING_KEY_ID = 1;
|
|||
const PROFILE_KEY_LENGTH = 32;
|
||||
const KEY_TOO_OLD_THRESHOLD = 14 * DAY;
|
||||
|
||||
export const KYBER_KEY_ID_KEY: StorageKeyByUuidKind = {
|
||||
[UUIDKind.ACI]: 'maxKyberPreKeyId',
|
||||
[UUIDKind.Unknown]: 'maxKyberPreKeyId',
|
||||
[UUIDKind.PNI]: 'maxKyberPreKeyIdPNI',
|
||||
export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'maxKyberPreKeyId',
|
||||
[ServiceIdKind.Unknown]: 'maxKyberPreKeyId',
|
||||
[ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI',
|
||||
};
|
||||
|
||||
const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY;
|
||||
const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5;
|
||||
const LAST_RESORT_KEY_MINIMUM = 5;
|
||||
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByUuidKind = {
|
||||
[UUIDKind.ACI]: 'lastResortKeyUpdateTime',
|
||||
[UUIDKind.Unknown]: 'lastResortKeyUpdateTime',
|
||||
[UUIDKind.PNI]: 'lastResortKeyUpdateTimePNI',
|
||||
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'lastResortKeyUpdateTime',
|
||||
[ServiceIdKind.Unknown]: 'lastResortKeyUpdateTime',
|
||||
[ServiceIdKind.PNI]: 'lastResortKeyUpdateTimePNI',
|
||||
};
|
||||
|
||||
const PRE_KEY_ARCHIVE_AGE = 90 * DAY;
|
||||
const PRE_KEY_GEN_BATCH_SIZE = 100;
|
||||
const PRE_KEY_MAX_COUNT = 200;
|
||||
const PRE_KEY_ID_KEY: StorageKeyByUuidKind = {
|
||||
[UUIDKind.ACI]: 'maxPreKeyId',
|
||||
[UUIDKind.Unknown]: 'maxPreKeyId',
|
||||
[UUIDKind.PNI]: 'maxPreKeyIdPNI',
|
||||
const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'maxPreKeyId',
|
||||
[ServiceIdKind.Unknown]: 'maxPreKeyId',
|
||||
[ServiceIdKind.PNI]: 'maxPreKeyIdPNI',
|
||||
};
|
||||
const PRE_KEY_MINIMUM = 10;
|
||||
|
||||
const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY;
|
||||
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByUuidKind = {
|
||||
[UUIDKind.ACI]: 'signedKeyId',
|
||||
[UUIDKind.Unknown]: 'signedKeyId',
|
||||
[UUIDKind.PNI]: 'signedKeyIdPNI',
|
||||
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'signedKeyId',
|
||||
[ServiceIdKind.Unknown]: 'signedKeyId',
|
||||
[ServiceIdKind.PNI]: 'signedKeyIdPNI',
|
||||
};
|
||||
|
||||
const SIGNED_PRE_KEY_ROTATION_AGE = DAY * 1.5;
|
||||
const SIGNED_PRE_KEY_MINIMUM = 5;
|
||||
const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByUuidKind = {
|
||||
[UUIDKind.ACI]: 'signedKeyUpdateTime',
|
||||
[UUIDKind.Unknown]: 'signedKeyUpdateTime',
|
||||
[UUIDKind.PNI]: 'signedKeyUpdateTimePNI',
|
||||
const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'signedKeyUpdateTime',
|
||||
[ServiceIdKind.Unknown]: 'signedKeyUpdateTime',
|
||||
[ServiceIdKind.PNI]: 'signedKeyUpdateTimePNI',
|
||||
};
|
||||
|
||||
type CreateAccountOptionsType = Readonly<{
|
||||
|
@ -111,7 +112,10 @@ type CreateAccountOptionsType = Readonly<{
|
|||
accessKey?: Uint8Array;
|
||||
}>;
|
||||
|
||||
function getNextKeyId(kind: UUIDKind, keys: StorageKeyByUuidKind): number {
|
||||
function getNextKeyId(
|
||||
kind: ServiceIdKind,
|
||||
keys: StorageKeyByServiceIdKind
|
||||
): number {
|
||||
const id = window.storage.get(keys[kind]);
|
||||
|
||||
if (isNumber(id)) {
|
||||
|
@ -119,8 +123,8 @@ function getNextKeyId(kind: UUIDKind, keys: StorageKeyByUuidKind): number {
|
|||
}
|
||||
|
||||
// For PNI ids, start with existing ACI id
|
||||
if (kind === UUIDKind.PNI) {
|
||||
return window.storage.get(keys[UUIDKind.ACI], STARTING_KEY_ID);
|
||||
if (kind === ServiceIdKind.PNI) {
|
||||
return window.storage.get(keys[ServiceIdKind.ACI], STARTING_KEY_ID);
|
||||
}
|
||||
|
||||
return STARTING_KEY_ID;
|
||||
|
@ -168,9 +172,9 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
async decryptDeviceName(base64: string): Promise<string> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const identityKey =
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair(ourAci);
|
||||
if (!identityKey) {
|
||||
throw new Error('decryptDeviceName: No identity key pair!');
|
||||
}
|
||||
|
@ -196,7 +200,7 @@ export default class AccountManager extends EventTarget {
|
|||
const { storage } = window.textsecure;
|
||||
const deviceName = storage.user.getDeviceName();
|
||||
const identityKeyPair = storage.protocol.getIdentityKeyPair(
|
||||
storage.user.getCheckedUuid()
|
||||
storage.user.getCheckedAci()
|
||||
);
|
||||
strictAssert(
|
||||
identityKeyPair !== undefined,
|
||||
|
@ -234,14 +238,14 @@ export default class AccountManager extends EventTarget {
|
|||
accessKey,
|
||||
});
|
||||
|
||||
const uploadKeys = async (kind: UUIDKind) => {
|
||||
const uploadKeys = async (kind: ServiceIdKind) => {
|
||||
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
|
||||
await this.server.registerKeys(keys, kind);
|
||||
await this._confirmKeys(keys, kind);
|
||||
};
|
||||
|
||||
await uploadKeys(UUIDKind.ACI);
|
||||
await uploadKeys(UUIDKind.PNI);
|
||||
await uploadKeys(ServiceIdKind.ACI);
|
||||
await uploadKeys(ServiceIdKind.PNI);
|
||||
} finally {
|
||||
this.server.finishRegistration(registrationBaton);
|
||||
}
|
||||
|
@ -344,14 +348,14 @@ export default class AccountManager extends EventTarget {
|
|||
readReceipts: provisionMessage.readReceipts,
|
||||
});
|
||||
|
||||
const uploadKeys = async (kind: UUIDKind) => {
|
||||
const uploadKeys = async (kind: ServiceIdKind) => {
|
||||
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
|
||||
|
||||
try {
|
||||
await this.server.registerKeys(keys, kind);
|
||||
await this._confirmKeys(keys, kind);
|
||||
} catch (error) {
|
||||
if (kind === UUIDKind.PNI) {
|
||||
if (kind === ServiceIdKind.PNI) {
|
||||
log.error(
|
||||
'Failed to upload PNI prekeys. Moving on',
|
||||
Errors.toLogFormat(error)
|
||||
|
@ -363,9 +367,9 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
};
|
||||
|
||||
await uploadKeys(UUIDKind.ACI);
|
||||
await uploadKeys(ServiceIdKind.ACI);
|
||||
if (provisionMessage.pniKeyPair) {
|
||||
await uploadKeys(UUIDKind.PNI);
|
||||
await uploadKeys(ServiceIdKind.PNI);
|
||||
}
|
||||
} finally {
|
||||
this.server.finishRegistration(registrationBaton);
|
||||
|
@ -375,12 +379,12 @@ export default class AccountManager extends EventTarget {
|
|||
});
|
||||
}
|
||||
|
||||
private getIdentityKeyOrThrow(ourUuid: UUID): KeyPairType {
|
||||
private getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType {
|
||||
const { storage } = window.textsecure;
|
||||
const store = storage.protocol;
|
||||
let identityKey: KeyPairType | undefined;
|
||||
try {
|
||||
identityKey = store.getIdentityKeyPair(ourUuid);
|
||||
identityKey = store.getIdentityKeyPair(ourServiceId);
|
||||
} catch (error) {
|
||||
const errorText = Errors.toLogFormat(error);
|
||||
throw new Error(
|
||||
|
@ -396,20 +400,20 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
private async generateNewPreKeys(
|
||||
uuidKind: UUIDKind,
|
||||
serviceIdKind: ServiceIdKind,
|
||||
count: number
|
||||
): Promise<Array<UploadPreKeyType>> {
|
||||
const logId = `AccountManager.generateNewPreKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.generateNewPreKeys(${serviceIdKind})`;
|
||||
const { storage } = window.textsecure;
|
||||
const store = storage.protocol;
|
||||
|
||||
const startId = getNextKeyId(uuidKind, PRE_KEY_ID_KEY);
|
||||
const startId = getNextKeyId(serviceIdKind, PRE_KEY_ID_KEY);
|
||||
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
|
||||
|
||||
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
||||
if (typeof startId !== 'number') {
|
||||
throw new Error(
|
||||
`${logId}: Invalid ${PRE_KEY_ID_KEY[uuidKind]} in storage`
|
||||
`${logId}: Invalid ${PRE_KEY_ID_KEY[serviceIdKind]} in storage`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -419,8 +423,8 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
await Promise.all([
|
||||
store.storePreKeys(ourUuid, toSave),
|
||||
storage.put(PRE_KEY_ID_KEY[uuidKind], startId + count),
|
||||
store.storePreKeys(ourServiceId, toSave),
|
||||
storage.put(PRE_KEY_ID_KEY[serviceIdKind], startId + count),
|
||||
]);
|
||||
|
||||
return toSave.map(key => ({
|
||||
|
@ -430,24 +434,24 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
private async generateNewKyberPreKeys(
|
||||
uuidKind: UUIDKind,
|
||||
serviceIdKind: ServiceIdKind,
|
||||
count: number
|
||||
): Promise<Array<UploadKyberPreKeyType>> {
|
||||
const logId = `AccountManager.generateNewKyberPreKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.generateNewKyberPreKeys(${serviceIdKind})`;
|
||||
const { storage } = window.textsecure;
|
||||
const store = storage.protocol;
|
||||
|
||||
const startId = getNextKeyId(uuidKind, KYBER_KEY_ID_KEY);
|
||||
const startId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
|
||||
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
|
||||
|
||||
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
||||
if (typeof startId !== 'number') {
|
||||
throw new Error(
|
||||
`${logId}: Invalid ${KYBER_KEY_ID_KEY[uuidKind]} in storage`
|
||||
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
|
||||
);
|
||||
}
|
||||
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
||||
|
||||
const toSave: Array<Omit<KyberPreKeyType, 'id'>> = [];
|
||||
const toUpload: Array<UploadKyberPreKeyType> = [];
|
||||
|
@ -460,7 +464,7 @@ export default class AccountManager extends EventTarget {
|
|||
isConfirmed: false,
|
||||
isLastResort: false,
|
||||
keyId,
|
||||
ourUuid: ourUuid.toString(),
|
||||
ourUuid: ourServiceId,
|
||||
});
|
||||
toUpload.push({
|
||||
keyId,
|
||||
|
@ -470,18 +474,18 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
await Promise.all([
|
||||
store.storeKyberPreKeys(ourUuid, toSave),
|
||||
storage.put(KYBER_KEY_ID_KEY[uuidKind], startId + count),
|
||||
store.storeKyberPreKeys(ourServiceId, toSave),
|
||||
storage.put(KYBER_KEY_ID_KEY[serviceIdKind], startId + count),
|
||||
]);
|
||||
|
||||
return toUpload;
|
||||
}
|
||||
|
||||
async maybeUpdateKeys(uuidKind: UUIDKind): Promise<void> {
|
||||
const logId = `maybeUpdateKeys(${uuidKind})`;
|
||||
async maybeUpdateKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
||||
const logId = `maybeUpdateKeys(${serviceIdKind})`;
|
||||
await this.queueTask(async () => {
|
||||
const { count: preKeyCount, pqCount: kyberPreKeyCount } =
|
||||
await this.server.getMyKeyCounts(uuidKind);
|
||||
await this.server.getMyKeyCounts(serviceIdKind);
|
||||
|
||||
let preKeys: Array<UploadPreKeyType> | undefined;
|
||||
if (preKeyCount < PRE_KEY_MINIMUM) {
|
||||
|
@ -489,7 +493,7 @@ export default class AccountManager extends EventTarget {
|
|||
`${logId}: Server prekey count is ${preKeyCount}, generating a new set`
|
||||
);
|
||||
preKeys = await this.generateNewPreKeys(
|
||||
uuidKind,
|
||||
serviceIdKind,
|
||||
PRE_KEY_GEN_BATCH_SIZE
|
||||
);
|
||||
}
|
||||
|
@ -500,15 +504,15 @@ export default class AccountManager extends EventTarget {
|
|||
`${logId}: Server kyber prekey count is ${kyberPreKeyCount}, generating a new set`
|
||||
);
|
||||
pqPreKeys = await this.generateNewKyberPreKeys(
|
||||
uuidKind,
|
||||
serviceIdKind,
|
||||
PRE_KEY_GEN_BATCH_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
|
||||
uuidKind
|
||||
serviceIdKind
|
||||
);
|
||||
const signedPreKey = await this.maybeUpdateSignedPreKey(uuidKind);
|
||||
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
|
||||
|
||||
if (
|
||||
!preKeys?.length &&
|
||||
|
@ -536,8 +540,8 @@ export default class AccountManager extends EventTarget {
|
|||
log.info(`${logId}: Uploading with ${keySummary.join(', ')}`);
|
||||
|
||||
const { storage } = window.textsecure;
|
||||
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
|
||||
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
||||
const toUpload = {
|
||||
identityKey: identityKey.pubKey,
|
||||
preKeys,
|
||||
|
@ -547,37 +551,37 @@ export default class AccountManager extends EventTarget {
|
|||
};
|
||||
|
||||
try {
|
||||
await this.server.registerKeys(toUpload, uuidKind);
|
||||
await this.server.registerKeys(toUpload, serviceIdKind);
|
||||
} catch (error) {
|
||||
log.error(`${logId} upload error:`, Errors.toLogFormat(error));
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this._confirmKeys(toUpload, uuidKind);
|
||||
await this._confirmKeys(toUpload, serviceIdKind);
|
||||
|
||||
const { count: updatedPreKeyCount, pqCount: updatedKyberPreKeyCount } =
|
||||
await this.server.getMyKeyCounts(uuidKind);
|
||||
await this.server.getMyKeyCounts(serviceIdKind);
|
||||
log.info(
|
||||
`${logId}: Successfully updated; ` +
|
||||
`server prekey count: ${updatedPreKeyCount}, ` +
|
||||
`server kyber prekey count: ${updatedKyberPreKeyCount}`
|
||||
);
|
||||
|
||||
await this._cleanSignedPreKeys(uuidKind);
|
||||
await this._cleanLastResortKeys(uuidKind);
|
||||
await this._cleanPreKeys(uuidKind);
|
||||
await this._cleanKyberPreKeys(uuidKind);
|
||||
await this._cleanSignedPreKeys(serviceIdKind);
|
||||
await this._cleanLastResortKeys(serviceIdKind);
|
||||
await this._cleanPreKeys(serviceIdKind);
|
||||
await this._cleanKyberPreKeys(serviceIdKind);
|
||||
});
|
||||
}
|
||||
|
||||
areKeysOutOfDate(uuidKind: UUIDKind): boolean {
|
||||
areKeysOutOfDate(serviceIdKind: ServiceIdKind): boolean {
|
||||
const signedPreKeyTime = window.storage.get(
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
0
|
||||
);
|
||||
const lastResortKeyTime = window.storage.get(
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
0
|
||||
);
|
||||
|
||||
|
@ -592,20 +596,21 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
private async maybeUpdateSignedPreKey(
|
||||
uuidKind: UUIDKind
|
||||
serviceIdKind: ServiceIdKind
|
||||
): Promise<UploadSignedPreKeyType | undefined> {
|
||||
const logId = `AccountManager.maybeUpdateSignedPreKey(${uuidKind})`;
|
||||
const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind})`;
|
||||
const store = window.textsecure.storage.protocol;
|
||||
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
const signedKeyId = getNextKeyId(uuidKind, SIGNED_PRE_KEY_ID_KEY);
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const signedKeyId = getNextKeyId(serviceIdKind, SIGNED_PRE_KEY_ID_KEY);
|
||||
if (typeof signedKeyId !== 'number') {
|
||||
throw new Error(
|
||||
`${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[uuidKind]} in storage`
|
||||
`${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[serviceIdKind]} in storage`
|
||||
);
|
||||
}
|
||||
|
||||
const keys = await store.loadSignedPreKeys(ourUuid);
|
||||
const keys = await store.loadSignedPreKeys(ourServiceId);
|
||||
const sortedKeys = orderBy(keys, ['created_at'], ['desc']);
|
||||
const confirmedKeys = sortedKeys.filter(key => key.confirmed);
|
||||
const mostRecent = confirmedKeys[0];
|
||||
|
@ -617,29 +622,29 @@ export default class AccountManager extends EventTarget {
|
|||
`most recent was created ${lastUpdate}. No need to update.`
|
||||
);
|
||||
const existing = window.storage.get(
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind]
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind]
|
||||
);
|
||||
if (lastUpdate && !existing) {
|
||||
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
|
||||
await window.storage.put(
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
lastUpdate
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
||||
|
||||
const key = await generateSignedPreKey(identityKey, signedKeyId);
|
||||
log.info(`${logId}: Saving new signed prekey`, key.keyId);
|
||||
|
||||
await Promise.all([
|
||||
window.textsecure.storage.put(
|
||||
SIGNED_PRE_KEY_ID_KEY[uuidKind],
|
||||
SIGNED_PRE_KEY_ID_KEY[serviceIdKind],
|
||||
signedKeyId + 1
|
||||
),
|
||||
store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair),
|
||||
store.storeSignedPreKey(ourServiceId, key.keyId, key.keyPair),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -650,20 +655,21 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
private async maybeUpdateLastResortKyberKey(
|
||||
uuidKind: UUIDKind
|
||||
serviceIdKind: ServiceIdKind
|
||||
): Promise<UploadSignedPreKeyType | undefined> {
|
||||
const logId = `maybeUpdateLastResortKyberKey(${uuidKind})`;
|
||||
const logId = `maybeUpdateLastResortKyberKey(${serviceIdKind})`;
|
||||
const store = window.textsecure.storage.protocol;
|
||||
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
const kyberKeyId = getNextKeyId(uuidKind, KYBER_KEY_ID_KEY);
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const kyberKeyId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
|
||||
if (typeof kyberKeyId !== 'number') {
|
||||
throw new Error(
|
||||
`${logId}: Invalid ${KYBER_KEY_ID_KEY[uuidKind]} in storage`
|
||||
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
|
||||
);
|
||||
}
|
||||
|
||||
const keys = store.loadKyberPreKeys(ourUuid, { isLastResort: true });
|
||||
const keys = store.loadKyberPreKeys(ourServiceId, { isLastResort: true });
|
||||
const sortedKeys = orderBy(keys, ['createdAt'], ['desc']);
|
||||
const confirmedKeys = sortedKeys.filter(key => key.isConfirmed);
|
||||
const mostRecent = confirmedKeys[0];
|
||||
|
@ -675,19 +681,19 @@ export default class AccountManager extends EventTarget {
|
|||
`most recent was created ${lastUpdate}. No need to update.`
|
||||
);
|
||||
const existing = window.storage.get(
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind]
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind]
|
||||
);
|
||||
if (lastUpdate && !existing) {
|
||||
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
|
||||
await window.storage.put(
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
lastUpdate
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
|
||||
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
||||
|
||||
const keyId = kyberKeyId;
|
||||
const record = await generateKyberPreKey(identityKey, keyId);
|
||||
|
@ -698,12 +704,15 @@ export default class AccountManager extends EventTarget {
|
|||
isConfirmed: false,
|
||||
isLastResort: true,
|
||||
keyId,
|
||||
ourUuid: ourUuid.toString(),
|
||||
ourUuid: ourServiceId,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
window.textsecure.storage.put(KYBER_KEY_ID_KEY[uuidKind], kyberKeyId + 1),
|
||||
store.storeKyberPreKeys(ourUuid, [key]),
|
||||
window.textsecure.storage.put(
|
||||
KYBER_KEY_ID_KEY[serviceIdKind],
|
||||
kyberKeyId + 1
|
||||
),
|
||||
store.storeKyberPreKeys(ourServiceId, [key]),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
@ -714,12 +723,13 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
// Exposed only for tests
|
||||
async _cleanSignedPreKeys(uuidKind: UUIDKind): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
async _cleanSignedPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const store = window.textsecure.storage.protocol;
|
||||
const logId = `AccountManager.cleanSignedPreKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.cleanSignedPreKeys(${serviceIdKind})`;
|
||||
|
||||
const allKeys = store.loadSignedPreKeys(ourUuid);
|
||||
const allKeys = store.loadSignedPreKeys(ourServiceId);
|
||||
const sortedKeys = orderBy(allKeys, ['created_at'], ['desc']);
|
||||
const confirmed = sortedKeys.filter(key => key.confirmed);
|
||||
const unconfirmed = sortedKeys.filter(key => !key.confirmed);
|
||||
|
@ -761,17 +771,20 @@ export default class AccountManager extends EventTarget {
|
|||
});
|
||||
if (toDelete.length > 0) {
|
||||
log.info(`${logId}: Removing ${toDelete.length} signed prekeys`);
|
||||
await store.removeSignedPreKeys(ourUuid, toDelete);
|
||||
await store.removeSignedPreKeys(ourServiceId, toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
// Exposed only for tests
|
||||
async _cleanLastResortKeys(uuidKind: UUIDKind): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
async _cleanLastResortKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const store = window.textsecure.storage.protocol;
|
||||
const logId = `AccountManager.cleanLastResortKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.cleanLastResortKeys(${serviceIdKind})`;
|
||||
|
||||
const allKeys = store.loadKyberPreKeys(ourUuid, { isLastResort: true });
|
||||
const allKeys = store.loadKyberPreKeys(ourServiceId, {
|
||||
isLastResort: true,
|
||||
});
|
||||
const sortedKeys = orderBy(allKeys, ['createdAt'], ['desc']);
|
||||
const confirmed = sortedKeys.filter(key => key.isConfirmed);
|
||||
const unconfirmed = sortedKeys.filter(key => !key.isConfirmed);
|
||||
|
@ -815,16 +828,17 @@ export default class AccountManager extends EventTarget {
|
|||
});
|
||||
if (toDelete.length > 0) {
|
||||
log.info(`${logId}: Removing ${toDelete.length} last resort keys`);
|
||||
await store.removeKyberPreKeys(ourUuid, toDelete);
|
||||
await store.removeKyberPreKeys(ourServiceId, toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
async _cleanPreKeys(uuidKind: UUIDKind): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
async _cleanPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const store = window.textsecure.storage.protocol;
|
||||
const logId = `AccountManager.cleanPreKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.cleanPreKeys(${serviceIdKind})`;
|
||||
|
||||
const preKeys = store.loadPreKeys(ourUuid);
|
||||
const preKeys = store.loadPreKeys(ourServiceId);
|
||||
const toDelete: Array<number> = [];
|
||||
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
|
||||
|
||||
|
@ -842,16 +856,19 @@ export default class AccountManager extends EventTarget {
|
|||
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
|
||||
if (toDelete.length > 0) {
|
||||
log.info(`${logId}: Removing ${toDelete.length} obsolete prekeys`);
|
||||
await store.removePreKeys(ourUuid, toDelete);
|
||||
await store.removePreKeys(ourServiceId, toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
async _cleanKyberPreKeys(uuidKind: UUIDKind): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||
async _cleanKyberPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
||||
const ourServiceId =
|
||||
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
||||
const store = window.textsecure.storage.protocol;
|
||||
const logId = `AccountManager.cleanKyberPreKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.cleanKyberPreKeys(${serviceIdKind})`;
|
||||
|
||||
const preKeys = store.loadKyberPreKeys(ourUuid, { isLastResort: false });
|
||||
const preKeys = store.loadKyberPreKeys(ourServiceId, {
|
||||
isLastResort: false,
|
||||
});
|
||||
const toDelete: Array<number> = [];
|
||||
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
|
||||
|
||||
|
@ -869,7 +886,7 @@ export default class AccountManager extends EventTarget {
|
|||
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
|
||||
if (toDelete.length > 0) {
|
||||
log.info(`${logId}: Removing ${toDelete.length} kyber keys`);
|
||||
await store.removeKyberPreKeys(ourUuid, toDelete);
|
||||
await store.removeKyberPreKeys(ourServiceId, toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -891,8 +908,8 @@ export default class AccountManager extends EventTarget {
|
|||
const pniRegistrationId = generateRegistrationId();
|
||||
|
||||
const previousNumber = storage.user.getNumber();
|
||||
const previousACI = storage.user.getUuid(UUIDKind.ACI)?.toString();
|
||||
const previousPNI = storage.user.getUuid(UUIDKind.PNI)?.toString();
|
||||
const previousACI = storage.user.getAci();
|
||||
const previousPNI = storage.user.getPni();
|
||||
|
||||
let encryptedDeviceName;
|
||||
if (deviceName) {
|
||||
|
@ -916,10 +933,10 @@ export default class AccountManager extends EventTarget {
|
|||
accessKey,
|
||||
});
|
||||
|
||||
const ourUuid = UUID.cast(response.uuid);
|
||||
const ourPni = UUID.cast(response.pni);
|
||||
const ourAci = normalizeAci(response.uuid, 'createAccount');
|
||||
const ourPni = normalizePni(response.pni, 'createAccount');
|
||||
|
||||
const uuidChanged = previousACI && ourUuid && previousACI !== ourUuid;
|
||||
const uuidChanged = previousACI && ourAci && previousACI !== ourAci;
|
||||
|
||||
// We only consider the number changed if we didn't have a UUID before
|
||||
const numberChanged =
|
||||
|
@ -977,7 +994,7 @@ export default class AccountManager extends EventTarget {
|
|||
// calls out to the user storage API to get the stored UUID and number
|
||||
// information.
|
||||
await storage.user.setCredentials({
|
||||
uuid: ourUuid,
|
||||
aci: ourAci,
|
||||
pni: ourPni,
|
||||
number,
|
||||
deviceId: response.deviceId ?? 1,
|
||||
|
@ -989,7 +1006,7 @@ export default class AccountManager extends EventTarget {
|
|||
// database. Your identity, for example, in the saveIdentityWithAttributes call
|
||||
// below.
|
||||
const { conversation } = window.ConversationController.maybeMergeContacts({
|
||||
aci: ourUuid,
|
||||
aci: ourAci,
|
||||
pni: ourPni,
|
||||
e164: number,
|
||||
reason: 'createAccount',
|
||||
|
@ -1009,12 +1026,12 @@ export default class AccountManager extends EventTarget {
|
|||
// update our own identity key, which may have changed
|
||||
// if we're relinking after a reinstall on the master device
|
||||
await Promise.all([
|
||||
storage.protocol.saveIdentityWithAttributes(new UUID(ourUuid), {
|
||||
storage.protocol.saveIdentityWithAttributes(ourAci, {
|
||||
...identityAttrs,
|
||||
publicKey: aciKeyPair.pubKey,
|
||||
}),
|
||||
pniKeyPair
|
||||
? storage.protocol.saveIdentityWithAttributes(new UUID(ourPni), {
|
||||
? storage.protocol.saveIdentityWithAttributes(ourPni, {
|
||||
...identityAttrs,
|
||||
publicKey: pniKeyPair.pubKey,
|
||||
})
|
||||
|
@ -1023,7 +1040,7 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
const identityKeyMap = {
|
||||
...(storage.get('identityKeyMap') || {}),
|
||||
[ourUuid]: aciKeyPair,
|
||||
[ourAci]: aciKeyPair,
|
||||
...(pniKeyPair
|
||||
? {
|
||||
[ourPni]: pniKeyPair,
|
||||
|
@ -1032,7 +1049,7 @@ export default class AccountManager extends EventTarget {
|
|||
};
|
||||
const registrationIdMap = {
|
||||
...(storage.get('registrationIdMap') || {}),
|
||||
[ourUuid]: registrationId,
|
||||
[ourAci]: registrationId,
|
||||
[ourPni]: pniRegistrationId,
|
||||
};
|
||||
|
||||
|
@ -1055,20 +1072,20 @@ export default class AccountManager extends EventTarget {
|
|||
// Exposed only for testing
|
||||
public async _confirmKeys(
|
||||
keys: UploadKeysType,
|
||||
uuidKind: UUIDKind
|
||||
serviceIdKind: ServiceIdKind
|
||||
): Promise<void> {
|
||||
const logId = `AccountManager.confirmKeys(${uuidKind})`;
|
||||
const logId = `AccountManager.confirmKeys(${serviceIdKind})`;
|
||||
const { storage } = window.textsecure;
|
||||
const store = storage.protocol;
|
||||
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
||||
|
||||
const updatedAt = Date.now();
|
||||
const { signedPreKey, pqLastResortPreKey } = keys;
|
||||
if (signedPreKey) {
|
||||
log.info(`${logId}: confirming signed prekey key`, signedPreKey.keyId);
|
||||
await store.confirmSignedPreKey(ourUuid, signedPreKey.keyId);
|
||||
await store.confirmSignedPreKey(ourServiceId, signedPreKey.keyId);
|
||||
await window.storage.put(
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
updatedAt
|
||||
);
|
||||
} else {
|
||||
|
@ -1080,9 +1097,9 @@ export default class AccountManager extends EventTarget {
|
|||
`${logId}: confirming last resort key`,
|
||||
pqLastResortPreKey.keyId
|
||||
);
|
||||
await store.confirmKyberPreKey(ourUuid, pqLastResortPreKey.keyId);
|
||||
await store.confirmKyberPreKey(ourServiceId, pqLastResortPreKey.keyId);
|
||||
await window.storage.put(
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
|
||||
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
||||
updatedAt
|
||||
);
|
||||
} else {
|
||||
|
@ -1093,23 +1110,23 @@ export default class AccountManager extends EventTarget {
|
|||
// Very similar to maybeUpdateKeys, but will always generate prekeys and doesn't upload
|
||||
async _generateKeys(
|
||||
count: number,
|
||||
uuidKind: UUIDKind,
|
||||
serviceIdKind: ServiceIdKind,
|
||||
maybeIdentityKey?: KeyPairType
|
||||
): Promise<UploadKeysType> {
|
||||
const logId = `AcountManager.generateKeys(${uuidKind})`;
|
||||
const logId = `AcountManager.generateKeys(${serviceIdKind})`;
|
||||
const { storage } = window.textsecure;
|
||||
const store = storage.protocol;
|
||||
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||
const ourUuid = storage.user.getCheckedServiceId(serviceIdKind);
|
||||
|
||||
const identityKey = maybeIdentityKey ?? store.getIdentityKeyPair(ourUuid);
|
||||
strictAssert(identityKey, 'generateKeys: No identity key pair!');
|
||||
|
||||
const preKeys = await this.generateNewPreKeys(uuidKind, count);
|
||||
const pqPreKeys = await this.generateNewKyberPreKeys(uuidKind, count);
|
||||
const preKeys = await this.generateNewPreKeys(serviceIdKind, count);
|
||||
const pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind, count);
|
||||
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
|
||||
uuidKind
|
||||
serviceIdKind
|
||||
);
|
||||
const signedPreKey = await this.maybeUpdateSignedPreKey(uuidKind);
|
||||
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
|
||||
|
||||
log.info(
|
||||
`${logId}: Generated ` +
|
||||
|
@ -1120,10 +1137,10 @@ export default class AccountManager extends EventTarget {
|
|||
);
|
||||
|
||||
// These are primarily for the summaries they log out
|
||||
await this._cleanPreKeys(uuidKind);
|
||||
await this._cleanKyberPreKeys(uuidKind);
|
||||
await this._cleanLastResortKeys(uuidKind);
|
||||
await this._cleanSignedPreKeys(uuidKind);
|
||||
await this._cleanPreKeys(serviceIdKind);
|
||||
await this._cleanKyberPreKeys(serviceIdKind);
|
||||
await this._cleanLastResortKeys(serviceIdKind);
|
||||
await this._cleanSignedPreKeys(serviceIdKind);
|
||||
|
||||
return {
|
||||
identityKey: identityKey.pubKey,
|
||||
|
@ -1139,11 +1156,14 @@ export default class AccountManager extends EventTarget {
|
|||
this.dispatchEvent(new Event('registration'));
|
||||
}
|
||||
|
||||
async setPni(pni: string, keyMaterial?: PniKeyMaterialType): Promise<void> {
|
||||
async setPni(
|
||||
pni: PniString,
|
||||
keyMaterial?: PniKeyMaterialType
|
||||
): Promise<void> {
|
||||
const logId = `AccountManager.setPni(${pni})`;
|
||||
const { storage } = window.textsecure;
|
||||
|
||||
const oldPni = storage.user.getUuid(UUIDKind.PNI)?.toString();
|
||||
const oldPni = storage.user.getPni();
|
||||
if (oldPni === pni && !keyMaterial) {
|
||||
return;
|
||||
}
|
||||
|
@ -1151,22 +1171,19 @@ export default class AccountManager extends EventTarget {
|
|||
log.info(`${logId}: updating from ${oldPni}`);
|
||||
|
||||
if (oldPni) {
|
||||
await storage.protocol.removeOurOldPni(new UUID(oldPni));
|
||||
await storage.protocol.removeOurOldPni(oldPni);
|
||||
}
|
||||
|
||||
await storage.user.setPni(pni);
|
||||
|
||||
if (keyMaterial) {
|
||||
await storage.protocol.updateOurPniKeyMaterial(
|
||||
new UUID(pni),
|
||||
keyMaterial
|
||||
);
|
||||
await storage.protocol.updateOurPniKeyMaterial(pni, keyMaterial);
|
||||
|
||||
// Intentionally not awaiting since this is processed on encrypted queue
|
||||
// of MessageReceiver.
|
||||
void this.queueTask(async () => {
|
||||
try {
|
||||
await this.maybeUpdateKeys(UUIDKind.PNI);
|
||||
await this.maybeUpdateKeys(ServiceIdKind.PNI);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`${logId}: Failed to upload PNI prekeys. Moving on`,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import protobuf from '../protobuf/wrap';
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import { normalizeAci } from '../types/ServiceId';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as log from '../logging/log';
|
||||
|
@ -120,7 +120,7 @@ export class ContactBuffer extends ParserBase<
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (!proto.uuid) {
|
||||
if (!proto.aci) {
|
||||
return proto;
|
||||
}
|
||||
|
||||
|
@ -130,18 +130,18 @@ export class ContactBuffer extends ParserBase<
|
|||
...proto,
|
||||
|
||||
verified:
|
||||
verified && verified.destinationUuid
|
||||
verified && verified.destinationAci
|
||||
? {
|
||||
...verified,
|
||||
|
||||
destinationUuid: normalizeUuid(
|
||||
verified.destinationUuid,
|
||||
'ContactBuffer.verified.destinationUuid'
|
||||
destinationAci: normalizeAci(
|
||||
verified.destinationAci,
|
||||
'ContactBuffer.verified.destinationAci'
|
||||
),
|
||||
}
|
||||
: verified,
|
||||
|
||||
uuid: normalizeUuid(proto.uuid, 'ContactBuffer.uuid'),
|
||||
aci: normalizeAci(proto.aci, 'ContactBuffer.aci'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import type { LibSignalErrorBase } from '@signalapp/libsignal-client';
|
||||
|
||||
import { parseRetryAfter } from '../util/parseRetryAfter';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
|
||||
import type { CallbackResultType } from './Types.d';
|
||||
import type { HeaderListType } from './WebAPI';
|
||||
|
@ -191,13 +192,13 @@ export class SendMessageChallengeError extends ReplayableError {
|
|||
}
|
||||
|
||||
export class SendMessageProtoError extends Error implements CallbackResultType {
|
||||
public readonly successfulIdentifiers?: Array<string>;
|
||||
public readonly successfulServiceIds?: Array<ServiceIdString>;
|
||||
|
||||
public readonly failoverIdentifiers?: Array<string>;
|
||||
public readonly failoverServiceIds?: Array<ServiceIdString>;
|
||||
|
||||
public readonly errors?: CallbackResultType['errors'];
|
||||
|
||||
public readonly unidentifiedDeliveries?: Array<string>;
|
||||
public readonly unidentifiedDeliveries?: Array<ServiceIdString>;
|
||||
|
||||
public readonly dataMessage: Uint8Array | undefined;
|
||||
|
||||
|
@ -210,13 +211,13 @@ export class SendMessageProtoError extends Error implements CallbackResultType {
|
|||
|
||||
public readonly timestamp?: number;
|
||||
|
||||
public readonly recipients?: Record<string, Array<number>>;
|
||||
public readonly recipients?: Record<ServiceIdString, Array<number>>;
|
||||
|
||||
public readonly sendIsNotFinal?: boolean;
|
||||
|
||||
constructor({
|
||||
successfulIdentifiers,
|
||||
failoverIdentifiers,
|
||||
successfulServiceIds,
|
||||
failoverServiceIds,
|
||||
errors,
|
||||
unidentifiedDeliveries,
|
||||
dataMessage,
|
||||
|
@ -229,8 +230,8 @@ export class SendMessageProtoError extends Error implements CallbackResultType {
|
|||
}: CallbackResultType) {
|
||||
super(`SendMessageProtoError: ${SendMessageProtoError.getMessage(errors)}`);
|
||||
|
||||
this.successfulIdentifiers = successfulIdentifiers;
|
||||
this.failoverIdentifiers = failoverIdentifiers;
|
||||
this.successfulServiceIds = successfulServiceIds;
|
||||
this.failoverServiceIds = failoverServiceIds;
|
||||
this.errors = errors;
|
||||
this.unidentifiedDeliveries = unidentifiedDeliveries;
|
||||
this.dataMessage = dataMessage;
|
||||
|
@ -271,11 +272,11 @@ export class MessageError extends ReplayableError {
|
|||
}
|
||||
|
||||
export class UnregisteredUserError extends Error {
|
||||
readonly identifier: string;
|
||||
readonly serviceId: string;
|
||||
|
||||
readonly httpError: HTTPError;
|
||||
|
||||
constructor(identifier: string, httpError: HTTPError) {
|
||||
constructor(serviceId: ServiceIdString, httpError: HTTPError) {
|
||||
const { message } = httpError;
|
||||
|
||||
super(message);
|
||||
|
@ -289,7 +290,7 @@ export class UnregisteredUserError extends Error {
|
|||
Error.captureStackTrace(this);
|
||||
}
|
||||
|
||||
this.identifier = identifier;
|
||||
this.serviceId = serviceId;
|
||||
this.httpError = httpError;
|
||||
|
||||
appendStack(this, httpError);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright 2017 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { UUID } from '../types/UUID';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import type { SignalProtocolStore } from '../SignalProtocolStore';
|
||||
|
||||
export function init(signalProtocolStore: SignalProtocolStore): void {
|
||||
signalProtocolStore.on(
|
||||
'keychange',
|
||||
async (uuid: UUID, reason: string): Promise<void> => {
|
||||
async (serviceId: ServiceIdString, reason: string): Promise<void> => {
|
||||
const conversation =
|
||||
await window.ConversationController.getOrCreateAndWait(
|
||||
uuid.toString(),
|
||||
serviceId,
|
||||
'private'
|
||||
);
|
||||
void conversation.addKeyChange(reason);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,14 +34,12 @@ import {
|
|||
HTTPError,
|
||||
} from './Errors';
|
||||
import type { CallbackResultType, CustomError } from './Types.d';
|
||||
import { isValidNumber } from '../types/PhoneNumber';
|
||||
import { Address } from '../types/Address';
|
||||
import * as Errors from '../types/errors';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID, isValidUuid } from '../types/UUID';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||
import { getKeysForServiceId } from './getKeysForServiceId';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
|
@ -51,7 +49,7 @@ export const enum SenderCertificateMode {
|
|||
}
|
||||
|
||||
export type SendLogCallbackType = (options: {
|
||||
identifier: string;
|
||||
serviceId: ServiceIdString;
|
||||
deviceIds: Array<number>;
|
||||
}) => Promise<void>;
|
||||
|
||||
|
@ -109,7 +107,7 @@ export default class OutgoingMessage {
|
|||
|
||||
timestamp: number;
|
||||
|
||||
identifiers: ReadonlyArray<string>;
|
||||
serviceIds: ReadonlyArray<ServiceIdString>;
|
||||
|
||||
message: Proto.Content | PlaintextContent;
|
||||
|
||||
|
@ -117,15 +115,15 @@ export default class OutgoingMessage {
|
|||
|
||||
plaintext?: Uint8Array;
|
||||
|
||||
identifiersCompleted: number;
|
||||
serviceIdsCompleted: number;
|
||||
|
||||
errors: Array<CustomError>;
|
||||
|
||||
successfulIdentifiers: Array<string>;
|
||||
successfulServiceIds: Array<ServiceIdString>;
|
||||
|
||||
failoverIdentifiers: Array<string>;
|
||||
failoverServiceIds: Array<ServiceIdString>;
|
||||
|
||||
unidentifiedDeliveries: Array<string>;
|
||||
unidentifiedDeliveries: Array<ServiceIdString>;
|
||||
|
||||
sendMetadata?: SendMetadataType;
|
||||
|
||||
|
@ -147,7 +145,7 @@ export default class OutgoingMessage {
|
|||
callback,
|
||||
contentHint,
|
||||
groupId,
|
||||
identifiers,
|
||||
serviceIds,
|
||||
message,
|
||||
options,
|
||||
sendLogCallback,
|
||||
|
@ -159,7 +157,7 @@ export default class OutgoingMessage {
|
|||
callback: (result: CallbackResultType) => void;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
identifiers: ReadonlyArray<string>;
|
||||
serviceIds: ReadonlyArray<ServiceIdString>;
|
||||
message: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
options?: OutgoingMessageOptionsType;
|
||||
sendLogCallback?: SendLogCallbackType;
|
||||
|
@ -178,17 +176,17 @@ export default class OutgoingMessage {
|
|||
|
||||
this.server = server;
|
||||
this.timestamp = timestamp;
|
||||
this.identifiers = identifiers;
|
||||
this.serviceIds = serviceIds;
|
||||
this.contentHint = contentHint;
|
||||
this.groupId = groupId;
|
||||
this.callback = callback;
|
||||
this.story = story;
|
||||
this.urgent = urgent;
|
||||
|
||||
this.identifiersCompleted = 0;
|
||||
this.serviceIdsCompleted = 0;
|
||||
this.errors = [];
|
||||
this.successfulIdentifiers = [];
|
||||
this.failoverIdentifiers = [];
|
||||
this.successfulServiceIds = [];
|
||||
this.failoverServiceIds = [];
|
||||
this.unidentifiedDeliveries = [];
|
||||
this.recipients = {};
|
||||
this.sendLogCallback = sendLogCallback;
|
||||
|
@ -198,8 +196,8 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
numberCompleted(): void {
|
||||
this.identifiersCompleted += 1;
|
||||
if (this.identifiersCompleted >= this.identifiers.length) {
|
||||
this.serviceIdsCompleted += 1;
|
||||
if (this.serviceIdsCompleted >= this.serviceIds.length) {
|
||||
const proto = this.message;
|
||||
const contentProto = this.getContentProtoBytes();
|
||||
const { timestamp, contentHint, recipients, urgent } = this;
|
||||
|
@ -221,8 +219,8 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
this.callback({
|
||||
successfulIdentifiers: this.successfulIdentifiers,
|
||||
failoverIdentifiers: this.failoverIdentifiers,
|
||||
successfulServiceIds: this.successfulServiceIds,
|
||||
failoverServiceIds: this.failoverServiceIds,
|
||||
errors: this.errors,
|
||||
unidentifiedDeliveries: this.unidentifiedDeliveries,
|
||||
|
||||
|
@ -239,7 +237,7 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
registerError(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
reason: string,
|
||||
providedError?: Error
|
||||
): void {
|
||||
|
@ -247,9 +245,9 @@ export default class OutgoingMessage {
|
|||
|
||||
if (!error || (error instanceof HTTPError && error.code !== 404)) {
|
||||
if (error && error.code === 428) {
|
||||
error = new SendMessageChallengeError(identifier, error);
|
||||
error = new SendMessageChallengeError(serviceId, error);
|
||||
} else {
|
||||
error = new OutgoingMessageError(identifier, null, null, error);
|
||||
error = new OutgoingMessageError(serviceId, null, null, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,51 +258,51 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
reloadDevicesAndSend(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
recurse?: boolean
|
||||
): () => Promise<void> {
|
||||
return async () => {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
ourServiceId: ourAci,
|
||||
serviceId,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'reloadDevicesAndSend: Got empty device list when loading device keys',
|
||||
undefined
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||
return this.doSendMessage(serviceId, deviceIds, recurse);
|
||||
};
|
||||
}
|
||||
|
||||
async getKeysForIdentifier(
|
||||
identifier: string,
|
||||
async getKeysForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
updateDevices?: Array<number>
|
||||
): Promise<void | Array<void | null>> {
|
||||
): Promise<void> {
|
||||
const { sendMetadata } = this;
|
||||
const info =
|
||||
sendMetadata && sendMetadata[identifier]
|
||||
? sendMetadata[identifier]
|
||||
sendMetadata && sendMetadata[serviceId]
|
||||
? sendMetadata[serviceId]
|
||||
: { accessKey: undefined };
|
||||
const { accessKey } = info;
|
||||
|
||||
const { accessKeyFailed } = await getKeysForIdentifier(
|
||||
identifier,
|
||||
const { accessKeyFailed } = await getKeysForServiceId(
|
||||
serviceId,
|
||||
this.server,
|
||||
updateDevices,
|
||||
accessKey
|
||||
);
|
||||
if (accessKeyFailed && !this.failoverIdentifiers.includes(identifier)) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
if (accessKeyFailed && !this.failoverServiceIds.includes(serviceId)) {
|
||||
this.failoverServiceIds.push(serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
async transmitMessage(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
jsonData: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
{ accessKey }: { accessKey?: string } = {}
|
||||
|
@ -312,19 +310,14 @@ export default class OutgoingMessage {
|
|||
let promise;
|
||||
|
||||
if (accessKey) {
|
||||
promise = this.server.sendMessagesUnauth(
|
||||
identifier,
|
||||
jsonData,
|
||||
timestamp,
|
||||
{
|
||||
accessKey,
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
}
|
||||
);
|
||||
promise = this.server.sendMessagesUnauth(serviceId, jsonData, timestamp, {
|
||||
accessKey,
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
});
|
||||
} else {
|
||||
promise = this.server.sendMessages(identifier, jsonData, timestamp, {
|
||||
promise = this.server.sendMessages(serviceId, jsonData, timestamp, {
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
|
@ -338,12 +331,12 @@ export default class OutgoingMessage {
|
|||
// 428 should throw SendMessageChallengeError
|
||||
// all other network errors can be retried later.
|
||||
if (e.code === 404) {
|
||||
throw new UnregisteredUserError(identifier, e);
|
||||
throw new UnregisteredUserError(serviceId, e);
|
||||
}
|
||||
if (e.code === 428) {
|
||||
throw new SendMessageChallengeError(identifier, e);
|
||||
throw new SendMessageChallengeError(serviceId, e);
|
||||
}
|
||||
throw new SendMessageNetworkError(identifier, jsonData, e);
|
||||
throw new SendMessageNetworkError(serviceId, jsonData, e);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
@ -394,12 +387,12 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
async doSendMessage(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
deviceIds: Array<number>,
|
||||
recurse?: boolean
|
||||
): Promise<void> {
|
||||
const { sendMetadata } = this;
|
||||
const { accessKey, senderCertificate } = sendMetadata?.[identifier] || {};
|
||||
const { accessKey, senderCertificate } = sendMetadata?.[serviceId] || {};
|
||||
|
||||
if (accessKey && !senderCertificate) {
|
||||
log.warn(
|
||||
|
@ -411,12 +404,9 @@ export default class OutgoingMessage {
|
|||
|
||||
// We don't send to ourselves unless sealedSender is enabled
|
||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||
if (
|
||||
(identifier === ourNumber || identifier === ourUuid.toString()) &&
|
||||
!sealedSender
|
||||
) {
|
||||
if ((serviceId === ourNumber || serviceId === ourAci) && !sealedSender) {
|
||||
deviceIds = reject(
|
||||
deviceIds,
|
||||
deviceId =>
|
||||
|
@ -427,15 +417,14 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
const sessionStore = new Sessions({ ourServiceId: ourAci });
|
||||
const identityKeyStore = new IdentityKeys({ ourServiceId: ourAci });
|
||||
|
||||
return Promise.all(
|
||||
deviceIds.map(async destinationDeviceId => {
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(theirUuid, destinationDeviceId)
|
||||
ourAci,
|
||||
new Address(serviceId, destinationDeviceId)
|
||||
);
|
||||
|
||||
return window.textsecure.storage.protocol.enqueueSessionJob<MessageType>(
|
||||
|
@ -443,7 +432,7 @@ export default class OutgoingMessage {
|
|||
`doSendMessage(${address.toString()})`,
|
||||
async () => {
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
theirUuid.toString(),
|
||||
serviceId,
|
||||
destinationDeviceId
|
||||
);
|
||||
|
||||
|
@ -517,21 +506,21 @@ export default class OutgoingMessage {
|
|||
)
|
||||
.then(async (jsonData: Array<MessageType>) => {
|
||||
if (sealedSender) {
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp, {
|
||||
return this.transmitMessage(serviceId, jsonData, this.timestamp, {
|
||||
accessKey,
|
||||
}).then(
|
||||
() => {
|
||||
this.recipients[identifier] = deviceIds;
|
||||
this.unidentifiedDeliveries.push(identifier);
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.recipients[serviceId] = deviceIds;
|
||||
this.unidentifiedDeliveries.push(serviceId);
|
||||
this.successfulServiceIds.push(serviceId);
|
||||
this.numberCompleted();
|
||||
|
||||
if (this.sendLogCallback) {
|
||||
void this.sendLogCallback({
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds,
|
||||
});
|
||||
} else if (this.successfulIdentifiers.length > 1) {
|
||||
} else if (this.successfulServiceIds.length > 1) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
|
||||
);
|
||||
|
@ -543,18 +532,18 @@ export default class OutgoingMessage {
|
|||
(error.code === 401 || error.code === 403)
|
||||
) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: Failing over to unsealed send for identifier ${identifier}`
|
||||
`OutgoingMessage.doSendMessage: Failing over to unsealed send for serviceId ${serviceId}`
|
||||
);
|
||||
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
if (this.failoverServiceIds.indexOf(serviceId) === -1) {
|
||||
this.failoverServiceIds.push(serviceId);
|
||||
}
|
||||
|
||||
// This ensures that we don't hit this codepath the next time through
|
||||
if (sendMetadata) {
|
||||
delete sendMetadata[identifier];
|
||||
delete sendMetadata[serviceId];
|
||||
}
|
||||
|
||||
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||
return this.doSendMessage(serviceId, deviceIds, recurse);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -562,18 +551,18 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp).then(
|
||||
return this.transmitMessage(serviceId, jsonData, this.timestamp).then(
|
||||
() => {
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.recipients[identifier] = deviceIds;
|
||||
this.successfulServiceIds.push(serviceId);
|
||||
this.recipients[serviceId] = deviceIds;
|
||||
this.numberCompleted();
|
||||
|
||||
if (this.sendLogCallback) {
|
||||
void this.sendLogCallback({
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds,
|
||||
});
|
||||
} else if (this.successfulIdentifiers.length > 1) {
|
||||
} else if (this.successfulServiceIds.length > 1) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
|
||||
);
|
||||
|
@ -588,7 +577,7 @@ export default class OutgoingMessage {
|
|||
) {
|
||||
if (!recurse) {
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'Hit retry limit attempting to reload device list',
|
||||
error
|
||||
);
|
||||
|
@ -602,18 +591,15 @@ export default class OutgoingMessage {
|
|||
};
|
||||
let p: Promise<any> = Promise.resolve();
|
||||
if (error.code === 409) {
|
||||
p = this.removeDeviceIdsForIdentifier(
|
||||
identifier,
|
||||
p = this.removeDeviceIdsForServiceId(
|
||||
serviceId,
|
||||
response.extraDevices || []
|
||||
);
|
||||
} else {
|
||||
p = Promise.all(
|
||||
(response.staleDevices || []).map(async (deviceId: number) => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(UUID.checkedLookup(identifier), deviceId)
|
||||
)
|
||||
new QualifiedAddress(ourAci, new Address(serviceId, deviceId))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -624,10 +610,10 @@ export default class OutgoingMessage {
|
|||
error.code === 410
|
||||
? response.staleDevices
|
||||
: response.missingDevices;
|
||||
return this.getKeysForIdentifier(identifier, resetDevices).then(
|
||||
return this.getKeysForServiceId(serviceId, resetDevices).then(
|
||||
// We continue to retry as long as the error code was 409; the assumption is
|
||||
// that we'll request new device info and the next request will succeed.
|
||||
this.reloadDevicesAndSend(identifier, error.code === 409)
|
||||
this.reloadDevicesAndSend(serviceId, error.code === 409)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -637,32 +623,30 @@ export default class OutgoingMessage {
|
|||
error instanceof LibSignalErrorBase &&
|
||||
error.code === ErrorCode.UntrustedIdentity
|
||||
) {
|
||||
newError = new OutgoingIdentityKeyError(identifier, error);
|
||||
newError = new OutgoingIdentityKeyError(serviceId, error);
|
||||
log.error(
|
||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds
|
||||
);
|
||||
|
||||
log.info('closing all sessions for', identifier);
|
||||
window.textsecure.storage.protocol
|
||||
.archiveAllSessions(UUID.checkedLookup(identifier))
|
||||
.then(
|
||||
() => {
|
||||
throw error;
|
||||
},
|
||||
innerError => {
|
||||
log.error(
|
||||
'doSendMessage: Error closing sessions: ' +
|
||||
`${Errors.toLogFormat(innerError)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
log.info('closing all sessions for', serviceId);
|
||||
window.textsecure.storage.protocol.archiveAllSessions(serviceId).then(
|
||||
() => {
|
||||
throw error;
|
||||
},
|
||||
innerError => {
|
||||
log.error(
|
||||
'doSendMessage: Error closing sessions: ' +
|
||||
`${Errors.toLogFormat(innerError)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'Failed to create or send message',
|
||||
newError
|
||||
);
|
||||
|
@ -671,87 +655,43 @@ export default class OutgoingMessage {
|
|||
});
|
||||
}
|
||||
|
||||
async removeDeviceIdsForIdentifier(
|
||||
identifier: string,
|
||||
async removeDeviceIdsForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
deviceIdsToRemove: Array<number>
|
||||
): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
await Promise.all(
|
||||
deviceIdsToRemove.map(async deviceId => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
|
||||
new QualifiedAddress(ourAci, new Address(serviceId, deviceId))
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async sendToIdentifier(providedIdentifier: string): Promise<void> {
|
||||
let identifier = providedIdentifier;
|
||||
async sendToServiceId(serviceId: ServiceIdString): Promise<void> {
|
||||
try {
|
||||
if (isValidUuid(identifier)) {
|
||||
// We're good!
|
||||
} else if (isValidNumber(identifier)) {
|
||||
if (!window.textsecure.server) {
|
||||
throw new Error(
|
||||
'sendToIdentifier: window.textsecure.server is not available!'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await updateConversationsWithUuidLookup({
|
||||
conversationController: window.ConversationController,
|
||||
conversations: [
|
||||
window.ConversationController.getOrCreate(identifier, 'private'),
|
||||
],
|
||||
server: window.textsecure.server,
|
||||
});
|
||||
|
||||
const uuid =
|
||||
window.ConversationController.get(identifier)?.get('uuid');
|
||||
if (!uuid) {
|
||||
throw new UnregisteredUserError(
|
||||
identifier,
|
||||
new HTTPError('User is not registered', {
|
||||
code: -1,
|
||||
headers: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
identifier = uuid;
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`sendToIdentifier: Failed to fetch UUID for identifier ${identifier}`,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`sendToIdentifier: identifier ${identifier} was neither a UUID or E164`
|
||||
);
|
||||
}
|
||||
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
ourServiceId: ourAci,
|
||||
serviceId,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
await this.getKeysForIdentifier(identifier);
|
||||
await this.getKeysForServiceId(serviceId);
|
||||
}
|
||||
await this.reloadDevicesAndSend(identifier, true)();
|
||||
await this.reloadDevicesAndSend(serviceId, true)();
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof LibSignalErrorBase &&
|
||||
error.code === ErrorCode.UntrustedIdentity
|
||||
) {
|
||||
const newError = new OutgoingIdentityKeyError(identifier, error);
|
||||
this.registerError(identifier, 'Untrusted identity', newError);
|
||||
const newError = new OutgoingIdentityKeyError(serviceId, error);
|
||||
this.registerError(serviceId, 'Untrusted identity', newError);
|
||||
} else {
|
||||
this.registerError(
|
||||
identifier,
|
||||
`Failed to retrieve new device keys for identifier ${identifier}`,
|
||||
serviceId,
|
||||
`Failed to retrieve new device keys for serviceId ${serviceId}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,15 +12,16 @@ import {
|
|||
} from '../Crypto';
|
||||
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import type { PniString, AciString } from '../types/ServiceId';
|
||||
import { normalizeAci, normalizePni } from '../types/ServiceId';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
|
||||
type ProvisionDecryptResult = {
|
||||
aciKeyPair: KeyPairType;
|
||||
pniKeyPair?: KeyPairType;
|
||||
number?: string;
|
||||
aci?: string;
|
||||
pni?: string;
|
||||
aci?: AciString;
|
||||
pni?: PniString;
|
||||
provisioningCode?: string;
|
||||
userAgent?: string;
|
||||
readReceipts?: boolean;
|
||||
|
@ -78,8 +79,8 @@ class ProvisioningCipherInner {
|
|||
aciKeyPair,
|
||||
pniKeyPair,
|
||||
number: provisionMessage.number,
|
||||
aci: normalizeUuid(aci, 'ProvisionMessage.aci'),
|
||||
pni: pni ? normalizeUuid(pni, 'ProvisionMessage.pni') : undefined,
|
||||
aci: normalizeAci(aci, 'ProvisionMessage.aci'),
|
||||
pni: pni ? normalizePni(pni, 'ProvisionMessage.pni') : undefined,
|
||||
provisioningCode: provisionMessage.provisioningCode,
|
||||
userAgent: provisionMessage.userAgent,
|
||||
readReceipts: provisionMessage.readReceipts,
|
||||
|
|
|
@ -17,7 +17,6 @@ import type { ConversationModel } from '../models/conversations';
|
|||
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { getTaggedConversationUuid } from '../util/getConversationUuid';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { SenderKeys } from '../LibSignalStores';
|
||||
|
@ -25,7 +24,8 @@ import type {
|
|||
TextAttachmentType,
|
||||
UploadedAttachmentType,
|
||||
} from '../types/Attachment';
|
||||
import { type UUID, type TaggedUUIDStringType, UUIDKind } from '../types/UUID';
|
||||
import type { AciString, ServiceIdString } from '../types/ServiceId';
|
||||
import { ServiceIdKind } from '../types/ServiceId';
|
||||
import type {
|
||||
ChallengeType,
|
||||
GetGroupLogOptionsType,
|
||||
|
@ -81,7 +81,7 @@ import { missingCaseError } from '../util/missingCaseError';
|
|||
import { drop } from '../util/drop';
|
||||
|
||||
export type SendMetadataType = {
|
||||
[identifier: string]: {
|
||||
[serviceId: ServiceIdString]: {
|
||||
accessKey: string;
|
||||
senderCertificate?: SerializedCertificateType;
|
||||
};
|
||||
|
@ -101,7 +101,7 @@ export type OutgoingQuoteAttachmentType = Readonly<{
|
|||
export type OutgoingQuoteType = Readonly<{
|
||||
isGiftBadge?: boolean;
|
||||
id?: number;
|
||||
authorUuid?: string;
|
||||
authorAci?: AciString;
|
||||
text?: string;
|
||||
attachments: ReadonlyArray<OutgoingQuoteAttachmentType>;
|
||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||
|
@ -125,7 +125,7 @@ export type GroupV2InfoType = {
|
|||
groupChange?: Uint8Array;
|
||||
masterKey: Uint8Array;
|
||||
revision: number;
|
||||
members: ReadonlyArray<string>;
|
||||
members: ReadonlyArray<ServiceIdString>;
|
||||
};
|
||||
|
||||
type GroupCallUpdateType = {
|
||||
|
@ -143,7 +143,7 @@ export type OutgoingStickerType = Readonly<{
|
|||
export type ReactionType = {
|
||||
emoji?: string;
|
||||
remove?: boolean;
|
||||
targetAuthorUuid?: string;
|
||||
targetAuthorAci?: AciString;
|
||||
targetTimestamp?: number;
|
||||
};
|
||||
|
||||
|
@ -176,7 +176,7 @@ export type MessageOptionsType = {
|
|||
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
||||
profileKey?: Uint8Array;
|
||||
quote?: OutgoingQuoteType;
|
||||
recipients: ReadonlyArray<string>;
|
||||
recipients: ReadonlyArray<ServiceIdString>;
|
||||
sticker?: OutgoingStickerType;
|
||||
reaction?: ReactionType;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
|
@ -232,7 +232,7 @@ class Message {
|
|||
|
||||
quote?: OutgoingQuoteType;
|
||||
|
||||
recipients: ReadonlyArray<string>;
|
||||
recipients: ReadonlyArray<ServiceIdString>;
|
||||
|
||||
sticker?: OutgoingStickerType;
|
||||
|
||||
|
@ -377,7 +377,7 @@ class Message {
|
|||
proto.reaction = new Proto.DataMessage.Reaction();
|
||||
proto.reaction.emoji = this.reaction.emoji || null;
|
||||
proto.reaction.remove = this.reaction.remove || false;
|
||||
proto.reaction.targetAuthorUuid = this.reaction.targetAuthorUuid || null;
|
||||
proto.reaction.targetAuthorAci = this.reaction.targetAuthorAci || null;
|
||||
proto.reaction.targetTimestamp =
|
||||
this.reaction.targetTimestamp === undefined
|
||||
? null
|
||||
|
@ -481,7 +481,7 @@ class Message {
|
|||
|
||||
quote.id =
|
||||
this.quote.id === undefined ? null : Long.fromNumber(this.quote.id);
|
||||
quote.authorUuid = this.quote.authorUuid || null;
|
||||
quote.authorAci = this.quote.authorAci || null;
|
||||
quote.text = this.quote.text || null;
|
||||
quote.attachments = this.quote.attachments.slice() || [];
|
||||
const bodyRanges = this.quote.bodyRanges || [];
|
||||
|
@ -490,7 +490,7 @@ class Message {
|
|||
bodyRange.start = range.start;
|
||||
bodyRange.length = range.length;
|
||||
if (BodyRange.isMention(range)) {
|
||||
bodyRange.mentionUuid = range.mentionUuid;
|
||||
bodyRange.mentionAci = range.mentionUuid;
|
||||
} else if (BodyRange.isFormatting(range)) {
|
||||
bodyRange.style = range.style;
|
||||
} else {
|
||||
|
@ -529,7 +529,7 @@ class Message {
|
|||
return {
|
||||
start,
|
||||
length,
|
||||
mentionUuid: bodyRange.mentionUuid,
|
||||
mentionAci: bodyRange.mentionUuid,
|
||||
};
|
||||
}
|
||||
if (BodyRange.isFormatting(bodyRange)) {
|
||||
|
@ -556,8 +556,8 @@ class Message {
|
|||
const { StoryContext } = Proto.DataMessage;
|
||||
|
||||
const storyContext = new StoryContext();
|
||||
if (this.storyContext.authorUuid) {
|
||||
storyContext.authorUuid = this.storyContext.authorUuid;
|
||||
if (this.storyContext.authorAci) {
|
||||
storyContext.authorAci = this.storyContext.authorAci;
|
||||
}
|
||||
storyContext.sentTimestamp = Long.fromNumber(this.storyContext.timestamp);
|
||||
|
||||
|
@ -610,12 +610,12 @@ export default class MessageSender {
|
|||
this.pendingMessages = {};
|
||||
}
|
||||
|
||||
async queueJobForIdentifier<T>(
|
||||
identifier: string,
|
||||
async queueJobForServiceId<T>(
|
||||
serviceId: ServiceIdString,
|
||||
runJob: () => Promise<T>
|
||||
): Promise<T> {
|
||||
const { id } = await window.ConversationController.getOrCreateAndWait(
|
||||
identifier,
|
||||
serviceId,
|
||||
'private'
|
||||
);
|
||||
this.pendingMessages[id] =
|
||||
|
@ -625,7 +625,7 @@ export default class MessageSender {
|
|||
|
||||
const taskWithTimeout = createTaskWithTimeout(
|
||||
runJob,
|
||||
`queueJobForIdentifier ${identifier} ${id}`
|
||||
`queueJobForServiceId ${serviceId} ${id}`
|
||||
);
|
||||
|
||||
return queue.add(taskWithTimeout);
|
||||
|
@ -808,9 +808,9 @@ export default class MessageSender {
|
|||
|
||||
getTypingContentMessage(
|
||||
options: Readonly<{
|
||||
recipientId?: string;
|
||||
recipientId?: ServiceIdString;
|
||||
groupId?: Uint8Array;
|
||||
groupMembers: ReadonlyArray<string>;
|
||||
groupMembers: ReadonlyArray<ServiceIdString>;
|
||||
isTyping: boolean;
|
||||
timestamp?: number;
|
||||
}>
|
||||
|
@ -877,19 +877,10 @@ export default class MessageSender {
|
|||
);
|
||||
}
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const groupMembers = groupV2?.members || [];
|
||||
|
||||
// We should always have a UUID but have this check just in case we don't.
|
||||
let isNotMe: (recipient: string) => boolean;
|
||||
if (myUuid) {
|
||||
isNotMe = r => r !== myE164 && r !== myUuid.toString();
|
||||
} else {
|
||||
isNotMe = r => r !== myE164;
|
||||
}
|
||||
|
||||
const blockedIdentifiers = new Set(
|
||||
concat(
|
||||
window.storage.blocked.getBlockedUuids(),
|
||||
|
@ -898,7 +889,7 @@ export default class MessageSender {
|
|||
);
|
||||
|
||||
const recipients = groupMembers.filter(
|
||||
recipient => isNotMe(recipient) && !blockedIdentifiers.has(recipient)
|
||||
recipient => recipient !== myAci && !blockedIdentifiers.has(recipient)
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -997,7 +988,7 @@ export default class MessageSender {
|
|||
groupId: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
recipients: ReadonlyArray<string>;
|
||||
recipients: ReadonlyArray<ServiceIdString>;
|
||||
sendLogCallback?: SendLogCallbackType;
|
||||
story?: boolean;
|
||||
timestamp: number;
|
||||
|
@ -1005,12 +996,12 @@ export default class MessageSender {
|
|||
}>): Promise<void> {
|
||||
const accountManager = window.getAccountManager();
|
||||
try {
|
||||
if (accountManager.areKeysOutOfDate(UUIDKind.ACI)) {
|
||||
if (accountManager.areKeysOutOfDate(ServiceIdKind.ACI)) {
|
||||
log.warn(
|
||||
`sendMessageProto/${timestamp}: Keys are out of date; updating before send`
|
||||
);
|
||||
await accountManager.maybeUpdateKeys(UUIDKind.ACI);
|
||||
if (accountManager.areKeysOutOfDate(UUIDKind.ACI)) {
|
||||
await accountManager.maybeUpdateKeys(ServiceIdKind.ACI);
|
||||
if (accountManager.areKeysOutOfDate(ServiceIdKind.ACI)) {
|
||||
throw new Error('Keys still out of date after update');
|
||||
}
|
||||
}
|
||||
|
@ -1028,7 +1019,7 @@ export default class MessageSender {
|
|||
callback,
|
||||
contentHint,
|
||||
groupId,
|
||||
identifiers: recipients,
|
||||
serviceIds: recipients,
|
||||
message: proto,
|
||||
options,
|
||||
sendLogCallback,
|
||||
|
@ -1038,10 +1029,10 @@ export default class MessageSender {
|
|||
urgent,
|
||||
});
|
||||
|
||||
recipients.forEach(identifier => {
|
||||
recipients.forEach(serviceId => {
|
||||
drop(
|
||||
this.queueJobForIdentifier(identifier, async () =>
|
||||
outgoing.sendToIdentifier(identifier)
|
||||
this.queueJobForServiceId(serviceId, async () =>
|
||||
outgoing.sendToServiceId(serviceId)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
@ -1058,7 +1049,7 @@ export default class MessageSender {
|
|||
story,
|
||||
}: Readonly<{
|
||||
timestamp: number;
|
||||
recipients: Array<string>;
|
||||
recipients: Array<ServiceIdString>;
|
||||
proto: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
|
@ -1094,7 +1085,7 @@ export default class MessageSender {
|
|||
async sendIndividualProto({
|
||||
contentHint,
|
||||
groupId,
|
||||
identifier,
|
||||
serviceId,
|
||||
options,
|
||||
proto,
|
||||
timestamp,
|
||||
|
@ -1102,13 +1093,13 @@ export default class MessageSender {
|
|||
}: Readonly<{
|
||||
contentHint: number;
|
||||
groupId?: string;
|
||||
identifier: string | undefined;
|
||||
serviceId: ServiceIdString | undefined;
|
||||
options?: SendOptionsType;
|
||||
proto: Proto.DataMessage | Proto.Content | PlaintextContent;
|
||||
timestamp: number;
|
||||
urgent: boolean;
|
||||
}>): Promise<CallbackResultType> {
|
||||
assertDev(identifier, "Identifier can't be undefined");
|
||||
assertDev(serviceId, "ServiceId can't be undefined");
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (res: CallbackResultType) => {
|
||||
if (res && res.errors && res.errors.length > 0) {
|
||||
|
@ -1124,7 +1115,7 @@ export default class MessageSender {
|
|||
groupId,
|
||||
options,
|
||||
proto,
|
||||
recipients: [identifier],
|
||||
recipients: [serviceId],
|
||||
timestamp,
|
||||
urgent,
|
||||
})
|
||||
|
@ -1134,7 +1125,7 @@ export default class MessageSender {
|
|||
|
||||
// You might wonder why this takes a groupId. models/messages.resend() can send a group
|
||||
// message to just one person.
|
||||
async sendMessageToIdentifier({
|
||||
async sendMessageToServiceId({
|
||||
attachments,
|
||||
bodyRanges,
|
||||
contact,
|
||||
|
@ -1143,7 +1134,7 @@ export default class MessageSender {
|
|||
editedMessageTimestamp,
|
||||
expireTimer,
|
||||
groupId,
|
||||
identifier,
|
||||
serviceId,
|
||||
messageText,
|
||||
options,
|
||||
preview,
|
||||
|
@ -1165,7 +1156,7 @@ export default class MessageSender {
|
|||
editedMessageTimestamp?: number;
|
||||
expireTimer: DurationInSeconds | undefined;
|
||||
groupId: string | undefined;
|
||||
identifier: string;
|
||||
serviceId: ServiceIdString;
|
||||
messageText: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
preview?: ReadonlyArray<OutgoingLinkPreviewType> | undefined;
|
||||
|
@ -1192,7 +1183,7 @@ export default class MessageSender {
|
|||
profileKey,
|
||||
quote,
|
||||
reaction,
|
||||
recipients: [identifier],
|
||||
recipients: [serviceId],
|
||||
sticker,
|
||||
storyContext,
|
||||
timestamp,
|
||||
|
@ -1215,7 +1206,7 @@ export default class MessageSender {
|
|||
encodedEditMessage,
|
||||
timestamp,
|
||||
destination,
|
||||
destinationUuid,
|
||||
destinationServiceId,
|
||||
expirationStartTimestamp,
|
||||
conversationIdsSentTo = [],
|
||||
conversationIdsWithSealedSender = new Set(),
|
||||
|
@ -1229,7 +1220,7 @@ export default class MessageSender {
|
|||
encodedEditMessage?: Uint8Array;
|
||||
timestamp: number;
|
||||
destination: string | undefined;
|
||||
destinationUuid: TaggedUUIDStringType | undefined;
|
||||
destinationServiceId: ServiceIdString | undefined;
|
||||
expirationStartTimestamp: number | null;
|
||||
conversationIdsSentTo?: Iterable<string>;
|
||||
conversationIdsWithSealedSender?: Set<string>;
|
||||
|
@ -1239,7 +1230,7 @@ export default class MessageSender {
|
|||
storyMessage?: Proto.StoryMessage;
|
||||
storyMessageRecipients?: ReadonlyArray<Proto.SyncMessage.Sent.IStoryMessageRecipient>;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const sentMessage = new Proto.SyncMessage.Sent();
|
||||
sentMessage.timestamp = Long.fromNumber(timestamp);
|
||||
|
@ -1254,10 +1245,8 @@ export default class MessageSender {
|
|||
if (destination) {
|
||||
sentMessage.destination = destination;
|
||||
}
|
||||
if (destinationUuid?.aci) {
|
||||
sentMessage.destinationAci = destinationUuid.aci;
|
||||
} else if (destinationUuid?.pni) {
|
||||
sentMessage.destinationPni = destinationUuid.pni;
|
||||
if (destinationServiceId) {
|
||||
sentMessage.destinationServiceId = destinationServiceId;
|
||||
}
|
||||
if (expirationStartTimestamp) {
|
||||
sentMessage.expirationStartTimestamp = Long.fromNumber(
|
||||
|
@ -1288,11 +1277,9 @@ export default class MessageSender {
|
|||
if (e164) {
|
||||
status.destination = e164;
|
||||
}
|
||||
const taggedUuid = getTaggedConversationUuid(conv.attributes);
|
||||
if (taggedUuid?.aci) {
|
||||
status.destinationAci = taggedUuid.aci;
|
||||
} else if (taggedUuid?.pni) {
|
||||
status.destinationPni = taggedUuid.pni;
|
||||
const serviceId = conv.getServiceId();
|
||||
if (serviceId) {
|
||||
status.destinationServiceId = serviceId;
|
||||
}
|
||||
}
|
||||
status.unidentified =
|
||||
|
@ -1310,7 +1297,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid.toString(),
|
||||
serviceId: myAci,
|
||||
proto: contentMessage,
|
||||
timestamp,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1320,7 +1307,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getRequestBlockSyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
||||
|
@ -1333,7 +1320,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1344,7 +1331,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getRequestConfigurationSyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
||||
|
@ -1357,7 +1344,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1368,7 +1355,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getRequestContactSyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
||||
|
@ -1381,7 +1368,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1392,7 +1379,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getFetchManifestSyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||
|
@ -1406,7 +1393,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1417,7 +1404,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getFetchLocalProfileSyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.LOCAL_PROFILE;
|
||||
|
@ -1431,7 +1418,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1442,7 +1429,7 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
static getRequestKeySyncMessage(): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
||||
|
@ -1456,7 +1443,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1474,7 +1461,7 @@ export default class MessageSender {
|
|||
}>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
syncMessage.read = [];
|
||||
|
@ -1492,7 +1479,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid.toString(),
|
||||
serviceId: myAci,
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1503,13 +1490,13 @@ export default class MessageSender {
|
|||
|
||||
async syncView(
|
||||
views: ReadonlyArray<{
|
||||
senderUuid?: string;
|
||||
senderAci?: AciString;
|
||||
senderE164?: string;
|
||||
timestamp: number;
|
||||
}>,
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
syncMessage.viewed = views.map(
|
||||
|
@ -1525,7 +1512,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid.toString(),
|
||||
serviceId: myAci,
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1536,7 +1523,7 @@ export default class MessageSender {
|
|||
|
||||
async syncViewOnceOpen(
|
||||
viewOnceOpens: ReadonlyArray<{
|
||||
senderUuid?: string;
|
||||
senderAci?: AciString;
|
||||
senderE164?: string;
|
||||
timestamp: number;
|
||||
}>,
|
||||
|
@ -1547,13 +1534,13 @@ export default class MessageSender {
|
|||
`syncViewOnceOpen: ${viewOnceOpens.length} opens provided. Can only handle one.`
|
||||
);
|
||||
}
|
||||
const { senderE164, senderUuid, timestamp } = viewOnceOpens[0];
|
||||
const { senderE164, senderAci, timestamp } = viewOnceOpens[0];
|
||||
|
||||
if (!senderUuid) {
|
||||
throw new Error('syncViewOnceOpen: Missing senderUuid');
|
||||
if (!senderAci) {
|
||||
throw new Error('syncViewOnceOpen: Missing senderAci');
|
||||
}
|
||||
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
|
||||
|
@ -1561,7 +1548,7 @@ export default class MessageSender {
|
|||
if (senderE164 !== undefined) {
|
||||
viewOnceOpen.sender = senderE164;
|
||||
}
|
||||
viewOnceOpen.senderUuid = senderUuid;
|
||||
viewOnceOpen.senderAci = senderAci;
|
||||
viewOnceOpen.timestamp = Long.fromNumber(timestamp);
|
||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||
|
||||
|
@ -1571,7 +1558,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid.toString(),
|
||||
serviceId: myAci,
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1583,12 +1570,12 @@ export default class MessageSender {
|
|||
static getMessageRequestResponseSync(
|
||||
options: Readonly<{
|
||||
threadE164?: string;
|
||||
threadUuid?: string;
|
||||
threadAci?: AciString;
|
||||
groupId?: Uint8Array;
|
||||
type: number;
|
||||
}>
|
||||
): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
|
||||
|
@ -1596,8 +1583,8 @@ export default class MessageSender {
|
|||
if (options.threadE164 !== undefined) {
|
||||
response.threadE164 = options.threadE164;
|
||||
}
|
||||
if (options.threadUuid !== undefined) {
|
||||
response.threadUuid = options.threadUuid;
|
||||
if (options.threadAci !== undefined) {
|
||||
response.threadAci = options.threadAci;
|
||||
}
|
||||
if (options.groupId) {
|
||||
response.groupId = options.groupId;
|
||||
|
@ -1612,7 +1599,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1629,7 +1616,7 @@ export default class MessageSender {
|
|||
installed: boolean;
|
||||
}>
|
||||
): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
||||
|
||||
const packOperations = operations.map(item => {
|
||||
|
@ -1653,7 +1640,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1665,13 +1652,13 @@ export default class MessageSender {
|
|||
|
||||
static getVerificationSync(
|
||||
destinationE164: string | undefined,
|
||||
destinationUuid: string | undefined,
|
||||
destinationAci: AciString | undefined,
|
||||
state: number,
|
||||
identityKey: Readonly<Uint8Array>
|
||||
): SingleProtoJobData {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const myAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
if (!destinationE164 && !destinationUuid) {
|
||||
if (!destinationE164 && !destinationAci) {
|
||||
throw new Error('syncVerification: Neither e164 nor UUID were provided');
|
||||
}
|
||||
|
||||
|
@ -1682,8 +1669,8 @@ export default class MessageSender {
|
|||
if (destinationE164) {
|
||||
verified.destination = destinationE164;
|
||||
}
|
||||
if (destinationUuid) {
|
||||
verified.destinationUuid = destinationUuid;
|
||||
if (destinationAci) {
|
||||
verified.destinationAci = destinationAci;
|
||||
}
|
||||
verified.identityKey = identityKey;
|
||||
verified.nullMessage = padding;
|
||||
|
@ -1698,7 +1685,7 @@ export default class MessageSender {
|
|||
|
||||
return {
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
identifier: myUuid.toString(),
|
||||
identifier: myAci,
|
||||
isSyncMessage: true,
|
||||
protoBase64: Bytes.toBase64(
|
||||
Proto.Content.encode(contentMessage).finish()
|
||||
|
@ -1711,17 +1698,17 @@ export default class MessageSender {
|
|||
// Sending messages to contacts
|
||||
|
||||
async sendCallingMessage(
|
||||
recipientId: string,
|
||||
serviceId: ServiceIdString,
|
||||
callingMessage: Readonly<Proto.ICallingMessage>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const recipients = [recipientId];
|
||||
const recipients = [serviceId];
|
||||
const finalTimestamp = Date.now();
|
||||
|
||||
const contentMessage = new Proto.Content();
|
||||
contentMessage.callingMessage = callingMessage;
|
||||
|
||||
const conversation = window.ConversationController.get(recipientId);
|
||||
const conversation = window.ConversationController.get(serviceId);
|
||||
|
||||
addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
|
@ -1744,8 +1731,7 @@ export default class MessageSender {
|
|||
|
||||
async sendDeliveryReceipt(
|
||||
options: Readonly<{
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
senderServiceId: ServiceIdString;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
|
@ -1759,8 +1745,7 @@ export default class MessageSender {
|
|||
|
||||
async sendReadReceipt(
|
||||
options: Readonly<{
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
senderServiceId: AciString;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
|
@ -1774,8 +1759,7 @@ export default class MessageSender {
|
|||
|
||||
async sendViewedReceipt(
|
||||
options: Readonly<{
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
senderServiceId: ServiceIdString;
|
||||
timestamps: Array<number>;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
|
@ -1788,26 +1772,18 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
private async sendReceiptMessage({
|
||||
senderE164,
|
||||
senderUuid,
|
||||
senderServiceId,
|
||||
timestamps,
|
||||
type,
|
||||
isDirectConversation,
|
||||
options,
|
||||
}: Readonly<{
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
senderServiceId: ServiceIdString;
|
||||
timestamps: Array<number>;
|
||||
type: Proto.ReceiptMessage.Type;
|
||||
isDirectConversation: boolean;
|
||||
options?: Readonly<SendOptionsType>;
|
||||
}>): Promise<CallbackResultType> {
|
||||
if (!senderUuid && !senderE164) {
|
||||
throw new Error(
|
||||
'sendReceiptMessage: Neither uuid nor e164 was provided!'
|
||||
);
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
|
||||
const receiptMessage = new Proto.ReceiptMessage();
|
||||
|
@ -1820,9 +1796,7 @@ export default class MessageSender {
|
|||
contentMessage.receiptMessage = receiptMessage;
|
||||
|
||||
if (isDirectConversation) {
|
||||
const conversation = window.ConversationController.get(
|
||||
senderUuid || senderE164
|
||||
);
|
||||
const conversation = window.ConversationController.get(senderServiceId);
|
||||
|
||||
addPniSignatureMessageToProto({
|
||||
conversation,
|
||||
|
@ -1834,7 +1808,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: senderUuid || senderE164,
|
||||
serviceId: senderServiceId,
|
||||
proto: contentMessage,
|
||||
timestamp,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1882,25 +1856,25 @@ export default class MessageSender {
|
|||
let initialSavePromise: Promise<number>;
|
||||
|
||||
return async ({
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds,
|
||||
}: {
|
||||
identifier: string;
|
||||
serviceId: ServiceIdString;
|
||||
deviceIds: Array<number>;
|
||||
}) => {
|
||||
if (!shouldSaveProto(sendType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
const conversation = window.ConversationController.get(serviceId);
|
||||
if (!conversation) {
|
||||
log.warn(
|
||||
`makeSendLogCallback: Unable to find conversation for identifier ${identifier}`
|
||||
`makeSendLogCallback: Unable to find conversation for serviceId ${serviceId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const recipientUuid = conversation.get('uuid');
|
||||
if (!recipientUuid) {
|
||||
const recipientServiceId = conversation.get('uuid');
|
||||
if (!recipientServiceId) {
|
||||
log.warn(
|
||||
`makeSendLogCallback: Conversation ${conversation.idForLogging()} had no UUID`
|
||||
);
|
||||
|
@ -1917,7 +1891,7 @@ export default class MessageSender {
|
|||
hasPniSignatureMessage,
|
||||
},
|
||||
{
|
||||
recipients: { [recipientUuid]: deviceIds },
|
||||
recipients: { [recipientServiceId]: deviceIds },
|
||||
messageIds: messageId ? [messageId] : [],
|
||||
}
|
||||
);
|
||||
|
@ -1926,7 +1900,7 @@ export default class MessageSender {
|
|||
const id = await initialSavePromise;
|
||||
await window.Signal.Data.insertProtoRecipients({
|
||||
id,
|
||||
recipientUuid,
|
||||
recipientServiceId,
|
||||
deviceIds,
|
||||
});
|
||||
}
|
||||
|
@ -1949,17 +1923,17 @@ export default class MessageSender {
|
|||
groupId: string | undefined;
|
||||
options?: SendOptionsType;
|
||||
proto: Proto.Content;
|
||||
recipients: ReadonlyArray<string>;
|
||||
recipients: ReadonlyArray<ServiceIdString>;
|
||||
sendLogCallback?: SendLogCallbackType;
|
||||
story?: boolean;
|
||||
timestamp: number;
|
||||
urgent: boolean;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
const identifiers = recipients.filter(id => id !== myE164 && id !== myUuid);
|
||||
const myAci = window.textsecure.storage.user.getAci();
|
||||
const serviceIds = recipients.filter(id => id !== myE164 && id !== myAci);
|
||||
|
||||
if (identifiers.length === 0) {
|
||||
if (serviceIds.length === 0) {
|
||||
const dataMessage = proto.dataMessage
|
||||
? Proto.DataMessage.encode(proto.dataMessage).finish()
|
||||
: undefined;
|
||||
|
@ -1972,8 +1946,8 @@ export default class MessageSender {
|
|||
dataMessage,
|
||||
editMessage,
|
||||
errors: [],
|
||||
failoverIdentifiers: [],
|
||||
successfulIdentifiers: [],
|
||||
failoverServiceIds: [],
|
||||
successfulServiceIds: [],
|
||||
unidentifiedDeliveries: [],
|
||||
contentHint,
|
||||
urgent,
|
||||
|
@ -1996,7 +1970,7 @@ export default class MessageSender {
|
|||
groupId,
|
||||
options,
|
||||
proto,
|
||||
recipients: identifiers,
|
||||
recipients: serviceIds,
|
||||
sendLogCallback,
|
||||
story,
|
||||
timestamp,
|
||||
|
@ -2013,26 +1987,26 @@ export default class MessageSender {
|
|||
timestamp,
|
||||
}: { throwIfNotInDatabase?: boolean; timestamp: number }
|
||||
): Promise<Proto.Content> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const ourDeviceId = parseIntOrThrow(
|
||||
window.textsecure.storage.user.getDeviceId(),
|
||||
'getSenderKeyDistributionMessage'
|
||||
);
|
||||
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
ourUuid.toString(),
|
||||
ourDeviceId
|
||||
);
|
||||
const protocolAddress = ProtocolAddress.new(ourAci, ourDeviceId);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(ourUuid, ourDeviceId)
|
||||
ourAci,
|
||||
new Address(ourAci, ourDeviceId)
|
||||
);
|
||||
|
||||
const senderKeyDistributionMessage =
|
||||
await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||
address,
|
||||
async () => {
|
||||
const senderKeyStore = new SenderKeys({ ourUuid, zone: GLOBAL_ZONE });
|
||||
const senderKeyStore = new SenderKeys({
|
||||
ourServiceId: ourAci,
|
||||
zone: GLOBAL_ZONE,
|
||||
});
|
||||
|
||||
if (throwIfNotInDatabase) {
|
||||
const key = await senderKeyStore.getSenderKey(
|
||||
|
@ -2070,7 +2044,7 @@ export default class MessageSender {
|
|||
contentHint,
|
||||
distributionId,
|
||||
groupId,
|
||||
identifiers,
|
||||
serviceIds,
|
||||
throwIfNotInDatabase,
|
||||
story,
|
||||
urgent,
|
||||
|
@ -2078,7 +2052,7 @@ export default class MessageSender {
|
|||
contentHint?: number;
|
||||
distributionId: string;
|
||||
groupId: string | undefined;
|
||||
identifiers: ReadonlyArray<string>;
|
||||
serviceIds: ReadonlyArray<ServiceIdString>;
|
||||
throwIfNotInDatabase?: boolean;
|
||||
story?: boolean;
|
||||
urgent: boolean;
|
||||
|
@ -2096,7 +2070,7 @@ export default class MessageSender {
|
|||
);
|
||||
|
||||
const sendLogCallback =
|
||||
identifiers.length > 1
|
||||
serviceIds.length > 1
|
||||
? this.makeSendLogCallback({
|
||||
contentHint: contentHint ?? ContentHint.IMPLICIT,
|
||||
proto: Buffer.from(Proto.Content.encode(contentMessage).finish()),
|
||||
|
@ -2112,7 +2086,7 @@ export default class MessageSender {
|
|||
groupId,
|
||||
options,
|
||||
proto: contentMessage,
|
||||
recipients: identifiers,
|
||||
recipients: serviceIds,
|
||||
sendLogCallback,
|
||||
story,
|
||||
timestamp,
|
||||
|
@ -2126,14 +2100,14 @@ export default class MessageSender {
|
|||
// directly to window.textsecure.messaging.server.<function>
|
||||
|
||||
async getProfile(
|
||||
uuid: UUID,
|
||||
serviceId: ServiceIdString,
|
||||
options: GetProfileOptionsType | GetProfileUnauthOptionsType
|
||||
): ReturnType<WebAPIType['getProfile']> {
|
||||
if (options.accessKey !== undefined) {
|
||||
return this.server.getProfileUnauth(uuid.toString(), options);
|
||||
return this.server.getProfileUnauth(serviceId, options);
|
||||
}
|
||||
|
||||
return this.server.getProfile(uuid.toString(), options);
|
||||
return this.server.getProfile(serviceId, options);
|
||||
}
|
||||
|
||||
async getAvatar(path: string): Promise<ReturnType<WebAPIType['getAvatar']>> {
|
||||
|
|
30
ts/textsecure/Types.d.ts
vendored
30
ts/textsecure/Types.d.ts
vendored
|
@ -3,7 +3,7 @@
|
|||
|
||||
import type { SignalService as Proto } from '../protobuf';
|
||||
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
||||
import type { UUID, UUIDStringType, TaggedUUIDStringType } from '../types/UUID';
|
||||
import type { ServiceIdString, PniString } from '../types/ServiceId';
|
||||
import type { TextAttachmentType } from '../types/Attachment';
|
||||
import type { GiftBadgeStates } from '../components/conversation/Message';
|
||||
import type { MIMEType } from '../types/MIME';
|
||||
|
@ -44,7 +44,7 @@ export type WebAPICredentials = {
|
|||
|
||||
export type DeviceType = {
|
||||
id: number;
|
||||
identifier: string;
|
||||
serviceId: ServiceIdString;
|
||||
registrationId: number;
|
||||
};
|
||||
|
||||
|
@ -88,10 +88,10 @@ export type ProcessedEnvelope = Readonly<{
|
|||
// Mostly from Proto.Envelope except for null/undefined
|
||||
type: Proto.Envelope.Type;
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceServiceId?: ServiceIdString;
|
||||
sourceDevice?: number;
|
||||
destinationUuid: UUID;
|
||||
updatedPni?: UUID;
|
||||
destinationServiceId: ServiceIdString;
|
||||
updatedPni?: PniString;
|
||||
timestamp: number;
|
||||
content?: Uint8Array;
|
||||
serverGuid: string;
|
||||
|
@ -138,7 +138,7 @@ export type ProcessedQuoteAttachment = {
|
|||
|
||||
export type ProcessedQuote = {
|
||||
id?: number;
|
||||
authorUuid?: string;
|
||||
authorAci?: string;
|
||||
text?: string;
|
||||
attachments: ReadonlyArray<ProcessedQuoteAttachment>;
|
||||
bodyRanges?: ReadonlyArray<ProcessedBodyRange>;
|
||||
|
@ -173,7 +173,7 @@ export type ProcessedSticker = {
|
|||
export type ProcessedReaction = {
|
||||
emoji?: string;
|
||||
remove: boolean;
|
||||
targetAuthorUuid?: string;
|
||||
targetAuthorAci?: string;
|
||||
targetTimestamp?: number;
|
||||
};
|
||||
|
||||
|
@ -225,7 +225,7 @@ export type ProcessedUnidentifiedDeliveryStatus = Omit<
|
|||
Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus,
|
||||
'destinationAci' | 'destinationPni'
|
||||
> & {
|
||||
destinationUuid?: TaggedUUIDStringType;
|
||||
destinationServiceId?: ServiceIdString;
|
||||
isAllowedToReplyToStory?: boolean;
|
||||
};
|
||||
|
||||
|
@ -233,7 +233,7 @@ export type ProcessedStoryMessageRecipient = Omit<
|
|||
Proto.SyncMessage.Sent.IStoryMessageRecipient,
|
||||
'destinationAci' | 'destinationPni'
|
||||
> & {
|
||||
destinationUuid?: TaggedUUIDStringType;
|
||||
destinationServiceId?: ServiceIdString;
|
||||
};
|
||||
|
||||
export type ProcessedSent = Omit<
|
||||
|
@ -245,7 +245,7 @@ export type ProcessedSent = Omit<
|
|||
| 'destinationPni'
|
||||
> & {
|
||||
destinationId?: string;
|
||||
destinationUuid?: TaggedUUIDStringType;
|
||||
destinationServiceId?: ServiceIdString;
|
||||
unidentifiedStatus?: Array<ProcessedUnidentifiedDeliveryStatus>;
|
||||
storyMessageRecipients?: Array<ProcessedStoryMessageRecipient>;
|
||||
};
|
||||
|
@ -260,10 +260,10 @@ export type CustomError = Error & {
|
|||
};
|
||||
|
||||
export type CallbackResultType = {
|
||||
successfulIdentifiers?: Array<string>;
|
||||
failoverIdentifiers?: Array<string>;
|
||||
successfulServiceIds?: Array<ServiceIdString>;
|
||||
failoverServiceIds?: Array<ServiceIdString>;
|
||||
errors?: Array<CustomError>;
|
||||
unidentifiedDeliveries?: Array<string>;
|
||||
unidentifiedDeliveries?: Array<ServiceIdString>;
|
||||
dataMessage: Uint8Array | undefined;
|
||||
editMessage: Uint8Array | undefined;
|
||||
|
||||
|
@ -275,7 +275,7 @@ export type CallbackResultType = {
|
|||
contentHint?: number;
|
||||
contentProto?: Uint8Array;
|
||||
timestamp?: number;
|
||||
recipients?: Record<string, Array<number>>;
|
||||
recipients?: Record<ServiceIdString, Array<number>>;
|
||||
urgent?: boolean;
|
||||
hasPniSignatureMessage?: boolean;
|
||||
};
|
||||
|
@ -292,6 +292,6 @@ export type PniKeyMaterialType = Readonly<{
|
|||
}>;
|
||||
|
||||
export type PniSignatureMessageType = Readonly<{
|
||||
pni: UUIDStringType;
|
||||
pni: PniString;
|
||||
signature: Uint8Array;
|
||||
}>;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { isNumber } from 'lodash';
|
|||
import * as durations from '../util/durations';
|
||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||
import * as Registration from '../util/registration';
|
||||
import { UUIDKind } from '../types/UUID';
|
||||
import { ServiceIdKind } from '../types/ServiceId';
|
||||
import * as log from '../logging/log';
|
||||
import * as Errors from '../types/errors';
|
||||
|
||||
|
@ -56,8 +56,8 @@ export class UpdateKeysListener {
|
|||
try {
|
||||
const accountManager = window.getAccountManager();
|
||||
|
||||
await accountManager.maybeUpdateKeys(UUIDKind.ACI);
|
||||
await accountManager.maybeUpdateKeys(UUIDKind.PNI);
|
||||
await accountManager.maybeUpdateKeys(ServiceIdKind.ACI);
|
||||
await accountManager.maybeUpdateKeys(ServiceIdKind.PNI);
|
||||
|
||||
this.scheduleNextUpdate();
|
||||
this.setTimeoutForNextRun();
|
||||
|
|
|
@ -32,8 +32,12 @@ import { createHTTPSAgent } from '../util/createHTTPSAgent';
|
|||
import type { SocketStatus } from '../types/SocketStatus';
|
||||
import { toLogFormat } from '../types/errors';
|
||||
import { isPackIdValid, redactPackId } from '../types/Stickers';
|
||||
import type { UUID, UUIDStringType } from '../types/UUID';
|
||||
import { UUIDKind } from '../types/UUID';
|
||||
import type { ServiceIdString, AciString, PniString } from '../types/ServiceId';
|
||||
import {
|
||||
ServiceIdKind,
|
||||
isAciString,
|
||||
isServiceIdString,
|
||||
} from '../types/ServiceId';
|
||||
import type { DirectoryConfigType } from '../types/RendererConfig';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { randomInt } from '../Crypto';
|
||||
|
@ -177,8 +181,24 @@ type BytesWithDetailsType = {
|
|||
response: Response;
|
||||
};
|
||||
|
||||
const aciSchema = z
|
||||
.string()
|
||||
.refine(isAciString)
|
||||
.transform(x => {
|
||||
assertDev(isAciString(x), 'Refine did not throw!');
|
||||
return x;
|
||||
});
|
||||
|
||||
const serviceIdSchema = z
|
||||
.string()
|
||||
.refine(isServiceIdString)
|
||||
.transform(x => {
|
||||
assertDev(isServiceIdString(x), 'Refine did not throw!');
|
||||
return x;
|
||||
});
|
||||
|
||||
export const multiRecipient200ResponseSchema = z.object({
|
||||
uuids404: z.array(z.string()).optional(),
|
||||
uuids404: z.array(serviceIdSchema).optional(),
|
||||
needsSync: z.boolean().optional(),
|
||||
});
|
||||
export type MultiRecipient200ResponseType = z.infer<
|
||||
|
@ -187,7 +207,7 @@ export type MultiRecipient200ResponseType = z.infer<
|
|||
|
||||
export const multiRecipient409ResponseSchema = z.array(
|
||||
z.object({
|
||||
uuid: z.string(),
|
||||
uuid: serviceIdSchema,
|
||||
devices: z.object({
|
||||
missingDevices: z.array(z.number()).optional(),
|
||||
extraDevices: z.array(z.number()).optional(),
|
||||
|
@ -200,7 +220,7 @@ export type MultiRecipient409ResponseType = z.infer<
|
|||
|
||||
export const multiRecipient410ResponseSchema = z.array(
|
||||
z.object({
|
||||
uuid: z.string(),
|
||||
uuid: serviceIdSchema,
|
||||
devices: z.object({
|
||||
staleDevices: z.array(z.number()).optional(),
|
||||
}),
|
||||
|
@ -708,7 +728,7 @@ export type GetAccountForUsernameOptionsType = Readonly<{
|
|||
}>;
|
||||
|
||||
const getAccountForUsernameResultZod = z.object({
|
||||
uuid: z.string(),
|
||||
uuid: aciSchema,
|
||||
});
|
||||
|
||||
export type GetAccountForUsernameResultType = z.infer<
|
||||
|
@ -748,14 +768,14 @@ const whoamiResultZod = z.object({
|
|||
export type WhoamiResultType = z.infer<typeof whoamiResultZod>;
|
||||
|
||||
export type ConfirmCodeResultType = Readonly<{
|
||||
uuid: UUIDStringType;
|
||||
pni: UUIDStringType;
|
||||
uuid: AciString;
|
||||
pni: PniString;
|
||||
deviceId?: number;
|
||||
}>;
|
||||
|
||||
export type CdsLookupOptionsType = Readonly<{
|
||||
e164s: ReadonlyArray<string>;
|
||||
acis?: ReadonlyArray<UUIDStringType>;
|
||||
acis?: ReadonlyArray<AciString>;
|
||||
accessKeys?: ReadonlyArray<string>;
|
||||
returnAcisWithoutUaks?: boolean;
|
||||
}>;
|
||||
|
@ -795,17 +815,22 @@ export type GetGroupCredentialsResultType = Readonly<{
|
|||
credentials: ReadonlyArray<GroupCredentialType>;
|
||||
}>;
|
||||
|
||||
const verifyAciResponse = z.object({
|
||||
const verifyServiceIdResponse = z.object({
|
||||
elements: z.array(
|
||||
z.object({
|
||||
aci: z.string(),
|
||||
uuid: serviceIdSchema,
|
||||
identityKey: z.string(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type VerifyAciRequestType = Array<{ aci: string; fingerprint: string }>;
|
||||
export type VerifyAciResponseType = z.infer<typeof verifyAciResponse>;
|
||||
export type VerifyServiceIdRequestType = Array<{
|
||||
uuid: ServiceIdString;
|
||||
fingerprint: string;
|
||||
}>;
|
||||
export type VerifyServiceIdResponseType = z.infer<
|
||||
typeof verifyServiceIdResponse
|
||||
>;
|
||||
|
||||
export type ReserveUsernameOptionsType = Readonly<{
|
||||
hashes: ReadonlyArray<Uint8Array>;
|
||||
|
@ -938,29 +963,29 @@ export type WebAPIType = {
|
|||
credentials: GroupCredentialsType
|
||||
) => Promise<GroupLogResponseType>;
|
||||
getIceServers: () => Promise<GetIceServersResultType>;
|
||||
getKeysForIdentifier: (
|
||||
identifier: string,
|
||||
getKeysForServiceId: (
|
||||
serviceId: ServiceIdString,
|
||||
deviceId?: number
|
||||
) => Promise<ServerKeysType>;
|
||||
getKeysForIdentifierUnauth: (
|
||||
identifier: string,
|
||||
getKeysForServiceIdUnauth: (
|
||||
serviceId: ServiceIdString,
|
||||
deviceId?: number,
|
||||
options?: { accessKey?: string }
|
||||
) => Promise<ServerKeysType>;
|
||||
getMyKeyCounts: (uuidKind: UUIDKind) => Promise<ServerKeyCountType>;
|
||||
getMyKeyCounts: (serviceIdKind: ServiceIdKind) => Promise<ServerKeyCountType>;
|
||||
getOnboardingStoryManifest: () => Promise<{
|
||||
version: string;
|
||||
languages: Record<string, Array<string>>;
|
||||
}>;
|
||||
getProfile: (
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
options: GetProfileOptionsType
|
||||
) => Promise<ProfileType>;
|
||||
getAccountForUsername: (
|
||||
options: GetAccountForUsernameOptionsType
|
||||
) => Promise<GetAccountForUsernameResultType>;
|
||||
getProfileUnauth: (
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
options: GetProfileUnauthOptionsType
|
||||
) => Promise<ProfileType>;
|
||||
getBadgeImageFile: (imageUrl: string) => Promise<Uint8Array>;
|
||||
|
@ -1004,8 +1029,8 @@ export type WebAPIType = {
|
|||
) => Promise<Proto.IGroupChange>;
|
||||
modifyStorageRecords: MessageSender['modifyStorageRecords'];
|
||||
postBatchIdentityCheck: (
|
||||
elements: VerifyAciRequestType
|
||||
) => Promise<VerifyAciResponseType>;
|
||||
elements: VerifyServiceIdRequestType
|
||||
) => Promise<VerifyServiceIdResponseType>;
|
||||
putEncryptedAttachment: (encryptedBin: Uint8Array) => Promise<string>;
|
||||
putProfile: (
|
||||
jsonData: ProfileRequestDataType
|
||||
|
@ -1029,20 +1054,23 @@ export type WebAPIType = {
|
|||
serverId: string
|
||||
) => Promise<ResolveUsernameLinkResultType>;
|
||||
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
||||
registerKeys: (genKeys: UploadKeysType, uuidKind: UUIDKind) => Promise<void>;
|
||||
registerKeys: (
|
||||
genKeys: UploadKeysType,
|
||||
serviceIdKind: ServiceIdKind
|
||||
) => Promise<void>;
|
||||
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
||||
reportMessage: (options: ReportMessageOptionsType) => Promise<void>;
|
||||
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
||||
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
||||
checkAccountExistence: (uuid: UUID) => Promise<boolean>;
|
||||
checkAccountExistence: (serviceId: ServiceIdString) => Promise<boolean>;
|
||||
sendMessages: (
|
||||
destination: string,
|
||||
destination: ServiceIdString,
|
||||
messageArray: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
options: { online?: boolean; story?: boolean; urgent?: boolean }
|
||||
) => Promise<void>;
|
||||
sendMessagesUnauth: (
|
||||
destination: string,
|
||||
destination: ServiceIdString,
|
||||
messageArray: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
options: {
|
||||
|
@ -1335,8 +1363,8 @@ export function initialize({
|
|||
getGroupLog,
|
||||
getHasSubscription,
|
||||
getIceServers,
|
||||
getKeysForIdentifier,
|
||||
getKeysForIdentifierUnauth,
|
||||
getKeysForServiceId,
|
||||
getKeysForServiceIdUnauth,
|
||||
getMyKeyCounts,
|
||||
getOnboardingStoryManifest,
|
||||
getProfile,
|
||||
|
@ -1458,14 +1486,14 @@ export function initialize({
|
|||
}
|
||||
}
|
||||
|
||||
function uuidKindToQuery(kind: UUIDKind): string {
|
||||
function serviceIdKindToQuery(kind: ServiceIdKind): string {
|
||||
let value: string;
|
||||
if (kind === UUIDKind.ACI) {
|
||||
if (kind === ServiceIdKind.ACI) {
|
||||
value = 'aci';
|
||||
} else if (kind === UUIDKind.PNI) {
|
||||
} else if (kind === ServiceIdKind.PNI) {
|
||||
value = 'pni';
|
||||
} else {
|
||||
throw new Error(`Unsupported UUIDKind: ${kind}`);
|
||||
throw new Error(`Unsupported ServiceIdKind: ${kind}`);
|
||||
}
|
||||
return `identity=${value}`;
|
||||
}
|
||||
|
@ -1676,7 +1704,9 @@ export function initialize({
|
|||
});
|
||||
}
|
||||
|
||||
async function postBatchIdentityCheck(elements: VerifyAciRequestType) {
|
||||
async function postBatchIdentityCheck(
|
||||
elements: VerifyServiceIdRequestType
|
||||
) {
|
||||
const res = await _ajax({
|
||||
data: JSON.stringify({ elements }),
|
||||
call: 'batchIdentityCheck',
|
||||
|
@ -1684,7 +1714,7 @@ export function initialize({
|
|||
responseType: 'json',
|
||||
});
|
||||
|
||||
const result = verifyAciResponse.safeParse(res);
|
||||
const result = verifyServiceIdResponse.safeParse(res);
|
||||
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
|
@ -1699,13 +1729,13 @@ export function initialize({
|
|||
}
|
||||
|
||||
function getProfileUrl(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
{
|
||||
profileKeyVersion,
|
||||
profileKeyCredentialRequest,
|
||||
}: GetProfileCommonOptionsType
|
||||
) {
|
||||
let profileUrl = `/${identifier}`;
|
||||
let profileUrl = `/${serviceId}`;
|
||||
if (profileKeyVersion !== undefined) {
|
||||
profileUrl += `/${profileKeyVersion}`;
|
||||
if (profileKeyCredentialRequest !== undefined) {
|
||||
|
@ -1724,7 +1754,7 @@ export function initialize({
|
|||
}
|
||||
|
||||
async function getProfile(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
options: GetProfileOptionsType
|
||||
) {
|
||||
const { profileKeyVersion, profileKeyCredentialRequest, userLanguages } =
|
||||
|
@ -1733,13 +1763,13 @@ export function initialize({
|
|||
return (await _ajax({
|
||||
call: 'profile',
|
||||
httpType: 'GET',
|
||||
urlParameters: getProfileUrl(identifier, options),
|
||||
urlParameters: getProfileUrl(serviceId, options),
|
||||
headers: {
|
||||
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
|
||||
},
|
||||
responseType: 'json',
|
||||
redactUrl: _createRedactor(
|
||||
identifier,
|
||||
serviceId,
|
||||
profileKeyVersion,
|
||||
profileKeyCredentialRequest
|
||||
),
|
||||
|
@ -1781,7 +1811,7 @@ export function initialize({
|
|||
}
|
||||
|
||||
async function getProfileUnauth(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
options: GetProfileUnauthOptionsType
|
||||
) {
|
||||
const {
|
||||
|
@ -1794,7 +1824,7 @@ export function initialize({
|
|||
return (await _ajax({
|
||||
call: 'profile',
|
||||
httpType: 'GET',
|
||||
urlParameters: getProfileUrl(identifier, options),
|
||||
urlParameters: getProfileUrl(serviceId, options),
|
||||
headers: {
|
||||
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
|
||||
},
|
||||
|
@ -1802,7 +1832,7 @@ export function initialize({
|
|||
unauthenticated: true,
|
||||
accessKey,
|
||||
redactUrl: _createRedactor(
|
||||
identifier,
|
||||
serviceId,
|
||||
profileKeyVersion,
|
||||
profileKeyCredentialRequest
|
||||
),
|
||||
|
@ -2007,12 +2037,12 @@ export function initialize({
|
|||
});
|
||||
}
|
||||
|
||||
async function checkAccountExistence(uuid: UUID) {
|
||||
async function checkAccountExistence(serviceId: ServiceIdString) {
|
||||
try {
|
||||
await _ajax({
|
||||
httpType: 'HEAD',
|
||||
call: 'accountExistence',
|
||||
urlParameters: `/${uuid.toString()}`,
|
||||
urlParameters: `/${serviceId}`,
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
});
|
||||
|
@ -2154,7 +2184,10 @@ export function initialize({
|
|||
signedPreKey?: JSONSignedPreKeyType;
|
||||
};
|
||||
|
||||
async function registerKeys(genKeys: UploadKeysType, uuidKind: UUIDKind) {
|
||||
async function registerKeys(
|
||||
genKeys: UploadKeysType,
|
||||
serviceIdKind: ServiceIdKind
|
||||
) {
|
||||
const preKeys = genKeys.preKeys?.map(key => ({
|
||||
keyId: key.keyId,
|
||||
publicKey: Bytes.toBase64(key.publicKey),
|
||||
|
@ -2209,7 +2242,7 @@ export function initialize({
|
|||
await _ajax({
|
||||
isRegistration: true,
|
||||
call: 'keys',
|
||||
urlParameters: `?${uuidKindToQuery(uuidKind)}`,
|
||||
urlParameters: `?${serviceIdKindToQuery(serviceIdKind)}`,
|
||||
httpType: 'PUT',
|
||||
jsonData: keys,
|
||||
});
|
||||
|
@ -2226,11 +2259,11 @@ export function initialize({
|
|||
}
|
||||
|
||||
async function getMyKeyCounts(
|
||||
uuidKind: UUIDKind
|
||||
serviceIdKind: ServiceIdKind
|
||||
): Promise<ServerKeyCountType> {
|
||||
const result = (await _ajax({
|
||||
call: 'keys',
|
||||
urlParameters: `?${uuidKindToQuery(uuidKind)}`,
|
||||
urlParameters: `?${serviceIdKindToQuery(serviceIdKind)}`,
|
||||
httpType: 'GET',
|
||||
responseType: 'json',
|
||||
validateResponse: { count: 'number', pqCount: 'number' },
|
||||
|
@ -2325,26 +2358,29 @@ export function initialize({
|
|||
};
|
||||
}
|
||||
|
||||
async function getKeysForIdentifier(identifier: string, deviceId?: number) {
|
||||
async function getKeysForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
deviceId?: number
|
||||
) {
|
||||
const keys = (await _ajax({
|
||||
call: 'keys',
|
||||
httpType: 'GET',
|
||||
urlParameters: `/${identifier}/${deviceId || '*'}?pq=true`,
|
||||
urlParameters: `/${serviceId}/${deviceId || '*'}?pq=true`,
|
||||
responseType: 'json',
|
||||
validateResponse: { identityKey: 'string', devices: 'object' },
|
||||
})) as ServerKeyResponseType;
|
||||
return handleKeys(keys);
|
||||
}
|
||||
|
||||
async function getKeysForIdentifierUnauth(
|
||||
identifier: string,
|
||||
async function getKeysForServiceIdUnauth(
|
||||
serviceId: ServiceIdString,
|
||||
deviceId?: number,
|
||||
{ accessKey }: { accessKey?: string } = {}
|
||||
) {
|
||||
const keys = (await _ajax({
|
||||
call: 'keys',
|
||||
httpType: 'GET',
|
||||
urlParameters: `/${identifier}/${deviceId || '*'}?pq=true`,
|
||||
urlParameters: `/${serviceId}/${deviceId || '*'}?pq=true`,
|
||||
responseType: 'json',
|
||||
validateResponse: { identityKey: 'string', devices: 'object' },
|
||||
unauthenticated: true,
|
||||
|
@ -2354,7 +2390,7 @@ export function initialize({
|
|||
}
|
||||
|
||||
async function sendMessagesUnauth(
|
||||
destination: string,
|
||||
destination: ServiceIdString,
|
||||
messages: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
{
|
||||
|
@ -2388,7 +2424,7 @@ export function initialize({
|
|||
}
|
||||
|
||||
async function sendMessages(
|
||||
destination: string,
|
||||
destination: ServiceIdString,
|
||||
messages: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
{
|
||||
|
|
|
@ -9,8 +9,9 @@ import Long from 'long';
|
|||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { UUID_BYTE_SIZE } from '../../types/UUID';
|
||||
import { isAciString, isUntaggedPniString } from '../../types/ServiceId';
|
||||
import * as Bytes from '../../Bytes';
|
||||
import { UUID_BYTE_SIZE } from '../../Crypto';
|
||||
import { uuidToBytes, bytesToUuid } from '../../util/uuidToBytes';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import type {
|
||||
|
@ -246,6 +247,14 @@ function decodeSingleResponse(
|
|||
const e164 = `+${e164Long.toString()}`;
|
||||
const pni = bytesToUuid(pniBytes);
|
||||
const aci = bytesToUuid(aciBytes);
|
||||
strictAssert(
|
||||
aci === undefined || isAciString(aci),
|
||||
'CDSI response has invalid ACI'
|
||||
);
|
||||
strictAssert(
|
||||
pni === undefined || isUntaggedPniString(pni),
|
||||
'CDSI response has invalid PNI'
|
||||
);
|
||||
|
||||
resultMap.set(e164, { pni, aci });
|
||||
}
|
||||
|
|
8
ts/textsecure/cds/Types.d.ts
vendored
8
ts/textsecure/cds/Types.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import type { AciString, UntaggedPniString } from '../../types/ServiceId';
|
||||
|
||||
export type CDSAuthType = Readonly<{
|
||||
username: string;
|
||||
|
@ -9,15 +9,15 @@ export type CDSAuthType = Readonly<{
|
|||
}>;
|
||||
|
||||
export type CDSResponseEntryType = Readonly<{
|
||||
aci: UUIDStringType | undefined;
|
||||
pni: UUIDStringType | undefined;
|
||||
aci: AciString | undefined;
|
||||
pni: UntaggedPniString | undefined;
|
||||
}>;
|
||||
|
||||
export type CDSResponseType = ReadonlyMap<string, CDSResponseEntryType>;
|
||||
|
||||
export type CDSRequestOptionsType = Readonly<{
|
||||
e164s: ReadonlyArray<string>;
|
||||
acis: ReadonlyArray<UUIDStringType>;
|
||||
acis: ReadonlyArray<AciString>;
|
||||
accessKeys: ReadonlyArray<string>;
|
||||
returnAcisWithoutUaks?: boolean;
|
||||
timeout?: number;
|
||||
|
|
|
@ -19,38 +19,34 @@ import {
|
|||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import type { ServerKeysType, WebAPIType } from './WebAPI';
|
||||
import * as log from '../logging/log';
|
||||
import { isRecord } from '../util/isRecord';
|
||||
|
||||
export async function getKeysForIdentifier(
|
||||
identifier: string,
|
||||
export async function getKeysForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
server: WebAPIType,
|
||||
devicesToUpdate?: Array<number>,
|
||||
accessKey?: string
|
||||
): Promise<{ accessKeyFailed?: boolean }> {
|
||||
try {
|
||||
const { keys, accessKeyFailed } = await getServerKeys(
|
||||
identifier,
|
||||
serviceId,
|
||||
server,
|
||||
accessKey
|
||||
);
|
||||
|
||||
await handleServerKeys(identifier, keys, devicesToUpdate);
|
||||
await handleServerKeys(serviceId, keys, devicesToUpdate);
|
||||
|
||||
return {
|
||||
accessKeyFailed,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPError && error.code === 404) {
|
||||
const theirUuid = UUID.lookup(identifier);
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(serviceId);
|
||||
|
||||
if (theirUuid) {
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(theirUuid);
|
||||
}
|
||||
|
||||
throw new UnregisteredUserError(identifier, error);
|
||||
throw new UnregisteredUserError(serviceId, error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -58,19 +54,19 @@ export async function getKeysForIdentifier(
|
|||
}
|
||||
|
||||
async function getServerKeys(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
server: WebAPIType,
|
||||
accessKey?: string
|
||||
): Promise<{ accessKeyFailed?: boolean; keys: ServerKeysType }> {
|
||||
try {
|
||||
if (!accessKey) {
|
||||
return {
|
||||
keys: await server.getKeysForIdentifier(identifier),
|
||||
keys: await server.getKeysForServiceId(serviceId),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
keys: await server.getKeysForIdentifierUnauth(identifier, undefined, {
|
||||
keys: await server.getKeysForServiceIdUnauth(serviceId, undefined, {
|
||||
accessKey,
|
||||
}),
|
||||
};
|
||||
|
@ -83,7 +79,7 @@ async function getServerKeys(
|
|||
) {
|
||||
return {
|
||||
accessKeyFailed: true,
|
||||
keys: await server.getKeysForIdentifier(identifier),
|
||||
keys: await server.getKeysForServiceId(serviceId),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -92,13 +88,13 @@ async function getServerKeys(
|
|||
}
|
||||
|
||||
async function handleServerKeys(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
response: ServerKeysType,
|
||||
devicesToUpdate?: Array<number>
|
||||
): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const sessionStore = new Sessions({ ourServiceId: ourAci });
|
||||
const identityKeyStore = new IdentityKeys({ ourServiceId: ourAci });
|
||||
|
||||
await Promise.all(
|
||||
response.devices.map(async device => {
|
||||
|
@ -113,19 +109,15 @@ async function handleServerKeys(
|
|||
|
||||
if (device.registrationId === 0) {
|
||||
log.info(
|
||||
`handleServerKeys/${identifier}: Got device registrationId zero!`
|
||||
`handleServerKeys/${serviceId}: Got device registrationId zero!`
|
||||
);
|
||||
}
|
||||
if (!signedPreKey) {
|
||||
throw new Error(
|
||||
`getKeysForIdentifier/${identifier}: Missing signed prekey for deviceId ${deviceId}`
|
||||
`getKeysForIdentifier/${serviceId}: Missing signed prekey for deviceId ${deviceId}`
|
||||
);
|
||||
}
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
theirUuid.toString(),
|
||||
deviceId
|
||||
);
|
||||
const protocolAddress = ProtocolAddress.new(serviceId, deviceId);
|
||||
const preKeyId = preKey?.keyId || null;
|
||||
const preKeyObject = preKey
|
||||
? PublicKey.deserialize(Buffer.from(preKey.publicKey))
|
||||
|
@ -160,14 +152,14 @@ async function handleServerKeys(
|
|||
);
|
||||
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(theirUuid, deviceId)
|
||||
ourAci,
|
||||
new Address(serviceId, deviceId)
|
||||
);
|
||||
|
||||
try {
|
||||
await window.textsecure.storage.protocol.enqueueSessionJob(
|
||||
address,
|
||||
`handleServerKeys(${identifier})`,
|
||||
`handleServerKeys(${serviceId})`,
|
||||
() =>
|
||||
processPreKeyBundle(
|
||||
preKeyBundle,
|
||||
|
@ -181,7 +173,7 @@ async function handleServerKeys(
|
|||
error instanceof LibSignalErrorBase &&
|
||||
error.code === ErrorCode.UntrustedIdentity
|
||||
) {
|
||||
throw new OutgoingIdentityKeyError(identifier, error);
|
||||
throw new OutgoingIdentityKeyError(serviceId, error);
|
||||
}
|
||||
throw error;
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
import type { PublicKey } from '@signalapp/libsignal-client';
|
||||
|
||||
import type { SignalService as Proto } from '../protobuf';
|
||||
import type { UUIDStringType, TaggedUUIDStringType } from '../types/UUID';
|
||||
import type { ServiceIdString, AciString } from '../types/ServiceId';
|
||||
import type { StoryDistributionIdString } from '../types/StoryDistributionId';
|
||||
import type {
|
||||
ProcessedEnvelope,
|
||||
ProcessedDataMessage,
|
||||
|
@ -41,7 +42,7 @@ export type TypingEventData = Readonly<{
|
|||
|
||||
export type TypingEventConfig = {
|
||||
sender?: string;
|
||||
senderUuid?: string;
|
||||
senderAci?: AciString;
|
||||
senderDevice: number;
|
||||
typing: TypingEventData;
|
||||
};
|
||||
|
@ -49,17 +50,17 @@ export type TypingEventConfig = {
|
|||
export class TypingEvent extends Event {
|
||||
public readonly sender?: string;
|
||||
|
||||
public readonly senderUuid?: string;
|
||||
public readonly senderAci?: AciString;
|
||||
|
||||
public readonly senderDevice: number;
|
||||
|
||||
public readonly typing: TypingEventData;
|
||||
|
||||
constructor({ sender, senderUuid, senderDevice, typing }: TypingEventConfig) {
|
||||
constructor({ sender, senderAci, senderDevice, typing }: TypingEventConfig) {
|
||||
super('typing');
|
||||
|
||||
this.sender = sender;
|
||||
this.senderUuid = senderUuid;
|
||||
this.senderAci = senderAci;
|
||||
this.senderDevice = senderDevice;
|
||||
this.typing = typing;
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ export type DeliveryEventData = Readonly<{
|
|||
timestamp: number;
|
||||
envelopeTimestamp: number;
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceServiceId?: ServiceIdString;
|
||||
sourceDevice?: number;
|
||||
wasSentEncrypted: boolean;
|
||||
}>;
|
||||
|
@ -134,7 +135,7 @@ export type DecryptionErrorEventData = Readonly<{
|
|||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
senderDevice: number;
|
||||
senderUuid: string;
|
||||
senderAci: AciString;
|
||||
timestamp: number;
|
||||
}>;
|
||||
|
||||
|
@ -149,7 +150,7 @@ export class DecryptionErrorEvent extends ConfirmableEvent {
|
|||
|
||||
export type InvalidPlaintextEventData = Readonly<{
|
||||
senderDevice: number;
|
||||
senderUuid: string;
|
||||
senderAci: AciString;
|
||||
timestamp: number;
|
||||
}>;
|
||||
|
||||
|
@ -162,7 +163,7 @@ export class InvalidPlaintextEvent extends Event {
|
|||
export type RetryRequestEventData = Readonly<{
|
||||
groupId?: string;
|
||||
ratchetKey?: PublicKey;
|
||||
requesterUuid: UUIDStringType;
|
||||
requesterAci: AciString;
|
||||
requesterDevice: number;
|
||||
senderDevice: number;
|
||||
sentAt: number;
|
||||
|
@ -180,7 +181,7 @@ export class RetryRequestEvent extends ConfirmableEvent {
|
|||
export type SentEventData = Readonly<{
|
||||
envelopeId: string;
|
||||
destination?: string;
|
||||
destinationUuid?: TaggedUUIDStringType;
|
||||
destinationServiceId?: ServiceIdString;
|
||||
timestamp?: number;
|
||||
serverTimestamp?: number;
|
||||
device: number | undefined;
|
||||
|
@ -190,7 +191,7 @@ export type SentEventData = Readonly<{
|
|||
receivedAtCounter: number;
|
||||
receivedAtDate: number;
|
||||
expirationStartTimestamp?: number;
|
||||
storyDistributionListId?: string;
|
||||
storyDistributionListId?: StoryDistributionIdString;
|
||||
}>;
|
||||
|
||||
export class SentEvent extends ConfirmableEvent {
|
||||
|
@ -201,7 +202,7 @@ export class SentEvent extends ConfirmableEvent {
|
|||
|
||||
export type ProfileKeyUpdateData = Readonly<{
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceAci?: AciString;
|
||||
profileKey: string;
|
||||
}>;
|
||||
|
||||
|
@ -217,9 +218,9 @@ export class ProfileKeyUpdateEvent extends ConfirmableEvent {
|
|||
export type MessageEventData = Readonly<{
|
||||
envelopeId: string;
|
||||
source?: string;
|
||||
sourceUuid: UUIDStringType;
|
||||
sourceAci: AciString;
|
||||
sourceDevice?: number;
|
||||
destinationUuid: UUIDStringType;
|
||||
destinationServiceId: ServiceIdString;
|
||||
timestamp: number;
|
||||
serverGuid?: string;
|
||||
serverTimestamp?: number;
|
||||
|
@ -242,7 +243,7 @@ export type ReadOrViewEventData = Readonly<{
|
|||
timestamp: number;
|
||||
envelopeTimestamp: number;
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceServiceId?: ServiceIdString;
|
||||
sourceDevice?: number;
|
||||
wasSentEncrypted: true;
|
||||
}>;
|
||||
|
@ -276,32 +277,32 @@ export class ConfigurationEvent extends ConfirmableEvent {
|
|||
|
||||
export type ViewOnceOpenSyncOptions = {
|
||||
source?: string;
|
||||
sourceUuid?: UUIDStringType;
|
||||
sourceAci?: AciString;
|
||||
timestamp?: number;
|
||||
};
|
||||
|
||||
export class ViewOnceOpenSyncEvent extends ConfirmableEvent {
|
||||
public readonly source?: string;
|
||||
|
||||
public readonly sourceUuid?: UUIDStringType;
|
||||
public readonly sourceAci?: AciString;
|
||||
|
||||
public readonly timestamp?: number;
|
||||
|
||||
constructor(
|
||||
{ source, sourceUuid, timestamp }: ViewOnceOpenSyncOptions,
|
||||
{ source, sourceAci, timestamp }: ViewOnceOpenSyncOptions,
|
||||
confirm: ConfirmCallback
|
||||
) {
|
||||
super('viewOnceOpenSync', confirm);
|
||||
|
||||
this.source = source;
|
||||
this.sourceUuid = sourceUuid;
|
||||
this.sourceAci = sourceAci;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageRequestResponseOptions = {
|
||||
threadE164?: string;
|
||||
threadUuid?: string;
|
||||
threadAci?: AciString;
|
||||
messageRequestResponseType: Proto.SyncMessage.IMessageRequestResponse['type'];
|
||||
groupId?: string;
|
||||
groupV2Id?: string;
|
||||
|
@ -310,7 +311,7 @@ export type MessageRequestResponseOptions = {
|
|||
export class MessageRequestResponseEvent extends ConfirmableEvent {
|
||||
public readonly threadE164?: string;
|
||||
|
||||
public readonly threadUuid?: string;
|
||||
public readonly threadAci?: AciString;
|
||||
|
||||
public readonly messageRequestResponseType?: MessageRequestResponseOptions['messageRequestResponseType'];
|
||||
|
||||
|
@ -321,7 +322,7 @@ export class MessageRequestResponseEvent extends ConfirmableEvent {
|
|||
constructor(
|
||||
{
|
||||
threadE164,
|
||||
threadUuid,
|
||||
threadAci,
|
||||
messageRequestResponseType,
|
||||
groupId,
|
||||
groupV2Id,
|
||||
|
@ -331,7 +332,7 @@ export class MessageRequestResponseEvent extends ConfirmableEvent {
|
|||
super('messageRequestResponse', confirm);
|
||||
|
||||
this.threadE164 = threadE164;
|
||||
this.threadUuid = threadUuid;
|
||||
this.threadAci = threadAci;
|
||||
this.messageRequestResponseType = messageRequestResponseType;
|
||||
this.groupId = groupId;
|
||||
this.groupV2Id = groupV2Id;
|
||||
|
@ -376,7 +377,7 @@ export type ReadSyncEventData = Readonly<{
|
|||
timestamp?: number;
|
||||
envelopeTimestamp: number;
|
||||
sender?: string;
|
||||
senderUuid?: string;
|
||||
senderAci?: AciString;
|
||||
}>;
|
||||
|
||||
export class ReadSyncEvent extends ConfirmableEvent {
|
||||
|
@ -392,7 +393,7 @@ export type ViewSyncEventData = Readonly<{
|
|||
timestamp?: number;
|
||||
envelopeTimestamp: number;
|
||||
senderE164?: string;
|
||||
senderUuid?: string;
|
||||
senderAci?: AciString;
|
||||
}>;
|
||||
|
||||
export class ViewSyncEvent extends ConfirmableEvent {
|
||||
|
@ -434,7 +435,7 @@ export class CallLogEventSyncEvent extends ConfirmableEvent {
|
|||
}
|
||||
|
||||
export type StoryRecipientUpdateData = Readonly<{
|
||||
destinationUuid: string;
|
||||
destinationServiceId: ServiceIdString;
|
||||
storyMessageRecipients: Array<Proto.SyncMessage.Sent.IStoryMessageRecipient>;
|
||||
timestamp: number;
|
||||
}>;
|
||||
|
|
|
@ -131,7 +131,7 @@ export function processQuote(
|
|||
|
||||
return {
|
||||
id: quote.id?.toNumber(),
|
||||
authorUuid: dropNull(quote.authorUuid),
|
||||
authorAci: dropNull(quote.authorAci),
|
||||
text: dropNull(quote.text),
|
||||
attachments: (quote.attachments ?? []).map(attachment => {
|
||||
return {
|
||||
|
@ -223,7 +223,7 @@ export function processReaction(
|
|||
return {
|
||||
emoji: dropNull(reaction.emoji),
|
||||
remove: Boolean(reaction.remove),
|
||||
targetAuthorUuid: dropNull(reaction.targetAuthorUuid),
|
||||
targetAuthorAci: dropNull(reaction.targetAuthorAci),
|
||||
targetTimestamp: reaction.targetTimestamp?.toNumber(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,49 +2,27 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import { normalizeServiceId } from '../types/ServiceId';
|
||||
import type { ProcessedSent, ProcessedSyncMessage } from './Types.d';
|
||||
import type { TaggedUUIDStringType } from '../types/UUID';
|
||||
|
||||
type ProtoUUIDTriple = Readonly<{
|
||||
destinationAci?: string | null;
|
||||
destinationPni?: string | null;
|
||||
type ProtoServiceId = Readonly<{
|
||||
destinationServiceId?: string | null;
|
||||
}>;
|
||||
|
||||
function toTaggedUuid({
|
||||
destinationAci,
|
||||
destinationPni,
|
||||
}: ProtoUUIDTriple): TaggedUUIDStringType | undefined {
|
||||
if (destinationAci) {
|
||||
return {
|
||||
aci: normalizeUuid(destinationAci, 'syncMessage.sent.destinationAci'),
|
||||
pni: undefined,
|
||||
};
|
||||
}
|
||||
if (destinationPni) {
|
||||
return {
|
||||
aci: undefined,
|
||||
pni: normalizeUuid(destinationPni, 'syncMessage.sent.destinationPni'),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function processProtoWithDestinationUuid<Input extends ProtoUUIDTriple>(
|
||||
function processProtoWithDestinationServiceId<Input extends ProtoServiceId>(
|
||||
input: Input
|
||||
): Omit<Input, keyof ProtoUUIDTriple> & {
|
||||
destinationUuid?: TaggedUUIDStringType;
|
||||
): Omit<Input, keyof ProtoServiceId> & {
|
||||
destinationServiceId?: ServiceIdString;
|
||||
} {
|
||||
const { destinationAci, destinationPni, ...remaining } = input;
|
||||
const { destinationServiceId, ...remaining } = input;
|
||||
|
||||
return {
|
||||
...remaining,
|
||||
|
||||
destinationUuid: toTaggedUuid({
|
||||
destinationAci,
|
||||
destinationPni,
|
||||
}),
|
||||
destinationServiceId: destinationServiceId
|
||||
? normalizeServiceId(destinationServiceId, 'processSyncMessage')
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,8 +34,7 @@ function processSent(
|
|||
}
|
||||
|
||||
const {
|
||||
destinationAci,
|
||||
destinationPni,
|
||||
destinationServiceId,
|
||||
unidentifiedStatus,
|
||||
storyMessageRecipients,
|
||||
...remaining
|
||||
|
@ -66,15 +43,14 @@ function processSent(
|
|||
return {
|
||||
...remaining,
|
||||
|
||||
destinationUuid: toTaggedUuid({
|
||||
destinationAci,
|
||||
destinationPni,
|
||||
}),
|
||||
destinationServiceId: destinationServiceId
|
||||
? normalizeServiceId(destinationServiceId, 'processSent')
|
||||
: undefined,
|
||||
unidentifiedStatus: unidentifiedStatus
|
||||
? unidentifiedStatus.map(processProtoWithDestinationUuid)
|
||||
? unidentifiedStatus.map(processProtoWithDestinationServiceId)
|
||||
: undefined,
|
||||
storyMessageRecipients: storyMessageRecipients
|
||||
? storyMessageRecipients.map(processProtoWithDestinationUuid)
|
||||
? storyMessageRecipients.map(processProtoWithDestinationServiceId)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { without } from 'lodash';
|
||||
|
||||
import type { StorageInterface } from '../../types/Storage.d';
|
||||
import type { ServiceIdString } from '../../types/ServiceId';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
const BLOCKED_NUMBERS_ID = 'blocked';
|
||||
|
@ -41,15 +42,15 @@ export class Blocked {
|
|||
await this.storage.put(BLOCKED_NUMBERS_ID, without(numbers, number));
|
||||
}
|
||||
|
||||
public getBlockedUuids(): Array<string> {
|
||||
return this.storage.get(BLOCKED_UUIDS_ID, new Array<string>());
|
||||
public getBlockedUuids(): Array<ServiceIdString> {
|
||||
return this.storage.get(BLOCKED_UUIDS_ID, new Array<ServiceIdString>());
|
||||
}
|
||||
|
||||
public isUuidBlocked(uuid: string): boolean {
|
||||
public isUuidBlocked(uuid: ServiceIdString): boolean {
|
||||
return this.getBlockedUuids().includes(uuid);
|
||||
}
|
||||
|
||||
public async addBlockedUuid(uuid: string): Promise<void> {
|
||||
public async addBlockedUuid(uuid: ServiceIdString): Promise<void> {
|
||||
const uuids = this.getBlockedUuids();
|
||||
if (uuids.includes(uuid)) {
|
||||
return;
|
||||
|
@ -59,7 +60,7 @@ export class Blocked {
|
|||
await this.storage.put(BLOCKED_UUIDS_ID, uuids.concat(uuid));
|
||||
}
|
||||
|
||||
public async removeBlockedUuid(uuid: string): Promise<void> {
|
||||
public async removeBlockedUuid(uuid: ServiceIdString): Promise<void> {
|
||||
const numbers = this.getBlockedUuids();
|
||||
if (!numbers.includes(uuid)) {
|
||||
return;
|
||||
|
|
|
@ -5,14 +5,19 @@ import type { WebAPICredentials } from '../Types.d';
|
|||
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import type { StorageInterface } from '../../types/Storage.d';
|
||||
import { UUID, UUIDKind } from '../../types/UUID';
|
||||
import type {
|
||||
AciString,
|
||||
PniString,
|
||||
ServiceIdString,
|
||||
} from '../../types/ServiceId';
|
||||
import { ServiceIdKind, isAciString, isPniString } from '../../types/ServiceId';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
import Helpers from '../Helpers';
|
||||
|
||||
export type SetCredentialsOptions = {
|
||||
uuid: string;
|
||||
pni: string;
|
||||
aci: AciString;
|
||||
pni: PniString;
|
||||
number: string;
|
||||
deviceId: number;
|
||||
deviceName?: string;
|
||||
|
@ -23,12 +28,12 @@ export class User {
|
|||
constructor(private readonly storage: StorageInterface) {}
|
||||
|
||||
public async setUuidAndDeviceId(
|
||||
uuid: string,
|
||||
aci: AciString,
|
||||
deviceId: number
|
||||
): Promise<void> {
|
||||
await this.storage.put('uuid_id', `${uuid}.${deviceId}`);
|
||||
await this.storage.put('uuid_id', `${aci}.${deviceId}`);
|
||||
|
||||
log.info('storage.user: uuid and device id changed');
|
||||
log.info('storage.user: aci and device id changed');
|
||||
}
|
||||
|
||||
public async setNumber(number: string): Promise<void> {
|
||||
|
@ -61,53 +66,78 @@ export class User {
|
|||
return Helpers.unencodeNumber(numberId)[0];
|
||||
}
|
||||
|
||||
public getUuid(uuidKind = UUIDKind.ACI): UUID | undefined {
|
||||
if (uuidKind === UUIDKind.PNI) {
|
||||
const pni = this.storage.get('pni');
|
||||
if (pni === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return new UUID(pni);
|
||||
public getPni(): PniString | undefined {
|
||||
const pni = this.storage.get('pni');
|
||||
if (pni === undefined || !isPniString(pni)) {
|
||||
return undefined;
|
||||
}
|
||||
return pni;
|
||||
}
|
||||
|
||||
public getAci(): AciString | undefined {
|
||||
const uuidId = this.storage.get('uuid_id');
|
||||
if (!uuidId) {
|
||||
return undefined;
|
||||
}
|
||||
const aci = Helpers.unencodeNumber(uuidId.toLowerCase())[0];
|
||||
if (!isAciString(aci)) {
|
||||
return undefined;
|
||||
}
|
||||
return aci;
|
||||
}
|
||||
|
||||
public getServiceId(
|
||||
serviceIdKind: ServiceIdKind
|
||||
): ServiceIdString | undefined {
|
||||
if (serviceIdKind === ServiceIdKind.PNI) {
|
||||
return this.getPni();
|
||||
}
|
||||
|
||||
strictAssert(
|
||||
uuidKind === UUIDKind.ACI,
|
||||
`Unsupported uuid kind: ${uuidKind}`
|
||||
serviceIdKind === ServiceIdKind.ACI,
|
||||
`Unsupported uuid kind: ${serviceIdKind}`
|
||||
);
|
||||
const uuid = this.storage.get('uuid_id');
|
||||
if (!uuid) {
|
||||
return undefined;
|
||||
}
|
||||
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
|
||||
return this.getAci();
|
||||
}
|
||||
|
||||
public getCheckedUuid(uuidKind?: UUIDKind): UUID {
|
||||
const uuid = this.getUuid(uuidKind);
|
||||
public getCheckedAci(): AciString {
|
||||
const aci = this.getAci();
|
||||
strictAssert(aci !== undefined, 'Must have our own ACI');
|
||||
return aci;
|
||||
}
|
||||
|
||||
public getCheckedPni(): PniString {
|
||||
const pni = this.getPni();
|
||||
strictAssert(pni !== undefined, 'Must have our own PNI');
|
||||
return pni;
|
||||
}
|
||||
|
||||
public getCheckedServiceId(serviceIdKind: ServiceIdKind): ServiceIdString {
|
||||
const uuid = this.getServiceId(serviceIdKind);
|
||||
strictAssert(uuid !== undefined, 'Must have our own uuid');
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public async setPni(pni: string): Promise<void> {
|
||||
await this.storage.put('pni', UUID.cast(pni));
|
||||
public async setPni(pni: PniString): Promise<void> {
|
||||
await this.storage.put('pni', pni);
|
||||
}
|
||||
|
||||
public getOurUuidKind(uuid: UUID): UUIDKind {
|
||||
const ourUuid = this.getUuid();
|
||||
|
||||
if (ourUuid?.toString() === uuid.toString()) {
|
||||
return UUIDKind.ACI;
|
||||
public getOurServiceIdKind(serviceId: ServiceIdString): ServiceIdKind {
|
||||
const ourAci = this.getAci();
|
||||
if (ourAci === serviceId) {
|
||||
return ServiceIdKind.ACI;
|
||||
}
|
||||
|
||||
const pni = this.getUuid(UUIDKind.PNI);
|
||||
if (pni?.toString() === uuid.toString()) {
|
||||
return UUIDKind.PNI;
|
||||
const pni = this.getPni();
|
||||
if (pni === serviceId) {
|
||||
return ServiceIdKind.PNI;
|
||||
}
|
||||
|
||||
return UUIDKind.Unknown;
|
||||
return ServiceIdKind.Unknown;
|
||||
}
|
||||
|
||||
public isOurUuid(uuid: UUID): boolean {
|
||||
return this.getOurUuidKind(uuid) !== UUIDKind.Unknown;
|
||||
public isOurServiceId(serviceId: ServiceIdString): boolean {
|
||||
return this.getOurServiceIdKind(serviceId) !== ServiceIdKind.Unknown;
|
||||
}
|
||||
|
||||
public getDeviceId(): number | undefined {
|
||||
|
@ -137,11 +167,11 @@ export class User {
|
|||
public async setCredentials(
|
||||
credentials: SetCredentialsOptions
|
||||
): Promise<void> {
|
||||
const { uuid, pni, number, deviceId, deviceName, password } = credentials;
|
||||
const { aci, pni, number, deviceId, deviceName, password } = credentials;
|
||||
|
||||
await Promise.all([
|
||||
this.storage.put('number_id', `${number}.${deviceId}`),
|
||||
this.storage.put('uuid_id', `${uuid}.${deviceId}`),
|
||||
this.storage.put('uuid_id', `${aci}.${deviceId}`),
|
||||
this.storage.put('password', password),
|
||||
this.setPni(pni),
|
||||
deviceName
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue