diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json index 7e686d8893..76568b076a 100644 --- a/packages/snaps-controllers/coverage.json +++ b/packages/snaps-controllers/coverage.json @@ -1,6 +1,6 @@ { - "branches": 91.09, - "functions": 96.49, - "lines": 97.77, + "branches": 90.96, + "functions": 96.51, + "lines": 97.78, "statements": 97.46 } diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.ts b/packages/snaps-controllers/src/snaps/SnapController.test.ts index 14fccf759e..e5da3495e9 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.test.ts @@ -42,6 +42,7 @@ import { getMockSnapFilesWithUpdatedChecksum, MOCK_SNAP_NAME, DEFAULT_SOURCE_PATH, + DEFAULT_ICON_PATH, } from '@metamask/snaps-utils/test-utils'; import type { SemVerRange, SemVerVersion } from '@metamask/utils'; import { @@ -3497,6 +3498,10 @@ describe('SnapController', () => { path: DEFAULT_SOURCE_PATH, value: stringToBytes(DEFAULT_SNAP_BUNDLE), }, + { + path: DEFAULT_ICON_PATH, + value: stringToBytes(DEFAULT_SNAP_ICON), + }, ], }, ]; @@ -3561,6 +3566,10 @@ describe('SnapController', () => { path: DEFAULT_SOURCE_PATH, value: stringToBytes(DEFAULT_SNAP_BUNDLE), }, + { + path: DEFAULT_ICON_PATH, + value: stringToBytes(DEFAULT_SNAP_ICON), + }, ], }, ]; @@ -3653,6 +3662,105 @@ describe('SnapController', () => { snapController.destroy(); }); + it('supports localized preinstalled snaps', async () => { + const rootMessenger = getControllerMessenger(); + jest.spyOn(rootMessenger, 'call'); + + // The snap should not have permission initially + rootMessenger.registerActionHandler( + 'PermissionController:getPermissions', + () => ({}), + ); + + const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ + manifest: getSnapManifest({ + proposedName: '{{ proposedName }}', + locales: ['locales/en.json'], + }), + localizationFiles: [getMockLocalizationFile()], + }); + + const preinstalledSnaps = [ + { + snapId: MOCK_SNAP_ID, + manifest: manifest.result, + files: [ + { + path: DEFAULT_SOURCE_PATH, + value: stringToBytes(DEFAULT_SNAP_BUNDLE), + }, + { + path: DEFAULT_ICON_PATH, + value: stringToBytes(DEFAULT_SNAP_ICON), + }, + { + path: 'locales/en.json', + value: stringToBytes(JSON.stringify(getMockLocalizationFile())), + }, + ], + }, + ]; + + const snapControllerOptions = getSnapControllerWithEESOptions({ + preinstalledSnaps, + rootMessenger, + }); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + expect(rootMessenger.call).toHaveBeenCalledWith( + 'PermissionController:grantPermissions', + { + approvedPermissions: { + 'endowment:rpc': { + caveats: [ + { type: 'rpcOrigin', value: { dapps: false, snaps: true } }, + ], + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + snap_confirm: {}, + }, + subject: { origin: MOCK_SNAP_ID }, + }, + ); + + // After install the snap should have permissions + rootMessenger.registerActionHandler( + 'PermissionController:getPermissions', + () => MOCK_SNAP_PERMISSIONS, + ); + + const result = await snapController.handleRequest({ + snapId: MOCK_SNAP_ID, + origin: MOCK_ORIGIN, + request: { method: 'foo' }, + handler: HandlerType.OnRpcRequest, + }); + + expect(result).toContain('foo'); + + const { + manifest: installedManifest, + localizationFiles: installedLocalizationFiles, + } = snapController.state.snaps[MOCK_SNAP_ID]; + + assert(installedLocalizationFiles); + const localizedManifest = getLocalizedSnapManifest( + installedManifest, + 'en', + installedLocalizationFiles, + ); + + expect(localizedManifest).toStrictEqual( + getSnapManifest({ + proposedName: 'Example Snap', + locales: ['locales/en.json'], + shasum: manifest.result.source.shasum, + }), + ); + + snapController.destroy(); + }); + it('authorizes permissions needed for snaps', async () => { const manifest = getSnapManifest(); const rootMessenger = getControllerMessenger(); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index c06f722fc0..e3c258e9b8 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -1028,14 +1028,28 @@ export class SnapController extends BaseController< assert(sourceCode, 'Source code not provided for preinstalled snap.'); + assert( + !iconPath || (iconPath && svgIcon), + 'Icon not provided for preinstalled snap.', + ); + assert( manifest.source.files === undefined, 'Auxiliary files are not currently supported for preinstalled snaps.', ); + const localizationFiles = + manifest.source.locales?.map((path) => + virtualFiles.find((file) => file.path === path), + ) ?? []; + + const validatedLocalizationFiles = getValidatedLocalizationFiles( + localizationFiles.filter(Boolean) as VirtualFile[], + ); + assert( - manifest.source.locales === undefined, - 'Localization files are not currently supported for preinstalled snaps.', + localizationFiles.length === validatedLocalizationFiles.length, + 'Missing localization files for preinstalled snap.', ); const filesObject: FetchedSnapFiles = { @@ -1043,7 +1057,7 @@ export class SnapController extends BaseController< sourceCode, svgIcon, auxiliaryFiles: [], - localizationFiles: [], + localizationFiles: validatedLocalizationFiles, }; // Add snap to the SnapController state