Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: replace the old version with a more maintainable Network Reachability test. #6392

Merged
merged 21 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions apps/mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1006,8 +1006,6 @@ PODS:
- react-native-mmkv (2.10.2):
- MMKV (>= 1.2.13)
- React-Core
- react-native-netinfo (11.4.1):
- React-Core
- react-native-randombytes (3.6.1):
- React-Core
- react-native-restart (0.0.27):
Expand Down Expand Up @@ -1369,7 +1367,6 @@ DEPENDENCIES:
- "react-native-lite-card (from `../../../node_modules/@onekeyfe/react-native-lite-card`)"
- react-native-metrix (from `../../../node_modules/react-native-metrix`)
- react-native-mmkv (from `../../../node_modules/react-native-mmkv`)
- "react-native-netinfo (from `../../../node_modules/@react-native-community/netinfo`)"
- react-native-randombytes (from `../../../node_modules/react-native-randombytes`)
- react-native-restart (from `../../../node_modules/react-native-restart`)
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
Expand Down Expand Up @@ -1576,8 +1573,6 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-metrix"
react-native-mmkv:
:path: "../../../node_modules/react-native-mmkv"
react-native-netinfo:
:path: "../../../node_modules/@react-native-community/netinfo"
react-native-randombytes:
:path: "../../../node_modules/react-native-randombytes"
react-native-restart:
Expand Down Expand Up @@ -1757,7 +1752,6 @@ SPEC CHECKSUMS:
react-native-lite-card: a7a68f79d02449a4a1eeea52c2cb52d592bbe014
react-native-metrix: dd791bfe9f9acfeb6bdd07da19747136ac977a4d
react-native-mmkv: 9ae7ca3977e8ef48dbf7f066974eb844c20b5fd7
react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284
Expand Down
1 change: 0 additions & 1 deletion apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"@onekeyhq/kit": "*",
"@onekeyhq/shared": "*",
"@react-native-async-storage/async-storage": "1.22.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "^9.1.0",
"@sentry/react-native": "6.4.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from './useBackHandler';
export * from './useDeferredPromise';
export * from './useForm';
export * from './useKeyboard';
export * from './useLayout';
export * from './useNetInfo';
export * from './useClipboard';
export * from './useColor';
export * from './usePreventRemove';
Expand Down
48 changes: 48 additions & 0 deletions packages/components/src/hooks/useDeferredPromise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useMemo } from 'react';

enum EDeferStatus {
pending = 'pending',
resolved = 'resolved',
rejected = 'rejected',
}
export type IDeferredPromise<DeferType> = {
resolve: (value: DeferType) => void;
reject: (value: unknown) => void;
reset: () => void;
promise: Promise<DeferType>;
status: EDeferStatus;
};

export const buildDeferredPromise = <DeferType>() => {
const deferred = {} as IDeferredPromise<DeferType>;

const buildPromise = () => {
const promise = new Promise<DeferType>((resolve, reject) => {
deferred.status = EDeferStatus.pending;
deferred.resolve = (value: DeferType) => {
deferred.status = EDeferStatus.resolved;
resolve(value);
};
deferred.reject = (reason: unknown) => {
deferred.status = EDeferStatus.rejected;
reject(reason);
};
});

deferred.promise = promise;
};

buildPromise();

deferred.reset = () => {
if (deferred.status !== EDeferStatus.pending) {
buildPromise();
}
};
return deferred;
};
huhuanming marked this conversation as resolved.
Show resolved Hide resolved

export function useDeferredPromise<DeferType>() {
const defer = useMemo(() => buildDeferredPromise<DeferType>(), []);
return defer;
}
183 changes: 183 additions & 0 deletions packages/components/src/hooks/useNetInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useEffect, useState } from 'react';

import { buildDeferredPromise } from './useDeferredPromise';
import {
getCurrentVisibilityState,
onVisibilityStateChange,
} from './useVisibilityChange';

export interface IReachabilityConfiguration {
reachabilityUrl: string;
reachabilityTest?: (response: { status: number }) => Promise<boolean>;
reachabilityMethod?: 'GET' | 'POST';
reachabilityLongTimeout?: number;
reachabilityShortTimeout?: number;
reachabilityRequestTimeout?: number;
}

export interface IReachabilityState {
isInternetReachable: boolean | null;
}

class NetInfo {
state: IReachabilityState = {
isInternetReachable: null,
};

prevIsInternetReachable = false;

listeners: Array<(state: { isInternetReachable: boolean | null }) => void> =
[];

defer = buildDeferredPromise<unknown>();

configuration = {
reachabilityUrl: '',
reachabilityMethod: 'GET',
reachabilityTest: (response: { status: number }) =>
Promise.resolve(response.status === 200),
reachabilityLongTimeout: 60 * 1000,
reachabilityShortTimeout: 5 * 1000,
reachabilityRequestTimeout: 10 * 1000,
};

isFetching = false;

pollingTimeoutId: ReturnType<typeof setTimeout> | null = null;

constructor(configuration: IReachabilityConfiguration) {
this.configure(configuration);

const handleVisibilityChange = (isVisible: boolean) => {
if (isVisible) {
this.defer.resolve(undefined);
} else {
this.defer.reset();
}
};

const isVisible = getCurrentVisibilityState();
handleVisibilityChange(isVisible);
onVisibilityStateChange(handleVisibilityChange);
}
huhuanming marked this conversation as resolved.
Show resolved Hide resolved

configure(configuration: IReachabilityConfiguration) {
this.configuration = {
...this.configuration,
...configuration,
};
}

currentState() {
return this.state;
}

updateState(state: { isInternetReachable: boolean | null }) {
this.state = state;
this.listeners.forEach((listener) => listener(state));
this.prevIsInternetReachable = !!state.isInternetReachable;
}

addEventListener(
listener: (state: { isInternetReachable: boolean | null }) => void,
) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter((l) => l !== listener);
};
}

async fetch() {
if (this.isFetching) return;
this.isFetching = true;
await this.defer.promise;

const {
reachabilityRequestTimeout,
reachabilityUrl,
reachabilityMethod,
reachabilityTest,
} = this.configuration;

const controller = new AbortController();
const timeoutId = setTimeout(
() => controller.abort(),
reachabilityRequestTimeout,
);

try {
const response = await fetch(reachabilityUrl, {
method: reachabilityMethod,
signal: controller.signal,
});

this.updateState({
isInternetReachable: await reachabilityTest(response),
});
} catch (error) {
console.error('Failed to fetch reachability:', error);
this.updateState({ isInternetReachable: false });
} finally {
huhuanming marked this conversation as resolved.
Show resolved Hide resolved
clearTimeout(timeoutId);
this.isFetching = false;
const { reachabilityShortTimeout, reachabilityLongTimeout } =
this.configuration;
this.pollingTimeoutId = setTimeout(
() => {
void this.fetch();
},
this.prevIsInternetReachable
? reachabilityLongTimeout
: reachabilityShortTimeout,
);
}
}

async start() {
void this.fetch();
}

async refresh() {
if (this.pollingTimeoutId) {
clearTimeout(this.pollingTimeoutId);
}
void this.fetch();
}
}

export const globalNetInfo = new NetInfo({
reachabilityUrl: '/wallet/v1/health',
});

export const configureNetInfo = (configuration: IReachabilityConfiguration) => {
globalNetInfo.configure(configuration);
void globalNetInfo.start();
};
huhuanming marked this conversation as resolved.
Show resolved Hide resolved

export const refreshNetInfo = () => {
void globalNetInfo.refresh();
};

export const useNetInfo = () => {
const [reachabilityState, setReachabilityState] = useState<
IReachabilityState & {
isRawInternetReachable: boolean | null;
}
>(() => {
const { isInternetReachable } = globalNetInfo.currentState();
return {
isInternetReachable: isInternetReachable ?? true,
isRawInternetReachable: isInternetReachable,
};
});
useEffect(() => {
const remove = globalNetInfo.addEventListener(({ isInternetReachable }) => {
setReachabilityState({
isInternetReachable: isInternetReachable ?? true,
isRawInternetReachable: isInternetReachable,
});
});
return remove;
}, []);
return reachabilityState;
};
5 changes: 2 additions & 3 deletions packages/kit/src/components/NetworkAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { memo } from 'react';

import { useIntl } from 'react-intl';

import { Alert } from '@onekeyhq/components';
import { Alert, useNetInfo } from '@onekeyhq/components';
import { ETranslations } from '@onekeyhq/shared/src/locale';
import { useNetInfo } from '@onekeyhq/shared/src/modules3rdParty/@react-native-community/netinfo';

function BasicNetworkAlert() {
const { isInternetReachable, isRawInternetReachable } = useNetInfo();
const { isInternetReachable } = useNetInfo();
const intl = useIntl();
return isInternetReachable ? null : (
<Alert
Expand Down
9 changes: 6 additions & 3 deletions packages/kit/src/components/Spotlight/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {

import { useIntl } from 'react-intl';

import type { IElement, IStackStyle } from '@onekeyhq/components';
import {
Button,
EPortalContainerConstantName,
Expand All @@ -28,18 +27,22 @@ import {
XStack,
YStack,
useBackHandler,
useDeferredPromise,
useMedia,
} from '@onekeyhq/components';
import type {
IDeferredPromise,
IElement,
IStackStyle,
} from '@onekeyhq/components';
import { useAppIsLockedAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms';
import { useSpotlightPersistAtom } from '@onekeyhq/kit-bg/src/states/jotai/atoms/spotlight';
import { ETranslations } from '@onekeyhq/shared/src/locale';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import type { ESpotlightTour } from '@onekeyhq/shared/src/spotlight';

import backgroundApiProxy from '../../background/instance/backgroundApiProxy';
import { useDeferredPromise } from '../../hooks/useDeferredPromise';

import type { IDeferredPromise } from '../../hooks/useDeferredPromise';
import type { View as NativeView } from 'react-native';

export type ISpotlightViewProps = PropsWithChildren<{
Expand Down
47 changes: 0 additions & 47 deletions packages/kit/src/hooks/useDeferredPromise.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/kit/src/hooks/usePromiseResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { debounce, isEmpty } from 'lodash';
import {
getCurrentVisibilityState,
onVisibilityStateChange,
useDeferredPromise,
useNetInfo,
} from '@onekeyhq/components';
import { useRouteIsFocused as useIsFocused } from '@onekeyhq/kit/src/hooks/useRouteIsFocused';
import { useNetInfo } from '@onekeyhq/shared/src/modules3rdParty/@react-native-community/netinfo';
import platformEnv from '@onekeyhq/shared/src/platformEnv';
import timerUtils from '@onekeyhq/shared/src/utils/timerUtils';

import { useDeferredPromise } from './useDeferredPromise';
import { useIsMounted } from './useIsMounted';
import { usePrevious } from './usePrevious';

Expand Down
Loading
Loading