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

feat: add debug menu to specify endpoint for mobile token server #5049

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
58 changes: 58 additions & 0 deletions src/mobile-token/DebugTokenServerAddress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {Button} from '@atb/components/button';
import {ThemeText} from '@atb/components/text';
import {storage} from '@atb/storage';
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding storage, we recently created usePersistedBoolState.
We might want to either create one for string as well, or make the existing one general purpose. @gorandalum

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it is possible to refactor it into a more generic usePersistedState.

import {StyleSheet} from '@atb/theme';
import {useEffect, useState} from 'react';
import {TextInput, View} from 'react-native';

export const DebugTokenServerAddress = () => {
const [ipAddress, setIpAddress] = useState<string>('');

const saveDebugIpAddress = async (ipAddress: string) => {
if (ipAddress.length > 0) {
const fullAddress = `http://${ipAddress}:8080`;
Copy link
Member

Choose a reason for hiding this comment

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

Not quite sure what's useful to test in practice, but should we have the input be the whole address instead of just the IP? If it's useful to e.g. for point a prod device to staging servers, where we don't have access to a server running on a local IP.

Copy link
Member

@jorelosorio jorelosorio Mar 4, 2025

Choose a reason for hiding this comment

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

We usually save that into the .env file and read from it.
NOTE: But I see there is a menu so, I agree then that should be full address here

Copy link
Member Author

Choose a reason for hiding this comment

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

We can change it to a whole IP (probably with a http:// prefix added) 👍🏼

Also, pointing a prod device to staging server won't work, because the tokens on Prod are different from tokens from Staging.

This is just to make it easier for testing on staging, either for reproducing a bug, or testing/developing new functionality. For example: we want to replicate a bug with creating tokens, we can just point the token server to local, instead of changing some parameters from the code to allow debug. These changes might be minor, but the added overhead of having to modify the server is something that we could have avoided with this menu.

await storage.set('@ATB_debug_token_server_ip_address', fullAddress);
} else {
await storage.remove('@ATB_debug_token_server_ip_address');
}
};

useEffect(() => {
const getStoredIpAddress = async () => {
const storedAddress = await storage.get(
'@ATB_debug_token_server_ip_address',
);
setIpAddress(storedAddress ?? '');
};
getStoredIpAddress();
}, []);

return (
<View>
<ThemeText>Server IP Address</ThemeText>

<TextInput
onChangeText={(text) => setIpAddress(text)}
value={ipAddress}
inputMode="numeric"
placeholder="e.g. 10.100.1.89 (Leave blank to use default)"
clearButtonMode="always"
/>
<Button
onPress={async () => await saveDebugIpAddress(ipAddress)}
text="Use IP Address"
style={styles.button}
expanded={true}
/>
</View>
);
};

const styles = StyleSheet.create({
button: {
marginVertical: 8,
},
container: {
marginVertical: 8,
},
});
51 changes: 43 additions & 8 deletions src/mobile-token/tokenService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
TokenReattestationRemoteTokenStateError,
TokenReattestationRequiredError,
} from '@entur-private/abt-token-server-javascript-interface';
import {storage} from '@atb/storage';
import {API_BASE_URL} from '@env';
import {getCurrentUserIdGlobal} from '@atb/auth/AuthContext';

const CorrelationIdHeaderName = 'Atb-Correlation-Id';
const SignedTokenHeaderName = 'Atb-Signed-Token';
Expand Down Expand Up @@ -55,6 +58,17 @@ const handleError = (err: any) => {
throw parseBffCallErrors(err.response?.data);
};

const getBaseUrl = async () => {
const debugUrl = await storage.get('@ATB_debug_token_server_ip_address');
const authId = getCurrentUserIdGlobal();
if (debugUrl && debugUrl.length > 0) {
client.defaults.headers.common[
'entur-customer-account-id'
] = `ATB:CustomerAccount:${authId}`;
return debugUrl;
} else return API_BASE_URL;
};

export const tokenService: TokenService = {
initiateNewMobileToken: async (
preferRequireAttestation,
Expand All @@ -74,14 +88,15 @@ export const tokenService: TokenService = {
[CorrelationIdHeaderName]: traceId,
[IsEmulatorHeaderName]: String(await isEmulator()),
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
skipErrorLogging: isRemoteTokenStateError,
timeout: 15000,
})
.then((res) => res.data.pendingTokenDetails)
.catch(handleError);
},
activateNewMobileToken: (pendingToken, correlationId) =>
activateNewMobileToken: async (pendingToken, correlationId) =>
client
.post<CompleteTokenInitializationResponse>(
'/tokens/v4/activate',
Expand All @@ -90,27 +105,29 @@ export const tokenService: TokenService = {
headers: {
[CorrelationIdHeaderName]: correlationId,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
},
)
.then((res) => res.data.activeTokenDetails)
.catch(handleError),
initiateMobileTokenRenewal: (token, secureContainer, correlationId) =>
initiateMobileTokenRenewal: async (token, secureContainer, correlationId) =>
client
.post<InitiateTokenRenewalResponse>('/tokens/v4/renew', undefined, {
headers: {
[CorrelationIdHeaderName]: correlationId,
[SignedTokenHeaderName]: secureContainer,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
})
.then((res) => res.data.pendingTokenDetails)
.catch(handleError),
completeMobileTokenRenewal: (
completeMobileTokenRenewal: async (
pendingToken,
secureContainer,
activatedToken,
Expand All @@ -125,14 +142,20 @@ export const tokenService: TokenService = {
[CorrelationIdHeaderName]: correlationId,
[SignedTokenHeaderName]: secureContainer,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
},
)
.then((res) => res.data.activeTokenDetails)
.catch(handleError),
reattestMobileToken: (token, secureContainer, reattestation, correlationId) =>
reattestMobileToken: async (
token,
secureContainer,
reattestation,
correlationId,
) =>
client
.get<GetTokenDetailsResponse>('/tokens/v4/details', {
headers: {
Expand All @@ -141,19 +164,21 @@ export const tokenService: TokenService = {
[AttestationHeaderName]: reattestation.data,
[AttestationTypeHeaderName]: reattestation.type,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
})
.then(() => {})
.catch(handleError),
getMobileTokenDetails: (token, secureContainer, traceId) =>
getMobileTokenDetails: async (token, secureContainer, traceId) =>
client
.get<GetTokenDetailsResponse>('/tokens/v4/details', {
headers: {
[CorrelationIdHeaderName]: traceId,
[SignedTokenHeaderName]: secureContainer,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
Expand All @@ -169,26 +194,32 @@ export const tokenService: TokenService = {
headers: {
[CorrelationIdHeaderName]: traceId,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
},
)
.then((res) => res.data.removed)
.catch(handleError),
listTokens: (traceId: string) =>
listTokens: async (traceId: string) =>
client
.get<ListResponse>('/tokens/v4/list', {
headers: {
[CorrelationIdHeaderName]: traceId,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
})
.then((res) => res.data.tokens)
.catch(handleError),
toggle: (tokenId: string, traceId: string, bypassRestrictions: boolean) =>
toggle: async (
tokenId: string,
traceId: string,
bypassRestrictions: boolean,
) =>
client
.post<ToggleResponse>(
'/tokens/v4/toggle',
Expand All @@ -197,6 +228,7 @@ export const tokenService: TokenService = {
headers: {
[CorrelationIdHeaderName]: traceId,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
Expand All @@ -205,9 +237,10 @@ export const tokenService: TokenService = {
.then((res) => res.data.tokens)
.catch(handleError),

getTokenToggleDetails: () =>
getTokenToggleDetails: async () =>
client
.get<TokenLimitResponse>('/tokens/v4/toggle/details', {
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
Expand Down Expand Up @@ -235,6 +268,7 @@ export const tokenService: TokenService = {
[AttestationHeaderName]: attestation?.data,
[AttestationTypeHeaderName]: attestation?.type,
},
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
skipErrorLogging: isRemoteTokenStateError,
Expand Down Expand Up @@ -265,6 +299,7 @@ export const tokenService: TokenService = {
mobileTokenErrorCorrelationId: traceId,
},
{
baseURL: await getBaseUrl(),
authWithIdToken: true,
timeout: 15000,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {useOnboardingContext} from '@atb/onboarding';
import Bugsnag from '@bugsnag/react-native';
import {useFeatureTogglesContext} from '@atb/modules/feature-toggles';
import {DebugSabotage} from '@atb/mobile-token/DebugSabotage';
import {DebugTokenServerAddress} from '@atb/mobile-token/DebugTokenServerAddress';

function setClipboard(content: string) {
Clipboard.setString(content);
Expand Down Expand Up @@ -508,6 +509,15 @@ export const Profile_DebugInfoScreen = () => {
</View>
}
/>
<ExpandableSectionItem
text="Modify Server Endpoint"
showIconText={true}
expandContent={
<View>
<DebugTokenServerAddress />
</View>
}
/>
</View>
}
/>
Expand Down
1 change: 1 addition & 0 deletions src/storage/StorageModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type StorageModelKeysTypes = keyof typeof StorageModelKeysEnum;
export type StorageModel = {
stored_user_locations: string;
install_id: string;
'@ATB_debug_token_server_ip_address': string;
'@ATB_feedback_display_stats': string;
'@ATB_journey_search-history': string;
'@ATB_last_mobile_token_user': string;
Expand Down
Loading