@calimero-network/mero-react 1.0.0-beta.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,25 @@
1
1
  import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
2
- import { MeroJs } from '@calimero-network/mero-js';
3
- export { MeroJs } from '@calimero-network/mero-js';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { createPortal } from 'react-dom';
2
+ import { parseAuthCallback, MeroJs, buildAuthLoginUrl, LocalStorageTokenStore } from '@calimero-network/mero-js';
3
+ export * from '@calimero-network/mero-js';
4
+ import { jsx } from 'react/jsx-runtime';
6
5
 
7
6
  // src/context/MeroContext.tsx
8
7
 
8
+ // src/types.ts
9
+ var AppMode = /* @__PURE__ */ ((AppMode2) => {
10
+ AppMode2["SingleContext"] = "single-context";
11
+ AppMode2["MultiContext"] = "multi-context";
12
+ AppMode2["Admin"] = "admin";
13
+ return AppMode2;
14
+ })(AppMode || {});
15
+ var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
16
+ ConnectionType2["RemoteAndLocal"] = "remote-and-local";
17
+ ConnectionType2["Remote"] = "remote";
18
+ ConnectionType2["Local"] = "local";
19
+ ConnectionType2["Custom"] = "custom";
20
+ return ConnectionType2;
21
+ })(ConnectionType || {});
22
+
9
23
  // src/storage/index.ts
10
24
  var STORAGE_KEYS = {
11
25
  ACCESS_TOKEN: "mero:access_token",
@@ -13,20 +27,25 @@ var STORAGE_KEYS = {
13
27
  EXPIRES_AT: "mero:expires_at",
14
28
  NODE_URL: "mero:node_url",
15
29
  APPLICATION_ID: "mero:application_id",
16
- CONTEXT_ID: "mero:context_id"
30
+ CONTEXT_ID: "mero:context_id",
31
+ CONTEXT_IDENTITY: "mero:context_identity"
17
32
  };
33
+ var _storageAvailable = null;
18
34
  function isLocalStorageAvailable() {
35
+ if (_storageAvailable !== null) return _storageAvailable;
19
36
  try {
20
37
  if (typeof window === "undefined" || !window.localStorage) {
38
+ _storageAvailable = false;
21
39
  return false;
22
40
  }
23
41
  const testKey = "__mero_test__";
24
42
  localStorage.setItem(testKey, "test");
25
43
  localStorage.removeItem(testKey);
26
- return true;
44
+ _storageAvailable = true;
27
45
  } catch {
28
- return false;
46
+ _storageAvailable = false;
29
47
  }
48
+ return _storageAvailable;
30
49
  }
31
50
  var localStorageTokenStorage = {
32
51
  async get() {
@@ -107,46 +126,37 @@ function setContextId(id) {
107
126
  if (!isLocalStorageAvailable()) return;
108
127
  localStorage.setItem(STORAGE_KEYS.CONTEXT_ID, id);
109
128
  }
129
+ function clearContextId() {
130
+ if (!isLocalStorageAvailable()) return;
131
+ localStorage.removeItem(STORAGE_KEYS.CONTEXT_ID);
132
+ }
133
+ function getContextIdentity() {
134
+ if (!isLocalStorageAvailable()) return null;
135
+ return localStorage.getItem(STORAGE_KEYS.CONTEXT_IDENTITY);
136
+ }
137
+ function setContextIdentity(id) {
138
+ if (!isLocalStorageAvailable()) return;
139
+ localStorage.setItem(STORAGE_KEYS.CONTEXT_IDENTITY, id);
140
+ }
141
+ function clearContextIdentity() {
142
+ if (!isLocalStorageAvailable()) return;
143
+ localStorage.removeItem(STORAGE_KEYS.CONTEXT_IDENTITY);
144
+ }
110
145
  function clearAllStorage() {
111
146
  if (!isLocalStorageAvailable()) return;
112
147
  Object.values(STORAGE_KEYS).forEach((key) => {
113
148
  localStorage.removeItem(key);
114
149
  });
115
150
  }
116
-
117
- // src/types.ts
118
- var AppMode = /* @__PURE__ */ ((AppMode2) => {
119
- AppMode2["SingleContext"] = "single-context";
120
- AppMode2["MultiContext"] = "multi-context";
121
- AppMode2["Admin"] = "admin";
122
- return AppMode2;
123
- })(AppMode || {});
124
- var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
125
- ConnectionType2["RemoteAndLocal"] = "remote-and-local";
126
- ConnectionType2["Remote"] = "remote";
127
- ConnectionType2["Local"] = "local";
128
- ConnectionType2["Custom"] = "custom";
129
- return ConnectionType2;
130
- })(ConnectionType || {});
131
- var EventStreamMode = /* @__PURE__ */ ((EventStreamMode2) => {
132
- EventStreamMode2["WebSocket"] = "websocket";
133
- EventStreamMode2["SSE"] = "sse";
134
- return EventStreamMode2;
135
- })(EventStreamMode || {});
136
- var defaultContextValue = {
137
- mero: null,
138
- isAuthenticated: false,
139
- isOnline: true,
140
- nodeUrl: null,
141
- applicationId: null,
142
- contextId: null,
143
- connectToNode: () => {
144
- },
145
- logout: () => {
146
- },
147
- isLoading: true
148
- };
149
- var MeroContext = createContext(defaultContextValue);
151
+ var MeroContext = createContext(null);
152
+ var isBrowser = typeof window !== "undefined";
153
+ var _tokenStore = null;
154
+ function getTokenStore() {
155
+ if (!_tokenStore) {
156
+ _tokenStore = new LocalStorageTokenStore();
157
+ }
158
+ return _tokenStore;
159
+ }
150
160
  function getPermissionsForMode(mode) {
151
161
  switch (mode) {
152
162
  case "single-context" /* SingleContext */:
@@ -159,27 +169,20 @@ function getPermissionsForMode(mode) {
159
169
  throw new Error(`Unsupported application mode: ${mode}`);
160
170
  }
161
171
  }
162
- function redirectToAuthLogin(params) {
163
- const authParams = new URLSearchParams();
164
- authParams.append("callback-url", params.callbackUrl);
165
- authParams.append("permissions", params.permissions.join(","));
166
- authParams.append("mode", params.mode);
167
- if (params.packageName) {
168
- authParams.append("package-name", params.packageName);
169
- if (params.packageVersion) {
170
- authParams.append("package-version", params.packageVersion);
171
- }
172
- if (params.registryUrl) {
173
- authParams.append("registry-url", params.registryUrl);
172
+ function parseJwtExpiry(token) {
173
+ try {
174
+ const parts = token.split(".");
175
+ if (parts.length === 3) {
176
+ let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
177
+ while (b64.length % 4) b64 += "=";
178
+ const payload = JSON.parse(atob(b64));
179
+ if (payload.exp && payload.exp > 0) {
180
+ return payload.exp * 1e3;
181
+ }
174
182
  }
175
- } else if (params.applicationId) {
176
- authParams.append("application-id", params.applicationId);
177
- }
178
- if (params.applicationPath) {
179
- authParams.append("application-path", params.applicationPath);
183
+ } catch {
180
184
  }
181
- authParams.append("app-url", params.nodeUrl);
182
- window.location.href = `${params.nodeUrl}/auth/login?${authParams.toString()}`;
185
+ return Date.now() + 36e5;
183
186
  }
184
187
  function MeroProvider({
185
188
  children,
@@ -187,8 +190,6 @@ function MeroProvider({
187
190
  packageName,
188
191
  packageVersion,
189
192
  registryUrl,
190
- applicationId: propApplicationId,
191
- applicationPath,
192
193
  timeoutMs = 3e4
193
194
  }) {
194
195
  const [mero, setMero] = useState(null);
@@ -197,15 +198,23 @@ function MeroProvider({
197
198
  const [isLoading, setIsLoading] = useState(true);
198
199
  const [nodeUrl, setNodeUrlState] = useState(() => getNodeUrl());
199
200
  const [applicationId, setApplicationIdState] = useState(
200
- () => getApplicationId() || propApplicationId || null
201
+ () => getApplicationId() || null
201
202
  );
202
203
  const [contextId, setContextIdState] = useState(() => getContextId());
204
+ const [contextIdentity, setContextIdentityState] = useState(() => getContextIdentity());
203
205
  const meroRef = useRef(null);
206
+ const callbackRef = useRef(void 0);
207
+ if (callbackRef.current === void 0 && isBrowser) {
208
+ callbackRef.current = parseAuthCallback(window.location.href);
209
+ }
204
210
  const createMeroInstance = useCallback(
205
211
  (url) => {
212
+ if (meroRef.current) {
213
+ meroRef.current.close();
214
+ }
206
215
  const instance = new MeroJs({
207
216
  baseUrl: url,
208
- tokenStorage: localStorageTokenStorage,
217
+ tokenStore: getTokenStore(),
209
218
  timeoutMs
210
219
  });
211
220
  meroRef.current = instance;
@@ -215,127 +224,35 @@ function MeroProvider({
215
224
  );
216
225
  const checkAuth = useCallback(async (instance) => {
217
226
  try {
218
- await instance.admin.contexts.listContexts();
227
+ await instance.admin.getContexts();
219
228
  return true;
220
229
  } catch {
221
230
  return false;
222
231
  }
223
232
  }, []);
224
- const processAuthCallback = useCallback(() => {
225
- const url = new URL(window.location.href);
226
- const rawHash = url.hash;
227
- const hash = rawHash.slice(1);
228
- console.log("========== AUTH CALLBACK DEBUG ==========");
229
- console.log("Raw URL:", window.location.href);
230
- console.log("Raw hash:", rawHash);
231
- console.log("Hash (without #):", hash);
232
- if (!hash) {
233
- console.log("No hash found, skipping auth callback");
234
- console.log("==========================================");
235
- return false;
236
- }
237
- const params = new URLSearchParams(hash);
238
- console.log("All hash params:");
239
- for (const [key, value] of params.entries()) {
240
- console.log(` ${key}: ${value.substring(0, 50)}${value.length > 50 ? "..." : ""}`);
241
- }
242
- console.log("==========================================");
243
- const accessToken = params.get("access_token");
244
- const refreshToken = params.get("refresh_token");
245
- const appId = params.get("application_id") || params.get("applicationId") || params.get("app_id");
246
- const ctxId = params.get("context_id") || params.get("contextId") || params.get("context");
247
- const expiresIn = params.get("expires_in");
248
- const nodeUrlParam = params.get("node_url") || params.get("nodeUrl") || params.get("node");
249
- if (!accessToken || !refreshToken) return false;
250
- if (nodeUrlParam) {
251
- const decodedNodeUrl = decodeURIComponent(nodeUrlParam);
252
- setNodeUrl(decodedNodeUrl);
253
- setNodeUrlState(decodedNodeUrl);
254
- console.log("[mero-react] SSO: Node URL from hash params:", decodedNodeUrl);
255
- }
256
- try {
257
- const decodedAccess = decodeURIComponent(accessToken);
258
- const decodedRefresh = decodeURIComponent(refreshToken);
259
- let expiresAt = Date.now() + 36e5;
260
- if (expiresIn) {
261
- expiresAt = Date.now() + parseInt(expiresIn, 10) * 1e3;
262
- } else {
263
- try {
264
- const parts = decodedAccess.split(".");
265
- if (parts.length === 3) {
266
- const payload = JSON.parse(atob(parts[1]));
267
- if (payload.exp) {
268
- expiresAt = payload.exp * 1e3;
269
- }
270
- }
271
- } catch {
272
- }
273
- }
274
- localStorageTokenStorage.set({
275
- access_token: decodedAccess,
276
- refresh_token: decodedRefresh,
277
- expires_at: expiresAt
278
- });
279
- if (appId) {
280
- setApplicationId(appId);
281
- setApplicationIdState(appId);
282
- }
283
- if (ctxId) {
284
- setContextId(ctxId);
285
- setContextIdState(ctxId);
286
- }
287
- params.delete("access_token");
288
- params.delete("refresh_token");
289
- params.delete("application_id");
290
- params.delete("applicationId");
291
- params.delete("app_id");
292
- params.delete("context_id");
293
- params.delete("contextId");
294
- params.delete("context");
295
- params.delete("expires_in");
296
- params.delete("node_url");
297
- params.delete("nodeUrl");
298
- params.delete("node");
299
- const newHash = params.toString();
300
- url.hash = newHash ? `#${newHash}` : "";
301
- window.history.replaceState({}, document.title, url.toString());
302
- console.log("[mero-react] Stored contextId:", ctxId, "applicationId:", appId);
303
- return true;
304
- } catch (e) {
305
- console.error("[mero-react] Failed to process auth callback:", e);
306
- return false;
307
- }
308
- }, []);
309
233
  const connectToNode = useCallback(
310
234
  (url) => {
235
+ if (!isBrowser) return;
311
236
  setNodeUrl(url);
312
237
  setNodeUrlState(url);
313
238
  const callbackUrl = new URL(window.location.href);
314
- if (callbackUrl.hash) {
315
- const hashParams = new URLSearchParams(callbackUrl.hash.substring(1));
316
- hashParams.delete("access_token");
317
- hashParams.delete("refresh_token");
318
- hashParams.delete("application_id");
319
- callbackUrl.hash = hashParams.toString() ? `#${hashParams.toString()}` : "";
320
- }
321
- const permissions = getPermissionsForMode(mode);
322
- redirectToAuthLogin({
323
- nodeUrl: url,
239
+ callbackUrl.hash = "";
240
+ const loginUrl = buildAuthLoginUrl(url, {
324
241
  callbackUrl: callbackUrl.toString(),
325
- permissions,
242
+ permissions: getPermissionsForMode(mode),
326
243
  mode,
327
244
  packageName,
328
245
  packageVersion,
329
- registryUrl,
330
- applicationId: propApplicationId,
331
- applicationPath
246
+ registryUrl
332
247
  });
248
+ window.location.href = loginUrl;
333
249
  },
334
- [mode, packageName, packageVersion, registryUrl, propApplicationId, applicationPath]
250
+ [mode, packageName, packageVersion, registryUrl]
335
251
  );
336
- const logout = useCallback(async () => {
252
+ const logout = useCallback(() => {
337
253
  if (meroRef.current) {
338
- await meroRef.current.clearToken();
254
+ meroRef.current.clearToken();
255
+ meroRef.current.close();
339
256
  }
340
257
  clearAllStorage();
341
258
  setMero(null);
@@ -343,56 +260,90 @@ function MeroProvider({
343
260
  setNodeUrlState(null);
344
261
  setApplicationIdState(null);
345
262
  setContextIdState(null);
263
+ setContextIdentityState(null);
346
264
  meroRef.current = null;
347
265
  }, []);
348
266
  useEffect(() => {
267
+ let active = true;
349
268
  const init = async () => {
350
- console.log("[mero-react] Init starting...");
351
- console.log("[mero-react] Current URL:", window.location.href);
352
- console.log("[mero-react] Stored nodeUrl:", getNodeUrl());
353
- console.log("[mero-react] Stored contextId:", getContextId());
354
- console.log("[mero-react] Stored applicationId:", getApplicationId());
355
- const hasCallback = processAuthCallback();
356
- console.log("[mero-react] hasCallback:", hasCallback);
357
- const savedUrl = getNodeUrl();
269
+ const callback = callbackRef.current;
270
+ if (callback) {
271
+ getTokenStore().setTokens({
272
+ access_token: callback.accessToken,
273
+ refresh_token: callback.refreshToken,
274
+ expires_at: parseJwtExpiry(callback.accessToken)
275
+ });
276
+ if (callback.applicationId) {
277
+ setApplicationId(callback.applicationId);
278
+ if (active) setApplicationIdState(callback.applicationId);
279
+ }
280
+ if (callback.contextId) {
281
+ setContextId(callback.contextId);
282
+ if (active) setContextIdState(callback.contextId);
283
+ }
284
+ if (callback.contextIdentity) {
285
+ setContextIdentity(callback.contextIdentity);
286
+ if (active) setContextIdentityState(callback.contextIdentity);
287
+ }
288
+ if (callback.nodeUrl) {
289
+ setNodeUrl(callback.nodeUrl);
290
+ if (active) setNodeUrlState(callback.nodeUrl);
291
+ }
292
+ if (isBrowser) {
293
+ window.history.replaceState({}, "", window.location.pathname + window.location.search);
294
+ }
295
+ callbackRef.current = null;
296
+ }
297
+ const savedUrl = callback?.nodeUrl || getNodeUrl();
358
298
  if (!savedUrl) {
359
- setIsLoading(false);
299
+ if (active) setIsLoading(false);
360
300
  return;
361
301
  }
362
302
  const instance = createMeroInstance(savedUrl);
363
- await instance.init();
364
303
  const authed = await checkAuth(instance);
304
+ if (!active) return;
365
305
  if (authed) {
366
306
  setMero(instance);
367
307
  setIsAuthenticated(true);
368
308
  setIsOnline(true);
369
- } else if (hasCallback) {
309
+ } else if (callback) {
370
310
  setMero(instance);
371
- setIsAuthenticated(false);
372
311
  }
373
312
  setIsLoading(false);
374
313
  };
375
- init();
376
- }, [createMeroInstance, checkAuth, processAuthCallback]);
314
+ init().catch((err) => {
315
+ console.error("[MeroProvider] Initialization failed:", err);
316
+ if (active) setIsLoading(false);
317
+ });
318
+ return () => {
319
+ active = false;
320
+ };
321
+ }, [createMeroInstance, checkAuth]);
377
322
  useEffect(() => {
378
323
  if (!isAuthenticated || !meroRef.current) return;
379
- const interval = setInterval(async () => {
380
- const instance = meroRef.current;
381
- if (!instance) return;
382
- const healthy = await checkAuth(instance);
383
- if (!healthy && isOnline) {
384
- setIsOnline(false);
385
- } else if (healthy && !isOnline) {
386
- setIsOnline(true);
324
+ let active = true;
325
+ const sse = meroRef.current.events;
326
+ const onConnect = () => {
327
+ if (active) setIsOnline(true);
328
+ };
329
+ const onError = (err) => {
330
+ if (!active) return;
331
+ setIsOnline(false);
332
+ if (err.message.includes("401")) {
333
+ logout();
387
334
  }
388
- }, 5e3);
389
- return () => clearInterval(interval);
390
- }, [isAuthenticated, isOnline, checkAuth]);
391
- useEffect(() => {
392
- if (propApplicationId && !applicationId) {
393
- setApplicationIdState(propApplicationId);
394
- }
395
- }, [propApplicationId, applicationId]);
335
+ };
336
+ sse.on("connect", onConnect);
337
+ sse.on("error", onError);
338
+ sse.connect().catch(() => {
339
+ if (active) setIsOnline(false);
340
+ });
341
+ return () => {
342
+ active = false;
343
+ sse.off("connect", onConnect);
344
+ sse.off("error", onError);
345
+ };
346
+ }, [isAuthenticated, mero]);
396
347
  const contextValue = useMemo(
397
348
  () => ({
398
349
  mero,
@@ -401,493 +352,122 @@ function MeroProvider({
401
352
  nodeUrl,
402
353
  applicationId,
403
354
  contextId,
355
+ contextIdentity,
404
356
  connectToNode,
405
357
  logout,
406
358
  isLoading
407
359
  }),
408
- [mero, isAuthenticated, isOnline, nodeUrl, applicationId, contextId, connectToNode, logout, isLoading]
360
+ [mero, isAuthenticated, isOnline, nodeUrl, applicationId, contextId, contextIdentity, connectToNode, logout, isLoading]
409
361
  );
410
362
  return /* @__PURE__ */ jsx(MeroContext.Provider, { value: contextValue, children });
411
363
  }
412
364
  function useMero() {
413
365
  const context = useContext(MeroContext);
414
- if (context === void 0) {
366
+ if (!context) {
415
367
  throw new Error("useMero must be used within a MeroProvider");
416
368
  }
417
369
  return context;
418
370
  }
419
- function isValidUrl(urlString) {
420
- if (!urlString || urlString.trim() === "") {
421
- return false;
422
- }
423
- try {
424
- const urlToTest = urlString.startsWith("http://") || urlString.startsWith("https://") ? urlString : `https://${urlString}`;
425
- const url = new URL(urlToTest);
426
- if (url.protocol !== "http:" && url.protocol !== "https:") {
427
- return false;
428
- }
429
- if (!url.hostname) {
430
- return false;
431
- }
432
- const hostname = url.hostname;
433
- if (hostname === "localhost") {
434
- return true;
435
- }
436
- const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
437
- if (ipv4Regex.test(hostname)) {
438
- const octets = hostname.split(".").map(Number);
439
- return octets.every((octet) => octet >= 0 && octet <= 255);
440
- }
441
- const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
442
- return domainRegex.test(hostname);
443
- } catch {
444
- return false;
445
- }
446
- }
447
- var styles = {
448
- overlay: {
449
- position: "fixed",
450
- top: 0,
451
- left: 0,
452
- right: 0,
453
- bottom: 0,
454
- backgroundColor: "rgba(0, 0, 0, 0.75)",
455
- display: "flex",
456
- alignItems: "center",
457
- justifyContent: "center",
458
- zIndex: 1e4,
459
- padding: "1rem"
460
- },
461
- content: {
462
- backgroundColor: "#1f2937",
463
- borderRadius: "12px",
464
- padding: "2rem",
465
- maxWidth: "400px",
466
- width: "100%",
467
- position: "relative",
468
- border: "1px solid #374151",
469
- boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)"
470
- },
471
- closeButton: {
472
- position: "absolute",
473
- top: "1rem",
474
- right: "1rem",
475
- background: "none",
476
- border: "none",
477
- fontSize: "1.5rem",
478
- color: "#9ca3af",
479
- cursor: "pointer",
480
- padding: "0.25rem",
481
- lineHeight: 1
482
- },
483
- header: {
484
- display: "flex",
485
- flexDirection: "column",
486
- alignItems: "center",
487
- gap: "0.75rem",
488
- marginBottom: "1.5rem"
489
- },
490
- title: {
491
- fontSize: "1.25rem",
492
- fontWeight: 600,
493
- color: "#f3f4f6",
494
- margin: 0
495
- },
496
- info: {
497
- color: "#9ca3af",
498
- textAlign: "center",
499
- marginBottom: "1.5rem",
500
- fontSize: "0.875rem"
501
- },
502
- error: {
503
- color: "#ef4444",
504
- backgroundColor: "rgba(239, 68, 68, 0.1)",
505
- border: "1px solid rgba(239, 68, 68, 0.3)",
506
- borderRadius: "6px",
507
- padding: "0.75rem",
508
- marginBottom: "1rem",
509
- fontSize: "0.875rem",
510
- textAlign: "center"
511
- },
512
- radioGroup: {
513
- display: "flex",
514
- gap: "1rem",
515
- marginBottom: "1rem",
516
- justifyContent: "center"
517
- },
518
- radioLabel: {
519
- display: "flex",
520
- alignItems: "center",
521
- gap: "0.5rem",
522
- color: "#e5e7eb",
523
- cursor: "pointer",
524
- padding: "0.5rem 1rem",
525
- borderRadius: "6px",
526
- border: "1px solid #374151",
527
- backgroundColor: "#111827",
528
- transition: "all 0.2s"
529
- },
530
- radioLabelActive: {
531
- borderColor: "#3b82f6",
532
- backgroundColor: "rgba(59, 130, 246, 0.1)"
533
- },
534
- input: {
535
- width: "100%",
536
- padding: "0.75rem 1rem",
537
- borderRadius: "6px",
538
- border: "1px solid #374151",
539
- backgroundColor: "#111827",
540
- color: "#f3f4f6",
541
- fontSize: "0.875rem",
542
- outline: "none",
543
- marginBottom: "1rem",
544
- boxSizing: "border-box"
545
- },
546
- localInfo: {
547
- color: "#9ca3af",
548
- fontSize: "0.875rem",
549
- textAlign: "center",
550
- padding: "0.75rem",
551
- backgroundColor: "#111827",
552
- borderRadius: "6px",
553
- marginBottom: "1rem"
554
- },
555
- buttonGroup: {
556
- display: "flex",
557
- justifyContent: "center"
558
- },
559
- button: {
560
- padding: "0.75rem 2rem",
561
- borderRadius: "6px",
562
- border: "none",
563
- fontSize: "0.875rem",
564
- fontWeight: 600,
565
- cursor: "pointer",
566
- backgroundColor: "#3b82f6",
567
- color: "white",
568
- transition: "all 0.2s"
569
- },
570
- buttonDisabled: {
571
- opacity: 0.5,
572
- cursor: "not-allowed"
573
- },
574
- loading: {
575
- display: "flex",
576
- flexDirection: "column",
577
- alignItems: "center",
578
- gap: "1rem",
579
- padding: "2rem",
580
- color: "#9ca3af"
581
- },
582
- spinner: {
583
- width: "2rem",
584
- height: "2rem",
585
- border: "3px solid #374151",
586
- borderTopColor: "#3b82f6",
587
- borderRadius: "50%",
588
- animation: "spin 1s linear infinite"
589
- }
590
- };
591
- function LoginModal({
592
- onConnect,
593
- onClose,
594
- connectionType,
595
- isOpen
596
- }) {
597
- const [nodeType, setNodeType] = useState("local");
598
- const [nodeUrl, setNodeUrl2] = useState("");
599
- const [isValid, setIsValid] = useState(true);
371
+ function useExecute(contextId, executorId) {
372
+ const { mero } = useMero();
600
373
  const [loading, setLoading] = useState(false);
601
374
  const [error, setError] = useState(null);
602
- const shouldShowLocal = connectionType === "remote-and-local" /* RemoteAndLocal */ || connectionType === "local" /* Local */;
603
- const shouldShowRemote = connectionType === "remote-and-local" /* RemoteAndLocal */ || connectionType === "remote" /* Remote */;
604
- const shouldShowRadioGroup = shouldShowLocal && shouldShowRemote;
375
+ const mountedRef = useRef(true);
605
376
  useEffect(() => {
606
- const savedUrl = localStorage.getItem("mero:node_url");
607
- if (savedUrl) {
608
- setNodeUrl2(savedUrl);
609
- }
377
+ mountedRef.current = true;
378
+ return () => {
379
+ mountedRef.current = false;
380
+ };
610
381
  }, []);
611
- useEffect(() => {
612
- if (connectionType === "local" /* Local */) {
613
- setNodeType("local");
614
- } else if (connectionType === "remote" /* Remote */) {
615
- setNodeType("remote");
616
- }
617
- }, [connectionType]);
618
- useEffect(() => {
619
- if (nodeType === "remote") {
620
- setIsValid(isValidUrl(nodeUrl));
621
- } else {
622
- setIsValid(true);
623
- }
624
- }, [nodeUrl, nodeType]);
625
- const handleConnect = useCallback(async () => {
626
- if (!isValid) return;
627
- setLoading(true);
628
- setError(null);
629
- const baseUrl = nodeType === "local" ? "http://node1.127.0.0.1.nip.io" : nodeUrl;
630
- try {
631
- const response = await fetch(
632
- new URL("admin-api/is-authed", baseUrl).toString()
633
- );
634
- if (response.ok || response.status === 401) {
635
- setLoading(false);
636
- const normalizedUrl = baseUrl.replace(/\/+$/, "");
637
- onConnect(normalizedUrl);
638
- } else {
639
- throw new Error(`Connection failed: ${response.statusText}`);
382
+ const execute = useCallback(
383
+ async (method, params) => {
384
+ if (!mero || !contextId || !executorId) {
385
+ if (mountedRef.current) setError(new Error("Not connected"));
386
+ return null;
640
387
  }
641
- } catch (err) {
642
- console.error("Connection failed:", err);
643
- setError("Failed to connect. Please check the URL and try again.");
644
- setLoading(false);
645
- }
646
- }, [isValid, nodeType, nodeUrl, onConnect]);
647
- if (!isOpen) {
648
- return null;
649
- }
650
- const modalContent = /* @__PURE__ */ jsxs(Fragment, { children: [
651
- /* @__PURE__ */ jsx("style", { children: `
652
- @keyframes spin {
653
- to { transform: rotate(360deg); }
654
- }
655
- ` }),
656
- /* @__PURE__ */ jsx("div", { style: styles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxs("div", { style: styles.content, onClick: (e) => e.stopPropagation(), children: [
657
- /* @__PURE__ */ jsx("button", { style: styles.closeButton, onClick: onClose, children: "\xD7" }),
658
- /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
659
- /* @__PURE__ */ jsx(MeroLogo, {}),
660
- /* @__PURE__ */ jsx("h1", { style: styles.title, children: "Connect to Calimero" })
661
- ] }),
662
- loading ? /* @__PURE__ */ jsxs("div", { style: styles.loading, children: [
663
- /* @__PURE__ */ jsx("p", { children: "Connecting to node..." }),
664
- /* @__PURE__ */ jsx("div", { style: styles.spinner })
665
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
666
- /* @__PURE__ */ jsx("p", { style: styles.info, children: shouldShowRadioGroup ? "Select your Calimero node type to continue." : connectionType === "local" /* Local */ ? "Connect to your local Calimero node." : "Enter your remote Calimero node URL." }),
667
- error && /* @__PURE__ */ jsx("p", { style: styles.error, children: error }),
668
- shouldShowRadioGroup && /* @__PURE__ */ jsxs("div", { style: styles.radioGroup, children: [
669
- /* @__PURE__ */ jsxs(
670
- "label",
671
- {
672
- style: {
673
- ...styles.radioLabel,
674
- ...nodeType === "local" ? styles.radioLabelActive : {}
675
- },
676
- onClick: () => setNodeType("local"),
677
- children: [
678
- /* @__PURE__ */ jsx(
679
- "input",
680
- {
681
- type: "radio",
682
- value: "local",
683
- checked: nodeType === "local",
684
- onChange: () => setNodeType("local"),
685
- style: { display: "none" }
686
- }
687
- ),
688
- "\u{1F3E0} Local"
689
- ]
690
- }
691
- ),
692
- /* @__PURE__ */ jsxs(
693
- "label",
694
- {
695
- style: {
696
- ...styles.radioLabel,
697
- ...nodeType === "remote" ? styles.radioLabelActive : {}
698
- },
699
- onClick: () => setNodeType("remote"),
700
- children: [
701
- /* @__PURE__ */ jsx(
702
- "input",
703
- {
704
- type: "radio",
705
- value: "remote",
706
- checked: nodeType === "remote",
707
- onChange: () => setNodeType("remote"),
708
- style: { display: "none" }
709
- }
710
- ),
711
- "\u{1F310} Remote"
712
- ]
713
- }
714
- )
715
- ] }),
716
- /* @__PURE__ */ jsx("div", { children: shouldShowRemote && nodeType === "remote" ? /* @__PURE__ */ jsx(
717
- "input",
718
- {
719
- type: "text",
720
- value: nodeUrl,
721
- onChange: (e) => setNodeUrl2(e.target.value),
722
- placeholder: "https://your-node-url.calimero.network",
723
- style: styles.input,
724
- onKeyDown: (e) => {
725
- if (e.key === "Enter" && isValid) {
726
- handleConnect();
727
- }
728
- }
729
- }
730
- ) : shouldShowLocal ? /* @__PURE__ */ jsxs("p", { style: styles.localInfo, children: [
731
- "Using default local node: ",
732
- /* @__PURE__ */ jsx("br", {}),
733
- /* @__PURE__ */ jsx("code", { style: { color: "#60a5fa" }, children: "http://node1.127.0.0.1.nip.io" })
734
- ] }) : null }),
735
- /* @__PURE__ */ jsx("div", { style: styles.buttonGroup, children: /* @__PURE__ */ jsx(
736
- "button",
737
- {
738
- onClick: handleConnect,
739
- disabled: !isValid || loading,
740
- style: {
741
- ...styles.button,
742
- ...!isValid || loading ? styles.buttonDisabled : {}
743
- },
744
- children: "Connect"
745
- }
746
- ) })
747
- ] })
748
- ] }) })
749
- ] });
750
- return createPortal(modalContent, document.body);
751
- }
752
- function MeroLogo() {
753
- return /* @__PURE__ */ jsx(
754
- "svg",
755
- {
756
- width: "40",
757
- height: "40",
758
- viewBox: "0 0 24 24",
759
- fill: "#3b82f6",
760
- xmlns: "http://www.w3.org/2000/svg",
761
- children: /* @__PURE__ */ jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" })
762
- }
388
+ if (mountedRef.current) {
389
+ setLoading(true);
390
+ setError(null);
391
+ }
392
+ try {
393
+ const result = await mero.rpc.execute({
394
+ contextId,
395
+ method,
396
+ argsJson: params,
397
+ executorPublicKey: executorId
398
+ });
399
+ return result;
400
+ } catch (err) {
401
+ const e = err instanceof Error ? err : new Error(String(err));
402
+ if (mountedRef.current) setError(e);
403
+ return null;
404
+ } finally {
405
+ if (mountedRef.current) setLoading(false);
406
+ }
407
+ },
408
+ [mero, contextId, executorId]
763
409
  );
410
+ return { execute, loading, error };
764
411
  }
765
- function ConnectButton({
766
- connectionType = "remote-and-local" /* RemoteAndLocal */,
767
- className,
768
- style
769
- }) {
770
- const { isAuthenticated, connectToNode, logout, nodeUrl, isOnline } = useMero();
771
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
772
- const [isModalOpen, setIsModalOpen] = useState(false);
773
- const dropdownRef = useRef(null);
412
+ function useSubscription(contextIds, callback) {
413
+ const { mero } = useMero();
414
+ const callbackRef = useRef(callback);
415
+ callbackRef.current = callback;
416
+ const contextIdsKey = JSON.stringify(contextIds);
774
417
  useEffect(() => {
775
- const handleClickOutside = (event) => {
776
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
777
- setIsDropdownOpen(false);
778
- }
418
+ if (!mero || contextIds.length === 0) return;
419
+ const sse = mero.events;
420
+ const handler = (event) => {
421
+ callbackRef.current(event);
422
+ };
423
+ sse.on("event", handler);
424
+ sse.connect().catch(() => {
425
+ });
426
+ sse.subscribe(contextIds).catch(() => {
427
+ });
428
+ return () => {
429
+ sse.off("event", handler);
430
+ };
431
+ }, [mero, contextIdsKey]);
432
+ }
433
+ function useContexts(applicationId) {
434
+ const { mero } = useMero();
435
+ const [contexts, setContexts] = useState([]);
436
+ const [loading, setLoading] = useState(false);
437
+ const [error, setError] = useState(null);
438
+ const mountedRef = useRef(true);
439
+ useEffect(() => {
440
+ mountedRef.current = true;
441
+ return () => {
442
+ mountedRef.current = false;
779
443
  };
780
- document.addEventListener("mousedown", handleClickOutside);
781
- return () => document.removeEventListener("mousedown", handleClickOutside);
782
444
  }, []);
783
- const dashboardUrl = useMemo(() => {
784
- if (!isAuthenticated || !nodeUrl) return "#";
785
- return new URL("admin-dashboard/", nodeUrl).toString();
786
- }, [isAuthenticated, nodeUrl]);
787
- const handleConnect = () => {
788
- if (typeof connectionType === "object" && connectionType.type === "custom" /* Custom */) {
789
- connectToNode(connectionType.url);
790
- return;
445
+ const refetch = useCallback(async () => {
446
+ if (!mero) return;
447
+ if (mountedRef.current) {
448
+ setLoading(true);
449
+ setError(null);
791
450
  }
792
- setIsModalOpen(true);
793
- };
794
- const handleModalConnect = (url) => {
795
- setIsModalOpen(false);
796
- connectToNode(url);
797
- };
798
- if (isAuthenticated && !isOnline) {
799
- return /* @__PURE__ */ jsxs(
800
- "button",
801
- {
802
- className: `mero-connect-button mero-reconnecting ${className || ""}`,
803
- style,
804
- disabled: true,
805
- children: [
806
- /* @__PURE__ */ jsx(MeroLogo2, {}),
807
- "Reconnecting..."
808
- ]
809
- }
810
- );
811
- }
812
- if (isAuthenticated) {
813
- return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "mero-connect-container", style: { position: "relative", display: "inline-block" }, children: [
814
- /* @__PURE__ */ jsxs(
815
- "button",
816
- {
817
- className: `mero-connect-button mero-connected ${className || ""}`,
818
- style,
819
- onClick: () => setIsDropdownOpen((prev) => !prev),
820
- children: [
821
- /* @__PURE__ */ jsx(MeroLogo2, {}),
822
- "Connected"
823
- ]
824
- }
825
- ),
826
- isDropdownOpen && /* @__PURE__ */ jsxs("div", { className: "mero-dropdown", children: [
827
- /* @__PURE__ */ jsx("div", { className: "mero-dropdown-info", title: nodeUrl || "", children: nodeUrl }),
828
- /* @__PURE__ */ jsx(
829
- "a",
830
- {
831
- href: dashboardUrl,
832
- target: "_blank",
833
- rel: "noopener noreferrer",
834
- className: "mero-dropdown-item",
835
- children: "Dashboard"
836
- }
837
- ),
838
- /* @__PURE__ */ jsx(
839
- "button",
840
- {
841
- className: "mero-dropdown-item",
842
- onClick: () => {
843
- setIsDropdownOpen(false);
844
- logout();
845
- },
846
- children: "Log out"
847
- }
848
- )
849
- ] })
850
- ] });
851
- }
852
- return /* @__PURE__ */ jsxs(Fragment, { children: [
853
- /* @__PURE__ */ jsxs(
854
- "button",
855
- {
856
- className: `mero-connect-button ${className || ""}`,
857
- style,
858
- onClick: handleConnect,
859
- children: [
860
- /* @__PURE__ */ jsx(MeroLogo2, {}),
861
- "Connect"
862
- ]
863
- }
864
- ),
865
- /* @__PURE__ */ jsx(
866
- LoginModal,
867
- {
868
- isOpen: isModalOpen,
869
- onConnect: handleModalConnect,
870
- onClose: () => setIsModalOpen(false),
871
- connectionType: typeof connectionType === "object" ? "remote-and-local" /* RemoteAndLocal */ : connectionType
451
+ try {
452
+ const response = await mero.admin.getContexts();
453
+ let list = (response.contexts ?? []).map((c) => ({ contextId: c.id, applicationId: c.applicationId }));
454
+ if (applicationId) {
455
+ list = list.filter((c) => c.applicationId === applicationId);
872
456
  }
873
- )
874
- ] });
875
- }
876
- function MeroLogo2() {
877
- return /* @__PURE__ */ jsx(
878
- "svg",
879
- {
880
- className: "mero-logo",
881
- width: "24",
882
- height: "24",
883
- viewBox: "0 0 24 24",
884
- fill: "currentColor",
885
- xmlns: "http://www.w3.org/2000/svg",
886
- children: /* @__PURE__ */ jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z" })
457
+ if (mountedRef.current) setContexts(list);
458
+ } catch (err) {
459
+ const e = err instanceof Error ? err : new Error(String(err));
460
+ if (mountedRef.current) setError(e);
461
+ } finally {
462
+ if (mountedRef.current) setLoading(false);
887
463
  }
888
- );
464
+ }, [mero, applicationId]);
465
+ useEffect(() => {
466
+ void refetch();
467
+ }, [refetch]);
468
+ return { contexts, loading, error, refetch };
889
469
  }
890
470
 
891
- export { AppMode, ConnectButton, ConnectionType, EventStreamMode, LoginModal, MeroContext, MeroProvider, clearAllStorage, clearApplicationId, clearNodeUrl, getApplicationId, getNodeUrl, localStorageTokenStorage, setApplicationId, setNodeUrl, useMero };
471
+ export { AppMode, ConnectionType, MeroContext, MeroProvider, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, getApplicationId, getContextId, getContextIdentity, getNodeUrl, localStorageTokenStorage, setApplicationId, setContextId, setContextIdentity, setNodeUrl, useContexts, useExecute, useMero, useSubscription };
892
472
  //# sourceMappingURL=index.js.map
893
473
  //# sourceMappingURL=index.js.map