@volr/react-ui 0.1.124 → 0.1.126

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,6 +1,6 @@
1
1
  import React13, { createContext, useContext, useState, useMemo, useEffect, useCallback, useRef, useId, useReducer } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import { useVolrContext, useInternalAuth, usePasskeyEnrollment, checkPrfCompatibility, getPlatformHint, useMpcConnection, VolrProvider, useVolrAuthCallback, useVolrPaymentApi, useUserBalances, useVolrLogin, useVolr, useWithdraw, useDepositListener, createGetNetworkInfo, useEIP6963 } from '@volr/react';
3
+ import { useVolrContext, useInternalAuth, usePasskeyEnrollment, checkPrfCompatibility, getPlatformHint, useMpcConnection, VolrProvider, useVolrAuthCallback, useVolrPaymentApi, completeMigration, requestMigration, decryptEntropyForMigration, listenForSeedRequests, useUserBalances, useVolrLogin, useVolr, useWithdraw, useDepositListener, createGetNetworkInfo, useEIP6963 } from '@volr/react';
4
4
  export { VolrProvider, useDepositListener, usePasskeyEnrollment, useVolr, useVolrLogin, useVolrPaymentApi } from '@volr/react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { clsx } from 'clsx';
@@ -2157,7 +2157,12 @@ function PasskeyEnrollView({
2157
2157
  }
2158
2158
  if (migrationInfo.needsMigration && migrationInfo.sourcePasskey) {
2159
2159
  const handleMigration = async () => {
2160
- await handleEnroll();
2160
+ const targetOrigin = typeof window !== "undefined" ? window.location.origin : "";
2161
+ const sourceRpId = migrationInfo.sourcePasskey.rpId;
2162
+ const sourceOrigin = sourceRpId === "localhost" ? "http://localhost" : `https://${sourceRpId}`;
2163
+ const url = new URL(sourceOrigin);
2164
+ url.searchParams.set("volr_migrate_to", targetOrigin);
2165
+ window.open(url.toString(), "_blank", "noopener,noreferrer");
2161
2166
  };
2162
2167
  return /* @__PURE__ */ jsx(
2163
2168
  PasskeyMigrationView,
@@ -5125,6 +5130,8 @@ function AccountModal({ isOpen, onClose, onError }) {
5125
5130
  const [currentView, setCurrentView] = useState("main");
5126
5131
  const [selectedPayment, setSelectedPayment] = useState(null);
5127
5132
  const [isLoggingOut, setIsLoggingOut] = useState(false);
5133
+ const prevUserRef = useRef(user ?? null);
5134
+ const [closingAfterLogin, setClosingAfterLogin] = useState(false);
5128
5135
  const [depositAssets, setDepositAssets] = useState([]);
5129
5136
  const [depositLoading, setDepositLoading] = useState(false);
5130
5137
  const [depositError, setDepositError] = useState(null);
@@ -5155,6 +5162,29 @@ function AccountModal({ isOpen, onClose, onError }) {
5155
5162
  }, 200);
5156
5163
  }
5157
5164
  }, [onClose]);
5165
+ useEffect(() => {
5166
+ if (!isOpen) {
5167
+ setClosingAfterLogin(false);
5168
+ prevUserRef.current = user ?? null;
5169
+ return;
5170
+ }
5171
+ const wasLoggedOut = prevUserRef.current == null;
5172
+ const isNowLoggedIn = user != null;
5173
+ if (wasLoggedOut && isNowLoggedIn) {
5174
+ setClosingAfterLogin(true);
5175
+ setTimeout(() => {
5176
+ onClose();
5177
+ }, 0);
5178
+ }
5179
+ prevUserRef.current = user ?? null;
5180
+ }, [isOpen, user, onClose]);
5181
+ if (closingAfterLogin && isOpen) {
5182
+ return /* @__PURE__ */ jsxs(Modal, { open: isOpen, onOpenChange: handleOpenChange, children: [
5183
+ /* @__PURE__ */ jsx(ModalHeader, { onClose: () => {
5184
+ } }),
5185
+ /* @__PURE__ */ jsx("div", { className: "volr:text-center volr:py-8", children: /* @__PURE__ */ jsx("p", { className: "volr:text-sm volr-text-secondary", children: "Completing login..." }) })
5186
+ ] });
5187
+ }
5158
5188
  if (!user) {
5159
5189
  return /* @__PURE__ */ jsx(SigninModal, { isOpen, onClose, onError });
5160
5190
  }
@@ -6632,6 +6662,17 @@ function SignRequestModal({ open, onOpenChange }) {
6632
6662
  );
6633
6663
  }
6634
6664
  var VolrUIContext = React13.createContext(null);
6665
+ function getCurrentRpId() {
6666
+ if (typeof window === "undefined") return "localhost";
6667
+ return window.location.hostname;
6668
+ }
6669
+ function isPasskeyDomainMismatch(user) {
6670
+ const currentRpId = getCurrentRpId();
6671
+ const registered = user?.registeredPasskeys;
6672
+ if (user?.keyStorageType !== "passkey") return false;
6673
+ if (!Array.isArray(registered) || registered.length === 0) return false;
6674
+ return !registered.some((p) => p?.rpId === currentRpId);
6675
+ }
6635
6676
  var useVolrUI = () => {
6636
6677
  const context = useContext(VolrUIContext);
6637
6678
  if (!context) {
@@ -6762,6 +6803,13 @@ function VolrUIProviderInner({
6762
6803
  keyStorageType
6763
6804
  },
6764
6805
  children: /* @__PURE__ */ jsxs(VolrModalProvider, { children: [
6806
+ /* @__PURE__ */ jsx(
6807
+ MigrationCoordinator,
6808
+ {
6809
+ onShowOnboarding: () => setShowOnboarding(true),
6810
+ onHideOnboarding: () => setShowOnboarding(false)
6811
+ }
6812
+ ),
6765
6813
  /* @__PURE__ */ jsx(
6766
6814
  OAuthCallbackHandler,
6767
6815
  {
@@ -6804,6 +6852,130 @@ function VolrUIProviderInner({
6804
6852
  }
6805
6853
  ) }) });
6806
6854
  }
6855
+ function normalizeOrigin(input) {
6856
+ try {
6857
+ const u = new URL(input);
6858
+ return `${u.protocol}//${u.host}`;
6859
+ } catch {
6860
+ return input;
6861
+ }
6862
+ }
6863
+ function MigrationCoordinator({
6864
+ onShowOnboarding,
6865
+ onHideOnboarding
6866
+ }) {
6867
+ const { user, setUser } = useVolrContext();
6868
+ const { client } = useInternalAuth();
6869
+ const [running, setRunning] = useState(false);
6870
+ useEffect(() => {
6871
+ if (typeof window === "undefined") return;
6872
+ const handler = (event) => {
6873
+ const data = event.data;
6874
+ if (data?.type === "VOLR_MIGRATION_DONE") {
6875
+ client.refreshSession().then((u) => u && setUser(u)).catch(() => void 0);
6876
+ onHideOnboarding();
6877
+ }
6878
+ };
6879
+ window.addEventListener("message", handler);
6880
+ return () => window.removeEventListener("message", handler);
6881
+ }, [client, setUser, onHideOnboarding]);
6882
+ useEffect(() => {
6883
+ if (typeof window === "undefined") return;
6884
+ if (running) return;
6885
+ const params = new URLSearchParams(window.location.search);
6886
+ const migrationToken = params.get("migration_token");
6887
+ const sourceOriginParam = params.get("source_origin");
6888
+ const isTargetPopup = Boolean(migrationToken && sourceOriginParam);
6889
+ const migrateTo = params.get("volr_migrate_to");
6890
+ const isSourceFlow = Boolean(migrateTo);
6891
+ if (!isTargetPopup && !isSourceFlow) {
6892
+ return;
6893
+ }
6894
+ const run = async () => {
6895
+ setRunning(true);
6896
+ try {
6897
+ if (isTargetPopup) {
6898
+ const sourceOrigin2 = normalizeOrigin(sourceOriginParam);
6899
+ const token2 = migrationToken;
6900
+ const refreshedUser2 = await client.refreshSession();
6901
+ const effectiveUser2 = refreshedUser2 ?? user;
6902
+ if (refreshedUser2) setUser(refreshedUser2);
6903
+ if (!effectiveUser2?.id || !effectiveUser2?.projectId) {
6904
+ throw new Error(
6905
+ "Migration requires an active session on the target site. Please log in first."
6906
+ );
6907
+ }
6908
+ const { requestSeedFromOpener } = await import('@volr/react');
6909
+ const seed = await requestSeedFromOpener(sourceOrigin2, token2);
6910
+ await completeMigration({
6911
+ client,
6912
+ userId: seed.userId,
6913
+ projectId: seed.projectId,
6914
+ migrationToken: token2,
6915
+ masterSeed: seed.masterSeed,
6916
+ rpName: effectiveUser2.projectName ?? "Volr",
6917
+ userEmail: effectiveUser2.email ?? null
6918
+ });
6919
+ try {
6920
+ window.opener?.postMessage({ type: "VOLR_MIGRATION_DONE" }, "*");
6921
+ } catch {
6922
+ }
6923
+ window.close();
6924
+ return;
6925
+ }
6926
+ const targetOrigin = normalizeOrigin(migrateTo);
6927
+ const sourceOrigin = window.location.origin;
6928
+ const refreshedUser = await client.refreshSession();
6929
+ const effectiveUser = refreshedUser ?? user;
6930
+ if (refreshedUser) setUser(refreshedUser);
6931
+ if (!effectiveUser?.id || !effectiveUser?.projectId) {
6932
+ throw new Error(
6933
+ "Migration requires an active session on the source site. Please log in first."
6934
+ );
6935
+ }
6936
+ const currentRpId = getCurrentRpId();
6937
+ const sourcePasskey = effectiveUser.registeredPasskeys?.find((p) => p?.rpId === currentRpId) ?? null;
6938
+ if (!sourcePasskey?.credentialId || !sourcePasskey?.blobUrl || !sourcePasskey?.prfInput) {
6939
+ throw new Error(
6940
+ `No passkey registered for source rpId=${currentRpId}. Cannot migrate from this site.`
6941
+ );
6942
+ }
6943
+ const token = await requestMigration({ client, targetOrigin });
6944
+ const entropy = await decryptEntropyForMigration({
6945
+ client,
6946
+ userId: effectiveUser.id,
6947
+ blobUrl: sourcePasskey.blobUrl,
6948
+ prfInput: sourcePasskey.prfInput,
6949
+ credentialId: sourcePasskey.credentialId,
6950
+ rpName: effectiveUser.projectName ?? "Volr"
6951
+ });
6952
+ const cleanup = listenForSeedRequests(
6953
+ [targetOrigin],
6954
+ async () => entropy,
6955
+ () => ({ userId: effectiveUser.id, projectId: effectiveUser.projectId })
6956
+ );
6957
+ const url = new URL(targetOrigin);
6958
+ url.searchParams.set("migration_token", token.migrationToken);
6959
+ url.searchParams.set("source_origin", sourceOrigin);
6960
+ window.open(
6961
+ url.toString(),
6962
+ "volr_migration",
6963
+ "width=500,height=650,left=100,top=100,popup=1"
6964
+ );
6965
+ onShowOnboarding();
6966
+ setTimeout(() => cleanup(), 2 * 60 * 1e3);
6967
+ return;
6968
+ } finally {
6969
+ setRunning(false);
6970
+ }
6971
+ };
6972
+ run().catch((e) => {
6973
+ console.error("[MigrationCoordinator] failed:", e);
6974
+ setRunning(false);
6975
+ });
6976
+ }, [client, user, setUser, running, onShowOnboarding, onHideOnboarding]);
6977
+ return null;
6978
+ }
6807
6979
  function AccountModalPortal() {
6808
6980
  const { isOpen, mode, close } = useVolrModal();
6809
6981
  const [portalRoot, setPortalRoot] = useState(null);
@@ -7033,6 +7205,7 @@ function OnboardingChecker({
7033
7205
  const { user, provider, isLoading } = useVolrContext();
7034
7206
  const { isOpen: isModalOpen } = useVolrModal();
7035
7207
  const modalWasOpened = React13.useRef(false);
7208
+ const pendingMismatchOnboarding = React13.useRef(false);
7036
7209
  useEffect(() => {
7037
7210
  if (isModalOpen) {
7038
7211
  modalWasOpened.current = true;
@@ -7042,15 +7215,21 @@ function OnboardingChecker({
7042
7215
  if (isLoading) {
7043
7216
  return;
7044
7217
  }
7218
+ const passkeyDomainMismatch = isPasskeyDomainMismatch(user);
7045
7219
  if (isModalOpen) {
7220
+ if (passkeyDomainMismatch) {
7221
+ pendingMismatchOnboarding.current = true;
7222
+ }
7046
7223
  onHideOnboarding();
7047
7224
  return;
7048
7225
  }
7049
7226
  if (modalWasOpened.current) {
7050
- onHideOnboarding();
7051
- return;
7227
+ if (!passkeyDomainMismatch && !pendingMismatchOnboarding.current) {
7228
+ onHideOnboarding();
7229
+ return;
7230
+ }
7052
7231
  }
7053
- if (user?.keyStorageType) {
7232
+ if (user?.keyStorageType && !passkeyDomainMismatch && !pendingMismatchOnboarding.current) {
7054
7233
  onHideOnboarding();
7055
7234
  return;
7056
7235
  }
@@ -7058,6 +7237,15 @@ function OnboardingChecker({
7058
7237
  onHideOnboarding();
7059
7238
  return;
7060
7239
  }
7240
+ if (pendingMismatchOnboarding.current) {
7241
+ pendingMismatchOnboarding.current = false;
7242
+ onShowOnboarding();
7243
+ return;
7244
+ }
7245
+ if (passkeyDomainMismatch) {
7246
+ onShowOnboarding();
7247
+ return;
7248
+ }
7061
7249
  if (user && enforceSelection) {
7062
7250
  if (!keyStorageType) {
7063
7251
  console.error(