From b2d2f53e48a06c7fa18bb870729ed8c2517c4add Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Thu, 30 Jan 2025 10:35:20 +0100 Subject: [PATCH] Fix: Zaps sent over NWC in fast sequence not paid by the wallet. Improved wallet keys management. (Fix.2) --- index.js | 5 +- package.json | 2 +- src/services/notificationService.ts | 123 +++++++++++++++++----------- src/services/syncQueueService.ts | 1 - 4 files changed, 79 insertions(+), 52 deletions(-) diff --git a/index.js b/index.js index fede4bc8..8df0f0dc 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,7 @@ function BootstrapApp() { // long running tasks notifee.registerForegroundService(async (notification) => { return new Promise(async (resolve) => { - log.trace('[registerForegroundService] Foreground service running for task:', notification) + log.trace('[registerForegroundService] Foreground service starting for task:', notification.data.task) try { @@ -74,7 +74,8 @@ notifee.onBackgroundEvent(async ({ type, detail }) => { return true }) -// Init push notifications +messaging().onMessage(NotificationService.onForegroundNotification) +messaging().setBackgroundMessageHandler(NotificationService.onBackgroundNotification) NotificationService.initNotifications() AppRegistry.registerComponent(appName, () => BootstrapApp) diff --git a/package.json b/package.json index 5ab14cc8..2990c5ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.1.11-beta.4", + "version": "0.1.11-beta.5", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/services/notificationService.ts b/src/services/notificationService.ts index 0f3cdd17..54e914d5 100644 --- a/src/services/notificationService.ts +++ b/src/services/notificationService.ts @@ -33,6 +33,23 @@ export type NotifyNwcRequestData = { } } +let _nwcQueue: any = undefined + +const getNwcQueue = function () { + if(!_nwcQueue) { + _nwcQueue = new TaskQueue({ + concurrency: 1, // strictly synchronous processing + returnError: true, + stopOnError: false, + taskPrioritizationMode: "head", + }) + return _nwcQueue as TaskQueue + } + + return _nwcQueue as TaskQueue +} + + const DEFAULT_CHANNEL_ID = 'default' const DEFAULT_CHANNEL_NAME = 'Minibits notifications' @@ -46,9 +63,6 @@ export const TEST_CHANNEL_ID = 'testDefault' export const TEST_CHANNEL_NAME = 'Minibits test tasks' const initNotifications = async () => { - messaging().onMessage(onForegroundNotification) - messaging().setBackgroundMessageHandler(onBackgroundNotification) - let enabled = await areNotificationsEnabled() log.trace(`[initNotifications] Push notifications are ${enabled ? 'enabled' : 'disabled'}.`) @@ -126,10 +140,19 @@ const onForegroundNotification = async function(remoteMessage: FirebaseMessaging return _receiveToLnurlHandler(remoteData) } - // Process NWC request notified by FCM message - if(remoteData.type === 'NotifyNwcRequestData') { - // log.trace('[onForegroundNotification] App is in foreground, skipping NWC requestHandler') - return _nwcRequestHandler(remoteData) + // Process NWC request notified by FCM message by dedicated queue to avoid race condition + // when starting foreground service + if(remoteData.type === 'NotifyNwcRequestData') { + const nwcQueue = getNwcQueue() + nwcQueue + .addTask(async () => { + await _nwcRequestHandler(remoteData) + }) + .then((result) => { + log.trace('nwcQueue task completed.') + }) + + return } throw new AppError(Err.VALIDATION_ERROR, 'Unknown remoteData.type', {remoteData}) @@ -151,15 +174,19 @@ const onBackgroundNotification = async function(remoteMessage: FirebaseMessaging return _receiveToLnurlHandler(remoteData) } - // Process NWC request notified by FCM message + // Process NWC request notified by FCM message by dedicated queue to avoid race condition + // when starting foreground service if(remoteData.type === 'NotifyNwcRequestData') { - if(isNwcRequestHandlerRunning) { - await delay(1000) - return _nwcRequestHandler(remoteData) - - } else { - return _nwcRequestHandler(remoteData) - } + const nwcQueue = getNwcQueue() + nwcQueue + .addTask(async () => { + await _nwcRequestHandler(remoteData) + }) + .then((result) => { + log.trace('nwcQueue task completed.') + }) + + return } throw new AppError(Err.VALIDATION_ERROR, 'Unknown remoteData.type', {remoteData}) @@ -169,31 +196,6 @@ const onBackgroundNotification = async function(remoteMessage: FirebaseMessaging } } -const isNotificationDisplayed = async function (options: { foregroundServiceOnly?: boolean }): Promise { - const { foregroundServiceOnly } = options - const notifications = await notifee.getDisplayedNotifications() - let isDisplayed: boolean = false - - log.trace('[isNotificationDisplayed] Displayed notifications', {notifications, foregroundServiceOnly}) - - for (const notification of notifications) { - if (foregroundServiceOnly) { - // Assuming `foreground` is a property that indicates if the notification is in the foreground - if (notification.notification.android?.asForegroundService === true) { - log.trace('[isNotificationDisplayed] foregroundServiceOnly true') - isDisplayed = true - } - } else { - // If foregroundOnly is false, return true as soon as we find any notification - isDisplayed = true - } - } - - // If no matching notification is found, return false - log.trace('[isNotificationDisplayed]', isDisplayed) - return isDisplayed -} - const _receiveToLnurlHandler = async function(remoteData: NotifyReceiveToLnurlData) { const {amount, unit, comment, zapSenderProfile} = remoteData.data @@ -206,11 +208,9 @@ const _receiveToLnurlHandler = async function(remoteData: NotifyReceiveToLnurlDa ) } -let isNwcRequestHandlerRunning = false -const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) { - log.trace('[_nwcRequestHandler] start') - isNwcRequestHandlerRunning = true +const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) { + log.trace('[_nwcRequestHandler] start') const {requestEvent} = remoteData.data const {nwcStore} = rootStoreInstance @@ -219,8 +219,7 @@ const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) { await setupRootStore(rootStoreInstance) } - // start new foreground service only if none is running - // const isForegroundServiceRunning = await isNotificationDisplayed({foregroundServiceOnly: true}) + // start new foreground service only if none is running const queue: TaskQueue = SyncQueue.getSyncQueue() const isNwcRequestTaskRunning = queue.getAllTasksDetails(['idle', 'running']) .some(task => String(task.taskId).includes('handleNwcRequestTask')) @@ -243,11 +242,13 @@ const _nwcRequestHandler = async function(remoteData: NotifyNwcRequestData) { }) } else { - // if fg already running, add new nwc command to the queue + // if fg service is already running, add new nwc command to the queue WalletTask.handleNwcRequestQueue({requestEvent}) } - isNwcRequestHandlerRunning = false + // make some room for foreground service to start and pass nwcRequest to the queue + // to avoid new one being attempted + await delay(500) log.trace('[_nwcRequestHandler] done') } @@ -311,6 +312,32 @@ const createLocalNotification = async function (title: string, body: string, lar } } +// unreliable and delayed for foreground service +const isNotificationDisplayed = async function (options: { foregroundServiceOnly?: boolean }): Promise { + const { foregroundServiceOnly } = options + const notifications = await notifee.getDisplayedNotifications() + let isDisplayed: boolean = false + + log.trace('[isNotificationDisplayed] Displayed notifications', {notifications, foregroundServiceOnly}) + + for (const notification of notifications) { + if (foregroundServiceOnly) { + // Assuming `foreground` is a property that indicates if the notification is in the foreground + if (notification.notification.android?.asForegroundService === true) { + log.trace('[isNotificationDisplayed] foregroundServiceOnly true') + isDisplayed = true + } + } else { + // If foregroundOnly is false, return true as soon as we find any notification + isDisplayed = true + } + } + + // If no matching notification is found, return false + log.trace('[isNotificationDisplayed]', isDisplayed) + return isDisplayed +} + const areNotificationsEnabled = async function (): Promise { const settings = await notifee.getNotificationSettings() diff --git a/src/services/syncQueueService.ts b/src/services/syncQueueService.ts index 1914441a..03ed1ca8 100644 --- a/src/services/syncQueueService.ts +++ b/src/services/syncQueueService.ts @@ -6,7 +6,6 @@ import { NotificationService } from "./notificationService" let _queue: any = undefined - const getSyncQueue = function () { if(!_queue) { _queue = new TaskQueue({