Import/export gift badges, other fields
This commit is contained in:
parent
af1c593fef
commit
e6b62001d3
10 changed files with 536 additions and 55 deletions
|
@ -349,6 +349,7 @@ message ChatItem {
|
|||
RemoteDeletedMessage remoteDeletedMessage = 14;
|
||||
ChatUpdateMessage updateMessage = 15;
|
||||
PaymentNotification paymentNotification = 16;
|
||||
GiftBadge giftBadge = 17;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,6 +440,18 @@ message PaymentNotification {
|
|||
|
||||
}
|
||||
|
||||
message GiftBadge {
|
||||
enum State {
|
||||
UNOPENED = 0;
|
||||
OPENED = 1;
|
||||
REDEEMED = 2;
|
||||
FAILED = 3;
|
||||
}
|
||||
|
||||
bytes receiptCredentialPresentation = 1;
|
||||
State state = 2;
|
||||
}
|
||||
|
||||
message ContactAttachment {
|
||||
message Name {
|
||||
optional string givenName = 1;
|
||||
|
@ -1114,4 +1127,4 @@ message ChatStyle {
|
|||
// Bubble setting is automatically determined based on the wallpaper setting.
|
||||
AutomaticBubbleColor autoBubbleColor = 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,6 +195,7 @@ message AccountRecord {
|
|||
optional bytes subscriberId = 21;
|
||||
optional string subscriberCurrencyCode = 22;
|
||||
optional bool displayBadgesOnProfile = 23;
|
||||
optional bool donorSubscriptionManuallyCancelled = 24;
|
||||
optional bool keepMutedChatsArchived = 25;
|
||||
optional bool hasSetMyStoriesPrivacy = 26;
|
||||
optional bool hasViewedOnboardingStory = 27;
|
||||
|
@ -202,12 +203,13 @@ message AccountRecord {
|
|||
optional bool storiesDisabled = 29;
|
||||
optional OptionalBool storyViewReceiptsEnabled = 30;
|
||||
reserved 31; // hasReadOnboardingStory
|
||||
reserved 32; // hasSeenGroupStoryEducationSheet
|
||||
optional bool hasSeenGroupStoryEducationSheet = 32;
|
||||
optional string username = 33;
|
||||
optional bool hasCompletedUsernameOnboarding = 34;
|
||||
optional UsernameLink usernameLink = 35;
|
||||
optional bytes backupsSubscriberId = 36;
|
||||
optional string backupsSubscriberCurrencyCode = 37;
|
||||
optional bool backupsSubscriptionManuallyCancelled = 38;
|
||||
}
|
||||
|
||||
message StoryDistributionListRecord {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Backups, SignalService } from '../../protobuf';
|
|||
import Data from '../../sql/Client';
|
||||
import type { PageMessagesCursorType } from '../../sql/Interface';
|
||||
import * as log from '../../logging/log';
|
||||
import { GiftBadgeStates } from '../../components/conversation/Message';
|
||||
import { StorySendMode, MY_STORY_ID } from '../../types/Stories';
|
||||
import {
|
||||
isPniString,
|
||||
|
@ -478,12 +479,20 @@ export class BackupExportStream extends Readable {
|
|||
? {
|
||||
subscriberId: backupsSubscriberId,
|
||||
currencyCode: storage.get('backupsSubscriberCurrencyCode'),
|
||||
manuallyCancelled: storage.get(
|
||||
'backupsSubscriptionManuallyCancelled',
|
||||
false
|
||||
),
|
||||
}
|
||||
: null,
|
||||
donationSubscriberData: Bytes.isNotEmpty(subscriberId)
|
||||
? {
|
||||
subscriberId,
|
||||
currencyCode: storage.get('subscriberCurrencyCode'),
|
||||
manuallyCancelled: storage.get(
|
||||
'donorSubscriptionManuallyCancelled',
|
||||
false
|
||||
),
|
||||
}
|
||||
: null,
|
||||
accountSettings: {
|
||||
|
@ -507,6 +516,9 @@ export class BackupExportStream extends Readable {
|
|||
hasCompletedUsernameOnboarding: storage.get(
|
||||
'hasCompletedUsernameOnboarding'
|
||||
),
|
||||
hasSeenGroupStoryEducationSheet: storage.get(
|
||||
'hasSeenGroupStoryEducationSheet'
|
||||
),
|
||||
phoneNumberSharingMode,
|
||||
},
|
||||
};
|
||||
|
@ -849,6 +861,31 @@ export class BackupExportStream extends Readable {
|
|||
sticker: stickerProto,
|
||||
reactions: this.getMessageReactions(message),
|
||||
};
|
||||
} else if (isGiftBadge(message)) {
|
||||
const { giftBadge } = message;
|
||||
strictAssert(giftBadge != null, 'Message must have gift badge');
|
||||
|
||||
let state: Backups.GiftBadge.State;
|
||||
switch (giftBadge.state) {
|
||||
case GiftBadgeStates.Unopened:
|
||||
state = Backups.GiftBadge.State.UNOPENED;
|
||||
break;
|
||||
case GiftBadgeStates.Opened:
|
||||
state = Backups.GiftBadge.State.OPENED;
|
||||
break;
|
||||
case GiftBadgeStates.Redeemed:
|
||||
state = Backups.GiftBadge.State.REDEEMED;
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(giftBadge.state);
|
||||
}
|
||||
|
||||
result.giftBadge = {
|
||||
receiptCredentialPresentation: Bytes.fromBase64(
|
||||
giftBadge.receiptCredentialPresentation
|
||||
),
|
||||
state,
|
||||
};
|
||||
} else {
|
||||
result.standardMessage = await this.toStandardMessage(
|
||||
message,
|
||||
|
@ -1187,10 +1224,6 @@ export class BackupExportStream extends Readable {
|
|||
return { kind: NonBubbleResultKind.Drop };
|
||||
}
|
||||
|
||||
if (isGiftBadge(message)) {
|
||||
// TODO (DESKTOP-6964): reuse quote's handling
|
||||
}
|
||||
|
||||
if (isGroupUpdate(message)) {
|
||||
// GV1 is deprecated.
|
||||
return { kind: NonBubbleResultKind.Drop };
|
||||
|
@ -1837,7 +1870,11 @@ export class BackupExportStream extends Readable {
|
|||
}: Pick<MessageAttributesType, 'reactions'>):
|
||||
| Array<Backups.IReaction>
|
||||
| undefined {
|
||||
return reactions?.map(reaction => {
|
||||
if (reactions == null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return reactions?.map((reaction, sortOrder) => {
|
||||
return {
|
||||
emoji: reaction.emoji,
|
||||
authorId: this.getOrPushPrivateRecipient({
|
||||
|
@ -1847,6 +1884,7 @@ export class BackupExportStream extends Readable {
|
|||
receivedTimestamp: getSafeLongFromTimestamp(
|
||||
reaction.receivedAtDate ?? reaction.timestamp
|
||||
),
|
||||
sortOrder: Long.fromNumber(sortOrder),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1856,12 +1894,14 @@ export class BackupExportStream extends Readable {
|
|||
editMessageReceivedAtMs,
|
||||
serverTimestamp,
|
||||
readStatus,
|
||||
unidentifiedDeliveryReceived,
|
||||
}: Pick<
|
||||
MessageAttributesType,
|
||||
| 'received_at_ms'
|
||||
| 'editMessageReceivedAtMs'
|
||||
| 'serverTimestamp'
|
||||
| 'readStatus'
|
||||
| 'unidentifiedDeliveryReceived'
|
||||
>): Backups.ChatItem.IIncomingMessageDetails {
|
||||
const dateReceived = editMessageReceivedAtMs || receivedAtMs;
|
||||
return {
|
||||
|
@ -1872,6 +1912,7 @@ export class BackupExportStream extends Readable {
|
|||
? getSafeLongFromTimestamp(serverTimestamp)
|
||||
: null,
|
||||
read: readStatus === ReadStatus.Read,
|
||||
sealedSender: unidentifiedDeliveryReceived === true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1879,10 +1920,22 @@ export class BackupExportStream extends Readable {
|
|||
sentAt: number,
|
||||
{
|
||||
sendStateByConversationId = {},
|
||||
}: Pick<MessageAttributesType, 'sendStateByConversationId'>
|
||||
unidentifiedDeliveries = [],
|
||||
errors = [],
|
||||
}: Pick<
|
||||
MessageAttributesType,
|
||||
'sendStateByConversationId' | 'unidentifiedDeliveries' | 'errors'
|
||||
>
|
||||
): Backups.ChatItem.IOutgoingMessageDetails {
|
||||
const BackupSendStatus = Backups.SendStatus.Status;
|
||||
|
||||
const sealedSenderServiceIds = new Set(unidentifiedDeliveries);
|
||||
const errorMap = new Map(
|
||||
errors.map(({ serviceId, name }) => {
|
||||
return [serviceId, name];
|
||||
})
|
||||
);
|
||||
|
||||
const sendStatus = new Array<Backups.ISendStatus>();
|
||||
for (const [id, entry] of Object.entries(sendStateByConversationId)) {
|
||||
const target = window.ConversationController.get(id);
|
||||
|
@ -1915,6 +1968,19 @@ export class BackupExportStream extends Readable {
|
|||
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:
|
||||
|
@ -1922,6 +1988,9 @@ export class BackupExportStream extends Readable {
|
|||
? getSafeLongFromTimestamp(entry.updatedAt)
|
||||
: null,
|
||||
deliveryStatus,
|
||||
networkFailure,
|
||||
identityKeyMismatch,
|
||||
sealedSender,
|
||||
});
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { Aci, Pni } from '@signalapp/libsignal-client';
|
||||
import { ReceiptCredentialPresentation } from '@signalapp/libsignal-client/zkgroup';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
import pMap from 'p-map';
|
||||
import { Writable } from 'stream';
|
||||
|
@ -11,6 +12,7 @@ import { Backups, SignalService } from '../../protobuf';
|
|||
import Data from '../../sql/Client';
|
||||
import type { StoryDistributionWithMembersType } from '../../sql/Interface';
|
||||
import * as log from '../../logging/log';
|
||||
import { GiftBadgeStates } from '../../components/conversation/Message';
|
||||
import { StorySendMode } from '../../types/Stories';
|
||||
import type { ServiceIdString, AciString } from '../../types/ServiceId';
|
||||
import { fromAciObject, fromPniObject } from '../../types/ServiceId';
|
||||
|
@ -27,6 +29,7 @@ import {
|
|||
} from '../../types/Stickers';
|
||||
import type {
|
||||
ConversationAttributesType,
|
||||
CustomError,
|
||||
MessageAttributesType,
|
||||
MessageReactionType,
|
||||
EditHistoryType,
|
||||
|
@ -34,7 +37,7 @@ import type {
|
|||
} from '../../model-types.d';
|
||||
import { assertDev, strictAssert } from '../../util/assert';
|
||||
import { getTimestampFromLong } from '../../util/timestampLongUtils';
|
||||
import { DurationInSeconds } from '../../util/durations';
|
||||
import { DurationInSeconds, SECOND } from '../../util/durations';
|
||||
import { dropNull } from '../../util/dropNull';
|
||||
import {
|
||||
deriveGroupID,
|
||||
|
@ -468,22 +471,36 @@ export class BackupImportStream extends Writable {
|
|||
await storage.put('avatarUrl', avatarUrlPath);
|
||||
}
|
||||
if (donationSubscriberData != null) {
|
||||
const { subscriberId, currencyCode } = donationSubscriberData;
|
||||
const { subscriberId, currencyCode, manuallyCancelled } =
|
||||
donationSubscriberData;
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
await storage.put('subscriberId', subscriberId);
|
||||
}
|
||||
if (currencyCode != null) {
|
||||
await storage.put('subscriberCurrencyCode', currencyCode);
|
||||
}
|
||||
if (manuallyCancelled != null) {
|
||||
await storage.put(
|
||||
'donorSubscriptionManuallyCancelled',
|
||||
manuallyCancelled
|
||||
);
|
||||
}
|
||||
}
|
||||
if (backupsSubscriberData != null) {
|
||||
const { subscriberId, currencyCode } = backupsSubscriberData;
|
||||
const { subscriberId, currencyCode, manuallyCancelled } =
|
||||
backupsSubscriberData;
|
||||
if (Bytes.isNotEmpty(subscriberId)) {
|
||||
await storage.put('backupsSubscriberId', subscriberId);
|
||||
}
|
||||
if (currencyCode != null) {
|
||||
await storage.put('backupsSubscriberCurrencyCode', currencyCode);
|
||||
}
|
||||
if (manuallyCancelled != null) {
|
||||
await storage.put(
|
||||
'backupsSubscriptionManuallyCancelled',
|
||||
manuallyCancelled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await storage.put(
|
||||
|
@ -503,6 +520,12 @@ export class BackupImportStream extends Writable {
|
|||
'preferContactAvatars',
|
||||
accountSettings?.preferContactAvatars === true
|
||||
);
|
||||
if (accountSettings?.universalExpireTimer) {
|
||||
await storage.put(
|
||||
'universalExpireTimer',
|
||||
accountSettings.universalExpireTimer
|
||||
);
|
||||
}
|
||||
await storage.put(
|
||||
'displayBadgesOnProfile',
|
||||
accountSettings?.displayBadgesOnProfile === true
|
||||
|
@ -532,8 +555,8 @@ export class BackupImportStream extends Writable {
|
|||
accountSettings?.hasCompletedUsernameOnboarding === true
|
||||
);
|
||||
await storage.put(
|
||||
'preferredReactionEmoji',
|
||||
accountSettings?.preferredReactionEmoji || []
|
||||
'hasSeenGroupStoryEducationSheet',
|
||||
accountSettings?.hasSeenGroupStoryEducationSheet === true
|
||||
);
|
||||
await storage.put(
|
||||
'preferredReactionEmoji',
|
||||
|
@ -618,6 +641,7 @@ export class BackupImportStream extends Writable {
|
|||
profileName: dropNull(contact.profileGivenName),
|
||||
profileFamilyName: dropNull(contact.profileFamilyName),
|
||||
hideStory: contact.hideStory === true,
|
||||
username: dropNull(contact.username),
|
||||
};
|
||||
|
||||
if (contact.notRegistered) {
|
||||
|
@ -871,8 +895,6 @@ export class BackupImportStream extends Writable {
|
|||
}
|
||||
|
||||
if (item.standardMessage) {
|
||||
// TODO (DESKTOP-6964): gift badge
|
||||
|
||||
attributes = {
|
||||
...attributes,
|
||||
...(await this.fromStandardMessage(item.standardMessage, chatConvo.id)),
|
||||
|
@ -961,6 +983,8 @@ export class BackupImportStream extends Writable {
|
|||
|
||||
const BackupSendStatus = Backups.SendStatus.Status;
|
||||
|
||||
const unidentifiedDeliveries = new Array<ServiceIdString>();
|
||||
const errors = new Array<CustomError>();
|
||||
for (const status of outgoing.sendStatus ?? []) {
|
||||
strictAssert(
|
||||
status.recipientId,
|
||||
|
@ -997,6 +1021,28 @@ export class BackupImportStream extends Writable {
|
|||
break;
|
||||
}
|
||||
|
||||
if (target.serviceId) {
|
||||
if (status.sealedSender) {
|
||||
unidentifiedDeliveries.push(target.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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sendStateByConversationId[target.id] = {
|
||||
status: sendStatus,
|
||||
updatedAt:
|
||||
|
@ -1011,6 +1057,10 @@ export class BackupImportStream extends Writable {
|
|||
patch: {
|
||||
sendStateByConversationId,
|
||||
received_at_ms: timestamp,
|
||||
unidentifiedDeliveries: unidentifiedDeliveries.length
|
||||
? unidentifiedDeliveries
|
||||
: undefined,
|
||||
errors: errors.length ? errors : undefined,
|
||||
},
|
||||
newActiveAt: timestamp,
|
||||
};
|
||||
|
@ -1018,12 +1068,15 @@ export class BackupImportStream extends Writable {
|
|||
if (incoming) {
|
||||
const receivedAtMs = incoming.dateReceived?.toNumber() ?? Date.now();
|
||||
|
||||
const unidentifiedDeliveryReceived = incoming.sealedSender === true;
|
||||
|
||||
if (incoming.read) {
|
||||
return {
|
||||
patch: {
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
received_at_ms: receivedAtMs,
|
||||
unidentifiedDeliveryReceived,
|
||||
},
|
||||
newActiveAt: receivedAtMs,
|
||||
};
|
||||
|
@ -1034,6 +1087,7 @@ export class BackupImportStream extends Writable {
|
|||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
received_at_ms: receivedAtMs,
|
||||
unidentifiedDeliveryReceived,
|
||||
},
|
||||
newActiveAt: receivedAtMs,
|
||||
unread: true,
|
||||
|
@ -1203,8 +1257,15 @@ export class BackupImportStream extends Writable {
|
|||
if (!reactions?.length) {
|
||||
return undefined;
|
||||
}
|
||||
return reactions.map(
|
||||
({ emoji, authorId, sentTimestamp, receivedTimestamp }) => {
|
||||
return reactions
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
if (a.sortOrder && b.sortOrder) {
|
||||
return a.sortOrder.comp(b.sortOrder);
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.map(({ emoji, authorId, sentTimestamp, receivedTimestamp }) => {
|
||||
strictAssert(emoji != null, 'reaction must have an emoji');
|
||||
strictAssert(authorId != null, 'reaction must have authorId');
|
||||
strictAssert(
|
||||
|
@ -1229,8 +1290,7 @@ export class BackupImportStream extends Writable {
|
|||
receivedAtDate: getTimestampFromLong(receivedTimestamp),
|
||||
timestamp: getTimestampFromLong(sentTimestamp),
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async fromNonBubbleChatItem(
|
||||
|
@ -1401,6 +1461,52 @@ export class BackupImportStream extends Writable {
|
|||
additionalMessages: [],
|
||||
};
|
||||
}
|
||||
if (chatItem.giftBadge) {
|
||||
const { giftBadge } = chatItem;
|
||||
strictAssert(
|
||||
Bytes.isNotEmpty(giftBadge.receiptCredentialPresentation),
|
||||
'Gift badge must have a presentation'
|
||||
);
|
||||
|
||||
let state: GiftBadgeStates;
|
||||
switch (giftBadge.state) {
|
||||
case Backups.GiftBadge.State.OPENED:
|
||||
state = GiftBadgeStates.Opened;
|
||||
break;
|
||||
|
||||
case Backups.GiftBadge.State.FAILED:
|
||||
case Backups.GiftBadge.State.REDEEMED:
|
||||
state = GiftBadgeStates.Redeemed;
|
||||
break;
|
||||
|
||||
case Backups.GiftBadge.State.UNOPENED:
|
||||
state = GiftBadgeStates.Unopened;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = GiftBadgeStates.Unopened;
|
||||
break;
|
||||
}
|
||||
|
||||
const receipt = new ReceiptCredentialPresentation(
|
||||
Buffer.from(giftBadge.receiptCredentialPresentation)
|
||||
);
|
||||
|
||||
return {
|
||||
message: {
|
||||
giftBadge: {
|
||||
receiptCredentialPresentation: Bytes.toBase64(
|
||||
giftBadge.receiptCredentialPresentation
|
||||
),
|
||||
expiration: Number(receipt.getReceiptExpirationTime()) * SECOND,
|
||||
id: undefined,
|
||||
level: Number(receipt.getReceiptLevel()),
|
||||
state,
|
||||
},
|
||||
},
|
||||
additionalMessages: [],
|
||||
};
|
||||
}
|
||||
if (chatItem.updateMessage) {
|
||||
return this.fromChatItemUpdateMessage(chatItem.updateMessage, options);
|
||||
}
|
||||
|
|
|
@ -397,6 +397,13 @@ export function toAccountRecord(
|
|||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
accountRecord.subscriberCurrencyCode = subscriberCurrencyCode;
|
||||
}
|
||||
const donorSubscriptionManuallyCancelled = window.storage.get(
|
||||
'donorSubscriptionManuallyCancelled'
|
||||
);
|
||||
if (typeof donorSubscriptionManuallyCancelled === 'boolean') {
|
||||
accountRecord.donorSubscriptionManuallyCancelled =
|
||||
donorSubscriptionManuallyCancelled;
|
||||
}
|
||||
const backupsSubscriberId = window.storage.get('backupsSubscriberId');
|
||||
if (Bytes.isNotEmpty(backupsSubscriberId)) {
|
||||
accountRecord.backupsSubscriberId = backupsSubscriberId;
|
||||
|
@ -407,6 +414,13 @@ export function toAccountRecord(
|
|||
if (typeof backupsSubscriberCurrencyCode === 'string') {
|
||||
accountRecord.backupsSubscriberCurrencyCode = backupsSubscriberCurrencyCode;
|
||||
}
|
||||
const backupsSubscriptionManuallyCancelled = window.storage.get(
|
||||
'backupsSubscriptionManuallyCancelled'
|
||||
);
|
||||
if (typeof backupsSubscriptionManuallyCancelled === 'boolean') {
|
||||
accountRecord.backupsSubscriptionManuallyCancelled =
|
||||
backupsSubscriptionManuallyCancelled;
|
||||
}
|
||||
const displayBadgesOnProfile = window.storage.get('displayBadgesOnProfile');
|
||||
if (displayBadgesOnProfile !== undefined) {
|
||||
accountRecord.displayBadgesOnProfile = displayBadgesOnProfile;
|
||||
|
@ -436,6 +450,14 @@ export function toAccountRecord(
|
|||
hasCompletedUsernameOnboarding;
|
||||
}
|
||||
|
||||
const hasSeenGroupStoryEducationSheet = window.storage.get(
|
||||
'hasSeenGroupStoryEducationSheet'
|
||||
);
|
||||
if (hasSeenGroupStoryEducationSheet !== undefined) {
|
||||
accountRecord.hasSeenGroupStoryEducationSheet =
|
||||
hasSeenGroupStoryEducationSheet;
|
||||
}
|
||||
|
||||
const hasStoriesDisabled = window.storage.get('hasStoriesDisabled');
|
||||
accountRecord.storiesDisabled = hasStoriesDisabled === true;
|
||||
|
||||
|
@ -1235,11 +1257,14 @@ export async function mergeAccountRecord(
|
|||
preferredReactionEmoji: rawPreferredReactionEmoji,
|
||||
subscriberId,
|
||||
subscriberCurrencyCode,
|
||||
donorSubscriptionManuallyCancelled,
|
||||
backupsSubscriberId,
|
||||
backupsSubscriberCurrencyCode,
|
||||
backupsSubscriptionManuallyCancelled,
|
||||
displayBadgesOnProfile,
|
||||
keepMutedChatsArchived,
|
||||
hasCompletedUsernameOnboarding,
|
||||
hasSeenGroupStoryEducationSheet,
|
||||
hasSetMyStoriesPrivacy,
|
||||
hasViewedOnboardingStory,
|
||||
storiesDisabled,
|
||||
|
@ -1448,6 +1473,12 @@ export async function mergeAccountRecord(
|
|||
if (typeof subscriberCurrencyCode === 'string') {
|
||||
await window.storage.put('subscriberCurrencyCode', subscriberCurrencyCode);
|
||||
}
|
||||
if (donorSubscriptionManuallyCancelled != null) {
|
||||
await window.storage.put(
|
||||
'donorSubscriptionManuallyCancelled',
|
||||
donorSubscriptionManuallyCancelled
|
||||
);
|
||||
}
|
||||
if (Bytes.isNotEmpty(backupsSubscriberId)) {
|
||||
await window.storage.put('backupsSubscriberId', backupsSubscriberId);
|
||||
}
|
||||
|
@ -1457,6 +1488,12 @@ export async function mergeAccountRecord(
|
|||
backupsSubscriberCurrencyCode
|
||||
);
|
||||
}
|
||||
if (backupsSubscriptionManuallyCancelled != null) {
|
||||
await window.storage.put(
|
||||
'backupsSubscriptionManuallyCancelled',
|
||||
backupsSubscriptionManuallyCancelled
|
||||
);
|
||||
}
|
||||
await window.storage.put(
|
||||
'displayBadgesOnProfile',
|
||||
Boolean(displayBadgesOnProfile)
|
||||
|
@ -1490,6 +1527,15 @@ export async function mergeAccountRecord(
|
|||
hasCompletedUsernameOnboardingBool
|
||||
);
|
||||
}
|
||||
{
|
||||
const hasCompletedUsernameOnboardingBool = Boolean(
|
||||
hasSeenGroupStoryEducationSheet
|
||||
);
|
||||
await window.storage.put(
|
||||
'hasSeenGroupStoryEducationSheet',
|
||||
hasCompletedUsernameOnboardingBool
|
||||
);
|
||||
}
|
||||
{
|
||||
const hasStoriesDisabled = Boolean(storiesDisabled);
|
||||
await window.storage.put('hasStoriesDisabled', hasStoriesDisabled);
|
||||
|
|
|
@ -118,6 +118,7 @@ describe('backup/attachments', () => {
|
|||
timestamp,
|
||||
readStatus: ReadStatus.Read,
|
||||
seenStatus: SeenStatus.Seen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { v4 as generateGuid } from 'uuid';
|
|||
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import { GiftBadgeStates } from '../../components/conversation/Message';
|
||||
|
||||
import Data from '../../sql/Client';
|
||||
import { generateAci } from '../../types/ServiceId';
|
||||
|
@ -15,6 +16,15 @@ import { setupBasics, symmetricRoundtripHarness, OUR_ACI } from './helpers';
|
|||
|
||||
const CONTACT_A = generateAci();
|
||||
|
||||
const BADGE_RECEIPT =
|
||||
'AEpyZxbRBT+T5PQw9Wcx1QE2aFvL7LoLir9V4UF09Kk9qiP4SpIlHdlWHrAICy6F' +
|
||||
'6WdbdCj45fY6cadDKbBmkw+abohRTJnItrFhyKurnA5X+mZHZv4OvS+aZFmAYS6J' +
|
||||
'W+hpkbI+Fk7Gu3mEix7Pgz1I2EwGFlUBpm7/nuD5A0cKLrUJAMM142fnOEervePV' +
|
||||
'bf0c6Sw5X5aCsBw9J+dxFUGAAAAAAAAAAMH58UUeUj2oH1jfqc0Hb2RUtdA3ee8X' +
|
||||
'0Pp83WT8njwFw5rNGSHeKqOvBZzfAhMGJoiz7l1XfIfsPIreaFb/tA9aq2bOAdDl' +
|
||||
'5OYlxxl6DnjQ3+g3k9ycpl0elkaQnPW2Ai7yjeJ/96K1qssR2a/2b7xi10dmTRGg' +
|
||||
'gebhZnroYYgIgK22ZgAAAABkAAAAAAAAAD9j4f77Xo2Ox5tVyrV2DUo=';
|
||||
|
||||
describe('backup/bubble messages', () => {
|
||||
let contactA: ConversationModel;
|
||||
|
||||
|
@ -48,6 +58,7 @@ describe('backup/bubble messages', () => {
|
|||
body: 'd',
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
editMessageTimestamp: 5,
|
||||
editMessageReceivedAtMs: 5,
|
||||
editHistory: [
|
||||
|
@ -89,6 +100,7 @@ describe('backup/bubble messages', () => {
|
|||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
unidentifiedDeliveries: [CONTACT_A],
|
||||
timestamp: 3,
|
||||
editMessageTimestamp: 5,
|
||||
editMessageReceivedAtMs: 5,
|
||||
|
@ -131,4 +143,224 @@ describe('backup/bubble messages', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('roundtrips unopened gift badge', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
timestamp: 3,
|
||||
giftBadge: {
|
||||
id: undefined,
|
||||
level: 100,
|
||||
expiration: 1723248000000,
|
||||
receiptCredentialPresentation: BADGE_RECEIPT,
|
||||
state: GiftBadgeStates.Opened,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('roundtrips opened gift badge', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
timestamp: 3,
|
||||
giftBadge: {
|
||||
id: undefined,
|
||||
level: 100,
|
||||
expiration: 1723248000000,
|
||||
receiptCredentialPresentation: BADGE_RECEIPT,
|
||||
state: GiftBadgeStates.Opened,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.skip('roundtrips gift badge quote', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
timestamp: 3,
|
||||
giftBadge: {
|
||||
id: undefined,
|
||||
level: 100,
|
||||
expiration: 1723248000000,
|
||||
receiptCredentialPresentation: BADGE_RECEIPT,
|
||||
state: GiftBadgeStates.Opened,
|
||||
},
|
||||
},
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 4,
|
||||
received_at_ms: 4,
|
||||
sent_at: 4,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
timestamp: 4,
|
||||
quote: {
|
||||
authorAci: CONTACT_A,
|
||||
attachments: [],
|
||||
id: 3,
|
||||
isViewOnce: false,
|
||||
isGiftBadge: true,
|
||||
messageId: '',
|
||||
referencedMessageNotFound: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('roundtrips sealed/unsealed incoming message', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: false,
|
||||
timestamp: 3,
|
||||
body: 'unsealed',
|
||||
},
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'incoming',
|
||||
received_at: 4,
|
||||
received_at_ms: 4,
|
||||
sent_at: 4,
|
||||
sourceServiceId: CONTACT_A,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
timestamp: 4,
|
||||
body: 'sealed',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('roundtrips sealed/unsealed outgoing message', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: OUR_ACI,
|
||||
sendStateByConversationId: {
|
||||
[contactA.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
unidentifiedDeliveries: undefined,
|
||||
timestamp: 3,
|
||||
body: 'unsealed',
|
||||
},
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
received_at: 4,
|
||||
received_at_ms: 4,
|
||||
sent_at: 4,
|
||||
sourceServiceId: OUR_ACI,
|
||||
sendStateByConversationId: {
|
||||
[contactA.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
unidentifiedDeliveries: [CONTACT_A],
|
||||
timestamp: 4,
|
||||
body: 'sealed',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('roundtrips messages with send errors', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
received_at: 3,
|
||||
received_at_ms: 3,
|
||||
sent_at: 3,
|
||||
sourceServiceId: OUR_ACI,
|
||||
sendStateByConversationId: {
|
||||
[contactA.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
serviceId: CONTACT_A,
|
||||
name: 'OutgoingIdentityKeyError',
|
||||
message: `The identity of ${CONTACT_A} has changed.`,
|
||||
},
|
||||
],
|
||||
timestamp: 3,
|
||||
body: 'body',
|
||||
},
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
id: generateGuid(),
|
||||
type: 'outgoing',
|
||||
received_at: 4,
|
||||
received_at_ms: 4,
|
||||
sent_at: 4,
|
||||
sourceServiceId: OUR_ACI,
|
||||
sendStateByConversationId: {
|
||||
[contactA.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
},
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
serviceId: CONTACT_A,
|
||||
name: 'OutgoingMessageError',
|
||||
message: 'no http error',
|
||||
},
|
||||
],
|
||||
timestamp: 4,
|
||||
body: 'body',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -93,36 +93,39 @@ function sortAndNormalize(
|
|||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
conversationId: mapConvoId(conversationId),
|
||||
reactions: reactions?.map(({ fromId, ...restOfReaction }) => {
|
||||
return {
|
||||
from: mapConvoId(fromId),
|
||||
...restOfReaction,
|
||||
};
|
||||
}),
|
||||
changedId: mapConvoId(changedId),
|
||||
key_changed: mapConvoId(keyChanged),
|
||||
verifiedChanged: mapConvoId(verifiedChanged),
|
||||
sendStateByConverationId: mapSendState(sendStateByConversationId),
|
||||
editHistory: editHistory?.map(history => {
|
||||
const {
|
||||
sendStateByConversationId: historySendState,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
received_at: _receivedAtHistory,
|
||||
...restOfHistory
|
||||
} = history;
|
||||
// Get rid of unserializable `undefined` values.
|
||||
return JSON.parse(
|
||||
JSON.stringify({
|
||||
...rest,
|
||||
conversationId: mapConvoId(conversationId),
|
||||
reactions: reactions?.map(({ fromId, ...restOfReaction }) => {
|
||||
return {
|
||||
from: mapConvoId(fromId),
|
||||
...restOfReaction,
|
||||
};
|
||||
}),
|
||||
changedId: mapConvoId(changedId),
|
||||
key_changed: mapConvoId(keyChanged),
|
||||
verifiedChanged: mapConvoId(verifiedChanged),
|
||||
sendStateByConverationId: mapSendState(sendStateByConversationId),
|
||||
editHistory: editHistory?.map(history => {
|
||||
const {
|
||||
sendStateByConversationId: historySendState,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
received_at: _receivedAtHistory,
|
||||
...restOfHistory
|
||||
} = history;
|
||||
|
||||
return {
|
||||
...restOfHistory,
|
||||
sendStateByConversationId: mapSendState(historySendState),
|
||||
};
|
||||
}),
|
||||
return {
|
||||
...restOfHistory,
|
||||
sendStateByConversationId: mapSendState(historySendState),
|
||||
};
|
||||
}),
|
||||
|
||||
// Not an original property, but useful
|
||||
isUnsupported: isUnsupportedMessage(message),
|
||||
};
|
||||
// Not an original property, but useful
|
||||
isUnsupported: isUnsupportedMessage(message),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
flags: Proto.DataMessage.Flags.END_SESSION,
|
||||
},
|
||||
]);
|
||||
|
@ -204,6 +205,7 @@ describe('backup/non-bubble messages', () => {
|
|||
timestamp: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -224,12 +226,12 @@ describe('backup/non-bubble messages', () => {
|
|||
timestamp: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// TODO: DESKTOP-7122
|
||||
it.skip('roundtrips bare payments notification', async () => {
|
||||
it('roundtrips bare payments notification', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
|
@ -243,6 +245,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
payment: {
|
||||
kind: PaymentEventKind.Notification,
|
||||
note: 'note with text',
|
||||
|
@ -251,8 +254,7 @@ describe('backup/non-bubble messages', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
// TODO: DESKTOP-7122
|
||||
it.skip('roundtrips full payments notification', async () => {
|
||||
it('roundtrips full payments notification', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
|
@ -266,6 +268,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
payment: {
|
||||
kind: PaymentEventKind.Notification,
|
||||
note: 'note with text',
|
||||
|
@ -297,6 +300,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
contact: [
|
||||
{
|
||||
name: {
|
||||
|
@ -339,6 +343,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
// TODO (DESKTOP-6845): properly handle data FilePointer
|
||||
sticker: {
|
||||
emoji: '👍',
|
||||
|
@ -373,6 +378,7 @@ describe('backup/non-bubble messages', () => {
|
|||
sourceDevice: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
isErased: true,
|
||||
},
|
||||
]);
|
||||
|
@ -475,8 +481,7 @@ describe('backup/non-bubble messages', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
// TODO: DESKTOP-7122
|
||||
it.skip('roundtrips unsupported message', async () => {
|
||||
it('roundtrips unsupported message', async () => {
|
||||
await symmetricRoundtripHarness([
|
||||
{
|
||||
conversationId: contactA.id,
|
||||
|
@ -490,8 +495,9 @@ describe('backup/non-bubble messages', () => {
|
|||
timestamp: 1,
|
||||
readStatus: ReadStatus.Unread,
|
||||
seenStatus: SeenStatus.Unseen,
|
||||
supportedVersionAtReceive: 1,
|
||||
requiredProtocolVersion: 2,
|
||||
unidentifiedDeliveryReceived: true,
|
||||
supportedVersionAtReceive: 5,
|
||||
requiredProtocolVersion: 6,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
3
ts/types/Storage.d.ts
vendored
3
ts/types/Storage.d.ts
vendored
|
@ -72,6 +72,7 @@ export type StorageAccessType = {
|
|||
hasCompletedUsernameOnboarding: boolean;
|
||||
hasCompletedUsernameLinkOnboarding: boolean;
|
||||
hasCompletedSafetyNumberOnboarding: boolean;
|
||||
hasSeenGroupStoryEducationSheet: boolean;
|
||||
hasViewedOnboardingStory: boolean;
|
||||
hasStoriesDisabled: boolean;
|
||||
storyViewReceiptsEnabled: boolean;
|
||||
|
@ -154,8 +155,10 @@ export type StorageAccessType = {
|
|||
areWeASubscriber: boolean;
|
||||
subscriberId: Uint8Array;
|
||||
subscriberCurrencyCode: string;
|
||||
donorSubscriptionManuallyCancelled: boolean;
|
||||
backupsSubscriberId: Uint8Array;
|
||||
backupsSubscriberCurrencyCode: string;
|
||||
backupsSubscriptionManuallyCancelled: boolean;
|
||||
displayBadgesOnProfile: boolean;
|
||||
keepMutedChatsArchived: boolean;
|
||||
usernameLastIntegrityCheck: number;
|
||||
|
|
Loading…
Reference in a new issue