2020-07-18 21:33:43 +02:00
import PushNotificationIOS from '@react-native-community/push-notification-ios' ;
2020-07-20 15:42:22 +02:00
import { Alert , Platform } from 'react-native' ;
2020-07-18 21:33:43 +02:00
import Frisbee from 'frisbee' ;
2020-07-20 15:42:22 +02:00
import { isEmulatorSync } from 'react-native-device-info' ;
2020-07-18 21:33:43 +02:00
import AsyncStorage from '@react-native-community/async-storage' ;
2020-07-23 16:20:40 +02:00
import loc from '../loc' ;
2020-07-18 21:33:43 +02:00
const PushNotification = require ( 'react-native-push-notification' ) ;
const constants = require ( './constants' ) ;
const PUSH _TOKEN = 'PUSH_TOKEN' ;
2020-07-31 15:43:55 +02:00
const GROUNDCONTROL _BASE _URI = 'GROUNDCONTROL_BASE_URI' ;
2020-07-18 21:33:43 +02:00
let alreadyConfigured = false ;
2020-07-31 15:43:55 +02:00
let baseURI = constants . groundControlUri ;
2020-07-18 21:33:43 +02:00
async function _setPushToken ( token ) {
token = JSON . stringify ( token ) ;
return AsyncStorage . setItem ( PUSH _TOKEN , token ) ;
}
2020-08-05 17:15:45 +02:00
async function getPushToken ( ) {
2020-07-18 21:33:43 +02:00
try {
let token = await AsyncStorage . getItem ( PUSH _TOKEN ) ;
token = JSON . parse ( token ) ;
return token ;
} catch ( _ ) { }
return false ;
}
/ * *
* Calls ` configure ` , which tries to obtain push token , save it , and registers all associated with
* notifications callbacks
*
* @ returns { Promise < boolean > } TRUE if acquired token , FALSE if not
* /
const configureNotifications = async function ( ) {
return new Promise ( function ( resolve ) {
PushNotification . configure ( {
// (optional) Called when Token is generated (iOS and Android)
onRegister : async function ( token ) {
console . log ( 'TOKEN:' , token ) ;
alreadyConfigured = true ;
await _setPushToken ( token ) ;
resolve ( true ) ;
} ,
// (required) Called when a remote is received or opened, or local notification is opened
onNotification : function ( notification ) {
console . log ( 'NOTIFICATION:' , notification ) ;
// process the notification
PushNotification . setApplicationIconBadgeNumber ( 0 ) ; // always reset badges to zero
// (required) Called when a remote is received or opened, or local notification is opened
notification . finish ( PushNotificationIOS . FetchResult . NoData ) ;
} ,
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
onAction : function ( notification ) {
console . log ( 'ACTION:' , notification . action ) ;
console . log ( 'NOTIFICATION:' , notification ) ;
// process the action
} ,
// (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)
onRegistrationError : function ( err ) {
console . error ( err . message , err ) ;
resolve ( false ) ;
} ,
// IOS ONLY (optional): default: all - Permissions to register.
permissions : {
alert : true ,
badge : true ,
sound : true ,
} ,
// Should the initial notification be popped automatically
// default: true
popInitialNotification : true ,
/ * *
* ( optional ) default : true
* - Specified if permissions ( ios ) and token ( android and ios ) will requested or not ,
* - if not , you must call PushNotificationsHandler . requestPermissions ( ) later
* - if you are not using remote notification or do not have Firebase installed , use this :
* requestPermissions : Platform . OS === 'ios'
* /
requestPermissions : true ,
} ) ;
} ) ;
} ;
/ * *
* Should be called when user is most interested in receiving push notifications .
* If we dont have a token it will show alert asking whether
* user wants to receive notifications , and if yes - will configure push notifications .
* FYI , on Android permissions are acquired when app is installed , so basically we dont need to ask ,
* we can just call ` configure ` . On iOS its different , and calling ` configure ` triggers system ' s dialog box .
*
* @ returns { Promise < boolean > } TRUE if permissions were obtained , FALSE otherwise
* /
const tryToObtainPermissions = async function ( ) {
2020-08-04 15:17:35 +02:00
if ( isEmulatorSync ( ) && Platform . OS === 'ios' ) {
2020-07-20 15:42:22 +02:00
console . log ( 'Running inside iOS emulator. Exiting Push Notification configuration...' ) ;
return false ;
}
2020-08-05 17:15:45 +02:00
if ( await getPushToken ( ) ) {
2020-07-18 21:33:43 +02:00
// we already have a token, no sense asking again, just configure pushes to register callbacks and we are done
if ( ! alreadyConfigured ) configureNotifications ( ) ; // no await so it executes in background while we return TRUE and use token
return true ;
}
return new Promise ( function ( resolve ) {
Alert . alert (
'Would you like to receive notifications when you get incoming payments?' ,
'' ,
[
{
text : 'Ask Me Later' ,
onPress : ( ) => {
resolve ( false ) ;
} ,
style : 'cancel' ,
} ,
{
text : loc . _ . ok ,
onPress : async ( ) => {
resolve ( await configureNotifications ( ) ) ;
} ,
style : 'default' ,
} ,
] ,
{ cancelable : false } ,
) ;
} ) ;
} ;
function _getHeaders ( ) {
return {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
} ,
} ;
}
2020-07-31 15:43:55 +02:00
async function _sleep ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
}
2020-07-18 21:33:43 +02:00
/ * *
* Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server , so later we could
* be notified if they were paid
*
* @ param addresses { string [ ] }
* @ param hashes { string [ ] }
2020-07-28 15:49:48 +02:00
* @ param txids { string [ ] }
2020-07-18 21:33:43 +02:00
* @ returns { Promise < object > } Response object from API rest call
* /
2020-07-28 15:49:48 +02:00
const majorTomToGroundControl = async function ( addresses , hashes , txids ) {
if ( ! Array . isArray ( addresses ) || ! Array . isArray ( hashes ) || ! Array . isArray ( txids ) )
throw new Error ( 'no addresses or hashes or txids provided' ) ;
2020-08-05 17:15:45 +02:00
const pushToken = await getPushToken ( ) ;
2020-07-18 21:33:43 +02:00
if ( ! pushToken || ! pushToken . token || ! pushToken . os ) return ;
2020-07-31 15:43:55 +02:00
const api = new Frisbee ( { baseURI } ) ;
2020-07-18 21:33:43 +02:00
return await api . post (
'/majorTomToGroundControl' ,
Object . assign ( { } , _getHeaders ( ) , {
body : {
addresses ,
hashes ,
2020-07-28 15:49:48 +02:00
txids ,
2020-07-18 21:33:43 +02:00
token : pushToken . token ,
os : pushToken . os ,
} ,
} ) ,
) ;
} ;
2020-07-30 14:22:06 +02:00
/ * *
* The opposite of ` majorTomToGroundControl ` call .
*
* @ param addresses { string [ ] }
* @ param hashes { string [ ] }
* @ param txids { string [ ] }
* @ returns { Promise < object > } Response object from API rest call
* /
const unsubscribe = async function ( addresses , hashes , txids ) {
if ( ! Array . isArray ( addresses ) || ! Array . isArray ( hashes ) || ! Array . isArray ( txids ) )
throw new Error ( 'no addresses or hashes or txids provided' ) ;
2020-08-05 17:15:45 +02:00
const pushToken = await getPushToken ( ) ;
2020-07-30 14:22:06 +02:00
if ( ! pushToken || ! pushToken . token || ! pushToken . os ) return ;
2020-07-31 15:43:55 +02:00
const api = new Frisbee ( { baseURI } ) ;
2020-07-30 14:22:06 +02:00
return await api . post (
'/unsubscribe' ,
Object . assign ( { } , _getHeaders ( ) , {
body : {
addresses ,
hashes ,
txids ,
token : pushToken . token ,
os : pushToken . os ,
} ,
} ) ,
) ;
} ;
2020-07-31 15:43:55 +02:00
const isNotificationsEnabled = async function ( ) {
2020-08-05 17:15:45 +02:00
const levels = await getLevels ( ) ;
return ! ! ( await getPushToken ( ) ) && ! ! levels . level _all ;
2020-07-31 15:43:55 +02:00
} ;
const getDefaultUri = function ( ) {
return constants . groundControlUri ;
} ;
const saveUri = async function ( uri ) {
baseURI = uri || constants . groundControlUri ; // settign the url to use currently. if not set - use default
return AsyncStorage . setItem ( GROUNDCONTROL _BASE _URI , uri ) ;
} ;
const getSavedUri = async function ( ) {
return AsyncStorage . getItem ( GROUNDCONTROL _BASE _URI ) ;
} ;
const isGroundControlUriValid = async function ( uri ) {
const apiCall = new Frisbee ( {
baseURI : uri ,
} ) ;
let response ;
try {
response = await Promise . race ( [ apiCall . get ( '/ping' , _getHeaders ( ) ) , _sleep ( 2000 ) ] ) ;
} catch ( _ ) { }
if ( ! response || ! response . body ) return false ; // either sleep expired or apiCall threw an exception
const json = response . body ;
if ( json . description ) return true ;
return false ;
} ;
2020-08-05 17:15:45 +02:00
/ * *
* Returns a permissions object :
* alert : boolean
* badge : boolean
* sound : boolean
*
* @ returns { Promise < Object > }
* /
const checkPermissions = async function ( ) {
return new Promise ( function ( resolve ) {
PushNotification . checkPermissions ( result => {
resolve ( result ) ;
} ) ;
} ) ;
} ;
/ * *
* Posts to groundcontrol info whether we want to opt in or out of specific notifications level
*
* @ param levelAll { Boolean }
* @ returns { Promise < * > }
* /
const setLevels = async function ( levelAll ) {
const pushToken = await getPushToken ( ) ;
if ( ! pushToken || ! pushToken . token || ! pushToken . os ) return ;
const api = new Frisbee ( { baseURI } ) ;
return await api . post (
'/setTokenConfiguration' ,
Object . assign ( { } , _getHeaders ( ) , {
body : {
level _all : ! ! levelAll ,
token : pushToken . token ,
os : pushToken . os ,
} ,
} ) ,
) ;
} ;
/ * *
* Queries groundcontrol for token configuration , which contains subscriptions to notification levels
*
* @ returns { Promise < { } | * > }
* /
const getLevels = async function ( ) {
const pushToken = await getPushToken ( ) ;
if ( ! pushToken || ! pushToken . token || ! pushToken . os ) return ;
const api = new Frisbee ( { baseURI } ) ;
let response ;
try {
response = await Promise . race ( [
api . post ( '/getTokenConfiguration' , Object . assign ( { } , _getHeaders ( ) , { body : { token : pushToken . token , os : pushToken . os } } ) ) ,
_sleep ( 3000 ) ,
] ) ;
} catch ( _ ) { }
if ( ! response || ! response . body ) return { } ; // either sleep expired or apiCall threw an exception
return response . body ;
} ;
2020-07-18 21:33:43 +02:00
// on app launch (load module):
( async ( ) => {
2020-07-31 15:43:55 +02:00
// first, fetching to see if app uses custom GroundControl server, not the default one
try {
const baseUriStored = await AsyncStorage . getItem ( GROUNDCONTROL _BASE _URI ) ;
if ( baseUriStored ) {
baseURI = baseUriStored ;
}
} catch ( _ ) { }
// every launch should clear badges:
PushNotification . setApplicationIconBadgeNumber ( 0 ) ;
2020-08-05 17:15:45 +02:00
if ( ! ( await getPushToken ( ) ) ) return ;
2020-07-18 21:33:43 +02:00
// if we previously had token that means we already acquired permission from the user and it is safe to call
// `configure` to register callbacks etc
await configureNotifications ( ) ;
} ) ( ) ;
module . exports . tryToObtainPermissions = tryToObtainPermissions ;
module . exports . majorTomToGroundControl = majorTomToGroundControl ;
2020-07-30 14:22:06 +02:00
module . exports . unsubscribe = unsubscribe ;
2020-07-31 15:43:55 +02:00
module . exports . isNotificationsEnabled = isNotificationsEnabled ;
module . exports . getDefaultUri = getDefaultUri ;
module . exports . saveUri = saveUri ;
module . exports . isGroundControlUriValid = isGroundControlUriValid ;
module . exports . getSavedUri = getSavedUri ;
2020-08-05 17:15:45 +02:00
module . exports . getPushToken = getPushToken ;
module . exports . checkPermissions = checkPermissions ;
module . exports . setLevels = setLevels ;