Skip to content

Commit

Permalink
feat: support communication across multiple windows (navigations, rel…
Browse files Browse the repository at this point in the history
…oads)
  • Loading branch information
KaiVandivier committed Feb 26, 2024
1 parent 382d0da commit 1ba9b3c
Showing 1 changed file with 64 additions and 48 deletions.
112 changes: 64 additions & 48 deletions services/plugin/src/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useDataQuery } from '@dhis2/app-service-data'
import postRobot from 'post-robot'
import React, {
ReactEventHandler,
useCallback,
useContext,
useEffect,
useMemo,
Expand Down Expand Up @@ -63,11 +64,6 @@ export const Plugin = ({
appShortName: pluginShortName,
})

const [communicationReceived, setCommunicationReceived] =
useState<boolean>(false)
const [prevCommunicationReceived, setPrevCommunicationReceived] =
useState<boolean>(false)

const [inErrorState, setInErrorState] = useState<boolean>(false)
const [pluginHeight, setPluginHeight] = useState<string | number>('150px')
const [pluginWidth, setPluginWidth] = useState<string | number>('500px')
Expand All @@ -93,63 +89,83 @@ export const Plugin = ({
/* eslint-enable react-hooks/exhaustive-deps */
)

useEffect(() => {
setCommunicationReceived(false)
}, [pluginEntryPoint])
const propsFromParentListenerRef = useRef<any>()
const communicationReceivedRef = useRef<boolean>(false)

useEffect(() => {
// if communicationReceived switches from false to true, the props have been sent
const prevCommunication = prevCommunicationReceived
setPrevCommunicationReceived(communicationReceived)
if (prevCommunication === false && communicationReceived === true) {
const setUpCommunication = useCallback(() => {
if (!iframeRef.current) {
return
}

if (iframeRef?.current) {
const iframeProps = {
...memoizedPropsToPass,
alertsAdd,
setPluginHeight,
setPluginWidth,
setInErrorState,
setCommunicationReceived,
}
const iframeProps = {
...memoizedPropsToPass,
alertsAdd,
setPluginHeight,
setPluginWidth,
setInErrorState,
// todo: what does this do? resets state from error component
// seems to work without...
// setCommunicationReceived,
}

// if iframe has not sent initial request, set up a listener
if (!communicationReceived && !inErrorState) {
const listener = postRobot.on(
// if iframe has not sent initial request, set up a listener
if (!communicationReceivedRef.current && !inErrorState) {
// avoid setting up twice
if (!propsFromParentListenerRef.current) {
propsFromParentListenerRef.current = postRobot.on(
'getPropsFromParent',
// listen for messages coming only from the iframe rendered by this component
{ window: iframeRef.current.contentWindow },
(): any => {
setCommunicationReceived(true)
communicationReceivedRef.current = true
return iframeProps
}
)
return () => listener.cancel()
}

// if iframe has sent initial request, send new props
if (
communicationReceived &&
iframeRef.current.contentWindow &&
!inErrorState
) {
postRobot
.send(
iframeRef.current.contentWindow,
'updated',
iframeProps
)
.catch((err) => {
// log postRobot errors, but do not bubble them up
console.error(err)
})
// return clean-up function
return () => {
propsFromParentListenerRef.current.cancel()
propsFromParentListenerRef.current = null
}
}
// prevCommunicationReceived update should not retrigger this hook
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [memoizedPropsToPass, communicationReceived, inErrorState, alertsAdd])

// if iframe has sent initial request, send new props
// (don't do before to avoid sending messages before listeners
// are ready)
if (iframeRef.current.contentWindow && !inErrorState) {
postRobot
.send(iframeRef.current.contentWindow, 'updated', iframeProps)
.catch((err) => {
// log postRobot errors, but do not bubble them up
console.error(err)
})
}
}, [memoizedPropsToPass, inErrorState, alertsAdd])

useEffect(() => {
// return the clean-up function
return setUpCommunication()
}, [setUpCommunication])

const handleLoad = useCallback(
(event) => {
// reset communication received
communicationReceivedRef.current = false
if (propsFromParentListenerRef.current) {
propsFromParentListenerRef.current.cancel()
propsFromParentListenerRef.current = null
}

// Need to set this up again whenever the iframe contentWindow
// changes (e.g. navigations or reloads)
setUpCommunication()

if (onLoad) {
onLoad(event)
}
},
[onLoad, setUpCommunication]
)

if (data && !pluginEntryPoint) {
return (
Expand All @@ -176,7 +192,7 @@ export const Plugin = ({
height: '100%',
border: 'none',
}}
onLoad={onLoad}
onLoad={handleLoad}
></iframe>
</div>
)
Expand Down

0 comments on commit 1ba9b3c

Please sign in to comment.