@botim/mp-debug-sdk 0.4.1 → 0.5.2

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
@@ -272,7 +272,7 @@ function resolveAgainstEndpoint(url, base) {
272
272
  function detectDeviceInfo(app, override) {
273
273
  const ua = typeof navigator !== "undefined" ? navigator.userAgent : void 0;
274
274
  return {
275
- deviceId: override?.deviceId ?? generateDeviceId(),
275
+ deviceId: override?.deviceId ?? loadOrCreateDeviceId(),
276
276
  platform: override?.platform ?? detectPlatform(ua),
277
277
  osVersion: override?.osVersion,
278
278
  appName: override?.appName ?? app.name,
@@ -288,6 +288,21 @@ function detectPlatform(ua) {
288
288
  if (/Mozilla|Chrome|Safari|Firefox/i.test(ua)) return "web";
289
289
  return "unknown";
290
290
  }
291
+ var DEVICE_ID_STORAGE_KEY = "botim-debug-sdk:device-id";
292
+ function loadOrCreateDeviceId() {
293
+ try {
294
+ const ls = typeof localStorage !== "undefined" ? localStorage : null;
295
+ if (ls) {
296
+ const stored = ls.getItem(DEVICE_ID_STORAGE_KEY);
297
+ if (stored && stored.length > 0) return stored;
298
+ const fresh = generateDeviceId();
299
+ ls.setItem(DEVICE_ID_STORAGE_KEY, fresh);
300
+ return fresh;
301
+ }
302
+ } catch {
303
+ }
304
+ return generateDeviceId();
305
+ }
291
306
  function generateDeviceId() {
292
307
  const c = typeof crypto !== "undefined" ? crypto : void 0;
293
308
  if (c?.randomUUID) return c.randomUUID();
@@ -562,6 +577,11 @@ function wrapFetch(opts) {
562
577
  method,
563
578
  url,
564
579
  status: res.status,
580
+ // statusText carries the human label ("OK", "Not Found"). Pre-HTTP/2
581
+ // responses always have it; HTTP/2+ defines it as empty by spec but
582
+ // most browsers synthesize one from the code, so this is reliable
583
+ // enough to display alongside the status code.
584
+ statusText: res.statusText || void 0,
565
585
  durationMs: Date.now() - start,
566
586
  resHeaders: headersFromResponse(res),
567
587
  resBody
@@ -575,7 +595,14 @@ function wrapFetch(opts) {
575
595
  url,
576
596
  durationMs: Date.now() - start,
577
597
  errorMessage: err instanceof Error ? err.message : String(err),
578
- errorName: err instanceof Error ? err.name : void 0
598
+ errorName: err instanceof Error ? err.name : void 0,
599
+ // Stack from the rejected promise — points into fetch internals
600
+ // and (when present) the call site that issued the request.
601
+ errorStack: err instanceof Error ? err.stack : void 0,
602
+ // undici frequently wraps the real reason in `cause` (e.g.
603
+ // `TypeError: fetch failed` outside, `Error: ECONNREFUSED` inside).
604
+ // Flatten the chain so operators don't have to dig.
605
+ errorCause: collectCauseChain(err)
579
606
  });
580
607
  throw err;
581
608
  }
@@ -584,6 +611,24 @@ function wrapFetch(opts) {
584
611
  target.fetch = original;
585
612
  };
586
613
  }
614
+ function collectCauseChain(err) {
615
+ if (!err || typeof err !== "object") return void 0;
616
+ const lines = [];
617
+ let cur = err.cause;
618
+ const seen = /* @__PURE__ */ new Set();
619
+ while (cur && lines.length < 5) {
620
+ if (seen.has(cur)) break;
621
+ seen.add(cur);
622
+ if (cur instanceof Error) {
623
+ lines.push(`${cur.name}: ${cur.message}`);
624
+ cur = cur.cause;
625
+ } else {
626
+ lines.push(String(cur));
627
+ cur = cur?.cause;
628
+ }
629
+ }
630
+ return lines.length ? lines.join("\n") : void 0;
631
+ }
587
632
  function wrapXHR(opts) {
588
633
  if (typeof XMLHttpRequest === "undefined") return () => {
589
634
  };
@@ -619,6 +664,7 @@ function wrapXHR(opts) {
619
664
  }
620
665
  s.start = Date.now();
621
666
  s.reqBody = typeof body === "string" ? body : void 0;
667
+ const sendSiteStack = captureCallSiteStack();
622
668
  opts.emit({
623
669
  phase: "request",
624
670
  reqId: s.reqId,
@@ -636,25 +682,32 @@ function wrapXHR(opts) {
636
682
  method: s.method,
637
683
  url: s.url,
638
684
  status: this.status,
685
+ // XHR exposes statusText directly; same display purpose as fetch.
686
+ statusText: this.statusText || void 0,
639
687
  durationMs: Date.now() - s.start,
640
688
  resHeaders: headers,
641
689
  resBody
642
690
  });
643
691
  };
644
- const onError = () => {
692
+ const onError = (kind) => () => {
645
693
  opts.emit({
646
694
  phase: "error",
647
695
  reqId: s.reqId,
648
696
  method: s.method,
649
697
  url: s.url,
650
698
  durationMs: Date.now() - s.start,
651
- errorMessage: this.statusText || "xhr error"
699
+ // Distinguish error/timeout/abort in the message — the standard
700
+ // XHR `statusText` is empty for `error` and unhelpful for the
701
+ // others, so we synthesise a clear label.
702
+ errorMessage: this.statusText || `xhr ${kind}`,
703
+ errorName: `XHR${kind[0].toUpperCase()}${kind.slice(1)}`,
704
+ errorStack: sendSiteStack
652
705
  });
653
706
  };
654
707
  this.addEventListener("load", onLoad);
655
- this.addEventListener("error", onError);
656
- this.addEventListener("timeout", onError);
657
- this.addEventListener("abort", onError);
708
+ this.addEventListener("error", onError("error"));
709
+ this.addEventListener("timeout", onError("timeout"));
710
+ this.addEventListener("abort", onError("abort"));
658
711
  return origSend.apply(this, [body]);
659
712
  };
660
713
  return () => {
@@ -663,6 +716,15 @@ function wrapXHR(opts) {
663
716
  proto.setRequestHeader = origSetReqHeader;
664
717
  };
665
718
  }
719
+ function captureCallSiteStack() {
720
+ try {
721
+ throw new Error("xhr-callsite");
722
+ } catch (err) {
723
+ if (!(err instanceof Error) || !err.stack) return void 0;
724
+ const lines = err.stack.split("\n");
725
+ return lines.slice(2).join("\n") || void 0;
726
+ }
727
+ }
666
728
  function parseXhrHeaders(raw) {
667
729
  const out = {};
668
730
  if (!raw) return out;