Skip to content

Commit 0f0d718

Browse files
Add snap_getWebSockets
1 parent c05a5e5 commit 0f0d718

File tree

8 files changed

+229
-6
lines changed

8 files changed

+229
-6
lines changed

packages/snaps-execution-environments/coverage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"branches": 90,
33
"functions": 94.69,
44
"lines": 90.41,
5-
"statements": 89.64
5+
"statements": 89.63
66
}

packages/snaps-rpc-methods/jest.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, {
1010
],
1111
coverageThreshold: {
1212
global: {
13-
branches: 95.06,
14-
functions: 98.68,
15-
lines: 98.83,
16-
statements: 98.52,
13+
branches: 95.1,
14+
functions: 98.69,
15+
lines: 98.84,
16+
statements: 98.55,
1717
},
1818
},
1919
});
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
2+
import { rpcErrors } from '@metamask/rpc-errors';
3+
import type {
4+
GetWebSocketsParams,
5+
GetWebSocketsResult,
6+
} from '@metamask/snaps-sdk';
7+
import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils';
8+
9+
import { getWebSocketsHandler } from './getWebSockets';
10+
11+
describe('snap_getWebSockets', () => {
12+
describe('openWebSocketHandler', () => {
13+
it('has the expected shape', () => {
14+
expect(getWebSocketsHandler).toMatchObject({
15+
methodNames: ['snap_getWebSockets'],
16+
implementation: expect.any(Function),
17+
hookNames: {
18+
hasPermission: true,
19+
getWebSockets: true,
20+
},
21+
});
22+
});
23+
});
24+
25+
describe('implementation', () => {
26+
it('throws if the origin does not have permission', async () => {
27+
const { implementation } = getWebSocketsHandler;
28+
29+
const getWebSockets = jest.fn();
30+
const hasPermission = jest.fn().mockReturnValue(false);
31+
const hooks = { hasPermission, getWebSockets };
32+
33+
const engine = new JsonRpcEngine();
34+
35+
engine.push((request, response, next, end) => {
36+
const result = implementation(
37+
request as JsonRpcRequest<GetWebSocketsParams>,
38+
response as PendingJsonRpcResponse<GetWebSocketsResult>,
39+
next,
40+
end,
41+
hooks,
42+
);
43+
44+
result?.catch(end);
45+
});
46+
47+
const response = await engine.handle({
48+
jsonrpc: '2.0',
49+
id: 1,
50+
method: 'snap_getWebSockets',
51+
});
52+
53+
expect(response).toStrictEqual({
54+
error: {
55+
code: 4100,
56+
message:
57+
'The requested account and/or method has not been authorized by the user.',
58+
stack: expect.any(String),
59+
},
60+
id: 1,
61+
jsonrpc: '2.0',
62+
});
63+
});
64+
65+
it('returns the open WebSocket connections', async () => {
66+
const { implementation } = getWebSocketsHandler;
67+
68+
const getWebSockets = jest
69+
.fn()
70+
.mockReturnValue([{ id: 'foo', url: 'wss://metamask.io' }]);
71+
const hasPermission = jest.fn().mockReturnValue(true);
72+
const hooks = { hasPermission, getWebSockets };
73+
74+
const engine = new JsonRpcEngine();
75+
76+
engine.push((request, response, next, end) => {
77+
const result = implementation(
78+
request as JsonRpcRequest<GetWebSocketsParams>,
79+
response as PendingJsonRpcResponse<GetWebSocketsResult>,
80+
next,
81+
end,
82+
hooks,
83+
);
84+
85+
result?.catch(end);
86+
});
87+
88+
const response = await engine.handle({
89+
jsonrpc: '2.0',
90+
id: 1,
91+
method: 'snap_getWebSockets',
92+
});
93+
94+
expect(response).toStrictEqual({
95+
jsonrpc: '2.0',
96+
id: 1,
97+
result: [{ id: 'foo', url: 'wss://metamask.io' }],
98+
});
99+
expect(getWebSockets).toHaveBeenCalledWith();
100+
});
101+
102+
it('returns an error thrown by the hook', async () => {
103+
const { implementation } = getWebSocketsHandler;
104+
105+
const getWebSockets = jest.fn().mockImplementation(() => {
106+
throw rpcErrors.internal();
107+
});
108+
const hasPermission = jest.fn().mockReturnValue(true);
109+
const hooks = { hasPermission, getWebSockets };
110+
111+
const engine = new JsonRpcEngine();
112+
113+
engine.push((request, response, next, end) => {
114+
const result = implementation(
115+
request as JsonRpcRequest<GetWebSocketsParams>,
116+
response as PendingJsonRpcResponse<GetWebSocketsResult>,
117+
next,
118+
end,
119+
hooks,
120+
);
121+
122+
result?.catch(end);
123+
});
124+
125+
const response = await engine.handle({
126+
jsonrpc: '2.0',
127+
id: 1,
128+
method: 'snap_getWebSockets',
129+
});
130+
131+
expect(response).toStrictEqual({
132+
error: {
133+
code: -32603,
134+
message: 'Internal JSON-RPC error.',
135+
stack: expect.any(String),
136+
},
137+
id: 1,
138+
jsonrpc: '2.0',
139+
});
140+
});
141+
});
142+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
2+
import type { PermittedHandlerExport } from '@metamask/permission-controller';
3+
import { providerErrors } from '@metamask/rpc-errors';
4+
import type {
5+
GetWebSocketsParams,
6+
GetWebSocketsResult,
7+
JsonRpcRequest,
8+
} from '@metamask/snaps-sdk';
9+
import type { PendingJsonRpcResponse } from '@metamask/utils';
10+
11+
import { SnapEndowments } from '../endowments';
12+
import type { MethodHooksObject } from '../utils';
13+
14+
const hookNames: MethodHooksObject<GetWebSocketsMethodHooks> = {
15+
hasPermission: true,
16+
getWebSockets: true,
17+
};
18+
19+
export type GetWebSocketsMethodHooks = {
20+
hasPermission: (permissionName: string) => boolean;
21+
getWebSockets: () => GetWebSocketsResult;
22+
};
23+
24+
/**
25+
* Handler for the `snap_getWebSockets` method.
26+
*/
27+
export const getWebSocketsHandler: PermittedHandlerExport<
28+
GetWebSocketsMethodHooks,
29+
GetWebSocketsParams,
30+
GetWebSocketsResult
31+
> = {
32+
methodNames: ['snap_getWebSockets'],
33+
implementation: getWebSocketsImplementation,
34+
hookNames,
35+
};
36+
37+
/**
38+
* The `snap_getWebSockets` method implementation.
39+
*
40+
* @param _req - The JSON-RPC request object. Not used by this function.
41+
* @param res - The JSON-RPC response object.
42+
* @param _next - The `json-rpc-engine` "next" callback. Not used by this function.
43+
* @param end - The `json-rpc-engine` "end" callback.
44+
* @param hooks - The RPC method hooks.
45+
* @param hooks.hasPermission - The function to check if a snap has the `endowment:network-access` permission.
46+
* @param hooks.getWebSockets - The function to get the connected WebSockets for the origin.
47+
* @returns Nothing.
48+
*/
49+
function getWebSocketsImplementation(
50+
_req: JsonRpcRequest<GetWebSocketsParams>,
51+
res: PendingJsonRpcResponse<GetWebSocketsResult>,
52+
_next: unknown,
53+
end: JsonRpcEngineEndCallback,
54+
{ hasPermission, getWebSockets }: GetWebSocketsMethodHooks,
55+
): void {
56+
if (!hasPermission(SnapEndowments.NetworkAccess)) {
57+
return end(providerErrors.unauthorized());
58+
}
59+
60+
try {
61+
res.result = getWebSockets();
62+
} catch (error) {
63+
return end(error);
64+
}
65+
66+
return end();
67+
}

packages/snaps-rpc-methods/src/permitted/handlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getInterfaceContextHandler } from './getInterfaceContext';
1212
import { getInterfaceStateHandler } from './getInterfaceState';
1313
import { getSnapsHandler } from './getSnaps';
1414
import { getStateHandler } from './getState';
15+
import { getWebSocketsHandler } from './getWebSockets';
1516
import { invokeKeyringHandler } from './invokeKeyring';
1617
import { invokeSnapSugarHandler } from './invokeSnapSugar';
1718
import { listEntropySourcesHandler } from './listEntropySources';
@@ -51,6 +52,7 @@ export const methodHandlers = {
5152
snap_openWebSocket: openWebSocketHandler,
5253
snap_closeWebSocket: closeWebSocketHandler,
5354
snap_sendWebSocketMessage: sendWebSocketMessageHandler,
55+
snap_getWebSockets: getWebSocketsHandler,
5456
};
5557
/* eslint-enable @typescript-eslint/naming-convention */
5658

packages/snaps-rpc-methods/src/permitted/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { CancelBackgroundEventMethodHooks } from './cancelBackgroundEvent';
22
import type { ClearStateHooks } from './clearState';
3+
import type { CloseWebSocketMethodHooks } from './closeWebSocket';
34
import type { CreateInterfaceMethodHooks } from './createInterface';
45
import type { ProviderRequestMethodHooks } from './experimentalProviderRequest';
56
import type { GetAllSnapsHooks } from './getAllSnaps';
@@ -9,10 +10,13 @@ import type { GetCurrencyRateMethodHooks } from './getCurrencyRate';
910
import type { GetInterfaceStateMethodHooks } from './getInterfaceState';
1011
import type { GetSnapsHooks } from './getSnaps';
1112
import type { GetStateHooks } from './getState';
13+
import type { GetWebSocketsMethodHooks } from './getWebSockets';
1214
import type { ListEntropySourcesHooks } from './listEntropySources';
15+
import type { OpenWebSocketMethodHooks } from './openWebSocket';
1316
import type { RequestSnapsHooks } from './requestSnaps';
1417
import type { ResolveInterfaceMethodHooks } from './resolveInterface';
1518
import type { ScheduleBackgroundEventMethodHooks } from './scheduleBackgroundEvent';
19+
import type { SendWebSocketMessageMethodHooks } from './sendWebSocketMessage';
1620
import type { SetStateHooks } from './setState';
1721
import type { UpdateInterfaceMethodHooks } from './updateInterface';
1822

@@ -32,7 +36,11 @@ export type PermittedRpcMethodHooks = ClearStateHooks &
3236
ScheduleBackgroundEventMethodHooks &
3337
CancelBackgroundEventMethodHooks &
3438
GetBackgroundEventsMethodHooks &
35-
SetStateHooks;
39+
SetStateHooks &
40+
OpenWebSocketMethodHooks &
41+
CloseWebSocketMethodHooks &
42+
SendWebSocketMessageMethodHooks &
43+
GetWebSocketsMethodHooks;
3644

3745
export * from './handlers';
3846
export * from './middleware';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type GetWebSocketsParams = never;
2+
3+
export type GetWebSocketsResult = { id: string; url: string }[];

packages/snaps-sdk/src/types/methods/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ export type * from './track-event';
3333
export type * from './open-web-socket';
3434
export type * from './close-web-socket';
3535
export type * from './send-web-socket-message';
36+
export type * from './get-web-sockets';

0 commit comments

Comments
 (0)