From a2cd9d53f73c0f35e505789fc26c2e012a3ac4d9 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Fri, 4 Oct 2024 14:46:45 -0700 Subject: [PATCH] Added logger and some usage (#947) --- packages/agent/src/connect.ts | 3 +- packages/agent/src/oidc.ts | 23 ++++++++++-- packages/common/src/index.ts | 1 + packages/common/src/logger.ts | 70 +++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 packages/common/src/logger.ts diff --git a/packages/agent/src/connect.ts b/packages/agent/src/connect.ts index 9208d3f39..fb19b9983 100644 --- a/packages/agent/src/connect.ts +++ b/packages/agent/src/connect.ts @@ -7,7 +7,7 @@ import { } from './oidc.js'; import { pollWithTtl } from './utils.js'; -import { Convert } from '@web5/common'; +import { Convert, logger } from '@web5/common'; import { CryptoUtils } from '@web5/crypto'; import { DidJwk } from '@web5/dids'; import { DwnInterfaceName, DwnMethodName } from '@tbd54566975/dwn-sdk-js'; @@ -94,6 +94,7 @@ async function initClient({ // a deeplink to a web5 compatible wallet. if the wallet scans this link it should receive // a route to its web5 connect provider flow and the params of where to fetch the auth request. + logger.log(`Wallet URI: ${walletUri}`); const generatedWalletUri = new URL(walletUri); generatedWalletUri.searchParams.set('request_uri', parData.request_uri); generatedWalletUri.searchParams.set( diff --git a/packages/agent/src/oidc.ts b/packages/agent/src/oidc.ts index dc40917c4..076a0b72a 100644 --- a/packages/agent/src/oidc.ts +++ b/packages/agent/src/oidc.ts @@ -1,4 +1,4 @@ -import { Convert, RequireOnly } from '@web5/common'; +import { Convert, logger, RequireOnly } from '@web5/common'; import { Ed25519, EdDsaAlgorithm, @@ -631,6 +631,7 @@ async function createPermissionGrants( const permissionsApi = new AgentPermissionsApi({ agent }); // TODO: cleanup all grants if one fails by deleting them from the DWN: https://github.com/TBD54566975/web5-js/issues/849 + logger.log(`Creating permission grants for ${scopes.length} scopes given...`); const permissionGrants = await Promise.all( scopes.map((scope) => { // check if the scope is a records permission scope, or a protocol configure scope, if so it should use a delegated permission. @@ -646,6 +647,7 @@ async function createPermissionGrants( }) ); + logger.log(`Sending ${permissionGrants.length} permission grants to remote DWN...`); const messagePromises = permissionGrants.map(async (grant) => { // Quirk: we have to pull out encodedData out of the message the schema validator doesn't want it there const { encodedData, ...rawMessage } = grant.message; @@ -661,6 +663,8 @@ async function createPermissionGrants( // check if the message was sent successfully, if the remote returns 409 the message may have come through already via sync if (reply.status.code !== 202 && reply.status.code !== 409) { + logger.error(`Error sending RecordsWrite: ${reply.status.detail}`); + logger.error(`RecordsWrite message: ${rawMessage}`); throw new Error( `Could not send the message. Error details: ${reply.status.detail}` ); @@ -669,8 +673,13 @@ async function createPermissionGrants( return grant.message; }); - const messages = await Promise.all(messagePromises); - return messages; + try { + const messages = await Promise.all(messagePromises); + return messages; + } catch (error) { + logger.error(`Error during batch-send of permission grants: ${error}`); + throw error; + } } /** @@ -695,6 +704,8 @@ async function prepareProtocol( `Could not fetch protocol: ${queryMessage.reply.status.detail}` ); } else if (queryMessage.reply.entries === undefined || queryMessage.reply.entries.length === 0) { + logger.log(`Protocol does not exist, creating: ${protocolDefinition.protocol}`); + // send the protocol definition to the remote DWN first, if it passes we can process it locally const { reply: sendReply, message: configureMessage } = await agent.sendDwnRequest({ author : selectedDid, @@ -717,6 +728,8 @@ async function prepareProtocol( }); } else { + logger.log(`Protocol already exists: ${protocolDefinition.protocol}`); + // the protocol already exists, let's make sure it exists on the remote DWN as the requesting app will need it const configureMessage = queryMessage.reply.entries![0]; const { reply: sendReply } = await agent.sendDwnRequest({ @@ -776,6 +789,7 @@ async function submitAuthResponse( const delegateGrants = (await Promise.all(delegateGrantPromises)).flat(); + logger.log('Generating auth response object...'); const responseObject = await Oidc.createResponseObject({ //* the IDP's did that was selected to be connected iss : selectedDid, @@ -790,6 +804,7 @@ async function submitAuthResponse( }); // Sign the Response Object using the ephemeral DID's signing key. + logger.log('Signing auth response object...'); const responseObjectJwt = await Oidc.signJwt({ did : delegateBearerDid, data : responseObject, @@ -801,6 +816,7 @@ async function submitAuthResponse( clientDid?.didDocument! ); + logger.log('Encrypting auth response object...'); const encryptedResponse = Oidc.encryptAuthResponse({ jwt : responseObjectJwt!, encryptionKey : sharedKey, @@ -813,6 +829,7 @@ async function submitAuthResponse( state : authRequest.state, }).toString(); + logger.log(`Sending auth response object to Web5 Connect server: ${authRequest.redirect_uri}`); await fetch(authRequest.redirect_uri, { body : formEncodedRequest, method : 'POST', diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 1a6095561..0f4b01691 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,6 +2,7 @@ export type * from './types.js'; export * from './cache.js'; export * from './convert.js'; +export * from './logger.js'; export * from './multicodec.js'; export * from './object.js'; export * from './stores.js'; diff --git a/packages/common/src/logger.ts b/packages/common/src/logger.ts new file mode 100644 index 000000000..86fbb7f6c --- /dev/null +++ b/packages/common/src/logger.ts @@ -0,0 +1,70 @@ +/** + * Web5 logger level. + */ +export enum Web5LogLevel { + Debug = 'debug', + Silent = 'silent', +} + +/** + * Web5 logger interface. + */ +export interface Web5LoggerInterface { + + /** + * Sets the log verbose level. + */ + setLogLevel(logLevel: Web5LogLevel): void; + + /** + * Same as `info()`. + * Logs an informational message. + */ + log (message: string): void; + + /** + * Logs an informational message. + */ + info(message: string): void; + + /** + * Logs an error message. + */ + error(message: string): void; +} + +/** + * A Web5 logger implementation. + */ +class Web5Logger implements Web5LoggerInterface { + private logLevel: Web5LogLevel = Web5LogLevel.Silent; // Default to silent/no-op log level + + setLogLevel(logLevel: Web5LogLevel): void { + this.logLevel = logLevel; + } + + public log(message: string): void { + this.info(message); + } + + public info(message: string): void { + if (this.logLevel === Web5LogLevel.Silent) { return; } + + console.info(message); + } + + public error(message: string): void { + if (this.logLevel === Web5LogLevel.Silent) { return; } + + console.error(message); + } +} + +// Export a singleton logger instance +export const logger = new Web5Logger(); + +// Attach logger to the global window object in browser environment for easy access to the logger instance. +// e.g. can call `web5logger.setLogLevel('debug');` directly in browser console. +if (typeof window !== 'undefined') { + (window as any).web5logger = logger; // Makes `web5Logger` accessible globally in browser +} \ No newline at end of file