Skip to content

Commit 943ff00

Browse files
committed
Add Snap export handler usage tracking
Use registry for metadata Replace controller with hook approach Fix broken unit tests Add unit tests Fix coverage after rebase Remove category Remove async from registry metadata and tracking Update throttling logic and tests Rearrange unit tests Update call to get registry metadata Remove unused getRegistryMetadata Refactor tracking call Add logic optimization for throttling function Skip preinstalled Snaps tracking Add error handling for MetaMetrics hook Add unit test for preinstalled snap
1 parent 88142f9 commit 943ff00

File tree

10 files changed

+541
-116
lines changed

10 files changed

+541
-116
lines changed
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 93.54,
3-
"functions": 97.38,
4-
"lines": 98.34,
5-
"statements": 98.08
2+
"branches": 93.64,
3+
"functions": 97.4,
4+
"lines": 98.36,
5+
"statements": 98.11
66
}

packages/snaps-controllers/src/snaps/SnapController.test.tsx

+190-70
Original file line numberDiff line numberDiff line change
@@ -4702,7 +4702,7 @@ describe('SnapController', () => {
47024702
},
47034703
});
47044704

4705-
expect(rootMessenger.call).toHaveBeenCalledTimes(4);
4705+
expect(rootMessenger.call).toHaveBeenCalledTimes(5);
47064706
expect(rootMessenger.call).toHaveBeenCalledWith(
47074707
'ExecutionService:handleRpcRequest',
47084708
MOCK_SNAP_ID,
@@ -9256,46 +9256,6 @@ describe('SnapController', () => {
92569256
});
92579257
});
92589258

9259-
describe('getRegistryMetadata', () => {
9260-
it('returns the metadata for a verified snap', async () => {
9261-
const registry = new MockSnapsRegistry();
9262-
const rootMessenger = getControllerMessenger(registry);
9263-
const messenger = getSnapControllerMessenger(rootMessenger);
9264-
registry.getMetadata.mockReturnValue({
9265-
name: 'Mock Snap',
9266-
});
9267-
9268-
const snapController = getSnapController(
9269-
getSnapControllerOptions({
9270-
messenger,
9271-
}),
9272-
);
9273-
9274-
expect(
9275-
await snapController.getRegistryMetadata(MOCK_SNAP_ID),
9276-
).toStrictEqual({
9277-
name: 'Mock Snap',
9278-
});
9279-
9280-
snapController.destroy();
9281-
});
9282-
9283-
it('returns null for a non-verified snap', async () => {
9284-
const registry = new MockSnapsRegistry();
9285-
const rootMessenger = getControllerMessenger(registry);
9286-
const messenger = getSnapControllerMessenger(rootMessenger);
9287-
const snapController = getSnapController(
9288-
getSnapControllerOptions({
9289-
messenger,
9290-
}),
9291-
);
9292-
9293-
expect(await snapController.getRegistryMetadata(MOCK_SNAP_ID)).toBeNull();
9294-
9295-
snapController.destroy();
9296-
});
9297-
});
9298-
92999259
describe('clearState', () => {
93009260
it('clears the state and terminates running snaps', async () => {
93019261
const rootMessenger = getControllerMessenger();
@@ -9458,6 +9418,195 @@ describe('SnapController', () => {
94589418

94599419
snapController.destroy();
94609420
});
9421+
9422+
it('should track event for allowed handler', async () => {
9423+
const mockTrackEvent = jest.fn();
9424+
const rootMessenger = getControllerMessenger();
9425+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9426+
getNodeEESMessenger(rootMessenger),
9427+
) as unknown as NodeThreadExecutionService;
9428+
9429+
const [snapController] = getSnapControllerWithEES(
9430+
getSnapControllerWithEESOptions({
9431+
rootMessenger,
9432+
trackEvent: mockTrackEvent,
9433+
state: {
9434+
snaps: getPersistedSnapsState(),
9435+
},
9436+
}),
9437+
executionEnvironmentStub,
9438+
);
9439+
9440+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9441+
await snapController.startSnap(snap.id);
9442+
9443+
await snapController.handleRequest({
9444+
snapId: snap.id,
9445+
origin: MOCK_ORIGIN,
9446+
handler: HandlerType.OnRpcRequest,
9447+
request: {
9448+
jsonrpc: '2.0',
9449+
method: 'test',
9450+
params: {},
9451+
id: 1,
9452+
},
9453+
});
9454+
9455+
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
9456+
expect(mockTrackEvent).toHaveBeenCalledWith({
9457+
event: 'SnapExportUsed',
9458+
category: 'Snaps',
9459+
properties: {
9460+
export: 'onRpcRequest',
9461+
origin: 'https://example.com',
9462+
// eslint-disable-next-line @typescript-eslint/naming-convention
9463+
snap_category: null,
9464+
// eslint-disable-next-line @typescript-eslint/naming-convention
9465+
snap_id: 'npm:@metamask/example-snap',
9466+
success: true,
9467+
},
9468+
});
9469+
snapController.destroy();
9470+
});
9471+
9472+
it('should not track event for disallowed handler', async () => {
9473+
const mockTrackEvent = jest.fn();
9474+
const rootMessenger = getControllerMessenger();
9475+
9476+
rootMessenger.registerActionHandler(
9477+
'PermissionController:getPermissions',
9478+
() => ({
9479+
[SnapEndowments.Cronjob]: {
9480+
caveats: [
9481+
{ type: SnapCaveatType.SnapCronjob, value: '* * * * *' },
9482+
],
9483+
date: 1664187844588,
9484+
id: 'izn0WGUO8cvq_jqvLQuQP',
9485+
invoker: MOCK_SNAP_ID,
9486+
parentCapability: SnapEndowments.Cronjob,
9487+
},
9488+
}),
9489+
);
9490+
9491+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9492+
getNodeEESMessenger(rootMessenger),
9493+
) as unknown as NodeThreadExecutionService;
9494+
9495+
const [snapController] = getSnapControllerWithEES(
9496+
getSnapControllerWithEESOptions({
9497+
environmentEndowmentPermissions: ['endowment:cronjob'],
9498+
rootMessenger,
9499+
trackEvent: mockTrackEvent,
9500+
state: {
9501+
snaps: getPersistedSnapsState(),
9502+
},
9503+
}),
9504+
executionEnvironmentStub,
9505+
);
9506+
9507+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9508+
await snapController.startSnap(snap.id);
9509+
9510+
await snapController.handleRequest({
9511+
snapId: snap.id,
9512+
origin: MOCK_ORIGIN,
9513+
handler: HandlerType.OnCronjob,
9514+
request: {
9515+
jsonrpc: '2.0',
9516+
method: 'test',
9517+
params: {},
9518+
id: 1,
9519+
},
9520+
});
9521+
9522+
expect(mockTrackEvent).not.toHaveBeenCalled();
9523+
snapController.destroy();
9524+
});
9525+
9526+
it('should properly handle error when MetaMetrics hook throws an error', async () => {
9527+
const log = jest.spyOn(console, 'error').mockImplementation();
9528+
const error = new Error('MetaMetrics hook error');
9529+
const mockTrackEvent = jest.fn().mockImplementation(() => {
9530+
throw error;
9531+
});
9532+
const rootMessenger = getControllerMessenger();
9533+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9534+
getNodeEESMessenger(rootMessenger),
9535+
) as unknown as NodeThreadExecutionService;
9536+
9537+
const [snapController] = getSnapControllerWithEES(
9538+
getSnapControllerWithEESOptions({
9539+
rootMessenger,
9540+
trackEvent: mockTrackEvent,
9541+
state: {
9542+
snaps: getPersistedSnapsState(),
9543+
},
9544+
}),
9545+
executionEnvironmentStub,
9546+
);
9547+
9548+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9549+
await snapController.startSnap(snap.id);
9550+
9551+
await snapController.handleRequest({
9552+
snapId: snap.id,
9553+
origin: MOCK_ORIGIN,
9554+
handler: HandlerType.OnRpcRequest,
9555+
request: {
9556+
jsonrpc: '2.0',
9557+
method: 'test',
9558+
params: {},
9559+
id: 1,
9560+
},
9561+
});
9562+
9563+
expect(mockTrackEvent).toHaveBeenCalled();
9564+
expect(log).toHaveBeenCalledWith(
9565+
expect.stringContaining(
9566+
'Error when calling MetaMetrics hook for snap',
9567+
),
9568+
);
9569+
snapController.destroy();
9570+
});
9571+
9572+
it('should not track event for preinstalled snap', async () => {
9573+
const mockTrackEvent = jest.fn();
9574+
const rootMessenger = getControllerMessenger();
9575+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9576+
getNodeEESMessenger(rootMessenger),
9577+
) as unknown as NodeThreadExecutionService;
9578+
9579+
const [snapController] = getSnapControllerWithEES(
9580+
getSnapControllerWithEESOptions({
9581+
rootMessenger,
9582+
trackEvent: mockTrackEvent,
9583+
state: {
9584+
snaps: getPersistedSnapsState(
9585+
getPersistedSnapObject({ preinstalled: true }),
9586+
),
9587+
},
9588+
}),
9589+
executionEnvironmentStub,
9590+
);
9591+
9592+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9593+
await snapController.startSnap(snap.id);
9594+
9595+
await snapController.handleRequest({
9596+
snapId: snap.id,
9597+
origin: MOCK_ORIGIN,
9598+
handler: HandlerType.OnRpcRequest,
9599+
request: {
9600+
jsonrpc: '2.0',
9601+
method: 'test',
9602+
params: {},
9603+
id: 1,
9604+
},
9605+
});
9606+
9607+
expect(mockTrackEvent).not.toHaveBeenCalled();
9608+
snapController.destroy();
9609+
});
94619610
});
94629611

94639612
it('handles a transaction insight request', async () => {
@@ -10430,35 +10579,6 @@ describe('SnapController', () => {
1043010579
});
1043110580
});
1043210581

10433-
describe('SnapController:getRegistryMetadata', () => {
10434-
it('calls SnapController.getRegistryMetadata()', async () => {
10435-
const registry = new MockSnapsRegistry();
10436-
const rootMessenger = getControllerMessenger(registry);
10437-
const messenger = getSnapControllerMessenger(rootMessenger);
10438-
10439-
registry.getMetadata.mockReturnValue({
10440-
name: 'Mock Snap',
10441-
});
10442-
10443-
const snapController = getSnapController(
10444-
getSnapControllerOptions({
10445-
messenger,
10446-
}),
10447-
);
10448-
10449-
expect(
10450-
await messenger.call(
10451-
'SnapController:getRegistryMetadata',
10452-
MOCK_SNAP_ID,
10453-
),
10454-
).toStrictEqual({
10455-
name: 'Mock Snap',
10456-
});
10457-
10458-
snapController.destroy();
10459-
});
10460-
});
10461-
1046210582
describe('SnapController:disconnectOrigin', () => {
1046310583
it('calls SnapController.removeSnapFromSubject()', () => {
1046410584
const messenger = getSnapControllerMessenger();

0 commit comments

Comments
 (0)