import { officeNamespacesForCustomFunctionsIframe } from '../../constants' ; import { currentEditorUrl } from 'common/lib/environment' ; import { generateLogString , stringifyPlusPlus } from 'common/lib/utilities/string' ; import generateCustomFunctionIframe from './run.customFunctions' ; import { initializeJupyter } from './jupyterRunner' ; import { SCRIPT_URLS } from 'common/lib/constants' ; import { addScriptTag } from 'common/lib/utilities/script-loader' ; const HEARTBEAT_URL = `${ currentEditorUrl }/custom-functions-heartbeat.html` ; const VERBOSE_MODE = false ; export const METHODS_EXPOSED_ON_CF_RUNNER_OUTER_FRAME = { scriptRunnerOnLoad : 'scriptRunnerOnLoad' , scriptRunnerOnLoadComplete : 'scriptRunnerOnLoadComplete' , } ; export default ( ) => { window . document . title = 'Script Lab - Custom Functions runner' ; const ScriptLabCustomFunctionsDictionary = { } ; ( window as any ) . ScriptLabCustomFunctionsDictionary = ScriptLabCustomFunctionsDictionary ; window . onmessage = async ( { origin , data } ) => { if ( origin !== currentEditorUrl ) { return ; } const { type , payload } = JSON . parse ( data ) ; switch ( type ) { case 'metadata' : { const initialPayload = payload as ICustomFunctionsIframeRunnerOnLoadPayload ; const scriptUrl = getScriptUrl ( initialPayload ) ; await addScriptTag ( scriptUrl ) . then ( ( ) => { ( CustomFunctions as any ) . delayInitialization ( ) ; } ) ; if ( initialPayload . pythonConfig ) { initializeJupyter ( initialPayload . pythonConfig ) ; } await initializeRunnableSnippets ( initialPayload ) ; for ( const key in ScriptLabCustomFunctionsDictionary ) { CustomFunctions . associate ( key , ScriptLabCustomFunctionsDictionary [ key ] ) ; } delete ( window as any ) . CustomFunctionMappings . __delay__ ; break ; } case 'refresh' : window . location . reload ( ) ; break ; default : throw new Error ( `Unexpected event type: ${ type }` ) ; } } ; addHeartbeat ( ) ; overwriteConsole ( '[SYSTEM]' , window ) ; logIfExtraLoggingEnabled ( 'Done preparing snippets' ) ; logIfExtraLoggingEnabled ( 'Custom functions runner is ready to evaluate your functions!' ) ; } ; let heartbeat ; function addHeartbeat ( ) { heartbeat = document . createElement ( 'iframe' ) ; heartbeat . style . display = 'none' ; heartbeat . src = HEARTBEAT_URL ; document . body . appendChild ( heartbeat ) ; } async function initializeRunnableSnippets ( fullPayload ) { return new Promise ( resolve => tryCatch ( ( ) => { let successfulRegistrationsCount = 0 ; window [ METHODS_EXPOSED_ON_CF_RUNNER_OUTER_FRAME . scriptRunnerOnLoad ] = ( contentWindow , id ) => tryCatch ( ( ) => { const snippetMetadata = fullPayload . typescriptMetadata . find ( item => item . solutionId === id ) ! ; overwriteConsole ( snippetMetadata . namespace , contentWindow ) ; contentWindow . onerror = ( ... args ) => console . error ( args ) ; logIfExtraLoggingEnabled ( `Snippet for namespace "${ snippetMetadata . namespace }" beginning to load.` ) ; officeNamespacesForCustomFunctionsIframe . forEach ( namespace => { ( contentWindow as any ) [ namespace ] = ( window as any ) [ namespace ] ; } ) ; } ) ; window [ METHODS_EXPOSED_ON_CF_RUNNER_OUTER_FRAME . scriptRunnerOnLoadComplete ] = ( ) => { if ( ++ successfulRegistrationsCount === fullPayload . typescriptMetadata . length ) { resolve ( ) ; } } ; fullPayload . typescriptMetadata . forEach ( customFuncData => { const iframe = document . createElement ( 'iframe' ) ; iframe . src = 'about:blank' ; document . head . insertBefore ( iframe , null ) ; const contentWindow = iframe . contentWindow ! ; contentWindow . document . open ( ) ; contentWindow . document . write ( generateCustomFunctionIframe ( customFuncData ) ) ; ( contentWindow as any ) . console = window . console ; contentWindow . onerror = ( ... args ) => { handleError ( { error : args } ) ; } ; contentWindow . document . close ( ) ; } ) ; } ) ) ; } function logIfExtraLoggingEnabled ( message ) { if ( VERBOSE_MODE ) { tryToSendLog ( { message : message , severity : 'info' , source : '[SYSTEM]' , } ) ; } } function overwriteConsole ( source , windowObject ) { const logTypes = [ 'log' , 'info' , 'warn' , 'error' ] ; logTypes . forEach ( methodName => ( ( windowObject . console as any ) [ methodName ] = consoleMsgTypeImplementation ( methodName ) ) ) ; function consoleMsgTypeImplementation ( severityType ) { return ( ... args ) => { const { severity , message } = generateLogString ( args , severityType ) ; tryToSendLog ( { source , severity , message , } ) ; } ; } } let logCounter = 0 ; function tryToSendLog ( data ) { try { if ( heartbeat && heartbeat . contentWindow ) { heartbeat . contentWindow . postMessage ( JSON . stringify ( { type : 'log' , payload : { id : logCounter ++ , message : data . message , severity : data . severity , } , } ) , HEARTBEAT_URL ) ; } } catch ( e ) { } } async function tryCatch ( func ) { try { await func ( ) ; } catch ( e ) { handleError ( e ) ; } } function handleError ( error ) { tryToSendLog ( { message : stringifyPlusPlus ( error ) , severity : 'error' , source : '[SYSTEM]' , } ) ; } function getScriptUrl ( initialPayload ) { if ( initialPayload . pythonConfig ) { return SCRIPT_URLS . CUSTOM_FUNCTIONS_RUNNER_WITH_JUPYTER_SUPPORT ; } if ( initialPayload . customFunctionsRuntimeUrl ) { return initialPayload . customFunctionsRuntimeUrl ; } return SCRIPT_URLS . CUSTOM_FUNCTIONS_RUNNER_DEFAULT ; }