Files
app.zeusln.zeus/stores/ConnectivityStore.ts
T

130 lines
4.3 KiB
TypeScript

import { action, observable, runInAction } from 'mobx';
import NetInfo, {
NetInfoState,
NetInfoSubscription
} from '@react-native-community/netinfo';
import SettingsStore from './SettingsStore';
const REACHABILITY_POLL_INTERVAL = 15000; // 15s
const OFFLINE_DEBOUNCE_MS = 10000; // 10s
const DEFAULT_REACHABILITY_HOST = 'mempool.space';
export default class ConnectivityStore {
@observable public isOffline: boolean = false;
private netInfoUnsubscribe: NetInfoSubscription | null = null;
private reachabilityInterval: ReturnType<typeof setInterval> | null = null;
private offlineDebounceTimer: ReturnType<typeof setTimeout> | null = null;
private reconnectCallbacks: Array<() => void> = [];
private settingsStore: SettingsStore;
constructor(settingsStore: SettingsStore) {
this.settingsStore = settingsStore;
}
private getReachabilityUrl = (): string => {
const { privacy } = this.settingsStore.settings;
const custom = privacy?.defaultBlockExplorer === 'Custom';
const host =
custom && privacy?.customBlockExplorer
? privacy.customBlockExplorer
: privacy?.defaultBlockExplorer || DEFAULT_REACHABILITY_HOST;
// Custom host may include scheme (e.g. http://mynode:3006#mempool.space)
if (custom && host.indexOf('://') !== -1) {
const hostUrl = host.split('#')[0];
return `${hostUrl}/api/blocks/tip/height`;
}
return `https://${host}/api/blocks/tip/height`;
};
public onReconnect = (callback: () => void) => {
this.reconnectCallbacks.push(callback);
};
private clearDebounce = () => {
if (this.offlineDebounceTimer) {
clearTimeout(this.offlineDebounceTimer);
this.offlineDebounceTimer = null;
}
};
private updateState = (state: NetInfoState) => {
// isConnected === false is a reliable native signal (airplane mode, no
// network interface) — surface immediately without debouncing
if (state.isConnected === false) {
this.clearDebounce();
runInAction(() => {
this.isOffline = true;
});
return;
}
// isInternetReachable === false can be a false positive on Android when
// the JS reachability fetch fails transiently; debounce before surfacing
if (state.isInternetReachable === false) {
if (!this.offlineDebounceTimer) {
this.offlineDebounceTimer = setTimeout(() => {
this.offlineDebounceTimer = null;
runInAction(() => {
this.isOffline = true;
});
}, OFFLINE_DEBOUNCE_MS);
}
return;
}
// Online — isConnected is not false and isInternetReachable is true or
// null (null = unknown, treat as online to avoid false positives)
this.clearDebounce();
const wasOffline = this.isOffline;
runInAction(() => {
this.isOffline = false;
});
if (wasOffline) {
this.reconnectCallbacks.forEach((cb) => cb());
}
};
@action
public start = () => {
if (this.netInfoUnsubscribe) return;
if (this.settingsStore.settings?.networking?.disableOfflineCheck)
return;
NetInfo.configure({
reachabilityUrl: this.getReachabilityUrl(),
reachabilityTest: async (response) => response.status === 200,
useNativeReachability: false
});
this.netInfoUnsubscribe = NetInfo.addEventListener(this.updateState);
// Poll every 15s so isInternetReachable gets re-evaluated
// after reconnect (the listener alone can leave it stale)
this.reachabilityInterval = setInterval(() => {
NetInfo.fetch().then(this.updateState);
}, REACHABILITY_POLL_INTERVAL);
};
@action
public stop = () => {
if (this.netInfoUnsubscribe) {
this.netInfoUnsubscribe();
this.netInfoUnsubscribe = null;
}
if (this.reachabilityInterval) {
clearInterval(this.reachabilityInterval);
this.reachabilityInterval = null;
}
this.clearDebounce();
};
@action
public reset = () => {
this.stop();
this.isOffline = false;
};
}