-
Notifications
You must be signed in to change notification settings - Fork 542
/
requestSnaps.ts
157 lines (145 loc) · 4.66 KB
/
requestSnaps.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
import {
PermissionConstraint,
RequestedPermissions,
} from '@metamask/permission-controller';
import { getSnapPermissionName } from '@metamask/snaps-utils';
import {
PermittedHandlerExport,
JsonRpcRequest,
PendingJsonRpcResponse,
JsonRpcEngineEndCallback,
} from '@metamask/types';
import { hasProperty, isObject } from '@metamask/utils';
import { ethErrors } from 'eth-rpc-errors';
import {
handleInstallSnaps,
InstallSnapsHook,
InstallSnapsResult,
} from './common/snapInstallation';
/**
* `wallet_requestSnaps` installs the requested Snaps and requests permission to use them if necessary.
*/
export const requestSnapsHandler: PermittedHandlerExport<
RequestSnapsHooks,
RequestedPermissions,
InstallSnapsResult
> = {
methodNames: ['wallet_requestSnaps'],
implementation: requestSnapsImplementation,
hookNames: {
installSnaps: true,
requestPermissions: true,
getPermissions: true,
},
};
export type RequestSnapsHooks = {
/**
* Installs the requested snaps if they are permitted.
*/
installSnaps: InstallSnapsHook;
/**
* Initiates a permission request for the requesting origin.
*
* @returns The result of the permissions request.
*/
requestPermissions: (
permissions: RequestedPermissions,
) => Promise<PermissionConstraint[]>;
/**
* Gets the current permissions for the requesting origin.
*
* @returns The current permissions of the requesting origin.
*/
getPermissions: () => Promise<
Record<string, PermissionConstraint> | undefined
>;
};
/**
* Checks whether existing permissions satisfy the requested permissions
*
* Note: Currently, we don't compare caveats, if any caveats are requested, we always return false.
*
* @param existingPermissions - The existing permissions for the origin.
* @param requestedPermissions - The requested permissions for the origin.
* @returns True if the existing permissions satisfy the requested permissions, otherwise false.
*/
function hasPermissions(
existingPermissions: Record<string, PermissionConstraint>,
requestedPermissions: RequestedPermissions,
): boolean {
return Object.entries(requestedPermissions).every(
([target, requestedPermission]) => {
if (
requestedPermission?.caveats &&
requestedPermission.caveats.length > 0
) {
return false;
}
return hasProperty(existingPermissions, target);
},
);
}
/**
* The `wallet_requestSnaps` method implementation.
* Tries to install the requested snaps and adds them to the JSON-RPC response.
*
* @param req - The JSON-RPC request object.
* @param res - The JSON-RPC response object.
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.installSnaps - A function that tries to install a given snap, prompting the user if necessary.
* @param hooks.requestPermissions - A function that requests permissions on
* behalf of a subject.
* @param hooks.getPermissions - A function that gets the current permissions.
* @returns A promise that resolves once the JSON-RPC response has been modified.
* @throws If the params are invalid.
*/
async function requestSnapsImplementation(
req: JsonRpcRequest<RequestedPermissions>,
res: PendingJsonRpcResponse<InstallSnapsResult>,
_next: unknown,
end: JsonRpcEngineEndCallback,
{ installSnaps, requestPermissions, getPermissions }: RequestSnapsHooks,
): Promise<void> {
const requestedSnaps = req.params;
if (!isObject(requestedSnaps)) {
return end(
ethErrors.rpc.invalidParams({
message: '"params" must be an object.',
}),
);
}
// Request the permission for the installing DApp to talk to the snap, if needed
// TODO: Should this be part of the install flow?
try {
// We expect the params to be the same as wallet_requestPermissions
const requestedPermissions = Object.keys(requestedSnaps).reduce<
Record<string, Partial<PermissionConstraint>>
>((acc, key) => {
acc[getSnapPermissionName(key)] = requestedSnaps[key];
return acc;
}, {});
const existingPermissions = await getPermissions();
if (
!existingPermissions ||
!hasPermissions(existingPermissions, requestedPermissions)
) {
const approvedPermissions = await requestPermissions(
requestedPermissions,
);
if (!approvedPermissions?.length) {
throw ethErrors.provider.userRejectedRequest({ data: req });
}
}
} catch (error) {
return end(error);
}
try {
res.result = await handleInstallSnaps(requestedSnaps, installSnaps);
} catch (error) {
res.error = error;
}
return end();
}