Skip to content

Commit

Permalink
Merge pull request #23516 from Expensify/tgolen-getmissingonyxupdates
Browse files Browse the repository at this point in the history
Implement the API changes for getting incremental reconnect updates
  • Loading branch information
danieldoglas authored Aug 7, 2023
2 parents 242bfe7 + 1f83bf5 commit 5249d02
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 56 deletions.
8 changes: 8 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ export default {
// Experimental memory only Onyx mode flag
IS_USING_MEMORY_ONLY_KEYS: 'isUsingMemoryOnlyKeys',

ONYX_UPDATES: {
// The ID of the last Onyx update that was applied to this client
LAST_UPDATE_ID: 'onyxUpdatesLastUpdateID',

// The ID of the previous Onyx update that was applied to this client
PREVIOUS_UPDATE_ID: 'onyxUpdatesPreviousUpdateID',
},

// Manual request tab selector
SELECTED_TAB: 'selectedTab',

Expand Down
17 changes: 13 additions & 4 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const propTypes = {
/** Opt-in experimental mode that prevents certain Onyx keys from persisting to disk */
isUsingMemoryOnlyKeys: PropTypes.bool,

/** The last Onyx update ID that is stored in Onyx (used for getting incremental updates when reconnecting) */
onyxUpdatesLastUpdateID: PropTypes.number,

...windowDimensionsPropTypes,
};

Expand All @@ -105,6 +108,7 @@ const defaultProps = {
email: null,
},
lastOpenedPublicRoomID: null,
onyxUpdatesLastUpdateID: 0,
};

class AuthScreens extends React.Component {
Expand All @@ -116,7 +120,7 @@ class AuthScreens extends React.Component {

componentDidMount() {
NetworkConnection.listenForReconnect();
NetworkConnection.onReconnect(() => App.reconnectApp());
NetworkConnection.onReconnect(() => App.reconnectApp(this.props.onyxUpdatesLastUpdateID));
PusherConnectionManager.init();
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
Expand All @@ -127,14 +131,16 @@ class AuthScreens extends React.Component {
});

// If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
// or returning from background. If so, we'll assume they have some app data already and we can call
// reconnectApp(onyxUpdatesLastUpdateID) instead of openApp().
// Note: If a Guide has enabled the memory only key mode then we do want to run OpenApp as their app will not be rehydrated with
// the correct state on refresh. They are explicitly opting out of storing data they would need (i.e. reports_) to take advantage of
// the optimizations performed during ReconnectApp.
if (this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession()) {
const shouldGetAllData = this.props.isUsingMemoryOnlyKeys || SessionUtils.didUserLogInDuringSession();
if (shouldGetAllData) {
App.openApp();
} else {
App.reconnectApp();
App.reconnectApp(this.props.onyxUpdatesLastUpdateID);
}

App.setUpPoliciesAndNavigate(this.props.session, !this.props.isSmallScreenWidth);
Expand Down Expand Up @@ -323,5 +329,8 @@ export default compose(
isUsingMemoryOnlyKeys: {
key: ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS,
},
onyxUpdatesLastUpdateID: {
key: ONYXKEYS.ONYX_UPDATES.LAST_UPDATE_ID,
},
}),
)(AuthScreens);
131 changes: 81 additions & 50 deletions src/libs/actions/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,63 +122,94 @@ AppState.addEventListener('change', (nextAppState) => {
});

/**
* Fetches data needed for app initialization
* @param {boolean} [isReconnecting]
* Gets the policy params that are passed to the server in the OpenApp and ReconnectApp API commands. This includes a full list of policy IDs the client knows about as well as when they were last modified.
* @returns {Promise}
*/
function openApp(isReconnecting = false) {
isReadyToOpenApp.then(() => {
const connectionID = Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (policies) => {
// When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID.
// we have locally. And then only update the user about chats with messages that have occurred after that reportActionID.
//
// - Look through the local report actions and reports to find the most recently modified report action or report.
// - We send this to the server so that it can compute which new chats the user needs to see and return only those as an optimization.
const params = {policyIDToLastModified: JSON.stringify(getNonOptimisticPolicyIDToLastModifiedMap(policies))};
if (isReconnecting) {
Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION);
params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified();
Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500);
}
Onyx.disconnect(connectionID);

// eslint-disable-next-line rulesdir/no-multiple-api-calls
const apiMethod = isReconnecting ? API.write : API.read;
apiMethod(isReconnecting ? 'ReconnectApp' : 'OpenApp', params, {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: true,
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: false,
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: false,
},
],
});
},
function getPolicyParamsForOpenOrReconnect() {
return new Promise((resolve) => {
isReadyToOpenApp.then(() => {
const connectionID = Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (policies) => {
Onyx.disconnect(connectionID);
resolve({policyIDToLastModified: JSON.stringify(getNonOptimisticPolicyIDToLastModifiedMap(policies))});
},
});
});
});
}

/**
* Refreshes data when the app reconnects
* Returns the Onyx data that is used for both the OpenApp and ReconnectApp API commands.
* @returns {Object}
*/
function getOnyxDataForOpenOrReconnect() {
return {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: true,
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: false,
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: ONYXKEYS.IS_LOADING_REPORT_DATA,
value: false,
},
],
};
}

/**
* Fetches data needed for app initialization
*/
function openApp() {
getPolicyParamsForOpenOrReconnect().then((policyParams) => {
API.read('OpenApp', policyParams, getOnyxDataForOpenOrReconnect());
});
}

/**
* Fetches data when the app reconnects to the network
* @param {Number} [updateIDFrom] the ID of the Onyx update that we want to start fetching from
* @param {Number} [updateIDTo] the ID of the Onyx update that we want to fetch up to
*/
function reconnectApp() {
openApp(true);
function reconnectApp(updateIDFrom = 0, updateIDTo = 0) {
console.debug(`[OnyxUpdates] App reconnecting with updateIDFrom: ${updateIDFrom} and updateIDTo: ${updateIDTo}`);
getPolicyParamsForOpenOrReconnect().then((policyParams) => {
const params = {...policyParams};

// When the app reconnects we do a fast "sync" of the LHN and only return chats that have new messages. We achieve this by sending the most recent reportActionID.
// we have locally. And then only update the user about chats with messages that have occurred after that reportActionID.
//
// - Look through the local report actions and reports to find the most recently modified report action or report.
// - We send this to the server so that it can compute which new chats the user needs to see and return only those as an optimization.
Timing.start(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION);
params.mostRecentReportActionLastModified = ReportActionsUtils.getMostRecentReportActionLastModified();
Timing.end(CONST.TIMING.CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION, '', 500);

// Include the update IDs when reconnecting so that the server can send incremental updates if they are available.
// Otherwise, a full set of app data will be returned.
if (updateIDFrom) {
params.updateIDFrom = updateIDFrom;
}

if (updateIDTo) {
params.updateIDTo = updateIDTo;
}

API.write('ReconnectApp', params, getOnyxDataForOpenOrReconnect());
});
}

/**
Expand Down
17 changes: 15 additions & 2 deletions src/libs/actions/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,22 @@ function subscribeToUserEvents() {
if (_.isArray(pushJSON)) {
updates = pushJSON;
} else {
// const lastUpdateID = pushJSON.lastUpdateID;
// const previousUpdateID = pushJSON.previousUpdateID;
updates = pushJSON.updates;

// Not always we'll have the lastUpdateID and previousUpdateID properties in the pusher update
// until we finish the migration to reliable updates. So let's check it before actually updating
// the properties in Onyx
if (pushJSON.lastUpdateID && pushJSON.previousUpdateID) {
console.debug('[OnyxUpdates] Received lastUpdateID from pusher', pushJSON.lastUpdateID);
console.debug('[OnyxUpdates] Received previousUpdateID from pusher', pushJSON.previousUpdateID);
// Store these values in Onyx to allow App.reconnectApp() to fetch incremental updates from the server when a previous session is being reconnected to.
Onyx.multiSet({
[ONYXKEYS.ONYX_UPDATES.LAST_UPDATE_ID]: Number(pushJSON.lastUpdateID || 0),
[ONYXKEYS.ONYX_UPDATES.PREVIOUS_UPDATE_ID]: Number(pushJSON.previousUpdateID || 0),
});
} else {
console.debug('[OnyxUpdates] No lastUpdateID and previousUpdateID provided');
}
}
_.each(updates, (multipleEvent) => {
PusherUtils.triggerMultiEventHandler(multipleEvent.eventType, multipleEvent.data);
Expand Down

0 comments on commit 5249d02

Please sign in to comment.