@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.mjs CHANGED
@@ -1,67 +1,5 @@
1
- // src/IdentityMesh.tsx
2
- import { useState as useState2, useEffect as useEffect2, useMemo } from "react";
3
-
4
- // src/hooks/useMeshData.ts
5
- import { useState, useEffect, useCallback } from "react";
6
- function useMeshData({
7
- apiUrl,
8
- vaultId,
9
- visitorId,
10
- onError
11
- }) {
12
- const [links, setLinks] = useState([]);
13
- const [visitorDetails, setVisitorDetails] = useState({});
14
- const [loading, setLoading] = useState(true);
15
- const [error, setError] = useState(null);
16
- const fetchData = useCallback(async () => {
17
- if (!apiUrl || !vaultId || !visitorId) {
18
- setLoading(false);
19
- return;
20
- }
21
- setLoading(true);
22
- setError(null);
23
- try {
24
- const response = await fetch(apiUrl, {
25
- method: "POST",
26
- headers: {
27
- "Content-Type": "application/json"
28
- },
29
- body: JSON.stringify({ vaultId, visitorId })
30
- });
31
- const data = await response.json();
32
- if (!response.ok || !data.success) {
33
- const err = new Error(data.error || `HTTP ${response.status}`);
34
- setError(err);
35
- onError == null ? void 0 : onError(err);
36
- setLinks([]);
37
- setVisitorDetails({});
38
- return;
39
- }
40
- if (data.data) {
41
- setLinks(data.data.links);
42
- setVisitorDetails(data.data.visitorDetails);
43
- }
44
- } catch (err) {
45
- const error2 = err instanceof Error ? err : new Error("Unknown error");
46
- setError(error2);
47
- onError == null ? void 0 : onError(error2);
48
- setLinks([]);
49
- setVisitorDetails({});
50
- } finally {
51
- setLoading(false);
52
- }
53
- }, [apiUrl, vaultId, visitorId, onError]);
54
- useEffect(() => {
55
- fetchData();
56
- }, [fetchData]);
57
- return {
58
- links,
59
- visitorDetails,
60
- loading,
61
- error,
62
- refetch: fetchData
63
- };
64
- }
1
+ // src/IdentityMeshRenderer.tsx
2
+ import { useState, useEffect, useMemo } from "react";
65
3
 
66
4
  // src/utils.tsx
67
5
  import { jsx } from "react/jsx-runtime";
@@ -170,21 +108,27 @@ function renderNodeShape(nodeTypes, type, x, y, size, isHovered, isSelected) {
170
108
  }
171
109
  }
172
110
 
173
- // src/IdentityMesh.tsx
111
+ // src/IdentityMeshRenderer.tsx
174
112
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
175
- function MeshRenderer({
176
- links,
177
- visitorDetails,
178
- selectedVisitor,
179
- onSelectVisitor,
180
- nodeTypes,
181
- height,
182
- showLegend,
183
- showIdentityCount
113
+ function IdentityMeshRenderer({
114
+ links = [],
115
+ visitorDetails = {},
116
+ selectedVisitor: externalSelectedVisitor,
117
+ onSelectVisitor: externalOnSelectVisitor,
118
+ nodeTypes: customNodeTypes,
119
+ height = 600,
120
+ showLegend = true,
121
+ showIdentityCount = true
184
122
  }) {
185
- const [hoveredNode, setHoveredNode] = useState2(null);
186
- const [animationPhase, setAnimationPhase] = useState2(0);
187
- useEffect2(() => {
123
+ const [hoveredNode, setHoveredNode] = useState(null);
124
+ const [animationPhase, setAnimationPhase] = useState(0);
125
+ const [internalSelectedVisitor, setInternalSelectedVisitor] = useState(null);
126
+ const selectedVisitor = externalSelectedVisitor != null ? externalSelectedVisitor : internalSelectedVisitor;
127
+ const onSelectVisitor = externalOnSelectVisitor != null ? externalOnSelectVisitor : setInternalSelectedVisitor;
128
+ const nodeTypes = useMemo(() => {
129
+ return { ...defaultNodeTypes, ...customNodeTypes };
130
+ }, [customNodeTypes]);
131
+ useEffect(() => {
188
132
  const interval = setInterval(() => {
189
133
  setAnimationPhase((p) => (p + 1) % 360);
190
134
  }, 50);
@@ -539,6 +483,441 @@ function MeshRenderer({
539
483
  ] })
540
484
  ] });
541
485
  }
486
+
487
+ // src/IdentityMesh.tsx
488
+ import { useState as useState3, useEffect as useEffect3, useMemo as useMemo2 } from "react";
489
+
490
+ // src/hooks/useMeshData.ts
491
+ import { useState as useState2, useEffect as useEffect2, useCallback } from "react";
492
+ function useMeshData({
493
+ apiUrl,
494
+ vaultId,
495
+ visitorId,
496
+ onError
497
+ }) {
498
+ const [links, setLinks] = useState2([]);
499
+ const [visitorDetails, setVisitorDetails] = useState2({});
500
+ const [loading, setLoading] = useState2(true);
501
+ const [error, setError] = useState2(null);
502
+ const fetchData = useCallback(async () => {
503
+ if (!apiUrl || !vaultId || !visitorId) {
504
+ setLoading(false);
505
+ return;
506
+ }
507
+ setLoading(true);
508
+ setError(null);
509
+ try {
510
+ const response = await fetch(apiUrl, {
511
+ method: "POST",
512
+ headers: {
513
+ "Content-Type": "application/json"
514
+ },
515
+ body: JSON.stringify({ vaultId, visitorId })
516
+ });
517
+ const data = await response.json();
518
+ if (!response.ok || !data.success) {
519
+ const err = new Error(data.error || `HTTP ${response.status}`);
520
+ setError(err);
521
+ onError == null ? void 0 : onError(err);
522
+ setLinks([]);
523
+ setVisitorDetails({});
524
+ return;
525
+ }
526
+ if (data.data) {
527
+ setLinks(data.data.links);
528
+ setVisitorDetails(data.data.visitorDetails);
529
+ }
530
+ } catch (err) {
531
+ const error2 = err instanceof Error ? err : new Error("Unknown error");
532
+ setError(error2);
533
+ onError == null ? void 0 : onError(error2);
534
+ setLinks([]);
535
+ setVisitorDetails({});
536
+ } finally {
537
+ setLoading(false);
538
+ }
539
+ }, [apiUrl, vaultId, visitorId, onError]);
540
+ useEffect2(() => {
541
+ fetchData();
542
+ }, [fetchData]);
543
+ return {
544
+ links,
545
+ visitorDetails,
546
+ loading,
547
+ error,
548
+ refetch: fetchData
549
+ };
550
+ }
551
+
552
+ // src/IdentityMesh.tsx
553
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
554
+ function MeshRenderer({
555
+ links,
556
+ visitorDetails,
557
+ selectedVisitor,
558
+ onSelectVisitor,
559
+ nodeTypes,
560
+ height,
561
+ showLegend,
562
+ showIdentityCount
563
+ }) {
564
+ const [hoveredNode, setHoveredNode] = useState3(null);
565
+ const [animationPhase, setAnimationPhase] = useState3(0);
566
+ useEffect3(() => {
567
+ const interval = setInterval(() => {
568
+ setAnimationPhase((p) => (p + 1) % 360);
569
+ }, 50);
570
+ return () => clearInterval(interval);
571
+ }, []);
572
+ const visitorGroups = useMemo2(() => {
573
+ const groups = {};
574
+ links.forEach((link) => {
575
+ if (!groups[link.visitorId]) groups[link.visitorId] = [];
576
+ groups[link.visitorId].push(link);
577
+ });
578
+ return groups;
579
+ }, [links]);
580
+ const identityLinks = useMemo2(() => {
581
+ const externalIdToVisitors = {};
582
+ links.forEach((link) => {
583
+ if (!externalIdToVisitors[link.externalId]) {
584
+ externalIdToVisitors[link.externalId] = /* @__PURE__ */ new Set();
585
+ }
586
+ externalIdToVisitors[link.externalId].add(link.visitorId);
587
+ });
588
+ const pairs = [];
589
+ const seenPairs = /* @__PURE__ */ new Set();
590
+ Object.entries(externalIdToVisitors).forEach(([externalId, visitors2]) => {
591
+ var _a;
592
+ if (visitors2.size > 1) {
593
+ const visitorList = Array.from(visitors2);
594
+ for (let i = 0; i < visitorList.length; i++) {
595
+ for (let j = i + 1; j < visitorList.length; j++) {
596
+ const pairKey = [visitorList[i], visitorList[j]].sort().join("-");
597
+ if (!seenPairs.has(pairKey)) {
598
+ seenPairs.add(pairKey);
599
+ const linkType = ((_a = links.find((l) => l.externalId === externalId)) == null ? void 0 : _a.externalType) || "unknown";
600
+ pairs.push({ v1: visitorList[i], v2: visitorList[j], sharedId: externalId, type: linkType });
601
+ }
602
+ }
603
+ }
604
+ }
605
+ });
606
+ return pairs;
607
+ }, [links]);
608
+ const visitors = Object.keys(visitorGroups);
609
+ const visitorPositions = useMemo2(() => {
610
+ const centerX = 400;
611
+ const centerY = 300;
612
+ const radius = Math.min(200, 80 + visitors.length * 25);
613
+ return visitors.reduce((acc, visitor, i) => {
614
+ const angle = i / visitors.length * Math.PI * 2 - Math.PI / 2;
615
+ acc[visitor] = {
616
+ x: centerX + Math.cos(angle) * radius,
617
+ y: centerY + Math.sin(angle) * radius
618
+ };
619
+ return acc;
620
+ }, {});
621
+ }, [visitors]);
622
+ const linkPositions = useMemo2(() => {
623
+ const positions = {};
624
+ Object.entries(visitorGroups).forEach(([visitorId, visitorLinks]) => {
625
+ const visitorPos = visitorPositions[visitorId];
626
+ if (!visitorPos) return;
627
+ const linkRadius = 60;
628
+ visitorLinks.forEach((link, i) => {
629
+ const angle = i / visitorLinks.length * Math.PI * 2 - Math.PI / 2;
630
+ positions[link.id] = {
631
+ x: visitorPos.x + Math.cos(angle) * linkRadius,
632
+ y: visitorPos.y + Math.sin(angle) * linkRadius,
633
+ type: link.externalType
634
+ };
635
+ });
636
+ });
637
+ return positions;
638
+ }, [visitorGroups, visitorPositions]);
639
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative", width: "100%", height, overflow: "hidden" }, children: [
640
+ /* @__PURE__ */ jsxs2("svg", { width: "100%", height: "100%", style: { position: "absolute", top: 0, left: 0, opacity: 0.1 }, children: [
641
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsx3("pattern", { id: "mesh-grid", width: "40", height: "40", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx3("path", { d: "M 40 0 L 0 0 0 40", fill: "none", stroke: "currentColor", strokeWidth: "0.5" }) }) }),
642
+ /* @__PURE__ */ jsx3("rect", { width: "100%", height: "100%", fill: "url(#mesh-grid)" })
643
+ ] }),
644
+ /* @__PURE__ */ jsxs2("svg", { width: "100%", height: "100%", viewBox: "0 0 800 600", style: { position: "relative" }, children: [
645
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs2("radialGradient", { id: "mesh-centerGlow", children: [
646
+ /* @__PURE__ */ jsx3("stop", { offset: "0%", stopColor: "rgba(0, 240, 255, 0.2)" }),
647
+ /* @__PURE__ */ jsx3("stop", { offset: "100%", stopColor: "rgba(0, 240, 255, 0)" })
648
+ ] }) }),
649
+ /* @__PURE__ */ jsx3("circle", { cx: "400", cy: "300", r: "280", fill: "url(#mesh-centerGlow)" }),
650
+ identityLinks.map((link, idx) => {
651
+ const p1 = visitorPositions[link.v1];
652
+ const p2 = visitorPositions[link.v2];
653
+ if (!p1 || !p2) return null;
654
+ const midX = (p1.x + p2.x) / 2;
655
+ const midY = (p1.y + p2.y) / 2;
656
+ const dx = p2.x - p1.x;
657
+ const dy = p2.y - p1.y;
658
+ const dist = Math.sqrt(dx * dx + dy * dy);
659
+ const curvature = Math.min(50, dist * 0.2);
660
+ const perpX = -dy / dist * curvature;
661
+ const perpY = dx / dist * curvature;
662
+ const ctrlX = midX + perpX;
663
+ const ctrlY = midY + perpY;
664
+ return /* @__PURE__ */ jsxs2("g", { children: [
665
+ /* @__PURE__ */ jsx3(
666
+ "path",
667
+ {
668
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
669
+ fill: "none",
670
+ stroke: "#ff6b6b",
671
+ strokeWidth: 8,
672
+ strokeOpacity: 0.2,
673
+ style: { filter: "blur(4px)" }
674
+ }
675
+ ),
676
+ /* @__PURE__ */ jsx3(
677
+ "path",
678
+ {
679
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
680
+ fill: "none",
681
+ stroke: "#ff6b6b",
682
+ strokeWidth: 3,
683
+ strokeOpacity: 0.8,
684
+ strokeDasharray: "8 4",
685
+ style: { strokeDashoffset: -animationPhase * 0.5 }
686
+ }
687
+ ),
688
+ /* @__PURE__ */ jsxs2("g", { transform: `translate(${ctrlX}, ${ctrlY})`, children: [
689
+ /* @__PURE__ */ jsx3("rect", { x: -45, y: -12, width: 90, height: 24, rx: 12, fill: "rgba(255, 107, 107, 0.9)" }),
690
+ /* @__PURE__ */ jsxs2("text", { x: 0, y: 4, textAnchor: "middle", fill: "white", fontSize: 9, fontWeight: "bold", children: [
691
+ "SAME ",
692
+ link.type === "wallet" ? "WALLET" : "VAULT"
693
+ ] })
694
+ ] }),
695
+ /* @__PURE__ */ jsx3("circle", { r: "4", fill: "#ff6b6b", children: /* @__PURE__ */ jsx3("animateMotion", { dur: "3s", repeatCount: "indefinite", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) }),
696
+ /* @__PURE__ */ jsx3("circle", { r: "4", fill: "#ffd93d", children: /* @__PURE__ */ jsx3("animateMotion", { dur: "3s", repeatCount: "indefinite", begin: "1.5s", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) })
697
+ ] }, `identity-${idx}`);
698
+ }),
699
+ visitors.map(
700
+ (v1, i) => visitors.slice(i + 1).map((v2) => {
701
+ const p1 = visitorPositions[v1];
702
+ const p2 = visitorPositions[v2];
703
+ if (!p1 || !p2) return null;
704
+ const distance = Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
705
+ const opacity = Math.max(0, 0.08 - distance / 3e3);
706
+ if (identityLinks.some((l) => l.v1 === v1 && l.v2 === v2 || l.v1 === v2 && l.v2 === v1)) return null;
707
+ return /* @__PURE__ */ jsx3(
708
+ "line",
709
+ {
710
+ x1: p1.x,
711
+ y1: p1.y,
712
+ x2: p2.x,
713
+ y2: p2.y,
714
+ stroke: "rgba(0, 240, 255, 0.15)",
715
+ strokeWidth: 1,
716
+ strokeDasharray: "2 4",
717
+ style: { opacity, strokeDashoffset: animationPhase % 6 }
718
+ },
719
+ `mesh-${v1}-${v2}`
720
+ );
721
+ })
722
+ ),
723
+ links.map((link) => {
724
+ const visitorPos = visitorPositions[link.visitorId];
725
+ const linkPos = linkPositions[link.id];
726
+ if (!visitorPos || !linkPos) return null;
727
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
728
+ const isVisitorSelected = selectedVisitor === link.visitorId;
729
+ const isLinkHovered = hoveredNode === link.id;
730
+ return /* @__PURE__ */ jsxs2("g", { children: [
731
+ /* @__PURE__ */ jsx3(
732
+ "line",
733
+ {
734
+ x1: visitorPos.x,
735
+ y1: visitorPos.y,
736
+ x2: linkPos.x,
737
+ y2: linkPos.y,
738
+ stroke: config.color,
739
+ strokeWidth: isVisitorSelected || isLinkHovered ? 2 : 1,
740
+ strokeOpacity: isVisitorSelected ? 0.8 : 0.3,
741
+ strokeDasharray: link.confidence && link.confidence < 0.9 ? "4 2" : "none"
742
+ }
743
+ ),
744
+ isVisitorSelected && /* @__PURE__ */ jsx3("circle", { r: "3", fill: config.color, children: /* @__PURE__ */ jsx3("animateMotion", { dur: "2s", repeatCount: "indefinite", path: `M${visitorPos.x},${visitorPos.y} L${linkPos.x},${linkPos.y}` }) })
745
+ ] }, `connection-${link.id}`);
746
+ }),
747
+ visitors.map((visitorId) => {
748
+ var _a, _b, _c, _d;
749
+ const pos = visitorPositions[visitorId];
750
+ if (!pos) return null;
751
+ const isSelected = selectedVisitor === visitorId;
752
+ const isHovered = hoveredNode === `visitor-${visitorId}`;
753
+ const linkCount = ((_a = visitorGroups[visitorId]) == null ? void 0 : _a.length) || 0;
754
+ const details = visitorDetails[visitorId];
755
+ const hasIdentityLink = identityLinks.some((l) => l.v1 === visitorId || l.v2 === visitorId);
756
+ return /* @__PURE__ */ jsxs2(
757
+ "g",
758
+ {
759
+ style: { cursor: "pointer" },
760
+ onMouseEnter: () => setHoveredNode(`visitor-${visitorId}`),
761
+ onMouseLeave: () => setHoveredNode(null),
762
+ onClick: () => onSelectVisitor(isSelected ? null : visitorId),
763
+ children: [
764
+ hasIdentityLink && /* @__PURE__ */ jsx3(
765
+ "circle",
766
+ {
767
+ cx: pos.x,
768
+ cy: pos.y,
769
+ r: isSelected ? 42 : isHovered ? 40 : 36,
770
+ fill: "none",
771
+ stroke: "#ff6b6b",
772
+ strokeWidth: 2,
773
+ strokeDasharray: "6 3",
774
+ style: {
775
+ transform: `rotate(${-animationPhase}deg)`,
776
+ transformOrigin: `${pos.x}px ${pos.y}px`,
777
+ filter: "drop-shadow(0 0 8px rgba(255, 107, 107, 0.6))"
778
+ }
779
+ }
780
+ ),
781
+ /* @__PURE__ */ jsx3(
782
+ "circle",
783
+ {
784
+ cx: pos.x,
785
+ cy: pos.y,
786
+ r: isSelected ? 35 : isHovered ? 32 : 28,
787
+ fill: "none",
788
+ stroke: "rgba(0, 240, 255, 0.3)",
789
+ strokeWidth: 2,
790
+ strokeDasharray: "4 2",
791
+ style: {
792
+ transform: `rotate(${animationPhase}deg)`,
793
+ transformOrigin: `${pos.x}px ${pos.y}px`
794
+ }
795
+ }
796
+ ),
797
+ /* @__PURE__ */ jsx3(
798
+ "circle",
799
+ {
800
+ cx: pos.x,
801
+ cy: pos.y,
802
+ r: isSelected ? 24 : isHovered ? 22 : 18,
803
+ fill: "rgba(0, 20, 30, 0.95)",
804
+ stroke: "#00f0ff",
805
+ strokeWidth: isSelected ? 3 : 2,
806
+ style: {
807
+ filter: `drop-shadow(0 0 ${isSelected ? 20 : isHovered ? 15 : 10}px rgba(0, 240, 255, 0.6))`,
808
+ transition: "all 0.3s ease"
809
+ }
810
+ }
811
+ ),
812
+ /* @__PURE__ */ jsx3("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 }),
813
+ /* @__PURE__ */ jsx3("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 }),
814
+ ((_d = details == null ? void 0 : details.location) == null ? void 0 : _d.country) && /* @__PURE__ */ jsxs2("text", { x: pos.x, y: pos.y + 60, textAnchor: "middle", fill: "rgba(255, 255, 255, 0.5)", fontSize: 9, children: [
815
+ getCountryFlag(details.location.country),
816
+ " ",
817
+ details.location.city || details.location.region
818
+ ] })
819
+ ]
820
+ },
821
+ `visitor-${visitorId}`
822
+ );
823
+ }),
824
+ links.map((link) => {
825
+ const pos = linkPositions[link.id];
826
+ if (!pos) return null;
827
+ const isHovered = hoveredNode === link.id;
828
+ const isVisitorSelected = selectedVisitor === link.visitorId;
829
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
830
+ const isSharedId = identityLinks.some((l) => l.sharedId === link.externalId);
831
+ return /* @__PURE__ */ jsxs2(
832
+ "g",
833
+ {
834
+ style: { cursor: "pointer", opacity: selectedVisitor && !isVisitorSelected ? 0.3 : 1 },
835
+ onMouseEnter: () => setHoveredNode(link.id),
836
+ onMouseLeave: () => setHoveredNode(null),
837
+ children: [
838
+ isSharedId && /* @__PURE__ */ jsx3(
839
+ "circle",
840
+ {
841
+ cx: pos.x,
842
+ cy: pos.y,
843
+ r: 22,
844
+ fill: "none",
845
+ stroke: "#ff6b6b",
846
+ strokeWidth: 2,
847
+ strokeOpacity: 0.6,
848
+ strokeDasharray: "3 2"
849
+ }
850
+ ),
851
+ renderNodeShape(nodeTypes, link.externalType, pos.x, pos.y, 14, isHovered, isVisitorSelected),
852
+ link.confidence && /* @__PURE__ */ jsx3(
853
+ "circle",
854
+ {
855
+ cx: pos.x,
856
+ cy: pos.y,
857
+ r: 18,
858
+ fill: "none",
859
+ stroke: config.color,
860
+ strokeWidth: 2,
861
+ strokeOpacity: 0.3,
862
+ strokeDasharray: `${link.confidence * 113} 113`,
863
+ strokeLinecap: "round",
864
+ transform: `rotate(-90, ${pos.x}, ${pos.y})`
865
+ }
866
+ ),
867
+ isHovered && /* @__PURE__ */ jsxs2("g", { children: [
868
+ /* @__PURE__ */ jsx3("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 }),
869
+ /* @__PURE__ */ jsxs2("text", { x: pos.x, y: pos.y - 42, textAnchor: "middle", fill: config.color, fontSize: 10, fontWeight: "bold", children: [
870
+ isSharedId ? "\u{1F517} " : "",
871
+ config.label
872
+ ] }),
873
+ /* @__PURE__ */ jsx3("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 })
874
+ ] })
875
+ ]
876
+ },
877
+ `node-${link.id}`
878
+ );
879
+ })
880
+ ] }),
881
+ showLegend && /* @__PURE__ */ jsxs2("div", { style: {
882
+ position: "absolute",
883
+ bottom: 20,
884
+ left: 20,
885
+ display: "flex",
886
+ gap: 16,
887
+ flexWrap: "wrap",
888
+ padding: "12px 16px",
889
+ background: "rgba(0, 10, 20, 0.9)",
890
+ borderRadius: 8,
891
+ border: "1px solid rgba(0, 240, 255, 0.2)"
892
+ }, children: [
893
+ Object.entries(nodeTypes).filter(([key]) => !["visitor", "device", "identity_link"].includes(key)).map(([key, config]) => /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
894
+ /* @__PURE__ */ jsxs2("svg", { width: 16, height: 16, viewBox: "0 0 24 24", children: [
895
+ config.shape === "hexagon" && /* @__PURE__ */ jsx3("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 }),
896
+ config.shape === "diamond" && /* @__PURE__ */ jsx3("polygon", { points: "12,2 22,12 12,22 2,12", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
897
+ config.shape === "circle" && /* @__PURE__ */ jsx3("circle", { cx: 12, cy: 12, r: 9, fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
898
+ config.shape === "triangle" && /* @__PURE__ */ jsx3("polygon", { points: "12,2 22,20 2,20", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 })
899
+ ] }),
900
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: config.label })
901
+ ] }, key)),
902
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
903
+ /* @__PURE__ */ jsx3("div", { style: { width: 20, height: 3, background: "#ff6b6b", borderRadius: 2 } }),
904
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: "Same Identity" })
905
+ ] })
906
+ ] }),
907
+ showIdentityCount && identityLinks.length > 0 && /* @__PURE__ */ jsxs2("div", { style: {
908
+ position: "absolute",
909
+ top: 20,
910
+ left: 20,
911
+ padding: "10px 14px",
912
+ background: "rgba(255, 107, 107, 0.15)",
913
+ borderRadius: 8,
914
+ border: "1px solid rgba(255, 107, 107, 0.4)"
915
+ }, children: [
916
+ /* @__PURE__ */ jsx3("div", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.6)", marginBottom: 4 }, children: "Cross-Device Identities" }),
917
+ /* @__PURE__ */ jsx3("div", { style: { fontSize: 20, fontWeight: "bold", color: "#ff6b6b" }, children: identityLinks.length })
918
+ ] })
919
+ ] });
920
+ }
542
921
  function IdentityMesh({
543
922
  apiUrl,
544
923
  vaultId,
@@ -552,14 +931,14 @@ function IdentityMesh({
552
931
  showLegend = true,
553
932
  showIdentityCount = true
554
933
  }) {
555
- const [selectedVisitor, setSelectedVisitor] = useState2(null);
934
+ const [selectedVisitor, setSelectedVisitor] = useState3(null);
556
935
  const { links, visitorDetails, loading, error } = useMeshData({
557
936
  apiUrl,
558
937
  vaultId,
559
938
  visitorId,
560
939
  onError
561
940
  });
562
- const nodeTypes = useMemo(() => {
941
+ const nodeTypes = useMemo2(() => {
563
942
  const merged = { ...defaultNodeTypes };
564
943
  if (theme == null ? void 0 : theme.nodeColors) {
565
944
  Object.entries(theme.nodeColors).forEach(([key, value]) => {
@@ -580,8 +959,8 @@ function IdentityMesh({
580
959
  ...style
581
960
  };
582
961
  if (loading) {
583
- return /* @__PURE__ */ jsx2("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
584
- /* @__PURE__ */ jsx2("div", { style: {
962
+ return /* @__PURE__ */ jsx3("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center" }, children: [
963
+ /* @__PURE__ */ jsx3("div", { style: {
585
964
  width: 40,
586
965
  height: 40,
587
966
  border: "3px solid rgba(0, 240, 255, 0.3)",
@@ -590,24 +969,24 @@ function IdentityMesh({
590
969
  animation: "spin 1s linear infinite",
591
970
  margin: "0 auto 12px"
592
971
  } }),
593
- /* @__PURE__ */ jsx2("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }),
594
- /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "Loading mesh data..." })
972
+ /* @__PURE__ */ jsx3("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }),
973
+ /* @__PURE__ */ jsx3("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "Loading mesh data..." })
595
974
  ] }) });
596
975
  }
597
976
  if (error) {
598
- return /* @__PURE__ */ jsx2("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 24 }, children: [
599
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u26A0\uFE0F" }),
600
- /* @__PURE__ */ jsx2("div", { style: { color: "#ff6b6b", fontSize: 14, marginBottom: 8 }, children: "Failed to load mesh" }),
601
- /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255, 255, 255, 0.5)", fontSize: 12 }, children: error.message })
977
+ return /* @__PURE__ */ jsx3("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center", padding: 24 }, children: [
978
+ /* @__PURE__ */ jsx3("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u26A0\uFE0F" }),
979
+ /* @__PURE__ */ jsx3("div", { style: { color: "#ff6b6b", fontSize: 14, marginBottom: 8 }, children: "Failed to load mesh" }),
980
+ /* @__PURE__ */ jsx3("div", { style: { color: "rgba(255, 255, 255, 0.5)", fontSize: 12 }, children: error.message })
602
981
  ] }) });
603
982
  }
604
983
  if (links.length === 0) {
605
- return /* @__PURE__ */ jsx2("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 24 }, children: [
606
- /* @__PURE__ */ jsx2("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u{1F517}" }),
607
- /* @__PURE__ */ jsx2("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "No connections found" })
984
+ return /* @__PURE__ */ jsx3("div", { className, style: { ...containerStyle, height, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center", padding: 24 }, children: [
985
+ /* @__PURE__ */ jsx3("div", { style: { fontSize: 32, marginBottom: 12 }, children: "\u{1F517}" }),
986
+ /* @__PURE__ */ jsx3("div", { style: { color: "rgba(255, 255, 255, 0.6)", fontSize: 14 }, children: "No connections found" })
608
987
  ] }) });
609
988
  }
610
- return /* @__PURE__ */ jsx2("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx2(
989
+ return /* @__PURE__ */ jsx3("div", { className, style: containerStyle, children: /* @__PURE__ */ jsx3(
611
990
  MeshRenderer,
612
991
  {
613
992
  links,
@@ -623,6 +1002,7 @@ function IdentityMesh({
623
1002
  }
624
1003
  export {
625
1004
  IdentityMesh,
1005
+ IdentityMeshRenderer,
626
1006
  defaultNodeTypes,
627
1007
  getBrowserIcon,
628
1008
  getCountryFlag,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emblemvault/identity-mesh",
3
- "version": "1.0.0",
4
- "description": "Embeddable Identity Mesh visualization component for displaying cross-device identity connections",
3
+ "version": "2.0.0",
4
+ "description": "Identity Mesh visualization component - use with @emblemvault/embeddable for embedding",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",