Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

I built a website blocker that fights its own user

Most website blockers have the same flaw: the off switch is one click away, and the moment you want to use it is the moment you shouldn't. I built Bonk, a Chrome website blocker, as a solo project. Making it meant desig

Most website blockers have the same flaw: the off switch is one click away, and the moment you want to use it is the moment you shouldn't.

I built Bonk, a Chrome website blocker, as a solo project. Making it meant designing against the impulse of the person running it. Here's what that looked like.

Blocking with declarativeNetRequest

Manifest V3 extensions can't use a blocking webRequest handler anymore, so Bonk blocks with declarativeNetRequest rules. Every blocked site becomes a rule that redirects to Bonk's own page, and the original address is passed along so the page can send you on afterwards:

const redirect = (page) => ({
  type: "redirect",
  redirect: { regexSubstitution: page + "?url=\\0" },
});

const addRules = sites.map((site, i) => ({
  id: ruleId(site, i),
  priority: blockPriority(state, site),
  action: redirect(BLOCKED_PAGE),
  condition: ruleCondition(site),
}));

The rules are rebuilt whenever stored settings change. Those events can overlap, so every sync goes through a promise queue to stop two updates interleaving.

Allow-list mode ("block everything except these sites") uses the same machinery. It's one low-priority rule that redirects every page, plus a higher-priority allow rule for each site you chose.

The off switch problem

A blocker is only useful if you can't undo it in a weak moment, so Bonk has two kinds of friction.

Hard mode locks your list for a set time. It also covers Chrome's extensions page. While it's on, any open chrome://extensions tab is sent to the blocked page, so the extension can't be disabled there:

const EXTENSIONS_PAGE = /^(chrome|edge|brave):\/\/extensions/;

async function guardExtensionsTab(tabId, url) {
  if (!EXTENSIONS_PAGE.test(url)) return;
  if (!isLocked(await getState())) return;
  chrome.tabs.update(tabId, { url: BLOCKED_PAGE + "?locked=1" })
    .catch(() => chrome.tabs.remove(tabId))
    .catch(() => {});
}

The turn-off challenge makes you type a whole sentence, like "I have the self control of a golden retriever", before Bonk will switch off. Pasting and dragging are blocked, so it has to be typed:

input.addEventListener("beforeinput", (event) => {
  if (event.inputType.startsWith("insertFrom")) event.preventDefault();
});

Neither is unbreakable. Another browser or another device is outside Bonk's reach. The aim is friction at the exact moment an impulse hits, not a security boundary.

Everything runs from timestamps

Manifest V3 service workers get shut down whenever Chrome likes, so a countdown held in memory can't be trusted. Timers like the Pomodoro and hard mode store an end time, not a countdown. Whether a site is blocked is a plain function of the saved state and the current time:

const sites = blockedSitesFor(state, new Date());

That means closing Chrome doesn't end hard mode, and a restarted worker works out the right answer from scratch. Alarms are used only to wake the extension up when a phase ends.

Making sounds without audio files

The Pomodoro sounds are built from plain tones and noise with the Web Audio API, so there are no audio files to license. Service workers can't play audio, so a hidden page plays them through the offscreen API:

await chrome.offscreen.createDocument({
  url: "sound.html",
  reasons: ["AUDIO_PLAYBACK"],
  justification: "Plays the Pomodoro sounds",
});

Testing the Pro key limit

Bonk has a paid tier where one key works on up to five devices, and I wanted to test that loop for real. The test script launches the real extension as several separate headless Chromes, each acting as a device. Every request goes to the real server code, with a stand-in for Stripe that remembers each customer's data. Nothing touches the internet, so I can add the sixth device, remove one, and cause an outage, then check what happens each time.

Try it

Bonk is free, with no account and no limit on how many sites you block. It's on the Chrome Web Store.

I'd like to hear what you'd do differently, especially about stopping people from undoing their own blocks without making the tool annoying to live with.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.