DEV Community

Raghav
Raghav

Posted on

Building a Chrome Extension to Boost Your Coding Skills: A Journey with Plasmo, Puppeteer, and LeetCode

In today's tech-driven world, LeetCode has become a go-to platform for sharpening problem-solving skills. Whether you're preparing for interviews or aiming to improve your daily problem-solving habits, staying on track can be challenging. So, what if your browser could help? In this post, I'll walk you through how I built a Chrome extension that locks you into solving LeetCode problems before letting you browse freely.

The extension automatically redirects you to LeetCode problems based on Striver’s DSA sheets, tracks your daily progress, and resets your goals each day—all while being fully customizable through a simple popup interface.

Let’s break it down.

Key Features of the Extension:

  1. Problem Redirection: Automatically redirects to a LeetCode problem when a new tab is opened until the daily goal is met.
  2. Daily Goal Tracker: Tracks how many problems you've solved and resets progress each day.
  3. Custom Problem Sheets: Uses problems from DSA sheets, dynamically scraped from Striver’s website.
  4. Customizable Settings: Users can configure the daily goal, the sheet they’re working on, and toggle the extension on/off via a popup interface.

Tech Stack

  1. Plasmo Framework: A modern framework to build powerful Chrome extensions.
  2. Puppeteer: Used to scrape LeetCode problems from Striver’s DSA sheet to keep the problem sets fresh.
  3. LeetCode API Monitoring: The extension listens for problem submissions and only stops redirection once the user solves their daily quota.

Now, let’s dive into how it all works.


Step 1: Scraping Striver’s DSA Sheets Using Puppeteer

To make sure the problem sets are up-to-date, I decided to scrape Striver’s DSA sheets directly from his website using Puppeteer, a headless browser automation tool. Here's how:

const puppeteer = require('puppeteer');

async function scrapeProblems(sheetUrl) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(sheetUrl);

  const problems = await page.evaluate(() => {
    return Array.from(document.querySelectorAll('table tbody tr')).map(row => {
      const cells = row.querySelectorAll('td');
      return {
        problemName: cells[0].innerText,
        lcLink: cells[1].querySelector('a').href
      };
    });
  });

  await browser.close();
  return problems;
}

Enter fullscreen mode Exit fullscreen mode

This script navigates to Striver's DSA sheets and extracts problem names and LeetCode links, saving them as JSON files for use within the extension.

Why Puppeteer?

Automating the process of scraping Striver's problem sheets ensures that users get the latest problems without needing to manually update anything. Puppeteer does the heavy lifting, allowing me to update the problem sets as frequently as necessary.


Step 2: Storing and Managing Problem Sets

Once the data is scraped, it’s imported into the extension as JSON files:

import * as striver191Probs from "leetcode-problems/striver191Probs.json";
import * as striverDSAbegineer from "leetcode-problems/striverDSAbegineer.json";
import * as striverExpertProbs from "leetcode-problems/striverExpertProbs.json";

Enter fullscreen mode Exit fullscreen mode

These files are then organized into "sheets," allowing users to switch between different difficulty levels:

const sheets = {
  sheet1: {
    problems: striver191Probs,
    markerKey: "striver191ProbsMarker"
  },
  sheet2: {
    problems: striverDSAbegineer,
    markerKey: "striverDSAbegineerMarker"
  },
  sheet3: {
    problems: striverExpertProbs,
    markerKey: "striverExpertProbsMarker"
  }
};

Enter fullscreen mode Exit fullscreen mode

Step 3: Tracking Daily Progress

The core functionality of the extension revolves around tracking how many problems have been solved each day. To do this, I used Chrome’s storage API to store and retrieve user progress:

async function getStorageData() {
  return new Promise((resolve) => {
    chrome.storage.local.get(null, (result) => {
      resolve(result);
    });
  });
}

async function updateStorageData(data) {
  return new Promise((resolve) => {
    chrome.storage.local.set(data, resolve);
  });
}

Enter fullscreen mode Exit fullscreen mode

Every time a problem is solved, we increment the problemsSolved counter and store it:

if (submissionAccepted && data.problemsSolved < data.dailyGoal) {
  await updateStorageData({
    problemsSolved: data.problemsSolved + 1
  });
}

Enter fullscreen mode Exit fullscreen mode

Daily Reset:

To keep things fresh, the extension resets progress at midnight using Chrome's alarm API:

function setupDailyResetAlarm() {
  chrome.alarms.create("dailyReset", {
    when: getNextMidnight(),
    periodInMinutes: 24 * 60
  });
}

function getNextMidnight() {
  const now = new Date();
  const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
  return tomorrow.getTime();
}

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === "dailyReset") {
    updateStorageData({ problemsSolved: 0 });
  }
});

Enter fullscreen mode Exit fullscreen mode

Step 4: Enforcing Redirection to LeetCode Problems

The real "magic" of the extension lies in its ability to redirect users to unsolved LeetCode problems whenever they open a new tab. This is handled using Chrome’s Declarative Net Request API, which allows us to dynamically redirect based on the user's progress.

When a new tab is created, the extension checks if the user has met their daily goal:

chrome.tabs.onCreated.addListener(async (tab) => {
  const data = await getStorageData();
  if (data.problemsSolved < data.dailyGoal) {
    const sheet = sheets[data.DSA_Sheet];
    const marker = data[sheet.markerKey];
    const redirectUrl = sheet.problems[marker].lcLink;

    setRedirectRuleForTab(redirectUrl);
  }
});

Enter fullscreen mode Exit fullscreen mode

Setting the Redirect Rule

The setRedirectRuleForTab() function creates a redirect rule to block access to anything but the LeetCode problem:

function setRedirectRuleForTab(redirectUrl) {
  const redirectRule = {
    id: 1,
    priority: 1,
    action: {
      type: "redirect",
      redirect: { url: redirectUrl }
    },
    condition: {
      urlFilter: "*://*/*",
      excludedRequestDomains: excludedSites,
      resourceTypes: ["main_frame"]
    }
  };

  chrome.declarativeNetRequest.updateDynamicRules({
    removeRuleIds: [1],
    addRules: [redirectRule]
  });
}

Enter fullscreen mode Exit fullscreen mode

Once the user submits their solution on LeetCode, the extension listens for the submission response and updates the progress tracker:

chrome.webRequest.onCompleted.addListener(thingsAfterLeetcodeResponse, {
  urls: ["<https://leetcode.com/submissions/detail/*/check/>"]
});

Enter fullscreen mode Exit fullscreen mode

If the problem is accepted, we increment the problem marker and remove the redirect rule when the daily goal is met.


Step 5: Customizing the Extension via Popup

To make the extension flexible, I added a popup interface using React, allowing users to update settings like:

  • Daily Goal: How many problems they want to solve each day.
  • Problem Sheet: Select between different difficulty levels.
  • Extension Toggle: Enable/disable the extension as needed.

The popup sends this data to the background script via chrome.runtime.sendMessage(), and the settings are persisted using the storage API.


Conclusion

By combining Plasmo for building the extension, Puppeteer for scraping problem data, and Chrome’s Declarative Net Request API for enforcing redirection, I was able to create an extension that keeps me focused on solving LeetCode problems every day.

This extension has helped me build better problem-solving habits, and it’s highly customizable to suit individual needs. Whether you're prepping for a tech interview or just want to stay sharp, this Chrome extension can be your coding coach.


If you're interested in building something similar or have questions about Chrome extension development, feel free to reach out! Happy coding!

Top comments (0)