Skip to content

Commit cb77ec5

Browse files
committed
1.0.0
1 parent 8607b47 commit cb77ec5

23 files changed

Lines changed: 5127 additions & 2 deletions

.editorconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[*]
4+
indent_style = tab
5+
indent_size = 4
6+
max_line_length = 180
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
releases/
4+
.vscode/
5+
.build.ps1

.prettierignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
**/node_modules
2+
**/dist
3+
**/releases
4+
**/.vscode
5+
.build.ps1

README.md

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,145 @@
1-
# htmx-debugger
2-
a Chrome extension debugging tool for htmx applications
1+
# htmx-debugger v1.0.0 - 2024-09-30
2+
3+
## Overview
4+
5+
htmx-debugger is a powerful Chrome extension designed to help developers debug and analyze htmx applications. It provides a comprehensive and user-friendly interface for viewing htmx events, requests, and responses in real-time. This tool is essential for understanding and troubleshooting htmx-powered web applications, making the development process smoother and more efficient.
6+
7+
## Features
8+
9+
- Real-time capture and display of htmx events
10+
- Intelligent grouping of related events for easier analysis
11+
- Powerful search functionality to filter events
12+
- One-click clear button to reset the debugger view
13+
- Flexible event filtering (All, Request, Response)
14+
- Collapsible event details for a cleaner interface
15+
- Detailed timing information for request-response cycles
16+
- Live connection status indicator
17+
- Automatic periodic connection checks for improved stability
18+
- Robust error handling and reporting
19+
20+
## Future
21+
22+
- Export functionality to save captured events as a JSON file
23+
- Import capability to load and analyze events from a JSON file
24+
25+
## Installation
26+
27+
### Chrome Web Store (Recommended)
28+
29+
1. Visit the [htmx-debugger page](https://chrome.google.com/webstore/detail/htmx-debugger/[extension-id]) on the Chrome Web Store.
30+
2. Click the "Add to Chrome" button.
31+
3. Confirm the installation when prompted.
32+
33+
### Manual Installation (For developers)
34+
35+
1. Clone this repository or download the source code.
36+
2. Open Google Chrome and navigate to `chrome://extensions/`.
37+
3. Enable "Developer mode" by toggling the switch in the top right corner.
38+
4. Click on "Load unpacked" and select the directory containing the extension files.
39+
5. The htmx-debugger extension should now appear in your list of installed extensions.
40+
41+
## Usage
42+
43+
### Opening the Debugger
44+
45+
1. Navigate to a webpage that uses htmx.
46+
2. Open Chrome DevTools (Right-click > Inspect or press F12).
47+
3. Look for the "htmx" tab in the DevTools panel. If you don't see it, click on the ">>" icon to find it in the list of additional tools.
48+
49+
### Debugging htmx Events
50+
51+
The debugger automatically captures htmx events as they occur on the page. Each event is displayed with the following information:
52+
53+
- Event type
54+
- Timestamp
55+
- Target element details
56+
- Event-specific details
57+
- XHR information (for relevant events)
58+
59+
### Using the Debugger Features
60+
61+
1. **Connection Status**: Check the connection status indicator in the panel header to ensure the debugger is properly connected.
62+
2. **Search**: Use the search bar at the top to filter events based on their content.
63+
3. **Clear**: Click the "Clear" button to reset the debugger and remove all captured events.
64+
4. **Event Filtering**: Use the filter buttons to show all events, only requests, or only responses.
65+
5. **Expand/Collapse**: Click on an event header to expand or collapse its details.
66+
6. **Timing Information**: For grouped events, the total duration is displayed at the bottom of the group.
67+
68+
## Troubleshooting
69+
70+
- If you don't see any events in the debugger, ensure that the webpage you're debugging is actually using htmx.
71+
- If the htmx tab doesn't appear in DevTools, try reloading the page or reopening DevTools.
72+
- Check the browser console for any error messages related to the extension.
73+
- If the connection status shows "Disconnected", try reloading the page.
74+
- If problems persist, try disabling and re-enabling the extension, reloading the webpage, or restarting Chrome.
75+
76+
## Development
77+
78+
### Key File Structure
79+
80+
- `background.js`: Background script for message handling and service worker setup
81+
- `content.js`: Content script for capturing htmx events and communication with background script
82+
- `devtools.html`: DevTools panel HTML
83+
- `devtools.js`: DevTools panel functionality
84+
- `manifest.json`: Extension configuration, permission or configuration changes
85+
- `panel.html`: Panel HTML for displaying captured events and debugger controls
86+
- `panel.js`: Panel functionality for handling event display and debugger features
87+
88+
There are many other build/development-related files in addtion to those listed above. If you're contributing or modifying for your own purposes you should recgonize these.
89+
90+
## Privacy Policy
91+
92+
htmx-debugger is committed to protecting user privacy and does not collect or use any personal user data. The extension operates solely within the context of the user's browser to provide debugging functionality for htmx applications.
93+
94+
### Permissions and Their Justifications
95+
96+
1. **activeTab**: This permission is required to access the current tab's content and inject the necessary scripts for debugging htmx events. It allows the extension to interact with the webpage being debugged without requiring broader permissions.
97+
98+
2. **alarms**: The alarms permission is used to schedule periodic connection checks, ensuring the debugger maintains a stable connection to the webpage. This improves the reliability of the debugging process.
99+
100+
3. **host permission**: Host permissions are necessary to allow the extension to function on any webpage that uses htmx. This broad access is required because htmx can be used on any website, and the debugger needs to be able to capture events regardless of the domain.
101+
102+
4. **remote code**: The extension does not execute any remote code. All functionality is contained within the extension package.
103+
104+
5. **scripting**: Scripting permissions are essential for the core functionality of the debugger. They allow the extension to inject the necessary scripts to capture and analyze htmx events on the webpage.
105+
106+
6. **storage**: The storage permission is used to save user preferences and debugging session data locally. This ensures that the user's debugging environment persists between browser sessions and allows for future features like saving and loading debug logs.
107+
108+
### Single Purpose Description
109+
110+
The single purpose of htmx-debugger is to provide a comprehensive debugging tool for web developers working with htmx-powered applications. It captures, displays, and analyzes htmx events in real-time, offering insights into the behavior and performance of htmx interactions within web pages.
111+
112+
### Data Usage Compliance
113+
114+
htmx-debugger complies with the Chrome Web Store Developer Program Policies regarding data usage. The extension:
115+
116+
- Does not collect any personal user data
117+
- Does not transmit any captured debugging information outside of the user's browser
118+
- Only processes information necessary for its core debugging functionality
119+
- Stores data locally and solely for the purpose of improving the user's debugging experience
120+
121+
By using htmx-debugger, users can be assured that their privacy is respected and that the extension operates within the bounds of its stated purpose as a development tool for htmx applications.
122+
123+
## Version History
124+
125+
- v1.0.0 (2024-09-30): Initial release
126+
- core features implemented
127+
- Chrome Web Store release
128+
129+
For a detailed changelog, please check the repository's commit log.
130+
131+
## License
132+
133+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
134+
135+
## Contact
136+
137+
For any questions, support, or feedback, please contact the author through standard channels.
138+
139+
## Repository
140+
141+
The source code for this project is available on GitHub:
142+
143+
https://github.com/NomadicDaddy/htmx-debugger
144+
145+
For bug reports or feature requests, please use the [issue tracker](https://github.com/NomadicDaddy/htmx-debugger/issues).

background.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/* eslint-env worker */
2+
3+
let connections = {};
4+
let messageQueue = [];
5+
let isProcessingQueue = false;
6+
let messageCounter = 0;
7+
const MAX_QUEUE_SIZE = 1000;
8+
const RATE_LIMIT_INTERVAL = 1000; // 1 second
9+
const MAX_MESSAGES_PER_INTERVAL = 100;
10+
11+
let heartbeatIntervals = {};
12+
13+
function logWithTimestamp(message, data) {
14+
console.log(`[${new Date().toISOString()}] ${message}`, data);
15+
}
16+
17+
function processMessageQueue() {
18+
if (isProcessingQueue || messageQueue.length === 0) return;
19+
20+
isProcessingQueue = true;
21+
const startTime = Date.now();
22+
let processedCount = 0;
23+
24+
while (messageQueue.length > 0 && processedCount < MAX_MESSAGES_PER_INTERVAL) {
25+
const { message, sender, sendResponse } = messageQueue.shift();
26+
try {
27+
handleMessage(message, sender, sendResponse);
28+
messageCounter++;
29+
} catch (error) {
30+
console.error('Error processing message:', error);
31+
reportError(error);
32+
}
33+
processedCount++;
34+
35+
if (Date.now() - startTime >= RATE_LIMIT_INTERVAL) {
36+
break;
37+
}
38+
}
39+
40+
isProcessingQueue = false;
41+
42+
if (messageQueue.length > 0) {
43+
setTimeout(processMessageQueue, RATE_LIMIT_INTERVAL);
44+
}
45+
46+
logWithTimestamp(`Processed ${processedCount} messages. Total messages: ${messageCounter}`);
47+
}
48+
49+
function queueMessage(message, sender, sendResponse) {
50+
if (messageQueue.length >= MAX_QUEUE_SIZE) {
51+
logWithTimestamp('Message queue full. Dropping oldest message.');
52+
messageQueue.shift();
53+
}
54+
messageQueue.push({ message, sender, sendResponse });
55+
processMessageQueue();
56+
}
57+
58+
function handleMessage(request, sender, sendResponse) {
59+
logWithTimestamp('Processing message:', request);
60+
61+
if (sender.tab) {
62+
const tabId = sender.tab.id;
63+
if (tabId in connections) {
64+
if (request.type === 'HTMX_EVENT' || request.type.startsWith('htmx:')) {
65+
logWithTimestamp(`Forwarding htmx event to panel for tab ${tabId}:`, request);
66+
connections[tabId].postMessage({
67+
type: 'HTMX_EVENT_FOR_PANEL',
68+
data: request.data, // Ensure we're sending the full data
69+
});
70+
logWithTimestamp(`htmx event sent to panel for tab ${tabId}`);
71+
} else {
72+
// Only forward non-CONNECTION_TEST messages to the panel
73+
if (request.type !== 'CONNECTION_TEST') {
74+
logWithTimestamp(`Forwarding message to panel for tab ${tabId}:`, request);
75+
connections[tabId].postMessage(request);
76+
logWithTimestamp(`Message sent to panel for tab ${tabId}`);
77+
} else {
78+
logWithTimestamp(`Received CONNECTION_TEST from tab ${tabId}`);
79+
}
80+
}
81+
} else {
82+
logWithTimestamp('Tab not found in connection list:', tabId);
83+
}
84+
} else if (request.type === 'TEST') {
85+
logWithTimestamp('Received test message:', request);
86+
Object.values(connections).forEach((port) => {
87+
port.postMessage(request);
88+
logWithTimestamp('Test message sent to panel');
89+
});
90+
} else {
91+
logWithTimestamp('sender.tab not defined and not a test message.');
92+
}
93+
94+
// Always send a response to avoid timeouts
95+
if (sendResponse) {
96+
sendResponse({ status: 'Message processed' });
97+
}
98+
}
99+
100+
function startHeartbeat(tabId) {
101+
if (heartbeatIntervals[tabId]) {
102+
clearInterval(heartbeatIntervals[tabId]);
103+
}
104+
heartbeatIntervals[tabId] = setInterval(() => {
105+
// Changed to setInterval (self is already recognized)
106+
if (connections[tabId]) {
107+
connections[tabId].postMessage({ type: 'HEARTBEAT' });
108+
} else {
109+
clearInterval(heartbeatIntervals[tabId]);
110+
delete heartbeatIntervals[tabId];
111+
}
112+
}, 5000); // Send heartbeat every 5 seconds
113+
}
114+
115+
// Listen for connections from the devtools panel
116+
chrome.runtime.onConnect.addListener(function (port) {
117+
if (port.name !== 'panel') return;
118+
119+
const extensionListener = function (message) {
120+
if (message.name === 'init') {
121+
connections[message.tabId] = port;
122+
logWithTimestamp(`Panel connected for tab ${message.tabId}`);
123+
startHeartbeat(message.tabId);
124+
}
125+
};
126+
127+
port.onMessage.addListener(extensionListener);
128+
129+
port.onDisconnect.addListener(function (disconnectedPort) {
130+
port.onMessage.removeListener(extensionListener);
131+
const tabs = Object.keys(connections);
132+
for (let i = 0, len = tabs.length; i < len; i++) {
133+
if (connections[tabs[i]] === disconnectedPort) {
134+
logWithTimestamp(`Panel disconnected for tab ${tabs[i]}`);
135+
delete connections[tabs[i]];
136+
clearInterval(heartbeatIntervals[tabs[i]]);
137+
delete heartbeatIntervals[tabs[i]];
138+
break;
139+
}
140+
}
141+
});
142+
});
143+
144+
// Listen for messages from content scripts
145+
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
146+
// Use queueMessage instead of directly calling handleMessage
147+
queueMessage(request, sender, sendResponse);
148+
return true; // Indicate that the response is sent asynchronously
149+
});
150+
151+
logWithTimestamp('Background service worker loaded');
152+
153+
// Keep the service worker alive and manage periodic tasks
154+
chrome.runtime.onInstalled.addListener(() => {
155+
console.log('Extension installed or updated');
156+
chrome.alarms.create('keep-alive', { periodInMinutes: 1 });
157+
chrome.alarms.create('reset-counter', { periodInMinutes: 60 }); // Reset counter every hour
158+
chrome.alarms.create('log-stats', { periodInMinutes: 5 }); // Log stats every 5 minutes
159+
});
160+
161+
chrome.runtime.onUpdateAvailable.addListener(() => {
162+
console.log('Extension update available. Reloading...');
163+
chrome.runtime.reload();
164+
});
165+
166+
chrome.alarms.onAlarm.addListener((alarm) => {
167+
switch (alarm.name) {
168+
case 'keep-alive':
169+
logWithTimestamp('Keep-alive ping');
170+
break;
171+
case 'reset-counter':
172+
logWithTimestamp(`Resetting message counter. Previous count: ${messageCounter}`);
173+
messageCounter = 0;
174+
break;
175+
case 'log-stats':
176+
logWithTimestamp(`Current message count: ${messageCounter}`);
177+
logWithTimestamp(`Current queue size: ${messageQueue.length}`);
178+
break;
179+
}
180+
});
181+
182+
function reportError(error) {
183+
console.error('Error in background script:', error);
184+
Object.values(connections).forEach((port) => {
185+
port.postMessage({ type: 'ERROR', error: error.message, stack: error.stack });
186+
});
187+
}
188+
189+
// Global error handling
190+
globalThis.addEventListener('error', (event) => {
191+
// Changed from self to globalThis
192+
reportError(event.error);
193+
});
194+
195+
globalThis.addEventListener('unhandledrejection', (event) => {
196+
// Changed from self to globalThis
197+
reportError(event.reason);
198+
});
199+
200+
// Example of avoiding window usage in background script
201+
chrome.runtime.onInstalled.addListener(() => {
202+
console.log('Extension installed');
203+
});
204+
205+
// If you need to communicate with content scripts or popup
206+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
207+
if (message.type === 'heartbeat') {
208+
// Handle heartbeat message
209+
sendResponse({ status: 'alive' });
210+
}
211+
212+
if (message.type === 'HTMX_EVENT') {
213+
chrome.runtime.sendMessage({
214+
type: 'HTMX_EVENT_FOR_PANEL',
215+
data: message.data,
216+
});
217+
}
218+
});

0 commit comments

Comments
 (0)