diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json
index 2835f676c3..bb85919513 100644
--- a/packages/examples/packages/browserify-plugin/snap.manifest.json
+++ b/packages/examples/packages/browserify-plugin/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "D5uPpprRmrMeqwvti5+Sy5PxuCdyFlMw8PA+7OlP/WY=",
+ "shasum": "LciQX9cjRXeSErEsKRm9OEFW8jAq9doqaa9b/TfGN3Y=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json
index d92a96e851..f3ae515fcb 100644
--- a/packages/examples/packages/browserify/snap.manifest.json
+++ b/packages/examples/packages/browserify/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "87AZ5qZxOtBTdc4cRBbCRTX5vYhIlhSSJgyUc8WWi1A=",
+ "shasum": "83SohbQ4Vp/wI2lFXL6tyuvWy7bLcRSL3yZikZEZrAg=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/snaps-controllers/coverage.json b/packages/snaps-controllers/coverage.json
index c10c3a0f6a..4709341243 100644
--- a/packages/snaps-controllers/coverage.json
+++ b/packages/snaps-controllers/coverage.json
@@ -1,6 +1,6 @@
{
- "branches": 92.73,
- "functions": 96.65,
- "lines": 97.99,
- "statements": 97.69
+ "branches": 92.85,
+ "functions": 96.71,
+ "lines": 98,
+ "statements": 97.7
}
diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx
index a3561840df..fb88438897 100644
--- a/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx
+++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx
@@ -1,5 +1,13 @@
+import { getPersistentState } from '@metamask/base-controller';
import type { SnapId } from '@metamask/snaps-sdk';
-import { form, image, input, panel, text } from '@metamask/snaps-sdk';
+import {
+ form,
+ image,
+ input,
+ panel,
+ text,
+ ContentType,
+} from '@metamask/snaps-sdk';
import {
Box,
Field,
@@ -29,13 +37,89 @@ jest.mock('@metamask/snaps-utils', () => ({
}));
describe('SnapInterfaceController', () => {
+ it('handles a notificationsListUpdated event where only stale notifications are deleted', async () => {
+ const rootMessenger = getRootSnapInterfaceControllerMessenger();
+ const controllerMessenger =
+ getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
+
+ const controller = new SnapInterfaceController({
+ messenger: controllerMessenger,
+ state: {
+ interfaces: {
+ // @ts-expect-error missing properties
+ '1': {
+ contentType: ContentType.Notification,
+ },
+ // @ts-expect-error missing properties
+ '2': {
+ contentType: ContentType.Dialog,
+ },
+ // @ts-expect-error missing properties
+ '3': {
+ contentType: ContentType.Notification,
+ },
+ },
+ },
+ });
+
+ rootMessenger.publish(
+ 'NotificationServicesController:notificationsListUpdated',
+ [{ type: 'snap', data: { detailedView: { interfaceId: '3' } } }],
+ );
+
+ expect(controller.state).toStrictEqual({
+ interfaces: {
+ '2': {
+ contentType: ContentType.Dialog,
+ },
+ '3': {
+ contentType: ContentType.Notification,
+ },
+ },
+ });
+ });
+
+ describe('constructor', () => {
+ it('persists notification interfaces', () => {
+ const rootMessenger = getRootSnapInterfaceControllerMessenger();
+ const controllerMessenger =
+ getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
+
+ const controller = new SnapInterfaceController({
+ messenger: controllerMessenger,
+ state: {
+ interfaces: {
+ // @ts-expect-error missing properties
+ '1': {
+ contentType: ContentType.Notification,
+ },
+ // @ts-expect-error missing properties
+ '2': {
+ contentType: ContentType.Dialog,
+ },
+ },
+ },
+ });
+
+ expect(
+ getPersistentState(controller.state, controller.metadata),
+ ).toStrictEqual({
+ interfaces: {
+ '1': {
+ contentType: ContentType.Notification,
+ },
+ },
+ });
+ });
+ });
+
describe('createInterface', () => {
it('can create a new interface', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -80,7 +164,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -130,7 +214,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -160,6 +244,41 @@ describe('SnapInterfaceController', () => {
expect(context).toStrictEqual({ foo: 'bar' });
});
+ it('supports providing an interface content type', async () => {
+ const rootMessenger = getRootSnapInterfaceControllerMessenger();
+ const controllerMessenger =
+ getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
+
+ // eslint-disable-next-line no-new
+ new SnapInterfaceController({
+ messenger: controllerMessenger,
+ });
+
+ const element = (
+
+
+ foo
+
+
+ );
+
+ const id = await rootMessenger.call(
+ 'SnapInterfaceController:createInterface',
+ MOCK_SNAP_ID,
+ element,
+ { foo: 'bar' },
+ ContentType.Notification,
+ );
+
+ const { contentType } = rootMessenger.call(
+ 'SnapInterfaceController:getInterface',
+ MOCK_SNAP_ID,
+ id,
+ );
+
+ expect(contentType).toStrictEqual(ContentType.Notification);
+ });
+
it('throws if interface context is too large', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger =
@@ -170,7 +289,7 @@ describe('SnapInterfaceController', () => {
.mockReturnValueOnce(1)
.mockReturnValueOnce(10_000_000);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -210,7 +329,7 @@ describe('SnapInterfaceController', () => {
() => ({ result: true, type: 'all' }),
);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -260,7 +379,7 @@ describe('SnapInterfaceController', () => {
() => ({ result: true, type: 'all' }),
);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -302,7 +421,7 @@ describe('SnapInterfaceController', () => {
jest.mocked(getJsonSizeUnsafe).mockReturnValueOnce(11_000_000);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -327,7 +446,7 @@ describe('SnapInterfaceController', () => {
jest.mocked(getJsonSizeUnsafe).mockReturnValueOnce(11_000_000);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -354,7 +473,7 @@ describe('SnapInterfaceController', () => {
false,
);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -377,7 +496,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -407,7 +526,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -437,7 +556,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -458,7 +577,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -501,7 +620,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -550,7 +669,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -604,7 +723,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -667,7 +786,7 @@ describe('SnapInterfaceController', () => {
() => ({ result: true, type: 'all' }),
);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -729,7 +848,7 @@ describe('SnapInterfaceController', () => {
() => ({ result: true, type: 'all' }),
);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -788,7 +907,7 @@ describe('SnapInterfaceController', () => {
.mockReturnValueOnce(1)
.mockReturnValueOnce(11_000_000);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -826,7 +945,7 @@ describe('SnapInterfaceController', () => {
.mockReturnValueOnce(1)
.mockReturnValueOnce(11_000_000);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -866,7 +985,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -899,7 +1018,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -921,7 +1040,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -956,7 +1075,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -991,7 +1110,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -1043,7 +1162,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -1074,7 +1193,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -1118,7 +1237,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -1150,7 +1269,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
@@ -1195,7 +1314,7 @@ describe('SnapInterfaceController', () => {
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
- /* eslint-disable-next-line no-new */
+ // eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});
diff --git a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts
index cca147b9d8..e12bc3ab14 100644
--- a/packages/snaps-controllers/src/interface/SnapInterfaceController.ts
+++ b/packages/snaps-controllers/src/interface/SnapInterfaceController.ts
@@ -18,10 +18,11 @@ import type {
ComponentOrElement,
InterfaceContext,
} from '@metamask/snaps-sdk';
+import { ContentType } from '@metamask/snaps-sdk';
import type { JSXElement } from '@metamask/snaps-sdk/jsx';
import { getJsonSizeUnsafe, validateJsxLinks } from '@metamask/snaps-utils';
import type { Json } from '@metamask/utils';
-import { assert } from '@metamask/utils';
+import { assert, hasProperty } from '@metamask/utils';
import { castDraft } from 'immer';
import { nanoid } from 'nanoid';
@@ -93,8 +94,38 @@ export type SnapInterfaceControllerStateChangeEvent =
SnapInterfaceControllerState
>;
+type OtherNotification = { type: string; [key: string]: unknown };
+
+export type ExpandedView = {
+ title: string;
+ interfaceId: string;
+ footerLink?: { href: string; text: string };
+};
+
+type NormalSnapNotificationData = { message: string; origin: string };
+
+type ExpandedSnapNotificationData = {
+ message: string;
+ origin: string;
+ detailedView: ExpandedView;
+};
+
+type SnapNotification = {
+ type: 'snap';
+ data: NormalSnapNotificationData | ExpandedSnapNotificationData;
+ readDate: string | null;
+};
+
+type Notification = OtherNotification | SnapNotification;
+
+type NotificationListUpdatedEvent = {
+ type: 'NotificationServicesController:notificationsListUpdated';
+ payload: [Notification[]];
+};
+
export type SnapInterfaceControllerEvents =
- SnapInterfaceControllerStateChangeEvent;
+ | SnapInterfaceControllerStateChangeEvent
+ | NotificationListUpdatedEvent;
export type SnapInterfaceControllerMessenger = RestrictedControllerMessenger<
typeof controllerName,
@@ -109,6 +140,7 @@ export type StoredInterface = {
content: JSXElement;
state: InterfaceState;
context: InterfaceContext | null;
+ contentType: ContentType | null;
};
export type SnapInterfaceControllerState = {
@@ -132,12 +164,32 @@ export class SnapInterfaceController extends BaseController<
super({
messenger,
metadata: {
- interfaces: { persist: false, anonymous: false },
+ interfaces: {
+ persist: (interfaces: Record) => {
+ return Object.entries(interfaces).reduce<
+ Record
+ >((persistedInterfaces, [id, snapInterface]) => {
+ switch (snapInterface.contentType) {
+ case ContentType.Notification:
+ persistedInterfaces[id] = snapInterface;
+ return persistedInterfaces;
+ default:
+ return persistedInterfaces;
+ }
+ }, {});
+ },
+ anonymous: false,
+ },
},
name: controllerName,
state: { interfaces: {}, ...state },
});
+ this.messagingSystem.subscribe(
+ 'NotificationServicesController:notificationsListUpdated',
+ this.#onNotificationsListUpdated.bind(this),
+ );
+
this.#registerMessageHandlers();
}
@@ -183,12 +235,14 @@ export class SnapInterfaceController extends BaseController<
* @param snapId - The snap id that created the interface.
* @param content - The interface content.
* @param context - An optional interface context object.
+ * @param contentType - The type of content.
* @returns The newly interface id.
*/
async createInterface(
snapId: SnapId,
content: ComponentOrElement,
context?: InterfaceContext,
+ contentType?: ContentType,
) {
const element = getJsxInterface(content);
await this.#validateContent(element);
@@ -205,6 +259,7 @@ export class SnapInterfaceController extends BaseController<
content: castDraft(element),
state: componentState,
context: context ?? null,
+ contentType: contentType ?? null,
};
});
@@ -393,4 +448,36 @@ export class SnapInterfaceController extends BaseController<
(id: string) => this.messagingSystem.call('SnapController:get', id),
);
}
+
+ #onNotificationsListUpdated(notificationsList: Notification[]) {
+ const snapNotificationsWithInterface = notificationsList.filter(
+ (notification) => {
+ return (
+ notification.type === 'snap' &&
+ hasProperty((notification as SnapNotification).data, 'detailedView')
+ );
+ },
+ );
+
+ const interfaceIdSet = new Set(
+ snapNotificationsWithInterface.map(
+ (notification) =>
+ (
+ (notification as SnapNotification)
+ .data as ExpandedSnapNotificationData
+ ).detailedView.interfaceId,
+ ),
+ );
+
+ this.update((state) => {
+ Object.entries(state.interfaces).forEach(([id, snapInterface]) => {
+ if (
+ snapInterface.contentType === ContentType.Notification &&
+ !interfaceIdSet.has(id)
+ ) {
+ delete state.interfaces[id];
+ }
+ });
+ });
+ }
}
diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts
index f7e24db998..4fcea6f3fb 100644
--- a/packages/snaps-controllers/src/snaps/SnapController.ts
+++ b/packages/snaps-controllers/src/snaps/SnapController.ts
@@ -46,6 +46,7 @@ import type {
RequestSnapsResult,
SnapId,
ComponentOrElement,
+ ContentType,
} from '@metamask/snaps-sdk';
import { AuxiliaryFileEncoding, getErrorMessage } from '@metamask/snaps-sdk';
import type {
@@ -3369,16 +3370,20 @@ export class SnapController extends BaseController<
*
* @param snapId - The snap ID.
* @param content - The initial interface content.
+ * @param contentType - The type of content.
* @returns An identifier that can be used to identify the interface.
*/
async #createInterface(
snapId: SnapId,
content: ComponentOrElement,
+ contentType?: ContentType,
): Promise {
return this.messagingSystem.call(
'SnapInterfaceController:createInterface',
snapId,
content,
+ undefined,
+ contentType,
);
}
@@ -3416,7 +3421,6 @@ export class SnapController extends BaseController<
// If a handler returns static content, we turn it into a dynamic UI
if (castResult && hasProperty(castResult, 'content')) {
const { content, ...rest } = castResult;
-
const id = await this.#createInterface(
snapId,
content as ComponentOrElement,
diff --git a/packages/snaps-controllers/src/test-utils/controller.ts b/packages/snaps-controllers/src/test-utils/controller.ts
index 52978aacaf..4bea263330 100644
--- a/packages/snaps-controllers/src/test-utils/controller.ts
+++ b/packages/snaps-controllers/src/test-utils/controller.ts
@@ -45,6 +45,7 @@ import type {
import type {
SnapInterfaceControllerActions,
SnapInterfaceControllerAllowedActions,
+ SnapInterfaceControllerEvents,
StoredInterface,
} from '../interface/SnapInterfaceController';
import type {
@@ -739,7 +740,7 @@ export const getRestrictedSnapsRegistryControllerMessenger = (
export const getRootSnapInterfaceControllerMessenger = () => {
const messenger = new MockControllerMessenger<
SnapInterfaceControllerActions | SnapInterfaceControllerAllowedActions,
- never
+ SnapInterfaceControllerEvents
>();
jest.spyOn(messenger, 'call');
@@ -756,7 +757,7 @@ export const getRestrictedSnapInterfaceControllerMessenger = (
const snapInterfaceControllerMessenger = messenger.getRestricted<
'SnapInterfaceController',
SnapInterfaceControllerAllowedActions['type'],
- never
+ SnapInterfaceControllerEvents['type']
>({
name: 'SnapInterfaceController',
allowedActions: [
@@ -765,7 +766,10 @@ export const getRestrictedSnapInterfaceControllerMessenger = (
'ApprovalController:hasRequest',
'ApprovalController:acceptRequest',
],
- allowedEvents: [],
+ allowedEvents: [
+ 'NotificationServicesController:notificationsListUpdated',
+ 'SnapInterfaceController:stateChange',
+ ],
});
if (mocked) {
diff --git a/packages/snaps-rpc-methods/src/permitted/createInterface.ts b/packages/snaps-rpc-methods/src/permitted/createInterface.ts
index 6dc66c53fb..1dd983036f 100644
--- a/packages/snaps-rpc-methods/src/permitted/createInterface.ts
+++ b/packages/snaps-rpc-methods/src/permitted/createInterface.ts
@@ -7,6 +7,7 @@ import type {
JsonRpcRequest,
ComponentOrElement,
InterfaceContext,
+ ContentType,
} from '@metamask/snaps-sdk';
import {
ComponentOrElementStruct,
@@ -30,6 +31,7 @@ export type CreateInterfaceMethodHooks = {
createInterface: (
ui: ComponentOrElement,
context?: InterfaceContext,
+ contentType?: ContentType,
) => Promise;
};
diff --git a/packages/snaps-rpc-methods/src/restricted/dialog.ts b/packages/snaps-rpc-methods/src/restricted/dialog.ts
index d4b5fb6f78..f9091b6c78 100644
--- a/packages/snaps-rpc-methods/src/restricted/dialog.ts
+++ b/packages/snaps-rpc-methods/src/restricted/dialog.ts
@@ -18,6 +18,8 @@ import type {
SnapId,
PromptDialog,
ComponentOrElement,
+ InterfaceContext,
+ ContentType,
} from '@metamask/snaps-sdk';
import type { InferMatching } from '@metamask/snaps-utils';
import type { Infer } from '@metamask/superstruct';
@@ -61,6 +63,8 @@ type RequestUserApproval = (
type CreateInterface = (
snapId: string,
content: ComponentOrElement,
+ context?: InterfaceContext,
+ contentType?: ContentType,
) => Promise;
type GetInterface = (
diff --git a/packages/snaps-sdk/src/types/interface.ts b/packages/snaps-sdk/src/types/interface.ts
index 3e8290cb3c..a587a0283c 100644
--- a/packages/snaps-sdk/src/types/interface.ts
+++ b/packages/snaps-sdk/src/types/interface.ts
@@ -45,3 +45,10 @@ export const ComponentOrElementStruct = selectiveUnion((value) => {
export const InterfaceContextStruct = record(string(), JsonStruct);
export type InterfaceContext = Infer;
+
+export enum ContentType {
+ Insight = 'Insight',
+ Dialog = 'Dialog',
+ Notification = 'Notification',
+ HomePage = 'HomePage',
+}
diff --git a/packages/snaps-simulation/src/controllers.ts b/packages/snaps-simulation/src/controllers.ts
index 804279c36d..0877480b7d 100644
--- a/packages/snaps-simulation/src/controllers.ts
+++ b/packages/snaps-simulation/src/controllers.ts
@@ -85,7 +85,9 @@ export function getControllers(options: GetControllersOptions): Controllers {
'ApprovalController:hasRequest',
'ApprovalController:acceptRequest',
],
- allowedEvents: [],
+ allowedEvents: [
+ 'NotificationServicesController:notificationsListUpdated',
+ ],
}),
});
diff --git a/packages/snaps-simulation/src/methods/hooks/interface.test.ts b/packages/snaps-simulation/src/methods/hooks/interface.test.ts
index ffad7cdaee..59bbe419d7 100644
--- a/packages/snaps-simulation/src/methods/hooks/interface.test.ts
+++ b/packages/snaps-simulation/src/methods/hooks/interface.test.ts
@@ -36,6 +36,7 @@ describe('getCreateInterfaceImplementation', () => {
MOCK_SNAP_ID,
content,
undefined,
+ undefined,
);
expect(result.content).toStrictEqual(getJsxElementFromComponent(content));
@@ -71,6 +72,7 @@ describe('getGetInterfaceImplementation', () => {
state: {},
snapId: MOCK_SNAP_ID,
context: null,
+ contentType: null,
});
});
});
diff --git a/packages/snaps-simulation/src/methods/hooks/interface.ts b/packages/snaps-simulation/src/methods/hooks/interface.ts
index 50a1343117..228742a338 100644
--- a/packages/snaps-simulation/src/methods/hooks/interface.ts
+++ b/packages/snaps-simulation/src/methods/hooks/interface.ts
@@ -1,4 +1,9 @@
-import type { Component, InterfaceContext, SnapId } from '@metamask/snaps-sdk';
+import type {
+ Component,
+ ContentType,
+ InterfaceContext,
+ SnapId,
+} from '@metamask/snaps-sdk';
import type { RootControllerMessenger } from '../../controllers';
@@ -15,12 +20,14 @@ export function getCreateInterfaceImplementation(
snapId: SnapId,
content: Component,
context?: InterfaceContext,
+ contentType?: ContentType,
) =>
controllerMessenger.call(
'SnapInterfaceController:createInterface',
snapId,
content,
context,
+ contentType,
);
}
diff --git a/packages/snaps-simulation/src/test-utils/controller.ts b/packages/snaps-simulation/src/test-utils/controller.ts
index 6913a30eb6..ac264f7cff 100644
--- a/packages/snaps-simulation/src/test-utils/controller.ts
+++ b/packages/snaps-simulation/src/test-utils/controller.ts
@@ -1,5 +1,8 @@
import { PhishingDetectorResultType } from '@metamask/phishing-controller';
-import type { SnapInterfaceControllerAllowedActions } from '@metamask/snaps-controllers';
+import type {
+ SnapInterfaceControllerAllowedActions,
+ SnapInterfaceControllerEvents,
+} from '@metamask/snaps-controllers';
import { MockControllerMessenger } from '@metamask/snaps-utils/test-utils';
import type { RootControllerAllowedActions } from '../controllers';
@@ -47,7 +50,8 @@ export const getRestrictedSnapInterfaceControllerMessenger = (
) => {
const snapInterfaceControllerMessenger = messenger.getRestricted<
'SnapInterfaceController',
- SnapInterfaceControllerAllowedActions['type']
+ SnapInterfaceControllerAllowedActions['type'],
+ SnapInterfaceControllerEvents['type']
>({
name: 'SnapInterfaceController',
allowedActions: [
@@ -56,7 +60,7 @@ export const getRestrictedSnapInterfaceControllerMessenger = (
'ApprovalController:hasRequest',
'ApprovalController:acceptRequest',
],
- allowedEvents: [],
+ allowedEvents: ['NotificationServicesController:notificationsListUpdated'],
});
return snapInterfaceControllerMessenger;
diff --git a/packages/snaps-simulator/src/features/simulation/hooks.test.ts b/packages/snaps-simulator/src/features/simulation/hooks.test.ts
index 3efee20f20..a7afbe0434 100644
--- a/packages/snaps-simulator/src/features/simulation/hooks.test.ts
+++ b/packages/snaps-simulator/src/features/simulation/hooks.test.ts
@@ -336,6 +336,7 @@ describe('updateInterface', () => {
snapId: snapId as SnapId,
content: Box({ children: Text({ children: 'foo' }) }),
context: null,
+ contentType: null,
}),
)
.silentRun();
diff --git a/packages/snaps-simulator/src/features/simulation/sagas.ts b/packages/snaps-simulator/src/features/simulation/sagas.ts
index 8b0f64d7a4..f3184ae0ef 100644
--- a/packages/snaps-simulator/src/features/simulation/sagas.ts
+++ b/packages/snaps-simulator/src/features/simulation/sagas.ts
@@ -194,7 +194,9 @@ export function* initSaga({ payload }: PayloadAction) {
`PhishingController:testOrigin`,
`PhishingController:maybeUpdateState`,
],
- allowedEvents: [],
+ allowedEvents: [
+ 'NotificationServicesController:notificationsListUpdated',
+ ],
}),
});
diff --git a/packages/snaps-simulator/src/features/simulation/test/controllers.ts b/packages/snaps-simulator/src/features/simulation/test/controllers.ts
index a28673c866..fd0e581bdd 100644
--- a/packages/snaps-simulator/src/features/simulation/test/controllers.ts
+++ b/packages/snaps-simulator/src/features/simulation/test/controllers.ts
@@ -20,7 +20,9 @@ export function getSnapInterfaceController() {
'PhishingController:maybeUpdateState',
'PhishingController:testOrigin',
],
- allowedEvents: [],
+ allowedEvents: [
+ 'NotificationServicesController:notificationsListUpdated',
+ ],
}),
});
}