Skip to content

Commit 126990b

Browse files
feat: Emit snapInstalled and snapUpdated for preinstalled Snaps (#2900)
Fixes an oversight where preinstalled Snaps would not emit `snapInstalled` and `snapUpdated`. This would cause lifecycle hooks among other things to not work. This PR also adds a `preinstalled` flag to the events, this will be necessary to not collect metrics for installation/update of preinstalled Snaps. Additionally, this PR fixes an oversight where preinstalled Snaps could have the manual update flow triggered. For now, we disallow this behaviour. Progresses #2899
1 parent 406f7a2 commit 126990b

File tree

3 files changed

+123
-6
lines changed

3 files changed

+123
-6
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 92.85,
2+
"branches": 92.89,
33
"functions": 96.71,
44
"lines": 98,
5-
"statements": 97.7
5+
"statements": 97.71
66
}

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,7 @@ describe('SnapController', () => {
966966
'SnapController:snapInstalled',
967967
getTruncatedSnap(),
968968
MOCK_ORIGIN,
969+
false,
969970
);
970971

971972
snapController.destroy();
@@ -4665,6 +4666,7 @@ describe('SnapController', () => {
46654666
it('supports preinstalled snaps', async () => {
46664667
const rootMessenger = getControllerMessenger();
46674668
jest.spyOn(rootMessenger, 'call');
4669+
jest.spyOn(rootMessenger, 'publish');
46684670

46694671
// The snap should not have permission initially
46704672
rootMessenger.registerActionHandler(
@@ -4711,6 +4713,13 @@ describe('SnapController', () => {
47114713
},
47124714
);
47134715

4716+
expect(rootMessenger.publish).toHaveBeenCalledWith(
4717+
'SnapController:snapInstalled',
4718+
getTruncatedSnap(),
4719+
'metamask',
4720+
true,
4721+
);
4722+
47144723
// After install the snap should have permissions
47154724
rootMessenger.registerActionHandler(
47164725
'PermissionController:getPermissions',
@@ -4880,6 +4889,7 @@ describe('SnapController', () => {
48804889
it('supports updating preinstalled snaps', async () => {
48814890
const rootMessenger = getControllerMessenger();
48824891
jest.spyOn(rootMessenger, 'call');
4892+
jest.spyOn(rootMessenger, 'publish');
48834893

48844894
const preinstalledSnaps = [
48854895
{
@@ -4934,6 +4944,21 @@ describe('SnapController', () => {
49344944
},
49354945
);
49364946

4947+
expect(rootMessenger.publish).toHaveBeenCalledWith(
4948+
'SnapController:snapUpdated',
4949+
getTruncatedSnap({
4950+
version: '1.2.3',
4951+
initialPermissions: {
4952+
'endowment:rpc': { dapps: false, snaps: true },
4953+
// eslint-disable-next-line @typescript-eslint/naming-convention
4954+
snap_getEntropy: {},
4955+
},
4956+
}),
4957+
'1.0.0',
4958+
'metamask',
4959+
true,
4960+
);
4961+
49374962
// After install the snap should have permissions
49384963
rootMessenger.registerActionHandler(
49394964
'PermissionController:getPermissions',
@@ -5092,6 +5117,66 @@ describe('SnapController', () => {
50925117
snapController.destroy();
50935118
});
50945119

5120+
it('disallows manual updates of preinstalled snaps', async () => {
5121+
const rootMessenger = getControllerMessenger();
5122+
jest.spyOn(rootMessenger, 'call');
5123+
5124+
// The snap should not have permissions initially
5125+
rootMessenger.registerActionHandler(
5126+
'PermissionController:getPermissions',
5127+
() => ({}),
5128+
);
5129+
5130+
const preinstalledSnaps = [
5131+
{
5132+
snapId: MOCK_SNAP_ID,
5133+
manifest: getSnapManifest(),
5134+
files: [
5135+
{
5136+
path: DEFAULT_SOURCE_PATH,
5137+
value: stringToBytes(DEFAULT_SNAP_BUNDLE),
5138+
},
5139+
{
5140+
path: DEFAULT_ICON_PATH,
5141+
value: stringToBytes(DEFAULT_SNAP_ICON),
5142+
},
5143+
],
5144+
},
5145+
];
5146+
5147+
const snapControllerOptions = getSnapControllerWithEESOptions({
5148+
preinstalledSnaps,
5149+
rootMessenger,
5150+
});
5151+
const [snapController] = getSnapControllerWithEES(snapControllerOptions);
5152+
5153+
// After install the snap should have permissions
5154+
rootMessenger.registerActionHandler(
5155+
'PermissionController:getPermissions',
5156+
() => MOCK_SNAP_PERMISSIONS,
5157+
);
5158+
5159+
const { manifest } = await getMockSnapFilesWithUpdatedChecksum({
5160+
manifest: getSnapManifest({
5161+
version: '1.1.0' as SemVerVersion,
5162+
}),
5163+
});
5164+
5165+
const detectSnapLocation = loopbackDetect({
5166+
manifest: manifest.result,
5167+
});
5168+
5169+
await expect(
5170+
snapController.updateSnap(
5171+
MOCK_ORIGIN,
5172+
MOCK_SNAP_ID,
5173+
detectSnapLocation(),
5174+
),
5175+
).rejects.toThrow('Preinstalled Snaps cannot be manually updated.');
5176+
5177+
snapController.destroy();
5178+
});
5179+
50955180
it('supports preinstalled Snaps specifying the hidden flag', async () => {
50965181
const rootMessenger = getControllerMessenger();
50975182
jest.spyOn(rootMessenger, 'call');
@@ -6772,6 +6857,7 @@ describe('SnapController', () => {
67726857
newSnapTruncated,
67736858
'1.0.0',
67746859
MOCK_ORIGIN,
6860+
false,
67756861
);
67766862

67776863
controller.destroy();

packages/snaps-controllers/src/snaps/SnapController.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ export type SnapInstallFailed = {
476476
*/
477477
export type SnapInstalled = {
478478
type: `${typeof controllerName}:snapInstalled`;
479-
payload: [snap: TruncatedSnap, origin: string];
479+
payload: [snap: TruncatedSnap, origin: string, preinstalled: boolean];
480480
};
481481

482482
/**
@@ -500,7 +500,12 @@ export type SnapUnblocked = {
500500
*/
501501
export type SnapUpdated = {
502502
type: `${typeof controllerName}:snapUpdated`;
503-
payload: [snap: TruncatedSnap, oldVersion: string, origin: string];
503+
payload: [
504+
snap: TruncatedSnap,
505+
oldVersion: string,
506+
origin: string,
507+
preinstalled: boolean,
508+
];
504509
};
505510

506511
/**
@@ -1229,6 +1234,24 @@ export class SnapController extends BaseController<
12291234
this.update((state) => {
12301235
state.snaps[snapId].status = SnapStatus.Stopped;
12311236
});
1237+
1238+
// Emit events
1239+
if (isUpdate) {
1240+
this.messagingSystem.publish(
1241+
'SnapController:snapUpdated',
1242+
this.getTruncatedExpect(snapId),
1243+
existingSnap.version,
1244+
'metamask',
1245+
true,
1246+
);
1247+
} else {
1248+
this.messagingSystem.publish(
1249+
'SnapController:snapInstalled',
1250+
this.getTruncatedExpect(snapId),
1251+
'metamask',
1252+
true,
1253+
);
1254+
}
12321255
}
12331256
}
12341257

@@ -2323,6 +2346,7 @@ export class SnapController extends BaseController<
23232346
`SnapController:snapInstalled`,
23242347
this.getTruncatedExpect(snapId),
23252348
origin,
2349+
false,
23262350
),
23272351
);
23282352

@@ -2332,6 +2356,7 @@ export class SnapController extends BaseController<
23322356
this.getTruncatedExpect(snapId),
23332357
oldVersion,
23342358
origin,
2359+
false,
23352360
),
23362361
);
23372362

@@ -2537,6 +2562,13 @@ export class SnapController extends BaseController<
25372562
): Promise<TruncatedSnap> {
25382563
this.#assertCanInstallSnaps();
25392564
this.#assertCanUsePlatform();
2565+
2566+
const snap = this.getExpect(snapId);
2567+
2568+
if (snap.preinstalled) {
2569+
throw new Error('Preinstalled Snaps cannot be manually updated.');
2570+
}
2571+
25402572
if (!isValidSemVerRange(newVersionRange)) {
25412573
throw new Error(
25422574
`Received invalid snap version range: "${newVersionRange}".`,
@@ -2557,8 +2589,6 @@ export class SnapController extends BaseController<
25572589
true,
25582590
);
25592591

2560-
const snap = this.getExpect(snapId);
2561-
25622592
const oldManifest = snap.manifest;
25632593

25642594
const newSnap = await fetchSnap(snapId, location);
@@ -2679,6 +2709,7 @@ export class SnapController extends BaseController<
26792709
truncatedSnap,
26802710
snap.version,
26812711
origin,
2712+
false,
26822713
);
26832714
}
26842715

0 commit comments

Comments
 (0)