@@ -83,6 +83,7 @@ class MetaMaskAndroidDriver {
8383 private running : boolean ;
8484 private windowWatcher : Promise < void > ;
8585 private readonly glue : MetaMaskAndroidGlue ;
86+ private lastActive : DOMHighResTimeStamp ;
8687
8788 private constructor (
8889 driver : Browser ,
@@ -94,6 +95,7 @@ class MetaMaskAndroidDriver {
9495 this . windowWatcher = this . watchWindows ( ) ;
9596 this . glue = glue ;
9697 this . capabilities = caps ;
98+ this . lastActive = performance . now ( ) ;
9799 }
98100
99101 public static async create (
@@ -125,6 +127,12 @@ class MetaMaskAndroidDriver {
125127 return new MetaMaskAndroidDriver ( driver , glue , capabilities ) ;
126128 }
127129
130+ private async activateApp ( driver : Browser ) : Promise < void > {
131+ await driver . executeScript ( "mobile: startActivity" , [
132+ { intent : "io.metamask/io.metamask.MainActivity" } ,
133+ ] ) ;
134+ }
135+
128136 public async unlockWithPassword ( driver : Browser ) : Promise < void > {
129137 const passwordTxt = await driver . $ (
130138 '//android.widget.EditText[@resource-id="login-password-input"]' ,
@@ -140,9 +148,7 @@ class MetaMaskAndroidDriver {
140148
141149 if ( 4 !== appState ) {
142150 // The app is not in the foreground.
143- await driver . executeScript ( "mobile: startActivity" , [
144- { intent : "io.metamask/io.metamask.MainActivity" } ,
145- ] ) ;
151+ this . activateApp ( driver ) ;
146152 }
147153
148154 try {
@@ -297,20 +303,32 @@ class MetaMaskAndroidDriver {
297303 ) ;
298304 }
299305
300- private async emitSignMessage (
301- driver : Browser ,
302- handle : string ,
303- ) : Promise < void > {
306+ private async emitSignMessage ( driver : Browser ) : Promise < Event > {
304307 logger . debug ( "emitting signmessage" ) ;
305308
306- throw new Error ( "emitSignMessage: not implemented" ) ;
309+ const message = await driver
310+ . $ (
311+ '//android.widget.TextView[@text="Message:"]/following-sibling::android.widget.TextView[@text]' ,
312+ )
313+ . getAttribute ( "text" )
314+ . then ( ( text ) => text . trim ( ) ) ;
307315
316+ const uuid = crypto . randomUUID ( ) ;
308317 this . glue . emit (
309318 "signmessage" ,
310- new SignMessageEvent ( handle , {
311- message : "TODO" ,
319+ new SignMessageEvent ( uuid , {
320+ message,
312321 } ) ,
313322 ) ;
323+ return { uuid } ;
324+ }
325+
326+ private isPersonalSignModal ( driver : Browser ) : Promise < boolean > {
327+ return driver
328+ . $ (
329+ '//android.view.ViewGroup[@resource-id="personal-signature-request"]' ,
330+ )
331+ . isExisting ( ) ;
314332 }
315333
316334 private async isSendTransactionModal ( driver : Browser ) : Promise < boolean > {
@@ -319,16 +337,11 @@ class MetaMaskAndroidDriver {
319337 ) ;
320338 const pillExists = pill . isExisting ( ) ;
321339
322- // TODO: find a better indicator of SendTransaction...
323- /*
324- const changes = await driver.$(
325- '//android.widget.TextView[@text="Estimated changes"]',
326- );
327- const changesExists = changes.isExisting();
328- */
340+ const toExists = driver
341+ . $ ( '//android.widget.TextView[@text="To:"]' )
342+ . isExisting ( ) ;
329343
330- const changesExists = Promise . resolve ( true ) ;
331- return ( await pillExists ) && ( await changesExists ) ;
344+ return ( await pillExists ) && ( await toExists ) ;
332345 }
333346
334347 private async isConnectAccountModal ( driver : Browser ) : Promise < boolean > {
@@ -352,6 +365,10 @@ class MetaMaskAndroidDriver {
352365 active : this . isSendTransactionModal ( driver ) ,
353366 handler : ( b : Browser ) => this . emitSendTransaction ( b ) ,
354367 } ,
368+ {
369+ active : this . isPersonalSignModal ( driver ) ,
370+ handler : ( b : Browser ) => this . emitSignMessage ( b ) ,
371+ } ,
355372 ] ;
356373
357374 let handler = null ;
@@ -387,11 +404,24 @@ class MetaMaskAndroidDriver {
387404 { appId : "io.metamask" } ,
388405 ] ) ;
389406
407+ const now = performance . now ( ) ;
408+
390409 if ( 4 !== appState ) {
391410 // The app is not in the foreground.
411+
412+ if ( now - this . lastActive > 30000.0 ) {
413+ // Need to flip back to the app every so often to check
414+ // for events.
415+ this . lastActive = now ; // Prevent spamming.
416+ await this . driver . lock ( async ( driver ) => {
417+ await this . activateApp ( driver ) ;
418+ } ) ;
419+ }
392420 continue ;
393421 }
394422
423+ this . lastActive = now ;
424+
395425 await this . driver . lock ( async ( driver ) => {
396426 this . pendingEvent = await this . event ( driver ) ;
397427 } ) ;
@@ -602,6 +632,19 @@ export class MetaMaskAndroidGlue extends Glue {
602632 ) ;
603633 await nameEdit . addValue ( chainName ) ;
604634
635+ const chainIdEdit = await driver . $ (
636+ '//android.widget.EditText[@resource-id="input-chain-id"]' ,
637+ ) ;
638+ while ( true ) {
639+ try {
640+ await chainIdEdit . clearValue ( ) ;
641+ await chainIdEdit . addValue ( action . chainId ) ;
642+ break ;
643+ } catch ( _ ) {
644+ // Sometimes MetaMask is fast enough to populate this field.
645+ }
646+ }
647+
605648 const rpcDrop = await driver . $ (
606649 '//android.view.ViewGroup[@resource-id="drop-down-rpc-menu"]' ,
607650 ) ;
@@ -622,15 +665,6 @@ export class MetaMaskAndroidGlue extends Glue {
622665 ) ;
623666 await confirmRpcBtn . click ( ) ;
624667
625- const chainIdEdit = await driver . $ (
626- '//android.widget.EditText[@resource-id="input-chain-id"]' ,
627- ) ;
628- try {
629- await chainIdEdit . addValue ( action . chainId ) ;
630- } catch ( _ ) {
631- // Sometimes MetaMask is fast enough to populate this field.
632- }
633-
634668 const symbolEdit = await driver . $ (
635669 '//android.widget.EditText[@resource-id="input-network-symbol"]' ,
636670 ) ;
@@ -704,7 +738,18 @@ export class MetaMaskAndroidGlue extends Glue {
704738 override async signMessage ( action : SignMessage ) : Promise < void > {
705739 const cb = await this . driver ;
706740 await cb . lock ( async ( driver ) => {
707- throw new Error ( "signMessage: not implemented" ) ;
741+ let btnXpath ;
742+ if ( action . action === "reject" ) {
743+ btnXpath =
744+ '//android.widget.Button[@content-desc="request-signature-cancel-button"]' ;
745+ } else if ( action . action === "approve" ) {
746+ btnXpath =
747+ '//android.widget.Button[@content-desc="request-signature-confirm-button"]' ;
748+ } else {
749+ throw new Error ( "signMessage: not implemented" ) ;
750+ }
751+
752+ await driver . $ ( btnXpath ) . click ( ) ;
708753 } , action . id ) ;
709754 }
710755
0 commit comments