Fix some backup integration tests

This commit is contained in:
Fedor Indutny 2024-09-12 16:48:27 -07:00 committed by GitHub
parent c68be0f86e
commit 3a991822c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 450 additions and 231 deletions

View file

@ -4290,7 +4290,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
```
## attest 0.1.0, libsignal-ffi 0.55.1, libsignal-jni 0.55.1, libsignal-jni-testing 0.55.1, libsignal-node 0.55.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-testing 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, libsignal-keytrans 0.0.1, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
## attest 0.1.0, libsignal-ffi 0.56.1, libsignal-jni 0.56.1, libsignal-jni-testing 0.56.1, libsignal-node 0.56.1, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-bridge-testing 0.1.0, libsignal-bridge-types 0.1.0, libsignal-core 0.1.0, signal-crypto 0.1.0, device-transfer 0.1.0, libsignal-keytrans 0.0.1, signal-media 0.1.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, signal-pin 0.1.0, poksho 0.7.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
```
GNU AFFERO GENERAL PUBLIC LICENSE
@ -9518,6 +9518,33 @@ SOFTWARE.
```
## tokio-socks 0.5.2
```
MIT License
Copyright (c) 2018 Yilin Chen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## zeroize 1.8.1
```

View file

@ -997,7 +997,9 @@ async function createWindow() {
mainWindow.webContents.send('ci:event', 'db-initialized', {});
const shouldShowWindow =
!app.getLoginItemSettings().wasOpenedAsHidden && !startInTray;
!app.getLoginItemSettings().wasOpenedAsHidden &&
!startInTray &&
!config.get<boolean>('ciIsBackupIntegration');
if (shouldShowWindow) {
getLogger().info('showing main window');
@ -2724,7 +2726,7 @@ ipc.on('get-config', async event => {
dnsFallback: await getDNSFallback(),
disableIPv6: DISABLE_IPV6,
ciBackupPath: config.get<string | null>('ciBackupPath') || undefined,
ciIsPlaintextBackup: config.get<boolean>('ciIsPlaintextBackup'),
ciIsBackupIntegration: config.get<boolean>('ciIsBackupIntegration'),
nodeVersion: process.versions.node,
hostname: os.hostname(),
osRelease: os.release(),

View file

@ -18,7 +18,7 @@
"updatesEnabled": false,
"ciMode": false,
"ciBackupPath": null,
"ciIsPlaintextBackup": false,
"ciIsBackupIntegration": false,
"forcePreloadBundle": false,
"openDevTools": false,
"buildCreation": 0,

9
package-lock.json generated
View file

@ -21,7 +21,7 @@
"@react-aria/utils": "3.16.0",
"@react-spring/web": "9.5.5",
"@signalapp/better-sqlite3": "8.8.1",
"@signalapp/libsignal-client": "0.55.1",
"@signalapp/libsignal-client": "0.56.1",
"@signalapp/ringrtc": "2.47.0",
"@types/fabric": "4.5.3",
"backbone": "1.4.0",
@ -7230,10 +7230,11 @@
}
},
"node_modules/@signalapp/libsignal-client": {
"version": "0.55.1",
"resolved": "https://registry.npmjs.org/@signalapp/libsignal-client/-/libsignal-client-0.55.1.tgz",
"integrity": "sha512-qa2sztxNy5QyXYg9Z8xH9zdYikwNORyWr/95HnLAdzf4YFGsee/8JS74L+2kAn55lE7CVD+EVpgXJYFFw2Gu/w==",
"version": "0.56.1",
"resolved": "https://registry.npmjs.org/@signalapp/libsignal-client/-/libsignal-client-0.56.1.tgz",
"integrity": "sha512-lgOdtf/63G1LuiHTrpxfwumwERuqSd1FM5Q5ZyBo8NUDQoQinG8nyza7XUXggaC/+VFggJUvAfZZvRNkJS7RTA==",
"hasInstallScript": true,
"license": "AGPL-3.0-only",
"dependencies": {
"node-gyp-build": "^4.2.3",
"type-fest": "^3.5.0",

View file

@ -105,7 +105,7 @@
"@react-aria/utils": "3.16.0",
"@react-spring/web": "9.5.5",
"@signalapp/better-sqlite3": "8.8.1",
"@signalapp/libsignal-client": "0.55.1",
"@signalapp/libsignal-client": "0.56.1",
"@signalapp/ringrtc": "2.47.0",
"@types/fabric": "4.5.3",
"backbone": "1.4.0",

View file

@ -276,7 +276,7 @@ message AdHocCall {
}
uint64 callId = 1;
// Refers to a Recipient with the `callLink` field set
// Refers to a `CallLink` recipient.
uint64 recipientId = 2;
State state = 3;
uint64 callTimestamp = 4;
@ -477,7 +477,6 @@ message ContactAttachment {
optional string prefix = 3;
optional string suffix = 4;
optional string middleName = 5;
optional string displayName = 6;
}
message Phone {
@ -652,10 +651,9 @@ message Quote {
optional uint64 targetSentTimestamp = 1; // null if the target message could not be found at time of quote insert
uint64 authorId = 2;
optional string text = 3;
optional Text text = 3;
repeated QuotedAttachment attachments = 4;
repeated BodyRange bodyRanges = 5;
Type type = 6;
Type type = 5;
}
message BodyRange {
@ -681,11 +679,9 @@ message Reaction {
string emoji = 1;
uint64 authorId = 2;
uint64 sentTimestamp = 3;
// Optional because some clients may not track this data
optional uint64 receivedTimestamp = 4;
// A higher sort order means that a reaction is more recent. Some clients may export this as
// incrementing numbers (e.g. 1, 2, 3), others as timestamps.
uint64 sortOrder = 5;
uint64 sortOrder = 4;
}
message ChatUpdateMessage {
@ -1069,7 +1065,7 @@ message StickerPack {
message ChatStyle {
message Gradient {
uint32 angle = 1; // degrees
repeated fixed32 colors = 2;
repeated fixed32 colors = 2; // 0xAARRGGBB
repeated float positions = 3; // percent from 0 to 1
}
@ -1077,7 +1073,7 @@ message ChatStyle {
uint64 id = 1;
oneof color {
fixed32 solid = 2;
fixed32 solid = 2; // 0xAARRGGBB
Gradient gradient = 3;
}
}

View file

@ -1,7 +1,9 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { format } from 'node:util';
import { ipcRenderer } from 'electron';
import { BackupLevel } from '@signalapp/libsignal-client/zkgroup';
import type { IPCResponse as ChallengeResponseType } from './challenge';
import type { MessageAttributesType } from './model-types.d';
@ -18,7 +20,7 @@ type ResolveType = (data: unknown) => void;
export type CIType = {
deviceName: string;
backupData?: Uint8Array;
isPlaintextBackup?: boolean;
isBackupIntegration?: boolean;
getConversationId: (address: string | null) => string | null;
getMessagesBySentAt(
sentAt: number
@ -38,18 +40,19 @@ export type CIType = {
exportBackupToDisk(path: string): Promise<void>;
exportPlaintextBackupToDisk(path: string): Promise<void>;
unlink: () => void;
print: (...args: ReadonlyArray<unknown>) => void;
};
export type GetCIOptionsType = Readonly<{
deviceName: string;
backupData?: Uint8Array;
isPlaintextBackup?: boolean;
isBackupIntegration?: boolean;
}>;
export function getCI({
deviceName,
backupData,
isPlaintextBackup,
isBackupIntegration,
}: GetCIOptionsType): CIType {
const eventListeners = new Map<string, Array<ResolveType>>();
const completedEvents = new Map<string, Array<unknown>>();
@ -174,13 +177,13 @@ export function getCI({
}
async function exportBackupToDisk(path: string) {
await backupsService.exportToDisk(path);
await backupsService.exportToDisk(path, BackupLevel.Media);
}
async function exportPlaintextBackupToDisk(path: string) {
await backupsService.exportToDisk(
path,
undefined,
BackupLevel.Media,
BackupType.TestOnlyPlaintext
);
}
@ -189,10 +192,14 @@ export function getCI({
window.Whisper.events.trigger('unlinkAndDisconnect');
}
function print(...args: ReadonlyArray<unknown>) {
handleEvent('print', format(...args));
}
return {
deviceName,
backupData,
isPlaintextBackup,
isBackupIntegration,
getConversationId,
getMessagesBySentAt,
handleEvent,
@ -204,5 +211,6 @@ export function getCI({
exportPlaintextBackupToDisk,
unlink,
getPendingEventCount,
print,
};
}

View file

@ -182,6 +182,10 @@ export class ConversationController {
// we can reset the mute state on the model. If the mute has already expired
// then we reset the state right away.
this._conversations.on('add', (model: ConversationModel): void => {
// Don't modify conversations in backup integration testing
if (window.SignalCI?.isBackupIntegration) {
return;
}
model.startMuteTimer();
});
}

View file

@ -20,7 +20,10 @@ import type { ConversationType } from '../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
import { groupBy } from '../../util/mapUtil';
import type { ContactNameColorType } from '../../types/Colors';
import { SendStatus } from '../../messages/MessageSendState';
import {
SendStatus,
type VisibleSendStatus,
} from '../../messages/MessageSendState';
import { WidthBreakpoint } from '../_util';
import * as log from '../../logging/log';
import { formatDateTimeLong } from '../../util/timestamp';
@ -234,7 +237,7 @@ export function MessageDetail({
}
function renderContactGroupHeaderText(
sendStatus: undefined | SendStatus
sendStatus: undefined | VisibleSendStatus
): string {
if (sendStatus === undefined) {
return i18n('icu:from');
@ -259,7 +262,7 @@ export function MessageDetail({
}
function renderContactGroup(
sendStatus: undefined | SendStatus,
sendStatus: undefined | VisibleSendStatus,
statusContacts: undefined | ReadonlyArray<Contact>
): ReactNode {
if (!statusContacts || !statusContacts.length) {
@ -295,15 +298,17 @@ export function MessageDetail({
return (
<div className="module-message-detail__contact-container">
{[
undefined,
SendStatus.Failed,
SendStatus.Viewed,
SendStatus.Read,
SendStatus.Delivered,
SendStatus.Sent,
SendStatus.Pending,
].map(sendStatus =>
{(
[
undefined,
SendStatus.Failed,
SendStatus.Viewed,
SendStatus.Read,
SendStatus.Delivered,
SendStatus.Sent,
SendStatus.Pending,
] as Array<VisibleSendStatus | undefined>
).map(sendStatus =>
renderContactGroup(sendStatus, contactsBySendStatus.get(sendStatus))
)}
</div>

View file

@ -5669,7 +5669,7 @@ export async function applyNewAvatar(
// Group has avatar; has it changed?
if (
newAvatarUrl &&
(!attributes.avatar || attributes.avatar.url !== newAvatarUrl)
(!attributes.avatar?.path || attributes.avatar.url !== newAvatarUrl)
) {
if (!attributes.secretParams) {
throw new Error('applyNewAvatar: group was missing secretParams!');

View file

@ -215,6 +215,9 @@ export class AttachmentDownloadManager extends JobManager<CoreAttachmentDownload
}
static async start(): Promise<void> {
if (window.SignalCI?.isBackupIntegration) {
return;
}
await AttachmentDownloadManager.instance.start();
}

View file

@ -33,6 +33,7 @@ export enum SendStatus {
Delivered = 'Delivered',
Read = 'Read',
Viewed = 'Viewed',
Skipped = 'Skipped',
}
export const parseMessageSendStatus = makeEnumParser(
@ -45,6 +46,14 @@ export const UNDELIVERED_SEND_STATUSES = [
SendStatus.Failed,
];
export type VisibleSendStatus =
| SendStatus.Failed
| SendStatus.Pending
| SendStatus.Sent
| SendStatus.Delivered
| SendStatus.Read
| SendStatus.Viewed;
const STATUS_NUMBERS: Record<SendStatus, number> = {
[SendStatus.Failed]: 0,
[SendStatus.Pending]: 1,
@ -52,6 +61,7 @@ const STATUS_NUMBERS: Record<SendStatus, number> = {
[SendStatus.Delivered]: 3,
[SendStatus.Read]: 4,
[SendStatus.Viewed]: 5,
[SendStatus.Skipped]: 6,
};
export const maxStatus = (a: SendStatus, b: SendStatus): SendStatus =>
@ -69,6 +79,8 @@ export const isSent = (status: SendStatus): boolean =>
STATUS_NUMBERS[status] >= STATUS_NUMBERS[SendStatus.Sent];
export const isFailed = (status: SendStatus): boolean =>
status === SendStatus.Failed;
export const isSkipped = (status: SendStatus): boolean =>
status === SendStatus.Skipped;
/**
* `SendState` combines `SendStatus` and a timestamp. You can use it to show things to the
@ -88,7 +100,8 @@ export type SendState = Readonly<{
| SendStatus.Sent
| SendStatus.Delivered
| SendStatus.Read
| SendStatus.Viewed;
| SendStatus.Viewed
| SendStatus.Skipped;
updatedAt?: number;
}>;

2
ts/model-types.d.ts vendored
View file

@ -119,7 +119,6 @@ export type MessageReactionType = {
fromId: string;
targetTimestamp: number;
timestamp: number;
receivedAtDate: undefined | number;
isSentByConversationId?: Record<string, boolean>;
};
@ -337,6 +336,7 @@ export type ConversationAttributesType = {
wallpaperPhotoPointerBase64?: string;
wallpaperPreset?: number;
dimWallpaperInDarkMode?: boolean;
autoBubbleColor?: boolean;
discoveredUnregisteredAt?: number;
firstUnregisteredAt?: number;

View file

@ -2257,7 +2257,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
fromId: reaction.fromId,
targetTimestamp: reaction.targetTimestamp,
timestamp: reaction.timestamp,
receivedAtDate: reaction.receivedAtDate,
isSentByConversationId: isFromThisDevice
? zipObject(conversation.getMemberConversationIds(), repeat(false))
: undefined,

View file

@ -49,7 +49,7 @@ import {
} from '../../util/whatTypeOfConversation';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
import { uuidToBytes } from '../../util/uuidToBytes';
import { assertDev, strictAssert } from '../../util/assert';
import { strictAssert } from '../../util/assert';
import { getSafeLongFromTimestamp } from '../../util/timestampLongUtils';
import { DAY, MINUTE, SECOND, DurationInSeconds } from '../../util/durations';
import {
@ -89,7 +89,10 @@ import * as Bytes from '../../Bytes';
import { canBeSynced as canPreferredReactionEmojiBeSynced } from '../../reactions/preferredReactionEmoji';
import { SendStatus } from '../../messages/MessageSendState';
import { BACKUP_VERSION } from './constants';
import { getMessageIdForLogging } from '../../util/idForLogging';
import {
getMessageIdForLogging,
getConversationIdForLogging,
} from '../../util/idForLogging';
import { makeLookup } from '../../util/makeLookup';
import type {
CallHistoryDetails,
@ -197,6 +200,8 @@ export class BackupExportStream extends Readable {
private readonly backupTimeMs = getSafeLongFromTimestamp(this.now);
private readonly convoIdToRecipientId = new Map<string, number>();
private readonly serviceIdToRecipientId = new Map<string, number>();
private readonly e164ToRecipientId = new Map<string, number>();
private readonly roomIdToRecipientId = new Map<string, number>();
private attachmentBackupJobs: Array<CoreAttachmentBackupJobType> = [];
private buffers = new Array<Uint8Array>();
@ -222,12 +227,14 @@ export class BackupExportStream extends Readable {
// TODO (DESKTOP-7344): Clear & add backup jobs in a single transaction
await DataWriter.clearAllAttachmentBackupJobs();
await Promise.all(
this.attachmentBackupJobs.map(job =>
AttachmentBackupManager.addJobAndMaybeThumbnailJob(job)
)
);
drop(AttachmentBackupManager.start());
if (!window.SignalCI?.isBackupIntegration) {
await Promise.all(
this.attachmentBackupJobs.map(job =>
AttachmentBackupManager.addJobAndMaybeThumbnailJob(job)
)
);
drop(AttachmentBackupManager.start());
}
log.info('BackupExportStream: finished');
}
})()
@ -407,7 +414,17 @@ export class BackupExportStream extends Readable {
let pinnedOrder: number | null = null;
if (attributes.isPinned) {
pinnedOrder = Math.max(0, pinnedConversationIds.indexOf(attributes.id));
const index = pinnedConversationIds.indexOf(attributes.id);
if (index === -1) {
const convoId = getConversationIdForLogging(attributes);
log.warn(`backups: ${convoId} is pinned, but is not on the list`);
}
pinnedOrder = Math.max(1, index + 1);
}
// Skip conversations that have no presence in left pane (no chats)
if (!attributes.isPinned && !attributes.active_at) {
continue;
}
this.pushFrame({
@ -438,6 +455,7 @@ export class BackupExportStream extends Readable {
color: attributes.conversationColor,
customColorId: attributes.customColorId,
dimWallpaperInDarkMode: attributes.dimWallpaperInDarkMode,
autoBubbleColor: attributes.autoBubbleColor,
}),
},
});
@ -686,22 +704,29 @@ export class BackupExportStream extends Readable {
};
}
private getRecipientIdentifier({
id,
serviceId,
e164,
}: GetRecipientIdOptionsType): string {
const identifier = serviceId ?? e164 ?? id;
assertDev(identifier, 'Identifier cannot be blank');
return identifier;
private getExistingRecipientId(
options: GetRecipientIdOptionsType
): Long | undefined {
let existing: number | undefined;
if (options.serviceId != null) {
existing = this.serviceIdToRecipientId.get(options.serviceId);
}
if (existing === undefined && options.e164 != null) {
existing = this.e164ToRecipientId.get(options.e164);
}
if (existing === undefined && options.id != null) {
existing = this.convoIdToRecipientId.get(options.id);
}
if (existing !== undefined) {
return Long.fromNumber(existing);
}
return undefined;
}
private getRecipientId(options: GetRecipientIdOptionsType): Long {
const identifier = this.getRecipientIdentifier(options);
const existing = this.convoIdToRecipientId.get(identifier);
const existing = this.getExistingRecipientId(options);
if (existing !== undefined) {
return Long.fromNumber(existing);
return existing;
}
const { id, serviceId, e164 } = options;
@ -713,10 +738,10 @@ export class BackupExportStream extends Readable {
this.convoIdToRecipientId.set(id, recipientId);
}
if (serviceId !== undefined) {
this.convoIdToRecipientId.set(serviceId, recipientId);
this.serviceIdToRecipientId.set(serviceId, recipientId);
}
if (e164 !== undefined) {
this.convoIdToRecipientId.set(e164, recipientId);
this.e164ToRecipientId.set(e164, recipientId);
}
const result = Long.fromNumber(recipientId);
@ -724,8 +749,8 @@ export class BackupExportStream extends Readable {
}
private getOrPushPrivateRecipient(options: GetRecipientIdOptionsType): Long {
const identifier = this.getRecipientIdentifier(options);
const needsPush = !this.convoIdToRecipientId.has(identifier);
const existing = this.getExistingRecipientId(options);
const needsPush = existing == null;
const result = this.getRecipientId(options);
if (needsPush) {
@ -832,9 +857,12 @@ export class BackupExportStream extends Readable {
title: {
title: convo.name ?? '',
},
description: {
descriptionText: convo.description ?? '',
},
description:
convo.description != null
? {
descriptionText: convo.description,
}
: null,
avatarUrl: convo.avatar?.url,
disappearingMessagesTimer:
convo.expireTimer != null
@ -848,9 +876,7 @@ export class BackupExportStream extends Readable {
version: convo.revision || 0,
members: convo.membersV2?.map(member => {
const memberConvo = window.ConversationController.get(member.aci);
strictAssert(memberConvo, 'Missing GV2 member');
const { profileKey } = memberConvo.attributes;
const { profileKey } = memberConvo?.attributes ?? {};
return {
userId: this.aciToBytes(member.aci),
@ -876,9 +902,7 @@ export class BackupExportStream extends Readable {
membersPendingAdminApproval: convo.pendingAdminApprovalV2?.map(
member => {
const memberConvo = window.ConversationController.get(member.aci);
strictAssert(memberConvo, 'Missing GV2 member pending approval');
const { profileKey } = memberConvo.attributes;
const { profileKey } = memberConvo?.attributes ?? {};
return {
userId: this.aciToBytes(member.aci),
profileKey: profileKey
@ -2024,7 +2048,15 @@ export class BackupExportStream extends Readable {
return {
targetSentTimestamp: Long.fromNumber(quote.id),
authorId,
text: quote.text,
text:
quote.text != null
? {
body: quote.text,
bodyRanges: quote.bodyRanges?.map(range =>
this.toBodyRange(range)
),
}
: null,
attachments: await Promise.all(
quote.attachments.map(
async (
@ -2044,7 +2076,6 @@ export class BackupExportStream extends Readable {
}
)
),
bodyRanges: quote.bodyRanges?.map(range => this.toBodyRange(range)),
type: quote.isGiftBadge
? Backups.Quote.Type.GIFTBADGE
: Backups.Quote.Type.NORMAL,
@ -2132,15 +2163,19 @@ export class BackupExportStream extends Readable {
// new keys so that we don't try to re-upload it again on the next export
}
const backupJob = await maybeGetBackupJobForAttachmentAndFilePointer({
attachment: updatedAttachment ?? attachment,
filePointer,
getBackupCdnInfo,
messageReceivedAt,
});
// We don't download attachments during integration tests and thus have no
// "iv" for an attachment and can't create a job
if (!window.SignalCI?.isBackupIntegration) {
const backupJob = await maybeGetBackupJobForAttachmentAndFilePointer({
attachment: updatedAttachment ?? attachment,
filePointer,
getBackupCdnInfo,
messageReceivedAt,
});
if (backupJob) {
this.attachmentBackupJobs.push(backupJob);
if (backupJob) {
this.attachmentBackupJobs.push(backupJob);
}
}
return filePointer;
@ -2162,9 +2197,6 @@ export class BackupExportStream extends Readable {
id: reaction.fromId,
}),
sentTimestamp: getSafeLongFromTimestamp(reaction.timestamp),
receivedTimestamp: getSafeLongFromTimestamp(
reaction.receivedAtDate ?? reaction.timestamp
),
sortOrder: Long.fromNumber(sortOrder),
};
});
@ -2259,6 +2291,9 @@ export class BackupExportStream extends Readable {
sealedSender,
});
break;
case SendStatus.Skipped:
sendStatus.skipped = {};
break;
case SendStatus.Failed: {
sendStatus.failed = new Backups.SendStatus.Failed();
if (!serviceId) {
@ -2273,6 +2308,10 @@ export class BackupExportStream extends Readable {
if (identityKeyMismatch) {
sendStatus.failed.reason =
Backups.SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH;
} else if (errorName === 'UnknownError') {
// See ts/backups/import.ts
sendStatus.failed.reason =
Backups.SendStatus.Failed.FailureReason.UNKNOWN;
} else {
sendStatus.failed.reason =
Backups.SendStatus.Failed.FailureReason.NETWORK;
@ -2430,7 +2469,7 @@ export class BackupExportStream extends Readable {
const result = new Array<Backups.ChatStyle.ICustomChatColor>();
for (const [uuid, color] of map.entries()) {
const id = Long.fromNumber(result.length);
const id = Long.fromNumber(result.length + 1);
this.customColorIdByUuid.set(uuid, id);
const start = hslToRGBInt(
@ -2465,18 +2504,25 @@ export class BackupExportStream extends Readable {
return result;
}
private toDefaultChatStyle(): Backups.IChatStyle {
private toDefaultChatStyle(): Backups.IChatStyle | null {
const defaultColor = window.storage.get('defaultConversationColor');
const wallpaperPhotoPointer = window.storage.get(
'defaultWallpaperPhotoPointer'
);
const wallpaperPreset = window.storage.get('defaultWallpaperPreset');
const dimWallpaperInDarkMode = window.storage.get(
'defaultDimWallpaperInDarkMode',
false
);
const autoBubbleColor = window.storage.get('defaultAutoBubbleColor');
return this.toChatStyle({
wallpaperPhotoPointer: window.storage.get('defaultWallpaperPhotoPointer'),
wallpaperPreset: window.storage.get('defaultWallpaperPreset'),
wallpaperPhotoPointer,
wallpaperPreset,
color: defaultColor?.color,
customColorId: defaultColor?.customColorData?.id,
dimWallpaperInDarkMode: window.storage.get(
'defaultDimWallpaperInDarkMode',
false
),
dimWallpaperInDarkMode,
autoBubbleColor,
});
}
@ -2486,18 +2532,30 @@ export class BackupExportStream extends Readable {
color,
customColorId,
dimWallpaperInDarkMode,
}: LocalChatStyle): Backups.IChatStyle {
autoBubbleColor,
}: LocalChatStyle): Backups.IChatStyle | null {
const result: Backups.IChatStyle = {
dimWallpaperInDarkMode,
};
// The defaults
if (
(color == null || color === 'ultramarine') &&
wallpaperPhotoPointer == null &&
wallpaperPreset == null &&
!dimWallpaperInDarkMode &&
(autoBubbleColor === true || autoBubbleColor == null)
) {
return null;
}
if (Bytes.isNotEmpty(wallpaperPhotoPointer)) {
result.wallpaperPhoto = Backups.FilePointer.decode(wallpaperPhotoPointer);
} else if (wallpaperPreset) {
result.wallpaperPreset = wallpaperPreset;
}
if (color == null) {
if (color == null || autoBubbleColor) {
result.autoBubbleColor = {};
return result;
}

View file

@ -380,8 +380,10 @@ export class BackupImportStream extends Writable {
// Schedule group avatar download.
await pMap(
[...this.pendingGroupAvatars.entries()],
([conversationId, newAvatarUrl]) => {
return groupAvatarJobQueue.add({ conversationId, newAvatarUrl });
async ([conversationId, newAvatarUrl]) => {
if (!window.SignalCI?.isBackupIntegration) {
await groupAvatarJobQueue.add({ conversationId, newAvatarUrl });
}
},
{ concurrency: MAX_CONCURRENCY }
);
@ -403,7 +405,9 @@ export class BackupImportStream extends Writable {
await DataReader.getSizeOfPendingBackupAttachmentDownloadJobs()
);
await AttachmentDownloadManager.start();
if (!window.SignalCI?.isBackupIntegration) {
await AttachmentDownloadManager.start();
}
done();
} catch (error) {
@ -724,6 +728,12 @@ export class BackupImportStream extends Writable {
defaultChatStyle.dimWallpaperInDarkMode
);
}
if (defaultChatStyle.autoBubbleColor != null) {
await window.storage.put(
'defaultAutoBubbleColor',
defaultChatStyle.autoBubbleColor
);
}
this.updateConversation(me);
}
@ -855,6 +865,11 @@ export class BackupImportStream extends Writable {
profileSharing: group.whitelisted === true,
hideStory: group.hideStory === true,
storySendMode,
avatar: avatarUrl
? {
url: avatarUrl,
}
: undefined,
// Snapshot
name: dropNull(title?.title),
@ -1098,8 +1113,12 @@ export class BackupImportStream extends Writable {
this.chatIdToConvo.set(chat.id.toNumber(), conversation);
// Make sure conversation appears in left pane
if (conversation.active_at == null) {
conversation.active_at = Math.max(chat.id.toNumber(), 1);
}
conversation.isArchived = chat.archived === true;
conversation.isPinned = chat.pinnedOrder != null;
conversation.isPinned = (chat.pinnedOrder || 0) !== 0;
conversation.expireTimer =
chat.expirationTimerMs && !chat.expirationTimerMs.isZero()
@ -1134,6 +1153,9 @@ export class BackupImportStream extends Writable {
if (chatStyle.dimWallpaperInDarkMode != null) {
conversation.dimWallpaperInDarkMode = chatStyle.dimWallpaperInDarkMode;
}
if (chatStyle.autoBubbleColor) {
conversation.autoBubbleColor = chatStyle.autoBubbleColor;
}
this.updateConversation(conversation);
@ -1334,10 +1356,6 @@ export class BackupImportStream extends Writable {
'status target conversation not found'
);
// Desktop does not keep track of users we did not attempt to send to
if (status.skipped) {
continue;
}
const { serviceId } = target;
let sendStatus: SendStatus;
@ -1379,7 +1397,6 @@ export class BackupImportStream extends Writable {
});
break;
case Backups.SendStatus.Failed.FailureReason.NETWORK:
case Backups.SendStatus.Failed.FailureReason.UNKNOWN:
errors.push({
serviceId,
name: 'OutgoingMessageError',
@ -1387,9 +1404,19 @@ export class BackupImportStream extends Writable {
message: 'no http error',
});
break;
case Backups.SendStatus.Failed.FailureReason.UNKNOWN:
errors.push({
serviceId,
name: 'UnknownError',
message: 'unknown error',
});
break;
default:
throw missingCaseError(status.failed.reason);
}
// Desktop does not keep track of users we did not attempt to send to
} else if (status.skipped) {
sendStatus = SendStatus.Skipped;
} else {
throw new Error(`Unknown sendStatus received: ${status}`);
}
@ -1416,7 +1443,8 @@ export class BackupImportStream extends Writable {
};
}
if (incoming) {
const receivedAtMs = incoming.dateReceived?.toNumber() ?? this.now;
const receivedAtMs = incoming.dateReceived?.toNumber() || this.now;
const serverTimestamp = incoming.dateServerSent?.toNumber() || undefined;
const unidentifiedDeliveryReceived = incoming.sealedSender === true;
@ -1426,6 +1454,7 @@ export class BackupImportStream extends Writable {
readStatus: ReadStatus.Read,
seenStatus: SeenStatus.Seen,
received_at_ms: receivedAtMs,
serverTimestamp,
unidentifiedDeliveryReceived,
},
newActiveAt: receivedAtMs,
@ -1437,6 +1466,7 @@ export class BackupImportStream extends Writable {
readStatus: ReadStatus.Unread,
seenStatus: SeenStatus.Unseen,
received_at_ms: receivedAtMs,
serverTimestamp,
unidentifiedDeliveryReceived,
},
newActiveAt: receivedAtMs,
@ -1574,10 +1604,10 @@ export class BackupImportStream extends Writable {
{
id: getTimestampFromLong(quote.targetSentTimestamp),
authorAci: authorConvo.serviceId,
text: dropNull(quote.text),
bodyRanges: quote.bodyRanges?.length
text: dropNull(quote.text?.body),
bodyRanges: quote.text?.bodyRanges?.length
? filterAndClean(
quote.bodyRanges.map(range => ({
quote.text?.bodyRanges.map(range => ({
...range,
mentionAci: range.mentionAci
? Aci.parseFromServiceIdBinary(
@ -1623,17 +1653,13 @@ export class BackupImportStream extends Writable {
}
return 0;
})
.map(({ emoji, authorId, sentTimestamp, receivedTimestamp }) => {
.map(({ emoji, authorId, sentTimestamp }) => {
strictAssert(emoji != null, 'reaction must have an emoji');
strictAssert(authorId != null, 'reaction must have authorId');
strictAssert(
sentTimestamp != null,
'reaction must have a sentTimestamp'
);
strictAssert(
receivedTimestamp != null,
'reaction must have a receivedTimestamp'
);
const authorConvo = this.recipientIdToConvo.get(authorId.toNumber());
strictAssert(
@ -1645,7 +1671,6 @@ export class BackupImportStream extends Writable {
emoji,
fromId: authorConvo.id,
targetTimestamp: getTimestampFromLong(sentTimestamp),
receivedAtDate: getTimestampFromLong(receivedTimestamp),
timestamp: getTimestampFromLong(sentTimestamp),
};
});
@ -1681,7 +1706,6 @@ export class BackupImportStream extends Writable {
prefix: dropNull(name.prefix),
suffix: dropNull(name.suffix),
middleName: dropNull(name.middleName),
displayName: dropNull(name.displayName),
}
: undefined,
number: number?.length
@ -2909,9 +2933,10 @@ export class BackupImportStream extends Writable {
return {
wallpaperPhotoPointer: undefined,
wallpaperPreset: undefined,
color: 'ultramarine',
color: undefined,
customColorData: undefined,
dimWallpaperInDarkMode: undefined,
autoBubbleColor: true,
};
}
@ -2929,11 +2954,13 @@ export class BackupImportStream extends Writable {
let color: ConversationColorType | undefined;
let customColorData: CustomColorDataType | undefined;
let autoBubbleColor = false;
if (chatStyle.autoBubbleColor) {
autoBubbleColor = true;
if (wallpaperPreset != null) {
color = WALLPAPER_TO_BUBBLE_COLOR.get(wallpaperPreset) || 'ultramarine';
color = WALLPAPER_TO_BUBBLE_COLOR.get(wallpaperPreset);
} else {
color = 'ultramarine';
color = undefined;
}
} else if (chatStyle.bubbleColorPreset != null) {
const { BubbleColorPreset } = Backups.ChatStyle;
@ -3025,6 +3052,7 @@ export class BackupImportStream extends Writable {
color,
customColorData,
dimWallpaperInDarkMode,
autoBubbleColor,
};
}
}

View file

@ -15,4 +15,5 @@ export type LocalChatStyle = Readonly<{
color: ConversationColorType | undefined;
customColorId: string | undefined;
dimWallpaperInDarkMode: boolean | undefined;
autoBubbleColor: boolean | undefined;
}>;

View file

@ -308,7 +308,7 @@ function startInstaller(): ThunkAction<
finishInstall({
deviceName: SignalCI.deviceName,
backupFile: SignalCI.backupData,
isPlaintextBackup: SignalCI.isPlaintextBackup,
isBackupIntegration: SignalCI.isBackupIntegration,
})
);
}

View file

@ -26,7 +26,6 @@ describe('reaction utilities', () => {
fromId: OUR_CONVO_ID,
targetTimestamp: Date.now(),
timestamp: Date.now(),
receivedAtDate: Date.now(),
...(isPending ? { isSentByConversationId: { [uuid()]: false } } : {}),
});

View file

@ -48,7 +48,7 @@ describe('backup/attachments', () => {
contactA = await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ systemGivenName: 'CONTACT_A' }
{ systemGivenName: 'CONTACT_A', active_at: 1 }
);
await loadAll();

View file

@ -86,32 +86,35 @@ describe('backup/groupv2/notifications', () => {
await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ pni: CONTACT_A_PNI, systemGivenName: 'CONTACT_A' }
{ pni: CONTACT_A_PNI, systemGivenName: 'CONTACT_A', active_at: 1 }
);
await window.ConversationController.getOrCreateAndWait(
CONTACT_B,
'private',
{ systemGivenName: 'CONTACT_B' }
{ systemGivenName: 'CONTACT_B', active_at: 1 }
);
await window.ConversationController.getOrCreateAndWait(
CONTACT_C,
'private',
{ systemGivenName: 'CONTACT_C' }
{ systemGivenName: 'CONTACT_C', active_at: 1 }
);
await window.ConversationController.getOrCreateAndWait(ADMIN_A, 'private', {
systemGivenName: 'ADMIN_A',
active_at: 1,
});
await window.ConversationController.getOrCreateAndWait(
INVITEE_A,
'private',
{
systemGivenName: 'INVITEE_A',
active_at: 1,
}
);
await window.ConversationController.getOrCreateAndWait(GROUP_ID, 'group', {
groupVersion: 2,
masterKey: Bytes.toBase64(getRandomBytes(32)),
name: 'Rock Enthusiasts',
active_at: 1,
});
await loadAll();

View file

@ -51,12 +51,12 @@ describe('backup/bubble messages', () => {
contactA = await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ systemGivenName: 'CONTACT_A' }
{ systemGivenName: 'CONTACT_A', active_at: 1 }
);
contactB = await window.ConversationController.getOrCreateAndWait(
CONTACT_B,
'private',
{ systemGivenName: 'CONTACT_B' }
{ systemGivenName: 'CONTACT_B', active_at: 1 }
);
gv1 = await window.ConversationController.getOrCreateAndWait(
@ -64,6 +64,7 @@ describe('backup/bubble messages', () => {
'group',
{
groupVersion: 1,
active_at: 1,
}
);

View file

@ -51,7 +51,7 @@ describe('backup/calling', () => {
contactA = await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ systemGivenName: 'CONTACT_A' }
{ systemGivenName: 'CONTACT_A', active_at: 1 }
);
groupA = await window.ConversationController.getOrCreateAndWait(
GROUP_ID_STRING,
@ -60,6 +60,7 @@ describe('backup/calling', () => {
groupVersion: 2,
masterKey: Bytes.toBase64(GROUP_MASTER_KEY),
name: 'Rock Enthusiasts',
active_at: 1,
}
);

View file

@ -43,7 +43,7 @@ describe('backup/non-bubble messages', () => {
contactA = await window.ConversationController.getOrCreateAndWait(
CONTACT_A,
'private',
{ systemGivenName: 'CONTACT_A' }
{ systemGivenName: 'CONTACT_A', active_at: 1 }
);
group = await window.ConversationController.getOrCreateAndWait(
@ -53,6 +53,7 @@ describe('backup/non-bubble messages', () => {
groupVersion: 2,
masterKey: Bytes.toBase64(getRandomBytes(32)),
name: 'Rock Enthusiasts',
active_at: 1,
}
);
@ -345,7 +346,6 @@ describe('backup/non-bubble messages', () => {
fromId: contactA.id,
targetTimestamp: 1,
timestamp: 1,
receivedAtDate: 1,
},
],
},
@ -385,7 +385,6 @@ describe('backup/non-bubble messages', () => {
fromId: contactA.id,
targetTimestamp: 1,
timestamp: 1,
receivedAtDate: 1,
},
],
},

View file

@ -0,0 +1,119 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-console */
import { cpus } from 'node:os';
import { inspect } from 'node:util';
import { basename } from 'node:path';
import { reporters } from 'mocha';
import pMap from 'p-map';
import logSymbols from 'log-symbols';
import {
ComparableBackup,
Purpose,
} from '@signalapp/libsignal-client/dist/MessageBackup';
import { FileStream } from '../../services/backups/util/FileStream';
import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
const WORKER_COUNT = process.env.WORKER_COUNT
? parseInt(process.env.WORKER_COUNT, 10)
: Math.min(8, cpus().length);
(reporters.base as unknown as { maxDiffSize: number }).maxDiffSize = Infinity;
const testFiles = process.argv.slice(2);
let total = 0;
let passed = 0;
let failed = 0;
function pass(): void {
process.stdout.write(`${logSymbols.success}`);
total += 1;
passed += 1;
}
function fail(filePath: string, error: string): void {
total += 1;
failed += 1;
console.log(`\n${logSymbols.error} ${basename(filePath)}`);
console.error(error);
}
async function runOne(filePath: string): Promise<void> {
const bootstrap = new Bootstrap({ contactCount: 0 });
let app: App | undefined;
try {
await bootstrap.init();
app = await bootstrap.link({
ciBackupPath: filePath,
ciIsBackupIntegration: true,
});
const backupPath = bootstrap.getBackupPath('backup.bin');
await app.exportPlaintextBackupToDisk(backupPath);
await app.close();
app = undefined;
const actualStream = new FileStream(backupPath);
const expectedStream = new FileStream(filePath);
try {
const actual = await ComparableBackup.fromUnencrypted(
Purpose.RemoteBackup,
actualStream,
BigInt(await actualStream.size())
);
const expected = await ComparableBackup.fromUnencrypted(
Purpose.RemoteBackup,
expectedStream,
BigInt(await expectedStream.size())
);
const actualString = actual.comparableString();
const expectedString = expected.comparableString();
if (actualString === expectedString) {
pass();
} else {
fail(
filePath,
reporters.base.generateDiff(
inspect(actualString, { depth: Infinity, sorted: true }),
inspect(expectedString, { depth: Infinity, sorted: true })
)
);
await bootstrap.saveLogs(app, basename(filePath));
}
} finally {
await actualStream.close();
await expectedStream.close();
}
} catch (error) {
await bootstrap.saveLogs(app, basename(filePath));
fail(filePath, error.stack);
} finally {
try {
await bootstrap.teardown();
} catch (error) {
console.error(`Failed to teardown ${basename(filePath)}`, error);
}
}
}
async function main(): Promise<void> {
await pMap(testFiles, runOne, { concurrency: WORKER_COUNT });
console.log(`${passed}/${total} (${failed} failures)`);
if (failed !== 0) {
process.exit(0);
}
}
main().catch(error => {
console.error(error);
process.exit(1);
});

View file

@ -1,86 +0,0 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { join } from 'node:path';
import createDebug from 'debug';
import fastGlob from 'fast-glob';
import {
ComparableBackup,
Purpose,
} from '@signalapp/libsignal-client/dist/MessageBackup';
import * as durations from '../../util/durations';
import { FileStream } from '../../services/backups/util/FileStream';
import type { App } from '../playwright';
import { Bootstrap } from '../bootstrap';
export const debug = createDebug('mock:test:backups');
const TEST_FOLDER = process.env.BACKUP_TEST_FOLDER;
describe('backups/integration', async function (this: Mocha.Suite) {
this.timeout(100 * durations.MINUTE);
if (!TEST_FOLDER) {
return;
}
let bootstrap: Bootstrap;
let app: App | undefined;
beforeEach(async () => {
bootstrap = new Bootstrap();
await bootstrap.init();
});
afterEach(async function (this: Mocha.Context) {
if (!bootstrap) {
return;
}
await bootstrap.maybeSaveLogs(this.currentTest, app);
await app?.close();
await bootstrap.teardown();
});
const testFiles = fastGlob.sync(join(TEST_FOLDER, '*.binproto'), {
onlyFiles: true,
});
testFiles.forEach(fullPath => {
it(`passes ${fullPath}`, async () => {
app = await bootstrap.link({
ciBackupPath: fullPath,
ciIsPlaintextBackup: true,
});
const backupPath = bootstrap.getBackupPath('backup.bin');
await app.exportPlaintextBackupToDisk(backupPath);
await app.close();
const actualStream = new FileStream(backupPath);
const expectedStream = new FileStream(fullPath);
try {
const actual = await ComparableBackup.fromUnencrypted(
Purpose.RemoteBackup,
actualStream,
BigInt(await actualStream.size())
);
const expected = await ComparableBackup.fromUnencrypted(
Purpose.RemoteBackup,
expectedStream,
BigInt(await expectedStream.size())
);
assert.strictEqual(
actual.comparableString(),
expected.comparableString()
);
} finally {
await actualStream.close();
await expectedStream.close();
}
});
});
});

View file

@ -164,7 +164,7 @@ export class Bootstrap {
private readonly randomId = crypto.randomBytes(8).toString('hex');
constructor(options: BootstrapOptions = {}) {
this.cdn3Path = path.join(os.tmpdir(), 'mock-signal-cdn3-');
this.cdn3Path = path.join(os.tmpdir(), `mock-signal-cdn3-${this.randomId}`);
this.server = new Server({
// Limit number of storage read keys for easier testing
maxStorageReadKeys: MAX_STORAGE_READ_KEYS,

View file

@ -3,7 +3,7 @@
import type { ElectronApplication, Page } from 'playwright';
import { _electron as electron } from 'playwright';
import { EventEmitter } from 'events';
import { EventEmitter, once } from 'events';
import pTimeout from 'p-timeout';
import type {
@ -12,6 +12,7 @@ import type {
} from '../challenge';
import type { ReceiptType } from '../types/Receipt';
import { SECOND } from '../util/durations';
import { drop } from '../util/drop';
export type AppLoadedInfoType = Readonly<{
loadTime: number;
@ -87,6 +88,8 @@ export class App extends EventEmitter {
}
this.privApp.on('close', () => this.emit('close'));
drop(this.printLoop());
}
public async waitForProvisionURL(): Promise<string> {
@ -239,4 +242,36 @@ export class App extends EventEmitter {
return this.privApp;
}
private async printLoop(): Promise<void> {
const kClosed: unique symbol = Symbol('kClosed');
const onClose = (async (): Promise<typeof kClosed> => {
try {
await once(this, 'close');
} catch {
// Ignore
}
return kClosed;
})();
// eslint-disable-next-line no-constant-condition
while (true) {
try {
// eslint-disable-next-line no-await-in-loop
const value = await Promise.race([
this.waitForEvent<string>('print', 0),
onClose,
]);
if (value === kClosed) {
break;
}
// eslint-disable-next-line no-console
console.error(`CI.print: ${value}`);
} catch {
// Ignore errors
}
}
}
}

View file

@ -126,7 +126,7 @@ type CreateAccountSharedOptionsType = Readonly<{
// Test-only
backupFile?: Uint8Array;
isPlaintextBackup?: boolean;
isBackupIntegration?: boolean;
}>;
type CreatePrimaryDeviceOptionsType = Readonly<{
@ -220,7 +220,7 @@ function signedPreKeyToUploadSignedPreKey({
export type ConfirmNumberResultType = Readonly<{
deviceName: string;
backupFile: Uint8Array | undefined;
isPlaintextBackup: boolean;
isBackupIntegration: boolean;
}>;
export default class AccountManager extends EventTarget {
@ -923,7 +923,7 @@ export default class AccountManager extends EventTarget {
readReceipts,
userAgent,
backupFile,
isPlaintextBackup,
isBackupIntegration,
} = options;
const { storage } = window.textsecure;
@ -969,7 +969,7 @@ export default class AccountManager extends EventTarget {
if (backupFile !== undefined) {
log.warn(
'createAccount: Restoring from ' +
`${isPlaintextBackup ? 'plaintext' : 'ciphertext'} backup; ` +
`${isBackupIntegration ? 'plaintext' : 'ciphertext'} backup; ` +
'deleting all previous data'
);
}
@ -1231,7 +1231,9 @@ export default class AccountManager extends EventTarget {
if (backupFile !== undefined) {
await backupsService.importBackup(
() => Readable.from([backupFile]),
isPlaintextBackup ? BackupType.TestOnlyPlaintext : BackupType.Ciphertext
isBackupIntegration
? BackupType.TestOnlyPlaintext
: BackupType.Ciphertext
);
}
}

View file

@ -69,7 +69,7 @@ type StateType = Readonly<
export type PrepareLinkDataOptionsType = Readonly<{
deviceName: string;
backupFile?: Uint8Array;
isPlaintextBackup?: boolean;
isBackupIntegration?: boolean;
}>;
export class Provisioner {
@ -153,7 +153,7 @@ export class Provisioner {
public prepareLinkData({
deviceName,
backupFile,
isPlaintextBackup,
isBackupIntegration,
}: PrepareLinkDataOptionsType): CreateLinkedDeviceOptionsType {
strictAssert(
this.state.step === Step.ReadyToLink,
@ -211,7 +211,7 @@ export class Provisioner {
MAX_DEVICE_NAME_LENGTH
),
backupFile,
isPlaintextBackup,
isBackupIntegration,
userAgent,
ourAci,
ourPni,

View file

@ -43,7 +43,7 @@ export const rendererConfigSchema = z.object({
disableIPv6: z.boolean(),
dnsFallback: DNSFallbackSchema,
ciBackupPath: configOptionalStringSchema,
ciIsPlaintextBackup: z.boolean(),
ciIsBackupIntegration: z.boolean(),
environment: environmentSchema,
isMockTestEnvironment: z.boolean(),
homePath: configRequiredStringSchema,

View file

@ -69,6 +69,7 @@ export type StorageAccessType = {
defaultWallpaperPhotoPointer: Uint8Array;
defaultWallpaperPreset: number;
defaultDimWallpaperInDarkMode: boolean;
defaultAutoBubbleColor: boolean;
customColors: CustomColorsItemType;
device_name: string;

View file

@ -25,6 +25,6 @@ if (config.ciMode) {
backupData: config.ciBackupPath
? fs.readFileSync(config.ciBackupPath)
: undefined,
isPlaintextBackup: config.ciIsPlaintextBackup === true,
isBackupIntegration: config.ciIsBackupIntegration === true,
});
}