feat: compress shareLink data when uploading to json server (#4225)
This commit is contained in:
parent
133ba19919
commit
896c476716
@ -234,7 +234,19 @@ const splitBuffers = (concatenatedBuffer: Uint8Array) => {
|
|||||||
|
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
||||||
// first chunk is the version (ignored for now)
|
// first chunk is the version
|
||||||
|
const version = dataView(
|
||||||
|
concatenatedBuffer,
|
||||||
|
NEXT_CHUNK_SIZE_DATAVIEW_BYTES,
|
||||||
|
cursor,
|
||||||
|
);
|
||||||
|
// If version is outside of the supported versions, throw an error.
|
||||||
|
// This usually means the buffer wasn't encoded using this API, so we'd only
|
||||||
|
// waste compute.
|
||||||
|
if (version > CONCAT_BUFFERS_VERSION) {
|
||||||
|
throw new Error(`invalid version ${version}`);
|
||||||
|
}
|
||||||
|
|
||||||
cursor += VERSION_DATAVIEW_BYTES;
|
cursor += VERSION_DATAVIEW_BYTES;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { compressData, decompressData } from "../../data/encode";
|
||||||
import {
|
import {
|
||||||
decryptData,
|
decryptData,
|
||||||
encryptData,
|
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
IV_LENGTH_BYTES,
|
IV_LENGTH_BYTES,
|
||||||
} from "../../data/encryption";
|
} from "../../data/encryption";
|
||||||
@ -109,29 +109,28 @@ export const getCollaborationLink = (data: {
|
|||||||
return `${window.location.origin}${window.location.pathname}#room=${data.roomId},${data.roomKey}`;
|
return `${window.location.origin}${window.location.pathname}#room=${data.roomId},${data.roomKey}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const importFromBackend = async (
|
/**
|
||||||
id: string,
|
* Decodes shareLink data using the legacy buffer format.
|
||||||
privateKey: string,
|
* @deprecated
|
||||||
): Promise<ImportedDataState> => {
|
*/
|
||||||
try {
|
const legacy_decodeFromBackend = async ({
|
||||||
const response = await fetch(`${BACKEND_V2_GET}${id}`);
|
buffer,
|
||||||
|
decryptionKey,
|
||||||
if (!response.ok) {
|
}: {
|
||||||
window.alert(t("alerts.importBackendFailed"));
|
buffer: ArrayBuffer;
|
||||||
return {};
|
decryptionKey: string;
|
||||||
}
|
}) => {
|
||||||
const buffer = await response.arrayBuffer();
|
|
||||||
|
|
||||||
let decrypted: ArrayBuffer;
|
let decrypted: ArrayBuffer;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Buffer should contain both the IV (fixed length) and encrypted data
|
// Buffer should contain both the IV (fixed length) and encrypted data
|
||||||
const iv = buffer.slice(0, IV_LENGTH_BYTES);
|
const iv = buffer.slice(0, IV_LENGTH_BYTES);
|
||||||
const encrypted = buffer.slice(IV_LENGTH_BYTES, buffer.byteLength);
|
const encrypted = buffer.slice(IV_LENGTH_BYTES, buffer.byteLength);
|
||||||
decrypted = await decryptData(new Uint8Array(iv), encrypted, privateKey);
|
decrypted = await decryptData(new Uint8Array(iv), encrypted, decryptionKey);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Fixed IV (old format, backward compatibility)
|
// Fixed IV (old format, backward compatibility)
|
||||||
const fixedIv = new Uint8Array(IV_LENGTH_BYTES);
|
const fixedIv = new Uint8Array(IV_LENGTH_BYTES);
|
||||||
decrypted = await decryptData(fixedIv, buffer, privateKey);
|
decrypted = await decryptData(fixedIv, buffer, decryptionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to convert the decrypted array buffer to a string
|
// We need to convert the decrypted array buffer to a string
|
||||||
@ -144,6 +143,43 @@ const importFromBackend = async (
|
|||||||
elements: data.elements || null,
|
elements: data.elements || null,
|
||||||
appState: data.appState || null,
|
appState: data.appState || null,
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const importFromBackend = async (
|
||||||
|
id: string,
|
||||||
|
decryptionKey: string,
|
||||||
|
): Promise<ImportedDataState> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BACKEND_V2_GET}${id}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
window.alert(t("alerts.importBackendFailed"));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: decodedBuffer } = await decompressData(
|
||||||
|
new Uint8Array(buffer),
|
||||||
|
{
|
||||||
|
decryptionKey,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const data: ImportedDataState = JSON.parse(
|
||||||
|
new TextDecoder().decode(decodedBuffer),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
elements: data.elements || null,
|
||||||
|
appState: data.appState || null,
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn(
|
||||||
|
"error when decoding shareLink data using the new format:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return legacy_decodeFromBackend({ buffer, decryptionKey });
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.alert(t("alerts.importBackendFailed"));
|
window.alert(t("alerts.importBackendFailed"));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -188,20 +224,14 @@ export const exportToBackend = async (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const json = serializeAsJSON(elements, appState, files, "database");
|
const encryptionKey = await generateEncryptionKey("string");
|
||||||
const encoded = new TextEncoder().encode(json);
|
|
||||||
|
|
||||||
const cryptoKey = await generateEncryptionKey("cryptoKey");
|
const payload = await compressData(
|
||||||
|
new TextEncoder().encode(
|
||||||
const { encryptedBuffer, iv } = await encryptData(cryptoKey, encoded);
|
serializeAsJSON(elements, appState, files, "database"),
|
||||||
|
),
|
||||||
// Concatenate IV with encrypted data (IV does not have to be secret).
|
{ encryptionKey },
|
||||||
const payloadBlob = new Blob([iv.buffer, encryptedBuffer]);
|
);
|
||||||
const payload = await new Response(payloadBlob).arrayBuffer();
|
|
||||||
|
|
||||||
// We use jwk encoding to be able to extract just the base64 encoded key.
|
|
||||||
// We will hardcode the rest of the attributes when importing back the key.
|
|
||||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", cryptoKey);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filesMap = new Map<FileId, BinaryFileData>();
|
const filesMap = new Map<FileId, BinaryFileData>();
|
||||||
@ -211,8 +241,6 @@ export const exportToBackend = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptionKey = exportedKey.k!;
|
|
||||||
|
|
||||||
const filesToUpload = await encodeFilesForUpload({
|
const filesToUpload = await encodeFilesForUpload({
|
||||||
files: filesMap,
|
files: filesMap,
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
@ -221,7 +249,7 @@ export const exportToBackend = async (
|
|||||||
|
|
||||||
const response = await fetch(BACKEND_V2_POST, {
|
const response = await fetch(BACKEND_V2_POST, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: payload,
|
body: payload.buffer,
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.id) {
|
if (json.id) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user