diff --git a/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue b/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue index a8974c496e..4b33d0acd9 100644 --- a/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue +++ b/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue @@ -51,6 +51,11 @@ export default { } }, emits: ['iframe-loaded', 'toggle-drawer', 'request-drawer-state'], + data () { + return { + posthogKeepAliveInterval: null + } + }, computed: { isInstanceTransitioningStates () { const pendingState = (Object.hasOwnProperty.call(this.instance, 'pendingStateChange') && this.instance.pendingStateChange) @@ -70,8 +75,16 @@ export default { mounted () { // adding event listener to listen for messages from the iframe window.addEventListener('message', this.eventListener) + // Dispatch a synthetic mousemove every 25 minutes to keep PostHog's idle + // session timer alive. PostHog resets its recording after ~30 minutes of + // inactivity on the parent page — but the user may be actively working + // inside the cross-origin iframe where events don't reach the parent. + this.posthogKeepAliveInterval = setInterval(() => { + document.dispatchEvent(new MouseEvent('mousemove')) + }, 25 * 60 * 1000) }, beforeUnmount () { + clearInterval(this.posthogKeepAliveInterval) // Clear the iframe src before unmount so PostHog's rrweb recorder can safely // detach its event listeners. Without this, rrweb throws a SecurityError when // calling contentWindow.removeEventListener on the cross-origin Node-RED iframe. @@ -90,6 +103,9 @@ export default { // todo this event listener should be moved in the messaging.service.js eventListener (event) { if (event.origin === this.instance.url) { + // Forward iframe activity to the parent page so PostHog's idle timer + // is reset when the user is active inside the cross-origin iframe. + document.dispatchEvent(new MouseEvent('mousemove')) switch (event.data.type) { case 'load': this.emitMessage('prevent-redirect', true) diff --git a/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue b/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue index acf2465256..fec01295e8 100644 --- a/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue +++ b/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue @@ -37,6 +37,11 @@ export default { default: false } }, + data () { + return { + posthogKeepAliveInterval: null + } + }, computed: { isDeviceRunning () { return this.computedStatus === 'running' @@ -58,13 +63,36 @@ export default { } } }, + mounted () { + window.addEventListener('message', this.eventListener) + // Dispatch a synthetic mousemove every 25 minutes to keep PostHog's idle + // session timer alive. PostHog resets its recording after ~30 minutes of + // inactivity on the parent page — but the user may be actively working + // inside the cross-origin iframe where events don't reach the parent. + this.posthogKeepAliveInterval = setInterval(() => { + document.dispatchEvent(new MouseEvent('mousemove')) + }, 25 * 60 * 1000) + }, beforeUnmount () { + clearInterval(this.posthogKeepAliveInterval) // Clear the iframe src before unmount so PostHog's rrweb recorder can safely // detach its event listeners. Without this, rrweb throws a SecurityError when // calling contentWindow.removeEventListener on the cross-origin Node-RED iframe. if (this.$refs.iframe) { this.$refs.iframe.src = 'about:blank' } + }, + unmounted () { + window.removeEventListener('message', this.eventListener) + }, + methods: { + eventListener (event) { + if (this.device?.editor?.url && event.origin === this.device.editor.url) { + // Forward iframe activity to the parent page so PostHog's idle timer + // is reset when the user is active inside the cross-origin iframe. + document.dispatchEvent(new MouseEvent('mousemove')) + } + } } }