Backup Server APIs
This commit is contained in:
parent
77aea40a63
commit
3eb0e30a23
14 changed files with 991 additions and 201 deletions
|
@ -49,6 +49,7 @@ import {
|
|||
untaggedPniSchema,
|
||||
} from '../types/ServiceId';
|
||||
import type { DirectoryConfigType } from '../types/RendererConfig';
|
||||
import type { BackupPresentationHeadersType } from '../types/backups';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { randomInt } from '../Crypto';
|
||||
import * as linkPreviewFetch from '../linkPreviews/linkPreviewFetch';
|
||||
|
@ -536,6 +537,10 @@ const URL_CALLS = {
|
|||
'dynamic/desktop/stories/onboarding/manifest.json',
|
||||
getStickerPackUpload: 'v1/sticker/pack/form',
|
||||
getArtAuth: 'v1/art/auth',
|
||||
getBackupCredentials: 'v1/archives/auth',
|
||||
getBackupCDNCredentials: 'v1/archives/auth/read',
|
||||
getBackupUploadForm: 'v1/archives/upload/form',
|
||||
getBackupMediaUploadForm: 'v1/archives/media/upload/form',
|
||||
groupLog: 'v1/groups/logs',
|
||||
groupJoinedAtVersion: 'v1/groups/joined_at_version',
|
||||
groups: 'v1/groups',
|
||||
|
@ -547,9 +552,15 @@ const URL_CALLS = {
|
|||
multiRecipient: 'v1/messages/multi_recipient',
|
||||
phoneNumberDiscoverability: 'v2/accounts/phone_number_discoverability',
|
||||
profile: 'v1/profile',
|
||||
backup: 'v1/archives',
|
||||
backupMedia: 'v1/archives/media',
|
||||
backupMediaBatch: 'v1/archives/media/batch',
|
||||
backupMediaDelete: 'v1/archives/media/delete',
|
||||
registration: 'v1/registration',
|
||||
registerCapabilities: 'v1/devices/capabilities',
|
||||
reportMessage: 'v1/messages/report',
|
||||
setBackupId: 'v1/archives/backupid',
|
||||
setBackupSignatureKey: 'v1/archives/keys',
|
||||
signed: 'v2/keys/signed',
|
||||
storageManifest: 'v1/storage/manifest',
|
||||
storageModify: 'v1/storage/',
|
||||
|
@ -599,6 +610,18 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
|
|||
|
||||
// Account V2
|
||||
'phoneNumberDiscoverability',
|
||||
|
||||
// Backups
|
||||
'getBackupCredentials',
|
||||
'getBackupCDNCredentials',
|
||||
'getBackupMediaUploadForm',
|
||||
'getBackupUploadForm',
|
||||
'backup',
|
||||
'backupMedia',
|
||||
'backupMediaBatch',
|
||||
'backupMediaDelete',
|
||||
'setBackupId',
|
||||
'setBackupSignatureKey',
|
||||
]);
|
||||
|
||||
type InitializeOptionsType = {
|
||||
|
@ -982,6 +1005,136 @@ export type RequestVerificationResultType = Readonly<{
|
|||
sessionId: string;
|
||||
}>;
|
||||
|
||||
export type SetBackupIdOptionsType = Readonly<{
|
||||
backupAuthCredentialRequest: Uint8Array;
|
||||
}>;
|
||||
|
||||
export type SetBackupSignatureKeyOptionsType = Readonly<{
|
||||
headers: BackupPresentationHeadersType;
|
||||
backupIdPublicKey: Uint8Array;
|
||||
}>;
|
||||
|
||||
export type BackupMediaItemType = Readonly<{
|
||||
sourceAttachment: Readonly<{
|
||||
cdn: number;
|
||||
key: string;
|
||||
}>;
|
||||
objectLength: number;
|
||||
mediaId: string;
|
||||
hmacKey: Uint8Array;
|
||||
encryptionKey: Uint8Array;
|
||||
iv: Uint8Array;
|
||||
}>;
|
||||
|
||||
export type BackupMediaBatchOptionsType = Readonly<{
|
||||
headers: BackupPresentationHeadersType;
|
||||
items: ReadonlyArray<BackupMediaItemType>;
|
||||
}>;
|
||||
|
||||
export const backupMediaBatchResponseSchema = z.object({
|
||||
responses: z
|
||||
.object({
|
||||
status: z.number(),
|
||||
failureReason: z.string().or(z.null()).optional(),
|
||||
cdn: z.number(),
|
||||
mediaId: z.string(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type BackupMediaBatchResponseType = z.infer<
|
||||
typeof backupMediaBatchResponseSchema
|
||||
>;
|
||||
|
||||
export type BackupListMediaOptionsType = Readonly<{
|
||||
headers: BackupPresentationHeadersType;
|
||||
cursor?: string;
|
||||
limit: number;
|
||||
}>;
|
||||
|
||||
export const backupListMediaResponseSchema = z.object({
|
||||
storedMediaObjects: z
|
||||
.object({
|
||||
cdn: z.number(),
|
||||
mediaId: z.string(),
|
||||
objectLength: z.number(),
|
||||
})
|
||||
.array(),
|
||||
backupDir: z.string(),
|
||||
mediaDir: z.string(),
|
||||
cursor: z.string().or(z.null()).optional(),
|
||||
});
|
||||
|
||||
export type BackupListMediaResponseType = z.infer<
|
||||
typeof backupListMediaResponseSchema
|
||||
>;
|
||||
|
||||
export type BackupDeleteMediaItemType = Readonly<{
|
||||
cdn: number;
|
||||
mediaId: string;
|
||||
}>;
|
||||
|
||||
export type BackupDeleteMediaOptionsType = Readonly<{
|
||||
headers: BackupPresentationHeadersType;
|
||||
mediaToDelete: ReadonlyArray<BackupDeleteMediaItemType>;
|
||||
}>;
|
||||
|
||||
export type GetBackupCredentialsOptionsType = Readonly<{
|
||||
startDayInMs: number;
|
||||
endDayInMs: number;
|
||||
}>;
|
||||
|
||||
export const getBackupCredentialsResponseSchema = z.object({
|
||||
credentials: z
|
||||
.object({
|
||||
credential: z.string().transform(x => Bytes.fromBase64(x)),
|
||||
redemptionTime: z
|
||||
.number()
|
||||
.transform(x => durations.DurationInSeconds.fromSeconds(x)),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type GetBackupCredentialsResponseType = z.infer<
|
||||
typeof getBackupCredentialsResponseSchema
|
||||
>;
|
||||
|
||||
export type GetBackupCDNCredentialsOptionsType = Readonly<{
|
||||
headers: BackupPresentationHeadersType;
|
||||
cdn: number;
|
||||
}>;
|
||||
|
||||
export const getBackupCDNCredentialsResponseSchema = z.object({
|
||||
headers: z.record(z.string(), z.string()),
|
||||
});
|
||||
|
||||
export type GetBackupCDNCredentialsResponseType = z.infer<
|
||||
typeof getBackupCDNCredentialsResponseSchema
|
||||
>;
|
||||
|
||||
export const getBackupInfoResponseSchema = z.object({
|
||||
cdn: z.number(),
|
||||
backupDir: z.string(),
|
||||
mediaDir: z.string(),
|
||||
backupName: z.string(),
|
||||
usedSpace: z.number().or(z.null()).optional(),
|
||||
});
|
||||
|
||||
export type GetBackupInfoResponseType = z.infer<
|
||||
typeof getBackupInfoResponseSchema
|
||||
>;
|
||||
|
||||
export const getBackupUploadFormResponseSchema = z.object({
|
||||
cdn: z.number(),
|
||||
key: z.string(),
|
||||
headers: z.record(z.string(), z.string()),
|
||||
signedUploadLocation: z.string(),
|
||||
});
|
||||
|
||||
export type GetBackupUploadFormResponseType = z.infer<
|
||||
typeof getBackupUploadFormResponseSchema
|
||||
>;
|
||||
|
||||
export type WebAPIType = {
|
||||
startRegistration(): unknown;
|
||||
finishRegistration(baton: unknown): void;
|
||||
|
@ -1166,6 +1319,33 @@ export type WebAPIType = {
|
|||
urgent?: boolean;
|
||||
}
|
||||
) => Promise<MultiRecipient200ResponseType>;
|
||||
getBackupInfo: (
|
||||
headers: BackupPresentationHeadersType
|
||||
) => Promise<GetBackupInfoResponseType>;
|
||||
getBackupUploadForm: (
|
||||
headers: BackupPresentationHeadersType
|
||||
) => Promise<GetBackupUploadFormResponseType>;
|
||||
getBackupMediaUploadForm: (
|
||||
headers: BackupPresentationHeadersType
|
||||
) => Promise<GetBackupUploadFormResponseType>;
|
||||
refreshBackup: (headers: BackupPresentationHeadersType) => Promise<void>;
|
||||
getBackupCredentials: (
|
||||
options: GetBackupCredentialsOptionsType
|
||||
) => Promise<GetBackupCredentialsResponseType>;
|
||||
getBackupCDNCredentials: (
|
||||
options: GetBackupCDNCredentialsOptionsType
|
||||
) => Promise<GetBackupCDNCredentialsResponseType>;
|
||||
setBackupId: (options: SetBackupIdOptionsType) => Promise<void>;
|
||||
setBackupSignatureKey: (
|
||||
options: SetBackupSignatureKeyOptionsType
|
||||
) => Promise<void>;
|
||||
backupMediaBatch: (
|
||||
options: BackupMediaBatchOptionsType
|
||||
) => Promise<BackupMediaBatchResponseType>;
|
||||
backupListMedia: (
|
||||
options: BackupListMediaOptionsType
|
||||
) => Promise<BackupListMediaResponseType>;
|
||||
backupDeleteMedia: (options: BackupDeleteMediaOptionsType) => Promise<void>;
|
||||
setPhoneNumberDiscoverability: (newValue: boolean) => Promise<void>;
|
||||
updateDeviceName: (deviceName: string) => Promise<void>;
|
||||
uploadAvatar: (
|
||||
|
@ -1447,6 +1627,9 @@ export function initialize({
|
|||
// Thanks, function hoisting!
|
||||
return {
|
||||
authenticate,
|
||||
backupDeleteMedia,
|
||||
backupListMedia,
|
||||
backupMediaBatch,
|
||||
cancelInflightRequests,
|
||||
cdsLookup,
|
||||
checkAccountExistence,
|
||||
|
@ -1466,6 +1649,11 @@ export function initialize({
|
|||
getAttachment,
|
||||
getAttachmentV2,
|
||||
getAvatar,
|
||||
getBackupCredentials,
|
||||
getBackupCDNCredentials,
|
||||
getBackupInfo,
|
||||
getBackupMediaUploadForm,
|
||||
getBackupUploadForm,
|
||||
getBadgeImageFile,
|
||||
getConfig,
|
||||
getGroup,
|
||||
|
@ -1507,6 +1695,7 @@ export function initialize({
|
|||
putProfile,
|
||||
putStickers,
|
||||
reconnect,
|
||||
refreshBackup,
|
||||
registerCapabilities,
|
||||
registerKeys,
|
||||
registerRequestHandler,
|
||||
|
@ -1520,6 +1709,8 @@ export function initialize({
|
|||
sendMessages,
|
||||
sendMessagesUnauth,
|
||||
sendWithSenderKey,
|
||||
setBackupId,
|
||||
setBackupSignatureKey,
|
||||
setPhoneNumberDiscoverability,
|
||||
startRegistration,
|
||||
unregisterRequestHandler,
|
||||
|
@ -2497,6 +2688,208 @@ export function initialize({
|
|||
});
|
||||
}
|
||||
|
||||
async function getBackupInfo(headers: BackupPresentationHeadersType) {
|
||||
const res = await _ajax({
|
||||
call: 'backup',
|
||||
httpType: 'GET',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return getBackupInfoResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function getBackupMediaUploadForm(
|
||||
headers: BackupPresentationHeadersType
|
||||
) {
|
||||
const res = await _ajax({
|
||||
call: 'getBackupMediaUploadForm',
|
||||
httpType: 'GET',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return getBackupUploadFormResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function getBackupUploadForm(headers: BackupPresentationHeadersType) {
|
||||
const res = await _ajax({
|
||||
call: 'getBackupUploadForm',
|
||||
httpType: 'GET',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return getBackupUploadFormResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function refreshBackup(headers: BackupPresentationHeadersType) {
|
||||
await _ajax({
|
||||
call: 'backup',
|
||||
httpType: 'POST',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
async function getBackupCredentials({
|
||||
startDayInMs,
|
||||
endDayInMs,
|
||||
}: GetBackupCredentialsOptionsType) {
|
||||
const startDayInSeconds = startDayInMs / durations.SECOND;
|
||||
const endDayInSeconds = endDayInMs / durations.SECOND;
|
||||
const res = await _ajax({
|
||||
call: 'getBackupCredentials',
|
||||
httpType: 'GET',
|
||||
urlParameters:
|
||||
`?redemptionStartSeconds=${startDayInSeconds}&` +
|
||||
`redemptionEndSeconds=${endDayInSeconds}`,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return getBackupCredentialsResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function getBackupCDNCredentials({
|
||||
headers,
|
||||
cdn,
|
||||
}: GetBackupCDNCredentialsOptionsType) {
|
||||
const res = await _ajax({
|
||||
call: 'getBackupCDNCredentials',
|
||||
httpType: 'GET',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
urlParameters: `?cdn=${cdn}`,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return getBackupCDNCredentialsResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function setBackupId({
|
||||
backupAuthCredentialRequest,
|
||||
}: SetBackupIdOptionsType) {
|
||||
await _ajax({
|
||||
call: 'setBackupId',
|
||||
httpType: 'PUT',
|
||||
jsonData: {
|
||||
backupAuthCredentialRequest: Bytes.toBase64(
|
||||
backupAuthCredentialRequest
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function setBackupSignatureKey({
|
||||
headers,
|
||||
backupIdPublicKey,
|
||||
}: SetBackupSignatureKeyOptionsType) {
|
||||
await _ajax({
|
||||
call: 'setBackupSignatureKey',
|
||||
httpType: 'PUT',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
jsonData: {
|
||||
backupIdPublicKey: Bytes.toBase64(backupIdPublicKey),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function backupMediaBatch({
|
||||
headers,
|
||||
items,
|
||||
}: BackupMediaBatchOptionsType) {
|
||||
const res = await _ajax({
|
||||
call: 'backupMediaBatch',
|
||||
httpType: 'PUT',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
responseType: 'json',
|
||||
jsonData: {
|
||||
items: items.map(item => {
|
||||
const {
|
||||
sourceAttachment,
|
||||
objectLength,
|
||||
mediaId,
|
||||
hmacKey,
|
||||
encryptionKey,
|
||||
iv,
|
||||
} = item;
|
||||
|
||||
return {
|
||||
sourceAttachment: {
|
||||
cdn: sourceAttachment.cdn,
|
||||
key: sourceAttachment.key,
|
||||
},
|
||||
objectLength,
|
||||
mediaId,
|
||||
hmacKey: Bytes.toBase64(hmacKey),
|
||||
encryptionKey: Bytes.toBase64(encryptionKey),
|
||||
iv: Bytes.toBase64(iv),
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
return backupMediaBatchResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function backupDeleteMedia({
|
||||
headers,
|
||||
mediaToDelete,
|
||||
}: BackupDeleteMediaOptionsType) {
|
||||
await _ajax({
|
||||
call: 'backupMediaDelete',
|
||||
httpType: 'POST',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
jsonData: {
|
||||
mediaToDelete: mediaToDelete.map(({ cdn, mediaId }) => {
|
||||
return {
|
||||
cdn,
|
||||
mediaId,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function backupListMedia({
|
||||
headers,
|
||||
cursor,
|
||||
limit,
|
||||
}: BackupListMediaOptionsType) {
|
||||
const params = new Array<string>();
|
||||
|
||||
if (cursor != null) {
|
||||
params.push(`cursor=${encodeURIComponent(cursor)}`);
|
||||
}
|
||||
params.push(`limit=${limit}`);
|
||||
|
||||
const res = await _ajax({
|
||||
call: 'backupMedia',
|
||||
httpType: 'GET',
|
||||
unauthenticated: true,
|
||||
accessKey: undefined,
|
||||
headers,
|
||||
responseType: 'json',
|
||||
urlParameters: `?${params.join('&')}`,
|
||||
});
|
||||
|
||||
return backupListMediaResponseSchema.parse(res);
|
||||
}
|
||||
|
||||
async function setPhoneNumberDiscoverability(newValue: boolean) {
|
||||
await _ajax({
|
||||
call: 'phoneNumberDiscoverability',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue