Skip to content
56 changes: 42 additions & 14 deletions src/libs/actions/Delegate.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {NativeModules} from 'react-native';
import Onyx from 'react-native-onyx';
import type {OnyxUpdate} from 'react-native-onyx';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import * as API from '@libs/API';
import type {AddDelegateParams, RemoveDelegateParams, UpdateDelegateRoleParams} from '@libs/API/parameters';
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import * as NetworkStore from '@libs/Network/NetworkStore';
import {getCurrentUserEmail} from '@libs/Network/NetworkStore';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -16,6 +15,7 @@ import type Credentials from '@src/types/onyx/Credentials';
import type Response from '@src/types/onyx/Response';
import type Session from '@src/types/onyx/Session';
import {confirmReadyToOpenApp, openApp} from './App';
import {getCurrentUserAccountID} from './Report';
import updateSessionAuthTokens from './Session/updateSessionAuthTokens';
import updateSessionUser from './Session/updateSessionUser';

Expand Down Expand Up @@ -51,6 +51,14 @@ Onyx.connect({
callback: (value) => (stashedSession = value ?? {}),
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (newActivePolicyID) => {
activePolicyID = newActivePolicyID;
},
});

const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [
ONYXKEYS.NVP_TRY_FOCUS_MODE,
ONYXKEYS.PREFERRED_THEME,
Expand All @@ -73,6 +81,8 @@ function connect(email: string) {
Onyx.set(ONYXKEYS.STASHED_CREDENTIALS, credentials);
Onyx.set(ONYXKEYS.STASHED_SESSION, session);

const previousAccountID = getCurrentUserAccountID();

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -130,6 +140,14 @@ function connect(email: string) {
Onyx.update(failureData);
return;
}
if (!activePolicyID) {
Log.alert('[Delegate] Unable to access activePolicyID');
Onyx.update(failureData);
return;
}
const restrictedToken = response.restrictedToken;
const policyID = activePolicyID;

return SequentialQueue.waitForIdle()
.then(() => Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS))
.then(() => {
Expand All @@ -138,9 +156,7 @@ function connect(email: string) {

NetworkStore.setAuthToken(response?.restrictedToken ?? null);
confirmReadyToOpenApp();
openApp();

NativeModules.HybridAppModule.switchAccount(email);
openApp().then(() => NativeModules.HybridAppModule.switchAccount(email, restrictedToken, policyID, String(previousAccountID)));
Copy link
Copy Markdown
Contributor

@c3024 c3024 Feb 4, 2025

Choose a reason for hiding this comment

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

-NativeModules.HybridAppModule.switchAccount(email, restrictedToken, policyID, String(previousAccountID)));
+NativeModules.HybridAppModule?.switchAccount(email, restrictedToken, policyID, String(previousAccountID)));

@war-in

I think we gotta check here if HybridAppModule exists before calling the function like we do everywhere else on the App.

This is crashing on web on dev and throws an error on production when a user switches to their main account from being a copilot.

Screen.Recording.2025-02-04.at.9.27.52.AM.mov

cc: @dangrous @chiragsalian

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ah okay thanks! is there an issue for that crash?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

oh hey it also crashes on connecting as delegate. Your fix works there too. @c3024 can you create a PR if you haven't already?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@dangrous

No, there is no issue created for this. I found this when I was checking something else. I am creating a PR. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR here

});
})
.catch((error) => {
Expand Down Expand Up @@ -195,22 +211,34 @@ function disconnect() {
return;
}

if (!response?.requesterID || !response?.requesterEmail) {
Log.alert('[Delegate] No requester data returned while disconnecting as a delegate');
return;
}

const requesterEmail = response.requesterEmail;
const authToken = response.authToken;
return SequentialQueue.waitForIdle()
.then(() => Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS))
.then(() => {
// Update authToken in Onyx and in our local variables so that API requests will use the new authToken
updateSessionAuthTokens(response?.authToken, response?.encryptedAuthToken);
Onyx.set(ONYXKEYS.CREDENTIALS, {
...stashedCredentials,
accountID: response.requesterID,
});
Onyx.set(ONYXKEYS.SESSION, {
...stashedSession,
accountID: response.requesterID,
email: requesterEmail,
authToken,
encryptedAuthToken: response.encryptedAuthToken,
});
Onyx.set(ONYXKEYS.STASHED_CREDENTIALS, {});
Onyx.set(ONYXKEYS.STASHED_SESSION, {});

NetworkStore.setAuthToken(response?.authToken ?? null);

Onyx.set(ONYXKEYS.CREDENTIALS, stashedCredentials);
Onyx.set(ONYXKEYS.SESSION, stashedSession);
Onyx.set(ONYXKEYS.STASHED_CREDENTIALS, {});
Onyx.set(ONYXKEYS.STASHED_SESSION, {});
confirmReadyToOpenApp();
openApp();

NativeModules.HybridAppModule.switchAccount(getCurrentUserEmail() ?? '');
openApp().then(() => NativeModules.HybridAppModule.switchAccount(requesterEmail, authToken, '', ''));
});
})
.catch((error) => {
Expand Down
28 changes: 23 additions & 5 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Navigation from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import * as MainQueue from '@libs/Network/MainQueue';
import * as NetworkStore from '@libs/Network/NetworkStore';
import {getCurrentUserEmail} from '@libs/Network/NetworkStore';
import NetworkConnection from '@libs/NetworkConnection';
import * as Pusher from '@libs/Pusher/pusher';
import {getReportIDFromLink, parseReportRouteParams as parseReportRouteParamsReportUtils} from '@libs/ReportUtils';
Expand Down Expand Up @@ -524,7 +525,7 @@ function signInAfterTransitionFromOldDot(transitionURL: string) {
nudgeMigrationTimestamp,
isSingleNewDotEntry,
primaryLogin,
shouldRemoveDelegatedAccess,
oldDotOriginalAccountEmail,
} = Object.fromEntries(
queryParams.split('&').map((param) => {
const [key, value] = param.split('=');
Expand All @@ -543,22 +544,39 @@ function signInAfterTransitionFromOldDot(transitionURL: string) {
const setSessionDataAndOpenApp = new Promise<Route>((resolve) => {
clearOnyxForNewAccount()
.then(() => {
if (!shouldRemoveDelegatedAccess) {
// This section controls copilot changes
const currentUserEmail = getCurrentUserEmail();

// If ND and OD account are the same - do nothing
if (email === currentUserEmail) {
return;
}

// If account was changed to original one on OD side - clear onyx
if (!oldDotOriginalAccountEmail) {
return Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS);
}

// If we're already logged in - do nothing, data will be set in next step
if (currentUserEmail) {
return;
}
return Onyx.clear(KEYS_TO_PRESERVE_DELEGATE_ACCESS);

// If we're not logged in - set stashed data
return Onyx.multiSet({
[ONYXKEYS.STASHED_CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword},
});
})
.then(() =>
Onyx.multiSet({
[ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)},
[ONYXKEYS.ACCOUNT]: {primaryLogin},
[ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword},
[ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: isSingleNewDotEntry === 'true',
[ONYXKEYS.NVP_TRYNEWDOT]: {
classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'},
nudgeMigration: nudgeMigrationTimestamp ? {timestamp: new Date(nudgeMigrationTimestamp)} : undefined,
},
}),
}).then(() => Onyx.merge(ONYXKEYS.ACCOUNT, {primaryLogin})),
)
.then(() => {
if (clearOnyxOnStart === 'true') {
Expand Down
2 changes: 1 addition & 1 deletion src/types/modules/react-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type StartupTimer from '@libs/StartupTimer/types';
type HybridAppModule = {
closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void;
completeOnboarding: (status: boolean) => void;
switchAccount: (newDotCurrentAccount: string) => void;
switchAccount: (newDotCurrentAccount: string, authToken: string, policyID: string, accountID: string) => void;
exitApp: () => void;
};

Expand Down
6 changes: 6 additions & 0 deletions src/types/onyx/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ type Response = {

/** If there is newer data to load for pagination commands */
hasNewerActions?: boolean;

/** The email of the original user (returned when in delegate mode) */
requesterEmail?: string;

/** The ID of the original user (returned when in delegate mode) */
requesterID?: number;
};

export default Response;