UNPKG

@enbox/api

Version:

SDK for accessing the features and capabilities of Web5

281 lines 17 kB
/** * NOTE: Added reference types here to avoid a `pnpm` bug during build. * https://github.com/TBD54566975/web5-js/pull/507 */ /// <reference types="@enbox/dwn-sdk-js" /> var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { Web5UserAgent } from '@enbox/user-agent'; import { DwnRegistrar, WalletConnect } from '@enbox/agent'; import { DidApi } from './did-api.js'; import { DwnApi } from './dwn-api.js'; import { VcApi } from './vc-api.js'; import { PermissionGrant } from './permission-grant.js'; /** * 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. */ export class Web5 { constructor({ agent, connectedDid, delegateDid }) { this.agent = agent; this.did = new DidApi({ agent, connectedDid }); this.dwn = new DwnApi({ agent, connectedDid, delegateDid }); this.vc = new VcApi({ agent, connectedDid }); } /** * Connects to a {@link Web5Agent}. Defaults to creating a local {@link Web5UserAgent} if one * isn't provided. * * If `walletConnectOptions` are provided, a WalletConnect flow will be initiated to import a delegated DID from an external wallet. * If there is a failure at any point during connecting and processing grants, all created DIDs and Identities as well as the provided grants * will be cleaned up and an error thrown. This allows for subsequent Connect attempts to be made without any errors. * * @param options - Optional overrides that can be provided when calling {@link Web5.connect}. * @returns A promise that resolves to a {@link Web5} instance and the connected DID. */ static connect({ agent, agentVault, connectedDid, password, recoveryPhrase, sync, techPreview, didCreateOptions, registration, walletConnectOptions, } = {}) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { let delegateDid; if (agent === undefined) { let registerSync = false; // A custom Web5Agent implementation was not specified, so use default managed user agent. const userAgent = yield Web5UserAgent.create({ agentVault }); agent = userAgent; // Warn the developer and application user of the security risks of using a static password. if (password === undefined) { password = 'insecure-static-phrase'; console.warn('%cSECURITY WARNING:%c ' + 'You have not set a password, which defaults to a static, guessable value. ' + 'This significantly compromises the security of your data. ' + 'Please configure a secure, unique password.', 'font-weight: bold; color: red;', 'font-weight: normal; color: inherit;'); } // Use the specified DWN endpoints or the latest TBD hosted DWN const serviceEndpointNodes = (_b = (_a = techPreview === null || techPreview === void 0 ? void 0 : techPreview.dwnEndpoints) !== null && _a !== void 0 ? _a : didCreateOptions === null || didCreateOptions === void 0 ? void 0 : didCreateOptions.dwnEndpoints) !== null && _b !== void 0 ? _b : ['https://enbox-production.up.railway.app']; // Initialize, if necessary, and start the agent. if (yield userAgent.firstLaunch()) { recoveryPhrase = yield userAgent.initialize({ password, recoveryPhrase, dwnEndpoints: serviceEndpointNodes }); } yield userAgent.start({ password }); // Attempt to retrieve the connected Identity if it exists. const connectedIdentity = yield userAgent.identity.connectedIdentity(); let identity; let connectedProtocols = []; if (connectedIdentity) { // if a connected identity is found, use it // TODO: In the future, implement a way to re-connect an already connected identity and apply additional grants/protocols identity = connectedIdentity; } else if (walletConnectOptions) { if (sync === 'off') { // Currently we require sync to be enabled when using WalletConnect // This is to ensure a connected app is not in a disjointed state from any other clients/app using the connectedDid throw new Error('Sync must not be disabled when using WalletConnect'); } // Since we are connecting a new identity, we will want to register sync for the connectedDid registerSync = true; // No connected identity found and connectOptions are provided, attempt to import a delegated DID from an external wallet try { const { permissionRequests } = walletConnectOptions, connectOptions = __rest(walletConnectOptions, ["permissionRequests"]); const walletPermissionRequests = permissionRequests.map(({ protocolDefinition, permissions }) => WalletConnect.createPermissionRequestForProtocol({ definition: protocolDefinition, permissions: permissions !== null && permissions !== void 0 ? permissions : [ 'read', 'write', 'delete', 'query', 'subscribe' ] })); const { delegatePortableDid, connectedDid, delegateGrants } = yield WalletConnect.initClient(Object.assign(Object.assign({}, connectOptions), { permissionRequests: walletPermissionRequests })); // Import the delegated DID as an Identity in the User Agent. // Setting the connectedDID in the metadata applies a relationship between the signer identity and the one it is impersonating. identity = yield userAgent.identity.import({ portableIdentity: { portableDid: delegatePortableDid, metadata: { connectedDid, name: 'Default', uri: delegatePortableDid.uri, tenant: agent.agentDid.uri, } } }); // Attempts to process the connected grants to be used by the delegateDID // If the process fails, we want to clean up the identity // the connected grants will return a de-duped array of protocol URIs that are used to register sync for those protocols connectedProtocols = yield this.processConnectedGrants({ agent, delegateDid: delegatePortableDid.uri, grants: delegateGrants }); } catch (error) { // clean up the DID and Identity if import fails and throw // TODO: Implement the ability to purge all of our messages as a tenant yield this.cleanUpIdentity({ identity, userAgent }); throw new Error(`Failed to connect to wallet: ${error.message}`); } } else { // No connected identity found and no connectOptions provided, use local Identities // Query the Agent's DWN tenant for identity records. const identities = yield userAgent.identity.list(); // If an existing identity is not found found, create a new one. const existingIdentityCount = identities.length; if (existingIdentityCount === 0) { // since we are creating a new identity, we will want to register sync for the created Did registerSync = true; // Generate a new Identity for the end-user. identity = yield userAgent.identity.create({ didMethod: 'dht', metadata: { name: 'Default' }, didOptions: { services: [ { id: 'dwn', type: 'DecentralizedWebNode', serviceEndpoint: serviceEndpointNodes, enc: '#enc', sig: '#sig', } ], verificationMethods: [ { algorithm: 'Ed25519', id: 'sig', purposes: ['assertionMethod', 'authentication'] }, { algorithm: 'secp256k1', id: 'enc', purposes: ['keyAgreement'] } ] } }); } else { // If multiple identities are found, use the first one. // TODO: Implement selecting a connectedDid from multiple identities identity = identities[0]; } } // If the stored identity has a connected DID, use it as the connected DID, otherwise use the identity's DID. connectedDid = (_c = identity.metadata.connectedDid) !== null && _c !== void 0 ? _c : identity.did.uri; // If the stored identity has a connected DID, use the identity DID as the delegated DID, otherwise it is undefined. delegateDid = identity.metadata.connectedDid ? identity.did.uri : undefined; if (registration !== undefined) { // If a registration object is passed, we attempt to register the AgentDID and the ConnectedDID with the DWN endpoints provided try { for (const dwnEndpoint of serviceEndpointNodes) { // check if endpoint needs registration const serverInfo = yield userAgent.rpc.getServerInfo(dwnEndpoint); if (serverInfo.registrationRequirements.length === 0) { // no registration required continue; } // register the agent DID yield DwnRegistrar.registerTenant(dwnEndpoint, agent.agentDid.uri); // register the connected Identity DID yield DwnRegistrar.registerTenant(dwnEndpoint, connectedDid); } // If no failures occurred, call the onSuccess callback registration.onSuccess(); } catch (error) { // for any failure, call the onFailure callback with the error registration.onFailure(error); } } // Enable sync, unless explicitly disabled. if (sync !== 'off') { // First, register the user identity for sync. // The connected protocols are used to register sync for only a subset of protocols from the connectedDid's DWN if (registerSync) { yield userAgent.sync.registerIdentity({ did: connectedDid, options: { delegateDid, protocols: connectedProtocols } }); if (walletConnectOptions !== undefined) { // If we are using WalletConnect, we should do a one-shot sync to pull down any messages that are associated with the connectedDid yield userAgent.sync.sync('pull'); } } // Enable sync using the specified interval or default. sync !== null && sync !== void 0 ? sync : (sync = '2m'); userAgent.sync.startSync({ interval: sync }) .catch((error) => { console.error(`Sync failed: ${error}`); }); } } const web5 = new Web5({ agent, connectedDid, delegateDid }); return { web5, did: connectedDid, delegateDid, recoveryPhrase }; }); } /** * Cleans up the DID, Keys and Identity. Primarily used by a failed WalletConnect import. * Does not throw on error, but logs to console. */ static cleanUpIdentity({ identity, userAgent }) { return __awaiter(this, void 0, void 0, function* () { try { // Delete the DID and the Associated Keys yield userAgent.did.delete({ didUri: identity.did.uri, tenant: identity.metadata.tenant, deleteKey: true, }); } catch (error) { console.error(`Failed to delete DID ${identity.did.uri}: ${error.message}`); } try { // Delete the Identity yield userAgent.identity.delete({ didUri: identity.did.uri }); } catch (error) { console.error(`Failed to delete Identity ${identity.metadata.name}: ${error.message}`); } }); } /** * A static method to process connected grants for a delegate DID. * * This will store the grants as the DWN owner to be used later when impersonating the connected DID. */ static processConnectedGrants({ grants, agent, delegateDid }) { return __awaiter(this, void 0, void 0, function* () { const connectedProtocols = new Set(); for (const grantMessage of grants) { // use the delegateDid as the connectedDid of the grant as they do not yet support impersonation/delegation const grant = yield PermissionGrant.parse({ connectedDid: delegateDid, agent, message: grantMessage }); // store the grant as the owner of the DWN, this will allow the delegateDid to use the grant when impersonating the connectedDid const { status } = yield grant.store(true); if (status.code !== 202) { throw new Error(`AgentDwnApi: Failed to process connected grant: ${status.detail}`); } const protocol = grant.scope.protocol; if (protocol) { connectedProtocols.add(protocol); } } // currently we return a de-duped set of protocols represented by these grants, this is used to register protocols for sync // we expect that any connected protocols will include MessagesQuery and MessagesRead grants that will allow it to sync return [...connectedProtocols]; }); } } //# sourceMappingURL=web5.js.map