Update libsignal to 0.50.0
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
33ec40d7b4
commit
8b969b5a0a
11 changed files with 462 additions and 157 deletions
|
@ -3373,6 +3373,10 @@ export class ConversationModel extends window.Backbone
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.attributes.removalStage !== 'justNotification') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.get('pendingRemovedContactNotification')) {
|
||||
return;
|
||||
}
|
||||
|
@ -4140,6 +4144,11 @@ export class ConversationModel extends window.Backbone
|
|||
this.maybeSetPendingUniversalTimer(stats.hasUserInitiatedMessages)
|
||||
)
|
||||
);
|
||||
drop(
|
||||
this.queueJob('maybeAddRemovedNotificaiton', async () =>
|
||||
this.maybeSetContactRemoved()
|
||||
)
|
||||
);
|
||||
|
||||
const { preview, activity } = stats;
|
||||
let previewMessage: MessageModel | undefined;
|
||||
|
|
|
@ -70,6 +70,7 @@ import {
|
|||
isVerifiedChange,
|
||||
isChangeNumberNotification,
|
||||
isJoinedSignalNotification,
|
||||
isTitleTransitionNotification,
|
||||
} from '../../state/selectors/message';
|
||||
import * as Bytes from '../../Bytes';
|
||||
import { canBeSynced as canPreferredReactionEmojiBeSynced } from '../../reactions/preferredReactionEmoji';
|
||||
|
@ -258,16 +259,21 @@ export class BackupExportStream extends Readable {
|
|||
recipient: {
|
||||
id: this.getDistributionListRecipientId(),
|
||||
distributionList: {
|
||||
name: list.name,
|
||||
distributionId: uuidToBytes(list.id),
|
||||
allowReplies: list.allowsReplies,
|
||||
deletionTimestamp: list.deletedAtTimestamp
|
||||
? Long.fromNumber(list.deletedAtTimestamp)
|
||||
: null,
|
||||
privacyMode,
|
||||
memberRecipientIds: list.members.map(serviceId =>
|
||||
this.getOrPushPrivateRecipient({ serviceId })
|
||||
),
|
||||
|
||||
distributionList: list.deletedAtTimestamp
|
||||
? null
|
||||
: {
|
||||
name: list.name,
|
||||
allowReplies: list.allowsReplies,
|
||||
privacyMode,
|
||||
memberRecipientIds: list.members.map(serviceId =>
|
||||
this.getOrPushPrivateRecipient({ serviceId })
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -451,6 +457,9 @@ export class BackupExportStream extends Readable {
|
|||
|
||||
const usernameLink = storage.get('usernameLink');
|
||||
|
||||
const subscriberId = storage.get('subscriberId');
|
||||
const backupsSubscriberId = storage.get('backupsSubscriberId');
|
||||
|
||||
return {
|
||||
profileKey: storage.get('profileKey'),
|
||||
username: me.get('username') || null,
|
||||
|
@ -465,8 +474,18 @@ export class BackupExportStream extends Readable {
|
|||
givenName: me.get('profileName'),
|
||||
familyName: me.get('profileFamilyName'),
|
||||
avatarUrlPath: storage.get('avatarUrl'),
|
||||
subscriberId: storage.get('subscriberId'),
|
||||
subscriberCurrencyCode: storage.get('subscriberCurrencyCode'),
|
||||
backupsSubscriberData: Bytes.isNotEmpty(backupsSubscriberId)
|
||||
? {
|
||||
subscriberId: backupsSubscriberId,
|
||||
currencyCode: storage.get('backupsSubscriberCurrencyCode'),
|
||||
}
|
||||
: null,
|
||||
donationSubscriberData: Bytes.isNotEmpty(subscriberId)
|
||||
? {
|
||||
subscriberId,
|
||||
currencyCode: storage.get('subscriberCurrencyCode'),
|
||||
}
|
||||
: null,
|
||||
accountSettings: {
|
||||
readReceipts: storage.get('read-receipt-setting'),
|
||||
sealedSenderIndicators: storage.get('sealedSenderIndicators'),
|
||||
|
@ -567,7 +586,17 @@ export class BackupExportStream extends Readable {
|
|||
if (isMe(convo)) {
|
||||
res.self = {};
|
||||
} else if (isDirectConversation(convo)) {
|
||||
const { Registered } = Backups.Contact;
|
||||
let visibility: Backups.Contact.Visibility;
|
||||
if (convo.removalStage == null) {
|
||||
visibility = Backups.Contact.Visibility.VISIBLE;
|
||||
} else if (convo.removalStage === 'justNotification') {
|
||||
visibility = Backups.Contact.Visibility.HIDDEN;
|
||||
} else if (convo.removalStage === 'messageRequest') {
|
||||
visibility = Backups.Contact.Visibility.HIDDEN_MESSAGE_REQUEST;
|
||||
} else {
|
||||
throw missingCaseError(convo.removalStage);
|
||||
}
|
||||
|
||||
res.contact = {
|
||||
aci:
|
||||
convo.serviceId && convo.serviceId !== convo.pni
|
||||
|
@ -581,13 +610,18 @@ export class BackupExportStream extends Readable {
|
|||
blocked: convo.serviceId
|
||||
? window.storage.blocked.isServiceIdBlocked(convo.serviceId)
|
||||
: null,
|
||||
hidden: convo.removalStage !== undefined,
|
||||
registered: isConversationUnregistered(convo)
|
||||
? Registered.NOT_REGISTERED
|
||||
: Registered.REGISTERED,
|
||||
unregisteredTimestamp: convo.firstUnregisteredAt
|
||||
? Long.fromNumber(convo.firstUnregisteredAt)
|
||||
: null,
|
||||
visibility,
|
||||
...(isConversationUnregistered(convo)
|
||||
? {
|
||||
notRegistered: {
|
||||
unregisteredTimestamp: convo.firstUnregisteredAt
|
||||
? Long.fromNumber(convo.firstUnregisteredAt)
|
||||
: null,
|
||||
},
|
||||
}
|
||||
: {
|
||||
registered: {},
|
||||
}),
|
||||
profileKey: convo.profileKey
|
||||
? Bytes.fromBase64(convo.profileKey)
|
||||
: null,
|
||||
|
@ -1089,6 +1123,27 @@ export class BackupExportStream extends Readable {
|
|||
return { kind: NonBubbleResultKind.Directionless, patch };
|
||||
}
|
||||
|
||||
if (isTitleTransitionNotification(message)) {
|
||||
strictAssert(
|
||||
message.titleTransition != null,
|
||||
'Missing title transition data'
|
||||
);
|
||||
const { renderInfo } = message.titleTransition;
|
||||
if (renderInfo.e164) {
|
||||
updateMessage.learnedProfileChange = {
|
||||
e164: Long.fromString(renderInfo.e164),
|
||||
};
|
||||
} else {
|
||||
strictAssert(
|
||||
renderInfo.username,
|
||||
'Title transition must have username or e164'
|
||||
);
|
||||
updateMessage.learnedProfileChange = { username: renderInfo.username };
|
||||
}
|
||||
|
||||
return { kind: NonBubbleResultKind.Directionless, patch };
|
||||
}
|
||||
|
||||
if (isDeliveryIssue(message)) {
|
||||
updateMessage.simpleUpdate = {
|
||||
type: Backups.SimpleChatUpdate.Type.BAD_DECRYPT,
|
||||
|
@ -1129,7 +1184,6 @@ export class BackupExportStream extends Readable {
|
|||
|
||||
if (isContactRemovedNotification(message)) {
|
||||
// Transient, drop it
|
||||
// TODO: DESKTOP-7124
|
||||
return { kind: NonBubbleResultKind.Drop };
|
||||
}
|
||||
|
||||
|
@ -1138,9 +1192,8 @@ export class BackupExportStream extends Readable {
|
|||
}
|
||||
|
||||
if (isGroupUpdate(message)) {
|
||||
// TODO (DESKTOP-6964)
|
||||
// these old-school message types are no longer generated but we probably
|
||||
// still want to render them
|
||||
// GV1 is deprecated.
|
||||
return { kind: NonBubbleResultKind.Drop };
|
||||
}
|
||||
|
||||
if (isUnsupportedMessage(message)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { isNumber } from 'lodash';
|
|||
|
||||
import { Backups, SignalService } from '../../protobuf';
|
||||
import Data from '../../sql/Client';
|
||||
import type { StoryDistributionWithMembersType } from '../../sql/Interface';
|
||||
import * as log from '../../logging/log';
|
||||
import { StorySendMode } from '../../types/Stories';
|
||||
import type { ServiceIdString, AciString } from '../../types/ServiceId';
|
||||
|
@ -426,8 +427,8 @@ export class BackupImportStream extends Writable {
|
|||
givenName,
|
||||
familyName,
|
||||
avatarUrlPath,
|
||||
subscriberId,
|
||||
subscriberCurrencyCode,
|
||||
backupsSubscriberData,
|
||||
donationSubscriberData,
|
||||
accountSettings,
|
||||
}: Backups.IAccountData): Promise<void> {
|
||||
strictAssert(this.ourConversation === undefined, 'Duplicate AccountData');
|
||||
|
@ -466,11 +467,23 @@ export class BackupImportStream extends Writable {
|
|||
if (avatarUrlPath != null) {
|
||||
await storage.put('avatarUrl', avatarUrlPath);
|
||||
}
|
||||
if (subscriberId != null) {
|
||||
await storage.put('subscriberId', subscriberId);
|
||||
if (donationSubscriberData != null) {
|
||||
const { subscriberId, currencyCode } = donationSubscriberData;
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
await storage.put('subscriberId', subscriberId);
|
||||
}
|
||||
if (currencyCode != null) {
|
||||
await storage.put('subscriberCurrencyCode', currencyCode);
|
||||
}
|
||||
}
|
||||
if (subscriberCurrencyCode != null) {
|
||||
await storage.put('subscriberCurrencyCode', subscriberCurrencyCode);
|
||||
if (backupsSubscriberData != null) {
|
||||
const { subscriberId, currencyCode } = backupsSubscriberData;
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
await storage.put('backupsSubscriberId', subscriberId);
|
||||
}
|
||||
if (currencyCode != null) {
|
||||
await storage.put('backupsSubscriberCurrencyCode', currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
await storage.put(
|
||||
|
@ -576,6 +589,20 @@ export class BackupImportStream extends Writable {
|
|||
: undefined;
|
||||
const e164 = contact.e164 ? `+${contact.e164}` : undefined;
|
||||
|
||||
let removalStage: 'justNotification' | 'messageRequest' | undefined;
|
||||
switch (contact.visibility) {
|
||||
case Backups.Contact.Visibility.HIDDEN:
|
||||
removalStage = 'justNotification';
|
||||
break;
|
||||
case Backups.Contact.Visibility.HIDDEN_MESSAGE_REQUEST:
|
||||
removalStage = 'messageRequest';
|
||||
break;
|
||||
case Backups.Contact.Visibility.VISIBLE:
|
||||
default:
|
||||
removalStage = undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
const attrs: ConversationAttributesType = {
|
||||
id: generateUuid(),
|
||||
type: 'private',
|
||||
|
@ -583,7 +610,7 @@ export class BackupImportStream extends Writable {
|
|||
serviceId: aci ?? pni,
|
||||
pni,
|
||||
e164,
|
||||
removalStage: contact.hidden ? 'messageRequest' : undefined,
|
||||
removalStage,
|
||||
profileKey: contact.profileKey
|
||||
? Bytes.toBase64(contact.profileKey)
|
||||
: undefined,
|
||||
|
@ -593,10 +620,16 @@ export class BackupImportStream extends Writable {
|
|||
hideStory: contact.hideStory === true,
|
||||
};
|
||||
|
||||
if (contact.registered === Backups.Contact.Registered.NOT_REGISTERED) {
|
||||
const timestamp = contact.unregisteredTimestamp?.toNumber() ?? Date.now();
|
||||
if (contact.notRegistered) {
|
||||
const timestamp =
|
||||
contact.notRegistered.unregisteredTimestamp?.toNumber() ?? Date.now();
|
||||
attrs.discoveredUnregisteredAt = timestamp;
|
||||
attrs.firstUnregisteredAt = timestamp;
|
||||
} else {
|
||||
strictAssert(
|
||||
contact.registered,
|
||||
'contact is either registered or unregistered'
|
||||
);
|
||||
}
|
||||
|
||||
if (contact.blocked) {
|
||||
|
@ -644,72 +677,92 @@ export class BackupImportStream extends Writable {
|
|||
}
|
||||
|
||||
private async fromDistributionList(
|
||||
list: Backups.IDistributionList
|
||||
listItem: Backups.IDistributionListItem
|
||||
): Promise<void> {
|
||||
strictAssert(
|
||||
Bytes.isNotEmpty(list.distributionId),
|
||||
Bytes.isNotEmpty(listItem.distributionId),
|
||||
'Missing distribution list id'
|
||||
);
|
||||
|
||||
const id = bytesToUuid(list.distributionId);
|
||||
const id = bytesToUuid(listItem.distributionId);
|
||||
strictAssert(isStoryDistributionId(id), 'Invalid distribution list id');
|
||||
|
||||
strictAssert(
|
||||
list.privacyMode != null,
|
||||
'Missing distribution list privacy mode'
|
||||
);
|
||||
|
||||
let isBlockList: boolean;
|
||||
const { PrivacyMode } = Backups.DistributionList;
|
||||
switch (list.privacyMode) {
|
||||
case PrivacyMode.ALL:
|
||||
strictAssert(
|
||||
!list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL privacy mode has members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ALL_EXCEPT:
|
||||
strictAssert(
|
||||
list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL_EXCEPT privacy mode has no members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ONLY_WITH:
|
||||
isBlockList = false;
|
||||
break;
|
||||
case PrivacyMode.UNKNOWN:
|
||||
throw new Error('Invalid privacy mode for distribution list');
|
||||
default:
|
||||
throw missingCaseError(list.privacyMode);
|
||||
}
|
||||
|
||||
const result = {
|
||||
const commonFields = {
|
||||
id,
|
||||
name: list.name ?? '',
|
||||
deletedAtTimestamp:
|
||||
list.deletionTimestamp == null
|
||||
? undefined
|
||||
: getTimestampFromLong(list.deletionTimestamp),
|
||||
allowsReplies: list.allowReplies === true,
|
||||
isBlockList,
|
||||
members: (list.memberRecipientIds || []).map(recipientId => {
|
||||
const convo = this.recipientIdToConvo.get(recipientId.toNumber());
|
||||
strictAssert(convo != null, 'Missing story distribution list member');
|
||||
strictAssert(
|
||||
convo.serviceId,
|
||||
'Story distribution list member has no serviceId'
|
||||
);
|
||||
|
||||
return convo.serviceId;
|
||||
}),
|
||||
|
||||
// Default values
|
||||
senderKeyInfo: undefined,
|
||||
storageNeedsSync: false,
|
||||
};
|
||||
|
||||
let result: StoryDistributionWithMembersType;
|
||||
if (listItem.deletionTimestamp == null) {
|
||||
const { distributionList: list } = listItem;
|
||||
strictAssert(
|
||||
list != null,
|
||||
'Distribution list is either present or deleted'
|
||||
);
|
||||
|
||||
strictAssert(
|
||||
list.privacyMode != null,
|
||||
'Missing distribution list privacy mode'
|
||||
);
|
||||
|
||||
let isBlockList: boolean;
|
||||
const { PrivacyMode } = Backups.DistributionList;
|
||||
switch (list.privacyMode) {
|
||||
case PrivacyMode.ALL:
|
||||
strictAssert(
|
||||
!list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL privacy mode has members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ALL_EXCEPT:
|
||||
strictAssert(
|
||||
list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL_EXCEPT privacy mode has no members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ONLY_WITH:
|
||||
isBlockList = false;
|
||||
break;
|
||||
case PrivacyMode.UNKNOWN:
|
||||
throw new Error('Invalid privacy mode for distribution list');
|
||||
default:
|
||||
throw missingCaseError(list.privacyMode);
|
||||
}
|
||||
|
||||
result = {
|
||||
...commonFields,
|
||||
name: list.name ?? '',
|
||||
allowsReplies: list.allowReplies === true,
|
||||
isBlockList,
|
||||
members: (list.memberRecipientIds || []).map(recipientId => {
|
||||
const convo = this.recipientIdToConvo.get(recipientId.toNumber());
|
||||
strictAssert(convo != null, 'Missing story distribution list member');
|
||||
strictAssert(
|
||||
convo.serviceId,
|
||||
'Story distribution list member has no serviceId'
|
||||
);
|
||||
|
||||
return convo.serviceId;
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
...commonFields,
|
||||
|
||||
name: '',
|
||||
allowsReplies: false,
|
||||
isBlockList: false,
|
||||
members: [],
|
||||
|
||||
deletedAtTimestamp: getTimestampFromLong(listItem.deletionTimestamp),
|
||||
};
|
||||
}
|
||||
|
||||
await Data.createNewStoryDistribution(result);
|
||||
}
|
||||
|
||||
|
@ -1426,6 +1479,27 @@ export class BackupImportStream extends Writable {
|
|||
};
|
||||
}
|
||||
|
||||
if (updateMessage.learnedProfileChange) {
|
||||
const { e164, username } = updateMessage.learnedProfileChange;
|
||||
strictAssert(
|
||||
e164 != null || username != null,
|
||||
'learnedProfileChange must have an old name'
|
||||
);
|
||||
return {
|
||||
message: {
|
||||
type: 'title-transition-notification',
|
||||
titleTransition: {
|
||||
renderInfo: {
|
||||
type: 'private',
|
||||
e164: e164 && !e164.isZero() ? `+${e164}` : undefined,
|
||||
username: dropNull(username),
|
||||
},
|
||||
},
|
||||
},
|
||||
additionalMessages: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (updateMessage.threadMerge) {
|
||||
const { previousE164 } = updateMessage.threadMerge;
|
||||
strictAssert(previousE164 != null, 'threadMerge must have an old e164');
|
||||
|
|
|
@ -388,13 +388,25 @@ export function toAccountRecord(
|
|||
accountRecord.pinnedConversations = pinnedConversations;
|
||||
|
||||
const subscriberId = window.storage.get('subscriberId');
|
||||
if (subscriberId instanceof Uint8Array) {
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
accountRecord.subscriberId = subscriberId;
|
||||
}
|
||||
const subscriberCurrencyCode = window.storage.get('subscriberCurrencyCode');
|
||||
const subscriberCurrencyCode = window.storage.get(
|
||||
'backupsSubscriberCurrencyCode'
|
||||
);
|
||||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
accountRecord.subscriberCurrencyCode = subscriberCurrencyCode;
|
||||
}
|
||||
const backupsSubscriberId = window.storage.get('backupsSubscriberId');
|
||||
if (Bytes.isNotEmpty(backupsSubscriberId)) {
|
||||
accountRecord.backupsSubscriberId = backupsSubscriberId;
|
||||
}
|
||||
const backupsSubscriberCurrencyCode = window.storage.get(
|
||||
'backupsSubscriberCurrencyCode'
|
||||
);
|
||||
if (typeof backupsSubscriberCurrencyCode === 'string') {
|
||||
accountRecord.backupsSubscriberCurrencyCode = backupsSubscriberCurrencyCode;
|
||||
}
|
||||
const displayBadgesOnProfile = window.storage.get('displayBadgesOnProfile');
|
||||
if (displayBadgesOnProfile !== undefined) {
|
||||
accountRecord.displayBadgesOnProfile = displayBadgesOnProfile;
|
||||
|
@ -1223,6 +1235,8 @@ export async function mergeAccountRecord(
|
|||
preferredReactionEmoji: rawPreferredReactionEmoji,
|
||||
subscriberId,
|
||||
subscriberCurrencyCode,
|
||||
backupsSubscriberId,
|
||||
backupsSubscriberCurrencyCode,
|
||||
displayBadgesOnProfile,
|
||||
keepMutedChatsArchived,
|
||||
hasCompletedUsernameOnboarding,
|
||||
|
@ -1428,12 +1442,21 @@ export async function mergeAccountRecord(
|
|||
);
|
||||
}
|
||||
|
||||
if (subscriberId instanceof Uint8Array) {
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
await window.storage.put('subscriberId', subscriberId);
|
||||
}
|
||||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
await window.storage.put('subscriberCurrencyCode', subscriberCurrencyCode);
|
||||
}
|
||||
if (Bytes.isNotEmpty(backupsSubscriberId)) {
|
||||
await window.storage.put('backupsSubscriberId', backupsSubscriberId);
|
||||
}
|
||||
if (typeof backupsSubscriberCurrencyCode === 'string') {
|
||||
await window.storage.put(
|
||||
'backupsSubscriberCurrencyCode',
|
||||
backupsSubscriberCurrencyCode
|
||||
);
|
||||
}
|
||||
await window.storage.put(
|
||||
'displayBadgesOnProfile',
|
||||
Boolean(displayBadgesOnProfile)
|
||||
|
|
|
@ -418,6 +418,26 @@ describe('backup/non-bubble messages', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('roundtrips title transition notification', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'title-transition-notification',
|
||||
received_at: 1,
|
||||
sent_at: 1,
|
||||
timestamp: 1,
|
||||
sourceServiceId: CONTACT_A,
|
||||
titleTransition: {
|
||||
renderInfo: {
|
||||
type: 'private',
|
||||
e164: '+12125551234',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('roundtrips thread merge', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
|
|
2
ts/types/Storage.d.ts
vendored
2
ts/types/Storage.d.ts
vendored
|
@ -154,6 +154,8 @@ export type StorageAccessType = {
|
|||
areWeASubscriber: boolean;
|
||||
subscriberId: Uint8Array;
|
||||
subscriberCurrencyCode: string;
|
||||
backupsSubscriberId: Uint8Array;
|
||||
backupsSubscriberCurrencyCode: string;
|
||||
displayBadgesOnProfile: boolean;
|
||||
keepMutedChatsArchived: boolean;
|
||||
usernameLastIntegrityCheck: number;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue