-
Notifications
You must be signed in to change notification settings - Fork 542
/
notify.ts
164 lines (146 loc) · 4.69 KB
/
notify.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import {
PermissionSpecificationBuilder,
PermissionType,
RestrictedMethodOptions,
ValidPermissionSpecification,
} from '@metamask/permission-controller';
import { NonEmptyArray, isObject } from '@metamask/utils';
import { ethErrors } from 'eth-rpc-errors';
const methodName = 'snap_notify';
// Move all the types to a shared place when implementing more notifications
export enum NotificationType {
Native = 'native',
InApp = 'inApp',
}
export type NotificationArgs = {
/**
* Enum type to determine notification type.
*/
type: NotificationType;
/**
* A message to show on the notification.
*/
message: string;
};
export type NotifyMethodHooks = {
/**
* @param snapId - The ID of the Snap that created the notification.
* @param args - The notification arguments.
*/
showNativeNotification: (
snapId: string,
args: NotificationArgs,
) => Promise<null>;
/**
* @param snapId - The ID of the Snap that created the notification.
* @param args - The notification arguments.
*/
showInAppNotification: (
snapId: string,
args: NotificationArgs,
) => Promise<null>;
};
type SpecificationBuilderOptions = {
allowedCaveats?: Readonly<NonEmptyArray<string>> | null;
methodHooks: NotifyMethodHooks;
};
type Specification = ValidPermissionSpecification<{
permissionType: PermissionType.RestrictedMethod;
targetKey: typeof methodName;
methodImplementation: ReturnType<typeof getImplementation>;
allowedCaveats: Readonly<NonEmptyArray<string>> | null;
}>;
/**
* The specification builder for the `snap_notify` permission.
* `snap_notify` allows snaps to send multiple types of notifications to its users.
*
* @param options - The specification builder options.
* @param options.allowedCaveats - The optional allowed caveats for the permission.
* @param options.methodHooks - The RPC method hooks needed by the method implementation.
* @returns The specification for the `snap_notify` permission.
*/
export const specificationBuilder: PermissionSpecificationBuilder<
PermissionType.RestrictedMethod,
SpecificationBuilderOptions,
Specification
> = ({ allowedCaveats = null, methodHooks }: SpecificationBuilderOptions) => {
return {
permissionType: PermissionType.RestrictedMethod,
targetKey: methodName,
allowedCaveats,
methodImplementation: getImplementation(methodHooks),
};
};
export const notifyBuilder = Object.freeze({
targetKey: methodName,
specificationBuilder,
methodHooks: {
showNativeNotification: true,
showInAppNotification: true,
},
} as const);
/**
* Builds the method implementation for `snap_notify`.
*
* @param hooks - The RPC method hooks.
* @param hooks.showNativeNotification - A function that shows a native browser notification.
* @param hooks.showInAppNotification - A function that shows a notification in the MetaMask UI.
* @returns The method implementation which returns `null` on success.
* @throws If the params are invalid.
*/
export function getImplementation({
showNativeNotification,
showInAppNotification,
}: NotifyMethodHooks) {
return async function implementation(
args: RestrictedMethodOptions<NotificationArgs>,
): Promise<null> {
const {
params,
context: { origin },
} = args;
const validatedParams = getValidatedParams(params);
switch (validatedParams.type) {
case NotificationType.Native:
return await showNativeNotification(origin, validatedParams);
case NotificationType.InApp:
return await showInAppNotification(origin, validatedParams);
default:
throw ethErrors.rpc.invalidParams({
message: 'Must specify a valid notification "type".',
});
}
};
}
/**
* Validates the notify method `params` and returns them cast to the correct
* type. Throws if validation fails.
*
* @param params - The unvalidated params object from the method request.
* @returns The validated method parameter object.
*/
export function getValidatedParams(params: unknown): NotificationArgs {
if (!isObject(params)) {
throw ethErrors.rpc.invalidParams({
message: 'Expected params to be a single object.',
});
}
const { type, message } = params;
if (
!type ||
typeof type !== 'string' ||
!(Object.values(NotificationType) as string[]).includes(type)
) {
throw ethErrors.rpc.invalidParams({
message: 'Must specify a valid notification "type".',
});
}
// Set to the max message length on a Mac notification for now.
if (!message || typeof message !== 'string' || message.length >= 50) {
throw ethErrors.rpc.invalidParams({
message:
'Must specify a non-empty string "message" less than 50 characters long.',
});
}
return params as NotificationArgs;
}