@metamask/snaps-execution-environments 10.4.1 → 11.0.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 (46) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/dist/common/BaseSnapExecutor.cjs +129 -189
  3. package/dist/common/BaseSnapExecutor.cjs.map +1 -1
  4. package/dist/common/BaseSnapExecutor.d.cts +0 -57
  5. package/dist/common/BaseSnapExecutor.d.cts.map +1 -1
  6. package/dist/common/BaseSnapExecutor.d.mts +0 -57
  7. package/dist/common/BaseSnapExecutor.d.mts.map +1 -1
  8. package/dist/common/BaseSnapExecutor.mjs +133 -193
  9. package/dist/common/BaseSnapExecutor.mjs.map +1 -1
  10. package/dist/common/commands.cjs +23 -29
  11. package/dist/common/commands.cjs.map +1 -1
  12. package/dist/common/commands.d.cts +12 -20
  13. package/dist/common/commands.d.cts.map +1 -1
  14. package/dist/common/commands.d.mts +12 -20
  15. package/dist/common/commands.d.mts.map +1 -1
  16. package/dist/common/commands.mjs +21 -27
  17. package/dist/common/commands.mjs.map +1 -1
  18. package/dist/common/validation.cjs +13 -25
  19. package/dist/common/validation.cjs.map +1 -1
  20. package/dist/common/validation.d.cts +69 -42
  21. package/dist/common/validation.d.cts.map +1 -1
  22. package/dist/common/validation.d.mts +69 -42
  23. package/dist/common/validation.d.mts.map +1 -1
  24. package/dist/common/validation.mjs +15 -27
  25. package/dist/common/validation.mjs.map +1 -1
  26. package/dist/webpack/iframe/bundle.js +1 -1
  27. package/dist/webpack/node-process/bundle.js +1 -1
  28. package/dist/webpack/node-thread/bundle.js +1 -1
  29. package/dist/webpack/webview/index.html +1 -1
  30. package/package.json +2 -2
  31. package/dist/common/lockdown/lockdown.cjs +0 -29
  32. package/dist/common/lockdown/lockdown.cjs.map +0 -1
  33. package/dist/common/lockdown/lockdown.d.cts +0 -7
  34. package/dist/common/lockdown/lockdown.d.cts.map +0 -1
  35. package/dist/common/lockdown/lockdown.d.mts +0 -7
  36. package/dist/common/lockdown/lockdown.d.mts.map +0 -1
  37. package/dist/common/lockdown/lockdown.mjs +0 -25
  38. package/dist/common/lockdown/lockdown.mjs.map +0 -1
  39. package/dist/common/sortParams.cjs +0 -35
  40. package/dist/common/sortParams.cjs.map +0 -1
  41. package/dist/common/sortParams.d.cts +0 -17
  42. package/dist/common/sortParams.d.cts.map +0 -1
  43. package/dist/common/sortParams.d.mts +0 -17
  44. package/dist/common/sortParams.d.mts.map +0 -1
  45. package/dist/common/sortParams.mjs +0 -31
  46. package/dist/common/sortParams.mjs.map +0 -1
@@ -12,16 +12,15 @@ const ObjectMultiplex = $importDefault($ObjectMultiplex);
12
12
  import { errorCodes, rpcErrors, serializeError } from "@metamask/rpc-errors";
13
13
  import { getErrorData } from "@metamask/snaps-sdk";
14
14
  import { SNAP_EXPORT_NAMES, logError, SNAP_EXPORTS, WrappedSnapError, unwrapError, logInfo } from "@metamask/snaps-utils";
15
- import { validate, is } from "@metamask/superstruct";
16
- import { assert, isJsonRpcRequest, hasProperty, getSafeJson, JsonRpcIdStruct } from "@metamask/utils";
15
+ import { is } from "@metamask/superstruct";
16
+ import { assert, isJsonRpcRequest, hasProperty, getSafeJson, JsonRpcIdStruct, createDeferredPromise } from "@metamask/utils";
17
17
  import { pipeline } from "readable-stream";
18
- import { getCommandMethodImplementations } from "./commands.mjs";
18
+ import { assertCommandParams, getHandlerArguments } from "./commands.mjs";
19
19
  import { createEndowments } from "./endowments/index.mjs";
20
20
  import { addEventListener, removeEventListener } from "./globalEvents.mjs";
21
21
  import { SnapProvider } from "./SnapProvider.mjs";
22
- import { sortParamKeys } from "./sortParams.mjs";
23
22
  import { assertEthereumOutboundRequest, assertSnapOutboundRequest, sanitizeRequestArguments, withTeardown, isValidResponse, isMultichainRequest, assertMultichainOutboundRequest } from "./utils.mjs";
24
- import { ExecuteSnapRequestArgumentsStruct, PingRequestArgumentsStruct, SnapRpcRequestArgumentsStruct, TerminateRequestArgumentsStruct } from "./validation.mjs";
23
+ import { ExecuteSnapRequestArgumentsStruct, SnapRpcRequestArgumentsStruct } from "./validation.mjs";
25
24
  import { log } from "../logging.mjs";
26
25
  const fallbackError = {
27
26
  code: errorCodes.rpc.internal,
@@ -32,109 +31,31 @@ const unhandledError = rpcErrors
32
31
  message: 'Unhandled Snap Error',
33
32
  })
34
33
  .serialize();
35
- /**
36
- * The supported methods in the execution environment. The validator checks the
37
- * incoming JSON-RPC request, and the `params` property is used for sorting the
38
- * parameters, if they are an object.
39
- */
40
- const EXECUTION_ENVIRONMENT_METHODS = {
41
- ping: {
42
- struct: PingRequestArgumentsStruct,
43
- params: [],
44
- },
45
- executeSnap: {
46
- struct: ExecuteSnapRequestArgumentsStruct,
47
- params: ['snapId', 'sourceCode', 'endowments'],
48
- },
49
- terminate: {
50
- struct: TerminateRequestArgumentsStruct,
51
- params: [],
52
- },
53
- snapRpc: {
54
- struct: SnapRpcRequestArgumentsStruct,
55
- params: ['target', 'handler', 'origin', 'request'],
56
- },
57
- };
58
34
  export class BaseSnapExecutor {
59
- // TODO: Either fix this lint violation or explain why it's necessary to
60
- // ignore.
61
- // eslint-disable-next-line no-restricted-syntax
62
- snapData;
63
- // TODO: Either fix this lint violation or explain why it's necessary to
64
- // ignore.
65
- // eslint-disable-next-line no-restricted-syntax
66
- commandStream;
67
- // TODO: Either fix this lint violation or explain why it's necessary to
68
- // ignore.
69
- // eslint-disable-next-line no-restricted-syntax
70
- rpcStream;
71
- // TODO: Either fix this lint violation or explain why it's necessary to
72
- // ignore.
73
- // eslint-disable-next-line no-restricted-syntax
74
- methods;
75
- // TODO: Either fix this lint violation or explain why it's necessary to
76
- // ignore.
77
- // eslint-disable-next-line no-restricted-syntax
78
- snapErrorHandler;
79
- // TODO: Either fix this lint violation or explain why it's necessary to
80
- // ignore.
81
- // eslint-disable-next-line no-restricted-syntax
82
- snapPromiseErrorHandler;
83
- // TODO: Either fix this lint violation or explain why it's necessary to
84
- // ignore.
85
- // eslint-disable-next-line no-restricted-syntax
86
- lastTeardown = 0;
35
+ #snapData;
36
+ #commandStream;
37
+ #rpcStream;
38
+ #snapErrorHandler;
39
+ #snapPromiseErrorHandler;
40
+ #teardownRef = { lastTeardown: 0 };
87
41
  constructor(commandStream, rpcStream) {
88
- this.snapData = new Map();
89
- this.commandStream = commandStream;
90
- this.commandStream.on('data', (data) => {
91
- this.onCommandRequest(data).catch((error) => {
92
- // TODO: Decide how to handle errors.
42
+ this.#snapData = new Map();
43
+ this.#commandStream = commandStream;
44
+ this.#commandStream.on('data', (data) => {
45
+ /* istanbul ignore next 2 */
46
+ this.#onCommandRequest(data).catch((error) => {
93
47
  logError(error);
94
48
  });
95
49
  });
96
- this.rpcStream = rpcStream;
97
- this.methods = getCommandMethodImplementations(this.startSnap.bind(this), async (target, handlerType, args) => {
98
- const data = this.snapData.get(target);
99
- // We're capturing the handler in case someone modifies the data object
100
- // before the call.
101
- const handler = data?.exports[handlerType];
102
- const { required } = SNAP_EXPORTS[handlerType];
103
- assert(!required || handler !== undefined, `No ${handlerType} handler exported for snap "${target}".`, rpcErrors.methodNotSupported);
104
- // Certain handlers are not required. If they are not exported, we
105
- // return null.
106
- if (!handler) {
107
- return null;
108
- }
109
- const result = await this.executeInSnapContext(target, async () =>
110
- // TODO: fix handler args type cast
111
- handler(args));
112
- // The handler might not return anything, but undefined is not valid JSON.
113
- if (result === undefined || result === null) {
114
- return null;
115
- }
116
- // /!\ Always return only sanitized JSON to prevent security flaws. /!\
117
- try {
118
- return getSafeJson(result);
119
- }
120
- catch (error) {
121
- throw rpcErrors.internal(`Received non-JSON-serializable value: ${error.message.replace(/^Assertion failed: /u, '')}`);
122
- }
123
- }, this.onTerminate.bind(this));
50
+ this.#rpcStream = rpcStream;
124
51
  }
125
- // TODO: Either fix this lint violation or explain why it's necessary to
126
- // ignore.
127
- // eslint-disable-next-line no-restricted-syntax
128
- errorHandler(error, data) {
52
+ #errorHandler(error, data) {
129
53
  const serializedError = serializeError(error, {
130
54
  fallbackError: unhandledError,
131
55
  shouldIncludeStack: false,
132
56
  shouldPreserveMessage: false,
133
57
  });
134
58
  const errorData = getErrorData(serializedError);
135
- // TODO: Either fix this lint violation or explain why it's necessary to
136
- // ignore.
137
- // eslint-disable-next-line promise/no-promise-in-callback
138
59
  this.#notify({
139
60
  method: 'UnhandledError',
140
61
  params: {
@@ -150,19 +71,14 @@ export class BaseSnapExecutor {
150
71
  logError(notifyError);
151
72
  });
152
73
  }
153
- // TODO: Either fix this lint violation or explain why it's necessary to
154
- // ignore.
155
- // eslint-disable-next-line no-restricted-syntax
156
- async onCommandRequest(message) {
74
+ async #onCommandRequest(message) {
157
75
  if (!isJsonRpcRequest(message)) {
158
76
  if (hasProperty(message, 'id') &&
159
77
  is(message.id, JsonRpcIdStruct)) {
160
78
  // Instead of throwing, we directly respond with an error.
161
79
  // We can only do this if the message ID is still valid.
162
- await this.#write({
80
+ await this.#respond(message.id, {
163
81
  error: serializeError(rpcErrors.internal('JSON-RPC requests must be JSON serializable objects.')),
164
- id: message.id,
165
- jsonrpc: '2.0',
166
82
  });
167
83
  }
168
84
  else {
@@ -171,38 +87,8 @@ export class BaseSnapExecutor {
171
87
  return;
172
88
  }
173
89
  const { id, method, params } = message;
174
- if (!hasProperty(EXECUTION_ENVIRONMENT_METHODS, method)) {
175
- await this.#respond(id, {
176
- error: rpcErrors
177
- .methodNotFound({
178
- data: {
179
- method,
180
- },
181
- })
182
- .serialize(),
183
- });
184
- return;
185
- }
186
- const methodObject = EXECUTION_ENVIRONMENT_METHODS[method];
187
- // support params by-name and by-position
188
- const paramsAsArray = sortParamKeys(methodObject.params, params);
189
- const [error] = validate(paramsAsArray, methodObject.struct);
190
- if (error) {
191
- await this.#respond(id, {
192
- error: rpcErrors
193
- .invalidParams({
194
- message: `Invalid parameters for method "${method}": ${error.message}.`,
195
- data: {
196
- method,
197
- params: paramsAsArray,
198
- },
199
- })
200
- .serialize(),
201
- });
202
- return;
203
- }
204
90
  try {
205
- const result = await this.methods[method](...paramsAsArray);
91
+ const result = await this.#handleCommand(method, params);
206
92
  await this.#respond(id, { result });
207
93
  }
208
94
  catch (rpcError) {
@@ -214,12 +100,83 @@ export class BaseSnapExecutor {
214
100
  });
215
101
  }
216
102
  }
103
+ /**
104
+ * Handle an incoming JSON-RPC command request.
105
+ *
106
+ * @param method - The JSON-RPC method name.
107
+ * @param params - The optional JSON-RPC parameters.
108
+ * @returns The response based on the JSON-RPC method invoked.
109
+ * @throws If the passed method is not available.
110
+ */
111
+ async #handleCommand(method, params) {
112
+ switch (method) {
113
+ case 'ping':
114
+ return 'OK';
115
+ case 'terminate': {
116
+ this.#handleTerminate();
117
+ return 'OK';
118
+ }
119
+ case 'executeSnap': {
120
+ assertCommandParams(method, params, ExecuteSnapRequestArgumentsStruct);
121
+ await this.#startSnap(params);
122
+ return 'OK';
123
+ }
124
+ case 'snapRpc': {
125
+ assertCommandParams(method, params, SnapRpcRequestArgumentsStruct);
126
+ return await this.#invokeSnap(params);
127
+ }
128
+ default:
129
+ throw rpcErrors.methodNotFound({
130
+ data: {
131
+ method,
132
+ },
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * Invoke an exported handler on a running Snap.
138
+ *
139
+ * @param options - An options bag.
140
+ * @param options.snapId - The Snap ID.
141
+ * @param options.handler - The handler to invoke.
142
+ * @param options.origin - The origin invoking the handler.
143
+ * @param options.request - The JSON-RPC request to invoke the handler with.
144
+ * @returns The result of invoking the handler on the Snap.
145
+ */
146
+ async #invokeSnap({ snapId, handler: handlerType, origin, request, }) {
147
+ const args = getHandlerArguments(origin, handlerType, request);
148
+ const data = this.#snapData.get(snapId);
149
+ // We're capturing the handler in case someone modifies the data object
150
+ // before the call.
151
+ const handler = data?.exports[handlerType];
152
+ const { required } = SNAP_EXPORTS[handlerType];
153
+ assert(!required || handler !== undefined, `No ${handlerType} handler exported for Snap "${snapId}".`, rpcErrors.methodNotSupported);
154
+ // Certain handlers are not required. If they are not exported, we
155
+ // return null.
156
+ if (!handler) {
157
+ return null;
158
+ }
159
+ const result = await this.#executeInSnapContext(snapId, async () =>
160
+ // TODO: fix handler args type cast
161
+ handler(args));
162
+ // The handler might not return anything, but undefined is not valid JSON.
163
+ if (result === undefined || result === null) {
164
+ return null;
165
+ }
166
+ // /!\ Always return only sanitized JSON to prevent security flaws. /!\
167
+ try {
168
+ return getSafeJson(result);
169
+ }
170
+ catch (error) {
171
+ throw rpcErrors.internal(`Received non-JSON-serializable value: ${error.message.replace(/^Assertion failed: /u, '')}`);
172
+ }
173
+ }
217
174
  // Awaitable function that writes back to the command stream
218
175
  // To prevent snap execution from blocking writing we wrap in a promise
219
176
  // and await it before continuing execution
220
177
  async #write(chunk) {
221
178
  return new Promise((resolve, reject) => {
222
- this.commandStream.write(chunk, (error) => {
179
+ this.#commandStream.write(chunk, (error) => {
223
180
  if (error) {
224
181
  reject(error);
225
182
  return;
@@ -258,28 +215,29 @@ export class BaseSnapExecutor {
258
215
  * Attempts to evaluate a snap in SES. Generates APIs for the snap. May throw
259
216
  * on errors.
260
217
  *
261
- * @param snapId - The id of the snap.
262
- * @param sourceCode - The source code of the snap, in IIFE format.
263
- * @param _endowments - An array of the names of the endowments.
218
+ * @param params - The parameters.
219
+ * @param params.snapId - The id of the snap.
220
+ * @param params.sourceCode - The source code of the snap, in IIFE format.
221
+ * @param params.endowments - An array of the names of the endowments.
264
222
  */
265
- async startSnap(snapId, sourceCode, _endowments) {
223
+ async #startSnap({ snapId, sourceCode, endowments: endowmentKeys, }) {
266
224
  log(`Starting snap '${snapId}' in worker.`);
267
- if (this.snapPromiseErrorHandler) {
268
- removeEventListener('unhandledrejection', this.snapPromiseErrorHandler);
225
+ if (this.#snapPromiseErrorHandler) {
226
+ removeEventListener('unhandledrejection', this.#snapPromiseErrorHandler);
269
227
  }
270
- if (this.snapErrorHandler) {
271
- removeEventListener('error', this.snapErrorHandler);
228
+ if (this.#snapErrorHandler) {
229
+ removeEventListener('error', this.#snapErrorHandler);
272
230
  }
273
- this.snapErrorHandler = (error) => {
274
- this.errorHandler(error.error, { snapId });
231
+ this.#snapErrorHandler = (error) => {
232
+ this.#errorHandler(error.error, { snapId });
275
233
  };
276
- this.snapPromiseErrorHandler = (error) => {
277
- this.errorHandler(error instanceof Error ? error : error.reason, {
234
+ this.#snapPromiseErrorHandler = (error) => {
235
+ this.#errorHandler(error instanceof Error ? error : error.reason, {
278
236
  snapId,
279
237
  });
280
238
  };
281
239
  const multiplex = new ObjectMultiplex();
282
- pipeline(this.rpcStream, multiplex, this.rpcStream, (error) => {
240
+ pipeline(this.#rpcStream, multiplex, this.#rpcStream, (error) => {
283
241
  if (error && !error.message?.match('Premature close')) {
284
242
  logError(`Provider stream failure.`, error);
285
243
  }
@@ -292,8 +250,8 @@ export class BaseSnapExecutor {
292
250
  rpcMiddleware: [createIdRemapMiddleware()],
293
251
  });
294
252
  multichainProvider.initializeSync();
295
- const snap = this.createSnapGlobal(provider, multichainProvider);
296
- const ethereum = this.createEIP1193Provider(provider);
253
+ const snap = this.#createSnapGlobal(provider, multichainProvider);
254
+ const ethereum = this.#createEIP1193Provider(provider);
297
255
  // We specifically use any type because the Snap can modify the object any way they want
298
256
  const snapModule = { exports: {} };
299
257
  try {
@@ -301,18 +259,18 @@ export class BaseSnapExecutor {
301
259
  snap,
302
260
  ethereum,
303
261
  snapId,
304
- endowments: _endowments,
262
+ endowments: endowmentKeys,
305
263
  notify: this.#notify.bind(this),
306
264
  });
307
265
  // !!! Ensure that this is the only place the data is being set.
308
266
  // Other methods access the object value and mutate its properties.
309
- this.snapData.set(snapId, {
267
+ this.#snapData.set(snapId, {
310
268
  idleTeardown: endowmentTeardown,
311
269
  runningEvaluations: new Set(),
312
270
  exports: {},
313
271
  });
314
- addEventListener('unhandledRejection', this.snapPromiseErrorHandler);
315
- addEventListener('error', this.snapErrorHandler);
272
+ addEventListener('unhandledRejection', this.#snapPromiseErrorHandler);
273
+ addEventListener('error', this.#snapErrorHandler);
316
274
  const compartment = new Compartment({
317
275
  ...endowments,
318
276
  module: snapModule,
@@ -326,13 +284,13 @@ export class BaseSnapExecutor {
326
284
  compartment.globalThis.self = compartment.globalThis;
327
285
  compartment.globalThis.global = compartment.globalThis;
328
286
  compartment.globalThis.window = compartment.globalThis;
329
- await this.executeInSnapContext(snapId, async () => {
287
+ await this.#executeInSnapContext(snapId, async () => {
330
288
  compartment.evaluate(sourceCode);
331
- await this.registerSnapExports(snapId, snapModule);
289
+ await this.#registerSnapExports(snapId, snapModule);
332
290
  });
333
291
  }
334
292
  catch (error) {
335
- this.removeSnap(snapId);
293
+ this.#removeSnap(snapId);
336
294
  const [cause] = unwrapError(error);
337
295
  throw rpcErrors.internal({
338
296
  message: `Error while running snap '${snapId}': ${cause.message}`,
@@ -346,18 +304,15 @@ export class BaseSnapExecutor {
346
304
  * Cancels all running evaluations of all snaps and clears all snap data.
347
305
  * NOTE:** Should only be called in response to the `terminate` RPC command.
348
306
  */
349
- onTerminate() {
307
+ #handleTerminate() {
350
308
  // `stop()` tears down snap endowments.
351
309
  // Teardown will also be run for each snap as soon as there are
352
310
  // no more running evaluations for that snap.
353
- this.snapData.forEach((data) => data.runningEvaluations.forEach((evaluation) => evaluation.stop()));
354
- this.snapData.clear();
311
+ this.#snapData.forEach((data) => data.runningEvaluations.forEach((evaluation) => evaluation.stop()));
312
+ this.#snapData.clear();
355
313
  }
356
- // TODO: Either fix this lint violation or explain why it's necessary to
357
- // ignore.
358
- // eslint-disable-next-line no-restricted-syntax
359
- async registerSnapExports(snapId, snapModule) {
360
- const data = this.snapData.get(snapId);
314
+ async #registerSnapExports(snapId, snapModule) {
315
+ const data = this.#snapData.get(snapId);
361
316
  // Somebody deleted the snap before we could register.
362
317
  if (!data) {
363
318
  return;
@@ -382,10 +337,7 @@ export class BaseSnapExecutor {
382
337
  * @param multichainProvider - A StreamProvider connected to the CAIP-27 client stream.
383
338
  * @returns The snap provider object.
384
339
  */
385
- // TODO: Either fix this lint violation or explain why it's necessary to
386
- // ignore.
387
- // eslint-disable-next-line no-restricted-syntax
388
- createSnapGlobal(provider, multichainProvider) {
340
+ #createSnapGlobal(provider, multichainProvider) {
389
341
  const originalRequest = provider.request.bind(provider);
390
342
  const originalMultichainRequest = multichainProvider.request.bind(multichainProvider);
391
343
  const request = async (args) => {
@@ -394,9 +346,9 @@ export class BaseSnapExecutor {
394
346
  assertSnapOutboundRequest(sanitizedArgs);
395
347
  if (isMultichainRequest(sanitizedArgs)) {
396
348
  assertMultichainOutboundRequest(sanitizedArgs);
397
- return await withTeardown(originalMultichainRequest(sanitizedArgs), this);
349
+ return await withTeardown(originalMultichainRequest(sanitizedArgs), this.#teardownRef);
398
350
  }
399
- return await withTeardown(originalRequest(sanitizedArgs), this);
351
+ return await withTeardown(originalRequest(sanitizedArgs), this.#teardownRef);
400
352
  };
401
353
  const snapsProvider = { request };
402
354
  return harden(snapsProvider);
@@ -407,16 +359,13 @@ export class BaseSnapExecutor {
407
359
  * @param provider - A StreamProvider connected to MetaMask.
408
360
  * @returns The EIP-1193 Ethereum provider object.
409
361
  */
410
- // TODO: Either fix this lint violation or explain why it's necessary to
411
- // ignore.
412
- // eslint-disable-next-line no-restricted-syntax
413
- createEIP1193Provider(provider) {
362
+ #createEIP1193Provider(provider) {
414
363
  const originalRequest = provider.request.bind(provider);
415
364
  const request = async (args) => {
416
365
  // As part of the sanitization, we validate that the args are valid JSON.
417
366
  const sanitizedArgs = sanitizeRequestArguments(args);
418
367
  assertEthereumOutboundRequest(sanitizedArgs);
419
- return await withTeardown(originalRequest(sanitizedArgs), this);
368
+ return await withTeardown(originalRequest(sanitizedArgs), this.#teardownRef);
420
369
  };
421
370
  const ethereumProvider = { request };
422
371
  return harden(ethereumProvider);
@@ -426,11 +375,8 @@ export class BaseSnapExecutor {
426
375
  *
427
376
  * @param snapId - The id of the snap to remove.
428
377
  */
429
- // TODO: Either fix this lint violation or explain why it's necessary to
430
- // ignore.
431
- // eslint-disable-next-line no-restricted-syntax
432
- removeSnap(snapId) {
433
- this.snapData.delete(snapId);
378
+ #removeSnap(snapId) {
379
+ this.#snapData.delete(snapId);
434
380
  }
435
381
  /**
436
382
  * Calls the specified executor function in the context of the specified snap.
@@ -443,20 +389,14 @@ export class BaseSnapExecutor {
443
389
  * @returns The executor's return value.
444
390
  * @template Result - The return value of the executor.
445
391
  */
446
- // TODO: Either fix this lint violation or explain why it's necessary to
447
- // ignore.
448
- // eslint-disable-next-line no-restricted-syntax
449
- async executeInSnapContext(snapId, executor) {
450
- const data = this.snapData.get(snapId);
392
+ async #executeInSnapContext(snapId, executor) {
393
+ const data = this.#snapData.get(snapId);
451
394
  if (data === undefined) {
452
395
  throw rpcErrors.internal(`Tried to execute in context of unknown snap: "${snapId}".`);
453
396
  }
454
- let stop;
455
- const stopPromise = new Promise((_resolve, reject) => (stop = () => reject(
456
- // TODO(rekmarks): Specify / standardize error code for this case.
457
- rpcErrors.internal(`The snap "${snapId}" has been terminated during execution.`))));
458
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
459
- const evaluationData = { stop: stop };
397
+ const { promise: stopPromise, reject } = createDeferredPromise();
398
+ const stop = () => reject(rpcErrors.internal(`The Snap "${snapId}" has been terminated during execution.`));
399
+ const evaluationData = { stop };
460
400
  try {
461
401
  data.runningEvaluations.add(evaluationData);
462
402
  // Notice that we have to await this executor.
@@ -470,7 +410,7 @@ export class BaseSnapExecutor {
470
410
  finally {
471
411
  data.runningEvaluations.delete(evaluationData);
472
412
  if (data.runningEvaluations.size === 0) {
473
- this.lastTeardown += 1;
413
+ this.#teardownRef.lastTeardown += 1;
474
414
  await data.idleTeardown();
475
415
  }
476
416
  }