@metamask/snaps-controllers 12.3.1 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/cronjob/CronjobController.cjs +250 -276
  3. package/dist/cronjob/CronjobController.cjs.map +1 -1
  4. package/dist/cronjob/CronjobController.d.cts +61 -78
  5. package/dist/cronjob/CronjobController.d.cts.map +1 -1
  6. package/dist/cronjob/CronjobController.d.mts +61 -78
  7. package/dist/cronjob/CronjobController.d.mts.map +1 -1
  8. package/dist/cronjob/CronjobController.mjs +251 -277
  9. package/dist/cronjob/CronjobController.mjs.map +1 -1
  10. package/dist/cronjob/utils.cjs +79 -0
  11. package/dist/cronjob/utils.cjs.map +1 -0
  12. package/dist/cronjob/utils.d.cts +25 -0
  13. package/dist/cronjob/utils.d.cts.map +1 -0
  14. package/dist/cronjob/utils.d.mts +25 -0
  15. package/dist/cronjob/utils.d.mts.map +1 -0
  16. package/dist/cronjob/utils.mjs +75 -0
  17. package/dist/cronjob/utils.mjs.map +1 -0
  18. package/dist/index.cjs +1 -0
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +1 -0
  21. package/dist/index.d.cts.map +1 -1
  22. package/dist/index.d.mts +1 -0
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +1 -0
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/insights/SnapInsightsController.cjs +199 -149
  27. package/dist/insights/SnapInsightsController.cjs.map +1 -1
  28. package/dist/insights/SnapInsightsController.mjs +198 -148
  29. package/dist/insights/SnapInsightsController.mjs.map +1 -1
  30. package/dist/interface/SnapInterfaceController.cjs +160 -101
  31. package/dist/interface/SnapInterfaceController.cjs.map +1 -1
  32. package/dist/interface/SnapInterfaceController.mjs +160 -101
  33. package/dist/interface/SnapInterfaceController.mjs.map +1 -1
  34. package/dist/multichain/MultichainRouter.cjs +117 -114
  35. package/dist/multichain/MultichainRouter.cjs.map +1 -1
  36. package/dist/multichain/MultichainRouter.mjs +117 -114
  37. package/dist/multichain/MultichainRouter.mjs.map +1 -1
  38. package/dist/services/AbstractExecutionService.cjs +131 -139
  39. package/dist/services/AbstractExecutionService.cjs.map +1 -1
  40. package/dist/services/AbstractExecutionService.mjs +131 -139
  41. package/dist/services/AbstractExecutionService.mjs.map +1 -1
  42. package/dist/services/ProxyPostMessageStream.cjs +19 -26
  43. package/dist/services/ProxyPostMessageStream.cjs.map +1 -1
  44. package/dist/services/ProxyPostMessageStream.mjs +19 -26
  45. package/dist/services/ProxyPostMessageStream.mjs.map +1 -1
  46. package/dist/services/iframe/IframeExecutionService.cjs +1 -0
  47. package/dist/services/iframe/IframeExecutionService.cjs.map +1 -1
  48. package/dist/services/iframe/IframeExecutionService.mjs +1 -0
  49. package/dist/services/iframe/IframeExecutionService.mjs.map +1 -1
  50. package/dist/services/offscreen/OffscreenExecutionService.cjs +3 -16
  51. package/dist/services/offscreen/OffscreenExecutionService.cjs.map +1 -1
  52. package/dist/services/offscreen/OffscreenExecutionService.mjs +3 -16
  53. package/dist/services/offscreen/OffscreenExecutionService.mjs.map +1 -1
  54. package/dist/services/proxy/ProxyExecutionService.cjs +4 -17
  55. package/dist/services/proxy/ProxyExecutionService.cjs.map +1 -1
  56. package/dist/services/proxy/ProxyExecutionService.mjs +4 -17
  57. package/dist/services/proxy/ProxyExecutionService.mjs.map +1 -1
  58. package/dist/services/webview/WebViewExecutionService.cjs +6 -19
  59. package/dist/services/webview/WebViewExecutionService.cjs.map +1 -1
  60. package/dist/services/webview/WebViewExecutionService.mjs +6 -19
  61. package/dist/services/webview/WebViewExecutionService.mjs.map +1 -1
  62. package/dist/services/webview/WebViewMessageStream.cjs +13 -26
  63. package/dist/services/webview/WebViewMessageStream.cjs.map +1 -1
  64. package/dist/services/webview/WebViewMessageStream.mjs +13 -26
  65. package/dist/services/webview/WebViewMessageStream.mjs.map +1 -1
  66. package/dist/snaps/SnapController.cjs +1432 -1204
  67. package/dist/snaps/SnapController.cjs.map +1 -1
  68. package/dist/snaps/SnapController.d.cts +20 -5
  69. package/dist/snaps/SnapController.d.cts.map +1 -1
  70. package/dist/snaps/SnapController.d.mts +20 -5
  71. package/dist/snaps/SnapController.d.mts.map +1 -1
  72. package/dist/snaps/SnapController.mjs +1432 -1204
  73. package/dist/snaps/SnapController.mjs.map +1 -1
  74. package/dist/snaps/Timer.cjs +4 -0
  75. package/dist/snaps/Timer.cjs.map +1 -1
  76. package/dist/snaps/Timer.mjs +4 -0
  77. package/dist/snaps/Timer.mjs.map +1 -1
  78. package/dist/snaps/constants.cjs +1 -0
  79. package/dist/snaps/constants.cjs.map +1 -1
  80. package/dist/snaps/constants.d.cts.map +1 -1
  81. package/dist/snaps/constants.d.mts.map +1 -1
  82. package/dist/snaps/constants.mjs +1 -0
  83. package/dist/snaps/constants.mjs.map +1 -1
  84. package/dist/snaps/index.cjs +1 -0
  85. package/dist/snaps/index.cjs.map +1 -1
  86. package/dist/snaps/index.d.cts +1 -0
  87. package/dist/snaps/index.d.cts.map +1 -1
  88. package/dist/snaps/index.d.mts +1 -0
  89. package/dist/snaps/index.d.mts.map +1 -1
  90. package/dist/snaps/index.mjs +1 -0
  91. package/dist/snaps/index.mjs.map +1 -1
  92. package/dist/snaps/location/http.cjs +20 -4
  93. package/dist/snaps/location/http.cjs.map +1 -1
  94. package/dist/snaps/location/http.mjs +20 -4
  95. package/dist/snaps/location/http.mjs.map +1 -1
  96. package/dist/snaps/location/local.cjs +4 -17
  97. package/dist/snaps/location/local.cjs.map +1 -1
  98. package/dist/snaps/location/local.mjs +4 -17
  99. package/dist/snaps/location/local.mjs.map +1 -1
  100. package/dist/snaps/location/npm.cjs +28 -48
  101. package/dist/snaps/location/npm.cjs.map +1 -1
  102. package/dist/snaps/location/npm.d.cts.map +1 -1
  103. package/dist/snaps/location/npm.d.mts.map +1 -1
  104. package/dist/snaps/location/npm.mjs +28 -48
  105. package/dist/snaps/location/npm.mjs.map +1 -1
  106. package/dist/snaps/registry/json.cjs +173 -166
  107. package/dist/snaps/registry/json.cjs.map +1 -1
  108. package/dist/snaps/registry/json.mjs +172 -165
  109. package/dist/snaps/registry/json.mjs.map +1 -1
  110. package/dist/utils.d.cts +1 -1
  111. package/dist/utils.d.mts +1 -1
  112. package/dist/websocket/WebSocketService.cjs +194 -0
  113. package/dist/websocket/WebSocketService.cjs.map +1 -0
  114. package/dist/websocket/WebSocketService.d.cts +33 -0
  115. package/dist/websocket/WebSocketService.d.cts.map +1 -0
  116. package/dist/websocket/WebSocketService.d.mts +33 -0
  117. package/dist/websocket/WebSocketService.d.mts.map +1 -0
  118. package/dist/websocket/WebSocketService.mjs +190 -0
  119. package/dist/websocket/WebSocketService.mjs.map +1 -0
  120. package/dist/websocket/index.cjs +18 -0
  121. package/dist/websocket/index.cjs.map +1 -0
  122. package/dist/websocket/index.d.cts +2 -0
  123. package/dist/websocket/index.d.cts.map +1 -0
  124. package/dist/websocket/index.d.mts +2 -0
  125. package/dist/websocket/index.d.mts.map +1 -0
  126. package/dist/websocket/index.mjs +2 -0
  127. package/dist/websocket/index.mjs.map +1 -0
  128. package/package.json +10 -10
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketService = void 0;
4
+ const rpc_errors_1 = require("@metamask/rpc-errors");
5
+ const snaps_utils_1 = require("@metamask/snaps-utils");
6
+ const utils_1 = require("@metamask/utils");
7
+ const nanoid_1 = require("nanoid");
8
+ const snaps_1 = require("../snaps/index.cjs");
9
+ const serviceName = 'WebSocketService';
10
+ class WebSocketService {
11
+ #messenger;
12
+ #sockets;
13
+ constructor({ messenger }) {
14
+ this.#messenger = messenger;
15
+ this.#sockets = new Map();
16
+ this.#messenger.registerActionHandler(`${serviceName}:open`, async (...args) => this.#open(...args));
17
+ this.#messenger.registerActionHandler(`${serviceName}:close`, (...args) => this.#close(...args));
18
+ this.#messenger.registerActionHandler(`${serviceName}:sendMessage`, async (...args) => this.#sendMessage(...args));
19
+ this.#messenger.registerActionHandler(`${serviceName}:getAll`, (...args) => this.#getAll(...args));
20
+ this.#messenger.subscribe('SnapController:snapUpdated', (snap) => {
21
+ this.#closeAll(snap.id);
22
+ });
23
+ this.#messenger.subscribe('SnapController:snapUninstalled', (snap) => {
24
+ this.#closeAll(snap.id);
25
+ });
26
+ // Due to local Snaps not currently emitting snapUninstalled we also have to
27
+ // listen to snapInstalled.
28
+ this.#messenger.subscribe('SnapController:snapInstalled', (snap) => {
29
+ this.#closeAll(snap.id);
30
+ });
31
+ }
32
+ /**
33
+ * Get information about a given WebSocket connection with an ID.
34
+ *
35
+ * @param snapId - The Snap ID.
36
+ * @param id - The identifier for the WebSocket connection.
37
+ * @returns Information abou the WebSocket connection.
38
+ * @throws If the WebSocket connection cannot be found.
39
+ */
40
+ #get(snapId, id) {
41
+ const socket = this.#sockets.get(id);
42
+ (0, utils_1.assert)(socket && socket.snapId === snapId, `Socket with ID "${id}" not found.`);
43
+ return socket;
44
+ }
45
+ /**
46
+ * Check whether a given Snap ID already has an open connection for a URL and protocol.
47
+ *
48
+ * @param snapId - The Snap ID.
49
+ * @param url - The URL.
50
+ * @param protocols - A protocols parameter.
51
+ * @returns True if a matching connection already exists, otherwise false.
52
+ */
53
+ #exists(snapId, url, protocols) {
54
+ return this.#getAll(snapId).some((socket) => socket.url === url && (0, snaps_utils_1.isEqual)(socket.protocols, protocols));
55
+ }
56
+ /**
57
+ * Handle sending a specific WebSocketEvent to a Snap.
58
+ *
59
+ * @param snapId - The Snap ID.
60
+ * @param event - The WebSocketEvent.
61
+ */
62
+ #handleEvent(snapId, event) {
63
+ this.#messenger
64
+ .call('SnapController:handleRequest', {
65
+ origin: snaps_1.METAMASK_ORIGIN,
66
+ snapId,
67
+ handler: snaps_utils_1.HandlerType.OnWebSocketEvent,
68
+ request: { method: '', params: { event } },
69
+ })
70
+ .catch((error) => {
71
+ (0, snaps_utils_1.logError)(`An error occurred while handling a WebSocket message for Snap "${snapId}":`, error);
72
+ });
73
+ }
74
+ /**
75
+ * Open a WebSocket connection.
76
+ *
77
+ * @param snapId - The Snap ID.
78
+ * @param url - The URL for the WebSocket connection.
79
+ * @param protocols - An optional parameter for protocols.
80
+ * @returns The identifier for the opened connection.
81
+ * @throws If the connection fails.
82
+ */
83
+ async #open(snapId, url, protocols = []) {
84
+ (0, utils_1.assert)(!this.#exists(snapId, url, protocols), `An open WebSocket connection to ${url} already exists.`);
85
+ const parsedUrl = new URL(url);
86
+ const { origin } = parsedUrl;
87
+ const id = (0, nanoid_1.nanoid)();
88
+ // eslint-disable-next-line no-restricted-globals
89
+ const socket = new WebSocket(url, protocols);
90
+ socket.binaryType = 'arraybuffer';
91
+ const { promise, resolve, reject } = (0, utils_1.createDeferredPromise)();
92
+ socket.addEventListener('open', () => {
93
+ resolve();
94
+ this.#handleEvent(snapId, {
95
+ type: 'open',
96
+ id,
97
+ origin,
98
+ });
99
+ });
100
+ socket.addEventListener('close', (event) => {
101
+ this.#handleEvent(snapId, {
102
+ type: 'close',
103
+ id,
104
+ origin,
105
+ code: event.code,
106
+ reason: event.reason,
107
+ // wasClean is not available on mobile.
108
+ wasClean: event.wasClean ?? null,
109
+ });
110
+ });
111
+ const errorListener = () => {
112
+ reject(rpc_errors_1.rpcErrors.resourceUnavailable('An error occurred while opening the WebSocket.'));
113
+ };
114
+ socket.addEventListener('error', errorListener);
115
+ socket.addEventListener('message', (event) => {
116
+ const isText = typeof event.data === 'string';
117
+ const data = isText
118
+ ? { type: 'text', message: event.data }
119
+ : {
120
+ type: 'binary',
121
+ // We request that the WebSocket gives us an array buffer.
122
+ message: Array.from(new Uint8Array(event.data)),
123
+ };
124
+ this.#handleEvent(snapId, {
125
+ type: 'message',
126
+ id,
127
+ origin,
128
+ data,
129
+ });
130
+ });
131
+ this.#sockets.set(id, {
132
+ id,
133
+ snapId,
134
+ url,
135
+ protocols,
136
+ socket,
137
+ openPromise: promise,
138
+ });
139
+ await promise;
140
+ socket.removeEventListener('error', errorListener);
141
+ return id;
142
+ }
143
+ /**
144
+ * Close a given WebSocket connection.
145
+ *
146
+ * @param snapId - The Snap ID.
147
+ * @param id - The identifier for the WebSocket connection.
148
+ */
149
+ #close(snapId, id) {
150
+ const { socket } = this.#get(snapId, id);
151
+ socket.close();
152
+ this.#sockets.delete(id);
153
+ }
154
+ /**
155
+ * Close all open connections for a given Snap ID.
156
+ *
157
+ * @param snapId - The Snap ID.
158
+ */
159
+ #closeAll(snapId) {
160
+ for (const socket of this.#getAll(snapId)) {
161
+ this.#close(snapId, socket.id);
162
+ }
163
+ }
164
+ /**
165
+ * Send a message from a given Snap ID to a WebSocket connection.
166
+ *
167
+ * @param snapId - The Snap ID.
168
+ * @param id - The identifier for the WebSocket connection.
169
+ * @param data - The message to send.
170
+ */
171
+ async #sendMessage(snapId, id, data) {
172
+ const { socket, openPromise } = this.#get(snapId, id);
173
+ await openPromise;
174
+ const wrappedData = Array.isArray(data) ? new Uint8Array(data) : data;
175
+ socket.send(wrappedData);
176
+ }
177
+ /**
178
+ * Get a list of all open WebSocket connections for a Snap ID.
179
+ *
180
+ * @param snapId - The Snap ID.
181
+ * @returns A list of WebSocket connections.
182
+ */
183
+ #getAll(snapId) {
184
+ return [...this.#sockets.values()]
185
+ .filter((socket) => socket.snapId === snapId)
186
+ .map((socket) => ({
187
+ id: socket.id,
188
+ url: socket.url,
189
+ protocols: socket.protocols,
190
+ }));
191
+ }
192
+ }
193
+ exports.WebSocketService = WebSocketService;
194
+ //# sourceMappingURL=WebSocketService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketService.cjs","sourceRoot":"","sources":["../../src/websocket/WebSocketService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RestrictedMessenger } from '@metamask/base-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n GetWebSocketsResult,\n SnapId,\n WebSocketEvent,\n} from '@metamask/snaps-sdk';\nimport { HandlerType, isEqual, logError } from '@metamask/snaps-utils';\nimport { assert, createDeferredPromise } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport type {\n HandleSnapRequest,\n SnapInstalled,\n SnapUninstalled,\n SnapUpdated,\n} from '../snaps';\nimport { METAMASK_ORIGIN } from '../snaps';\n\nconst serviceName = 'WebSocketService';\n\nexport type WebSocketServiceOpenAction = {\n type: `${typeof serviceName}:open`;\n handler: (\n snapId: SnapId,\n url: string,\n protocols?: string[],\n ) => Promise<string>;\n};\n\nexport type WebSocketServiceCloseAction = {\n type: `${typeof serviceName}:close`;\n handler: (snapId: SnapId, id: string) => void;\n};\n\nexport type WebSocketServiceSendMessageAction = {\n type: `${typeof serviceName}:sendMessage`;\n handler: (\n snapId: SnapId,\n id: string,\n data: string | number[],\n ) => Promise<void>;\n};\n\nexport type WebSocketServiceGetAllAction = {\n type: `${typeof serviceName}:getAll`;\n handler: (snapId: SnapId) => GetWebSocketsResult;\n};\n\nexport type WebSocketServiceActions =\n | WebSocketServiceOpenAction\n | WebSocketServiceCloseAction\n | WebSocketServiceSendMessageAction\n | WebSocketServiceGetAllAction;\n\nexport type WebSocketServiceAllowedActions = HandleSnapRequest;\n\nexport type WebSocketServiceEvents =\n | SnapUninstalled\n | SnapUpdated\n | SnapInstalled;\n\nexport type WebSocketServiceMessenger = RestrictedMessenger<\n 'WebSocketService',\n WebSocketServiceActions | WebSocketServiceAllowedActions,\n WebSocketServiceEvents,\n WebSocketServiceAllowedActions['type'],\n WebSocketServiceEvents['type']\n>;\n\ntype WebSocketServiceArgs = {\n messenger: WebSocketServiceMessenger;\n};\n\ntype InternalSocket = {\n id: string;\n snapId: SnapId;\n url: string;\n protocols: string[];\n openPromise: Promise<void>;\n // eslint-disable-next-line no-restricted-globals\n socket: WebSocket;\n};\n\nexport class WebSocketService {\n readonly #messenger: WebSocketServiceMessenger;\n\n readonly #sockets: Map<string, InternalSocket>;\n\n constructor({ messenger }: WebSocketServiceArgs) {\n this.#messenger = messenger;\n this.#sockets = new Map();\n\n this.#messenger.registerActionHandler(\n `${serviceName}:open`,\n async (...args) => this.#open(...args),\n );\n\n this.#messenger.registerActionHandler(`${serviceName}:close`, (...args) =>\n this.#close(...args),\n );\n\n this.#messenger.registerActionHandler(\n `${serviceName}:sendMessage`,\n async (...args) => this.#sendMessage(...args),\n );\n\n this.#messenger.registerActionHandler(`${serviceName}:getAll`, (...args) =>\n this.#getAll(...args),\n );\n\n this.#messenger.subscribe('SnapController:snapUpdated', (snap) => {\n this.#closeAll(snap.id);\n });\n\n this.#messenger.subscribe('SnapController:snapUninstalled', (snap) => {\n this.#closeAll(snap.id);\n });\n\n // Due to local Snaps not currently emitting snapUninstalled we also have to\n // listen to snapInstalled.\n this.#messenger.subscribe('SnapController:snapInstalled', (snap) => {\n this.#closeAll(snap.id);\n });\n }\n\n /**\n * Get information about a given WebSocket connection with an ID.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n * @returns Information abou the WebSocket connection.\n * @throws If the WebSocket connection cannot be found.\n */\n #get(snapId: SnapId, id: string) {\n const socket = this.#sockets.get(id);\n\n assert(\n socket && socket.snapId === snapId,\n `Socket with ID \"${id}\" not found.`,\n );\n\n return socket;\n }\n\n /**\n * Check whether a given Snap ID already has an open connection for a URL and protocol.\n *\n * @param snapId - The Snap ID.\n * @param url - The URL.\n * @param protocols - A protocols parameter.\n * @returns True if a matching connection already exists, otherwise false.\n */\n #exists(snapId: SnapId, url: string, protocols: string[]) {\n return this.#getAll(snapId).some(\n (socket) => socket.url === url && isEqual(socket.protocols, protocols),\n );\n }\n\n /**\n * Handle sending a specific WebSocketEvent to a Snap.\n *\n * @param snapId - The Snap ID.\n * @param event - The WebSocketEvent.\n */\n #handleEvent(snapId: SnapId, event: WebSocketEvent) {\n this.#messenger\n .call('SnapController:handleRequest', {\n origin: METAMASK_ORIGIN,\n snapId,\n handler: HandlerType.OnWebSocketEvent,\n request: { method: '', params: { event } },\n })\n .catch((error) => {\n logError(\n `An error occurred while handling a WebSocket message for Snap \"${snapId}\":`,\n error,\n );\n });\n }\n\n /**\n * Open a WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param url - The URL for the WebSocket connection.\n * @param protocols - An optional parameter for protocols.\n * @returns The identifier for the opened connection.\n * @throws If the connection fails.\n */\n async #open(snapId: SnapId, url: string, protocols: string[] = []) {\n assert(\n !this.#exists(snapId, url, protocols),\n `An open WebSocket connection to ${url} already exists.`,\n );\n\n const parsedUrl = new URL(url);\n const { origin } = parsedUrl;\n\n const id = nanoid();\n\n // eslint-disable-next-line no-restricted-globals\n const socket = new WebSocket(url, protocols);\n socket.binaryType = 'arraybuffer';\n\n const { promise, resolve, reject } = createDeferredPromise();\n\n socket.addEventListener('open', () => {\n resolve();\n this.#handleEvent(snapId, {\n type: 'open',\n id,\n origin,\n });\n });\n\n socket.addEventListener('close', (event) => {\n this.#handleEvent(snapId, {\n type: 'close',\n id,\n origin,\n code: event.code,\n reason: event.reason,\n // wasClean is not available on mobile.\n wasClean: event.wasClean ?? null,\n });\n });\n\n const errorListener = () => {\n reject(\n rpcErrors.resourceUnavailable(\n 'An error occurred while opening the WebSocket.',\n ),\n );\n };\n\n socket.addEventListener('error', errorListener);\n\n socket.addEventListener('message', (event) => {\n const isText = typeof event.data === 'string';\n const data = isText\n ? { type: 'text' as const, message: event.data }\n : {\n type: 'binary' as const,\n // We request that the WebSocket gives us an array buffer.\n message: Array.from(new Uint8Array(event.data)),\n };\n\n this.#handleEvent(snapId, {\n type: 'message',\n id,\n origin,\n data,\n });\n });\n\n this.#sockets.set(id, {\n id,\n snapId,\n url,\n protocols,\n socket,\n openPromise: promise,\n });\n\n await promise;\n\n socket.removeEventListener('error', errorListener);\n\n return id;\n }\n\n /**\n * Close a given WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n */\n #close(snapId: SnapId, id: string) {\n const { socket } = this.#get(snapId, id);\n\n socket.close();\n\n this.#sockets.delete(id);\n }\n\n /**\n * Close all open connections for a given Snap ID.\n *\n * @param snapId - The Snap ID.\n */\n #closeAll(snapId: SnapId) {\n for (const socket of this.#getAll(snapId)) {\n this.#close(snapId, socket.id);\n }\n }\n\n /**\n * Send a message from a given Snap ID to a WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n * @param data - The message to send.\n */\n async #sendMessage(snapId: SnapId, id: string, data: string | number[]) {\n const { socket, openPromise } = this.#get(snapId, id);\n\n await openPromise;\n\n const wrappedData = Array.isArray(data) ? new Uint8Array(data) : data;\n\n socket.send(wrappedData);\n }\n\n /**\n * Get a list of all open WebSocket connections for a Snap ID.\n *\n * @param snapId - The Snap ID.\n * @returns A list of WebSocket connections.\n */\n #getAll(snapId: SnapId) {\n return [...this.#sockets.values()]\n .filter((socket) => socket.snapId === snapId)\n .map((socket) => ({\n id: socket.id,\n url: socket.url,\n protocols: socket.protocols,\n }));\n }\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import type { RestrictedMessenger } from "@metamask/base-controller";
2
+ import type { GetWebSocketsResult, SnapId } from "@metamask/snaps-sdk";
3
+ import type { HandleSnapRequest, SnapInstalled, SnapUninstalled, SnapUpdated } from "../snaps/index.cjs";
4
+ declare const serviceName = "WebSocketService";
5
+ export type WebSocketServiceOpenAction = {
6
+ type: `${typeof serviceName}:open`;
7
+ handler: (snapId: SnapId, url: string, protocols?: string[]) => Promise<string>;
8
+ };
9
+ export type WebSocketServiceCloseAction = {
10
+ type: `${typeof serviceName}:close`;
11
+ handler: (snapId: SnapId, id: string) => void;
12
+ };
13
+ export type WebSocketServiceSendMessageAction = {
14
+ type: `${typeof serviceName}:sendMessage`;
15
+ handler: (snapId: SnapId, id: string, data: string | number[]) => Promise<void>;
16
+ };
17
+ export type WebSocketServiceGetAllAction = {
18
+ type: `${typeof serviceName}:getAll`;
19
+ handler: (snapId: SnapId) => GetWebSocketsResult;
20
+ };
21
+ export type WebSocketServiceActions = WebSocketServiceOpenAction | WebSocketServiceCloseAction | WebSocketServiceSendMessageAction | WebSocketServiceGetAllAction;
22
+ export type WebSocketServiceAllowedActions = HandleSnapRequest;
23
+ export type WebSocketServiceEvents = SnapUninstalled | SnapUpdated | SnapInstalled;
24
+ export type WebSocketServiceMessenger = RestrictedMessenger<'WebSocketService', WebSocketServiceActions | WebSocketServiceAllowedActions, WebSocketServiceEvents, WebSocketServiceAllowedActions['type'], WebSocketServiceEvents['type']>;
25
+ type WebSocketServiceArgs = {
26
+ messenger: WebSocketServiceMessenger;
27
+ };
28
+ export declare class WebSocketService {
29
+ #private;
30
+ constructor({ messenger }: WebSocketServiceArgs);
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=WebSocketService.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketService.d.cts","sourceRoot":"","sources":["../../src/websocket/WebSocketService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kCAAkC;AAErE,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EAEP,4BAA4B;AAK7B,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,WAAW,EACZ,2BAAiB;AAGlB,QAAA,MAAM,WAAW,qBAAqB,CAAC;AAEvC,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,GAAG,OAAO,WAAW,OAAO,CAAC;IACnC,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAAE,KACjB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,GAAG,OAAO,WAAW,QAAQ,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,IAAI,EAAE,GAAG,OAAO,WAAW,cAAc,CAAC;IAC1C,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KACpB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,GAAG,OAAO,WAAW,SAAS,CAAC;IACrC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,2BAA2B,GAC3B,iCAAiC,GACjC,4BAA4B,CAAC;AAEjC,MAAM,MAAM,8BAA8B,GAAG,iBAAiB,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GACf,WAAW,GACX,aAAa,CAAC;AAElB,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CACzD,kBAAkB,EAClB,uBAAuB,GAAG,8BAA8B,EACxD,sBAAsB,EACtB,8BAA8B,CAAC,MAAM,CAAC,EACtC,sBAAsB,CAAC,MAAM,CAAC,CAC/B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,yBAAyB,CAAC;CACtC,CAAC;AAYF,qBAAa,gBAAgB;;gBAKf,EAAE,SAAS,EAAE,EAAE,oBAAoB;CAgPhD"}
@@ -0,0 +1,33 @@
1
+ import type { RestrictedMessenger } from "@metamask/base-controller";
2
+ import type { GetWebSocketsResult, SnapId } from "@metamask/snaps-sdk";
3
+ import type { HandleSnapRequest, SnapInstalled, SnapUninstalled, SnapUpdated } from "../snaps/index.mjs";
4
+ declare const serviceName = "WebSocketService";
5
+ export type WebSocketServiceOpenAction = {
6
+ type: `${typeof serviceName}:open`;
7
+ handler: (snapId: SnapId, url: string, protocols?: string[]) => Promise<string>;
8
+ };
9
+ export type WebSocketServiceCloseAction = {
10
+ type: `${typeof serviceName}:close`;
11
+ handler: (snapId: SnapId, id: string) => void;
12
+ };
13
+ export type WebSocketServiceSendMessageAction = {
14
+ type: `${typeof serviceName}:sendMessage`;
15
+ handler: (snapId: SnapId, id: string, data: string | number[]) => Promise<void>;
16
+ };
17
+ export type WebSocketServiceGetAllAction = {
18
+ type: `${typeof serviceName}:getAll`;
19
+ handler: (snapId: SnapId) => GetWebSocketsResult;
20
+ };
21
+ export type WebSocketServiceActions = WebSocketServiceOpenAction | WebSocketServiceCloseAction | WebSocketServiceSendMessageAction | WebSocketServiceGetAllAction;
22
+ export type WebSocketServiceAllowedActions = HandleSnapRequest;
23
+ export type WebSocketServiceEvents = SnapUninstalled | SnapUpdated | SnapInstalled;
24
+ export type WebSocketServiceMessenger = RestrictedMessenger<'WebSocketService', WebSocketServiceActions | WebSocketServiceAllowedActions, WebSocketServiceEvents, WebSocketServiceAllowedActions['type'], WebSocketServiceEvents['type']>;
25
+ type WebSocketServiceArgs = {
26
+ messenger: WebSocketServiceMessenger;
27
+ };
28
+ export declare class WebSocketService {
29
+ #private;
30
+ constructor({ messenger }: WebSocketServiceArgs);
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=WebSocketService.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketService.d.mts","sourceRoot":"","sources":["../../src/websocket/WebSocketService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kCAAkC;AAErE,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EAEP,4BAA4B;AAK7B,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,WAAW,EACZ,2BAAiB;AAGlB,QAAA,MAAM,WAAW,qBAAqB,CAAC;AAEvC,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,GAAG,OAAO,WAAW,OAAO,CAAC;IACnC,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAAE,KACjB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,GAAG,OAAO,WAAW,QAAQ,CAAC;IACpC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,IAAI,EAAE,GAAG,OAAO,WAAW,cAAc,CAAC;IAC1C,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KACpB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,GAAG,OAAO,WAAW,SAAS,CAAC;IACrC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,2BAA2B,GAC3B,iCAAiC,GACjC,4BAA4B,CAAC;AAEjC,MAAM,MAAM,8BAA8B,GAAG,iBAAiB,CAAC;AAE/D,MAAM,MAAM,sBAAsB,GAC9B,eAAe,GACf,WAAW,GACX,aAAa,CAAC;AAElB,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CACzD,kBAAkB,EAClB,uBAAuB,GAAG,8BAA8B,EACxD,sBAAsB,EACtB,8BAA8B,CAAC,MAAM,CAAC,EACtC,sBAAsB,CAAC,MAAM,CAAC,CAC/B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,SAAS,EAAE,yBAAyB,CAAC;CACtC,CAAC;AAYF,qBAAa,gBAAgB;;gBAKf,EAAE,SAAS,EAAE,EAAE,oBAAoB;CAgPhD"}
@@ -0,0 +1,190 @@
1
+ import { rpcErrors } from "@metamask/rpc-errors";
2
+ import { HandlerType, isEqual, logError } from "@metamask/snaps-utils";
3
+ import { assert, createDeferredPromise } from "@metamask/utils";
4
+ import { nanoid } from "nanoid";
5
+ import { METAMASK_ORIGIN } from "../snaps/index.mjs";
6
+ const serviceName = 'WebSocketService';
7
+ export class WebSocketService {
8
+ #messenger;
9
+ #sockets;
10
+ constructor({ messenger }) {
11
+ this.#messenger = messenger;
12
+ this.#sockets = new Map();
13
+ this.#messenger.registerActionHandler(`${serviceName}:open`, async (...args) => this.#open(...args));
14
+ this.#messenger.registerActionHandler(`${serviceName}:close`, (...args) => this.#close(...args));
15
+ this.#messenger.registerActionHandler(`${serviceName}:sendMessage`, async (...args) => this.#sendMessage(...args));
16
+ this.#messenger.registerActionHandler(`${serviceName}:getAll`, (...args) => this.#getAll(...args));
17
+ this.#messenger.subscribe('SnapController:snapUpdated', (snap) => {
18
+ this.#closeAll(snap.id);
19
+ });
20
+ this.#messenger.subscribe('SnapController:snapUninstalled', (snap) => {
21
+ this.#closeAll(snap.id);
22
+ });
23
+ // Due to local Snaps not currently emitting snapUninstalled we also have to
24
+ // listen to snapInstalled.
25
+ this.#messenger.subscribe('SnapController:snapInstalled', (snap) => {
26
+ this.#closeAll(snap.id);
27
+ });
28
+ }
29
+ /**
30
+ * Get information about a given WebSocket connection with an ID.
31
+ *
32
+ * @param snapId - The Snap ID.
33
+ * @param id - The identifier for the WebSocket connection.
34
+ * @returns Information abou the WebSocket connection.
35
+ * @throws If the WebSocket connection cannot be found.
36
+ */
37
+ #get(snapId, id) {
38
+ const socket = this.#sockets.get(id);
39
+ assert(socket && socket.snapId === snapId, `Socket with ID "${id}" not found.`);
40
+ return socket;
41
+ }
42
+ /**
43
+ * Check whether a given Snap ID already has an open connection for a URL and protocol.
44
+ *
45
+ * @param snapId - The Snap ID.
46
+ * @param url - The URL.
47
+ * @param protocols - A protocols parameter.
48
+ * @returns True if a matching connection already exists, otherwise false.
49
+ */
50
+ #exists(snapId, url, protocols) {
51
+ return this.#getAll(snapId).some((socket) => socket.url === url && isEqual(socket.protocols, protocols));
52
+ }
53
+ /**
54
+ * Handle sending a specific WebSocketEvent to a Snap.
55
+ *
56
+ * @param snapId - The Snap ID.
57
+ * @param event - The WebSocketEvent.
58
+ */
59
+ #handleEvent(snapId, event) {
60
+ this.#messenger
61
+ .call('SnapController:handleRequest', {
62
+ origin: METAMASK_ORIGIN,
63
+ snapId,
64
+ handler: HandlerType.OnWebSocketEvent,
65
+ request: { method: '', params: { event } },
66
+ })
67
+ .catch((error) => {
68
+ logError(`An error occurred while handling a WebSocket message for Snap "${snapId}":`, error);
69
+ });
70
+ }
71
+ /**
72
+ * Open a WebSocket connection.
73
+ *
74
+ * @param snapId - The Snap ID.
75
+ * @param url - The URL for the WebSocket connection.
76
+ * @param protocols - An optional parameter for protocols.
77
+ * @returns The identifier for the opened connection.
78
+ * @throws If the connection fails.
79
+ */
80
+ async #open(snapId, url, protocols = []) {
81
+ assert(!this.#exists(snapId, url, protocols), `An open WebSocket connection to ${url} already exists.`);
82
+ const parsedUrl = new URL(url);
83
+ const { origin } = parsedUrl;
84
+ const id = nanoid();
85
+ // eslint-disable-next-line no-restricted-globals
86
+ const socket = new WebSocket(url, protocols);
87
+ socket.binaryType = 'arraybuffer';
88
+ const { promise, resolve, reject } = createDeferredPromise();
89
+ socket.addEventListener('open', () => {
90
+ resolve();
91
+ this.#handleEvent(snapId, {
92
+ type: 'open',
93
+ id,
94
+ origin,
95
+ });
96
+ });
97
+ socket.addEventListener('close', (event) => {
98
+ this.#handleEvent(snapId, {
99
+ type: 'close',
100
+ id,
101
+ origin,
102
+ code: event.code,
103
+ reason: event.reason,
104
+ // wasClean is not available on mobile.
105
+ wasClean: event.wasClean ?? null,
106
+ });
107
+ });
108
+ const errorListener = () => {
109
+ reject(rpcErrors.resourceUnavailable('An error occurred while opening the WebSocket.'));
110
+ };
111
+ socket.addEventListener('error', errorListener);
112
+ socket.addEventListener('message', (event) => {
113
+ const isText = typeof event.data === 'string';
114
+ const data = isText
115
+ ? { type: 'text', message: event.data }
116
+ : {
117
+ type: 'binary',
118
+ // We request that the WebSocket gives us an array buffer.
119
+ message: Array.from(new Uint8Array(event.data)),
120
+ };
121
+ this.#handleEvent(snapId, {
122
+ type: 'message',
123
+ id,
124
+ origin,
125
+ data,
126
+ });
127
+ });
128
+ this.#sockets.set(id, {
129
+ id,
130
+ snapId,
131
+ url,
132
+ protocols,
133
+ socket,
134
+ openPromise: promise,
135
+ });
136
+ await promise;
137
+ socket.removeEventListener('error', errorListener);
138
+ return id;
139
+ }
140
+ /**
141
+ * Close a given WebSocket connection.
142
+ *
143
+ * @param snapId - The Snap ID.
144
+ * @param id - The identifier for the WebSocket connection.
145
+ */
146
+ #close(snapId, id) {
147
+ const { socket } = this.#get(snapId, id);
148
+ socket.close();
149
+ this.#sockets.delete(id);
150
+ }
151
+ /**
152
+ * Close all open connections for a given Snap ID.
153
+ *
154
+ * @param snapId - The Snap ID.
155
+ */
156
+ #closeAll(snapId) {
157
+ for (const socket of this.#getAll(snapId)) {
158
+ this.#close(snapId, socket.id);
159
+ }
160
+ }
161
+ /**
162
+ * Send a message from a given Snap ID to a WebSocket connection.
163
+ *
164
+ * @param snapId - The Snap ID.
165
+ * @param id - The identifier for the WebSocket connection.
166
+ * @param data - The message to send.
167
+ */
168
+ async #sendMessage(snapId, id, data) {
169
+ const { socket, openPromise } = this.#get(snapId, id);
170
+ await openPromise;
171
+ const wrappedData = Array.isArray(data) ? new Uint8Array(data) : data;
172
+ socket.send(wrappedData);
173
+ }
174
+ /**
175
+ * Get a list of all open WebSocket connections for a Snap ID.
176
+ *
177
+ * @param snapId - The Snap ID.
178
+ * @returns A list of WebSocket connections.
179
+ */
180
+ #getAll(snapId) {
181
+ return [...this.#sockets.values()]
182
+ .filter((socket) => socket.snapId === snapId)
183
+ .map((socket) => ({
184
+ id: socket.id,
185
+ url: socket.url,
186
+ protocols: socket.protocols,
187
+ }));
188
+ }
189
+ }
190
+ //# sourceMappingURL=WebSocketService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketService.mjs","sourceRoot":"","sources":["../../src/websocket/WebSocketService.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,6BAA6B;AAMjD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,8BAA8B;AACvE,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,wBAAwB;AAChE,OAAO,EAAE,MAAM,EAAE,eAAe;AAQhC,OAAO,EAAE,eAAe,EAAE,2BAAiB;AAE3C,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAiEvC,MAAM,OAAO,gBAAgB;IAClB,UAAU,CAA4B;IAEtC,QAAQ,CAA8B;IAE/C,YAAY,EAAE,SAAS,EAAwB;QAC7C,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;QAE1B,IAAI,CAAC,UAAU,CAAC,qBAAqB,CACnC,GAAG,WAAW,OAAO,EACrB,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CACvC,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,GAAG,WAAW,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CACxE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CACrB,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,qBAAqB,CACnC,GAAG,WAAW,cAAc,EAC5B,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,CAC9C,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,GAAG,WAAW,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CACzE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CACtB,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,4BAA4B,EAAE,CAAC,IAAI,EAAE,EAAE;YAC/D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,gCAAgC,EAAE,CAAC,IAAI,EAAE,EAAE;YACnE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,4EAA4E;QAC5E,2BAA2B;QAC3B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,8BAA8B,EAAE,CAAC,IAAI,EAAE,EAAE;YACjE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,IAAI,CAAC,MAAc,EAAE,EAAU;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAErC,MAAM,CACJ,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAClC,mBAAmB,EAAE,cAAc,CACpC,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,MAAc,EAAE,GAAW,EAAE,SAAmB;QACtD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAC9B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CACvE,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,MAAc,EAAE,KAAqB;QAChD,IAAI,CAAC,UAAU;aACZ,IAAI,CAAC,8BAA8B,EAAE;YACpC,MAAM,EAAE,eAAe;YACvB,MAAM;YACN,OAAO,EAAE,WAAW,CAAC,gBAAgB;YACrC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE;SAC3C,CAAC;aACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,QAAQ,CACN,kEAAkE,MAAM,IAAI,EAC5E,KAAK,CACN,CAAC;QACJ,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,GAAW,EAAE,YAAsB,EAAE;QAC/D,MAAM,CACJ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,SAAS,CAAC,EACrC,mCAAmC,GAAG,kBAAkB,CACzD,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QAE7B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QAEpB,iDAAiD;QACjD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,GAAG,aAAa,CAAC;QAElC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,qBAAqB,EAAE,CAAC;QAE7D,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;YACnC,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;gBACxB,IAAI,EAAE,MAAM;gBACZ,EAAE;gBACF,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACzC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;gBACxB,IAAI,EAAE,OAAO;gBACb,EAAE;gBACF,MAAM;gBACN,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,uCAAuC;gBACvC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;aACjC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,MAAM,CACJ,SAAS,CAAC,mBAAmB,CAC3B,gDAAgD,CACjD,CACF,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEhD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3C,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;YAC9C,MAAM,IAAI,GAAG,MAAM;gBACjB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE;gBAChD,CAAC,CAAC;oBACE,IAAI,EAAE,QAAiB;oBACvB,0DAA0D;oBAC1D,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;iBAChD,CAAC;YAEN,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;gBACxB,IAAI,EAAE,SAAS;gBACf,EAAE;gBACF,MAAM;gBACN,IAAI;aACL,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE;YACpB,EAAE;YACF,MAAM;YACN,GAAG;YACH,SAAS;YACT,MAAM;YACN,WAAW,EAAE,OAAO;SACrB,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC;QAEd,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEnD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,MAAc,EAAE,EAAU;QAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEzC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,MAAc;QACtB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,EAAU,EAAE,IAAuB;QACpE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEtD,MAAM,WAAW,CAAC;QAElB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAEtE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,MAAc;QACpB,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;aAC/B,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC;aAC5C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC,CAAC,CAAC;IACR,CAAC;CACF","sourcesContent":["import type { RestrictedMessenger } from '@metamask/base-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n GetWebSocketsResult,\n SnapId,\n WebSocketEvent,\n} from '@metamask/snaps-sdk';\nimport { HandlerType, isEqual, logError } from '@metamask/snaps-utils';\nimport { assert, createDeferredPromise } from '@metamask/utils';\nimport { nanoid } from 'nanoid';\n\nimport type {\n HandleSnapRequest,\n SnapInstalled,\n SnapUninstalled,\n SnapUpdated,\n} from '../snaps';\nimport { METAMASK_ORIGIN } from '../snaps';\n\nconst serviceName = 'WebSocketService';\n\nexport type WebSocketServiceOpenAction = {\n type: `${typeof serviceName}:open`;\n handler: (\n snapId: SnapId,\n url: string,\n protocols?: string[],\n ) => Promise<string>;\n};\n\nexport type WebSocketServiceCloseAction = {\n type: `${typeof serviceName}:close`;\n handler: (snapId: SnapId, id: string) => void;\n};\n\nexport type WebSocketServiceSendMessageAction = {\n type: `${typeof serviceName}:sendMessage`;\n handler: (\n snapId: SnapId,\n id: string,\n data: string | number[],\n ) => Promise<void>;\n};\n\nexport type WebSocketServiceGetAllAction = {\n type: `${typeof serviceName}:getAll`;\n handler: (snapId: SnapId) => GetWebSocketsResult;\n};\n\nexport type WebSocketServiceActions =\n | WebSocketServiceOpenAction\n | WebSocketServiceCloseAction\n | WebSocketServiceSendMessageAction\n | WebSocketServiceGetAllAction;\n\nexport type WebSocketServiceAllowedActions = HandleSnapRequest;\n\nexport type WebSocketServiceEvents =\n | SnapUninstalled\n | SnapUpdated\n | SnapInstalled;\n\nexport type WebSocketServiceMessenger = RestrictedMessenger<\n 'WebSocketService',\n WebSocketServiceActions | WebSocketServiceAllowedActions,\n WebSocketServiceEvents,\n WebSocketServiceAllowedActions['type'],\n WebSocketServiceEvents['type']\n>;\n\ntype WebSocketServiceArgs = {\n messenger: WebSocketServiceMessenger;\n};\n\ntype InternalSocket = {\n id: string;\n snapId: SnapId;\n url: string;\n protocols: string[];\n openPromise: Promise<void>;\n // eslint-disable-next-line no-restricted-globals\n socket: WebSocket;\n};\n\nexport class WebSocketService {\n readonly #messenger: WebSocketServiceMessenger;\n\n readonly #sockets: Map<string, InternalSocket>;\n\n constructor({ messenger }: WebSocketServiceArgs) {\n this.#messenger = messenger;\n this.#sockets = new Map();\n\n this.#messenger.registerActionHandler(\n `${serviceName}:open`,\n async (...args) => this.#open(...args),\n );\n\n this.#messenger.registerActionHandler(`${serviceName}:close`, (...args) =>\n this.#close(...args),\n );\n\n this.#messenger.registerActionHandler(\n `${serviceName}:sendMessage`,\n async (...args) => this.#sendMessage(...args),\n );\n\n this.#messenger.registerActionHandler(`${serviceName}:getAll`, (...args) =>\n this.#getAll(...args),\n );\n\n this.#messenger.subscribe('SnapController:snapUpdated', (snap) => {\n this.#closeAll(snap.id);\n });\n\n this.#messenger.subscribe('SnapController:snapUninstalled', (snap) => {\n this.#closeAll(snap.id);\n });\n\n // Due to local Snaps not currently emitting snapUninstalled we also have to\n // listen to snapInstalled.\n this.#messenger.subscribe('SnapController:snapInstalled', (snap) => {\n this.#closeAll(snap.id);\n });\n }\n\n /**\n * Get information about a given WebSocket connection with an ID.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n * @returns Information abou the WebSocket connection.\n * @throws If the WebSocket connection cannot be found.\n */\n #get(snapId: SnapId, id: string) {\n const socket = this.#sockets.get(id);\n\n assert(\n socket && socket.snapId === snapId,\n `Socket with ID \"${id}\" not found.`,\n );\n\n return socket;\n }\n\n /**\n * Check whether a given Snap ID already has an open connection for a URL and protocol.\n *\n * @param snapId - The Snap ID.\n * @param url - The URL.\n * @param protocols - A protocols parameter.\n * @returns True if a matching connection already exists, otherwise false.\n */\n #exists(snapId: SnapId, url: string, protocols: string[]) {\n return this.#getAll(snapId).some(\n (socket) => socket.url === url && isEqual(socket.protocols, protocols),\n );\n }\n\n /**\n * Handle sending a specific WebSocketEvent to a Snap.\n *\n * @param snapId - The Snap ID.\n * @param event - The WebSocketEvent.\n */\n #handleEvent(snapId: SnapId, event: WebSocketEvent) {\n this.#messenger\n .call('SnapController:handleRequest', {\n origin: METAMASK_ORIGIN,\n snapId,\n handler: HandlerType.OnWebSocketEvent,\n request: { method: '', params: { event } },\n })\n .catch((error) => {\n logError(\n `An error occurred while handling a WebSocket message for Snap \"${snapId}\":`,\n error,\n );\n });\n }\n\n /**\n * Open a WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param url - The URL for the WebSocket connection.\n * @param protocols - An optional parameter for protocols.\n * @returns The identifier for the opened connection.\n * @throws If the connection fails.\n */\n async #open(snapId: SnapId, url: string, protocols: string[] = []) {\n assert(\n !this.#exists(snapId, url, protocols),\n `An open WebSocket connection to ${url} already exists.`,\n );\n\n const parsedUrl = new URL(url);\n const { origin } = parsedUrl;\n\n const id = nanoid();\n\n // eslint-disable-next-line no-restricted-globals\n const socket = new WebSocket(url, protocols);\n socket.binaryType = 'arraybuffer';\n\n const { promise, resolve, reject } = createDeferredPromise();\n\n socket.addEventListener('open', () => {\n resolve();\n this.#handleEvent(snapId, {\n type: 'open',\n id,\n origin,\n });\n });\n\n socket.addEventListener('close', (event) => {\n this.#handleEvent(snapId, {\n type: 'close',\n id,\n origin,\n code: event.code,\n reason: event.reason,\n // wasClean is not available on mobile.\n wasClean: event.wasClean ?? null,\n });\n });\n\n const errorListener = () => {\n reject(\n rpcErrors.resourceUnavailable(\n 'An error occurred while opening the WebSocket.',\n ),\n );\n };\n\n socket.addEventListener('error', errorListener);\n\n socket.addEventListener('message', (event) => {\n const isText = typeof event.data === 'string';\n const data = isText\n ? { type: 'text' as const, message: event.data }\n : {\n type: 'binary' as const,\n // We request that the WebSocket gives us an array buffer.\n message: Array.from(new Uint8Array(event.data)),\n };\n\n this.#handleEvent(snapId, {\n type: 'message',\n id,\n origin,\n data,\n });\n });\n\n this.#sockets.set(id, {\n id,\n snapId,\n url,\n protocols,\n socket,\n openPromise: promise,\n });\n\n await promise;\n\n socket.removeEventListener('error', errorListener);\n\n return id;\n }\n\n /**\n * Close a given WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n */\n #close(snapId: SnapId, id: string) {\n const { socket } = this.#get(snapId, id);\n\n socket.close();\n\n this.#sockets.delete(id);\n }\n\n /**\n * Close all open connections for a given Snap ID.\n *\n * @param snapId - The Snap ID.\n */\n #closeAll(snapId: SnapId) {\n for (const socket of this.#getAll(snapId)) {\n this.#close(snapId, socket.id);\n }\n }\n\n /**\n * Send a message from a given Snap ID to a WebSocket connection.\n *\n * @param snapId - The Snap ID.\n * @param id - The identifier for the WebSocket connection.\n * @param data - The message to send.\n */\n async #sendMessage(snapId: SnapId, id: string, data: string | number[]) {\n const { socket, openPromise } = this.#get(snapId, id);\n\n await openPromise;\n\n const wrappedData = Array.isArray(data) ? new Uint8Array(data) : data;\n\n socket.send(wrappedData);\n }\n\n /**\n * Get a list of all open WebSocket connections for a Snap ID.\n *\n * @param snapId - The Snap ID.\n * @returns A list of WebSocket connections.\n */\n #getAll(snapId: SnapId) {\n return [...this.#sockets.values()]\n .filter((socket) => socket.snapId === snapId)\n .map((socket) => ({\n id: socket.id,\n url: socket.url,\n protocols: socket.protocols,\n }));\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./WebSocketService.cjs"), exports);
18
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yDAAmC","sourcesContent":["export * from './WebSocketService';\n"]}
@@ -0,0 +1,2 @@
1
+ export * from "./WebSocketService.cjs";
2
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":"AAAA,uCAAmC"}
@@ -0,0 +1,2 @@
1
+ export * from "./WebSocketService.mjs";
2
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":"AAAA,uCAAmC"}
@@ -0,0 +1,2 @@
1
+ export * from "./WebSocketService.mjs";
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/websocket/index.ts"],"names":[],"mappings":"AAAA,uCAAmC","sourcesContent":["export * from './WebSocketService';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask/snaps-controllers",
3
- "version": "12.3.1",
3
+ "version": "13.1.0",
4
4
  "description": "Controllers for MetaMask Snaps",
5
5
  "keywords": [
6
6
  "MetaMask",
@@ -87,18 +87,18 @@
87
87
  "@metamask/key-tree": "^10.1.1",
88
88
  "@metamask/object-multiplex": "^2.1.0",
89
89
  "@metamask/permission-controller": "^11.0.6",
90
- "@metamask/phishing-controller": "^12.5.0",
90
+ "@metamask/phishing-controller": "^12.6.0",
91
91
  "@metamask/post-message-stream": "^10.0.0",
92
92
  "@metamask/rpc-errors": "^7.0.2",
93
93
  "@metamask/snaps-registry": "^3.2.3",
94
- "@metamask/snaps-rpc-methods": "^12.4.0",
95
- "@metamask/snaps-sdk": "^7.1.0",
96
- "@metamask/snaps-utils": "^9.4.0",
94
+ "@metamask/snaps-rpc-methods": "^13.1.0",
95
+ "@metamask/snaps-sdk": "^8.1.0",
96
+ "@metamask/snaps-utils": "^10.1.0",
97
97
  "@metamask/utils": "^11.4.0",
98
98
  "@xstate/fsm": "^2.0.0",
99
99
  "async-mutex": "^0.5.0",
100
- "browserify-zlib": "^0.2.0",
101
100
  "concat-stream": "^2.0.0",
101
+ "cron-parser": "^4.5.0",
102
102
  "fast-deep-equal": "^3.1.3",
103
103
  "get-npm-tarball-url": "^2.0.3",
104
104
  "immer": "^9.0.6",
@@ -115,8 +115,8 @@
115
115
  "@metamask/browser-passworder": "^6.0.0",
116
116
  "@metamask/template-snap": "^0.7.0",
117
117
  "@noble/hashes": "^1.7.1",
118
- "@swc/core": "1.3.78",
119
- "@swc/jest": "^0.2.26",
118
+ "@swc/core": "1.11.31",
119
+ "@swc/jest": "^0.2.38",
120
120
  "@testing-library/dom": "^10.4.0",
121
121
  "@ts-bridge/cli": "^0.6.1",
122
122
  "@types/chrome": "^0.0.237",
@@ -149,7 +149,7 @@
149
149
  "vitest": "^3.1.1"
150
150
  },
151
151
  "peerDependencies": {
152
- "@metamask/snaps-execution-environments": "^8.1.0"
152
+ "@metamask/snaps-execution-environments": "^9.1.0"
153
153
  },
154
154
  "peerDependenciesMeta": {
155
155
  "@metamask/snaps-execution-environments": {
@@ -157,7 +157,7 @@
157
157
  }
158
158
  },
159
159
  "engines": {
160
- "node": "^18.16 || >=20"
160
+ "node": "^20 || >=22"
161
161
  },
162
162
  "publishConfig": {
163
163
  "access": "public",