Skip to content

Commit 8e281e6

Browse files
committed
feat: v0.3.2 — code quality, sats display fix, wallet banner fix
- Modularized background handler modules: extracted shared types (LocalAccountEntry, StoredActivityEntry), helpers (resetLocalGraph, buildStrategyCSS, withIdentityGuard), removed dead exports and duplicate code - Fixed sats displaying with decimals in wallet balance, transactions, invoice previews, and payment prompts - Fixed wallet setup banner persisting on home screen after wallet configuration by re-checking wallet state when menu overlay closes
1 parent 198d1de commit 8e281e6

16 files changed

Lines changed: 142 additions & 155 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.3.2] - 2026-03-10
6+
7+
### Changed
8+
- **Modularized background service worker** — split the monolithic `background.ts` (~2800 lines) into 8 focused handler modules under `lib/bg/`: state, wot-handlers, misc-handlers, domain-handlers, vault-handlers, wallet-handlers, nip07-handlers, onboarding-handlers; background.ts is now a ~300-line orchestrator with Map-based dispatch
9+
- **Code quality improvements** — eliminated duplicate types (`DistanceInfo`, `LocalAccountEntry`), extracted shared helpers (`resetLocalGraph`, `buildStrategyCSS`, `withIdentityGuard`), converted key zeroing to try/finally pattern, removed dead code and unnecessary exports
10+
11+
### Fixed
12+
- **Sats display shows whole numbers** — wallet balance, transaction amounts, invoice previews, and payment prompts no longer show decimal fractions
13+
- **Wallet setup banner persists after setup** — the "Set up wallet" banner on the home screen now disappears immediately after configuring a wallet, instead of requiring a restart
14+
515
## [0.3.1] - 2026-03-10
616

717
### Changed

background.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11

22
import browser from './lib/browser.ts';
33
import { RemoteOracle } from './lib/api.ts';
4-
import { LocalGraph } from './lib/graph.ts';
54
import * as storage from './lib/storage.ts';
65
import * as vault from './lib/vault.ts';
76
import * as signer from './lib/signer.ts';
@@ -13,7 +12,7 @@ import type { ScoringConfig } from './lib/types.ts';
1312
// ── State & handler modules ──
1413

1514
import {
16-
config, setOracle, setLocalGraph,
15+
config, setOracle, resetLocalGraph,
1716
PRIVILEGED_METHODS, NIP07_SIGNING_METHODS,
1817
checkRateLimit, npubToHex,
1918
type HandlerFn,
@@ -98,7 +97,7 @@ async function loadConfig(): Promise<void> {
9897
}
9998

10099
setOracle(new RemoteOracle(config.oracleUrl));
101-
setLocalGraph(new LocalGraph());
100+
resetLocalGraph();
102101

103102
// Clean up stale sync state from interrupted syncs
104103
try {
@@ -163,13 +162,9 @@ async function handleRequest({ method, params }: { method: string; params: Recor
163162
throw new Error(`Rate limit exceeded for ${method}. Max 50 requests per second.`);
164163
}
165164

166-
// Runtime validation of page-supplied NIP-07 params
165+
// NIP-07: validate params and gate behind domain allowlist
167166
if (method.startsWith('nip07_')) {
168167
validateNip07Params(method, params);
169-
}
170-
171-
// Gate all NIP-07 methods behind the "Connect this site" allowlist
172-
if (method.startsWith('nip07_')) {
173168
const origin = params?.origin as string;
174169
if (!origin || !(await isDomainAllowed(origin))) {
175170
logActivity({ domain: origin || 'unknown', method: method.replace('nip07_', ''), decision: 'blocked' });
@@ -229,7 +224,7 @@ async function handleRequest({ method, params }: { method: string; params: Recor
229224
(method === 'getDistanceBatch' || method === 'getTrustScoreBatch')) {
230225
const remapped: Record<string, unknown> = {};
231226
for (const [key, val] of Object.entries(result as Record<string, unknown>)) {
232-
remapped[_batchKeyMap.get(key) || key] = val;
227+
remapped[_batchKeyMap!.get(key) || key] = val;
233228
}
234229
return remapped;
235230
}

lib/bg/domain-handlers.ts

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
*/
55

66
import browser from '../browser.ts';
7-
import { getDomainFromUrl } from '../../src/shared/url.ts';
8-
import { getDefaultsForDomain } from '../../src/shared/adapterDefaults.ts';
9-
import { isRestrictedUrl, sanitizeCSS, type HandlerFn } from './state.ts';
7+
import { getDomainFromUrl } from '@shared/url.ts';
8+
import { getDefaultsForDomain } from '@shared/adapterDefaults.ts';
9+
import { isRestrictedUrl, sanitizeCSS, type HandlerFn, type LocalAccountEntry } from './state.ts';
1010

1111
// ── Domain permission functions ──
1212

@@ -137,7 +137,7 @@ export async function refreshBadgesOnAllTabs(): Promise<void> {
137137

138138
export async function isActiveAccountReadOnly(): Promise<boolean> {
139139
const data = await browser.storage.local.get(['accounts', 'activeAccountId']) as Record<string, unknown>;
140-
const acct = ((data.accounts as Array<{ id: string; readOnly?: boolean; type?: string }>) || []).find(a => a.id === data.activeAccountId);
140+
const acct = ((data.accounts as LocalAccountEntry[]) || []).find(a => a.id === data.activeAccountId);
141141
return !!(acct?.readOnly || acct?.type === 'npub');
142142
}
143143

@@ -157,6 +157,26 @@ async function setIdentityDisabled(domain: string, disabled: boolean): Promise<b
157157
return true;
158158
}
159159

160+
// ── CSS strategy helpers ──
161+
162+
/** Build per-strategy custom CSS from an adapter config. */
163+
function buildStrategyCSS(cfg: Record<string, unknown>): string[] {
164+
const parts: string[] = [];
165+
if (cfg.customCSS) parts.push(sanitizeCSS(cfg.customCSS as string));
166+
if (Array.isArray(cfg.strategies)) {
167+
for (let i = 0; i < cfg.strategies.length; i++) {
168+
const s = cfg.strategies[i] as Record<string, unknown>;
169+
if (s.customCSS && s.enabled !== false) {
170+
parts.push(sanitizeCSS(s.customCSS as string).replace(
171+
/\.wot-badge(?![-\w])/g,
172+
`.wot-badge[data-wot-strategy="${i}"]`
173+
));
174+
}
175+
}
176+
}
177+
return parts;
178+
}
179+
160180
// ── Enable for current domain ──
161181

162182
async function enableForCurrentDomain(): Promise<{ ok: boolean; domain?: string; error: string | null }> {
@@ -222,20 +242,7 @@ export async function injectIntoTab(tabId: number, url: string): Promise<boolean
222242
});
223243
}
224244
if (domain && effectiveConfig[domain]) {
225-
const cfg = effectiveConfig[domain] as Record<string, unknown>;
226-
const parts: string[] = [];
227-
if (cfg.customCSS) parts.push(sanitizeCSS(cfg.customCSS as string));
228-
if (Array.isArray(cfg.strategies)) {
229-
for (let i = 0; i < cfg.strategies.length; i++) {
230-
const s = cfg.strategies[i] as Record<string, unknown>;
231-
if (s.customCSS && s.enabled !== false) {
232-
parts.push(sanitizeCSS(s.customCSS as string).replace(
233-
/\.wot-badge(?![-\w])/g,
234-
`.wot-badge[data-wot-strategy="${i}"]`
235-
));
236-
}
237-
}
238-
}
245+
const parts = buildStrategyCSS(effectiveConfig[domain] as Record<string, unknown>);
239246
if (parts.length > 0) {
240247
await browser.scripting.insertCSS({
241248
target: { tabId },
@@ -424,18 +431,7 @@ export const handlers = new Map<string, HandlerFn>([
424431
try {
425432
const effectiveCfg: Record<string, unknown> = {};
426433
effectiveCfg[td] = previewConfig;
427-
const parts: string[] = [];
428-
if (Array.isArray(previewConfig.strategies)) {
429-
for (let i = 0; i < previewConfig.strategies.length; i++) {
430-
const s = previewConfig.strategies[i] as Record<string, unknown>;
431-
if (s.customCSS && s.enabled !== false) {
432-
parts.push(sanitizeCSS(s.customCSS as string).replace(
433-
/\.wot-badge(?![-\w])/g,
434-
`.wot-badge[data-wot-strategy="${i}"]`
435-
));
436-
}
437-
}
438-
}
434+
const parts = buildStrategyCSS(previewConfig);
439435
if (parts.length > 0) {
440436
await browser.scripting.insertCSS({
441437
target: { tabId: tab.id! },

lib/bg/misc-handlers.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ interface ActivityEntry {
2525
theirPubkey?: string;
2626
}
2727

28+
interface StoredActivityEntry {
29+
timestamp: number;
30+
domain?: string;
31+
method: string;
32+
kind?: number | null;
33+
decision: string;
34+
pubkey?: string | null;
35+
event?: { tags?: string[][] } & Record<string, unknown>;
36+
theirPubkey?: string;
37+
}
38+
2839
// ── Activity Log ──
2940

3041
export async function logActivity(entry: ActivityEntry): Promise<void> {
@@ -251,14 +262,14 @@ export const handlers = new Map<string, HandlerFn>([
251262
if (!hasFilter) {
252263
await browser.storage.local.remove('activityLog');
253264
} else {
254-
const allLog = ((await browser.storage.local.get(['activityLog'])) as Record<string, any[]>).activityLog || [];
265+
const allLog = ((await browser.storage.local.get(['activityLog'])) as Record<string, StoredActivityEntry[]>).activityLog || [];
255266
const typeMethods: Record<string, string[]> = {
256267
signEvent: ['signEvent'], getPublicKey: ['getPublicKey'],
257268
encrypt: ['nip04Encrypt', 'nip44Encrypt'], decrypt: ['nip04Decrypt', 'nip44Decrypt'],
258269
nip04Encrypt: ['nip04Encrypt'], nip04Decrypt: ['nip04Decrypt'],
259270
nip44Encrypt: ['nip44Encrypt'], nip44Decrypt: ['nip44Decrypt'],
260271
};
261-
const kept = allLog.filter((e: any) => {
272+
const kept = allLog.filter((e: StoredActivityEntry) => {
262273
if (params.accountPubkey && e.pubkey !== params.accountPubkey) return true;
263274
if (params.domain && e.domain !== params.domain) return true;
264275
if (params.typeFilter) {
@@ -406,8 +417,6 @@ export const handlers = new Map<string, HandlerFn>([
406417
};
407418

408419
const signed = await signEvent(event, privkeyBytes);
409-
privkeyBytes.fill(0);
410-
411420
const broadcastUrls = relayUrls.length > 0 ? relayUrls : config.relays;
412421
const result = await broadcastEvent(signed, broadcastUrls);
413422

@@ -417,9 +426,8 @@ export const handlers = new Map<string, HandlerFn>([
417426
});
418427

419428
return { ok: true, sent: result.sent, failed: result.failed };
420-
} catch (e) {
429+
} finally {
421430
privkeyBytes.fill(0);
422-
throw e;
423431
}
424432
}],
425433

lib/bg/nip07-handlers.ts

Lines changed: 32 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,30 @@ export function validateNip07Params(method: string, params: Record<string, unkno
3535
}
3636
}
3737

38+
// ── Helpers ──
39+
40+
/** Wraps an encrypt/decrypt handler with identity-disabled check and activity logging. */
41+
function withIdentityGuard(
42+
method: string,
43+
fn: (origin: string, params: Record<string, unknown>) => Promise<unknown>,
44+
): HandlerFn {
45+
return async (params) => {
46+
const origin = params.origin as string;
47+
if (origin && await isIdentityDisabled(origin)) {
48+
logActivity({ domain: origin, method, decision: 'blocked' });
49+
throw new Error('Identity access disabled for this site');
50+
}
51+
try {
52+
const result = await fn(origin, params);
53+
logActivity({ domain: origin, method, decision: 'approved', theirPubkey: params.pubkey as string });
54+
return result;
55+
} catch (e) {
56+
logActivity({ domain: origin, method, decision: 'rejected', theirPubkey: params.pubkey as string });
57+
throw e;
58+
}
59+
};
60+
}
61+
3862
// ── Handler Map ──
3963

4064
export const handlers = new Map<string, HandlerFn>([
@@ -82,69 +106,17 @@ export const handlers = new Map<string, HandlerFn>([
82106
return relayObj;
83107
}],
84108

85-
['nip07_nip04Encrypt', async (params) => {
86-
const nip07Origin = params.origin as string;
87-
if (nip07Origin && await isIdentityDisabled(nip07Origin)) {
88-
logActivity({ domain: nip07Origin, method: 'nip04Encrypt', decision: 'blocked' });
89-
throw new Error('Identity access disabled for this site');
90-
}
91-
try {
92-
const result = await signer.handleNip04Encrypt(params.pubkey as string, params.plaintext as string, nip07Origin);
93-
logActivity({ domain: nip07Origin, method: 'nip04Encrypt', decision: 'approved', theirPubkey: params.pubkey as string });
94-
return result;
95-
} catch (e) {
96-
logActivity({ domain: nip07Origin, method: 'nip04Encrypt', decision: 'rejected', theirPubkey: params.pubkey as string });
97-
throw e;
98-
}
99-
}],
109+
['nip07_nip04Encrypt', withIdentityGuard('nip04Encrypt', (origin, params) =>
110+
signer.handleNip04Encrypt(params.pubkey as string, params.plaintext as string, origin))],
100111

101-
['nip07_nip04Decrypt', async (params) => {
102-
const nip07Origin = params.origin as string;
103-
if (nip07Origin && await isIdentityDisabled(nip07Origin)) {
104-
logActivity({ domain: nip07Origin, method: 'nip04Decrypt', decision: 'blocked' });
105-
throw new Error('Identity access disabled for this site');
106-
}
107-
try {
108-
const result = await signer.handleNip04Decrypt(params.pubkey as string, params.ciphertext as string, nip07Origin);
109-
logActivity({ domain: nip07Origin, method: 'nip04Decrypt', decision: 'approved', theirPubkey: params.pubkey as string });
110-
return result;
111-
} catch (e) {
112-
logActivity({ domain: nip07Origin, method: 'nip04Decrypt', decision: 'rejected', theirPubkey: params.pubkey as string });
113-
throw e;
114-
}
115-
}],
112+
['nip07_nip04Decrypt', withIdentityGuard('nip04Decrypt', (origin, params) =>
113+
signer.handleNip04Decrypt(params.pubkey as string, params.ciphertext as string, origin))],
116114

117-
['nip07_nip44Encrypt', async (params) => {
118-
const nip07Origin = params.origin as string;
119-
if (nip07Origin && await isIdentityDisabled(nip07Origin)) {
120-
logActivity({ domain: nip07Origin, method: 'nip44Encrypt', decision: 'blocked' });
121-
throw new Error('Identity access disabled for this site');
122-
}
123-
try {
124-
const result = await signer.handleNip44Encrypt(params.pubkey as string, params.plaintext as string, nip07Origin);
125-
logActivity({ domain: nip07Origin, method: 'nip44Encrypt', decision: 'approved', theirPubkey: params.pubkey as string });
126-
return result;
127-
} catch (e) {
128-
logActivity({ domain: nip07Origin, method: 'nip44Encrypt', decision: 'rejected', theirPubkey: params.pubkey as string });
129-
throw e;
130-
}
131-
}],
115+
['nip07_nip44Encrypt', withIdentityGuard('nip44Encrypt', (origin, params) =>
116+
signer.handleNip44Encrypt(params.pubkey as string, params.plaintext as string, origin))],
132117

133-
['nip07_nip44Decrypt', async (params) => {
134-
const nip07Origin = params.origin as string;
135-
if (nip07Origin && await isIdentityDisabled(nip07Origin)) {
136-
logActivity({ domain: nip07Origin, method: 'nip44Decrypt', decision: 'blocked' });
137-
throw new Error('Identity access disabled for this site');
138-
}
139-
try {
140-
const result = await signer.handleNip44Decrypt(params.pubkey as string, params.ciphertext as string, nip07Origin);
141-
logActivity({ domain: nip07Origin, method: 'nip44Decrypt', decision: 'approved', theirPubkey: params.pubkey as string });
142-
return result;
143-
} catch (e) {
144-
logActivity({ domain: nip07Origin, method: 'nip44Decrypt', decision: 'rejected', theirPubkey: params.pubkey as string });
145-
throw e;
146-
}
147-
}],
118+
['nip07_nip44Decrypt', withIdentityGuard('nip44Decrypt', (origin, params) =>
119+
signer.handleNip44Decrypt(params.pubkey as string, params.ciphertext as string, origin))],
148120

149121
// ── Signer permission management ──
150122

0 commit comments

Comments
 (0)