Download backup on link

This commit is contained in:
Fedor Indutny 2024-08-08 12:22:48 -07:00 committed by GitHub
parent ec36ae7f26
commit 5c350a0e3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 67 additions and 2 deletions

View file

@ -1482,6 +1482,12 @@ export async function startApp(): Promise<void> {
) )
); );
// Now that we authenticated - time to download the backup!
if (isBackupEnabled()) {
backupsService.start();
drop(backupsService.download());
}
// Cancel throttled calls to refreshRemoteConfig since our auth changed. // Cancel throttled calls to refreshRemoteConfig since our auth changed.
window.Signal.RemoteConfig.maybeRefreshRemoteConfig.cancel(); window.Signal.RemoteConfig.maybeRefreshRemoteConfig.cancel();
drop(window.Signal.RemoteConfig.maybeRefreshRemoteConfig(server)); drop(window.Signal.RemoteConfig.maybeRefreshRemoteConfig(server));

View file

@ -1,6 +1,7 @@
// Copyright 2024 Signal Messenger, LLC // Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { type Readable } from 'node:stream';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import type { import type {
WebAPIType, WebAPIType,
@ -66,6 +67,18 @@ export class BackupAPI {
}); });
} }
public async download(): Promise<Readable> {
const { cdn, backupDir, backupName } = await this.getInfo();
const { headers } = await this.credentials.getCDNReadCredentials(cdn);
return this.server.getBackupStream({
cdn,
backupDir,
backupName,
headers,
});
}
public async getMediaUploadForm(): Promise<AttachmentUploadFormResponseType> { public async getMediaUploadForm(): Promise<AttachmentUploadFormResponseType> {
return this.server.getBackupMediaUploadForm( return this.server.getBackupMediaUploadForm(
await this.credentials.getHeadersForToday() await this.credentials.getHeadersForToday()

View file

@ -129,6 +129,21 @@ export class BackupsService {
return backupsService.importBackup(() => createReadStream(backupFile)); return backupsService.importBackup(() => createReadStream(backupFile));
} }
public async download(): Promise<void> {
const path = window.Signal.Migrations.getAbsoluteTempPath(
randomBytes(32).toString('hex')
);
const stream = await this.api.download();
await pipeline(stream, createWriteStream(path));
try {
await this.importFromDisk(path);
} finally {
await unlink(path);
}
}
public async importBackup(createBackupStream: () => Readable): Promise<void> { public async importBackup(createBackupStream: () => Readable): Promise<void> {
strictAssert(!this.isRunning, 'BackupService is already running'); strictAssert(!this.isRunning, 'BackupService is already running');
@ -281,7 +296,7 @@ export class BackupsService {
await this.api.refresh(); await this.api.refresh();
log.info('Backup: refreshed'); log.info('Backup: refreshed');
} catch (error) { } catch (error) {
log.error('Backup: periodic refresh kufailed', Errors.toLogFormat(error)); log.error('Backup: periodic refresh failed', Errors.toLogFormat(error));
} }
} }
} }

View file

@ -1147,8 +1147,15 @@ export type GetBackupCDNCredentialsResponseType = z.infer<
typeof getBackupCDNCredentialsResponseSchema typeof getBackupCDNCredentialsResponseSchema
>; >;
export type GetBackupStreamOptionsType = Readonly<{
cdn: number;
backupDir: string;
backupName: string;
headers: Record<string, string>;
}>;
export const getBackupInfoResponseSchema = z.object({ export const getBackupInfoResponseSchema = z.object({
cdn: z.number(), cdn: z.literal(3),
backupDir: z.string(), backupDir: z.string(),
mediaDir: z.string(), mediaDir: z.string(),
backupName: z.string(), backupName: z.string(),
@ -1380,6 +1387,7 @@ export type WebAPIType = {
getBackupInfo: ( getBackupInfo: (
headers: BackupPresentationHeadersType headers: BackupPresentationHeadersType
) => Promise<GetBackupInfoResponseType>; ) => Promise<GetBackupInfoResponseType>;
getBackupStream: (options: GetBackupStreamOptionsType) => Promise<Readable>;
getBackupUploadForm: ( getBackupUploadForm: (
headers: BackupPresentationHeadersType headers: BackupPresentationHeadersType
) => Promise<AttachmentUploadFormResponseType>; ) => Promise<AttachmentUploadFormResponseType>;
@ -1707,6 +1715,7 @@ export function initialize({
getBackupCredentials, getBackupCredentials,
getBackupCDNCredentials, getBackupCDNCredentials,
getBackupInfo, getBackupInfo,
getBackupStream,
getBackupMediaUploadForm, getBackupMediaUploadForm,
getBackupUploadForm, getBackupUploadForm,
getBadgeImageFile, getBadgeImageFile,
@ -2764,6 +2773,20 @@ export function initialize({
return getBackupInfoResponseSchema.parse(res); return getBackupInfoResponseSchema.parse(res);
} }
async function getBackupStream({
headers,
cdn,
backupDir,
backupName,
}: GetBackupStreamOptionsType): Promise<Readable> {
return _getAttachment({
cdnPath: `/backups/${encodeURIComponent(backupDir)}/${encodeURIComponent(backupName)}`,
cdnNumber: cdn,
redactor: _createRedactor(backupDir, backupName),
headers,
});
}
async function getBackupMediaUploadForm( async function getBackupMediaUploadForm(
headers: BackupPresentationHeadersType headers: BackupPresentationHeadersType
) { ) {

View file

@ -2,7 +2,15 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as RemoteConfig from '../RemoteConfig'; import * as RemoteConfig from '../RemoteConfig';
import { Environment, getEnvironment } from '../environment';
import { isStaging } from './version';
export function isBackupEnabled(): boolean { export function isBackupEnabled(): boolean {
if (getEnvironment() === Environment.Staging) {
return true;
}
if (isStaging(window.getVersion())) {
return true;
}
return Boolean(RemoteConfig.isEnabled('desktop.backup.credentialFetch')); return Boolean(RemoteConfig.isEnabled('desktop.backup.credentialFetch'));
} }