Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/snaps-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"lodash": "^4.17.21",
"prettier": "^3.3.3",
"rimraf": "^4.1.2",
"ses": "^1.14.0",
"ts-node": "^10.9.1",
"tsx": "^4.20.3",
"typescript": "~5.3.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import { createStreamMiddleware } from '@metamask/json-rpc-middleware-stream';
import ObjectMultiplex from '@metamask/object-multiplex';
import type { BasePostMessageStream } from '@metamask/post-message-stream';
import { JsonRpcError } from '@metamask/rpc-errors';
import type { SnapRpcHookArgs } from '@metamask/snaps-utils';
import { SNAP_STREAM_NAMES, logError, logWarning } from '@metamask/snaps-utils';
Expand Down Expand Up @@ -47,7 +46,7 @@ export type ExecutionServiceArgs = {
export type JobStreams = {
command: Duplex;
rpc: Duplex;
_connection: BasePostMessageStream;
_connection: Duplex;
};

export type Job<WorkerType> = {
Expand Down Expand Up @@ -351,7 +350,7 @@ export abstract class AbstractExecutionService<WorkerType>
*/
protected abstract initEnvStream(snapId: string): Promise<{
worker: WorkerType;
stream: BasePostMessageStream;
stream: Duplex;
}>;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// eslint-disable-next-line import-x/no-unassigned-import
import 'ses';
import { HandlerType } from '@metamask/snaps-utils';
import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';
import { describe, expect, it } from 'vitest';

import { NativeExecutionService } from './NativeExecutionService';
import { METAMASK_ORIGIN } from '../../snaps';
import { createService } from '../../test-utils/service';

lockdown({
domainTaming: 'unsafe',
errorTaming: 'unsafe',
stackFiltering: 'verbose',
overrideTaming: 'severe',
errorTrapping: 'none',
});

describe('NativeExecutionService', () => {
it('works', async () => {
const { service } = createService(NativeExecutionService);

await service.executeSnap({
snapId: MOCK_SNAP_ID,
sourceCode: `module.exports.onRpcRequest = () => { console.log('foo'); return 'bar'; }`,
endowments: ['console'],
});

const result = await service.handleRpcRequest(MOCK_SNAP_ID, {
origin: METAMASK_ORIGIN,
request: {
method: 'foo',
},
handler: HandlerType.OnRpcRequest,
});

expect(result).toBe('bar');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NativeSnapExecutor } from '@metamask/snaps-execution-environments';
import type { Json } from '@metamask/utils';
import { Duplex } from 'readable-stream';

import {
AbstractExecutionService,
type TerminateJobArgs,
} from '../AbstractExecutionService';

export class NativeExecutionService extends AbstractExecutionService<NativeSnapExecutor> {
protected terminateJob(_job: TerminateJobArgs<NativeSnapExecutor>): void {
// no-op
}

protected async initEnvStream(
_snapId: string,
): Promise<{ worker: NativeSnapExecutor; stream: Duplex }> {
// TODO: Sanity check this.
const workerStream = new Duplex({
objectMode: true,
read() {
return undefined;
},
write(chunk: Json, encoding: BufferEncoding, callback) {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stream.push(chunk, encoding);
callback();
},
});
const stream = new Duplex({
objectMode: true,
read() {
return undefined;
},
write(chunk: Json, encoding: BufferEncoding, callback) {
workerStream.push(chunk, encoding);
callback();
},
});

// NOTE: Initializes a Snap executor that runs in the same JS thread as the execution service.
// Does not provide process isolation.
const worker = NativeSnapExecutor.initialize(workerStream);

return Promise.resolve({ worker, stream });
}
}
1 change: 1 addition & 0 deletions packages/snaps-controllers/src/services/native/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './NativeExecutionService';
1 change: 1 addition & 0 deletions packages/snaps-controllers/src/services/react-native.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from '.';
export * from './webview';
export * from './native';
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import consoleEndowment from './console';
import crypto from './crypto';
import date from './date';

Check failure on line 3 in packages/snaps-execution-environments/src/common/endowments/commonEndowmentFactory.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

'date' is defined but never used
import interval from './interval';
import math from './math';
import network from './network';

Check failure on line 6 in packages/snaps-execution-environments/src/common/endowments/commonEndowmentFactory.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

'network' is defined but never used
import textDecoder from './textDecoder';
import textEncoder from './textEncoder';
import timeout from './timeout';
Expand All @@ -28,29 +28,29 @@

// Array of common endowments
const commonEndowments: CommonEndowmentSpecification[] = [
{ endowment: AbortController, name: 'AbortController' },
{ endowment: AbortSignal, name: 'AbortSignal' },
{ endowment: ArrayBuffer, name: 'ArrayBuffer' },
{ endowment: atob, name: 'atob', bind: true },
{ endowment: BigInt, name: 'BigInt' },
{ endowment: BigInt64Array, name: 'BigInt64Array' },
{ endowment: BigUint64Array, name: 'BigUint64Array' },
{ endowment: btoa, name: 'btoa', bind: true },
{ endowment: DataView, name: 'DataView' },
{ endowment: Float32Array, name: 'Float32Array' },
{ endowment: Float64Array, name: 'Float64Array' },
{ endowment: Intl, name: 'Intl' },
{ endowment: Int8Array, name: 'Int8Array' },
{ endowment: Int16Array, name: 'Int16Array' },
{ endowment: Int32Array, name: 'Int32Array' },
{ endowment: globalThis.isSecureContext, name: 'isSecureContext' },
{ endowment: Uint8Array, name: 'Uint8Array' },
{ endowment: Uint8ClampedArray, name: 'Uint8ClampedArray' },
{ endowment: Uint16Array, name: 'Uint16Array' },
{ endowment: Uint32Array, name: 'Uint32Array' },
{ endowment: URL, name: 'URL' },
{ endowment: URLSearchParams, name: 'URLSearchParams' },
{ endowment: WebAssembly, name: 'WebAssembly' },
{ endowment: rootRealmGlobal.AbortController, name: 'AbortController' },
{ endowment: rootRealmGlobal.AbortSignal, name: 'AbortSignal' },
{ endowment: rootRealmGlobal.ArrayBuffer, name: 'ArrayBuffer' },
{ endowment: rootRealmGlobal.atob, name: 'atob', bind: true },
{ endowment: rootRealmGlobal.BigInt, name: 'BigInt' },
{ endowment: rootRealmGlobal.BigInt64Array, name: 'BigInt64Array' },
{ endowment: rootRealmGlobal.BigUint64Array, name: 'BigUint64Array' },
{ endowment: rootRealmGlobal.btoa, name: 'btoa', bind: true },
{ endowment: rootRealmGlobal.DataView, name: 'DataView' },
{ endowment: rootRealmGlobal.Float32Array, name: 'Float32Array' },
{ endowment: rootRealmGlobal.Float64Array, name: 'Float64Array' },
{ endowment: rootRealmGlobal.Intl, name: 'Intl' },
{ endowment: rootRealmGlobal.Int8Array, name: 'Int8Array' },
{ endowment: rootRealmGlobal.Int16Array, name: 'Int16Array' },
{ endowment: rootRealmGlobal.Int32Array, name: 'Int32Array' },
{ endowment: rootRealmGlobal.isSecureContext, name: 'isSecureContext' },
{ endowment: rootRealmGlobal.Uint8Array, name: 'Uint8Array' },
{ endowment: rootRealmGlobal.Uint8ClampedArray, name: 'Uint8ClampedArray' },
{ endowment: rootRealmGlobal.Uint16Array, name: 'Uint16Array' },
{ endowment: rootRealmGlobal.Uint32Array, name: 'Uint32Array' },
{ endowment: rootRealmGlobal.URL, name: 'URL' },
{ endowment: rootRealmGlobal.URLSearchParams, name: 'URLSearchParams' },
// { endowment: WebAssembly, name: 'WebAssembly' },
];

/**
Expand All @@ -65,11 +65,11 @@
crypto,
interval,
math,
network,
// network,
timeout,
textDecoder,
textEncoder,
date,
// date,
consoleEndowment,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
'Crypto endowment requires `globalThis.crypto` to be defined.',
);

assert(
/**assert(

Check failure on line 11 in packages/snaps-execution-environments/src/common/endowments/crypto.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Expected exception block, space or tab after '/**' in comment
rootRealmGlobal.SubtleCrypto,
'Crypto endowment requires `globalThis.SubtleCrypto` to be defined.',
);
);**/

return {
crypto: harden(rootRealmGlobal.crypto),
SubtleCrypto: harden(rootRealmGlobal.SubtleCrypto),
// SubtleCrypto: harden(rootRealmGlobal.SubtleCrypto),
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { EndowmentFactoryOptions } from './commonEndowmentFactory';
import { withTeardown } from '../utils';
import { rootRealmGlobal } from '../globalObject';

Check failure on line 5 in packages/snaps-execution-environments/src/common/endowments/network.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

`../globalObject` import should occur before import of `../utils`

/**
* This class wraps a Response object.
Expand Down Expand Up @@ -177,10 +178,10 @@
const teardownRef = { lastTeardown: 0 };

// Remove items from openConnections after they were garbage collected
const cleanup = new FinalizationRegistry<() => void>(
const cleanup = 'FinalizationRegistry' in rootRealmGlobal ? new FinalizationRegistry<() => void>(

Check failure on line 181 in packages/snaps-execution-environments/src/common/endowments/network.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Replace `·'FinalizationRegistry'·in·rootRealmGlobal` with `⏎····'FinalizationRegistry'·in·rootRealmGlobal⏎·····`
/* istanbul ignore next: can't test garbage collection without modifying node parameters */

Check failure on line 182 in packages/snaps-execution-environments/src/common/endowments/network.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Insert `······`
(callback) => callback(),

Check failure on line 183 in packages/snaps-execution-environments/src/common/endowments/network.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Insert `······`
);
) : undefined;

Check failure on line 184 in packages/snaps-execution-environments/src/common/endowments/network.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Replace `)` with `······)⏎·····`

const _fetch: typeof fetch = async (
input: RequestInfo | URL,
Expand Down Expand Up @@ -279,7 +280,7 @@
},
};
openConnections.add(openBodyConnection);
cleanup.register(
cleanup?.register(
res.body,
/* istanbul ignore next: can't test garbage collection without modifying node parameters */
() => openConnections.delete(openBodyConnection),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// @ts-expect-error No types.
import { endowmentsToolkit } from 'lavamoat-core/src/endowmentsToolkit'

Check failure on line 2 in packages/snaps-execution-environments/src/common/globalObject.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

Insert `;`

Check failure on line 2 in packages/snaps-execution-environments/src/common/globalObject.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint

'lavamoat-core' should be listed in the project's dependencies. Run 'npm i -S lavamoat-core' to add it

enum GlobalObjectNames {
// The globalThis entry is incorrectly identified as shadowing the global
// globalThis.
Expand Down Expand Up @@ -33,10 +36,12 @@
}
/* eslint-enable no-negated-condition */

const { copyWrappedGlobals } = endowmentsToolkit();

/**
* A platform-agnostic alias for the root realm global object.
*/
const rootRealmGlobal = _rootRealmGlobal;
const rootRealmGlobal = copyWrappedGlobals(_rootRealmGlobal, {}, ['globalThis', 'global', 'self', 'window']);

/**
* The string literal corresponding to the name of the root realm global object.
Expand Down
1 change: 1 addition & 0 deletions packages/snaps-execution-environments/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './proxy';
export * from './native';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ObjectMultiplex from '@metamask/object-multiplex';
import { logError, SNAP_STREAM_NAMES } from '@metamask/snaps-utils';
import type { Duplex } from 'readable-stream';
import { pipeline } from 'readable-stream';

import { BaseSnapExecutor } from '../common/BaseSnapExecutor';

export class NativeSnapExecutor extends BaseSnapExecutor {
static initialize(stream: Duplex) {
const mux = new ObjectMultiplex();
pipeline(stream, mux, stream, (error) => {
if (error) {
logError(`Parent stream failure, closing worker.`, error);
}
self.close();
});

const commandStream = mux.createStream(SNAP_STREAM_NAMES.COMMAND);
const rpcStream = mux.createStream(SNAP_STREAM_NAMES.JSON_RPC);

return new NativeSnapExecutor(commandStream, rpcStream);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './NativeSnapExecutor';
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4311,6 +4311,7 @@ __metadata:
readable-web-to-node-stream: "npm:^3.0.2"
rimraf: "npm:^4.1.2"
semver: "npm:^7.5.4"
ses: "npm:^1.14.0"
tar-stream: "npm:^3.1.7"
ts-node: "npm:^10.9.1"
tsx: "npm:^4.20.3"
Expand Down
Loading