Skip to content

Commit 9a0e101

Browse files
authored
har:view now has UI to easily rebuild the extension (#20)
2 parents a48d2b2 + cdc6d34 commit 9a0e101

File tree

2 files changed

+168
-28
lines changed

2 files changed

+168
-28
lines changed

browser-extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"scripts": {
3838
"biome": "biome check .",
3939
"biome:fix": "biome check --write .",
40+
"biome:fix:unsafe": "biome check --write --unsafe .",
4041
"wxt:build": "wxt build",
4142
"wxt:build:firefox": "wxt build -b firefox",
4243
"compile": "tsc --noEmit",

browser-extension/tests/har-view.ts

Lines changed: 167 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/**
2+
* HAR Page Viewer Test Server
3+
*
4+
* This Express server serves recorded HAR files as live web pages for testing.
5+
* It provides two viewing modes: 'clean' (original page) and 'gitcasso' (with extension injected).
6+
*
7+
* Key components:
8+
* - Loads HAR files from ./har/ directory based on PAGES index in `./har/_har_index.ts`
9+
* - Patches URLs in HTML to serve assets locally from HAR data
10+
* - Handles asset serving by matching HAR entries to requested paths
11+
*
12+
* Development notes:
13+
* - Injects Gitcasso content script in 'gitcasso' mode with location patching
14+
* - Location patching uses history.pushState to simulate original URLs
15+
* - Chrome APIs are mocked for extension testing outside browser context
16+
* - Extension assets served from `./output/chrome-mv3-dev` via `/chrome-mv3-dev` route
17+
* - Floating rebuild button in gitcasso mode triggers `npx wxt build --mode development` and then refresh
18+
*/
19+
20+
import { spawn } from 'node:child_process'
121
import { error } from 'node:console'
222
import fs from 'node:fs/promises'
323
import path from 'node:path'
@@ -10,6 +30,9 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
1030
const app = express()
1131
const PORT = 3001
1232

33+
// Middleware to parse JSON bodies
34+
app.use(express.json())
35+
1336
// Store HAR json
1437
const harCache = new Map<keyof typeof PAGES, Har>()
1538

@@ -25,19 +48,6 @@ function getUrlParts(key: keyof typeof PAGES) {
2548
}
2649
}
2750

28-
// Check if WXT dev server is running
29-
async function checkDevServer(): Promise<boolean> {
30-
try {
31-
const response = await fetch('http://localhost:3000/@vite/client', {
32-
method: 'HEAD',
33-
signal: AbortSignal.timeout(2000),
34-
})
35-
return response.ok
36-
} catch {
37-
return false
38-
}
39-
}
40-
4151
// Load and cache HAR file
4252
async function loadHar(key: keyof typeof PAGES): Promise<Har> {
4353
if (harCache.has(key)) {
@@ -65,16 +75,6 @@ app.get('/', async (_req, res) => {
6575
const harDir = path.join(__dirname, 'har')
6676
const files = await fs.readdir(harDir)
6777
const harFiles = files.filter((file) => file.endsWith('.har'))
68-
const devServerRunning = await checkDevServer()
69-
70-
const devServerWarning = !devServerRunning
71-
? `
72-
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; padding: 15px; margin-bottom: 20px;">
73-
<strong>⚠️ Warning:</strong> WXT dev server is not running on localhost:3000<br>
74-
<small>Gitcasso-enabled links won't work. Run <code>npm run dev</code> to start the server and <strong>then refresh this page</strong>.</small>
75-
</div>
76-
`
77-
: ''
7878

7979
const links = harFiles
8080
.map((file) => {
@@ -84,9 +84,7 @@ app.get('/', async (_req, res) => {
8484
<div style="margin-bottom: 10px; font-weight: bold; color: #555;">${basename}</div>
8585
<div style="display: flex; gap: 10px;">
8686
<a href="/har/${basename}/clean" style="flex: 1; text-align: center;">🔍 Clean</a>
87-
<a href="/har/${basename}/gitcasso" style="flex: 1; text-align: center; ${!devServerRunning ? 'opacity: 0.5; pointer-events: none;' : ''}">
88-
🚀 Gitcasso-enabled
89-
</a>
87+
<a href="/har/${basename}/gitcasso" style="flex: 1; text-align: center;">🚀 Gitcasso</a>
9088
</div>
9189
</li>
9290
`
@@ -124,7 +122,6 @@ app.get('/', async (_req, res) => {
124122
</head>
125123
<body>
126124
<h1>📄 HAR Page Viewer</h1>
127-
${devServerWarning}
128125
<p>Select a recorded page to view:</p>
129126
<ul>${links}</ul>
130127
</body>
@@ -245,6 +242,64 @@ app.get('/asset/:key/*', async (req, res) => {
245242
return res.status(404).send('Asset not found')
246243
}
247244
})
245+
// Serve extension assets from filesystem
246+
app.use('/chrome-mv3-dev', express.static(path.join(__dirname, '..', '.output', 'chrome-mv3-dev')))
247+
248+
// Rebuild endpoint
249+
app.post('/rebuild', async (_req, res) => {
250+
try {
251+
console.log('Rebuild triggered via API')
252+
253+
// Run npx wxt build --mode development
254+
const buildProcess = spawn('npx', ['wxt', 'build', '--mode', 'development'], {
255+
cwd: path.join(__dirname, '..'),
256+
stdio: ['pipe', 'pipe', 'pipe'],
257+
})
258+
259+
let stdout = ''
260+
let stderr = ''
261+
262+
buildProcess.stdout.on('data', (data) => {
263+
stdout += data.toString()
264+
console.log('[BUILD]', data.toString().trim())
265+
})
266+
267+
buildProcess.stderr.on('data', (data) => {
268+
stderr += data.toString()
269+
console.error('[BUILD ERROR]', data.toString().trim())
270+
})
271+
272+
buildProcess.on('close', (code) => {
273+
if (code === 0) {
274+
console.log('Build completed successfully')
275+
res.json({ message: 'Build completed successfully', success: true })
276+
} else {
277+
console.error('Build failed with code:', code)
278+
res.status(500).json({
279+
error: stderr || stdout,
280+
message: 'Build failed',
281+
success: false,
282+
})
283+
}
284+
})
285+
286+
buildProcess.on('error', (error) => {
287+
console.error('Failed to start build process:', error)
288+
res.status(500).json({
289+
error: error.message,
290+
message: 'Failed to start build process',
291+
success: false,
292+
})
293+
})
294+
} catch (error) {
295+
console.error('Rebuild endpoint error:', error)
296+
res.status(500).json({
297+
error: error instanceof Error ? error.message : 'Unknown error',
298+
message: 'Internal server error',
299+
success: false,
300+
})
301+
}
302+
})
248303

249304
app.listen(PORT, () => {
250305
console.log(`HAR Page Viewer running at http://localhost:${PORT}`)
@@ -271,7 +326,7 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) {
271326
});
272327
273328
// Fetch and patch the content script to remove webextension-polyfill issues
274-
fetch('http://localhost:3000/.output/chrome-mv3-dev/content-scripts/content.js')
329+
fetch('/chrome-mv3-dev/content-scripts/content.js')
275330
.then(response => response.text())
276331
.then(code => {
277332
console.log('Fetched content script, patching webextension-polyfill...');
@@ -303,6 +358,90 @@ function injectGitcassoScript(key: keyof typeof PAGES, html: string) {
303358
.catch(error => {
304359
console.error('Failed to load and patch content script:', error);
305360
});
361+
362+
// Create floating rebuild button
363+
const rebuildButton = document.createElement('div');
364+
rebuildButton.id = 'gitcasso-rebuild-btn';
365+
rebuildButton.innerHTML = '🔄';
366+
rebuildButton.title = 'Rebuild Extension';
367+
rebuildButton.style.cssText = \`
368+
position: fixed;
369+
top: 20px;
370+
right: 20px;
371+
width: 50px;
372+
height: 50px;
373+
background: #007acc;
374+
color: white;
375+
border-radius: 50%;
376+
display: flex;
377+
align-items: center;
378+
justify-content: center;
379+
font-size: 20px;
380+
cursor: pointer;
381+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
382+
z-index: 999999;
383+
user-select: none;
384+
transition: all 0.2s ease;
385+
font-family: system-ui, -apple-system, sans-serif;
386+
\`;
387+
388+
rebuildButton.addEventListener('mouseenter', () => {
389+
rebuildButton.style.transform = 'scale(1.1)';
390+
rebuildButton.style.backgroundColor = '#005a9e';
391+
});
392+
393+
rebuildButton.addEventListener('mouseleave', () => {
394+
rebuildButton.style.transform = 'scale(1)';
395+
rebuildButton.style.backgroundColor = '#007acc';
396+
});
397+
398+
rebuildButton.addEventListener('click', async () => {
399+
try {
400+
rebuildButton.innerHTML = '⏳';
401+
rebuildButton.style.backgroundColor = '#ffa500';
402+
rebuildButton.title = 'Rebuilding...';
403+
404+
const response = await fetch('/rebuild', {
405+
method: 'POST',
406+
headers: { 'Content-Type': 'application/json' }
407+
});
408+
409+
const result = await response.json();
410+
411+
if (result.success) {
412+
rebuildButton.innerHTML = '✅';
413+
rebuildButton.style.backgroundColor = '#28a745';
414+
rebuildButton.title = 'Build successful! Reloading...';
415+
416+
setTimeout(() => {
417+
location.reload(true);
418+
}, 1000);
419+
} else {
420+
rebuildButton.innerHTML = '❌';
421+
rebuildButton.style.backgroundColor = '#dc3545';
422+
rebuildButton.title = 'Build failed: ' + (result.error || result.message);
423+
424+
setTimeout(() => {
425+
rebuildButton.innerHTML = '🔄';
426+
rebuildButton.style.backgroundColor = '#007acc';
427+
rebuildButton.title = 'Rebuild Extension';
428+
}, 3000);
429+
}
430+
} catch (error) {
431+
console.error('Rebuild failed:', error);
432+
rebuildButton.innerHTML = '❌';
433+
rebuildButton.style.backgroundColor = '#dc3545';
434+
rebuildButton.title = 'Network error: ' + error.message;
435+
436+
setTimeout(() => {
437+
rebuildButton.innerHTML = '🔄';
438+
rebuildButton.style.backgroundColor = '#007acc';
439+
rebuildButton.title = 'Rebuild Extension';
440+
}, 3000);
441+
}
442+
});
443+
444+
document.body.appendChild(rebuildButton);
306445
</script>
307446
`
308447
if (!html.includes('</body>')) {

0 commit comments

Comments
 (0)