Skip to content

fix(extension): await init in onMessage to fix lost click after SW sleeps (MV3 cold-start race)#24

Open
thiagomendonca-eu wants to merge 1 commit into
etsd-tech:mainfrom
thiagomendonca-eu:fix/sw-cold-start-init-race
Open

fix(extension): await init in onMessage to fix lost click after SW sleeps (MV3 cold-start race)#24
thiagomendonca-eu wants to merge 1 commit into
etsd-tech:mainfrom
thiagomendonca-eu:fix/sw-cold-start-init-race

Conversation

@thiagomendonca-eu

Copy link
Copy Markdown

What

Fixes the MV3 service-worker cold-start race that drops Option+Click selections after the worker goes idle.

Closes #19

Problem

ElementSenderService disconnects its WebSocket after 10s of inactivity, removing the last thing keeping the service worker alive, so Chrome terminates the SW. The next Option+Click wakes it via runtime.sendMessage, but the onMessage handler used elementSender / currentConfig directly while the top-level initialize() was still awaiting the async config load:

async function initialize() {
  currentConfig = await ConfigStorageService.load(); // async
  elementSender = new ElementSenderService();
}

chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
  if (request.type === 'DOM_ELEMENT_POINTED' && request.data) {
    elementSender.sendElement(...); // elementSender is undefined on a cold start
  }
});

initialize();

On a cold start the message arrives before initialize() resolves, so elementSender is undefinedTypeError → the click is silently lost. This matches the diagnosis already in #19 ("initialize() hasn't completed when the message handler fires"). In practice, users had to keep the SW console open (which keeps the worker alive) for pointing to work.

Fix

Make initialization idempotent (ensureInitialized() returns a cached promise) and await it inside the listener before using elementSender / currentConfig. Following MV3 messaging guidance, the listener stays synchronous, the async work runs in an inner IIFE, and it returns true to keep the channel open for sendResponse.

Testing

  • Windows 11, Chrome 145, extension 0.6.0, Claude Code.
  • Before: once the SW went inactive, the first Option+Click was lost and get-pointed-element returned stale/empty data — unless the SW console was kept open.
  • After: let the SW go inactive, closed all devtools, then Option+Click → element was sent and get-pointed-element returned it immediately. No console needed.

Single-file change; the listener contract (synchronous + return true) is preserved.

…eeps

The MV3 service worker is terminated when idle (the WebSocket idle timer
disconnects after 10s, removing the last keep-alive). The next Option+Click
wakes the SW via runtime.sendMessage, but the onMessage handler used
elementSender / currentConfig directly while initialize() was still running
its async config load. On a cold start the handler fires before init
completes, so elementSender is undefined and the click is silently lost —
users had to open the SW console (which keeps it alive) to make it work.

Make initialization idempotent (ensureInitialized) and await it inside the
listener before sending. The listener stays synchronous and returns true to
keep the channel open for the async sendResponse, per MV3 messaging guidance.

Fixes etsd-tech#19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Subsequent Option+Click selections not received after first one

1 participant