From edd8ccaad1b58a88d7bb79c9287d1699a0464ceb Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 10 Mar 2026 11:31:46 -0700 Subject: [PATCH 1/2] Add 25 minute keep alive interval and mouse move event forward to parent --- .../HostedInstanceEditorWrapper.vue | 11 +++++++++ .../RemoteInstanceEditorWrapper.vue | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue b/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue index a8974c496e..881dc433ac 100644 --- a/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue +++ b/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue @@ -70,8 +70,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 +98,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..0529b245f2 100644 --- a/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue +++ b/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue @@ -58,13 +58,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')) + } + } } } From 25067771fc10d29704d68d9eb532d655b302b831 Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Wed, 11 Mar 2026 14:26:16 -0700 Subject: [PATCH 2/2] Add posthogKeepAliveInterval to data block --- .../immersive-editor/HostedInstanceEditorWrapper.vue | 5 +++++ .../immersive-editor/RemoteInstanceEditorWrapper.vue | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue b/frontend/src/components/immersive-editor/HostedInstanceEditorWrapper.vue index 881dc433ac..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) diff --git a/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue b/frontend/src/components/immersive-editor/RemoteInstanceEditorWrapper.vue index 0529b245f2..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'