Skip to content

hybrid app switch account if we have a stashed session for use#58538

Merged
MariaHCD merged 9 commits intoExpensify:mainfrom
s77rt:newdot-hybrid-switch-account
Apr 7, 2025
Merged

hybrid app switch account if we have a stashed session for use#58538
MariaHCD merged 9 commits intoExpensify:mainfrom
s77rt:newdot-hybrid-switch-account

Conversation

@s77rt
Copy link
Member

@s77rt s77rt commented Mar 16, 2025

Explanation of Change

Fixed Issues

$ #58465
PROPOSAL:

Tests

Precondition:

  • User A is a main account
  • User B is a copilot with full copilot access
  1. As User A navigate to Settings > Security
  2. Remove Copilot access for User B
  3. As User B try to make some changes in profile section (Add display name, status..)
  4. Verify that: User B will be signed out as copilot and signed in to their original account
  • Verify that no errors appear in the JS console

Offline tests

n/a

QA Steps

Same as Tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
android.mov
Android: mWeb Chrome
iOS: Native
Screen.Recording.2025-03-16.at.11.07.08.PM.mov
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov
MacOS: Desktop

@s77rt s77rt marked this pull request as ready for review March 17, 2025 23:45
@s77rt s77rt requested a review from a team as a code owner March 17, 2025 23:45
@melvin-bot melvin-bot bot requested review from ikevin127 and removed request for a team March 17, 2025 23:45
@melvin-bot
Copy link

melvin-bot bot commented Mar 17, 2025

@ikevin127 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@ikevin127
Copy link
Contributor

ikevin127 commented Mar 18, 2025

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: Native 🟢
android.mp4
Android: HybridApp 🟢
android.mp4
Android: mWeb Chrome 🟢
android-mweb.mp4
iOS: Native 🟢
ios-hybrid.mov
iOS: HybridApp 🟢
ios-hybrid.mov
iOS: mWeb Safari 🟢
ios-mweb.mov
MacOS: Chrome / Safari 🟢
web.mov
MacOS: Desktop 🟢
desktop.mov

@s77rt
Copy link
Member Author

s77rt commented Mar 18, 2025

@ikevin127 Please be sure to test on the hybrid app

@ikevin127
Copy link
Contributor

@s77rt Will do, I got both iOS / Android running on dev, will add videos of HybridApp for both 👍

@ikevin127
Copy link
Contributor

@s77rt Here's a video from MacOS: Chrome / Safari (Web), looks like an automated quick logout and re-login (see below). Is this expected from a UI / UX point of view ?

I'm almost done with the Reviewer Checklist, let me know about the web behaviour and I'll approve if everything else looks good 👍

MacOS: Chrome / Safari (Web)
web.mov

@s77rt
Copy link
Member Author

s77rt commented Mar 18, 2025

@ikevin127 That's already the case on main for supportal requests. I agree it's not a very smooth transition but it's outside the scope of this issue.

@ikevin127
Copy link
Contributor

ikevin127 commented Mar 18, 2025

@s77rt Got it, while further testing I found issues on iOS: HybridApp in 2 scenarios:

  1. Full Copilot Access test email (gmail w/ +) logging in on HybridApp will see OD UI initially until switching to copilot account and restarting app which changes the UI to ND:
iOS: HybridApp (OD > ND > OD)
ios-hybrid-od.mp4

What seems to happen here when removing copilot access is the app will display the session expired UI and Switching accounts loading indicator and when I close & reopen the app -> I'm on login screen instead of logged-in on the original account.

  1. Full / Limited Copilot Access test email (gmail w/ +) logging in on HybridApp will see ND UI from the start, then switches to copilot account and upon removing copilot access logic seems to be stuck in a Logout loop with a large number of API calls being made, and nothing changes in the app's UI:
iOS: Full Access iOS: Limited Access
ios-hybrid-full.mov
ios-hybrid-limited.mp4

Not sure what's causing this but it's a blocker when it comes to this PR tests.

Important

I performed fresh builds on both iOS and Android HybridApp specifically on this PRs branch to ensure I don't have outdated code / patches / modules.

♻️ Will perform similar tests on Android: HybridApp to see if the issues persists there as well.

Edit: Same thing (infinite logout loop) seems to happen on Android: HybridApp as well:

Android: HybridApp
Screen.Recording.2025-03-18.at.16.mp4

@s77rt
Copy link
Member Author

s77rt commented Mar 19, 2025

@ikevin127 Thanks for catching that! The bug should be fixed now, can you please double check?

@ikevin127
Copy link
Contributor

@s77rt Just checked, the infinite loop issue was fixed with the latest commit and I completed most of the checklist except for Android where I encountered issues:

  1. On Android: HybridApp when copilot is removed I get the session expired UI and remains there. I have to restart the app 1-2 times to actually be logged back into original account (see video below). Also here are some console logs of what happens API calls wise during this Android issue:
Logs
[info] [Onyx] merge called for key: account properties: delegatedAccess hasChanged: true - "" 
[info] [API] Called API makeRequestWithSideEffects - {"command":"DisconnectAsDelegate"} 
[info] [API] Preparing request - {"command":"DisconnectAsDelegate","type":"makeRequestWithSideEffects"} 
[info] [API] Applying optimistic data - {"command":"DisconnectAsDelegate","type":"makeRequestWithSideEffects"} 
[info] [Network] Making API request - {"command":"DisconnectAsDelegate"} {"request": {"command": "DisconnectAsDelegate", "data": {"apiRequestType": "makeRequestWithSideEffects"}, "failureData": [[Object]], "initiatedOffline": false, "requestID": 15, "successData": [[Object]]}, "response": undefined}
[info] [Onyx] merge called for key: account properties: delegatedAccess hasChanged: true - "" 
[OnyxUpdateManager] Done applying Pusher update
[info] [Network] Finished API request in 588ms - {"command":"DisconnectAsDelegate","jsonCode":408,"requestID":"922ae341cbdbfa32-SJC"} {"request": {"command": "DisconnectAsDelegate", "data": {"apiRequestType": "makeRequestWithSideEffects"}, "failureData": [[Object]], "initiatedOffline": false, "requestID": 15, "successData": [[Object]]}, "response": {"jsonCode": 408, "message": "The account you are trying to use is deleted.", "requestID": "922ae341cbdbfa32-SJC", "title": "Session expired"}}
[info] Redirecting to Sign In because signOut() was called - "" 
[OnyxUpdateManager] Applying https update
[info] [OnyxUpdateManager] Applying update type: https with lastUpdateID: 0 - {"command":"DisconnectAsDelegate"} 
[info] [Onyx] merge called for key: account properties: delegatedAccess hasChanged: true - "" 
[OnyxUpdateManager] Done applying HTTPS update
[alrt] [Delegate] No auth token returned while disconnecting as a delegate - {"stack":"\"Error\\n    at alert (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.me.mobiexpensifyg.dev&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:175066:50)\\n    at anonymous (http://10.0.2.2:8081/index.bundle//&platform=android&dev=true&lazy=true&minify=false&app=org.me.mobiexpensifyg.dev&modulesOnly=false&runModule=true&excludeSource=true&sourcePaths=url-server:362335:27)\\n    at tryCallOne (address at InternalBytecode.js:1:1180)\\n    at anonymous (address at InternalBytecode.js:1:1874)\""} 
[info] [PersistedRequests] hit Onyx connect callback - {"isValNullish":true} 
[info] [OnyxDerived] dependency conciergeReportID for derived key conciergeChatReportID changed, recomputing - "" 
[info] [PushNotification] Unsubscribing from push notifications. - "" 
[info] [OnyxDerived] dependency report_ for derived key conciergeChatReportID changed, recomputing - "" 
[info] [OnyxDerived] value for key conciergeChatReportID changed, updating it in Onyx - {"old":"1603788335316209","new":null} 
[info] [Onyx] merge called for key: network properties: lastOfflineAt hasChanged: true - "" 
[info] [API] Called API write - {"command":"PusherPing","pingID":"7460322926414146777","pingTimestamp":1742365552062} 
[info] [API] Preparing request - {"command":"PusherPing","type":"write"} 
[info] [Onyx] set called for key: networkRequestQueue properties: 0 hasChanged: true - "" 
[info] [Pusher PINGPONG] Sending a PING to the server: 7460322926414146777 timestamp: 1742365552062 - "" 
[info] [PersistedRequests] hit Onyx connect callback - {"isValNullish":false} 
[info] [Network] Making API request - {"command":"PusherPing"} {"request": {"command": "PusherPing", "data": {"apiRequestType": "write", "canCancel": true, "pingID": "7460322926414146777", "pingTimestamp": 1742365552062, "pusherSocketID": "830873.11010157", "shouldRetry": true}, "initiatedOffline": false, "requestID": 16}, "response": undefined}
[info] [Pusher PINGPONG] Checking for late PONG events - "" 
[info] [Pusher PINGPONG] The server has not replied to the PING event in 152944 ms so going offline - "" 
[info] [Onyx] merge called for key: session properties: authToken,encryptedAuthToken,creationDate,accountID,email hasChanged: true - "" 
[info] [API] Called API write - {"command":"OpenApp","enablePriorityModeFilter":true,"policyIDList":[]} 
[info] [API] Preparing request - {"command":"OpenApp","type":"write"} 
[info] [API] Applying optimistic data - {"command":"OpenApp","type":"write"} 
[info] [SequentialQueue] Conflict action for command OpenApp - push: - "" 
[info] [Onyx] set called for key: networkRequestQueue properties: 0 hasChanged: true - "" 
[info] [PersistedRequests] hit Onyx connect callback - {"isValNullish":false} 
[info] [Onyx] merge called for key: isLoadingApp hasChanged: true - ""
Android: HybridApp Issue
android-hybrid.mov
  1. On Android: mWeb when copilot is removed I get logged-out and that's it, I'm left on the login screen:
Android: mWeb Issue
android-mweb.mov
  1. On iOS: HybridApp with OD account > switch to copilot > ND > copilot removed, same thing happens as described at the top of this comment:
iOS: HybridApp (OD > ND > OD) Issue
ios-hybrid-od.mp4

@s77rt
Copy link
Member Author

s77rt commented Mar 20, 2025

@ikevin127

Regarding the Android bug: Let's handle this last as I'm having trouble building the hybrid app on Android.

Regarding the Android mWeb bug: I was not able to reproduce. Can you please check again and if there are any conditions for reproduction?

Screen.Recording.2025-03-20.at.12.27.50.AM.mov

Regarding the iOS bug (OD copilot):
When you switch to a copilot account on OD we don't stash the session, thus in ND when access is removed, we have no stored session to use

@s77rt
Copy link
Member Author

s77rt commented Mar 20, 2025

I will raise a slack thread for the OD copilot case

@ikevin127
Copy link
Contributor

ikevin127 commented Mar 20, 2025

@s77rt Thanks for getting back!

Let's handle this last as I'm having trouble building the hybrid app on Android.

Sure, I think this is the only blocker at the moment. Here's a recent Slack thread of another C+ having trouble building Android which managed to fix the issue eventually - maybe that'll help.

🔄 I'm also going to try the PR - HybridApp build on a real Android device to see if the issue persists.

🟢 I'm going to try again on the Android: mWeb issue, this time with a real device and report back 👍

Edit: ✅ Android: mWeb works, tried on real device and behaviour is similar to web -> works as expected -> video added to Reviewer Checklist.

I will raise a slack thread for the OD copilot case

Makes sense, let's see what others say as maybe it's out of scope or just not an easy fix for this issue.

@s77rt
Copy link
Member Author

s77rt commented Mar 20, 2025

Regarding the OD copilot issue, I have found a solution for that but need to wait for https://github.com/Expensify/Mobile-Expensify/pull/13483 to be merged first

@ikevin127
Copy link
Contributor

🟢 Reviewer Checklist completed, Android build succeeded (after some hurdles) and things work as expected on a real device (OnePlus 7T - Android 14), you should test on a real device since on emulator I experienced issues (mentioned above) while testing.

🔄 Build keept failing with someting related to react-native-live-markdown / reanimated, I had to make sure Mobile-Expensify submodule is updated and I also cd into Mobile-Expensify where I ensured to pull the latest main changes and I run the command npm run clean, after which I returned to E/App and performed rebuild of HybridApp and it worked ✅

@s77rt The only thing remaining is the OD > ND transition issue, let me know once the other PR is merged and this PR is ready to test again in order to confirm the fix and approve if everything works as expected 👍

@ikevin127
Copy link
Contributor

@s77rt I asked for some help and merged https://github.com/Expensify/Mobile-Expensify/pull/13483, let me know if you're going to try Android: HybridApp build in order to add the videos to your PR Author Checklist.

I'll perform the rebuild as well and if the issue mentioned above is solved I'll approve, otherwise will let you know!

@s77rt
Copy link
Member Author

s77rt commented Apr 4, 2025

@ikevin127 Please hold retesting, we need changes to make use of the merged PR

@s77rt
Copy link
Member Author

s77rt commented Apr 4, 2025

@ikevin127 You can retest now.

The OD issue is fixed.

Screen.Recording.2025-04-04.at.10.04.03.AM.mov

I also tested on Android for the expired session bug but was not able to reproduce. Can you confirm if this is fixed from your end as well

android.mov

Copy link
Contributor

@ikevin127 ikevin127 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM

update.mp4

@melvin-bot melvin-bot bot requested a review from MariaHCD April 4, 2025 19:25
@MariaHCD MariaHCD merged commit bbcf20a into Expensify:main Apr 7, 2025
18 checks passed
@OSBotify
Copy link
Contributor

OSBotify commented Apr 7, 2025

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions
Copy link
Contributor

github-actions bot commented Apr 7, 2025

🚀 Deployed to staging by https://github.com/MariaHCD in version: 9.1.24-2 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@github-actions
Copy link
Contributor

github-actions bot commented Apr 9, 2025

🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.1.24-10 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 failure ❌
🍎 iOS 🍎 success ✅

@github-actions
Copy link
Contributor

github-actions bot commented Apr 9, 2025

🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.1.24-10 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 failure ❌
🍎 iOS 🍎 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants