From 77179af92b6ce727b3b2d559808ea979542ad200 Mon Sep 17 00:00:00 2001 From: Daan Verweij Date: Mon, 16 Feb 2026 09:59:42 +0100 Subject: [PATCH] Added EventListener for Appstate change to reconnect silently closed connections Added set to null when closing connection --- .../src/EZWebsocketNative.tsx | 60 +++++++++++++++--- .../widgets/jjrplus.EZWebsocketNative.mpk | Bin 6717 -> 7561 bytes 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/widgets/easyWebsocketNative/src/EZWebsocketNative.tsx b/widgets/easyWebsocketNative/src/EZWebsocketNative.tsx index 63a5c87..1de6257 100644 --- a/widgets/easyWebsocketNative/src/EZWebsocketNative.tsx +++ b/widgets/easyWebsocketNative/src/EZWebsocketNative.tsx @@ -1,5 +1,5 @@ import { MutableRefObject, useEffect, useRef } from "react"; -import { TextStyle, ViewStyle } from "react-native"; +import { TextStyle, ViewStyle, AppState, AppStateStatus } from "react-native"; import { Style } from "@mendix/pluggable-widgets-tools"; @@ -30,14 +30,7 @@ export function EZWebsocketNative({ // Check if there is no open connection already if ( connection.current === null && - // Make sure all values are initiated - objectId.status === "available" && - websocketIdentifier.status === "available" && - (!messageAttribute || messageAttribute.status === "available") && - (!onCloseMicroflowParameterValue || onCloseMicroflowParameterValue.status === "available") && - (!actionConfig || !actionConfig.find(config => { - return config.action?.canExecute == false; //This check ensures parameters from Datasource flows are available in actions - })) + canStartConnection() ) { startConnection(); } @@ -45,11 +38,27 @@ export function EZWebsocketNative({ useEffect(() => { return () => { - // Close connection on unmount + // Close connection on unmount and empty connection ref so we can reconnect on remount connection.current?.close(); + connection.current = null; }; }, []); + useEffect(() => { + //Add EventListener for AppState changes to be able to reconnect when the app comes back from background and connection was silently closed + const sub = AppState.addEventListener("change", (nextState: AppStateStatus) => { + if (nextState === "active") { + if (canStartConnection()) { + reconnect(); + } else { + console.debug("Skipped reconnect on resume: parameters not ready yet"); + } + } + }); + + return () => sub.remove(); + }, []); + const startConnection = () => { // Open websocket connection // The replace action makes sure that applications without ssl connect to ws:// and with ssl connect to wss:// @@ -79,6 +88,10 @@ export function EZWebsocketNative({ ws.onclose = event => { console.debug(event); + + // Mark as closed so we can reconnect + connection.current = null; + // Timeout event if (event.code === 1001 && timeoutAction && timeoutAction.canExecute) { timeoutAction.execute(); @@ -128,5 +141,32 @@ export function EZWebsocketNative({ }; }; + const reconnect = () => { + // If there is an existing socket, close it first + if (connection.current && connection.current.readyState !== WebSocket.CLOSED) { + try { + connection.current.close(); + } catch (e) { + console.warn("Error while closing WS before reconnect", e); + } + } + connection.current = null; + startConnection(); + }; + + const canStartConnection = (): boolean => { + return ( + // Make sure all values are initiated. Made into reusable function as it is used in multiple places now + objectId.status === "available" && + websocketIdentifier.status === "available" && + (!messageAttribute || messageAttribute.status === "available") && + (!onCloseMicroflowParameterValue || onCloseMicroflowParameterValue.status === "available") && + (!actionConfig || + !actionConfig.find(config => { + return config.action?.canExecute == false; + })) + ); +}; + return null; } diff --git a/widgets/tests/testProject/widgets/jjrplus.EZWebsocketNative.mpk b/widgets/tests/testProject/widgets/jjrplus.EZWebsocketNative.mpk index 479e7d0501b373fd54d79d35d338fce30ef03b70..fbcd1c89575b94c7bdcd98e7a7e3d68b42876072 100644 GIT binary patch delta 4090 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 xRKH2RrRKhGAw-h^*)ad_TZ7lG>7@Jb5deVx=O