/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import * as Types from './types'; import * as PrivateTypes from './privateTypes'; interface InternetReachabilityCheckHandler { promise: Promise; cancel: () => void; } export default class InternetReachability { private _configuration: Types.NetInfoConfiguration; private _listener: PrivateTypes.NetInfoInternetReachabilityChangeListener; private _isInternetReachable: boolean | null | undefined = undefined; private _currentInternetReachabilityCheckHandler: InternetReachabilityCheckHandler | null = null; private _currentTimeoutHandle: ReturnType | null = null; constructor( configuration: Types.NetInfoConfiguration, listener: PrivateTypes.NetInfoInternetReachabilityChangeListener, ) { this._configuration = configuration; this._listener = listener; } private _setIsInternetReachable = ( isInternetReachable: boolean | null | undefined, ): void => { if (this._isInternetReachable === isInternetReachable) { return; } this._isInternetReachable = isInternetReachable; this._listener(this._isInternetReachable); }; private _setExpectsConnection = (expectsConnection: boolean): void => { // Cancel any pending check if (this._currentInternetReachabilityCheckHandler !== null) { this._currentInternetReachabilityCheckHandler.cancel(); this._currentInternetReachabilityCheckHandler = null; } // Cancel any pending timeout if (this._currentTimeoutHandle !== null) { clearTimeout(this._currentTimeoutHandle); this._currentTimeoutHandle = null; } if (expectsConnection) { // If we expect a connection, start the process for finding if we have one // Set the state to "null" if it was previously false if (!this._isInternetReachable) { this._setIsInternetReachable(null); } // Start a network request to check for internet this._currentInternetReachabilityCheckHandler = this._checkInternetReachability(); } else { // If we don't expect a connection, just change the state to "false" this._setIsInternetReachable(false); } }; private _checkInternetReachability = (): InternetReachabilityCheckHandler => { const responsePromise = fetch(this._configuration.reachabilityUrl, { method: 'HEAD', cache: 'no-cache', }); // Create promise that will reject after the request timeout has been reached let timeoutHandle: ReturnType; const timeoutPromise = new Promise( (_, reject): void => { timeoutHandle = setTimeout( (): void => reject('timedout'), this._configuration.reachabilityRequestTimeout, ); }, ); // Create promise that makes it possible to cancel a pending request through a reject let cancel: () => void = (): void => {}; const cancelPromise = new Promise( (_, reject): void => { cancel = (): void => reject('canceled'); }, ); const promise = Promise.race([ responsePromise, timeoutPromise, cancelPromise, ]) .then( (response): Promise => { return this._configuration.reachabilityTest(response); }, ) .then( (result): void => { this._setIsInternetReachable(result); const nextTimeoutInterval = this._isInternetReachable ? this._configuration.reachabilityLongTimeout : this._configuration.reachabilityShortTimeout; this._currentTimeoutHandle = setTimeout( this._checkInternetReachability, nextTimeoutInterval, ); }, ) .catch( (error: Error | 'timedout' | 'canceled'): void => { if (error !== 'canceled') { this._setIsInternetReachable(false); this._currentTimeoutHandle = setTimeout( this._checkInternetReachability, this._configuration.reachabilityShortTimeout, ); } }, ) // Clear request timeout and propagate any errors .then( (): void => { clearTimeout(timeoutHandle); }, (error: Error): void => { clearTimeout(timeoutHandle); throw error; }, ); return { promise, cancel, }; }; public update = (state: PrivateTypes.NetInfoNativeModuleState): void => { if (typeof state.isInternetReachable === 'boolean') { this._setIsInternetReachable(state.isInternetReachable); } else { this._setExpectsConnection(state.isConnected); } }; public currentState = (): boolean | null | undefined => { return this._isInternetReachable; }; public tearDown = (): void => { // Cancel any pending check if (this._currentInternetReachabilityCheckHandler !== null) { this._currentInternetReachabilityCheckHandler.cancel(); this._currentInternetReachabilityCheckHandler = null; } // Cancel any pending timeout if (this._currentTimeoutHandle !== null) { clearTimeout(this._currentTimeoutHandle); this._currentTimeoutHandle = null; } }; }