import { EventEmitter } from 'events' ; import objectsRegistry from './objects-registry' ; import { isPromise , isSerializableObject , deserialize , serialize } from '../common/type-utils' ; import type { MetaTypeFromRenderer , ObjectMember , MetaType , ObjProtoDescriptor } from '../common/types' ; import { ipcMain , WebContents , IpcMainEvent , app } from 'electron' ; import { IPC_MESSAGES } from '../common/ipc-messages' ; import { getElectronBinding } from '../common/get-electron-binding' ; const v8Util = getElectronBinding ( 'v8_util' ) ; const hasWebPrefsRemoteModuleAPI = ( ( ) => { const electronVersion = Number ( process . versions . electron ?. split ( "." ) ?. [ 0 ] ) ; return Number . isNaN ( electronVersion ) || electronVersion < 14 ; } ) ( ) ; const FUNCTION_PROPERTIES = [ 'length' , 'name' , 'arguments' , 'caller' , 'prototype' ] ; type RendererFunctionId = [ string , number ] ; type FinalizerInfo = { id : RendererFunctionId ; webContents : WebContents ; frameId : number ; } ; type CallIntoRenderer = ( ... args : any [ ] ) => void ; const rendererFunctionCache = new Map < string , WeakRef < CallIntoRenderer > > ( ) ; const finalizationRegistry = new FinalizationRegistry ( ( fi ) => { const mapKey = fi . id [ 0 ] + '~' + fi . id [ 1 ] ; const ref = rendererFunctionCache . get ( mapKey ) ; if ( ref !== undefined && ref . deref ( ) === undefined ) { rendererFunctionCache . delete ( mapKey ) ; if ( ! fi . webContents . isDestroyed ( ) ) { try { fi . webContents . sendToFrame ( fi . frameId , IPC_MESSAGES . RENDERER_RELEASE_CALLBACK , fi . id [ 0 ] , fi . id [ 1 ] ) ; } catch ( error ) { console . warn ( `sendToFrame() failed: ${ error }` ) ; } } } } ) ; function getCachedRendererFunction ( id ) { const mapKey = id [ 0 ] + '~' + id [ 1 ] ; const ref = rendererFunctionCache . get ( mapKey ) ; if ( ref !== undefined ) { const deref = ref . deref ( ) ; if ( deref !== undefined ) return deref ; } } function setCachedRendererFunction ( id , wc , frameId , value ) { const wr = new WeakRef ( value ) ; const mapKey = id [ 0 ] + '~' + id [ 1 ] ; rendererFunctionCache . set ( mapKey , wr ) ; finalizationRegistry . register ( value , { id , webContents : wc , frameId } as FinalizerInfo ) ; return value ; } const locationInfo = new WeakMap < Object , string > ( ) ; const getObjectMembers = function ( object ) : ObjectMember [ ] { let names = Object . getOwnPropertyNames ( object ) ; if ( typeof object === 'function' ) { names = names . filter ( ( name ) => { return ! FUNCTION_PROPERTIES . includes ( name ) ; } ) ; } return names . map ( ( name ) => { const descriptor = Object . getOwnPropertyDescriptor ( object , name ) ! ; let type ; let writable = false ; if ( descriptor . get === undefined && typeof object [ name ] === 'function' ) { type = 'method' ; } else { if ( descriptor . set || descriptor . writable ) writable = true ; type = 'get' ; } return { name , enumerable : descriptor . enumerable , writable , type } ; } ) ; } ; const getObjectPrototype = function ( object ) : ObjProtoDescriptor { const proto = Object . getPrototypeOf ( object ) ; if ( proto === null || proto === Object . prototype ) return null ; return { members : getObjectMembers ( proto ) , proto : getObjectPrototype ( proto ) } ; } ; const valueToMeta = function ( sender , contextId , value , optimizeSimpleObject = false ) : MetaType { let type ; switch ( typeof value ) { case 'object' : if ( value instanceof Buffer ) { type = 'buffer' ; } else if ( value && value . constructor && value . constructor . name === 'NativeImage' ) { type = 'nativeimage' ; } else if ( Array . isArray ( value ) ) { type = 'array' ; } else if ( value instanceof Error ) { type = 'error' ; } else if ( isSerializableObject ( value ) ) { type = 'value' ; } else if ( isPromise ( value ) ) { type = 'promise' ; } else if ( Object . prototype . hasOwnProperty . call ( value , 'callee' ) && value . length != null ) { type = 'array' ; } else if ( optimizeSimpleObject && v8Util . getHiddenValue ( value , 'simple' ) ) { type = 'value' ; } else { type = 'object' ; } break ; case 'function' : type = 'function' ; break ; default : type = 'value' ; break ; } if ( type === 'array' ) { return { type , members : value . map ( ( el ) => valueToMeta ( sender , contextId , el , optimizeSimpleObject ) ) } ; } else if ( type === 'nativeimage' ) { return { type , value : serialize ( value ) } ; } else if ( type === 'object' || type === 'function' ) { return { type , name : value . constructor ? value . constructor . name : '' , id : objectsRegistry . add ( sender , contextId , value ) , members : getObjectMembers ( value ) , proto : getObjectPrototype ( value ) } ; } else if ( type === 'buffer' ) { return { type , value } ; } else if ( type === 'promise' ) { value . then ( function ( ) { } , function ( ) { } ) ; return { type , then : valueToMeta ( sender , contextId , function ( onFulfilled , onRejected ) { value . then ( onFulfilled , onRejected ) ; } ) } ; } else if ( type === 'error' ) { return { type , value , members : Object . keys ( value ) . map ( name => ( { name , value : valueToMeta ( sender , contextId , value [ name ] ) } ) ) } ; } else { return { type : 'value' , value } ; } } ; const throwRPCError = function ( message ) { const error = new Error ( message ) as Error & { code : string ; errno : number ; } ; error . code = 'EBADRPC' ; error . errno = - 72 ; throw error ; } ; const removeRemoteListenersAndLogWarning = ( sender , callIntoRenderer ) => { const location = locationInfo . get ( callIntoRenderer ) ; let message = 'Attempting to call a function in a renderer window that has been closed or released.' + `\nFunction provided here: ${ location }` ; if ( sender instanceof EventEmitter ) { const remoteEvents = sender . eventNames ( ) . filter ( ( eventName ) => { return sender . listeners ( eventName ) . includes ( callIntoRenderer ) ; } ) ; if ( remoteEvents . length > 0 ) { message += `\nRemote event names: ${ remoteEvents . join ( ', ' ) }` ; remoteEvents . forEach ( ( eventName ) => { sender . removeListener ( eventName , callIntoRenderer ) ; } ) ; } } console . warn ( message ) ; } ; const fakeConstructor = ( constructor , name ) => new Proxy ( Object , { get ( target , prop , receiver ) { if ( prop === 'name' ) { return name ; } else { return Reflect . get ( target , prop , receiver ) ; } } } ) ; const unwrapArgs = function ( sender , frameId , contextId , args ) { const metaToValue = function ( meta ) : any { switch ( meta . type ) { case 'nativeimage' : return deserialize ( meta . value ) ; case 'value' : return meta . value ; case 'remote-object' : return objectsRegistry . get ( meta . id ) ; case 'array' : return unwrapArgs ( sender , frameId , contextId , meta . value ) ; case 'buffer' : return Buffer . from ( meta . value . buffer , meta . value . byteOffset , meta . value . byteLength ) ; case 'promise' : return Promise . resolve ( { then : metaToValue ( meta . then ) } ) ; case 'object' : { const ret = meta . name !== 'Object' ? Object . create ( { constructor : fakeConstructor ( Object , meta . name ) } ) : { } ; for ( const { name , value } of meta . members ) { ret [ name ] = metaToValue ( value ) ; } return ret ; } case 'function-with-return-value' : { const returnValue = metaToValue ( meta . value ) ; return function ( ) { return returnValue ; } ; } case 'function' : { const objectId = [ contextId , meta . id ] ; const cachedFunction = getCachedRendererFunction ( objectId ) ; if ( cachedFunction !== undefined ) { return cachedFunction ; } const callIntoRenderer = function ( this , ... args ) { let succeed = false ; if ( ! sender . isDestroyed ( ) ) { try { succeed = sender . sendToFrame ( frameId , IPC_MESSAGES . RENDERER_CALLBACK , contextId , meta . id , valueToMeta ( sender , contextId , args ) ) as any as boolean !== false ; } catch ( error ) { console . warn ( `sendToFrame() failed: ${ error }` ) ; } } if ( ! succeed ) { removeRemoteListenersAndLogWarning ( this , callIntoRenderer ) ; } } ; locationInfo . set ( callIntoRenderer , meta . location ) ; Object . defineProperty ( callIntoRenderer , 'length' , { value : meta . length } ) ; setCachedRendererFunction ( objectId , sender , frameId , callIntoRenderer ) ; return callIntoRenderer ; } default : throw new TypeError ( `Unknown type: ${ ( meta as any ) . type }` ) ; } } ; return args . map ( metaToValue ) ; } ; const isRemoteModuleEnabledImpl = function ( contents ) { const webPreferences = contents . getLastWebPreferences ( ) || { } ; return ( webPreferences as any ) . enableRemoteModule != null ? ! ! ( webPreferences as any ) . enableRemoteModule : false ; } ; const isRemoteModuleEnabledCache = new WeakMap < WebContents , boolean > ( ) ; export const isRemoteModuleEnabled = function ( contents ) { if ( hasWebPrefsRemoteModuleAPI && ! isRemoteModuleEnabledCache . has ( contents ) ) { isRemoteModuleEnabledCache . set ( contents , isRemoteModuleEnabledImpl ( contents ) ) ; } return isRemoteModuleEnabledCache . get ( contents ) ; } ; export function enable ( contents ) { isRemoteModuleEnabledCache . set ( contents , true ) ; } const handleRemoteCommand = function ( channel , handler ) { ipcMain . on ( channel , ( event , contextId , ... args ) => { let returnValue ; if ( ! isRemoteModuleEnabled ( event . sender ) ) { event . returnValue = { type : 'exception' , value : valueToMeta ( event . sender , contextId , new Error ( '@electron/remote is disabled for this WebContents. Call require("@electron/remote/main").enable(webContents) to enable it.' ) ) } ; return ; } try { returnValue = handler ( event , contextId , ... args ) ; } catch ( error ) { returnValue = { type : 'exception' , value : valueToMeta ( event . sender , contextId , error ) , } ; } if ( returnValue !== undefined ) { event . returnValue = returnValue ; } } ) ; } ; const emitCustomEvent = function ( contents , eventName , ... args ) { const event = { sender : contents , returnValue : undefined as any , defaultPrevented : false } ; app . emit ( eventName , event , contents , ... args ) ; contents . emit ( eventName , event , ... args ) ; return event ; } ; const logStack = function ( contents , code , stack ) { if ( stack ) { console . warn ( `WebContents (${ contents . id }): ${ code }` , stack ) ; } } ; let initialized = false ; export function initialize ( ) { if ( initialized ) throw new Error ( '@electron/remote has already been initialized' ) ; initialized = true ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_WRONG_CONTEXT_ERROR , function ( event , contextId , passedContextId , id ) { const objectId = [ passedContextId , id ] ; const cachedFunction = getCachedRendererFunction ( objectId ) ; if ( cachedFunction === undefined ) { return ; } removeRemoteListenersAndLogWarning ( event . sender , cachedFunction ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_REQUIRE , function ( event , contextId , moduleName , stack ) { logStack ( event . sender , `remote.require('${ moduleName }')` , stack ) ; const customEvent = emitCustomEvent ( event . sender , 'remote-require' , moduleName ) ; if ( customEvent . returnValue === undefined ) { if ( customEvent . defaultPrevented ) { throw new Error ( `Blocked remote.require('${ moduleName }')` ) ; } else { customEvent . returnValue = process . mainModule ! . require ( moduleName ) ; } } return valueToMeta ( event . sender , contextId , customEvent . returnValue ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_GET_BUILTIN , function ( event , contextId , moduleName , stack ) { logStack ( event . sender , `remote.getBuiltin('${ moduleName }')` , stack ) ; const customEvent = emitCustomEvent ( event . sender , 'remote-get-builtin' , moduleName ) ; if ( customEvent . returnValue === undefined ) { if ( customEvent . defaultPrevented ) { throw new Error ( `Blocked remote.getBuiltin('${ moduleName }')` ) ; } else { customEvent . returnValue = ( require ( 'electron' ) as any ) [ moduleName ] ; } } return valueToMeta ( event . sender , contextId , customEvent . returnValue ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_GET_GLOBAL , function ( event , contextId , globalName , stack ) { logStack ( event . sender , `remote.getGlobal('${ globalName }')` , stack ) ; const customEvent = emitCustomEvent ( event . sender , 'remote-get-global' , globalName ) ; if ( customEvent . returnValue === undefined ) { if ( customEvent . defaultPrevented ) { throw new Error ( `Blocked remote.getGlobal('${ globalName }')` ) ; } else { customEvent . returnValue = ( global as any ) [ globalName ] ; } } return valueToMeta ( event . sender , contextId , customEvent . returnValue ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_GET_CURRENT_WINDOW , function ( event , contextId , stack ) { logStack ( event . sender , 'remote.getCurrentWindow()' , stack ) ; const customEvent = emitCustomEvent ( event . sender , 'remote-get-current-window' ) ; if ( customEvent . returnValue === undefined ) { if ( customEvent . defaultPrevented ) { throw new Error ( 'Blocked remote.getCurrentWindow()' ) ; } else { customEvent . returnValue = event . sender . getOwnerBrowserWindow ( ) ; } } return valueToMeta ( event . sender , contextId , customEvent . returnValue ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_GET_CURRENT_WEB_CONTENTS , function ( event , contextId , stack ) { logStack ( event . sender , 'remote.getCurrentWebContents()' , stack ) ; const customEvent = emitCustomEvent ( event . sender , 'remote-get-current-web-contents' ) ; if ( customEvent . returnValue === undefined ) { if ( customEvent . defaultPrevented ) { throw new Error ( 'Blocked remote.getCurrentWebContents()' ) ; } else { customEvent . returnValue = event . sender ; } } return valueToMeta ( event . sender , contextId , customEvent . returnValue ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_CONSTRUCTOR , function ( event , contextId , id , args ) { args = unwrapArgs ( event . sender , event . frameId , contextId , args ) ; const constructor = objectsRegistry . get ( id ) ; if ( constructor == null ) { throwRPCError ( `Cannot call constructor on missing remote object ${ id }` ) ; } return valueToMeta ( event . sender , contextId , new constructor ( ... args ) ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_FUNCTION_CALL , function ( event , contextId , id , args ) { args = unwrapArgs ( event . sender , event . frameId , contextId , args ) ; const func = objectsRegistry . get ( id ) ; if ( func == null ) { throwRPCError ( `Cannot call function on missing remote object ${ id }` ) ; } try { return valueToMeta ( event . sender , contextId , func ( ... args ) , true ) ; } catch ( error ) { const err = new Error ( `Could not call remote function '${ func . name || 'anonymous' }'. Check that the function signature is correct. Underlying error: ${ error . message }\nUnderlying stack: ${ error . stack }\n` ) ; ( err as any ) . cause = error ; throw err ; } } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_MEMBER_CONSTRUCTOR , function ( event , contextId , id , method , args ) { args = unwrapArgs ( event . sender , event . frameId , contextId , args ) ; const object = objectsRegistry . get ( id ) ; if ( object == null ) { throwRPCError ( `Cannot call constructor '${ method }' on missing remote object ${ id }` ) ; } return valueToMeta ( event . sender , contextId , new object [ method ] ( ... args ) ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_MEMBER_CALL , function ( event , contextId , id , method , args ) { args = unwrapArgs ( event . sender , event . frameId , contextId , args ) ; const object = objectsRegistry . get ( id ) ; if ( object == null ) { throwRPCError ( `Cannot call method '${ method }' on missing remote object ${ id }` ) ; } try { return valueToMeta ( event . sender , contextId , object [ method ] ( ... args ) , true ) ; } catch ( error ) { const err = new Error ( `Could not call remote method '${ method }'. Check that the method signature is correct. Underlying error: ${ error . message }\nUnderlying stack: ${ error . stack }\n` ) ; ( err as any ) . cause = error ; throw err ; } } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_MEMBER_SET , function ( event , contextId , id , name , args ) { args = unwrapArgs ( event . sender , event . frameId , contextId , args ) ; const obj = objectsRegistry . get ( id ) ; if ( obj == null ) { throwRPCError ( `Cannot set property '${ name }' on missing remote object ${ id }` ) ; } obj [ name ] = args [ 0 ] ; return null ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_MEMBER_GET , function ( event , contextId , id , name ) { const obj = objectsRegistry . get ( id ) ; if ( obj == null ) { throwRPCError ( `Cannot get property '${ name }' on missing remote object ${ id }` ) ; } return valueToMeta ( event . sender , contextId , obj [ name ] ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_DEREFERENCE , function ( event , contextId , id ) { objectsRegistry . remove ( event . sender , contextId , id ) ; } ) ; handleRemoteCommand ( IPC_MESSAGES . BROWSER_CONTEXT_RELEASE , ( event , contextId ) => { objectsRegistry . clear ( event . sender , contextId ) ; return null ; } ) ; }