Skip to content

How ResistGate Keeps Sites Blocked When Chrome Stops Its Service Worker

An alarm only wakes the worker up. The expiry time saved in storage decides whether a site opens.

Chrome extensions2 min read

Software engineer building Chrome extensions, web apps, and applied AI tools.

Contents

When you finish ResistGate's typing challenge, you get a few minutes on the site you were trying to open. Then the block comes back. In a Manifest V3 extension, that second part is the hard one.

The worker you can't count on

An extension's background code runs as a service worker, and Chrome stops it after about 30 seconds without events. Anything in memory goes with it, including a setTimeout counting down your 15 minutes.

chrome.alarms outlives the worker and wakes it back up. But Chrome's docs say alarms may be cleared when the browser restarts, and declarativeNetRequest session rules, which do the actual redirecting, are cleared then too. So neither an alarm nor a rule can be the only record that an access window exists.

Save the deadline

When you earn access, ResistGate writes one record to chrome.storage.local:

temporaryAccess[domain] = {
  grantedAt: now,
  expiresAt,
  duration
};

expiresAt is a plain timestamp. Everything else is worked out from it.

Rebuild everything from storage

One function, updateBlockingRules, turns that record into browser behavior. It does the same three things every time it runs:

  1. Drop every access record whose expiresAt has passed.
  2. Make the alarms match what's left. ResistGate's alarms with no record get cleared, and each remaining record gets an alarm at its expiresAt.
  3. Remove all session rules, then add a redirect rule for every blocked domain without active access.

It runs when the worker starts, when the browser starts, when settings or access records change in storage, and when one of its alarms fires. Every call goes through one promise queue, so two updates never overlap.

The alarm handler doesn't block anything itself. It asks for the same update as everyone else. If the worker was asleep when your window ended, the next run sees an expiresAt in the past and the redirect rule goes back. If a browser restart wiped the alarms and rules, startup recreates both from storage.

A Vitest test hands the cleanup step one live record and one expired record and checks that only the live one survives. I still check the real thing by hand in Chrome: block a site, earn access, wait, and watch it block again.

The gap that's left

Storage can tell a waking worker that a window has ended. It can't wake the worker. If Chrome dropped an alarm and nothing else happened, the site would stay open until the next event. Browser startup is the case the docs warn about, and that's exactly when the alarms get rebuilt.

What I'd carry into any event-driven code: save the state you need to recover, give one function the job of turning it into effects, and let every event ask that function to run again.

The code is on GitHub if you want to read updateBlockingRules and syncExpiryAlarms in background.js.

A tiny vote of confidence

Was this note worth your time?