logbubble 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/auto.ts +4 -0
- package/core/console.ts +79 -0
- package/core/dom.ts +33 -0
- package/core/fetch.ts +41 -0
- package/core/xhr.ts +38 -0
- package/dist/auto.d.ts +1 -0
- package/dist/auto.js +4 -0
- package/dist/core/console.d.ts +1 -0
- package/dist/core/console.js +76 -0
- package/dist/core/dom.d.ts +1 -0
- package/dist/core/dom.js +26 -0
- package/dist/core/fetch.d.ts +1 -0
- package/dist/core/fetch.js +34 -0
- package/dist/core/xhr.d.ts +1 -0
- package/dist/core/xhr.js +34 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +34 -0
- package/dist/ui/logStore.d.ts +16 -0
- package/dist/ui/logStore.js +36 -0
- package/dist/ui/logUI.d.ts +22 -0
- package/dist/ui/logUI.js +363 -0
- package/init.ts +41 -0
- package/package.json +37 -0
- package/ui/logStore.ts +49 -0
- package/ui/logUI.ts +395 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LogBubble Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# LogBubble 🫧
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/logbubble)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/dktiwary007/LogBubble)
|
|
6
|
+
|
|
7
|
+
A lightweight, floating log viewer for monitoring network requests and console logs in web applications. Perfect for debugging web apps, Capacitor apps, and Progressive Web Apps (PWAs) directly on mobile devices or in production-like environments.
|
|
8
|
+
|
|
9
|
+
[View on npm](https://www.npmjs.com/package/logbubble)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✨ **Floating UI**: Non-intrusive bubble interface that stays accessible without blocking your app
|
|
14
|
+
🌐 **Network Monitoring**: Automatically logs `fetch`, `XMLHttpRequest`, and dynamic script/link loading
|
|
15
|
+
📝 **Console Logging**: Captures `console.log`, `console.warn`, `console.error`, `console.info`, and `console.debug`
|
|
16
|
+
🔔 **Unread Badge**: Shows count of new logs since last view
|
|
17
|
+
📋 **Copy to Clipboard**: Export all logs with timestamps
|
|
18
|
+
🔄 **Auto-initialization**: Works out of the box with zero configuration
|
|
19
|
+
🎨 **Clean UI**: Modern, minimalist design with color-coded log types
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install LogBubble from npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install logbubble
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or with yarn:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add logbubble
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Auto-initialization (Recommended)
|
|
38
|
+
|
|
39
|
+
Simply import LogBubble and it will automatically initialize:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
import "logbubble";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The floating bubble will appear in the top-right corner of your app.
|
|
46
|
+
|
|
47
|
+
### Manual initialization
|
|
48
|
+
|
|
49
|
+
If you want more control over when LogBubble initializes:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
import { initNetworkLogger } from "logbubble";
|
|
53
|
+
|
|
54
|
+
// Initialize when ready
|
|
55
|
+
initNetworkLogger();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Note:** LogBubble has a built-in guard against duplicate initialization, so calling `initNetworkLogger()` multiple times is safe.
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
### React (Vite/CRA)
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// main.jsx or index.js
|
|
66
|
+
import React from "react";
|
|
67
|
+
import ReactDOM from "react-dom/client";
|
|
68
|
+
import App from "./App";
|
|
69
|
+
import "logbubble"; // Add this line
|
|
70
|
+
|
|
71
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
72
|
+
<React.StrictMode>
|
|
73
|
+
<App />
|
|
74
|
+
</React.StrictMode>,
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Capacitor Apps
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// main.jsx
|
|
82
|
+
import React from "react";
|
|
83
|
+
import ReactDOM from "react-dom/client";
|
|
84
|
+
import App from "./App";
|
|
85
|
+
import { initNetworkLogger } from "logbubble";
|
|
86
|
+
|
|
87
|
+
// Initialize LogBubble for mobile debugging
|
|
88
|
+
initNetworkLogger();
|
|
89
|
+
|
|
90
|
+
ReactDOM.createRoot(document.getElementById("root")).render(
|
|
91
|
+
<React.StrictMode>
|
|
92
|
+
<App />
|
|
93
|
+
</React.StrictMode>,
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Vanilla JavaScript
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<!DOCTYPE html>
|
|
101
|
+
<html>
|
|
102
|
+
<head>
|
|
103
|
+
<title>My App</title>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<script type="module">
|
|
107
|
+
import "logbubble";
|
|
108
|
+
|
|
109
|
+
// Your app code
|
|
110
|
+
fetch("https://api.example.com/data")
|
|
111
|
+
.then((res) => res.json())
|
|
112
|
+
.then((data) => console.log("Data:", data));
|
|
113
|
+
</script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## What Gets Logged?
|
|
119
|
+
|
|
120
|
+
LogBubble automatically intercepts and logs:
|
|
121
|
+
|
|
122
|
+
- **Fetch requests**: `[NET] GET https://api.example.com/users 200 145ms`
|
|
123
|
+
- **XHR requests**: `[NET] POST https://api.example.com/login 201 312ms`
|
|
124
|
+
- **Dynamic scripts/links**: `[NET] SCRIPT https://cdn.example.com/lib.js LOADED`
|
|
125
|
+
- **Console messages**: `[LOG] User logged in`, `[WARN] Connection slow`, `[ERROR] Failed to load`
|
|
126
|
+
|
|
127
|
+
## UI Controls
|
|
128
|
+
|
|
129
|
+
- **Bubble**: Click to toggle the log window
|
|
130
|
+
- **Badge**: Shows unread log count (disappears when window is opened)
|
|
131
|
+
- **Clear**: Remove all logs
|
|
132
|
+
- **Copy**: Copy all logs to clipboard with timestamps
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
### Build
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm run build
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Watch mode
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run dev
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Project Structure
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
LogBubble/
|
|
152
|
+
├── core/ # Patching logic for fetch, XHR, DOM, console
|
|
153
|
+
├── ui/ # Floating UI and log store
|
|
154
|
+
├── init.ts # Main entry point with auto-init
|
|
155
|
+
├── auto.ts # Side-effect entry point
|
|
156
|
+
├── dist/ # Compiled output
|
|
157
|
+
└── package.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## API
|
|
161
|
+
|
|
162
|
+
### `initNetworkLogger()`
|
|
163
|
+
|
|
164
|
+
Initializes LogBubble and starts intercepting network requests and console logs. Safe to call multiple times (duplicate calls are ignored).
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
import { initNetworkLogger } from "logbubble";
|
|
168
|
+
initNetworkLogger();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Global Access
|
|
172
|
+
|
|
173
|
+
When in a browser environment, LogBubble exposes itself globally:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// Available on window object
|
|
177
|
+
window.initNetworkLogger();
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Browser Support
|
|
181
|
+
|
|
182
|
+
LogBubble works in all modern browsers and WebView environments:
|
|
183
|
+
|
|
184
|
+
- ✅ Chrome/Edge (Chromium)
|
|
185
|
+
- ✅ Firefox
|
|
186
|
+
- ✅ Safari
|
|
187
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
188
|
+
- ✅ Capacitor WebView
|
|
189
|
+
- ✅ Cordova WebView
|
|
190
|
+
|
|
191
|
+
## Use Cases
|
|
192
|
+
|
|
193
|
+
- 🐛 **Mobile Debugging**: Debug network issues on real devices without DevTools
|
|
194
|
+
- 🚀 **Production Testing**: Monitor requests in production-like environments
|
|
195
|
+
- 📱 **Capacitor/PWA Development**: Essential for debugging hybrid mobile apps
|
|
196
|
+
- 🧪 **QA Testing**: Allow testers to capture and share logs easily
|
|
197
|
+
- 📊 **Performance Monitoring**: Track request timing and errors
|
|
198
|
+
|
|
199
|
+
## Known Limitations
|
|
200
|
+
|
|
201
|
+
- Browser-only (requires `window` and DOM APIs)
|
|
202
|
+
- Logs are stored in memory only (cleared on page refresh)
|
|
203
|
+
- Maximum 500 logs retained (older logs are automatically removed)
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
### UI not appearing
|
|
208
|
+
|
|
209
|
+
Ensure LogBubble is imported at the entry point of your application (before React renders or before any other scripts run).
|
|
210
|
+
|
|
211
|
+
### Duplicate logs
|
|
212
|
+
|
|
213
|
+
If you see duplicate logs, check that `initNetworkLogger()` is not being called multiple times. LogBubble has a guard against this, but if you call it manually AND import the auto-init module, it may appear twice.
|
|
214
|
+
|
|
215
|
+
### Not capturing network requests
|
|
216
|
+
|
|
217
|
+
Make sure your app is using native `fetch` or `XMLHttpRequest` APIs. Some custom HTTP clients may not be automatically patched.
|
|
218
|
+
|
|
219
|
+
## Support
|
|
220
|
+
|
|
221
|
+
For issues, feature requests, or questions, please open an issue on [GitHub](https://github.com/dktiwary007/LogBubble/issues).
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT
|
|
226
|
+
|
|
227
|
+
## Contributing
|
|
228
|
+
|
|
229
|
+
Contributions are welcome! Please feel free to submit issues or pull requests on [GitHub](https://github.com/dktiwary007/LogBubble).
|
|
230
|
+
|
|
231
|
+
### Getting Started with Development
|
|
232
|
+
|
|
233
|
+
If you want to contribute to LogBubble:
|
|
234
|
+
|
|
235
|
+
1. Clone the repository
|
|
236
|
+
2. Install dependencies: `npm install`
|
|
237
|
+
3. Build the project: `npm run build`
|
|
238
|
+
4. Watch for changes: `npm run dev`
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
Built with ❤️ for developers who need better mobile debugging tools.
|
|
243
|
+
|
|
244
|
+
**Made by [Deepak Kumar](https://github.com/dktiwary007)**
|
package/auto.ts
ADDED
package/core/console.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// console.ts
|
|
2
|
+
// Patches console methods to log to the network logger UI
|
|
3
|
+
|
|
4
|
+
import { logStore } from "../ui/logStore";
|
|
5
|
+
|
|
6
|
+
let isPatching = false;
|
|
7
|
+
|
|
8
|
+
export function patchConsole() {
|
|
9
|
+
if (typeof window === "undefined" || typeof console === "undefined") return;
|
|
10
|
+
|
|
11
|
+
const originalLog = console.log;
|
|
12
|
+
const originalWarn = console.warn;
|
|
13
|
+
const originalError = console.error;
|
|
14
|
+
const originalInfo = console.info;
|
|
15
|
+
const originalDebug = console.debug;
|
|
16
|
+
|
|
17
|
+
console.log = function (...args: any[]) {
|
|
18
|
+
originalLog.apply(console, args);
|
|
19
|
+
if (!isPatching) {
|
|
20
|
+
isPatching = true;
|
|
21
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
22
|
+
logStore.addLog(`[LOG] ${message}`, "plugin");
|
|
23
|
+
isPatching = false;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
console.warn = function (...args: any[]) {
|
|
28
|
+
originalWarn.apply(console, args);
|
|
29
|
+
if (!isPatching) {
|
|
30
|
+
isPatching = true;
|
|
31
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
32
|
+
logStore.addLog(`[WARN] ${message}`, "plugin");
|
|
33
|
+
isPatching = false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
console.error = function (...args: any[]) {
|
|
38
|
+
originalError.apply(console, args);
|
|
39
|
+
if (!isPatching) {
|
|
40
|
+
isPatching = true;
|
|
41
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
42
|
+
logStore.addLog(`[ERROR] ${message}`, "plugin");
|
|
43
|
+
isPatching = false;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
console.info = function (...args: any[]) {
|
|
48
|
+
originalInfo.apply(console, args);
|
|
49
|
+
if (!isPatching) {
|
|
50
|
+
isPatching = true;
|
|
51
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
52
|
+
logStore.addLog(`[INFO] ${message}`, "plugin");
|
|
53
|
+
isPatching = false;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
console.debug = function (...args: any[]) {
|
|
58
|
+
originalDebug.apply(console, args);
|
|
59
|
+
if (!isPatching) {
|
|
60
|
+
isPatching = true;
|
|
61
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
62
|
+
logStore.addLog(`[DEBUG] ${message}`, "plugin");
|
|
63
|
+
isPatching = false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatArg(arg: any): string {
|
|
69
|
+
if (arg === null) return "null";
|
|
70
|
+
if (arg === undefined) return "undefined";
|
|
71
|
+
if (typeof arg === "string") return arg;
|
|
72
|
+
if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
|
|
73
|
+
if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
|
|
74
|
+
try {
|
|
75
|
+
return JSON.stringify(arg);
|
|
76
|
+
} catch {
|
|
77
|
+
return String(arg);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/core/dom.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// dom.ts
|
|
2
|
+
// (Optional) Patch dynamic <script> and <link> loading for network logging
|
|
3
|
+
|
|
4
|
+
import { logStore } from "../ui/logStore";
|
|
5
|
+
|
|
6
|
+
export function patchDOM(logFn: (msg: string) => void) {
|
|
7
|
+
if (typeof window === "undefined") return;
|
|
8
|
+
const origCreateElement = document.createElement;
|
|
9
|
+
document.createElement = function (
|
|
10
|
+
this: Document,
|
|
11
|
+
tag: string,
|
|
12
|
+
options?: ElementCreationOptions,
|
|
13
|
+
) {
|
|
14
|
+
const el = origCreateElement.call(this, tag, options);
|
|
15
|
+
if (tag === "script" || tag === "link") {
|
|
16
|
+
el.addEventListener("load", function () {
|
|
17
|
+
const src =
|
|
18
|
+
(el as HTMLScriptElement).src || (el as HTMLLinkElement).href;
|
|
19
|
+
const message = `[NET] ${tag.toUpperCase()} ${src} LOADED`;
|
|
20
|
+
logFn(message);
|
|
21
|
+
logStore.addLog(message, "dom");
|
|
22
|
+
});
|
|
23
|
+
el.addEventListener("error", function () {
|
|
24
|
+
const src =
|
|
25
|
+
(el as HTMLScriptElement).src || (el as HTMLLinkElement).href;
|
|
26
|
+
const message = `[NET] ${tag.toUpperCase()} ${src} ERROR`;
|
|
27
|
+
logFn(message);
|
|
28
|
+
logStore.addLog(message, "dom");
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return el;
|
|
32
|
+
} as typeof document.createElement;
|
|
33
|
+
}
|
package/core/fetch.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// fetch.ts
|
|
2
|
+
// Patches window.fetch to log network requests in dev mode (browser/WebView only)
|
|
3
|
+
|
|
4
|
+
import { logStore } from "../ui/logStore";
|
|
5
|
+
|
|
6
|
+
export function patchFetch(logFn: (msg: string) => void) {
|
|
7
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function")
|
|
8
|
+
return;
|
|
9
|
+
const originalFetch = window.fetch;
|
|
10
|
+
window.fetch = async function (
|
|
11
|
+
this: typeof globalThis,
|
|
12
|
+
input: RequestInfo | URL,
|
|
13
|
+
init?: RequestInit,
|
|
14
|
+
) {
|
|
15
|
+
const method =
|
|
16
|
+
(init && init.method) ||
|
|
17
|
+
(typeof input === "object" && "method" in input && input.method) ||
|
|
18
|
+
"GET";
|
|
19
|
+
const url =
|
|
20
|
+
typeof input === "string"
|
|
21
|
+
? input
|
|
22
|
+
: input instanceof URL
|
|
23
|
+
? input.href
|
|
24
|
+
: input.url;
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
const response = await originalFetch.call(this, input, init);
|
|
28
|
+
const ms = Date.now() - start;
|
|
29
|
+
const message = `[NET] ${method} ${url} ${response.status} ${ms}ms`;
|
|
30
|
+
logFn(message);
|
|
31
|
+
logStore.addLog(message, "fetch");
|
|
32
|
+
return response;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const ms = Date.now() - start;
|
|
35
|
+
const message = `[NET] ${method} ${url} ERROR ${ms}ms`;
|
|
36
|
+
logFn(message);
|
|
37
|
+
logStore.addLog(message, "fetch");
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
} as typeof window.fetch;
|
|
41
|
+
}
|
package/core/xhr.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// xhr.ts
|
|
2
|
+
// Patches XMLHttpRequest to log network requests in dev mode (browser/WebView only)
|
|
3
|
+
|
|
4
|
+
import { logStore } from "../ui/logStore";
|
|
5
|
+
|
|
6
|
+
export function patchXHR(logFn: (msg: string) => void) {
|
|
7
|
+
if (
|
|
8
|
+
typeof window === "undefined" ||
|
|
9
|
+
typeof window.XMLHttpRequest !== "function"
|
|
10
|
+
)
|
|
11
|
+
return;
|
|
12
|
+
const OriginalXHR = window.XMLHttpRequest;
|
|
13
|
+
function PatchedXHR(this: XMLHttpRequest) {
|
|
14
|
+
const xhr = new OriginalXHR();
|
|
15
|
+
let url = "";
|
|
16
|
+
let method = "";
|
|
17
|
+
let start = 0;
|
|
18
|
+
xhr.open = new Proxy(xhr.open, {
|
|
19
|
+
apply(target, thisArg, args: [string, string | URL, ...any[]]) {
|
|
20
|
+
method = args[0];
|
|
21
|
+
url = typeof args[1] === "string" ? args[1] : args[1].href;
|
|
22
|
+
return Reflect.apply(target, thisArg, args);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
xhr.addEventListener("loadstart", () => {
|
|
26
|
+
start = Date.now();
|
|
27
|
+
});
|
|
28
|
+
xhr.addEventListener("loadend", () => {
|
|
29
|
+
const ms = Date.now() - start;
|
|
30
|
+
const message = `[NET] ${method} ${url} ${xhr.status} ${ms}ms`;
|
|
31
|
+
logFn(message);
|
|
32
|
+
logStore.addLog(message, "xhr");
|
|
33
|
+
});
|
|
34
|
+
return xhr;
|
|
35
|
+
}
|
|
36
|
+
PatchedXHR.prototype = OriginalXHR.prototype;
|
|
37
|
+
window.XMLHttpRequest = PatchedXHR as any;
|
|
38
|
+
}
|
package/dist/auto.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/auto.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function patchConsole(): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// console.ts
|
|
2
|
+
// Patches console methods to log to the network logger UI
|
|
3
|
+
import { logStore } from "../ui/logStore";
|
|
4
|
+
let isPatching = false;
|
|
5
|
+
export function patchConsole() {
|
|
6
|
+
if (typeof window === "undefined" || typeof console === "undefined")
|
|
7
|
+
return;
|
|
8
|
+
const originalLog = console.log;
|
|
9
|
+
const originalWarn = console.warn;
|
|
10
|
+
const originalError = console.error;
|
|
11
|
+
const originalInfo = console.info;
|
|
12
|
+
const originalDebug = console.debug;
|
|
13
|
+
console.log = function (...args) {
|
|
14
|
+
originalLog.apply(console, args);
|
|
15
|
+
if (!isPatching) {
|
|
16
|
+
isPatching = true;
|
|
17
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
18
|
+
logStore.addLog(`[LOG] ${message}`, "plugin");
|
|
19
|
+
isPatching = false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
console.warn = function (...args) {
|
|
23
|
+
originalWarn.apply(console, args);
|
|
24
|
+
if (!isPatching) {
|
|
25
|
+
isPatching = true;
|
|
26
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
27
|
+
logStore.addLog(`[WARN] ${message}`, "plugin");
|
|
28
|
+
isPatching = false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
console.error = function (...args) {
|
|
32
|
+
originalError.apply(console, args);
|
|
33
|
+
if (!isPatching) {
|
|
34
|
+
isPatching = true;
|
|
35
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
36
|
+
logStore.addLog(`[ERROR] ${message}`, "plugin");
|
|
37
|
+
isPatching = false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
console.info = function (...args) {
|
|
41
|
+
originalInfo.apply(console, args);
|
|
42
|
+
if (!isPatching) {
|
|
43
|
+
isPatching = true;
|
|
44
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
45
|
+
logStore.addLog(`[INFO] ${message}`, "plugin");
|
|
46
|
+
isPatching = false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
console.debug = function (...args) {
|
|
50
|
+
originalDebug.apply(console, args);
|
|
51
|
+
if (!isPatching) {
|
|
52
|
+
isPatching = true;
|
|
53
|
+
const message = args.map((arg) => formatArg(arg)).join(" ");
|
|
54
|
+
logStore.addLog(`[DEBUG] ${message}`, "plugin");
|
|
55
|
+
isPatching = false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function formatArg(arg) {
|
|
60
|
+
if (arg === null)
|
|
61
|
+
return "null";
|
|
62
|
+
if (arg === undefined)
|
|
63
|
+
return "undefined";
|
|
64
|
+
if (typeof arg === "string")
|
|
65
|
+
return arg;
|
|
66
|
+
if (typeof arg === "number" || typeof arg === "boolean")
|
|
67
|
+
return String(arg);
|
|
68
|
+
if (arg instanceof Error)
|
|
69
|
+
return `${arg.name}: ${arg.message}`;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(arg);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return String(arg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function patchDOM(logFn: (msg: string) => void): void;
|
package/dist/core/dom.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// dom.ts
|
|
2
|
+
// (Optional) Patch dynamic <script> and <link> loading for network logging
|
|
3
|
+
import { logStore } from "../ui/logStore";
|
|
4
|
+
export function patchDOM(logFn) {
|
|
5
|
+
if (typeof window === "undefined")
|
|
6
|
+
return;
|
|
7
|
+
const origCreateElement = document.createElement;
|
|
8
|
+
document.createElement = function (tag, options) {
|
|
9
|
+
const el = origCreateElement.call(this, tag, options);
|
|
10
|
+
if (tag === "script" || tag === "link") {
|
|
11
|
+
el.addEventListener("load", function () {
|
|
12
|
+
const src = el.src || el.href;
|
|
13
|
+
const message = `[NET] ${tag.toUpperCase()} ${src} LOADED`;
|
|
14
|
+
logFn(message);
|
|
15
|
+
logStore.addLog(message, "dom");
|
|
16
|
+
});
|
|
17
|
+
el.addEventListener("error", function () {
|
|
18
|
+
const src = el.src || el.href;
|
|
19
|
+
const message = `[NET] ${tag.toUpperCase()} ${src} ERROR`;
|
|
20
|
+
logFn(message);
|
|
21
|
+
logStore.addLog(message, "dom");
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return el;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function patchFetch(logFn: (msg: string) => void): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// fetch.ts
|
|
2
|
+
// Patches window.fetch to log network requests in dev mode (browser/WebView only)
|
|
3
|
+
import { logStore } from "../ui/logStore";
|
|
4
|
+
export function patchFetch(logFn) {
|
|
5
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function")
|
|
6
|
+
return;
|
|
7
|
+
const originalFetch = window.fetch;
|
|
8
|
+
window.fetch = async function (input, init) {
|
|
9
|
+
const method = (init && init.method) ||
|
|
10
|
+
(typeof input === "object" && "method" in input && input.method) ||
|
|
11
|
+
"GET";
|
|
12
|
+
const url = typeof input === "string"
|
|
13
|
+
? input
|
|
14
|
+
: input instanceof URL
|
|
15
|
+
? input.href
|
|
16
|
+
: input.url;
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
const response = await originalFetch.call(this, input, init);
|
|
20
|
+
const ms = Date.now() - start;
|
|
21
|
+
const message = `[NET] ${method} ${url} ${response.status} ${ms}ms`;
|
|
22
|
+
logFn(message);
|
|
23
|
+
logStore.addLog(message, "fetch");
|
|
24
|
+
return response;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const ms = Date.now() - start;
|
|
28
|
+
const message = `[NET] ${method} ${url} ERROR ${ms}ms`;
|
|
29
|
+
logFn(message);
|
|
30
|
+
logStore.addLog(message, "fetch");
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function patchXHR(logFn: (msg: string) => void): void;
|
package/dist/core/xhr.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// xhr.ts
|
|
2
|
+
// Patches XMLHttpRequest to log network requests in dev mode (browser/WebView only)
|
|
3
|
+
import { logStore } from "../ui/logStore";
|
|
4
|
+
export function patchXHR(logFn) {
|
|
5
|
+
if (typeof window === "undefined" ||
|
|
6
|
+
typeof window.XMLHttpRequest !== "function")
|
|
7
|
+
return;
|
|
8
|
+
const OriginalXHR = window.XMLHttpRequest;
|
|
9
|
+
function PatchedXHR() {
|
|
10
|
+
const xhr = new OriginalXHR();
|
|
11
|
+
let url = "";
|
|
12
|
+
let method = "";
|
|
13
|
+
let start = 0;
|
|
14
|
+
xhr.open = new Proxy(xhr.open, {
|
|
15
|
+
apply(target, thisArg, args) {
|
|
16
|
+
method = args[0];
|
|
17
|
+
url = typeof args[1] === "string" ? args[1] : args[1].href;
|
|
18
|
+
return Reflect.apply(target, thisArg, args);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
xhr.addEventListener("loadstart", () => {
|
|
22
|
+
start = Date.now();
|
|
23
|
+
});
|
|
24
|
+
xhr.addEventListener("loadend", () => {
|
|
25
|
+
const ms = Date.now() - start;
|
|
26
|
+
const message = `[NET] ${method} ${url} ${xhr.status} ${ms}ms`;
|
|
27
|
+
logFn(message);
|
|
28
|
+
logStore.addLog(message, "xhr");
|
|
29
|
+
});
|
|
30
|
+
return xhr;
|
|
31
|
+
}
|
|
32
|
+
PatchedXHR.prototype = OriginalXHR.prototype;
|
|
33
|
+
window.XMLHttpRequest = PatchedXHR;
|
|
34
|
+
}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initNetworkLogger(): void;
|