Vibe Coding Take 2


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:
For a quick summary my two mistakes were:
Writing short prompts
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!
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.