React Native Exposure Notification Service is a react native module, which provides a common interface to Apple/Google's Exposure Notification APIs.
For more on contact tracing see:
To integrate with your react-native app:
# with npm
npm install --save react-native-exposure-notification-service
# with yarn
yarn add react-native-exposure-notification-service
React Native Exposure Notifications uses autolinking to allow your project discover and use this code.
On Android there are no further steps.
CocoaPods on iOS needs this extra step:
cd ios && pod install && cd ..
There are multiple ways to use the React Native Exposure Notification Service in your React application. You can use it directly via the ExposureNotificationModule
and manage the application state yourself, or you can use the ExposureProvider
React Context implementation, where the application state is managed for you.
import ExposureNotificationModule from 'react-native-exposure-notification-service';
ExposureNotificationModule.start();
const canSupport = await ExposureNotificationModule.canSupport();
Used to check if the device can support the relevant exposure notification API. This returns a promise that resolves a boolean determining if the device can support tracing or not.
exposure api is available on the device.
const supported = await ExposureNotificationModule.isSupported();
Used to check if the device has the contact tracing APIs installed. This returns a promise that resolves with true if contact tracing is supported.
const enabled = await ExposureNotificationModule.exposureEnabled();
Use to check if the contact tracing is enabled. This returns a promise that resolves with true if contact tracing is enabled.
Note: On android, if enabled is true, tracing has started.
const authorised = await ExposureNotificationModule.isAuthorised();
Use to check if the user has authorised contact tracing. Calling this method will NOT trigger an authorisation request. This returns a promise that resolves with a string representing the current authorisation state and can be one of: granted
, denied
, blocked
, unavailable
or unknown
const authorised = await ExposureNotificationModule.authoriseExposure();
Use to trigger an authorisation dialogue. This returns a promise that resolves with true if the user has authorised contact tracing, if they denied the request then false is returned.
ExposureNotificationModule.configure(options);
Use to configure the module. This method is synchronous, and should be called before start etc. It takes an options object as a parameter with the following properties:
exposureCheckFrequency
: a number representing the period between exposure downloads in minutesserverURL
: a string representing the the server api url (should not have trailing /)keyServerUrl
: a string representing the the key server api url (should not have trailing /). Will default to serverURLkeyServerType
: a string representing the the key server type, options are nearform or google. Defaults to nearformauthToken
: a string representing the current authorization tokenrefreshToken
: a string representing a token used to refresh the authorization tokenstoreExposuresFor
: a number representing the number of days to store data forfileLimit
: a number representing the file limitversion
: a string representing the app version numbernotificationTitle
: a string representing the title for positive exposure notifications popup,notificationDesc
: a string representing the description for positive exposure notifications popup,callbackNumber
: a string representing the phone number of a user if opted into automatic callback on positive exposure notification,analyticsOptin
: a boolean representing whether the user opted in or not
const started = await ExposureNotificationModule.start();
Use to start exposure notifications. This method should only be called when canSupport(), isSupported() and isAuthorised() all return/resolve positive values and after configure() has been called.
A promise is returned and will resolve to true after a successful start, otherwise it will resolve to false.
const status = await ExposureNotificationModule.status();
Used to get the current start status. This method returns a promise that resolves to a map containing 2 keys state
and type
both with string values.
The state can return as active
, disabled
, unavailable
or unknown
, and if set to disabled
will contain the type presenting the reason why.
The type can return as bluetooth
, exposure
, resolution
, paused
, starting
and its meaning should be read in combination with state
, i.e. a state of disabled
and a type of bluetooth
indicates that ENS is disabled because bluetooth is off.
State changes also trigger the event onStatusChanged
.
ExposureNotificationModule.stop();
Used to stop contact tracing and all scheduled tasks. Exposure notifications must be authorised again after this method is called.
ExposureNotificationModule.pause();
Used to pause contact tracing. Use start() to unpause.
const result = await ExposureNotificationModule.deleteAllData();
Used to delete all app related data including config & exposure data. This returns a promise that resolves true if all data is successfully removed.
const result = await ExposureNotificationModule.deleteExposureData();
Used to deletes exposure data but leaves configuration data intact. This returns a promise that resolves true if exposure data is successfully removed.
const keys = await ExposureNotificationModule.getDiagnosisKeys();
Used to retrieve a devices own diagnosis keys (typically all keys before today within a 14 day window). This returns a promise that resolves to an array of maps containing the key keyData
, encoded as a base64 string. This key should be used in export files for exposure matching. If the user denies the request, the returned promise will reject.
Note: this will trigger a dialog from the underlying exposure notifications API, requesting permission from the device user.
ExposureNotificationModule.checkExposure();
Used to manually check exposure during testing. Typically checkExposure is performed in background on schedule specified in configure. This facilitates an immediate check.
On successful matches, this will raise a notification to the user, and also raise an exposure
event to the app
ExposureNotificationModule.simulateExposure();
Used to manually generate an exposure alert during testing to make it easier to validate UX flows around exposure events.
This will raise a notification to the user, and also raise an exposure
event to the app.
const contacts = await ExposureNotificationModule.getCloseContacts();
Used to retrieve the summary array of matches recorded. This async function returns a promise that resolves to a array of maps with the following keys:
exposureAlertDate
attenuationDurations
daysSinceLastExposure
matchedKeyCount
maxRiskScore
summationRiskScore
const result = await ExposureNotificationModule.triggerUpdate();
Used to trigger play services update should the user be using a version older than 201817017
Create an emitter
object using the ExposureNotificationModule
import {NativeEventEmitter} from 'react-native';
import ExposureNotificationModule from 'react-native-exposure-notification-service';
const emitter = new NativeEventEmitter(ExposureNotificationModule);
In a component or custom hook, you could use an effect to subscribe to the native module's events as follows:
useEffect(() => {
function handleEvent(ev) {
if (ev.exposure) {
console.log(
'You have come in contact with someone diagnosed with COVID-19'
);
}
// handle other events...
}
const subscription = emitter.addListener('exposureEvent', handleEvent);
return () => {
subscription.remove();
emitter.removeListener('exposureEvent', handleEvent);
};
}, []);
There are two major events:
exposure
: fires when an exposure match event happens, which could be used to update the UI (contains a string)onStatusChange
: fires when the exposure tracing status changes. This event is a map map containing 2 keysstate
andtype
both with string values.
When exposure
fires, getCloseContacts()
would typically be called to retrieve the summary information.
When onStatusChange
fires, it will contain the same data returned from a call to status()
Note: Other events may fire, depending on the platform and whether debug/release. These are mostly for debug and error logging only and should not be used for triggering application logic.
You should add ExposureProvider
in your app root component.
import {
ExposureProvider,
useExposure
} from 'react-native-exposure-notification-service';
function Root() {
return (
<ExposureProvider
traceConfiguration={{
exposureCheckInterval: 120,
storeExposuresFor: 14,
fileLimit: 1,
fileLimitiOS: 2
}
serverUrl="https://your.exposure.api/api"
keyServerUrl="https://your.exposure.api/api"
keyServerType=KeyServerType.nearform
authToken="your-api-auth-token"
refreshToken="your-api-refresh-token"
notificationTitle="Close contact detected"
notificationDescription="Open the app for instructions">
<App />
</ExposureProvider>
);
}
boolean
(default false
) If true, will start the exposure notification service if the user has given permission
object
Tracing related configuration options
{
exposureCheckInterval: number;
storeExposuresFor: number;
fileLimit: number;
fileLimitiOS: number;
}
string
The build version of your application
string
The URL of your exposure API
string
The auth token for your exposure API
string
The refresh token for your exposure API
string
The title of a close contact push notification
string
The description of a close contact push notification
string
The phone number to be used for health authority callbacks
boolean
(default false
) Consent to send analytics to your exposure API's /metrics
endpoint
Use the useExposure
hook in any component to consume the ExposureProvider
context & methods
import {useExposure} from 'react-native-exposure-notification-service';
function MyComponent() {
const {status, permissions, requestPermissions} = useExposure();
return (
<View>
<Text>Exposure status: {status}</Text>
<Text>Exposure permissions: {JSON.stringify(permissions, null, 2)}</Text>
<Button onPress={requestPermissions}>Ask for exposure permissions</Button>
</View>
);
}
All of these values are available on the return value of useExposure
Status
The current status of the exposure service
boolean
The exposure API is available on the device.
boolean
This device can support the exposure API
boolean
The user has authorised the exposure API
boolean
The exposure API is enabled & has started
CloseContact[] | null
An array of recorded matches
boolean
The native module has successfully initialised
ExposurePermissions
The current status of permissions for exposure
& notifications
() => void
Start the exposure API, check & update the status & check for close contacts
Calls ExposureNotificationModule.start()
() => void
Stop the exposure API & check & update status
Calls ExposureNotificationModule.stop()
() => void
Configure the native module (this is called internally once permissions have been granted)
Calls ExposureNotificationModule.configure()
(readDetails: boolean, skipTimeCheck: boolean) => void
Calls ExposureNotificationModule.checkExposure()
() => Promise<any[]>
Calls ExposureNotificationModule.getDiagnosisKeys()
() => Promise<boolean>
Calls ExposureNotificationModule.exposureEnabled()
() => Promise<boolean>
Calls ExposureNotificationModule.authoriseExposure()
() => Promise<void>
Calls ExposureNotificationModule.deleteAllData()
& checks & update the status
() => Promise<void>
Manually check whether the device supports the exposure API and update the context
() => Promise<CloseContact[] | null>
Manually retrieve a summary of matched records and update the context
Calls ExposureNotificationModule.getCloseContacts()
() => Promise<{[key: string]: any}>
Get log data from the exposure API
Calls ExposureNotificationModule.getLogData()
() => Promise<string | undefined>
Triggers a play services update on Android if possible
Calls ExposureNotificationModule.triggerUpdate()
() => Promise<void>
Deletes exposure data & updates the context
Calls ExposureNotificationModule.deleteExposureData()
() => Promise<void>
Checks the current status of exposure
and notifications
permissions and updates the context
() => Promise<void>
Requests permissions from the user for exposure
and notifications
permissions and updates the context
() => Promise<Version>
Returns the version number for the app
() => Promise<string>
Returns the bundle identifier / package name for the app
(setStateAction: SetStateAction<State>) => void
Not recommended: Manually update the state in the exposure context. Can be useful for development & simulating different states.
A test application to run the methods above, can be found under the test-app
folder.
To run this, from the terminal:
cd test-app
yarn
yarn start
- and in a separate terminal window
yarn android
oryarn ios
Typically, it is better to run the test application on a device rather than a simulator.
Note: The check exposure function will not succeed unless connected to a valid server, if you have access to a valid server, modify ./test-app/config.js
with your settings.
To debug in android you will need to generate a debug keystore file, from the terminal execute:
cd android/app
then
keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000
cd ../..
When building/running an app using the native module, several issues can arise.
The Exposure Notification API provided by Google uses the Nearby API installed with Google Play Services.
This API is being rolled out by Google so that as many devices as possible support the API.
The minimum android version required is 23 (marshmallow).
The application performs a test to determine if the required API is available on the device using the isSupported()
method. If it is not installed google play services must be updated with the Nearby API.
If you have trouble with this, try triggerUpdate()
to see if Play Services can be updated or update manually.
Applications using the Exposure API should be government agencies, and so not all applications can access the API directly.
If using minify when building an .apk on android, classes are often obfuscated, in which case some errors can arise when using reflection.
To keep required classes from being obfuscated, edit you proguard-rules.pro
and add the following keep rules
-keep public class com.horcrux.svg.** {*;}
-keep class com.google.crypto.tink.** { *; }
-keep class net.sqlcipher.** { *; }
-keep class net.sqlcipher.database.* { *; }
-keep class * extends androidx.room.RoomDatabase
-keep class * extends com.google.auto
-keep class org.checkerframework.checker.nullness.qual.** { *; }
If you make changes to the native module, and are referencing the package in your project, you may need to re-install it occasionally. You can do this by pointing at a different commit, branch or version in your package.json.
You can link to commit using #<commit>
at the end of the git reference, or add #<feature>\/<branch>
or if versioning use, #semver:<semver>
(if the repo has any tags or refs matching that range). If none of these are specified, then the master branch is used.
eg.
{
"dependencies": {
"react-native-exposure-notification": "git+https://github.com/covidgreen/react-native-exposure-notification.git#semver:^1.0"
}
}
If you are developing and make changes to a branch, and are not seeing changes being reflected in your react-native app, you can try reinstall the module as follows:
yarn remove react-native-exposure-notification && yarn add git+https://github.com/covidgreen/react-native-exposure-notification.git`
You can also link to the module directly on your file system if developing locally:
yarn add file:<path-to-module>`
In order to upload/download diagnosis keys for exposure notifications, an applications using this module needs to connect to a server that accepts upload of tokens, and packages them into valid export zip files.
- @colmharte - Colm Harte [email protected]
- @jasnell - James M Snell [email protected]
- @aspiringarc - Gar Mac Críosta [email protected]
- @ShaunBaker - Shaun Baker [email protected]
- @floridemai - Paul Negrutiu [email protected]
- @jackdclark - Jack Clark [email protected]
- @andreaforni - Andrea Forni [email protected]
- @jackmurdoch - Jack Murdoch [email protected]
- @moogster31 - Katie Roberts [email protected]
- TBD
- TBD
- TBD
Linux Foundation Public Health
Copyright (c) 2020 Health Service Executive (HSE) Copyright (c) The COVID Green Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.