vantmetry 0.0.2 → 0.0.4

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/core/tracker.d.ts CHANGED
@@ -15,6 +15,7 @@ export declare class VantmetryTracker implements VantmetryInstance {
15
15
  info(message: string, details?: LogDetails): void;
16
16
  debug(message: string, details?: LogDetails): void;
17
17
  flush(): Promise<void>;
18
+ destroy(): Promise<void>;
18
19
  private addToBuffer;
19
20
  captureAutoError(payload: LogPayload): void;
20
21
  private getSignature;
@@ -1,10 +1,13 @@
1
1
  import { VantmetryConfig } from './types';
2
2
  export declare class TransportManager {
3
3
  private wtSession;
4
+ private wtInitPromise;
4
5
  private readonly endpoint;
5
6
  private readonly wtEndpoint;
6
7
  private readonly debug;
7
8
  constructor(config: VantmetryConfig);
8
9
  private initWT;
10
+ private connectWT;
9
11
  send(payload: string): Promise<void>;
12
+ close(): void;
10
13
  }
package/core/types.d.ts CHANGED
@@ -13,6 +13,7 @@ export interface VantmetryInstance {
13
13
  info: (message: string, details?: LogDetails) => void;
14
14
  debug: (message: string, details?: LogDetails) => void;
15
15
  flush: () => Promise<void>;
16
+ destroy: () => Promise<void>;
16
17
  }
17
18
  declare global {
18
19
  interface Window {
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { g as t } from "./init-DeOqWgSl.js";
2
- import { V as i, i as g, a as f } from "./init-DeOqWgSl.js";
3
- const n = {
1
+ import { g as t } from "./init-BfWOgJup.js";
2
+ import { V as i, i as g, a as u } from "./init-BfWOgJup.js";
3
+ const o = {
4
4
  get isReady() {
5
5
  return t().isReady;
6
6
  },
@@ -18,11 +18,14 @@ const n = {
18
18
  },
19
19
  flush() {
20
20
  return t().flush();
21
+ },
22
+ destroy() {
23
+ return t().destroy();
21
24
  }
22
25
  };
23
26
  export {
24
27
  i as VantmetryTracker,
25
28
  g as init,
26
- f as initGlobalListeners,
27
- n as logger
29
+ u as initGlobalListeners,
30
+ o as logger
28
31
  };
@@ -1,37 +1,40 @@
1
- let u = null;
2
- function T(n) {
3
- u = n;
1
+ let f = null;
2
+ function T(i) {
3
+ f = i;
4
4
  }
5
- function k() {
6
- if (!u)
5
+ function A() {
6
+ if (!f)
7
7
  throw new Error("[Vantmetry] Not initialized. Call init() before using logger.");
8
- return u;
8
+ return f;
9
9
  }
10
10
  function E() {
11
- return u !== null;
11
+ return f !== null;
12
12
  }
13
- const a = {
13
+ const c = {
14
14
  ERROR: "ERROR",
15
15
  INFO: "INFO",
16
16
  WARN: "WARN",
17
17
  DEBUG: "DEBUG"
18
- }, b = "https://ingestor.vantmetry.com:4433";
19
- class S {
18
+ }, S = "https://ingestor.vantmetry.com:4433";
19
+ class b {
20
20
  wtSession = null;
21
+ wtInitPromise = null;
21
22
  endpoint;
22
23
  wtEndpoint;
23
24
  debug;
24
25
  constructor(e) {
25
- const t = (e.ingestorUrl ?? b).replace(/\/$/, "");
26
+ const t = (e.ingestorUrl ?? S).replace(/\/$/, "");
26
27
  this.endpoint = `${t}/api/ingestor/push/tcp?public_key=${e.publicKey}`, this.wtEndpoint = `${t}/api/ingestor/push/udp?public_key=${e.publicKey}`;
27
28
  try {
28
29
  this.debug = typeof window < "u" && !!window.localStorage?.getItem("vantmetry_debug");
29
30
  } catch {
30
31
  this.debug = !1;
31
32
  }
32
- this.initWT();
33
33
  }
34
- async initWT() {
34
+ initWT() {
35
+ return this.wtInitPromise ? this.wtInitPromise : (this.wtInitPromise = this.connectWT(), this.wtInitPromise);
36
+ }
37
+ async connectWT() {
35
38
  if ("WebTransport" in window) {
36
39
  await new Promise((e) => setTimeout(e, 200));
37
40
  try {
@@ -42,7 +45,7 @@ class S {
42
45
  }
43
46
  }
44
47
  async send(e) {
45
- if (this.wtSession)
48
+ if (await this.initWT(), this.wtSession)
46
49
  try {
47
50
  const s = (await this.wtSession.createUnidirectionalStream()).getWriter();
48
51
  await s.write(new TextEncoder().encode(e)), await s.close();
@@ -64,8 +67,11 @@ class S {
64
67
  this.debug && console.error("Vantmetry send failed", t);
65
68
  });
66
69
  }
70
+ close() {
71
+ this.wtSession && (this.wtSession.close(), this.wtSession = null);
72
+ }
67
73
  }
68
- const c = {
74
+ const u = {
69
75
  // Matches standard email formats
70
76
  email: /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g,
71
77
  // Matches standard CC groupings: 4-4-4-[3-4] with required separators, or Amex 4-6-5 format.
@@ -93,36 +99,36 @@ const c = {
93
99
  "client_secret",
94
100
  "auth"
95
101
  ]);
96
- function l(n) {
97
- let e = n;
98
- return e = e.replace(c.email, (t) => {
102
+ function d(i) {
103
+ let e = i;
104
+ return e = e.replace(u.email, (t) => {
99
105
  const s = t.split("@");
100
106
  if (s.length !== 2)
101
107
  return t;
102
- const [r, i] = s;
103
- return `${r.charAt(0)}***@${i}`;
104
- }), e = e.replace(c.creditCard, (t) => {
105
- const s = t.replace(/[-\s]/g, ""), r = s.slice(-4);
106
- return "*".repeat(s.length - 4) + r;
107
- }), e = e.replace(c.ssn, (t) => `***-**-${t.slice(-4)}`), e = e.replace(c.jwt, "[JWT REDACTED]"), e = e.replace(c.authHeader, "$1 [TOKEN REDACTED]"), e;
108
+ const [n, a] = s;
109
+ return `${n.charAt(0)}***@${a}`;
110
+ }), e = e.replace(u.creditCard, (t) => {
111
+ const s = t.replace(/[-\s]/g, ""), n = s.slice(-4);
112
+ return "*".repeat(s.length - 4) + n;
113
+ }), e = e.replace(u.ssn, (t) => `***-**-${t.slice(-4)}`), e = e.replace(u.jwt, "[JWT REDACTED]"), e = e.replace(u.authHeader, "$1 [TOKEN REDACTED]"), e;
108
114
  }
109
- function h(n, e = /* @__PURE__ */ new WeakSet()) {
110
- if (typeof n == "string")
111
- return l(n);
112
- if (!n || typeof n != "object")
113
- return n;
114
- if (e.has(n))
115
+ function h(i, e = /* @__PURE__ */ new WeakSet()) {
116
+ if (typeof i == "string")
117
+ return d(i);
118
+ if (!i || typeof i != "object")
119
+ return i;
120
+ if (e.has(i))
115
121
  return "[Circular]";
116
- if (e.add(n), Array.isArray(n))
117
- return n.map((s) => h(s, e));
122
+ if (e.add(i), Array.isArray(i))
123
+ return i.map((s) => h(s, e));
118
124
  const t = {};
119
- for (const [s, r] of Object.entries(n)) {
120
- const i = s.toLowerCase();
121
- if (p.has(i) || Array.from(p).some((o) => i.includes(o))) {
125
+ for (const [s, n] of Object.entries(i)) {
126
+ const a = s.toLowerCase();
127
+ if (p.has(a) || Array.from(p).some((r) => a.includes(r))) {
122
128
  t[s] = "[REDACTED]";
123
129
  continue;
124
130
  }
125
- typeof r == "string" ? t[s] = l(r) : typeof r == "object" && r !== null ? t[s] = h(r, e) : t[s] = r;
131
+ typeof n == "string" ? t[s] = d(n) : typeof n == "object" && n !== null ? t[s] = h(n, e) : t[s] = n;
126
132
  }
127
133
  return t;
128
134
  }
@@ -139,32 +145,37 @@ class _ {
139
145
  TTL_MS = 6e4;
140
146
  MAX_EVENTS_PER_SEC = 100;
141
147
  constructor(e) {
142
- this.transport = new S(e);
148
+ this.transport = new b(e);
143
149
  }
144
150
  // --- Public API ---
145
151
  error(e, t) {
146
- this.addToBuffer({ severity: a.ERROR, type: "manual", message: e, details: t });
152
+ this.addToBuffer({ severity: c.ERROR, type: "manual", message: e, details: t });
147
153
  }
148
154
  warn(e, t) {
149
- this.addToBuffer({ severity: a.WARN, type: "manual", message: e, details: t });
155
+ this.addToBuffer({ severity: c.WARN, type: "manual", message: e, details: t });
150
156
  }
151
157
  info(e, t) {
152
- this.addToBuffer({ severity: a.INFO, type: "manual", message: e, details: t });
158
+ this.addToBuffer({ severity: c.INFO, type: "manual", message: e, details: t });
153
159
  }
154
160
  debug(e, t) {
155
- this.addToBuffer({ severity: a.DEBUG, type: "manual", message: e, details: t });
161
+ this.addToBuffer({ severity: c.DEBUG, type: "manual", message: e, details: t });
156
162
  }
157
163
  async flush() {
158
- if (this.buffer.length === 0)
164
+ if (this.buffer.length === 0) {
165
+ this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = null);
159
166
  return;
167
+ }
160
168
  const e = Date.now();
161
169
  for (const s of this.buffer)
162
170
  this.sentErrors.set(this.getSignature(s), e);
163
- for (const [s, r] of this.sentErrors.entries())
164
- e - r >= this.TTL_MS && this.sentErrors.delete(s);
171
+ for (const [s, n] of this.sentErrors.entries())
172
+ e - n >= this.TTL_MS && this.sentErrors.delete(s);
165
173
  const t = JSON.stringify(this.buffer);
166
174
  this.buffer = [], this.flushTimer && (clearTimeout(this.flushTimer), this.flushTimer = null), await this.transport.send(t);
167
175
  }
176
+ async destroy() {
177
+ this.transport.close(), await this.flush();
178
+ }
168
179
  // --- Internal Logic ---
169
180
  addToBuffer(e) {
170
181
  if (!this.isReady)
@@ -174,20 +185,23 @@ class _ {
174
185
  this.isReady = !1, console.error("[Vantmetry] Logging disabled to save browser CPU due to infinite loop detection.");
175
186
  return;
176
187
  }
177
- const s = this.getSignature(e), r = this.sentErrors.get(s);
178
- if (r && t - r < this.TTL_MS)
188
+ const s = this.getSignature(e), n = this.sentErrors.get(s);
189
+ if (n && t - n < this.TTL_MS)
179
190
  return;
180
- const i = this.buffer.find((m) => this.getSignature(m) === s);
181
- if (i) {
182
- i.count = (i.count || 1) + 1;
191
+ const a = this.buffer.find((y) => this.getSignature(y) === s);
192
+ if (a) {
193
+ a.count = (a.count || 1) + 1;
183
194
  return;
184
195
  }
185
- const { message: o, details: f, stack: d } = e, g = typeof o == "string" ? l(o) : o, y = typeof f == "object" ? h(f) : f, w = typeof d == "string" ? l(d) : d;
196
+ let { message: r, stack: o } = e;
197
+ const { details: l } = e;
198
+ r instanceof Error && (o = o ?? r.stack, r = r.message || String(r));
199
+ const g = typeof r == "string" ? d(r) : r, w = typeof l == "object" ? h(l) : l, m = typeof o == "string" ? d(o) : o;
186
200
  this.buffer.push({
187
201
  ...e,
188
202
  message: g,
189
- details: y,
190
- stack: w,
203
+ details: w,
204
+ stack: m,
191
205
  count: 1,
192
206
  ts: Date.now(),
193
207
  url: window.location.href,
@@ -203,60 +217,67 @@ class _ {
203
217
  return `${e.type}:${e.severity}:${e.message}`;
204
218
  }
205
219
  }
206
- function A(n) {
220
+ function k(i) {
207
221
  const e = console.error;
208
222
  console.error = function(...t) {
209
- if (e.apply(console, t), !n._isCapturingConsoleError) {
210
- n._isCapturingConsoleError = !0;
223
+ if (e.apply(console, t), !i._isCapturingConsoleError) {
224
+ i._isCapturingConsoleError = !0;
211
225
  try {
212
- let s, r;
213
- const i = t.find((o) => o instanceof Error);
214
- i ? (s = i.message || String(i), r = i.stack) : s = t.map((o) => {
215
- if (typeof o == "string") return o;
216
- try {
217
- return JSON.stringify(o);
218
- } catch {
219
- return String(o);
220
- }
221
- }).join(" "), n.captureAutoError({
226
+ let s, n;
227
+ const a = t.findIndex((o) => o instanceof Error), r = t[a];
228
+ if (r) {
229
+ const o = t.slice(0, a).filter((l) => typeof l == "string").join(" ");
230
+ s = o ? `${o}: ${r.message || String(r)}` : r.message || String(r), n = r.stack;
231
+ } else
232
+ s = t.map((o) => {
233
+ if (typeof o == "string") return o;
234
+ try {
235
+ return JSON.stringify(o);
236
+ } catch {
237
+ return String(o);
238
+ }
239
+ }).join(" ");
240
+ i.captureAutoError({
222
241
  type: "console.error",
223
242
  message: s || "Unknown console.error",
224
- stack: r,
225
- severity: a.ERROR
243
+ stack: n,
244
+ severity: c.ERROR
226
245
  });
227
246
  } finally {
228
- n._isCapturingConsoleError = !1;
247
+ i._isCapturingConsoleError = !1;
229
248
  }
230
249
  }
231
250
  }, window.addEventListener("error", function(t) {
232
- n.captureAutoError({
251
+ i.captureAutoError({
233
252
  type: "crash",
234
253
  message: t.message || "Script error.",
235
254
  stack: t.error?.stack,
236
255
  loc: `${t.filename}:${t.lineno}:${t.colno}`,
237
- severity: a.ERROR
256
+ severity: c.ERROR
238
257
  });
239
258
  }, { capture: !0 }), window.addEventListener("unhandledrejection", function(t) {
240
- const s = t.reason;
241
- n.captureAutoError({
259
+ const s = t.reason, n = s instanceof Error;
260
+ i.captureAutoError({
242
261
  type: "promise",
243
- message: s instanceof Error ? s.message : String(s),
244
- stack: s instanceof Error ? s.stack : void 0,
245
- severity: a.ERROR
262
+ message: n ? s.message : String(s),
263
+ stack: n ? s.stack : new Error(`Unhandled rejection: ${String(s)}`).stack,
264
+ severity: c.ERROR
246
265
  });
247
- }, { capture: !0 }), document.addEventListener("visibilitychange", function() {
248
- document.visibilityState === "hidden" && n.flush();
266
+ }, { capture: !0 }), window.addEventListener("visibilitychange", function() {
267
+ document.visibilityState === "hidden" && i.flush();
268
+ }), window.addEventListener("pagehide", () => {
269
+ i.destroy();
249
270
  });
250
271
  }
251
- function C(n) {
272
+ function I(i) {
252
273
  if (E())
253
274
  return;
254
- const e = new _(n);
255
- T(e), A(e);
275
+ const e = new _(i);
276
+ T(e), k(e);
256
277
  }
257
278
  export {
258
279
  _ as V,
259
- A as a,
260
- k as g,
261
- C as i
280
+ k as a,
281
+ A as g,
282
+ I as i
262
283
  };
package/next/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as i } from "react/jsx-runtime";
2
2
  import n from "next/script";
3
- import { i as e } from "../init-DeOqWgSl.js";
3
+ import { i as e } from "../init-BfWOgJup.js";
4
4
  function m({ publicKey: t, ingestorUrl: r }) {
5
5
  return /* @__PURE__ */ i(
6
6
  n,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vantmetry",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Lightweight frontend error tracking with minimal browser impact.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/react/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as s } from "react/jsx-runtime";
2
2
  import { createContext as i, Component as a, useEffect as u, useContext as c } from "react";
3
3
  import { logger as o } from "../index.js";
4
- import { i as m } from "../init-DeOqWgSl.js";
4
+ import { i as m } from "../init-BfWOgJup.js";
5
5
  const n = i(null);
6
6
  function h({ publicKey: t, ingestorUrl: r, children: e }) {
7
7
  return u(() => {
@@ -13,9 +13,14 @@ export function initGlobalListeners(tracker: VantmetryTracker) {
13
13
  let message: string;
14
14
  let stack: string | undefined;
15
15
 
16
- const errorObj = args.find((arg) => arg instanceof Error) as Error | undefined;
16
+ const errorIndex = args.findIndex((arg) => arg instanceof Error);
17
+ const errorObj = args[errorIndex] as Error | undefined;
17
18
  if (errorObj) {
18
- message = errorObj.message || String(errorObj);
19
+ const prefix = args
20
+ .slice(0, errorIndex)
21
+ .filter((a) => typeof a === 'string')
22
+ .join(' ');
23
+ message = prefix ? `${prefix}: ${errorObj.message || String(errorObj)}` : errorObj.message || String(errorObj);
19
24
  stack = errorObj.stack;
20
25
  } else {
21
26
  message = args
@@ -53,18 +58,24 @@ export function initGlobalListeners(tracker: VantmetryTracker) {
53
58
 
54
59
  window.addEventListener('unhandledrejection', function (event: PromiseRejectionEvent) {
55
60
  const reason = event.reason;
61
+ const isError = reason instanceof Error;
56
62
  tracker.captureAutoError({
57
63
  type: 'promise',
58
- message: reason instanceof Error ? reason.message : String(reason),
59
- stack: reason instanceof Error ? reason.stack : undefined,
64
+ message: isError ? reason.message : String(reason),
65
+ stack: isError ? reason.stack : new Error(`Unhandled rejection: ${String(reason)}`).stack,
60
66
  severity: LogLevel.ERROR,
61
67
  });
62
68
  }, { capture: true });
63
69
 
64
- // Flush on page unload
65
- document.addEventListener('visibilitychange', function () {
70
+ // Flush on page unload or visibility change
71
+ window.addEventListener('visibilitychange', function () {
66
72
  if (document.visibilityState === 'hidden') {
67
73
  void tracker.flush();
68
74
  }
69
75
  });
76
+
77
+ // Enable bfcache restoration by closing transport on navigate away
78
+ window.addEventListener('pagehide', () => {
79
+ void tracker.destroy();
80
+ });
70
81
  }
@@ -43,6 +43,10 @@ export class VantmetryTracker implements VantmetryInstance {
43
43
 
44
44
  public async flush() {
45
45
  if (this.buffer.length === 0) {
46
+ if (this.flushTimer) {
47
+ clearTimeout(this.flushTimer);
48
+ this.flushTimer = null;
49
+ }
46
50
  return;
47
51
  }
48
52
 
@@ -69,6 +73,11 @@ export class VantmetryTracker implements VantmetryInstance {
69
73
  await this.transport.send(dataPayload);
70
74
  }
71
75
 
76
+ public async destroy() {
77
+ this.transport.close();
78
+ await this.flush();
79
+ }
80
+
72
81
  // --- Internal Logic ---
73
82
 
74
83
  private addToBuffer(payload: LogPayload) {
@@ -106,7 +115,13 @@ export class VantmetryTracker implements VantmetryInstance {
106
115
  return;
107
116
  }
108
117
 
109
- const { message, details, stack } = payload;
118
+ let { message, stack } = payload;
119
+ const { details } = payload;
120
+
121
+ if (message instanceof Error) {
122
+ stack = stack ?? message.stack;
123
+ message = message.message || String(message);
124
+ }
110
125
 
111
126
  const maskedMessage = typeof message === 'string' ? maskPII(message) : message;
112
127
  const maskedDetails = typeof details === 'object' ? maskObjectPII(details) : details;
@@ -4,6 +4,7 @@ const DEFAULT_INGESTOR_URL = 'https://ingestor.vantmetry.com:4433';
4
4
 
5
5
  export class TransportManager {
6
6
  private wtSession: WebTransport | null = null;
7
+ private wtInitPromise: Promise<void> | null = null;
7
8
  private readonly endpoint: string;
8
9
  private readonly wtEndpoint: string;
9
10
  private readonly debug: boolean;
@@ -18,11 +19,17 @@ export class TransportManager {
18
19
  } catch {
19
20
  this.debug = false;
20
21
  }
22
+ }
21
23
 
22
- void this.initWT();
24
+ private initWT(): Promise<void> {
25
+ if (this.wtInitPromise) {
26
+ return this.wtInitPromise;
27
+ }
28
+ this.wtInitPromise = this.connectWT();
29
+ return this.wtInitPromise;
23
30
  }
24
31
 
25
- private async initWT() {
32
+ private async connectWT(): Promise<void> {
26
33
  if (!('WebTransport' in window)) {
27
34
  return;
28
35
  }
@@ -46,6 +53,8 @@ export class TransportManager {
46
53
  }
47
54
 
48
55
  public async send(payload: string): Promise<void> {
56
+ await this.initWT();
57
+
49
58
  if (this.wtSession) {
50
59
  try {
51
60
  const stream = await this.wtSession.createUnidirectionalStream();
@@ -79,4 +88,11 @@ export class TransportManager {
79
88
  }
80
89
  });
81
90
  }
91
+
92
+ public close() {
93
+ if (this.wtSession) {
94
+ this.wtSession.close();
95
+ this.wtSession = null;
96
+ }
97
+ }
82
98
  }
package/src/core/types.ts CHANGED
@@ -15,6 +15,7 @@ export interface VantmetryInstance {
15
15
  info: (message: string, details?: LogDetails) => void;
16
16
  debug: (message: string, details?: LogDetails) => void;
17
17
  flush: () => Promise<void>;
18
+ destroy: () => Promise<void>;
18
19
  }
19
20
 
20
21
  declare global {
package/src/index.ts CHANGED
@@ -25,4 +25,7 @@ export const logger: VantmetryInstance = {
25
25
  flush() {
26
26
  return getInstance().flush();
27
27
  },
28
+ destroy() {
29
+ return getInstance().destroy();
30
+ },
28
31
  };