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
13 changes: 13 additions & 0 deletions SharedNativeModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NativeModules } from 'react-native';

// Define type for native module
interface SharedNativeModuleType {
trackEvent: (eventName: string, eventParams: Record<string, string>) => void;
localize: (path: string, params?: Record<string, string>) => Promise<string>;
}

// Safely cast NativeModules
export const { SharedNativeModule } = NativeModules as {
SharedNativeModule: SharedNativeModuleType;
};

2 changes: 0 additions & 2 deletions TurnkeyModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ interface TurnkeyNativeModuleType {
onAppleAuthRequest: (nonce: string, publicKey: string) => void;

onUploadDydxAddressUploadResponse: (dydxAddress: string, result: string) => void;

onTrackingEvent: (eventName: string, eventParams: Record<string, string>) => void;
}

// Safely cast NativeModules
Expand Down
3 changes: 2 additions & 1 deletion TurnkeyReact/SharedConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export type TurnkeyConfigs = {
turnkeyOrgId: string,
backendApiUrl: string,
deploymentUri: string,
tosUrl: string,
privacyUrl: string,
theme: "light" | "dark" | "classicDark" | undefined,
enableAppleLoginIn: boolean,
strings: Record<string, string>,
isSamsungDevice: boolean,
};
7 changes: 4 additions & 3 deletions TurnkeyReact/TurnkeyAddress.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import { DeviceEventEmitter, NativeModules } from 'react-native';
import { FetchDepositAddressesEvent, NativeToJsRequestEvent, TurnkeyNativeModule } from '../TurnkeyModule';
import { DeviceEventEmitter } from 'react-native';
import { FetchDepositAddressesEvent, TurnkeyNativeModule } from '../TurnkeyModule';
import { SharedNativeModule } from '../SharedNativeModule';

DeviceEventEmitter.addListener(
'FetchDepositAddresses',
Expand Down Expand Up @@ -31,7 +32,7 @@ DeviceEventEmitter.addListener(
TurnkeyNativeModule.onJsResponse(callbackId, rawResponse);

} catch (error: any) {
TurnkeyNativeModule.onTrackingEvent("TurnkeyFetchDepositAddressError", { "dydxAddress": dydxAddress, "error": error.message });
SharedNativeModule.trackEvent("TurnkeyFetchDepositAddressError", { "dydxAddress": dydxAddress, "error": error.message });
console.error("Error during sign-in: ", error, error.message);
TurnkeyNativeModule.onJsResponse(callbackId, error.message);
}
Expand Down
49 changes: 33 additions & 16 deletions TurnkeyReact/components/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { currentTheme } from '../../rn_style/themes/currentTheme';
import { DydxTurnkeySession } from '../providers/dydxTurnkeySession';
import RenderHTML from "react-native-render-html";
import { useWindowDimensions } from "react-native";
import { useLocalizedString } from '../../useLocalizedString';

const renderError = () => {
const {
Expand Down Expand Up @@ -109,12 +110,28 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {
const showContinueModal = continueModal && hasError === false;

const { width } = useWindowDimensions();
const source = {
html: configs.strings["APP.ONBOARDING.TOS_SHORT"],

const tos = useLocalizedString("APP.HEADER.TERMS_OF_USE");
const tosLink = `<a href="${configs.tosUrl}">${tos}</a>`;
const privacy = useLocalizedString("APP.ONBOARDING.PRIVACY_POLICY");
const privacyLink = `<a href="${configs.privacyUrl}">${privacy}</a>`;

const tosText = useLocalizedString("APP.ONBOARDING.TOS_SHORT", {
"TERMS_LINK": tosLink,
"PRIVACY_POLICY_LINK": privacyLink,
}) ?? "";
const tosHtml = {
html: tosText
};

const MemoizedRenderHTML = React.memo(RenderHTML);

const signInTitle = useLocalizedString("APP.TURNKEY_ONBOARD.SIGN_IN_TITLE");
const signInDescription = useLocalizedString("APP.TURNKEY_ONBOARD.SIGN_IN_DESCRIPTION");
const orText = useLocalizedString("APP.GENERAL.OR") ?? "Or";
const signInDesktopText = useLocalizedString("APP.TURNKEY_ONBOARD.SIGN_IN_DESKTOP");
const signInWalletText = useLocalizedString("APP.TURNKEY_ONBOARD.SIGN_IN_WALLET");

return (
<ScrollView
bounces={false} // iOS
Expand All @@ -136,9 +153,9 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {
<View style={styles.dragHandle} />

{/* Header */}
<Text style={styles.title}>{configs.strings["APP.TURNKEY_ONBOARD.SIGN_IN_TITLE"]}</Text>
<Text style={styles.title}>{signInTitle}</Text>
<Text style={styles.subtitle}>
{configs.strings["APP.TURNKEY_ONBOARD.SIGN_IN_DESCRIPTION"]}
{signInDescription}
</Text>

{/* Social icons row */}
Expand Down Expand Up @@ -180,7 +197,7 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {
{/* Divider */}
<View style={styles.dividerContainer}>
<View style={styles.divider} />
<Text style={styles.dividerText}>{configs.strings["APP.GENERAL.OR"]}</Text>
<Text style={styles.dividerText}>{orText}</Text>
<View style={styles.divider} />
</View>

Expand All @@ -205,7 +222,7 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {
source={require('../../rn_style/assets/icon_desktop.png')}
style={{ width: 18, height: 18, marginEnd: 8, tintColor: currentTheme.colors.textSecondary }}
/>
<Text style={styles.actionButtonText}>{configs.strings["APP.TURNKEY_ONBOARD.SIGN_IN_DESKTOP"]}</Text>
<Text style={styles.actionButtonText}>{signInDesktopText}</Text>
<Image
source={require('../../rn_style/assets/chevron_right.png')}
style={{ height: 10, tintColor: currentTheme.colors.textTertiary }}
Expand All @@ -223,7 +240,7 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {
source={require('../../rn_style/assets/icon_wallet.png')}
style={{ width: 16, height: 16, marginEnd: 8, tintColor: currentTheme.colors.textSecondary }}
/>
<Text style={styles.actionButtonText}>{configs.strings["APP.TURNKEY_ONBOARD.SIGN_IN_WALLET"]}</Text>
<Text style={styles.actionButtonText}>{signInWalletText}</Text>
<Image
source={require('../../rn_style/assets/chevron_right.png')}
style={{ height: 10, tintColor: currentTheme.colors.textTertiary }}
Expand All @@ -233,8 +250,8 @@ export const Auth = ({ configs }: { configs: TurnkeyConfigs }) => {

<MemoizedRenderHTML
contentWidth={width * 0.9} // 90% of screen width
source={source}
baseStyle={{ textAlign: "center" }} // center text inside
source={tosHtml}
baseStyle={{ textAlign: "center", paddingBottom: 20 }} // center text inside

tagsStyles={{
body: { fontFamily: "Satoshi-Regular", fontSize: 11, color: currentTheme.colors.textTertiary },
Expand Down Expand Up @@ -271,19 +288,19 @@ const ContinueSignInModal = ({
styles,
providerName,
}: ContinueSignInModalProps) => {
var signInTitle: string
var signInTitle: string | null = null;
switch (providerName?.toLowerCase()) {
case "google":
signInTitle = configs.strings['APP.TURNKEY_ONBOARD.SIGN_IN_GOOGLE'];
signInTitle = useLocalizedString('APP.TURNKEY_ONBOARD.SIGN_IN_GOOGLE');
break;
case "apple":
signInTitle = configs.strings['APP.TURNKEY_ONBOARD.SIGN_IN_APPLE'];
signInTitle = useLocalizedString('APP.TURNKEY_ONBOARD.SIGN_IN_APPLE');
break;
case "email":
signInTitle = configs.strings['APP.TURNKEY_ONBOARD.SIGN_IN_EMAIL'];
case "email":
signInTitle = useLocalizedString('APP.TURNKEY_ONBOARD.SIGN_IN_EMAIL');
break;
default:
signInTitle = configs.strings['APP.TURNKEY_ONBOARD.CONTINUE_SIGN_IN_TITLE'];
signInTitle = useLocalizedString('APP.TURNKEY_ONBOARD.CONTINUE_SIGN_IN_TITLE');
}
return (
<Modal
Expand Down Expand Up @@ -334,7 +351,7 @@ const ContinueSignInModal = ({
paddingHorizontal: 24,
}}
>
{configs.strings['APP.TURNKEY_ONBOARD.CONTINUE_SIGN_IN_DESCRIPTION']}
{useLocalizedString('APP.TURNKEY_ONBOARD.CONTINUE_SIGN_IN_DESCRIPTION')}
</Text>
</View>
</Modal>
Expand Down
27 changes: 17 additions & 10 deletions TurnkeyReact/components/EmailInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { Input } from "../components/ui/input";
import { useThemedStyles } from '../turnkeyStyle';
import { Image, Modal, View, TouchableOpacity } from "react-native";
import { Text } from './ui/text';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useAuthRelay } from "../hooks/useAuthRelay";
import { TurnkeyConfigs } from "../sharedConfigs";
import { EmbeddedKeyAndNonce } from "./useEmbeddedKeyAndNonce";
import { Button } from "./ui/button";
import { OtpType } from "../lib/types";
import { currentTheme } from "../../rn_style/themes/currentTheme";
import { TurnkeyNativeModule } from "../../TurnkeyModule";
import { SharedNativeModule } from "../../SharedNativeModule";
import { useLocalizedString } from '../../useLocalizedString';

interface EmailInputProps {
embeddedKeyAndNonce: EmbeddedKeyAndNonce;
Expand All @@ -23,17 +24,20 @@ export const EmailInput = ({
configs,
focusChanged,
}: EmailInputProps) => {
const { initOtpLogin, completeOtpAuth, state } = useAuthRelay();
const { initOtpLogin, state } = useAuthRelay();
const [email, setEmail] = useState<string>('');
const [isValidEmail, setIsValidEmail] = useState<boolean>(false);
const styles = useThemedStyles(currentTheme);

const [checkEmailModalVisible, setCheckEmailModalVisible] = useState(false);
const [showResendButton, setShowResendButton] = useState(false);

const emailPlaceholder = useLocalizedString("APP.TURNKEY_ONBOARD.EMAIL_PLACEHOLDER");
const emailLabel = useLocalizedString("APP.GENERAL.EMAIL");

const handleEmailSubmit = () => {
if (isValidEmail) {
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginInitiated", { "signinMethod": "email" });
SharedNativeModule.trackEvent("TurnkeyLoginInitiated", { "signinMethod": "email" });
initOtpLogin({
otpType: OtpType.Email,
contact: email,
Expand All @@ -56,7 +60,7 @@ export const EmailInput = ({
visible={checkEmailModalVisible}
onClose={() => setCheckEmailModalVisible(false)}
onResend={() => {
TurnkeyNativeModule.onTrackingEvent("TurnkeyResendEMailClick", {});
SharedNativeModule.trackEvent("TurnkeyResendEMailClick", {});
handleEmailSubmit();
}}
showResendButton={showResendButton}
Expand All @@ -70,7 +74,7 @@ export const EmailInput = ({
fontSize: currentTheme.fontSizes.medium,
color: currentTheme.colors.textTertiary,
}}>
{configs.strings["APP.GENERAL.EMAIL"] + ":"}
{emailLabel + ":"}
</Text>
) : (
<Image
Expand All @@ -91,7 +95,7 @@ export const EmailInput = ({
autoCorrect={false}
keyboardType="email-address"
placeholderTextColor={currentTheme.colors.textTertiary}
placeholder={configs.strings["APP.TURNKEY_ONBOARD.EMAIL_PLACEHOLDER"]}
placeholder={emailPlaceholder || "Email"}
value={email && email.length > 0 ? email : defaultEmail} // Workaround for a React Native bug on Samsung devices
onChangeText={(text: string) => {
const trimmedText = text.trim();
Expand Down Expand Up @@ -143,6 +147,9 @@ const CheckEmailModal = ({
currentTheme,
styles,
}: CheckEmailModalProps) => {
const checkEmailTitle = useLocalizedString('APP.TURNKEY_ONBOARD.CHECK_EMAIL_TITLE');
const checkEmailDescription = useLocalizedString('APP.TURNKEY_ONBOARD.CHECK_EMAIL_DESCRIPTION');
const resendText = useLocalizedString('APP.TURNKEY_ONBOARD.RESEND');
return (
<Modal
visible={visible}
Expand Down Expand Up @@ -183,7 +190,7 @@ const CheckEmailModal = ({
marginBottom: 8,
}}
>
{configs.strings['APP.TURNKEY_ONBOARD.CHECK_EMAIL_TITLE']}
{checkEmailTitle}
</Text>
<Text
style={{
Expand All @@ -193,7 +200,7 @@ const CheckEmailModal = ({
marginBottom: 24,
}}
>
{configs.strings['APP.TURNKEY_ONBOARD.CHECK_EMAIL_DESCRIPTION']}
{checkEmailDescription}
</Text>

{showResendButton && (
Expand Down Expand Up @@ -224,7 +231,7 @@ const CheckEmailModal = ({
fontSize: currentTheme.fontSizes.small,
}}
>
{configs.strings['APP.TURNKEY_ONBOARD.RESEND']}
{resendText}
</Text>
</View>
</Button>
Expand Down
5 changes: 3 additions & 2 deletions TurnkeyReact/components/OAuthInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useEffect } from "react";
import { currentTheme } from "../../rn_style/themes/currentTheme";
import { useThemedStyles } from "../turnkeyStyle";
import { Platform } from 'react-native';
import { SharedNativeModule } from "../../SharedNativeModule";

type OAuthProps = {
onSuccess: (params: OAuthRequest) => Promise<void>;
Expand All @@ -28,7 +29,7 @@ export const GoogleAuthButton: React.FC<OAuthProps> = ({

const handlePress = async () => {
try {
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginInitiated", { "signinMethod": "google" });
SharedNativeModule.trackEvent("TurnkeyLoginInitiated", { "signinMethod": "google" });
await handleGoogleOAuth({
clientId: configs.googleClientId,
nonce: embeddedKeyAndNonce.nonce!,
Expand Down Expand Up @@ -94,7 +95,7 @@ export const AppleAuthButton: React.FC<OAuthProps> = ({
})

const handleAppleAuth = async () => {
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginInitiated", { "signinMethod": "apple" });
SharedNativeModule.trackEvent("TurnkeyLoginInitiated", { "signinMethod": "apple" });
if (!embeddedKeyAndNonce.nonce) {
console.error("Nonce is not ready");
return;
Expand Down
13 changes: 7 additions & 6 deletions TurnkeyReact/providers/authRelayProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getValueWithKey, setValueWithKey } from "../lib/store";
import { STORAGE_KEY } from "../lib/constants";
import { jwtDecode } from 'jwt-decode';
import { decodeBase64 } from "../lib/utils";
import { SharedNativeModule } from "../../SharedNativeModule";

type AuthActionType =
| { type: "PASSKEY"; payload: User }
Expand Down Expand Up @@ -228,7 +229,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
return Promise.resolve(dydxSession);

} catch (error: any) {
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginError", { "signinMethod": "email", "error": error.message });
SharedNativeModule.trackEvent("TurnkeyLoginError", { "signinMethod": "email", "error": error.message });

console.error("Error decrypting credential bundle:", error);
dispatch({ type: "ERROR", payload: error.message });
Expand Down Expand Up @@ -258,7 +259,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
const result = await handleOauthResponse(response, embeddedKeyAndNonce, configs, providerName, undefined);
return Promise.resolve(result);
} catch (error: any) {
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginError", { "signinMethod": providerName, "error": error.message });
SharedNativeModule.trackEvent("TurnkeyLoginError", { "signinMethod": providerName, "error": error.message });
console.error("Error during sign-in: ", error, error.message);
dispatch({ type: "ERROR", payload: error.message });
} finally {
Expand Down Expand Up @@ -341,7 +342,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
} else if (loginMethod === LoginMethod.Email) {
signInMethod = "email";
}
TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginError", { "signinMethod": signInMethod, "error": error.message });
SharedNativeModule.trackEvent("TurnkeyLoginError", { "signinMethod": signInMethod, "error": error.message });
console.error("Error during sign-in: ", error, error.message);
dispatch({ type: "ERROR", payload: error.message });
} finally {
Expand All @@ -360,7 +361,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
if (!salt) {
const alreadyExists = response.alreadyExists;
if (alreadyExists) {
throw new Error(configs.strings["ERRORS.TURNKEY_ONBOARDING.USER_ALREADY_HAS_TURNKEY"],);
throw new Error(await SharedNativeModule.localize("ERRORS.TURNKEY_ONBOARDING.USER_ALREADY_HAS_TURNKEY"));
}
throw new Error("No salt provided in response");
}
Expand Down Expand Up @@ -410,7 +411,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
throw new Error("Unable to export wallet mnemonics");
}

TurnkeyNativeModule.onTrackingEvent("TurnkeyLoginCompleted", { "signinMethod": loginMethod });
SharedNativeModule.trackEvent("TurnkeyLoginCompleted", { "signinMethod": loginMethod });

TurnkeyNativeModule.onAuthCompleted(
signed,
Expand Down Expand Up @@ -505,7 +506,7 @@ export const AuthRelayProvider: React.FC<AuthRelayProviderProps> = ({
// TODO(turnkey): handle policy returned in response

} catch (error: any) {
TurnkeyNativeModule.onTrackingEvent("UploadAddressError", { dydxAddress, "error": error.message });
SharedNativeModule.trackEvent("UploadAddressError", { dydxAddress, "error": error.message });
console.error("Error during sign-in: ", error, error.message);
dispatch({ type: "ERROR", payload: error.message });
throw error;
Expand Down
Loading