@@ -65,6 +65,7 @@ import type {
6565 TruncatedSnapFields ,
6666} from '@metamask/snaps-utils' ;
6767import {
68+ withMutex ,
6869 logWarning ,
6970 getPlatformVersion ,
7071 assertIsSnapManifest ,
@@ -252,6 +253,16 @@ export interface SnapRuntimeData {
252253 * A boolean flag to determine whether the Snap is currently being stopped.
253254 */
254255 stopping : boolean ;
256+
257+ /**
258+ * Cached encrypted state of the Snap.
259+ */
260+ state : Record < string , Json > | null ;
261+
262+ /**
263+ * Cached unencrypted state of the Snap.
264+ */
265+ unencryptedState : Record < string , Json > | null ;
255266}
256267
257268export type SnapError = {
@@ -906,6 +917,7 @@ export class SnapController extends BaseController<
906917 this . _onOutboundResponse = this . _onOutboundResponse . bind ( this ) ;
907918 this . #rollbackSnapshots = new Map ( ) ;
908919 this . #snapsRuntimeData = new Map ( ) ;
920+
909921 this . #pollForLastRequestStatus( ) ;
910922
911923 /* eslint-disable @typescript-eslint/unbound-method */
@@ -1860,6 +1872,68 @@ export class SnapController extends BaseController<
18601872 return JSON . stringify ( encryptedState ) ;
18611873 }
18621874
1875+ /**
1876+ * Get the new Snap state to persist based on the given state and encryption
1877+ * flag.
1878+ *
1879+ * - If the state is null, return null.
1880+ * - If the state should not be encrypted, return the JSON stringified state.
1881+ * - Otherwise if the state should be encrypted, return the encrypted state.
1882+ *
1883+ * @param snapId - The Snap ID.
1884+ * @param state - The state to persist.
1885+ * @param encrypted - A flag to indicate whether to use encrypted storage or
1886+ * not.
1887+ * @returns The state to persist.
1888+ */
1889+ async #getStateToPersist(
1890+ snapId : SnapId ,
1891+ state : Record < string , Json > | null ,
1892+ encrypted : boolean ,
1893+ ) {
1894+ if ( state === null ) {
1895+ return null ;
1896+ }
1897+
1898+ if ( encrypted ) {
1899+ return await this . #encryptSnapState( snapId , state ) ;
1900+ }
1901+
1902+ return JSON . stringify ( state ) ;
1903+ }
1904+
1905+ /**
1906+ * Persist the state of a Snap.
1907+ *
1908+ * @param snapId - The Snap ID.
1909+ * @param newSnapState - The new state of the Snap.
1910+ * @param encrypted - A flag to indicate whether to use encrypted storage or
1911+ * not.
1912+ */
1913+ #persistSnapState = withMutex (
1914+ async (
1915+ snapId : SnapId ,
1916+ newSnapState : Record < string , Json > | null ,
1917+ encrypted : boolean ,
1918+ ) => {
1919+ const newState = await this . #getStateToPersist(
1920+ snapId ,
1921+ newSnapState ,
1922+ encrypted ,
1923+ ) ;
1924+
1925+ if ( encrypted ) {
1926+ return this . update ( ( state ) => {
1927+ state . snapStates [ snapId ] = newState ;
1928+ } ) ;
1929+ }
1930+
1931+ return this . update ( ( state ) => {
1932+ state . unencryptedSnapStates [ snapId ] = newState ;
1933+ } ) ;
1934+ } ,
1935+ ) ;
1936+
18631937 /**
18641938 * Updates the own state of the snap with the given id.
18651939 * This is distinct from the state MetaMask uses to manage snaps.
@@ -1873,17 +1947,19 @@ export class SnapController extends BaseController<
18731947 newSnapState : Record < string , Json > ,
18741948 encrypted : boolean ,
18751949 ) {
1876- if ( encrypted ) {
1877- const encryptedState = await this . #encryptSnapState( snapId , newSnapState ) ;
1950+ const runtime = this . #getRuntimeExpect( snapId ) ;
18781951
1879- this . update ( ( state ) => {
1880- state . snapStates [ snapId ] = encryptedState ;
1881- } ) ;
1952+ if ( encrypted ) {
1953+ runtime . state = newSnapState ;
18821954 } else {
1883- this . update ( ( state ) => {
1884- state . unencryptedSnapStates [ snapId ] = JSON . stringify ( newSnapState ) ;
1885- } ) ;
1955+ runtime . unencryptedState = newSnapState ;
18861956 }
1957+
1958+ // This is intentionally run asynchronously to avoid blocking the main
1959+ // thread.
1960+ this . #persistSnapState( snapId , newSnapState , encrypted ) . catch ( ( error ) => {
1961+ logError ( error ) ;
1962+ } ) ;
18871963 }
18881964
18891965 /**
@@ -1894,12 +1970,17 @@ export class SnapController extends BaseController<
18941970 * @param encrypted - A flag to indicate whether to use encrypted storage or not.
18951971 */
18961972 clearSnapState ( snapId : SnapId , encrypted : boolean ) {
1897- this . update ( ( state ) => {
1898- if ( encrypted ) {
1899- state . snapStates [ snapId ] = null ;
1900- } else {
1901- state . unencryptedSnapStates [ snapId ] = null ;
1902- }
1973+ const runtime = this . #getRuntimeExpect( snapId ) ;
1974+ if ( encrypted ) {
1975+ runtime . state = null ;
1976+ } else {
1977+ runtime . unencryptedState = null ;
1978+ }
1979+
1980+ // This is intentionally run asynchronously to avoid blocking the main
1981+ // thread.
1982+ this . #persistSnapState( snapId , null , encrypted ) . catch ( ( error ) => {
1983+ logError ( error ) ;
19031984 } ) ;
19041985 }
19051986
@@ -1912,6 +1993,13 @@ export class SnapController extends BaseController<
19121993 * @returns The requested snap state or null if no state exists.
19131994 */
19141995 async getSnapState ( snapId : SnapId , encrypted : boolean ) : Promise < Json > {
1996+ const runtime = this . #getRuntimeExpect( snapId ) ;
1997+ const cachedState = encrypted ? runtime . state : runtime . unencryptedState ;
1998+
1999+ if ( cachedState !== undefined ) {
2000+ return cachedState ;
2001+ }
2002+
19152003 const state = encrypted
19162004 ? this . state . snapStates [ snapId ]
19172005 : this . state . unencryptedSnapStates [ snapId ] ;
@@ -1921,11 +2009,17 @@ export class SnapController extends BaseController<
19212009 }
19222010
19232011 if ( ! encrypted ) {
1924- // For performance reasons, we do not validate that the state is JSON, since we control serialization.
1925- return JSON . parse ( state ) ;
2012+ // For performance reasons, we do not validate that the state is JSON,
2013+ // since we control serialization.
2014+ const json = JSON . parse ( state ) ;
2015+ runtime . unencryptedState = json ;
2016+
2017+ return json ;
19262018 }
19272019
19282020 const decrypted = await this . #decryptSnapState( snapId , state ) ;
2021+ runtime . state = decrypted ;
2022+
19292023 return decrypted ;
19302024 }
19312025
@@ -3706,6 +3800,8 @@ export class SnapController extends BaseController<
37063800 pendingOutboundRequests : 0 ,
37073801 interpreter,
37083802 stopping : false ,
3803+ state : undefined ,
3804+ unencryptedState : undefined ,
37093805 } ) ;
37103806 }
37113807
0 commit comments