Improve message targeting for incoming reactions
This commit is contained in:
parent
f02a11bc9b
commit
a0b4126b52
19 changed files with 769 additions and 180 deletions
|
@ -1,7 +1,15 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import {
|
||||
type Device,
|
||||
type Group,
|
||||
PrimaryDevice,
|
||||
type Proto,
|
||||
StorageState,
|
||||
} from '@signalapp/mock-server';
|
||||
import { assert } from 'chai';
|
||||
import Long from 'long';
|
||||
import type { Locator, Page } from 'playwright';
|
||||
import { expect } from 'playwright/test';
|
||||
|
||||
|
@ -85,3 +93,187 @@ export async function expectSystemMessages(
|
|||
expected
|
||||
);
|
||||
}
|
||||
|
||||
function getDevice(author: PrimaryDevice | Device): Device {
|
||||
return author instanceof PrimaryDevice ? author.device : author;
|
||||
}
|
||||
|
||||
type GroupInfo = {
|
||||
group: Group;
|
||||
members: Array<PrimaryDevice>;
|
||||
};
|
||||
|
||||
function maybeWrapInSyncMessage({
|
||||
isSync,
|
||||
to,
|
||||
sentTo,
|
||||
dataMessage,
|
||||
}: {
|
||||
isSync: boolean;
|
||||
to: PrimaryDevice | Device;
|
||||
sentTo?: Array<PrimaryDevice | Device>;
|
||||
dataMessage: Proto.IDataMessage;
|
||||
}): Proto.IContent {
|
||||
return isSync
|
||||
? {
|
||||
syncMessage: {
|
||||
sent: {
|
||||
destinationServiceId: getDevice(to).aci,
|
||||
message: dataMessage,
|
||||
timestamp: dataMessage.timestamp,
|
||||
unidentifiedStatus: (sentTo ?? [to]).map(contact => ({
|
||||
destinationServiceId: getDevice(contact).aci,
|
||||
destination: getDevice(contact).number,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
: { dataMessage };
|
||||
}
|
||||
|
||||
function isToGroup(to: Device | PrimaryDevice | GroupInfo): to is GroupInfo {
|
||||
return 'group' in to;
|
||||
}
|
||||
|
||||
export function sendTextMessage({
|
||||
from,
|
||||
to,
|
||||
text,
|
||||
desktop,
|
||||
timestamp = Date.now(),
|
||||
}: {
|
||||
from: PrimaryDevice;
|
||||
to: PrimaryDevice | Device | GroupInfo;
|
||||
text: string;
|
||||
desktop: Device;
|
||||
timestamp?: number;
|
||||
}): Promise<void> {
|
||||
const isSync = from.secondaryDevices.includes(desktop);
|
||||
const toDevice = isSync || isToGroup(to) ? desktop : getDevice(to);
|
||||
const groupInfo = isToGroup(to) ? to : undefined;
|
||||
return from.sendRaw(
|
||||
toDevice,
|
||||
maybeWrapInSyncMessage({
|
||||
isSync,
|
||||
to: to as PrimaryDevice,
|
||||
dataMessage: {
|
||||
body: text,
|
||||
timestamp: Long.fromNumber(timestamp),
|
||||
groupV2: groupInfo
|
||||
? {
|
||||
masterKey: groupInfo.group.masterKey,
|
||||
revision: groupInfo.group.revision,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
sentTo: groupInfo ? groupInfo.members : [to as PrimaryDevice | Device],
|
||||
}),
|
||||
{ timestamp }
|
||||
);
|
||||
}
|
||||
|
||||
export function sendReaction({
|
||||
from,
|
||||
to,
|
||||
targetAuthor,
|
||||
targetMessageTimestamp,
|
||||
emoji = '👍',
|
||||
reactionTimestamp = Date.now(),
|
||||
desktop,
|
||||
}: {
|
||||
from: PrimaryDevice;
|
||||
to: PrimaryDevice | Device;
|
||||
targetAuthor: PrimaryDevice | Device;
|
||||
targetMessageTimestamp: number;
|
||||
emoji: string;
|
||||
reactionTimestamp?: number;
|
||||
desktop: Device;
|
||||
}): Promise<void> {
|
||||
const isSync = from.secondaryDevices.includes(desktop);
|
||||
return from.sendRaw(
|
||||
isSync ? desktop : getDevice(to),
|
||||
maybeWrapInSyncMessage({
|
||||
isSync,
|
||||
to,
|
||||
dataMessage: {
|
||||
timestamp: Long.fromNumber(reactionTimestamp),
|
||||
reaction: {
|
||||
emoji,
|
||||
targetAuthorAci: getDevice(targetAuthor).aci,
|
||||
targetTimestamp: Long.fromNumber(targetMessageTimestamp),
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
timestamp: reactionTimestamp,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getStorageState(phone: PrimaryDevice) {
|
||||
return (await phone.getStorageState()) ?? StorageState.getEmpty();
|
||||
}
|
||||
|
||||
export async function createGroup(
|
||||
phone: PrimaryDevice,
|
||||
otherMembers: Array<PrimaryDevice>,
|
||||
groupTitle: string
|
||||
): Promise<Group> {
|
||||
const group = await phone.createGroup({
|
||||
title: groupTitle,
|
||||
members: [phone, ...otherMembers],
|
||||
});
|
||||
let state = await getStorageState(phone);
|
||||
|
||||
state = state
|
||||
.addGroup(group, {
|
||||
whitelisted: true,
|
||||
})
|
||||
.pinGroup(group);
|
||||
|
||||
// Finally whitelist and pin contacts
|
||||
for (const member of otherMembers) {
|
||||
state = state.addContact(member, {
|
||||
whitelisted: true,
|
||||
serviceE164: member.device.number,
|
||||
identityKey: member.publicKey.serialize(),
|
||||
profileKey: member.profileKey.serialize(),
|
||||
givenName: member.profileName,
|
||||
});
|
||||
}
|
||||
await phone.setStorageState(state);
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function clickOnConversation(
|
||||
page: Page,
|
||||
contact: PrimaryDevice
|
||||
): Promise<void> {
|
||||
const leftPane = page.locator('#LeftPane');
|
||||
await leftPane.getByTestId(contact.device.aci).click();
|
||||
}
|
||||
export async function pinContact(
|
||||
phone: PrimaryDevice,
|
||||
contact: PrimaryDevice
|
||||
): Promise<void> {
|
||||
const state = await getStorageState(phone);
|
||||
state.pin(contact);
|
||||
await phone.setStorageState(state);
|
||||
}
|
||||
|
||||
export function acceptConversation(page: Page): Promise<void> {
|
||||
return page
|
||||
.locator('.module-message-request-actions button >> "Accept"')
|
||||
.click();
|
||||
}
|
||||
|
||||
export function getTimeline(page: Page): Locator {
|
||||
return page.locator('.module-timeline__messages__container');
|
||||
}
|
||||
|
||||
export function getMessageInTimelineByTimestamp(
|
||||
page: Page,
|
||||
timestamp: number
|
||||
): Locator {
|
||||
return getTimeline(page).getByTestId(`${timestamp}`);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue