Include and process destinationPniIdentityKey
This commit is contained in:
parent
711e321d16
commit
e031d136a1
11 changed files with 238 additions and 214 deletions
|
@ -209,7 +209,7 @@
|
|||
"@formatjs/intl": "2.6.7",
|
||||
"@indutny/rezip-electron": "1.3.1",
|
||||
"@mixer/parallel-prettier": "2.0.3",
|
||||
"@signalapp/mock-server": "6.0.0",
|
||||
"@signalapp/mock-server": "6.1.0",
|
||||
"@storybook/addon-a11y": "7.4.5",
|
||||
"@storybook/addon-actions": "7.4.5",
|
||||
"@storybook/addon-controls": "7.4.5",
|
||||
|
|
|
@ -450,9 +450,11 @@ message Verified {
|
|||
message SyncMessage {
|
||||
message Sent {
|
||||
message UnidentifiedDeliveryStatus {
|
||||
optional string destination = 1;
|
||||
optional string destination = 1;
|
||||
optional string destinationServiceId = 3;
|
||||
optional bool unidentified = 2;
|
||||
optional bool unidentified = 2;
|
||||
reserved /* destinationPni */ 4;
|
||||
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
|
||||
}
|
||||
|
||||
message StoryMessageRecipient {
|
||||
|
|
|
@ -136,6 +136,11 @@ export type SessionTransactionOptions = Readonly<{
|
|||
zone?: Zone;
|
||||
}>;
|
||||
|
||||
export type SaveIdentityOptions = Readonly<{
|
||||
zone?: Zone;
|
||||
noOverwrite?: boolean;
|
||||
}>;
|
||||
|
||||
export type VerifyAlternateIdentityOptionsType = Readonly<{
|
||||
aci: AciString;
|
||||
pni: PniString;
|
||||
|
@ -2049,7 +2054,7 @@ export class SignalProtocolStore extends EventEmitter {
|
|||
encodedAddress: Address,
|
||||
publicKey: Uint8Array,
|
||||
nonblockingApproval = false,
|
||||
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
|
||||
{ zone = GLOBAL_ZONE, noOverwrite = false }: SaveIdentityOptions = {}
|
||||
): Promise<boolean> {
|
||||
if (!this.identityKeys) {
|
||||
throw new Error('saveIdentity: this.identityKeys not yet cached!');
|
||||
|
@ -2100,6 +2105,10 @@ export class SignalProtocolStore extends EventEmitter {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (noOverwrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const identityKeyChanged = !constantTimeEqual(
|
||||
identityRecord.publicKey,
|
||||
publicKey
|
||||
|
|
|
@ -5,6 +5,7 @@ import { isNumber, throttle, groupBy } from 'lodash';
|
|||
import { render } from 'react-dom';
|
||||
import { batch as batchDispatch } from 'react-redux';
|
||||
import PQueue from 'p-queue';
|
||||
import pMap from 'p-map';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import * as Registration from './util/registration';
|
||||
|
@ -52,6 +53,7 @@ import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
|
|||
import * as KeyboardLayout from './services/keyboardLayout';
|
||||
import * as StorageService from './services/storage';
|
||||
import { usernameIntegrity } from './services/usernameIntegrity';
|
||||
import { updateIdentityKey } from './services/profiles';
|
||||
import { RoutineProfileRefresher } from './routineProfileRefresh';
|
||||
import { isOlderThan } from './util/timestamp';
|
||||
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
|
||||
|
@ -2536,46 +2538,74 @@ export async function startApp(): Promise<void> {
|
|||
return confirm();
|
||||
}
|
||||
|
||||
function createSentMessage(
|
||||
async function createSentMessage(
|
||||
data: SentEventData,
|
||||
descriptor: MessageDescriptor
|
||||
) {
|
||||
const now = Date.now();
|
||||
const timestamp = data.timestamp || now;
|
||||
const logId = `createSentMessage(${timestamp})`;
|
||||
|
||||
const ourId = window.ConversationController.getOurConversationIdOrThrow();
|
||||
|
||||
const { unidentifiedStatus = [] } = data;
|
||||
|
||||
const sendStateByConversationId: SendStateByConversationId =
|
||||
unidentifiedStatus.reduce(
|
||||
(
|
||||
result: SendStateByConversationId,
|
||||
{ destinationServiceId, destination, isAllowedToReplyToStory }
|
||||
) => {
|
||||
const conversation = window.ConversationController.get(
|
||||
destinationServiceId || destination
|
||||
);
|
||||
if (!conversation || conversation.id === ourId) {
|
||||
return result;
|
||||
}
|
||||
const sendStateByConversationId: SendStateByConversationId = {
|
||||
[ourId]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
...result,
|
||||
[conversation.id]: {
|
||||
isAllowedToReplyToStory,
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
};
|
||||
},
|
||||
{
|
||||
[ourId]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: timestamp,
|
||||
},
|
||||
}
|
||||
for (const {
|
||||
destinationServiceId,
|
||||
destination,
|
||||
isAllowedToReplyToStory,
|
||||
} of unidentifiedStatus) {
|
||||
const conversation = window.ConversationController.get(
|
||||
destinationServiceId || destination
|
||||
);
|
||||
if (!conversation || conversation.id === ourId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sendStateByConversationId[conversation.id] = {
|
||||
isAllowedToReplyToStory,
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
await pMap(
|
||||
unidentifiedStatus,
|
||||
async ({ destinationServiceId, destinationPniIdentityKey }) => {
|
||||
if (!Bytes.isNotEmpty(destinationPniIdentityKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPniString(destinationServiceId)) {
|
||||
log.warn(
|
||||
`${logId}: received an destinationPniIdentityKey for ` +
|
||||
`an invalid PNI: ${destinationServiceId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = await updateIdentityKey(
|
||||
destinationPniIdentityKey,
|
||||
destinationServiceId,
|
||||
{
|
||||
noOverwrite: true,
|
||||
}
|
||||
);
|
||||
if (changed) {
|
||||
log.info(
|
||||
`${logId}: Updated identity key for ${destinationServiceId}`
|
||||
);
|
||||
}
|
||||
},
|
||||
{ concurrency: 10 }
|
||||
);
|
||||
|
||||
let unidentifiedDeliveries: Array<string> = [];
|
||||
if (unidentifiedStatus.length) {
|
||||
|
@ -2720,7 +2750,7 @@ export async function startApp(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
const message = createSentMessage(data, messageDescriptor);
|
||||
const message = await createSentMessage(data, messageDescriptor);
|
||||
|
||||
if (data.message.reaction) {
|
||||
strictAssert(
|
||||
|
|
|
@ -2027,9 +2027,12 @@ export class ConversationModel extends window.Backbone
|
|||
incrementSentMessageCount({ dry = false }: { dry?: boolean } = {}):
|
||||
| Partial<ConversationAttributesType>
|
||||
| undefined {
|
||||
const needsTitleTransition =
|
||||
hasNumberTitle(this.attributes) || hasUsernameTitle(this.attributes);
|
||||
const update = {
|
||||
messageCount: (this.get('messageCount') || 0) + 1,
|
||||
sentMessageCount: (this.get('sentMessageCount') || 0) + 1,
|
||||
...(needsTitleTransition ? { needsTitleTransition: true } : {}),
|
||||
};
|
||||
|
||||
if (dry) {
|
||||
|
@ -3719,13 +3722,10 @@ export class ConversationModel extends window.Backbone
|
|||
};
|
||||
|
||||
const isEditMessage = Boolean(message.get('editHistory'));
|
||||
const needsTitleTransition =
|
||||
hasNumberTitle(this.attributes) || hasUsernameTitle(this.attributes);
|
||||
|
||||
this.set({
|
||||
...draftProperties,
|
||||
...(enabledProfileSharing ? { profileSharing: true } : {}),
|
||||
...(needsTitleTransition ? { needsTitleTransition: true } : {}),
|
||||
...(dontAddMessage
|
||||
? {}
|
||||
: this.incrementSentMessageCount({ dry: true })),
|
||||
|
|
|
@ -354,7 +354,8 @@ async function doGetProfile(c: ConversationModel): Promise<void> {
|
|||
}
|
||||
|
||||
if (profile.identityKey) {
|
||||
await updateIdentityKey(profile.identityKey, serviceId);
|
||||
const identityKeyBytes = Bytes.fromBase64(profile.identityKey);
|
||||
await updateIdentityKey(identityKeyBytes, serviceId);
|
||||
}
|
||||
|
||||
// Update accessKey to prevent race conditions. Since we run asynchronous
|
||||
|
@ -596,19 +597,24 @@ async function doGetProfile(c: ConversationModel): Promise<void> {
|
|||
window.Signal.Data.updateConversation(c.attributes);
|
||||
}
|
||||
|
||||
export type UpdateIdentityKeyOptionsType = Readonly<{
|
||||
noOverwrite?: boolean;
|
||||
}>;
|
||||
|
||||
export async function updateIdentityKey(
|
||||
identityKey: string,
|
||||
serviceId: ServiceIdString
|
||||
): Promise<void> {
|
||||
if (!identityKey) {
|
||||
return;
|
||||
identityKey: Uint8Array,
|
||||
serviceId: ServiceIdString,
|
||||
{ noOverwrite = false }: UpdateIdentityKeyOptionsType = {}
|
||||
): Promise<boolean> {
|
||||
if (!Bytes.isNotEmpty(identityKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const identityKeyBytes = Bytes.fromBase64(identityKey);
|
||||
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
||||
new Address(serviceId, 1),
|
||||
identityKeyBytes,
|
||||
false
|
||||
identityKey,
|
||||
false,
|
||||
{ noOverwrite }
|
||||
);
|
||||
if (changed) {
|
||||
log.info(`updateIdentityKey(${serviceId}): changed`);
|
||||
|
@ -619,4 +625,6 @@ export async function updateIdentityKey(
|
|||
new QualifiedAddress(ourAci, new Address(serviceId, 1))
|
||||
);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import createDebug from 'debug';
|
||||
import Long from 'long';
|
||||
|
||||
import type { App } from '../playwright';
|
||||
import * as durations from '../../util/durations';
|
||||
import { Bootstrap } from '../bootstrap';
|
||||
|
||||
export const debug = createDebug('mock:test:sendSync');
|
||||
|
||||
describe('sendSync', function (this: Mocha.Suite) {
|
||||
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 (this: Mocha.Context) {
|
||||
if (!bootstrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
await bootstrap.maybeSaveLogs(this.currentTest, app);
|
||||
await app.close();
|
||||
await bootstrap.teardown();
|
||||
});
|
||||
|
||||
it('creates conversation for sendSync to PNI', async () => {
|
||||
const { desktop, phone, server } = bootstrap;
|
||||
|
||||
debug('Creating stranger');
|
||||
const STRANGER_NAME = 'Mysterious Stranger';
|
||||
const stranger = await server.createPrimaryDevice({
|
||||
profileName: STRANGER_NAME,
|
||||
});
|
||||
|
||||
const timestamp = Date.now();
|
||||
const messageText = 'hey there, just reaching out';
|
||||
const destinationServiceId = stranger.device.pni;
|
||||
const destination = stranger.device.number;
|
||||
const originalDataMessage = {
|
||||
body: messageText,
|
||||
timestamp: Long.fromNumber(timestamp),
|
||||
};
|
||||
const content = {
|
||||
syncMessage: {
|
||||
sent: {
|
||||
destinationServiceId,
|
||||
destination,
|
||||
timestamp: Long.fromNumber(timestamp),
|
||||
message: originalDataMessage,
|
||||
},
|
||||
},
|
||||
};
|
||||
const sendOptions = {
|
||||
timestamp,
|
||||
};
|
||||
await phone.sendRaw(desktop, content, sendOptions);
|
||||
|
||||
const page = await app.getWindow();
|
||||
const leftPane = page.locator('#LeftPane');
|
||||
|
||||
debug('checking left pane for conversation');
|
||||
const strangerName = await leftPane
|
||||
.locator(
|
||||
'.module-conversation-list__item--contact-or-conversation .module-contact-name'
|
||||
)
|
||||
.first()
|
||||
.innerText();
|
||||
|
||||
assert.equal(
|
||||
strangerName.slice(-4),
|
||||
destination?.slice(-4),
|
||||
'no profile, just phone number'
|
||||
);
|
||||
|
||||
debug('opening conversation');
|
||||
await leftPane
|
||||
.locator('.module-conversation-list__item--contact-or-conversation')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
debug('checking for latest message');
|
||||
await page.locator(`.module-message__text >> "${messageText}"`).waitFor();
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import Long from 'long';
|
||||
import { Pni } from '@signalapp/libsignal-client';
|
||||
import {
|
||||
ServiceIdKind,
|
||||
|
@ -9,7 +10,6 @@ import {
|
|||
ReceiptType,
|
||||
StorageState,
|
||||
} from '@signalapp/mock-server';
|
||||
import type { PrimaryDevice } from '@signalapp/mock-server';
|
||||
import createDebug from 'debug';
|
||||
|
||||
import * as durations from '../../util/durations';
|
||||
|
@ -33,17 +33,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
|
||||
let bootstrap: Bootstrap;
|
||||
let app: App;
|
||||
let pniContact: PrimaryDevice;
|
||||
|
||||
beforeEach(async () => {
|
||||
bootstrap = new Bootstrap();
|
||||
bootstrap = new Bootstrap({ contactCount: 0 });
|
||||
await bootstrap.init();
|
||||
|
||||
const { server, phone } = bootstrap;
|
||||
|
||||
pniContact = await server.createPrimaryDevice({
|
||||
profileName: 'ACI Contact',
|
||||
});
|
||||
const { phone } = bootstrap;
|
||||
|
||||
let state = StorageState.getEmpty();
|
||||
|
||||
|
@ -52,21 +47,6 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
e164: phone.device.number,
|
||||
});
|
||||
|
||||
state = state.addContact(
|
||||
pniContact,
|
||||
{
|
||||
whitelisted: true,
|
||||
serviceE164: pniContact.device.number,
|
||||
identityKey: pniContact.getPublicKey(ServiceIdKind.PNI).serialize(),
|
||||
givenName: undefined,
|
||||
familyName: undefined,
|
||||
},
|
||||
ServiceIdKind.PNI
|
||||
);
|
||||
|
||||
// Just to make PNI Contact visible in the left pane
|
||||
state = state.pin(pniContact, ServiceIdKind.PNI);
|
||||
|
||||
// Add my story
|
||||
state = state.addRecord({
|
||||
type: IdentifierType.STORY_DISTRIBUTION_LIST,
|
||||
|
@ -140,14 +120,14 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
assert.isTrue(isValid, `Invalid pni signature from ${source}`);
|
||||
};
|
||||
|
||||
debug('sending a message to our PNI');
|
||||
debug('Send a message to our PNI');
|
||||
await stranger.sendText(desktop, 'A message to PNI', {
|
||||
serviceIdKind: ServiceIdKind.PNI,
|
||||
withProfileKey: true,
|
||||
timestamp: bootstrap.getTimestamp(),
|
||||
});
|
||||
|
||||
debug('opening conversation with the stranger');
|
||||
debug('Open conversation with the stranger');
|
||||
await leftPane
|
||||
.locator(`[data-testid="${stranger.toContact().aci}"]`)
|
||||
.click();
|
||||
|
@ -157,7 +137,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
.locator('.module-message-request-actions button >> "Accept"')
|
||||
.click();
|
||||
|
||||
debug('Waiting for a pniSignatureMessage');
|
||||
debug('Wait for a pniSignatureMessage');
|
||||
{
|
||||
const { source, content } = await stranger.waitForMessage();
|
||||
|
||||
|
@ -171,7 +151,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
await compositionInput.type('first');
|
||||
await compositionInput.press('Enter');
|
||||
}
|
||||
debug('Waiting for the first message with pni signature');
|
||||
debug('Wait for the first message with pni signature');
|
||||
{
|
||||
const { source, content, body, dataMessage } =
|
||||
await stranger.waitForMessage();
|
||||
|
@ -185,7 +165,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
checkPniSignature(content.pniSignatureMessage, 'first message');
|
||||
|
||||
const receiptTimestamp = bootstrap.getTimestamp();
|
||||
debug('Sending unencrypted receipt', receiptTimestamp);
|
||||
debug('Send unencrypted receipt', receiptTimestamp);
|
||||
|
||||
await stranger.sendUnencryptedReceipt(desktop, {
|
||||
messageTimestamp: dataMessage.timestamp?.toNumber() ?? 0,
|
||||
|
@ -199,7 +179,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
await compositionInput.type('second');
|
||||
await compositionInput.press('Enter');
|
||||
}
|
||||
debug('Waiting for the second message with pni signature');
|
||||
debug('Wait for the second message with pni signature');
|
||||
{
|
||||
const { source, content, body, dataMessage } =
|
||||
await stranger.waitForMessage();
|
||||
|
@ -213,7 +193,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
checkPniSignature(content.pniSignatureMessage, 'second message');
|
||||
|
||||
const receiptTimestamp = bootstrap.getTimestamp();
|
||||
debug('Sending encrypted receipt', receiptTimestamp);
|
||||
debug('Send encrypted receipt', receiptTimestamp);
|
||||
|
||||
await stranger.sendReceipt(desktop, {
|
||||
type: ReceiptType.Delivery,
|
||||
|
@ -233,7 +213,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
await compositionInput.type('third');
|
||||
await compositionInput.press('Enter');
|
||||
}
|
||||
debug('Waiting for the third message without pni signature');
|
||||
debug('Wait for the third message without pni signature');
|
||||
{
|
||||
const { source, content, body } = await stranger.waitForMessage();
|
||||
|
||||
|
@ -262,55 +242,126 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
});
|
||||
|
||||
it('should be received by Desktop and trigger contact merge', async () => {
|
||||
const { desktop, phone } = bootstrap;
|
||||
const { desktop, phone, server } = bootstrap;
|
||||
|
||||
const window = await app.getWindow();
|
||||
|
||||
const leftPane = window.locator('#LeftPane');
|
||||
|
||||
debug('opening conversation with the pni contact');
|
||||
await leftPane
|
||||
.locator('.module-conversation-list__item--contact-or-conversation')
|
||||
.first()
|
||||
.click();
|
||||
debug('Capture storage service state before messaging');
|
||||
let state = await phone.expectStorageState('state before messaging');
|
||||
|
||||
debug('Enter a PNI message text');
|
||||
debug('Create stranger');
|
||||
const STRANGER_NAME = 'Mysterious Stranger';
|
||||
const stranger = await server.createPrimaryDevice({
|
||||
profileName: STRANGER_NAME,
|
||||
});
|
||||
|
||||
debug('Send a PNI sync message');
|
||||
const timestamp = bootstrap.getTimestamp();
|
||||
const destinationServiceId = stranger.device.pni;
|
||||
const destination = stranger.device.number;
|
||||
const destinationPniIdentityKey = await stranger.device.getIdentityKey(
|
||||
ServiceIdKind.PNI
|
||||
);
|
||||
const originalDataMessage = {
|
||||
body: 'Hello PNI',
|
||||
timestamp: Long.fromNumber(timestamp),
|
||||
};
|
||||
const content = {
|
||||
syncMessage: {
|
||||
sent: {
|
||||
destinationServiceId,
|
||||
destination,
|
||||
timestamp: Long.fromNumber(timestamp),
|
||||
message: originalDataMessage,
|
||||
unidentifiedStatus: [
|
||||
{
|
||||
destinationServiceId,
|
||||
destination,
|
||||
destinationPniIdentityKey: destinationPniIdentityKey.serialize(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const sendOptions = {
|
||||
timestamp,
|
||||
};
|
||||
await phone.sendRaw(desktop, content, sendOptions);
|
||||
|
||||
debug('Wait for updated storage service state with PNI contact');
|
||||
{
|
||||
const compositionInput = await app.waitForEnabledComposer();
|
||||
const newState = await phone.waitForStorageState({
|
||||
after: state,
|
||||
});
|
||||
|
||||
await compositionInput.type('Hello PNI');
|
||||
await compositionInput.press('Enter');
|
||||
const aciRecord = newState.getContact(stranger, ServiceIdKind.ACI);
|
||||
assert.isUndefined(aciRecord, 'ACI contact must not be created');
|
||||
|
||||
const pniRecord = newState.getContact(stranger, ServiceIdKind.PNI);
|
||||
assert.deepEqual(
|
||||
pniRecord?.identityKey,
|
||||
destinationPniIdentityKey.serialize(),
|
||||
'PNI contact must have correct identity key'
|
||||
);
|
||||
|
||||
state = newState;
|
||||
}
|
||||
|
||||
debug('Waiting for a PNI message');
|
||||
{
|
||||
const { source, body, serviceIdKind } = await pniContact.waitForMessage();
|
||||
debug('Open conversation with the pni contact');
|
||||
const contactElem = leftPane.locator(
|
||||
`[data-testid="${stranger.device.pni}"]`
|
||||
);
|
||||
await contactElem.click();
|
||||
|
||||
assert.strictEqual(source, desktop, 'PNI message has valid source');
|
||||
assert.strictEqual(body, 'Hello PNI', 'PNI message has valid body');
|
||||
assert.strictEqual(
|
||||
serviceIdKind,
|
||||
ServiceIdKind.PNI,
|
||||
'PNI message has valid destination'
|
||||
debug('Verify that left pane shows phone number');
|
||||
{
|
||||
const strangerName = await contactElem
|
||||
.locator('.module-contact-name')
|
||||
.first()
|
||||
.innerText();
|
||||
assert.equal(
|
||||
strangerName.slice(-4),
|
||||
destination?.slice(-4),
|
||||
'no profile, just phone number'
|
||||
);
|
||||
}
|
||||
|
||||
debug('Capture storage service state before merging');
|
||||
const state = await phone.expectStorageState('state before merge');
|
||||
debug('Verify that we are in MR state');
|
||||
const conversationStack = window.locator('.Inbox__conversation-stack');
|
||||
await conversationStack
|
||||
.locator('.module-message-request-actions button >> "Continue"')
|
||||
.waitFor();
|
||||
|
||||
debug('Enter a draft text without hitting enter');
|
||||
debug('Clear message request state on phone');
|
||||
{
|
||||
const compositionInput = await app.waitForEnabledComposer();
|
||||
const newState = state.updateContact(
|
||||
stranger,
|
||||
{
|
||||
whitelisted: true,
|
||||
},
|
||||
ServiceIdKind.PNI
|
||||
);
|
||||
|
||||
await compositionInput.type('Draft text');
|
||||
await phone.setStorageState(newState, state);
|
||||
await phone.sendFetchStorage({
|
||||
timestamp: bootstrap.getTimestamp(),
|
||||
});
|
||||
state = newState;
|
||||
}
|
||||
|
||||
debug('Wait for MR state to disappear');
|
||||
await conversationStack
|
||||
.locator('.module-message-request-actions button >> "Continue"')
|
||||
.waitFor({ state: 'hidden' });
|
||||
|
||||
debug('Send back the response with profile key and pni signature');
|
||||
|
||||
const ourKey = await desktop.popSingleUseKey();
|
||||
await pniContact.addSingleUseKey(desktop, ourKey);
|
||||
await stranger.addSingleUseKey(desktop, ourKey);
|
||||
|
||||
await pniContact.sendText(desktop, 'Hello Desktop!', {
|
||||
await stranger.sendText(desktop, 'Hello Desktop!', {
|
||||
timestamp: bootstrap.getTimestamp(),
|
||||
withPniSignature: true,
|
||||
withProfileKey: true,
|
||||
|
@ -318,7 +369,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
|
||||
debug('Wait for merge to happen');
|
||||
await leftPane
|
||||
.locator(`[data-testid="${pniContact.toContact().aci}"]`)
|
||||
.locator(`[data-testid="${stranger.toContact().aci}"]`)
|
||||
.waitFor();
|
||||
|
||||
{
|
||||
|
@ -330,9 +381,9 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
await compositionInput.press('Enter');
|
||||
}
|
||||
|
||||
debug('Waiting for a ACI message');
|
||||
debug('Wait for a ACI message');
|
||||
{
|
||||
const { source, body, serviceIdKind } = await pniContact.waitForMessage();
|
||||
const { source, body, serviceIdKind } = await stranger.waitForMessage();
|
||||
|
||||
assert.strictEqual(source, desktop, 'ACI message has valid source');
|
||||
assert.strictEqual(body, 'Hello ACI', 'ACI message has valid body');
|
||||
|
@ -350,8 +401,8 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
after: state,
|
||||
});
|
||||
|
||||
const pniRecord = newState.getContact(pniContact, ServiceIdKind.PNI);
|
||||
const aciRecord = newState.getContact(pniContact, ServiceIdKind.ACI);
|
||||
const pniRecord = newState.getContact(stranger, ServiceIdKind.PNI);
|
||||
const aciRecord = newState.getContact(stranger, ServiceIdKind.ACI);
|
||||
assert.strictEqual(
|
||||
aciRecord,
|
||||
pniRecord,
|
||||
|
@ -359,12 +410,12 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) {
|
|||
);
|
||||
assert(aciRecord, 'ACI Contact must be in storage service');
|
||||
|
||||
assert.strictEqual(aciRecord?.aci, pniContact.device.aci);
|
||||
assert.strictEqual(aciRecord?.aci, stranger.device.aci);
|
||||
assert.strictEqual(
|
||||
aciRecord?.pni &&
|
||||
isUntaggedPniString(aciRecord?.pni) &&
|
||||
toTaggedPni(aciRecord?.pni),
|
||||
pniContact.device.pni
|
||||
stranger.device.pni
|
||||
);
|
||||
assert.strictEqual(aciRecord?.pniSignatureVerified, true);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { z } from 'zod';
|
||||
import Long from 'long';
|
||||
import PQueue from 'p-queue';
|
||||
import pMap from 'p-map';
|
||||
import type { PlaintextContent } from '@signalapp/libsignal-client';
|
||||
import {
|
||||
Pni,
|
||||
|
@ -26,7 +27,11 @@ import type {
|
|||
UploadedAttachmentType,
|
||||
} from '../types/Attachment';
|
||||
import type { AciString, ServiceIdString } from '../types/ServiceId';
|
||||
import { ServiceIdKind, serviceIdSchema } from '../types/ServiceId';
|
||||
import {
|
||||
ServiceIdKind,
|
||||
serviceIdSchema,
|
||||
isPniString,
|
||||
} from '../types/ServiceId';
|
||||
import type {
|
||||
ChallengeType,
|
||||
GetGroupLogOptionsType,
|
||||
|
@ -63,7 +68,7 @@ import type {
|
|||
LinkPreviewImage,
|
||||
LinkPreviewMetadata,
|
||||
} from '../linkPreviews/linkPreviewFetch';
|
||||
import { concat, isEmpty, map } from '../util/iterables';
|
||||
import { concat, isEmpty } from '../util/iterables';
|
||||
import type { SendTypesType } from '../util/handleMessageSend';
|
||||
import { shouldSaveProto, sendTypesEnum } from '../util/handleMessageSend';
|
||||
import type { DurationInSeconds } from '../util/durations';
|
||||
|
@ -1267,8 +1272,9 @@ export default class MessageSender {
|
|||
// Though this field has 'unidentified' in the name, it should have entries for each
|
||||
// number we sent to.
|
||||
if (!isEmpty(conversationIdsSentTo)) {
|
||||
sentMessage.unidentifiedStatus = [
|
||||
...map(conversationIdsSentTo, conversationId => {
|
||||
sentMessage.unidentifiedStatus = await pMap(
|
||||
conversationIdsSentTo,
|
||||
async conversationId => {
|
||||
const status =
|
||||
new Proto.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||
const conv = window.ConversationController.get(conversationId);
|
||||
|
@ -1281,12 +1287,22 @@ export default class MessageSender {
|
|||
if (serviceId) {
|
||||
status.destinationServiceId = serviceId;
|
||||
}
|
||||
if (isPniString(serviceId)) {
|
||||
const pniIdentityKey =
|
||||
await window.textsecure.storage.protocol.loadIdentityKey(
|
||||
serviceId
|
||||
);
|
||||
if (pniIdentityKey) {
|
||||
status.destinationPniIdentityKey = pniIdentityKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
status.unidentified =
|
||||
conversationIdsWithSealedSender.has(conversationId);
|
||||
return status;
|
||||
}),
|
||||
];
|
||||
},
|
||||
{ concurrency: 10 }
|
||||
);
|
||||
}
|
||||
|
||||
const syncMessage = MessageSender.createSyncMessage();
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as log from '../logging/log';
|
|||
import { isNotNil } from './isNotNil';
|
||||
import { updateIdentityKey } from '../services/profiles';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import * as Bytes from '../Bytes';
|
||||
|
||||
export async function verifyStoryListMembers(
|
||||
serviceIds: Array<ServiceIdString>
|
||||
|
@ -49,7 +50,8 @@ export async function verifyStoryListMembers(
|
|||
verifiedServiceIds.delete(serviceId);
|
||||
|
||||
if (identityKey) {
|
||||
await updateIdentityKey(identityKey, serviceId);
|
||||
const identityKeyBytes = Bytes.fromBase64(identityKey);
|
||||
await updateIdentityKey(identityKeyBytes, serviceId);
|
||||
} else {
|
||||
await window.ConversationController.get(serviceId)?.getProfiles();
|
||||
}
|
||||
|
|
|
@ -4001,10 +4001,10 @@
|
|||
type-fest "^3.5.0"
|
||||
uuid "^8.3.0"
|
||||
|
||||
"@signalapp/mock-server@6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.0.0.tgz#a67e18b5cb928749c379c219c775a412ad5c181b"
|
||||
integrity sha512-hzKqCQ8A0xSScn9bztwZnjdizI15wTuEjj/uwmzWylzsPxbcXkOOL+db9O0uTOKbDIl6nJFrsUFqQ8R6LC8TAg==
|
||||
"@signalapp/mock-server@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-6.1.0.tgz#5cb26ed475ca74c74800d9abff4c0c7954c59f54"
|
||||
integrity sha512-SBfN61aRqhtH7hHsdVl3waS1HFI5RCBcQJJ3o5yHXTx1xnXvp1VCjSQ85Vlg57+0IihBPZqqYxyEuNN+5Fnp8g==
|
||||
dependencies:
|
||||
"@signalapp/libsignal-client" "^0.39.2"
|
||||
debug "^4.3.2"
|
||||
|
|
Loading…
Reference in a new issue