From 9f98ac09f956ae5d297da7d96ef293723e915f33 Mon Sep 17 00:00:00 2001 From: Mothana Date: Mon, 2 Sep 2024 14:26:26 +0400 Subject: [PATCH 1/2] use capacitor/app for foreground-background detection --- src/Components/Background.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Components/Background.tsx b/src/Components/Background.tsx index e9e32e22..ff4afc2c 100644 --- a/src/Components/Background.tsx +++ b/src/Components/Background.tsx @@ -26,6 +26,8 @@ import Toast from "./Toast"; import { useHistory } from "react-router"; import { RemoteBackup } from "./BackgroundJobs/RemoteBackup"; +import { App } from "@capacitor/app"; + export const Background = () => { const history = useHistory(); @@ -308,19 +310,15 @@ export const Background = () => { }, [savedAssets, nodedUp]); useEffect(() => { - - const visiblityHandler = () => { - if (document.visibilityState === "visible") { - setRefresh(Math.random()) + const listener = App.addListener("appStateChange", (state) => { + if (state.isActive) { + checkClipboard(); + setRefresh(Math.random()); } - checkClipboard(); - } - window.addEventListener("visibilitychange", visiblityHandler) - window.addEventListener("focus", checkClipboard); + }) return () => { - window.removeEventListener("visibilitychange", visiblityHandler); - window.removeEventListener("focus", checkClipboard); + listener.remove(); }; }, [checkClipboard]) From ac1fdcd7d3439fc29b4c3d1b1df975fccf8706c5 Mon Sep 17 00:00:00 2001 From: Mothana Date: Mon, 2 Sep 2024 15:00:59 +0400 Subject: [PATCH 2/2] pause health check when in background, resume when back to foreground --- src/Components/Background.tsx | 1 - src/Components/BackgroundJobs/HealthCheck.tsx | 255 ++++++++++-------- 2 files changed, 141 insertions(+), 115 deletions(-) diff --git a/src/Components/Background.tsx b/src/Components/Background.tsx index ff4afc2c..b1572eea 100644 --- a/src/Components/Background.tsx +++ b/src/Components/Background.tsx @@ -25,7 +25,6 @@ import { toast } from "react-toastify"; import Toast from "./Toast"; import { useHistory } from "react-router"; import { RemoteBackup } from "./BackgroundJobs/RemoteBackup"; - import { App } from "@capacitor/app"; diff --git a/src/Components/BackgroundJobs/HealthCheck.tsx b/src/Components/BackgroundJobs/HealthCheck.tsx index a91e660f..830e39d3 100644 --- a/src/Components/BackgroundJobs/HealthCheck.tsx +++ b/src/Components/BackgroundJobs/HealthCheck.tsx @@ -1,132 +1,159 @@ -import { useCallback, useEffect, useMemo } from "react" -import { getAllNostrClients, getNostrClient, nostrCallback, subToBeacons } from "../../Api/nostr" +import { useCallback, useEffect, useRef, useState } from "react" +import { getAllNostrClients, nostrCallback, subToBeacons } from "../../Api/nostr" import { toast } from "react-toastify"; import Toast from "../Toast"; import { useDispatch, useSelector } from "../../State/store"; import { editPaySources } from "../../State/Slices/paySourcesSlice"; import { editSpendSources } from "../../State/Slices/spendSourcesSlice"; +import { App } from "@capacitor/app"; const SubsCheckIntervalMs = 20 * 1000 const SubsThresholdMs = 10 * 1000 const BeaconMaxAgeSeconds = 2 * 60 export const HealthCheck = () => { - const paySource = useSelector(({ paySource }) => paySource) - const spendSource = useSelector(({ spendSource }) => spendSource) - const dispatch = useDispatch(); - const updateSourceState = useCallback((source: string, state: { disconnected: boolean, name?: string }) => { - const payEntryId = paySource.order.find(s => s.startsWith(source)) - if (payEntryId) { - const payEntry = paySource.sources[payEntryId] - let doUpdate = false - const update = { ...payEntry } - if (payEntry.disconnected !== state.disconnected) { - update.disconnected = state.disconnected - doUpdate = true - } - if (state.name && payEntry.label !== state.name) { - update.label = state.name - doUpdate = true - } - if (doUpdate) { - console.log("updating pay source", source) - dispatch({ - type: editPaySources.type, - payload: update, - meta: { skipChangelog: true } - }) - } - } - const spendEntryId = spendSource.order.find(s => s.startsWith(source)) - if (spendEntryId) { - const spendEntry = spendSource.sources[spendEntryId] - let doUpdate = false - const update = { ...spendEntry } - if (spendEntry.disconnected !== state.disconnected) { - update.disconnected = state.disconnected - doUpdate = true - } - if (state.name && spendEntry.label !== state.name) { - update.label = state.name - doUpdate = true - } - if (doUpdate) { - console.log("updating spend source", source) + const paySource = useSelector(({ paySource }) => paySource) + const spendSource = useSelector(({ spendSource }) => spendSource) + const dispatch = useDispatch(); - dispatch({ - type: editSpendSources.type, - payload: update, - meta: { skipChangelog: true } - }) - } - } - }, [paySource, spendSource]) + const [isAppActive, setIsAppActive] = useState(true); + const intervalRef = useRef(null); - const checkHealth = useCallback(() => { - getAllNostrClients().forEach((wrapper) => { - const state = wrapper.getClientState() - let oldestSingleSub: nostrCallback | undefined = undefined - wrapper.getSingleSubs().forEach(([_, cb]) => { - if (!oldestSingleSub || oldestSingleSub.startedAtMillis > cb.startedAtMillis) { - oldestSingleSub = cb - } - }) - if (!oldestSingleSub) { - console.log("no active single subs") - return - } - const now = Date.now() - const startedAtMillis = (oldestSingleSub as nostrCallback).startedAtMillis - if (now - startedAtMillis < SubsThresholdMs) { - console.log("oldest single sub is less than ", SubsThresholdMs, " seconds old") - return - } - console.log("oldest sub is", (startedAtMillis - now) / 1000, "seconds old!") - if (now - state.latestResponseAtMillis <= SubsThresholdMs) { - console.log("latest response is less than ", SubsThresholdMs, " seconds old") - return - } - console.log("latest response is more than ", SubsThresholdMs, " seconds old, checking beacon state") - wrapper.checkBeaconHealth(BeaconMaxAgeSeconds).then(beacon => { - if (!beacon) { - console.log("service is down, beacon is older than", BeaconMaxAgeSeconds, "seconds, disconnecting") - wrapper.disconnectCalls() - toast.error() - updateSourceState(wrapper.getPubDst(), { disconnected: true, }) - return - } - updateSourceState(wrapper.getPubDst(), { disconnected: false, name: beacon.data.name }) - }) - }) + const updateSourceState = useCallback((source: string, state: { disconnected: boolean, name?: string }) => { + const payEntryId = paySource.order.find(s => s.startsWith(source)) + if (payEntryId) { + const payEntry = paySource.sources[payEntryId] + let doUpdate = false + const update = { ...payEntry } + if (payEntry.disconnected !== state.disconnected) { + update.disconnected = state.disconnected + doUpdate = true + } + if (state.name && payEntry.label !== state.name) { + update.label = state.name + doUpdate = true + } + if (doUpdate) { + console.log("updating pay source", source) + dispatch({ + type: editPaySources.type, + payload: update, + meta: { skipChangelog: true } + }) + } + } + const spendEntryId = spendSource.order.find(s => s.startsWith(source)) + if (spendEntryId) { + const spendEntry = spendSource.sources[spendEntryId] + let doUpdate = false + const update = { ...spendEntry } + if (spendEntry.disconnected !== state.disconnected) { + update.disconnected = state.disconnected + doUpdate = true + } + if (state.name && spendEntry.label !== state.name) { + update.label = state.name + doUpdate = true + } + if (doUpdate) { + console.log("updating spend source", source) - }, [updateSourceState]) + dispatch({ + type: editSpendSources.type, + payload: update, + meta: { skipChangelog: true } + }) + } + } + }, [paySource, spendSource, dispatch]) - useEffect(() => { - const interval = setInterval(() => { - checkHealth() - }, SubsCheckIntervalMs) - return () => { - clearInterval(interval) - } - }, [checkHealth]) - useEffect(() => { - return subToBeacons(up => { - updateSourceState(up.createdByPub, { disconnected: false, name: up.name }) - }) - }, [updateSourceState]) - /* - const updateSourceState = (source: string, connected: boolean) => { - const payEntry = paySource.find(s => s.pasteField === source) - if (payEntry) { - dispatch(editPaySources({ ...payEntry, disconnected: !connected })) - } - const spendEntry = spendSource.find(s => s.pasteField === source) - if (spendEntry) { - dispatch(editSpendSources({ ...spendEntry, disconnected: !connected })) - } - } - */ - return null + const checkHealth = useCallback(() => { + if (!isAppActive) { + console.log("App is in the background, skipping health check"); + return; + } + + getAllNostrClients().forEach((wrapper) => { + const state = wrapper.getClientState() + let oldestSingleSub: nostrCallback | undefined = undefined + wrapper.getSingleSubs().forEach(([_, cb]) => { + if (!oldestSingleSub || oldestSingleSub.startedAtMillis > cb.startedAtMillis) { + oldestSingleSub = cb + } + }) + if (!oldestSingleSub) { + console.log("no active single subs") + return + } + const now = Date.now() + const startedAtMillis = (oldestSingleSub as nostrCallback).startedAtMillis + if (now - startedAtMillis < SubsThresholdMs) { + console.log("oldest single sub is less than ", SubsThresholdMs, " seconds old") + return + } + console.log("oldest sub is", (startedAtMillis - now) / 1000, "seconds old!") + if (now - state.latestResponseAtMillis <= SubsThresholdMs) { + console.log("latest response is less than ", SubsThresholdMs, " seconds old") + return + } + console.log("latest response is more than ", SubsThresholdMs, " seconds old, checking beacon state") + wrapper.checkBeaconHealth(BeaconMaxAgeSeconds).then(beacon => { + if (!beacon) { + console.log("service is down, beacon is older than", BeaconMaxAgeSeconds, "seconds, disconnecting") + wrapper.disconnectCalls() + toast.error() + updateSourceState(wrapper.getPubDst(), { disconnected: true, }) + return + } + updateSourceState(wrapper.getPubDst(), { disconnected: false, name: beacon.data.name }) + + }) + }) + + }, [updateSourceState, isAppActive]) + + useEffect(() => { + if (isAppActive) { + intervalRef.current = setInterval(() => { + checkHealth(); + }, SubsCheckIntervalMs); + } else if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + } + }, [checkHealth, dispatch, isAppActive]) + + useEffect(() => { + const listener = App.addListener("appStateChange", (state) => setIsAppActive(state.isActive)); + + return () => { + listener.remove(); + } + }, []) + + useEffect(() => { + return subToBeacons(up => { + updateSourceState(up.createdByPub, { disconnected: false, name: up.name }) + }) + }, [updateSourceState]) + /* + const updateSourceState = (source: string, connected: boolean) => { + const payEntry = paySource.find(s => s.pasteField === source) + if (payEntry) { + dispatch(editPaySources({ ...payEntry, disconnected: !connected })) + } + const spendEntry = spendSource.find(s => s.pasteField === source) + if (spendEntry) { + dispatch(editSpendSources({ ...spendEntry, disconnected: !connected })) + } + } + */ + return null } \ No newline at end of file