Skip to content

Commit

Permalink
feat: added export state log feature
Browse files Browse the repository at this point in the history
  • Loading branch information
albertolive committed Oct 10, 2024
1 parent 2e869d1 commit 0c28023
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 13 deletions.
38 changes: 36 additions & 2 deletions packages/custodyController/src/custody.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CUSTODIAN_TYPES } from "@metamask-institutional/custody-keyring";
import { CUSTODIAN_TYPES, IApiCallLogEntry } from "@metamask-institutional/custody-keyring";
import { ITransactionStatusMap } from "@metamask-institutional/types";
import { ObservableStore } from "@metamask/obs-store";

Expand All @@ -16,8 +16,9 @@ import { toChecksumHexAddress } from "./utils";
*/
export class CustodyController {
public store;

private readonly MAX_LOG_ENTRIES = 500;
public captureException: (e: Error) => void;

/**
* Creates a new controller instance
*
Expand All @@ -29,12 +30,45 @@ export class CustodyController {
this.store = new ObservableStore({
custodyAccountDetails: {} as { [key: string]: CustodyAccountDetails },
custodianConnectRequest: {},
apiRequestLogs: [],
...initState,
});

this.captureException = captureException;
}

storeApiCallLog(apiLogEntry: IApiCallLogEntry): void {
const { apiRequestLogs } = this.store.getState();

const updatedApiRequestLogs = apiRequestLogs ? [...apiRequestLogs] : [];

if (updatedApiRequestLogs.length >= this.MAX_LOG_ENTRIES) {
updatedApiRequestLogs.shift();
}

updatedApiRequestLogs.push(apiLogEntry);

this.store.updateState({ apiRequestLogs: updatedApiRequestLogs });
}

sanitizeAndLogApiCall(apiLogEntry: IApiCallLogEntry): void {
const { method, endpoint, success, timestamp, errorMessage, responseData } = apiLogEntry;

const sanitizedEntry: IApiCallLogEntry = {
method,
endpoint,
success,
timestamp,
responseData: success ? responseData : undefined,
};

if (!success && errorMessage) {
sanitizedEntry.errorMessage = errorMessage;
}

this.storeApiCallLog(sanitizedEntry);
}

storeCustodyStatusMap(custody: string, custodyStatusMap: ITransactionStatusMap): void {
try {
const { custodyStatusMaps } = this.store.getState();
Expand Down
8 changes: 8 additions & 0 deletions packages/custodyKeyring/src/CustodyKeyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import crypto from "crypto";
import { EventEmitter } from "events";

import {
API_REQUEST_LOG_EVENT,
DEFAULT_MAX_CACHE_AGE,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
REFRESH_TOKEN_CHANGE_EVENT,
} from "./constants";
import { IApiCallLogEntry } from "./interfaces/IApiCallLogEntry";
import { ICustodianEnvironment } from "./interfaces/ICustodianEnvironment";
import { ICustodyKeyringOptions } from "./interfaces/ICustodyKeyringOptions";
import { ISerializedKeyring } from "./interfaces/ISerializedKeyring";
Expand Down Expand Up @@ -249,6 +251,10 @@ export abstract class CustodyKeyring extends EventEmitter {
this.emit(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event); // Propagate the event to the extension where it calls for the keyrings to be persisted
}

emitApiRequestLogEvent(event: IApiCallLogEntry): void {
this.emit(API_REQUEST_LOG_EVENT, event);
}

createAuthDetails(token: string): AuthDetails {
let authDetails: AuthDetails;

Expand Down Expand Up @@ -287,6 +293,8 @@ export abstract class CustodyKeyring extends EventEmitter {
this.handleInteractiveRefreshTokenChangeEvent(event),
);

sdk.on(API_REQUEST_LOG_EVENT, (event: IApiCallLogEntry) => this.emitApiRequestLogEvent(event));

this.sdkList.push({
sdk,
hash,
Expand Down
1 change: 1 addition & 0 deletions packages/custodyKeyring/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const DEFAULT_MAX_CACHE_AGE = 60;
export const REFRESH_TOKEN_CHANGE_EVENT = "refresh_token_change";
export const INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT = "interactive_replacement_token_change";
export const API_REQUEST_LOG_EVENT = "API_REQUEST_LOG_EVENT";
1 change: 1 addition & 0 deletions packages/custodyKeyring/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { CUSTODIAN_TYPES } from "./custodianTypes";
export { CustodyKeyring } from "./CustodyKeyring";
export { MMIConfiguration } from "./types/MMIConfiguration";
export { IJsonRpcCustodian } from "./interfaces/IJsonRpcCustodian";
export { IApiCallLogEntry } from "./interfaces/IApiCallLogEntry";
export { MmiConfigurationController } from "./MmiConfiguration";

export { IMmiConfigurationControllerOptions } from "./interfaces/IMmiConfigurationControllerOptions";
Expand Down
8 changes: 8 additions & 0 deletions packages/custodyKeyring/src/interfaces/IApiCallLogEntry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface IApiCallLogEntry {
method: string;
endpoint: string;
success: boolean;
timestamp: string;
errorMessage?: string;
responseData?: any;
}
11 changes: 10 additions & 1 deletion packages/sdk/src/classes/MMISDK.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IApiCallLogEntry } from "@metamask-institutional/custody-keyring";
import { SimpleCache } from "@metamask-institutional/simplecache";
import {
AuthDetails,
Expand All @@ -16,7 +17,11 @@ import { CustodianApiConstructor, ICustodianApi } from "src/interfaces/ICustodia
import { SignedMessageMetadata } from "src/types/SignedMessageMetadata";
import { SignedTypedMessageMetadata } from "src/types/SignedTypedMessageMetadata";

import { INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, REFRESH_TOKEN_CHANGE_EVENT } from "../constants/constants";
import {
API_REQUEST_LOG_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
REFRESH_TOKEN_CHANGE_EVENT,
} from "../constants/constants";
import { IEthereumAccount } from "../interfaces/IEthereumAccount";
import { IEthereumAccountCustodianDetails } from "../interfaces/IEthereumAccountCustodianDetails";
import { MessageTypes, TypedMessage } from "../interfaces/ITypedMessage";
Expand Down Expand Up @@ -49,6 +54,10 @@ export class MMISDK extends EventEmitter {
this.custodianApi.on(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event => {
this.emit(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event);
});

this.custodianApi.on(API_REQUEST_LOG_EVENT, (event: IApiCallLogEntry) => {
this.emit(API_REQUEST_LOG_EVENT, event);
});
}

// Do an in-situ replacement of the auth details
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/constants/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const REFRESH_TOKEN_CHANGE_EVENT = "refresh_token_change";
export const INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT = "interactive_replacement_token_change";
export const DEFAULT_MAX_CACHE_AGE = 60;
export const API_REQUEST_LOG_EVENT = "API_REQUEST_LOG_EVENT";
14 changes: 10 additions & 4 deletions packages/sdk/src/custodianApi/eca3/ECA3CustodianApi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IApiCallLogEntry } from "@metamask-institutional/custody-keyring";
import { SimpleCache } from "@metamask-institutional/simplecache";
import {
AuthTypes,
Expand All @@ -11,12 +12,14 @@ import {
} from "@metamask-institutional/types";
import { EventEmitter } from "events";
import { SignedMessageMetadata } from "src/types/SignedMessageMetadata";
import { SignedMessageParams } from "src/types/SignedMessageParams";
import { SignedTypedMessageMetadata } from "src/types/SignedTypedMessageMetadata";
import { SignedTypedMessageParams } from "src/types/SignedTypedMessageParams";

import { AccountHierarchyNode } from "../../classes/AccountHierarchyNode";
import { INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, REFRESH_TOKEN_CHANGE_EVENT } from "../../constants/constants";
import {
API_REQUEST_LOG_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
REFRESH_TOKEN_CHANGE_EVENT,
} from "../../constants/constants";
import { ICustodianApi } from "../../interfaces/ICustodianApi";
import { IEthereumAccount } from "../../interfaces/IEthereumAccount";
import { IEthereumAccountCustodianDetails } from "../../interfaces/IEthereumAccountCustodianDetails";
Expand All @@ -25,7 +28,6 @@ import { CreateTransactionMetadata } from "../../types/CreateTransactionMetadata
import { ECA3Client } from "./ECA3Client";
import { JsonRpcTransactionParams } from "./rpc-payloads/JsonRpcCreateTransactionPayload";
import { JsonRpcReplaceTransactionParams } from "./rpc-payloads/JsonRpcReplaceTransactionPayload";
import { JsonRpcListAccountsSignedResponse } from "./rpc-responses/JsonRpcListAccountsSignedResponse";
import { hexlify } from "./util/hexlify";
import { mapStatusObjectToStatusText } from "./util/mapStatusObjectToStatusText";

Expand Down Expand Up @@ -55,6 +57,10 @@ export class ECA3CustodianApi extends EventEmitter implements ICustodianApi {
this.client.on(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event => {
this.emit(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event);
});

this.client.on(API_REQUEST_LOG_EVENT, (event: IApiCallLogEntry) => {
this.emit(API_REQUEST_LOG_EVENT, event);
});
}

getAccountHierarchy(): Promise<AccountHierarchyNode> {
Expand Down
23 changes: 21 additions & 2 deletions packages/sdk/src/custodianApi/json-rpc/JsonRpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { IRefreshTokenChangeEvent } from "@metamask-institutional/types";
import crypto from "crypto";
import { EventEmitter } from "events";

import { INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, REFRESH_TOKEN_CHANGE_EVENT } from "../../constants/constants";
import {
API_REQUEST_LOG_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
REFRESH_TOKEN_CHANGE_EVENT,
} from "../../constants/constants";
import { JsonRpcResult } from "./interfaces/JsonRpcResult";
import { JsonRpcCreateTransactionPayload } from "./rpc-payloads/JsonRpcCreateTransactionPayload";
import { JsonRpcGetSignedMessageByIdPayload } from "./rpc-payloads/JsonRpcGetSignedMessageByIdPayload";
Expand Down Expand Up @@ -33,7 +37,7 @@ export class JsonRpcClient extends EventEmitter {
constructor(private apiBaseUrl: string, private refreshToken: string, private refreshTokenUrl: string) {
super();

this.call = factory(`${apiBaseUrl}/v1/json-rpc`);
this.call = factory(`${this.apiBaseUrl}/v1/json-rpc`, this.emit.bind(this));

this.cache = new SimpleCache();
}
Expand Down Expand Up @@ -127,8 +131,23 @@ export class JsonRpcClient extends EventEmitter {
this.emit(REFRESH_TOKEN_CHANGE_EVENT, payload);
}

this.emit(API_REQUEST_LOG_EVENT, {
method: "POST",
endpoint: this.refreshTokenUrl,
success: response.ok,
timestamp: new Date().toISOString(),
errorMessage: response.ok ? undefined : responseJson.message,
});

return responseJson.access_token;
} catch (error) {
this.emit(API_REQUEST_LOG_EVENT, {
method: "POST",
endpoint: this.refreshTokenUrl,
success: false,
timestamp: new Date().toISOString(),
errorMessage: error.message,
});
throw new Error(`Error getting the Access Token: ${error}`);
}
}
Expand Down
11 changes: 10 additions & 1 deletion packages/sdk/src/custodianApi/json-rpc/JsonRpcCustodianApi.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IApiCallLogEntry } from "@metamask-institutional/custody-keyring";
import { SimpleCache } from "@metamask-institutional/simplecache";
import {
AuthTypes,
Expand All @@ -12,7 +13,11 @@ import {
import { EventEmitter } from "events";

import { AccountHierarchyNode } from "../../classes/AccountHierarchyNode";
import { INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, REFRESH_TOKEN_CHANGE_EVENT } from "../../constants/constants";
import {
API_REQUEST_LOG_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
REFRESH_TOKEN_CHANGE_EVENT,
} from "../../constants/constants";
import { ICustodianApi } from "../../interfaces/ICustodianApi";
import { IEthereumAccount } from "../../interfaces/IEthereumAccount";
import { IEthereumAccountCustodianDetails } from "../../interfaces/IEthereumAccountCustodianDetails";
Expand Down Expand Up @@ -49,6 +54,10 @@ export class JsonRpcCustodianApi extends EventEmitter implements ICustodianApi {
this.client.on(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event => {
this.emit(INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT, event);
});

this.client.on(API_REQUEST_LOG_EVENT, (event: IApiCallLogEntry) => {
this.emit(API_REQUEST_LOG_EVENT, event);
});
}

getAccountHierarchy(): Promise<AccountHierarchyNode> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("json-rpc-call", () => {
describe("json-rpc-call", () => {
it("should call the JSON RPC endpoint with the appropriate method and parameters", async () => {
fetchMock.mockResponseOnce(JSON.stringify({ result: "test" }));
const call = factory("http://test/json-rpc");
const call = factory("http://test/json-rpc", jest.fn());

await call("test", { some: "parameter" }, "access_token");

Expand All @@ -37,7 +37,7 @@ describe("json-rpc-call", () => {
}),
);

const call = factory("http://test/json-rpc");
const call = factory("http://test/json-rpc", jest.fn());

await expect(call("test", { some: "parameter" }, "access_token")).rejects.toThrow("Test error");
});
Expand Down
23 changes: 22 additions & 1 deletion packages/sdk/src/custodianApi/json-rpc/util/json-rpc-call.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { IApiCallLogEntry } from "@metamask-institutional/custody-keyring/src/interfaces/IApiCallLogEntry";

import { API_REQUEST_LOG_EVENT } from "../../../constants/constants";
import { JsonRpcError } from "../interfaces/JsonRpcError";
import { JsonRpcResult } from "../interfaces/JsonRpcResult";

export default function (jsonRpcEndpoint: string) {
export default function (jsonRpcEndpoint: string, emit: (eventName: string, eventData: IApiCallLogEntry) => void) {
let requestId = 0;

return async function jsonRpcCall<T1, T2>(method: string, params: T1, accessToken: string): Promise<T2> {
Expand Down Expand Up @@ -30,6 +33,15 @@ export default function (jsonRpcEndpoint: string) {

responseJson = await response.json();

emit(API_REQUEST_LOG_EVENT, {
method,
endpoint: jsonRpcEndpoint,
success: !responseJson.error,
timestamp: new Date().toISOString(),
errorMessage: responseJson.error ? responseJson.error.message : undefined,
responseData: responseJson.result,
});

if ((responseJson as JsonRpcError).error) {
console.log("JSON-RPC <", method, requestId, responseJson, jsonRpcEndpoint);
throw new Error((responseJson as JsonRpcError).error.message);
Expand All @@ -42,6 +54,15 @@ export default function (jsonRpcEndpoint: string) {

console.log("JSON-RPC <", method, requestId, e, jsonRpcEndpoint);

emit(API_REQUEST_LOG_EVENT, {
method,
endpoint: jsonRpcEndpoint,
success: false,
timestamp: new Date().toISOString(),
errorMessage: e.message,
responseData: null,
});

throw e;
}

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { ICustodianApi } from "./interfaces/ICustodianApi";
export {
REFRESH_TOKEN_CHANGE_EVENT,
INTERACTIVE_REPLACEMENT_TOKEN_CHANGE_EVENT,
API_REQUEST_LOG_EVENT,
DEFAULT_MAX_CACHE_AGE,
} from "./constants/constants";

Expand Down

0 comments on commit 0c28023

Please sign in to comment.