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/dist/init.js ADDED
@@ -0,0 +1,34 @@
1
+ // init.ts
2
+ // Entry point for web-only dev-network-logger
3
+ import { patchFetch } from "./core/fetch";
4
+ import { patchXHR } from "./core/xhr";
5
+ import { patchDOM } from "./core/dom";
6
+ import { patchConsole } from "./core/console";
7
+ import { logUI } from "./ui/logUI";
8
+ let isInitialized = false;
9
+ function isDev() {
10
+ return (typeof process !== "undefined" &&
11
+ process.env &&
12
+ (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev"));
13
+ }
14
+ export function initNetworkLogger() {
15
+ if (isInitialized) {
16
+ console.warn("[LogBubble] Already initialized, skipping duplicate init");
17
+ return;
18
+ }
19
+ isInitialized = true;
20
+ logUI.init();
21
+ // Patch fetch/XHR/DOM/Console in dev mode
22
+ patchFetch(() => { });
23
+ patchXHR(() => { });
24
+ patchDOM(() => { });
25
+ patchConsole();
26
+ }
27
+ // Auto-init for browser
28
+ if (typeof window !== "undefined") {
29
+ window.initNetworkLogger = initNetworkLogger;
30
+ // Optionally auto-init in dev
31
+ // if (isDev()) {
32
+ initNetworkLogger();
33
+ // }
34
+ }
@@ -0,0 +1,16 @@
1
+ export interface LogEntry {
2
+ timestamp: number;
3
+ message: string;
4
+ type: "fetch" | "xhr" | "dom" | "plugin";
5
+ }
6
+ declare class LogStore {
7
+ private logs;
8
+ private listeners;
9
+ private maxLogs;
10
+ addLog(message: string, type?: LogEntry["type"]): void;
11
+ getLogs(): LogEntry[];
12
+ clearLogs(): void;
13
+ subscribe(listener: (log: LogEntry) => void): () => void;
14
+ }
15
+ export declare const logStore: LogStore;
16
+ export {};
@@ -0,0 +1,36 @@
1
+ // logStore.ts
2
+ // Global log store and API for collecting logs from JS
3
+ class LogStore {
4
+ constructor() {
5
+ this.logs = [];
6
+ this.listeners = [];
7
+ this.maxLogs = 500;
8
+ }
9
+ addLog(message, type = "fetch") {
10
+ const log = {
11
+ timestamp: Date.now(),
12
+ message,
13
+ type,
14
+ };
15
+ this.logs.push(log);
16
+ // Keep only the last maxLogs entries
17
+ if (this.logs.length > this.maxLogs) {
18
+ this.logs.shift();
19
+ }
20
+ // Notify all listeners
21
+ this.listeners.forEach((listener) => listener(log));
22
+ }
23
+ getLogs() {
24
+ return [...this.logs];
25
+ }
26
+ clearLogs() {
27
+ this.logs = [];
28
+ }
29
+ subscribe(listener) {
30
+ this.listeners.push(listener);
31
+ return () => {
32
+ this.listeners = this.listeners.filter((l) => l !== listener);
33
+ };
34
+ }
35
+ }
36
+ export const logStore = new LogStore();
@@ -0,0 +1,22 @@
1
+ export declare class LogUI {
2
+ private rootEl;
3
+ private buttonEl;
4
+ private badgeEl;
5
+ private backdropEl;
6
+ private windowEl;
7
+ private contentEl;
8
+ private isVisible;
9
+ private unreadCount;
10
+ private unsubscribe;
11
+ init(): void;
12
+ private createUI;
13
+ private attachEventListeners;
14
+ private subscribeToLogs;
15
+ private addLogToUI;
16
+ private toggleWindow;
17
+ private updateBadge;
18
+ private clearLogs;
19
+ private copyLogs;
20
+ destroy(): void;
21
+ }
22
+ export declare const logUI: LogUI;
@@ -0,0 +1,363 @@
1
+ // logUI.ts
2
+ // Vanilla JS floating log UI
3
+ import { logStore } from "./logStore";
4
+ const UI_STYLES = `
5
+ #dev-net-logger-root * {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ padding: 0;
9
+ }
10
+
11
+ #dev-net-logger-button {
12
+ position: fixed;
13
+ top: 10px;
14
+ right: 10px;
15
+ width: 48px;
16
+ height: 48px;
17
+ border-radius: 50%;
18
+ background: #2563eb;
19
+ color: white;
20
+ border: none;
21
+ font-size: 18px;
22
+ font-weight: bold;
23
+ cursor: pointer;
24
+ z-index: 999999;
25
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ font-family: monospace;
30
+ }
31
+
32
+ #dev-net-logger-button:active {
33
+ transform: scale(0.95);
34
+ }
35
+
36
+ #dev-net-logger-badge {
37
+ position: absolute;
38
+ top: -4px;
39
+ right: -4px;
40
+ min-width: 18px;
41
+ height: 18px;
42
+ border-radius: 9px;
43
+ background: #ef4444;
44
+ color: white;
45
+ font-size: 10px;
46
+ font-weight: bold;
47
+ display: none;
48
+ align-items: center;
49
+ justify-content: center;
50
+ padding: 0 4px;
51
+ font-family: monospace;
52
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
53
+ }
54
+
55
+ #dev-net-logger-badge.visible {
56
+ display: flex;
57
+ }
58
+
59
+ #dev-net-logger-backdrop {
60
+ position: fixed;
61
+ top: 0;
62
+ left: 0;
63
+ right: 0;
64
+ bottom: 0;
65
+ background: rgba(0, 0, 0, 0.4);
66
+ z-index: 999997;
67
+ display: none;
68
+ }
69
+
70
+ #dev-net-logger-backdrop.visible {
71
+ display: block;
72
+ }
73
+
74
+ #dev-net-logger-window {
75
+ position: fixed;
76
+ top: 70px;
77
+ right: 10px;
78
+ width: calc(100vw - 20px);
79
+ max-width: 500px;
80
+ height: 60vh;
81
+ background: rgba(0, 0, 0, 0.95);
82
+ border-radius: 8px;
83
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
84
+ z-index: 999998;
85
+ display: none;
86
+ flex-direction: column;
87
+ overflow: hidden;
88
+ }
89
+
90
+ #dev-net-logger-window.visible {
91
+ display: flex;
92
+ }
93
+
94
+ #dev-net-logger-header {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ align-items: center;
98
+ padding: 12px 16px;
99
+ background: #1e293b;
100
+ border-bottom: 1px solid #334155;
101
+ }
102
+
103
+ #dev-net-logger-title {
104
+ color: #f1f5f9;
105
+ font-size: 14px;
106
+ font-weight: bold;
107
+ font-family: monospace;
108
+ }
109
+
110
+ #dev-net-logger-controls {
111
+ display: flex;
112
+ gap: 8px;
113
+ }
114
+
115
+ #dev-net-logger-controls button {
116
+ padding: 4px 8px;
117
+ font-size: 11px;
118
+ background: #334155;
119
+ color: #f1f5f9;
120
+ border: none;
121
+ border-radius: 4px;
122
+ cursor: pointer;
123
+ font-family: monospace;
124
+ }
125
+
126
+ #dev-net-logger-controls button:active {
127
+ background: #475569;
128
+ }
129
+
130
+ #dev-net-logger-content {
131
+ flex: 1;
132
+ overflow-y: auto;
133
+ padding: 8px;
134
+ font-family: monospace;
135
+ font-size: 11px;
136
+ line-height: 1.4;
137
+ color: #e2e8f0;
138
+ }
139
+
140
+ .log-entry {
141
+ padding: 6px 8px;
142
+ margin-bottom: 4px;
143
+ border-radius: 4px;
144
+ background: #0f172a;
145
+ border-left: 3px solid #64748b;
146
+ word-break: break-word;
147
+ overflow-wrap: break-word;
148
+ display: flex;
149
+ flex-wrap: wrap;
150
+ align-items: baseline;
151
+ }
152
+
153
+ .log-entry.type-fetch {
154
+ border-left-color: #3b82f6;
155
+ }
156
+
157
+ .log-entry.type-xhr {
158
+ border-left-color: #8b5cf6;
159
+ }
160
+
161
+ .log-entry.type-dom {
162
+ border-left-color: #f59e0b;
163
+ }
164
+
165
+ .log-entry.type-plugin {
166
+ border-left-color: #ec4899;
167
+ }
168
+
169
+ .log-timestamp {
170
+ color: #64748b;
171
+ font-size: 10px;
172
+ margin-right: 8px;
173
+ flex-shrink: 0;
174
+ }
175
+
176
+ .log-message {
177
+ color: #e2e8f0;
178
+ word-break: break-word;
179
+ overflow-wrap: break-word;
180
+ flex: 1;
181
+ min-width: 0;
182
+ }
183
+ `;
184
+ export class LogUI {
185
+ constructor() {
186
+ this.rootEl = null;
187
+ this.buttonEl = null;
188
+ this.badgeEl = null;
189
+ this.backdropEl = null;
190
+ this.windowEl = null;
191
+ this.contentEl = null;
192
+ this.isVisible = false;
193
+ this.unreadCount = 0;
194
+ this.unsubscribe = null;
195
+ }
196
+ init() {
197
+ if (typeof window === "undefined" || typeof document === "undefined")
198
+ return;
199
+ if (this.rootEl)
200
+ return; // Already initialized
201
+ this.createUI();
202
+ this.attachEventListeners();
203
+ this.subscribeToLogs();
204
+ }
205
+ createUI() {
206
+ // Inject styles
207
+ const styleEl = document.createElement("style");
208
+ styleEl.textContent = UI_STYLES;
209
+ document.head.appendChild(styleEl);
210
+ // Create root container
211
+ this.rootEl = document.createElement("div");
212
+ this.rootEl.id = "dev-net-logger-root";
213
+ // Create button
214
+ this.buttonEl = document.createElement("div");
215
+ this.buttonEl.id = "dev-net-logger-button";
216
+ this.buttonEl.textContent = "📡";
217
+ this.buttonEl.title = "Network Logger";
218
+ // Create badge
219
+ this.badgeEl = document.createElement("div");
220
+ this.badgeEl.id = "dev-net-logger-badge";
221
+ this.buttonEl.appendChild(this.badgeEl);
222
+ // Create backdrop
223
+ this.backdropEl = document.createElement("div");
224
+ this.backdropEl.id = "dev-net-logger-backdrop";
225
+ // Create window
226
+ this.windowEl = document.createElement("div");
227
+ this.windowEl.id = "dev-net-logger-window";
228
+ // Create header
229
+ const headerEl = document.createElement("div");
230
+ headerEl.id = "dev-net-logger-header";
231
+ const titleEl = document.createElement("div");
232
+ titleEl.id = "dev-net-logger-title";
233
+ titleEl.textContent = "Network Logs";
234
+ const controlsEl = document.createElement("div");
235
+ controlsEl.id = "dev-net-logger-controls";
236
+ const clearBtn = document.createElement("button");
237
+ clearBtn.textContent = "Clear";
238
+ clearBtn.onclick = () => this.clearLogs();
239
+ const copyBtn = document.createElement("button");
240
+ copyBtn.textContent = "Copy";
241
+ copyBtn.onclick = () => this.copyLogs();
242
+ controlsEl.appendChild(clearBtn);
243
+ controlsEl.appendChild(copyBtn);
244
+ headerEl.appendChild(titleEl);
245
+ headerEl.appendChild(controlsEl);
246
+ // Create content area
247
+ this.contentEl = document.createElement("div");
248
+ this.contentEl.id = "dev-net-logger-content";
249
+ this.windowEl.appendChild(headerEl);
250
+ this.windowEl.appendChild(this.contentEl);
251
+ this.rootEl.appendChild(this.buttonEl);
252
+ this.rootEl.appendChild(this.backdropEl);
253
+ this.rootEl.appendChild(this.windowEl);
254
+ document.body.appendChild(this.rootEl);
255
+ }
256
+ attachEventListeners() {
257
+ if (!this.buttonEl || !this.windowEl || !this.backdropEl)
258
+ return;
259
+ this.buttonEl.addEventListener("click", () => {
260
+ this.toggleWindow();
261
+ });
262
+ // Close window when clicking backdrop
263
+ this.backdropEl.addEventListener("click", () => {
264
+ this.toggleWindow();
265
+ });
266
+ }
267
+ subscribeToLogs() {
268
+ // Load existing logs
269
+ const existingLogs = logStore.getLogs();
270
+ existingLogs.forEach((log) => this.addLogToUI(log));
271
+ // Subscribe to new logs
272
+ this.unsubscribe = logStore.subscribe((log) => {
273
+ this.addLogToUI(log);
274
+ });
275
+ }
276
+ addLogToUI(log) {
277
+ if (!this.contentEl)
278
+ return;
279
+ // Increment unread count if window is closed
280
+ if (!this.isVisible) {
281
+ this.unreadCount++;
282
+ this.updateBadge();
283
+ }
284
+ const logEl = document.createElement("div");
285
+ logEl.className = `log-entry type-${log.type}`;
286
+ const timestampEl = document.createElement("span");
287
+ timestampEl.className = "log-timestamp";
288
+ const date = new Date(log.timestamp);
289
+ timestampEl.textContent = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
290
+ const messageEl = document.createElement("span");
291
+ messageEl.className = "log-message";
292
+ messageEl.textContent = log.message;
293
+ logEl.appendChild(timestampEl);
294
+ logEl.appendChild(messageEl);
295
+ this.contentEl.appendChild(logEl);
296
+ // Auto-scroll to bottom
297
+ this.contentEl.scrollTop = this.contentEl.scrollHeight;
298
+ }
299
+ toggleWindow() {
300
+ if (!this.windowEl || !this.backdropEl)
301
+ return;
302
+ this.isVisible = !this.isVisible;
303
+ if (this.isVisible) {
304
+ this.windowEl.classList.add("visible");
305
+ this.backdropEl.classList.add("visible");
306
+ // Prevent body scroll
307
+ document.body.style.overflow = "hidden";
308
+ // Reset unread count when opening
309
+ this.unreadCount = 0;
310
+ this.updateBadge();
311
+ }
312
+ else {
313
+ this.windowEl.classList.remove("visible");
314
+ this.backdropEl.classList.remove("visible");
315
+ // Restore body scroll
316
+ document.body.style.overflow = "";
317
+ }
318
+ }
319
+ updateBadge() {
320
+ if (!this.badgeEl)
321
+ return;
322
+ if (this.unreadCount > 0) {
323
+ this.badgeEl.textContent =
324
+ this.unreadCount > 99 ? "99+" : String(this.unreadCount);
325
+ this.badgeEl.classList.add("visible");
326
+ }
327
+ else {
328
+ this.badgeEl.classList.remove("visible");
329
+ }
330
+ }
331
+ clearLogs() {
332
+ if (!this.contentEl)
333
+ return;
334
+ logStore.clearLogs();
335
+ this.contentEl.innerHTML = "";
336
+ this.unreadCount = 0;
337
+ this.updateBadge();
338
+ }
339
+ copyLogs() {
340
+ const logs = logStore.getLogs();
341
+ const text = logs
342
+ .map((log) => {
343
+ const date = new Date(log.timestamp);
344
+ const time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`;
345
+ return `[${time}] ${log.message}`;
346
+ })
347
+ .join("\n");
348
+ if (navigator.clipboard) {
349
+ navigator.clipboard.writeText(text).then(() => {
350
+ alert("Logs copied to clipboard!");
351
+ });
352
+ }
353
+ }
354
+ destroy() {
355
+ if (this.unsubscribe) {
356
+ this.unsubscribe();
357
+ }
358
+ if (this.rootEl) {
359
+ this.rootEl.remove();
360
+ }
361
+ }
362
+ }
363
+ export const logUI = new LogUI();
package/init.ts ADDED
@@ -0,0 +1,41 @@
1
+ // init.ts
2
+ // Entry point for web-only dev-network-logger
3
+ import { patchFetch } from "./core/fetch";
4
+ import { patchXHR } from "./core/xhr";
5
+ import { patchDOM } from "./core/dom";
6
+ import { patchConsole } from "./core/console";
7
+ import { logUI } from "./ui/logUI";
8
+
9
+ let isInitialized = false;
10
+
11
+ function isDev() {
12
+ return (
13
+ typeof process !== "undefined" &&
14
+ process.env &&
15
+ (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev")
16
+ );
17
+ }
18
+
19
+ export function initNetworkLogger() {
20
+ if (isInitialized) {
21
+ console.warn("[LogBubble] Already initialized, skipping duplicate init");
22
+ return;
23
+ }
24
+ isInitialized = true;
25
+
26
+ logUI.init();
27
+ // Patch fetch/XHR/DOM/Console in dev mode
28
+ patchFetch(() => {});
29
+ patchXHR(() => {});
30
+ patchDOM(() => {});
31
+ patchConsole();
32
+ }
33
+
34
+ // Auto-init for browser
35
+ if (typeof window !== "undefined") {
36
+ (window as any).initNetworkLogger = initNetworkLogger;
37
+ // Optionally auto-init in dev
38
+ // if (isDev()) {
39
+ initNetworkLogger();
40
+ // }
41
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "logbubble",
3
+ "version": "0.1.0",
4
+ "description": "LogBubble: Web floating logger for network and console logs",
5
+ "main": "dist/init.js",
6
+ "types": "dist/init.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "core",
10
+ "ui",
11
+ "init.ts",
12
+ "auto.ts"
13
+ ],
14
+ "sideEffects": [
15
+ "./auto.ts"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^25.0.9",
23
+ "typescript": "^5.0.0"
24
+ },
25
+ "keywords": [
26
+ "logbubble",
27
+ "network",
28
+ "console",
29
+ "logger",
30
+ "web",
31
+ "devtools",
32
+ "ui",
33
+ "bubble"
34
+ ],
35
+ "author": "",
36
+ "license": "MIT"
37
+ }
package/ui/logStore.ts ADDED
@@ -0,0 +1,49 @@
1
+ // logStore.ts
2
+ // Global log store and API for collecting logs from JS
3
+
4
+ export interface LogEntry {
5
+ timestamp: number;
6
+ message: string;
7
+ type: "fetch" | "xhr" | "dom" | "plugin";
8
+ }
9
+
10
+ class LogStore {
11
+ private logs: LogEntry[] = [];
12
+ private listeners: Array<(log: LogEntry) => void> = [];
13
+ private maxLogs = 500;
14
+
15
+ addLog(message: string, type: LogEntry["type"] = "fetch") {
16
+ const log: LogEntry = {
17
+ timestamp: Date.now(),
18
+ message,
19
+ type,
20
+ };
21
+
22
+ this.logs.push(log);
23
+
24
+ // Keep only the last maxLogs entries
25
+ if (this.logs.length > this.maxLogs) {
26
+ this.logs.shift();
27
+ }
28
+
29
+ // Notify all listeners
30
+ this.listeners.forEach((listener) => listener(log));
31
+ }
32
+
33
+ getLogs(): LogEntry[] {
34
+ return [...this.logs];
35
+ }
36
+
37
+ clearLogs() {
38
+ this.logs = [];
39
+ }
40
+
41
+ subscribe(listener: (log: LogEntry) => void) {
42
+ this.listeners.push(listener);
43
+ return () => {
44
+ this.listeners = this.listeners.filter((l) => l !== listener);
45
+ };
46
+ }
47
+ }
48
+
49
+ export const logStore = new LogStore();