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