Skip to content
Last updated

Enabling Elements in React Native

Like other platforms, React requires the same two or three simple steps: Add a web view, handle events/messages, and, in some cases, authenticate the user.

Step 1. Set Up a Web View

Component used: WebView

Required Web View Configuration
  • Enable JavaScript
  • Enable Web Storage (DOM Storage)

Web View Sample

elements-in-react.tsx
    // Step 1: Setup a "web view"
    <View style={styles.container}>
      <Text style={styles.text}>DailyPay Elements</Text>
      <View style={styles.webview}>
        <WebView
          cacheEnabled={false} // Disable caching for development
          ref={dpElementWebView}
          source={{ uri: dpElementUrl.toString() }}
          style={{ flex: 1 }}
          onMessage={handleMessageFromElement}
          domStorageEnabled={true}  
          javaScriptEnabled={true}  
        />
      </View>
      <View style={{ height: 100, margin: 10, flex: 1 }}>
        <Text style={styles.text}>Log:</Text>
        <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
          <Text style={styles.text}>{logMessages}</Text>
        </ScrollView>
      </View>
      <View>
        <Text style={styles.text}>Type:</Text>
        <TextInput
          style={styles.input}
          value={inputMessageType}
          onChangeText={setInputMessageType}
        />
        <Text style={styles.text}>Payload (JSON):</Text>
        <TextInput
          style={styles.input}
          value={inputMessagePayload}
          onChangeText={setInputMessagePayload}
        />
        <Button
          title="Send Message"
          onPress={() =>
            sendMessageToElement(inputMessageType, JSON.parse(inputMessagePayload))
          }
        />
      </View>
    </View>
    

Step 2. Set Up Event/Message Handling

Sending Events

Method used: postMessage

Please Log In to view this content.

Send Message Sample Code

elements-in-react.tsx
  // Step 2: Setup Communications: Event/Message Handling
  // Step 2a: Send Event to Elements
  const sendMessageToElement = (type: string, payload: object) => {
    const message = { type: type, payload: payload };
    if (dpElementWebView.current) {
      dpElementWebView.current.postMessage(JSON.stringify(message), dpElementOrigin);
      setLogMessages(
        (prevMessages) =>
          "Sent: " + JSON.stringify(message) + "\n" + prevMessages,
      );
    }
  };

  

Listening for Events

Property used: onMessage

Please Log In to view this content.

Receive Message Sample Code

elements-in-react.tsx
  // Step 2b: Listen for events from Elements
  const handleMessageFromElement = (event: WebViewMessageEvent) => {
    const receivedEvent = event.nativeEvent;
    // Log the raw event for debugging and illustration
    console.log("Raw event received:", receivedEvent);
    // It is crucial to verify the origin of the message for security reasons.
    if (receivedEvent.url.startsWith(dpElementOrigin) === false) {
      console.warn("Received message from unknown origin:", receivedEvent.url);
      return;
    }
    // Parse the received data - it should be a JSON formatted string
    let receivedMessage;
    try {
      receivedMessage = JSON.parse(receivedEvent.data);
    } catch (e) {
      console.error("Invalid JSON in payload input:", e);
      return;
    }
    console.log("Message from WebView:", receivedMessage);
    if (receivedMessage && receivedMessage.type) {
      setLogMessages(
        (prevMessages) =>
          "Received: " + JSON.stringify(receivedMessage) + "\n" + prevMessages,
      );
      switch (receivedMessage.type) {
        case "DAILYPAY_CARD_TOKENIZE_RESPONSE":
          console.log("DAILYPAY_CARD_TOKENIZE_RESPONSE received: ", receivedMessage.success);
          console.log("DAILYPAY_CARD_TOKENIZE_RESPONSE received: ", receivedMessage.response);
          // Call API create account function
          break;
        case "EXT_LOG":
          // Log if needed
          console.log("EXT_LOG received: ", receivedMessage.payload);
          break;
        case "EXT_NEED_AUTH":
          console.log("EXT_NEED_AUTH received: ", receivedMessage.payload);
          handleExtNeedAuth();
          break;
        default:
          console.log("Unhandled event type: ", receivedMessage.type);
      }
    }
  };
  

Step 3. Set Up User Authentication (Optional)

Authentication Message Sample Code:

elements-in-react.tsx
  // Step 3: Setup Authentication
  var dpAuthTokens: AuthorizeResult | RefreshResult | null = null;
  const dpAuthConfig: AuthConfiguration = {
    issuer: 'https://auth.uat.dailypay.com',
    clientId: 'REPLACE_WITH_YOUR_OAUTH_CLIENT_ID',
    redirectUrl: 'REPLACE_WITH_YOUR_APP_REDIRECT_URI',
    scopes: ['user:read'],
    serviceConfiguration: {
      authorizationEndpoint: 'https://auth.uat.dailypay.com/oauth2/auth',
      tokenEndpoint: 'https://auth.uat.dailypay.com/oauth2/token',
    },
  };

  const dpAuthenticateUser = async () => {
    try {
      const dpAuthTokens: AuthorizeResult = await authorize(dpAuthConfig);
      console.log('Authorization successful:', dpAuthTokens);
      // Save token in secure storage
      await SecureStore.setItemAsync('dpAuthTokens', JSON.stringify(dpAuthTokens));
      // Send token to WebView
      sendMessageToElement('GIVE_TOKEN', { token: dpAuthTokens.accessToken });
    } catch (error) {
      console.error('Authorization failed:', error);
      dpAuthTokens = null;
      // Handle authentication errors
    }      
  };
  
  const handleExtNeedAuth = async () => {
    // Check secure storage for existing tokens
    let dpAuthTokensString = await SecureStore.getItemAsync('dpAuthTokens');
    if (dpAuthTokensString) {
      dpAuthTokens = JSON.parse(dpAuthTokensString);
      console.log('Found existing tokens:', dpAuthTokens);
      // Check if token is expired
      if (dpAuthTokens && dpAuthTokens.refreshToken && Date.parse(dpAuthTokens.accessTokenExpirationDate) < Date.now()) {
        try{
          dpAuthTokens = await refresh(dpAuthConfig, {
            refreshToken: dpAuthTokens.refreshToken,
          });
          console.log('Token refresh successful:', dpAuthTokens);
          await SecureStore.setItemAsync('dpAuthTokens', JSON.stringify(dpAuthTokens));
        } catch (error) {
          console.warn('Token refresh failed:', error);
          dpAuthTokens = null;
          dpAuthenticateUser();
        };
      };
    } else {
      console.log('No tokens found, initiating login.');
      dpAuthenticateUser();
    };
    if (dpAuthTokens) {
      console.log(dpAuthTokens)
      sendMessageToElement('GIVE_TOKEN', { token: dpAuthTokens.accessToken });
    } else {
      console.warn('No valid tokens available, user needs to log in.');
    };
  };
  

Full Sample Code

elements-in-react.tsx
import * as SecureStore from 'expo-secure-store';
import React, { useRef, useState } from "react";
import {
  Button,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  View,
} from "react-native";
import { AuthConfiguration, authorize, AuthorizeResult, refresh, RefreshResult } from 'react-native-app-auth';
import { WebView, WebViewMessageEvent } from "react-native-webview";

export default function Index() {
  const dpElementOrigin = 'https://elements/uat.dailypay.com';
  const dpElementUrl = new URL(dpElementOrigin + '/v1/available-earnings');
  dpElementUrl.searchParams.append('client_id', 'REPLACE_WITH_YOUR_CLIENT_ID');
  dpElementUrl.searchParams.append('implementation_id', 'REPLACE_WITH_YOUR_IMPLEMENTATION_ID');
  // Not required but useful during development to avoid caching issues
  dpElementUrl.searchParams.append('cache_bust', Date.now().toString());
  const dpElementWebView = useRef<Window | null>(null);

  const [inputMessageType, setInputMessageType] = useState("GIVE_TOKEN");
  const [inputMessagePayload, setInputMessagePayload] =
    useState('{ "token": "" }');
  const [logMessages, setLogMessages] = useState("");

  // Step 3: Setup Authentication
  var dpAuthTokens: AuthorizeResult | RefreshResult | null = null;
  const dpAuthConfig: AuthConfiguration = {
    issuer: 'https://auth.uat.dailypay.com',
    clientId: 'REPLACE_WITH_YOUR_OAUTH_CLIENT_ID',
    redirectUrl: 'REPLACE_WITH_YOUR_APP_REDIRECT_URI',
    scopes: ['user:read'],
    serviceConfiguration: {
      authorizationEndpoint: 'https://auth.uat.dailypay.com/oauth2/auth',
      tokenEndpoint: 'https://auth.uat.dailypay.com/oauth2/token',
    },
  };

  const dpAuthenticateUser = async () => {
    try {
      const dpAuthTokens: AuthorizeResult = await authorize(dpAuthConfig);
      console.log('Authorization successful:', dpAuthTokens);
      // Save token in secure storage
      await SecureStore.setItemAsync('dpAuthTokens', JSON.stringify(dpAuthTokens));
      // Send token to WebView
      sendMessageToElement('GIVE_TOKEN', { token: dpAuthTokens.accessToken });
    } catch (error) {
      console.error('Authorization failed:', error);
      dpAuthTokens = null;
      // Handle authentication errors
    }      
  };
  
  const handleExtNeedAuth = async () => {
    // Check secure storage for existing tokens
    let dpAuthTokensString = await SecureStore.getItemAsync('dpAuthTokens');
    if (dpAuthTokensString) {
      dpAuthTokens = JSON.parse(dpAuthTokensString);
      console.log('Found existing tokens:', dpAuthTokens);
      // Check if token is expired
      if (dpAuthTokens && dpAuthTokens.refreshToken && Date.parse(dpAuthTokens.accessTokenExpirationDate) < Date.now()) {
        try{
          dpAuthTokens = await refresh(dpAuthConfig, {
            refreshToken: dpAuthTokens.refreshToken,
          });
          console.log('Token refresh successful:', dpAuthTokens);
          await SecureStore.setItemAsync('dpAuthTokens', JSON.stringify(dpAuthTokens));
        } catch (error) {
          console.warn('Token refresh failed:', error);
          dpAuthTokens = null;
          dpAuthenticateUser();
        };
      };
    } else {
      console.log('No tokens found, initiating login.');
      dpAuthenticateUser();
    };
    if (dpAuthTokens) {
      console.log(dpAuthTokens)
      sendMessageToElement('GIVE_TOKEN', { token: dpAuthTokens.accessToken });
    } else {
      console.warn('No valid tokens available, user needs to log in.');
    };
  };
  // Step 3 End  
    
  // Step 2: Setup Communications: Event/Message Handling
  // Step 2a: Send Event to Elements
  const sendMessageToElement = (type: string, payload: object) => {
    const message = { type: type, payload: payload };
    if (dpElementWebView.current) {
      dpElementWebView.current.postMessage(JSON.stringify(message), dpElementOrigin);
      setLogMessages(
        (prevMessages) =>
          "Sent: " + JSON.stringify(message) + "\n" + prevMessages,
      );
    }
  };

  // Step 2b: Listen for events from Elements
  const handleMessageFromElement = (event: WebViewMessageEvent) => {
    const receivedEvent = event.nativeEvent;
    // Log the raw event for debugging and illustration
    console.log("Raw event received:", receivedEvent);
    // It is crucial to verify the origin of the message for security reasons.
    if (receivedEvent.url.startsWith(dpElementOrigin) === false) {
      console.warn("Received message from unknown origin:", receivedEvent.url);
      return;
    }
    // Parse the received data - it should be a JSON formatted string
    let receivedMessage;
    try {
      receivedMessage = JSON.parse(receivedEvent.data);
    } catch (e) {
      console.error("Invalid JSON in payload input:", e);
      return;
    }
    console.log("Message from WebView:", receivedMessage);
    if (receivedMessage && receivedMessage.type) {
      setLogMessages(
        (prevMessages) =>
          "Received: " + JSON.stringify(receivedMessage) + "\n" + prevMessages,
      );
      switch (receivedMessage.type) {
        case "DAILYPAY_CARD_TOKENIZE_RESPONSE":
          console.log("DAILYPAY_CARD_TOKENIZE_RESPONSE received: ", receivedMessage.success);
          console.log("DAILYPAY_CARD_TOKENIZE_RESPONSE received: ", receivedMessage.response);
          // Call API create account function
          break;
        case "EXT_LOG":
          // Log if needed
          console.log("EXT_LOG received: ", receivedMessage.payload);
          break;
        case "EXT_NEED_AUTH":
          console.log("EXT_NEED_AUTH received: ", receivedMessage.payload);
          handleExtNeedAuth();
          break;
        default:
          console.log("Unhandled event type: ", receivedMessage.type);
      }
    }
  };
  // Step 2 End

  return (
    // Step 1: Setup a "web view"
    <View style={styles.container}>
      <Text style={styles.text}>DailyPay Elements</Text>
      <View style={styles.webview}>
        <WebView
          cacheEnabled={false} // Disable caching for development
          ref={dpElementWebView}
          source={{ uri: dpElementUrl.toString() }}
          style={{ flex: 1 }}
          onMessage={handleMessageFromElement}
          domStorageEnabled={true}  
          javaScriptEnabled={true}  
        />
      </View>
      <View style={{ height: 100, margin: 10, flex: 1 }}>
        <Text style={styles.text}>Log:</Text>
        <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
          <Text style={styles.text}>{logMessages}</Text>
        </ScrollView>
      </View>
      <View>
        <Text style={styles.text}>Type:</Text>
        <TextInput
          style={styles.input}
          value={inputMessageType}
          onChangeText={setInputMessageType}
        />
        <Text style={styles.text}>Payload (JSON):</Text>
        <TextInput
          style={styles.input}
          value={inputMessagePayload}
          onChangeText={setInputMessagePayload}
        />
        <Button
          title="Send Message"
          onPress={() =>
            sendMessageToElement(inputMessageType, JSON.parse(inputMessagePayload))
          }
        />
      </View>
    </View>
    // Step 1 End
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#25292e",
    justifyContent: "center",
    alignItems: "center",
  },
  input: {
    height: 40,
    borderWidth: 1,
    padding: 10,
    width: 200,
    backgroundColor: "#fff",
  },
  text: {
    color: "#fff",
  },
  webview: {
    width: 380,
    height: 180,
    borderRadius: 10,
    overflow: 'hidden',
  },
});