Revert the envelope terminology

This commit is contained in:
Manav Rathi
2025-06-12 14:34:34 +05:30
parent 8585f3881c
commit 23609c4bb9
3 changed files with 65 additions and 67 deletions

View File

@@ -463,12 +463,12 @@ export type CollectionSubType =
/**
* Mutable private metadata associated with an {@link Collection}.
*
* - Unlike {@link CollectionPublicMagicMetadata} this is only available to the
* owner of the file.
* - Unlike {@link CollectionPublicMagicMetadataData} this is only available to
* the owner of the file.
*
* See: [Note: Private magic metadata is called magic metadata on remote]
*/
export interface CollectionPrivateMagicMetadata {
export interface CollectionPrivateMagicMetadataData {
/**
* The (owner specific) visibility of the collection.
*

View File

@@ -3,8 +3,7 @@ import { z } from "zod/v4";
/**
* Zod schema of the mutable metadatum objects that we send to or receive from
* remote. Even though it is named so, it is not the metadata itself, but an
* envelope containing the encrypted metadata contents with some bookkeeping
* remote. It contains an encrypted metadata JSON object with some bookkeeping
* information attached.
*
* See {@link RemoteMagicMetadata} for the corresponding type.
@@ -32,12 +31,12 @@ export const RemoteMagicMetadataSchema = z.object({
*
* See: [Note: Metadatum]
*
* This is the magic metadata envelope that we send to and receive from remote.
* It contains the encrypted contents, and a version + count of fields that help
* ensure that clients do not overwrite updates with stale objects.
* This is the magic metadata that we send to and receive from remote. It
* contains an encrypted JSON object, and a version + count of entries to ensure
* that clients do not overwrite already applied updates with stale objects.
*
* When decrypt these into specific local {@link MagicMetadataEnvelope}
* instantiations before using and storing them on the client.
* When decrypt these into specific local {@link MagicMetadata} instantiations
* before using and storing them on the client.
*
* See {@link RemoteMagicMetadataSchema} for the corresponding Zod schema. The
* same structure is used to store the metadata for various kinds of objects
@@ -89,11 +88,13 @@ export interface RemoteMagicMetadata {
* by the client.
*
* The bookkeeping fields ({@link version} and {@link count}) are copied over
* from the remote envelope ({@link RemoteMagicMetadata}), while the encrypted
* contents ({@link data} and {@link header}) are decrypted into a JSON object.
* from the remote object ({@link RemoteMagicMetadata}), while the encrypted
* contents ({@link data} and {@link header} of {@link RemoteMagicMetadata}) are
* decrypted into a JSON object that is stored as {@link data}.
*
* Since different types of magic metadata fields have different JSON contents,
* so this decrypted JSON object is parameterized as `T` in this generic type.
* Since different types of magic metadata fields are expected to contain
* different JSON objects as {@link data}, the type is generic, with the generic
* parameter `T` standing for the type of the JSON object.
*
* > Since TypeScript does not currently have a native JSON type, this T doesn't
* > have other constraints enforced by the type system, but it is meant to be a
@@ -106,71 +107,64 @@ export interface RemoteMagicMetadata {
* sense - to describe the shape of any of the magic metadata like fields used
* in various types. See: [Note: Metadatum].
*/
export interface MagicMetadataEnvelope<T = unknown> {
export interface MagicMetadata<T = unknown> {
version: number;
count: number;
/**
* The "metadata" itself.
*
* This is expected to be a JSON object, whose exact schema depends on the
* field for which this envelope is being used.
* magic metadata field in the parent object.
*/
data: T;
}
/**
* Encrypt the provided magic metadata envelope into a form that can be used in
* communication with remote, updating the count if needed.
* Encrypt the provided magic metadata into a form that can be used in
* communication with remote, trimming the JSON object and updating the count.
*
* @param envelope The decrypted {@link MagicMetadataEnvelope} that
* is being used by the client.
* @param magicMetadata The decrypted {@link MagicMetadata} that is being used
* by the client.
*
* As a usability convenience, this function allows passing `undefined` as the
* {@link envelope}. In such cases, it will return `undefined` too.
* Any entries in {@link magicMetadata}'s {@link data} that are `null` or
* `undefined` are discarded before obtaining the final object that will be
* encrypted (and whose count will be used).
*
* It is okay for the count in the envelope to be out of sync with the count in
* the actual JSON object contents, this function will update the envelope count
* with the number of entries in the JSON object.
*
* Any entries in {@link envelope} that are `null` or `undefined`
* are discarded before obtaining the final object that will be encrypted (and
* whose count will be used).
* So in particular, it is okay for the count in the {@link magicMetadata} to be
* out of sync with the count in the `data` JSON object because this function
* will recompute the count using the trimmed JSON object.
*
* @param key The base64 encoded key to use for encrypting the {@link data}
* contents of {@link envelope}.
* contents of {@link magicMetadata}.
*
* The specific key used depends on the object whose metadata this is. For
* example, if this were the {@link pubMagicMetadata} associated with an
* {@link EnteFile}, then this would be the file's key.
* The specific key used depends on the object whose magic metadata field this
* is meant for. For example, if this were the {@link pubMagicMetadata}
* associated with an {@link EnteFile}, then this would be the file's key.
*
* @returns a {@link RemoteMagicMetadata} object that contains the encrypted
* contents of the {@link data} present in the provided
* {@link envelope}. The {@link version} is copied over from the
* provided {@link envelope}, while the {@link count} is obtained
* from the number of entries in the trimmed JSON object obtained from the
* {@link data} property of {@link envelope}.
*
* contents of the {@link data} present in the provided {@link magicMetadata}.
* The {@link version} is copied over from the provided {@link magicMetadata},
* while the {@link count} is obtained from the number of entries in the trimmed
* JSON object that was encrypted.
*/
export const encryptMagicMetadata = async (
envelope: MagicMetadataEnvelope | undefined,
magicMetadata: MagicMetadata,
key: string,
): Promise<RemoteMagicMetadata | undefined> => {
if (!envelope) return undefined;
const { version } = magicMetadata;
const { version } = envelope;
const newEnvelope = createMagicMetadataEnvelope(envelope.data);
const { count } = newEnvelope;
const newMM = createMagicMetadata(magicMetadata.data);
const { count } = newMM;
const { encryptedData: data, decryptionHeader: header } =
await encryptMetadataJSON(newEnvelope.data, key);
await encryptMetadataJSON(newMM.data, key);
return { version, count, data, header };
};
/**
* A function to wrap an arbitrary JSON object in an envelope expected of
* various magic metadata fields.
* A function to wrap an arbitrary JSON object in the {@link MagicMetadata}
* envelope used for the various magic metadata fields.
*
* A trimmed JSON object is obtained from the provided JSON object by removing
* any entries whose values is `undefined` or `null`.
@@ -182,14 +176,14 @@ export const encryptMagicMetadata = async (
* - The `data` is set to the trimmed JSON object.
*
* {@link encryptMagicMetadata} internally uses this function to obtain an
* up-to-date envelope before computing the count and encrypting the contents.
* This function is also exported for use in places where we don't have an
* existing envelope, and would like to create one from scratch.
* trimmed `data` and its corresponding `count`. This function is also exported
* for use in places where we just have a JSON object and would like to a new
* {@link MagicMetadata} object to house it from scratch.
*
* @param data The "metadata" JSON object. Since TypeScript does not have a JSON
* type, this uses the `unknown` type.
* @param data A JSON object. Since TypeScript does not have a JSON type, this
* uses the `unknown` type.
*/
export const createMagicMetadataEnvelope = (data: unknown) => {
export const createMagicMetadata = (data: unknown) => {
// Discard any entries that are `undefined` or `null`.
const jsonObject = Object.fromEntries(
Object.entries(data ?? {}).filter(
@@ -203,16 +197,19 @@ export const createMagicMetadataEnvelope = (data: unknown) => {
};
/**
* Decrypt the magic metadata (envelope) received from remote into an object
* suitable for use and persistence by the client.
* Decrypt the magic metadata received from remote into an object suitable for
* use and persistence by the client.
*
* This is meant as the inverse of {@link encryptMagicMetadata}, see that
* function's documentation for more information.
* function's documentation for details.
*
* As a usability convenience, this function allows passing `undefined` as the
* {@link remoteMagicMetadata}. In such cases, it will return `undefined` too.
*/
export const decryptMagicMetadata = async (
remoteMagicMetadata: RemoteMagicMetadata | undefined,
key: string,
): Promise<MagicMetadataEnvelope | undefined> => {
): Promise<MagicMetadata | undefined> => {
if (!remoteMagicMetadata) return undefined;
const {

View File

@@ -17,7 +17,7 @@ import {
import { type EnteFile } from "ente-media/file";
import { ItemVisibility } from "ente-media/file-metadata";
import {
createMagicMetadataEnvelope,
createMagicMetadata,
encryptMagicMetadata,
} from "ente-media/magic-metadata";
import { batch } from "ente-utils/array";
@@ -102,13 +102,13 @@ export const getCollectionUserFacingName = (collection: Collection) => {
*
* @param type The type of the new collection.
*
* @param magicMetadata Optional metadata to use as the collection's private
* @param magicMetadataData Optional metadata to use as the collection's private
* mutable metadata when creating the new collection.
*/
export const createCollection2 = async (
name: string,
type: CollectionType,
magicMetadata?: CollectionPrivateMagicMetadata,
magicMetadataData?: CollectionPrivateMagicMetadata,
): Promise<Collection2> => {
const masterKey = await ensureMasterKeyFromSession();
const collectionKey = await generateKey();
@@ -116,10 +116,12 @@ export const createCollection2 = async (
await encryptBox(collectionKey, masterKey);
const { encryptedData: encryptedName, nonce: nameDecryptionNonce } =
await encryptBox(new TextEncoder().encode(name), collectionKey);
const remoteMagicMetadata = await encryptMagicMetadata(
createMagicMetadataEnvelope(magicMetadata),
collectionKey,
);
const magicMetadata = magicMetadataData
? await encryptMagicMetadata(
createMagicMetadata(magicMetadataData),
collectionKey,
)
: undefined;
const remoteCollection = await postCollections({
encryptedKey,
@@ -127,9 +129,8 @@ export const createCollection2 = async (
encryptedName,
nameDecryptionNonce,
type,
...(remoteMagicMetadata ? { magicMetadata: remoteMagicMetadata } : {}),
...(magicMetadata ? { magicMetadata } : {}),
});
return decryptRemoteCollectionUsingKey(remoteCollection, masterKey);
};