Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "hTwghLmR+2f8zwjEcFujq/ojCDoMnQRcYBPMJ73IqGE=",
"shasum": "/Wc+1sY2V4NwgUxQT81/nquwTBDsw2vdNEkkYd8g+0Q=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -18,6 +18,7 @@
},
"initialPermissions": {
"snap_dialog": {},
"snap_notify": {},
"endowment:lifecycle-hooks": {}
},
"platformVersion": "9.2.0",
Expand Down
41 changes: 41 additions & 0 deletions packages/examples/packages/lifecycle-hooks/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
OnActiveHandler,
OnInstallHandler,
OnStartHandler,
OnUpdateHandler,
Expand Down Expand Up @@ -85,3 +86,43 @@ export const onUpdate: OnUpdateHandler = async () => {
},
});
};

/**
* Handle activation of the client. This handler is called when the client is
* activated, and can be used to perform any initialization that is required.
*
* This handler is optional.
*
* @see https://docs.metamask.io/snaps/reference/entry-points/#onactive
* @returns The JSON-RPC response.
*/
export const onActive: OnActiveHandler = async () => {
return await snap.request({
method: 'snap_notify',
params: {
type: 'inApp',
message:
'The client was activated, and the "onActive" handler was called.',
},
});
};

/**
* Handle deactivation of the client. This handler is called when the client
* is deactivated, and can be used to perform any cleanup that is required.
*
* This handler is optional.
*
* @see https://docs.metamask.io/snaps/reference/entry-points/#oninactive
* @returns The JSON-RPC response.
*/
export const onInactive = async () => {
return await snap.request({
method: 'snap_notify',
params: {
type: 'inApp',
message:
'The client was deactivated, and the "onInactive" handler was called.',
},
});
};
183 changes: 181 additions & 2 deletions packages/snaps-controllers/src/snaps/SnapController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Caveat,
SubjectPermissions,
ValidPermission,
CaveatConstraint,
} from '@metamask/permission-controller';
import { SubjectType } from '@metamask/permission-controller';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
Expand Down Expand Up @@ -10336,7 +10337,11 @@ describe('SnapController', () => {

rootMessenger.registerActionHandler(
'PermissionController:getPermissions',
(origin) => {
(
origin,
): SubjectPermissions<
ValidPermission<TargetName, CaveatConstraint>
> => {
if (origin === MOCK_SNAP_ID) {
return {
[SnapEndowments.LifecycleHooks]:
Expand Down Expand Up @@ -10443,7 +10448,7 @@ describe('SnapController', () => {
await sleep(10);

expect(consoleErrorSpy).toHaveBeenCalledWith(
`Error when calling \`onStart\` lifecycle hook for Snap "npm:@metamask/example-snap": Test error in lifecycle hook.`,
`Error calling lifecycle hook "onStart" for Snap "npm:@metamask/example-snap": Test error in lifecycle hook.`,
);

snapController.destroy();
Expand Down Expand Up @@ -12369,4 +12374,178 @@ describe('SnapController', () => {
snapController.destroy();
});
});

describe('SnapController:setClientActive', () => {
it('calls the `onActive` lifecycle hook for all Snaps when called with `true`', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);

rootMessenger.registerActionHandler(
'PermissionController:hasPermission',
() => true,
);

rootMessenger.registerActionHandler(
'PermissionController:getPermissions',
(
origin,
): SubjectPermissions<ValidPermission<string, CaveatConstraint>> => {
if (origin === MOCK_SNAP_ID) {
return {
[SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION,
};
}

return {};
},
);

const manifest = getSnapManifest({
initialPermissions: {
[SnapEndowments.LifecycleHooks]: {},
},
});

const snapController = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(getPersistedSnapObject({ manifest })),
},
}),
);

messenger.call('SnapController:setClientActive', true);
await sleep(10);

expect(messenger.call).toHaveBeenNthCalledWith(
2,
'PermissionController:hasPermission',
MOCK_SNAP_ID,
SnapEndowments.LifecycleHooks,
);

expect(messenger.call).toHaveBeenNthCalledWith(
3,
'PermissionController:hasPermission',
MOCK_SNAP_ID,
SnapEndowments.LifecycleHooks,
);

expect(messenger.call).toHaveBeenNthCalledWith(
4,
'PermissionController:getPermissions',
MOCK_SNAP_ID,
);

expect(messenger.call).toHaveBeenNthCalledWith(
5,
'ExecutionService:executeSnap',
expect.any(Object),
);

expect(messenger.call).toHaveBeenNthCalledWith(
6,
'ExecutionService:handleRpcRequest',
MOCK_SNAP_ID,
{
handler: HandlerType.OnActive,
origin: METAMASK_ORIGIN,
request: {
jsonrpc: '2.0',
id: expect.any(String),
method: HandlerType.OnActive,
},
},
);

snapController.destroy();
});

it('calls the `onInactive` lifecycle hook for all Snaps when called with `false`', async () => {
const rootMessenger = getControllerMessenger();
const messenger = getSnapControllerMessenger(rootMessenger);

rootMessenger.registerActionHandler(
'PermissionController:hasPermission',
() => true,
);

rootMessenger.registerActionHandler(
'PermissionController:getPermissions',
(
origin,
): SubjectPermissions<ValidPermission<string, CaveatConstraint>> => {
if (origin === MOCK_SNAP_ID) {
return {
[SnapEndowments.LifecycleHooks]: MOCK_LIFECYCLE_HOOKS_PERMISSION,
};
}

return {};
},
);

const manifest = getSnapManifest({
initialPermissions: {
[SnapEndowments.LifecycleHooks]: {},
},
});

const snapController = getSnapController(
getSnapControllerOptions({
messenger,
state: {
snaps: getPersistedSnapsState(getPersistedSnapObject({ manifest })),
},
}),
);

messenger.call('SnapController:setClientActive', false);
await sleep(10);

expect(messenger.call).toHaveBeenNthCalledWith(
2,
'PermissionController:hasPermission',
MOCK_SNAP_ID,
SnapEndowments.LifecycleHooks,
);

expect(messenger.call).toHaveBeenNthCalledWith(
3,
'PermissionController:hasPermission',
MOCK_SNAP_ID,
SnapEndowments.LifecycleHooks,
);

expect(messenger.call).toHaveBeenNthCalledWith(
4,
'PermissionController:getPermissions',
MOCK_SNAP_ID,
);

expect(messenger.call).toHaveBeenNthCalledWith(
5,
'ExecutionService:executeSnap',
expect.any(Object),
);

expect(messenger.call).toHaveBeenNthCalledWith(
6,
'ExecutionService:handleRpcRequest',
MOCK_SNAP_ID,
{
handler: HandlerType.OnInactive,
origin: METAMASK_ORIGIN,
request: {
jsonrpc: '2.0',
id: expect.any(String),
method: HandlerType.OnInactive,
},
},
);

snapController.destroy();
});
});
});
Loading
Loading