@calimero-network/mero-react 4.3.2 → 4.3.4

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.d.cts CHANGED
@@ -761,6 +761,18 @@ declare function setNodeUrl(url: string): void;
761
761
  * Clear the node URL
762
762
  */
763
763
  declare function clearNodeUrl(): void;
764
+ /**
765
+ * Get the node URL that minted the token bundle currently in the TokenStore.
766
+ */
767
+ declare function getTokenNodeUrl(): string | null;
768
+ /**
769
+ * Record the node URL that minted the token bundle now in the TokenStore.
770
+ */
771
+ declare function setTokenNodeUrl(url: string): void;
772
+ /**
773
+ * Clear the token bundle's node URL
774
+ */
775
+ declare function clearTokenNodeUrl(): void;
764
776
  /**
765
777
  * Get the stored application ID
766
778
  */
@@ -802,4 +814,4 @@ declare function clearContextIdentity(): void;
802
814
  */
803
815
  declare function clearAllStorage(): void;
804
816
 
805
- export { type AppContext, AppMode, type ApplicationContextRecord, CalimeroLogo, type CalimeroLogoProps, ConnectButton, type ConnectButtonProps, ConnectionType, type ContextDiscoveryOptions, type ContextDiscoveryState, type CustomConnectionConfig, DEFAULT_LOCAL_NODE_PORTS, type DiscoverLocalNodesOptions, type ExecutionResult, LoginModal, type LoginModalProps, MERO_CSS_VARS, MeroContext, type MeroContextValue, MeroProvider, type MeroProviderConfig, type MeroProviderProps, type MeroTheme, MigrationAdminPanel, type MigrationAdminPanelProps, MigrationPendingBanner, type MigrationPendingBannerProps, type ResolvedMeroTheme, type SetMetadataInput, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
817
+ export { type AppContext, AppMode, type ApplicationContextRecord, CalimeroLogo, type CalimeroLogoProps, ConnectButton, type ConnectButtonProps, ConnectionType, type ContextDiscoveryOptions, type ContextDiscoveryState, type CustomConnectionConfig, DEFAULT_LOCAL_NODE_PORTS, type DiscoverLocalNodesOptions, type ExecutionResult, LoginModal, type LoginModalProps, MERO_CSS_VARS, MeroContext, type MeroContextValue, MeroProvider, type MeroProviderConfig, type MeroProviderProps, type MeroTheme, MigrationAdminPanel, type MigrationAdminPanelProps, MigrationPendingBanner, type MigrationPendingBannerProps, type ResolvedMeroTheme, type SetMetadataInput, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, clearTokenNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, getTokenNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, setTokenNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
package/dist/index.d.ts CHANGED
@@ -761,6 +761,18 @@ declare function setNodeUrl(url: string): void;
761
761
  * Clear the node URL
762
762
  */
763
763
  declare function clearNodeUrl(): void;
764
+ /**
765
+ * Get the node URL that minted the token bundle currently in the TokenStore.
766
+ */
767
+ declare function getTokenNodeUrl(): string | null;
768
+ /**
769
+ * Record the node URL that minted the token bundle now in the TokenStore.
770
+ */
771
+ declare function setTokenNodeUrl(url: string): void;
772
+ /**
773
+ * Clear the token bundle's node URL
774
+ */
775
+ declare function clearTokenNodeUrl(): void;
764
776
  /**
765
777
  * Get the stored application ID
766
778
  */
@@ -802,4 +814,4 @@ declare function clearContextIdentity(): void;
802
814
  */
803
815
  declare function clearAllStorage(): void;
804
816
 
805
- export { type AppContext, AppMode, type ApplicationContextRecord, CalimeroLogo, type CalimeroLogoProps, ConnectButton, type ConnectButtonProps, ConnectionType, type ContextDiscoveryOptions, type ContextDiscoveryState, type CustomConnectionConfig, DEFAULT_LOCAL_NODE_PORTS, type DiscoverLocalNodesOptions, type ExecutionResult, LoginModal, type LoginModalProps, MERO_CSS_VARS, MeroContext, type MeroContextValue, MeroProvider, type MeroProviderConfig, type MeroProviderProps, type MeroTheme, MigrationAdminPanel, type MigrationAdminPanelProps, MigrationPendingBanner, type MigrationPendingBannerProps, type ResolvedMeroTheme, type SetMetadataInput, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
817
+ export { type AppContext, AppMode, type ApplicationContextRecord, CalimeroLogo, type CalimeroLogoProps, ConnectButton, type ConnectButtonProps, ConnectionType, type ContextDiscoveryOptions, type ContextDiscoveryState, type CustomConnectionConfig, DEFAULT_LOCAL_NODE_PORTS, type DiscoverLocalNodesOptions, type ExecutionResult, LoginModal, type LoginModalProps, MERO_CSS_VARS, MeroContext, type MeroContextValue, MeroProvider, type MeroProviderConfig, type MeroProviderProps, type MeroTheme, MigrationAdminPanel, type MigrationAdminPanelProps, MigrationPendingBanner, type MigrationPendingBannerProps, type ResolvedMeroTheme, type SetMetadataInput, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, clearTokenNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, getTokenNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, setTokenNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
package/dist/index.js CHANGED
@@ -67,12 +67,85 @@ function resolveTrustedNodeUrl(params) {
67
67
  return { url: null, rejected: true };
68
68
  }
69
69
 
70
+ // src/auth/token-adoption.ts
71
+ var HOUR_MS = 36e5;
72
+ function decodeJwtClaims(token) {
73
+ try {
74
+ const parts = token.split(".");
75
+ if (parts.length !== 3) return null;
76
+ let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
77
+ while (b64.length % 4) b64 += "=";
78
+ const payload = JSON.parse(atob(b64));
79
+ if (!payload || typeof payload !== "object") return null;
80
+ const { iat, exp } = payload;
81
+ return {
82
+ iat: typeof iat === "number" && iat > 0 ? iat : void 0,
83
+ exp: typeof exp === "number" && exp > 0 ? exp : void 0
84
+ };
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function expiresAtFromJwt(token, fallbackMs = Date.now() + HOUR_MS) {
90
+ const exp = decodeJwtClaims(token)?.exp;
91
+ return exp ? exp * 1e3 : fallbackMs;
92
+ }
93
+ function isCallbackNewer(callbackAccessToken, stored) {
94
+ const incoming = decodeJwtClaims(callbackAccessToken);
95
+ if (!incoming) return false;
96
+ const current = decodeJwtClaims(stored.access_token);
97
+ if (current) {
98
+ if (incoming.iat !== void 0 && current.iat !== void 0 && incoming.iat !== current.iat) {
99
+ return incoming.iat > current.iat;
100
+ }
101
+ if (incoming.exp !== void 0 && current.exp !== void 0 && incoming.exp !== current.exp) {
102
+ return incoming.exp > current.exp;
103
+ }
104
+ return false;
105
+ }
106
+ return incoming.exp !== void 0 && incoming.exp * 1e3 > stored.expires_at;
107
+ }
108
+ function resolveTokenAdoption(params) {
109
+ const { callbackAccessToken, callbackRefreshToken, callbackNodeUrl, stored, storedNodeUrl } = params;
110
+ const bundle = (refreshToken2) => ({
111
+ access_token: callbackAccessToken,
112
+ refresh_token: refreshToken2,
113
+ expires_at: expiresAtFromJwt(callbackAccessToken)
114
+ });
115
+ if (!stored?.access_token) {
116
+ return { adopt: true, tokens: bundle(callbackRefreshToken), reason: "fresh-login" };
117
+ }
118
+ const sameNode = !!storedNodeUrl && sameOrigin(callbackNodeUrl, storedNodeUrl);
119
+ if (!sameNode) {
120
+ return { adopt: true, tokens: bundle(callbackRefreshToken), reason: "node-changed" };
121
+ }
122
+ const refreshToken = callbackRefreshToken || stored.refresh_token;
123
+ if (isCallbackNewer(callbackAccessToken, stored)) {
124
+ return { adopt: true, tokens: bundle(refreshToken), reason: "newer" };
125
+ }
126
+ if (!stored.refresh_token && callbackRefreshToken) {
127
+ return {
128
+ adopt: true,
129
+ tokens: { ...stored, refresh_token: callbackRefreshToken },
130
+ reason: "fill-refresh"
131
+ };
132
+ }
133
+ return { adopt: false, reason: "stale" };
134
+ }
135
+
70
136
  // src/storage/index.ts
71
137
  var STORAGE_KEYS = {
72
138
  ACCESS_TOKEN: "mero:access_token",
73
139
  REFRESH_TOKEN: "mero:refresh_token",
74
140
  EXPIRES_AT: "mero:expires_at",
75
141
  NODE_URL: "mero:node_url",
142
+ // The node the token bundle currently in the TokenStore was issued by. Distinct
143
+ // from NODE_URL, which `connectToNode` overwrites with the *target* node before
144
+ // redirecting to login — so at callback time NODE_URL names where we are going,
145
+ // not who minted the tokens we still hold. Telling those apart is what lets the
146
+ // SSO callback re-seed on a node change without mistaking a different node's
147
+ // bundle for a rotated one (see auth/token-adoption.ts).
148
+ TOKEN_NODE_URL: "mero:token_node_url",
76
149
  APPLICATION_ID: "mero:application_id",
77
150
  CONTEXT_ID: "mero:context_id",
78
151
  CONTEXT_IDENTITY: "mero:context_identity"
@@ -153,6 +226,18 @@ function clearNodeUrl() {
153
226
  if (!isLocalStorageAvailable()) return;
154
227
  localStorage.removeItem(STORAGE_KEYS.NODE_URL);
155
228
  }
229
+ function getTokenNodeUrl() {
230
+ if (!isLocalStorageAvailable()) return null;
231
+ return localStorage.getItem(STORAGE_KEYS.TOKEN_NODE_URL);
232
+ }
233
+ function setTokenNodeUrl(url) {
234
+ if (!isLocalStorageAvailable()) return;
235
+ localStorage.setItem(STORAGE_KEYS.TOKEN_NODE_URL, url);
236
+ }
237
+ function clearTokenNodeUrl() {
238
+ if (!isLocalStorageAvailable()) return;
239
+ localStorage.removeItem(STORAGE_KEYS.TOKEN_NODE_URL);
240
+ }
156
241
  function getApplicationId() {
157
242
  if (!isLocalStorageAvailable()) return null;
158
243
  return localStorage.getItem(STORAGE_KEYS.APPLICATION_ID);
@@ -218,21 +303,6 @@ function getPermissionsForMode(mode) {
218
303
  throw new Error(`Unsupported application mode: ${mode}`);
219
304
  }
220
305
  }
221
- function parseJwtExpiry(token) {
222
- try {
223
- const parts = token.split(".");
224
- if (parts.length === 3) {
225
- let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
226
- while (b64.length % 4) b64 += "=";
227
- const payload = JSON.parse(atob(b64));
228
- if (payload.exp && payload.exp > 0) {
229
- return payload.exp * 1e3;
230
- }
231
- }
232
- } catch {
233
- }
234
- return Date.now() + 36e5;
235
- }
236
306
  function MeroProvider({
237
307
  children,
238
308
  mode,
@@ -330,9 +400,11 @@ function MeroProvider({
330
400
  const callback = callbackRef.current;
331
401
  let nodeFromCallback = null;
332
402
  if (callback) {
403
+ const initiatedNodeUrl = getNodeUrl();
404
+ const tokenNodeUrl = getTokenNodeUrl() ?? initiatedNodeUrl;
333
405
  const { url, rejected } = resolveTrustedNodeUrl({
334
406
  candidate: callback.nodeUrl,
335
- initiated: getNodeUrl(),
407
+ initiated: initiatedNodeUrl,
336
408
  allowedNodeUrls
337
409
  });
338
410
  if (isBrowser) {
@@ -344,11 +416,21 @@ function MeroProvider({
344
416
  "[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."
345
417
  );
346
418
  } else if (url) {
347
- tokenStore.setTokens({
348
- access_token: callback.accessToken,
349
- refresh_token: callback.refreshToken,
350
- expires_at: parseJwtExpiry(callback.accessToken)
419
+ const decision = resolveTokenAdoption({
420
+ callbackAccessToken: callback.accessToken,
421
+ callbackRefreshToken: callback.refreshToken,
422
+ callbackNodeUrl: url,
423
+ stored: tokenStore.getTokens(),
424
+ storedNodeUrl: tokenNodeUrl
351
425
  });
426
+ if (decision.adopt) {
427
+ tokenStore.setTokens(decision.tokens);
428
+ setTokenNodeUrl(url);
429
+ } else {
430
+ console.warn(
431
+ "[MeroProvider] Ignoring the SSO callback token bundle: it is stale (not newer than the tokens already stored for this node). Adopting it would replay an already-rotated refresh token, which the node revokes the whole token family for."
432
+ );
433
+ }
352
434
  if (callback.applicationId) {
353
435
  setApplicationId(callback.applicationId);
354
436
  if (active) setApplicationIdState(callback.applicationId);
@@ -2452,6 +2534,6 @@ function MigrationAdminPanel({ namespaceId, pollIntervalMs, className }) {
2452
2534
  ] });
2453
2535
  }
2454
2536
 
2455
- export { AppMode, CalimeroLogo, ConnectButton, ConnectionType, DEFAULT_LOCAL_NODE_PORTS, LoginModal, MERO_CSS_VARS, MeroContext, MeroProvider, MigrationAdminPanel, MigrationPendingBanner, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
2537
+ export { AppMode, CalimeroLogo, ConnectButton, ConnectionType, DEFAULT_LOCAL_NODE_PORTS, LoginModal, MERO_CSS_VARS, MeroContext, MeroProvider, MigrationAdminPanel, MigrationPendingBanner, clearAllStorage, clearApplicationId, clearContextId, clearContextIdentity, clearNodeUrl, clearTokenNodeUrl, cssVar, defaultMeroTheme, discoverLocalNodes, getApplicationId, getContextId, getContextIdentity, getNodeUrl, getTokenNodeUrl, localNodeUrl, localStorageTokenStorage, nodeEndpoint, probeNodeHealth, resolveMeroTheme, setApplicationId, setContextId, setContextIdentity, setNodeUrl, setTokenNodeUrl, themeToCssVars, useAddGroupMembers, useAppVersion, useApplicationContexts, useContextDiscovery, useContextGroup, useContexts, useCreateContext, useCreateGroupInNamespace, useCreateNamespace, useCreateNamespaceInvitation, useDefaultCapabilities, useDeleteContext, useDeleteGroup, useDeleteNamespace, useDetachContextFromGroup, useExecute, useGroupCapabilities, useGroupContexts, useGroupInfo, useGroupInvitations, useGroupMembers, useGroupMetadata, useGroupUpgradeStatus, useJoinContext, useJoinGroup, useJoinNamespace, useJoinSubgroupInheritance, useLatestVersion, useMemberMetadata, useMero, useMigrationStatus, useMyAuthoredMigration, useNamespace, useNamespaceGroups, useNamespaceIdentity, useNamespaces, useNamespacesForApplication, useRegisterGroupSigningKey, useRemoveGroupMembers, useReparentGroup, useResyncContext, useRetryGroupUpgrade, useSetContextMetadata, useSetDefaultCapabilities, useSetGroupMetadata, useSetMemberMetadata, useSetSubgroupVisibility, useSetTeeAdmissionPolicy, useSubgroupVisibility, useSubgroups, useSubscription, useSyncGroup, useUpdateGroupSettings, useUpdateMemberRole, useUpgradeGroup };
2456
2538
  //# sourceMappingURL=index.js.map
2457
2539
  //# sourceMappingURL=index.js.map