[fix] latest NetInfo API

Update the NetInfo API to conform to the latest React Native
implementation. Web-only properties are also included now.

Fix #662
Close #663
This commit is contained in:
Charlie Croom
2017-09-28 14:44:11 -07:00
committed by Nicolas Gallagher
parent 0d29458874
commit 6ef19c3ccd
4 changed files with 160 additions and 54 deletions

View File

@@ -1,9 +1,19 @@
# NetInfo
`NetInfo` asynchronously determines the online/offline status of the
application.
application and depending on browser support via
[NetworkInformation API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation),
additional information about the connection.
Connection types:
EffectiveConnectionType:
* `4g`
* `3g`
* `2g`
* `slow-2g`
* `unknown`
ConnectionType:
* `bluetooth` - The user agent is using a Bluetooth connection.
* `cellular` - The user agent is using a cellular connection (e.g., EDGE, HSPA, LTE, etc.).
@@ -18,12 +28,12 @@ Connection types:
## Methods
Note that support for retrieving the connection type depends upon browswer
support (and is limited to mobile browsers). It will default to `unknown` when
support and the current platform. It will default to `unknown` when
support is missing.
static **addEventListener**(eventName: ChangeEventName, handler: Function)
static **fetch**(): Promise
static **getConnectionInfo**(): Promise
static **removeEventListener**(eventName: ChangeEventName, handler: Function)
@@ -36,7 +46,7 @@ internet connectivity. Use this if you are only interested with whether the devi
**isConnected.addEventListener**(eventName: ChangeEventName, handler: Function)
**isConnected.fetch**(): Promise
**isConnected.getConnectionInfo**(): Promise
**isConnected.removeEventListener**(eventName: ChangeEventName, handler: Function)
@@ -45,24 +55,25 @@ internet connectivity. Use this if you are only interested with whether the devi
Fetching the connection type:
```js
NetInfo.fetch().then((connectionType) => {
console.log('Connection type:', connectionType);
NetInfo.getConnectionInfo().then(({ effectiveType, type }) => {
console.log('Effective connection type:', effectiveType);
console.log('Connection type:', type);
});
```
Subscribing to changes in the connection type:
```js
const handleConnectivityTypeChange = (connectionType) => {
console.log('Current connection type:', connectionType);
const handleConnectivityTypeChange = ({ effectiveType }) => {
console.log('Current effective connection type:', effectiveType);
}
NetInfo.addEventListener('change', handleConnectivityTypeChange);
NetInfo.addEventListener('connectionChange', handleConnectivityTypeChange);
```
Fetching the connection status:
```js
NetInfo.isConnected.fetch().then((isConnected) => {
NetInfo.isConnected.getConnectionInfo().then((isConnected) => {
console.log('Connection status:', (isConnected ? 'online' : 'offline'));
});
```
@@ -73,5 +84,5 @@ Subscribing to changes in the connection status:
const handleConnectivityStatusChange = (isConnected) => {
console.log('Current connection status:', (isConnected ? 'online' : 'offline'));
}
NetInfo.isConnected.addEventListener('change', handleConnectivityStatusChange);
NetInfo.isConnected.addEventListener('connectionChange', handleConnectivityStatusChange);
```

View File

@@ -8,61 +8,94 @@ import UIExplorer, {
Code,
Description,
DocItem,
ExternalLink,
Section,
storiesOf,
TextList
storiesOf
} from '../../ui-explorer';
const NetInfoScreen = () => (
<UIExplorer title="NetInfo" url="2-apis/NetInfo">
<Description>
<AppText>
NetInfo asynchronously determines the online/offline status of the application.
NetInfo asynchronously determines the online/offline status and additional connection
information (where available) of the application.
</AppText>
<AppText>
Note that support for retrieving the connection type depends upon browser support (and is
limited to mobile browsers). It will default to <Code>unknown</Code> when support is
missing.
Note that connection type information is limited to how well the browser supports the{' '}
<ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation">
NetworkInformation API
</ExternalLink>. Connection types will be <Code>unknown</Code> when support is missing.
</AppText>
</Description>
<Section title="Types">
<DocItem
description={
<AppText>
One of <Code>slow-2g</Code>, <Code>2g</Code>, <Code>3g</Code>, <Code>4g</Code>,{' '}
<Code>unknown</Code>.
</AppText>
}
name="ConnectionType"
/>
<DocItem
description={
<AppText>
One of <Code>bluebooth</Code>, <Code>cellular</Code>, <Code>ethernet</Code>,{' '}
<Code>mixed</Code>, <Code>mixed</Code>, <Code>none</Code>, <Code>other</Code>,{' '}
<Code>unknown</Code>, <Code>wifi</Code>, <Code>wimax</Code>
</AppText>
}
name="EffectiveConnectionType"
/>
<DocItem
description={
<Code>{`{
effectiveType: EffectiveConnectionType;
type: ConnectionType;
downlink?: number;
downlinkMax?: number;
rtt?: number;
}`}</Code>
}
name="ConnectionEventType"
/>
</Section>
<Section title="Methods">
<DocItem
description={[
description={
<AppText>
Invokes the listener whenever network status changes. The listener receives one of the
following connectivity types (from the DOM connection API):
</AppText>,
<TextList
items={[
'bluetooth',
'cellular',
'ethernet',
'mixed',
'none',
'other',
'unknown',
'wifi',
'wimax'
]}
/>
]}
Adds an event handler. The <Code>connectionChange</Code> event fires when the network
status changes. The argument to the event handler is an object of type{' '}
<Code>ConnectionEventType</Code>.
</AppText>
}
example={{
code: "NetInfo.addEventListener('change', (connectionType) => {})"
code: `NetInfo.addEventListener('connectionChange', ({ effectiveType, type }) => {
console.log('Effective connection type:', effectiveType);
console.log('Connection type:', type);
})`
}}
name="static addEventListener"
typeInfo="(eventName, handler) => void"
/>
<DocItem
description="Returns a promise that resolves with one of the connectivity types listed above."
description={
<AppText>
Returns a promise that resolves with an object of type <Code>ConnectionEventType</Code>.
</AppText>
}
example={{
code: `NetInfo.fetch().then((connectionType) => {
console.log('Connection type:', connectionType);
code: `NetInfo.getConnectionInfo().then(({ effectiveType, type }) => {
console.log('Effective connection type:', effectiveType);
console.log('Connection type:', type);
});`
}}
name="static fetch"
typeInfo="() => Promise<string>"
name="static getConnectionInfo"
typeInfo="() => Promise<ConnectionEventType>"
/>
<DocItem
@@ -76,7 +109,7 @@ const NetInfoScreen = () => (
<DocItem
description="An object with the same methods as above but the listener receives a boolean which represents the internet connectivity. Use this if you are only interested with whether the device has internet connectivity."
example={{
code: `NetInfo.isConnected.fetch().then((isConnected) => {
code: `NetInfo.isConnected.getConnectionInfo().then((isConnected) => {
console.log('Connection status:', (isConnected ? 'online' : 'offline'));
});`
}}

View File

@@ -3,31 +3,44 @@
import NetInfo from '..';
describe('apis/NetInfo', () => {
describe('getConnectionInfo', () => {
test('fills out basic fields', done => {
NetInfo.getConnectionInfo().then(result => {
expect(result.effectiveType).toBeDefined();
expect(result.type).toBeDefined();
done();
});
});
});
describe('isConnected', () => {
const handler = () => {};
afterEach(() => {
try {
NetInfo.isConnected.removeEventListener('change', handler);
NetInfo.isConnected.removeEventListener('connectionChange', handler);
} catch (e) {}
});
describe('addEventListener', () => {
test('throws if the provided "eventType" is not supported', () => {
expect(() => NetInfo.isConnected.addEventListener('foo', handler)).toThrow();
expect(() => NetInfo.isConnected.addEventListener('change', handler)).not.toThrow();
expect(() =>
NetInfo.isConnected.addEventListener('connectionChange', handler)
).not.toThrow();
});
});
describe('removeEventListener', () => {
test('throws if the handler is not registered', () => {
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).toThrow;
expect(() => NetInfo.isConnected.removeEventListener('connectionChange', handler)).toThrow;
});
test('throws if the provided "eventType" is not supported', () => {
NetInfo.isConnected.addEventListener('change', handler);
NetInfo.isConnected.addEventListener('connectionChange', handler);
expect(() => NetInfo.isConnected.removeEventListener('foo', handler)).toThrow;
expect(() => NetInfo.isConnected.removeEventListener('change', handler)).not.toThrow;
expect(() => NetInfo.isConnected.removeEventListener('connectionChange', handler)).not
.toThrow;
});
});
});

View File

@@ -20,7 +20,27 @@ const connection =
window.navigator.mozConnection ||
window.navigator.webkitConnection);
const eventTypes = ['change'];
// Prevent the underlying event handlers from leaking and include additional
// properties available in browsers
const getConnectionInfoObject = () => {
const result = {};
if (!connection) {
return result;
}
for (const prop in connection) {
if (typeof connection[prop] !== 'function') {
result[prop] = connection[prop];
}
}
return result;
};
// Map React Native events to browser equivalents
const eventTypesMap = {
change: 'change',
connectionChange: 'change'
};
const eventTypes = Object.keys(eventTypesMap);
const connectionListeners = [];
@@ -31,6 +51,9 @@ const connectionListeners = [];
const NetInfo = {
addEventListener(type: string, handler: Function): { remove: () => void } {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
if (type === 'change') {
console.warn('Listening to event `change` is deprecated. Use `connectionChange` instead.');
}
if (!connection) {
console.error(
'Network Connection API is not supported. Not listening for connection type changes.'
@@ -40,21 +63,25 @@ const NetInfo = {
};
}
connection.addEventListener(type, handler);
connection.addEventListener(eventTypesMap[type], handler);
return {
remove: () => NetInfo.removeEventListener(type, handler)
remove: () => NetInfo.removeEventListener(eventTypesMap[type], handler)
};
},
removeEventListener(type: string, handler: Function): void {
invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type);
if (type === 'change') {
console.warn('Listening to event `change` is deprecated. Use `connectionChange` instead.');
}
if (!connection) {
return;
}
connection.removeEventListener(type, handler);
connection.removeEventListener(eventTypesMap[type], handler);
},
fetch(): Promise<any> {
console.warn('`fetch` is deprecated. Use `getConnectionInfo` instead.');
return new Promise((resolve, reject) => {
try {
resolve(connection.type);
@@ -64,6 +91,16 @@ const NetInfo = {
});
},
getConnectionInfo(): Promise<Object> {
return new Promise((resolve, reject) => {
resolve({
effectiveType: 'unknown',
type: 'unknown',
...getConnectionInfoObject()
});
});
},
isConnected: {
addEventListener(type: string, handler: Function): { remove: () => void } {
invariant(
@@ -71,6 +108,10 @@ const NetInfo = {
'Trying to subscribe to unknown event: "%s"',
type
);
if (type === 'change') {
console.warn('Listening to event `change` is deprecated. Use `connectionChange` instead.');
}
const onlineCallback = () => handler(true);
const offlineCallback = () => handler(false);
connectionListeners.push([handler, onlineCallback, offlineCallback]);
@@ -79,7 +120,7 @@ const NetInfo = {
window.addEventListener('offline', offlineCallback, false);
return {
remove: () => NetInfo.isConnected.removeEventListener(type, handler)
remove: () => NetInfo.isConnected.removeEventListener(eventTypesMap[type], handler)
};
},
@@ -89,6 +130,9 @@ const NetInfo = {
'Trying to subscribe to unknown event: "%s"',
type
);
if (type === 'change') {
console.warn('Listening to event `change` is deprecated. Use `connectionChange` instead.');
}
const listenerIndex = findIndex(connectionListeners, pair => pair[0] === handler);
invariant(
@@ -103,7 +147,12 @@ const NetInfo = {
connectionListeners.splice(listenerIndex, 1);
},
fetch(): Promise<any> {
fetch(): Promise<boolean> {
console.warn('`fetch` is deprecated. Use `getConnectionInfo` instead.');
return NetInfo.isConnected.getConnectionInfo();
},
getConnectionInfo(): Promise<boolean> {
return new Promise((resolve, reject) => {
try {
resolve(window.navigator.onLine);