From 7c8b20f8189a313084f6879ce1c57a0be4c38998 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 30 Apr 2026 22:02:38 -0500 Subject: [PATCH] fix: drop chrome.alarms permission; 45s drop check via setTimeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chrome Web Store update prompts users on permission additions, and `alarms` was added in #55 just to keep the 45s post-broadcast drop-check firing across service-worker idle eviction. The drop-check is diagnostic UX (warns the user when a tx isn't propagating), not load-bearing for tx safety — the 8s check still uses setTimeout (< 30s, always fires), and during active dApp interaction the SW is kept warm by block-polling so 45s setTimeout fires in the common case anyway. When the SW idles immediately after broadcast, we miss the 45s warning; the tx outcome is unchanged. Removes: - alarms permission from chrome-extension/manifest.js - chrome.alarms.create + onAlarm listener in ethereumHandler - DROP_CHECK_ALARM_PREFIX constant + name-encoded delay parsing Bumps to 0.0.30. Co-Authored-By: Claude Opus 4.7 (1M context) --- chrome-extension/manifest.js | 2 +- chrome-extension/package.json | 2 +- .../src/background/chains/ethereumHandler.ts | 48 ++++--------------- package.json | 4 +- packages/dev-utils/package.json | 2 +- packages/hmr/package.json | 2 +- packages/i18n/package.json | 2 +- packages/shared/package.json | 2 +- packages/storage/package.json | 2 +- packages/tailwind-config/package.json | 2 +- packages/tsconfig/package.json | 2 +- packages/ui/package.json | 2 +- packages/vite-config/package.json | 2 +- packages/zipper/package.json | 2 +- pages/content-runtime/package.json | 2 +- pages/content-ui/package.json | 2 +- pages/content/package.json | 2 +- pages/devtools-panel/package.json | 2 +- pages/devtools/package.json | 2 +- pages/options/package.json | 2 +- pages/side-panel/package.json | 2 +- tests/e2e/package.json | 2 +- 22 files changed, 32 insertions(+), 60 deletions(-) diff --git a/chrome-extension/manifest.js b/chrome-extension/manifest.js index 6c381f8..e3bf00a 100755 --- a/chrome-extension/manifest.js +++ b/chrome-extension/manifest.js @@ -37,7 +37,7 @@ const manifest = deepmerge( version: packageJson.version, description: '__MSG_extensionDescription__', host_permissions: [''], - permissions: ['storage', 'tabs', 'commands', 'alarms'], + permissions: ['storage', 'tabs', 'commands'], options_page: 'options/index.html', background: { service_worker: 'background.iife.js', diff --git a/chrome-extension/package.json b/chrome-extension/package.json index a108dcc..1f2b99f 100644 --- a/chrome-extension/package.json +++ b/chrome-extension/package.json @@ -1,6 +1,6 @@ { "name": "chrome-extension", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - core settings", "type": "module", "scripts": { diff --git a/chrome-extension/src/background/chains/ethereumHandler.ts b/chrome-extension/src/background/chains/ethereumHandler.ts index 13bd499..4788319 100644 --- a/chrome-extension/src/background/chains/ethereumHandler.ts +++ b/chrome-extension/src/background/chains/ethereumHandler.ts @@ -1376,15 +1376,16 @@ const signTypedData = async (params: any, KEEPKEY_WALLET: any, ADDRESS: string, * a clear warning and emits a runtime message so the side-panel (or any * listener) can surface it. See RETRO_uniswap_swap_dropped_tx.md. * - * Two delivery mechanisms: - * - Short delays (< 30s): setTimeout. Best-effort; only fires if the - * MV3 service worker is still alive. The 8s check usually hits while - * the SW is still warm from the broadcast. - * - Long delays (>= 30s): chrome.alarms. Survives SW suspension — - * the alarm wakes the SW, registering the listener at module load. - * Production minimum delay is 30s; we use 45s for the eviction probe. + * Best-effort via setTimeout. Both 8s and 45s checks fire only if the + * MV3 service worker is still alive at delay time. The 8s check almost + * always hits because the broadcast just happened. The 45s check fires + * during active dApp interaction (block polling / eth_chainId pings + * keep the SW warm) but may miss if the SW idles immediately after + * broadcast — acceptable trade for not requiring the chrome.alarms + * permission, which would gate Chrome Web Store updates on user + * re-consent. The drop warning is diagnostic UX; the tx outcome is + * unchanged when the warning is missed. */ -const DROP_CHECK_ALARM_PREFIX = 'eth-drop-check-'; // Map hash → URL that successfully accepted the broadcast. Drop-check // then queries that exact RPC instead of running getProvider() again, @@ -1431,38 +1432,9 @@ const performDropCheck = async (hash: string, scheduledDelayMs: number) => { const scheduleDropCheck = (hash: string, delayMs: number, successUrl?: string) => { if (successUrl) dropCheckUrlByHash.set(hash, successUrl); - if (delayMs < 30_000) { - setTimeout(() => performDropCheck(hash, delayMs), delayMs); - return; - } - // Encode delay in alarm name so the listener can recover it without - // a separate storage round-trip. Alarms are unique by name; suffixing - // with delayMs lets us schedule multiple checks for the same hash. - const alarmName = `${DROP_CHECK_ALARM_PREFIX}${hash}-${delayMs}`; - try { - chrome.alarms.create(alarmName, { when: Date.now() + delayMs }); - } catch (e) { - console.warn('[DROP-CHECK] alarm scheduling failed, falling back to setTimeout:', e); - setTimeout(() => performDropCheck(hash, delayMs), delayMs); - } + setTimeout(() => performDropCheck(hash, delayMs), delayMs); }; -// Registered at module load — re-runs on every service-worker startup, -// which is exactly when the alarm fires and wakes the SW. (URL hint -// is not recovered across SW restart; drop-check falls back to -// getProvider in that case.) -if (typeof chrome !== 'undefined' && chrome.alarms?.onAlarm) { - chrome.alarms.onAlarm.addListener(alarm => { - if (!alarm.name.startsWith(DROP_CHECK_ALARM_PREFIX)) return; - const rest = alarm.name.slice(DROP_CHECK_ALARM_PREFIX.length); - const lastDash = rest.lastIndexOf('-'); - if (lastDash <= 0) return; - const hash = rest.slice(0, lastDash); - const delayMs = Number(rest.slice(lastDash + 1)); - void performDropCheck(hash, Number.isFinite(delayMs) ? delayMs : 0); - }); -} - /** * Classify a broadcast error so the failover loop knows whether to try * the next RPC or stop and surface it. diff --git a/package.json b/package.json index 21578ea..738fc23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keepkey-client", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension for KeepKey", "license": "GNU General Public License v3.0", "repository": { @@ -36,7 +36,7 @@ "react-dom": "18.3.1" }, "devDependencies": { - "@types/chrome": "^0.0.290", + "@types/chrome": "^0.0.300", "@types/node": "^20.16.11", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 4a66652..6ca7030 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@extension/dev-utils", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - dev utils", "private": true, "sideEffects": false, diff --git a/packages/hmr/package.json b/packages/hmr/package.json index 92af841..983c7e7 100644 --- a/packages/hmr/package.json +++ b/packages/hmr/package.json @@ -1,6 +1,6 @@ { "name": "@extension/hmr", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - hot module reload/refresh", "private": true, "sideEffects": true, diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 94b454c..d9c2b0f 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@extension/i18n", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - internationalization", "private": true, "sideEffects": false, diff --git a/packages/shared/package.json b/packages/shared/package.json index 59ede00..5187f5e 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@extension/shared", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - shared code", "private": true, "sideEffects": false, diff --git a/packages/storage/package.json b/packages/storage/package.json index afe164b..1e75003 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -1,6 +1,6 @@ { "name": "@extension/storage", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - storage", "private": true, "sideEffects": false, diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index 6872962..c897f2e 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@extension/tailwindcss-config", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - tailwindcss configuration", "main": "tailwind.config.ts", "private": true diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index fc2e158..a2bb18e 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@extension/tsconfig", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - tsconfig", "private": true } diff --git a/packages/ui/package.json b/packages/ui/package.json index d4936eb..2606ef8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@extension/ui", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - ui components", "private": true, "sideEffects": false, diff --git a/packages/vite-config/package.json b/packages/vite-config/package.json index c8485dc..0826c32 100644 --- a/packages/vite-config/package.json +++ b/packages/vite-config/package.json @@ -1,6 +1,6 @@ { "name": "@extension/vite-config", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - vite base configuration", "main": "index.mjs", "type": "module", diff --git a/packages/zipper/package.json b/packages/zipper/package.json index 080f40e..e62b43d 100644 --- a/packages/zipper/package.json +++ b/packages/zipper/package.json @@ -1,6 +1,6 @@ { "name": "@extension/zipper", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - zipper", "private": true, "sideEffects": false, diff --git a/pages/content-runtime/package.json b/pages/content-runtime/package.json index 040565e..2d2306b 100644 --- a/pages/content-runtime/package.json +++ b/pages/content-runtime/package.json @@ -1,6 +1,6 @@ { "name": "@extension/content-runtime-script", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - content runtime script", "private": true, "sideEffects": true, diff --git a/pages/content-ui/package.json b/pages/content-ui/package.json index 88bd113..0b5c34f 100644 --- a/pages/content-ui/package.json +++ b/pages/content-ui/package.json @@ -1,6 +1,6 @@ { "name": "@extension/content-ui", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - content ui", "type": "module", "private": true, diff --git a/pages/content/package.json b/pages/content/package.json index 895ae56..12a4ccf 100644 --- a/pages/content/package.json +++ b/pages/content/package.json @@ -1,6 +1,6 @@ { "name": "@extension/content-script", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - content script", "private": true, "sideEffects": true, diff --git a/pages/devtools-panel/package.json b/pages/devtools-panel/package.json index 5b4f7e9..8ea74ad 100644 --- a/pages/devtools-panel/package.json +++ b/pages/devtools-panel/package.json @@ -1,6 +1,6 @@ { "name": "@extension/devtools-panel", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - devtools panel", "private": true, "sideEffects": true, diff --git a/pages/devtools/package.json b/pages/devtools/package.json index 18f9228..9a3027d 100644 --- a/pages/devtools/package.json +++ b/pages/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@extension/devtools", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - devtools", "private": true, "sideEffects": true, diff --git a/pages/options/package.json b/pages/options/package.json index 40cf163..c48a654 100644 --- a/pages/options/package.json +++ b/pages/options/package.json @@ -1,6 +1,6 @@ { "name": "@extension/options", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - options", "private": true, "sideEffects": true, diff --git a/pages/side-panel/package.json b/pages/side-panel/package.json index 80751b3..8596f56 100644 --- a/pages/side-panel/package.json +++ b/pages/side-panel/package.json @@ -1,6 +1,6 @@ { "name": "@extension/sidepanel", - "version": "0.0.29", + "version": "0.0.30", "description": "chrome extension - side panel", "private": true, "sideEffects": true, diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 3b56dfe..3cb1c9a 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@extension/e2e", - "version": "0.0.29", + "version": "0.0.30", "description": "E2e tests configuration boilerplate", "private": true, "type": "module",