@calimero-network/mero-react 4.0.0 → 4.1.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/README.md +3 -1
- package/dist/index.cjs +311 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +307 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,8 @@ Props (`MeroProviderConfig & { children }`):
|
|
|
91
91
|
| `packageVersion` | `string` | No | Specific version (defaults to latest) |
|
|
92
92
|
| `registryUrl` | `string` | No | Registry URL override |
|
|
93
93
|
| `timeoutMs` | `number` | No | HTTP request timeout (default 30000) |
|
|
94
|
+
| `allowedNodeUrls` | `string[]` | No | Origins the OAuth callback may authenticate against. The initiated node is always trusted; this allowlist additionally permits direct-callback entry. A callback `node_url` matching neither is rejected. |
|
|
95
|
+
| `tokenStore` | `TokenStore` | No | Pluggable access/refresh token store. Defaults to localStorage. Pass a `MemoryTokenStore` or cookie-backed store for sensitive deployments (localStorage tokens are XSS-readable). |
|
|
94
96
|
|
|
95
97
|
Modes and their permissions:
|
|
96
98
|
|
|
@@ -100,7 +102,7 @@ Modes and their permissions:
|
|
|
100
102
|
| `MultiContext` | `context:create`, `context:list`, `context:execute` | Apps managing multiple contexts |
|
|
101
103
|
| `Admin` | `admin` | Admin dashboards, dev tools |
|
|
102
104
|
|
|
103
|
-
Auth flow: when `connectToNode(url)` is called, the provider redirects to the node's auth page. After login, the node redirects back with tokens in the URL hash. The provider processes these once (StrictMode-safe via ref) and sets `isAuthenticated = true`.
|
|
105
|
+
Auth flow: when `connectToNode(url)` is called, the provider redirects to the node's auth page. After login, the node redirects back with tokens in the URL hash. The provider processes these once (StrictMode-safe via ref) and sets `isAuthenticated = true`. The callback's `node_url` is validated against the node login was initiated with (or `allowedNodeUrls`); a `node_url` matching neither is rejected.
|
|
104
106
|
|
|
105
107
|
Online detection: the provider opens an SSE connection to the node after auth. `isOnline` reflects the SSE connection state — no polling.
|
|
106
108
|
|
package/dist/index.cjs
CHANGED
|
@@ -473,6 +473,59 @@ function CalimeroLogo({
|
|
|
473
473
|
);
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
// src/utils/nodeDiscovery.ts
|
|
477
|
+
var DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529];
|
|
478
|
+
var LOCAL_HOST = "localhost";
|
|
479
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 2e3;
|
|
480
|
+
function localNodeUrl(port) {
|
|
481
|
+
return `http://${LOCAL_HOST}:${port}`;
|
|
482
|
+
}
|
|
483
|
+
function nodeEndpoint(baseUrl, path) {
|
|
484
|
+
const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
485
|
+
return new URL(path, base).toString();
|
|
486
|
+
}
|
|
487
|
+
async function probeNodeHealth(baseUrl, options = {}) {
|
|
488
|
+
const { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = options;
|
|
489
|
+
if (signal?.aborted) return false;
|
|
490
|
+
const controller = new AbortController();
|
|
491
|
+
const onAbort = () => controller.abort();
|
|
492
|
+
const listenerAdded = !!signal;
|
|
493
|
+
if (listenerAdded) signal.addEventListener("abort", onAbort, { once: true });
|
|
494
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
495
|
+
try {
|
|
496
|
+
const url = nodeEndpoint(baseUrl, "admin-api/health");
|
|
497
|
+
const res = await fetch(url, {
|
|
498
|
+
method: "GET",
|
|
499
|
+
signal: controller.signal
|
|
500
|
+
});
|
|
501
|
+
if (!res.ok) return false;
|
|
502
|
+
try {
|
|
503
|
+
const body = await res.json();
|
|
504
|
+
const status = body?.data?.status !== void 0 ? body?.data?.status : body?.status;
|
|
505
|
+
if (status === void 0) return true;
|
|
506
|
+
return typeof status === "string" && status.toLowerCase() === "alive";
|
|
507
|
+
} catch {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
return false;
|
|
512
|
+
} finally {
|
|
513
|
+
clearTimeout(timer);
|
|
514
|
+
if (listenerAdded) signal.removeEventListener("abort", onAbort);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function discoverLocalNodes(options = {}) {
|
|
518
|
+
const { ports = DEFAULT_LOCAL_NODE_PORTS, timeoutMs, signal } = options;
|
|
519
|
+
const results = await Promise.all(
|
|
520
|
+
ports.map(async (port) => {
|
|
521
|
+
const url = localNodeUrl(port);
|
|
522
|
+
const ok = await probeNodeHealth(url, { timeoutMs, signal });
|
|
523
|
+
return ok ? url : null;
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
return results.filter((url) => url !== null);
|
|
527
|
+
}
|
|
528
|
+
|
|
476
529
|
// src/theme.ts
|
|
477
530
|
var defaultMeroTheme = Object.freeze({
|
|
478
531
|
primary: "#a5ff11",
|
|
@@ -521,6 +574,8 @@ function themeToCssVars(theme) {
|
|
|
521
574
|
function cssVar(theme, key) {
|
|
522
575
|
return `var(${MERO_CSS_VARS[key]}, ${theme[key]})`;
|
|
523
576
|
}
|
|
577
|
+
var DEFAULT_LOCAL_NODE_URL = "http://node1.127.0.0.1.nip.io";
|
|
578
|
+
var CUSTOM_SELECTION = "__custom__";
|
|
524
579
|
function isValidUrl(urlString) {
|
|
525
580
|
if (!urlString || urlString.trim() === "") {
|
|
526
581
|
return false;
|
|
@@ -549,6 +604,9 @@ function isValidUrl(urlString) {
|
|
|
549
604
|
return false;
|
|
550
605
|
}
|
|
551
606
|
}
|
|
607
|
+
function displayNodeUrl(url) {
|
|
608
|
+
return url.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
609
|
+
}
|
|
552
610
|
function tint(color, percent) {
|
|
553
611
|
return `color-mix(in srgb, ${color} ${percent}%, transparent)`;
|
|
554
612
|
}
|
|
@@ -653,10 +711,60 @@ function buildStyles(t) {
|
|
|
653
711
|
transition: "all 0.15s ease"
|
|
654
712
|
},
|
|
655
713
|
radioLabelActive: {
|
|
656
|
-
|
|
714
|
+
border: `1px solid ${accent}`,
|
|
715
|
+
backgroundColor: accentGlow,
|
|
716
|
+
color: text
|
|
717
|
+
},
|
|
718
|
+
radioList: {
|
|
719
|
+
display: "flex",
|
|
720
|
+
flexDirection: "column",
|
|
721
|
+
gap: "0.5rem",
|
|
722
|
+
marginBottom: "1rem"
|
|
723
|
+
},
|
|
724
|
+
radioItem: {
|
|
725
|
+
display: "flex",
|
|
726
|
+
alignItems: "center",
|
|
727
|
+
gap: "0.625rem",
|
|
728
|
+
color: text,
|
|
729
|
+
cursor: "pointer",
|
|
730
|
+
padding: "0.75rem 1rem",
|
|
731
|
+
borderRadius: radius,
|
|
732
|
+
border: `1px solid ${border}`,
|
|
733
|
+
backgroundColor: bgSecondary,
|
|
734
|
+
transition: "all 0.15s ease",
|
|
735
|
+
fontSize: "0.875rem"
|
|
736
|
+
},
|
|
737
|
+
radioItemActive: {
|
|
738
|
+
// Override the full `border` shorthand (not just borderColor) so React
|
|
739
|
+
// never has to mix shorthand + longhand on the same element.
|
|
740
|
+
border: `1px solid ${accent}`,
|
|
657
741
|
backgroundColor: accentGlow,
|
|
658
742
|
color: text
|
|
659
743
|
},
|
|
744
|
+
radioIndicator: {
|
|
745
|
+
flexShrink: 0,
|
|
746
|
+
width: "1rem",
|
|
747
|
+
height: "1rem",
|
|
748
|
+
borderRadius: "50%",
|
|
749
|
+
border: `2px solid ${border}`,
|
|
750
|
+
display: "flex",
|
|
751
|
+
alignItems: "center",
|
|
752
|
+
justifyContent: "center"
|
|
753
|
+
},
|
|
754
|
+
radioIndicatorActive: {
|
|
755
|
+
border: `2px solid ${accent}`
|
|
756
|
+
},
|
|
757
|
+
radioDot: {
|
|
758
|
+
width: "0.5rem",
|
|
759
|
+
height: "0.5rem",
|
|
760
|
+
borderRadius: "50%",
|
|
761
|
+
backgroundColor: accent
|
|
762
|
+
},
|
|
763
|
+
nodeMeta: {
|
|
764
|
+
marginLeft: "auto",
|
|
765
|
+
fontSize: "0.75rem",
|
|
766
|
+
color: textSecondary
|
|
767
|
+
},
|
|
660
768
|
input: {
|
|
661
769
|
width: "100%",
|
|
662
770
|
padding: "0.75rem 1rem",
|
|
@@ -682,6 +790,29 @@ function buildStyles(t) {
|
|
|
682
790
|
localInfoCode: {
|
|
683
791
|
color: accent
|
|
684
792
|
},
|
|
793
|
+
noNodeInfo: {
|
|
794
|
+
color: textSecondary,
|
|
795
|
+
fontSize: "0.875rem",
|
|
796
|
+
textAlign: "center",
|
|
797
|
+
padding: "0.75rem",
|
|
798
|
+
backgroundColor: bgSecondary,
|
|
799
|
+
borderRadius: radius,
|
|
800
|
+
marginBottom: "1rem",
|
|
801
|
+
border: `1px solid ${border}`
|
|
802
|
+
},
|
|
803
|
+
toolbar: {
|
|
804
|
+
display: "flex",
|
|
805
|
+
justifyContent: "center",
|
|
806
|
+
marginBottom: "1rem"
|
|
807
|
+
},
|
|
808
|
+
rescan: {
|
|
809
|
+
background: "none",
|
|
810
|
+
border: "none",
|
|
811
|
+
color: accent,
|
|
812
|
+
cursor: "pointer",
|
|
813
|
+
fontSize: "0.8125rem",
|
|
814
|
+
padding: "0.25rem 0.5rem"
|
|
815
|
+
},
|
|
685
816
|
buttonGroup: {
|
|
686
817
|
display: "flex",
|
|
687
818
|
justifyContent: "center"
|
|
@@ -709,6 +840,15 @@ function buildStyles(t) {
|
|
|
709
840
|
padding: "2rem",
|
|
710
841
|
color: textSecondary
|
|
711
842
|
},
|
|
843
|
+
discovering: {
|
|
844
|
+
display: "flex",
|
|
845
|
+
flexDirection: "column",
|
|
846
|
+
alignItems: "center",
|
|
847
|
+
gap: "0.75rem",
|
|
848
|
+
padding: "1rem",
|
|
849
|
+
color: textSecondary,
|
|
850
|
+
fontSize: "0.875rem"
|
|
851
|
+
},
|
|
712
852
|
spinner: {
|
|
713
853
|
width: "2rem",
|
|
714
854
|
height: "2rem",
|
|
@@ -716,6 +856,14 @@ function buildStyles(t) {
|
|
|
716
856
|
borderTopColor: accent,
|
|
717
857
|
borderRadius: "50%",
|
|
718
858
|
animation: "meroSpin 1s linear infinite"
|
|
859
|
+
},
|
|
860
|
+
spinnerSmall: {
|
|
861
|
+
width: "1.5rem",
|
|
862
|
+
height: "1.5rem",
|
|
863
|
+
border: `3px solid ${border}`,
|
|
864
|
+
borderTopColor: accent,
|
|
865
|
+
borderRadius: "50%",
|
|
866
|
+
animation: "meroSpin 1s linear infinite"
|
|
719
867
|
}
|
|
720
868
|
};
|
|
721
869
|
}
|
|
@@ -724,11 +872,14 @@ function LoginModal({
|
|
|
724
872
|
onClose,
|
|
725
873
|
connectionType,
|
|
726
874
|
isOpen,
|
|
727
|
-
theme
|
|
875
|
+
theme,
|
|
876
|
+
localNodePorts = DEFAULT_LOCAL_NODE_PORTS
|
|
728
877
|
}) {
|
|
729
878
|
const [nodeType, setNodeType] = react.useState("local");
|
|
730
|
-
const [
|
|
731
|
-
const [
|
|
879
|
+
const [selected, setSelected] = react.useState(CUSTOM_SELECTION);
|
|
880
|
+
const [discovered, setDiscovered] = react.useState([]);
|
|
881
|
+
const [discovering, setDiscovering] = react.useState(false);
|
|
882
|
+
const [customUrl, setCustomUrl] = react.useState("");
|
|
732
883
|
const [loading, setLoading] = react.useState(false);
|
|
733
884
|
const [error, setError] = react.useState(null);
|
|
734
885
|
const resolved = react.useMemo(() => resolveMeroTheme(theme), [theme]);
|
|
@@ -743,7 +894,7 @@ function LoginModal({
|
|
|
743
894
|
react.useEffect(() => {
|
|
744
895
|
const savedUrl = localStorage.getItem("mero:node_url");
|
|
745
896
|
if (savedUrl) {
|
|
746
|
-
|
|
897
|
+
setCustomUrl(savedUrl);
|
|
747
898
|
}
|
|
748
899
|
}, []);
|
|
749
900
|
react.useEffect(() => {
|
|
@@ -753,25 +904,56 @@ function LoginModal({
|
|
|
753
904
|
setNodeType("remote");
|
|
754
905
|
}
|
|
755
906
|
}, [connectionType]);
|
|
907
|
+
const [scanNonce, setScanNonce] = react.useState(0);
|
|
908
|
+
const portsKey = react.useMemo(() => localNodePorts.join(","), [localNodePorts]);
|
|
909
|
+
const remoteActive = isOpen && shouldShowRemote && nodeType === "remote";
|
|
756
910
|
react.useEffect(() => {
|
|
757
|
-
if (
|
|
758
|
-
|
|
759
|
-
} else {
|
|
760
|
-
setIsValid(true);
|
|
911
|
+
if (!remoteActive) {
|
|
912
|
+
return;
|
|
761
913
|
}
|
|
762
|
-
|
|
914
|
+
const controller = new AbortController();
|
|
915
|
+
let active = true;
|
|
916
|
+
setDiscovering(true);
|
|
917
|
+
setDiscovered([]);
|
|
918
|
+
setError(null);
|
|
919
|
+
discoverLocalNodes({ ports: localNodePorts, signal: controller.signal }).then((nodes) => {
|
|
920
|
+
if (!active) return;
|
|
921
|
+
setDiscovered(nodes);
|
|
922
|
+
setSelected(nodes.length > 0 ? nodes[0] : CUSTOM_SELECTION);
|
|
923
|
+
}).catch(() => {
|
|
924
|
+
if (active) setSelected(CUSTOM_SELECTION);
|
|
925
|
+
}).finally(() => {
|
|
926
|
+
if (active) setDiscovering(false);
|
|
927
|
+
});
|
|
928
|
+
return () => {
|
|
929
|
+
active = false;
|
|
930
|
+
controller.abort();
|
|
931
|
+
};
|
|
932
|
+
}, [remoteActive, portsKey, scanNonce]);
|
|
933
|
+
const isCustom = selected === CUSTOM_SELECTION;
|
|
934
|
+
const hasDiscovered = discovered.length > 0;
|
|
935
|
+
const canConnect = !loading && (nodeType === "local" ? true : discovering ? false : isCustom ? isValidUrl(customUrl) : true);
|
|
936
|
+
const canConnectRef = react.useRef(canConnect);
|
|
937
|
+
canConnectRef.current = canConnect;
|
|
763
938
|
const handleConnect = react.useCallback(async () => {
|
|
764
|
-
|
|
939
|
+
const targetUrl = nodeType === "local" ? DEFAULT_LOCAL_NODE_URL : selected === CUSTOM_SELECTION ? customUrl : selected;
|
|
940
|
+
const usingDiscovered = nodeType === "remote" && selected !== CUSTOM_SELECTION;
|
|
941
|
+
if (nodeType === "remote" && !usingDiscovered && !isValidUrl(targetUrl)) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const normalizedUrl = targetUrl.replace(/\/+$/, "");
|
|
945
|
+
if (usingDiscovered) {
|
|
946
|
+
onConnect(normalizedUrl);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
765
949
|
setLoading(true);
|
|
766
950
|
setError(null);
|
|
767
|
-
const baseUrl = nodeType === "local" ? "http://node1.127.0.0.1.nip.io" : nodeUrl;
|
|
768
951
|
try {
|
|
769
952
|
const response = await fetch(
|
|
770
|
-
|
|
953
|
+
nodeEndpoint(normalizedUrl, "admin-api/is-authed")
|
|
771
954
|
);
|
|
772
955
|
if (response.ok || response.status === 401) {
|
|
773
956
|
setLoading(false);
|
|
774
|
-
const normalizedUrl = baseUrl.replace(/\/+$/, "");
|
|
775
957
|
onConnect(normalizedUrl);
|
|
776
958
|
} else {
|
|
777
959
|
throw new Error(`Connection failed: ${response.statusText}`);
|
|
@@ -781,10 +963,108 @@ function LoginModal({
|
|
|
781
963
|
setError("Failed to connect. Please check the URL and try again.");
|
|
782
964
|
setLoading(false);
|
|
783
965
|
}
|
|
784
|
-
}, [
|
|
966
|
+
}, [nodeType, selected, customUrl, onConnect]);
|
|
785
967
|
if (!isOpen) {
|
|
786
968
|
return null;
|
|
787
969
|
}
|
|
970
|
+
const showManualInput = isCustom;
|
|
971
|
+
const renderRadio = (value, label, meta) => {
|
|
972
|
+
const active = selected === value;
|
|
973
|
+
return (
|
|
974
|
+
// Selection is driven solely by the radio input's `onChange` — clicking
|
|
975
|
+
// anywhere on the wrapping label forwards to the input, and keyboard
|
|
976
|
+
// users can tab to / arrow through the (visually hidden but focusable)
|
|
977
|
+
// input. A label `onClick` here would double-fire `setSelected`.
|
|
978
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
979
|
+
"label",
|
|
980
|
+
{
|
|
981
|
+
"data-testid": `node-option-${value === CUSTOM_SELECTION ? "custom" : displayNodeUrl(value)}`,
|
|
982
|
+
style: {
|
|
983
|
+
...styles.radioItem,
|
|
984
|
+
...active ? styles.radioItemActive : {}
|
|
985
|
+
},
|
|
986
|
+
children: [
|
|
987
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
988
|
+
"input",
|
|
989
|
+
{
|
|
990
|
+
type: "radio",
|
|
991
|
+
name: "mero-node",
|
|
992
|
+
value,
|
|
993
|
+
checked: active,
|
|
994
|
+
onChange: () => setSelected(value),
|
|
995
|
+
style: { position: "absolute", opacity: 0 }
|
|
996
|
+
}
|
|
997
|
+
),
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
999
|
+
"span",
|
|
1000
|
+
{
|
|
1001
|
+
style: {
|
|
1002
|
+
...styles.radioIndicator,
|
|
1003
|
+
...active ? styles.radioIndicatorActive : {}
|
|
1004
|
+
},
|
|
1005
|
+
children: active && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.radioDot })
|
|
1006
|
+
}
|
|
1007
|
+
),
|
|
1008
|
+
label,
|
|
1009
|
+
meta && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.nodeMeta, children: meta })
|
|
1010
|
+
]
|
|
1011
|
+
},
|
|
1012
|
+
value
|
|
1013
|
+
)
|
|
1014
|
+
);
|
|
1015
|
+
};
|
|
1016
|
+
const renderRemoteView = () => {
|
|
1017
|
+
if (discovering) {
|
|
1018
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.discovering, "data-testid": "node-discovering", children: [
|
|
1019
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.spinnerSmall }),
|
|
1020
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "Searching for local nodes..." })
|
|
1021
|
+
] });
|
|
1022
|
+
}
|
|
1023
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.info, children: hasDiscovered ? "Select a discovered node, or enter a node URL manually." : "No local node found. Enter a node URL to continue." }),
|
|
1025
|
+
hasDiscovered && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1026
|
+
"div",
|
|
1027
|
+
{
|
|
1028
|
+
style: styles.radioList,
|
|
1029
|
+
role: "radiogroup",
|
|
1030
|
+
"aria-label": "Available nodes",
|
|
1031
|
+
children: [
|
|
1032
|
+
discovered.map(
|
|
1033
|
+
(url) => renderRadio(url, displayNodeUrl(url), "local")
|
|
1034
|
+
),
|
|
1035
|
+
renderRadio(CUSTOM_SELECTION, "Enter node URL manually")
|
|
1036
|
+
]
|
|
1037
|
+
}
|
|
1038
|
+
),
|
|
1039
|
+
showManualInput && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1040
|
+
"input",
|
|
1041
|
+
{
|
|
1042
|
+
type: "text",
|
|
1043
|
+
value: customUrl,
|
|
1044
|
+
onChange: (e) => setCustomUrl(e.target.value),
|
|
1045
|
+
placeholder: "https://your-node-url.calimero.network",
|
|
1046
|
+
style: styles.input,
|
|
1047
|
+
"data-testid": "node-url-input",
|
|
1048
|
+
autoFocus: !hasDiscovered,
|
|
1049
|
+
onKeyDown: (e) => {
|
|
1050
|
+
if (e.key === "Enter" && canConnectRef.current) {
|
|
1051
|
+
handleConnect();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
),
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1057
|
+
"button",
|
|
1058
|
+
{
|
|
1059
|
+
type: "button",
|
|
1060
|
+
style: styles.rescan,
|
|
1061
|
+
onClick: () => setScanNonce((n) => n + 1),
|
|
1062
|
+
"data-testid": "rescan-button",
|
|
1063
|
+
children: "\u21BB Rescan local nodes"
|
|
1064
|
+
}
|
|
1065
|
+
) })
|
|
1066
|
+
] });
|
|
1067
|
+
};
|
|
788
1068
|
const modalContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
789
1069
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
790
1070
|
@keyframes meroSpin { to { transform: rotate(360deg); } }
|
|
@@ -792,7 +1072,7 @@ function LoginModal({
|
|
|
792
1072
|
@keyframes meroSlideIn { from { transform: translateY(-12px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
|
793
1073
|
` }),
|
|
794
1074
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...themeVars, ...styles.overlay }, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.content, onClick: (e) => e.stopPropagation(), children: [
|
|
795
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { style: styles.closeButton, onClick: onClose, children: "\xD7" }),
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { style: styles.closeButton, onClick: onClose, "aria-label": "Close", children: "\xD7" }),
|
|
796
1076
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.header, children: [
|
|
797
1077
|
/* @__PURE__ */ jsxRuntime.jsx(CalimeroLogo, { size: 44, color: cssVar(resolved, "primary") }),
|
|
798
1078
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { style: styles.title, children: "Connect to Calimero" })
|
|
@@ -801,12 +1081,13 @@ function LoginModal({
|
|
|
801
1081
|
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "Connecting to node..." }),
|
|
802
1082
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.spinner })
|
|
803
1083
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
804
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.info, children:
|
|
1084
|
+
shouldShowRadioGroup && /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.info, children: "Select your Calimero node type to continue." }),
|
|
805
1085
|
error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.error, children: error }),
|
|
806
1086
|
shouldShowRadioGroup && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.radioGroup, children: [
|
|
807
1087
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
808
1088
|
"label",
|
|
809
1089
|
{
|
|
1090
|
+
"data-testid": "node-type-local",
|
|
810
1091
|
style: {
|
|
811
1092
|
...styles.radioLabel,
|
|
812
1093
|
...nodeType === "local" ? styles.radioLabelActive : {}
|
|
@@ -830,6 +1111,7 @@ function LoginModal({
|
|
|
830
1111
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
831
1112
|
"label",
|
|
832
1113
|
{
|
|
1114
|
+
"data-testid": "node-type-remote",
|
|
833
1115
|
style: {
|
|
834
1116
|
...styles.radioLabel,
|
|
835
1117
|
...nodeType === "remote" ? styles.radioLabelActive : {}
|
|
@@ -851,34 +1133,22 @@ function LoginModal({
|
|
|
851
1133
|
}
|
|
852
1134
|
)
|
|
853
1135
|
] }),
|
|
854
|
-
/* @__PURE__ */ jsxRuntime.
|
|
855
|
-
"input",
|
|
856
|
-
{
|
|
857
|
-
type: "text",
|
|
858
|
-
value: nodeUrl,
|
|
859
|
-
onChange: (e) => setNodeUrl2(e.target.value),
|
|
860
|
-
placeholder: "https://your-node-url.calimero.network",
|
|
861
|
-
style: styles.input,
|
|
862
|
-
onKeyDown: (e) => {
|
|
863
|
-
if (e.key === "Enter" && isValid) {
|
|
864
|
-
handleConnect();
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
) : shouldShowLocal ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: styles.localInfo, children: [
|
|
1136
|
+
nodeType === "local" && shouldShowLocal && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: styles.localInfo, children: [
|
|
869
1137
|
"Using default local node: ",
|
|
870
1138
|
/* @__PURE__ */ jsxRuntime.jsx("br", {}),
|
|
871
|
-
/* @__PURE__ */ jsxRuntime.jsx("code", { style: styles.localInfoCode, children:
|
|
872
|
-
] })
|
|
1139
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { style: styles.localInfoCode, children: DEFAULT_LOCAL_NODE_URL })
|
|
1140
|
+
] }),
|
|
1141
|
+
nodeType === "remote" && shouldShowRemote && renderRemoteView(),
|
|
873
1142
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.buttonGroup, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
874
1143
|
"button",
|
|
875
1144
|
{
|
|
876
1145
|
onClick: handleConnect,
|
|
877
|
-
disabled: !
|
|
1146
|
+
disabled: !canConnect,
|
|
878
1147
|
style: {
|
|
879
1148
|
...styles.button,
|
|
880
|
-
...!
|
|
1149
|
+
...!canConnect ? styles.buttonDisabled : {}
|
|
881
1150
|
},
|
|
1151
|
+
"data-testid": "connect-button",
|
|
882
1152
|
children: "Connect"
|
|
883
1153
|
}
|
|
884
1154
|
) })
|
|
@@ -2670,6 +2940,7 @@ exports.AppMode = AppMode;
|
|
|
2670
2940
|
exports.CalimeroLogo = CalimeroLogo;
|
|
2671
2941
|
exports.ConnectButton = ConnectButton;
|
|
2672
2942
|
exports.ConnectionType = ConnectionType;
|
|
2943
|
+
exports.DEFAULT_LOCAL_NODE_PORTS = DEFAULT_LOCAL_NODE_PORTS;
|
|
2673
2944
|
exports.LoginModal = LoginModal;
|
|
2674
2945
|
exports.MERO_CSS_VARS = MERO_CSS_VARS;
|
|
2675
2946
|
exports.MeroContext = MeroContext;
|
|
@@ -2683,11 +2954,15 @@ exports.clearContextIdentity = clearContextIdentity;
|
|
|
2683
2954
|
exports.clearNodeUrl = clearNodeUrl;
|
|
2684
2955
|
exports.cssVar = cssVar;
|
|
2685
2956
|
exports.defaultMeroTheme = defaultMeroTheme;
|
|
2957
|
+
exports.discoverLocalNodes = discoverLocalNodes;
|
|
2686
2958
|
exports.getApplicationId = getApplicationId;
|
|
2687
2959
|
exports.getContextId = getContextId;
|
|
2688
2960
|
exports.getContextIdentity = getContextIdentity;
|
|
2689
2961
|
exports.getNodeUrl = getNodeUrl;
|
|
2962
|
+
exports.localNodeUrl = localNodeUrl;
|
|
2690
2963
|
exports.localStorageTokenStorage = localStorageTokenStorage;
|
|
2964
|
+
exports.nodeEndpoint = nodeEndpoint;
|
|
2965
|
+
exports.probeNodeHealth = probeNodeHealth;
|
|
2691
2966
|
exports.resolveMeroTheme = resolveMeroTheme;
|
|
2692
2967
|
exports.setApplicationId = setApplicationId;
|
|
2693
2968
|
exports.setContextId = setContextId;
|