Vibe Coding Take 2

VishnuVishnu
4 min read

After realising my mistakes from my last attempt at vibe coding, which I will henceforth call Ai assisted development. I tackled the same project again. I wanted to build an app that could detect all urls and javascript files on the page separately.

Before we do get into it, please subscribe to my blog.

For those who want to read my first attempt you can find it here:

Vibe Coding Part 1

For a quick summary my two mistakes were:

  1. Writing short prompts

  2. Not understanding the api and frameworks I was using, even at a basic level.

I corrected mistake 2 by reading up on extensions api. I now know the difference between content and background scripts. I also understand how message passing works which will come later.

With the script that I had written earlier I found multiple issues, it only worked on the loaded urls, and it didn’t look forward at other urls on the page leading to being kind of useless to what I actually wanted to do.

So I went back to claude better armed, my prompt is below:

create a chrome extension which grabs all url hostnames and javascript urls, 
puts them into two seperate arrays and then logs them when page load is complete

My prompt had a much clearer description on exactly what I wanted to do, and Claude generated a ton of code for me and surprisingly (compared to my previous effort), I only had one error.

Why did this error occur, 
Uncaught SyntaxError: Identifier 'observer' has already been declared do not rewrite code

Claude actually solved it this time:

I see the issue. The error "Uncaught SyntaxError: Identifier 'observer' has already been 
declared" happens because we have both the content script automatically injected by the 
manifest (via the content_scripts entry) AND manually injected through the 
chrome.scripting.executeScript call in background.js. 

This creates a conflict where the content script runs twice, causing the 'observer' variable 
to be declared multiple times.Let's fix this by choosing one injection method. Since we have 
it defined in the manifest's content_scripts section, we should remove the manual injection 
from background.js:

Claude also generated extraneous code. The entire background.js is useless as claude did not pass any messages from content.js to it.

//content.js
// Initialize arrays to store the hostnames and JavaScript URLs
hostnames = [];
jsUrls = [];

// Function to extract hostname from a URL
function extractHostname(url) {
  try {
    return new URL(url, window.location.href).hostname;
  } catch (e) {
    return null;
  }
}

// Function to collect all hostnames from various elements
function collectHostnames() {
  // Add current page hostname
  const currentHostname = window.location.hostname;
  if (currentHostname && !hostnames.includes(currentHostname)) {
    hostnames.push(currentHostname);
  }

  // Collect hostnames from all links
  document.querySelectorAll('a[href]').forEach(link => {
    const hostname = extractHostname(link.href);
    if (hostname && !hostnames.includes(hostname)) {
      hostnames.push(hostname);
    }
  });

  // Collect hostnames from images
  document.querySelectorAll('img[src]').forEach(img => {
    const hostname = extractHostname(img.src);
    if (hostname && !hostnames.includes(hostname)) {
      hostnames.push(hostname);
    }
  });

  // Collect hostnames from iframes
  document.querySelectorAll('iframe[src]').forEach(iframe => {
    const hostname = extractHostname(iframe.src);
    if (hostname && !hostnames.includes(hostname)) {
      hostnames.push(hostname);
    }
  });

  // Collect hostnames from sources
  document.querySelectorAll('source[src]').forEach(source => {
    const hostname = extractHostname(source.src);
    if (hostname && !hostnames.includes(hostname)) {
      hostnames.push(hostname);
    }
  });

  // Collect hostnames from audio/video elements
  document.querySelectorAll('audio[src], video[src]').forEach(media => {
    const hostname = extractHostname(media.src);
    if (hostname && !hostnames.includes(hostname)) {
      hostnames.push(hostname);
    }
  });
}

// Function to collect all JavaScript URLs
function collectJsUrls() {
  document.querySelectorAll('script[src]').forEach(script => {
    const url = script.src;
    if (url && !jsUrls.includes(url)) {
      jsUrls.push(url);
    }
  });
}

// Function to log the collected data
function logData() {
  console.log('--- URL and JS Logger Extension ---');
  console.log('Hostnames collected:', hostnames);
  console.log('JavaScript URLs collected:', jsUrls);

  // Send data to background script
  chrome.runtime.sendMessage({
    action: 'urlDataCollected',
    data: {
      hostnames: hostnames,
      jsUrls: jsUrls
    }
  });
}

// Wait for the page to fully load
window.addEventListener('load', () => {
  // Give a short delay to ensure dynamic content has loaded
  setTimeout(() => {
    collectHostnames();
    collectJsUrls();
    logData();
  }, 1000);
});

// Observe DOM changes to catch dynamically added content
const observer = new MutationObserver((mutations) => {
  let shouldUpdate = false;

  mutations.forEach(mutation => {
    if (mutation.addedNodes.length) {
      shouldUpdate = true;
    }
  });

  if (shouldUpdate) {
    collectHostnames();
    collectJsUrls();
  }
});

// Start observing the document
observer.observe(document.documentElement, {
  childList: true,
  subtree: true
});

// Stop observing after 10 seconds to prevent continuous updates
setTimeout(() => {
  observer.disconnect();
  collectHostnames();
  collectJsUrls();
  logData();
}, 10000);

My favourite part of all this is it runs. As a proof of concept it works much better than what I had built and Claude’s first attempt. This actually demonstrates that vibe coding is actually useful!

0
Subscribe to my newsletter

Read articles from Vishnu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vishnu
Vishnu

A science and tech enthusiast with opinions on a variety of topics.