@tarslogs/client 0.1.0 → 0.2.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/{hono-DIKyVc9C.d.cts → hono-CB3Uka-S.d.cts} +7 -0
- package/dist/{hono-DIKyVc9C.d.ts → hono-CB3Uka-S.d.ts} +7 -0
- package/dist/hono.cjs +123 -2
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +123 -2
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +134 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +133 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -37,9 +37,12 @@ declare class TarslogsClient {
|
|
|
37
37
|
private config;
|
|
38
38
|
private transport;
|
|
39
39
|
private context;
|
|
40
|
+
private deviceInfo;
|
|
41
|
+
private sessionId;
|
|
40
42
|
/**
|
|
41
43
|
* Initialize the client with configuration.
|
|
42
44
|
* Must be called before any other methods.
|
|
45
|
+
* Auto-detects browser, OS, device, and screen info in browser environments.
|
|
43
46
|
*/
|
|
44
47
|
init(config: TarslogsConfig): void;
|
|
45
48
|
/**
|
|
@@ -51,6 +54,10 @@ declare class TarslogsClient {
|
|
|
51
54
|
* Clear all context (e.g., on logout).
|
|
52
55
|
*/
|
|
53
56
|
clearContext(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Build the full context object for an error/metric.
|
|
59
|
+
*/
|
|
60
|
+
private buildContext;
|
|
54
61
|
/**
|
|
55
62
|
* Capture an error and send to TarsLogs.
|
|
56
63
|
*/
|
|
@@ -37,9 +37,12 @@ declare class TarslogsClient {
|
|
|
37
37
|
private config;
|
|
38
38
|
private transport;
|
|
39
39
|
private context;
|
|
40
|
+
private deviceInfo;
|
|
41
|
+
private sessionId;
|
|
40
42
|
/**
|
|
41
43
|
* Initialize the client with configuration.
|
|
42
44
|
* Must be called before any other methods.
|
|
45
|
+
* Auto-detects browser, OS, device, and screen info in browser environments.
|
|
43
46
|
*/
|
|
44
47
|
init(config: TarslogsConfig): void;
|
|
45
48
|
/**
|
|
@@ -51,6 +54,10 @@ declare class TarslogsClient {
|
|
|
51
54
|
* Clear all context (e.g., on logout).
|
|
52
55
|
*/
|
|
53
56
|
clearContext(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Build the full context object for an error/metric.
|
|
59
|
+
*/
|
|
60
|
+
private buildContext;
|
|
54
61
|
/**
|
|
55
62
|
* Capture an error and send to TarsLogs.
|
|
56
63
|
*/
|
package/dist/hono.cjs
CHANGED
|
@@ -186,18 +186,111 @@ var Transport = class {
|
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
188
|
|
|
189
|
+
// src/device.ts
|
|
190
|
+
function detectDevice() {
|
|
191
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const ua = navigator.userAgent;
|
|
195
|
+
return {
|
|
196
|
+
browser: detectBrowser(ua),
|
|
197
|
+
browserVersion: detectBrowserVersion(ua),
|
|
198
|
+
os: detectOS(ua),
|
|
199
|
+
osVersion: detectOSVersion(ua),
|
|
200
|
+
deviceType: detectDeviceType(ua),
|
|
201
|
+
screenResolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : "unknown",
|
|
202
|
+
viewportSize: `${window.innerWidth}x${window.innerHeight}`,
|
|
203
|
+
language: navigator.language || "unknown",
|
|
204
|
+
referrer: typeof document !== "undefined" ? document.referrer : ""
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function detectBrowser(ua) {
|
|
208
|
+
if (ua.includes("Firefox/")) return "Firefox";
|
|
209
|
+
if (ua.includes("Edg/")) return "Edge";
|
|
210
|
+
if (ua.includes("OPR/") || ua.includes("Opera/")) return "Opera";
|
|
211
|
+
if (ua.includes("Chrome/") && !ua.includes("Edg/")) return "Chrome";
|
|
212
|
+
if (ua.includes("Safari/") && !ua.includes("Chrome/")) return "Safari";
|
|
213
|
+
if (ua.includes("MSIE") || ua.includes("Trident/")) return "IE";
|
|
214
|
+
return "Unknown";
|
|
215
|
+
}
|
|
216
|
+
function detectBrowserVersion(ua) {
|
|
217
|
+
const patterns = [
|
|
218
|
+
["Firefox", /Firefox\/(\d+[\d.]*)/],
|
|
219
|
+
["Edge", /Edg\/(\d+[\d.]*)/],
|
|
220
|
+
["Opera", /(?:OPR|Opera)\/(\d+[\d.]*)/],
|
|
221
|
+
["Chrome", /Chrome\/(\d+[\d.]*)/],
|
|
222
|
+
["Safari", /Version\/(\d+[\d.]*)/],
|
|
223
|
+
["IE", /(?:MSIE |rv:)(\d+[\d.]*)/]
|
|
224
|
+
];
|
|
225
|
+
for (const [, regex] of patterns) {
|
|
226
|
+
const match = ua.match(regex);
|
|
227
|
+
if (match) return match[1];
|
|
228
|
+
}
|
|
229
|
+
return "unknown";
|
|
230
|
+
}
|
|
231
|
+
function detectOS(ua) {
|
|
232
|
+
if (ua.includes("Windows")) return "Windows";
|
|
233
|
+
if (ua.includes("Mac OS X") || ua.includes("Macintosh")) return "macOS";
|
|
234
|
+
if (ua.includes("CrOS")) return "Chrome OS";
|
|
235
|
+
if (ua.includes("Linux") && !ua.includes("Android")) return "Linux";
|
|
236
|
+
if (ua.includes("Android")) return "Android";
|
|
237
|
+
if (/iPad|iPhone|iPod/.test(ua)) return "iOS";
|
|
238
|
+
return "Unknown";
|
|
239
|
+
}
|
|
240
|
+
function detectOSVersion(ua) {
|
|
241
|
+
const patterns = [
|
|
242
|
+
["Windows", /Windows NT (\d+[\d.]*)/, (v) => {
|
|
243
|
+
const map = { "10.0": "10/11", "6.3": "8.1", "6.2": "8", "6.1": "7" };
|
|
244
|
+
return map[v] || v;
|
|
245
|
+
}],
|
|
246
|
+
["macOS", /Mac OS X (\d+[._\d]*)/, (v) => v.replace(/_/g, ".")],
|
|
247
|
+
["Android", /Android (\d+[\d.]*)/],
|
|
248
|
+
["iOS", /OS (\d+[_\d]*)/, (v) => v.replace(/_/g, ".")]
|
|
249
|
+
];
|
|
250
|
+
for (const [, regex, transform] of patterns) {
|
|
251
|
+
const match = ua.match(regex);
|
|
252
|
+
if (match) return transform ? transform(match[1]) : match[1];
|
|
253
|
+
}
|
|
254
|
+
return "unknown";
|
|
255
|
+
}
|
|
256
|
+
function detectDeviceType(ua) {
|
|
257
|
+
if (/iPad|Android(?!.*Mobile)/.test(ua) && typeof screen !== "undefined" && Math.min(screen.width, screen.height) > 600) {
|
|
258
|
+
return "tablet";
|
|
259
|
+
}
|
|
260
|
+
if (/Mobi|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
|
|
261
|
+
return "mobile";
|
|
262
|
+
}
|
|
263
|
+
if (typeof navigator !== "undefined" && /Windows|Macintosh|Linux/.test(ua) && !/Mobi/i.test(ua)) {
|
|
264
|
+
return "desktop";
|
|
265
|
+
}
|
|
266
|
+
return "unknown";
|
|
267
|
+
}
|
|
268
|
+
|
|
189
269
|
// src/core.ts
|
|
270
|
+
function generateSessionId() {
|
|
271
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
272
|
+
const bytes = new Uint8Array(8);
|
|
273
|
+
crypto.getRandomValues(bytes);
|
|
274
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
275
|
+
}
|
|
276
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
277
|
+
}
|
|
190
278
|
var TarslogsClient = class {
|
|
191
279
|
config = null;
|
|
192
280
|
transport = new Transport();
|
|
193
281
|
context = {};
|
|
282
|
+
deviceInfo = null;
|
|
283
|
+
sessionId = "";
|
|
194
284
|
/**
|
|
195
285
|
* Initialize the client with configuration.
|
|
196
286
|
* Must be called before any other methods.
|
|
287
|
+
* Auto-detects browser, OS, device, and screen info in browser environments.
|
|
197
288
|
*/
|
|
198
289
|
init(config) {
|
|
199
290
|
this.config = config;
|
|
200
291
|
this.transport.init(config);
|
|
292
|
+
this.sessionId = generateSessionId();
|
|
293
|
+
this.deviceInfo = detectDevice();
|
|
201
294
|
}
|
|
202
295
|
/**
|
|
203
296
|
* Merge additional context that will be sent with all errors and metrics.
|
|
@@ -212,6 +305,34 @@ var TarslogsClient = class {
|
|
|
212
305
|
clearContext() {
|
|
213
306
|
this.context = {};
|
|
214
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Build the full context object for an error/metric.
|
|
310
|
+
*/
|
|
311
|
+
buildContext(extra) {
|
|
312
|
+
const ctx = {
|
|
313
|
+
sessionId: this.sessionId,
|
|
314
|
+
...this.context,
|
|
315
|
+
...extra
|
|
316
|
+
};
|
|
317
|
+
if (typeof window !== "undefined" && window.location) {
|
|
318
|
+
ctx.url = window.location.href;
|
|
319
|
+
ctx.path = window.location.pathname;
|
|
320
|
+
}
|
|
321
|
+
if (this.deviceInfo) {
|
|
322
|
+
ctx.browser = this.deviceInfo.browser;
|
|
323
|
+
ctx.browserVersion = this.deviceInfo.browserVersion;
|
|
324
|
+
ctx.os = this.deviceInfo.os;
|
|
325
|
+
ctx.osVersion = this.deviceInfo.osVersion;
|
|
326
|
+
ctx.deviceType = this.deviceInfo.deviceType;
|
|
327
|
+
ctx.screenResolution = this.deviceInfo.screenResolution;
|
|
328
|
+
ctx.viewportSize = this.deviceInfo.viewportSize;
|
|
329
|
+
ctx.language = this.deviceInfo.language;
|
|
330
|
+
if (this.deviceInfo.referrer) {
|
|
331
|
+
ctx.referrer = this.deviceInfo.referrer;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return ctx;
|
|
335
|
+
}
|
|
215
336
|
/**
|
|
216
337
|
* Capture an error and send to TarsLogs.
|
|
217
338
|
*/
|
|
@@ -223,7 +344,7 @@ var TarslogsClient = class {
|
|
|
223
344
|
message: error,
|
|
224
345
|
environment: this.config.environment,
|
|
225
346
|
version: this.config.version,
|
|
226
|
-
context:
|
|
347
|
+
context: this.buildContext(extra),
|
|
227
348
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
228
349
|
};
|
|
229
350
|
} else {
|
|
@@ -232,7 +353,7 @@ var TarslogsClient = class {
|
|
|
232
353
|
stack: error.stack,
|
|
233
354
|
environment: this.config.environment,
|
|
234
355
|
version: this.config.version,
|
|
235
|
-
context:
|
|
356
|
+
context: this.buildContext(extra),
|
|
236
357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
237
358
|
};
|
|
238
359
|
if (error.stack) {
|
package/dist/hono.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hono.ts","../src/transport.ts","../src/core.ts"],"sourcesContent":["import { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\n// Server-side Hono middleware — NO browser globals (window, document, navigator)\n\ninterface HonoContext {\n req: {\n method: string\n path: string\n url: string\n }\n res: {\n status: number\n }\n json: (data: unknown, status?: number) => Response\n}\n\ntype HonoNext = () => Promise<void>\n\ninterface HonoMiddleware {\n (c: HonoContext, next: HonoNext): Promise<Response | void>\n}\n\ninterface HonoApp {\n use: (path: string, ...handlers: HonoMiddleware[]) => void\n}\n\n/**\n * Hono middleware factory that catches errors in route handlers\n * and reports them to TarsLogs.\n *\n * Usage:\n * app.use('*', tarslogsMiddleware(tarslogsClient))\n */\nexport function tarslogsMiddleware(client: TarslogsClient): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n try {\n await next()\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err))\n\n client.captureError(error, {\n source: 'hono-middleware',\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n })\n\n // Re-throw so Hono's error handler can deal with it\n throw err\n }\n }\n}\n\n/**\n * One-line setup: initializes a TarslogsClient and attaches\n * the middleware to a Hono app.\n *\n * Usage:\n * const tarslogs = tarslogsHono(app, { url: '...', projectId: '...', apiKey: '...' })\n */\nexport function tarslogsHono(app: HonoApp, config: TarslogsConfig): TarslogsClient {\n const client = new TarslogsClient()\n client.init(config)\n app.use('*', tarslogsMiddleware(client))\n return client\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\n\ninterface RateLimitState {\n timestamps: number[]\n maxPerMinute: number\n}\n\nexport class Transport {\n private config: TarslogsConfig | null = null\n private errorQueue: ErrorPayload[] = []\n private metricQueue: MetricPayload[] = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private rateLimitState: RateLimitState = {\n timestamps: [],\n maxPerMinute: 30,\n }\n private retryQueue: Array<{ url: string; body: string }> = []\n\n init(config: TarslogsConfig): void {\n this.config = config\n this.rateLimitState.maxPerMinute = config.rateLimitPerMinute ?? 30\n\n // Start periodic flush\n this.flushTimer = setInterval(() => {\n this.flushAll()\n }, 5000)\n\n // Register page unload handler (browser only)\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushAllBeacon()\n }\n })\n window.addEventListener('pagehide', () => {\n this.flushAllBeacon()\n })\n }\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n console.log('[tarslogs]', ...args)\n }\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now()\n const windowStart = now - 60_000\n\n // Remove timestamps outside the window\n this.rateLimitState.timestamps = this.rateLimitState.timestamps.filter(\n (t) => t > windowStart\n )\n\n if (this.rateLimitState.timestamps.length >= this.rateLimitState.maxPerMinute) {\n this.log('Rate limit reached, dropping payload')\n return false\n }\n\n this.rateLimitState.timestamps.push(now)\n return true\n }\n\n enqueueError(payload: ErrorPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.errorQueue.push(payload)\n this.log('Error queued:', payload.message)\n\n if (this.errorQueue.length >= 10) {\n this.flushErrors()\n }\n }\n\n enqueueMetric(payload: MetricPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.metricQueue.push(payload)\n this.log('Metric queued:', payload.name, payload.value)\n\n if (this.metricQueue.length >= 10) {\n this.flushMetrics()\n }\n }\n\n private getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'x-tarslogs-key': this.config!.apiKey,\n }\n }\n\n private async sendFetch(url: string, body: string): Promise<boolean> {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n body,\n })\n if (!res.ok) {\n this.log('Send failed:', res.status, res.statusText)\n return false\n }\n this.log('Send success:', url)\n return true\n } catch (err) {\n this.log('Send error:', err)\n return false\n }\n }\n\n private async sendWithRetry(url: string, body: string): Promise<void> {\n const success = await this.sendFetch(url, body)\n if (!success) {\n // Retry once after 5s\n this.retryQueue.push({ url, body })\n setTimeout(() => {\n this.processRetryQueue()\n }, 5000)\n }\n }\n\n private async processRetryQueue(): Promise<void> {\n const items = this.retryQueue.splice(0, this.retryQueue.length)\n for (const item of items) {\n await this.sendFetch(item.url, item.body)\n }\n }\n\n private async flushErrors(): Promise<void> {\n if (!this.config || this.errorQueue.length === 0) return\n\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const url = `${this.config.url}/ingest/errors`\n const body = JSON.stringify({ errors })\n\n this.log(`Flushing ${errors.length} errors`)\n await this.sendWithRetry(url, body)\n }\n\n private async flushMetrics(): Promise<void> {\n if (!this.config || this.metricQueue.length === 0) return\n\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const url = `${this.config.url}/ingest/metrics`\n const body = JSON.stringify({ metrics })\n\n this.log(`Flushing ${metrics.length} metrics`)\n await this.sendWithRetry(url, body)\n }\n\n private flushAllBeacon(): void {\n if (!this.config) return\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const blob = new Blob(\n [JSON.stringify({ errors })],\n { type: 'application/json' }\n )\n // sendBeacon doesn't support custom headers, so we embed the key in the URL\n navigator.sendBeacon(\n `${this.config.url}/ingest/errors?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent errors:', errors.length)\n }\n\n if (this.metricQueue.length > 0) {\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const blob = new Blob(\n [JSON.stringify({ metrics })],\n { type: 'application/json' }\n )\n navigator.sendBeacon(\n `${this.config.url}/ingest/metrics?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent metrics:', metrics.length)\n }\n } else {\n // Fallback to fetch\n this.flushAll()\n }\n }\n\n async flushAll(): Promise<void> {\n await Promise.all([this.flushErrors(), this.flushMetrics()])\n }\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\nimport { Transport } from './transport'\n\nexport type { TarslogsConfig } from './types'\n\nexport class TarslogsClient {\n private config: TarslogsConfig | null = null\n private transport = new Transport()\n private context: Record<string, any> = {}\n\n /**\n * Initialize the client with configuration.\n * Must be called before any other methods.\n */\n init(config: TarslogsConfig): void {\n this.config = config\n this.transport.init(config)\n }\n\n /**\n * Merge additional context that will be sent with all errors and metrics.\n * Useful for setting userId, route, etc.\n */\n setContext(ctx: Record<string, any>): void {\n this.context = { ...this.context, ...ctx }\n }\n\n /**\n * Clear all context (e.g., on logout).\n */\n clearContext(): void {\n this.context = {}\n }\n\n /**\n * Capture an error and send to TarsLogs.\n */\n captureError(error: Error | string, extra?: Record<string, any>): void {\n if (!this.config) return\n\n let payload: ErrorPayload\n\n if (typeof error === 'string') {\n payload = {\n message: error,\n environment: this.config.environment,\n version: this.config.version,\n context: { ...this.context, ...extra },\n timestamp: new Date().toISOString(),\n }\n } else {\n payload = {\n message: error.message,\n stack: error.stack,\n environment: this.config.environment,\n version: this.config.version,\n context: { ...this.context, ...extra },\n timestamp: new Date().toISOString(),\n }\n\n // Try to extract file/line/col from stack\n if (error.stack) {\n const match = error.stack.match(/(?:at\\s+.+\\s+\\(|at\\s+)(.+):(\\d+):(\\d+)\\)?/)\n if (match) {\n payload.file = match[1]\n payload.line = parseInt(match[2], 10)\n payload.col = parseInt(match[3], 10)\n }\n }\n }\n\n this.transport.enqueueError(payload)\n }\n\n /**\n * Increment a counter metric.\n */\n increment(name: string, value: number = 1, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'counter',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Set a gauge metric (point-in-time value).\n */\n gauge(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'gauge',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Record a histogram metric (distribution of values).\n */\n histogram(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'histogram',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Flush all pending payloads immediately.\n * Call before page unload or process exit.\n */\n async flush(): Promise<void> {\n await this.transport.flushAll()\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/hono.ts","../src/transport.ts","../src/device.ts","../src/core.ts"],"sourcesContent":["import { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\n// Server-side Hono middleware — NO browser globals (window, document, navigator)\n\ninterface HonoContext {\n req: {\n method: string\n path: string\n url: string\n }\n res: {\n status: number\n }\n json: (data: unknown, status?: number) => Response\n}\n\ntype HonoNext = () => Promise<void>\n\ninterface HonoMiddleware {\n (c: HonoContext, next: HonoNext): Promise<Response | void>\n}\n\ninterface HonoApp {\n use: (path: string, ...handlers: HonoMiddleware[]) => void\n}\n\n/**\n * Hono middleware factory that catches errors in route handlers\n * and reports them to TarsLogs.\n *\n * Usage:\n * app.use('*', tarslogsMiddleware(tarslogsClient))\n */\nexport function tarslogsMiddleware(client: TarslogsClient): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n try {\n await next()\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err))\n\n client.captureError(error, {\n source: 'hono-middleware',\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n })\n\n // Re-throw so Hono's error handler can deal with it\n throw err\n }\n }\n}\n\n/**\n * One-line setup: initializes a TarslogsClient and attaches\n * the middleware to a Hono app.\n *\n * Usage:\n * const tarslogs = tarslogsHono(app, { url: '...', projectId: '...', apiKey: '...' })\n */\nexport function tarslogsHono(app: HonoApp, config: TarslogsConfig): TarslogsClient {\n const client = new TarslogsClient()\n client.init(config)\n app.use('*', tarslogsMiddleware(client))\n return client\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\n\ninterface RateLimitState {\n timestamps: number[]\n maxPerMinute: number\n}\n\nexport class Transport {\n private config: TarslogsConfig | null = null\n private errorQueue: ErrorPayload[] = []\n private metricQueue: MetricPayload[] = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private rateLimitState: RateLimitState = {\n timestamps: [],\n maxPerMinute: 30,\n }\n private retryQueue: Array<{ url: string; body: string }> = []\n\n init(config: TarslogsConfig): void {\n this.config = config\n this.rateLimitState.maxPerMinute = config.rateLimitPerMinute ?? 30\n\n // Start periodic flush\n this.flushTimer = setInterval(() => {\n this.flushAll()\n }, 5000)\n\n // Register page unload handler (browser only)\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushAllBeacon()\n }\n })\n window.addEventListener('pagehide', () => {\n this.flushAllBeacon()\n })\n }\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n console.log('[tarslogs]', ...args)\n }\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now()\n const windowStart = now - 60_000\n\n // Remove timestamps outside the window\n this.rateLimitState.timestamps = this.rateLimitState.timestamps.filter(\n (t) => t > windowStart\n )\n\n if (this.rateLimitState.timestamps.length >= this.rateLimitState.maxPerMinute) {\n this.log('Rate limit reached, dropping payload')\n return false\n }\n\n this.rateLimitState.timestamps.push(now)\n return true\n }\n\n enqueueError(payload: ErrorPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.errorQueue.push(payload)\n this.log('Error queued:', payload.message)\n\n if (this.errorQueue.length >= 10) {\n this.flushErrors()\n }\n }\n\n enqueueMetric(payload: MetricPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.metricQueue.push(payload)\n this.log('Metric queued:', payload.name, payload.value)\n\n if (this.metricQueue.length >= 10) {\n this.flushMetrics()\n }\n }\n\n private getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'x-tarslogs-key': this.config!.apiKey,\n }\n }\n\n private async sendFetch(url: string, body: string): Promise<boolean> {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n body,\n })\n if (!res.ok) {\n this.log('Send failed:', res.status, res.statusText)\n return false\n }\n this.log('Send success:', url)\n return true\n } catch (err) {\n this.log('Send error:', err)\n return false\n }\n }\n\n private async sendWithRetry(url: string, body: string): Promise<void> {\n const success = await this.sendFetch(url, body)\n if (!success) {\n // Retry once after 5s\n this.retryQueue.push({ url, body })\n setTimeout(() => {\n this.processRetryQueue()\n }, 5000)\n }\n }\n\n private async processRetryQueue(): Promise<void> {\n const items = this.retryQueue.splice(0, this.retryQueue.length)\n for (const item of items) {\n await this.sendFetch(item.url, item.body)\n }\n }\n\n private async flushErrors(): Promise<void> {\n if (!this.config || this.errorQueue.length === 0) return\n\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const url = `${this.config.url}/ingest/errors`\n const body = JSON.stringify({ errors })\n\n this.log(`Flushing ${errors.length} errors`)\n await this.sendWithRetry(url, body)\n }\n\n private async flushMetrics(): Promise<void> {\n if (!this.config || this.metricQueue.length === 0) return\n\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const url = `${this.config.url}/ingest/metrics`\n const body = JSON.stringify({ metrics })\n\n this.log(`Flushing ${metrics.length} metrics`)\n await this.sendWithRetry(url, body)\n }\n\n private flushAllBeacon(): void {\n if (!this.config) return\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const blob = new Blob(\n [JSON.stringify({ errors })],\n { type: 'application/json' }\n )\n // sendBeacon doesn't support custom headers, so we embed the key in the URL\n navigator.sendBeacon(\n `${this.config.url}/ingest/errors?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent errors:', errors.length)\n }\n\n if (this.metricQueue.length > 0) {\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const blob = new Blob(\n [JSON.stringify({ metrics })],\n { type: 'application/json' }\n )\n navigator.sendBeacon(\n `${this.config.url}/ingest/metrics?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent metrics:', metrics.length)\n }\n } else {\n // Fallback to fetch\n this.flushAll()\n }\n }\n\n async flushAll(): Promise<void> {\n await Promise.all([this.flushErrors(), this.flushMetrics()])\n }\n}\n","/**\n * Auto-detect browser, OS, device, and screen info.\n * Only runs in browser environments.\n */\n\nexport interface DeviceInfo {\n browser: string\n browserVersion: string\n os: string\n osVersion: string\n deviceType: 'desktop' | 'tablet' | 'mobile' | 'unknown'\n screenResolution: string\n viewportSize: string\n language: string\n referrer: string\n}\n\nexport function detectDevice(): DeviceInfo | null {\n if (typeof navigator === 'undefined' || typeof window === 'undefined') {\n return null\n }\n\n const ua = navigator.userAgent\n\n return {\n browser: detectBrowser(ua),\n browserVersion: detectBrowserVersion(ua),\n os: detectOS(ua),\n osVersion: detectOSVersion(ua),\n deviceType: detectDeviceType(ua),\n screenResolution: typeof screen !== 'undefined'\n ? `${screen.width}x${screen.height}`\n : 'unknown',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n language: navigator.language || 'unknown',\n referrer: typeof document !== 'undefined' ? document.referrer : '',\n }\n}\n\nfunction detectBrowser(ua: string): string {\n // Order matters — check more specific strings first\n if (ua.includes('Firefox/')) return 'Firefox'\n if (ua.includes('Edg/')) return 'Edge'\n if (ua.includes('OPR/') || ua.includes('Opera/')) return 'Opera'\n if (ua.includes('Chrome/') && !ua.includes('Edg/')) return 'Chrome'\n if (ua.includes('Safari/') && !ua.includes('Chrome/')) return 'Safari'\n if (ua.includes('MSIE') || ua.includes('Trident/')) return 'IE'\n return 'Unknown'\n}\n\nfunction detectBrowserVersion(ua: string): string {\n const patterns: [string, RegExp][] = [\n ['Firefox', /Firefox\\/(\\d+[\\d.]*)/],\n ['Edge', /Edg\\/(\\d+[\\d.]*)/],\n ['Opera', /(?:OPR|Opera)\\/(\\d+[\\d.]*)/],\n ['Chrome', /Chrome\\/(\\d+[\\d.]*)/],\n ['Safari', /Version\\/(\\d+[\\d.]*)/],\n ['IE', /(?:MSIE |rv:)(\\d+[\\d.]*)/],\n ]\n for (const [, regex] of patterns) {\n const match = ua.match(regex)\n if (match) return match[1]\n }\n return 'unknown'\n}\n\nfunction detectOS(ua: string): string {\n if (ua.includes('Windows')) return 'Windows'\n if (ua.includes('Mac OS X') || ua.includes('Macintosh')) return 'macOS'\n if (ua.includes('CrOS')) return 'Chrome OS'\n if (ua.includes('Linux') && !ua.includes('Android')) return 'Linux'\n if (ua.includes('Android')) return 'Android'\n if (/iPad|iPhone|iPod/.test(ua)) return 'iOS'\n return 'Unknown'\n}\n\nfunction detectOSVersion(ua: string): string {\n const patterns: [string, RegExp, ((m: string) => string)?][] = [\n ['Windows', /Windows NT (\\d+[\\d.]*)/, (v) => {\n const map: Record<string, string> = { '10.0': '10/11', '6.3': '8.1', '6.2': '8', '6.1': '7' }\n return map[v] || v\n }],\n ['macOS', /Mac OS X (\\d+[._\\d]*)/, (v) => v.replace(/_/g, '.')],\n ['Android', /Android (\\d+[\\d.]*)/],\n ['iOS', /OS (\\d+[_\\d]*)/, (v) => v.replace(/_/g, '.')],\n ]\n for (const [, regex, transform] of patterns) {\n const match = ua.match(regex)\n if (match) return transform ? transform(match[1]) : match[1]\n }\n return 'unknown'\n}\n\nfunction detectDeviceType(ua: string): 'desktop' | 'tablet' | 'mobile' | 'unknown' {\n if (/iPad|Android(?!.*Mobile)/.test(ua) && typeof screen !== 'undefined' && Math.min(screen.width, screen.height) > 600) {\n return 'tablet'\n }\n if (/Mobi|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {\n return 'mobile'\n }\n if (typeof navigator !== 'undefined' && /Windows|Macintosh|Linux/.test(ua) && !/Mobi/i.test(ua)) {\n return 'desktop'\n }\n return 'unknown'\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\nimport { Transport } from './transport'\nimport { detectDevice } from './device'\nimport type { DeviceInfo } from './device'\n\nexport type { TarslogsConfig } from './types'\n\nfunction generateSessionId(): string {\n // Random 16-char hex string\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const bytes = new Uint8Array(8)\n crypto.getRandomValues(bytes)\n return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')\n }\n return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)\n}\n\nexport class TarslogsClient {\n private config: TarslogsConfig | null = null\n private transport = new Transport()\n private context: Record<string, any> = {}\n private deviceInfo: DeviceInfo | null = null\n private sessionId: string = ''\n\n /**\n * Initialize the client with configuration.\n * Must be called before any other methods.\n * Auto-detects browser, OS, device, and screen info in browser environments.\n */\n init(config: TarslogsConfig): void {\n this.config = config\n this.transport.init(config)\n this.sessionId = generateSessionId()\n this.deviceInfo = detectDevice()\n }\n\n /**\n * Merge additional context that will be sent with all errors and metrics.\n * Useful for setting userId, route, etc.\n */\n setContext(ctx: Record<string, any>): void {\n this.context = { ...this.context, ...ctx }\n }\n\n /**\n * Clear all context (e.g., on logout).\n */\n clearContext(): void {\n this.context = {}\n }\n\n /**\n * Build the full context object for an error/metric.\n */\n private buildContext(extra?: Record<string, any>): Record<string, any> {\n const ctx: Record<string, any> = {\n sessionId: this.sessionId,\n ...this.context,\n ...extra,\n }\n\n // Add current URL\n if (typeof window !== 'undefined' && window.location) {\n ctx.url = window.location.href\n ctx.path = window.location.pathname\n }\n\n // Add device info\n if (this.deviceInfo) {\n ctx.browser = this.deviceInfo.browser\n ctx.browserVersion = this.deviceInfo.browserVersion\n ctx.os = this.deviceInfo.os\n ctx.osVersion = this.deviceInfo.osVersion\n ctx.deviceType = this.deviceInfo.deviceType\n ctx.screenResolution = this.deviceInfo.screenResolution\n ctx.viewportSize = this.deviceInfo.viewportSize\n ctx.language = this.deviceInfo.language\n if (this.deviceInfo.referrer) {\n ctx.referrer = this.deviceInfo.referrer\n }\n }\n\n return ctx\n }\n\n /**\n * Capture an error and send to TarsLogs.\n */\n captureError(error: Error | string, extra?: Record<string, any>): void {\n if (!this.config) return\n\n let payload: ErrorPayload\n\n if (typeof error === 'string') {\n payload = {\n message: error,\n environment: this.config.environment,\n version: this.config.version,\n context: this.buildContext(extra),\n timestamp: new Date().toISOString(),\n }\n } else {\n payload = {\n message: error.message,\n stack: error.stack,\n environment: this.config.environment,\n version: this.config.version,\n context: this.buildContext(extra),\n timestamp: new Date().toISOString(),\n }\n\n // Try to extract file/line/col from stack\n if (error.stack) {\n const match = error.stack.match(/(?:at\\s+.+\\s+\\(|at\\s+)(.+):(\\d+):(\\d+)\\)?/)\n if (match) {\n payload.file = match[1]\n payload.line = parseInt(match[2], 10)\n payload.col = parseInt(match[3], 10)\n }\n }\n }\n\n this.transport.enqueueError(payload)\n }\n\n /**\n * Increment a counter metric.\n */\n increment(name: string, value: number = 1, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'counter',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Set a gauge metric (point-in-time value).\n */\n gauge(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'gauge',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Record a histogram metric (distribution of values).\n */\n histogram(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'histogram',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Flush all pending payloads immediately.\n * Call before page unload or process exit.\n */\n async flush(): Promise<void> {\n await this.transport.flushAll()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,YAAN,MAAgB;AAAA,EACb,SAAgC;AAAA,EAChC,aAA6B,CAAC;AAAA,EAC9B,cAA+B,CAAC;AAAA,EAChC,aAAoD;AAAA,EACpD,iBAAiC;AAAA,IACvC,YAAY,CAAC;AAAA,IACb,cAAc;AAAA,EAChB;AAAA,EACQ,aAAmD,CAAC;AAAA,EAE5D,KAAK,QAA8B;AACjC,SAAK,SAAS;AACd,SAAK,eAAe,eAAe,OAAO,sBAAsB;AAGhE,SAAK,aAAa,YAAY,MAAM;AAClC,WAAK,SAAS;AAAA,IAChB,GAAG,GAAI;AAGP,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,qBAAqB,YAAY;AAClF,aAAO,iBAAiB,oBAAoB,MAAM;AAChD,YAAI,SAAS,oBAAoB,UAAU;AACzC,eAAK,eAAe;AAAA,QACtB;AAAA,MACF,CAAC;AACD,aAAO,iBAAiB,YAAY,MAAM;AACxC,aAAK,eAAe;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,YAAY;AACnB,oBAAc,KAAK,UAAU;AAC7B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,OAAO,MAAuB;AACpC,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ,IAAI,cAAc,GAAG,IAAI;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,iBAA0B;AAChC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,cAAc,MAAM;AAG1B,SAAK,eAAe,aAAa,KAAK,eAAe,WAAW;AAAA,MAC9D,CAAC,MAAM,IAAI;AAAA,IACb;AAEA,QAAI,KAAK,eAAe,WAAW,UAAU,KAAK,eAAe,cAAc;AAC7E,WAAK,IAAI,sCAAsC;AAC/C,aAAO;AAAA,IACT;AAEA,SAAK,eAAe,WAAW,KAAK,GAAG;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,SAA6B;AACxC,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI,CAAC,KAAK,eAAe,EAAG;AAE5B,SAAK,WAAW,KAAK,OAAO;AAC5B,SAAK,IAAI,iBAAiB,QAAQ,OAAO;AAEzC,QAAI,KAAK,WAAW,UAAU,IAAI;AAChC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,cAAc,SAA8B;AAC1C,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI,CAAC,KAAK,eAAe,EAAG;AAE5B,SAAK,YAAY,KAAK,OAAO;AAC7B,SAAK,IAAI,kBAAkB,QAAQ,MAAM,QAAQ,KAAK;AAEtD,QAAI,KAAK,YAAY,UAAU,IAAI;AACjC,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,aAAqC;AAC3C,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,kBAAkB,KAAK,OAAQ;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAc,UAAU,KAAa,MAAgC;AACnE,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B,QAAQ;AAAA,QACR,SAAS,KAAK,WAAW;AAAA,QACzB;AAAA,MACF,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,aAAK,IAAI,gBAAgB,IAAI,QAAQ,IAAI,UAAU;AACnD,eAAO;AAAA,MACT;AACA,WAAK,IAAI,iBAAiB,GAAG;AAC7B,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,IAAI,eAAe,GAAG;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,KAAa,MAA6B;AACpE,UAAM,UAAU,MAAM,KAAK,UAAU,KAAK,IAAI;AAC9C,QAAI,CAAC,SAAS;AAEZ,WAAK,WAAW,KAAK,EAAE,KAAK,KAAK,CAAC;AAClC,iBAAW,MAAM;AACf,aAAK,kBAAkB;AAAA,MACzB,GAAG,GAAI;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,oBAAmC;AAC/C,UAAM,QAAQ,KAAK,WAAW,OAAO,GAAG,KAAK,WAAW,MAAM;AAC9D,eAAW,QAAQ,OAAO;AACxB,YAAM,KAAK,UAAU,KAAK,KAAK,KAAK,IAAI;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,QAAI,CAAC,KAAK,UAAU,KAAK,WAAW,WAAW,EAAG;AAElD,UAAM,SAAS,KAAK,WAAW,OAAO,GAAG,KAAK,WAAW,MAAM;AAC/D,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG;AAC9B,UAAM,OAAO,KAAK,UAAU,EAAE,OAAO,CAAC;AAEtC,SAAK,IAAI,YAAY,OAAO,MAAM,SAAS;AAC3C,UAAM,KAAK,cAAc,KAAK,IAAI;AAAA,EACpC;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,CAAC,KAAK,UAAU,KAAK,YAAY,WAAW,EAAG;AAEnD,UAAM,UAAU,KAAK,YAAY,OAAO,GAAG,KAAK,YAAY,MAAM;AAClE,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG;AAC9B,UAAM,OAAO,KAAK,UAAU,EAAE,QAAQ,CAAC;AAEvC,SAAK,IAAI,YAAY,QAAQ,MAAM,UAAU;AAC7C,UAAM,KAAK,cAAc,KAAK,IAAI;AAAA,EACpC;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,CAAC,KAAK,OAAQ;AAElB,QAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,YAAY;AAClF,UAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,cAAM,SAAS,KAAK,WAAW,OAAO,GAAG,KAAK,WAAW,MAAM;AAC/D,cAAM,OAAO,IAAI;AAAA,UACf,CAAC,KAAK,UAAU,EAAE,OAAO,CAAC,CAAC;AAAA,UAC3B,EAAE,MAAM,mBAAmB;AAAA,QAC7B;AAEA,kBAAU;AAAA,UACR,GAAG,KAAK,OAAO,GAAG,sBAAsB,KAAK,OAAO,MAAM;AAAA,UAC1D;AAAA,QACF;AACA,aAAK,IAAI,uBAAuB,OAAO,MAAM;AAAA,MAC/C;AAEA,UAAI,KAAK,YAAY,SAAS,GAAG;AAC/B,cAAM,UAAU,KAAK,YAAY,OAAO,GAAG,KAAK,YAAY,MAAM;AAClE,cAAM,OAAO,IAAI;AAAA,UACf,CAAC,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AAAA,UAC5B,EAAE,MAAM,mBAAmB;AAAA,QAC7B;AACA,kBAAU;AAAA,UACR,GAAG,KAAK,OAAO,GAAG,uBAAuB,KAAK,OAAO,MAAM;AAAA,UAC3D;AAAA,QACF;AACA,aAAK,IAAI,wBAAwB,QAAQ,MAAM;AAAA,MACjD;AAAA,IACF,OAAO;AAEL,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,QAAQ,IAAI,CAAC,KAAK,YAAY,GAAG,KAAK,aAAa,CAAC,CAAC;AAAA,EAC7D;AACF;;;ACvLO,SAAS,eAAkC;AAChD,MAAI,OAAO,cAAc,eAAe,OAAO,WAAW,aAAa;AACrE,WAAO;AAAA,EACT;AAEA,QAAM,KAAK,UAAU;AAErB,SAAO;AAAA,IACL,SAAS,cAAc,EAAE;AAAA,IACzB,gBAAgB,qBAAqB,EAAE;AAAA,IACvC,IAAI,SAAS,EAAE;AAAA,IACf,WAAW,gBAAgB,EAAE;AAAA,IAC7B,YAAY,iBAAiB,EAAE;AAAA,IAC/B,kBAAkB,OAAO,WAAW,cAChC,GAAG,OAAO,KAAK,IAAI,OAAO,MAAM,KAChC;AAAA,IACJ,cAAc,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW;AAAA,IACxD,UAAU,UAAU,YAAY;AAAA,IAChC,UAAU,OAAO,aAAa,cAAc,SAAS,WAAW;AAAA,EAClE;AACF;AAEA,SAAS,cAAc,IAAoB;AAEzC,MAAI,GAAG,SAAS,UAAU,EAAG,QAAO;AACpC,MAAI,GAAG,SAAS,MAAM,EAAG,QAAO;AAChC,MAAI,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,QAAQ,EAAG,QAAO;AACzD,MAAI,GAAG,SAAS,SAAS,KAAK,CAAC,GAAG,SAAS,MAAM,EAAG,QAAO;AAC3D,MAAI,GAAG,SAAS,SAAS,KAAK,CAAC,GAAG,SAAS,SAAS,EAAG,QAAO;AAC9D,MAAI,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,UAAU,EAAG,QAAO;AAC3D,SAAO;AACT;AAEA,SAAS,qBAAqB,IAAoB;AAChD,QAAM,WAA+B;AAAA,IACnC,CAAC,WAAW,sBAAsB;AAAA,IAClC,CAAC,QAAQ,kBAAkB;AAAA,IAC3B,CAAC,SAAS,4BAA4B;AAAA,IACtC,CAAC,UAAU,qBAAqB;AAAA,IAChC,CAAC,UAAU,sBAAsB;AAAA,IACjC,CAAC,MAAM,0BAA0B;AAAA,EACnC;AACA,aAAW,CAAC,EAAE,KAAK,KAAK,UAAU;AAChC,UAAM,QAAQ,GAAG,MAAM,KAAK;AAC5B,QAAI,MAAO,QAAO,MAAM,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,SAAS,SAAS,IAAoB;AACpC,MAAI,GAAG,SAAS,SAAS,EAAG,QAAO;AACnC,MAAI,GAAG,SAAS,UAAU,KAAK,GAAG,SAAS,WAAW,EAAG,QAAO;AAChE,MAAI,GAAG,SAAS,MAAM,EAAG,QAAO;AAChC,MAAI,GAAG,SAAS,OAAO,KAAK,CAAC,GAAG,SAAS,SAAS,EAAG,QAAO;AAC5D,MAAI,GAAG,SAAS,SAAS,EAAG,QAAO;AACnC,MAAI,mBAAmB,KAAK,EAAE,EAAG,QAAO;AACxC,SAAO;AACT;AAEA,SAAS,gBAAgB,IAAoB;AAC3C,QAAM,WAAyD;AAAA,IAC7D,CAAC,WAAW,0BAA0B,CAAC,MAAM;AAC3C,YAAM,MAA8B,EAAE,QAAQ,SAAS,OAAO,OAAO,OAAO,KAAK,OAAO,IAAI;AAC5F,aAAO,IAAI,CAAC,KAAK;AAAA,IACnB,CAAC;AAAA,IACD,CAAC,SAAS,yBAAyB,CAAC,MAAM,EAAE,QAAQ,MAAM,GAAG,CAAC;AAAA,IAC9D,CAAC,WAAW,qBAAqB;AAAA,IACjC,CAAC,OAAO,kBAAkB,CAAC,MAAM,EAAE,QAAQ,MAAM,GAAG,CAAC;AAAA,EACvD;AACA,aAAW,CAAC,EAAE,OAAO,SAAS,KAAK,UAAU;AAC3C,UAAM,QAAQ,GAAG,MAAM,KAAK;AAC5B,QAAI,MAAO,QAAO,YAAY,UAAU,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC;AAAA,EAC7D;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,IAAyD;AACjF,MAAI,2BAA2B,KAAK,EAAE,KAAK,OAAO,WAAW,eAAe,KAAK,IAAI,OAAO,OAAO,OAAO,MAAM,IAAI,KAAK;AACvH,WAAO;AAAA,EACT;AACA,MAAI,mEAAmE,KAAK,EAAE,GAAG;AAC/E,WAAO;AAAA,EACT;AACA,MAAI,OAAO,cAAc,eAAe,0BAA0B,KAAK,EAAE,KAAK,CAAC,QAAQ,KAAK,EAAE,GAAG;AAC/F,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;ACjGA,SAAS,oBAA4B;AAEnC,MAAI,OAAO,WAAW,eAAe,OAAO,iBAAiB;AAC3D,UAAM,QAAQ,IAAI,WAAW,CAAC;AAC9B,WAAO,gBAAgB,KAAK;AAC5B,WAAO,MAAM,KAAK,KAAK,EAAE,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAAA,EAC5E;AACA,SAAO,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AACjF;AAEO,IAAM,iBAAN,MAAqB;AAAA,EAClB,SAAgC;AAAA,EAChC,YAAY,IAAI,UAAU;AAAA,EAC1B,UAA+B,CAAC;AAAA,EAChC,aAAgC;AAAA,EAChC,YAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,KAAK,QAA8B;AACjC,SAAK,SAAS;AACd,SAAK,UAAU,KAAK,MAAM;AAC1B,SAAK,YAAY,kBAAkB;AACnC,SAAK,aAAa,aAAa;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,KAAgC;AACzC,SAAK,UAAU,EAAE,GAAG,KAAK,SAAS,GAAG,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,UAAU,CAAC;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,OAAkD;AACrE,UAAM,MAA2B;AAAA,MAC/B,WAAW,KAAK;AAAA,MAChB,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AAGA,QAAI,OAAO,WAAW,eAAe,OAAO,UAAU;AACpD,UAAI,MAAM,OAAO,SAAS;AAC1B,UAAI,OAAO,OAAO,SAAS;AAAA,IAC7B;AAGA,QAAI,KAAK,YAAY;AACnB,UAAI,UAAU,KAAK,WAAW;AAC9B,UAAI,iBAAiB,KAAK,WAAW;AACrC,UAAI,KAAK,KAAK,WAAW;AACzB,UAAI,YAAY,KAAK,WAAW;AAChC,UAAI,aAAa,KAAK,WAAW;AACjC,UAAI,mBAAmB,KAAK,WAAW;AACvC,UAAI,eAAe,KAAK,WAAW;AACnC,UAAI,WAAW,KAAK,WAAW;AAC/B,UAAI,KAAK,WAAW,UAAU;AAC5B,YAAI,WAAW,KAAK,WAAW;AAAA,MACjC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,OAAuB,OAAmC;AACrE,QAAI,CAAC,KAAK,OAAQ;AAElB,QAAI;AAEJ,QAAI,OAAO,UAAU,UAAU;AAC7B,gBAAU;AAAA,QACR,SAAS;AAAA,QACT,aAAa,KAAK,OAAO;AAAA,QACzB,SAAS,KAAK,OAAO;AAAA,QACrB,SAAS,KAAK,aAAa,KAAK;AAAA,QAChC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAAA,IACF,OAAO;AACL,gBAAU;AAAA,QACR,SAAS,MAAM;AAAA,QACf,OAAO,MAAM;AAAA,QACb,aAAa,KAAK,OAAO;AAAA,QACzB,SAAS,KAAK,OAAO;AAAA,QACrB,SAAS,KAAK,aAAa,KAAK;AAAA,QAChC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AAGA,UAAI,MAAM,OAAO;AACf,cAAM,QAAQ,MAAM,MAAM,MAAM,2CAA2C;AAC3E,YAAI,OAAO;AACT,kBAAQ,OAAO,MAAM,CAAC;AACtB,kBAAQ,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AACpC,kBAAQ,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,SAAK,UAAU,aAAa,OAAO;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,MAAc,QAAgB,GAAG,MAAqC;AAC9E,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,SAAK,UAAU,cAAc,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAc,OAAe,MAAqC;AACtE,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,SAAK,UAAU,cAAc,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,MAAc,OAAe,MAAqC;AAC1E,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,UAAyB;AAAA,MAC7B;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,SAAK,UAAU,cAAc,OAAO;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,UAAM,KAAK,UAAU,SAAS;AAAA,EAChC;AACF;;;AH/IO,SAAS,mBAAmB,QAAwC;AACzE,SAAO,OAAO,GAAgB,SAA6C;AACzE,QAAI;AACF,YAAM,KAAK;AAAA,IACb,SAAS,KAAc;AACrB,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAEhE,aAAO,aAAa,OAAO;AAAA,QACzB,QAAQ;AAAA,QACR,QAAQ,EAAE,IAAI;AAAA,QACd,MAAM,EAAE,IAAI;AAAA,QACZ,KAAK,EAAE,IAAI;AAAA,MACb,CAAC;AAGD,YAAM;AAAA,IACR;AAAA,EACF;AACF;AASO,SAAS,aAAa,KAAc,QAAwC;AACjF,QAAM,SAAS,IAAI,eAAe;AAClC,SAAO,KAAK,MAAM;AAClB,MAAI,IAAI,KAAK,mBAAmB,MAAM,CAAC;AACvC,SAAO;AACT;","names":[]}
|
package/dist/hono.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-
|
|
1
|
+
export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-CB3Uka-S.cjs';
|
package/dist/hono.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-
|
|
1
|
+
export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-CB3Uka-S.js';
|
package/dist/hono.js
CHANGED
|
@@ -159,18 +159,111 @@ var Transport = class {
|
|
|
159
159
|
}
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
// src/device.ts
|
|
163
|
+
function detectDevice() {
|
|
164
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const ua = navigator.userAgent;
|
|
168
|
+
return {
|
|
169
|
+
browser: detectBrowser(ua),
|
|
170
|
+
browserVersion: detectBrowserVersion(ua),
|
|
171
|
+
os: detectOS(ua),
|
|
172
|
+
osVersion: detectOSVersion(ua),
|
|
173
|
+
deviceType: detectDeviceType(ua),
|
|
174
|
+
screenResolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : "unknown",
|
|
175
|
+
viewportSize: `${window.innerWidth}x${window.innerHeight}`,
|
|
176
|
+
language: navigator.language || "unknown",
|
|
177
|
+
referrer: typeof document !== "undefined" ? document.referrer : ""
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function detectBrowser(ua) {
|
|
181
|
+
if (ua.includes("Firefox/")) return "Firefox";
|
|
182
|
+
if (ua.includes("Edg/")) return "Edge";
|
|
183
|
+
if (ua.includes("OPR/") || ua.includes("Opera/")) return "Opera";
|
|
184
|
+
if (ua.includes("Chrome/") && !ua.includes("Edg/")) return "Chrome";
|
|
185
|
+
if (ua.includes("Safari/") && !ua.includes("Chrome/")) return "Safari";
|
|
186
|
+
if (ua.includes("MSIE") || ua.includes("Trident/")) return "IE";
|
|
187
|
+
return "Unknown";
|
|
188
|
+
}
|
|
189
|
+
function detectBrowserVersion(ua) {
|
|
190
|
+
const patterns = [
|
|
191
|
+
["Firefox", /Firefox\/(\d+[\d.]*)/],
|
|
192
|
+
["Edge", /Edg\/(\d+[\d.]*)/],
|
|
193
|
+
["Opera", /(?:OPR|Opera)\/(\d+[\d.]*)/],
|
|
194
|
+
["Chrome", /Chrome\/(\d+[\d.]*)/],
|
|
195
|
+
["Safari", /Version\/(\d+[\d.]*)/],
|
|
196
|
+
["IE", /(?:MSIE |rv:)(\d+[\d.]*)/]
|
|
197
|
+
];
|
|
198
|
+
for (const [, regex] of patterns) {
|
|
199
|
+
const match = ua.match(regex);
|
|
200
|
+
if (match) return match[1];
|
|
201
|
+
}
|
|
202
|
+
return "unknown";
|
|
203
|
+
}
|
|
204
|
+
function detectOS(ua) {
|
|
205
|
+
if (ua.includes("Windows")) return "Windows";
|
|
206
|
+
if (ua.includes("Mac OS X") || ua.includes("Macintosh")) return "macOS";
|
|
207
|
+
if (ua.includes("CrOS")) return "Chrome OS";
|
|
208
|
+
if (ua.includes("Linux") && !ua.includes("Android")) return "Linux";
|
|
209
|
+
if (ua.includes("Android")) return "Android";
|
|
210
|
+
if (/iPad|iPhone|iPod/.test(ua)) return "iOS";
|
|
211
|
+
return "Unknown";
|
|
212
|
+
}
|
|
213
|
+
function detectOSVersion(ua) {
|
|
214
|
+
const patterns = [
|
|
215
|
+
["Windows", /Windows NT (\d+[\d.]*)/, (v) => {
|
|
216
|
+
const map = { "10.0": "10/11", "6.3": "8.1", "6.2": "8", "6.1": "7" };
|
|
217
|
+
return map[v] || v;
|
|
218
|
+
}],
|
|
219
|
+
["macOS", /Mac OS X (\d+[._\d]*)/, (v) => v.replace(/_/g, ".")],
|
|
220
|
+
["Android", /Android (\d+[\d.]*)/],
|
|
221
|
+
["iOS", /OS (\d+[_\d]*)/, (v) => v.replace(/_/g, ".")]
|
|
222
|
+
];
|
|
223
|
+
for (const [, regex, transform] of patterns) {
|
|
224
|
+
const match = ua.match(regex);
|
|
225
|
+
if (match) return transform ? transform(match[1]) : match[1];
|
|
226
|
+
}
|
|
227
|
+
return "unknown";
|
|
228
|
+
}
|
|
229
|
+
function detectDeviceType(ua) {
|
|
230
|
+
if (/iPad|Android(?!.*Mobile)/.test(ua) && typeof screen !== "undefined" && Math.min(screen.width, screen.height) > 600) {
|
|
231
|
+
return "tablet";
|
|
232
|
+
}
|
|
233
|
+
if (/Mobi|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
|
|
234
|
+
return "mobile";
|
|
235
|
+
}
|
|
236
|
+
if (typeof navigator !== "undefined" && /Windows|Macintosh|Linux/.test(ua) && !/Mobi/i.test(ua)) {
|
|
237
|
+
return "desktop";
|
|
238
|
+
}
|
|
239
|
+
return "unknown";
|
|
240
|
+
}
|
|
241
|
+
|
|
162
242
|
// src/core.ts
|
|
243
|
+
function generateSessionId() {
|
|
244
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
245
|
+
const bytes = new Uint8Array(8);
|
|
246
|
+
crypto.getRandomValues(bytes);
|
|
247
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
248
|
+
}
|
|
249
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
250
|
+
}
|
|
163
251
|
var TarslogsClient = class {
|
|
164
252
|
config = null;
|
|
165
253
|
transport = new Transport();
|
|
166
254
|
context = {};
|
|
255
|
+
deviceInfo = null;
|
|
256
|
+
sessionId = "";
|
|
167
257
|
/**
|
|
168
258
|
* Initialize the client with configuration.
|
|
169
259
|
* Must be called before any other methods.
|
|
260
|
+
* Auto-detects browser, OS, device, and screen info in browser environments.
|
|
170
261
|
*/
|
|
171
262
|
init(config) {
|
|
172
263
|
this.config = config;
|
|
173
264
|
this.transport.init(config);
|
|
265
|
+
this.sessionId = generateSessionId();
|
|
266
|
+
this.deviceInfo = detectDevice();
|
|
174
267
|
}
|
|
175
268
|
/**
|
|
176
269
|
* Merge additional context that will be sent with all errors and metrics.
|
|
@@ -185,6 +278,34 @@ var TarslogsClient = class {
|
|
|
185
278
|
clearContext() {
|
|
186
279
|
this.context = {};
|
|
187
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Build the full context object for an error/metric.
|
|
283
|
+
*/
|
|
284
|
+
buildContext(extra) {
|
|
285
|
+
const ctx = {
|
|
286
|
+
sessionId: this.sessionId,
|
|
287
|
+
...this.context,
|
|
288
|
+
...extra
|
|
289
|
+
};
|
|
290
|
+
if (typeof window !== "undefined" && window.location) {
|
|
291
|
+
ctx.url = window.location.href;
|
|
292
|
+
ctx.path = window.location.pathname;
|
|
293
|
+
}
|
|
294
|
+
if (this.deviceInfo) {
|
|
295
|
+
ctx.browser = this.deviceInfo.browser;
|
|
296
|
+
ctx.browserVersion = this.deviceInfo.browserVersion;
|
|
297
|
+
ctx.os = this.deviceInfo.os;
|
|
298
|
+
ctx.osVersion = this.deviceInfo.osVersion;
|
|
299
|
+
ctx.deviceType = this.deviceInfo.deviceType;
|
|
300
|
+
ctx.screenResolution = this.deviceInfo.screenResolution;
|
|
301
|
+
ctx.viewportSize = this.deviceInfo.viewportSize;
|
|
302
|
+
ctx.language = this.deviceInfo.language;
|
|
303
|
+
if (this.deviceInfo.referrer) {
|
|
304
|
+
ctx.referrer = this.deviceInfo.referrer;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return ctx;
|
|
308
|
+
}
|
|
188
309
|
/**
|
|
189
310
|
* Capture an error and send to TarsLogs.
|
|
190
311
|
*/
|
|
@@ -196,7 +317,7 @@ var TarslogsClient = class {
|
|
|
196
317
|
message: error,
|
|
197
318
|
environment: this.config.environment,
|
|
198
319
|
version: this.config.version,
|
|
199
|
-
context:
|
|
320
|
+
context: this.buildContext(extra),
|
|
200
321
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
201
322
|
};
|
|
202
323
|
} else {
|
|
@@ -205,7 +326,7 @@ var TarslogsClient = class {
|
|
|
205
326
|
stack: error.stack,
|
|
206
327
|
environment: this.config.environment,
|
|
207
328
|
version: this.config.version,
|
|
208
|
-
context:
|
|
329
|
+
context: this.buildContext(extra),
|
|
209
330
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
210
331
|
};
|
|
211
332
|
if (error.stack) {
|
package/dist/hono.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transport.ts","../src/core.ts","../src/hono.ts"],"sourcesContent":["import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\n\ninterface RateLimitState {\n timestamps: number[]\n maxPerMinute: number\n}\n\nexport class Transport {\n private config: TarslogsConfig | null = null\n private errorQueue: ErrorPayload[] = []\n private metricQueue: MetricPayload[] = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private rateLimitState: RateLimitState = {\n timestamps: [],\n maxPerMinute: 30,\n }\n private retryQueue: Array<{ url: string; body: string }> = []\n\n init(config: TarslogsConfig): void {\n this.config = config\n this.rateLimitState.maxPerMinute = config.rateLimitPerMinute ?? 30\n\n // Start periodic flush\n this.flushTimer = setInterval(() => {\n this.flushAll()\n }, 5000)\n\n // Register page unload handler (browser only)\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushAllBeacon()\n }\n })\n window.addEventListener('pagehide', () => {\n this.flushAllBeacon()\n })\n }\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n console.log('[tarslogs]', ...args)\n }\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now()\n const windowStart = now - 60_000\n\n // Remove timestamps outside the window\n this.rateLimitState.timestamps = this.rateLimitState.timestamps.filter(\n (t) => t > windowStart\n )\n\n if (this.rateLimitState.timestamps.length >= this.rateLimitState.maxPerMinute) {\n this.log('Rate limit reached, dropping payload')\n return false\n }\n\n this.rateLimitState.timestamps.push(now)\n return true\n }\n\n enqueueError(payload: ErrorPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.errorQueue.push(payload)\n this.log('Error queued:', payload.message)\n\n if (this.errorQueue.length >= 10) {\n this.flushErrors()\n }\n }\n\n enqueueMetric(payload: MetricPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.metricQueue.push(payload)\n this.log('Metric queued:', payload.name, payload.value)\n\n if (this.metricQueue.length >= 10) {\n this.flushMetrics()\n }\n }\n\n private getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'x-tarslogs-key': this.config!.apiKey,\n }\n }\n\n private async sendFetch(url: string, body: string): Promise<boolean> {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n body,\n })\n if (!res.ok) {\n this.log('Send failed:', res.status, res.statusText)\n return false\n }\n this.log('Send success:', url)\n return true\n } catch (err) {\n this.log('Send error:', err)\n return false\n }\n }\n\n private async sendWithRetry(url: string, body: string): Promise<void> {\n const success = await this.sendFetch(url, body)\n if (!success) {\n // Retry once after 5s\n this.retryQueue.push({ url, body })\n setTimeout(() => {\n this.processRetryQueue()\n }, 5000)\n }\n }\n\n private async processRetryQueue(): Promise<void> {\n const items = this.retryQueue.splice(0, this.retryQueue.length)\n for (const item of items) {\n await this.sendFetch(item.url, item.body)\n }\n }\n\n private async flushErrors(): Promise<void> {\n if (!this.config || this.errorQueue.length === 0) return\n\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const url = `${this.config.url}/ingest/errors`\n const body = JSON.stringify({ errors })\n\n this.log(`Flushing ${errors.length} errors`)\n await this.sendWithRetry(url, body)\n }\n\n private async flushMetrics(): Promise<void> {\n if (!this.config || this.metricQueue.length === 0) return\n\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const url = `${this.config.url}/ingest/metrics`\n const body = JSON.stringify({ metrics })\n\n this.log(`Flushing ${metrics.length} metrics`)\n await this.sendWithRetry(url, body)\n }\n\n private flushAllBeacon(): void {\n if (!this.config) return\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const blob = new Blob(\n [JSON.stringify({ errors })],\n { type: 'application/json' }\n )\n // sendBeacon doesn't support custom headers, so we embed the key in the URL\n navigator.sendBeacon(\n `${this.config.url}/ingest/errors?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent errors:', errors.length)\n }\n\n if (this.metricQueue.length > 0) {\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const blob = new Blob(\n [JSON.stringify({ metrics })],\n { type: 'application/json' }\n )\n navigator.sendBeacon(\n `${this.config.url}/ingest/metrics?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent metrics:', metrics.length)\n }\n } else {\n // Fallback to fetch\n this.flushAll()\n }\n }\n\n async flushAll(): Promise<void> {\n await Promise.all([this.flushErrors(), this.flushMetrics()])\n }\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\nimport { Transport } from './transport'\n\nexport type { TarslogsConfig } from './types'\n\nexport class TarslogsClient {\n private config: TarslogsConfig | null = null\n private transport = new Transport()\n private context: Record<string, any> = {}\n\n /**\n * Initialize the client with configuration.\n * Must be called before any other methods.\n */\n init(config: TarslogsConfig): void {\n this.config = config\n this.transport.init(config)\n }\n\n /**\n * Merge additional context that will be sent with all errors and metrics.\n * Useful for setting userId, route, etc.\n */\n setContext(ctx: Record<string, any>): void {\n this.context = { ...this.context, ...ctx }\n }\n\n /**\n * Clear all context (e.g., on logout).\n */\n clearContext(): void {\n this.context = {}\n }\n\n /**\n * Capture an error and send to TarsLogs.\n */\n captureError(error: Error | string, extra?: Record<string, any>): void {\n if (!this.config) return\n\n let payload: ErrorPayload\n\n if (typeof error === 'string') {\n payload = {\n message: error,\n environment: this.config.environment,\n version: this.config.version,\n context: { ...this.context, ...extra },\n timestamp: new Date().toISOString(),\n }\n } else {\n payload = {\n message: error.message,\n stack: error.stack,\n environment: this.config.environment,\n version: this.config.version,\n context: { ...this.context, ...extra },\n timestamp: new Date().toISOString(),\n }\n\n // Try to extract file/line/col from stack\n if (error.stack) {\n const match = error.stack.match(/(?:at\\s+.+\\s+\\(|at\\s+)(.+):(\\d+):(\\d+)\\)?/)\n if (match) {\n payload.file = match[1]\n payload.line = parseInt(match[2], 10)\n payload.col = parseInt(match[3], 10)\n }\n }\n }\n\n this.transport.enqueueError(payload)\n }\n\n /**\n * Increment a counter metric.\n */\n increment(name: string, value: number = 1, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'counter',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Set a gauge metric (point-in-time value).\n */\n gauge(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'gauge',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Record a histogram metric (distribution of values).\n */\n histogram(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'histogram',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Flush all pending payloads immediately.\n * Call before page unload or process exit.\n */\n async flush(): Promise<void> {\n await this.transport.flushAll()\n }\n}\n","import { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\n// Server-side Hono middleware — NO browser globals (window, document, navigator)\n\ninterface HonoContext {\n req: {\n method: string\n path: string\n url: string\n }\n res: {\n status: number\n }\n json: (data: unknown, status?: number) => Response\n}\n\ntype HonoNext = () => Promise<void>\n\ninterface HonoMiddleware {\n (c: HonoContext, next: HonoNext): Promise<Response | void>\n}\n\ninterface HonoApp {\n use: (path: string, ...handlers: HonoMiddleware[]) => void\n}\n\n/**\n * Hono middleware factory that catches errors in route handlers\n * and reports them to TarsLogs.\n *\n * Usage:\n * app.use('*', tarslogsMiddleware(tarslogsClient))\n */\nexport function tarslogsMiddleware(client: TarslogsClient): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n try {\n await next()\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err))\n\n client.captureError(error, {\n source: 'hono-middleware',\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n })\n\n // Re-throw so Hono's error handler can deal with it\n throw err\n }\n }\n}\n\n/**\n * One-line setup: initializes a TarslogsClient and attaches\n * the middleware to a Hono app.\n *\n * Usage:\n * const tarslogs = tarslogsHono(app, { url: '...', projectId: '...', apiKey: '...' })\n */\nexport function tarslogsHono(app: HonoApp, config: TarslogsConfig): TarslogsClient {\n const client = new TarslogsClient()\n client.init(config)\n app.use('*', tarslogsMiddleware(client))\n return client\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/transport.ts","../src/device.ts","../src/core.ts","../src/hono.ts"],"sourcesContent":["import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\n\ninterface RateLimitState {\n timestamps: number[]\n maxPerMinute: number\n}\n\nexport class Transport {\n private config: TarslogsConfig | null = null\n private errorQueue: ErrorPayload[] = []\n private metricQueue: MetricPayload[] = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private rateLimitState: RateLimitState = {\n timestamps: [],\n maxPerMinute: 30,\n }\n private retryQueue: Array<{ url: string; body: string }> = []\n\n init(config: TarslogsConfig): void {\n this.config = config\n this.rateLimitState.maxPerMinute = config.rateLimitPerMinute ?? 30\n\n // Start periodic flush\n this.flushTimer = setInterval(() => {\n this.flushAll()\n }, 5000)\n\n // Register page unload handler (browser only)\n if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.flushAllBeacon()\n }\n })\n window.addEventListener('pagehide', () => {\n this.flushAllBeacon()\n })\n }\n }\n\n destroy(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n }\n\n private log(...args: unknown[]): void {\n if (this.config?.debug) {\n console.log('[tarslogs]', ...args)\n }\n }\n\n private checkRateLimit(): boolean {\n const now = Date.now()\n const windowStart = now - 60_000\n\n // Remove timestamps outside the window\n this.rateLimitState.timestamps = this.rateLimitState.timestamps.filter(\n (t) => t > windowStart\n )\n\n if (this.rateLimitState.timestamps.length >= this.rateLimitState.maxPerMinute) {\n this.log('Rate limit reached, dropping payload')\n return false\n }\n\n this.rateLimitState.timestamps.push(now)\n return true\n }\n\n enqueueError(payload: ErrorPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.errorQueue.push(payload)\n this.log('Error queued:', payload.message)\n\n if (this.errorQueue.length >= 10) {\n this.flushErrors()\n }\n }\n\n enqueueMetric(payload: MetricPayload): void {\n if (!this.config) return\n if (!this.checkRateLimit()) return\n\n this.metricQueue.push(payload)\n this.log('Metric queued:', payload.name, payload.value)\n\n if (this.metricQueue.length >= 10) {\n this.flushMetrics()\n }\n }\n\n private getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n 'x-tarslogs-key': this.config!.apiKey,\n }\n }\n\n private async sendFetch(url: string, body: string): Promise<boolean> {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: this.getHeaders(),\n body,\n })\n if (!res.ok) {\n this.log('Send failed:', res.status, res.statusText)\n return false\n }\n this.log('Send success:', url)\n return true\n } catch (err) {\n this.log('Send error:', err)\n return false\n }\n }\n\n private async sendWithRetry(url: string, body: string): Promise<void> {\n const success = await this.sendFetch(url, body)\n if (!success) {\n // Retry once after 5s\n this.retryQueue.push({ url, body })\n setTimeout(() => {\n this.processRetryQueue()\n }, 5000)\n }\n }\n\n private async processRetryQueue(): Promise<void> {\n const items = this.retryQueue.splice(0, this.retryQueue.length)\n for (const item of items) {\n await this.sendFetch(item.url, item.body)\n }\n }\n\n private async flushErrors(): Promise<void> {\n if (!this.config || this.errorQueue.length === 0) return\n\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const url = `${this.config.url}/ingest/errors`\n const body = JSON.stringify({ errors })\n\n this.log(`Flushing ${errors.length} errors`)\n await this.sendWithRetry(url, body)\n }\n\n private async flushMetrics(): Promise<void> {\n if (!this.config || this.metricQueue.length === 0) return\n\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const url = `${this.config.url}/ingest/metrics`\n const body = JSON.stringify({ metrics })\n\n this.log(`Flushing ${metrics.length} metrics`)\n await this.sendWithRetry(url, body)\n }\n\n private flushAllBeacon(): void {\n if (!this.config) return\n\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0, this.errorQueue.length)\n const blob = new Blob(\n [JSON.stringify({ errors })],\n { type: 'application/json' }\n )\n // sendBeacon doesn't support custom headers, so we embed the key in the URL\n navigator.sendBeacon(\n `${this.config.url}/ingest/errors?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent errors:', errors.length)\n }\n\n if (this.metricQueue.length > 0) {\n const metrics = this.metricQueue.splice(0, this.metricQueue.length)\n const blob = new Blob(\n [JSON.stringify({ metrics })],\n { type: 'application/json' }\n )\n navigator.sendBeacon(\n `${this.config.url}/ingest/metrics?key=${this.config.apiKey}`,\n blob\n )\n this.log('Beacon sent metrics:', metrics.length)\n }\n } else {\n // Fallback to fetch\n this.flushAll()\n }\n }\n\n async flushAll(): Promise<void> {\n await Promise.all([this.flushErrors(), this.flushMetrics()])\n }\n}\n","/**\n * Auto-detect browser, OS, device, and screen info.\n * Only runs in browser environments.\n */\n\nexport interface DeviceInfo {\n browser: string\n browserVersion: string\n os: string\n osVersion: string\n deviceType: 'desktop' | 'tablet' | 'mobile' | 'unknown'\n screenResolution: string\n viewportSize: string\n language: string\n referrer: string\n}\n\nexport function detectDevice(): DeviceInfo | null {\n if (typeof navigator === 'undefined' || typeof window === 'undefined') {\n return null\n }\n\n const ua = navigator.userAgent\n\n return {\n browser: detectBrowser(ua),\n browserVersion: detectBrowserVersion(ua),\n os: detectOS(ua),\n osVersion: detectOSVersion(ua),\n deviceType: detectDeviceType(ua),\n screenResolution: typeof screen !== 'undefined'\n ? `${screen.width}x${screen.height}`\n : 'unknown',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n language: navigator.language || 'unknown',\n referrer: typeof document !== 'undefined' ? document.referrer : '',\n }\n}\n\nfunction detectBrowser(ua: string): string {\n // Order matters — check more specific strings first\n if (ua.includes('Firefox/')) return 'Firefox'\n if (ua.includes('Edg/')) return 'Edge'\n if (ua.includes('OPR/') || ua.includes('Opera/')) return 'Opera'\n if (ua.includes('Chrome/') && !ua.includes('Edg/')) return 'Chrome'\n if (ua.includes('Safari/') && !ua.includes('Chrome/')) return 'Safari'\n if (ua.includes('MSIE') || ua.includes('Trident/')) return 'IE'\n return 'Unknown'\n}\n\nfunction detectBrowserVersion(ua: string): string {\n const patterns: [string, RegExp][] = [\n ['Firefox', /Firefox\\/(\\d+[\\d.]*)/],\n ['Edge', /Edg\\/(\\d+[\\d.]*)/],\n ['Opera', /(?:OPR|Opera)\\/(\\d+[\\d.]*)/],\n ['Chrome', /Chrome\\/(\\d+[\\d.]*)/],\n ['Safari', /Version\\/(\\d+[\\d.]*)/],\n ['IE', /(?:MSIE |rv:)(\\d+[\\d.]*)/],\n ]\n for (const [, regex] of patterns) {\n const match = ua.match(regex)\n if (match) return match[1]\n }\n return 'unknown'\n}\n\nfunction detectOS(ua: string): string {\n if (ua.includes('Windows')) return 'Windows'\n if (ua.includes('Mac OS X') || ua.includes('Macintosh')) return 'macOS'\n if (ua.includes('CrOS')) return 'Chrome OS'\n if (ua.includes('Linux') && !ua.includes('Android')) return 'Linux'\n if (ua.includes('Android')) return 'Android'\n if (/iPad|iPhone|iPod/.test(ua)) return 'iOS'\n return 'Unknown'\n}\n\nfunction detectOSVersion(ua: string): string {\n const patterns: [string, RegExp, ((m: string) => string)?][] = [\n ['Windows', /Windows NT (\\d+[\\d.]*)/, (v) => {\n const map: Record<string, string> = { '10.0': '10/11', '6.3': '8.1', '6.2': '8', '6.1': '7' }\n return map[v] || v\n }],\n ['macOS', /Mac OS X (\\d+[._\\d]*)/, (v) => v.replace(/_/g, '.')],\n ['Android', /Android (\\d+[\\d.]*)/],\n ['iOS', /OS (\\d+[_\\d]*)/, (v) => v.replace(/_/g, '.')],\n ]\n for (const [, regex, transform] of patterns) {\n const match = ua.match(regex)\n if (match) return transform ? transform(match[1]) : match[1]\n }\n return 'unknown'\n}\n\nfunction detectDeviceType(ua: string): 'desktop' | 'tablet' | 'mobile' | 'unknown' {\n if (/iPad|Android(?!.*Mobile)/.test(ua) && typeof screen !== 'undefined' && Math.min(screen.width, screen.height) > 600) {\n return 'tablet'\n }\n if (/Mobi|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {\n return 'mobile'\n }\n if (typeof navigator !== 'undefined' && /Windows|Macintosh|Linux/.test(ua) && !/Mobi/i.test(ua)) {\n return 'desktop'\n }\n return 'unknown'\n}\n","import type { TarslogsConfig, ErrorPayload, MetricPayload } from './types'\nimport { Transport } from './transport'\nimport { detectDevice } from './device'\nimport type { DeviceInfo } from './device'\n\nexport type { TarslogsConfig } from './types'\n\nfunction generateSessionId(): string {\n // Random 16-char hex string\n if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n const bytes = new Uint8Array(8)\n crypto.getRandomValues(bytes)\n return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')\n }\n return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)\n}\n\nexport class TarslogsClient {\n private config: TarslogsConfig | null = null\n private transport = new Transport()\n private context: Record<string, any> = {}\n private deviceInfo: DeviceInfo | null = null\n private sessionId: string = ''\n\n /**\n * Initialize the client with configuration.\n * Must be called before any other methods.\n * Auto-detects browser, OS, device, and screen info in browser environments.\n */\n init(config: TarslogsConfig): void {\n this.config = config\n this.transport.init(config)\n this.sessionId = generateSessionId()\n this.deviceInfo = detectDevice()\n }\n\n /**\n * Merge additional context that will be sent with all errors and metrics.\n * Useful for setting userId, route, etc.\n */\n setContext(ctx: Record<string, any>): void {\n this.context = { ...this.context, ...ctx }\n }\n\n /**\n * Clear all context (e.g., on logout).\n */\n clearContext(): void {\n this.context = {}\n }\n\n /**\n * Build the full context object for an error/metric.\n */\n private buildContext(extra?: Record<string, any>): Record<string, any> {\n const ctx: Record<string, any> = {\n sessionId: this.sessionId,\n ...this.context,\n ...extra,\n }\n\n // Add current URL\n if (typeof window !== 'undefined' && window.location) {\n ctx.url = window.location.href\n ctx.path = window.location.pathname\n }\n\n // Add device info\n if (this.deviceInfo) {\n ctx.browser = this.deviceInfo.browser\n ctx.browserVersion = this.deviceInfo.browserVersion\n ctx.os = this.deviceInfo.os\n ctx.osVersion = this.deviceInfo.osVersion\n ctx.deviceType = this.deviceInfo.deviceType\n ctx.screenResolution = this.deviceInfo.screenResolution\n ctx.viewportSize = this.deviceInfo.viewportSize\n ctx.language = this.deviceInfo.language\n if (this.deviceInfo.referrer) {\n ctx.referrer = this.deviceInfo.referrer\n }\n }\n\n return ctx\n }\n\n /**\n * Capture an error and send to TarsLogs.\n */\n captureError(error: Error | string, extra?: Record<string, any>): void {\n if (!this.config) return\n\n let payload: ErrorPayload\n\n if (typeof error === 'string') {\n payload = {\n message: error,\n environment: this.config.environment,\n version: this.config.version,\n context: this.buildContext(extra),\n timestamp: new Date().toISOString(),\n }\n } else {\n payload = {\n message: error.message,\n stack: error.stack,\n environment: this.config.environment,\n version: this.config.version,\n context: this.buildContext(extra),\n timestamp: new Date().toISOString(),\n }\n\n // Try to extract file/line/col from stack\n if (error.stack) {\n const match = error.stack.match(/(?:at\\s+.+\\s+\\(|at\\s+)(.+):(\\d+):(\\d+)\\)?/)\n if (match) {\n payload.file = match[1]\n payload.line = parseInt(match[2], 10)\n payload.col = parseInt(match[3], 10)\n }\n }\n }\n\n this.transport.enqueueError(payload)\n }\n\n /**\n * Increment a counter metric.\n */\n increment(name: string, value: number = 1, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'counter',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Set a gauge metric (point-in-time value).\n */\n gauge(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'gauge',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Record a histogram metric (distribution of values).\n */\n histogram(name: string, value: number, tags?: Record<string, string>): void {\n if (!this.config) return\n const payload: MetricPayload = {\n name,\n type: 'histogram',\n value,\n tags,\n timestamp: new Date().toISOString(),\n }\n this.transport.enqueueMetric(payload)\n }\n\n /**\n * Flush all pending payloads immediately.\n * Call before page unload or process exit.\n */\n async flush(): Promise<void> {\n await this.transport.flushAll()\n }\n}\n","import { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\n// Server-side Hono middleware — NO browser globals (window, document, navigator)\n\ninterface HonoContext {\n req: {\n method: string\n path: string\n url: string\n }\n res: {\n status: number\n }\n json: (data: unknown, status?: number) => Response\n}\n\ntype HonoNext = () => Promise<void>\n\ninterface HonoMiddleware {\n (c: HonoContext, next: HonoNext): Promise<Response | void>\n}\n\ninterface HonoApp {\n use: (path: string, ...handlers: HonoMiddleware[]) => void\n}\n\n/**\n * Hono middleware factory that catches errors in route handlers\n * and reports them to TarsLogs.\n *\n * Usage:\n * app.use('*', tarslogsMiddleware(tarslogsClient))\n */\nexport function tarslogsMiddleware(client: TarslogsClient): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext): Promise<Response | void> => {\n try {\n await next()\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err))\n\n client.captureError(error, {\n source: 'hono-middleware',\n method: c.req.method,\n path: c.req.path,\n url: c.req.url,\n })\n\n // Re-throw so Hono's error handler can deal with it\n throw err\n }\n }\n}\n\n/**\n * One-line setup: initializes a TarslogsClient and attaches\n * the middleware to a Hono app.\n *\n * Usage:\n * const tarslogs = tarslogsHono(app, { url: '...', projectId: '...', apiKey: '...' })\n */\nexport function tarslogsHono(app: HonoApp, config: TarslogsConfig): TarslogsClient {\n const client = new TarslogsClient()\n client.init(config)\n app.use('*', tarslogsMiddleware(client))\n return client\n}\n"],"mappings":"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","names":[]}
|