Backups: support direct story replies
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
f97e08bcee
commit
e20be50f01
9 changed files with 417 additions and 32 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -194,7 +194,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'signalapp/Signal-Message-Backup-Tests'
|
||||
ref: '05062de9656c5ed7c7e6c6a49897b42e7ad083fc'
|
||||
ref: '22d7f507b61691e0a7da1fd4b233f219bdaf2280'
|
||||
path: 'backup-integration-tests'
|
||||
|
||||
- run: xvfb-run --auto-servernum npm run test-electron
|
||||
|
|
|
@ -6052,7 +6052,7 @@ third-party/chromium/LICENSE.
|
|||
|
||||
```
|
||||
|
||||
## windows-core 0.52.0, windows-sys 0.45.0, windows-sys 0.52.0, windows-sys 0.59.0, windows-targets 0.42.2, windows-targets 0.48.5, windows-targets 0.52.6, windows_aarch64_msvc 0.42.2, windows_aarch64_msvc 0.48.5, windows_aarch64_msvc 0.52.6, windows_x86_64_gnu 0.48.5, windows_x86_64_gnu 0.52.6, windows_x86_64_msvc 0.42.2, windows_x86_64_msvc 0.48.5, windows_x86_64_msvc 0.52.6
|
||||
## windows-core 0.52.0, windows-sys 0.45.0, windows-sys 0.52.0, windows-sys 0.59.0, windows-targets 0.42.2, windows-targets 0.52.6, windows_aarch64_msvc 0.42.2, windows_aarch64_msvc 0.52.6, windows_x86_64_gnu 0.52.6, windows_x86_64_msvc 0.42.2, windows_x86_64_msvc 0.52.6
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
@ -10809,7 +10809,7 @@ SOFTWARE.
|
|||
|
||||
```
|
||||
|
||||
## derive_more 0.99.18
|
||||
## derive_more-impl 1.0.0, derive_more 0.99.18, derive_more 1.0.0
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
|
9
package-lock.json
generated
9
package-lock.json
generated
|
@ -22,7 +22,7 @@
|
|||
"@react-aria/utils": "3.25.3",
|
||||
"@react-spring/web": "9.7.5",
|
||||
"@signalapp/better-sqlite3": "9.0.9",
|
||||
"@signalapp/libsignal-client": "0.65.0",
|
||||
"@signalapp/libsignal-client": "0.65.2",
|
||||
"@signalapp/ringrtc": "2.49.3",
|
||||
"@types/fabric": "4.5.3",
|
||||
"backbone": "1.6.0",
|
||||
|
@ -6471,11 +6471,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@signalapp/libsignal-client": {
|
||||
"version": "0.65.0",
|
||||
"resolved": "https://registry.npmjs.org/@signalapp/libsignal-client/-/libsignal-client-0.65.0.tgz",
|
||||
"integrity": "sha512-6bb+454/ZoOu6EhHdKHSFVoNKRhka1FhqfiDL/FYLf79g+Nz4kXNxCMRJwYO3hzPAgjjs/6Q7S1mXr9CfS7Kkg==",
|
||||
"version": "0.65.2",
|
||||
"resolved": "https://registry.npmjs.org/@signalapp/libsignal-client/-/libsignal-client-0.65.2.tgz",
|
||||
"integrity": "sha512-OfemOQcVxykG8fwUwqkbxt1Vg0HZSGrrWG8wqjJbXj93kOOmQNkpuiJlfm+NBqNYkTYnwRy8rd6PQbu04q7NeA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.8.0",
|
||||
"type-fest": "^4.26.0",
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
"@react-aria/utils": "3.25.3",
|
||||
"@react-spring/web": "9.7.5",
|
||||
"@signalapp/better-sqlite3": "9.0.9",
|
||||
"@signalapp/libsignal-client": "0.65.0",
|
||||
"@signalapp/libsignal-client": "0.65.2",
|
||||
"@signalapp/ringrtc": "2.49.3",
|
||||
"@types/fabric": "4.5.3",
|
||||
"backbone": "1.6.0",
|
||||
|
|
|
@ -173,6 +173,7 @@ message Contact {
|
|||
optional bytes identityKey = 14;
|
||||
IdentityState identityState = 15;
|
||||
Name nickname = 16; // absent iff both `given` and `family` are empty
|
||||
string note = 17;
|
||||
}
|
||||
|
||||
message Group {
|
||||
|
@ -380,6 +381,7 @@ message ChatItem {
|
|||
PaymentNotification paymentNotification = 16;
|
||||
GiftBadge giftBadge = 17;
|
||||
ViewOnceMessage viewOnceMessage = 18;
|
||||
DirectStoryReplyMessage directStoryReplyMessage = 19; // group story reply messages are not backed up
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,6 +450,21 @@ message ContactMessage {
|
|||
repeated Reaction reactions = 2;
|
||||
}
|
||||
|
||||
message DirectStoryReplyMessage {
|
||||
message TextReply {
|
||||
Text text = 1;
|
||||
FilePointer longText = 2;
|
||||
}
|
||||
|
||||
oneof reply {
|
||||
TextReply textReply = 1;
|
||||
string emoji = 2;
|
||||
}
|
||||
|
||||
repeated Reaction reactions = 3;
|
||||
reserved /*storySentTimestamp*/ 4;
|
||||
}
|
||||
|
||||
message PaymentNotification {
|
||||
message TransactionDetails {
|
||||
message MobileCoinTxoIdentification { // Used to map to payments on the ledger
|
||||
|
@ -1241,4 +1258,4 @@ message ChatFolder {
|
|||
FolderType folderType = 6;
|
||||
repeated uint64 includedRecipientIds = 7; // generated recipient id of groups, contacts, and/or note to self
|
||||
repeated uint64 excludedRecipientIds = 8; // generated recipient id of groups, contacts, and/or note to self
|
||||
}
|
||||
}
|
|
@ -842,7 +842,7 @@ export class BackupExportStream extends Readable {
|
|||
identityKey = identityKeysById.get(convo.serviceId);
|
||||
}
|
||||
|
||||
const { nicknameGivenName, nicknameFamilyName } = convo;
|
||||
const { nicknameGivenName, nicknameFamilyName, note } = convo;
|
||||
|
||||
res.contact = {
|
||||
aci:
|
||||
|
@ -887,6 +887,7 @@ export class BackupExportStream extends Readable {
|
|||
family: nicknameFamilyName,
|
||||
}
|
||||
: null,
|
||||
note,
|
||||
};
|
||||
} else if (isGroupV2(convo) && convo.masterKey) {
|
||||
let storySendMode: Backups.Group.StorySendMode;
|
||||
|
@ -994,6 +995,19 @@ export class BackupExportStream extends Readable {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (message.type === 'story') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
conversation &&
|
||||
isGroupV2(conversation.attributes) &&
|
||||
message.storyReplyContext
|
||||
) {
|
||||
// We drop group story replies
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const expirationTimestamp = calculateExpirationTimestamp(message);
|
||||
if (expirationTimestamp != null && expirationTimestamp <= this.#now + DAY) {
|
||||
// Message expires too soon
|
||||
|
@ -1220,6 +1234,17 @@ export class BackupExportStream extends Readable {
|
|||
state,
|
||||
};
|
||||
}
|
||||
} else if (message.storyReplyContext) {
|
||||
result.directStoryReplyMessage = await this.#toDirectStoryReplyMessage({
|
||||
message,
|
||||
backupLevel,
|
||||
});
|
||||
|
||||
result.revisions = await this.#toChatItemRevisions(
|
||||
result,
|
||||
message,
|
||||
backupLevel
|
||||
);
|
||||
} else {
|
||||
result.standardMessage = await this.#toStandardMessage({
|
||||
message,
|
||||
|
@ -2527,6 +2552,51 @@ export class BackupExportStream extends Readable {
|
|||
};
|
||||
}
|
||||
|
||||
async #toDirectStoryReplyMessage({
|
||||
message,
|
||||
backupLevel,
|
||||
}: {
|
||||
message: Pick<
|
||||
MessageAttributesType,
|
||||
| 'body'
|
||||
| 'bodyAttachment'
|
||||
| 'bodyRanges'
|
||||
| 'storyReaction'
|
||||
| 'storyReplyContext'
|
||||
| 'received_at'
|
||||
| 'reactions'
|
||||
>;
|
||||
backupLevel: BackupLevel;
|
||||
}): Promise<Backups.IDirectStoryReplyMessage> {
|
||||
const result = new Backups.DirectStoryReplyMessage({
|
||||
reactions: this.#getMessageReactions(message),
|
||||
});
|
||||
|
||||
if (message.storyReaction) {
|
||||
result.emoji = message.storyReaction.emoji;
|
||||
} else {
|
||||
result.textReply = {
|
||||
longText: message.bodyAttachment
|
||||
? await this.#processAttachment({
|
||||
attachment: message.bodyAttachment,
|
||||
backupLevel,
|
||||
messageReceivedAt: message.received_at,
|
||||
})
|
||||
: undefined,
|
||||
text:
|
||||
message.body != null
|
||||
? {
|
||||
body: message.body ? trimBody(message.body) : undefined,
|
||||
bodyRanges: message.bodyRanges?.map(range =>
|
||||
this.#toBodyRange(range)
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async #toViewOnceMessage({
|
||||
message,
|
||||
backupLevel,
|
||||
|
@ -2568,7 +2638,7 @@ export class BackupExportStream extends Readable {
|
|||
// The first history is the copy of the current message
|
||||
.slice(1)
|
||||
.map(async history => {
|
||||
return {
|
||||
const result: Backups.IChatItem = {
|
||||
// Required fields
|
||||
chatId: parent.chatId,
|
||||
authorId: parent.authorId,
|
||||
|
@ -2586,16 +2656,23 @@ export class BackupExportStream extends Readable {
|
|||
incoming: isOutgoing
|
||||
? undefined
|
||||
: this.#getIncomingMessageDetails(history),
|
||||
|
||||
// Message itself
|
||||
standardMessage: await this.#toStandardMessage({
|
||||
message: history,
|
||||
backupLevel,
|
||||
}),
|
||||
};
|
||||
|
||||
// Backups use oldest to newest order
|
||||
if (parent.directStoryReplyMessage) {
|
||||
result.directStoryReplyMessage =
|
||||
await this.#toDirectStoryReplyMessage({
|
||||
message: history,
|
||||
backupLevel,
|
||||
});
|
||||
} else {
|
||||
result.standardMessage = await this.#toStandardMessage({
|
||||
message: history,
|
||||
backupLevel,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
})
|
||||
// Backups use oldest to newest order
|
||||
.reverse()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -914,6 +914,7 @@ export class BackupImportStream extends Writable {
|
|||
expireTimerVersion: 1,
|
||||
nicknameGivenName: dropNull(contact.nickname?.given),
|
||||
nicknameFamilyName: dropNull(contact.nickname?.family),
|
||||
note: dropNull(contact.note),
|
||||
};
|
||||
|
||||
if (serviceId != null && Bytes.isNotEmpty(contact.identityKey)) {
|
||||
|
@ -1441,6 +1442,26 @@ export class BackupImportStream extends Writable {
|
|||
...attributes,
|
||||
...(await this.#fromViewOnceMessage(item.viewOnceMessage)),
|
||||
};
|
||||
} else if (item.directStoryReplyMessage) {
|
||||
strictAssert(item.directionless == null, 'reply cannot be directionless');
|
||||
let storyAuthorAci: AciString | undefined;
|
||||
if (item.incoming) {
|
||||
strictAssert(this.#aboutMe?.aci, 'about me must exist');
|
||||
storyAuthorAci = this.#aboutMe.aci;
|
||||
} else {
|
||||
strictAssert(
|
||||
isAciString(chatConvo.serviceId),
|
||||
'must have ACI for story author'
|
||||
);
|
||||
storyAuthorAci = chatConvo.serviceId;
|
||||
}
|
||||
attributes = {
|
||||
...attributes,
|
||||
...this.#fromDirectStoryReplyMessage(
|
||||
item.directStoryReplyMessage,
|
||||
storyAuthorAci
|
||||
),
|
||||
};
|
||||
} else {
|
||||
const result = await this.#fromNonBubbleChatItem(item, {
|
||||
aboutMe,
|
||||
|
@ -1472,8 +1493,8 @@ export class BackupImportStream extends Writable {
|
|||
|
||||
if (item.revisions?.length) {
|
||||
strictAssert(
|
||||
item.standardMessage,
|
||||
`${logId}: Only standard message can have revisions`
|
||||
item.standardMessage || item.directStoryReplyMessage,
|
||||
`${logId}: Only standard or story reply message can have revisions`
|
||||
);
|
||||
|
||||
const history = await this.#fromRevisions({
|
||||
|
@ -1793,9 +1814,6 @@ export class BackupImportStream extends Writable {
|
|||
isStory: false,
|
||||
})
|
||||
) {
|
||||
if (isNightly(window.getVersion())) {
|
||||
throw new Error(`${logId}: dropping invalid link preview`);
|
||||
}
|
||||
log.warn(`${logId}: dropping invalid link preview`);
|
||||
return;
|
||||
}
|
||||
|
@ -1836,6 +1854,55 @@ export class BackupImportStream extends Writable {
|
|||
};
|
||||
}
|
||||
|
||||
#fromDirectStoryReplyMessage(
|
||||
directStoryReplyMessage: Backups.IDirectStoryReplyMessage,
|
||||
storyAuthorAci: AciString
|
||||
): Partial<MessageAttributesType> {
|
||||
const { reactions, textReply, emoji } = directStoryReplyMessage;
|
||||
|
||||
const result: Partial<MessageAttributesType> = {
|
||||
reactions: this.#fromReactions(reactions),
|
||||
storyReplyContext: {
|
||||
authorAci: storyAuthorAci,
|
||||
messageId: '', // stories are never imported
|
||||
},
|
||||
};
|
||||
|
||||
if (textReply) {
|
||||
result.body = textReply.text?.body ?? undefined;
|
||||
result.bodyRanges = this.#fromBodyRanges(textReply.text);
|
||||
result.bodyAttachment = textReply.longText
|
||||
? convertFilePointerToAttachment(textReply.longText)
|
||||
: undefined;
|
||||
} else if (emoji) {
|
||||
result.storyReaction = {
|
||||
emoji,
|
||||
targetAuthorAci: storyAuthorAci,
|
||||
targetTimestamp: 0, // stories are never imported
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async #fromDirectStoryReplyRevision(
|
||||
revision: Backups.IDirectStoryReplyMessage
|
||||
): Promise<Partial<EditHistoryType>> {
|
||||
const { textReply } = revision;
|
||||
|
||||
if (!textReply) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
body: textReply.text?.body ?? undefined,
|
||||
bodyRanges: this.#fromBodyRanges(textReply.text),
|
||||
bodyAttachment: textReply.longText
|
||||
? convertFilePointerToAttachment(textReply.longText)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async #fromRevisions({
|
||||
mainMessage,
|
||||
revisions,
|
||||
|
@ -1849,8 +1916,8 @@ export class BackupImportStream extends Writable {
|
|||
revisions
|
||||
.map(async rev => {
|
||||
strictAssert(
|
||||
rev.standardMessage,
|
||||
'Edit history has non-standard messages'
|
||||
rev.standardMessage || rev.directStoryReplyMessage,
|
||||
'Edit history on a message that does not support revisions'
|
||||
);
|
||||
|
||||
const timestamp = getCheckedTimestampFromLong(rev.dateSent);
|
||||
|
@ -1866,11 +1933,7 @@ export class BackupImportStream extends Writable {
|
|||
},
|
||||
} = this.#fromDirectionDetails(rev, timestamp);
|
||||
|
||||
return {
|
||||
...(await this.#fromStandardMessage({
|
||||
logId,
|
||||
data: rev.standardMessage,
|
||||
})),
|
||||
const commonFields = {
|
||||
timestamp,
|
||||
received_at: incrementMessageCounter(),
|
||||
sendStateByConversationId,
|
||||
|
@ -1880,6 +1943,28 @@ export class BackupImportStream extends Writable {
|
|||
readStatus,
|
||||
unidentifiedDeliveryReceived,
|
||||
};
|
||||
|
||||
if (rev.standardMessage) {
|
||||
return {
|
||||
...(await this.#fromStandardMessage({
|
||||
logId,
|
||||
data: rev.standardMessage,
|
||||
})),
|
||||
...commonFields,
|
||||
};
|
||||
}
|
||||
|
||||
if (rev.directStoryReplyMessage) {
|
||||
return {
|
||||
...(await this.#fromDirectStoryReplyRevision(
|
||||
rev.directStoryReplyMessage
|
||||
)),
|
||||
...commonFields,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
'Edit history on a message that does not support revisions'
|
||||
);
|
||||
})
|
||||
// Fix order: from newest to oldest
|
||||
.reverse()
|
||||
|
|
|
@ -24,6 +24,8 @@ import {
|
|||
import { loadAllAndReinitializeRedux } from '../../services/allLoaders';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import type { MessageAttributesType } from '../../model-types';
|
||||
import { TEXT_ATTACHMENT } from '../../types/MIME';
|
||||
import { MY_STORY_ID } from '../../types/Stories';
|
||||
|
||||
const CONTACT_A = generateAci();
|
||||
const CONTACT_B = generateAci();
|
||||
|
@ -817,4 +819,209 @@ describe('backup/bubble messages', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
describe('stories', () => {
|
||||
const GROUP_ID = Bytes.toBase64(getRandomBytes(32));
|
||||
let group: ConversationModel | undefined;
|
||||
let ourConversation: ConversationModel | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
group = await window.ConversationController.getOrCreateAndWait(
|
||||
GROUP_ID,
|
||||
'group',
|
||||
{
|
||||
groupVersion: 2,
|
||||
masterKey: Bytes.toBase64(getRandomBytes(32)),
|
||||
name: 'Rock Enthusiasts',
|
||||
active_at: 1,
|
||||
}
|
||||
);
|
||||
ourConversation = window.ConversationController.get(OUR_ACI);
|
||||
});
|
||||
it('does not export stories', async () => {
|
||||
strictAssert(ourConversation, 'conversations exist');
|
||||
strictAssert(group, 'conversations exist');
|
||||
const commonProps = {
|
||||
type: 'story',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
sourceServiceId: OUR_ACI,
|
||||
attachments: [
|
||||
{
|
||||
contentType: TEXT_ATTACHMENT,
|
||||
size: 4,
|
||||
textAttachment: {
|
||||
color: 4285041620,
|
||||
text: 'test',
|
||||
textForegroundColor: 4294967295,
|
||||
textStyle: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
expirationStartTimestamp: Date.now(),
|
||||
expireTimer: DurationInSeconds.fromMillis(WEEK),
|
||||
} satisfies Partial<MessageAttributesType>;
|
||||
|
||||
const directStory: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
conversationId: ourConversation.id,
|
||||
storyDistributionListId: MY_STORY_ID,
|
||||
};
|
||||
|
||||
const groupStory: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
conversationId: group.id,
|
||||
};
|
||||
|
||||
await asymmetricRoundtripHarness([directStory, groupStory], []);
|
||||
});
|
||||
it('roundtrips direct story emoji replies', async () => {
|
||||
strictAssert(ourConversation, 'conversations exist');
|
||||
const commonProps = {
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
conversationId: contactA.id,
|
||||
} satisfies Partial<MessageAttributesType>;
|
||||
|
||||
const incomingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
unidentifiedDeliveryReceived: true,
|
||||
sourceServiceId: CONTACT_A,
|
||||
storyReaction: {
|
||||
emoji: '🤷♂️',
|
||||
targetAuthorAci: OUR_ACI,
|
||||
targetTimestamp: 0, // targetTimestamp is not roundtripped
|
||||
},
|
||||
storyReplyContext: {
|
||||
authorAci: OUR_ACI,
|
||||
messageId: '',
|
||||
},
|
||||
};
|
||||
|
||||
const outgoingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
sourceServiceId: OUR_ACI,
|
||||
storyReaction: {
|
||||
emoji: '🤷♂️',
|
||||
targetAuthorAci: CONTACT_A,
|
||||
targetTimestamp: 0, // targetTimestamp is not roundtripped
|
||||
},
|
||||
storyReplyContext: {
|
||||
authorAci: CONTACT_A,
|
||||
messageId: '',
|
||||
},
|
||||
sendStateByConversationId: {
|
||||
[CONTACT_A]: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await symmetricRoundtripHarness([incomingReply, outgoingReply]);
|
||||
});
|
||||
it('roundtrips direct story text replies', async () => {
|
||||
strictAssert(ourConversation, 'conversations exist');
|
||||
const commonProps = {
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
conversationId: contactA.id,
|
||||
body: 'text reply to story',
|
||||
} satisfies Partial<MessageAttributesType>;
|
||||
|
||||
const incomingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
unidentifiedDeliveryReceived: true,
|
||||
sourceServiceId: CONTACT_A,
|
||||
storyReplyContext: {
|
||||
authorAci: OUR_ACI,
|
||||
messageId: '',
|
||||
},
|
||||
};
|
||||
|
||||
const outgoingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
sourceServiceId: OUR_ACI,
|
||||
storyReplyContext: {
|
||||
authorAci: CONTACT_A,
|
||||
messageId: '',
|
||||
},
|
||||
sendStateByConversationId: {
|
||||
[CONTACT_A]: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await symmetricRoundtripHarness([incomingReply, outgoingReply]);
|
||||
});
|
||||
|
||||
it('does not export group story replies', async () => {
|
||||
strictAssert(ourConversation, 'conversations exist');
|
||||
strictAssert(group, 'conversations exist');
|
||||
const commonProps = {
|
||||
conversationId: group.id,
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
timestamp: 3,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
body: 'text reply to story',
|
||||
} satisfies Partial<MessageAttributesType>;
|
||||
|
||||
const incomingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
unidentifiedDeliveryReceived: true,
|
||||
sourceServiceId: CONTACT_A,
|
||||
storyReplyContext: {
|
||||
authorAci: OUR_ACI,
|
||||
messageId: '',
|
||||
},
|
||||
};
|
||||
|
||||
const outgoingReply: MessageAttributesType = {
|
||||
...commonProps,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
sourceServiceId: OUR_ACI,
|
||||
storyReplyContext: {
|
||||
authorAci: CONTACT_A,
|
||||
messageId: '',
|
||||
},
|
||||
sendStateByConversationId: {
|
||||
[CONTACT_A]: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await asymmetricRoundtripHarness([incomingReply, outgoingReply], []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -584,7 +584,7 @@ describe('backup/non-bubble messages', () => {
|
|||
id: generateGuid(),
|
||||
type: 'message-request-response-event',
|
||||
received_at: 1,
|
||||
sourceServiceId: CONTACT_A,
|
||||
sourceServiceId: OUR_ACI,
|
||||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
|
|
Loading…
Reference in a new issue