Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Push notification onyx updates not applied when notification is selected #42510

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5dce826
Merge branch 'main' into @chrispader/add-deferred-updates--queue-func…
chrispader May 23, 2024
65a43c1
apply notification updates once a push notification is selected
chrispader May 23, 2024
9f0a892
Update src/libs/Notification/PushNotification/subscribePushNotificati…
chrispader May 24, 2024
435cb59
Merge branch 'main' into @chrispader/fix/push-notifications-defer-ony…
chrispader May 24, 2024
56645b7
fix: pr comments
chrispader May 24, 2024
08bb933
fix: don't wait for notification updates being applied before navigat…
chrispader May 24, 2024
aec6383
fix: wrong import
chrispader May 24, 2024
427c4fa
fix: resolve gh comments
chrispader May 28, 2024
73ecced
rename function
chrispader May 28, 2024
dc2372f
fix: apply onyx updates in order
chrispader May 29, 2024
e2e7940
fix: missing spread operator
chrispader May 29, 2024
5098cef
Merge branch 'main' into @chrispader/fix/push-notifications-defer-ony…
chrispader May 31, 2024
62b38a5
Merge branch 'main' into @chrispader/fix/push-notifications-defer-ony…
chrispader May 31, 2024
c2361cb
Merge branch 'main' into @chrispader/fix/push-notifications-defer-ony…
chrispader Jun 3, 2024
a2a85b9
add logs to DeferredOnyxUpdates
chrispader Jun 3, 2024
f7895e2
fix: wait for notifications to be fetched before navigating to report
chrispader Jun 3, 2024
955247e
fix: other logs
chrispader Jun 3, 2024
6fb2993
don't enqueue updates when no notifications received
chrispader Jun 3, 2024
aa9503b
rename logs for consistency
chrispader Jun 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type {PushPayload} from '@ua/react-native-airship';
import type {PushNotificationData} from './NotificationType';

function getPushNotificationData(notification: PushPayload): PushNotificationData {
let payload = notification.extras.payload;

// On Android, some notification payloads are sent as a JSON string rather than an object
if (typeof payload === 'string') {
payload = JSON.parse(payload);
}

return payload as PushNotificationData;
}

export default getPushNotificationData;
10 changes: 2 additions & 8 deletions src/libs/Notification/PushNotification/index.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Log from '@libs/Log';
import * as PushNotificationActions from '@userActions/PushNotification';
import ONYXKEYS from '@src/ONYXKEYS';
import ForegroundNotifications from './ForegroundNotifications';
import getPushNotificationData from './getPushNotificationData';
import type {PushNotificationData} from './NotificationType';
import NotificationType from './NotificationType';
import type {ClearNotifications, Deregister, Init, OnReceived, OnSelected, Register} from './types';
Expand All @@ -27,14 +28,7 @@ const notificationEventActionMap: NotificationEventActionMap = {};
*/
function pushNotificationEventCallback(eventType: EventType, notification: PushPayload) {
const actionMap = notificationEventActionMap[eventType] ?? {};
let payload = notification.extras.payload;

// On Android, some notification payloads are sent as a JSON string rather than an object
if (typeof payload === 'string') {
payload = JSON.parse(payload);
}

const data = payload as PushNotificationData;
const data = getPushNotificationData(notification);

Log.info(`[PushNotification] Callback triggered for ${eventType}`);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import Airship from '@ua/react-native-airship';
import Onyx from 'react-native-onyx';
import applyOnyxUpdatesReliably from '@libs/actions/applyOnyxUpdatesReliably';
import type {DeferredUpdatesDictionary} from '@libs/actions/OnyxUpdateManager/types';
import * as DeferredOnyxUpdates from '@libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates';
import * as ActiveClientManager from '@libs/ActiveClientManager';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import getPushNotificationData from '@libs/Notification/PushNotification/getPushNotificationData';
import type {ReportActionPushNotificationData} from '@libs/Notification/PushNotification/NotificationType';
import getPolicyEmployeeAccountIDs from '@libs/PolicyEmployeeListUtils';
import {extractPolicyIDFromPath} from '@libs/PolicyUtils';
Expand All @@ -13,6 +17,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {OnyxUpdatesFromServer} from '@src/types/onyx';
import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import PushNotification from '..';

Expand All @@ -27,6 +32,20 @@ Onyx.connect({
},
});

function buildOnyxUpdatesFromServer({onyxData, lastUpdateID, previousUpdateID}: {onyxData: OnyxServerUpdate[]; lastUpdateID: number; previousUpdateID: number}) {
chrispader marked this conversation as resolved.
Show resolved Hide resolved
return {
type: CONST.ONYX_UPDATE_TYPES.AIRSHIP,
lastUpdateID,
previousUpdateID,
updates: [
{
eventType: 'eventType',
data: onyxData,
},
],
} as OnyxUpdatesFromServer;
}

function getLastUpdateIDAppliedToClient(): Promise<number> {
return new Promise((resolve) => {
Onyx.connect({
Expand All @@ -50,17 +69,8 @@ function applyOnyxData({reportID, reportActionID, onyxData, lastUpdateID, previo
}

Log.info('[PushNotification] reliable onyx update received', false, {lastUpdateID, previousUpdateID, onyxDataCount: onyxData?.length ?? 0});
const updates: OnyxUpdatesFromServer = {
type: CONST.ONYX_UPDATE_TYPES.AIRSHIP,
lastUpdateID,
previousUpdateID,
updates: [
{
eventType: 'eventType',
data: onyxData,
},
],
};

const updates = buildOnyxUpdatesFromServer({onyxData, lastUpdateID, previousUpdateID});

/**
* When this callback runs in the background on Android (via Headless JS), no other Onyx.connect callbacks will run. This means that
Expand All @@ -71,16 +81,51 @@ function applyOnyxData({reportID, reportActionID, onyxData, lastUpdateID, previo
}

function navigateToReport({reportID, reportActionID}: ReportActionPushNotificationData): Promise<void> {
Log.info('[PushNotification] Navigating to report', false, {reportID, reportActionID});
Log.info('[PushNotification] Adding push notification updates to deferred updates queue', false, {reportID, reportActionID});

// Onyx data from push notifications might not have been applied when they were received in the background
// due to OS limitations. So we'll also attempt to apply them here so they can display immediately. Reliable
// updates will prevent any old updates from being duplicated and any gaps in them will be handled
Airship.push
.getActiveNotifications()
.then((notifications) => {

if (notifications.length === 0) {
return;
}

const onyxUpdates = notifications.reduce<DeferredUpdatesDictionary>((updates, notification) => {
const pushNotificationData = getPushNotificationData(notification);
const lastUpdateID = pushNotificationData.lastUpdateID;
const previousUpdateID = pushNotificationData.previousUpdateID;

if (pushNotificationData.onyxData == null || lastUpdateID == null || previousUpdateID == null) {
return updates;
}

const newUpdates = buildOnyxUpdatesFromServer({onyxData: pushNotificationData.onyxData, lastUpdateID, previousUpdateID});

const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath);
const report = getReport(reportID.toString());
const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : [];
const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID);
// eslint-disable-next-line no-param-reassign
updates[lastUpdateID] = newUpdates;
return updates;
}, {});

Navigation.isNavigationReady()
if (Object.keys(onyxUpdates).length === 0) {
return;
}

DeferredOnyxUpdates.enqueueAndProcess(onyxUpdates);
})
.then(Navigation.isNavigationReady)
.then(Navigation.waitForProtectedRoutes)
.then(() => {
Log.info('[PushNotification] Navigating to report', false, {reportID, reportActionID});

const policyID = lastVisitedPath && extractPolicyIDFromPath(lastVisitedPath);
const report = getReport(reportID.toString());
const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : [];
const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID);

// The attachment modal remains open when navigating to the report so we need to close it
Modal.close(() => {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Onyx from 'react-native-onyx';
import type {DeferredUpdatesDictionary} from '@libs/actions/OnyxUpdateManager/types';
import Log from '@libs/Log';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
import ONYXKEYS from '@src/ONYXKEYS';
import type {OnyxUpdatesFromServer, Response} from '@src/types/onyx';
Expand Down Expand Up @@ -59,11 +60,15 @@ function isEmpty() {
* Manually processes and applies the updates from the deferred updates queue. (used e.g. for push notifications)
*/
function process() {
Log.info('[DeferredOnyxUpdates] Processing manually enqueued updates', false, {lastUpdateIDs: Object.keys(deferredUpdates)});

if (missingOnyxUpdatesQueryPromise) {
missingOnyxUpdatesQueryPromise.finally(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates);
}

missingOnyxUpdatesQueryPromise = OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates();

return missingOnyxUpdatesQueryPromise;
}

type EnqueueDeferredOnyxUpdatesOptions = {
Expand All @@ -83,9 +88,13 @@ function enqueue(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, opt
// We check here if the "updates" param is a single update.
// If so, we only need to insert one update into the deferred updates queue.
if (isValidOnyxUpdateFromServer(updates)) {
Log.info('[DeferredOnyxUpdates] Manually enqueuing update', false, {lastUpdateID: updates.lastUpdateID});

const lastUpdateID = Number(updates.lastUpdateID);
deferredUpdates[lastUpdateID] = updates;
} else {
Log.info('[DeferredOnyxUpdates] Manually enqueuing updates', false, {lastUpdateIDs: Object.keys(updates)});

// If the "updates" param is an object, we need to insert multiple updates into the deferred updates queue.
Object.entries(updates).forEach(([lastUpdateIDString, update]) => {
const lastUpdateID = Number(lastUpdateIDString);
Expand All @@ -104,7 +113,7 @@ function enqueue(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, opt
*/
function enqueueAndProcess(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, options?: EnqueueDeferredOnyxUpdatesOptions) {
enqueue(updates, options);
process();
return process();
}

type ClearDeferredOnyxUpdatesOptions = {
Expand Down
6 changes: 3 additions & 3 deletions src/libs/actions/OnyxUpdateManager/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function detectGapsAndSplit(updates: DeferredUpdatesDictionary, clientLastUpdate
function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousParams?: {newLastUpdateIDFromClient: number; latestMissingUpdateID: number}): Promise<void> {
const lastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0;

Log.info('[DeferredUpdates] Processing deferred updates', false, {lastUpdateIDFromClient, previousParams});
Log.info('[DeferredOnyxUpdates] Processing deferred updates', false, {lastUpdateIDFromClient, previousParams});

// We only want to apply deferred updates that are newer than the last update that was applied to the client.
// At this point, the missing updates from "GetMissingOnyxUpdates" have been applied already, so we can safely filter out.
Expand All @@ -102,7 +102,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa
// If we detected a gap in the deferred updates, only apply the deferred updates before the gap,
// re-fetch the missing updates and then apply the remaining deferred updates after the gap
if (latestMissingUpdateID) {
Log.info('[DeferredUpdates] Gap detected in deferred updates', false, {lastUpdateIDFromClient, latestMissingUpdateID});
Log.info('[DeferredOnyxUpdates] Gap detected in deferred updates', false, {lastUpdateIDFromClient, latestMissingUpdateID});

return new Promise((resolve, reject) => {
DeferredOnyxUpdates.clear({shouldUnpauseSequentialQueue: false, shouldResetGetMissingOnyxUpdatesPromise: false});
Expand All @@ -127,7 +127,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa

// Prevent info loops of calls to GetMissingOnyxMessages
if (previousParams?.newLastUpdateIDFromClient === newLastUpdateIDFromClient && previousParams?.latestMissingUpdateID === latestMissingUpdateID) {
Log.info('[DeferredUpdates] Aborting call to GetMissingOnyxMessages, repeated params', false, {lastUpdateIDFromClient, latestMissingUpdateID, previousParams});
Log.info('[DeferredOnyxUpdates] Aborting call to GetMissingOnyxMessages, repeated params', false, {lastUpdateIDFromClient, latestMissingUpdateID, previousParams});
resolve(undefined);
return;
}
Expand Down
Loading