Time Sink: Visualize Your Browsing History Unlike Ever Before

Time Sink: Visualize Your Browsing History Unlike Ever Before

N.B: The Time Sink Chrome extension is currently in the pending review state by the chrome team. Please visit the project's github repo for instruction on how to download the chrome extension locally. Alternatively I have also added a quick screen cast walking through the application:

Time Sink Demo Screen Cast

Link to Time Sink Application on Vercel || GitHub Link

The first step to being more productive is to understand how you are currently using your time. Time Sink allows you to visualize your browsing history using d3.js to paint a clearer picture of how you spend your time on the internet. Because browsing details may contain sensitive information, no data is transmitted to external servers at any time.

Tech Stack

As part of the Vercel-Hashnode hackathon, I wanted to dig a little deeper and challenge myself to learn a variety of new skills. This was my first exposure to many of the following tools making the experience all the more fulfilling!

  • Chrome Extension (Javascript)
  • Figma - Design tool
  • Next.js - front-end framework
  • Bulma.io - css framework
  • d3.js - data visualization
  • Vercel - hosting

The Plan

Conceptually, the idea was pretty simple: access the chrome browsing history and format that data to be consumed by d3. How hard could it be? Maintaining privacy was one of my key priorities. I needed to figure out how to access this data locally and consume it using an app hosted on Vercel without making any network calls.

I quickly realized that the only way to access one's chrome browsing history programmatically is via a chrome extension. The extension would query all the local browsing history and provide it as a local javascript variable that would then be picked up by the Time Sink web application to process and visualize.

The Build Process

Part 1: Building the Chrome extension

In order to build a chrome extension, I had to familiarize myself with its architecture. An easy way to understand the architecture is to think of there being three isolated domains that communicate with each other via messages in order to perform tasks in different domains within the browser.

Background Script

The background script acts as a controller for the chrome extension. As the name suggests, this script is run in the background when a new instance of the browser is opened. Since chrome guarantees this script will run on launch, global listeners are set up in this file. Given the appropriate permissions, the background script is the only place that will have access to chrome's internal APIs as well.

The Popup script is pretty straight forward. It is the script that runs when you interact with the chrome extension icon. This script is usually embedded in a popup.html allowing you to customize the popup window that opens when clicking on the extension icon.

Content script

The content script is embedded in the actual web page that the user is currently on and has access to the page document allowing the developer to interact with the content on the page. This script still is isolated from the browser and does not have access to any variables defined locally in the browser console. Because this script can be memory intensive the chrome extension configuration allows you to be able to control when the script is run (continue reading to see how this is defined in the manifest.json file)

Now that you have a better understanding of the architecture from a high level, let's understand how the Time-Sink extension was built.

Upon installing the extension or loading a new instance of the browser, the background script sets up the following listeners:

  1. It subscribes to the onClick event on the chrome extension icon. As the name suggests, this event would be triggered when someone clicks the extension icon. When triggered, the listener is configured to open the time-sink application in a new tab.

  2. The background script also sets up a custom listener, this time listening for a message with the identifier: getData. This would trigger the script to pull the user's browsing data via the chrome API. This step was a little more involved because it dealt with callbacks that would run for each individual history item in the chrome API. In order to work around this, I put a promise-loop to make sure that all the data was collected before responding back to the message. It is important to note that this code will only be run when the background script receives the getData message.

Because I only need to collect browsing history while we are on the time-sink site, I limit the content script to run only when the URL matches the application URL. The Content script is a relatively simple module. The script only has two functions:

  1. Upon loading, it sends a message with a getData identifier. (This is the message that the background script has been waiting for). The script then waits for a response that contains the visitHistory key, which it then appends to the current local javaScript environment in the tab.

As mentioned briefly above, in order to access the history API, the chrome extension needs the appropriate permissions. Such permissions are declared in the manifest.js file as shown below.

{
  "manifest_version": 2,
  "version": "1.0",
  "name": "Time Sink",
  "description": "Visualize your chrome activity to determine your surfing habits.",
  "permissions": [
    "history",
    "tabs"
  ],
  "browser_action": {
    "default_icon": "timeSink.png"
  },
  "icons": {
    "16": "timeSink.png",
    "48": "timeSink.png",
    "128": "timeSink.png"
  },
  "background": {
    "scripts": [
      "backgroundScript.js"
    ]
  },
  "content_scripts": [
    {
      "matches": [
        "https://time-sink.vercel.app/*"
      ],
      "js": [
        "contentScript.js"
      ]
    }
  ]
}

That's it! Simple!

Part 2: Building the Front-end

A huge shout out to the team at Vercel! NextJs just works. I have not experienced such a seamless development cycle that contains literally no configuration.

Because the Time-Sink application is dependent on the data provided by the chrome extension (which takes ~1.5 seconds to load), I used a plugin in Figma to create a custom loading animation:

Time Sink Loading Gif

This animation can be seen when the application is waiting for the visitHistory object to be defined in the console. I liked how this came out so much that I even created a dedicated page for the logo! It's not linked from any other page, so if you want to see it in action for more than ~1 second, you'll need to dig through the code! (Feel free to put the link to the page in the comments github ) I used Bulma to speed up the styling process so that the bulk of my time is spent where I knew I'll need it most - Learning d3!

Learning D3 is where I spent the majority of my time during this process. The learning curve is pretty steep but the basics are pretty simple and the rest comes with familiarity with its extensive API over time. At a really high level, d3 allows you to programmatically interact with the DOM. In most instances, d3 is used to build visual representations of data using the svg html tag. Here are a few of the charts I built for Time-Sink.

wordCloud.png Word cloud of words used in your search queries on popular search engines

sitesVisited.png A Bubble Chart to represent what domains you visit most represented by the size of the bubble


In conclusion, this has been a great experience. I learned many new things along the way! A huge thank you to #VercelHashnode again for the opportunity!

Don't forget to download the extension and share it on social media! I look forward to continuing to work on this!

Link to Time Sink Application on Vercel || GitHub Link