Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
611556b
feat(seedless-onboarding): add dataType support for secret data items
huggingbot Dec 2, 2025
85275f5
refactor: Removed default dataType parameter from createToprfKeyAndBa…
huggingbot Dec 2, 2025
fc73107
refactor: update documentation for secret data item methods
huggingbot Dec 2, 2025
3afa879
refactor: enhance primary secret data validation in SeedlessOnboardin…
huggingbot Dec 2, 2025
90d642b
fix: Update sorting mechanism to prioritize PrimarySrp dataType over …
huggingbot Dec 2, 2025
ac52a76
feat(seedless-onboarding-controller): add createdAt field and sort by…
huggingbot Dec 3, 2025
11c09f8
feat(seedless-onboarding-controller): add storage metadata to SecretM…
huggingbot Dec 3, 2025
2885314
fix(seedless-onboarding-controller): use timestamp extraction for TIM…
huggingbot Dec 5, 2025
3e2b477
chore: Update version
huggingbot Dec 16, 2025
f0b9638
Merge branch 'main' into feat/data-type
huggingbot Dec 16, 2025
ebcc4e0
chore: Update CHANGELOG and remove deprecated methods in SecretMetadata
huggingbot Dec 16, 2025
b279fa5
fix: improve sorting logic for secret data with mixed createdAt values
huggingbot Dec 18, 2025
074d8af
feat: add runMigrations for legacy dataType migration
huggingbot Jan 5, 2026
eb77007
feat: add setMigrationVersion method for direct migration version set…
huggingbot Jan 5, 2026
0bde969
Merge branch 'main' into feat/data-type
huggingbot Jan 5, 2026
7634383
feat: update SecretMetadata to use SecretDataItemOutput for storageVe…
huggingbot Jan 6, 2026
12d0ed0
fix: update SecretMetadata method signature to require storageMetadat…
huggingbot Jan 6, 2026
3aae970
refactor: remove SecretMetadataVersion from SecretMetadata and relate…
huggingbot Jan 6, 2026
e820ef5
fix: handle null dataType in SeedlessOnboardingController validation
huggingbot Jan 6, 2026
22b695e
refactor: replace type param with dataType in addNewSecretData
huggingbot Jan 6, 2026
c0dcad7
refactor: update SecretMetadata to streamline constructor options and…
huggingbot Jan 6, 2026
6e5a3ce
fix: ensure password synchronization before running migrations in See…
huggingbot Jan 6, 2026
ed01779
fix: handle null dataType in SecretMetadata constructor options
huggingbot Jan 6, 2026
10c0c09
refactor: remove updateSecretDataItem and batchUpdateSecretDataItems …
huggingbot Jan 6, 2026
2f02601
refactor: rename SeedlessOnboardingMigrationVersion.DataType to Seedl…
huggingbot Jan 6, 2026
4019328
refactor: move secret metadata sorting logic to SecretMetadata.compare
huggingbot Jan 6, 2026
2f3255a
Merge branch 'main' into feat/data-type
huggingbot Jan 6, 2026
b78c9af
chore: Update changelog
huggingbot Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/seedless-onboarding-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `dataType` parameter to `addNewSecretData` method for categorizing secret data on insert ([#7284](https://github.com/MetaMask/core/pull/7284))
- Add `updateSecretDataItem` method to update fields for existing items by `itemId` ([#7284](https://github.com/MetaMask/core/pull/7284))
- Add `batchUpdateSecretDataItems` method to batch update fields for multiple items ([#7284](https://github.com/MetaMask/core/pull/7284))
- Add `itemId`, `dataType`, and `createdAt` storage-level properties to `SecretMetadata` ([#7284](https://github.com/MetaMask/core/pull/7284))
- Add `SecretMetadata.compareByTimestamp` static method for comparing metadata by timestamp ([#7284](https://github.com/MetaMask/core/pull/7284))
- Add `SecretMetadata.matchesType` static method for checking if metadata matches a given type ([#7284](https://github.com/MetaMask/core/pull/7284))
- Re-export `EncAccountDataType` from `@metamask/toprf-secure-backup` ([#7284](https://github.com/MetaMask/core/pull/7284))

### Changed

- **BREAKING:** Remove `parseSecretsFromMetadataStore`, `fromBatch`, and `sort` methods from `SecretMetadata` ([#7284](https://github.com/MetaMask/core/pull/7284))
- Use `SecretMetadata.compareByTimestamp` for sorting
- Use `SecretMetadata.matchesType` for filtering

### Fixed

- Fix TIMEUUID sorting by extracting actual timestamps instead of using lexicographic comparison ([#7284](https://github.com/MetaMask/core/pull/7284))

## [7.1.0]

### Added
Expand Down
155 changes: 76 additions & 79 deletions packages/seedless-onboarding-controller/src/SecretMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { EncAccountDataType } from '@metamask/toprf-secure-backup';
import {
base64ToBytes,
bytesToBase64,
Expand Down Expand Up @@ -42,6 +43,15 @@
* const secretMetadata = new SecretMetadata(secret);
* ```
*/
/**
* Storage-level metadata from the metadata store (not encrypted).
*/
type StorageMetadata = {
itemId?: string;
dataType?: EncAccountDataType;
createdAt?: string;
};

export class SecretMetadata<DataType extends SecretDataType = Uint8Array>
implements ISecretMetadata<DataType>
{
Expand All @@ -53,50 +63,35 @@

readonly #version: SecretMetadataVersion;

// Storage-level metadata (not encrypted)
readonly #itemId?: string;

readonly #dataType?: EncAccountDataType;

readonly #createdAt?: string;

/**
* Create a new SecretMetadata instance.
*
* @param data - The secret to add metadata to.
* @param options - The options for the secret metadata.
* @param options.timestamp - The timestamp when the secret was created.
* @param options.type - The type of the secret.
* @param options.version - The version of the secret metadata.
* @param storageMetadata - Storage-level metadata from the metadata store.
*/
constructor(data: DataType, options?: Partial<SecretMetadataOptions>) {
constructor(
data: DataType,
options?: Partial<SecretMetadataOptions>,
storageMetadata?: StorageMetadata,
) {
this.#data = data;
this.#timestamp = options?.timestamp ?? Date.now();
this.#type = options?.type ?? SecretType.Mnemonic;
this.#version = options?.version ?? SecretMetadataVersion.V1;
}

/**
* Create an Array of SecretMetadata instances from an array of secrets.
*
* To respect the order of the secrets, we add the index to the timestamp
* so that the first secret backup will have the oldest timestamp
* and the last secret backup will have the newest timestamp.
*
* @param batchData - The data to add metadata to.
* @param batchData.value - The SeedPhrase/PrivateKey to add metadata to.
* @param batchData.options - The options for the seed phrase metadata.
* @returns The SecretMetadata instances.
*/
static fromBatch<DataType extends SecretDataType = Uint8Array>(
batchData: {
value: DataType;
options?: Partial<SecretMetadataOptions>;
}[],
): SecretMetadata<DataType>[] {
const timestamp = Date.now();
return batchData.map((data, index) => {
// To respect the order of the seed phrases, we add the index to the timestamp
// so that the first seed phrase backup will have the oldest timestamp
// and the last seed phrase backup will have the newest timestamp
const backupCreatedAt = data.options?.timestamp ?? timestamp + index * 5;
return new SecretMetadata(data.value, {
timestamp: backupCreatedAt,
type: data.options?.type,
});
});
this.#itemId = storageMetadata?.itemId;
this.#dataType = storageMetadata?.dataType;
this.#createdAt = storageMetadata?.createdAt;
}

/**
Expand All @@ -122,42 +117,16 @@
}
}

/**
* Parse the SecretMetadata from the metadata store and return the array of SecretMetadata instances.
*
* This method also sorts the secrets by timestamp in ascending order, i.e. the oldest secret will be the first element in the array.
*
* @param secretMetadataArr - The array of SecretMetadata from the metadata store.
* @param filterType - The type of the secret to filter.
* @returns The array of SecretMetadata instances.
*/
static parseSecretsFromMetadataStore<
DataType extends SecretDataType = Uint8Array,
>(
secretMetadataArr: Uint8Array[],
filterType?: SecretType,
): SecretMetadata<DataType>[] {
const parsedSecertMetadata = secretMetadataArr.map((metadata) =>
SecretMetadata.fromRawMetadata<DataType>(metadata),
);

const secrets = SecretMetadata.sort(parsedSecertMetadata);

if (filterType) {
return secrets.filter((secret) => secret.type === filterType);
}

return secrets;
}

/**
* Parse and create the SecretMetadata instance from the raw metadata bytes.
*
* @param rawMetadata - The raw metadata.
* @param storageMetadata - Storage-level metadata from the metadata store.
* @returns The parsed secret metadata.
*/
static fromRawMetadata<DataType extends SecretDataType>(
rawMetadata: Uint8Array,
storageMetadata?: StorageMetadata,
): SecretMetadata<DataType> {
const serializedMetadata = bytesToString(rawMetadata);
const parsedMetadata = JSON.parse(serializedMetadata);
Expand All @@ -175,31 +144,47 @@
data = parsedMetadata.data as DataType;
}

return new SecretMetadata<DataType>(data, {
timestamp: parsedMetadata.timestamp,
type,
version,
});
return new SecretMetadata<DataType>(
data,
{
timestamp: parsedMetadata.timestamp,
type,
version,
},
storageMetadata,
);
}

/**
* Sort the seed phrases by timestamp.
*
* @param data - The secret metadata array to sort.
* @param order - The order to sort the seed phrases. Default is `desc`.
* Compare two SecretMetadata instances by timestamp.
*
* @returns The sorted secret metadata array.
* @param a - The first SecretMetadata instance.
* @param b - The second SecretMetadata instance.
* @param order - The sort order. Default is 'asc'.
* @returns A negative number if a < b, positive if a > b, zero if equal.
*/
static sort<DataType extends SecretDataType = Uint8Array>(
data: SecretMetadata<DataType>[],
static compareByTimestamp<DataType extends SecretDataType = SecretDataType>(
a: SecretMetadata<DataType>,
b: SecretMetadata<DataType>,
order: 'asc' | 'desc' = 'asc',
): SecretMetadata<DataType>[] {
return data.sort((a, b) => {
if (order === 'asc') {
return a.timestamp - b.timestamp;
}
return b.timestamp - a.timestamp;
});
): number {
return order === 'asc'
? a.timestamp - b.timestamp
: b.timestamp - a.timestamp;
}

/**
* Check if a SecretMetadata instance matches the given type.
*
* @param secret - The SecretMetadata instance to check.
* @param type - The type to match against.
* @returns True if the secret matches the type.
*/
static matchesType<DataType extends SecretDataType = SecretDataType>(
secret: SecretMetadata<DataType>,
type: SecretType,
): boolean {
return secret.type === type;
}

get data(): DataType {
Expand All @@ -218,6 +203,18 @@
return this.#version;
}

get itemId() {

Check failure on line 206 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function

Check failure on line 206 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function
return this.#itemId;
}

get dataType() {

Check failure on line 210 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function

Check failure on line 210 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function
return this.#dataType;
}

get createdAt() {

Check failure on line 214 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function

Check failure on line 214 in packages/seedless-onboarding-controller/src/SecretMetadata.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Missing return type on function
return this.#createdAt;
}

/**
* Serialize the secret metadata and convert it to a Uint8Array.
*
Expand Down
Loading
Loading