@jhits/plugin-telemetry 0.0.1 → 0.0.3

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/package.json CHANGED
@@ -1,53 +1,53 @@
1
1
  {
2
2
  "name": "@jhits/plugin-telemetry",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "System logging and telemetry utilities for the JHITS ecosystem",
5
5
  "publishConfig": {
6
- "access": "public"
6
+ "access": "public"
7
7
  },
8
8
  "main": "./src/index.ts",
9
9
  "types": "./src/index.ts",
10
10
  "exports": {
11
- ".": {
12
- "types": "./src/index.ts",
13
- "default": "./src/index.ts"
14
- },
15
- "./server": {
16
- "types": "./src/server.ts",
17
- "default": "./src/server.ts"
18
- },
19
- "./api/route": {
20
- "types": "./src/api/route.ts",
21
- "default": "./src/api/route.ts"
22
- },
23
- "./api/handler": {
24
- "types": "./src/api/handler.ts",
25
- "default": "./src/api/handler.ts"
26
- },
27
- "./utils/logCleaner": {
28
- "types": "./src/utils/logCleaner.ts",
29
- "default": "./src/utils/logCleaner.ts"
30
- }
11
+ ".": {
12
+ "types": "./src/index.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./server": {
16
+ "types": "./src/server.ts",
17
+ "default": "./src/server.ts"
18
+ },
19
+ "./api/route": {
20
+ "types": "./src/api/route.ts",
21
+ "default": "./src/api/route.ts"
22
+ },
23
+ "./api/handler": {
24
+ "types": "./src/api/handler.ts",
25
+ "default": "./src/api/handler.ts"
26
+ },
27
+ "./utils/logCleaner": {
28
+ "types": "./src/utils/logCleaner.ts",
29
+ "default": "./src/utils/logCleaner.ts"
30
+ }
31
31
  },
32
32
  "dependencies": {
33
- "@jhits/plugin-core": "^0.0.1"
33
+ "@jhits/plugin-core": "^0.0.1"
34
34
  },
35
35
  "peerDependencies": {
36
- "next": ">=15.0.0",
37
- "react": ">=18.0.0",
38
- "react-dom": ">=18.0.0"
36
+ "next": ">=15.0.0",
37
+ "react": ">=18.0.0",
38
+ "react-dom": ">=18.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@types/node": "^20.19.27",
42
- "@types/react": "^19",
43
- "@types/react-dom": "^19",
44
- "next": "16.1.1",
45
- "react": "19.2.3",
46
- "react-dom": "19.2.3",
47
- "typescript": "^5"
41
+ "@types/node": "^20.19.27",
42
+ "@types/react": "^19",
43
+ "@types/react-dom": "^19",
44
+ "next": "16.1.1",
45
+ "react": "19.2.3",
46
+ "react-dom": "19.2.3",
47
+ "typescript": "^5"
48
48
  },
49
49
  "files": [
50
- "src",
51
- "package.json"
50
+ "src",
51
+ "package.json"
52
52
  ]
53
- }
53
+ }
@@ -1,3 +1,5 @@
1
+ import 'server-only';
2
+
1
3
  /**
2
4
  * Telemetry API Handler
3
5
  * Plugin-mounted API handler for telemetry logging
@@ -82,14 +84,14 @@ function formatLogEntry(entry: TelemetryEntry): string {
82
84
  const context = entry.context || 'unknown';
83
85
  const userId = entry.userId || 'anonymous';
84
86
  const sessionId = entry.sessionId || 'unknown';
85
-
87
+
86
88
  // Sanitize data to prevent log injection
87
- const sanitizedData = typeof entry.data === 'object'
89
+ const sanitizedData = typeof entry.data === 'object'
88
90
  ? JSON.stringify(entry.data).replace(/\n/g, '\\n').replace(/\r/g, '\\r')
89
91
  : String(entry.data);
90
-
92
+
91
93
  const stackStr = entry.stack ? ` "stack":"${entry.stack.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"` : '';
92
-
94
+
93
95
  return `[${timestamp}] [${context}] [${userId}] [${sessionId}] [${entry.category}] ${entry.message} ${sanitizedData}${stackStr}\n`;
94
96
  }
95
97
 
@@ -133,16 +135,16 @@ function validateEntry(entry: any): TelemetryEntry | null {
133
135
  */
134
136
  function extractContext(request: NextRequest): { userId?: string; sessionId?: string } {
135
137
  // Try to get user ID from headers (custom header)
136
- const userId = request.headers.get('x-user-id') ||
137
- request.headers.get('x-userid') ||
138
- undefined;
139
-
138
+ const userId = request.headers.get('x-user-id') ||
139
+ request.headers.get('x-userid') ||
140
+ undefined;
141
+
140
142
  // Try to get session ID from headers or cookies
141
143
  const sessionId = request.headers.get('x-session-id') ||
142
- request.headers.get('x-sessionid') ||
143
- request.cookies.get('sessionId')?.value ||
144
- request.cookies.get('session')?.value ||
145
- undefined;
144
+ request.headers.get('x-sessionid') ||
145
+ request.cookies.get('sessionId')?.value ||
146
+ request.cookies.get('session')?.value ||
147
+ undefined;
146
148
 
147
149
  return { userId, sessionId };
148
150
  }
@@ -154,19 +156,19 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
154
156
  try {
155
157
  // Ensure log directory exists
156
158
  await ensureLogDir();
157
-
159
+
158
160
  // Rotate log if needed
159
161
  await rotateLogIfNeeded();
160
-
162
+
161
163
  // Extract context from request
162
164
  const { userId, sessionId } = extractContext(request);
163
-
165
+
164
166
  // Parse request body
165
167
  const body = await request.json();
166
-
168
+
167
169
  // Handle single entry or batch of entries
168
170
  const entries = Array.isArray(body) ? body : [body];
169
-
171
+
170
172
  // Rate limiting: Check batch size
171
173
  if (entries.length > MAX_BATCH_SIZE) {
172
174
  return NextResponse.json(
@@ -174,7 +176,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
174
176
  { status: 400 }
175
177
  );
176
178
  }
177
-
179
+
178
180
  // Validate and sanitize entries
179
181
  const validEntries: TelemetryEntry[] = [];
180
182
  for (const entry of entries) {
@@ -190,20 +192,20 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
190
192
  validEntries.push(validated);
191
193
  }
192
194
  }
193
-
195
+
194
196
  if (validEntries.length === 0) {
195
197
  return NextResponse.json(
196
198
  { success: false, error: 'No valid entries to log' },
197
199
  { status: 400 }
198
200
  );
199
201
  }
200
-
202
+
201
203
  // Format and append each entry
202
204
  const logLines = validEntries.map(formatLogEntry).join('');
203
205
  await fs.appendFile(LOG_FILE, logLines, 'utf8');
204
-
205
- return NextResponse.json({
206
- success: true,
206
+
207
+ return NextResponse.json({
208
+ success: true,
207
209
  logged: validEntries.length,
208
210
  rejected: entries.length - validEntries.length
209
211
  });
@@ -227,17 +229,17 @@ export function telemetryHandler(req: any, res: any): void {
227
229
  try {
228
230
  await ensureLogDir();
229
231
  await rotateLogIfNeeded();
230
-
232
+
231
233
  const body = req.body;
232
234
  const entries = Array.isArray(body) ? body : [body];
233
-
235
+
234
236
  if (entries.length > MAX_BATCH_SIZE) {
235
- return res.status(400).json({
236
- success: false,
237
- error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`
237
+ return res.status(400).json({
238
+ success: false,
239
+ error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`
238
240
  });
239
241
  }
240
-
242
+
241
243
  const validEntries: TelemetryEntry[] = [];
242
244
  for (const entry of entries) {
243
245
  const validated = validateEntry(entry);
@@ -248,19 +250,19 @@ export function telemetryHandler(req: any, res: any): void {
248
250
  validEntries.push(validated);
249
251
  }
250
252
  }
251
-
253
+
252
254
  if (validEntries.length === 0) {
253
- return res.status(400).json({
254
- success: false,
255
- error: 'No valid entries to log'
255
+ return res.status(400).json({
256
+ success: false,
257
+ error: 'No valid entries to log'
256
258
  });
257
259
  }
258
-
260
+
259
261
  const logLines = validEntries.map(formatLogEntry).join('');
260
262
  await fs.appendFile(LOG_FILE, logLines, 'utf8');
261
-
262
- res.json({
263
- success: true,
263
+
264
+ res.json({
265
+ success: true,
264
266
  logged: validEntries.length,
265
267
  rejected: entries.length - validEntries.length
266
268
  });
@@ -269,7 +271,7 @@ export function telemetryHandler(req: any, res: any): void {
269
271
  res.status(500).json({ success: false, error: 'Failed to write log' });
270
272
  }
271
273
  };
272
-
274
+
273
275
  handler();
274
276
  }
275
277
 
package/src/api/route.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import 'server-only';
2
+
1
3
  /**
2
4
  * Telemetry API Route Handler
3
5
  * Server-only wrapper for the telemetry handler
package/src/server.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import 'server-only';
2
+
1
3
  /**
2
4
  * Server-only exports for @jhits/plugin-telemetry
3
5
  * These exports should only be used in server-side code (API routes, server components)
@@ -80,10 +82,10 @@ function formatLogEntry(entry: TelemetryEntry, debug: boolean = false): string {
80
82
  const context = entry.context || 'unknown';
81
83
  const userId = entry.userId || 'anonymous';
82
84
  const sessionId = entry.sessionId || 'unknown';
83
-
85
+
84
86
  // Check for debug mode via environment variable
85
87
  const isDebugMode = debug || process.env.TELEMETRY_DEBUG === 'true';
86
-
88
+
87
89
  if (isDebugMode) {
88
90
  // Pretty-printed format for debugging
89
91
  const logObject = {
@@ -99,12 +101,12 @@ function formatLogEntry(entry: TelemetryEntry, debug: boolean = false): string {
99
101
  return JSON.stringify(logObject, null, 2) + '\n';
100
102
  } else {
101
103
  // Single-line JSON format for production (easier grep/tailing)
102
- const sanitizedData = typeof entry.data === 'object'
104
+ const sanitizedData = typeof entry.data === 'object'
103
105
  ? JSON.stringify(entry.data).replace(/\n/g, '\\n').replace(/\r/g, '\\r')
104
106
  : String(entry.data);
105
-
107
+
106
108
  const stackStr = entry.stack ? ` "stack":"${entry.stack.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"` : '';
107
-
109
+
108
110
  return `[${timestamp}] [${context}] [${userId}] [${sessionId}] [${entry.category}] ${entry.message} ${sanitizedData}${stackStr}\n`;
109
111
  }
110
112
  }
@@ -149,16 +151,16 @@ function validateEntry(entry: any): TelemetryEntry | null {
149
151
  */
150
152
  function extractContext(request: NextRequest): { userId?: string; sessionId?: string } {
151
153
  // Try to get user ID from headers (custom header)
152
- const userId = request.headers.get('x-user-id') ||
153
- request.headers.get('x-userid') ||
154
- undefined;
155
-
154
+ const userId = request.headers.get('x-user-id') ||
155
+ request.headers.get('x-userid') ||
156
+ undefined;
157
+
156
158
  // Try to get session ID from headers or cookies
157
159
  const sessionId = request.headers.get('x-session-id') ||
158
- request.headers.get('x-sessionid') ||
159
- request.cookies.get('sessionId')?.value ||
160
- request.cookies.get('session')?.value ||
161
- undefined;
160
+ request.headers.get('x-sessionid') ||
161
+ request.cookies.get('sessionId')?.value ||
162
+ request.cookies.get('session')?.value ||
163
+ undefined;
162
164
 
163
165
  return { userId, sessionId };
164
166
  }
@@ -178,13 +180,13 @@ export async function telemetryHandler(
178
180
  try {
179
181
  // Ensure log directory exists
180
182
  await ensureLogDir();
181
-
183
+
182
184
  // Rotate log if needed
183
185
  await rotateLogIfNeeded();
184
-
186
+
185
187
  // Handle single entry or batch of entries
186
188
  const entries = Array.isArray(data) ? data : [data];
187
-
189
+
188
190
  // Rate limiting: Check batch size
189
191
  if (entries.length > MAX_BATCH_SIZE) {
190
192
  return {
@@ -192,7 +194,7 @@ export async function telemetryHandler(
192
194
  error: `Batch size exceeds maximum of ${MAX_BATCH_SIZE}`
193
195
  };
194
196
  }
195
-
197
+
196
198
  // Validate and sanitize entries
197
199
  const validEntries: TelemetryEntry[] = [];
198
200
  for (const entry of entries) {
@@ -208,19 +210,19 @@ export async function telemetryHandler(
208
210
  validEntries.push(validated);
209
211
  }
210
212
  }
211
-
213
+
212
214
  if (validEntries.length === 0) {
213
215
  return {
214
216
  success: false,
215
217
  error: 'No valid entries to log'
216
218
  };
217
219
  }
218
-
220
+
219
221
  // Format and append each entry (check for debug mode)
220
222
  const debugMode = process.env.TELEMETRY_DEBUG === 'true';
221
223
  const logLines = validEntries.map(entry => formatLogEntry(entry, debugMode)).join('');
222
224
  await fs.appendFile(LOG_FILE, logLines, 'utf8');
223
-
225
+
224
226
  return {
225
227
  success: true,
226
228
  logged: validEntries.length,