Handling of remote notifications and fixes

This commit is contained in:
minibits-cash
2024-08-20 17:46:50 +02:00
parent 7774ba4b63
commit 9aaf81c3f1
4 changed files with 142 additions and 58 deletions
+2 -2
View File
@@ -15,7 +15,7 @@ function BootstrapApp() {
// Setup notification listeners and handlers
messaging().onMessage(NotificationService.onReceiveRemoteNotification)
messaging().setBackgroundMessageHandler(NotificationService.onReceiveRemoteNotification)
messaging().onMessage(NotificationService.onForegroundNotification)
messaging().setBackgroundMessageHandler(NotificationService.onBackgroundNotification)
AppRegistry.registerComponent(appName, () => BootstrapApp)
+32 -9
View File
@@ -70,7 +70,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
return NostrClient.getMinibitsRelays()
},
get supportedMethods() {
return ['pay_invoice', 'get_balance', 'get_info', 'list_transactions']
return ['pay_invoice', 'get_balance', 'get_info']
}
}))
.views(self => ({
@@ -111,9 +111,6 @@ export const NwcConnectionModel = types.model('NwcConnection', {
resetMeltQuoteInFlight() {
self.meltQuoteInFlight = undefined
},
/*setListenerExists(exists: boolean) {
self.listenerExists = exists
}*/
}))
.actions(self => ({
sendResponse: flow(function* sendResponse(nwcResponse: NwcResponse | NwcError) {
@@ -327,12 +324,12 @@ export const NwcConnectionModel = types.model('NwcConnection', {
break
}
self.setEventInFlight(requestEvent)
// only early errors are immediately returned, transfer result is handled via event handler
nwcResponse = yield self.handlePayInvoice(nwcRequest) as Promise<NwcError>
// no early error, transfer initiated, exit and create response in transfer result event handler
if(!nwcResponse) {
self.setEventInFlight(requestEvent)
return
}
break
@@ -346,13 +343,19 @@ export const NwcConnectionModel = types.model('NwcConnection', {
log.error(message, {nwcRequest})
}
self.setEventInFlight(requestEvent)
yield self.sendResponse(nwcResponse)
}),
}))
.actions(self => ({
receiveNwcEvents: flow(function* receiveNwcEvents() {
log.trace('[receiveNwcEvents] start for connection', {name: self.name})
receiveNwcEvents () {
log.trace('[receiveNwcEvents] start listening for NWC events', {
name: self.name,
connectionPubkey: self.connectionPubkey,
walletPubkey: self.walletPubkey
})
self.resetEventInFlight()
self.resetMeltQuoteInFlight()
@@ -431,7 +434,7 @@ export const NwcConnectionModel = types.model('NwcConnection', {
log.error(e.name, e.message)
return
}
})
}
}))
@@ -458,7 +461,7 @@ export const NwcStoreModel = types
return self.nwcConnections
},
get supportedMethods() {
return ['pay_invoice', 'get_balance', 'get_info', 'list_transactions']
return ['pay_invoice', 'get_balance', 'get_info']
}
}))
.actions(self => ({
@@ -534,8 +537,28 @@ export const NwcStoreModel = types
conn.receiveNwcEvents()
}
if (self.all.length > 0) {
EventEmitter.on('ev_transferTask_result', self.handleTransferTaskResult)
}
},
handleNwcRequestFromNotification: flow(function* handleNwcRequestFromNotification(requestEvent: NostrEvent) {
// We need to select the connection the request belongs to
const connection = self.nwcConnections.find(c => c.connectionPubkey === requestEvent.pubkey)
if(connection) {
const decryptedContent = yield NostrClient.decryptNip04(requestEvent.pubkey, requestEvent.content)
const nwcRequest: NwcRequest = JSON.parse(decryptedContent)
if(nwcRequest.method === 'pay_invoice') {
EventEmitter.on('ev_transferTask_result', self.handleTransferTaskResult)
}
yield connection.handleRequest(requestEvent)
return nwcRequest
} else {
log.warn('[handleNwcRequestFromNotification] No connectionPubkey matches the event pubkey', {requestEvent})
}
})
}))
.views(self => ({
get all() {
+3
View File
@@ -83,6 +83,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
transactionsStore,
paymentRequestsStore,
userSettingsStore,
nwcStore
} = useStores()
const pagerRef = useRef<PagerView>(null)
@@ -160,6 +161,8 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
WalletTask.handleInFlight().catch(e => false)
// Create websocket subscriptions to receive tokens or payment requests by NOSTR DMs
WalletTask.receiveEventsFromRelays().catch(e => false)
// Create websocket subscriptions to receive NWC requests from remote wallets (if any)
nwcStore.receiveNwcEvents()
// log.trace('[getInitialData]', 'walletProfile', walletProfileStore)
const preferredUnit: MintUnit = userSettingsStore.preferredUnit
const pageIndex = groupedMints.findIndex((m) => m.unit === preferredUnit)
+98 -40
View File
@@ -6,10 +6,10 @@ import {
} from '@env'
import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'
import { MintUnit, formatCurrency, getCurrency } from './wallet/currency'
import { NostrClient, NostrProfile } from './nostrService'
import { NostrClient, NostrEvent, NostrProfile } from './nostrService'
import AppError, { Err } from '../utils/AppError'
import { Platform } from 'react-native'
import { rootStoreInstance } from '../models';
import { RootStoreModel, rootStoreInstance, setupRootStore } from '../models';
export type NotifyReceiveToLnurlData = {
type: 'NotifyReceiveToLnurlData',
@@ -21,17 +21,103 @@ export type NotifyReceiveToLnurlData = {
}
}
const {
proofsStore,
relaysStore,
walletStore,
} = rootStoreInstance
export type NotifyNwcRequestData = {
type: 'NotifyNwcRequestData',
data: {
requestEvent: NostrEvent,
}
}
// Remote notification receive handler
const onReceiveRemoteNotification = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
log.debug('[onReceiveRemoteNotification]', {remoteMessage})
const onForegroundNotification = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
log.trace('[onForegroundReceiveNotification]', {remoteMessage})
try {
const remoteData = await _getRemoteData(remoteMessage)
// Process notification about incoming lightning address / zap payment
if(remoteData.type === 'NotifyReceiveToLnurlData') {
return _receiveToLnurlHandler(remoteData)
}
// Process NWC request notified by FCM message
if(remoteData.type === 'NotifyNwcRequestData') {
// Ingnore when in foreground as we should be connected to relays
log.trace('[onForegroundNotification] App is in foreground, skipping NWC requestHandler')
return
}
throw new AppError(Err.VALIDATION_ERROR, 'Unknown remoteData.type', {remoteData})
} catch (e: any) {
log.error(e.name, e.message)
}
}
const onBackgroundNotification = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
log.trace('[onBackgroundReceiveNotification]', {remoteMessage})
try {
const remoteData = await _getRemoteData(remoteMessage)
// Process notification about incoming lightning address / zap payment
if(remoteData.type === 'NotifyReceiveToLnurlData') {
return _receiveToLnurlHandler(remoteData)
}
// Process NWC request notified by FCM message
if(remoteData.type === 'NotifyNwcRequestData') {
return _nwcRequestHandler(remoteData)
}
throw new AppError(Err.VALIDATION_ERROR, 'Unknown remoteData.type', {remoteData})
} catch (e: any) {
log.error(e.name, e.message)
}
}
const _receiveToLnurlHandler = async function(remoteData: NotifyReceiveToLnurlData) {
const {amount, unit, comment, zapSenderProfile} = remoteData.data
const currencyCode = getCurrency(unit as MintUnit).code
await createLocalNotification(
`<b>⚡${formatCurrency(amount, currencyCode)} ${currencyCode}</b> incoming!`,
`${zapSenderProfile ? 'Zap' : 'Payment'} from <b>${zapSenderProfile?.nip05 || 'unknown payer'}</b> is ready to be received.${comment ? ' Message from sender: ' + comment : ''}`,
zapSenderProfile?.picture
)
}
const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) {
const {requestEvent} = remoteData.data
const {nwcStore} = rootStoreInstance
// If app is killed, state is not loaded
if(nwcStore.all.length === 0) {
await setupRootStore(rootStoreInstance)
}
log.trace('[_nwcRequestHandler]', {connections: nwcStore.all})
const nwcRequest = await nwcStore.handleNwcRequestFromNotification(requestEvent)
if(nwcRequest) {
const body = nwcRequest.method === 'pay_invoice' ? 'Pay invoice' : nwcRequest.method === 'get_balance' ? 'Get wallet balance' : null
if(body) {
await createLocalNotification(
`Received <b>Nostr Wallet Connect</b> request`,
body,
)
}
} else {
log.warn('[_nwcRequestHandler] Could not get nwcRequest from requestEvent', {remoteData})
}
}
const _getRemoteData = async function(remoteMessage: FirebaseMessagingTypes.RemoteMessage) {
const {encrypted} = remoteMessage.data!
if (!encrypted) {
@@ -45,36 +131,7 @@ const onReceiveRemoteNotification = async function(remoteMessage: FirebaseMessag
throw new AppError(Err.VALIDATION_ERROR, 'Unknown remote message data received', {data: remoteMessage.data})
}
const remoteData = JSON.parse(decrypted)
// Remote notification building and showing per remoteData.type
if(remoteData.type === 'NotifyReceiveToLnurlData' || remoteData.type === 'RemoteMessageReceiveToLnurl') {
const {amount, unit, comment, zapSenderProfile} = remoteData.data
const currencyCode = getCurrency(unit as MintUnit).code
/* const mint = await walletStore.getMint('https://mint.minibits.cash/Bitcoin')
const {keysets} = await mint.getKeySets()
log.trace('[onReceiveRemoteNotification]', {mint: mint.mintUrl, keysets}) */
await createLocalNotification(
`<b>⚡${formatCurrency(amount, currencyCode)} ${currencyCode}</b> incoming!`,
`${zapSenderProfile ? 'Zap' : 'Payment'} from <b>${zapSenderProfile?.nip05 || 'unknown payer'}</b> is ready to be received.${comment ? ' Message from sender: ' + comment : ''}`,
zapSenderProfile?.picture
)
return
}
throw new AppError(Err.VALIDATION_ERROR, 'Unknown remoteData.type', {remoteData})
} catch (e: any) {
log.error(e.name, e.message)
}
return JSON.parse(decrypted)
}
@@ -170,7 +227,8 @@ const areNotificationsEnabled = async function (): Promise<boolean> {
}
export const NotificationService = {
onReceiveRemoteNotification,
onForegroundNotification,
onBackgroundNotification,
createLocalNotification,
// updateLocalNotification,
// cancelNotification,