Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@web5/api/web5.recover #844

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d99b70d
add DWN service to HdIdentityValue.initialize => DidDht.create
Jul 5, 2024
1cb589b
formatting nit
Jul 8, 2024
e05cf54
pass down dwnEndpoints: Web5.connect() => Web5UserAgent.initialize() …
Jul 9, 2024
79f088e
revert formatting changes
Jul 9, 2024
c31ee7c
fix logic fubar
Jul 10, 2024
dcac4ad
typo
Jul 10, 2024
ef3502c
move serviceEndpoints up
Jul 13, 2024
39557cf
merge in main
Jul 19, 2024
3f49b59
formatting nit
Jul 8, 2024
b1c41db
pass down dwnEndpoints: Web5.connect() => Web5UserAgent.initialize() …
Jul 9, 2024
2f39608
revert formatting changes
Jul 9, 2024
795302c
fix logic fubar
Jul 10, 2024
6627df5
typo
Jul 10, 2024
8ec282c
Web5.recover method
Jul 12, 2024
1426be1
update
Jul 19, 2024
537ea88
align with main; slight edit to Web5.connect
Jul 30, 2024
87e6a12
update to be more readable and simpler
Jul 30, 2024
b4e3db8
align with web5-agent/register-did-service-endpoints
Aug 5, 2024
90273fa
update dwn list
Aug 8, 2024
326bca7
update
Jul 19, 2024
36db3d0
Merge remote-tracking branch 'upstream/main'
Aug 14, 2024
3ad91ec
align with lastest TBD54566975:main
Aug 14, 2024
22ff391
align with main
Aug 14, 2024
ec29a05
readd recover code
Aug 14, 2024
9f52986
align versioning
Aug 14, 2024
524bce1
update
Jul 19, 2024
3cfa816
align with main
Aug 19, 2024
b146d66
delete two serviceEndpointNodes const declarations in favor of prior …
Aug 19, 2024
c5a4234
fix linting errors
Aug 20, 2024
a0f0c1c
align branch with latest web5-agent/register-did-service-endpoints
Aug 20, 2024
df162e4
Merge remote-tracking branch 'upstream/main' into api/web5-recover
Aug 20, 2024
121f615
recovery code: attempting to recover records
Aug 27, 2024
aa21a26
fetch upstream/main updates
Aug 27, 2024
d7252ff
Merge remote-tracking branch 'upstream/main'
Aug 29, 2024
e1ff3c3
Merge remote-tracking branch 'upstream/main'
Sep 4, 2024
582e4ea
align with main
Sep 4, 2024
f49de99
remove push and pull methods from sync-api
Sep 4, 2024
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
2 changes: 1 addition & 1 deletion packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@
"sinon": "18.0.0",
"typescript": "5.1.6"
}
}
}
4 changes: 4 additions & 0 deletions packages/agent/src/sync-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,8 @@
public stopSync(): void {
this._syncEngine.stopSync();
}

public syncOnce(params: {syncDirection: string } = {syncDirection: 'push'}): Promise<void> {
return this._syncEngine.syncOnce(params);
}

Check warning on line 62 in packages/agent/src/sync-api.ts

View check run for this annotation

Codecov / codecov/patch

packages/agent/src/sync-api.ts#L61-L62

Added lines #L61 - L62 were not covered by tests
}
16 changes: 16 additions & 0 deletions packages/agent/src/sync-engine-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,22 @@
}
}

public syncOnce({ syncDirection }: { syncDirection: string }): Promise<void> {
return new Promise((resolve, reject) => {
if (syncDirection === 'push') {
this.push();
this.pull();
resolve();
} else if (syncDirection === 'pull') {
this.pull();
this.push();
resolve();
} else {
throw new Error('SyncEngineLevel: Invalid sync direction.');
}
});
}

Check warning on line 329 in packages/agent/src/sync-engine-level.ts

View check run for this annotation

Codecov / codecov/patch

packages/agent/src/sync-engine-level.ts#L316-L329

Added lines #L316 - L329 were not covered by tests

/**
* 202: message was successfully written to the remote DWN
* 204: an initial write message was written without any data, cannot yet be read until a subsequent message is written with data
Expand Down
1 change: 1 addition & 0 deletions packages/agent/src/types/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface SyncEngine {
sync(direction?: 'push' | 'pull'): Promise<void>;
startSync(params: { interval: string }): Promise<void>;
stopSync(): void;
syncOnce(params: { syncDirection: string }): Promise<void>;
}
23 changes: 22 additions & 1 deletion packages/api/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Convert, universalTypeOf } from '@web5/common';

Check warning on line 1 in packages/api/src/utils.ts

View workflow job for this annotation

GitHub Actions / tbdocs-reporter

extractor: typedoc:missing-docs

utils.validateRecoverParams (CallSignature) does not have any documentation.

/**
* Converts various data types to a `Blob` object, automatically detecting the data type or using
Expand Down Expand Up @@ -62,6 +62,27 @@
return { dataBlob, dataFormat };
}

export function validateRecoverParams(params: any): void {
if (!params || typeof params !== 'object') {
throw new Error('Invalid recovery parameters.');
}
if (!params.password || typeof params.password !== 'string') {
throw new Error('Invalid password.');
}
if (!params.recoveryPhrase || typeof params.recoveryPhrase !== 'string') {
throw new Error('Invalid recoveryPhrase.');
}
if (!params.dwnEndpoints || !Array.isArray(params.dwnEndpoints) || params.dwnEndpoints.length === 0) {
throw new Error('Invalid DWN endpoints.');
}
if (params.dids && (!Array.isArray(params.dids) || params.dids.length === 0)) {
throw new Error('Invalid DIDs.');
}
if (params.sync && typeof params.sync !== 'string') {
throw new Error('Invalid sync interval.');
}
}

Check warning on line 84 in packages/api/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/api/src/utils.ts#L66-L84

Added lines #L66 - L84 were not covered by tests

/**
* The `SendCache` class provides a static caching mechanism to optimize the process of sending
* records to remote DWN targets by minimizing redundant sends.
Expand Down Expand Up @@ -125,4 +146,4 @@
targetCache.delete(firstTarget);
}
}
}
}
161 changes: 160 additions & 1 deletion packages/api/src/web5.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**

Check warning on line 1 in packages/api/src/web5.ts

View workflow job for this annotation

GitHub Actions / tbdocs-reporter

extractor: typedoc:missing-docs

Web5RecoverParams (TypeAlias) does not have any documentation.
* NOTE: Added reference types here to avoid a `pnpm` bug during build.
* https://github.com/TBD54566975/web5-js/pull/507
*/
/// <reference types="@tbd54566975/dwn-sdk-js" />

import type {
import {
BearerIdentity,
DwnDataEncodedRecordsWriteMessage,
DwnMessagesPermissionScope,
Expand All @@ -23,6 +23,8 @@
import { DwnApi } from './dwn-api.js';
import { VcApi } from './vc-api.js';
import { PermissionGrant } from './permission-grant.js';
import { validateRecoverParams } from './utils.js';
import { DwnError } from '@tbd54566975/dwn-sdk-js';

/** Override defaults configured during the technical preview phase. */
export type TechPreviewOptions = {
Expand Down Expand Up @@ -206,6 +208,51 @@
delegateDid?: string;
};

export type Web5RecoverParams = {
/**
* The Web5 app `password` is used to protect data on the device the application is running on.
*
* See {@link Web5ConnectOptions.password} for more information.
*/
password: string;

/**
* The `recoveryPhrase` is a unique, secure key for recovering the identity vault.
*
* See {@link Web5ConnectOptions.recoveryPhrase} for more information.
*/
recoveryPhrase: string;

/**
* The dwnEndpoints are used to register DWN service endpoints to the agent DID allowing for full
* recovery of the agent did and its associated connected dids from the listed DWN(s).
* Also allows for the use of the agentDid as the connectedDid. E.g., server side applications
* that do not need the agentDid/connectedDid(s) pattern.
*
* See {@link TechPreviewOptions.dwnEndpoints} and {@link HdIdentityVaultInitializeParams} for more information.
*/
dwnEndpoints: string[];

/**
* The connected DID(s) to recover from the DWN. Can be 1 or more.
* Leave blank to recover all DIDs from the DWN.
*/
dids?: string[];

/**
*
* Passing a sync interval will engage the sync process after recovery similar to the connect process.
*
* Enable synchronization of DWN records between local and remote DWNs.
* Sync defaults to running every 2 minutes and can be set to any value accepted by `ms()`.
* To disable sync, leave undefined or set to 'off'. Turning sync off will allow the recover function to complete
* and return after recovering the Web5 data.
*
* See {@link Web5ConnectOptions.sync} for more information.
*/
sync?: string;
};

/**
* The main Web5 API interface. It manages the creation of a DID if needed, the connection to the
* local DWN and all the web5 main foundational APIs such as VC, syncing, etc.
Expand Down Expand Up @@ -512,4 +559,116 @@
// we expect that any connected protocols will include MessagesQuery and MessagesRead grants that will allow it to sync
return [...connectedProtocols];
}

/**
*
* Recovers a {@link Web5Agent} from a recovery phrase and connects it to the local DWN.
*
* @param params - Params used to recover the agent and connect to the local DWN.
* @param params.password - The Web5 app `password` used to protect data on the device.
* @param params.recoveryPhrase - The `recoveryPhrase` used to recover the identity vault.
* @param params.dwnEndpoints - The DWN service endpoints to register to the agent DID.
* @param params.dids - Optional. The connected DID(s) to recover from the DWN. Can be 1 or more.
* @param params.sync - Optional. Enable synchronization of DWN records between local and remote DWNs.
* @returns A promise that resolves to a {@link Web5} instance.
*/
static async recover({
password, recoveryPhrase, dwnEndpoints, dids, sync
}: Web5RecoverParams): Promise<Web5ConnectResult> {
// Validate the recovery parameters.
validateRecoverParams({ password, recoveryPhrase, dwnEndpoints, dids, sync });

// Get default tech preview hosted nodes if not provided.
dwnEndpoints ??= ['https://dwn.tbddev.org/beta'];

// Create a new agent.
const agent = await Web5UserAgent.create();
if(!await agent.firstLaunch()) {
throw new Error('recover() failed: agent already initialized');
}

// Initialize the agent with the provided recovery phrase, password and dwnEndpoints.
await agent.initialize({ password, recoveryPhrase, dwnEndpoints });

// Start the agent.
await agent.start({ password });

// Enable sync using the specified interval or default.
await agent.sync.syncOnce({ syncDirection: 'pull' })
.catch((error: any) => {
console.error(`Sync pull failed: ${error}`);
});

// Create a new Web5 instance.
const web5 = new Web5({ agent, connectedDid: agent.agentDid.uri });

// Query the DWN for the agent's connected DID records.
const { records, status, cursor } = await web5.dwn.records.query({
from : agent.agentDid.uri,
message : {
filter: {
protocol: 'https://tbd.website/dwn/permissions',
},
},
});

// Check if the DWN query was successful. Throw an error if it was not.
const { code, detail } = status ?? {};
if((code < 200 || code > 299) || (code < 300 && code > 399)) {
console.error(`DWN query failed: ${code} - ${detail}`);
throw new DwnError(code.toString(), `recover() failed: DWN query for did records unsuccessful: ${detail}`);
}

console.log('status', status);
console.log('cursor', cursor);
console.log('records', records);

// Check if any records were found. Throw an error if none are found.
if(!records || !records?.length) {
throw new Error('recover() failed: no DID records found in the DWN');
}

// Get the agent identities and check if any exist.
const agentIdentities = await agent.identity.list();
if (!agentIdentities.length) {
throw new Error('recover() failed: no identities found in the agent');
}

// Filter through the agent identities to find only the dids provided for recovery.
const matchingIdentities: BearerIdentity[] = agentIdentities.filter(
agentIdentity => dids.find(did => did === agentIdentity.did.uri)
);

// Check if any provided dids match those in the agent identities list.
const matchingIdentitiesLength = matchingIdentities.length;
if (!matchingIdentitiesLength) {
throw new Error('recover() failed: no matching dids found in the agent');
}

// If one matching identity is found, use it as the connected did.
// Else reduce the matching identities down to the identity with the most highest versionId.
const identity: BearerIdentity = matchingIdentitiesLength === 1
? matchingIdentities[0]
: matchingIdentities.reduce(
(prevDid, currDid) => prevDid.did.metadata.versionId > currDid.did.metadata.versionId
? prevDid
: currDid
);

const connectedDid = identity.did.uri;

if (sync && sync !== 'off') {
// First, register the user identity for sync.
await agent.sync.registerIdentity({ did: connectedDid });

// Enable sync using the specified interval or default.
sync ??= '2m';
agent.sync.startSync({ interval: sync })
.catch((error: any) => {
console.error(`Sync failed: ${error}`);
});
}

return { web5, did: connectedDid };
}

Check warning on line 673 in packages/api/src/web5.ts

View check run for this annotation

Codecov / codecov/patch

packages/api/src/web5.ts#L576-L673

Added lines #L576 - L673 were not covered by tests
}
Loading