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 51888 cors errors are displayed for attachments #53407

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e8c5c33
fix cors issues in attachments
Kalydosos Dec 18, 2024
b3371ae
fix cors issues in attachments
Kalydosos Dec 18, 2024
28f7a27
fix cors issues
Kalydosos Dec 19, 2024
433717d
Merge branch 'Expensify:main' into fix-51888-cors-errors-are-displaye…
Kalydosos Dec 19, 2024
88cf61a
Update
Kalydosos Dec 19, 2024
be7f93b
fix cors issues
Kalydosos Dec 19, 2024
f797f2f
fix cors issues
Kalydosos Dec 19, 2024
bc0c48b
fix cors issues
Kalydosos Dec 19, 2024
c7aab35
fix cors issues
Kalydosos Dec 20, 2024
e5d504d
fix cors issues
Kalydosos Dec 20, 2024
170e7e8
fix cors issues
Kalydosos Dec 20, 2024
7ba9f8a
fix cors issues
Kalydosos Dec 20, 2024
d25f0b0
fix cors errors on attachments
Kalydosos Dec 23, 2024
65fb38c
fix cors errors on attachments
Kalydosos Dec 23, 2024
d9cbf9f
Merge branch 'Expensify:main' into fix-51888-cors-errors-are-displaye…
Kalydosos Dec 23, 2024
3040f19
fix cors errors on attachments
Kalydosos Dec 25, 2024
cdf9209
Merge branch 'fix-51888-cors-errors-are-displayed-for-attachments' of…
Kalydosos Dec 25, 2024
bffc444
Merge branch 'Expensify:main' into fix-51888-cors-errors-are-displaye…
Kalydosos Dec 25, 2024
b9f1240
Merge branch 'fix-51888-cors-errors-are-displayed-for-attachments' of…
Kalydosos Dec 25, 2024
93a3c05
Merge branch 'Expensify:main' into fix-51888-cors-errors-are-displaye…
Kalydosos Dec 26, 2024
873dcec
fix cors errors on attachments
Kalydosos Dec 26, 2024
5cbae41
fix cors errors on attachments
Kalydosos Dec 26, 2024
ae15c5b
fix cors errors on attachments
Kalydosos Dec 31, 2024
ee77c8d
Merge branch 'main' into fix-51888-cors-errors-are-displayed-for-atta…
Kalydosos Dec 31, 2024
bbe9e2e
fix cors errors on attachments
Kalydosos Dec 31, 2024
80fff80
Merge branch 'main' into fix-51888-cors-errors-are-displayed-for-atta…
Kalydosos Jan 3, 2025
1c7807a
Update src/CONST.ts
Kalydosos Jan 3, 2025
4174956
Update src/components/Image/index.tsx
Kalydosos Jan 3, 2025
528a03c
Update src/components/Image/index.tsx
Kalydosos Jan 3, 2025
c66c460
updates after review
Kalydosos Jan 3, 2025
7cb8b5f
Merge branch 'fix-51888-cors-errors-are-displayed-for-attachments' of…
Kalydosos Jan 3, 2025
fe93bc8
updates after review
Kalydosos Jan 3, 2025
c9dc455
updates after review
Kalydosos Jan 3, 2025
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
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,8 @@ const CONST = {
UNKNOWN: 'unknown',
},
},
// the number of milliseconds for an idle session to expire
Kalydosos marked this conversation as resolved.
Show resolved Hide resolved
SESSION_EXPIRATION_TIME_MS: 2 * 3600 * 1000, // 2 hours
WEEK_STARTS_ON: 1, // Monday
DEFAULT_TIME_ZONE: {automatic: true, selected: 'America/Los_Angeles'},
DEFAULT_ACCOUNT_DATA: {errors: null, success: '', isLoading: false},
Expand Down Expand Up @@ -1581,6 +1583,7 @@ const CONST = {
ATTACHMENT_PREVIEW_ATTRIBUTE: 'src',
ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE: 'data-name',
ATTACHMENT_LOCAL_URL_PREFIX: ['blob:', 'file:'],
ATTACHMENT_OR_RECEIPT_LOCAL_URL: /^https:\/\/(www\.)?([a-z0-9_-]+\.)*expensify.com(:[0-9]+)?\/(chat-attachments|receipts)/,
ATTACHMENT_THUMBNAIL_URL_ATTRIBUTE: 'data-expensify-thumbnail-url',
ATTACHMENT_THUMBNAIL_WIDTH_ATTRIBUTE: 'data-expensify-width',
ATTACHMENT_THUMBNAIL_HEIGHT_ATTRIBUTE: 'data-expensify-height',
Expand Down
2 changes: 1 addition & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ const ROUTES = {
ATTACHMENTS: {
route: 'attachment',
getRoute: (
reportID: string,
reportID: string | undefined,
type: ValueOf<typeof CONST.ATTACHMENT_TYPE>,
url: string,
accountID?: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function extractAttachments(
}

if (name === 'img' && attribs.src) {
const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
const expensifySource = attribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] ?? (new RegExp(CONST.ATTACHMENT_OR_RECEIPT_LOCAL_URL, 'i').test(attribs.src) ? attribs.src : null);
const source = tryResolveUrlFromApiRoot(expensifySource || attribs.src);
const previewSource = tryResolveUrlFromApiRoot(attribs.src);
const sourceLinkKey = `${source}|${currentLink}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ function ImageRenderer({tnode}: ImageRendererProps) {
// Concierge responder attachments are uploaded to S3 without any access
// control and thus require no authToken to verify access.
//
const attachmentSourceAttribute = htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE];
const attachmentSourceAttribute =
htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] ?? (new RegExp(CONST.ATTACHMENT_OR_RECEIPT_LOCAL_URL, 'i').test(htmlAttribs.src) ? htmlAttribs.src : null);
const isAttachmentOrReceipt = !!attachmentSourceAttribute;

// Files created/uploaded/hosted by App should resolve from API ROOT. Other URLs aren't modified
Expand Down Expand Up @@ -105,14 +106,14 @@ function ImageRenderer({tnode}: ImageRendererProps) {
}

const attachmentLink = tnode.parent?.attributes?.href;
const route = ROUTES.ATTACHMENTS?.getRoute(reportID ?? '-1', type, source, accountID, isAttachmentOrReceipt, fileName, attachmentLink);
const route = ROUTES.ATTACHMENTS?.getRoute(reportID, type, source, accountID, isAttachmentOrReceipt, fileName, attachmentLink);
Navigation.navigate(route);
}}
onLongPress={(event) => {
if (isDisabled) {
return;
}
showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs));
showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs));
}}
shouldUseHapticsOnLongPress
accessibilityRole={CONST.ROLE.BUTTON}
Expand Down
102 changes: 81 additions & 21 deletions src/components/Image/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React, {useCallback, useContext, useMemo, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import {useSession} from '@components/OnyxProvider';
import {isExpiredSession} from '@libs/actions/Session';
import activateReauthenticator from '@libs/actions/Session/Reauthenticator';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import BaseImage from './BaseImage';
import {ImageBehaviorContext} from './ImageBehaviorContextProvider';
import type {ImageOnLoadEvent, ImageOnyxProps, ImageOwnProps, ImageProps} from './types';
import type {ImageOnLoadEvent, ImageProps} from './types';

function Image({source: propsSource, isAuthTokenRequired = false, session, onLoad, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, style, ...forwardedProps}: ImageProps) {
function Image({source: propsSource, isAuthTokenRequired = false, onLoad, objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL, style, ...forwardedProps}: ImageProps) {
const [aspectRatio, setAspectRatio] = useState<string | number | null>(null);
const isObjectPositionTop = objectPosition === CONST.IMAGE_OBJECT_POSITION.TOP;
const session = useSession();

const {shouldSetAspectRatioInStyle} = useContext(ImageBehaviorContext);

Expand Down Expand Up @@ -37,58 +40,115 @@ function Image({source: propsSource, isAuthTokenRequired = false, session, onLoa
},
[onLoad, updateAspectRatio],
);

// an accepted session is either received less than 60s after the previous
// or is the first valid session since previous session expired
Copy link
Contributor

Choose a reason for hiding this comment

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

Please explain why and what is an acceptedSession in the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlinoz accepted sessions are sessions of a certain criteria that we think can necessitate a reload of the images because images sources barely changes unless specific events occur like network issues (offline/online) per example. Here we target new session received less than 60s after the previous session (that could be from fresh reauthentication, the previous session was not necessarily expired) or new session after the previous session was expired (based on timestamp gap between the 2 creationDate and the freshness of the new session).

const isAcceptedSession = useCallback((sessionCreationDateDiff: number, sessionCreationDate: number) => {
return sessionCreationDateDiff < 60000 || (sessionCreationDateDiff >= CONST.SESSION_EXPIRATION_TIME_MS && new Date().getTime() - sessionCreationDate < 60000);
}, []);

/**
* trying to figure out if the current session is expired or fresh from a necessary reauthentication
*/
const previousSessionAge = useRef<number | undefined>();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a hook called usePrevious, let use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok i'll check that

Copy link
Contributor

Choose a reason for hiding this comment

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

Did we give up on using usePrevious?

const validSessionAge: number | undefined = useMemo(() => {
// for performance gain, the processing is reserved to attachments images only
if (!isAuthTokenRequired) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't get this comment, can you explain it to me please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlinoz yes we want the session age aspects and related processing to be applied only to images that require authentication (attachments images and receipts) because some other images use the same Image component but are not connected to the cors errors bceause they dont require authentication

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, left a suggestion

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// for performance gain, the processing is reserved to attachments images only
// Authentication is required only for certain types of images (attachments and receipts), so let's only calculate the session age for those

return undefined;
}
if (session?.creationDate) {
if (previousSessionAge.current) {
// most likely a reauthentication happens
// but unless the calculated source is different from the previous, the image wont reload
if (isAcceptedSession(session.creationDate - previousSessionAge.current, session.creationDate)) {
Kalydosos marked this conversation as resolved.
Show resolved Hide resolved
return session.creationDate;
}
return previousSessionAge.current;
}
if (isExpiredSession(session.creationDate)) {
return new Date().getTime();
}
return session.creationDate;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's explain why we return the current time if the session is expired

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlinoz yes we kind of reset the countdown to now so future sessions creationDate can be compared to that time

}
return undefined;
}, [session, isAuthTokenRequired, isAcceptedSession]);
useEffect(() => {
if (!isAuthTokenRequired) {
return;
}
previousSessionAge.current = validSessionAge;
});

/**
* Check if the image source is a URL - if so the `encryptedAuthToken` is appended
* to the source.
*/
// source could be a result of require or a number or an object but all are expected so no unsafe-assignment
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// source could be a result of require or a number or an object but all are expected so no unsafe-assignment

const source = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, we didn't need it before, can't we have the correct type here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlinoz, there was quite some changes around the code just below, maybe it's not needed anymore. I will remove it to see if no eslint error (there was none locally after removing it)

if (typeof propsSource === 'object' && 'uri' in propsSource) {
if (typeof propsSource.uri === 'number') {
return propsSource.uri;
}
const authToken = session?.encryptedAuthToken ?? null;
if (isAuthTokenRequired && authToken) {
return {
...propsSource,
headers: {
[CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken,
},
};
if (!!session?.creationDate && !isExpiredSession(session.creationDate)) {
// session valid
return {
Kalydosos marked this conversation as resolved.
Show resolved Hide resolved
...propsSource,
headers: {
[CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken,
},
};
}
if (session) {
activateReauthenticator(session);
}
return undefined;
}
}
return propsSource;
// The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034.
// but we still need the image to reload sometimes (exemple : when the current session is expired)
// by forcing a recalculation of the source (which value could indeed change) through the modification of the variable validSessionAge
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// but we still need the image to reload sometimes (exemple : when the current session is expired)
// but we still need the image to reload sometimes (example : when the current session is expired)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

a french touch, 😄

// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [propsSource, isAuthTokenRequired]);
}, [propsSource, isAuthTokenRequired, validSessionAge]);
useEffect(() => {
rlinoz marked this conversation as resolved.
Show resolved Hide resolved
if (!isAuthTokenRequired || source !== undefined) {
return;
}
forwardedProps?.waitForSession?.();
}, [source, isAuthTokenRequired, forwardedProps]);

/**
* If the image fails to load and the object position is top, we should hide the image by setting the opacity to 0.
*/
const shouldOpacityBeZero = isObjectPositionTop && !aspectRatio;

if (source === undefined && !!forwardedProps?.waitForSession) {
return undefined;
}
if (source === undefined) {
return <FullScreenLoadingIndicator />;
}
return (
<BaseImage
// eslint-disable-next-line react/jsx-props-no-spreading
{...forwardedProps}
onLoad={handleLoad}
style={[style, shouldSetAspectRatioInStyle && aspectRatio ? {aspectRatio, height: 'auto'} : {}, shouldOpacityBeZero && {opacity: 0}]}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
source={source}
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a comment to explain why we disable eslint here

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we don't need this anymore, right?

Suggested change
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rlinoz i am not sure, but i will remove to see

/>
);
}

function imagePropsAreEqual(prevProps: ImageOwnProps, nextProps: ImageOwnProps) {
function imagePropsAreEqual(prevProps: ImageProps, nextProps: ImageProps) {
return prevProps.source === nextProps.source;
}

const ImageWithOnyx = React.memo(
withOnyx<ImageProps, ImageOnyxProps>({
session: {
key: ONYXKEYS.SESSION,
},
})(Image),
imagePropsAreEqual,
);
const ImageWithOnyx = React.memo(Image, imagePropsAreEqual);

ImageWithOnyx.displayName = 'Image';

Expand Down
17 changes: 8 additions & 9 deletions src/components/Image/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import type {ImageSource} from 'expo-image';
import type {ImageRequireSource, ImageResizeMode, ImageStyle, ImageURISource, StyleProp} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {Session} from '@src/types/onyx';

type ExpoImageSource = ImageSource | number | ImageSource[];

type ImageObjectPosition = ValueOf<typeof CONST.IMAGE_OBJECT_POSITION>;

type ImageOnyxProps = {
/** Session info for the currently logged in user. */
session: OnyxEntry<Session>;
};

type ImageOnLoadEvent = {
nativeEvent: {
width: number;
Expand Down Expand Up @@ -53,8 +46,14 @@ type ImageOwnProps = BaseImageProps & {

/** The object position of image */
objectPosition?: ImageObjectPosition;

/** the image should wait for a valid session to reload
* At the moment this function is called, the image is not in cache anymore
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/** the image should wait for a valid session to reload
* At the moment this function is called, the image is not in cache anymore
/**
* Called when the image should wait for a valid session to reload
* At the moment this function is called, the image is not in cache anymore

Copy link
Contributor Author

@Kalydosos Kalydosos Jan 3, 2025

Choose a reason for hiding this comment

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

done 👍

* cf issue#51888
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace with link to what you are referecing

*/
waitForSession?: () => void;
};

type ImageProps = ImageOnyxProps & ImageOwnProps;
type ImageProps = ImageOwnProps;

export type {BaseImageProps, ImageOwnProps, ImageOnyxProps, ImageProps, ExpoImageSource, ImageOnLoadEvent, ImageObjectPosition};
export type {BaseImageProps, ImageOwnProps, ImageProps, ExpoImageSource, ImageOnLoadEvent, ImageObjectPosition};
6 changes: 6 additions & 0 deletions src/components/ImageView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV
/>
);
}

return (
<View
// eslint-disable-next-line react-compiler/react-compiler
Expand All @@ -238,6 +239,11 @@ function ImageView({isAuthTokenRequired = false, url, fileName, onError}: ImageV
resizeMode={RESIZE_MODES.contain}
onLoadStart={imageLoadingStart}
onLoad={imageLoad}
waitForSession={() => {
setIsLoading(true);
setZoomScale(0);
setIsZoomed(false);
}}
onError={onError}
/>
</PressableWithoutFeedback>
Expand Down
6 changes: 6 additions & 0 deletions src/components/ImageWithSizeCalculation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ function ImageWithSizeCalculation({url, altText, style, onMeasure, onLoadFailure
}}
onError={onError}
onLoad={imageLoadedSuccessfully}
waitForSession={() => {
// at the moment this function is called the image is not in cache anymore
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// at the moment this function is called the image is not in cache anymore
// At the moment this function is called the image is not in cache anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done 👍

isLoadedRef.current = false;
setIsImageCached(false);
setIsLoading(true);
}}
objectPosition={objectPosition}
/>
{isLoading && !isImageCached && !isOffline && <FullscreenLoadingIndicator style={[styles.opacity1, styles.bgTransparent]} />}
Expand Down
8 changes: 8 additions & 0 deletions src/components/Lightbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan
updateContentSize(e);
setLightboxImageLoaded(true);
}}
waitForSession={() => {
// only active lightbox should call this function
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we explain why in the comment please?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

if (!isActive || isFallbackVisible || !isLightboxVisible) {
return;
}
setContentSize(cachedImageDimensions.get(uri));
setLightboxImageLoaded(false);
}}
/>
</MultiGestureCanvas>
</View>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ShowContextMenuContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ ShowContextMenuContext.displayName = 'ShowContextMenuContext';
function showContextMenuForReport(
event: GestureResponderEvent | MouseEvent,
anchor: ContextMenuAnchor,
reportID: string,
reportID: string | undefined,
action: OnyxEntry<ReportAction>,
checkIfContextMenuActive: () => void,
isArchivedRoom = false,
Expand All @@ -60,7 +60,7 @@ function showContextMenuForReport(
anchor,
reportID,
action?.reportActionID,
ReportUtils.getOriginalReportID(reportID, action),
reportID ? ReportUtils.getOriginalReportID(reportID, action) : undefined,
undefined,
checkIfContextMenuActive,
checkIfContextMenuActive,
Expand Down
9 changes: 9 additions & 0 deletions src/components/TestToolMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ function TestToolMenu() {
/>
</TestToolRow>

{/* Sends an expired session to the FE and invalidates the session by the same time in the BE. Action is delayed for 15s */}
<TestToolRow title={translate('initialSettingsPage.troubleshoot.authenticationStatus')}>
<Button
small
text={translate('initialSettingsPage.troubleshoot.invalidateWithDelay')}
onPress={() => Session.expireSessionWithDelay()}
/>
</TestToolRow>

<TestCrash />
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,7 @@ const translations = {
debugMode: 'Debug mode',
invalidFile: 'Invalid file',
invalidFileDescription: 'The file you are trying to import is not valid. Please try again.',
invalidateWithDelay: 'Invalidate with delay',
},
debugConsole: {
saveLog: 'Save log',
Expand Down
3 changes: 2 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,7 +1270,8 @@ const translations = {
usingImportedState: 'Estás utilizando el estado importado. Pulsa aquí para borrarlo.',
debugMode: 'Modo depuración',
invalidFile: 'Archivo inválido',
invalidFileDescription: 'El archivo que estás intentando importar no es válido. Por favor, inténtalo de nuevo.',
invalidFileDescription: 'El archivo que ests intentando importar no es válido. Por favor, inténtalo de nuevo.',
invalidateWithDelay: 'Invalidar con retraso',
},
debugConsole: {
saveLog: 'Guardar registro',
Expand Down
1 change: 1 addition & 0 deletions src/libs/E2E/actions/e2eLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function (): Promise<boolean> {
.then((response) => {
Onyx.merge(ONYXKEYS.SESSION, {
authToken: response.authToken,
creationDate: new Date().getTime(),
email: e2eUserCredentials.email,
});
console.debug('[E2E] Signed in finished!');
Expand Down
Loading
Loading