Skip to content

Commit d9c1325

Browse files
committed
Add support for state methods to snaps-simulation
1 parent 6d1af36 commit d9c1325

File tree

14 files changed

+506
-129
lines changed

14 files changed

+506
-129
lines changed

packages/examples/packages/browserify-plugin/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "REliam7FRJGwKCI4NaC2G3mBSD5iYR7DCEhrXLcBDqA=",
10+
"shasum": "82KbG3cf0wtxooJpWzHeM1g4FhO8O7zSYCAAGNPshfM=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/browserify/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "NItYOhAaWlS9q2r59/zlpoyVUyxojfsc5xMh65mLIwQ=",
10+
"shasum": "5LsB950haZGnl0q5K7M4XgSh5J2e0p5O1Ptl/e6kpSQ=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/manage-state/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "3jbTBm2Gtm5+qWdWgNR2sgwEGwWmKsGK7QPeXN9yOpE=",
10+
"shasum": "fIXije73reQctVWFkOL9kdLWns7uDs7UWbPPL1J0f2o=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/snaps-simulation/src/controllers.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {
55
} from '@metamask/permission-controller';
66

77
import { getControllers } from './controllers';
8-
import type { MiddlewareHooks } from './simulation';
8+
import type { RestrictedMiddlewareHooks } from './simulation';
99
import { getMockOptions } from './test-utils';
1010

11-
const MOCK_HOOKS: MiddlewareHooks = {
11+
const MOCK_HOOKS: RestrictedMiddlewareHooks = {
1212
getIsLocked: jest.fn(),
1313
getMnemonic: jest.fn(),
1414
getSnapFile: jest.fn(),

packages/snaps-simulation/src/controllers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { getSafeJson } from '@metamask/utils';
2929
import { getPermissionSpecifications } from './methods';
3030
import { UNRESTRICTED_METHODS } from './methods/constants';
3131
import type { SimulationOptions } from './options';
32-
import type { MiddlewareHooks } from './simulation';
32+
import type { RestrictedMiddlewareHooks } from './simulation';
3333
import type { RunSagaFunction } from './store';
3434

3535
export type RootControllerAllowedActions =
@@ -49,7 +49,7 @@ export type RootControllerMessenger = ControllerMessenger<
4949

5050
export type GetControllersOptions = {
5151
controllerMessenger: ControllerMessenger<any, any>;
52-
hooks: MiddlewareHooks;
52+
hooks: RestrictedMiddlewareHooks;
5353
runSaga: RunSagaFunction;
5454
options: SimulationOptions;
5555
};

packages/snaps-simulation/src/methods/hooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './get-preferences';
2+
export * from './interface';
23
export * from './notifications';
4+
export * from './permitted';
35
export * from './request-user-approval';
46
export * from './state';
5-
export * from './interface';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './state';
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { createStore, getState, setState } from '../../../store';
2+
import { getMockOptions } from '../../../test-utils';
3+
import {
4+
getPermittedClearSnapStateMethodImplementation,
5+
getPermittedGetSnapStateMethodImplementation,
6+
getPermittedUpdateSnapStateMethodImplementation,
7+
} from './state';
8+
9+
describe('getPermittedGetSnapStateMethodImplementation', () => {
10+
it('returns the implementation of the `getSnapState` hook', async () => {
11+
const { store, runSaga } = createStore(getMockOptions());
12+
const fn = getPermittedGetSnapStateMethodImplementation(runSaga);
13+
14+
expect(await fn(true)).toBeNull();
15+
16+
store.dispatch(
17+
setState({
18+
state: JSON.stringify({
19+
foo: 'bar',
20+
}),
21+
encrypted: true,
22+
}),
23+
);
24+
25+
expect(await fn(true)).toStrictEqual({
26+
foo: 'bar',
27+
});
28+
});
29+
30+
it('returns the implementation of the `getSnapState` hook for unencrypted state', async () => {
31+
const { store, runSaga } = createStore(getMockOptions());
32+
const fn = getPermittedGetSnapStateMethodImplementation(runSaga);
33+
34+
expect(await fn(false)).toBeNull();
35+
36+
store.dispatch(
37+
setState({
38+
state: JSON.stringify({
39+
foo: 'bar',
40+
}),
41+
encrypted: false,
42+
}),
43+
);
44+
45+
expect(await fn(false)).toStrictEqual({
46+
foo: 'bar',
47+
});
48+
});
49+
});
50+
51+
describe('getPermittedUpdateSnapStateMethodImplementation', () => {
52+
it('returns the implementation of the `updateSnapState` hook', async () => {
53+
const { store, runSaga } = createStore(getMockOptions());
54+
const fn = getPermittedUpdateSnapStateMethodImplementation(runSaga);
55+
56+
expect(getState(true)(store.getState())).toBeNull();
57+
58+
await fn({ foo: 'bar' }, true);
59+
60+
expect(getState(true)(store.getState())).toStrictEqual(
61+
JSON.stringify({
62+
foo: 'bar',
63+
}),
64+
);
65+
});
66+
67+
it('returns the implementation of the `updateSnapState` hook for unencrypted state', async () => {
68+
const { store, runSaga } = createStore(getMockOptions());
69+
const fn = getPermittedUpdateSnapStateMethodImplementation(runSaga);
70+
71+
expect(getState(false)(store.getState())).toBeNull();
72+
73+
await fn({ foo: 'bar' }, false);
74+
75+
expect(getState(false)(store.getState())).toStrictEqual(
76+
JSON.stringify({
77+
foo: 'bar',
78+
}),
79+
);
80+
});
81+
});
82+
83+
describe('getPermittedClearSnapStateMethodImplementation', () => {
84+
it('returns the implementation of the `clearSnapState` hook', async () => {
85+
const { store, runSaga } = createStore(getMockOptions());
86+
const fn = getPermittedClearSnapStateMethodImplementation(runSaga);
87+
88+
store.dispatch(
89+
setState({
90+
state: JSON.stringify({
91+
foo: 'bar',
92+
}),
93+
encrypted: true,
94+
}),
95+
);
96+
97+
await fn(true);
98+
99+
expect(getState(true)(store.getState())).toBeNull();
100+
});
101+
102+
it('returns the implementation of the `clearSnapState` hook for unencrypted state', async () => {
103+
const { store, runSaga } = createStore(getMockOptions());
104+
const fn = getPermittedClearSnapStateMethodImplementation(runSaga);
105+
106+
store.dispatch(
107+
setState({
108+
state: JSON.stringify({
109+
foo: 'bar',
110+
}),
111+
encrypted: false,
112+
}),
113+
);
114+
115+
await fn(false);
116+
117+
expect(getState(false)(store.getState())).toBeNull();
118+
});
119+
});
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { parseJson } from '@metamask/snaps-utils';
2+
import type { Json } from '@metamask/utils';
3+
import type { SagaIterator } from 'redux-saga';
4+
import { put, select } from 'redux-saga/effects';
5+
6+
import type { RunSagaFunction } from '../../../store';
7+
import { clearState, getState, setState } from '../../../store';
8+
9+
/**
10+
* Get the Snap state from the store.
11+
*
12+
* @param encrypted - Whether to get the encrypted or unencrypted state.
13+
* Defaults to encrypted state.
14+
* @returns The state of the Snap.
15+
* @yields Selects the state from the store.
16+
*/
17+
function* getSnapStateImplementation(encrypted: boolean): SagaIterator<string> {
18+
const state = yield select(getState(encrypted));
19+
// TODO: Use actual decryption implementation
20+
return parseJson(state);
21+
}
22+
23+
/**
24+
* Get the implementation of the `getSnapState` hook.
25+
*
26+
* @param runSaga - The function to run a saga outside the usual Redux flow.
27+
* @returns The implementation of the `getSnapState` hook.
28+
*/
29+
export function getPermittedGetSnapStateMethodImplementation(
30+
runSaga: RunSagaFunction,
31+
) {
32+
return async (...args: Parameters<typeof getSnapStateImplementation>) => {
33+
return await runSaga(getSnapStateImplementation, ...args).toPromise();
34+
};
35+
}
36+
37+
/**
38+
* Update the Snap state in the store.
39+
*
40+
* @param newState - The new state.
41+
* @param encrypted - Whether to update the encrypted or unencrypted state.
42+
* Defaults to encrypted state.
43+
* @yields Puts the new state in the store.
44+
*/
45+
function* updateSnapStateImplementation(
46+
newState: Record<string, Json>,
47+
encrypted: boolean,
48+
): SagaIterator<void> {
49+
// TODO: Use actual encryption implementation
50+
yield put(setState({ state: JSON.stringify(newState), encrypted }));
51+
}
52+
53+
/**
54+
* Get the implementation of the `updateSnapState` hook.
55+
*
56+
* @param runSaga - The function to run a saga outside the usual Redux flow.
57+
* @returns The implementation of the `updateSnapState` hook.
58+
*/
59+
export function getPermittedUpdateSnapStateMethodImplementation(
60+
runSaga: RunSagaFunction,
61+
) {
62+
return async (...args: Parameters<typeof updateSnapStateImplementation>) => {
63+
return await runSaga(updateSnapStateImplementation, ...args).toPromise();
64+
};
65+
}
66+
67+
/**
68+
* Clear the Snap state in the store.
69+
*
70+
* @param encrypted - Whether to clear the encrypted or unencrypted state.
71+
* Defaults to encrypted state.
72+
* @yields Puts the new state in the store.
73+
*/
74+
function* clearSnapStateImplementation(encrypted: boolean): SagaIterator<void> {
75+
yield put(clearState({ encrypted }));
76+
}
77+
78+
/**
79+
* Get the implementation of the `clearSnapState` hook.
80+
*
81+
* @param runSaga - The function to run a saga outside the usual Redux flow.
82+
* @returns The implementation of the `clearSnapState` hook.
83+
*/
84+
export function getPermittedClearSnapStateMethodImplementation(
85+
runSaga: RunSagaFunction,
86+
) {
87+
return async (...args: Parameters<typeof clearSnapStateImplementation>) => {
88+
runSaga(clearSnapStateImplementation, ...args).result();
89+
};
90+
}

packages/snaps-simulation/src/methods/specifications.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '@metamask/snaps-utils/test-utils';
66

77
import { getControllers, registerSnap } from '../controllers';
8-
import type { MiddlewareHooks } from '../simulation';
8+
import type { RestrictedMiddlewareHooks } from '../simulation';
99
import { getMockOptions } from '../test-utils/options';
1010
import {
1111
asyncResolve,
@@ -14,7 +14,7 @@ import {
1414
resolve,
1515
} from './specifications';
1616

17-
const MOCK_HOOKS: MiddlewareHooks = {
17+
const MOCK_HOOKS: RestrictedMiddlewareHooks = {
1818
getMnemonic: jest.fn(),
1919
getSnapFile: jest.fn(),
2020
createInterface: jest.fn(),

0 commit comments

Comments
 (0)