We're hiring
Products

Channels

Beams

Chatkit

DocsTutorialsSupportCareersPusher Blog
Sign InSign Up
Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Products

Channels

Build scalable, realtime features into your apps

Features Pricing

Beams

Send push notifications programmatically at scale

Pricing

Chatkit

Build chat into your app in hours, not days

Pricing
Developers

Docs

Read the docs to learn how to use our products

Channels Beams Chatkit

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Status

Check on the status of any of our products

Sign InSign Up

Add YouTube and web page link previews to your React Native chat app

  • Wern Ancheta
April 11th, 2019
You will need Node 11+, Yarn and React Native 0.5+ installed on your machine.

In this tutorial, you will learn how to add link previews to your React Native chat app. When you share links to your social media accounts, or on the Messenger app a preview is often generated. Those previews are what we’re going to build in this tutorial.

Prerequisites

Basic knowledge of Node, React, and React Native is required to follow this tutorial.

We’ll be using the following package versions:

  • Node 11.2
  • Yarn 1.13
  • React Native 0.59

You’ll also need a Chatkit app instance. This tutorial assumes that you already know how to create one. Be sure to enable the test token provider as that’s what we’ll be using.

Lastly, you’ll need an ngrok account, we’ll use it to expose the server to the internet.

App overview

We’re going to build a chat app that automatically determines URL’s from the user inputted text. This URL is then used to fetch the thumbnail image and title of the page. Note that this heavily relies on the website’s use of Open Graph Tags. This means that previews won’t be generated for websites that don’t use it.

The app flow starts in the login screen where the user has to enter their username and their friend’s username. The friend’s username doesn’t already need to have been used for logging in at this point.

Once logged in, the user is automatically navigated to the chat screen where they can start sending messages. If the message contains a URL, a thumbnail image preview is shown. When they click on it, it will launch the app that can display the content. Note that the app has only been tested with Youtube video URL’s and URL’s to web pages. Any other content (for example, PDF, audio) haven’t been tested.

You can find the code on its GitHub repo.

Bootstrapping the app

To save on time, I’ve already set up a starter branch in which the navigation and the styles for each page are already added. Start by cloning the repo, switching to the starter branch and installing the dependencies:

    git clone https://github.com/anchetaWern/RNChatkitLinkPreviews
    cd RNChatkitLinkPreviews
    git checkout starter
    yarn
    react-native eject

Once the dependencies are installed, link the packages which need to be linked:

    react-native link react-native-gesture-handler
    react-native link react-native-vector-icons
    react-native link react-native-config

Next, update your android/app/build.gradle file and add the following. This allows us to copy the Gradle file from this module at build time. This helps us avoid problems when upgrading the package later on:

    apply from: "../../node_modules/react-native/react.gradle"

    // add these:
    apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
    apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

Next, install the server dependencies:

    cd server
    yarn

In this tutorial, we’ll be using the following packages for the app:

  • @pusher/chatkit - for using Chatkit to easily implement a chat app.
  • react-native-gifted-chat - for building the chat UI.
  • react-native-link-preview - for extracting Open Graph data from web pages.
  • axios - for making requests to the server.

The following are used for the server:

  • @pusher/chatkit-server - Chatkit’s server-side library. We’ll mainly use it to create users and rooms.
  • express - for spinning up a server.

Add the server code

Now that you’ve set up the app, we’re now ready to add the code. Start by creating a server/server.js file and add the following. This will import all the packages we need for the server:

    // server/server.js

    const express = require("express"); // for spinning up a server
    const bodyParser = require("body-parser"); // middleware for parsing data passed in the request body
    const cors = require("cors"); // for allowing any host to make a request to the server
    const Chatkit = require("@pusher/chatkit-server"); // Chatkit's server component library

    require("dotenv").config(); // for using .env files

Next, get the Chatkit configuration from the .env file (we’ll create this file later) at the root of the server directory and initialize Chatkit and the server:

    const app = express();
    const INSTANCE_LOCATOR_ID = process.env.CHATKIT_INSTANCE_LOCATOR_ID;
    const CHATKIT_SECRET = process.env.CHATKIT_SECRET_KEY;

    const chatkit = new Chatkit.default({
      instanceLocator: `v1:us1:${INSTANCE_LOCATOR_ID}`,
      key: CHATKIT_SECRET
    });

    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(bodyParser.json());
    app.use(cors());

Next, add the route for responding to users who are logging in to the app. This is responsible for creating a corresponding Chatkit user if the user_id doesn’t already exist. In order to use Chatkit, a user has to be created first. We will then log in this user in the app later on:

    let rooms = []; // array for storing the rooms and its users

    app.post("/users", async (req, res) => {
      const { user_id, username } = req.body;
      try {
        let user = await chatkit.createUser({
          id: user_id,
          name: username
        });
        res.sendStatus(200);
      } catch (err) {
        if (err.error === "services/chatkit/user_already_exists") {
          console.log("user already exists!");
          res.sendStatus(201);
        } else {
          console.log("error occurred: ");
          let statusCode = err.error.status;
          if (statusCode >= 100 && statusCode < 600) {
            res.status(statusCode);
          } else {
            res.status(500);
          }
        }
      }
    });

Next is the route for creating a room. The app will make a request to this route when they get navigated to the Chat screen. It’s mainly responsible for creating a chat room. Since we’re only creating a one-to-one chat, only the first user who logs in to a non-existent room will trigger the creation of it. The second user, and any other instances after that won’t trigger the creation of the room since it has already been created. This route responds with an object containing the id and name of the room. The id will be used by the app to subscribe to the room so the user receives an update in realtime every time a new message is sent in the room:

    app.post("/rooms", async (req, res) => {
      const { user_id, room_name } = req.body;
      let room_data = rooms.find(room => {
        return room_name == room.name;
      });

      if (!room_data) {
        let room = await chatkit.createRoom({
          creatorId: user_id,
          name: room_name
        });

        rooms.push({
          id: room.id,
          name: room_name
        });

        res.send(room);
      } else {
        console.log('existing room');
        res.send(room_data);
      }
    });

Lastly, expose the server to port 5000:

    const PORT = 5000;
    app.listen(PORT, (err) => {
      if (err) {
        console.error(err);
      } else {
        console.log(`Running on ports ${PORT}`);
      }
    });

Don’t forget to update the .env file and add your Chatkit credentials. You can find these on the Credentials tab of your Chatkit app instance. Omit the v1:us1: from the instance locator as we’ve already prepended it when we used it in the server.js file:

    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID"
    CHATKIT_SECRET_KEY="YOUR CHATKIT SECRET KEY"

Add the app code

Next, we now proceed to add the code for the app. But first, update the .env file at the root of the project directory and add your Chatkit credentials. Just like the server’s .env file, also omit the v1:us1: from the instance locator:

    CHATKIT_INSTANCE_LOCATOR_ID="YOUR CHATKIT INSTANCE LOCATOR ID"
    CHATKIT_SECRET_KEY="YOUR CHATKIT SECRET KEY"

Login screen

The user is first greeted by the login screen when they open the app so let’s start with it. Start by importing the packages we need:

    // src/screens/Login.js
    import React, { Component } from 'react';
    import { View, Text, TextInput, TouchableOpacity } from 'react-native';
    import stringHash from 'string-hash'; // for getting the hash of the username
    import axios from 'axios'; // for making HTTP requests

    const CHAT_SERVER = "YOUR_NGROK_HTTPS_URL/users"; // don't forget to replace this later

Create the component:

    class LoginScreen extends Component {
      static navigationOptions = {
        title: "Login"
      };

      state = {
        username: "",
        friends_username: "", 
        is_loading: false
      };

      // next: add render()
    }

Render the UI:

    render() {
      return (
        <View style={styles.wrapper}>
          <View style={styles.container}>
            <View style={styles.main}>

              <View style={styles.fieldContainer}>
                <Text style={styles.label}>Enter your username</Text>
                <TextInput
                  style={styles.textInput}
                  onChangeText={username => this.setState({ username })}
                  value={this.state.username}
                />
              </View>

              <View style={styles.fieldContainer}>
                <Text style={styles.label}>Enter friend's username</Text>
                <TextInput
                  style={styles.textInput}
                  onChangeText={friends_username =>
                    this.setState({ friends_username })
                  }
                  value={this.state.friends_username}
                />
              </View>

              {!this.state.is_loading && (
                <TouchableOpacity onPress={this.enterChat}>
                  <View style={styles.button}>
                    <Text style={styles.buttonText}>Login</Text>
                  </View>
                </TouchableOpacity>
              )}

              {this.state.is_loading && (
                <Text style={styles.loadingText}>Loading...</Text>
              )}
            </View>
          </View>
        </View>
      );
    }

When the user clicks on the login button, the following function is executed. This makes a request to the /users route in order to create a Chatkit user instance for this user. After that, it navigates the user to the Chat screen:

    enterChat = async () => {
      const username = this.state.username;
      const friends_username = this.state.friends_username;
      const user_id = stringHash(username).toString();

      this.setState({
        is_loading: true
      });

      if (username && friends_username) {

        try {
          await axios.post(
            CHAT_SERVER,
            {
              user_id: user_id,
              username: username
            }
          );

          this.props.navigation.navigate("Chat", {
            user_id,
            username,
            friends_username
          });

        } catch (e) {
          console.log(`error logging in: ${e}`);
        }

        await this.setState({
          is_loading: false,
          username: "",
          friends_username: ""
        });
      }
    };

Chat screen

The Chat screen is where the main bulk of the app is. Start by importing the packages we need, as well as the custom components used for rendering the link preview (we’ll create them later):

    import React, { Component } from 'react';
    import { View, ActivityIndicator } from 'react-native';
    import { GiftedChat, Send, Message } from 'react-native-gifted-chat';
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client';
    import axios from 'axios';
    import Config from 'react-native-config';

    import ChatBubble from '../components/ChatBubble';
    import Preview from '../components/Preview';

Next, add your Chatkit credentials:

    const CHATKIT_INSTANCE_LOCATOR_ID = `v1:us1:${Config.CHATKIT_INSTANCE_LOCATOR_ID}`;
    const CHATKIT_SECRET_KEY = Config.CHATKIT_SECRET_KEY;
    const CHATKIT_TOKEN_PROVIDER_ENDPOINT = `https://us1.pusherplatform.io/services/chatkit_token_provider/v1/${Config.CHATKIT_INSTANCE_LOCATOR_ID}/token`;

    const CHAT_SERVER = "YOUR NGROK HTTPS URL/rooms"; // don't forget to replace this

Next, create the component and initialize the state values that we will be using. Then inside the constructor, get the nav params that were passed from the login screen earlier. The username and the friend’s username is combined to come up with the room name:

    class Chat extends Component {

      static navigationOptions = ({ navigation }) => {
        const { params } = navigation.state;

        return {
          headerTitle: `Chat with ${params.friends_username}`
        };
      };


      state = {
        messages: [], // array for storing the messages currently displayed in the UI
        is_initialized: false, // whether Chatkit has initialized
        is_loading: false, // whether the app is currently loading messages
        show_load_earlier: false // whether to show the button for loading earlier messages or not
      };


      constructor(props) {
        super(props);
        const { navigation } = this.props;
        const user_id = navigation.getParam("user_id");
        const username = navigation.getParam("username");
        const friends_username = navigation.getParam("friends_username");

        const members = [username, friends_username];
        members.sort();

        this.user_id = user_id;
        this.username = username;
        this.room_name = members.join("-");
      }  

      // next: add componentDidMount()
    }

Once the component is mounted, we initialize Chatkit’s chat manager and connect the current user via the id that was returned by the server when they logged in:


async componentDidMount() {

  const chatManager = new ChatManager({
    instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID,
    userId: this.user_id, // connect the current user
    tokenProvider: new TokenProvider({ url: CHATKIT_TOKEN_PROVIDER_ENDPOINT })
  });

  try {
    let currentUser = await chatManager.connect();
    this.currentUser = currentUser;

    // create the room if it's not already created
    const response = await axios.post(
      CHAT_SERVER,
      {
        user_id: this.user_id,
        room_name: this.room_name
      }
    );

    const room = response.data; // room details 

    this.room_id = room.id.toString();
    await this.currentUser.subscribeToRoom({
      roomId: this.room_id, // the ID of the room you're subscribing to (must be an integer)
      hooks: {
        onMessage: this.onReceive // execute the onReceive function every time a new message is received
      }
    });

    this.setState({
      is_initialized: true // hide the loading animation
    });

  } catch (err) {
    console.log("error with chat manager: ", err);
  }

}

    // next: add onReceive()

Note: In the code above, we’re using Chatkit’s test token provider. For production apps, be sure to spin up your own token provider.

Next, declare the function that will be executed every time a new message is received (this also includes messages sent by the current user). This function uses the getMessage function to extract the relevant data from the message object, then it uses GiftedChat’s append method to add the new message at the end of the messages array:

    onReceive = async (data) => {
      const { message } = await this.getMessage(data);

      await this.setState((previousState) => ({
        messages: GiftedChat.append(previousState.messages, message)
      }));

      // show button for loading older messages if it goes over the default messages count limit
      if (this.state.messages.length > 9) {
        this.setState({
          show_load_earlier: true
        });
      }
    };

Here’s the getMessage function:

    getMessage = async ({ id, senderId, text, createdAt }) => {

      const msg_data = {
        _id: id, // message ID
        text: text, // the actual text message
        createdAt: new Date(createdAt),
        user: {
          _id: senderId,
          name: senderId,
          avatar: "https://cdn.pixabay.com/photo/2016/08/08/09/17/avatar-1577909_960_720.png" // the avatar displayed for users other than the current user
        }
      };

      return {
        message: msg_data
      };
    };

Here’s the function for prepending the older messages at the start of the messages array. This uses Chatkit’s fetchMessages method to fetch older messages. The important thing to remember here is the initialId and the direction. The initialId is the ID of the message where the fetching would start, while the direction is the direction of the fetch (more recent to less recent). So if the initialId is 10, it would return [9, 8, 7, 6, 5...]. We then append each of these to a new array (earlier_messages) and prepend it to the current messages array. We’re using the asyncForEach function instead of a normal forEach or for loop because we’re using the async..await pattern to fetch each individual message. Normal loops wouldn’t wait for this, that’s why we’re using a custom asyncForEach function:

    loadEarlierMessages = async () => {
      this.setState({
        is_loading: true // show the loading animation
      });

      const earliest_message_id = Math.min(
        ...this.state.messages.map(m => parseInt(m._id))
      );

      try {
        let messages = await this.currentUser.fetchMessages({
          roomId: this.room_id,
          initialId: earliest_message_id,
          direction: "older",
          limit: 10
        });

        if (!messages.length) {
          this.setState({
            show_load_earlier: false
          });
        }

        let earlier_messages = [];
        await this.asyncForEach(messages, async (msg) => {
          let { message } = await this.getMessage(msg);
          earlier_messages.push(message);
        });

        await this.setState(previousState => ({
          messages: previousState.messages.concat(earlier_messages)
        }));
      } catch (err) {
        console.log("error occured while trying to load older messages", err);
      }

      await this.setState({
        is_loading: false // hide the loading animation
      });
    };

Here’s the asyncForEach function. It uses callbacks in order to manage the async..await pattern:

    asyncForEach = async (array, callback) => {
      for (let index = 0; index < array.length; index++) {
        await callback(array[index], index, array);
      }
    };

Next, render the chat UI. The GiftedChat component handles the bulk of the work here, so all we have to do is pass the data and the functions that it’s expecting:

    render() {
      const { is_loading, is_initialized, messages, show_load_earlier } = this.state;

      return (
        <View style={styles.container}>
          {(is_loading || !is_initialized) && (
            <ActivityIndicator
              size="small"
              color="#0064e1"
              style={styles.loader}
            />
          )}

          {is_initialized && (
            <GiftedChat
              messages={messages}
              onSend={messages => this.onSend(messages)}
              user={{
                _id: this.user_id
              }}
              loadEarlier={show_load_earlier}
              onLoadEarlier={this.loadEarlierMessages}
              renderSend={this.renderSend}
              renderMessage={this.renderMessage}
            />
          )}
        </View>
      );
    }

    // add onSend()

The onSend function is executed when the send button is clicked by the user. This uses Chatkit’s sendMessage method to send a message to a specific room. This message can only contain plain text. URL’s are also plain text so we can actually extract URL’s from the text in order to get the thumbnail image preview. We will get to that later, for now, add the following code:

    onSend([message]) {
      const msg = {
        text: message.text,
        roomId: this.room_id
      };

      this.setState({
        is_sending: true // show loading animation instead of the send button so they can't click it while the message is sending
      });

      this.currentUser.sendMessage(msg).then(() => {
        this.setState({
          is_sending: false // show the send button
        });
      });
    }

renderSend is used for customizing the button for sending messages. The default send button component in React Native Gifted Chat is great, but we want to show a loading animation instead of it if the message is currently being sent:

    renderSend = props => {
      if (this.state.is_sending) {
        return (
          <ActivityIndicator
            size="small"
            color="#0064e1"
            style={[styles.loader, styles.sendLoader]}
          />
        );
      }

      return <Send {...props} />;
    };

Next is the renderMessage function. This is used for customizing the chat bubble which contains the message. So this is actually where we get to implement the main topic of this tutorial. React Native Gifted Chat automatically passes the message object to this function. The text is available at currentMessage.text. We extract the URL’s from this text and pass in a custom renderBubble function if a URL was extracted. Otherwise, we simply pass null which is actually the default value for the renderBubble function. Passing null means that React Native Gifted Chat will use its default function for rendering the chat bubble:

    renderMessage = (msg) => {
      const url_matches = msg.currentMessage.text.match(/\bhttps?:\/\/\S+/gi);
      const renderBubble = (url_matches) ? this.renderPreview.bind(this, url_matches) : null;

      const modified_msg = {
        ...msg,
        renderBubble
      }

      return <Message {...modified_msg} />
    }

Here’s the code for the renderPreview function. This will only use the first URL that was extracted. It then uses the ChatBubble and Preview component to render a custom chat bubble. We will also render the title of the web page so we determine the text color based on the position of the chat bubble. Messages sent by the current user are always on the right and it has a background color of blue, that’s why we’re using white for the text color. On the other hand, messages sent by the other user have a light gray background so we use black for the text color:

    renderPreview = (url_matches, bubbleProps) => {

      const uri = url_matches[0]; // extract only the first URL
      const text_color = (bubbleProps.position == 'right') ? '#FFF' : '#000';
      const modified_bubbleProps = {
        ...bubbleProps,
        uri
      };
      return (
        <ChatBubble {...modified_bubbleProps}>
          <Preview uri={uri} text_color={text_color} />
        </ChatBubble>
      );
    }

ChatBubble component

Here’s the code for the ChatBubble component. It is based off of React Native Gifted Chat’s Bubble component. We’re using it as a wrapper for the Preview component. But aside from the preview, we also need to render part of the original text, as well as the time in which the message was sent. I said “part” because we no longer need to render the URL since we’re rendering the preview. That’s what we’re doing in the code below. Then we use Gifted Chat’s MessageText and Time component to render the text and the time:

    // src/components/ChatBubble.js
    import React from 'react';
    import { View } from 'react-native';
    import { MessageText, Time } from 'react-native-gifted-chat';

    const ChatBubble = (props) => {
      const { position, children, currentMessage, uri } = props;
      const text = currentMessage.text.replace(uri, '');
      const updated_props = { ...props, currentMessage: { text } };
      return (
        <View style={styles[position].container}>
          <View style={styles[position].wrapper}>
            <MessageText {...updated_props} />
            {children}
            <Time {...updated_props} />
          </View>
        </View>
      );
    }

    // next: add styles

Here are the styles:

    const styles = {
      left: {
        container: {
          flex: 1,
          alignItems: 'flex-start',
        },
        wrapper: {
          borderRadius: 15,
          backgroundColor: '#f0f0f0',
          marginRight: 60,
          minHeight: 20,
          justifyContent: 'flex-end',
        }
      },
      right: {
        container: {
          flex: 1,
          alignItems: 'flex-end',
        },
        wrapper: {
          borderRadius: 15,
          backgroundColor: '#0084ff',
          marginLeft: 60,
          minHeight: 20,
          justifyContent: 'flex-end',
        }
      }
    }

    export default ChatBubble;

Preview component

Time to finally work on the Preview component. This is the component that actually renders the thumbnail image preview for a specific URL. Start by importing the packages we need:

    // src/components/Preview.js
    import React, { Component } from 'react';
    import { TouchableOpacity, View, ImageBackground, Text, Linking } from 'react-native';
    import LinkPreview from 'react-native-link-preview';
    import Icon from 'react-native-vector-icons/FontAwesome';

Next, create the component and initialize the state values that we will be using:

    class Preview extends Component {

      state = {
        title: null, // title of the web page
        image_url: null, // URL of the thumbnail image preview
        link: null, // URL to open when the chat bubble is clicked
        link_type: null, // type of content displayed by the webpage (e.g. article, video)
        has_preview: false // whether to display the preview or not
      }

      // next: add componentDidMount()
    }

Once the component is mounted, we use the React Native Link Preview library to extract the title, thumbnail image URL, and the media type displayed by the web page. This will make a request to the URL to fetch the data we want. Which means that it will actually take some time depending on your internet connection (it will probably take less time than what it would take to load the page on your browser because it doesn’t actually render anything, it’s just extracting Open Graph data from the page). Once the data is extracted, we update the state with the data:

    async componentDidMount() {
      const { uri } = this.props;
      try {
        const { title, images, url, mediaType } = await LinkPreview.getPreview(uri);

        await this.setState({
          title: title,
          image_url: images[0],
          link: url,
          link_type: mediaType,
          has_preview: true // display the preview
        });

      } catch (e) {
        console.log('error occurred: ', e);
      }
    }

Next, render the UI. We’re still wrapping everything in a View component instead of TouchableOpacity because not all URL’s can have a preview. If there’s a network error, or there’s no Open Graph data available, we want to render the full URL. But it won’t actually be clickable because we want to avoid security risks. 42.8% of websites already use Open Graph, so it’s safe to assume that websites who do not use it are either outdated or insecure. In the code below, we also determine whether the content type is a video or not. If it is, then we show a play icon to suggest that the content can be played:

    render() {
      const { image_url, title, link, link_type, has_preview } = this.state;
      const { text_color } = this.props;
      const is_video = (link_type && link_type.indexOf('video') !== -1) ? true : false;

      return (
        <View style={styles.container}>
          {
            has_preview &&
            <TouchableOpacity onPress={this.openLink}>
              <ImageBackground
                style={styles.preview}
                source={{uri: image_url}}
                imageStyle={{resizeMode: 'contain'}}
              >
                {
                  is_video &&
                  <Icon name="play-circle" style={styles.play} size={50} color="#FFF" />
                }
              </ImageBackground>
              <View>
                <Text style={[styles.title, { color: text_color }]}>{title}</Text>
              </View>
            </TouchableOpacity>
          }

          {
            !this.state.has_preview &&
            <Text style={styles.link}>{link}</Text>
          }
        </View>
      );
    }

    // next: add openLink()

Here’s the openLink function. This uses React Native’s built-in Linking library to automatically determine the app to be used for opening the link. If the app is available on the user’s device, it will automatically launch it to display the content:

    openLink = async () => {
      const { link } = this.state;
      try {
        const supported = await Linking.canOpenURL(link); // determine if the device has the app for opening the link
        if (supported) {
          return Linking.openURL(link); // launch the app in question
        }
      } catch(e) {
        console.log('error occurred: ', e);
      }
    }

Lastly, add the styles:

    const styles = {
      container: {
        width: 130,
        margin: 10
      },
      preview: {
        width: 130,
        height: 90,
        alignItems: 'center',
        justifyContent: 'center'
      },
      play: {
        opacity: 0.75
      },
      title: {
        fontSize: 10
      },
      link: {
        color: '#0178ff'
      }
    }

    export default Preview;

Running the app

Now you’re ready to run the app. Start by running the server:

    cd server
    yarn start

Next, make ngrok expose the server. This will output the URL in which it’s being served:

    ./ngrok http 5000

Update the src/screens/Login.js and src/screens/Chat.js file with the ngrok HTTPS URL that you see on your terminal.

Finally, you can run the app:

    react-native run-android
    react-native run-ios

Once the app is running, enter a random value for both text fields but make sure to switch the same values on another device (username on device A becomes the friend’s username on device B, friend’s username on device A becomes the username on device B). If you don’t have another device to test on, you can simply use the Chatkit console to send messages:

Note: The Chatkit console doesn’t auto-refresh, so once the room is created by the server, you have to refresh the page for the newly created room to show up. From there, you can send a message to the room.

Conclusion

In this tutorial, you learned how to add thumbnail image previews for URL’s shared in a React Native chat app. You also learned that sharing links is a risk factor in your app. That’s why we prevented users from visiting links which don’t use Open Graph. Although its usage doesn’t ensure that the websites are secure, it’s important to have security measures in place.

For production apps, I recommend that you pipe the requests to your own server first instead of making a direct request from the app itself. This way, you should be able to intervene if the website turned out to be insecure.

You can find the code used in this tutorial on its GitHub repo.

Clone the project repository
  • Chat
  • JavaScript
  • Node.js
  • React Native
  • Android
  • iOS
  • Chatkit

Products

  • Channels
  • Beams
  • Chatkit

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.