Handle PniChangeNumber
This commit is contained in:
parent
412f07d2a2
commit
79b48115e6
|
@ -389,7 +389,6 @@ async function prepareUrl(
|
||||||
directoryV3Url: config.get<string | null>('directoryV3Url') || undefined,
|
directoryV3Url: config.get<string | null>('directoryV3Url') || undefined,
|
||||||
directoryV3MRENCLAVE:
|
directoryV3MRENCLAVE:
|
||||||
config.get<string | null>('directoryV3MRENCLAVE') || undefined,
|
config.get<string | null>('directoryV3MRENCLAVE') || undefined,
|
||||||
directoryV3Root: config.get<string | null>('directoryV3Root') || undefined,
|
|
||||||
});
|
});
|
||||||
if (!directoryConfig.success) {
|
if (!directoryConfig.success) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
"directoryV2PublicKey": null,
|
"directoryV2PublicKey": null,
|
||||||
"directoryV2CodeHashes": null,
|
"directoryV2CodeHashes": null,
|
||||||
"directoryV3Url": "https://cdsi.staging.signal.org",
|
"directoryV3Url": "https://cdsi.staging.signal.org",
|
||||||
"directoryV3MRENCLAVE": "e5eaa62da3514e8b37ccabddb87e52e7f319ccf5120a13f9e1b42b87ec9dd3dd",
|
"directoryV3MRENCLAVE": "7b75dd6e862decef9b37132d54be082441917a7790e82fe44f9cf653de03a75f",
|
||||||
"directoryV3Root": "-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n",
|
|
||||||
"cdn": {
|
"cdn": {
|
||||||
"0": "https://cdn-staging.signal.org",
|
"0": "https://cdn-staging.signal.org",
|
||||||
"2": "https://cdn2-staging.signal.org"
|
"2": "https://cdn2-staging.signal.org"
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
"@indutny/frameless-titlebar": "2.3.5",
|
"@indutny/frameless-titlebar": "2.3.5",
|
||||||
"@popperjs/core": "2.9.2",
|
"@popperjs/core": "2.9.2",
|
||||||
"@react-spring/web": "9.4.5",
|
"@react-spring/web": "9.4.5",
|
||||||
"@signalapp/libsignal-client": "0.18.1",
|
"@signalapp/libsignal-client": "0.19.1",
|
||||||
"@sindresorhus/is": "0.8.0",
|
"@sindresorhus/is": "0.8.0",
|
||||||
"@types/fabric": "4.5.3",
|
"@types/fabric": "4.5.3",
|
||||||
"abort-controller": "3.0.0",
|
"abort-controller": "3.0.0",
|
||||||
|
@ -190,7 +190,7 @@
|
||||||
"@babel/preset-typescript": "7.17.12",
|
"@babel/preset-typescript": "7.17.12",
|
||||||
"@electron/fuses": "1.5.0",
|
"@electron/fuses": "1.5.0",
|
||||||
"@mixer/parallel-prettier": "2.0.1",
|
"@mixer/parallel-prettier": "2.0.1",
|
||||||
"@signalapp/mock-server": "2.1.0",
|
"@signalapp/mock-server": "2.3.0",
|
||||||
"@storybook/addon-a11y": "6.5.6",
|
"@storybook/addon-a11y": "6.5.6",
|
||||||
"@storybook/addon-actions": "6.5.6",
|
"@storybook/addon-actions": "6.5.6",
|
||||||
"@storybook/addon-controls": "6.5.6",
|
"@storybook/addon-controls": "6.5.6",
|
||||||
|
|
|
@ -35,7 +35,8 @@ message Envelope {
|
||||||
optional uint64 serverTimestamp = 10;
|
optional uint64 serverTimestamp = 10;
|
||||||
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
|
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
|
||||||
optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
|
optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
|
||||||
// next: 15
|
optional string updated_pni = 15;
|
||||||
|
// next: 16
|
||||||
}
|
}
|
||||||
|
|
||||||
message Content {
|
message Content {
|
||||||
|
@ -509,6 +510,12 @@ message SyncMessage {
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PniChangeNumber {
|
||||||
|
optional bytes identityKeyPair = 1; // Serialized libsignal-client IdentityKeyPair
|
||||||
|
optional bytes signedPreKey = 2; // Serialized libsignal-client SignedPreKeyRecord
|
||||||
|
optional uint32 registrationId = 3;
|
||||||
|
}
|
||||||
|
|
||||||
optional Sent sent = 1;
|
optional Sent sent = 1;
|
||||||
optional Contacts contacts = 2;
|
optional Contacts contacts = 2;
|
||||||
optional Groups groups = 3;
|
optional Groups groups = 3;
|
||||||
|
@ -526,6 +533,7 @@ message SyncMessage {
|
||||||
reserved 15; // not yet added
|
reserved 15; // not yet added
|
||||||
repeated Viewed viewed = 16;
|
repeated Viewed viewed = 16;
|
||||||
optional PniIdentity pniIdentity = 17;
|
optional PniIdentity pniIdentity = 17;
|
||||||
|
optional PniChangeNumber pniChangeNumber = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AttachmentPointer {
|
message AttachmentPointer {
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber, omit } from 'lodash';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Direction,
|
Direction,
|
||||||
|
IdentityKeyPair,
|
||||||
PreKeyRecord,
|
PreKeyRecord,
|
||||||
PrivateKey,
|
PrivateKey,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
@ -31,6 +32,7 @@ import type {
|
||||||
IdentityKeyIdType,
|
IdentityKeyIdType,
|
||||||
KeyPairType,
|
KeyPairType,
|
||||||
OuterSignedPrekeyType,
|
OuterSignedPrekeyType,
|
||||||
|
PniKeyMaterialType,
|
||||||
PreKeyIdType,
|
PreKeyIdType,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
SenderKeyIdType,
|
SenderKeyIdType,
|
||||||
|
@ -255,8 +257,8 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
for (const key of Object.keys(map.value)) {
|
for (const key of Object.keys(map.value)) {
|
||||||
const { privKey, pubKey } = map.value[key];
|
const { privKey, pubKey } = map.value[key];
|
||||||
this.ourIdentityKeys.set(new UUID(key).toString(), {
|
this.ourIdentityKeys.set(new UUID(key).toString(), {
|
||||||
privKey: Bytes.fromBase64(privKey),
|
privKey,
|
||||||
pubKey: Bytes.fromBase64(pubKey),
|
pubKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
|
@ -461,7 +463,8 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
ourUuid: UUID,
|
ourUuid: UUID,
|
||||||
keyId: number,
|
keyId: number,
|
||||||
keyPair: KeyPairType,
|
keyPair: KeyPairType,
|
||||||
confirmed?: boolean
|
confirmed?: boolean,
|
||||||
|
createdAt = Date.now()
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.signedPreKeys) {
|
if (!this.signedPreKeys) {
|
||||||
throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!');
|
throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!');
|
||||||
|
@ -475,7 +478,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
keyId,
|
keyId,
|
||||||
publicKey: keyPair.pubKey,
|
publicKey: keyPair.pubKey,
|
||||||
privateKey: keyPair.privKey,
|
privateKey: keyPair.privKey,
|
||||||
created_at: Date.now(),
|
created_at: createdAt,
|
||||||
confirmed: Boolean(confirmed),
|
confirmed: Boolean(confirmed),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1935,6 +1938,101 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async removeOurOldPni(oldPni: UUID): Promise<void> {
|
||||||
|
const { storage } = window;
|
||||||
|
|
||||||
|
log.info(`SignalProtocolStore.removeOurOldPni(${oldPni})`);
|
||||||
|
|
||||||
|
// Update caches
|
||||||
|
this.ourIdentityKeys.delete(oldPni.toString());
|
||||||
|
this.ourRegistrationIds.delete(oldPni.toString());
|
||||||
|
|
||||||
|
const preKeyPrefix = `${oldPni.toString()}:`;
|
||||||
|
if (this.preKeys) {
|
||||||
|
for (const key of this.preKeys.keys()) {
|
||||||
|
if (key.startsWith(preKeyPrefix)) {
|
||||||
|
this.preKeys.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.signedPreKeys) {
|
||||||
|
for (const key of this.signedPreKeys.keys()) {
|
||||||
|
if (key.startsWith(preKeyPrefix)) {
|
||||||
|
this.signedPreKeys.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
await Promise.all([
|
||||||
|
storage.put(
|
||||||
|
'identityKeyMap',
|
||||||
|
omit(storage.get('identityKeyMap') || {}, oldPni.toString())
|
||||||
|
),
|
||||||
|
storage.put(
|
||||||
|
'registrationIdMap',
|
||||||
|
omit(storage.get('registrationIdMap') || {}, oldPni.toString())
|
||||||
|
),
|
||||||
|
window.Signal.Data.removePreKeysByUuid(oldPni.toString()),
|
||||||
|
window.Signal.Data.removeSignedPreKeysByUuid(oldPni.toString()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOurPniKeyMaterial(
|
||||||
|
pni: UUID,
|
||||||
|
{
|
||||||
|
identityKeyPair: identityBytes,
|
||||||
|
signedPreKey: signedPreKeyBytes,
|
||||||
|
registrationId,
|
||||||
|
}: PniKeyMaterialType
|
||||||
|
): Promise<void> {
|
||||||
|
log.info(`SignalProtocolStore.updateOurPniKeyMaterial(${pni})`);
|
||||||
|
|
||||||
|
const identityKeyPair = IdentityKeyPair.deserialize(
|
||||||
|
Buffer.from(identityBytes)
|
||||||
|
);
|
||||||
|
const signedPreKey = SignedPreKeyRecord.deserialize(
|
||||||
|
Buffer.from(signedPreKeyBytes)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { storage } = window;
|
||||||
|
|
||||||
|
const pniPublicKey = identityKeyPair.publicKey.serialize();
|
||||||
|
const pniPrivateKey = identityKeyPair.privateKey.serialize();
|
||||||
|
|
||||||
|
// Update caches
|
||||||
|
this.ourIdentityKeys.set(pni.toString(), {
|
||||||
|
pubKey: pniPublicKey,
|
||||||
|
privKey: pniPrivateKey,
|
||||||
|
});
|
||||||
|
this.ourRegistrationIds.set(pni.toString(), registrationId);
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
await Promise.all([
|
||||||
|
storage.put('identityKeyMap', {
|
||||||
|
...(storage.get('identityKeyMap') || {}),
|
||||||
|
[pni.toString()]: {
|
||||||
|
pubKey: pniPublicKey,
|
||||||
|
privKey: pniPrivateKey,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
storage.put('registrationIdMap', {
|
||||||
|
...(storage.get('registrationIdMap') || {}),
|
||||||
|
[pni.toString()]: registrationId,
|
||||||
|
}),
|
||||||
|
this.storeSignedPreKey(
|
||||||
|
pni,
|
||||||
|
signedPreKey.id(),
|
||||||
|
{
|
||||||
|
privKey: signedPreKey.privateKey().serialize(),
|
||||||
|
pubKey: signedPreKey.publicKey().serialize(),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
signedPreKey.timestamp()
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
async removeAllData(): Promise<void> {
|
async removeAllData(): Promise<void> {
|
||||||
await window.Signal.Data.removeAll();
|
await window.Signal.Data.removeAll();
|
||||||
await this.hydrateCaches();
|
await this.hydrateCaches();
|
||||||
|
|
|
@ -79,7 +79,6 @@ import type {
|
||||||
FetchLatestEvent,
|
FetchLatestEvent,
|
||||||
GroupEvent,
|
GroupEvent,
|
||||||
KeysEvent,
|
KeysEvent,
|
||||||
PNIIdentityEvent,
|
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageEventData,
|
MessageEventData,
|
||||||
MessageRequestResponseEvent,
|
MessageRequestResponseEvent,
|
||||||
|
@ -395,10 +394,6 @@ export async function startApp(): Promise<void> {
|
||||||
queuedEventListener(onFetchLatestSync)
|
queuedEventListener(onFetchLatestSync)
|
||||||
);
|
);
|
||||||
messageReceiver.addEventListener('keys', queuedEventListener(onKeysSync));
|
messageReceiver.addEventListener('keys', queuedEventListener(onKeysSync));
|
||||||
messageReceiver.addEventListener(
|
|
||||||
'pniIdentity',
|
|
||||||
queuedEventListener(onPNIIdentitySync)
|
|
||||||
);
|
|
||||||
messageReceiver.addEventListener(
|
messageReceiver.addEventListener(
|
||||||
'storyRecipientUpdate',
|
'storyRecipientUpdate',
|
||||||
queuedEventListener(onStoryRecipientUpdate, false)
|
queuedEventListener(onStoryRecipientUpdate, false)
|
||||||
|
@ -3670,15 +3665,6 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onPNIIdentitySync(ev: PNIIdentityEvent) {
|
|
||||||
ev.confirm();
|
|
||||||
|
|
||||||
log.info('onPNIIdentitySync: updating PNI keys');
|
|
||||||
const manager = window.getAccountManager();
|
|
||||||
const { privateKey: privKey, publicKey: pubKey } = ev.data;
|
|
||||||
await manager.updatePNIIdentity({ privKey, pubKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onMessageRequestResponse(ev: MessageRequestResponseEvent) {
|
async function onMessageRequestResponse(ev: MessageRequestResponseEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
import './wrap';
|
import './wrap';
|
||||||
|
|
||||||
import { signalservice as SignalService } from './compiled';
|
import { signalservice as SignalService, signal as Signal } from './compiled';
|
||||||
|
|
||||||
export { SignalService };
|
export { SignalService, Signal };
|
||||||
|
|
|
@ -17,7 +17,7 @@ import * as log from '../logging/log';
|
||||||
|
|
||||||
export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
|
export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
|
||||||
|
|
||||||
type CredentialsDataType = Array<GroupCredentialType>;
|
type CredentialsDataType = ReadonlyArray<GroupCredentialType>;
|
||||||
type RequestDatesType = {
|
type RequestDatesType = {
|
||||||
startDayInMs: number;
|
startDayInMs: number;
|
||||||
endDayInMs: number;
|
endDayInMs: number;
|
||||||
|
@ -145,33 +145,40 @@ export async function maybeFetchNewCredentials(): Promise<void> {
|
||||||
`${logId}: fetching credentials for ${startDayInMs} through ${endDayInMs}`
|
`${logId}: fetching credentials for ${startDayInMs} through ${endDayInMs}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO(indutny): In the future we'd like to avoid this extra call all the time
|
|
||||||
const { pni } = await server.whoami();
|
|
||||||
if (!pni) {
|
|
||||||
log.info(`${logId}: no PNI, returning early`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serverPublicParamsBase64 = window.getServerPublicParams();
|
const serverPublicParamsBase64 = window.getServerPublicParams();
|
||||||
const clientZKAuthOperations = getClientZkAuthOperations(
|
const clientZKAuthOperations = getClientZkAuthOperations(
|
||||||
serverPublicParamsBase64
|
serverPublicParamsBase64
|
||||||
);
|
);
|
||||||
const newCredentials = sortCredentials(
|
|
||||||
await server.getGroupCredentials({ startDayInMs, endDayInMs })
|
|
||||||
).map((item: GroupCredentialType) => {
|
|
||||||
const authCredential = clientZKAuthOperations.receiveAuthCredentialWithPni(
|
|
||||||
aci,
|
|
||||||
pni,
|
|
||||||
item.redemptionTime,
|
|
||||||
new AuthCredentialWithPniResponse(Buffer.from(item.credential, 'base64'))
|
|
||||||
);
|
|
||||||
const credential = authCredential.serialize().toString('base64');
|
|
||||||
|
|
||||||
return {
|
const { pni, credentials: rawCredentials } = await server.getGroupCredentials(
|
||||||
redemptionTime: item.redemptionTime * durations.SECOND,
|
{ startDayInMs, endDayInMs }
|
||||||
credential,
|
);
|
||||||
};
|
strictAssert(pni, 'Server must give pni along with group credentials');
|
||||||
});
|
|
||||||
|
const localPni = window.storage.user.getUuid(UUIDKind.PNI);
|
||||||
|
if (pni !== localPni?.toString()) {
|
||||||
|
log.error(`${logId}: local PNI ${localPni}, does not match remote ${pni}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCredentials = sortCredentials(rawCredentials).map(
|
||||||
|
(item: GroupCredentialType) => {
|
||||||
|
const authCredential =
|
||||||
|
clientZKAuthOperations.receiveAuthCredentialWithPni(
|
||||||
|
aci,
|
||||||
|
pni,
|
||||||
|
item.redemptionTime,
|
||||||
|
new AuthCredentialWithPniResponse(
|
||||||
|
Buffer.from(item.credential, 'base64')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const credential = authCredential.serialize().toString('base64');
|
||||||
|
|
||||||
|
return {
|
||||||
|
redemptionTime: item.redemptionTime * durations.SECOND,
|
||||||
|
credential,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const today = toDayMillis(Date.now());
|
const today = toDayMillis(Date.now());
|
||||||
const previousCleaned = previous
|
const previousCleaned = previous
|
||||||
|
|
449
ts/sql/Client.ts
449
ts/sql/Client.ts
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,7 @@
|
||||||
// Copyright 2020-2022 Signal Messenger, LLC
|
// Copyright 2020-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import type {
|
import type {
|
||||||
ConversationAttributesType,
|
ConversationAttributesType,
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
|
@ -15,7 +13,7 @@ import type { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||||
import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
|
import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
|
||||||
import type { StorageAccessType } from '../types/Storage.d';
|
import type { StorageAccessType } from '../types/Storage.d';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import type { BodyRangesType } from '../types/Util';
|
import type { BodyRangesType, BytesToStrings } from '../types/Util';
|
||||||
import type { QualifiedAddressStringType } from '../types/QualifiedAddress';
|
import type { QualifiedAddressStringType } from '../types/QualifiedAddress';
|
||||||
import type { UUIDStringType } from '../types/UUID';
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import type { BadgeType } from '../badges/types';
|
import type { BadgeType } from '../badges/types';
|
||||||
|
@ -66,14 +64,27 @@ export type IdentityKeyType = {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
verified: number;
|
verified: number;
|
||||||
};
|
};
|
||||||
|
export type StoredIdentityKeyType = {
|
||||||
|
firstUse: boolean;
|
||||||
|
id: UUIDStringType | `conversation:${string}`;
|
||||||
|
nonblockingApproval: boolean;
|
||||||
|
publicKey: string;
|
||||||
|
timestamp: number;
|
||||||
|
verified: number;
|
||||||
|
};
|
||||||
export type IdentityKeyIdType = IdentityKeyType['id'];
|
export type IdentityKeyIdType = IdentityKeyType['id'];
|
||||||
|
|
||||||
export type ItemKeyType = keyof StorageAccessType;
|
export type ItemKeyType = keyof StorageAccessType;
|
||||||
export type AllItemsType = Partial<StorageAccessType>;
|
export type AllItemsType = Partial<StorageAccessType>;
|
||||||
|
export type StoredAllItemsType = Partial<BytesToStrings<StorageAccessType>>;
|
||||||
export type ItemType<K extends ItemKeyType> = {
|
export type ItemType<K extends ItemKeyType> = {
|
||||||
id: K;
|
id: K;
|
||||||
value: StorageAccessType[K];
|
value: StorageAccessType[K];
|
||||||
};
|
};
|
||||||
|
export type StoredItemType<K extends ItemKeyType> = {
|
||||||
|
id: K;
|
||||||
|
value: BytesToStrings<StorageAccessType[K]>;
|
||||||
|
};
|
||||||
export type MessageType = MessageAttributesType;
|
export type MessageType = MessageAttributesType;
|
||||||
export type MessageTypeUnhydrated = {
|
export type MessageTypeUnhydrated = {
|
||||||
json: string;
|
json: string;
|
||||||
|
@ -85,6 +96,13 @@ export type PreKeyType = {
|
||||||
privateKey: Uint8Array;
|
privateKey: Uint8Array;
|
||||||
publicKey: Uint8Array;
|
publicKey: Uint8Array;
|
||||||
};
|
};
|
||||||
|
export type StoredPreKeyType = {
|
||||||
|
id: `${UUIDStringType}:${number}`;
|
||||||
|
keyId: number;
|
||||||
|
ourUuid: UUIDStringType;
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
};
|
||||||
export type PreKeyIdType = PreKeyType['id'];
|
export type PreKeyIdType = PreKeyType['id'];
|
||||||
export type ServerSearchResultMessageType = {
|
export type ServerSearchResultMessageType = {
|
||||||
json: string;
|
json: string;
|
||||||
|
@ -149,6 +167,15 @@ export type SignedPreKeyType = {
|
||||||
privateKey: Uint8Array;
|
privateKey: Uint8Array;
|
||||||
publicKey: Uint8Array;
|
publicKey: Uint8Array;
|
||||||
};
|
};
|
||||||
|
export type StoredSignedPreKeyType = {
|
||||||
|
confirmed: boolean;
|
||||||
|
created_at: number;
|
||||||
|
ourUuid: UUIDStringType;
|
||||||
|
id: `${UUIDStringType}:${number}`;
|
||||||
|
keyId: number;
|
||||||
|
privateKey: string;
|
||||||
|
publicKey: string;
|
||||||
|
};
|
||||||
export type SignedPreKeyIdType = SignedPreKeyType['id'];
|
export type SignedPreKeyIdType = SignedPreKeyType['id'];
|
||||||
|
|
||||||
export type StickerType = Readonly<{
|
export type StickerType = Readonly<{
|
||||||
|
@ -205,6 +232,7 @@ export type UnprocessedType = {
|
||||||
sourceUuid?: UUIDStringType;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
destinationUuid?: string;
|
destinationUuid?: string;
|
||||||
|
updatedPni?: string;
|
||||||
serverGuid?: string;
|
serverGuid?: string;
|
||||||
serverTimestamp?: number;
|
serverTimestamp?: number;
|
||||||
decrypted?: string;
|
decrypted?: string;
|
||||||
|
@ -262,41 +290,49 @@ export type StoryReadType = Readonly<{
|
||||||
storyReadDate: number;
|
storyReadDate: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ReactionResultType = Pick<
|
||||||
|
ReactionType,
|
||||||
|
'targetAuthorUuid' | 'targetTimestamp' | 'messageId'
|
||||||
|
> & { rowid: number };
|
||||||
|
|
||||||
|
export type GetUnreadByConversationAndMarkReadResultType = Array<
|
||||||
|
{ originalReadStatus: ReadStatus | undefined } & Pick<
|
||||||
|
MessageType,
|
||||||
|
| 'id'
|
||||||
|
| 'source'
|
||||||
|
| 'sourceUuid'
|
||||||
|
| 'sent_at'
|
||||||
|
| 'type'
|
||||||
|
| 'readStatus'
|
||||||
|
| 'seenStatus'
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type GetConversationRangeCenteredOnMessageResultType<Message> =
|
||||||
|
Readonly<{
|
||||||
|
older: Array<Message>;
|
||||||
|
newer: Array<Message>;
|
||||||
|
metrics: ConversationMetricsType;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type DataInterface = {
|
export type DataInterface = {
|
||||||
close: () => Promise<void>;
|
close: () => Promise<void>;
|
||||||
removeDB: () => Promise<void>;
|
removeDB: () => Promise<void>;
|
||||||
removeIndexedDBFiles: () => Promise<void>;
|
removeIndexedDBFiles: () => Promise<void>;
|
||||||
|
|
||||||
createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise<void>;
|
|
||||||
getIdentityKeyById: (
|
|
||||||
id: IdentityKeyIdType
|
|
||||||
) => Promise<IdentityKeyType | undefined>;
|
|
||||||
bulkAddIdentityKeys: (array: Array<IdentityKeyType>) => Promise<void>;
|
|
||||||
removeIdentityKeyById: (id: IdentityKeyIdType) => Promise<void>;
|
removeIdentityKeyById: (id: IdentityKeyIdType) => Promise<void>;
|
||||||
removeAllIdentityKeys: () => Promise<void>;
|
removeAllIdentityKeys: () => Promise<void>;
|
||||||
getAllIdentityKeys: () => Promise<Array<IdentityKeyType>>;
|
|
||||||
|
|
||||||
createOrUpdatePreKey: (data: PreKeyType) => Promise<void>;
|
|
||||||
getPreKeyById: (id: PreKeyIdType) => Promise<PreKeyType | undefined>;
|
|
||||||
bulkAddPreKeys: (array: Array<PreKeyType>) => Promise<void>;
|
|
||||||
removePreKeyById: (id: PreKeyIdType) => Promise<void>;
|
removePreKeyById: (id: PreKeyIdType) => Promise<void>;
|
||||||
|
removePreKeysByUuid: (uuid: UUIDStringType) => Promise<void>;
|
||||||
removeAllPreKeys: () => Promise<void>;
|
removeAllPreKeys: () => Promise<void>;
|
||||||
getAllPreKeys: () => Promise<Array<PreKeyType>>;
|
|
||||||
|
|
||||||
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise<void>;
|
|
||||||
getSignedPreKeyById: (
|
|
||||||
id: SignedPreKeyIdType
|
|
||||||
) => Promise<SignedPreKeyType | undefined>;
|
|
||||||
bulkAddSignedPreKeys: (array: Array<SignedPreKeyType>) => Promise<void>;
|
|
||||||
removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise<void>;
|
removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise<void>;
|
||||||
|
removeSignedPreKeysByUuid: (uuid: UUIDStringType) => Promise<void>;
|
||||||
removeAllSignedPreKeys: () => Promise<void>;
|
removeAllSignedPreKeys: () => Promise<void>;
|
||||||
getAllSignedPreKeys: () => Promise<Array<SignedPreKeyType>>;
|
|
||||||
|
|
||||||
createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>): Promise<void>;
|
|
||||||
getItemById<K extends ItemKeyType>(id: K): Promise<ItemType<K> | undefined>;
|
|
||||||
removeItemById: (id: ItemKeyType) => Promise<void>;
|
|
||||||
removeAllItems: () => Promise<void>;
|
removeAllItems: () => Promise<void>;
|
||||||
getAllItems: () => Promise<AllItemsType>;
|
removeItemById: (id: ItemKeyType) => Promise<void>;
|
||||||
|
|
||||||
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
||||||
getSenderKeyById: (id: SenderKeyIdType) => Promise<SenderKeyType | undefined>;
|
getSenderKeyById: (id: SenderKeyIdType) => Promise<SenderKeyType | undefined>;
|
||||||
|
@ -402,29 +438,12 @@ export type DataInterface = {
|
||||||
newestUnreadAt: number;
|
newestUnreadAt: number;
|
||||||
readAt?: number;
|
readAt?: number;
|
||||||
storyId?: UUIDStringType;
|
storyId?: UUIDStringType;
|
||||||
}) => Promise<
|
}) => Promise<GetUnreadByConversationAndMarkReadResultType>;
|
||||||
Array<
|
|
||||||
{ originalReadStatus: ReadStatus | undefined } & Pick<
|
|
||||||
MessageType,
|
|
||||||
| 'id'
|
|
||||||
| 'readStatus'
|
|
||||||
| 'seenStatus'
|
|
||||||
| 'sent_at'
|
|
||||||
| 'source'
|
|
||||||
| 'sourceUuid'
|
|
||||||
| 'type'
|
|
||||||
>
|
|
||||||
>
|
|
||||||
>;
|
|
||||||
getUnreadReactionsAndMarkRead: (options: {
|
getUnreadReactionsAndMarkRead: (options: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
newestUnreadAt: number;
|
newestUnreadAt: number;
|
||||||
storyId?: UUIDStringType;
|
storyId?: UUIDStringType;
|
||||||
}) => Promise<
|
}) => Promise<Array<ReactionResultType>>;
|
||||||
Array<
|
|
||||||
Pick<ReactionType, 'targetAuthorUuid' | 'targetTimestamp' | 'messageId'>
|
|
||||||
>
|
|
||||||
>;
|
|
||||||
markReactionAsRead: (
|
markReactionAsRead: (
|
||||||
targetAuthorUuid: string,
|
targetAuthorUuid: string,
|
||||||
targetTimestamp: number
|
targetTimestamp: number
|
||||||
|
@ -671,11 +690,36 @@ export type ServerInterface = DataInterface & {
|
||||||
receivedAt: number;
|
receivedAt: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
storyId: UUIDStringType | undefined;
|
storyId: UUIDStringType | undefined;
|
||||||
}) => Promise<{
|
}) => Promise<
|
||||||
older: Array<MessageTypeUnhydrated>;
|
GetConversationRangeCenteredOnMessageResultType<MessageTypeUnhydrated>
|
||||||
newer: Array<MessageTypeUnhydrated>;
|
>;
|
||||||
metrics: ConversationMetricsType;
|
|
||||||
}>;
|
createOrUpdateIdentityKey: (data: StoredIdentityKeyType) => Promise<void>;
|
||||||
|
getIdentityKeyById: (
|
||||||
|
id: IdentityKeyIdType
|
||||||
|
) => Promise<StoredIdentityKeyType | undefined>;
|
||||||
|
bulkAddIdentityKeys: (array: Array<StoredIdentityKeyType>) => Promise<void>;
|
||||||
|
getAllIdentityKeys: () => Promise<Array<StoredIdentityKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdatePreKey: (data: StoredPreKeyType) => Promise<void>;
|
||||||
|
getPreKeyById: (id: PreKeyIdType) => Promise<StoredPreKeyType | undefined>;
|
||||||
|
bulkAddPreKeys: (array: Array<StoredPreKeyType>) => Promise<void>;
|
||||||
|
getAllPreKeys: () => Promise<Array<StoredPreKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdateSignedPreKey: (data: StoredSignedPreKeyType) => Promise<void>;
|
||||||
|
getSignedPreKeyById: (
|
||||||
|
id: SignedPreKeyIdType
|
||||||
|
) => Promise<StoredSignedPreKeyType | undefined>;
|
||||||
|
bulkAddSignedPreKeys: (array: Array<StoredSignedPreKeyType>) => Promise<void>;
|
||||||
|
getAllSignedPreKeys: () => Promise<Array<StoredSignedPreKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdateItem<K extends ItemKeyType>(
|
||||||
|
data: StoredItemType<K>
|
||||||
|
): Promise<void>;
|
||||||
|
getItemById<K extends ItemKeyType>(
|
||||||
|
id: K
|
||||||
|
): Promise<StoredItemType<K> | undefined>;
|
||||||
|
getAllItems: () => Promise<StoredAllItemsType>;
|
||||||
|
|
||||||
// Server-only
|
// Server-only
|
||||||
|
|
||||||
|
@ -744,11 +788,30 @@ export type ClientInterface = DataInterface & {
|
||||||
receivedAt: number;
|
receivedAt: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
storyId: UUIDStringType | undefined;
|
storyId: UUIDStringType | undefined;
|
||||||
}) => Promise<{
|
}) => Promise<GetConversationRangeCenteredOnMessageResultType<MessageType>>;
|
||||||
older: Array<MessageAttributesType>;
|
|
||||||
newer: Array<MessageAttributesType>;
|
createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise<void>;
|
||||||
metrics: ConversationMetricsType;
|
getIdentityKeyById: (
|
||||||
}>;
|
id: IdentityKeyIdType
|
||||||
|
) => Promise<IdentityKeyType | undefined>;
|
||||||
|
bulkAddIdentityKeys: (array: Array<IdentityKeyType>) => Promise<void>;
|
||||||
|
getAllIdentityKeys: () => Promise<Array<IdentityKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdatePreKey: (data: PreKeyType) => Promise<void>;
|
||||||
|
getPreKeyById: (id: PreKeyIdType) => Promise<PreKeyType | undefined>;
|
||||||
|
bulkAddPreKeys: (array: Array<PreKeyType>) => Promise<void>;
|
||||||
|
getAllPreKeys: () => Promise<Array<PreKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise<void>;
|
||||||
|
getSignedPreKeyById: (
|
||||||
|
id: SignedPreKeyIdType
|
||||||
|
) => Promise<SignedPreKeyType | undefined>;
|
||||||
|
bulkAddSignedPreKeys: (array: Array<SignedPreKeyType>) => Promise<void>;
|
||||||
|
getAllSignedPreKeys: () => Promise<Array<SignedPreKeyType>>;
|
||||||
|
|
||||||
|
createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>): Promise<void>;
|
||||||
|
getItemById<K extends ItemKeyType>(id: K): Promise<ItemType<K> | undefined>;
|
||||||
|
getAllItems: () => Promise<AllItemsType>;
|
||||||
|
|
||||||
// Client-side only
|
// Client-side only
|
||||||
|
|
||||||
|
@ -774,10 +837,10 @@ export type ClientInterface = DataInterface & {
|
||||||
export type ClientJobType = {
|
export type ClientJobType = {
|
||||||
fnName: string;
|
fnName: string;
|
||||||
start: number;
|
start: number;
|
||||||
resolve?: Function;
|
resolve?: (value: unknown) => void;
|
||||||
reject?: Function;
|
reject?: (error: Error) => void;
|
||||||
|
|
||||||
// Only in DEBUG mode
|
// Only in DEBUG mode
|
||||||
complete?: boolean;
|
complete?: boolean;
|
||||||
args?: Array<any>;
|
args?: ReadonlyArray<unknown>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,22 +71,25 @@ import {
|
||||||
import { updateSchema } from './migrations';
|
import { updateSchema } from './migrations';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AllItemsType,
|
StoredAllItemsType,
|
||||||
AttachmentDownloadJobType,
|
AttachmentDownloadJobType,
|
||||||
ConversationMetricsType,
|
ConversationMetricsType,
|
||||||
ConversationType,
|
ConversationType,
|
||||||
DeleteSentProtoRecipientOptionsType,
|
DeleteSentProtoRecipientOptionsType,
|
||||||
EmojiType,
|
EmojiType,
|
||||||
|
GetConversationRangeCenteredOnMessageResultType,
|
||||||
|
GetUnreadByConversationAndMarkReadResultType,
|
||||||
IdentityKeyIdType,
|
IdentityKeyIdType,
|
||||||
IdentityKeyType,
|
StoredIdentityKeyType,
|
||||||
ItemKeyType,
|
ItemKeyType,
|
||||||
ItemType,
|
StoredItemType,
|
||||||
ConversationMessageStatsType,
|
ConversationMessageStatsType,
|
||||||
MessageMetricsType,
|
MessageMetricsType,
|
||||||
MessageType,
|
MessageType,
|
||||||
MessageTypeUnhydrated,
|
MessageTypeUnhydrated,
|
||||||
PreKeyIdType,
|
PreKeyIdType,
|
||||||
PreKeyType,
|
ReactionResultType,
|
||||||
|
StoredPreKeyType,
|
||||||
ServerSearchResultMessageType,
|
ServerSearchResultMessageType,
|
||||||
SenderKeyIdType,
|
SenderKeyIdType,
|
||||||
SenderKeyType,
|
SenderKeyType,
|
||||||
|
@ -100,7 +103,7 @@ import type {
|
||||||
SessionIdType,
|
SessionIdType,
|
||||||
SessionType,
|
SessionType,
|
||||||
SignedPreKeyIdType,
|
SignedPreKeyIdType,
|
||||||
SignedPreKeyType,
|
StoredSignedPreKeyType,
|
||||||
StickerPackStatusType,
|
StickerPackStatusType,
|
||||||
StickerPackType,
|
StickerPackType,
|
||||||
StickerType,
|
StickerType,
|
||||||
|
@ -149,6 +152,7 @@ const dataInterface: ServerInterface = {
|
||||||
getPreKeyById,
|
getPreKeyById,
|
||||||
bulkAddPreKeys,
|
bulkAddPreKeys,
|
||||||
removePreKeyById,
|
removePreKeyById,
|
||||||
|
removePreKeysByUuid,
|
||||||
removeAllPreKeys,
|
removeAllPreKeys,
|
||||||
getAllPreKeys,
|
getAllPreKeys,
|
||||||
|
|
||||||
|
@ -156,6 +160,7 @@ const dataInterface: ServerInterface = {
|
||||||
getSignedPreKeyById,
|
getSignedPreKeyById,
|
||||||
bulkAddSignedPreKeys,
|
bulkAddSignedPreKeys,
|
||||||
removeSignedPreKeyById,
|
removeSignedPreKeyById,
|
||||||
|
removeSignedPreKeysByUuid,
|
||||||
removeAllSignedPreKeys,
|
removeAllSignedPreKeys,
|
||||||
getAllSignedPreKeys,
|
getAllSignedPreKeys,
|
||||||
|
|
||||||
|
@ -634,16 +639,18 @@ function getInstance(): Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
const IDENTITY_KEYS_TABLE = 'identityKeys';
|
const IDENTITY_KEYS_TABLE = 'identityKeys';
|
||||||
async function createOrUpdateIdentityKey(data: IdentityKeyType): Promise<void> {
|
async function createOrUpdateIdentityKey(
|
||||||
|
data: StoredIdentityKeyType
|
||||||
|
): Promise<void> {
|
||||||
return createOrUpdate(getInstance(), IDENTITY_KEYS_TABLE, data);
|
return createOrUpdate(getInstance(), IDENTITY_KEYS_TABLE, data);
|
||||||
}
|
}
|
||||||
async function getIdentityKeyById(
|
async function getIdentityKeyById(
|
||||||
id: IdentityKeyIdType
|
id: IdentityKeyIdType
|
||||||
): Promise<IdentityKeyType | undefined> {
|
): Promise<StoredIdentityKeyType | undefined> {
|
||||||
return getById(getInstance(), IDENTITY_KEYS_TABLE, id);
|
return getById(getInstance(), IDENTITY_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function bulkAddIdentityKeys(
|
async function bulkAddIdentityKeys(
|
||||||
array: Array<IdentityKeyType>
|
array: Array<StoredIdentityKeyType>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return bulkAdd(getInstance(), IDENTITY_KEYS_TABLE, array);
|
return bulkAdd(getInstance(), IDENTITY_KEYS_TABLE, array);
|
||||||
}
|
}
|
||||||
|
@ -653,55 +660,67 @@ async function removeIdentityKeyById(id: IdentityKeyIdType): Promise<void> {
|
||||||
async function removeAllIdentityKeys(): Promise<void> {
|
async function removeAllIdentityKeys(): Promise<void> {
|
||||||
return removeAllFromTable(getInstance(), IDENTITY_KEYS_TABLE);
|
return removeAllFromTable(getInstance(), IDENTITY_KEYS_TABLE);
|
||||||
}
|
}
|
||||||
async function getAllIdentityKeys(): Promise<Array<IdentityKeyType>> {
|
async function getAllIdentityKeys(): Promise<Array<StoredIdentityKeyType>> {
|
||||||
return getAllFromTable(getInstance(), IDENTITY_KEYS_TABLE);
|
return getAllFromTable(getInstance(), IDENTITY_KEYS_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRE_KEYS_TABLE = 'preKeys';
|
const PRE_KEYS_TABLE = 'preKeys';
|
||||||
async function createOrUpdatePreKey(data: PreKeyType): Promise<void> {
|
async function createOrUpdatePreKey(data: StoredPreKeyType): Promise<void> {
|
||||||
return createOrUpdate(getInstance(), PRE_KEYS_TABLE, data);
|
return createOrUpdate(getInstance(), PRE_KEYS_TABLE, data);
|
||||||
}
|
}
|
||||||
async function getPreKeyById(
|
async function getPreKeyById(
|
||||||
id: PreKeyIdType
|
id: PreKeyIdType
|
||||||
): Promise<PreKeyType | undefined> {
|
): Promise<StoredPreKeyType | undefined> {
|
||||||
return getById(getInstance(), PRE_KEYS_TABLE, id);
|
return getById(getInstance(), PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function bulkAddPreKeys(array: Array<PreKeyType>): Promise<void> {
|
async function bulkAddPreKeys(array: Array<StoredPreKeyType>): Promise<void> {
|
||||||
return bulkAdd(getInstance(), PRE_KEYS_TABLE, array);
|
return bulkAdd(getInstance(), PRE_KEYS_TABLE, array);
|
||||||
}
|
}
|
||||||
async function removePreKeyById(id: PreKeyIdType): Promise<void> {
|
async function removePreKeyById(id: PreKeyIdType): Promise<void> {
|
||||||
return removeById(getInstance(), PRE_KEYS_TABLE, id);
|
return removeById(getInstance(), PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
|
async function removePreKeysByUuid(uuid: UUIDStringType): Promise<void> {
|
||||||
|
const db = getInstance();
|
||||||
|
db.prepare<Query>('DELETE FROM preKeys WHERE ourUuid IS $uuid;').run({
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
}
|
||||||
async function removeAllPreKeys(): Promise<void> {
|
async function removeAllPreKeys(): Promise<void> {
|
||||||
return removeAllFromTable(getInstance(), PRE_KEYS_TABLE);
|
return removeAllFromTable(getInstance(), PRE_KEYS_TABLE);
|
||||||
}
|
}
|
||||||
async function getAllPreKeys(): Promise<Array<PreKeyType>> {
|
async function getAllPreKeys(): Promise<Array<StoredPreKeyType>> {
|
||||||
return getAllFromTable(getInstance(), PRE_KEYS_TABLE);
|
return getAllFromTable(getInstance(), PRE_KEYS_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys';
|
const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys';
|
||||||
async function createOrUpdateSignedPreKey(
|
async function createOrUpdateSignedPreKey(
|
||||||
data: SignedPreKeyType
|
data: StoredSignedPreKeyType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return createOrUpdate(getInstance(), SIGNED_PRE_KEYS_TABLE, data);
|
return createOrUpdate(getInstance(), SIGNED_PRE_KEYS_TABLE, data);
|
||||||
}
|
}
|
||||||
async function getSignedPreKeyById(
|
async function getSignedPreKeyById(
|
||||||
id: SignedPreKeyIdType
|
id: SignedPreKeyIdType
|
||||||
): Promise<SignedPreKeyType | undefined> {
|
): Promise<StoredSignedPreKeyType | undefined> {
|
||||||
return getById(getInstance(), SIGNED_PRE_KEYS_TABLE, id);
|
return getById(getInstance(), SIGNED_PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function bulkAddSignedPreKeys(
|
async function bulkAddSignedPreKeys(
|
||||||
array: Array<SignedPreKeyType>
|
array: Array<StoredSignedPreKeyType>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return bulkAdd(getInstance(), SIGNED_PRE_KEYS_TABLE, array);
|
return bulkAdd(getInstance(), SIGNED_PRE_KEYS_TABLE, array);
|
||||||
}
|
}
|
||||||
async function removeSignedPreKeyById(id: SignedPreKeyIdType): Promise<void> {
|
async function removeSignedPreKeyById(id: SignedPreKeyIdType): Promise<void> {
|
||||||
return removeById(getInstance(), SIGNED_PRE_KEYS_TABLE, id);
|
return removeById(getInstance(), SIGNED_PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
|
async function removeSignedPreKeysByUuid(uuid: UUIDStringType): Promise<void> {
|
||||||
|
const db = getInstance();
|
||||||
|
db.prepare<Query>('DELETE FROM signedPreKeys WHERE ourUuid IS $uuid;').run({
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
}
|
||||||
async function removeAllSignedPreKeys(): Promise<void> {
|
async function removeAllSignedPreKeys(): Promise<void> {
|
||||||
return removeAllFromTable(getInstance(), SIGNED_PRE_KEYS_TABLE);
|
return removeAllFromTable(getInstance(), SIGNED_PRE_KEYS_TABLE);
|
||||||
}
|
}
|
||||||
async function getAllSignedPreKeys(): Promise<Array<SignedPreKeyType>> {
|
async function getAllSignedPreKeys(): Promise<Array<StoredSignedPreKeyType>> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const rows: JSONRows = db
|
const rows: JSONRows = db
|
||||||
.prepare<EmptyQuery>(
|
.prepare<EmptyQuery>(
|
||||||
|
@ -718,16 +737,16 @@ async function getAllSignedPreKeys(): Promise<Array<SignedPreKeyType>> {
|
||||||
|
|
||||||
const ITEMS_TABLE = 'items';
|
const ITEMS_TABLE = 'items';
|
||||||
async function createOrUpdateItem<K extends ItemKeyType>(
|
async function createOrUpdateItem<K extends ItemKeyType>(
|
||||||
data: ItemType<K>
|
data: StoredItemType<K>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return createOrUpdate(getInstance(), ITEMS_TABLE, data);
|
return createOrUpdate(getInstance(), ITEMS_TABLE, data);
|
||||||
}
|
}
|
||||||
async function getItemById<K extends ItemKeyType>(
|
async function getItemById<K extends ItemKeyType>(
|
||||||
id: K
|
id: K
|
||||||
): Promise<ItemType<K> | undefined> {
|
): Promise<StoredItemType<K> | undefined> {
|
||||||
return getById(getInstance(), ITEMS_TABLE, id);
|
return getById(getInstance(), ITEMS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function getAllItems(): Promise<AllItemsType> {
|
async function getAllItems(): Promise<StoredAllItemsType> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const rows: JSONRows = db
|
const rows: JSONRows = db
|
||||||
.prepare<EmptyQuery>('SELECT json FROM items ORDER BY id ASC;')
|
.prepare<EmptyQuery>('SELECT json FROM items ORDER BY id ASC;')
|
||||||
|
@ -743,7 +762,7 @@ async function getAllItems(): Promise<AllItemsType> {
|
||||||
result[id] = value;
|
result[id] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result as unknown as AllItemsType;
|
return result as unknown as StoredAllItemsType;
|
||||||
}
|
}
|
||||||
async function removeItemById(id: ItemKeyType): Promise<void> {
|
async function removeItemById(id: ItemKeyType): Promise<void> {
|
||||||
return removeById(getInstance(), ITEMS_TABLE, id);
|
return removeById(getInstance(), ITEMS_TABLE, id);
|
||||||
|
@ -2097,20 +2116,7 @@ async function getUnreadByConversationAndMarkRead({
|
||||||
newestUnreadAt: number;
|
newestUnreadAt: number;
|
||||||
storyId?: UUIDStringType;
|
storyId?: UUIDStringType;
|
||||||
readAt?: number;
|
readAt?: number;
|
||||||
}): Promise<
|
}): Promise<GetUnreadByConversationAndMarkReadResultType> {
|
||||||
Array<
|
|
||||||
{ originalReadStatus: ReadStatus | undefined } & Pick<
|
|
||||||
MessageType,
|
|
||||||
| 'id'
|
|
||||||
| 'source'
|
|
||||||
| 'sourceUuid'
|
|
||||||
| 'sent_at'
|
|
||||||
| 'type'
|
|
||||||
| 'readStatus'
|
|
||||||
| 'seenStatus'
|
|
||||||
>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const expirationStartTimestamp = Math.min(Date.now(), readAt ?? Infinity);
|
const expirationStartTimestamp = Math.min(Date.now(), readAt ?? Infinity);
|
||||||
|
@ -2203,10 +2209,6 @@ async function getUnreadByConversationAndMarkRead({
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReactionResultType = Pick<
|
|
||||||
ReactionType,
|
|
||||||
'targetAuthorUuid' | 'targetTimestamp' | 'messageId'
|
|
||||||
> & { rowid: number };
|
|
||||||
async function getUnreadReactionsAndMarkRead({
|
async function getUnreadReactionsAndMarkRead({
|
||||||
conversationId,
|
conversationId,
|
||||||
newestUnreadAt,
|
newestUnreadAt,
|
||||||
|
@ -2869,11 +2871,9 @@ async function getConversationRangeCenteredOnMessage({
|
||||||
receivedAt: number;
|
receivedAt: number;
|
||||||
sentAt?: number;
|
sentAt?: number;
|
||||||
storyId: UUIDStringType | undefined;
|
storyId: UUIDStringType | undefined;
|
||||||
}): Promise<{
|
}): Promise<
|
||||||
older: Array<MessageTypeUnhydrated>;
|
GetConversationRangeCenteredOnMessageResultType<MessageTypeUnhydrated>
|
||||||
newer: Array<MessageTypeUnhydrated>;
|
> {
|
||||||
metrics: ConversationMetricsType;
|
|
||||||
}> {
|
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Database } from 'better-sqlite3';
|
||||||
|
|
||||||
|
import type { LoggerType } from '../../types/Logging';
|
||||||
|
|
||||||
|
export default function updateToSchemaVersion64(
|
||||||
|
currentVersion: number,
|
||||||
|
db: Database,
|
||||||
|
logger: LoggerType
|
||||||
|
): void {
|
||||||
|
if (currentVersion >= 64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.exec(
|
||||||
|
`
|
||||||
|
ALTER TABLE preKeys
|
||||||
|
ADD COLUMN ourUuid STRING
|
||||||
|
GENERATED ALWAYS AS (json_extract(json, '$.ourUuid'));
|
||||||
|
|
||||||
|
CREATE INDEX preKeys_ourUuid ON preKeys (ourUuid);
|
||||||
|
|
||||||
|
ALTER TABLE signedPreKeys
|
||||||
|
ADD COLUMN ourUuid STRING
|
||||||
|
GENERATED ALWAYS AS (json_extract(json, '$.ourUuid'));
|
||||||
|
|
||||||
|
CREATE INDEX signedPreKeys_ourUuid ON signedPreKeys (ourUuid);
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
db.pragma('user_version = 64');
|
||||||
|
})();
|
||||||
|
|
||||||
|
logger.info('updateToSchemaVersion64: success!');
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ import updateToSchemaVersion60 from './60-update-expiring-index';
|
||||||
import updateToSchemaVersion61 from './61-distribution-list-storage';
|
import updateToSchemaVersion61 from './61-distribution-list-storage';
|
||||||
import updateToSchemaVersion62 from './62-add-urgent-to-send-log';
|
import updateToSchemaVersion62 from './62-add-urgent-to-send-log';
|
||||||
import updateToSchemaVersion63 from './63-add-urgent-to-unprocessed';
|
import updateToSchemaVersion63 from './63-add-urgent-to-unprocessed';
|
||||||
|
import updateToSchemaVersion64 from './64-uuid-column-for-pre-keys';
|
||||||
|
|
||||||
function updateToSchemaVersion1(
|
function updateToSchemaVersion1(
|
||||||
currentVersion: number,
|
currentVersion: number,
|
||||||
|
@ -1941,6 +1942,7 @@ export const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion61,
|
updateToSchemaVersion61,
|
||||||
updateToSchemaVersion62,
|
updateToSchemaVersion62,
|
||||||
updateToSchemaVersion63,
|
updateToSchemaVersion63,
|
||||||
|
updateToSchemaVersion64,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function updateSchema(db: Database, logger: LoggerType): void {
|
export function updateSchema(db: Database, logger: LoggerType): void {
|
||||||
|
|
|
@ -7,8 +7,12 @@ import chai, { assert } from 'chai';
|
||||||
import chaiAsPromised from 'chai-as-promised';
|
import chaiAsPromised from 'chai-as-promised';
|
||||||
import {
|
import {
|
||||||
Direction,
|
Direction,
|
||||||
|
IdentityKeyPair,
|
||||||
|
PrivateKey,
|
||||||
|
PublicKey,
|
||||||
SenderKeyRecord,
|
SenderKeyRecord,
|
||||||
SessionRecord,
|
SessionRecord,
|
||||||
|
SignedPreKeyRecord,
|
||||||
} from '@signalapp/libsignal-client';
|
} from '@signalapp/libsignal-client';
|
||||||
|
|
||||||
import { signal } from '../protobuf/compiled';
|
import { signal } from '../protobuf/compiled';
|
||||||
|
@ -18,7 +22,11 @@ import { Zone } from '../util/Zone';
|
||||||
|
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import { getRandomBytes, constantTimeEqual } from '../Crypto';
|
import { getRandomBytes, constantTimeEqual } from '../Crypto';
|
||||||
import { clampPrivateKey, setPublicKeyTypeByte } from '../Curve';
|
import {
|
||||||
|
clampPrivateKey,
|
||||||
|
setPublicKeyTypeByte,
|
||||||
|
generateSignedPreKey,
|
||||||
|
} from '../Curve';
|
||||||
import type { SignalProtocolStore } from '../SignalProtocolStore';
|
import type { SignalProtocolStore } from '../SignalProtocolStore';
|
||||||
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
|
@ -134,8 +142,8 @@ describe('SignalProtocolStore', () => {
|
||||||
window.storage.put('registrationIdMap', { [ourUuid.toString()]: 1337 });
|
window.storage.put('registrationIdMap', { [ourUuid.toString()]: 1337 });
|
||||||
window.storage.put('identityKeyMap', {
|
window.storage.put('identityKeyMap', {
|
||||||
[ourUuid.toString()]: {
|
[ourUuid.toString()]: {
|
||||||
privKey: Bytes.toBase64(identityKey.privKey),
|
privKey: identityKey.privKey,
|
||||||
pubKey: Bytes.toBase64(identityKey.pubKey),
|
pubKey: identityKey.pubKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await window.storage.fetch();
|
await window.storage.fetch();
|
||||||
|
@ -1766,4 +1774,80 @@ describe('SignalProtocolStore', () => {
|
||||||
assert.strictEqual(items.length, 0);
|
assert.strictEqual(items.length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('removeOurOldPni/updateOurPniKeyMaterial', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await store.storePreKey(ourUuid, 2, testKey);
|
||||||
|
await store.storeSignedPreKey(ourUuid, 3, testKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes old data and sets new', async () => {
|
||||||
|
const oldPni = ourUuid;
|
||||||
|
const newPni = UUID.generate();
|
||||||
|
|
||||||
|
const newIdentity = IdentityKeyPair.generate();
|
||||||
|
|
||||||
|
const data = generateSignedPreKey(
|
||||||
|
{
|
||||||
|
pubKey: newIdentity.publicKey.serialize(),
|
||||||
|
privKey: newIdentity.privateKey.serialize(),
|
||||||
|
},
|
||||||
|
8201
|
||||||
|
);
|
||||||
|
const createdAt = Date.now() - 1241;
|
||||||
|
const signedPreKey = SignedPreKeyRecord.new(
|
||||||
|
data.keyId,
|
||||||
|
createdAt,
|
||||||
|
PublicKey.deserialize(Buffer.from(data.keyPair.pubKey)),
|
||||||
|
PrivateKey.deserialize(Buffer.from(data.keyPair.privKey)),
|
||||||
|
Buffer.from(data.signature)
|
||||||
|
);
|
||||||
|
|
||||||
|
await store.removeOurOldPni(oldPni);
|
||||||
|
await store.updateOurPniKeyMaterial(newPni, {
|
||||||
|
identityKeyPair: newIdentity.serialize(),
|
||||||
|
signedPreKey: signedPreKey.serialize(),
|
||||||
|
registrationId: 5231,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Old data has to be removed
|
||||||
|
assert.isUndefined(await store.getIdentityKeyPair(oldPni));
|
||||||
|
assert.isUndefined(await store.getLocalRegistrationId(oldPni));
|
||||||
|
assert.isUndefined(await store.loadPreKey(oldPni, 2));
|
||||||
|
assert.isUndefined(await store.loadSignedPreKey(oldPni, 3));
|
||||||
|
|
||||||
|
// New data has to be added
|
||||||
|
const storedIdentity = await store.getIdentityKeyPair(newPni);
|
||||||
|
if (!storedIdentity) {
|
||||||
|
throw new Error('New identity not found');
|
||||||
|
}
|
||||||
|
assert.isTrue(
|
||||||
|
Bytes.areEqual(
|
||||||
|
storedIdentity.privKey,
|
||||||
|
newIdentity.privateKey.serialize()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert.isTrue(
|
||||||
|
Bytes.areEqual(storedIdentity.pubKey, newIdentity.publicKey.serialize())
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedSignedPreKey = await store.loadSignedPreKey(newPni, 8201);
|
||||||
|
if (!storedSignedPreKey) {
|
||||||
|
throw new Error('New signed pre key not found');
|
||||||
|
}
|
||||||
|
assert.isTrue(
|
||||||
|
Bytes.areEqual(
|
||||||
|
storedSignedPreKey.publicKey().serialize(),
|
||||||
|
data.keyPair.pubKey
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert.isTrue(
|
||||||
|
Bytes.areEqual(
|
||||||
|
storedSignedPreKey.privateKey().serialize(),
|
||||||
|
data.keyPair.privKey
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert.strictEqual(storedSignedPreKey.timestamp(), createdAt);
|
||||||
|
// Note: signature is ignored.
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { toBase64 } from '../../Bytes';
|
|
||||||
import { constantTimeEqual } from '../../Crypto';
|
import { constantTimeEqual } from '../../Crypto';
|
||||||
import { generateKeyPair } from '../../Curve';
|
import { generateKeyPair } from '../../Curve';
|
||||||
import type { GeneratedKeysType } from '../../textsecure/AccountManager';
|
import type { GeneratedKeysType } from '../../textsecure/AccountManager';
|
||||||
|
@ -71,10 +70,7 @@ describe('Key generation', function thisNeeded() {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const keyPair = generateKeyPair();
|
const keyPair = generateKeyPair();
|
||||||
await textsecure.storage.put('identityKeyMap', {
|
await textsecure.storage.put('identityKeyMap', {
|
||||||
[ourUuid.toString()]: {
|
[ourUuid.toString()]: keyPair,
|
||||||
privKey: toBase64(keyPair.privKey),
|
|
||||||
pubKey: toBase64(keyPair.pubKey),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await textsecure.storage.user.setUuidAndDeviceId(ourUuid.toString(), 1);
|
await textsecure.storage.user.setUuidAndDeviceId(ourUuid.toString(), 1);
|
||||||
await textsecure.storage.protocol.hydrateCaches();
|
await textsecure.storage.protocol.hydrateCaches();
|
||||||
|
|
|
@ -330,8 +330,6 @@ export class Bootstrap {
|
||||||
directoryV3Url: url,
|
directoryV3Url: url,
|
||||||
directoryV3MRENCLAVE:
|
directoryV3MRENCLAVE:
|
||||||
'51133fecb3fa18aaf0c8f64cb763656d3272d9faaacdb26ae7df082e414fb142',
|
'51133fecb3fa18aaf0c8f64cb763656d3272d9faaacdb26ae7df082e414fb142',
|
||||||
directoryV3Root:
|
|
||||||
'-----BEGIN CERTIFICATE-----\nMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw\naDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv\ncnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ\nBgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG\nA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0\naW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT\nAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7\n1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB\nuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ\nMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50\nZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV\nUr9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI\nKoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg\nAiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=\n-----END CERTIFICATE-----\n',
|
|
||||||
|
|
||||||
...this.options.extraConfig,
|
...this.options.extraConfig,
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { UUIDKind } from '@signalapp/mock-server';
|
||||||
|
import createDebug from 'debug';
|
||||||
|
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
import type { App } from '../bootstrap';
|
||||||
|
|
||||||
|
export const debug = createDebug('mock:test:change-number');
|
||||||
|
|
||||||
|
describe('change number', function needsName() {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap: Bootstrap;
|
||||||
|
let app: App;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
bootstrap = new Bootstrap();
|
||||||
|
await bootstrap.init();
|
||||||
|
app = await bootstrap.link();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function after() {
|
||||||
|
if (this.currentTest?.state !== 'passed') {
|
||||||
|
await bootstrap.saveLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept sync message and update keys', async () => {
|
||||||
|
const { server, phone, desktop, contacts } = bootstrap;
|
||||||
|
|
||||||
|
const [first] = contacts;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
|
||||||
|
debug('prepare a message for original PNI');
|
||||||
|
const messageBefore = await first.encryptText(desktop, 'Before', {
|
||||||
|
uuidKind: UUIDKind.PNI,
|
||||||
|
});
|
||||||
|
|
||||||
|
debug('preparing change number');
|
||||||
|
const changeNumber = await phone.prepareChangeNumber();
|
||||||
|
|
||||||
|
const newKey = await desktop.popSingleUseKey(UUIDKind.PNI);
|
||||||
|
await first.addSingleUseKey(desktop, newKey, UUIDKind.PNI);
|
||||||
|
|
||||||
|
debug('prepare a message for updated PNI');
|
||||||
|
const messageAfter = await first.encryptText(desktop, 'After', {
|
||||||
|
uuidKind: UUIDKind.PNI,
|
||||||
|
});
|
||||||
|
|
||||||
|
debug('sending all messages');
|
||||||
|
await Promise.all([
|
||||||
|
server.send(desktop, messageBefore),
|
||||||
|
phone.sendChangeNumber(changeNumber),
|
||||||
|
server.send(desktop, messageAfter),
|
||||||
|
]);
|
||||||
|
|
||||||
|
debug('opening conversation with the first contact');
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(first.profileName)}] ` +
|
||||||
|
' >> "After"'
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
debug('done');
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import type { PrimaryDevice, Group } from '@signalapp/mock-server';
|
import type { PrimaryDevice, Group } from '@signalapp/mock-server';
|
||||||
import { StorageState, Proto } from '@signalapp/mock-server';
|
import { StorageState, Proto, UUIDKind } from '@signalapp/mock-server';
|
||||||
import createDebug from 'debug';
|
import createDebug from 'debug';
|
||||||
|
|
||||||
import * as durations from '../../util/durations';
|
import * as durations from '../../util/durations';
|
||||||
|
@ -55,7 +55,7 @@ describe('gv2', function needsName() {
|
||||||
identityState: Proto.ContactRecord.IdentityState.VERIFIED,
|
identityState: Proto.ContactRecord.IdentityState.VERIFIED,
|
||||||
whitelisted: true,
|
whitelisted: true,
|
||||||
|
|
||||||
identityKey: pniContact.pniPublicKey.serialize(),
|
identityKey: pniContact.getPublicKey(UUIDKind.PNI).serialize(),
|
||||||
|
|
||||||
// Give PNI as the uuid!
|
// Give PNI as the uuid!
|
||||||
serviceUuid: pniContact.device.pni,
|
serviceUuid: pniContact.device.pni,
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { mapObjectWithSpec } from '../../util/mapObjectWithSpec';
|
||||||
|
|
||||||
|
describe('mapObjectWithSpec', () => {
|
||||||
|
const increment = (value: number) => value + 1;
|
||||||
|
|
||||||
|
it('maps a single key/value pair', () => {
|
||||||
|
assert.deepStrictEqual(mapObjectWithSpec('a', { a: 1 }, increment), {
|
||||||
|
a: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps a multiple key/value pairs', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mapObjectWithSpec(['a', 'b'], { a: 1, b: 2 }, increment),
|
||||||
|
{ a: 2, b: 3 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps a key with a value spec', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mapObjectWithSpec(
|
||||||
|
{
|
||||||
|
key: 'a',
|
||||||
|
valueSpec: ['b', 'c'],
|
||||||
|
},
|
||||||
|
{ a: { b: 1, c: 2 } },
|
||||||
|
increment
|
||||||
|
),
|
||||||
|
{ a: { b: 2, c: 3 } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maps a map with a value spec', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mapObjectWithSpec(
|
||||||
|
{
|
||||||
|
isMap: true,
|
||||||
|
valueSpec: ['b', 'c'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key1: { b: 1, c: 2 },
|
||||||
|
key2: { b: 3, c: 4 },
|
||||||
|
},
|
||||||
|
increment
|
||||||
|
),
|
||||||
|
{
|
||||||
|
key1: { b: 2, c: 3 },
|
||||||
|
key2: { b: 4, c: 5 },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('map undefined to undefined', () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
mapObjectWithSpec('a', undefined, increment),
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -7,7 +7,7 @@ import { omit } from 'lodash';
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
import type { WebAPIType } from './WebAPI';
|
import type { WebAPIType } from './WebAPI';
|
||||||
import { HTTPError } from './Errors';
|
import { HTTPError } from './Errors';
|
||||||
import type { KeyPairType } from './Types.d';
|
import type { KeyPairType, PniKeyMaterialType } from './Types.d';
|
||||||
import ProvisioningCipher from './ProvisioningCipher';
|
import ProvisioningCipher from './ProvisioningCipher';
|
||||||
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
||||||
import createTaskWithTimeout from './TaskWithTimeout';
|
import createTaskWithTimeout from './TaskWithTimeout';
|
||||||
|
@ -332,6 +332,7 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, uuidKind);
|
const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, uuidKind);
|
||||||
await this.server.registerKeys(keys, uuidKind);
|
await this.server.registerKeys(keys, uuidKind);
|
||||||
|
await this.confirmKeys(keys, uuidKind);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,16 +650,10 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
const identityKeyMap = {
|
const identityKeyMap = {
|
||||||
...(storage.get('identityKeyMap') || {}),
|
...(storage.get('identityKeyMap') || {}),
|
||||||
[ourUuid]: {
|
[ourUuid]: aciKeyPair,
|
||||||
pubKey: Bytes.toBase64(aciKeyPair.pubKey),
|
|
||||||
privKey: Bytes.toBase64(aciKeyPair.privKey),
|
|
||||||
},
|
|
||||||
...(pniKeyPair
|
...(pniKeyPair
|
||||||
? {
|
? {
|
||||||
[ourPni]: {
|
[ourPni]: pniKeyPair,
|
||||||
pubKey: Bytes.toBase64(pniKeyPair.pubKey),
|
|
||||||
privKey: Bytes.toBase64(pniKeyPair.privKey),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
@ -702,30 +697,18 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
log.info('AccountManager.updatePNIIdentity: generating new keys');
|
log.info('AccountManager.updatePNIIdentity: generating new keys');
|
||||||
|
|
||||||
return this.queueTask(async () => {
|
await this.queueTask(async () => {
|
||||||
const keys = await this.generateKeys(
|
|
||||||
SIGNED_KEY_GEN_BATCH_SIZE,
|
|
||||||
UUIDKind.PNI,
|
|
||||||
identityKeyPair
|
|
||||||
);
|
|
||||||
await this.server.registerKeys(keys, UUIDKind.PNI);
|
|
||||||
await this.confirmKeys(keys, UUIDKind.PNI);
|
|
||||||
|
|
||||||
// Server has accepted our keys which means we have the latest PNI identity
|
// Server has accepted our keys which means we have the latest PNI identity
|
||||||
// now that doesn't conflict the PNI identity of the primary device.
|
// now that doesn't conflict the PNI identity of the primary device.
|
||||||
log.info(
|
log.info(
|
||||||
'AccountManager.updatePNIIdentity: updating identity key ' +
|
'AccountManager.updatePNIIdentity: updating identity key ' +
|
||||||
'and registration id'
|
'and registration id'
|
||||||
);
|
);
|
||||||
const { pubKey, privKey } = identityKeyPair;
|
|
||||||
|
|
||||||
const pni = storage.user.getCheckedUuid(UUIDKind.PNI);
|
const pni = storage.user.getCheckedUuid(UUIDKind.PNI);
|
||||||
const identityKeyMap = {
|
const identityKeyMap = {
|
||||||
...(storage.get('identityKeyMap') || {}),
|
...(storage.get('identityKeyMap') || {}),
|
||||||
[pni.toString()]: {
|
[pni.toString()]: identityKeyPair,
|
||||||
pubKey: Bytes.toBase64(pubKey),
|
|
||||||
privKey: Bytes.toBase64(privKey),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const aci = storage.user.getCheckedUuid(UUIDKind.ACI);
|
const aci = storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
|
@ -744,6 +727,26 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
await storage.protocol.hydrateCaches();
|
await storage.protocol.hydrateCaches();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Intentionally not awaiting becase `updatePNIIdentity` runs on an
|
||||||
|
// Encrypted queue of MessageReceiver and we don't want to await remote
|
||||||
|
// endpoints and block message processing.
|
||||||
|
this.queueTask(async () => {
|
||||||
|
try {
|
||||||
|
const keys = await this.generateKeys(
|
||||||
|
SIGNED_KEY_GEN_BATCH_SIZE,
|
||||||
|
UUIDKind.PNI,
|
||||||
|
identityKeyPair
|
||||||
|
);
|
||||||
|
await this.server.registerKeys(keys, UUIDKind.PNI);
|
||||||
|
await this.confirmKeys(keys, UUIDKind.PNI);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
'updatePNIIdentity: Failed to upload PNI prekeys. Moving on',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes the same object returned by generateKeys
|
// Takes the same object returned by generateKeys
|
||||||
|
@ -841,30 +844,50 @@ export default class AccountManager extends EventTarget {
|
||||||
this.dispatchEvent(new Event('registration'));
|
this.dispatchEvent(new Event('registration'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPni(pni: string): Promise<void> {
|
async setPni(pni: string, keyMaterial?: PniKeyMaterialType): Promise<void> {
|
||||||
const { storage } = window.textsecure;
|
const { storage } = window.textsecure;
|
||||||
|
|
||||||
const oldPni = storage.user.getUuid(UUIDKind.PNI)?.toString();
|
const oldPni = storage.user.getUuid(UUIDKind.PNI)?.toString();
|
||||||
if (oldPni === pni) {
|
if (oldPni === pni && !keyMaterial) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(`AccountManager.setPni(${pni}): updating from ${oldPni}`);
|
||||||
|
|
||||||
if (oldPni) {
|
if (oldPni) {
|
||||||
await Promise.all([
|
await storage.protocol.removeOurOldPni(new UUID(oldPni));
|
||||||
storage.put(
|
|
||||||
'identityKeyMap',
|
|
||||||
omit(storage.get('identityKeyMap') || {}, oldPni)
|
|
||||||
),
|
|
||||||
storage.put(
|
|
||||||
'registrationIdMap',
|
|
||||||
omit(storage.get('registrationIdMap') || {}, oldPni)
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`AccountManager.setPni: updating pni from ${oldPni} to ${pni}`);
|
|
||||||
await storage.user.setPni(pni);
|
await storage.user.setPni(pni);
|
||||||
|
|
||||||
await storage.protocol.hydrateCaches();
|
if (keyMaterial) {
|
||||||
|
await storage.protocol.updateOurPniKeyMaterial(
|
||||||
|
new UUID(pni),
|
||||||
|
keyMaterial
|
||||||
|
);
|
||||||
|
|
||||||
|
// Intentionally not awaiting since this is processed on encrypted queue
|
||||||
|
// of MessageReceiver.
|
||||||
|
this.queueTask(async () => {
|
||||||
|
try {
|
||||||
|
const keys = await this.generateKeys(
|
||||||
|
SIGNED_KEY_GEN_BATCH_SIZE,
|
||||||
|
UUIDKind.PNI
|
||||||
|
);
|
||||||
|
await this.server.registerKeys(keys, UUIDKind.PNI);
|
||||||
|
await this.confirmKeys(keys, UUIDKind.PNI);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
'setPni: Failed to upload PNI prekeys. Moving on',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PNI has changed and credentials are no longer valid
|
||||||
|
await storage.put('groupCredentials', []);
|
||||||
|
} else {
|
||||||
|
log.warn(`AccountManager.setPni(${pni}): no key material`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,6 @@ import {
|
||||||
MessageRequestResponseEvent,
|
MessageRequestResponseEvent,
|
||||||
FetchLatestEvent,
|
FetchLatestEvent,
|
||||||
KeysEvent,
|
KeysEvent,
|
||||||
PNIIdentityEvent,
|
|
||||||
StickerPackEvent,
|
StickerPackEvent,
|
||||||
ReadSyncEvent,
|
ReadSyncEvent,
|
||||||
ViewSyncEvent,
|
ViewSyncEvent,
|
||||||
|
@ -256,8 +255,6 @@ export default class MessageReceiver
|
||||||
|
|
||||||
private stoppingProcessing?: boolean;
|
private stoppingProcessing?: boolean;
|
||||||
|
|
||||||
private pendingPNIIdentityEvent?: PNIIdentityEvent;
|
|
||||||
|
|
||||||
constructor({ server, storage, serverTrustRoot }: MessageReceiverOptions) {
|
constructor({ server, storage, serverTrustRoot }: MessageReceiverOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -376,6 +373,14 @@ export default class MessageReceiver
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
: ourUuid,
|
: ourUuid,
|
||||||
|
updatedPni: decoded.updatedPni
|
||||||
|
? new UUID(
|
||||||
|
normalizeUuid(
|
||||||
|
decoded.updatedPni,
|
||||||
|
'MessageReceiver.handleRequest.updatedPni'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
timestamp: decoded.timestamp?.toNumber(),
|
timestamp: decoded.timestamp?.toNumber(),
|
||||||
content: dropNull(decoded.content),
|
content: dropNull(decoded.content),
|
||||||
serverGuid: decoded.serverGuid,
|
serverGuid: decoded.serverGuid,
|
||||||
|
@ -535,11 +540,6 @@ export default class MessageReceiver
|
||||||
handler: (ev: KeysEvent) => void
|
handler: (ev: KeysEvent) => void
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
public override addEventListener(
|
|
||||||
name: 'pniIdentity',
|
|
||||||
handler: (ev: PNIIdentityEvent) => void
|
|
||||||
): void;
|
|
||||||
|
|
||||||
public override addEventListener(
|
public override addEventListener(
|
||||||
name: 'sticker-pack',
|
name: 'sticker-pack',
|
||||||
handler: (ev: StickerPackEvent) => void
|
handler: (ev: StickerPackEvent) => void
|
||||||
|
@ -671,13 +671,6 @@ export default class MessageReceiver
|
||||||
this.isEmptied = true;
|
this.isEmptied = true;
|
||||||
|
|
||||||
this.maybeScheduleRetryTimeout();
|
this.maybeScheduleRetryTimeout();
|
||||||
|
|
||||||
// Emit PNI identity event after processing the queue
|
|
||||||
const { pendingPNIIdentityEvent } = this;
|
|
||||||
this.pendingPNIIdentityEvent = undefined;
|
|
||||||
if (pendingPNIIdentityEvent) {
|
|
||||||
await this.dispatchAndWait(pendingPNIIdentityEvent);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForDecryptedQueue = async () => {
|
const waitForDecryptedQueue = async () => {
|
||||||
|
@ -772,6 +765,9 @@ export default class MessageReceiver
|
||||||
destinationUuid: new UUID(
|
destinationUuid: new UUID(
|
||||||
decoded.destinationUuid || item.destinationUuid || ourUuid.toString()
|
decoded.destinationUuid || item.destinationUuid || ourUuid.toString()
|
||||||
),
|
),
|
||||||
|
updatedPni: decoded.updatedPni
|
||||||
|
? new UUID(decoded.updatedPni)
|
||||||
|
: undefined,
|
||||||
timestamp: decoded.timestamp?.toNumber(),
|
timestamp: decoded.timestamp?.toNumber(),
|
||||||
content: dropNull(decoded.content),
|
content: dropNull(decoded.content),
|
||||||
serverGuid: decoded.serverGuid,
|
serverGuid: decoded.serverGuid,
|
||||||
|
@ -902,16 +898,6 @@ export default class MessageReceiver
|
||||||
items.map(async ({ data, envelope }) => {
|
items.map(async ({ data, envelope }) => {
|
||||||
try {
|
try {
|
||||||
const { destinationUuid } = envelope;
|
const { destinationUuid } = envelope;
|
||||||
const uuidKind =
|
|
||||||
this.storage.user.getOurUuidKind(destinationUuid);
|
|
||||||
if (uuidKind === UUIDKind.Unknown) {
|
|
||||||
log.warn(
|
|
||||||
'MessageReceiver.decryptAndCacheBatch: ' +
|
|
||||||
`Rejecting envelope ${getEnvelopeId(envelope)}, ` +
|
|
||||||
`unknown uuid: ${destinationUuid}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let stores = storesMap.get(destinationUuid.toString());
|
let stores = storesMap.get(destinationUuid.toString());
|
||||||
if (!stores) {
|
if (!stores) {
|
||||||
|
@ -935,8 +921,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const result = await this.queueEncryptedEnvelope(
|
const result = await this.queueEncryptedEnvelope(
|
||||||
stores,
|
stores,
|
||||||
envelope,
|
envelope
|
||||||
uuidKind
|
|
||||||
);
|
);
|
||||||
if (result.plaintext) {
|
if (result.plaintext) {
|
||||||
decrypted.push({
|
decrypted.push({
|
||||||
|
@ -972,6 +957,7 @@ export default class MessageReceiver
|
||||||
sourceUuid: envelope.sourceUuid,
|
sourceUuid: envelope.sourceUuid,
|
||||||
sourceDevice: envelope.sourceDevice,
|
sourceDevice: envelope.sourceDevice,
|
||||||
destinationUuid: envelope.destinationUuid.toString(),
|
destinationUuid: envelope.destinationUuid.toString(),
|
||||||
|
updatedPni: envelope.updatedPni?.toString(),
|
||||||
serverGuid: envelope.serverGuid,
|
serverGuid: envelope.serverGuid,
|
||||||
serverTimestamp: envelope.serverTimestamp,
|
serverTimestamp: envelope.serverTimestamp,
|
||||||
decrypted: Bytes.toBase64(plaintext),
|
decrypted: Bytes.toBase64(plaintext),
|
||||||
|
@ -1089,13 +1075,23 @@ export default class MessageReceiver
|
||||||
|
|
||||||
private async queueEncryptedEnvelope(
|
private async queueEncryptedEnvelope(
|
||||||
stores: LockedStores,
|
stores: LockedStores,
|
||||||
envelope: ProcessedEnvelope,
|
envelope: ProcessedEnvelope
|
||||||
uuidKind: UUIDKind
|
|
||||||
): Promise<DecryptResult> {
|
): Promise<DecryptResult> {
|
||||||
let logId = getEnvelopeId(envelope);
|
let logId = getEnvelopeId(envelope);
|
||||||
log.info(`queueing ${uuidKind} envelope`, logId);
|
log.info('queueing envelope', logId);
|
||||||
|
|
||||||
const task = async (): Promise<DecryptResult> => {
|
const task = async (): Promise<DecryptResult> => {
|
||||||
|
const { destinationUuid } = envelope;
|
||||||
|
const uuidKind = this.storage.user.getOurUuidKind(destinationUuid);
|
||||||
|
if (uuidKind === UUIDKind.Unknown) {
|
||||||
|
log.warn(
|
||||||
|
'MessageReceiver.decryptAndCacheBatch: ' +
|
||||||
|
`Rejecting envelope ${getEnvelopeId(envelope)}, ` +
|
||||||
|
`unknown uuid: ${destinationUuid}`
|
||||||
|
);
|
||||||
|
return { plaintext: undefined, envelope };
|
||||||
|
}
|
||||||
|
|
||||||
const unsealedEnvelope = await this.unsealEnvelope(
|
const unsealedEnvelope = await this.unsealEnvelope(
|
||||||
stores,
|
stores,
|
||||||
envelope,
|
envelope,
|
||||||
|
@ -1311,6 +1307,19 @@ export default class MessageReceiver
|
||||||
content.senderKeyDistributionMessage
|
content.senderKeyDistributionMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some sync messages have to be fully processed in the middle of
|
||||||
|
// decryption queue since subsequent envelopes use their key material.
|
||||||
|
const { syncMessage } = content;
|
||||||
|
if (syncMessage?.pniIdentity) {
|
||||||
|
await this.handlePNIIdentity(envelope, syncMessage.pniIdentity);
|
||||||
|
return { plaintext: undefined, envelope };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syncMessage?.pniChangeNumber) {
|
||||||
|
await this.handlePNIChangeNumber(envelope, syncMessage.pniChangeNumber);
|
||||||
|
return { plaintext: undefined, envelope };
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
'MessageReceiver.decryptEnvelope: Failed to process sender ' +
|
'MessageReceiver.decryptEnvelope: Failed to process sender ' +
|
||||||
|
@ -2575,6 +2584,9 @@ export default class MessageReceiver
|
||||||
if (envelope.sourceDevice == ourDeviceId) {
|
if (envelope.sourceDevice == ourDeviceId) {
|
||||||
throw new Error('Received sync message from our own device');
|
throw new Error('Received sync message from our own device');
|
||||||
}
|
}
|
||||||
|
if (syncMessage.pniIdentity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (syncMessage.sent) {
|
if (syncMessage.sent) {
|
||||||
const sentMessage = syncMessage.sent;
|
const sentMessage = syncMessage.sent;
|
||||||
|
|
||||||
|
@ -2615,7 +2627,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
if (this.isInvalidGroupData(sentMessage.message, envelope)) {
|
if (this.isInvalidGroupData(sentMessage.message, envelope)) {
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.checkGroupV1Data(sentMessage.message);
|
await this.checkGroupV1Data(sentMessage.message);
|
||||||
|
@ -2633,11 +2645,11 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
if (syncMessage.contacts) {
|
if (syncMessage.contacts) {
|
||||||
this.handleContacts(envelope, syncMessage.contacts);
|
this.handleContacts(envelope, syncMessage.contacts);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (syncMessage.groups) {
|
if (syncMessage.groups) {
|
||||||
this.handleGroups(envelope, syncMessage.groups);
|
this.handleGroups(envelope, syncMessage.groups);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (syncMessage.blocked) {
|
if (syncMessage.blocked) {
|
||||||
return this.handleBlocked(envelope, syncMessage.blocked);
|
return this.handleBlocked(envelope, syncMessage.blocked);
|
||||||
|
@ -2645,7 +2657,7 @@ export default class MessageReceiver
|
||||||
if (syncMessage.request) {
|
if (syncMessage.request) {
|
||||||
log.info('Got SyncMessage Request');
|
log.info('Got SyncMessage Request');
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (syncMessage.read && syncMessage.read.length) {
|
if (syncMessage.read && syncMessage.read.length) {
|
||||||
return this.handleRead(envelope, syncMessage.read);
|
return this.handleRead(envelope, syncMessage.read);
|
||||||
|
@ -2653,7 +2665,7 @@ export default class MessageReceiver
|
||||||
if (syncMessage.verified) {
|
if (syncMessage.verified) {
|
||||||
log.info('Got verified sync message, dropping');
|
log.info('Got verified sync message, dropping');
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (syncMessage.configuration) {
|
if (syncMessage.configuration) {
|
||||||
return this.handleConfiguration(envelope, syncMessage.configuration);
|
return this.handleConfiguration(envelope, syncMessage.configuration);
|
||||||
|
@ -2682,9 +2694,6 @@ export default class MessageReceiver
|
||||||
if (syncMessage.keys) {
|
if (syncMessage.keys) {
|
||||||
return this.handleKeys(envelope, syncMessage.keys);
|
return this.handleKeys(envelope, syncMessage.keys);
|
||||||
}
|
}
|
||||||
if (syncMessage.pniIdentity) {
|
|
||||||
return this.handlePNIIdentity(envelope, syncMessage.pniIdentity);
|
|
||||||
}
|
|
||||||
if (syncMessage.viewed && syncMessage.viewed.length) {
|
if (syncMessage.viewed && syncMessage.viewed.length) {
|
||||||
return this.handleViewed(envelope, syncMessage.viewed);
|
return this.handleViewed(envelope, syncMessage.viewed);
|
||||||
}
|
}
|
||||||
|
@ -2693,7 +2702,6 @@ export default class MessageReceiver
|
||||||
log.warn(
|
log.warn(
|
||||||
`handleSyncMessage/${getEnvelopeId(envelope)}: Got empty SyncMessage`
|
`handleSyncMessage/${getEnvelopeId(envelope)}: Got empty SyncMessage`
|
||||||
);
|
);
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleConfiguration(
|
private async handleConfiguration(
|
||||||
|
@ -2813,6 +2821,7 @@ export default class MessageReceiver
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs on TaskType.Encrypted queue
|
||||||
private async handlePNIIdentity(
|
private async handlePNIIdentity(
|
||||||
envelope: ProcessedEnvelope,
|
envelope: ProcessedEnvelope,
|
||||||
{ publicKey, privateKey }: Proto.SyncMessage.IPniIdentity
|
{ publicKey, privateKey }: Proto.SyncMessage.IPniIdentity
|
||||||
|
@ -2823,22 +2832,47 @@ export default class MessageReceiver
|
||||||
|
|
||||||
if (!publicKey || !privateKey) {
|
if (!publicKey || !privateKey) {
|
||||||
log.warn('MessageReceiver: empty pni identity sync message');
|
log.warn('MessageReceiver: empty pni identity sync message');
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ev = new PNIIdentityEvent(
|
const manager = window.getAccountManager();
|
||||||
{ publicKey, privateKey },
|
await manager.updatePNIIdentity({ privKey: privateKey, pubKey: publicKey });
|
||||||
this.removeFromCache.bind(this, envelope)
|
}
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isEmptied) {
|
// Runs on TaskType.Encrypted queue
|
||||||
log.info('MessageReceiver: emitting pni identity sync message');
|
private async handlePNIChangeNumber(
|
||||||
return this.dispatchAndWait(ev);
|
envelope: ProcessedEnvelope,
|
||||||
|
{
|
||||||
|
identityKeyPair,
|
||||||
|
signedPreKey,
|
||||||
|
registrationId,
|
||||||
|
}: Proto.SyncMessage.IPniChangeNumber
|
||||||
|
): Promise<void> {
|
||||||
|
log.info('MessageReceiver: got pni change number sync message');
|
||||||
|
|
||||||
|
logUnexpectedUrgentValue(envelope, 'pniIdentitySync');
|
||||||
|
|
||||||
|
const { updatedPni } = envelope;
|
||||||
|
if (!updatedPni) {
|
||||||
|
log.warn('MessageReceiver: missing pni in change number sync message');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('MessageReceiver: scheduling pni identity sync message');
|
if (
|
||||||
this.pendingPNIIdentityEvent?.confirm();
|
!Bytes.isNotEmpty(identityKeyPair) ||
|
||||||
this.pendingPNIIdentityEvent = ev;
|
!Bytes.isNotEmpty(signedPreKey) ||
|
||||||
|
!isNumber(registrationId)
|
||||||
|
) {
|
||||||
|
log.warn('MessageReceiver: empty pni change number sync message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manager = window.getAccountManager();
|
||||||
|
await manager.setPni(updatedPni.toString(), {
|
||||||
|
identityKeyPair,
|
||||||
|
signedPreKey,
|
||||||
|
registrationId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleStickerPackOperation(
|
private async handleStickerPackOperation(
|
||||||
|
|
|
@ -87,6 +87,7 @@ export type ProcessedEnvelope = Readonly<{
|
||||||
sourceUuid?: UUIDStringType;
|
sourceUuid?: UUIDStringType;
|
||||||
sourceDevice?: number;
|
sourceDevice?: number;
|
||||||
destinationUuid: UUID;
|
destinationUuid: UUID;
|
||||||
|
updatedPni?: UUID;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
content?: Uint8Array;
|
content?: Uint8Array;
|
||||||
serverGuid: string;
|
serverGuid: string;
|
||||||
|
@ -270,3 +271,9 @@ export interface CallbackResultType {
|
||||||
export interface IRequestHandler {
|
export interface IRequestHandler {
|
||||||
handleRequest(request: IncomingWebSocketRequest): void;
|
handleRequest(request: IncomingWebSocketRequest): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PniKeyMaterialType = Readonly<{
|
||||||
|
identityKeyPair: Uint8Array;
|
||||||
|
signedPreKey: Uint8Array;
|
||||||
|
registrationId: number;
|
||||||
|
}>;
|
||||||
|
|
|
@ -566,7 +566,6 @@ type DirectoryV3OptionsType = Readonly<{
|
||||||
directoryVersion: 3;
|
directoryVersion: 3;
|
||||||
directoryV3Url: string;
|
directoryV3Url: string;
|
||||||
directoryV3MRENCLAVE: string;
|
directoryV3MRENCLAVE: string;
|
||||||
directoryV3Root: string;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type OptionalDirectoryFieldsType = {
|
type OptionalDirectoryFieldsType = {
|
||||||
|
@ -578,7 +577,6 @@ type OptionalDirectoryFieldsType = {
|
||||||
directoryV2CodeHashes?: unknown;
|
directoryV2CodeHashes?: unknown;
|
||||||
directoryV3Url?: unknown;
|
directoryV3Url?: unknown;
|
||||||
directoryV3MRENCLAVE?: unknown;
|
directoryV3MRENCLAVE?: unknown;
|
||||||
directoryV3Root?: unknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type DirectoryOptionsType = OptionalDirectoryFieldsType &
|
type DirectoryOptionsType = OptionalDirectoryFieldsType &
|
||||||
|
@ -803,6 +801,11 @@ export type GetGroupCredentialsOptionsType = Readonly<{
|
||||||
endDayInMs: number;
|
endDayInMs: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type GetGroupCredentialsResultType = Readonly<{
|
||||||
|
pni?: string | null;
|
||||||
|
credentials: ReadonlyArray<GroupCredentialType>;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type WebAPIType = {
|
export type WebAPIType = {
|
||||||
startRegistration(): unknown;
|
startRegistration(): unknown;
|
||||||
finishRegistration(baton: unknown): void;
|
finishRegistration(baton: unknown): void;
|
||||||
|
@ -831,7 +834,7 @@ export type WebAPIType = {
|
||||||
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
||||||
getGroupCredentials: (
|
getGroupCredentials: (
|
||||||
options: GetGroupCredentialsOptionsType
|
options: GetGroupCredentialsOptionsType
|
||||||
) => Promise<Array<GroupCredentialType>>;
|
) => Promise<GetGroupCredentialsResultType>;
|
||||||
getGroupExternalCredential: (
|
getGroupExternalCredential: (
|
||||||
options: GroupCredentialsType
|
options: GroupCredentialsType
|
||||||
) => Promise<Proto.GroupExternalCredential>;
|
) => Promise<Proto.GroupExternalCredential>;
|
||||||
|
@ -1205,8 +1208,7 @@ export function initialize({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (directoryConfig.directoryVersion === 3) {
|
} else if (directoryConfig.directoryVersion === 3) {
|
||||||
const { directoryV3Url, directoryV3MRENCLAVE, directoryV3Root } =
|
const { directoryV3Url, directoryV3MRENCLAVE } = directoryConfig;
|
||||||
directoryConfig;
|
|
||||||
|
|
||||||
cds = new CDSI({
|
cds = new CDSI({
|
||||||
logger: log,
|
logger: log,
|
||||||
|
@ -1214,7 +1216,6 @@ export function initialize({
|
||||||
|
|
||||||
url: directoryV3Url,
|
url: directoryV3Url,
|
||||||
mrenclave: directoryV3MRENCLAVE,
|
mrenclave: directoryV3MRENCLAVE,
|
||||||
root: directoryV3Root,
|
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
version,
|
version,
|
||||||
|
|
||||||
|
@ -2510,7 +2511,7 @@ export function initialize({
|
||||||
async function getGroupCredentials({
|
async function getGroupCredentials({
|
||||||
startDayInMs,
|
startDayInMs,
|
||||||
endDayInMs,
|
endDayInMs,
|
||||||
}: GetGroupCredentialsOptionsType): Promise<Array<GroupCredentialType>> {
|
}: GetGroupCredentialsOptionsType): Promise<GetGroupCredentialsResultType> {
|
||||||
const startDayInSeconds = startDayInMs / durations.SECOND;
|
const startDayInSeconds = startDayInMs / durations.SECOND;
|
||||||
const endDayInSeconds = endDayInMs / durations.SECOND;
|
const endDayInSeconds = endDayInMs / durations.SECOND;
|
||||||
const response = (await _ajax({
|
const response = (await _ajax({
|
||||||
|
@ -2522,7 +2523,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
})) as CredentialResponseType;
|
})) as CredentialResponseType;
|
||||||
|
|
||||||
return response.credentials;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getGroupExternalCredential(
|
async function getGroupExternalCredential(
|
||||||
|
|
|
@ -10,20 +10,16 @@ import { CDSSocketManagerBase } from './CDSSocketManagerBase';
|
||||||
|
|
||||||
export type CDSIOptionsType = Readonly<{
|
export type CDSIOptionsType = Readonly<{
|
||||||
mrenclave: string;
|
mrenclave: string;
|
||||||
root: string;
|
|
||||||
}> &
|
}> &
|
||||||
CDSSocketManagerBaseOptionsType;
|
CDSSocketManagerBaseOptionsType;
|
||||||
|
|
||||||
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
||||||
private readonly mrenclave: Buffer;
|
private readonly mrenclave: Buffer;
|
||||||
|
|
||||||
private readonly trustedCaCert: Buffer;
|
|
||||||
|
|
||||||
constructor(options: CDSIOptionsType) {
|
constructor(options: CDSIOptionsType) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
|
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
|
||||||
this.trustedCaCert = Buffer.from(options.root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getSocketUrl(): string {
|
protected override getSocketUrl(): string {
|
||||||
|
@ -37,7 +33,6 @@ export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
socket,
|
socket,
|
||||||
mrenclave: this.mrenclave,
|
mrenclave: this.mrenclave,
|
||||||
trustedCaCert: this.trustedCaCert,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,12 @@
|
||||||
import { Cds2Client } from '@signalapp/libsignal-client';
|
import { Cds2Client } from '@signalapp/libsignal-client';
|
||||||
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { DAY } from '../../util/durations';
|
|
||||||
import { SignalService as Proto } from '../../protobuf';
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
import { CDSSocketBase, CDSSocketState } from './CDSSocketBase';
|
import { CDSSocketBase, CDSSocketState } from './CDSSocketBase';
|
||||||
import type { CDSSocketBaseOptionsType } from './CDSSocketBase';
|
import type { CDSSocketBaseOptionsType } from './CDSSocketBase';
|
||||||
|
|
||||||
export type CDSISocketOptionsType = Readonly<{
|
export type CDSISocketOptionsType = Readonly<{
|
||||||
mrenclave: Buffer;
|
mrenclave: Buffer;
|
||||||
trustedCaCert: Buffer;
|
|
||||||
}> &
|
}> &
|
||||||
CDSSocketBaseOptionsType;
|
CDSSocketBaseOptionsType;
|
||||||
|
|
||||||
|
@ -30,15 +28,14 @@ export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
|
||||||
await this.socketIterator.next();
|
await this.socketIterator.next();
|
||||||
strictAssert(!done, 'CDSI socket closed before handshake');
|
strictAssert(!done, 'CDSI socket closed before handshake');
|
||||||
|
|
||||||
const earliestValidTimestamp = new Date(Date.now() - DAY);
|
const earliestValidTimestamp = new Date();
|
||||||
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
this.privCdsClient === undefined,
|
this.privCdsClient === undefined,
|
||||||
'CDSI handshake called twice'
|
'CDSI handshake called twice'
|
||||||
);
|
);
|
||||||
this.privCdsClient = Cds2Client.new_NOT_FOR_PRODUCTION(
|
this.privCdsClient = Cds2Client.new(
|
||||||
this.options.mrenclave,
|
this.options.mrenclave,
|
||||||
this.options.trustedCaCert,
|
|
||||||
attestationMessage,
|
attestationMessage,
|
||||||
earliestValidTimestamp
|
earliestValidTimestamp
|
||||||
);
|
);
|
||||||
|
|
|
@ -357,20 +357,6 @@ export class KeysEvent extends ConfirmableEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PNIIdentityEventData = Readonly<{
|
|
||||||
publicKey: Uint8Array;
|
|
||||||
privateKey: Uint8Array;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export class PNIIdentityEvent extends ConfirmableEvent {
|
|
||||||
constructor(
|
|
||||||
public readonly data: PNIIdentityEventData,
|
|
||||||
confirm: ConfirmCallback
|
|
||||||
) {
|
|
||||||
super('pniIdentity', confirm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StickerPackEventData = Readonly<{
|
export type StickerPackEventData = Readonly<{
|
||||||
id?: string;
|
id?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|
|
@ -36,7 +36,6 @@ const directoryV3ConfigSchema = z.object({
|
||||||
directoryVersion: z.literal(3),
|
directoryVersion: z.literal(3),
|
||||||
directoryV3Url: configRequiredStringSchema,
|
directoryV3Url: configRequiredStringSchema,
|
||||||
directoryV3MRENCLAVE: configRequiredStringSchema,
|
directoryV3MRENCLAVE: configRequiredStringSchema,
|
||||||
directoryV3Root: configRequiredStringSchema,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const directoryConfigSchema = z
|
export const directoryConfigSchema = z
|
||||||
|
@ -50,7 +49,6 @@ export const directoryConfigSchema = z
|
||||||
directoryV2Url: configOptionalUnknownSchema,
|
directoryV2Url: configOptionalUnknownSchema,
|
||||||
directoryV3Url: configOptionalUnknownSchema,
|
directoryV3Url: configOptionalUnknownSchema,
|
||||||
directoryV3MRENCLAVE: configOptionalUnknownSchema,
|
directoryV3MRENCLAVE: configOptionalUnknownSchema,
|
||||||
directoryV3Root: configOptionalUnknownSchema,
|
|
||||||
})
|
})
|
||||||
.and(
|
.and(
|
||||||
directoryV1ConfigSchema
|
directoryV1ConfigSchema
|
||||||
|
|
|
@ -35,8 +35,8 @@ export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
|
||||||
export type IdentityKeyMap = Record<
|
export type IdentityKeyMap = Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
privKey: string;
|
privKey: Uint8Array;
|
||||||
pubKey: string;
|
pubKey: Uint8Array;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|
|
@ -61,3 +61,7 @@ type InternalAssertProps<
|
||||||
export type AssertProps<Result, Value> = InternalAssertProps<Result, Value>;
|
export type AssertProps<Result, Value> = InternalAssertProps<Result, Value>;
|
||||||
|
|
||||||
export type UnwrapPromise<Value> = Value extends Promise<infer T> ? T : Value;
|
export type UnwrapPromise<Value> = Value extends Promise<infer T> ? T : Value;
|
||||||
|
|
||||||
|
export type BytesToStrings<Value> = Value extends Uint8Array
|
||||||
|
? string
|
||||||
|
: { [Key in keyof Value]: BytesToStrings<Value[Key]> };
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { cloneDeep, get, set } from 'lodash';
|
||||||
|
|
||||||
|
export type ObjectMappingSpecType =
|
||||||
|
| string
|
||||||
|
| ReadonlyArray<ObjectMappingSpecType>
|
||||||
|
| Readonly<{
|
||||||
|
key: string;
|
||||||
|
valueSpec: ObjectMappingSpecType;
|
||||||
|
}>
|
||||||
|
| Readonly<{
|
||||||
|
isMap: true;
|
||||||
|
valueSpec: ObjectMappingSpecType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function mapObjectWithSpec<Input, Output>(
|
||||||
|
spec: ObjectMappingSpecType,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
data: any,
|
||||||
|
map: (value: Input) => Output,
|
||||||
|
target = cloneDeep(data)
|
||||||
|
): any {
|
||||||
|
if (!data) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof spec === 'string') {
|
||||||
|
const value = get(data, spec);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
set(target, spec, map(value));
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('isMap' in spec) {
|
||||||
|
for (const key of Object.keys(data)) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
target[key] = mapObjectWithSpec(
|
||||||
|
spec.valueSpec,
|
||||||
|
data[key],
|
||||||
|
map,
|
||||||
|
target[key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('key' in spec) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
target[spec.key] = mapObjectWithSpec(
|
||||||
|
spec.valueSpec,
|
||||||
|
data[spec.key],
|
||||||
|
map,
|
||||||
|
target[spec.key]
|
||||||
|
);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of spec) {
|
||||||
|
mapObjectWithSpec(key, data, map, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ export async function updateOurUsernameAndPni(): Promise<void> {
|
||||||
);
|
);
|
||||||
|
|
||||||
const me = window.ConversationController.getOurConversationOrThrow();
|
const me = window.ConversationController.getOurConversationOrThrow();
|
||||||
const { username, pni } = await server.whoami();
|
const { username } = await server.whoami();
|
||||||
|
|
||||||
me.set({ username: dropNull(username) });
|
me.set({ username: dropNull(username) });
|
||||||
window.Signal.Data.updateConversation(me.attributes);
|
window.Signal.Data.updateConversation(me.attributes);
|
||||||
|
@ -23,6 +23,4 @@ export async function updateOurUsernameAndPni(): Promise<void> {
|
||||||
manager,
|
manager,
|
||||||
'updateOurUsernameAndPni: AccountManager not available'
|
'updateOurUsernameAndPni: AccountManager not available'
|
||||||
);
|
);
|
||||||
|
|
||||||
await manager.setPni(pni);
|
|
||||||
}
|
}
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -1745,20 +1745,20 @@
|
||||||
"@react-spring/shared" "~9.4.5"
|
"@react-spring/shared" "~9.4.5"
|
||||||
"@react-spring/types" "~9.4.5"
|
"@react-spring/types" "~9.4.5"
|
||||||
|
|
||||||
"@signalapp/libsignal-client@0.18.1", "@signalapp/libsignal-client@^0.18.1":
|
"@signalapp/libsignal-client@0.19.1", "@signalapp/libsignal-client@^0.19.1":
|
||||||
version "0.18.1"
|
version "0.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.18.1.tgz#6b499cdcc952f1981c6367f68484cf3275be3b31"
|
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.19.1.tgz#ccc12f0f034fe522940ba176a4518b4a05162b6d"
|
||||||
integrity sha512-43NcTYpahImlWHBDaNFmn7QaeXZHkFkTtb4m+ZWgzU0mkS1M8V+orGen2XuDvNiu+9HQmW4Lg7FV1deXhWtIRA==
|
integrity sha512-x6qMjLxoq39oXnoUI8vA1Pd+fitEuYdA828LwBZIY0gxdBVv4D2DNB2kmyiGH2KtqHucnsRSz216gBOWbI2Q/g==
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build "^4.2.3"
|
node-gyp-build "^4.2.3"
|
||||||
uuid "^8.3.0"
|
uuid "^8.3.0"
|
||||||
|
|
||||||
"@signalapp/mock-server@2.1.0":
|
"@signalapp/mock-server@2.3.0":
|
||||||
version "2.1.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-2.1.0.tgz#25e42aad9ec2bc76c92173e7894f1aec4c2bb719"
|
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-2.3.0.tgz#96e75fbc8d8b5f62a6deec89f9fc1bb1a4210c89"
|
||||||
integrity sha512-AoeCRw8hOv4F+YQ6um/ZZiskaS1SsAXoQPgSMK69/xfDcPURJnVU6KB5Fy3chU2ZF0SZyWzS8vF3QguFKsIFWA==
|
integrity sha512-BNvT9/FbEBOKBd+2T8ImqOfKygCDLHl8bzQImRDrh3Umy7fmqYhTiuZb7WslCojEpjwH6fvZ6KfXkZDzkahqjg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@signalapp/libsignal-client" "^0.18.1"
|
"@signalapp/libsignal-client" "^0.19.1"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
long "^4.0.0"
|
long "^4.0.0"
|
||||||
micro "^9.3.4"
|
micro "^9.3.4"
|
||||||
|
|
Loading…
Reference in New Issue