Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 17 additions & 44 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, use
import type {NativeEventSubscription} from 'react-native';
import {AppState, Linking, NativeModules, Platform} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {useOnyx, withOnyx} from 'react-native-onyx';
import Onyx, {useOnyx} from 'react-native-onyx';
import ConfirmModal from './components/ConfirmModal';
import DeeplinkWrapper from './components/DeeplinkWrapper';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
Expand Down Expand Up @@ -55,7 +55,7 @@ Onyx.registerLogger(({level, message}) => {
}
});

type ExpensifyOnyxProps = {
type ExpensifyProps = {
/** Whether the app is waiting for the server's response to determine if a room is public */
isCheckingPublicRoom: OnyxEntry<boolean>;

Expand All @@ -77,18 +77,7 @@ type ExpensifyOnyxProps = {
/** Last visited path in the app */
lastVisitedPath: OnyxEntry<string | undefined>;
};

type ExpensifyProps = ExpensifyOnyxProps;

function Expensify({
isCheckingPublicRoom = true,
updateAvailable,
isSidebarLoaded = false,
screenShareRequest,
updateRequired = false,
focusModeNotification = false,
lastVisitedPath,
}: ExpensifyProps) {
function Expensify() {
const appStateChangeListener = useRef<NativeEventSubscription | null>(null);
const [isNavigationReady, setIsNavigationReady] = useState(false);
const [isOnyxMigrated, setIsOnyxMigrated] = useState(false);
Expand All @@ -98,7 +87,15 @@ function Expensify({
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE);
const [userMetadata] = useOnyx(ONYXKEYS.USER_METADATA);
const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false);
const [isCheckingPublicRoom] = useOnyx(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM);

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.

We should keep the default value of isCheckingPublicRoom = true as previously, there is a hidden bug that cause login page briefly shown when navigate via public room link as anonymous. More details here: #64638 (comment)

const [updateAvailable] = useOnyx(ONYXKEYS.UPDATE_AVAILABLE);
const [updateRequired] = useOnyx(ONYXKEYS.UPDATE_REQUIRED);
const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED);
const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST);
const [focusModeNotification] = useOnyx(ONYXKEYS.FOCUS_MODE_NOTIFICATION);
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH);

useEffect(() => {
if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) {
Expand Down Expand Up @@ -148,15 +145,17 @@ function Expensify({
// Initialize this client as being an active client
ActiveClientManager.init();

// Initialize Fullstory lib
FS.init();

// Used for the offline indicator appearing when someone is offline
const unsubscribeNetInfo = NetworkConnection.subscribeToNetInfo();

return unsubscribeNetInfo;
}, []);

useEffect(() => {
// Initialize Fullstory lib
FS.init(userMetadata);
}, [userMetadata]);

// Log the platform and config to debug .env issues
useEffect(() => {
Log.info('App launched', false, {Platform, CONFIG});
Expand Down Expand Up @@ -304,30 +303,4 @@ function Expensify({

Expensify.displayName = 'Expensify';

export default withOnyx<ExpensifyProps, ExpensifyOnyxProps>({
isCheckingPublicRoom: {
key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM,
initWithStoredValues: false,
},
updateAvailable: {
key: ONYXKEYS.UPDATE_AVAILABLE,
initWithStoredValues: false,
},
updateRequired: {
key: ONYXKEYS.UPDATE_REQUIRED,
initWithStoredValues: false,
},

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.

@danieldoglas I think there's a very likely possibility this change has been leading to issues with the updateRequired logic. The intended behavior is that this value will only be true when it is explicitly set to true AFTER the component has mounted. If the value gets stuck on true then the user will get stuck and be unable to upgrade since they'd be on the correct version already, but have out of date Onyx values.

isSidebarLoaded: {
key: ONYXKEYS.IS_SIDEBAR_LOADED,
},
screenShareRequest: {
key: ONYXKEYS.SCREEN_SHARE_REQUEST,
},
focusModeNotification: {
key: ONYXKEYS.FOCUS_MODE_NOTIFICATION,
initWithStoredValues: false,
},
lastVisitedPath: {
key: ONYXKEYS.LAST_VISITED_PATH,
},
})(Expensify);
export default Expensify;
53 changes: 22 additions & 31 deletions src/libs/Fullstory/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import FullStory, {FSPage} from '@fullstory/react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import CONST from '@src/CONST';
import * as Environment from '@src/libs/Environment/Environment';
import ONYXKEYS from '@src/ONYXKEYS';
import type {UserMetadata} from '@src/types/onyx';

/**
Expand All @@ -14,18 +12,8 @@ const FS = {
/**
* Initializes FullStory
*/
init: () => {
Environment.getEnvironment().then((envName: string) => {
// We only want to start fullstory if the app is running in production
// Since we don't use it in other environments, it is also disabled in build.gradle to speed up Android build times
// See https://github.com/Expensify/App/pull/50206 for more information
if (envName !== CONST.ENVIRONMENT.PRODUCTION) {
return;
}
FullStory.restart();
const [session] = useOnyx(ONYXKEYS.USER_METADATA);
FS.fsIdentify(session);
});
init: (value: OnyxEntry<UserMetadata>) => {
FS.consentAndIdentify(value);
},

/**
Expand All @@ -42,32 +30,35 @@ const FS = {
* Initializes the FullStory metadata with the provided metadata information.
*/
consentAndIdentify: (value: OnyxEntry<UserMetadata>) => {
// On the first subscribe for UserMetadta, this function will be called. We need
// to confirm that we actually have any value here before proceeding.
if (!value?.accountID) {
return;
}
try {
// We only use FullStory in production environment
FullStory.consent(true);
FS.fsIdentify(value);
// We only use FullStory in production environment. We need to check this here
// after the init function since this function is also called on updates for
// UserMetadata onyx key.
Environment.getEnvironment().then((envName: string) => {
if (envName !== CONST.ENVIRONMENT.PRODUCTION) {
return;
}
FullStory.restart();
FullStory.consent(true);
FS.fsIdentify(value, envName);
});
} catch (e) {
// error handler
}
},

/**
* Sets the FullStory user identity based on the provided metadata information.
* If the metadata is null or the email is 'undefined', the user identity is anonymized.
* If the metadata contains an accountID, the user identity is defined with it.
*/
fsIdentify: (metadata: OnyxEntry<UserMetadata>) => {
if (!metadata?.accountID) {
// anonymize FullStory user identity metadata
FullStory.anonymize();
} else {
Environment.getEnvironment().then((envName: string) => {
// define FullStory user identity
const localMetadata = metadata;
localMetadata.environment = envName;
FullStory.identify(String(localMetadata.accountID), localMetadata);
});
}
fsIdentify: (metadata: UserMetadata, envName: string) => {
const localMetadata = metadata;
localMetadata.environment = envName;
FullStory.identify(String(localMetadata.accountID), localMetadata);
},
};

Expand Down
48 changes: 20 additions & 28 deletions src/libs/Fullstory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,11 @@ const FS = {
*/
onReady: () =>
new Promise((resolve) => {
Environment.getEnvironment().then((envName: string) => {
if (CONST.ENVIRONMENT.PRODUCTION !== envName) {
return;
}
// Initialised via HEAD snippet
if (!isInitialized()) {
init({orgId: ''}, resolve);
} else {
FullStory('observe', {type: 'start', callback: resolve});
}
});
if (!isInitialized()) {
init({orgId: ''}, resolve);
} else {
FullStory('observe', {type: 'start', callback: resolve});
}
}),

/**
Expand All @@ -56,18 +50,21 @@ const FS = {
* Initializes the FullStory metadata with the provided metadata information.
*/
consentAndIdentify: (value: OnyxEntry<UserMetadata>) => {
// On the first subscribe for UserMetadata, this function will be called. We need
// to confirm that we actually have any value here before proceeding.
if (!value?.accountID) {
return;
}
try {
Environment.getEnvironment().then((envName: string) => {
if (CONST.ENVIRONMENT.PRODUCTION !== envName) {
return;
}
FS.onReady().then(() => {
FS.consent(true);
if (value) {
const localMetadata = value;
localMetadata.environment = envName;
FS.fsIdentify(localMetadata);
}
const localMetadata = value;
localMetadata.environment = envName;
FS.fsIdentify(localMetadata);
});
});
} catch (e) {
Expand All @@ -80,23 +77,18 @@ const FS = {
* If the metadata does not contain an email, the user identity is anonymized.
* If the metadata contains an accountID, the user identity is defined with it.
*/
fsIdentify: (metadata: OnyxEntry<UserMetadata>) => {
if (!metadata?.accountID) {
// anonymize FullStory user identity metadata
FS.anonymize();
} else {
// define FullStory user identity
FullStory('setIdentity', {
uid: String(metadata.accountID),
properties: metadata,
});
}
fsIdentify: (metadata: UserMetadata) => {
FullStory('setIdentity', {
uid: String(metadata.accountID),
properties: metadata,
});
},

/**
* Init function, created so we're consistent with the native file
*/
init: () => {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
init: (_value: OnyxEntry<UserMetadata>) => {},
};

export default FS;
Expand Down