import React , { Component } from 'react' ; import { currentEditorUrl } from 'common/lib/environment' ; import { RUNNER_TO_EDITOR_HEARTBEAT_REQUESTS , EDITOR_HEARTBEAT_TO_RUNNER_RESPONSES , IEditorHeartbeatToRunnerResponse , } from 'common/lib/constants' ; import { ScriptLabError } from 'common/lib/utilities/error' ; const LOCAL_STORAGE_POLLING_INTERVAL = 300 ; const heartbeatEditorUrl = `${ currentEditorUrl }/#/heartbeat` ; export interface IProps { host : string ; onReceiveNewActiveSolution : ( solution : ISolution | null ) => void ; onReceivedMessageToPassToUserSnippet : ( message : IEditorHeartbeatToRunnerResponse ) => void ; } interface IState { activeSolution ? : ISolution | null ; } class Heartbeat extends Component < IProps , IState > { node = React . createRef < HTMLIFrameElement > ( ) ; state : IState = { activeSolution : undefined } ; pollingInterval : any ; componentDidMount ( ) { this . pollingInterval = setInterval ( ( ) => { this . requestActiveSolution ( ) ; } , LOCAL_STORAGE_POLLING_INTERVAL ) ; window . onmessage = this . onWindowMessage ; } componentWillUnmount ( ) { clearInterval ( this . pollingInterval ) ; window . onmessage = null ; } sendMessage = ( message ) => { if ( this . node . current ) { this . node . current . contentWindow ! . postMessage ( message , currentEditorUrl ) ; } } ; private requestActiveSolution = ( ) => { this . sendMessage ( `${ RUNNER_TO_EDITOR_HEARTBEAT_REQUESTS . GET_ACTIVE_SOLUTION }/${ this . props . host }` ) ; } ; private onWindowMessage = ( { origin , data } ) => { if ( origin !== currentEditorUrl ) { return ; } const processActiveSolution = ( solutionText ) => { const solution = JSON . parse ( solutionText ) as ISolution | null ; if ( solution && solution . options . isCustomFunctionsSolution ) { window . location . href = `${ currentEditorUrl }/custom-functions.html` ; } if ( this . checkIfSolutionChanged ( solution ) ) { this . setState ( { activeSolution : solution } ) ; this . props . onReceiveNewActiveSolution ( solution ) ; } } ; try { const parsedData = data as IEditorHeartbeatToRunnerResponse ; const responsesMap = { undefined : ( ) => { processActiveSolution ( parsedData as any ) ; } , [ EDITOR_HEARTBEAT_TO_RUNNER_RESPONSES . ACTIVE_SOLUTION ] : ( ) => { processActiveSolution ( parsedData . contents ) ; } , [ EDITOR_HEARTBEAT_TO_RUNNER_RESPONSES . PASS_MESSAGE_TO_USER_SNIPPET ] : ( ) => { this . props . onReceivedMessageToPassToUserSnippet ( parsedData . contents ) ; } , } ; const appropriateResponse = responsesMap [ parsedData . type ] ; if ( appropriateResponse ) { appropriateResponse ( ) ; } else { throw new ScriptLabError ( 'Invalid heartbeat message received' , data ) ; } } catch ( err ) { console . error ( err ) ; } } ; render ( ) { return ( < iframe style = { { display : 'none' } } title = "heartbeat" src = { heartbeatEditorUrl } ref = { this . node } / > ) ; } private checkIfSolutionChanged ( solution ) { if ( ( ! this . state . activeSolution && solution ) || solution === null ) { return true ; } if ( solution ) { if ( solution . id !== this . state . activeSolution . id ) { return true ; } if ( solution . dateLastModified > this . state . activeSolution . dateLastModified ) { return true ; } } return false ; } } export default Heartbeat ;