@jhits/plugin-telemetry 0.0.5 → 0.0.7

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.
@@ -0,0 +1,8 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { TelemetryService as ITelemetryService } from './types';
3
+ export interface TelemetryProviderProps {
4
+ children: ReactNode;
5
+ }
6
+ export declare const TelemetryProvider: React.FC<TelemetryProviderProps>;
7
+ export declare const useTelemetry: () => ITelemetryService;
8
+ //# sourceMappingURL=TelemetryProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelemetryProvider.d.ts","sourceRoot":"","sources":["../src/TelemetryProvider.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,gBAAgB,IAAI,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAKhE,MAAM,WAAW,sBAAsB;IACnC,QAAQ,EAAE,SAAS,CAAC;CACvB;AAED,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAM9D,CAAC;AAEF,eAAO,MAAM,YAAY,QAAO,iBAO/B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Telemetry Provider
3
+ * React context provider for telemetry functionality
4
+ */
5
+ "use client";
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ import { createContext, useContext } from 'react';
8
+ import { telemetryService } from './TelemetryService';
9
+ const TelemetryContext = createContext(null);
10
+ export const TelemetryProvider = ({ children }) => {
11
+ return (_jsx(TelemetryContext.Provider, { value: telemetryService, children: children }));
12
+ };
13
+ export const useTelemetry = () => {
14
+ const context = useContext(TelemetryContext);
15
+ if (!context) {
16
+ // Fallback to service directly if context is not available
17
+ return telemetryService;
18
+ }
19
+ return context;
20
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Telemetry Service
3
+ * Core service for logging and managing telemetry data
4
+ * Uses backend file-based logging with buffering
5
+ */
6
+ import { TelemetryEntry, TelemetryCategory, TelemetryService as ITelemetryService } from './types';
7
+ declare class TelemetryServiceClass implements ITelemetryService {
8
+ private buffer;
9
+ private flushTimer;
10
+ private defaultContext;
11
+ private generateId;
12
+ /**
13
+ * Safe JSON stringify that handles circular references and DOM/React elements
14
+ * Uses WeakSet to track seen objects and returns "[Circular]" for repeated references
15
+ */
16
+ private safeStringify;
17
+ /**
18
+ * Get safe JSON serializer that handles circular references and React Fiber nodes
19
+ */
20
+ /**
21
+ * Send entries to backend API
22
+ * CRITICAL: Uses safe serializer to prevent circular JSON errors
23
+ */
24
+ private sendToBackend;
25
+ /**
26
+ * Schedule a flush of the buffer
27
+ */
28
+ private scheduleFlush;
29
+ /**
30
+ * Flush buffer to backend immediately
31
+ */
32
+ flush(useBeacon?: boolean): Promise<void>;
33
+ log(category: TelemetryCategory, message: string, data?: any, context?: string): void;
34
+ error(message: string, error: Error | unknown, data?: any, context?: string): void;
35
+ setContext(context: string): void;
36
+ exportLogs(): void;
37
+ clearLogs(): void;
38
+ getLogs(): TelemetryEntry[];
39
+ }
40
+ export declare const telemetryService: TelemetryServiceClass;
41
+ export {};
42
+ //# sourceMappingURL=TelemetryService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelemetryService.d.ts","sourceRoot":"","sources":["../src/TelemetryService.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,IAAI,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAMnG,cAAM,qBAAsB,YAAW,iBAAiB;IACpD,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,cAAc,CAAoB;IAE1C,OAAO,CAAC,UAAU;IAIlB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkFrB;;OAEG;IACH;;;OAGG;YACW,aAAa;IAsC3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAYrB;;OAEG;IACG,KAAK,CAAC,SAAS,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IActD,GAAG,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAkCrF,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IA4BlF,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIjC,UAAU,IAAI,IAAI;IAUlB,SAAS,IAAI,IAAI;IAajB,OAAO,IAAI,cAAc,EAAE;CAI9B;AAoDD,eAAO,MAAM,gBAAgB,uBAA8B,CAAC"}
@@ -0,0 +1,285 @@
1
+ const API_ENDPOINT = '/api/dashboard/telemetry';
2
+ const BUFFER_INTERVAL_MS = 5000; // Send buffered logs every 5 seconds
3
+ const MAX_BUFFER_SIZE = 50; // Max entries to buffer before forcing send
4
+ class TelemetryServiceClass {
5
+ constructor() {
6
+ this.buffer = [];
7
+ this.flushTimer = null;
8
+ this.defaultContext = 'system';
9
+ }
10
+ generateId() {
11
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
12
+ }
13
+ /**
14
+ * Safe JSON stringify that handles circular references and DOM/React elements
15
+ * Uses WeakSet to track seen objects and returns "[Circular]" for repeated references
16
+ */
17
+ safeStringify(value, space) {
18
+ const seen = new WeakSet();
19
+ const isDOMNode = (val) => {
20
+ return (typeof Node !== 'undefined' && val instanceof Node ||
21
+ typeof Element !== 'undefined' && val instanceof Element ||
22
+ (val && typeof val === 'object' && 'nodeType' in val && 'nodeName' in val));
23
+ };
24
+ const isReactElement = (val) => {
25
+ return (val &&
26
+ typeof val === 'object' &&
27
+ (val.$$typeof === Symbol.for('react.element') ||
28
+ val.$$typeof === Symbol.for('react.portal') ||
29
+ Object.keys(val).some(key => key.startsWith('__react') || key.startsWith('_react'))));
30
+ };
31
+ const isEvent = (val) => {
32
+ return (val &&
33
+ typeof val === 'object' &&
34
+ (val instanceof Event ||
35
+ (typeof Event !== 'undefined' && val.constructor?.name === 'Event') ||
36
+ (val.target && val.currentTarget && val.type)));
37
+ };
38
+ const replacer = (key, val) => {
39
+ // Skip React internal keys
40
+ if (key.startsWith('__react') || key.startsWith('_react')) {
41
+ return '[React Internal]';
42
+ }
43
+ // Handle null/undefined
44
+ if (val === null || val === undefined) {
45
+ return val;
46
+ }
47
+ // Handle DOM nodes
48
+ if (isDOMNode(val)) {
49
+ return '[DOM Node]';
50
+ }
51
+ // Handle React elements
52
+ if (isReactElement(val)) {
53
+ return '[React Element]';
54
+ }
55
+ // Handle Events
56
+ if (isEvent(val)) {
57
+ return `[Event: ${val.type || 'unknown'}]`;
58
+ }
59
+ // Handle functions
60
+ if (typeof val === 'function') {
61
+ return `[Function: ${val.name || 'anonymous'}]`;
62
+ }
63
+ // Handle circular references
64
+ if (typeof val === 'object') {
65
+ if (seen.has(val)) {
66
+ return '[Circular]';
67
+ }
68
+ seen.add(val);
69
+ }
70
+ return val;
71
+ };
72
+ try {
73
+ return JSON.stringify(value, replacer, space);
74
+ }
75
+ catch (error) {
76
+ // Fallback if stringify still fails
77
+ return JSON.stringify({
78
+ error: 'Failed to stringify',
79
+ message: error instanceof Error ? error.message : String(error),
80
+ type: typeof value,
81
+ }, null, space);
82
+ }
83
+ }
84
+ /**
85
+ * Get safe JSON serializer that handles circular references and React Fiber nodes
86
+ */
87
+ /**
88
+ * Send entries to backend API
89
+ * CRITICAL: Uses safe serializer to prevent circular JSON errors
90
+ */
91
+ async sendToBackend(entries, useBeacon = false) {
92
+ if (entries.length === 0)
93
+ return;
94
+ try {
95
+ // SAFE SERIALIZER to prevent Circular structure errors
96
+ const getCircularReplacer = () => {
97
+ const seen = new WeakSet();
98
+ return (key, value) => {
99
+ if (typeof value === "object" && value !== null) {
100
+ if (seen.has(value))
101
+ return "[Circular]";
102
+ seen.add(value);
103
+ // Strip out DOM elements and React internals
104
+ if (value instanceof Node)
105
+ return `[DOM: ${value.nodeName}]`;
106
+ if (key.startsWith('__react'))
107
+ return "[ReactInternal]";
108
+ }
109
+ return value;
110
+ };
111
+ };
112
+ const data = JSON.stringify(entries, getCircularReplacer());
113
+ if (navigator.sendBeacon && useBeacon) {
114
+ const blob = new Blob([data], { type: 'application/json' });
115
+ if (navigator.sendBeacon(API_ENDPOINT, blob))
116
+ return;
117
+ }
118
+ const response = await fetch(API_ENDPOINT, {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: data,
122
+ });
123
+ if (!response.ok)
124
+ throw new Error(`Status ${response.status}`);
125
+ }
126
+ catch (error) {
127
+ console.error('[TelemetryService] Serialization or Network failure:', error);
128
+ }
129
+ }
130
+ /**
131
+ * Schedule a flush of the buffer
132
+ */
133
+ scheduleFlush() {
134
+ // Clear existing timer
135
+ if (this.flushTimer) {
136
+ clearTimeout(this.flushTimer);
137
+ }
138
+ // Schedule new flush
139
+ this.flushTimer = setTimeout(() => {
140
+ this.flush();
141
+ }, BUFFER_INTERVAL_MS);
142
+ }
143
+ /**
144
+ * Flush buffer to backend immediately
145
+ */
146
+ async flush(useBeacon = false) {
147
+ if (this.buffer.length === 0)
148
+ return;
149
+ const entriesToSend = [...this.buffer];
150
+ this.buffer = [];
151
+ if (this.flushTimer) {
152
+ clearTimeout(this.flushTimer);
153
+ this.flushTimer = null;
154
+ }
155
+ await this.sendToBackend(entriesToSend, useBeacon);
156
+ }
157
+ log(category, message, data, context) {
158
+ const entry = {
159
+ id: this.generateId(),
160
+ timestamp: Date.now(),
161
+ category,
162
+ message,
163
+ data: data || {},
164
+ context: context || this.defaultContext,
165
+ };
166
+ // Add to buffer
167
+ this.buffer.push(entry);
168
+ // Also log to console for immediate visibility (less intrusive, emoji-coded breadcrumbs)
169
+ // Only log in development or if explicitly enabled
170
+ if (process.env.NODE_ENV === 'development' || process.env.TELEMETRY_VERBOSE === 'true') {
171
+ const emoji = category === 'DRAG_DROP' ? '📦' : category === 'STATE' ? '📊' : category === 'ERROR' ? '❌' : '🎨';
172
+ // Use safeStringify for console logs too to prevent circular reference errors
173
+ const safeData = data ? this.safeStringify(data) : '';
174
+ console.log(`${emoji} [${entry.context}] ${message}`, safeData || '');
175
+ }
176
+ // Send immediately if ERROR, otherwise schedule flush
177
+ if (category === 'ERROR') {
178
+ this.flush();
179
+ }
180
+ else if (this.buffer.length >= MAX_BUFFER_SIZE) {
181
+ // Force flush if buffer is getting large
182
+ this.flush();
183
+ }
184
+ else {
185
+ // Schedule regular flush
186
+ this.scheduleFlush();
187
+ }
188
+ }
189
+ error(message, error, data, context) {
190
+ const errorObj = error instanceof Error ? error : new Error(String(error));
191
+ const entry = {
192
+ id: this.generateId(),
193
+ timestamp: Date.now(),
194
+ category: 'ERROR',
195
+ message,
196
+ data: {
197
+ ...data,
198
+ errorMessage: errorObj.message,
199
+ errorName: errorObj.name,
200
+ },
201
+ stack: errorObj.stack,
202
+ context: context || this.defaultContext,
203
+ };
204
+ // Add to buffer
205
+ this.buffer.push(entry);
206
+ // Also log to console for immediate visibility (errors always shown, but with safe stringify)
207
+ // Use safeStringify to prevent circular reference errors in console
208
+ const safeData = data ? this.safeStringify(data) : '';
209
+ console.error(`❌ [${entry.context}] ${message}`, error instanceof Error ? error.message : String(error), safeData || '');
210
+ // Errors are sent immediately
211
+ this.flush();
212
+ }
213
+ setContext(context) {
214
+ this.defaultContext = context;
215
+ }
216
+ exportLogs() {
217
+ // Flush any pending logs first
218
+ this.flush().then(() => {
219
+ // Inform user that logs are on the server
220
+ alert('Telemetry logs are stored on the server at: logs/editor-debug.log\n\nCheck the server logs directory for the file.');
221
+ }).catch(() => {
222
+ alert('Telemetry logs are stored on the server. Check logs/editor-debug.log on the server.');
223
+ });
224
+ }
225
+ clearLogs() {
226
+ // Clear the buffer (logs are already on server, can't clear server logs from client)
227
+ this.buffer = [];
228
+ if (this.flushTimer) {
229
+ clearTimeout(this.flushTimer);
230
+ this.flushTimer = null;
231
+ }
232
+ // Less intrusive console log
233
+ if (process.env.NODE_ENV === 'development' || process.env.TELEMETRY_VERBOSE === 'true') {
234
+ console.log('🧹 Telemetry buffer cleared');
235
+ }
236
+ }
237
+ getLogs() {
238
+ // Return current buffer (server logs are not accessible from client)
239
+ return [...this.buffer];
240
+ }
241
+ }
242
+ // Initialize global error handlers
243
+ const initializeGlobalErrorHandlers = (service) => {
244
+ // Capture window.onerror
245
+ const originalOnError = window.onerror;
246
+ window.onerror = (message, source, lineno, colno, error) => {
247
+ service.error('Global Error Handler', error || new Error(String(message)), {
248
+ message: String(message),
249
+ source: String(source),
250
+ lineno,
251
+ colno,
252
+ });
253
+ if (originalOnError) {
254
+ return originalOnError(message, source, lineno, colno, error);
255
+ }
256
+ return false;
257
+ };
258
+ // Capture unhandled promise rejections
259
+ window.addEventListener('unhandledrejection', (event) => {
260
+ service.error('Unhandled Promise Rejection', event.reason, {
261
+ // Don't include the promise object itself (can cause circular references)
262
+ reason: event.reason instanceof Error ? event.reason.message : String(event.reason),
263
+ });
264
+ });
265
+ };
266
+ // Initialize unload handler for flushing logs
267
+ const initializeUnloadHandler = (service) => {
268
+ // Flush logs on page unload using sendBeacon
269
+ window.addEventListener('beforeunload', () => {
270
+ service.flush(true); // Use sendBeacon for unload
271
+ });
272
+ // Also handle visibility change (tab switch, minimize, etc.)
273
+ document.addEventListener('visibilitychange', () => {
274
+ if (document.visibilityState === 'hidden') {
275
+ service.flush(true); // Use sendBeacon when tab becomes hidden
276
+ }
277
+ });
278
+ };
279
+ // Create singleton instance
280
+ export const telemetryService = new TelemetryServiceClass();
281
+ // Initialize global error handlers and unload handler
282
+ if (typeof window !== 'undefined') {
283
+ initializeGlobalErrorHandlers(telemetryService);
284
+ initializeUnloadHandler(telemetryService);
285
+ }
@@ -0,0 +1,20 @@
1
+ import 'server-only';
2
+ /**
3
+ * Telemetry API Handler
4
+ * Plugin-mounted API handler for telemetry logging
5
+ * Compatible with Next.js API routes and Express
6
+ *
7
+ * IMPORTANT: This file should ONLY be imported in server-side API routes.
8
+ * Do NOT import this in client-side code.
9
+ */
10
+ import { NextRequest, NextResponse } from 'next/server';
11
+ /**
12
+ * Next.js API Route Handler
13
+ */
14
+ export declare function POST(request: NextRequest): Promise<NextResponse>;
15
+ /**
16
+ * Express-compatible handler
17
+ * For use with Express.js or other Node.js frameworks
18
+ */
19
+ export declare function telemetryHandler(req: any, res: any): void;
20
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/api/handler.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6IxD;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAgEtE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAmDzD"}
@@ -0,0 +1,229 @@
1
+ import 'server-only';
2
+ /**
3
+ * Telemetry API Handler
4
+ * Plugin-mounted API handler for telemetry logging
5
+ * Compatible with Next.js API routes and Express
6
+ *
7
+ * IMPORTANT: This file should ONLY be imported in server-side API routes.
8
+ * Do NOT import this in client-side code.
9
+ */
10
+ import { NextResponse } from 'next/server';
11
+ import fs from 'fs/promises';
12
+ import path from 'path';
13
+ const LOG_DIR = path.join(process.cwd(), 'logs');
14
+ const LOG_FILE = path.join(LOG_DIR, 'jhits-system.log');
15
+ const OLD_LOG_FILE = path.join(LOG_DIR, 'jhits-system.old.log');
16
+ const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
17
+ const MAX_BATCH_SIZE = 100; // Maximum entries per request
18
+ const MAX_ENTRY_SIZE = 100 * 1024; // 100KB per entry
19
+ /**
20
+ * Ensure logs directory exists
21
+ */
22
+ async function ensureLogDir() {
23
+ try {
24
+ await fs.access(LOG_DIR);
25
+ }
26
+ catch {
27
+ await fs.mkdir(LOG_DIR, { recursive: true });
28
+ }
29
+ }
30
+ /**
31
+ * Check log file size and rotate if necessary
32
+ */
33
+ async function rotateLogIfNeeded() {
34
+ try {
35
+ const stats = await fs.stat(LOG_FILE);
36
+ if (stats.size > MAX_LOG_SIZE) {
37
+ // Rename current log to old log
38
+ try {
39
+ await fs.rename(LOG_FILE, OLD_LOG_FILE);
40
+ }
41
+ catch (error) {
42
+ // If old log exists, remove it first
43
+ if (error.code === 'EEXIST' || error.code === 'ENOENT') {
44
+ try {
45
+ await fs.unlink(OLD_LOG_FILE);
46
+ await fs.rename(LOG_FILE, OLD_LOG_FILE);
47
+ }
48
+ catch {
49
+ // If still fails, just overwrite
50
+ await fs.copyFile(LOG_FILE, OLD_LOG_FILE);
51
+ await fs.unlink(LOG_FILE);
52
+ }
53
+ }
54
+ else {
55
+ throw error;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ catch (error) {
61
+ // If file doesn't exist, that's fine - we'll create it
62
+ if (error.code !== 'ENOENT') {
63
+ console.error('[Telemetry Handler] Error checking log size:', error);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Format log entry as single-line JSON
69
+ */
70
+ function formatLogEntry(entry) {
71
+ const timestamp = new Date(entry.timestamp).toISOString();
72
+ const context = entry.context || 'unknown';
73
+ const userId = entry.userId || 'anonymous';
74
+ const sessionId = entry.sessionId || 'unknown';
75
+ // Sanitize data to prevent log injection
76
+ const sanitizedData = typeof entry.data === 'object'
77
+ ? JSON.stringify(entry.data).replace(/\n/g, '\\n').replace(/\r/g, '\\r')
78
+ : String(entry.data);
79
+ const stackStr = entry.stack ? ` "stack":"${entry.stack.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"` : '';
80
+ return `[${timestamp}] [${context}] [${userId}] [${sessionId}] [${entry.category}] ${entry.message} ${sanitizedData}${stackStr}\n`;
81
+ }
82
+ /**
83
+ * Validate and sanitize telemetry entry
84
+ */
85
+ function validateEntry(entry) {
86
+ // Basic validation
87
+ if (!entry || typeof entry !== 'object') {
88
+ return null;
89
+ }
90
+ // Check required fields
91
+ if (!entry.id || !entry.timestamp || !entry.category || !entry.message) {
92
+ return null;
93
+ }
94
+ // Size check to prevent log injection attacks
95
+ const entrySize = JSON.stringify(entry).length;
96
+ if (entrySize > MAX_ENTRY_SIZE) {
97
+ console.warn('[Telemetry Handler] Entry too large, skipping:', entry.id);
98
+ return null;
99
+ }
100
+ // Sanitize string fields
101
+ return {
102
+ id: String(entry.id).substring(0, 100),
103
+ timestamp: Number(entry.timestamp),
104
+ category: String(entry.category).substring(0, 50),
105
+ message: String(entry.message).substring(0, 500),
106
+ data: entry.data || {},
107
+ stack: entry.stack ? String(entry.stack).substring(0, 5000) : undefined,
108
+ context: entry.context ? String(entry.context).substring(0, 100) : undefined,
109
+ userId: entry.userId ? String(entry.userId).substring(0, 100) : undefined,
110
+ sessionId: entry.sessionId ? String(entry.sessionId).substring(0, 100) : undefined,
111
+ };
112
+ }
113
+ /**
114
+ * Extract user/session info from request headers or cookies
115
+ */
116
+ function extractContext(request) {
117
+ // Try to get user ID from headers (custom header)
118
+ const userId = request.headers.get('x-user-id') ||
119
+ request.headers.get('x-userid') ||
120
+ undefined;
121
+ // Try to get session ID from headers or cookies
122
+ const sessionId = request.headers.get('x-session-id') ||
123
+ request.headers.get('x-sessionid') ||
124
+ request.cookies.get('sessionId')?.value ||
125
+ request.cookies.get('session')?.value ||
126
+ undefined;
127
+ return { userId, sessionId };
128
+ }
129
+ /**
130
+ * Next.js API Route Handler
131
+ */
132
+ export async function POST(request) {
133
+ try {
134
+ // Ensure log directory exists
135
+ await ensureLogDir();
136
+ // Rotate log if needed
137
+ await rotateLogIfNeeded();
138
+ // Extract context from request
139
+ const { userId, sessionId } = extractContext(request);
140
+ // Parse request body
141
+ const body = await request.json();
142
+ // Handle single entry or batch of entries
143
+ const entries = Array.isArray(body) ? body : [body];
144
+ // Rate limiting: Check batch size
145
+ if (entries.length > MAX_BATCH_SIZE) {
146
+ return NextResponse.json({ success: false, error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}` }, { status: 400 });
147
+ }
148
+ // Validate and sanitize entries
149
+ const validEntries = [];
150
+ for (const entry of entries) {
151
+ const validated = validateEntry(entry);
152
+ if (validated) {
153
+ // Add context from request if not present in entry
154
+ if (!validated.userId && userId) {
155
+ validated.userId = userId;
156
+ }
157
+ if (!validated.sessionId && sessionId) {
158
+ validated.sessionId = sessionId;
159
+ }
160
+ validEntries.push(validated);
161
+ }
162
+ }
163
+ if (validEntries.length === 0) {
164
+ return NextResponse.json({ success: false, error: 'No valid entries to log' }, { status: 400 });
165
+ }
166
+ // Format and append each entry
167
+ const logLines = validEntries.map(formatLogEntry).join('');
168
+ await fs.appendFile(LOG_FILE, logLines, 'utf8');
169
+ return NextResponse.json({
170
+ success: true,
171
+ logged: validEntries.length,
172
+ rejected: entries.length - validEntries.length
173
+ });
174
+ }
175
+ catch (error) {
176
+ console.error('[Telemetry Handler] Error writing log:', error);
177
+ return NextResponse.json({ success: false, error: 'Failed to write log' }, { status: 500 });
178
+ }
179
+ }
180
+ /**
181
+ * Express-compatible handler
182
+ * For use with Express.js or other Node.js frameworks
183
+ */
184
+ export function telemetryHandler(req, res) {
185
+ // This is a simplified version for Express
186
+ // In a real Express setup, you'd need to adapt the NextRequest/NextResponse types
187
+ const handler = async () => {
188
+ try {
189
+ await ensureLogDir();
190
+ await rotateLogIfNeeded();
191
+ const body = req.body;
192
+ const entries = Array.isArray(body) ? body : [body];
193
+ if (entries.length > MAX_BATCH_SIZE) {
194
+ return res.status(400).json({
195
+ success: false,
196
+ error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`
197
+ });
198
+ }
199
+ const validEntries = [];
200
+ for (const entry of entries) {
201
+ const validated = validateEntry(entry);
202
+ if (validated) {
203
+ // Extract from Express request
204
+ validated.userId = validated.userId || req.headers['x-user-id'] || req.user?.id;
205
+ validated.sessionId = validated.sessionId || req.headers['x-session-id'] || req.session?.id;
206
+ validEntries.push(validated);
207
+ }
208
+ }
209
+ if (validEntries.length === 0) {
210
+ return res.status(400).json({
211
+ success: false,
212
+ error: 'No valid entries to log'
213
+ });
214
+ }
215
+ const logLines = validEntries.map(formatLogEntry).join('');
216
+ await fs.appendFile(LOG_FILE, logLines, 'utf8');
217
+ res.json({
218
+ success: true,
219
+ logged: validEntries.length,
220
+ rejected: entries.length - validEntries.length
221
+ });
222
+ }
223
+ catch (error) {
224
+ console.error('[Telemetry Handler] Error writing log:', error);
225
+ res.status(500).json({ success: false, error: 'Failed to write log' });
226
+ }
227
+ };
228
+ handler();
229
+ }
@@ -0,0 +1,9 @@
1
+ import 'server-only';
2
+ /**
3
+ * Telemetry API Route Handler
4
+ * Server-only wrapper for the telemetry handler
5
+ * This file should ONLY be used in Next.js API routes
6
+ */
7
+ import { POST as handlerPOST } from './handler';
8
+ export { handlerPOST as POST };
9
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/api/route.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;GAIG;AACH,OAAO,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,WAAW,IAAI,IAAI,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import 'server-only';
2
+ /**
3
+ * Telemetry API Route Handler
4
+ * Server-only wrapper for the telemetry handler
5
+ * This file should ONLY be used in Next.js API routes
6
+ */
7
+ import { POST as handlerPOST } from './handler';
8
+ export { handlerPOST as POST };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Plugin Telemetry
3
+ * Standalone telemetry package for JHITS v2
4
+ *
5
+ * NOTE: API handler is NOT exported here to prevent client-side bundling.
6
+ * Import the handler directly from '@jhits/plugin-telemetry/api/handler' in server-side API routes only.
7
+ */
8
+ export { TelemetryProvider, useTelemetry } from './TelemetryProvider';
9
+ export { telemetryService } from './TelemetryService';
10
+ export type { TelemetryEntry, TelemetryCategory, TelemetryService } from './types';
11
+ export { summarizeBlock, summarizeBlocks, createBlockActionPayload } from './utils/logCleaner';
12
+ export type { BlockSummary, MoveBlockData, DeleteBlockData, InsertBlockData } from './utils/logCleaner';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC/F,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Plugin Telemetry
3
+ * Standalone telemetry package for JHITS v2
4
+ *
5
+ * NOTE: API handler is NOT exported here to prevent client-side bundling.
6
+ * Import the handler directly from '@jhits/plugin-telemetry/api/handler' in server-side API routes only.
7
+ */
8
+ export { TelemetryProvider, useTelemetry } from './TelemetryProvider';
9
+ export { telemetryService } from './TelemetryService';
10
+ export { summarizeBlock, summarizeBlocks, createBlockActionPayload } from './utils/logCleaner';
@@ -0,0 +1,55 @@
1
+ import 'server-only';
2
+ /**
3
+ * Server-only exports for @jhits/plugin-telemetry
4
+ * These exports should only be used in server-side code (API routes, server components)
5
+ */
6
+ interface PluginRegistration {
7
+ name: string;
8
+ version: string;
9
+ description?: string;
10
+ routes: Array<{
11
+ path: string;
12
+ component: string;
13
+ }>;
14
+ apiHandlers: Array<{
15
+ method: string;
16
+ path: string;
17
+ handler: string;
18
+ }>;
19
+ cssFiles: string[];
20
+ setup?: () => Promise<void>;
21
+ dependencies?: string[];
22
+ config?: Record<string, any>;
23
+ }
24
+ import { NextRequest, NextResponse } from 'next/server';
25
+ /**
26
+ * Main API handler for telemetry plugin
27
+ */
28
+ export declare function handleApi(req: NextRequest, path: string[], config: any): Promise<NextResponse>;
29
+ /**
30
+ * Plugin registration for auto-configuration
31
+ */
32
+ export declare function registerPlugin(): Promise<PluginRegistration>;
33
+ /**
34
+ * API handler for retrieving logs
35
+ */
36
+ export declare function getLogsHandler(req: NextRequest, path: string[], config: any): Promise<NextResponse>;
37
+ /**
38
+ * Telemetry Handler
39
+ * Server-only function for processing telemetry data
40
+ *
41
+ * @param data - Telemetry entry or array of entries
42
+ * @param context - Optional context with userId and sessionId
43
+ * @returns Result object with success status and logging information
44
+ */
45
+ export declare function telemetryHandler(data: any, context?: {
46
+ userId?: string;
47
+ sessionId?: string;
48
+ }): Promise<{
49
+ success: boolean;
50
+ logged?: number;
51
+ rejected?: number;
52
+ error?: string;
53
+ }>;
54
+ export {};
55
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;GAGG;AAGH,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,WAAW,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoExD;;GAEG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,GAAG,GACV,OAAO,CAAC,YAAY,CAAC,CA8BvB;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAyClE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,GAAG,GACV,OAAO,CAAC,YAAY,CAAC,CAkCvB;AA8FD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAClC,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2DnF"}
package/dist/server.js ADDED
@@ -0,0 +1,313 @@
1
+ import 'server-only';
2
+ import { NextResponse } from 'next/server';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ const LOG_DIR = path.join(process.cwd(), 'logs');
6
+ const LOG_FILE = path.join(LOG_DIR, 'jhits-system.log');
7
+ const OLD_LOG_FILE = path.join(LOG_DIR, 'jhits-system.old.log');
8
+ const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB
9
+ const MAX_BATCH_SIZE = 100; // Maximum entries per request
10
+ const MAX_ENTRY_SIZE = 100 * 1024; // 100KB per entry
11
+ /**
12
+ * Ensure logs directory exists
13
+ */
14
+ async function ensureLogDir() {
15
+ try {
16
+ await fs.access(LOG_DIR);
17
+ }
18
+ catch {
19
+ await fs.mkdir(LOG_DIR, { recursive: true });
20
+ }
21
+ }
22
+ /**
23
+ * Check log file size and rotate if necessary
24
+ */
25
+ async function rotateLogIfNeeded() {
26
+ try {
27
+ const stats = await fs.stat(LOG_FILE);
28
+ if (stats.size > MAX_LOG_SIZE) {
29
+ // Rename current log to old log
30
+ try {
31
+ await fs.rename(LOG_FILE, OLD_LOG_FILE);
32
+ }
33
+ catch (error) {
34
+ // If old log exists, remove it first
35
+ if (error.code === 'EEXIST' || error.code === 'ENOENT') {
36
+ try {
37
+ await fs.unlink(OLD_LOG_FILE);
38
+ await fs.rename(LOG_FILE, OLD_LOG_FILE);
39
+ }
40
+ catch {
41
+ // If still fails, just overwrite
42
+ await fs.copyFile(LOG_FILE, OLD_LOG_FILE);
43
+ await fs.unlink(LOG_FILE);
44
+ }
45
+ }
46
+ else {
47
+ throw error;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ catch (error) {
53
+ // If file doesn't exist, that's fine - we'll create it
54
+ if (error.code !== 'ENOENT') {
55
+ console.error('[Telemetry Handler] Error checking log size:', error);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Main API handler for telemetry plugin
61
+ */
62
+ export async function handleApi(req, path, config) {
63
+ try {
64
+ const { searchParams } = new URL(req.url);
65
+ const action = path[0] || 'log';
66
+ switch (action) {
67
+ case 'log':
68
+ const data = await req.json();
69
+ const result = await telemetryHandler(data, {
70
+ userId: req.headers.get('x-user-id') || undefined,
71
+ sessionId: req.headers.get('x-session-id') || undefined
72
+ });
73
+ return NextResponse.json(result);
74
+ case 'logs':
75
+ return await getLogsHandler(req, path, config);
76
+ default:
77
+ return NextResponse.json({
78
+ success: false,
79
+ error: 'Unknown telemetry action'
80
+ }, { status: 404 });
81
+ }
82
+ }
83
+ catch (error) {
84
+ console.error('[Telemetry API] Error:', error);
85
+ return NextResponse.json({
86
+ success: false,
87
+ error: 'Internal server error'
88
+ }, { status: 500 });
89
+ }
90
+ }
91
+ /**
92
+ * Plugin registration for auto-configuration
93
+ */
94
+ export async function registerPlugin() {
95
+ return {
96
+ name: 'telemetry',
97
+ version: '0.0.1',
98
+ description: 'System telemetry and logging plugin',
99
+ routes: [
100
+ {
101
+ path: '/dashboard/telemetry',
102
+ component: 'TelemetryDashboard'
103
+ }
104
+ ],
105
+ apiHandlers: [
106
+ {
107
+ method: 'POST',
108
+ path: '/log',
109
+ handler: 'telemetryHandler'
110
+ },
111
+ {
112
+ method: 'GET',
113
+ path: '/logs',
114
+ handler: 'getLogsHandler'
115
+ }
116
+ ],
117
+ cssFiles: ['telemetry-styles.css'],
118
+ setup: async () => {
119
+ // Create logs directory
120
+ const { mkdir } = await import('fs/promises');
121
+ const { join } = await import('path');
122
+ try {
123
+ await mkdir(join(process.cwd(), 'logs'), { recursive: true });
124
+ }
125
+ catch (error) {
126
+ // Directory might already exist
127
+ }
128
+ },
129
+ dependencies: [],
130
+ config: {
131
+ maxLogSize: 10 * 1024 * 1024, // 10MB
132
+ maxBatchSize: 100,
133
+ maxEntrySize: 100 * 1024 // 100KB
134
+ }
135
+ };
136
+ }
137
+ /**
138
+ * API handler for retrieving logs
139
+ */
140
+ export async function getLogsHandler(req, path, config) {
141
+ try {
142
+ const fs = await import('fs/promises');
143
+ const path = await import('path');
144
+ const LOG_FILE = path.join(process.cwd(), 'logs', 'jhits-system.log');
145
+ const { searchParams } = new URL(req.url);
146
+ const lines = parseInt(searchParams.get('lines') || '100');
147
+ const category = searchParams.get('category');
148
+ // Read log file
149
+ const logContent = await fs.readFile(LOG_FILE, 'utf8');
150
+ const logLines = logContent.split('\n').filter(line => line.trim());
151
+ // Filter by category if specified
152
+ const filteredLines = category
153
+ ? logLines.filter(line => line.includes(`[${category}]`))
154
+ : logLines;
155
+ // Get last N lines
156
+ const recentLines = filteredLines.slice(-lines);
157
+ return NextResponse.json({
158
+ success: true,
159
+ logs: recentLines,
160
+ total: filteredLines.length
161
+ });
162
+ }
163
+ catch (error) {
164
+ return NextResponse.json({
165
+ success: false,
166
+ error: 'Failed to read logs',
167
+ logs: []
168
+ }, { status: 500 });
169
+ }
170
+ }
171
+ /**
172
+ * Format log entry as single-line JSON (or pretty-printed in debug mode)
173
+ * CRITICAL: Single-line format for easier grep/tailing, but pretty-printed if debug flag is on
174
+ */
175
+ function formatLogEntry(entry, debug = false) {
176
+ const timestamp = new Date(entry.timestamp).toISOString();
177
+ const context = entry.context || 'unknown';
178
+ const userId = entry.userId || 'anonymous';
179
+ const sessionId = entry.sessionId || 'unknown';
180
+ // Check for debug mode via environment variable
181
+ const isDebugMode = debug || process.env.TELEMETRY_DEBUG === 'true';
182
+ if (isDebugMode) {
183
+ // Pretty-printed format for debugging
184
+ const logObject = {
185
+ timestamp,
186
+ context,
187
+ userId,
188
+ sessionId,
189
+ category: entry.category,
190
+ message: entry.message,
191
+ data: entry.data,
192
+ ...(entry.stack && { stack: entry.stack })
193
+ };
194
+ return JSON.stringify(logObject, null, 2) + '\n';
195
+ }
196
+ else {
197
+ // Single-line JSON format for production (easier grep/tailing)
198
+ const sanitizedData = typeof entry.data === 'object'
199
+ ? JSON.stringify(entry.data).replace(/\n/g, '\\n').replace(/\r/g, '\\r')
200
+ : String(entry.data);
201
+ const stackStr = entry.stack ? ` "stack":"${entry.stack.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"` : '';
202
+ return `[${timestamp}] [${context}] [${userId}] [${sessionId}] [${entry.category}] ${entry.message} ${sanitizedData}${stackStr}\n`;
203
+ }
204
+ }
205
+ /**
206
+ * Validate and sanitize telemetry entry
207
+ */
208
+ function validateEntry(entry) {
209
+ // Basic validation
210
+ if (!entry || typeof entry !== 'object') {
211
+ return null;
212
+ }
213
+ // Check required fields
214
+ if (!entry.id || !entry.timestamp || !entry.category || !entry.message) {
215
+ return null;
216
+ }
217
+ // Size check to prevent log injection attacks
218
+ const entrySize = JSON.stringify(entry).length;
219
+ if (entrySize > MAX_ENTRY_SIZE) {
220
+ console.warn('[Telemetry Handler] Entry too large, skipping:', entry.id);
221
+ return null;
222
+ }
223
+ // Sanitize string fields
224
+ return {
225
+ id: String(entry.id).substring(0, 100),
226
+ timestamp: Number(entry.timestamp),
227
+ category: String(entry.category).substring(0, 50),
228
+ message: String(entry.message).substring(0, 500),
229
+ data: entry.data || {},
230
+ stack: entry.stack ? String(entry.stack).substring(0, 5000) : undefined,
231
+ context: entry.context ? String(entry.context).substring(0, 100) : undefined,
232
+ userId: entry.userId ? String(entry.userId).substring(0, 100) : undefined,
233
+ sessionId: entry.sessionId ? String(entry.sessionId).substring(0, 100) : undefined,
234
+ };
235
+ }
236
+ /**
237
+ * Extract user/session info from request headers or cookies
238
+ */
239
+ function extractContext(request) {
240
+ // Try to get user ID from headers (custom header)
241
+ const userId = request.headers.get('x-user-id') ||
242
+ request.headers.get('x-userid') ||
243
+ undefined;
244
+ // Try to get session ID from headers or cookies
245
+ const sessionId = request.headers.get('x-session-id') ||
246
+ request.headers.get('x-sessionid') ||
247
+ request.cookies.get('sessionId')?.value ||
248
+ request.cookies.get('session')?.value ||
249
+ undefined;
250
+ return { userId, sessionId };
251
+ }
252
+ /**
253
+ * Telemetry Handler
254
+ * Server-only function for processing telemetry data
255
+ *
256
+ * @param data - Telemetry entry or array of entries
257
+ * @param context - Optional context with userId and sessionId
258
+ * @returns Result object with success status and logging information
259
+ */
260
+ export async function telemetryHandler(data, context) {
261
+ try {
262
+ // Ensure log directory exists
263
+ await ensureLogDir();
264
+ // Rotate log if needed
265
+ await rotateLogIfNeeded();
266
+ // Handle single entry or batch of entries
267
+ const entries = Array.isArray(data) ? data : [data];
268
+ // Rate limiting: Check batch size
269
+ if (entries.length > MAX_BATCH_SIZE) {
270
+ return {
271
+ success: false,
272
+ error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`
273
+ };
274
+ }
275
+ // Validate and sanitize entries
276
+ const validEntries = [];
277
+ for (const entry of entries) {
278
+ const validated = validateEntry(entry);
279
+ if (validated) {
280
+ // Add context if provided
281
+ if (context?.userId && !validated.userId) {
282
+ validated.userId = context.userId;
283
+ }
284
+ if (context?.sessionId && !validated.sessionId) {
285
+ validated.sessionId = context.sessionId;
286
+ }
287
+ validEntries.push(validated);
288
+ }
289
+ }
290
+ if (validEntries.length === 0) {
291
+ return {
292
+ success: false,
293
+ error: 'No valid entries to log'
294
+ };
295
+ }
296
+ // Format and append each entry (check for debug mode)
297
+ const debugMode = process.env.TELEMETRY_DEBUG === 'true';
298
+ const logLines = validEntries.map(entry => formatLogEntry(entry, debugMode)).join('');
299
+ await fs.appendFile(LOG_FILE, logLines, 'utf8');
300
+ return {
301
+ success: true,
302
+ logged: validEntries.length,
303
+ rejected: entries.length - validEntries.length
304
+ };
305
+ }
306
+ catch (error) {
307
+ console.error('[Telemetry Handler] Error writing log:', error);
308
+ return {
309
+ success: false,
310
+ error: 'Failed to write log',
311
+ };
312
+ }
313
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Telemetry Types
3
+ * Type definitions for the telemetry system
4
+ */
5
+ export type TelemetryCategory = 'DRAG_DROP' | 'STATE' | 'ERROR' | 'UI';
6
+ export interface TelemetryEntry {
7
+ id: string;
8
+ timestamp: number;
9
+ category: TelemetryCategory;
10
+ message: string;
11
+ data: any;
12
+ stack?: string;
13
+ context?: string;
14
+ userId?: string;
15
+ sessionId?: string;
16
+ }
17
+ export interface TelemetryService {
18
+ log: (category: TelemetryCategory, message: string, data?: any, context?: string) => void;
19
+ error: (message: string, error: Error | unknown, data?: any, context?: string) => void;
20
+ exportLogs: () => void;
21
+ clearLogs: () => void;
22
+ getLogs: () => TelemetryEntry[];
23
+ flush: () => Promise<void>;
24
+ setContext: (context: string) => void;
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1F,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACvF,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,cAAc,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Telemetry Types
3
+ * Type definitions for the telemetry system
4
+ */
5
+ export {};
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Log Cleaner Utilities
3
+ * Optimizes telemetry payloads by summarizing large data structures
4
+ */
5
+ interface BlockLike {
6
+ id: string | number;
7
+ type?: string;
8
+ nestedBlocks?: BlockLike[];
9
+ }
10
+ /**
11
+ * Summarized block representation for telemetry
12
+ */
13
+ export interface BlockSummary {
14
+ id: string;
15
+ type: string;
16
+ childCount: number;
17
+ }
18
+ /**
19
+ * Summarize a single block to reduce log payload size
20
+ * Only includes essential information: id, type, and child count
21
+ */
22
+ export declare function summarizeBlock(block: BlockLike | {
23
+ id: string;
24
+ type: string;
25
+ nestedBlocks?: any[];
26
+ }): BlockSummary;
27
+ /**
28
+ * Summarize an array of blocks to reduce log payload size
29
+ * Recursively summarizes nested blocks up to a maximum depth
30
+ */
31
+ export declare function summarizeBlocks(blocks: BlockLike[] | undefined, maxDepth?: number): BlockSummary[];
32
+ /**
33
+ * Extract action-specific data for telemetry
34
+ */
35
+ export interface MoveBlockData {
36
+ blockId: string;
37
+ fromParent: string | null;
38
+ toParent: string | null;
39
+ fromIndex?: number;
40
+ toIndex: number;
41
+ }
42
+ export interface DeleteBlockData {
43
+ blockId: string;
44
+ blockType: string;
45
+ parentId: string | null;
46
+ }
47
+ export interface InsertBlockData {
48
+ newBlockType: string;
49
+ parentId: string | null;
50
+ index: number;
51
+ newBlockId?: string;
52
+ }
53
+ /**
54
+ * Create optimized telemetry payload for block operations
55
+ * Removes full block arrays and only includes essential action data
56
+ */
57
+ export declare function createBlockActionPayload(actionType: 'MOVE_BLOCK' | 'DELETE_BLOCK' | 'INSERT_BLOCK', data: MoveBlockData | DeleteBlockData | InsertBlockData, options?: {
58
+ includeBlocks?: boolean;
59
+ affectedBlockId?: string;
60
+ }): Record<string, any>;
61
+ export {};
62
+ //# sourceMappingURL=logCleaner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logCleaner.d.ts","sourceRoot":"","sources":["../../src/utils/logCleaner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,UAAU,SAAS;IACf,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,GAAG,YAAY,CAMlH;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,EAC/B,QAAQ,GAAE,MAAU,GACrB,YAAY,EAAE,CAoBhB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,UAAU,EAAE,YAAY,GAAG,cAAc,GAAG,cAAc,EAC1D,IAAI,EAAE,aAAa,GAAG,eAAe,GAAG,eAAe,EACvD,OAAO,CAAC,EAAE;IACN,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,GACF,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAYrB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Log Cleaner Utilities
3
+ * Optimizes telemetry payloads by summarizing large data structures
4
+ */
5
+ /**
6
+ * Summarize a single block to reduce log payload size
7
+ * Only includes essential information: id, type, and child count
8
+ */
9
+ export function summarizeBlock(block) {
10
+ return {
11
+ id: String(block.id),
12
+ type: String(block.type || 'unknown'),
13
+ childCount: Array.isArray(block.nestedBlocks) ? block.nestedBlocks.length : 0
14
+ };
15
+ }
16
+ /**
17
+ * Summarize an array of blocks to reduce log payload size
18
+ * Recursively summarizes nested blocks up to a maximum depth
19
+ */
20
+ export function summarizeBlocks(blocks, maxDepth = 2) {
21
+ if (!Array.isArray(blocks) || blocks.length === 0) {
22
+ return [];
23
+ }
24
+ return blocks.map(block => {
25
+ const summary = {
26
+ id: String(block.id),
27
+ type: String(block.type || 'unknown'),
28
+ childCount: Array.isArray(block.nestedBlocks) ? block.nestedBlocks.length : 0
29
+ };
30
+ // Optionally include nested summaries if depth allows (for debugging)
31
+ if (maxDepth > 0 && block.nestedBlocks && block.nestedBlocks.length > 0) {
32
+ // Only include nested summaries in debug mode
33
+ // For production, just the childCount is sufficient
34
+ }
35
+ return summary;
36
+ });
37
+ }
38
+ /**
39
+ * Create optimized telemetry payload for block operations
40
+ * Removes full block arrays and only includes essential action data
41
+ */
42
+ export function createBlockActionPayload(actionType, data, options) {
43
+ const payload = {
44
+ actionType,
45
+ ...data
46
+ };
47
+ // Only include full blocks for critical errors
48
+ if (options?.includeBlocks && options.affectedBlockId) {
49
+ payload.affectedBlockId = options.affectedBlockId;
50
+ }
51
+ return payload;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhits/plugin-telemetry",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "System logging and telemetry utilities for the JHITS ecosystem",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -30,7 +30,7 @@
30
30
  }
31
31
  },
32
32
  "dependencies": {
33
- "@jhits/plugin-core": "^0.0.2"
33
+ "@jhits/plugin-core": "0.0.2"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "next": ">=15.0.0",