@calimero-network/mero-react 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -348,7 +348,7 @@ useContexts, useApplicationContexts, useContextGroup, useContextDiscovery
348
348
  useCreateContext, useDeleteContext, useJoinContext
349
349
  useGroupInfo, useGroupMembers, useGroupContexts, useGroupInvitations, useGroupCapabilities
350
350
  useJoinGroup, useDeleteGroup, useAddGroupMembers, useRemoveGroupMembers
351
- useSyncGroup, useNestGroup, useUnnestGroup, useSubgroups
351
+ useSyncGroup, useReparentGroup, useSubgroups
352
352
  useUpgradeGroup, useGroupUpgradeStatus, useRetryGroupUpgrade
353
353
  useRegisterGroupSigningKey, useUpdateGroupSettings
354
354
  useSetGroupMetadata, useSetMemberMetadata, useSetContextMetadata
package/dist/index.cjs CHANGED
@@ -45,6 +45,28 @@ var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
45
45
  return ConnectionType2;
46
46
  })(ConnectionType || {});
47
47
 
48
+ // src/auth/node-trust.ts
49
+ function sameOrigin(a, b) {
50
+ try {
51
+ return new URL(a).origin === new URL(b).origin;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ function resolveTrustedNodeUrl(params) {
57
+ const { candidate, initiated, allowedNodeUrls } = params;
58
+ if (!candidate) {
59
+ return { url: initiated ?? null, rejected: false };
60
+ }
61
+ if (initiated) {
62
+ return sameOrigin(candidate, initiated) ? { url: candidate, rejected: false } : { url: null, rejected: true };
63
+ }
64
+ if (allowedNodeUrls && allowedNodeUrls.length > 0) {
65
+ return allowedNodeUrls.some((u) => sameOrigin(candidate, u)) ? { url: candidate, rejected: false } : { url: null, rejected: true };
66
+ }
67
+ return { url: null, rejected: true };
68
+ }
69
+
48
70
  // src/storage/index.ts
49
71
  var STORAGE_KEYS = {
50
72
  ACCESS_TOKEN: "mero:access_token",
@@ -175,13 +197,6 @@ function clearAllStorage() {
175
197
  }
176
198
  var MeroContext = react.createContext(null);
177
199
  var isBrowser = typeof window !== "undefined";
178
- var _tokenStore = null;
179
- function getTokenStore() {
180
- if (!_tokenStore) {
181
- _tokenStore = new meroJs.LocalStorageTokenStore();
182
- }
183
- return _tokenStore;
184
- }
185
200
  function getPermissionsForMode(mode) {
186
201
  switch (mode) {
187
202
  case "single-context" /* SingleContext */:
@@ -215,8 +230,14 @@ function MeroProvider({
215
230
  packageName,
216
231
  packageVersion,
217
232
  registryUrl,
218
- timeoutMs = 3e4
233
+ timeoutMs = 3e4,
234
+ allowedNodeUrls,
235
+ tokenStore: tokenStoreProp
219
236
  }) {
237
+ const tokenStore = react.useMemo(
238
+ () => tokenStoreProp ?? new meroJs.LocalStorageTokenStore(),
239
+ [tokenStoreProp]
240
+ );
220
241
  const [mero, setMero] = react.useState(null);
221
242
  const [isAuthenticated, setIsAuthenticated] = react.useState(false);
222
243
  const [isOnline, setIsOnline] = react.useState(true);
@@ -239,13 +260,13 @@ function MeroProvider({
239
260
  }
240
261
  const instance = new meroJs.MeroJs({
241
262
  baseUrl: url,
242
- tokenStore: getTokenStore(),
263
+ tokenStore,
243
264
  timeoutMs
244
265
  });
245
266
  meroRef.current = instance;
246
267
  return instance;
247
268
  },
248
- [timeoutMs]
269
+ [timeoutMs, tokenStore]
249
270
  );
250
271
  const checkAuth = react.useCallback(async (instance) => {
251
272
  try {
@@ -279,6 +300,7 @@ function MeroProvider({
279
300
  meroRef.current.clearToken();
280
301
  meroRef.current.close();
281
302
  }
303
+ tokenStore.clear();
282
304
  clearAllStorage();
283
305
  setMero(null);
284
306
  setIsAuthenticated(false);
@@ -287,39 +309,50 @@ function MeroProvider({
287
309
  setContextIdState(null);
288
310
  setContextIdentityState(null);
289
311
  meroRef.current = null;
290
- }, []);
312
+ }, [tokenStore]);
291
313
  react.useEffect(() => {
292
314
  let active = true;
293
315
  const init = async () => {
294
316
  const callback = callbackRef.current;
317
+ let nodeFromCallback = null;
295
318
  if (callback) {
296
- getTokenStore().setTokens({
297
- access_token: callback.accessToken,
298
- refresh_token: callback.refreshToken,
299
- expires_at: parseJwtExpiry(callback.accessToken)
319
+ const { url, rejected } = resolveTrustedNodeUrl({
320
+ candidate: callback.nodeUrl,
321
+ initiated: getNodeUrl(),
322
+ allowedNodeUrls
300
323
  });
301
- if (callback.applicationId) {
302
- setApplicationId(callback.applicationId);
303
- if (active) setApplicationIdState(callback.applicationId);
304
- }
305
- if (callback.contextId) {
306
- setContextId(callback.contextId);
307
- if (active) setContextIdState(callback.contextId);
308
- }
309
- if (callback.contextIdentity) {
310
- setContextIdentity(callback.contextIdentity);
311
- if (active) setContextIdentityState(callback.contextIdentity);
312
- }
313
- if (callback.nodeUrl) {
314
- setNodeUrl(callback.nodeUrl);
315
- if (active) setNodeUrlState(callback.nodeUrl);
316
- }
317
324
  if (isBrowser) {
318
325
  window.history.replaceState({}, "", window.location.pathname + window.location.search);
319
326
  }
320
327
  callbackRef.current = null;
328
+ if (rejected) {
329
+ console.error(
330
+ "[MeroProvider] OAuth callback node_url is not trusted (it does not match the node login was initiated with, nor `allowedNodeUrls`). Ignoring the callback; no tokens stored."
331
+ );
332
+ } else if (url) {
333
+ tokenStore.setTokens({
334
+ access_token: callback.accessToken,
335
+ refresh_token: callback.refreshToken,
336
+ expires_at: parseJwtExpiry(callback.accessToken)
337
+ });
338
+ if (callback.applicationId) {
339
+ setApplicationId(callback.applicationId);
340
+ if (active) setApplicationIdState(callback.applicationId);
341
+ }
342
+ if (callback.contextId) {
343
+ setContextId(callback.contextId);
344
+ if (active) setContextIdState(callback.contextId);
345
+ }
346
+ if (callback.contextIdentity) {
347
+ setContextIdentity(callback.contextIdentity);
348
+ if (active) setContextIdentityState(callback.contextIdentity);
349
+ }
350
+ setNodeUrl(url);
351
+ if (active) setNodeUrlState(url);
352
+ nodeFromCallback = url;
353
+ }
321
354
  }
322
- const savedUrl = callback?.nodeUrl || getNodeUrl();
355
+ const savedUrl = nodeFromCallback || getNodeUrl();
323
356
  if (!savedUrl) {
324
357
  if (active) setIsLoading(false);
325
358
  return;
@@ -331,7 +364,7 @@ function MeroProvider({
331
364
  setMero(instance);
332
365
  setIsAuthenticated(true);
333
366
  setIsOnline(true);
334
- } else if (callback) {
367
+ } else if (nodeFromCallback) {
335
368
  setMero(instance);
336
369
  }
337
370
  setIsLoading(false);
@@ -343,7 +376,7 @@ function MeroProvider({
343
376
  return () => {
344
377
  active = false;
345
378
  };
346
- }, [createMeroInstance, checkAuth]);
379
+ }, [createMeroInstance, checkAuth, allowedNodeUrls, tokenStore]);
347
380
  react.useEffect(() => {
348
381
  if (!isAuthenticated || !meroRef.current) return;
349
382
  let active = true;
@@ -2240,29 +2273,17 @@ function useRetryGroupUpgrade() {
2240
2273
  );
2241
2274
  return { retryGroupUpgrade, loading, error };
2242
2275
  }
2243
- function useNestGroup() {
2244
- const { mero } = useMero();
2245
- const { loading, error, run } = useAsyncMutation();
2246
- const nestGroup = react.useCallback(
2247
- async (parentGroupId, request) => {
2248
- if (!mero) return null;
2249
- return run(() => mero.admin.nestGroup(parentGroupId, request));
2250
- },
2251
- [mero, run]
2252
- );
2253
- return { nestGroup, loading, error };
2254
- }
2255
- function useUnnestGroup() {
2276
+ function useReparentGroup() {
2256
2277
  const { mero } = useMero();
2257
2278
  const { loading, error, run } = useAsyncMutation();
2258
- const unnestGroup = react.useCallback(
2259
- async (parentGroupId, request) => {
2279
+ const reparentGroup = react.useCallback(
2280
+ async (childGroupId, request) => {
2260
2281
  if (!mero) return null;
2261
- return run(() => mero.admin.unnestGroup(parentGroupId, request));
2282
+ return run(() => mero.admin.reparentGroup(childGroupId, request));
2262
2283
  },
2263
2284
  [mero, run]
2264
2285
  );
2265
- return { unnestGroup, loading, error };
2286
+ return { reparentGroup, loading, error };
2266
2287
  }
2267
2288
  function useSubgroups(groupId) {
2268
2289
  const { mero } = useMero();
@@ -2710,9 +2731,9 @@ exports.useNamespaceGroups = useNamespaceGroups;
2710
2731
  exports.useNamespaceIdentity = useNamespaceIdentity;
2711
2732
  exports.useNamespaces = useNamespaces;
2712
2733
  exports.useNamespacesForApplication = useNamespacesForApplication;
2713
- exports.useNestGroup = useNestGroup;
2714
2734
  exports.useRegisterGroupSigningKey = useRegisterGroupSigningKey;
2715
2735
  exports.useRemoveGroupMembers = useRemoveGroupMembers;
2736
+ exports.useReparentGroup = useReparentGroup;
2716
2737
  exports.useResyncContext = useResyncContext;
2717
2738
  exports.useRetryGroupUpgrade = useRetryGroupUpgrade;
2718
2739
  exports.useSetContextMetadata = useSetContextMetadata;
@@ -2725,7 +2746,6 @@ exports.useSubgroupVisibility = useSubgroupVisibility;
2725
2746
  exports.useSubgroups = useSubgroups;
2726
2747
  exports.useSubscription = useSubscription;
2727
2748
  exports.useSyncGroup = useSyncGroup;
2728
- exports.useUnnestGroup = useUnnestGroup;
2729
2749
  exports.useUpdateGroupSettings = useUpdateGroupSettings;
2730
2750
  exports.useUpdateMemberRole = useUpdateMemberRole;
2731
2751
  exports.useUpgradeGroup = useUpgradeGroup;