import { Env , FnRequestInteraction , MessageSender , APP_PORT , } from "@keplr-wallet/router" ; import { openPopupWindow as openPopupWindowInner } from "@keplr-wallet/popup" ; import { InExtensionMessageRequester } from "../requester" ; class PromiseQueue { protected workingOnPromise : boolean = false ; protected queue : { fn : ( ) => Promise < unknown > ; resolve : ( result : any ) => void ; reject : ( e : any ) => void ; } [ ] = [ ] ; enqueue < R > ( fn ) { return new Promise < R > ( ( resolve , reject ) => { this . queue . push ( { fn , resolve , reject , } ) ; this . dequeue ( ) ; } ) ; } protected dequeue ( ) { if ( this . workingOnPromise ) { return ; } const item = this . queue . shift ( ) ; if ( ! item ) { return ; } this . workingOnPromise = true ; item . fn ( ) . then ( ( result ) => { item . resolve ( result ) ; } ) . catch ( ( e ) => { item . reject ( e ) ; } ) . finally ( ( ) => { this . workingOnPromise = false ; this . dequeue ( ) ; } ) ; } } const openPopupQueue = new PromiseQueue ( ) ; async function openPopupWindow ( url , channel = "default" ) { return await openPopupQueue . enqueue ( ( ) => openPopupWindowInner ( url , channel ) ) ; } export class ExtensionEnv { static readonly produceEnv = ( sender , routerMeta ) : Env => { const isInternalMsg = ExtensionEnv . checkIsInternalMessage ( sender , browser . runtime . id , browser . runtime . getURL ( "/" ) ) ; const queryString = `interaction=true&interactionInternal=${ isInternalMsg }` ; const openAndSendMsg = async ( url , msg , options ) => { if ( url . startsWith ( "/" ) ) { url = url . slice ( 1 ) ; } url = browser . runtime . getURL ( "/popup.html#/" + url ) ; if ( url . includes ( "?" ) ) { url += "&" + queryString ; } else { url += "?" + queryString ; } const windowId = await openPopupWindow ( url , options ?. channel ) ; const window = await browser . windows . get ( windowId , { populate : true , } ) ; const tabId = window . tabs ! [ 0 ] . id ! ; await ( async ( ) => { const tab = await browser . tabs . get ( tabId ) ; if ( tab . status === "complete" ) { return ; } return new Promise < void > ( ( resolve ) => { browser . tabs . onUpdated . addListener ( ( _tabId , changeInfo ) => { if ( tabId === _tabId && changeInfo . status === "complete" ) { resolve ( ) ; } } ) ; } ) ; } ) ( ) ; return await InExtensionMessageRequester . sendMessageToTab ( tabId , APP_PORT , msg ) ; } ; if ( ! isInternalMsg ) { return { isInternalMsg , requestInteraction : openAndSendMsg , } ; } else { const requestInteraction = async ( url , msg , options ) => { if ( options ?. forceOpenWindow ) { return await openAndSendMsg ( url , msg , options ) ; } if ( url . startsWith ( "/" ) ) { url = url . slice ( 1 ) ; } url = browser . runtime . getURL ( "/popup.html#/" + url ) ; if ( url . includes ( "?" ) ) { url += "&" + queryString ; } else { url += "?" + queryString ; } const backgroundPage = await browser . runtime . getBackgroundPage ( ) ; const views = browser . extension . getViews ( { tabId : sender . tab ?. id , } ) . filter ( ( window ) => { return ( window . location . href !== backgroundPage . location . href && ( routerMeta . routerId == null || routerMeta . routerId === window . keplrExtensionRouterId ) ) ; } ) ; if ( views . length > 0 ) { for ( const view of views ) { view . location . href = url ; } } msg . routerMeta = { ... msg . routerMeta , receiverRouterId : routerMeta . routerId , } ; return await new InExtensionMessageRequester ( ) . sendMessage ( APP_PORT , msg ) ; } ; return { isInternalMsg , requestInteraction , } ; } } ; public static readonly checkIsInternalMessage = ( sender , extensionId , extensionUrl ) : boolean => { if ( ! sender . url ) { throw new Error ( "Empty sender url" ) ; } const url = new URL ( sender . url ) ; if ( ! url . origin || url . origin === "null" ) { throw new Error ( "Invalid sender url" ) ; } const browserURL = new URL ( extensionUrl ) ; if ( ! browserURL . origin || browserURL . origin === "null" ) { throw new Error ( "Invalid browser url" ) ; } if ( url . origin !== browserURL . origin ) { return false ; } return sender . id === extensionId ; } ; }