-
Notifications
You must be signed in to change notification settings - Fork 77
fix(chrome-extension): keep MV3 service worker alive while SSE bridge expects to be open #29900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -619,6 +619,10 @@ function createSseConnection(mode: SseMode): SseConnection { | |||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (authError) { | ||||||||||||||||||||||
| shouldConnect = false; | ||||||||||||||||||||||
| // Auth-required is a hard stop: no automatic reconnect will | ||||||||||||||||||||||
| // succeed until the user re-signs-in, so let the worker idle | ||||||||||||||||||||||
| // out instead of waking every 30 s. | ||||||||||||||||||||||
| void clearKeepaliveAlarm(); | ||||||||||||||||||||||
| setConnectionHealth("auth_required", { | ||||||||||||||||||||||
| lastErrorMessage: authError, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
@@ -896,6 +900,54 @@ function disconnect(): void { | |||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ── Keep-alive (MV3 service-worker liveness) ───────────────────────── | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const KEEPALIVE_ALARM_NAME = "vellum-relay-keepalive"; | ||||||||||||||||||||||
| const KEEPALIVE_PERIOD_MIN = 0.5; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async function ensureKeepaliveAlarm(): Promise<void> { | ||||||||||||||||||||||
| const existing = await chrome.alarms.get(KEEPALIVE_ALARM_NAME); | ||||||||||||||||||||||
| if (existing) return; | ||||||||||||||||||||||
| await chrome.alarms.create(KEEPALIVE_ALARM_NAME, { | ||||||||||||||||||||||
| periodInMinutes: KEEPALIVE_PERIOD_MIN, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async function clearKeepaliveAlarm(): Promise<void> { | ||||||||||||||||||||||
| await chrome.alarms.clear(KEEPALIVE_ALARM_NAME); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| chrome.alarms.onAlarm.addListener((alarm) => { | ||||||||||||||||||||||
| if (alarm.name !== KEEPALIVE_ALARM_NAME) return; | ||||||||||||||||||||||
| if (shouldConnect && !(sseConnection?.isOpen() ?? false)) { | ||||||||||||||||||||||
| void connect({ interactive: false }).catch((err) => { | ||||||||||||||||||||||
| const detail = err instanceof Error ? err.message : String(err); | ||||||||||||||||||||||
| console.warn(`[vellum-relay] Keepalive reconnect failed: ${detail}`); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // On install/update, only register the alarm if we already have an | ||||||||||||||||||||||
| // active auto-connect intent (e.g. an update installing over a | ||||||||||||||||||||||
| // connected install). For a fresh install with no prior connect, | ||||||||||||||||||||||
| // the alarm is created when the user first presses Connect — that | ||||||||||||||||||||||
| // avoids burning a wake-up every 30 s on installs that never connect. | ||||||||||||||||||||||
| chrome.runtime.onInstalled.addListener(() => { | ||||||||||||||||||||||
| void chrome.storage.local.get(AUTO_CONNECT_KEY).then((result) => { | ||||||||||||||||||||||
| if (result[AUTO_CONNECT_KEY] === true) { | ||||||||||||||||||||||
| void ensureKeepaliveAlarm(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
Comment on lines
+935
to
+941
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Creating the keepalive alarm unconditionally in Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in
Comment on lines
+935
to
+941
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already addressed in 7c3b66c — |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| chrome.runtime.onStartup.addListener(() => { | ||||||||||||||||||||||
| void chrome.storage.local.get(AUTO_CONNECT_KEY).then((result) => { | ||||||||||||||||||||||
| if (result[AUTO_CONNECT_KEY] === true) { | ||||||||||||||||||||||
| void ensureKeepaliveAlarm(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ── Extension message listener (from popup) ───────────────────────── | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | ||||||||||||||||||||||
|
|
@@ -910,6 +962,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | |||||||||||||||||||||
| // was in-flight — their pause intent takes precedence. | ||||||||||||||||||||||
| if (shouldConnect) { | ||||||||||||||||||||||
| await setAutoConnect(true); | ||||||||||||||||||||||
| await ensureKeepaliveAlarm(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| sendResponseFn({ ok: true }); | ||||||||||||||||||||||
| }) | ||||||||||||||||||||||
|
|
@@ -922,6 +975,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | |||||||||||||||||||||
| // must not leave the flag set, otherwise the next bootstrap | ||||||||||||||||||||||
| // would retry a doomed connect. | ||||||||||||||||||||||
| await setAutoConnect(false); | ||||||||||||||||||||||
| await clearKeepaliveAlarm(); | ||||||||||||||||||||||
| const serializedError = serializeWorkerError(err); | ||||||||||||||||||||||
| const errorMessage = serializedError.error; | ||||||||||||||||||||||
| // Classify the failure: auth-related errors (MissingTokenError) | ||||||||||||||||||||||
|
|
@@ -947,6 +1001,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | |||||||||||||||||||||
| if (message.type === "pause" || message.type === "disconnect") { | ||||||||||||||||||||||
| shouldConnect = false; | ||||||||||||||||||||||
| setConnectionHealth("paused"); | ||||||||||||||||||||||
| void clearKeepaliveAlarm(); | ||||||||||||||||||||||
| // Await the storage write so MV3 can't terminate the worker before | ||||||||||||||||||||||
| // the autoConnect flag is persisted to false. | ||||||||||||||||||||||
| setAutoConnect(false) | ||||||||||||||||||||||
|
|
@@ -1238,6 +1293,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | |||||||||||||||||||||
| disconnect(); | ||||||||||||||||||||||
| setConnectionHealth("paused"); | ||||||||||||||||||||||
| clearEventLog(); | ||||||||||||||||||||||
| await clearKeepaliveAlarm(); | ||||||||||||||||||||||
| await setAutoConnect(false); | ||||||||||||||||||||||
| await clearSession(); | ||||||||||||||||||||||
| await clearSelectedAssistant(); | ||||||||||||||||||||||
|
|
@@ -1253,6 +1309,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponseFn) => { | |||||||||||||||||||||
| disconnect(); | ||||||||||||||||||||||
| setConnectionHealth("paused"); | ||||||||||||||||||||||
| clearEventLog(); | ||||||||||||||||||||||
| await clearKeepaliveAlarm(); | ||||||||||||||||||||||
| await setAutoConnect(false); | ||||||||||||||||||||||
| await clearStoredUserMode(); | ||||||||||||||||||||||
| sendResponseFn({ ok: true }); | ||||||||||||||||||||||
|
|
@@ -1325,6 +1382,7 @@ async function bootstrap(): Promise<void> { | |||||||||||||||||||||
| const result = await chrome.storage.local.get(AUTO_CONNECT_KEY); | ||||||||||||||||||||||
| if (result[AUTO_CONNECT_KEY] !== true) return; | ||||||||||||||||||||||
| shouldConnect = true; | ||||||||||||||||||||||
| await ensureKeepaliveAlarm(); | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| await connect({ interactive: false }); | ||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||
|
|
@@ -1333,6 +1391,7 @@ async function bootstrap(): Promise<void> { | |||||||||||||||||||||
| // sign in / pair to try again. Persist the error detail exactly | ||||||||||||||||||||||
| // once so the popup can surface it, then stop retrying. | ||||||||||||||||||||||
| shouldConnect = false; | ||||||||||||||||||||||
| void clearKeepaliveAlarm(); | ||||||||||||||||||||||
| if (err instanceof MissingTokenError) { | ||||||||||||||||||||||
| console.warn(`[vellum-relay] Skipping auto-connect: ${err.message}`); | ||||||||||||||||||||||
| setConnectionHealth("auth_required", { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,11 @@ | |
| "manifest_version": 3, | ||
| "name": "Vellum Assistant", | ||
| "version": "0.8.0", | ||
| "minimum_chrome_version": "116", | ||
| "minimum_chrome_version": "120", | ||
| "description": "Bridges the Vellum assistant to your live browser session via Chrome DevTools Protocol (CDP) through chrome.debugger.", | ||
| "homepage_url": "https://www.vellum.ai", | ||
| "permissions": [ | ||
| "alarms", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc: @noanflaherty are we as sensitive to new perms now that we are post launch? |
||
| "debugger", | ||
| "identity", | ||
| "storage", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚩 SSE onClose auth-error path sets shouldConnect=false but doesn't clear alarm
In the
onClosecallback atworker.ts:620-631, when an auth error is received,shouldConnectis set tofalseandsseConnectionis nulled, butclearKeepaliveAlarm()is not called. The alarm will continue firing every 30 seconds. The alarm listener at line 964 checksshouldConnectfirst, so it won't attempt reconnection — the alarm is functionally inert but still wakes the worker. This is similar to thecloud-logout/self-hosted-disconnectomission reported as BUG-0004, but this path is slightly different: the user hasn't explicitly stopped, and a re-auth flow may follow. Clearing the alarm here would be correct but is a less clear-cut omission since the user may re-authenticate shortly.(Refers to lines 620-631)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in
7c3b66c33d. The SSEonCloseauth-error branch nowvoid clearKeepaliveAlarm()alongside settingshouldConnect = false. Auth-required is a hard stop until the user re-signs-in, so there's no value in keeping the worker awake.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already addressed in 7c3b66c — both
cloud-logoutandself-hosted-disconnecthandlers callawait clearKeepaliveAlarm()immediately afterdisconnect()and before persistingsetAutoConnect(false). SSE auth-error close is also covered (cleared onauth_requiredhealth transition).