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