Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,19 @@ public NetworkStatus getNetworkStatus() {
android.net.Network activeNetwork = this.connectivityManager.getActiveNetwork();
NetworkCapabilities capabilities = this.connectivityManager.getNetworkCapabilities(this.connectivityManager.getActiveNetwork());
if (activeNetwork != null && capabilities != null) {
networkStatus.connected =
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
boolean validated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
boolean hasInternet = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
networkStatus.connected = validated && hasInternet;

if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI;
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR;
} else {
networkStatus.connectionType = NetworkStatus.ConnectionType.UNKNOWN;
}
networkStatus.internetReachable = networkStatus.connected;
networkStatus.state = networkStatus.connected ? "online" : "limited";
}
}
return networkStatus;
Expand All @@ -104,6 +107,8 @@ private NetworkStatus getAndParseNetworkInfo() {
} else if (typeName.equals("MOBILE")) {
networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR;
}
networkStatus.internetReachable = networkStatus.connected;
networkStatus.state = networkStatus.connected ? "online" : "offline";
}
return networkStatus;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public void load() {
JSObject jsObject = new JSObject();
jsObject.put("connected", false);
jsObject.put("connectionType", "none");
jsObject.put("internetReachable", false);
jsObject.put("state", "offline");
notifyListeners(NETWORK_CHANGE_EVENT, jsObject);
} else {
updateNetworkStatus();
Expand Down Expand Up @@ -89,6 +91,8 @@ private JSObject parseNetworkStatus(NetworkStatus networkStatus) {
JSObject jsObject = new JSObject();
jsObject.put("connected", networkStatus.connected);
jsObject.put("connectionType", networkStatus.connectionType.getConnectionType());
jsObject.put("internetReachable", networkStatus.internetReachable);
jsObject.put("state", networkStatus.state);
return jsObject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ public String getConnectionType() {

public boolean connected = false;
public ConnectionType connectionType = ConnectionType.NONE;
public boolean internetReachable = false;
public String state = "offline";
}
115 changes: 106 additions & 9 deletions network/ios/Sources/NetworkPlugin/Network.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,132 @@
import Foundation
public typealias NetworkStatusChangeObserver = (NetworkStatus) -> Void

public typealias NetworkConnectionChangedObserver = (Network.Connection) -> Void
public struct NetworkStatus {
let connection: Network.Connection
let internetReachable: Bool
let state: String
var connected: Bool { connection.isConnected }
var connectionType: String { connection.jsStringValue }
}

public class Network {
public enum NetworkError: Error {
case initializationFailed
}

public enum Connection {
case unavailable, wifi, cellular
}

internal private(set) var reachability: Reachability?
var statusObserver: NetworkConnectionChangedObserver?

var statusObserver: NetworkStatusChangeObserver?

private let probeURLs: [URL] = [
URL(string: "https://www.google.com/generate_204")!,
URL(string: "https://captive.apple.com")!,
URL(string: "https://one.one.one.one")!,
URL(string: "https://www.msftconnecttest.com/connecttest.txt")!
]

private lazy var probeSession: URLSession = {
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 5
config.timeoutIntervalForResource = 5
config.waitsForConnectivity = false
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
return URLSession(configuration: config)
}()

private var lastEmitted: NetworkStatus?

init() throws {
reachability = try Reachability()
if reachability == nil {
throw NetworkError.initializationFailed
}
// setup our callback(s) and start notifications
reachability?.whenReachable = { [weak self] reachable in
self?.statusObserver?(reachable.connection.equivalentEnum)
self?.emitStatus(for: reachable.connection.equivalentEnum)
}
reachability?.whenUnreachable = { [weak self] _ in
self?.statusObserver?(Connection.unavailable)
self?.emitImmediate(.unavailable, internetReachable: false)
}
try reachability?.startNotifier()
}

func getStatus(completion: @escaping (NetworkStatus) -> Void) {
let connection = currentConnection()
if !connection.isConnected {
completion(buildStatus(connection: connection, internetReachable: false))
return
}

probeInternet { [weak self] reachable in
guard let self = self else { return }
completion(self.buildStatus(connection: connection, internetReachable: reachable))
}
}

func currentConnection() -> Connection {
reachability?.connection.equivalentEnum ?? .unavailable
}

private func emitStatus(for connection: Connection) {
if !connection.isConnected {
emitImmediate(connection, internetReachable: false)
return
}

probeInternet { [weak self] reachable in
guard let self = self else { return }
self.emitIfChanged(self.buildStatus(connection: connection, internetReachable: reachable))
}
}

private func emitImmediate(_ connection: Connection, internetReachable: Bool) {
emitIfChanged(buildStatus(connection: connection, internetReachable: internetReachable))
}

private func emitIfChanged(_ status: NetworkStatus) {
guard status.connection != lastEmitted?.connection
|| status.internetReachable != lastEmitted?.internetReachable
|| status.state != lastEmitted?.state else { return }
lastEmitted = status
statusObserver?(status)
}

private func probeInternet(completion: @escaping (Bool) -> Void) {
let group = DispatchGroup()
var reachable = false
let lock = NSLock()
for url in probeURLs {
group.enter()
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
probeSession.dataTask(with: request) { _, response, _ in
if let http = response as? HTTPURLResponse, (200..<400).contains(http.statusCode) {
lock.lock()
reachable = true
lock.unlock()
}
group.leave()
}.resume()
}

func currentStatus() -> Network.Connection {
return reachability?.connection.equivalentEnum ?? Connection.unavailable
group.notify(queue: .global(qos: .utility)) {
completion(reachable)
}
}

private func buildStatus(connection: Connection, internetReachable: Bool) -> NetworkStatus {
let state: String
if !connection.isConnected {
state = "offline"
} else if internetReachable {
state = "online"
} else {
state = "limited"
}
return NetworkStatus(connection: connection, internetReachable: internetReachable, state: state)
}
}

Expand Down
28 changes: 23 additions & 5 deletions network/ios/Sources/NetworkPlugin/NetworkPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ public class NetworkPlugin: CAPPlugin, CAPBridgedPlugin {
do {
implementation = try Network()
implementation?.statusObserver = { [weak self] status in
CAPLog.print(status.logMessage)
CAPLog.print("\(status.connection.logMessage) | state=\(status.state)")
self?.notifyListeners("networkStatusChange", data: [
"connected": status.isConnected,
"connectionType": status.jsStringValue
"connected": status.connected,
"connectionType": status.connectionType,
"internetReachable": status.internetReachable,
"state": status.state
])
}
} catch let error {
Expand All @@ -27,8 +29,24 @@ public class NetworkPlugin: CAPPlugin, CAPBridgedPlugin {
}

@objc func getStatus(_ call: CAPPluginCall) {
let status = implementation?.currentStatus() ?? Network.Connection.unavailable
call.resolve(["connected": status.isConnected, "connectionType": status.jsStringValue])
guard let implementation = implementation else {
call.resolve([
"connected": false,
"connectionType": "none",
"internetReachable": false,
"state": "offline"
])
return
}

implementation.getStatus { status in
call.resolve([
"connected": status.connected,
"connectionType": status.connectionType,
"internetReachable": status.internetReachable,
"state": status.state
])
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions network/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ export interface ConnectionStatus {
* @since 1.0.0
*/
connectionType: ConnectionType;

/**
* Whether the device can reach the internet or not.
*/
internetReachable: boolean;

/**
* The current state of the internet connection.
*/
state: 'online' | 'limited' | 'offline';
}

/**
Expand Down
6 changes: 6 additions & 0 deletions network/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export class NetworkWeb extends WebPlugin implements NetworkPlugin {
const status: ConnectionStatus = {
connected,
connectionType: connected ? connectionType : 'none',
internetReachable: connected,
state: connected ? 'online' : 'offline',
};

return status;
Expand All @@ -80,6 +82,8 @@ export class NetworkWeb extends WebPlugin implements NetworkPlugin {
const status: ConnectionStatus = {
connected: true,
connectionType: connectionType,
internetReachable: true,
state: 'online',
};

this.notifyListeners('networkStatusChange', status);
Expand All @@ -89,6 +93,8 @@ export class NetworkWeb extends WebPlugin implements NetworkPlugin {
const status: ConnectionStatus = {
connected: false,
connectionType: 'none',
internetReachable: false,
state: 'offline'
};

this.notifyListeners('networkStatusChange', status);
Expand Down