Update backup import/export with new SendStatus, FilePointer, and GroupSnapshot updates
This commit is contained in:
parent
f44a16489c
commit
301f7a505a
9 changed files with 239 additions and 119 deletions
|
@ -86,7 +86,6 @@ import {
|
|||
import * as Bytes from '../../Bytes';
|
||||
import { canBeSynced as canPreferredReactionEmojiBeSynced } from '../../reactions/preferredReactionEmoji';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import { deriveGroupFields } from '../../groups';
|
||||
import { BACKUP_VERSION } from './constants';
|
||||
import { getMessageIdForLogging } from '../../util/idForLogging';
|
||||
import { getCallsHistoryForRedux } from '../callHistoryLoader';
|
||||
|
@ -811,20 +810,12 @@ export class BackupExportStream extends Readable {
|
|||
|
||||
const masterKey = Bytes.fromBase64(convo.masterKey);
|
||||
|
||||
let publicKey;
|
||||
if (convo.publicParams) {
|
||||
publicKey = Bytes.fromBase64(convo.publicParams);
|
||||
} else {
|
||||
({ publicParams: publicKey } = deriveGroupFields(masterKey));
|
||||
}
|
||||
|
||||
res.group = {
|
||||
masterKey,
|
||||
whitelisted: convo.profileSharing,
|
||||
hideStory: convo.hideStory === true,
|
||||
storySendMode,
|
||||
snapshot: {
|
||||
publicKey,
|
||||
title: {
|
||||
title: convo.name ?? '',
|
||||
},
|
||||
|
@ -2204,8 +2195,6 @@ export class BackupExportStream extends Readable {
|
|||
'sendStateByConversationId' | 'unidentifiedDeliveries' | 'errors'
|
||||
>
|
||||
): Backups.ChatItem.IOutgoingMessageDetails {
|
||||
const BackupSendStatus = Backups.SendStatus.Status;
|
||||
|
||||
const sealedSenderServiceIds = new Set(unidentifiedDeliveries);
|
||||
const errorMap = new Map(
|
||||
errors.map(({ serviceId, name }) => {
|
||||
|
@ -2213,65 +2202,78 @@ export class BackupExportStream extends Readable {
|
|||
})
|
||||
);
|
||||
|
||||
const sendStatus = new Array<Backups.ISendStatus>();
|
||||
const sendStatuses = new Array<Backups.ISendStatus>();
|
||||
for (const [id, entry] of Object.entries(sendStateByConversationId)) {
|
||||
const target = window.ConversationController.get(id);
|
||||
if (!target) {
|
||||
log.warn(`backups: no send target for a message ${sentAt}`);
|
||||
continue;
|
||||
}
|
||||
const { serviceId } = target.attributes;
|
||||
const recipientId = this.getOrPushPrivateRecipient(target.attributes);
|
||||
const timestamp =
|
||||
entry.updatedAt != null
|
||||
? getSafeLongFromTimestamp(entry.updatedAt)
|
||||
: null;
|
||||
|
||||
const sendStatus = new Backups.SendStatus({ recipientId, timestamp });
|
||||
|
||||
const sealedSender = serviceId
|
||||
? sealedSenderServiceIds.has(serviceId)
|
||||
: false;
|
||||
|
||||
let deliveryStatus: Backups.SendStatus.Status;
|
||||
switch (entry.status) {
|
||||
case SendStatus.Pending:
|
||||
deliveryStatus = BackupSendStatus.PENDING;
|
||||
sendStatus.pending = new Backups.SendStatus.Pending();
|
||||
break;
|
||||
case SendStatus.Sent:
|
||||
deliveryStatus = BackupSendStatus.SENT;
|
||||
sendStatus.sent = new Backups.SendStatus.Sent({
|
||||
sealedSender,
|
||||
});
|
||||
break;
|
||||
case SendStatus.Delivered:
|
||||
deliveryStatus = BackupSendStatus.DELIVERED;
|
||||
sendStatus.delivered = new Backups.SendStatus.Delivered({
|
||||
sealedSender,
|
||||
});
|
||||
break;
|
||||
case SendStatus.Read:
|
||||
deliveryStatus = BackupSendStatus.READ;
|
||||
sendStatus.read = new Backups.SendStatus.Read({
|
||||
sealedSender,
|
||||
});
|
||||
break;
|
||||
case SendStatus.Viewed:
|
||||
deliveryStatus = BackupSendStatus.VIEWED;
|
||||
sendStatus.viewed = new Backups.SendStatus.Viewed({
|
||||
sealedSender,
|
||||
});
|
||||
break;
|
||||
case SendStatus.Failed:
|
||||
deliveryStatus = BackupSendStatus.FAILED;
|
||||
case SendStatus.Failed: {
|
||||
sendStatus.failed = new Backups.SendStatus.Failed();
|
||||
if (!serviceId) {
|
||||
break;
|
||||
}
|
||||
const errorName = errorMap.get(serviceId);
|
||||
if (!errorName) {
|
||||
break;
|
||||
}
|
||||
|
||||
const identityKeyMismatch = errorName === 'OutgoingIdentityKeyError';
|
||||
if (identityKeyMismatch) {
|
||||
sendStatus.failed.reason =
|
||||
Backups.SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH;
|
||||
} else {
|
||||
sendStatus.failed.reason =
|
||||
Backups.SendStatus.Failed.FailureReason.NETWORK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw missingCaseError(entry.status);
|
||||
}
|
||||
|
||||
const { serviceId } = target.attributes;
|
||||
let networkFailure = false;
|
||||
let identityKeyMismatch = false;
|
||||
let sealedSender = false;
|
||||
if (serviceId) {
|
||||
const errorName = errorMap.get(serviceId);
|
||||
if (errorName !== undefined) {
|
||||
identityKeyMismatch = errorName === 'OutgoingIdentityKeyError';
|
||||
networkFailure = !identityKeyMismatch;
|
||||
}
|
||||
sealedSender = sealedSenderServiceIds.has(serviceId);
|
||||
}
|
||||
|
||||
sendStatus.push({
|
||||
recipientId: this.getOrPushPrivateRecipient(target.attributes),
|
||||
lastStatusUpdateTimestamp:
|
||||
entry.updatedAt != null
|
||||
? getSafeLongFromTimestamp(entry.updatedAt)
|
||||
: null,
|
||||
deliveryStatus,
|
||||
networkFailure,
|
||||
identityKeyMismatch,
|
||||
sealedSender,
|
||||
});
|
||||
sendStatuses.push(sendStatus);
|
||||
}
|
||||
return {
|
||||
sendStatus,
|
||||
sendStatus: sendStatuses,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1278,8 +1278,6 @@ export class BackupImportStream extends Writable {
|
|||
if (outgoing) {
|
||||
const sendStateByConversationId: SendStateByConversationId = {};
|
||||
|
||||
const BackupSendStatus = Backups.SendStatus.Status;
|
||||
|
||||
const unidentifiedDeliveries = new Array<ServiceIdString>();
|
||||
const errors = new Array<CustomError>();
|
||||
for (const status of outgoing.sendStatus ?? []) {
|
||||
|
@ -1295,57 +1293,71 @@ export class BackupImportStream extends Writable {
|
|||
'status target conversation not found'
|
||||
);
|
||||
|
||||
let sendStatus: SendStatus;
|
||||
switch (status.deliveryStatus) {
|
||||
case BackupSendStatus.PENDING:
|
||||
sendStatus = SendStatus.Pending;
|
||||
break;
|
||||
case BackupSendStatus.SENT:
|
||||
sendStatus = SendStatus.Sent;
|
||||
break;
|
||||
case BackupSendStatus.DELIVERED:
|
||||
sendStatus = SendStatus.Delivered;
|
||||
break;
|
||||
case BackupSendStatus.READ:
|
||||
sendStatus = SendStatus.Read;
|
||||
break;
|
||||
case BackupSendStatus.VIEWED:
|
||||
sendStatus = SendStatus.Viewed;
|
||||
break;
|
||||
case BackupSendStatus.FAILED:
|
||||
default:
|
||||
sendStatus = SendStatus.Failed;
|
||||
break;
|
||||
// Desktop does not keep track of users we did not attempt to send to
|
||||
if (status.skipped) {
|
||||
continue;
|
||||
}
|
||||
const { serviceId } = target;
|
||||
|
||||
if (target.serviceId) {
|
||||
if (status.sealedSender) {
|
||||
unidentifiedDeliveries.push(target.serviceId);
|
||||
let sendStatus: SendStatus;
|
||||
if (status.pending) {
|
||||
sendStatus = SendStatus.Pending;
|
||||
} else if (status.sent) {
|
||||
sendStatus = SendStatus.Sent;
|
||||
if (serviceId && status.sent.sealedSender) {
|
||||
unidentifiedDeliveries.push(serviceId);
|
||||
}
|
||||
|
||||
if (status.identityKeyMismatch) {
|
||||
errors.push({
|
||||
serviceId: target.serviceId,
|
||||
name: 'OutgoingIdentityKeyError',
|
||||
// See: ts/textsecure/Errors
|
||||
message: `The identity of ${target.serviceId} has changed.`,
|
||||
});
|
||||
} else if (status.networkFailure) {
|
||||
errors.push({
|
||||
serviceId: target.serviceId,
|
||||
name: 'OutgoingMessageError',
|
||||
// See: ts/textsecure/Errors
|
||||
message: 'no http error',
|
||||
});
|
||||
} else if (status.delivered) {
|
||||
sendStatus = SendStatus.Delivered;
|
||||
if (serviceId && status.delivered.sealedSender) {
|
||||
unidentifiedDeliveries.push(serviceId);
|
||||
}
|
||||
} else if (status.read) {
|
||||
sendStatus = SendStatus.Read;
|
||||
if (serviceId && status.read.sealedSender) {
|
||||
unidentifiedDeliveries.push(serviceId);
|
||||
}
|
||||
} else if (status.viewed) {
|
||||
sendStatus = SendStatus.Viewed;
|
||||
if (serviceId && status.viewed.sealedSender) {
|
||||
unidentifiedDeliveries.push(serviceId);
|
||||
}
|
||||
} else if (status.failed) {
|
||||
sendStatus = SendStatus.Failed;
|
||||
strictAssert(
|
||||
status.failed.reason != null,
|
||||
'Failure reason must exist'
|
||||
);
|
||||
switch (status.failed.reason) {
|
||||
case Backups.SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH:
|
||||
errors.push({
|
||||
serviceId,
|
||||
name: 'OutgoingIdentityKeyError',
|
||||
// See: ts/textsecure/Errors
|
||||
message: `The identity of ${serviceId} has changed.`,
|
||||
});
|
||||
break;
|
||||
case Backups.SendStatus.Failed.FailureReason.NETWORK:
|
||||
case Backups.SendStatus.Failed.FailureReason.UNKNOWN:
|
||||
errors.push({
|
||||
serviceId,
|
||||
name: 'OutgoingMessageError',
|
||||
// See: ts/textsecure/Errors
|
||||
message: 'no http error',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(status.failed.reason);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown sendStatus received: ${status}`);
|
||||
}
|
||||
|
||||
sendStateByConversationId[target.id] = {
|
||||
status: sendStatus,
|
||||
updatedAt:
|
||||
status.lastStatusUpdateTimestamp != null &&
|
||||
!status.lastStatusUpdateTimestamp.isZero()
|
||||
? getTimestampFromLong(status.lastStatusUpdateTimestamp)
|
||||
status.timestamp != null && !status.timestamp.isZero()
|
||||
? getTimestampFromLong(status.timestamp)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ export function convertFilePointerToAttachment(
|
|||
cdnNumber: transitCdnNumber ?? undefined,
|
||||
key: key?.length ? Bytes.toBase64(key) : undefined,
|
||||
digest: digest?.length ? Bytes.toBase64(digest) : undefined,
|
||||
size: size?.toNumber() ?? 0,
|
||||
size: size ?? 0,
|
||||
backupLocator: mediaName
|
||||
? {
|
||||
mediaName,
|
||||
|
@ -401,7 +401,7 @@ function getBackupLocator(attachment: AttachmentDownloadableFromBackupTier) {
|
|||
cdnNumber: attachment.backupLocator.cdnNumber,
|
||||
digest: Bytes.fromBase64(attachment.digest),
|
||||
key: Bytes.fromBase64(attachment.key),
|
||||
size: Long.fromNumber(attachment.size),
|
||||
size: attachment.size,
|
||||
transitCdnKey: attachment.cdnKey,
|
||||
transitCdnNumber: attachment.cdnNumber,
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
} from './helpers';
|
||||
|
||||
const CONTACT_A = generateAci();
|
||||
const CONTACT_B = generateAci();
|
||||
const GV1_ID = Bytes.toBinary(getRandomBytes(ID_V1_LENGTH));
|
||||
|
||||
const BADGE_RECEIPT =
|
||||
|
@ -37,6 +38,7 @@ const BADGE_RECEIPT =
|
|||
|
||||
describe('backup/bubble messages', () => {
|
||||
let contactA: ConversationModel;
|
||||
let contactB: ConversationModel;
|
||||
let gv1: ConversationModel;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -51,6 +53,11 @@ describe('backup/bubble messages', () => {
|
|||
'private',
|
||||
{ systemGivenName: 'CONTACT_A' }
|
||||
);
|
||||
contactB = await window.ConversationController.getOrCreateAndWait(
|
||||
CONTACT_B,
|
||||
'private',
|
||||
{ systemGivenName: 'CONTACT_B' }
|
||||
);
|
||||
|
||||
gv1 = await window.ConversationController.getOrCreateAndWait(
|
||||
GV1_ID,
|
||||
|
@ -346,12 +353,15 @@ describe('backup/bubble messages', () => {
|
|||
[contactA.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
[contactB.id]: {
|
||||
status: SendStatus.Failed,
|
||||
},
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
serviceId: CONTACT_A,
|
||||
serviceId: CONTACT_B,
|
||||
name: 'OutgoingIdentityKeyError',
|
||||
message: `The identity of ${CONTACT_A} has changed.`,
|
||||
message: `The identity of ${CONTACT_B} has changed.`,
|
||||
},
|
||||
],
|
||||
timestamp: 3,
|
||||
|
@ -367,6 +377,9 @@ describe('backup/bubble messages', () => {
|
|||
sourceServiceId: OUR_ACI,
|
||||
sendStateByConversationId: {
|
||||
[contactA.id]: {
|
||||
status: SendStatus.Failed,
|
||||
},
|
||||
[contactB.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('convertFilePointerToAttachment', () => {
|
|||
backupLocator: new Backups.FilePointer.BackupLocator({
|
||||
mediaName: 'mediaName',
|
||||
cdnNumber: 3,
|
||||
size: Long.fromNumber(128),
|
||||
size: 128,
|
||||
key: Bytes.fromString('key'),
|
||||
digest: Bytes.fromString('digest'),
|
||||
transitCdnKey: 'transitCdnKey',
|
||||
|
@ -215,7 +215,7 @@ const defaultBackupLocator = new Backups.FilePointer.BackupLocator({
|
|||
cdnNumber: null,
|
||||
key: Bytes.fromBase64('key'),
|
||||
digest: defaultDigest,
|
||||
size: Long.fromNumber(100),
|
||||
size: 100,
|
||||
transitCdnKey: 'cdnKey',
|
||||
transitCdnNumber: 2,
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue