@emblemvault/identity-mesh 1.0.0 → 2.0.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.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  IdentityMesh: () => IdentityMesh,
24
+ IdentityMeshRenderer: () => IdentityMeshRenderer,
24
25
  defaultNodeTypes: () => defaultNodeTypes,
25
26
  getBrowserIcon: () => getBrowserIcon,
26
27
  getCountryFlag: () => getCountryFlag,
@@ -30,70 +31,8 @@ __export(index_exports, {
30
31
  });
31
32
  module.exports = __toCommonJS(index_exports);
32
33
 
33
- // src/IdentityMesh.tsx
34
- var import_react2 = require("react");
35
-
36
- // src/hooks/useMeshData.ts
34
+ // src/IdentityMeshRenderer.tsx
37
35
  var import_react = require("react");
38
- function useMeshData({
39
- apiUrl,
40
- vaultId,
41
- visitorId,
42
- onError
43
- }) {
44
- const [links, setLinks] = (0, import_react.useState)([]);
45
- const [visitorDetails, setVisitorDetails] = (0, import_react.useState)({});
46
- const [loading, setLoading] = (0, import_react.useState)(true);
47
- const [error, setError] = (0, import_react.useState)(null);
48
- const fetchData = (0, import_react.useCallback)(async () => {
49
- if (!apiUrl || !vaultId || !visitorId) {
50
- setLoading(false);
51
- return;
52
- }
53
- setLoading(true);
54
- setError(null);
55
- try {
56
- const response = await fetch(apiUrl, {
57
- method: "POST",
58
- headers: {
59
- "Content-Type": "application/json"
60
- },
61
- body: JSON.stringify({ vaultId, visitorId })
62
- });
63
- const data = await response.json();
64
- if (!response.ok || !data.success) {
65
- const err = new Error(data.error || `HTTP ${response.status}`);
66
- setError(err);
67
- onError == null ? void 0 : onError(err);
68
- setLinks([]);
69
- setVisitorDetails({});
70
- return;
71
- }
72
- if (data.data) {
73
- setLinks(data.data.links);
74
- setVisitorDetails(data.data.visitorDetails);
75
- }
76
- } catch (err) {
77
- const error2 = err instanceof Error ? err : new Error("Unknown error");
78
- setError(error2);
79
- onError == null ? void 0 : onError(error2);
80
- setLinks([]);
81
- setVisitorDetails({});
82
- } finally {
83
- setLoading(false);
84
- }
85
- }, [apiUrl, vaultId, visitorId, onError]);
86
- (0, import_react.useEffect)(() => {
87
- fetchData();
88
- }, [fetchData]);
89
- return {
90
- links,
91
- visitorDetails,
92
- loading,
93
- error,
94
- refetch: fetchData
95
- };
96
- }
97
36
 
98
37
  // src/utils.tsx
99
38
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -202,27 +141,33 @@ function renderNodeShape(nodeTypes, type, x, y, size, isHovered, isSelected) {
202
141
  }
203
142
  }
204
143
 
205
- // src/IdentityMesh.tsx
144
+ // src/IdentityMeshRenderer.tsx
206
145
  var import_jsx_runtime2 = require("react/jsx-runtime");
207
- function MeshRenderer({
208
- links,
209
- visitorDetails,
210
- selectedVisitor,
211
- onSelectVisitor,
212
- nodeTypes,
213
- height,
214
- showLegend,
215
- showIdentityCount
146
+ function IdentityMeshRenderer({
147
+ links = [],
148
+ visitorDetails = {},
149
+ selectedVisitor: externalSelectedVisitor,
150
+ onSelectVisitor: externalOnSelectVisitor,
151
+ nodeTypes: customNodeTypes,
152
+ height = 600,
153
+ showLegend = true,
154
+ showIdentityCount = true
216
155
  }) {
217
- const [hoveredNode, setHoveredNode] = (0, import_react2.useState)(null);
218
- const [animationPhase, setAnimationPhase] = (0, import_react2.useState)(0);
219
- (0, import_react2.useEffect)(() => {
156
+ const [hoveredNode, setHoveredNode] = (0, import_react.useState)(null);
157
+ const [animationPhase, setAnimationPhase] = (0, import_react.useState)(0);
158
+ const [internalSelectedVisitor, setInternalSelectedVisitor] = (0, import_react.useState)(null);
159
+ const selectedVisitor = externalSelectedVisitor != null ? externalSelectedVisitor : internalSelectedVisitor;
160
+ const onSelectVisitor = externalOnSelectVisitor != null ? externalOnSelectVisitor : setInternalSelectedVisitor;
161
+ const nodeTypes = (0, import_react.useMemo)(() => {
162
+ return { ...defaultNodeTypes, ...customNodeTypes };
163
+ }, [customNodeTypes]);
164
+ (0, import_react.useEffect)(() => {
220
165
  const interval = setInterval(() => {
221
166
  setAnimationPhase((p) => (p + 1) % 360);
222
167
  }, 50);
223
168
  return () => clearInterval(interval);
224
169
  }, []);
225
- const visitorGroups = (0, import_react2.useMemo)(() => {
170
+ const visitorGroups = (0, import_react.useMemo)(() => {
226
171
  const groups = {};
227
172
  links.forEach((link) => {
228
173
  if (!groups[link.visitorId]) groups[link.visitorId] = [];
@@ -230,7 +175,7 @@ function MeshRenderer({
230
175
  });
231
176
  return groups;
232
177
  }, [links]);
233
- const identityLinks = (0, import_react2.useMemo)(() => {
178
+ const identityLinks = (0, import_react.useMemo)(() => {
234
179
  const externalIdToVisitors = {};
235
180
  links.forEach((link) => {
236
181
  if (!externalIdToVisitors[link.externalId]) {
@@ -259,7 +204,7 @@ function MeshRenderer({
259
204
  return pairs;
260
205
  }, [links]);
261
206
  const visitors = Object.keys(visitorGroups);
262
- const visitorPositions = (0, import_react2.useMemo)(() => {
207
+ const visitorPositions = (0, import_react.useMemo)(() => {
263
208
  const centerX = 400;
264
209
  const centerY = 300;
265
210
  const radius = Math.min(200, 80 + visitors.length * 25);
@@ -272,7 +217,7 @@ function MeshRenderer({
272
217
  return acc;
273
218
  }, {});
274
219
  }, [visitors]);
275
- const linkPositions = (0, import_react2.useMemo)(() => {
220
+ const linkPositions = (0, import_react.useMemo)(() => {
276
221
  const positions = {};
277
222
  Object.entries(visitorGroups).forEach(([visitorId, visitorLinks]) => {
278
223
  const visitorPos = visitorPositions[visitorId];
@@ -571,6 +516,441 @@ function MeshRenderer({
571
516
  ] })
572
517
  ] });
573
518
  }
519
+
520
+ // src/IdentityMesh.tsx
521
+ var import_react3 = require("react");
522
+
523
+ // src/hooks/useMeshData.ts
524
+ var import_react2 = require("react");
525
+ function useMeshData({
526
+ apiUrl,
527
+ vaultId,
528
+ visitorId,
529
+ onError
530
+ }) {
531
+ const [links, setLinks] = (0, import_react2.useState)([]);
532
+ const [visitorDetails, setVisitorDetails] = (0, import_react2.useState)({});
533
+ const [loading, setLoading] = (0, import_react2.useState)(true);
534
+ const [error, setError] = (0, import_react2.useState)(null);
535
+ const fetchData = (0, import_react2.useCallback)(async () => {
536
+ if (!apiUrl || !vaultId || !visitorId) {
537
+ setLoading(false);
538
+ return;
539
+ }
540
+ setLoading(true);
541
+ setError(null);
542
+ try {
543
+ const response = await fetch(apiUrl, {
544
+ method: "POST",
545
+ headers: {
546
+ "Content-Type": "application/json"
547
+ },
548
+ body: JSON.stringify({ vaultId, visitorId })
549
+ });
550
+ const data = await response.json();
551
+ if (!response.ok || !data.success) {
552
+ const err = new Error(data.error || `HTTP ${response.status}`);
553
+ setError(err);
554
+ onError == null ? void 0 : onError(err);
555
+ setLinks([]);
556
+ setVisitorDetails({});
557
+ return;
558
+ }
559
+ if (data.data) {
560
+ setLinks(data.data.links);
561
+ setVisitorDetails(data.data.visitorDetails);
562
+ }
563
+ } catch (err) {
564
+ const error2 = err instanceof Error ? err : new Error("Unknown error");
565
+ setError(error2);
566
+ onError == null ? void 0 : onError(error2);
567
+ setLinks([]);
568
+ setVisitorDetails({});
569
+ } finally {
570
+ setLoading(false);
571
+ }
572
+ }, [apiUrl, vaultId, visitorId, onError]);
573
+ (0, import_react2.useEffect)(() => {
574
+ fetchData();
575
+ }, [fetchData]);
576
+ return {
577
+ links,
578
+ visitorDetails,
579
+ loading,
580
+ error,
581
+ refetch: fetchData
582
+ };
583
+ }
584
+
585
+ // src/IdentityMesh.tsx
586
+ var import_jsx_runtime3 = require("react/jsx-runtime");
587
+ function MeshRenderer({
588
+ links,
589
+ visitorDetails,
590
+ selectedVisitor,
591
+ onSelectVisitor,
592
+ nodeTypes,
593
+ height,
594
+ showLegend,
595
+ showIdentityCount
596
+ }) {
597
+ const [hoveredNode, setHoveredNode] = (0, import_react3.useState)(null);
598
+ const [animationPhase, setAnimationPhase] = (0, import_react3.useState)(0);
599
+ (0, import_react3.useEffect)(() => {
600
+ const interval = setInterval(() => {
601
+ setAnimationPhase((p) => (p + 1) % 360);
602
+ }, 50);
603
+ return () => clearInterval(interval);
604
+ }, []);
605
+ const visitorGroups = (0, import_react3.useMemo)(() => {
606
+ const groups = {};
607
+ links.forEach((link) => {
608
+ if (!groups[link.visitorId]) groups[link.visitorId] = [];
609
+ groups[link.visitorId].push(link);
610
+ });
611
+ return groups;
612
+ }, [links]);
613
+ const identityLinks = (0, import_react3.useMemo)(() => {
614
+ const externalIdToVisitors = {};
615
+ links.forEach((link) => {
616
+ if (!externalIdToVisitors[link.externalId]) {
617
+ externalIdToVisitors[link.externalId] = /* @__PURE__ */ new Set();
618
+ }
619
+ externalIdToVisitors[link.externalId].add(link.visitorId);
620
+ });
621
+ const pairs = [];
622
+ const seenPairs = /* @__PURE__ */ new Set();
623
+ Object.entries(externalIdToVisitors).forEach(([externalId, visitors2]) => {
624
+ var _a;
625
+ if (visitors2.size > 1) {
626
+ const visitorList = Array.from(visitors2);
627
+ for (let i = 0; i < visitorList.length; i++) {
628
+ for (let j = i + 1; j < visitorList.length; j++) {
629
+ const pairKey = [visitorList[i], visitorList[j]].sort().join("-");
630
+ if (!seenPairs.has(pairKey)) {
631
+ seenPairs.add(pairKey);
632
+ const linkType = ((_a = links.find((l) => l.externalId === externalId)) == null ? void 0 : _a.externalType) || "unknown";
633
+ pairs.push({ v1: visitorList[i], v2: visitorList[j], sharedId: externalId, type: linkType });
634
+ }
635
+ }
636
+ }
637
+ }
638
+ });
639
+ return pairs;
640
+ }, [links]);
641
+ const visitors = Object.keys(visitorGroups);
642
+ const visitorPositions = (0, import_react3.useMemo)(() => {
643
+ const centerX = 400;
644
+ const centerY = 300;
645
+ const radius = Math.min(200, 80 + visitors.length * 25);
646
+ return visitors.reduce((acc, visitor, i) => {
647
+ const angle = i / visitors.length * Math.PI * 2 - Math.PI / 2;
648
+ acc[visitor] = {
649
+ x: centerX + Math.cos(angle) * radius,
650
+ y: centerY + Math.sin(angle) * radius
651
+ };
652
+ return acc;
653
+ }, {});
654
+ }, [visitors]);
655
+ const linkPositions = (0, import_react3.useMemo)(() => {
656
+ const positions = {};
657
+ Object.entries(visitorGroups).forEach(([visitorId, visitorLinks]) => {
658
+ const visitorPos = visitorPositions[visitorId];
659
+ if (!visitorPos) return;
660
+ const linkRadius = 60;
661
+ visitorLinks.forEach((link, i) => {
662
+ const angle = i / visitorLinks.length * Math.PI * 2 - Math.PI / 2;
663
+ positions[link.id] = {
664
+ x: visitorPos.x + Math.cos(angle) * linkRadius,
665
+ y: visitorPos.y + Math.sin(angle) * linkRadius,
666
+ type: link.externalType
667
+ };
668
+ });
669
+ });
670
+ return positions;
671
+ }, [visitorGroups, visitorPositions]);
672
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative", width: "100%", height, overflow: "hidden" }, children: [
673
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "100%", height: "100%", style: { position: "absolute", top: 0, left: 0, opacity: 0.1 }, children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pattern", { id: "mesh-grid", width: "40", height: "40", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M 40 0 L 0 0 0 40", fill: "none", stroke: "currentColor", strokeWidth: "0.5" }) }) }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "100%", height: "100%", fill: "url(#mesh-grid)" })
676
+ ] }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "100%", height: "100%", viewBox: "0 0 800 600", style: { position: "relative" }, children: [
678
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("radialGradient", { id: "mesh-centerGlow", children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "0%", stopColor: "rgba(0, 240, 255, 0.2)" }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("stop", { offset: "100%", stopColor: "rgba(0, 240, 255, 0)" })
681
+ ] }) }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "400", cy: "300", r: "280", fill: "url(#mesh-centerGlow)" }),
683
+ identityLinks.map((link, idx) => {
684
+ const p1 = visitorPositions[link.v1];
685
+ const p2 = visitorPositions[link.v2];
686
+ if (!p1 || !p2) return null;
687
+ const midX = (p1.x + p2.x) / 2;
688
+ const midY = (p1.y + p2.y) / 2;
689
+ const dx = p2.x - p1.x;
690
+ const dy = p2.y - p1.y;
691
+ const dist = Math.sqrt(dx * dx + dy * dy);
692
+ const curvature = Math.min(50, dist * 0.2);
693
+ const perpX = -dy / dist * curvature;
694
+ const perpY = dx / dist * curvature;
695
+ const ctrlX = midX + perpX;
696
+ const ctrlY = midY + perpY;
697
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
699
+ "path",
700
+ {
701
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
702
+ fill: "none",
703
+ stroke: "#ff6b6b",
704
+ strokeWidth: 8,
705
+ strokeOpacity: 0.2,
706
+ style: { filter: "blur(4px)" }
707
+ }
708
+ ),
709
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
710
+ "path",
711
+ {
712
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
713
+ fill: "none",
714
+ stroke: "#ff6b6b",
715
+ strokeWidth: 3,
716
+ strokeOpacity: 0.8,
717
+ strokeDasharray: "8 4",
718
+ style: { strokeDashoffset: -animationPhase * 0.5 }
719
+ }
720
+ ),
721
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { transform: `translate(${ctrlX}, ${ctrlY})`, children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: -45, y: -12, width: 90, height: 24, rx: 12, fill: "rgba(255, 107, 107, 0.9)" }),
723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("text", { x: 0, y: 4, textAnchor: "middle", fill: "white", fontSize: 9, fontWeight: "bold", children: [
724
+ "SAME ",
725
+ link.type === "wallet" ? "WALLET" : "VAULT"
726
+ ] })
727
+ ] }),
728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { r: "4", fill: "#ff6b6b", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("animateMotion", { dur: "3s", repeatCount: "indefinite", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { r: "4", fill: "#ffd93d", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("animateMotion", { dur: "3s", repeatCount: "indefinite", begin: "1.5s", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) })
730
+ ] }, `identity-${idx}`);
731
+ }),
732
+ visitors.map(
733
+ (v1, i) => visitors.slice(i + 1).map((v2) => {
734
+ const p1 = visitorPositions[v1];
735
+ const p2 = visitorPositions[v2];
736
+ if (!p1 || !p2) return null;
737
+ const distance = Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
738
+ const opacity = Math.max(0, 0.08 - distance / 3e3);
739
+ if (identityLinks.some((l) => l.v1 === v1 && l.v2 === v2 || l.v1 === v2 && l.v2 === v1)) return null;
740
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
741
+ "line",
742
+ {
743
+ x1: p1.x,
744
+ y1: p1.y,
745
+ x2: p2.x,
746
+ y2: p2.y,
747
+ stroke: "rgba(0, 240, 255, 0.15)",
748
+ strokeWidth: 1,
749
+ strokeDasharray: "2 4",
750
+ style: { opacity, strokeDashoffset: animationPhase % 6 }
751
+ },
752
+ `mesh-${v1}-${v2}`
753
+ );
754
+ })
755
+ ),
756
+ links.map((link) => {
757
+ const visitorPos = visitorPositions[link.visitorId];
758
+ const linkPos = linkPositions[link.id];
759
+ if (!visitorPos || !linkPos) return null;
760
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
761
+ const isVisitorSelected = selectedVisitor === link.visitorId;
762
+ const isLinkHovered = hoveredNode === link.id;
763
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
765
+ "line",
766
+ {
767
+ x1: visitorPos.x,
768
+ y1: visitorPos.y,
769
+ x2: linkPos.x,
770
+ y2: linkPos.y,
771
+ stroke: config.color,
772
+ strokeWidth: isVisitorSelected || isLinkHovered ? 2 : 1,
773
+ strokeOpacity: isVisitorSelected ? 0.8 : 0.3,
774
+ strokeDasharray: link.confidence && link.confidence < 0.9 ? "4 2" : "none"
775
+ }
776
+ ),
777
+ isVisitorSelected && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { r: "3", fill: config.color, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("animateMotion", { dur: "2s", repeatCount: "indefinite", path: `M${visitorPos.x},${visitorPos.y} L${linkPos.x},${linkPos.y}` }) })
778
+ ] }, `connection-${link.id}`);
779
+ }),
780
+ visitors.map((visitorId) => {
781
+ var _a, _b, _c, _d;
782
+ const pos = visitorPositions[visitorId];
783
+ if (!pos) return null;
784
+ const isSelected = selectedVisitor === visitorId;
785
+ const isHovered = hoveredNode === `visitor-${visitorId}`;
786
+ const linkCount = ((_a = visitorGroups[visitorId]) == null ? void 0 : _a.length) || 0;
787
+ const details = visitorDetails[visitorId];
788
+ const hasIdentityLink = identityLinks.some((l) => l.v1 === visitorId || l.v2 === visitorId);
789
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
790
+ "g",
791
+ {
792
+ style: { cursor: "pointer" },
793
+ onMouseEnter: () => setHoveredNode(`visitor-${visitorId}`),
794
+ onMouseLeave: () => setHoveredNode(null),
795
+ onClick: () => onSelectVisitor(isSelected ? null : visitorId),
796
+ children: [
797
+ hasIdentityLink && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
798
+ "circle",
799
+ {
800
+ cx: pos.x,
801
+ cy: pos.y,
802
+ r: isSelected ? 42 : isHovered ? 40 : 36,
803
+ fill: "none",
804
+ stroke: "#ff6b6b",
805
+ strokeWidth: 2,
806
+ strokeDasharray: "6 3",
807
+ style: {
808
+ transform: `rotate(${-animationPhase}deg)`,
809
+ transformOrigin: `${pos.x}px ${pos.y}px`,
810
+ filter: "drop-shadow(0 0 8px rgba(255, 107, 107, 0.6))"
811
+ }
812
+ }
813
+ ),
814
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
815
+ "circle",
816
+ {
817
+ cx: pos.x,
818
+ cy: pos.y,
819
+ r: isSelected ? 35 : isHovered ? 32 : 28,
820
+ fill: "none",
821
+ stroke: "rgba(0, 240, 255, 0.3)",
822
+ strokeWidth: 2,
823
+ strokeDasharray: "4 2",
824
+ style: {
825
+ transform: `rotate(${animationPhase}deg)`,
826
+ transformOrigin: `${pos.x}px ${pos.y}px`
827
+ }
828
+ }
829
+ ),
830
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
831
+ "circle",
832
+ {
833
+ cx: pos.x,
834
+ cy: pos.y,
835
+ r: isSelected ? 24 : isHovered ? 22 : 18,
836
+ fill: "rgba(0, 20, 30, 0.95)",
837
+ stroke: "#00f0ff",
838
+ strokeWidth: isSelected ? 3 : 2,
839
+ style: {
840
+ filter: `drop-shadow(0 0 ${isSelected ? 20 : isHovered ? 15 : 10}px rgba(0, 240, 255, 0.6))`,
841
+ transition: "all 0.3s ease"
842
+ }
843
+ }
844
+ ),
845
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: pos.x, y: pos.y + 5, textAnchor: "middle", fill: "#00f0ff", fontSize: isSelected ? 16 : 14, fontWeight: "bold", children: details ? getDeviceIcon((_b = details.fingerprint) == null ? void 0 : _b.platform, (_c = details.fingerprint) == null ? void 0 : _c.touchSupport) : linkCount }),
846
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: pos.x, y: pos.y + 48, textAnchor: "middle", fill: "rgba(255, 255, 255, 0.8)", fontSize: 10, fontFamily: "monospace", children: visitorId.length > 12 ? visitorId.slice(0, 12) + "..." : visitorId }),
847
+ ((_d = details == null ? void 0 : details.location) == null ? void 0 : _d.country) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("text", { x: pos.x, y: pos.y + 60, textAnchor: "middle", fill: "rgba(255, 255, 255, 0.5)", fontSize: 9, children: [
848
+ getCountryFlag(details.location.country),
849
+ " ",
850
+ details.location.city || details.location.region
851
+ ] })
852
+ ]
853
+ },
854
+ `visitor-${visitorId}`
855
+ );
856
+ }),
857
+ links.map((link) => {
858
+ const pos = linkPositions[link.id];
859
+ if (!pos) return null;
860
+ const isHovered = hoveredNode === link.id;
861
+ const isVisitorSelected = selectedVisitor === link.visitorId;
862
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
863
+ const isSharedId = identityLinks.some((l) => l.sharedId === link.externalId);
864
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
865
+ "g",
866
+ {
867
+ style: { cursor: "pointer", opacity: selectedVisitor && !isVisitorSelected ? 0.3 : 1 },
868
+ onMouseEnter: () => setHoveredNode(link.id),
869
+ onMouseLeave: () => setHoveredNode(null),
870
+ children: [
871
+ isSharedId && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
872
+ "circle",
873
+ {
874
+ cx: pos.x,
875
+ cy: pos.y,
876
+ r: 22,
877
+ fill: "none",
878
+ stroke: "#ff6b6b",
879
+ strokeWidth: 2,
880
+ strokeOpacity: 0.6,
881
+ strokeDasharray: "3 2"
882
+ }
883
+ ),
884
+ renderNodeShape(nodeTypes, link.externalType, pos.x, pos.y, 14, isHovered, isVisitorSelected),
885
+ link.confidence && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
886
+ "circle",
887
+ {
888
+ cx: pos.x,
889
+ cy: pos.y,
890
+ r: 18,
891
+ fill: "none",
892
+ stroke: config.color,
893
+ strokeWidth: 2,
894
+ strokeOpacity: 0.3,
895
+ strokeDasharray: `${link.confidence * 113} 113`,
896
+ strokeLinecap: "round",
897
+ transform: `rotate(-90, ${pos.x}, ${pos.y})`
898
+ }
899
+ ),
900
+ isHovered && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("g", { children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: pos.x - 70, y: pos.y - 60, width: 140, height: 48, rx: 6, fill: "rgba(0, 10, 20, 0.95)", stroke: config.color, strokeWidth: 1 }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("text", { x: pos.x, y: pos.y - 42, textAnchor: "middle", fill: config.color, fontSize: 10, fontWeight: "bold", children: [
903
+ isSharedId ? "\u{1F517} " : "",
904
+ config.label
905
+ ] }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: pos.x, y: pos.y - 28, textAnchor: "middle", fill: "rgba(255, 255, 255, 0.7)", fontSize: 9, fontFamily: "monospace", children: link.externalId.length > 18 ? link.externalId.slice(0, 18) + "..." : link.externalId })
907
+ ] })
908
+ ]
909
+ },
910
+ `node-${link.id}`
911
+ );
912
+ })
913
+ ] }),
914
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
915
+ position: "absolute",
916
+ bottom: 20,
917
+ left: 20,
918
+ display: "flex",
919
+ gap: 16,
920
+ flexWrap: "wrap",
921
+ padding: "12px 16px",
922
+ background: "rgba(0, 10, 20, 0.9)",
923
+ borderRadius: 8,
924
+ border: "1px solid rgba(0, 240, 255, 0.2)"
925
+ }, children: [
926
+ Object.entries(nodeTypes).filter(([key]) => !["visitor", "device", "identity_link"].includes(key)).map(([key, config]) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
927
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", children: [
928
+ config.shape === "hexagon" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "12,2 22,7 22,17 12,22 2,17 2,7", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
929
+ config.shape === "diamond" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "12,2 22,12 12,22 2,12", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
930
+ config.shape === "circle" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: 12, cy: 12, r: 9, fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
931
+ config.shape === "triangle" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "12,2 22,20 2,20", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 })
932
+ ] }),
933
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: config.label })
934
+ ] }, key)),
935
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
936
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { width: 20, height: 3, background: "#ff6b6b", borderRadius: 2 } }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: "Same Identity" })
938
+ ] })
939
+ ] }),
940
+ showIdentityCount && identityLinks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
941
+ position: "absolute",
942
+ top: 20,
943
+ left: 20,
944
+ padding: "10px 14px",
945
+ background: "rgba(255, 107, 107, 0.15)",
946
+ borderRadius: 8,
947
+ border: "1px solid rgba(255, 107, 107, 0.4)"
948
+ }, children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.6)", marginBottom: 4 }, children: "Cross-Device Identities" }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 20, fontWeight: "bold", color: "#ff6b6b" }, children: identityLinks.length })
951
+ ] })
952
+ ] });
953
+ }
574
954
  function IdentityMesh({
575
955
  apiUrl,
576
956
  vaultId,
@@ -584,14 +964,14 @@ function IdentityMesh({
584
964
  showLegend = true,
585
965
  showIdentityCount = true
586
966
  }) {
587
- const [selectedVisitor, setSelectedVisitor] = (0, import_react2.useState)(null);
967
+ const [selectedVisitor, setSelectedVisitor] = (0, import_react3.useState)(null);
588
968
  const { links, visitorDetails, loading, error } = useMeshData({
589
969
  apiUrl,
590
970
  vaultId,
591
971
  visitorId,
592
972
  onError
593
973
  });
594
- const nodeTypes = (0, import_react2.useMemo)(() => {
974
+ const nodeTypes = (0, import_react3.useMemo)(() => {
595
975
  const merged = { ...defaultNodeTypes };
596
976
  if (theme == null ? void 0 : theme.nodeColors) {
597
977
  Object.entries(theme.nodeColors).forEach(([key, value]) => {
@@ -612,8 +992,8 @@ function IdentityMesh({
612
992
  ...style
613
993
  };
614
994
  if (loading) {
615
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { textAlign: "center" }, children: [
616
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: {
995
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center" }, children: [
996
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
617
997
  width: 40,
618
998
  height: 40,
619
999
  border: "3px solid rgba(0, 240, 255, 0.3)",
@@ -622,24 +1002,24 @@ function IdentityMesh({
622
1002
  animation: "spin 1s linear infinite",
623
1003
  margin: "0 auto 12px"
624
1004
  } }),
625
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }),
626
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "Loading mesh data..." })
1005
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "Loading mesh data..." })
627
1007
  ] }) });
628
1008
  }
629
1009
  if (error) {
630
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { textAlign: "center", padding: 24 }, children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u26A0\uFE0F" }),
632
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#ff6b6b", fontSize: 14, marginBottom: 8 }, children: "Failed to load mesh" }),
633
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.5)", fontSize: 12 }, children: error.message })
1010
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center", padding: 24 }, children: [
1011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u26A0\uFE0F" }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#ff6b6b", fontSize: 14, marginBottom: 8 }, children: "Failed to load mesh" }),
1013
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.5)", fontSize: 12 }, children: error.message })
634
1014
  ] }) });
635
1015
  }
636
1016
  if (links.length === 0) {
637
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { textAlign: "center", padding: 24 }, children: [
638
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u{1F517}" }),
639
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "No connections found" })
1017
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center", padding: 24 }, children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u{1F517}" }),
1019
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "No connections found" })
640
1020
  ] }) });
641
1021
  }
642
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1022
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
643
1023
  MeshRenderer,
644
1024
  {
645
1025
  links,
@@ -656,6 +1036,7 @@ function IdentityMesh({
656
1036
  // Annotate the CommonJS export names for ESM import in node:
657
1037
  0 && (module.exports = {
658
1038
  IdentityMesh,
1039
+ IdentityMeshRenderer,
659
1040
  defaultNodeTypes,
660
1041
  getBrowserIcon,
661
1042
  getCountryFlag,