diff --git a/packages/examples/packages/preinstalled/snap.manifest.json b/packages/examples/packages/preinstalled/snap.manifest.json index 4b3921c284..d2ae728132 100644 --- a/packages/examples/packages/preinstalled/snap.manifest.json +++ b/packages/examples/packages/preinstalled/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "orIGELPnJuNGSXC/IH7XrDciP6y+csrN9oWh//+m7DI=", + "shasum": "yNJdBPGXVZJZHIjQtrlPGXA+BlDBZhjZIswZbTjuYkM=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/preinstalled/src/index.tsx b/packages/examples/packages/preinstalled/src/index.tsx index 1323dc3c65..09ae77058b 100644 --- a/packages/examples/packages/preinstalled/src/index.tsx +++ b/packages/examples/packages/preinstalled/src/index.tsx @@ -64,6 +64,23 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { }); } + case 'startTrace': + return await snap.request({ + method: 'snap_startTrace', + params: { + name: 'Test Snap Trace', + }, + }); + + case 'endTrace': { + return await snap.request({ + method: 'snap_endTrace', + params: { + name: 'Test Snap Trace', + }, + }); + } + default: throw new MethodNotFoundError({ method: request.method }); } diff --git a/packages/snaps-rpc-methods/jest.config.js b/packages/snaps-rpc-methods/jest.config.js index cae0972ed8..c589611a1b 100644 --- a/packages/snaps-rpc-methods/jest.config.js +++ b/packages/snaps-rpc-methods/jest.config.js @@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, { ], coverageThreshold: { global: { - branches: 95.32, - functions: 98.73, - lines: 98.89, - statements: 98.59, + branches: 95.37, + functions: 98.76, + lines: 98.92, + statements: 98.62, }, }, }); diff --git a/packages/snaps-rpc-methods/src/permitted/endTrace.test.ts b/packages/snaps-rpc-methods/src/permitted/endTrace.test.ts new file mode 100644 index 0000000000..1077ca49b7 --- /dev/null +++ b/packages/snaps-rpc-methods/src/permitted/endTrace.test.ts @@ -0,0 +1,173 @@ +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import type { EndTraceParams, EndTraceResult } from '@metamask/snaps-sdk'; +import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; + +import { endTraceHandler } from './endTrace'; + +describe('snap_endTrace', () => { + describe('endTraceHandler', () => { + it('has the expected shape', () => { + expect(endTraceHandler).toMatchObject({ + methodNames: ['snap_endTrace'], + implementation: expect.any(Function), + hookNames: { + endTrace: true, + getSnap: true, + }, + }); + }); + }); + + describe('implementation', () => { + it('calls the `endTrace` hook with the provided parameters', async () => { + const { implementation } = endTraceHandler; + + const endTrace = jest.fn().mockReturnValue(null); + + const getSnap = jest.fn().mockReturnValue({ preinstalled: true }); + const hooks = { endTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response as PendingJsonRpcResponse, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_endTrace', + params: { + id: 'test-id', + name: 'Test Trace', + timestamp: 1234567890, + }, + }); + + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + result: null, + }); + + expect(endTrace).toHaveBeenCalledWith({ + id: 'test-id', + name: 'Test Trace', + timestamp: 1234567890, + }); + }); + + it('throws an error if the Snap is not preinstalled', async () => { + const { implementation } = endTraceHandler; + + const endTrace = jest.fn(); + const getSnap = jest.fn().mockReturnValue({ preinstalled: false }); + const hooks = { endTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response as PendingJsonRpcResponse, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_endTrace', + params: { + id: 'test-id', + name: 'Test Trace', + }, + }); + + expect(endTrace).not.toHaveBeenCalled(); + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + error: { + code: -32601, + message: 'The method does not exist / is not available.', + stack: expect.any(String), + }, + }); + }); + + it.each([ + [ + { foo: 'bar' }, + 'Invalid params: At path: name -- Expected a string, but received: undefined.', + ], + [ + { name: undefined }, + 'Invalid params: At path: name -- Expected a string, but received: undefined.', + ], + [ + { name: 'Test Trace', id: 123 }, + 'Invalid params: At path: id -- Expected a string, but received: 123.', + ], + [ + { name: 'Test Trace', id: 'test-id', timestamp: 'not-a-number' }, + 'Invalid params: At path: timestamp -- Expected a number, but received: "not-a-number".', + ], + ])( + 'throws an error if the parameters are invalid', + async (params, error) => { + const { implementation } = endTraceHandler; + + const endTrace = jest.fn(); + const getSnap = jest.fn().mockReturnValue({ preinstalled: true }); + const hooks = { endTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response as PendingJsonRpcResponse, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + // @ts-expect-error: Intentionally passing invalid params. + // eslint-disable-next-line @typescript-eslint/await-thenable + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_endTrace', + params, + }); + + expect(endTrace).not.toHaveBeenCalled(); + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + error: { + code: -32602, + message: error, + stack: expect.any(String), + }, + }); + }, + ); + }); +}); diff --git a/packages/snaps-rpc-methods/src/permitted/endTrace.ts b/packages/snaps-rpc-methods/src/permitted/endTrace.ts new file mode 100644 index 0000000000..58a3263ba8 --- /dev/null +++ b/packages/snaps-rpc-methods/src/permitted/endTrace.ts @@ -0,0 +1,132 @@ +import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; +import type { PermittedHandlerExport } from '@metamask/permission-controller'; +import { rpcErrors } from '@metamask/rpc-errors'; +import type { + JsonRpcRequest, + EndTraceParams, + EndTraceResult, + EndTraceRequest, +} from '@metamask/snaps-sdk'; +import type { InferMatching, Snap } from '@metamask/snaps-utils'; +import { + number, + create, + object, + string, + StructError, + exactOptional, +} from '@metamask/superstruct'; +import type { PendingJsonRpcResponse } from '@metamask/utils'; + +import type { MethodHooksObject } from '../utils'; + +const hookNames: MethodHooksObject = { + endTrace: true, + getSnap: true, +}; + +export type EndTraceMethodHooks = { + /** + * End a performance trace in Sentry. + * + * @param request - The trace request object. + * @returns The performance trace context. + */ + endTrace: (request: EndTraceRequest) => void; + + /** + * Get Snap metadata. + * + * @param snapId - The ID of a Snap. + */ + getSnap: (snapId: string) => Snap | undefined; +}; + +const EndTraceParametersStruct = object({ + id: exactOptional(string()), + name: string(), + timestamp: exactOptional(number()), +}); + +export type EndTraceParameters = InferMatching< + typeof EndTraceParametersStruct, + EndTraceParams +>; + +/** + * Handler for the `snap_endTrace` method. + */ +export const endTraceHandler: PermittedHandlerExport< + EndTraceMethodHooks, + EndTraceParameters, + EndTraceResult +> = { + methodNames: ['snap_endTrace'], + implementation: getEndTraceImplementation, + hookNames, +}; + +/** + * The `snap_endTrace` method implementation. This method is used to end a + * performance trace in Sentry. It is only available to preinstalled Snaps. + * + * @param request - The JSON-RPC request object. + * @param response - The JSON-RPC response object. + * @param _next - The `json-rpc-engine` "next" callback. Not used by this + * function. + * @param end - The `json-rpc-engine` "end" callback. + * @param hooks - The RPC method hooks. + * @param hooks.endTrace - The hook function to end a performance trace. + * @param hooks.getSnap - The hook function to get Snap metadata. + * @returns Nothing. + */ +function getEndTraceImplementation( + request: JsonRpcRequest, + response: PendingJsonRpcResponse, + _next: unknown, + end: JsonRpcEngineEndCallback, + { endTrace, getSnap }: EndTraceMethodHooks, +): void { + const snap = getSnap( + (request as JsonRpcRequest & { origin: string }).origin, + ); + + if (!snap?.preinstalled) { + return end(rpcErrors.methodNotFound()); + } + + const { params } = request; + + try { + const validatedParams = getValidatedParams(params); + endTrace(validatedParams); + + response.result = null; + } catch (error) { + return end(error); + } + + return end(); +} + +/** + * Validate the parameters for the `snap_endTrace` method. + * + * @param params - Parameters to validate. + * @returns Validated parameters. + * @throws Throws RPC error if validation fails. + */ +function getValidatedParams(params: unknown): EndTraceParameters { + try { + return create(params, EndTraceParametersStruct); + } catch (error) { + if (error instanceof StructError) { + throw rpcErrors.invalidParams({ + message: `Invalid params: ${error.message}.`, + }); + } + + /* istanbul ignore next */ + throw rpcErrors.internal(); + } +} diff --git a/packages/snaps-rpc-methods/src/permitted/handlers.ts b/packages/snaps-rpc-methods/src/permitted/handlers.ts index 55b4b0673d..d70c5d38ff 100644 --- a/packages/snaps-rpc-methods/src/permitted/handlers.ts +++ b/packages/snaps-rpc-methods/src/permitted/handlers.ts @@ -2,6 +2,7 @@ import { cancelBackgroundEventHandler } from './cancelBackgroundEvent'; import { clearStateHandler } from './clearState'; import { closeWebSocketHandler } from './closeWebSocket'; import { createInterfaceHandler } from './createInterface'; +import { endTraceHandler } from './endTrace'; import { providerRequestHandler } from './experimentalProviderRequest'; import { getAllSnapsHandler } from './getAllSnaps'; import { getBackgroundEventsHandler } from './getBackgroundEvents'; @@ -22,6 +23,7 @@ import { resolveInterfaceHandler } from './resolveInterface'; import { scheduleBackgroundEventHandler } from './scheduleBackgroundEvent'; import { sendWebSocketMessageHandler } from './sendWebSocketMessage'; import { setStateHandler } from './setState'; +import { startTraceHandler } from './startTrace'; import { trackErrorHandler } from './trackError'; import { trackEventHandler } from './trackEvent'; import { updateInterfaceHandler } from './updateInterface'; @@ -55,6 +57,8 @@ export const methodHandlers = { snap_closeWebSocket: closeWebSocketHandler, snap_sendWebSocketMessage: sendWebSocketMessageHandler, snap_getWebSockets: getWebSocketsHandler, + snap_startTrace: startTraceHandler, + snap_endTrace: endTraceHandler, }; /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/packages/snaps-rpc-methods/src/permitted/index.ts b/packages/snaps-rpc-methods/src/permitted/index.ts index cd4f8eba3b..ee858d6e04 100644 --- a/packages/snaps-rpc-methods/src/permitted/index.ts +++ b/packages/snaps-rpc-methods/src/permitted/index.ts @@ -2,6 +2,7 @@ import type { CancelBackgroundEventMethodHooks } from './cancelBackgroundEvent'; import type { ClearStateHooks } from './clearState'; import type { CloseWebSocketMethodHooks } from './closeWebSocket'; import type { CreateInterfaceMethodHooks } from './createInterface'; +import type { EndTraceMethodHooks } from './endTrace'; import type { ProviderRequestMethodHooks } from './experimentalProviderRequest'; import type { GetAllSnapsHooks } from './getAllSnaps'; import type { GetBackgroundEventsMethodHooks } from './getBackgroundEvents'; @@ -18,6 +19,7 @@ import type { ResolveInterfaceMethodHooks } from './resolveInterface'; import type { ScheduleBackgroundEventMethodHooks } from './scheduleBackgroundEvent'; import type { SendWebSocketMessageMethodHooks } from './sendWebSocketMessage'; import type { SetStateHooks } from './setState'; +import type { StartTraceMethodHooks } from './startTrace'; import type { TrackErrorMethodHooks } from './trackError'; import type { TrackEventMethodHooks } from './trackEvent'; import type { UpdateInterfaceMethodHooks } from './updateInterface'; @@ -44,7 +46,9 @@ export type PermittedRpcMethodHooks = ClearStateHooks & SendWebSocketMessageMethodHooks & GetWebSocketsMethodHooks & TrackEventMethodHooks & - TrackErrorMethodHooks; + TrackErrorMethodHooks & + StartTraceMethodHooks & + EndTraceMethodHooks; export * from './handlers'; export * from './middleware'; diff --git a/packages/snaps-rpc-methods/src/permitted/startTrace.test.ts b/packages/snaps-rpc-methods/src/permitted/startTrace.test.ts new file mode 100644 index 0000000000..24120101d0 --- /dev/null +++ b/packages/snaps-rpc-methods/src/permitted/startTrace.test.ts @@ -0,0 +1,188 @@ +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import type { StartTraceParams } from '@metamask/snaps-sdk'; +import type { JsonRpcRequest } from '@metamask/utils'; + +import { startTraceHandler } from './startTrace'; + +describe('snap_startTrace', () => { + describe('startTraceHandler', () => { + it('has the expected shape', () => { + expect(startTraceHandler).toMatchObject({ + methodNames: ['snap_startTrace'], + implementation: expect.any(Function), + hookNames: { + startTrace: true, + getSnap: true, + }, + }); + }); + }); + + describe('implementation', () => { + it('calls the `startTrace` hook with the provided parameters', async () => { + const { implementation } = startTraceHandler; + + const startTrace = jest.fn().mockReturnValue({ + traceId: 'test-trace-id', + }); + + const getSnap = jest.fn().mockReturnValue({ preinstalled: true }); + const hooks = { startTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_startTrace', + params: { + id: 'test-id', + name: 'Test Trace', + data: { foo: 'bar' }, + tags: { tag1: 'value1', tag2: 42 }, + startTime: 1234567890, + }, + }); + + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + result: { + traceId: 'test-trace-id', + }, + }); + + expect(startTrace).toHaveBeenCalledWith({ + id: 'test-id', + name: 'Test Trace', + data: { foo: 'bar' }, + tags: { tag1: 'value1', tag2: 42 }, + startTime: 1234567890, + }); + }); + + it('throws an error if the Snap is not preinstalled', async () => { + const { implementation } = startTraceHandler; + + const startTrace = jest.fn(); + const getSnap = jest.fn().mockReturnValue({ preinstalled: false }); + const hooks = { startTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_startTrace', + params: { + id: 'test-id', + name: 'Test Trace', + data: { foo: 'bar' }, + tags: { tag1: 'value1', tag2: 42 }, + startTime: 1234567890, + }, + }); + + expect(startTrace).not.toHaveBeenCalled(); + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + error: { + code: -32601, + message: 'The method does not exist / is not available.', + stack: expect.any(String), + }, + }); + }); + + it.each([ + [ + { foo: 'bar' }, + 'Invalid params: At path: name -- Expected a string, but received: undefined.', + ], + [ + { name: undefined }, + 'Invalid params: At path: name -- Expected a string, but received: undefined.', + ], + [ + { name: 'Test Trace', id: 123 }, + 'Invalid params: At path: id -- Expected a string, but received: 123.', + ], + [ + { name: 'Test Trace', id: 'test-id', data: 'not-an-object' }, + 'Invalid params: At path: data -- Expected an object, but received: "not-an-object".', + ], + [ + { name: 'Test Trace', id: 'test-id', tags: 'not-an-object' }, + 'Invalid params: At path: tags -- Expected an object, but received: "not-an-object".', + ], + ])( + 'throws an error if the parameters are invalid', + async (params, error) => { + const { implementation } = startTraceHandler; + + const startTrace = jest.fn(); + const getSnap = jest.fn().mockReturnValue({ preinstalled: true }); + const hooks = { startTrace, getSnap }; + + const engine = new JsonRpcEngine(); + + engine.push((request, response, next, end) => { + const result = implementation( + request as JsonRpcRequest, + response, + next, + end, + hooks, + ); + + result?.catch(end); + }); + + // @ts-expect-error: Intentionally passing invalid params. + // eslint-disable-next-line @typescript-eslint/await-thenable + const response = await engine.handle({ + jsonrpc: '2.0', + id: 1, + method: 'snap_startTrace', + params, + }); + + expect(startTrace).not.toHaveBeenCalled(); + expect(response).toStrictEqual({ + jsonrpc: '2.0', + id: 1, + error: { + code: -32602, + message: error, + stack: expect.any(String), + }, + }); + }, + ); + }); +}); diff --git a/packages/snaps-rpc-methods/src/permitted/startTrace.ts b/packages/snaps-rpc-methods/src/permitted/startTrace.ts new file mode 100644 index 0000000000..865574d9cf --- /dev/null +++ b/packages/snaps-rpc-methods/src/permitted/startTrace.ts @@ -0,0 +1,138 @@ +import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; +import type { PermittedHandlerExport } from '@metamask/permission-controller'; +import { rpcErrors } from '@metamask/rpc-errors'; +import type { + JsonRpcRequest, + StartTraceParams, + StartTraceResult, + TraceContext, + TraceRequest, +} from '@metamask/snaps-sdk'; +import type { InferMatching, Snap } from '@metamask/snaps-utils'; +import { + boolean, + number, + record, + union, + create, + object, + string, + StructError, + exactOptional, +} from '@metamask/superstruct'; +import type { PendingJsonRpcResponse } from '@metamask/utils'; +import { JsonStruct } from '@metamask/utils'; + +import type { MethodHooksObject } from '../utils'; + +const hookNames: MethodHooksObject = { + startTrace: true, + getSnap: true, +}; + +export type StartTraceMethodHooks = { + /** + * Start a performance trace in Sentry. + * + * @param request - The trace request object. + * @returns The performance trace context. + */ + startTrace: (request: TraceRequest) => TraceContext; + + /** + * Get Snap metadata. + * + * @param snapId - The ID of a Snap. + */ + getSnap: (snapId: string) => Snap | undefined; +}; + +const StartTraceParametersStruct = object({ + data: exactOptional(record(string(), union([string(), number(), boolean()]))), + id: exactOptional(string()), + name: string(), + parentContext: exactOptional(JsonStruct), + startTime: exactOptional(number()), + tags: exactOptional(record(string(), union([string(), number(), boolean()]))), +}); + +export type StartTraceParameters = InferMatching< + typeof StartTraceParametersStruct, + StartTraceParams +>; + +/** + * Handler for the `snap_startTrace` method. + */ +export const startTraceHandler: PermittedHandlerExport< + StartTraceMethodHooks, + StartTraceParameters, + StartTraceResult +> = { + methodNames: ['snap_startTrace'], + implementation: getStartTraceImplementation, + hookNames, +}; + +/** + * The `snap_startTrace` method implementation. This method is used to start a + * performance trace in Sentry. It is only available to preinstalled Snaps. + * + * @param request - The JSON-RPC request object. + * @param response - The JSON-RPC response object. + * @param _next - The `json-rpc-engine` "next" callback. Not used by this + * function. + * @param end - The `json-rpc-engine` "end" callback. + * @param hooks - The RPC method hooks. + * @param hooks.startTrace - The hook function to start a performance trace. + * @param hooks.getSnap - The hook function to get Snap metadata. + * @returns Nothing. + */ +function getStartTraceImplementation( + request: JsonRpcRequest, + response: PendingJsonRpcResponse, + _next: unknown, + end: JsonRpcEngineEndCallback, + { startTrace, getSnap }: StartTraceMethodHooks, +): void { + const snap = getSnap( + (request as JsonRpcRequest & { origin: string }).origin, + ); + + if (!snap?.preinstalled) { + return end(rpcErrors.methodNotFound()); + } + + const { params } = request; + + try { + const validatedParams = getValidatedParams(params); + response.result = startTrace(validatedParams); + } catch (error) { + return end(error); + } + + return end(); +} + +/** + * Validate the parameters for the `snap_startTrace` method. + * + * @param params - Parameters to validate. + * @returns Validated parameters. + * @throws Throws RPC error if validation fails. + */ +function getValidatedParams(params: unknown): StartTraceParameters { + try { + return create(params, StartTraceParametersStruct); + } catch (error) { + if (error instanceof StructError) { + throw rpcErrors.invalidParams({ + message: `Invalid params: ${error.message}.`, + }); + } + + /* istanbul ignore next */ + throw rpcErrors.internal(); + } +} diff --git a/packages/snaps-rpc-methods/src/permitted/trackError.ts b/packages/snaps-rpc-methods/src/permitted/trackError.ts index d754c180e2..7d599de512 100644 --- a/packages/snaps-rpc-methods/src/permitted/trackError.ts +++ b/packages/snaps-rpc-methods/src/permitted/trackError.ts @@ -117,7 +117,7 @@ function getTrackErrorImplementation( } /** - * Validates the parameters for the snap_trackEvent method. + * Validate the parameters for the `snap_trackError` method. * * @param params - Parameters to validate. * @returns Validated parameters. diff --git a/packages/snaps-sdk/src/types/methods/end-trace.ts b/packages/snaps-sdk/src/types/methods/end-trace.ts new file mode 100644 index 0000000000..59f718c615 --- /dev/null +++ b/packages/snaps-sdk/src/types/methods/end-trace.ts @@ -0,0 +1,35 @@ +import type { TraceName } from './start-trace'; + +/** + * A request to end a pending trace. + */ +export type EndTraceRequest = { + /** + * The unique identifier of the trace. + * Defaults to 'default' if not provided. + */ + id?: string; + + /** + * The name of the trace. + */ + name: TraceName; + + /** + * Override the end time of the trace. + */ + timestamp?: number; +}; + +/** + * The request parameters for the `snap_endTrace` method. This method is used + * to end a performance trace in Sentry. + * + * Note that this method is only available to preinstalled Snaps. + */ +export type EndTraceParams = EndTraceRequest; + +/** + * The result returned by the `snap_endTrace` method. + */ +export type EndTraceResult = null; diff --git a/packages/snaps-sdk/src/types/methods/index.ts b/packages/snaps-sdk/src/types/methods/index.ts index 46f60a12df..72da8be705 100644 --- a/packages/snaps-sdk/src/types/methods/index.ts +++ b/packages/snaps-sdk/src/types/methods/index.ts @@ -1,6 +1,7 @@ export type * from './clear-state'; export type * from './create-interface'; export * from './dialog'; +export type * from './end-trace'; export type * from './get-bip32-entropy'; export type * from './get-bip32-public-key'; export type * from './get-bip44-entropy'; @@ -23,6 +24,7 @@ export type * from './methods'; export * from './notify'; export type * from './provider-request'; export type * from './request-snaps'; +export type * from './start-trace'; export type * from './update-interface'; export type * from './resolve-interface'; export type * from './schedule-background-event'; diff --git a/packages/snaps-sdk/src/types/methods/methods.ts b/packages/snaps-sdk/src/types/methods/methods.ts index b6f855fcdc..0668c1700a 100644 --- a/packages/snaps-sdk/src/types/methods/methods.ts +++ b/packages/snaps-sdk/src/types/methods/methods.ts @@ -12,6 +12,7 @@ import type { CreateInterfaceResult, } from './create-interface'; import type { DialogParams, DialogResult } from './dialog'; +import type { EndTraceParams, EndTraceResult } from './end-trace'; import type { GetBackgroundEventsParams, GetBackgroundEventsResult, @@ -90,6 +91,7 @@ import type { SendWebSocketMessageResult, } from './send-web-socket-message'; import type { SetStateParams, SetStateResult } from './set-state'; +import type { StartTraceParams, StartTraceResult } from './start-trace'; import type { TrackErrorParams, TrackErrorResult } from './track-error'; import type { TrackEventParams, TrackEventResult } from './track-event'; import type { @@ -106,6 +108,7 @@ export type SnapMethods = { /* eslint-disable @typescript-eslint/naming-convention */ snap_clearState: [ClearStateParams, ClearStateResult]; snap_dialog: [DialogParams, DialogResult]; + snap_endTrace: [EndTraceParams, EndTraceResult]; snap_getBip32Entropy: [GetBip32EntropyParams, GetBip32EntropyResult]; snap_getBip32PublicKey: [GetBip32PublicKeyParams, GetBip32PublicKeyResult]; snap_getBip44Entropy: [GetBip44EntropyParams, GetBip44EntropyResult]; @@ -141,6 +144,7 @@ export type SnapMethods = { ]; snap_resolveInterface: [ResolveInterfaceParams, ResolveInterfaceResult]; snap_setState: [SetStateParams, SetStateResult]; + snap_startTrace: [StartTraceParams, StartTraceResult]; snap_trackEvent: [TrackEventParams, TrackEventResult]; snap_trackError: [TrackErrorParams, TrackErrorResult]; snap_openWebSocket: [OpenWebSocketParams, OpenWebSocketResult]; diff --git a/packages/snaps-sdk/src/types/methods/start-trace.ts b/packages/snaps-sdk/src/types/methods/start-trace.ts new file mode 100644 index 0000000000..9a9c371df1 --- /dev/null +++ b/packages/snaps-sdk/src/types/methods/start-trace.ts @@ -0,0 +1,68 @@ +import type { Json } from '@metamask/utils'; + +/** + * The name of the trace context. + * + * The client uses an enum to define the trace names, but to avoid needing to + * copy and update the enum in multiple places, we use a string type here. + */ +export type TraceName = string; + +/** + * A context object to associate traces with each other and generate nested + * traces. + */ +export type TraceContext = Json; + +/** + * A request to create a new performance trace in Sentry. + */ +// This type is copied from `metamask-extension`, and should match that type. +export type TraceRequest = { + /** + * Custom data to associate with the trace. + */ + data?: Record; + + /** + * A unique identifier when not tracing a callback. + * Defaults to 'default' if not provided. + */ + id?: string; + + /** + * The name of the trace. + */ + name: TraceName; + + /** + * The parent context of the trace. + * If provided, the trace will be nested under the parent trace. + */ + parentContext?: TraceContext; + + /** + * Override the start time of the trace. + */ + startTime?: number; + + /** + * Custom tags to associate with the trace. + */ + tags?: Record; +}; + +/** + * The request parameters for the `snap_startTrace` method. This method is used + * to start a performance trace in Sentry. + * + * Note that this method is only available to preinstalled Snaps. + */ +export type StartTraceParams = TraceRequest; + +/** + * The result returned by the `snap_startTrace` method. + * + * This is the trace context that can be used to end the trace later. + */ +export type StartTraceResult = TraceContext; diff --git a/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx b/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx index d2af9c7046..3d7c394f85 100644 --- a/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx +++ b/packages/test-snaps/src/features/snaps/preinstalled/Preinstalled.tsx @@ -30,6 +30,20 @@ export const Preinstalled: FunctionComponent = () => { }).catch(logError); }; + const handleStartTrace = () => { + invokeSnap({ + snapId: PREINSTALLED_SNAP_ID, + method: 'startTrace', + }).catch(logError); + }; + + const handleEndTrace = () => { + invokeSnap({ + snapId: PREINSTALLED_SNAP_ID, + method: 'endTrace', + }).catch(logError); + }; + return ( { Track error + + + + {JSON.stringify(data, null, 2)}