@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/index.cjs CHANGED
@@ -187,27 +187,126 @@ var init_transport = __esm({
187
187
  }
188
188
  });
189
189
 
190
+ // src/device.ts
191
+ function detectDevice() {
192
+ if (typeof navigator === "undefined" || typeof window === "undefined") {
193
+ return null;
194
+ }
195
+ const ua = navigator.userAgent;
196
+ return {
197
+ browser: detectBrowser(ua),
198
+ browserVersion: detectBrowserVersion(ua),
199
+ os: detectOS(ua),
200
+ osVersion: detectOSVersion(ua),
201
+ deviceType: detectDeviceType(ua),
202
+ screenResolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : "unknown",
203
+ viewportSize: `${window.innerWidth}x${window.innerHeight}`,
204
+ language: navigator.language || "unknown",
205
+ referrer: typeof document !== "undefined" ? document.referrer : ""
206
+ };
207
+ }
208
+ function detectBrowser(ua) {
209
+ if (ua.includes("Firefox/")) return "Firefox";
210
+ if (ua.includes("Edg/")) return "Edge";
211
+ if (ua.includes("OPR/") || ua.includes("Opera/")) return "Opera";
212
+ if (ua.includes("Chrome/") && !ua.includes("Edg/")) return "Chrome";
213
+ if (ua.includes("Safari/") && !ua.includes("Chrome/")) return "Safari";
214
+ if (ua.includes("MSIE") || ua.includes("Trident/")) return "IE";
215
+ return "Unknown";
216
+ }
217
+ function detectBrowserVersion(ua) {
218
+ const patterns = [
219
+ ["Firefox", /Firefox\/(\d+[\d.]*)/],
220
+ ["Edge", /Edg\/(\d+[\d.]*)/],
221
+ ["Opera", /(?:OPR|Opera)\/(\d+[\d.]*)/],
222
+ ["Chrome", /Chrome\/(\d+[\d.]*)/],
223
+ ["Safari", /Version\/(\d+[\d.]*)/],
224
+ ["IE", /(?:MSIE |rv:)(\d+[\d.]*)/]
225
+ ];
226
+ for (const [, regex] of patterns) {
227
+ const match = ua.match(regex);
228
+ if (match) return match[1];
229
+ }
230
+ return "unknown";
231
+ }
232
+ function detectOS(ua) {
233
+ if (ua.includes("Windows")) return "Windows";
234
+ if (ua.includes("Mac OS X") || ua.includes("Macintosh")) return "macOS";
235
+ if (ua.includes("CrOS")) return "Chrome OS";
236
+ if (ua.includes("Linux") && !ua.includes("Android")) return "Linux";
237
+ if (ua.includes("Android")) return "Android";
238
+ if (/iPad|iPhone|iPod/.test(ua)) return "iOS";
239
+ return "Unknown";
240
+ }
241
+ function detectOSVersion(ua) {
242
+ const patterns = [
243
+ ["Windows", /Windows NT (\d+[\d.]*)/, (v) => {
244
+ const map = { "10.0": "10/11", "6.3": "8.1", "6.2": "8", "6.1": "7" };
245
+ return map[v] || v;
246
+ }],
247
+ ["macOS", /Mac OS X (\d+[._\d]*)/, (v) => v.replace(/_/g, ".")],
248
+ ["Android", /Android (\d+[\d.]*)/],
249
+ ["iOS", /OS (\d+[_\d]*)/, (v) => v.replace(/_/g, ".")]
250
+ ];
251
+ for (const [, regex, transform] of patterns) {
252
+ const match = ua.match(regex);
253
+ if (match) return transform ? transform(match[1]) : match[1];
254
+ }
255
+ return "unknown";
256
+ }
257
+ function detectDeviceType(ua) {
258
+ if (/iPad|Android(?!.*Mobile)/.test(ua) && typeof screen !== "undefined" && Math.min(screen.width, screen.height) > 600) {
259
+ return "tablet";
260
+ }
261
+ if (/Mobi|Android.*Mobile|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
262
+ return "mobile";
263
+ }
264
+ if (typeof navigator !== "undefined" && /Windows|Macintosh|Linux/.test(ua) && !/Mobi/i.test(ua)) {
265
+ return "desktop";
266
+ }
267
+ return "unknown";
268
+ }
269
+ var init_device = __esm({
270
+ "src/device.ts"() {
271
+ "use strict";
272
+ }
273
+ });
274
+
190
275
  // src/core.ts
191
276
  var core_exports = {};
192
277
  __export(core_exports, {
193
278
  TarslogsClient: () => TarslogsClient
194
279
  });
280
+ function generateSessionId() {
281
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
282
+ const bytes = new Uint8Array(8);
283
+ crypto.getRandomValues(bytes);
284
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
285
+ }
286
+ return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
287
+ }
195
288
  var TarslogsClient;
196
289
  var init_core = __esm({
197
290
  "src/core.ts"() {
198
291
  "use strict";
199
292
  init_transport();
293
+ init_device();
200
294
  TarslogsClient = class {
201
295
  config = null;
202
296
  transport = new Transport();
203
297
  context = {};
298
+ deviceInfo = null;
299
+ sessionId = "";
204
300
  /**
205
301
  * Initialize the client with configuration.
206
302
  * Must be called before any other methods.
303
+ * Auto-detects browser, OS, device, and screen info in browser environments.
207
304
  */
208
305
  init(config) {
209
306
  this.config = config;
210
307
  this.transport.init(config);
308
+ this.sessionId = generateSessionId();
309
+ this.deviceInfo = detectDevice();
211
310
  }
212
311
  /**
213
312
  * Merge additional context that will be sent with all errors and metrics.
@@ -222,6 +321,34 @@ var init_core = __esm({
222
321
  clearContext() {
223
322
  this.context = {};
224
323
  }
324
+ /**
325
+ * Build the full context object for an error/metric.
326
+ */
327
+ buildContext(extra) {
328
+ const ctx = {
329
+ sessionId: this.sessionId,
330
+ ...this.context,
331
+ ...extra
332
+ };
333
+ if (typeof window !== "undefined" && window.location) {
334
+ ctx.url = window.location.href;
335
+ ctx.path = window.location.pathname;
336
+ }
337
+ if (this.deviceInfo) {
338
+ ctx.browser = this.deviceInfo.browser;
339
+ ctx.browserVersion = this.deviceInfo.browserVersion;
340
+ ctx.os = this.deviceInfo.os;
341
+ ctx.osVersion = this.deviceInfo.osVersion;
342
+ ctx.deviceType = this.deviceInfo.deviceType;
343
+ ctx.screenResolution = this.deviceInfo.screenResolution;
344
+ ctx.viewportSize = this.deviceInfo.viewportSize;
345
+ ctx.language = this.deviceInfo.language;
346
+ if (this.deviceInfo.referrer) {
347
+ ctx.referrer = this.deviceInfo.referrer;
348
+ }
349
+ }
350
+ return ctx;
351
+ }
225
352
  /**
226
353
  * Capture an error and send to TarsLogs.
227
354
  */
@@ -233,7 +360,7 @@ var init_core = __esm({
233
360
  message: error,
234
361
  environment: this.config.environment,
235
362
  version: this.config.version,
236
- context: { ...this.context, ...extra },
363
+ context: this.buildContext(extra),
237
364
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
238
365
  };
239
366
  } else {
@@ -242,7 +369,7 @@ var init_core = __esm({
242
369
  stack: error.stack,
243
370
  environment: this.config.environment,
244
371
  version: this.config.version,
245
- context: { ...this.context, ...extra },
372
+ context: this.buildContext(extra),
246
373
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
247
374
  };
248
375
  if (error.stack) {
@@ -315,6 +442,7 @@ __export(src_exports, {
315
442
  TarslogsClient: () => TarslogsClient,
316
443
  TarslogsVue: () => TarslogsVue,
317
444
  Transport: () => Transport,
445
+ detectDevice: () => detectDevice,
318
446
  setupAutoCapture: () => setupAutoCapture,
319
447
  tarslogs: () => tarslogs,
320
448
  tarslogsHono: () => tarslogsHono,
@@ -377,6 +505,9 @@ function setupAutoCapture(client) {
377
505
  };
378
506
  }
379
507
 
508
+ // src/index.ts
509
+ init_device();
510
+
380
511
  // src/vue.ts
381
512
  var TarslogsVue = {
382
513
  install(app, options) {
@@ -455,6 +586,7 @@ var tarslogs = new TarslogsClient();
455
586
  TarslogsClient,
456
587
  TarslogsVue,
457
588
  Transport,
589
+ detectDevice,
458
590
  setupAutoCapture,
459
591
  tarslogs,
460
592
  tarslogsHono,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transport.ts","../src/core.ts","../src/index.ts","../src/auto.ts","../src/vue.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","export { TarslogsClient } from './core'\nexport type { TarslogsConfig } from './core'\nexport type { ErrorPayload, MetricPayload } from './types'\nexport { Transport } from './transport'\nexport { setupAutoCapture } from './auto'\nexport { TarslogsVue } from './vue'\nexport type { TarslogsVueOptions } from './vue'\nexport { tarslogsMiddleware, tarslogsHono } from './hono'\n\n// Default singleton instance\nimport { TarslogsClient } from './core'\nexport const tarslogs = new TarslogsClient()\n","import type { TarslogsClient } from './core'\n\n/**\n * Setup automatic error capture via window.onerror and window.onunhandledrejection.\n * Only works in browser environments.\n */\nexport function setupAutoCapture(client: TarslogsClient): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const originalOnError = window.onerror\n const originalOnUnhandledRejection = window.onunhandledrejection\n\n window.onerror = function (\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ) {\n if (error) {\n client.captureError(error, {\n source: 'window.onerror',\n file: source,\n line: lineno,\n col: colno,\n })\n } else {\n const msg = typeof message === 'string' ? message : 'Unknown error'\n client.captureError(msg, {\n source: 'window.onerror',\n file: source,\n line: lineno,\n col: colno,\n })\n }\n\n // Call original handler if it exists\n if (typeof originalOnError === 'function') {\n return originalOnError.call(window, message, source, lineno, colno, error)\n }\n return false\n }\n\n window.onunhandledrejection = function (event: PromiseRejectionEvent) {\n const reason = event.reason\n\n if (reason instanceof Error) {\n client.captureError(reason, {\n source: 'unhandledrejection',\n })\n } else {\n const message = typeof reason === 'string' ? reason : 'Unhandled promise rejection'\n client.captureError(message, {\n source: 'unhandledrejection',\n rawReason: String(reason),\n })\n }\n\n // Call original handler if it exists\n if (typeof originalOnUnhandledRejection === 'function') {\n return originalOnUnhandledRejection.call(window, event)\n }\n }\n\n // Return cleanup function\n return () => {\n window.onerror = originalOnError\n window.onunhandledrejection = originalOnUnhandledRejection\n }\n}\n","import type { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\ninterface VueApp {\n config: {\n errorHandler?: (\n err: unknown,\n instance: any,\n info: string\n ) => void\n }\n}\n\nexport interface TarslogsVueOptions {\n /** TarslogsClient instance. If not provided, uses the default singleton. */\n client?: TarslogsClient\n /** Config to auto-init the client (ignored if client is already initialized). */\n config?: TarslogsConfig\n}\n\n/**\n * Vue 3 plugin that hooks into app.config.errorHandler to capture Vue errors.\n *\n * Usage:\n * import { TarslogsVue, tarslogs } from '@tarslogs/client'\n * tarslogs.init({ ... })\n * app.use(TarslogsVue, { client: tarslogs })\n */\nexport const TarslogsVue = {\n install(app: VueApp, options?: TarslogsVueOptions): void {\n // Need a reference to the client — must be passed via options\n let client: TarslogsClient | undefined = options?.client\n\n if (!client) {\n // Try to lazily import the singleton\n // (This avoids circular dependency issues)\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('./core')\n client = new mod.TarslogsClient()\n if (options?.config) {\n client!.init(options.config)\n }\n } catch {\n console.warn('[tarslogs] No client provided to TarslogsVue plugin. Pass { client } in options.')\n return\n }\n }\n\n const prevHandler = app.config.errorHandler\n\n app.config.errorHandler = (err: unknown, instance: any, info: string) => {\n if (!client) return\n\n const extra: Record<string, any> = {\n source: 'vue',\n lifecycleHook: info,\n }\n\n // Extract component name\n if (instance) {\n const componentName =\n instance.$options?.name ||\n instance.$options?.__name ||\n instance.$.type?.name ||\n instance.$.type?.__name ||\n 'AnonymousComponent'\n extra.component = componentName\n\n // Extract props (shallow, non-reactive)\n if (instance.$props) {\n try {\n extra.props = JSON.parse(JSON.stringify(instance.$props))\n } catch {\n // Props might have circular refs, skip\n }\n }\n }\n\n if (err instanceof Error) {\n client!.captureError(err, extra)\n } else {\n client!.captureError(String(err), extra)\n }\n\n // Call previous handler if it exists\n if (typeof prevHandler === 'function') {\n prevHandler(err, instance, info)\n }\n }\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/index.ts","../src/auto.ts","../src/vue.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","export { TarslogsClient } from './core'\nexport type { TarslogsConfig } from './core'\nexport type { ErrorPayload, MetricPayload } from './types'\nexport { Transport } from './transport'\nexport { setupAutoCapture } from './auto'\nexport { detectDevice } from './device'\nexport type { DeviceInfo } from './device'\nexport { TarslogsVue } from './vue'\nexport type { TarslogsVueOptions } from './vue'\nexport { tarslogsMiddleware, tarslogsHono } from './hono'\n\n// Default singleton instance\nimport { TarslogsClient } from './core'\nexport const tarslogs = new TarslogsClient()\n","import type { TarslogsClient } from './core'\n\n/**\n * Setup automatic error capture via window.onerror and window.onunhandledrejection.\n * Only works in browser environments.\n */\nexport function setupAutoCapture(client: TarslogsClient): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const originalOnError = window.onerror\n const originalOnUnhandledRejection = window.onunhandledrejection\n\n window.onerror = function (\n message: string | Event,\n source?: string,\n lineno?: number,\n colno?: number,\n error?: Error\n ) {\n if (error) {\n client.captureError(error, {\n source: 'window.onerror',\n file: source,\n line: lineno,\n col: colno,\n })\n } else {\n const msg = typeof message === 'string' ? message : 'Unknown error'\n client.captureError(msg, {\n source: 'window.onerror',\n file: source,\n line: lineno,\n col: colno,\n })\n }\n\n // Call original handler if it exists\n if (typeof originalOnError === 'function') {\n return originalOnError.call(window, message, source, lineno, colno, error)\n }\n return false\n }\n\n window.onunhandledrejection = function (event: PromiseRejectionEvent) {\n const reason = event.reason\n\n if (reason instanceof Error) {\n client.captureError(reason, {\n source: 'unhandledrejection',\n })\n } else {\n const message = typeof reason === 'string' ? reason : 'Unhandled promise rejection'\n client.captureError(message, {\n source: 'unhandledrejection',\n rawReason: String(reason),\n })\n }\n\n // Call original handler if it exists\n if (typeof originalOnUnhandledRejection === 'function') {\n return originalOnUnhandledRejection.call(window, event)\n }\n }\n\n // Return cleanup function\n return () => {\n window.onerror = originalOnError\n window.onunhandledrejection = originalOnUnhandledRejection\n }\n}\n","import type { TarslogsClient } from './core'\nimport type { TarslogsConfig } from './types'\n\ninterface VueApp {\n config: {\n errorHandler?: (\n err: unknown,\n instance: any,\n info: string\n ) => void\n }\n}\n\nexport interface TarslogsVueOptions {\n /** TarslogsClient instance. If not provided, uses the default singleton. */\n client?: TarslogsClient\n /** Config to auto-init the client (ignored if client is already initialized). */\n config?: TarslogsConfig\n}\n\n/**\n * Vue 3 plugin that hooks into app.config.errorHandler to capture Vue errors.\n *\n * Usage:\n * import { TarslogsVue, tarslogs } from '@tarslogs/client'\n * tarslogs.init({ ... })\n * app.use(TarslogsVue, { client: tarslogs })\n */\nexport const TarslogsVue = {\n install(app: VueApp, options?: TarslogsVueOptions): void {\n // Need a reference to the client — must be passed via options\n let client: TarslogsClient | undefined = options?.client\n\n if (!client) {\n // Try to lazily import the singleton\n // (This avoids circular dependency issues)\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('./core')\n client = new mod.TarslogsClient()\n if (options?.config) {\n client!.init(options.config)\n }\n } catch {\n console.warn('[tarslogs] No client provided to TarslogsVue plugin. Pass { client } in options.')\n return\n }\n }\n\n const prevHandler = app.config.errorHandler\n\n app.config.errorHandler = (err: unknown, instance: any, info: string) => {\n if (!client) return\n\n const extra: Record<string, any> = {\n source: 'vue',\n lifecycleHook: info,\n }\n\n // Extract component name\n if (instance) {\n const componentName =\n instance.$options?.name ||\n instance.$options?.__name ||\n instance.$.type?.name ||\n instance.$.type?.__name ||\n 'AnonymousComponent'\n extra.component = componentName\n\n // Extract props (shallow, non-reactive)\n if (instance.$props) {\n try {\n extra.props = JSON.parse(JSON.stringify(instance.$props))\n } catch {\n // Props might have circular refs, skip\n }\n }\n }\n\n if (err instanceof Error) {\n client!.captureError(err, extra)\n } else {\n client!.captureError(String(err), extra)\n }\n\n // Call previous handler if it exists\n if (typeof prevHandler === 'function') {\n prevHandler(err, instance, info)\n }\n }\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":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TarslogsConfig, E as ErrorPayload, M as MetricPayload, a as TarslogsClient } from './hono-DIKyVc9C.cjs';
2
- export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-DIKyVc9C.cjs';
1
+ import { T as TarslogsConfig, E as ErrorPayload, M as MetricPayload, a as TarslogsClient } from './hono-CB3Uka-S.cjs';
2
+ export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-CB3Uka-S.cjs';
3
3
 
4
4
  declare class Transport {
5
5
  private config;
@@ -30,6 +30,23 @@ declare class Transport {
30
30
  */
31
31
  declare function setupAutoCapture(client: TarslogsClient): () => void;
32
32
 
33
+ /**
34
+ * Auto-detect browser, OS, device, and screen info.
35
+ * Only runs in browser environments.
36
+ */
37
+ interface DeviceInfo {
38
+ browser: string;
39
+ browserVersion: string;
40
+ os: string;
41
+ osVersion: string;
42
+ deviceType: 'desktop' | 'tablet' | 'mobile' | 'unknown';
43
+ screenResolution: string;
44
+ viewportSize: string;
45
+ language: string;
46
+ referrer: string;
47
+ }
48
+ declare function detectDevice(): DeviceInfo | null;
49
+
33
50
  interface VueApp {
34
51
  config: {
35
52
  errorHandler?: (err: unknown, instance: any, info: string) => void;
@@ -55,4 +72,4 @@ declare const TarslogsVue: {
55
72
 
56
73
  declare const tarslogs: TarslogsClient;
57
74
 
58
- export { ErrorPayload, MetricPayload, TarslogsClient, TarslogsConfig, TarslogsVue, type TarslogsVueOptions, Transport, setupAutoCapture, tarslogs };
75
+ export { type DeviceInfo, ErrorPayload, MetricPayload, TarslogsClient, TarslogsConfig, TarslogsVue, type TarslogsVueOptions, Transport, detectDevice, setupAutoCapture, tarslogs };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TarslogsConfig, E as ErrorPayload, M as MetricPayload, a as TarslogsClient } from './hono-DIKyVc9C.js';
2
- export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-DIKyVc9C.js';
1
+ import { T as TarslogsConfig, E as ErrorPayload, M as MetricPayload, a as TarslogsClient } from './hono-CB3Uka-S.js';
2
+ export { t as tarslogsHono, b as tarslogsMiddleware } from './hono-CB3Uka-S.js';
3
3
 
4
4
  declare class Transport {
5
5
  private config;
@@ -30,6 +30,23 @@ declare class Transport {
30
30
  */
31
31
  declare function setupAutoCapture(client: TarslogsClient): () => void;
32
32
 
33
+ /**
34
+ * Auto-detect browser, OS, device, and screen info.
35
+ * Only runs in browser environments.
36
+ */
37
+ interface DeviceInfo {
38
+ browser: string;
39
+ browserVersion: string;
40
+ os: string;
41
+ osVersion: string;
42
+ deviceType: 'desktop' | 'tablet' | 'mobile' | 'unknown';
43
+ screenResolution: string;
44
+ viewportSize: string;
45
+ language: string;
46
+ referrer: string;
47
+ }
48
+ declare function detectDevice(): DeviceInfo | null;
49
+
33
50
  interface VueApp {
34
51
  config: {
35
52
  errorHandler?: (err: unknown, instance: any, info: string) => void;
@@ -55,4 +72,4 @@ declare const TarslogsVue: {
55
72
 
56
73
  declare const tarslogs: TarslogsClient;
57
74
 
58
- export { ErrorPayload, MetricPayload, TarslogsClient, TarslogsConfig, TarslogsVue, type TarslogsVueOptions, Transport, setupAutoCapture, tarslogs };
75
+ export { type DeviceInfo, ErrorPayload, MetricPayload, TarslogsClient, TarslogsConfig, TarslogsVue, type TarslogsVueOptions, Transport, detectDevice, setupAutoCapture, tarslogs };
package/dist/index.js CHANGED
@@ -186,27 +186,126 @@ var init_transport = __esm({
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
+ var init_device = __esm({
269
+ "src/device.ts"() {
270
+ "use strict";
271
+ }
272
+ });
273
+
189
274
  // src/core.ts
190
275
  var core_exports = {};
191
276
  __export(core_exports, {
192
277
  TarslogsClient: () => TarslogsClient
193
278
  });
279
+ function generateSessionId() {
280
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
281
+ const bytes = new Uint8Array(8);
282
+ crypto.getRandomValues(bytes);
283
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
284
+ }
285
+ return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
286
+ }
194
287
  var TarslogsClient;
195
288
  var init_core = __esm({
196
289
  "src/core.ts"() {
197
290
  "use strict";
198
291
  init_transport();
292
+ init_device();
199
293
  TarslogsClient = class {
200
294
  config = null;
201
295
  transport = new Transport();
202
296
  context = {};
297
+ deviceInfo = null;
298
+ sessionId = "";
203
299
  /**
204
300
  * Initialize the client with configuration.
205
301
  * Must be called before any other methods.
302
+ * Auto-detects browser, OS, device, and screen info in browser environments.
206
303
  */
207
304
  init(config) {
208
305
  this.config = config;
209
306
  this.transport.init(config);
307
+ this.sessionId = generateSessionId();
308
+ this.deviceInfo = detectDevice();
210
309
  }
211
310
  /**
212
311
  * Merge additional context that will be sent with all errors and metrics.
@@ -221,6 +320,34 @@ var init_core = __esm({
221
320
  clearContext() {
222
321
  this.context = {};
223
322
  }
323
+ /**
324
+ * Build the full context object for an error/metric.
325
+ */
326
+ buildContext(extra) {
327
+ const ctx = {
328
+ sessionId: this.sessionId,
329
+ ...this.context,
330
+ ...extra
331
+ };
332
+ if (typeof window !== "undefined" && window.location) {
333
+ ctx.url = window.location.href;
334
+ ctx.path = window.location.pathname;
335
+ }
336
+ if (this.deviceInfo) {
337
+ ctx.browser = this.deviceInfo.browser;
338
+ ctx.browserVersion = this.deviceInfo.browserVersion;
339
+ ctx.os = this.deviceInfo.os;
340
+ ctx.osVersion = this.deviceInfo.osVersion;
341
+ ctx.deviceType = this.deviceInfo.deviceType;
342
+ ctx.screenResolution = this.deviceInfo.screenResolution;
343
+ ctx.viewportSize = this.deviceInfo.viewportSize;
344
+ ctx.language = this.deviceInfo.language;
345
+ if (this.deviceInfo.referrer) {
346
+ ctx.referrer = this.deviceInfo.referrer;
347
+ }
348
+ }
349
+ return ctx;
350
+ }
224
351
  /**
225
352
  * Capture an error and send to TarsLogs.
226
353
  */
@@ -232,7 +359,7 @@ var init_core = __esm({
232
359
  message: error,
233
360
  environment: this.config.environment,
234
361
  version: this.config.version,
235
- context: { ...this.context, ...extra },
362
+ context: this.buildContext(extra),
236
363
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
237
364
  };
238
365
  } else {
@@ -241,7 +368,7 @@ var init_core = __esm({
241
368
  stack: error.stack,
242
369
  environment: this.config.environment,
243
370
  version: this.config.version,
244
- context: { ...this.context, ...extra },
371
+ context: this.buildContext(extra),
245
372
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
246
373
  };
247
374
  if (error.stack) {
@@ -365,6 +492,9 @@ function setupAutoCapture(client) {
365
492
  };
366
493
  }
367
494
 
495
+ // src/index.ts
496
+ init_device();
497
+
368
498
  // src/vue.ts
369
499
  var TarslogsVue = {
370
500
  install(app, options) {
@@ -442,6 +572,7 @@ export {
442
572
  TarslogsClient,
443
573
  TarslogsVue,
444
574
  Transport,
575
+ detectDevice,
445
576
  setupAutoCapture,
446
577
  tarslogs,
447
578
  tarslogsHono,