TutorialsChat

Adding URL Previews to Chat

Jul 3, 2023

URL Preview

Over the years, chat applications have been frequently used by people in private and group channels to communicate through sending and receiving texts, media assets, and links to external resources. When sharing a link in a chat conversation, how would the receiver know where the unknown link redirects to before opening it? Through a URL Preview.

 

URL Previews do not happen automatically, hence, you need to implement it within your application. This tutorial will first explain the URL preview feature and how it improves the user experience of a collaborative application. The tutorial will also provide you with a hands-on demonstration of how to generate URL previews through the /embed endpoint within the Weavy Web API

What are URL Previews?

A URL Preview retrieves rich data points such as an image, page title, and description from the metadata of an external website and renders them in a digestible format for users when a link is added to a message.

Link previews are important for increasing the Click-Through-Rate (CTR) within an application and it also improves the application’s User Experience (UX) as users get a quick glimpse of the link’s content before opening it. E-commerce websites are leveraging link previews to boost product sales as they display key data points like a product’s price and quantity alongside a Call To Action link within a generated preview for buyers to make a quick decision. 

When implementing a URL Preview, developers have two main options: either retrieve the target website’s HTML document and parse it manually to extract the preview data or use third-party services to extract the data. A preview can also be generated and stored either when the sender inserts the link or when the receiver gets a message containing a URL.  


API services and libraries for URL Previews

The following are three API Services used for URL previews; 

  1. Open Graph Protocol built by Meta to provide developers with the ability to customize what details of their web pages are displayed in a preview. Developers add the graph tags to the head HTML element of the web page to specify the custom data to be rendered whenever a link from the website is shared within a social network. 

  2.  oEmbed which uses the Open Format to provide a standard way of embedding preview content from other websites. Similar to the Open Graph Protocol, developers have to add the support for oEmbed to their website then other applications can retrieve that data via GET HTTP requests. 

    Adding support for oEmbed is possible through the iframely library for JavaScript applications or the OEmbed.Net for .NET applications. 

  3.  LinkPreview which is a third-party external API service for generating preview data in JSON format from a website. LinkPreview relies on a website either having an open graph, meta tags, and other content clues for it to generate preview data.

    Developers need to register for any of the usage tiers, then obtain the API key to access the service via RESTful API requests to the endpoints. 

In addition to API services, there are libraries that abstract the url preview extraction process. All you need to do is to provide a target URL and they will return the preview data for it. Examples of these libraries include link-preview-js for JavaScript and UrlPreview for .NET applications. 


Challenges of URL Previews 

While a URL preview feature is invaluable, there are several challenges and edge-case scenarios that developers are bound to face during its implementation. Let’s consider three of these challenges: 


Data privacy concerns

Despite being a relatively minor feature, security researchers have highlighted ways the logic used for implementing a URL preview could unintentionally violate a user’s data privacy. 

Developers building chat apps promising end-to-end encrypted communication will be violating their user’s privacy by using third-party API services to generate URL previews as the shared links could lead to sensitive documents that should be kept private.  

When previews are generated on the message receiver’s end, there is the possibility of a  malicious user reverse-engineering the feature to extract another user’s browser details such as the IP address because the unknown link is automatically opened by the app to generate a preview, even if it contains malicious code.     


Cross-Origin Resource Sharing (CORS) limitation: 

The CORS mechanism controls access to the resources within a website such as fonts from a different domain as a security measure. Websites without a proper CORS configuration will make it difficult to generate a URL preview as the page metadata will be inaccessible due to the CORS protection.  

Demo: implementing URL Previews in an app 

Prerequisites 

The next sections within this tutorial are hands-on to guide you through the process of building a React.js application. To follow along, you will need to have; 

  • Basic knowledge of JavaScript to follow along with the code demonstrations for building the chat application. 
  • A Weavy Pro account to access the Weavy Chat API. If you do not have an account, Weavy provides a Pro Trial tier to explore the Chat API free for 30 days. 


Preparing Weavy resources 

Open your Weavy web dashboard to either create an environment or use an existing one for the demo application. 


Next, you need to either create or obtain an existing API Key to authenticate the API requests you will make to create resources within your Weavy environment. 

Click the Generate API Key button to begin the process of creating an API Key.


Provide a Name and Expiration value within the launched Generate API Key modal, then click the Generate Key button to create the key.


After completing the guide, you will find your credentials on the Weavy environment page as shown in the image below;



Take note of the URL and Key values as you will make use of them over the next steps. 


Creating a Weavy Chat App 

Launch the terminal or command prompt application on your computer as you will execute various cURL commands to create resources within your Weavy environment.

Execute the command below to create a Chat app type within your Weavy account. Make sure to replace the WEAVY_DOMAIN and WEAVY_KEY placeholders with the corresponding credentials from the Weavy dashboard. Replace the PROJECT_NAME placeholder with a preferred name for the Weavy application.    

curl {WEAVY_DOMAIN}/api/apps \
-H "Authorization: Bearer {WEAVY_API_KEY}" \
-H "Content-Type: application/json" -d "{ 'uid': 'PROJECT_NAME', 'type': 'chat' }"




Take note of the id property within the JSON response from creating the Chat App. You will need to provide it as a URL parameter when interacting with the Weavy Web API. 

At this point, you have a functional Weavy account with a Chat app and the authentication key. Let’s proceed to use these resources while building the web application. 


Bootstrapping a React.js application

Within this section, you will build a demo React.js application to create, retrieve and render the data from the Chat app you created in the previous section. As a demo application, you will only focus on implementing the post and embeds retrieval, then you will use TailwindCSS to rapidly style the application using inline classes.

To begin, execute the command below to create a React.js application named weavy-react-chat-app through the Vite CLI:

npm create vite@latest weavy-react-chat-app --template react


Open the generated weavy-react-chat-app project in your preferred code editor. You will have to edit and create new files within the next steps


Initializing TailwindCSS

TailwindCSS is a modern utility-first CSS framework that provides developers with the ability to rapidly style elements through the use of inline classes.

Execute the command below to install the three (3) libraries you need to enable TailwindCSS within the application. 

npm install -D tailwindcss postcss autoprefixer react-icons 


Execute the next command below to generate a base TailwindCSS configuration within the project.

npx tailwindcss init -p


Open the generated tailwind.config.js file and replace the content object with the code below to specify code directories for TailwindCSS.

curl {WEAVY_DOMAIN}/api/apps \
-H "Authorization: Bearer {WEAVY_API_KEY}" \
-H "Content-Type: application/json" -d "{ 'uid': 'PROJECT_NAME', 'type': 'chat' }"


Replace the entire boilerplate code within the src/index.css file with the code below to add the TailwindCSS directives and complete the setup.

# weavy-react-chat-app/src/index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

 

Creating a Weavy User and Token

Execute the cURL command below to create a User within your Weavy environment. The cURL command contains an object payload to specify the user’s uid and name values respectively. 

curl {WEAVY_DOMAIN}/api/users \
-H "Authorization: Bearer {WEAVY_API_KEY}" \
-H "Content-Type: application/json" \
-d "{ 'uid': 'chat_app_user-01', 'name': 'John Doe'}"

 



Next, you will add the created user to the Weavy application. This step will ensure that the user can retrieve the contents of the chat application.

Replace the placeholders with their corresponding values and execute the cURL command below to make a PUT request to the /api/apps/{id}/members/{userid} endpoint to add the user to the Weavy application. 

curl -X PUT {WEAVY_DOMAIN}/api/apps/{api_id}/members/{user_id} \
-H "Authorization: Bearer {WEAVY_API_KEY}"


Next, you will generate a token associated with the user you created. You will need the token to authenticate the API request from the React app to the Weavy Web API.

Execute the command to execute a POST request to generate an access token for the user you have created. Ensure to replace the placeholders within the POST request as it expects to use the exact uid value specified when the user was created. 

curl -X POST {WEAVY_DOMAIN}/api/users/{user_uid}/tokens \
-H "Authorization: Bearer {WEAVY_API_KEY}"


The image shows the response from the POST request containing an access_token property for the token and an expires_in value for the token duration. In a production-ready application, generating a user token should be a part of your authentication flow and the returned value should be stored for subsequent requests in the application.


It is strongly recommended not to use your API Key when interacting with the Weavy API from a front-end client. Instead, use the access_token value generated by a user.

With the user token retrieved, let’s proceed to securely store the credentials needed in a .env file.

Create a .env file at the root directory of the weavy-react-chat-app project to securely store your Weavy resource credentials separately from the application code.

Replace the placeholders and add your Weavy Domain, User Token, and App ID values to the .env file using the format below: 

#  weavy-react-chat-app/.env
VITE_WEAVY_DOMAIN=WEAVY_DOMAIN_URL
VITE_WEAVY_ACCESS_TOKEN=USER_TOKEN
VITE_APP_ID=CHAT_APP_ID

 

Creating a Weavy API client 

Within the weavy-react-chat-app/src directory, create an api.js file to store a reusable API client for interacting with the Weavy Web API. 

Add the JavaScript code block within the code block below into the api.js file:

//  weavy-react-chat-app/src/api.js
const DOMAIN = import.meta.env.VITE_WEAVY_DOMAIN;
const KEY = import.meta.env.VITE_WEAVY_ACCESS_TOKEN;
 
export const ApiClient = async ({ endpoint, method, body }) => {
  if (!DOMAIN || !KEY) {
    console.error(`Weavy Environment Variables Missing!`);
    return;
  }
 
  try {
    const request = await fetch(`${DOMAIN}/api/${endpoint}`, {
      method: method || "GET",
      headers: {
        Authorization: `Bearer ${KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
 
    return await request.json();
  } catch (e) {
    console.log(`Error with ${endpoint} endpoint:`, e);
  }
};


The asynchronous ApiClient function above uses the browser fetch API to make HTTP requests to your Weavy environment to create or retrieve Chat and embeds. The HTTP requests are authenticated by placing your Weavy API Key within the .env file in the request header.

With the ApiClient function, you will not need to repeat the fetch logic within the React components. All you will need to do is to pass in the endpoint, method, and body parameters to interact with different endpoints within the Weavy Web API.     

Building the React.js components

Open the existing App.jsx file within the weavy-react-chat-app/src directory, You will modify the existing code to fetch and display all items in your Weavy Chat app. The App component will also display a form input element for users to write and submit a new post.

To understand how the App component functions, you will gradually piece it together across multiple steps. 

First, add the content of the code block below into the App.jsx file. Although the code is missing a return statement, it uses the ApiClient() function in a useEffect() hook to fetch Chat and store the response in the component's local state. 

// weavy-react-chat-app/src/App.jsx

import { useEffect, useState } from "react";
import { BsSend } from "react-icons/bs";
import { ApiClient } from "./api";
import PostCard from "./PostCard";
 
const APP_ID = import.meta.env.VITE_APP_ID;
const extractUrlFromText = (text) => text.match(/(https?:\/\/[^ ]*)/);
 
function App() {
  const [chat, setAvailableChats] = useState(null);
  const [message, setMessage] = useState("");
 
  useEffect(() => {
    (async () => {
      const chatData = await ApiClient({ endpoint: `apps/${APP_ID}/messages` });
 
      setAvailableChats(chatData?.data);
    })();
  }, []);
 
  const handleSubmit = async () => {
    const extractedUrl = extractUrlFromText(message);
    let embedId = null;
 
    if (extractedUrl) {
      const createEmbedData = await ApiClient({
        endpoint: `/embeds`,
        method: "POST",
        body: {
          url: extractedUrl[0],
        },
      });
 
      embedId = createEmbedData?.id;
    }
 
    const createChatData = await ApiClient({
      endpoint: `apps/${APP_ID}/messages`,
      method: "POST",
      body: {
        text: message,
        embed_id: embedId,
      },
    });
 
    setAvailableChats(Array.isArray(chat) ? [...chat, createChatData] : [createChatData]);
    setMessage("");
  };

	// ADD RETURN STATEMENT HERE
  );
}

export default App;


Of interest in the code above is the handleSubmit() function that will be executed at the click of a button. Through the steps outlined below, the handleSubmit() function handles submitting a post and generating a URL preview using the Weavy /embed endpoint. 

  • First, the extractUrlText() helper function uses a regular expression to extract all the URLs from the input field text and store them in the extractedUrl variable. 
  • If there was an extracted URL, the ApiClient() will be used to make a POST HTTP request to the /embed endpoint to create an embed and the id property returned in the embed response will be stored in the embedId variable.
    Only the first URL extracted from the input field is used to create an embed because a Post entity can only have a single embed attached. 
  • Finally, a POST request will be made to the /embed endpoint with the embed ID and input text in its request body to create a post having an embed within the Weavy app.

Add the next block of code containing a return statement into the App component right within the section presently labeled "ADD RETURN STATEMENT HERE"

// weavy-react-chat-app/src/App.jsx
return (
   <div className="flex w-full bg-gray-100 h-full justify-center">
     <div className="max-w-[650px] shadow m-auto h-full bg-[white] w-full ">
       <div className="bg-gray-200">
         <h1 className="text-center py-4 text-xl font-semibold">Weavy URL Preview Chat</h1>
       </div>

       <div className="px-6">
         <div>
           {chat && (
             <ul>
               {chat.map(({ text, id, embed, created_at, created_by }) => (
                 <li className="my-8" key={id}>
                   <PostCard {...{ text, embed, created_at, created_by }} />
                 </li>))}
             </ul>
           )}
         </div>

         <br />
         <form className="border h-[60px] w-full flex">
           <div className="w-full bg-gray-100 flex items-center">
             <input
               value={message}                onChange={(e) => setMessage(e.target.value)}
               placeholder="Enter A Message"
               className="p-4 w-full"
             />
           </div>

           <div
             onClick={() => handleSubmit()}
             className="flex mx-4 text-3xl items-center cursor-pointer"
           >
             <BsSend />
          </div>         </form>       </div>     </div>   </div> );

The code above renders the application layout with a header, a list of retrieved Chat, and an input form with a submit button.

The App component above is using a PostCard component which does not exist yet within the project. Let’s create it!

Create a PostCard.jsx file within the weavy-react-chat-app/src/ directory.

Add the content of the code block below into the PostCard.jsx file to build the PostCard component. The PostCard component will display each Post within your Weavy app using data passed in as props from the parent App component.

 

import React from "react";
import { AiOutlineLink } from "react-icons/ai";
 
const truncateText = (text, length) =>
  text.split(" ").slice(0, length).join(" ");
 
const PostCard = ({ text, embed, created_at, created_by }) => (
  <div className="rounded overflow-hidden shadow-lg mt-4 bg-white ">
    {embed && (
      <div className="flex row bg-gray-100">
        {embed?.image && (
          <img
            className="w-[150px] h-[100px] object-cover"
            src={embed.thumbnail_url}
            alt={embed.title}
          />
        )}
 
        <div className="ml-4 flex items-center py-1">
          <div>
            <a target={"_blank"} rel="no-opener" href={embed.original_url}>
              <p className="font-semibold"> {embed.title} </p>
            </a>
 
            <p> {truncateText(embed.description, 10)}... </p>
          </div>
        </div>
 
        <div className="ml-2 mr-2 mt-1 text-2xl">
          <AiOutlineLink />
        </div>
      </div>
    )}
 
    <div className="px-2 py-4">
      <p className="text-xl mb-4  font-semibold ">{text}</p>
 
      <p className="text-gray-500 mb-2">
        Posted By <b> {created_by.display_name} </b> On{" "}
        <b>{new Date(created_at).toLocaleDateString()} </b>
      </p>
    </div>
  </div>
);
 
export default PostCard;


The PostCard component uses conditional statements to handle cases where a Post does not contain a preview or a preview does not have a thumbnail. The preview card also uses an anchor element with the extracted URL attached to the text to enable users to open the link in a new tab.

The demo application uses the description property within the embed object to display the link preview description. If you want to display the rich objects within a preview, you may consider using the iframe property value which contains parsed HTML.  

At this point, you have finished building the demo application for trying out URL previews with Weavy. Let’s proceed to start the Vite server and try out the React application. 

Testing Weavy link previews 

Execute the command below to start and serve the React application on your localhost; 

npm run dev 

 

  

Open your web browser and navigate to the running application at http://127.0.0.1:5173/.

Type in a post with a link, then click the icon to submit the post. Try creating a post again without a link to have a combination of Chat with and without link previews as shown in the image below.

The image below shows three Chat. Two have link previews generated from the URL within the post text and only one has a thumbnail from the link preview. 

Weavy

Share this post