@emblemvault/identity-mesh 1.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 ADDED
@@ -0,0 +1,665 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ IdentityMesh: () => IdentityMesh,
24
+ defaultNodeTypes: () => defaultNodeTypes,
25
+ getBrowserIcon: () => getBrowserIcon,
26
+ getCountryFlag: () => getCountryFlag,
27
+ getDeviceIcon: () => getDeviceIcon,
28
+ renderNodeShape: () => renderNodeShape,
29
+ useMeshData: () => useMeshData
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/IdentityMesh.tsx
34
+ var import_react2 = require("react");
35
+
36
+ // src/hooks/useMeshData.ts
37
+ 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
+
98
+ // src/utils.tsx
99
+ var import_jsx_runtime = require("react/jsx-runtime");
100
+ var defaultNodeTypes = {
101
+ visitor: { color: "#00f0ff", glow: "rgba(0, 240, 255, 0.5)", label: "Visitor", shape: "circle" },
102
+ device: { color: "#8b5cf6", glow: "rgba(139, 92, 246, 0.5)", label: "Device", shape: "circle" },
103
+ vault_account: { color: "#00f0ff", glow: "rgba(0, 240, 255, 0.6)", label: "Vault", shape: "hexagon" },
104
+ wallet: { color: "#a855f7", glow: "rgba(168, 85, 247, 0.6)", label: "Wallet", shape: "diamond" },
105
+ user_account: { color: "#00ff88", glow: "rgba(0, 255, 136, 0.6)", label: "User", shape: "circle" },
106
+ session: { color: "#ffb800", glow: "rgba(255, 184, 0, 0.5)", label: "Session", shape: "triangle" },
107
+ identity_link: { color: "#ff6b6b", glow: "rgba(255, 107, 107, 0.6)", label: "Same Identity", shape: "circle" }
108
+ };
109
+ function getCountryFlag(code) {
110
+ if (!code) return "";
111
+ const codePoints = code.toUpperCase().split("").map((char) => 127397 + char.charCodeAt(0));
112
+ return String.fromCodePoint(...codePoints);
113
+ }
114
+ function getDeviceIcon(platform, touch) {
115
+ const p = (platform == null ? void 0 : platform.toLowerCase()) || "";
116
+ if (p.includes("iphone") || p.includes("android")) return "\u{1F4F1}";
117
+ if (p.includes("ipad")) return "\u{1F4F1}";
118
+ if (touch) return "\u{1F4F1}";
119
+ if (p.includes("mac") || p.includes("win") || p.includes("linux")) return "\u{1F4BB}";
120
+ return "\u{1F5A5}\uFE0F";
121
+ }
122
+ function getBrowserIcon(browser) {
123
+ const b = (browser == null ? void 0 : browser.toLowerCase()) || "";
124
+ if (b.includes("chrome")) return "\u{1F310}";
125
+ if (b.includes("firefox")) return "\u{1F98A}";
126
+ if (b.includes("safari")) return "\u{1F9ED}";
127
+ if (b.includes("edge")) return "\u{1F4D0}";
128
+ return "\u{1F310}";
129
+ }
130
+ function renderNodeShape(nodeTypes, type, x, y, size, isHovered, isSelected) {
131
+ const config = nodeTypes[type] || nodeTypes.session || defaultNodeTypes.session;
132
+ const scale = isHovered ? 1.2 : isSelected ? 1.1 : 1;
133
+ const actualSize = size * scale;
134
+ const glowIntensity = isHovered ? 20 : isSelected ? 15 : 8;
135
+ const filter = `drop-shadow(0 0 ${glowIntensity}px ${config.glow})`;
136
+ const style = { filter, transition: "all 0.3s ease" };
137
+ switch (config.shape) {
138
+ case "hexagon": {
139
+ const hexPoints = Array.from({ length: 6 }, (_, i) => {
140
+ const angle = i / 6 * Math.PI * 2 - Math.PI / 2;
141
+ return `${x + Math.cos(angle) * actualSize},${y + Math.sin(angle) * actualSize}`;
142
+ }).join(" ");
143
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
144
+ "polygon",
145
+ {
146
+ points: hexPoints,
147
+ fill: `${config.color}22`,
148
+ stroke: config.color,
149
+ strokeWidth: 2,
150
+ style
151
+ }
152
+ );
153
+ }
154
+ case "diamond": {
155
+ const diamondPoints = [
156
+ `${x},${y - actualSize}`,
157
+ `${x + actualSize * 0.7},${y}`,
158
+ `${x},${y + actualSize}`,
159
+ `${x - actualSize * 0.7},${y}`
160
+ ].join(" ");
161
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
162
+ "polygon",
163
+ {
164
+ points: diamondPoints,
165
+ fill: `${config.color}22`,
166
+ stroke: config.color,
167
+ strokeWidth: 2,
168
+ style
169
+ }
170
+ );
171
+ }
172
+ case "triangle": {
173
+ const triPoints = [
174
+ `${x},${y - actualSize}`,
175
+ `${x + actualSize * 0.866},${y + actualSize * 0.5}`,
176
+ `${x - actualSize * 0.866},${y + actualSize * 0.5}`
177
+ ].join(" ");
178
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
179
+ "polygon",
180
+ {
181
+ points: triPoints,
182
+ fill: `${config.color}22`,
183
+ stroke: config.color,
184
+ strokeWidth: 2,
185
+ style
186
+ }
187
+ );
188
+ }
189
+ default:
190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
191
+ "circle",
192
+ {
193
+ cx: x,
194
+ cy: y,
195
+ r: actualSize,
196
+ fill: `${config.color}22`,
197
+ stroke: config.color,
198
+ strokeWidth: 2,
199
+ style
200
+ }
201
+ );
202
+ }
203
+ }
204
+
205
+ // src/IdentityMesh.tsx
206
+ 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
216
+ }) {
217
+ const [hoveredNode, setHoveredNode] = (0, import_react2.useState)(null);
218
+ const [animationPhase, setAnimationPhase] = (0, import_react2.useState)(0);
219
+ (0, import_react2.useEffect)(() => {
220
+ const interval = setInterval(() => {
221
+ setAnimationPhase((p) => (p + 1) % 360);
222
+ }, 50);
223
+ return () => clearInterval(interval);
224
+ }, []);
225
+ const visitorGroups = (0, import_react2.useMemo)(() => {
226
+ const groups = {};
227
+ links.forEach((link) => {
228
+ if (!groups[link.visitorId]) groups[link.visitorId] = [];
229
+ groups[link.visitorId].push(link);
230
+ });
231
+ return groups;
232
+ }, [links]);
233
+ const identityLinks = (0, import_react2.useMemo)(() => {
234
+ const externalIdToVisitors = {};
235
+ links.forEach((link) => {
236
+ if (!externalIdToVisitors[link.externalId]) {
237
+ externalIdToVisitors[link.externalId] = /* @__PURE__ */ new Set();
238
+ }
239
+ externalIdToVisitors[link.externalId].add(link.visitorId);
240
+ });
241
+ const pairs = [];
242
+ const seenPairs = /* @__PURE__ */ new Set();
243
+ Object.entries(externalIdToVisitors).forEach(([externalId, visitors2]) => {
244
+ var _a;
245
+ if (visitors2.size > 1) {
246
+ const visitorList = Array.from(visitors2);
247
+ for (let i = 0; i < visitorList.length; i++) {
248
+ for (let j = i + 1; j < visitorList.length; j++) {
249
+ const pairKey = [visitorList[i], visitorList[j]].sort().join("-");
250
+ if (!seenPairs.has(pairKey)) {
251
+ seenPairs.add(pairKey);
252
+ const linkType = ((_a = links.find((l) => l.externalId === externalId)) == null ? void 0 : _a.externalType) || "unknown";
253
+ pairs.push({ v1: visitorList[i], v2: visitorList[j], sharedId: externalId, type: linkType });
254
+ }
255
+ }
256
+ }
257
+ }
258
+ });
259
+ return pairs;
260
+ }, [links]);
261
+ const visitors = Object.keys(visitorGroups);
262
+ const visitorPositions = (0, import_react2.useMemo)(() => {
263
+ const centerX = 400;
264
+ const centerY = 300;
265
+ const radius = Math.min(200, 80 + visitors.length * 25);
266
+ return visitors.reduce((acc, visitor, i) => {
267
+ const angle = i / visitors.length * Math.PI * 2 - Math.PI / 2;
268
+ acc[visitor] = {
269
+ x: centerX + Math.cos(angle) * radius,
270
+ y: centerY + Math.sin(angle) * radius
271
+ };
272
+ return acc;
273
+ }, {});
274
+ }, [visitors]);
275
+ const linkPositions = (0, import_react2.useMemo)(() => {
276
+ const positions = {};
277
+ Object.entries(visitorGroups).forEach(([visitorId, visitorLinks]) => {
278
+ const visitorPos = visitorPositions[visitorId];
279
+ if (!visitorPos) return;
280
+ const linkRadius = 60;
281
+ visitorLinks.forEach((link, i) => {
282
+ const angle = i / visitorLinks.length * Math.PI * 2 - Math.PI / 2;
283
+ positions[link.id] = {
284
+ x: visitorPos.x + Math.cos(angle) * linkRadius,
285
+ y: visitorPos.y + Math.sin(angle) * linkRadius,
286
+ type: link.externalType
287
+ };
288
+ });
289
+ });
290
+ return positions;
291
+ }, [visitorGroups, visitorPositions]);
292
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative", width: "100%", height, overflow: "hidden" }, children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "100%", height: "100%", style: { position: "absolute", top: 0, left: 0, opacity: 0.1 }, children: [
294
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("pattern", { id: "mesh-grid", width: "40", height: "40", patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M 40 0 L 0 0 0 40", fill: "none", stroke: "currentColor", strokeWidth: "0.5" }) }) }),
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "100%", height: "100%", fill: "url(#mesh-grid)" })
296
+ ] }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "100%", height: "100%", viewBox: "0 0 800 600", style: { position: "relative" }, children: [
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("radialGradient", { id: "mesh-centerGlow", children: [
299
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "0%", stopColor: "rgba(0, 240, 255, 0.2)" }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: "100%", stopColor: "rgba(0, 240, 255, 0)" })
301
+ ] }) }),
302
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "400", cy: "300", r: "280", fill: "url(#mesh-centerGlow)" }),
303
+ identityLinks.map((link, idx) => {
304
+ const p1 = visitorPositions[link.v1];
305
+ const p2 = visitorPositions[link.v2];
306
+ if (!p1 || !p2) return null;
307
+ const midX = (p1.x + p2.x) / 2;
308
+ const midY = (p1.y + p2.y) / 2;
309
+ const dx = p2.x - p1.x;
310
+ const dy = p2.y - p1.y;
311
+ const dist = Math.sqrt(dx * dx + dy * dy);
312
+ const curvature = Math.min(50, dist * 0.2);
313
+ const perpX = -dy / dist * curvature;
314
+ const perpY = dx / dist * curvature;
315
+ const ctrlX = midX + perpX;
316
+ const ctrlY = midY + perpY;
317
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
319
+ "path",
320
+ {
321
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
322
+ fill: "none",
323
+ stroke: "#ff6b6b",
324
+ strokeWidth: 8,
325
+ strokeOpacity: 0.2,
326
+ style: { filter: "blur(4px)" }
327
+ }
328
+ ),
329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
330
+ "path",
331
+ {
332
+ d: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}`,
333
+ fill: "none",
334
+ stroke: "#ff6b6b",
335
+ strokeWidth: 3,
336
+ strokeOpacity: 0.8,
337
+ strokeDasharray: "8 4",
338
+ style: { strokeDashoffset: -animationPhase * 0.5 }
339
+ }
340
+ ),
341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { transform: `translate(${ctrlX}, ${ctrlY})`, children: [
342
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: -45, y: -12, width: 90, height: 24, rx: 12, fill: "rgba(255, 107, 107, 0.9)" }),
343
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("text", { x: 0, y: 4, textAnchor: "middle", fill: "white", fontSize: 9, fontWeight: "bold", children: [
344
+ "SAME ",
345
+ link.type === "wallet" ? "WALLET" : "VAULT"
346
+ ] })
347
+ ] }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { r: "4", fill: "#ff6b6b", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("animateMotion", { dur: "3s", repeatCount: "indefinite", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { r: "4", fill: "#ffd93d", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("animateMotion", { dur: "3s", repeatCount: "indefinite", begin: "1.5s", path: `M ${p1.x} ${p1.y} Q ${ctrlX} ${ctrlY} ${p2.x} ${p2.y}` }) })
350
+ ] }, `identity-${idx}`);
351
+ }),
352
+ visitors.map(
353
+ (v1, i) => visitors.slice(i + 1).map((v2) => {
354
+ const p1 = visitorPositions[v1];
355
+ const p2 = visitorPositions[v2];
356
+ if (!p1 || !p2) return null;
357
+ const distance = Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
358
+ const opacity = Math.max(0, 0.08 - distance / 3e3);
359
+ if (identityLinks.some((l) => l.v1 === v1 && l.v2 === v2 || l.v1 === v2 && l.v2 === v1)) return null;
360
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
361
+ "line",
362
+ {
363
+ x1: p1.x,
364
+ y1: p1.y,
365
+ x2: p2.x,
366
+ y2: p2.y,
367
+ stroke: "rgba(0, 240, 255, 0.15)",
368
+ strokeWidth: 1,
369
+ strokeDasharray: "2 4",
370
+ style: { opacity, strokeDashoffset: animationPhase % 6 }
371
+ },
372
+ `mesh-${v1}-${v2}`
373
+ );
374
+ })
375
+ ),
376
+ links.map((link) => {
377
+ const visitorPos = visitorPositions[link.visitorId];
378
+ const linkPos = linkPositions[link.id];
379
+ if (!visitorPos || !linkPos) return null;
380
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
381
+ const isVisitorSelected = selectedVisitor === link.visitorId;
382
+ const isLinkHovered = hoveredNode === link.id;
383
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
384
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
385
+ "line",
386
+ {
387
+ x1: visitorPos.x,
388
+ y1: visitorPos.y,
389
+ x2: linkPos.x,
390
+ y2: linkPos.y,
391
+ stroke: config.color,
392
+ strokeWidth: isVisitorSelected || isLinkHovered ? 2 : 1,
393
+ strokeOpacity: isVisitorSelected ? 0.8 : 0.3,
394
+ strokeDasharray: link.confidence && link.confidence < 0.9 ? "4 2" : "none"
395
+ }
396
+ ),
397
+ isVisitorSelected && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { r: "3", fill: config.color, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("animateMotion", { dur: "2s", repeatCount: "indefinite", path: `M${visitorPos.x},${visitorPos.y} L${linkPos.x},${linkPos.y}` }) })
398
+ ] }, `connection-${link.id}`);
399
+ }),
400
+ visitors.map((visitorId) => {
401
+ var _a, _b, _c, _d;
402
+ const pos = visitorPositions[visitorId];
403
+ if (!pos) return null;
404
+ const isSelected = selectedVisitor === visitorId;
405
+ const isHovered = hoveredNode === `visitor-${visitorId}`;
406
+ const linkCount = ((_a = visitorGroups[visitorId]) == null ? void 0 : _a.length) || 0;
407
+ const details = visitorDetails[visitorId];
408
+ const hasIdentityLink = identityLinks.some((l) => l.v1 === visitorId || l.v2 === visitorId);
409
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
410
+ "g",
411
+ {
412
+ style: { cursor: "pointer" },
413
+ onMouseEnter: () => setHoveredNode(`visitor-${visitorId}`),
414
+ onMouseLeave: () => setHoveredNode(null),
415
+ onClick: () => onSelectVisitor(isSelected ? null : visitorId),
416
+ children: [
417
+ hasIdentityLink && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
418
+ "circle",
419
+ {
420
+ cx: pos.x,
421
+ cy: pos.y,
422
+ r: isSelected ? 42 : isHovered ? 40 : 36,
423
+ fill: "none",
424
+ stroke: "#ff6b6b",
425
+ strokeWidth: 2,
426
+ strokeDasharray: "6 3",
427
+ style: {
428
+ transform: `rotate(${-animationPhase}deg)`,
429
+ transformOrigin: `${pos.x}px ${pos.y}px`,
430
+ filter: "drop-shadow(0 0 8px rgba(255, 107, 107, 0.6))"
431
+ }
432
+ }
433
+ ),
434
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
435
+ "circle",
436
+ {
437
+ cx: pos.x,
438
+ cy: pos.y,
439
+ r: isSelected ? 35 : isHovered ? 32 : 28,
440
+ fill: "none",
441
+ stroke: "rgba(0, 240, 255, 0.3)",
442
+ strokeWidth: 2,
443
+ strokeDasharray: "4 2",
444
+ style: {
445
+ transform: `rotate(${animationPhase}deg)`,
446
+ transformOrigin: `${pos.x}px ${pos.y}px`
447
+ }
448
+ }
449
+ ),
450
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
451
+ "circle",
452
+ {
453
+ cx: pos.x,
454
+ cy: pos.y,
455
+ r: isSelected ? 24 : isHovered ? 22 : 18,
456
+ fill: "rgba(0, 20, 30, 0.95)",
457
+ stroke: "#00f0ff",
458
+ strokeWidth: isSelected ? 3 : 2,
459
+ style: {
460
+ filter: `drop-shadow(0 0 ${isSelected ? 20 : isHovered ? 15 : 10}px rgba(0, 240, 255, 0.6))`,
461
+ transition: "all 0.3s ease"
462
+ }
463
+ }
464
+ ),
465
+ /* @__PURE__ */ (0, import_jsx_runtime2.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 }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime2.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 }),
467
+ ((_d = details == null ? void 0 : details.location) == null ? void 0 : _d.country) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("text", { x: pos.x, y: pos.y + 60, textAnchor: "middle", fill: "rgba(255, 255, 255, 0.5)", fontSize: 9, children: [
468
+ getCountryFlag(details.location.country),
469
+ " ",
470
+ details.location.city || details.location.region
471
+ ] })
472
+ ]
473
+ },
474
+ `visitor-${visitorId}`
475
+ );
476
+ }),
477
+ links.map((link) => {
478
+ const pos = linkPositions[link.id];
479
+ if (!pos) return null;
480
+ const isHovered = hoveredNode === link.id;
481
+ const isVisitorSelected = selectedVisitor === link.visitorId;
482
+ const config = nodeTypes[link.externalType] || nodeTypes.session || defaultNodeTypes.session;
483
+ const isSharedId = identityLinks.some((l) => l.sharedId === link.externalId);
484
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
485
+ "g",
486
+ {
487
+ style: { cursor: "pointer", opacity: selectedVisitor && !isVisitorSelected ? 0.3 : 1 },
488
+ onMouseEnter: () => setHoveredNode(link.id),
489
+ onMouseLeave: () => setHoveredNode(null),
490
+ children: [
491
+ isSharedId && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
492
+ "circle",
493
+ {
494
+ cx: pos.x,
495
+ cy: pos.y,
496
+ r: 22,
497
+ fill: "none",
498
+ stroke: "#ff6b6b",
499
+ strokeWidth: 2,
500
+ strokeOpacity: 0.6,
501
+ strokeDasharray: "3 2"
502
+ }
503
+ ),
504
+ renderNodeShape(nodeTypes, link.externalType, pos.x, pos.y, 14, isHovered, isVisitorSelected),
505
+ link.confidence && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
506
+ "circle",
507
+ {
508
+ cx: pos.x,
509
+ cy: pos.y,
510
+ r: 18,
511
+ fill: "none",
512
+ stroke: config.color,
513
+ strokeWidth: 2,
514
+ strokeOpacity: 0.3,
515
+ strokeDasharray: `${link.confidence * 113} 113`,
516
+ strokeLinecap: "round",
517
+ transform: `rotate(-90, ${pos.x}, ${pos.y})`
518
+ }
519
+ ),
520
+ isHovered && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
521
+ /* @__PURE__ */ (0, import_jsx_runtime2.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 }),
522
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("text", { x: pos.x, y: pos.y - 42, textAnchor: "middle", fill: config.color, fontSize: 10, fontWeight: "bold", children: [
523
+ isSharedId ? "\u{1F517} " : "",
524
+ config.label
525
+ ] }),
526
+ /* @__PURE__ */ (0, import_jsx_runtime2.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 })
527
+ ] })
528
+ ]
529
+ },
530
+ `node-${link.id}`
531
+ );
532
+ })
533
+ ] }),
534
+ showLegend && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
535
+ position: "absolute",
536
+ bottom: 20,
537
+ left: 20,
538
+ display: "flex",
539
+ gap: 16,
540
+ flexWrap: "wrap",
541
+ padding: "12px 16px",
542
+ background: "rgba(0, 10, 20, 0.9)",
543
+ borderRadius: 8,
544
+ border: "1px solid rgba(0, 240, 255, 0.2)"
545
+ }, children: [
546
+ Object.entries(nodeTypes).filter(([key]) => !["visitor", "device", "identity_link"].includes(key)).map(([key, config]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: 16, height: 16, viewBox: "0 0 24 24", children: [
548
+ config.shape === "hexagon" && /* @__PURE__ */ (0, import_jsx_runtime2.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 }),
549
+ config.shape === "diamond" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "12,2 22,12 12,22 2,12", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
550
+ config.shape === "circle" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: 12, cy: 12, r: 9, fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 }),
551
+ config.shape === "triangle" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "12,2 22,20 2,20", fill: config.color, fillOpacity: 0.3, stroke: config.color, strokeWidth: 2 })
552
+ ] }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: config.label })
554
+ ] }, key)),
555
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: 20, height: 3, background: "#ff6b6b", borderRadius: 2 } }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.7)" }, children: "Same Identity" })
558
+ ] })
559
+ ] }),
560
+ showIdentityCount && identityLinks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: {
561
+ position: "absolute",
562
+ top: 20,
563
+ left: 20,
564
+ padding: "10px 14px",
565
+ background: "rgba(255, 107, 107, 0.15)",
566
+ borderRadius: 8,
567
+ border: "1px solid rgba(255, 107, 107, 0.4)"
568
+ }, children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 10, color: "rgba(255, 255, 255, 0.6)", marginBottom: 4 }, children: "Cross-Device Identities" }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 20, fontWeight: "bold", color: "#ff6b6b" }, children: identityLinks.length })
571
+ ] })
572
+ ] });
573
+ }
574
+ function IdentityMesh({
575
+ apiUrl,
576
+ vaultId,
577
+ visitorId,
578
+ onError,
579
+ onNodeSelect,
580
+ theme,
581
+ className,
582
+ style,
583
+ height = 600,
584
+ showLegend = true,
585
+ showIdentityCount = true
586
+ }) {
587
+ const [selectedVisitor, setSelectedVisitor] = (0, import_react2.useState)(null);
588
+ const { links, visitorDetails, loading, error } = useMeshData({
589
+ apiUrl,
590
+ vaultId,
591
+ visitorId,
592
+ onError
593
+ });
594
+ const nodeTypes = (0, import_react2.useMemo)(() => {
595
+ const merged = { ...defaultNodeTypes };
596
+ if (theme == null ? void 0 : theme.nodeColors) {
597
+ Object.entries(theme.nodeColors).forEach(([key, value]) => {
598
+ if (value) merged[key] = value;
599
+ });
600
+ }
601
+ return merged;
602
+ }, [theme == null ? void 0 : theme.nodeColors]);
603
+ const handleSelectVisitor = (id) => {
604
+ setSelectedVisitor(id);
605
+ onNodeSelect == null ? void 0 : onNodeSelect(id, id ? "visitor" : null);
606
+ };
607
+ const containerStyle = {
608
+ background: (theme == null ? void 0 : theme.background) || "#0a0a0f",
609
+ color: (theme == null ? void 0 : theme.gridColor) || "#ffffff",
610
+ borderRadius: 8,
611
+ overflow: "hidden",
612
+ ...style
613
+ };
614
+ 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: {
617
+ width: 40,
618
+ height: 40,
619
+ border: "3px solid rgba(0, 240, 255, 0.3)",
620
+ borderTopColor: "#00f0ff",
621
+ borderRadius: "50%",
622
+ animation: "spin 1s linear infinite",
623
+ margin: "0 auto 12px"
624
+ } }),
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..." })
627
+ ] }) });
628
+ }
629
+ 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 })
634
+ ] }) });
635
+ }
636
+ 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" })
640
+ ] }) });
641
+ }
642
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
643
+ MeshRenderer,
644
+ {
645
+ links,
646
+ visitorDetails,
647
+ selectedVisitor,
648
+ onSelectVisitor: handleSelectVisitor,
649
+ nodeTypes,
650
+ height,
651
+ showLegend,
652
+ showIdentityCount
653
+ }
654
+ ) });
655
+ }
656
+ // Annotate the CommonJS export names for ESM import in node:
657
+ 0 && (module.exports = {
658
+ IdentityMesh,
659
+ defaultNodeTypes,
660
+ getBrowserIcon,
661
+ getCountryFlag,
662
+ getDeviceIcon,
663
+ renderNodeShape,
664
+ useMeshData
665
+ });