Skip to content

Commit 1306850

Browse files
authored
feat: Background Service Worker (#4) (#15)
## Summary Implements the background service worker that wires all plugins together using SDK Kit. Closes #4 ## Changes ### Background Service Worker - Created `src/background/index.ts` with SDK initialization - Registered all three plugins (storage, matcher, interceptor) - Added event listeners for debugging - Auto-enables interceptor on SDK ready ### Plugin Fixes - Fixed plugin namespacing to match SDK Kit's API - `sdk.storage.*` for Chrome Storage Plugin - `sdk.matcher.*` for Pattern Matcher Plugin - `sdk.interceptor.*` for Request Interceptor Plugin - Updated `requestInterceptor` to always call `updateDynamicRules` for consistency ### Testing Infrastructure - Set up Vitest + Happy-DOM for unit testing - Created `tests/setup.ts` with Chrome API mocks - **35 tests - 100% passing:** - Chrome Storage Plugin (11 tests) - Pattern Matcher Plugin (11 tests) - Request Interceptor Plugin (13 tests) - Type-safe mocks using `as any` casts for Chrome callback APIs ### Build & Config - Updated Vite config (simplified rollup options) - Updated manifest paths for vite-plugin-web-extension - Created placeholder HTML files for options and popup pages - Configured Biome to allow flexibility in test files ## Testing ```bash pnpm test # All 35 tests pass pnpm type-check # No TypeScript errors pnpm build # Extension builds successfully ```
1 parent 3f91f4d commit 1306850

16 files changed

+1374
-363
lines changed

biome.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
"style": {
2929
"useNodejsImportProtocol": "error",
3030
"useTemplate": "error"
31+
},
32+
"suspicious": {
33+
"noExplicitAny": "off"
3134
}
3235
}
3336
},

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
"lint": "biome check .",
1212
"lint:fix": "biome check --write .",
1313
"format": "biome format --write .",
14+
"test": "vitest run",
15+
"test:watch": "vitest",
16+
"test:ui": "vitest --ui",
1417
"prepare": "husky",
1518
"lint-staged": "biome check --write --no-errors-on-unmatched"
1619
},
@@ -37,12 +40,15 @@
3740
"@types/react": "^19.0.2",
3841
"@types/react-dom": "^19.0.2",
3942
"@vitejs/plugin-react": "^4.3.4",
43+
"@vitest/ui": "^4.0.16",
4044
"autoprefixer": "^10.4.20",
45+
"happy-dom": "^20.0.11",
4146
"husky": "^9.1.7",
4247
"postcss": "^8.4.49",
4348
"tailwindcss": "^3.4.17",
4449
"typescript": "^5.7.3",
4550
"vite": "^6.0.7",
46-
"vite-plugin-web-extension": "^4.1.9"
51+
"vite-plugin-web-extension": "^4.1.9",
52+
"vitest": "^4.0.16"
4753
}
4854
}

pnpm-lock.yaml

Lines changed: 337 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/background/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Auth Header Injector - Background Service Worker
3+
*
4+
* Initializes SDK Kit with all plugins and manages header injection.
5+
*/
6+
7+
import { SDK } from '@lytics/sdk-kit';
8+
import { chromeStoragePlugin } from './plugins/chromeStorage';
9+
import { patternMatcherPlugin } from './plugins/patternMatcher';
10+
import { requestInterceptorPlugin } from './plugins/requestInterceptor';
11+
12+
console.log('[Auth Header Injector] Initializing...');
13+
14+
// Create SDK instance
15+
const sdk = new SDK({
16+
name: 'auth-header-injector',
17+
version: '0.1.0',
18+
});
19+
20+
// Register plugins
21+
sdk.use(chromeStoragePlugin).use(patternMatcherPlugin).use(requestInterceptorPlugin);
22+
23+
// Listen to SDK events for debugging
24+
sdk.on('sdk:ready', () => {
25+
console.log('[Auth Header Injector] Ready!');
26+
});
27+
28+
sdk.on('interceptor:enabled', (data) => {
29+
console.log(`[Auth Header Injector] Interceptor enabled with ${data.ruleCount} rule(s)`);
30+
});
31+
32+
sdk.on('interceptor:disabled', () => {
33+
console.log('[Auth Header Injector] Interceptor disabled');
34+
});
35+
36+
sdk.on('interceptor:error', (error) => {
37+
console.error('[Auth Header Injector] Interceptor error:', error);
38+
});
39+
40+
// Initialize SDK
41+
(async () => {
42+
await sdk.init();
43+
})();
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { chromeStoragePlugin } from '@/background/plugins/chromeStorage';
2+
import { SDK } from '@lytics/sdk-kit';
3+
import { beforeEach, describe, expect, it, vi } from 'vitest';
4+
5+
describe('Chrome Storage Plugin', () => {
6+
let sdk: SDK;
7+
8+
beforeEach(async () => {
9+
sdk = new SDK({ name: 'test-sdk' });
10+
sdk.use(chromeStoragePlugin);
11+
await sdk.init();
12+
vi.clearAllMocks();
13+
});
14+
15+
describe('get', () => {
16+
it('should get value from chrome.storage.sync', async () => {
17+
const mockValue = { test: 'value' };
18+
(vi.mocked(chrome.storage.sync.get) as any).mockResolvedValue({ testKey: mockValue });
19+
20+
const result = await (sdk as any).storage.get('testKey');
21+
22+
expect(chrome.storage.sync.get).toHaveBeenCalledWith('testKey');
23+
expect(result).toEqual(mockValue);
24+
});
25+
26+
it('should return null if key does not exist', async () => {
27+
(vi.mocked(chrome.storage.sync.get) as any).mockResolvedValue({});
28+
29+
const result = await (sdk as any).storage.get('nonexistent');
30+
31+
expect(result).toBeNull();
32+
});
33+
34+
it('should handle errors and emit storage:error', async () => {
35+
const error = new Error('Storage error');
36+
vi.mocked(chrome.storage.sync.get).mockRejectedValue(error);
37+
38+
const errorSpy = vi.fn();
39+
sdk.on('storage:error', errorSpy);
40+
41+
await expect((sdk as any).storage.get('testKey')).rejects.toThrow('Storage error');
42+
expect(errorSpy).toHaveBeenCalledWith({
43+
operation: 'get',
44+
key: 'testKey',
45+
error: 'Storage error',
46+
});
47+
});
48+
});
49+
50+
describe('set', () => {
51+
it('should set value in chrome.storage.sync', async () => {
52+
(vi.mocked(chrome.storage.sync.set) as any).mockResolvedValue();
53+
54+
await (sdk as any).storage.set('testKey', { data: 'test' });
55+
56+
expect(chrome.storage.sync.set).toHaveBeenCalledWith({ testKey: { data: 'test' } });
57+
});
58+
59+
it('should emit storage:set event', async () => {
60+
(vi.mocked(chrome.storage.sync.set) as any).mockResolvedValue();
61+
62+
const setSpy = vi.fn();
63+
sdk.on('storage:set', setSpy);
64+
65+
await (sdk as any).storage.set('testKey', 'value');
66+
67+
expect(setSpy).toHaveBeenCalledWith({ key: 'testKey' });
68+
});
69+
70+
it('should handle errors and emit storage:error', async () => {
71+
const error = new Error('Set error');
72+
vi.mocked(chrome.storage.sync.set).mockRejectedValue(error);
73+
74+
const errorSpy = vi.fn();
75+
sdk.on('storage:error', errorSpy);
76+
77+
await expect((sdk as any).storage.set('testKey', 'value')).rejects.toThrow('Set error');
78+
expect(errorSpy).toHaveBeenCalledWith({
79+
operation: 'set',
80+
key: 'testKey',
81+
error: 'Set error',
82+
});
83+
});
84+
});
85+
86+
describe('remove', () => {
87+
it('should remove key from chrome.storage.sync', async () => {
88+
(vi.mocked(chrome.storage.sync.remove) as any).mockResolvedValue();
89+
90+
await (sdk as any).storage.remove('testKey');
91+
92+
expect(chrome.storage.sync.remove).toHaveBeenCalledWith('testKey');
93+
});
94+
95+
it('should emit storage:remove event', async () => {
96+
(vi.mocked(chrome.storage.sync.remove) as any).mockResolvedValue();
97+
98+
const removeSpy = vi.fn();
99+
sdk.on('storage:remove', removeSpy);
100+
101+
await (sdk as any).storage.remove('testKey');
102+
103+
expect(removeSpy).toHaveBeenCalledWith({ key: 'testKey' });
104+
});
105+
});
106+
107+
describe('clear', () => {
108+
it('should clear all data from chrome.storage.sync', async () => {
109+
(vi.mocked(chrome.storage.sync.clear) as any).mockResolvedValue();
110+
111+
await (sdk as any).storage.clear();
112+
113+
expect(chrome.storage.sync.clear).toHaveBeenCalled();
114+
});
115+
116+
it('should emit storage:clear event', async () => {
117+
(vi.mocked(chrome.storage.sync.clear) as any).mockResolvedValue();
118+
119+
const clearSpy = vi.fn();
120+
sdk.on('storage:clear', clearSpy);
121+
122+
await (sdk as any).storage.clear();
123+
124+
expect(clearSpy).toHaveBeenCalled();
125+
});
126+
});
127+
128+
describe('getAll', () => {
129+
it('should get all items from chrome.storage.sync', async () => {
130+
const mockData = { key1: 'value1', key2: 'value2' };
131+
(vi.mocked(chrome.storage.sync.get) as any).mockResolvedValue(mockData);
132+
133+
const result = await (sdk as any).storage.getAll();
134+
135+
expect(chrome.storage.sync.get).toHaveBeenCalledWith(null);
136+
expect(result).toEqual(mockData);
137+
});
138+
});
139+
});

0 commit comments

Comments
 (0)