@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.cjs CHANGED
@@ -2162,7 +2162,12 @@ function PasskeyEnrollView({
2162
2162
  }
2163
2163
  if (migrationInfo.needsMigration && migrationInfo.sourcePasskey) {
2164
2164
  const handleMigration = async () => {
2165
- await handleEnroll();
2165
+ const targetOrigin = typeof window !== "undefined" ? window.location.origin : "";
2166
+ const sourceRpId = migrationInfo.sourcePasskey.rpId;
2167
+ const sourceOrigin = sourceRpId === "localhost" ? "http://localhost" : `https://${sourceRpId}`;
2168
+ const url = new URL(sourceOrigin);
2169
+ url.searchParams.set("volr_migrate_to", targetOrigin);
2170
+ window.open(url.toString(), "_blank", "noopener,noreferrer");
2166
2171
  };
2167
2172
  return /* @__PURE__ */ jsxRuntime.jsx(
2168
2173
  PasskeyMigrationView,
@@ -5130,6 +5135,8 @@ function AccountModal({ isOpen, onClose, onError }) {
5130
5135
  const [currentView, setCurrentView] = React13.useState("main");
5131
5136
  const [selectedPayment, setSelectedPayment] = React13.useState(null);
5132
5137
  const [isLoggingOut, setIsLoggingOut] = React13.useState(false);
5138
+ const prevUserRef = React13.useRef(user ?? null);
5139
+ const [closingAfterLogin, setClosingAfterLogin] = React13.useState(false);
5133
5140
  const [depositAssets, setDepositAssets] = React13.useState([]);
5134
5141
  const [depositLoading, setDepositLoading] = React13.useState(false);
5135
5142
  const [depositError, setDepositError] = React13.useState(null);
@@ -5160,6 +5167,29 @@ function AccountModal({ isOpen, onClose, onError }) {
5160
5167
  }, 200);
5161
5168
  }
5162
5169
  }, [onClose]);
5170
+ React13.useEffect(() => {
5171
+ if (!isOpen) {
5172
+ setClosingAfterLogin(false);
5173
+ prevUserRef.current = user ?? null;
5174
+ return;
5175
+ }
5176
+ const wasLoggedOut = prevUserRef.current == null;
5177
+ const isNowLoggedIn = user != null;
5178
+ if (wasLoggedOut && isNowLoggedIn) {
5179
+ setClosingAfterLogin(true);
5180
+ setTimeout(() => {
5181
+ onClose();
5182
+ }, 0);
5183
+ }
5184
+ prevUserRef.current = user ?? null;
5185
+ }, [isOpen, user, onClose]);
5186
+ if (closingAfterLogin && isOpen) {
5187
+ return /* @__PURE__ */ jsxRuntime.jsxs(Modal, { open: isOpen, onOpenChange: handleOpenChange, children: [
5188
+ /* @__PURE__ */ jsxRuntime.jsx(ModalHeader, { onClose: () => {
5189
+ } }),
5190
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "volr:text-center volr:py-8", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "volr:text-sm volr-text-secondary", children: "Completing login..." }) })
5191
+ ] });
5192
+ }
5163
5193
  if (!user) {
5164
5194
  return /* @__PURE__ */ jsxRuntime.jsx(SigninModal, { isOpen, onClose, onError });
5165
5195
  }
@@ -6637,6 +6667,17 @@ function SignRequestModal({ open, onOpenChange }) {
6637
6667
  );
6638
6668
  }
6639
6669
  var VolrUIContext = React13__default.default.createContext(null);
6670
+ function getCurrentRpId() {
6671
+ if (typeof window === "undefined") return "localhost";
6672
+ return window.location.hostname;
6673
+ }
6674
+ function isPasskeyDomainMismatch(user) {
6675
+ const currentRpId = getCurrentRpId();
6676
+ const registered = user?.registeredPasskeys;
6677
+ if (user?.keyStorageType !== "passkey") return false;
6678
+ if (!Array.isArray(registered) || registered.length === 0) return false;
6679
+ return !registered.some((p) => p?.rpId === currentRpId);
6680
+ }
6640
6681
  var useVolrUI = () => {
6641
6682
  const context = React13.useContext(VolrUIContext);
6642
6683
  if (!context) {
@@ -6767,6 +6808,13 @@ function VolrUIProviderInner({
6767
6808
  keyStorageType
6768
6809
  },
6769
6810
  children: /* @__PURE__ */ jsxRuntime.jsxs(VolrModalProvider, { children: [
6811
+ /* @__PURE__ */ jsxRuntime.jsx(
6812
+ MigrationCoordinator,
6813
+ {
6814
+ onShowOnboarding: () => setShowOnboarding(true),
6815
+ onHideOnboarding: () => setShowOnboarding(false)
6816
+ }
6817
+ ),
6770
6818
  /* @__PURE__ */ jsxRuntime.jsx(
6771
6819
  OAuthCallbackHandler,
6772
6820
  {
@@ -6809,6 +6857,130 @@ function VolrUIProviderInner({
6809
6857
  }
6810
6858
  ) }) });
6811
6859
  }
6860
+ function normalizeOrigin(input) {
6861
+ try {
6862
+ const u = new URL(input);
6863
+ return `${u.protocol}//${u.host}`;
6864
+ } catch {
6865
+ return input;
6866
+ }
6867
+ }
6868
+ function MigrationCoordinator({
6869
+ onShowOnboarding,
6870
+ onHideOnboarding
6871
+ }) {
6872
+ const { user, setUser } = react.useVolrContext();
6873
+ const { client } = react.useInternalAuth();
6874
+ const [running, setRunning] = React13.useState(false);
6875
+ React13.useEffect(() => {
6876
+ if (typeof window === "undefined") return;
6877
+ const handler = (event) => {
6878
+ const data = event.data;
6879
+ if (data?.type === "VOLR_MIGRATION_DONE") {
6880
+ client.refreshSession().then((u) => u && setUser(u)).catch(() => void 0);
6881
+ onHideOnboarding();
6882
+ }
6883
+ };
6884
+ window.addEventListener("message", handler);
6885
+ return () => window.removeEventListener("message", handler);
6886
+ }, [client, setUser, onHideOnboarding]);
6887
+ React13.useEffect(() => {
6888
+ if (typeof window === "undefined") return;
6889
+ if (running) return;
6890
+ const params = new URLSearchParams(window.location.search);
6891
+ const migrationToken = params.get("migration_token");
6892
+ const sourceOriginParam = params.get("source_origin");
6893
+ const isTargetPopup = Boolean(migrationToken && sourceOriginParam);
6894
+ const migrateTo = params.get("volr_migrate_to");
6895
+ const isSourceFlow = Boolean(migrateTo);
6896
+ if (!isTargetPopup && !isSourceFlow) {
6897
+ return;
6898
+ }
6899
+ const run = async () => {
6900
+ setRunning(true);
6901
+ try {
6902
+ if (isTargetPopup) {
6903
+ const sourceOrigin2 = normalizeOrigin(sourceOriginParam);
6904
+ const token2 = migrationToken;
6905
+ const refreshedUser2 = await client.refreshSession();
6906
+ const effectiveUser2 = refreshedUser2 ?? user;
6907
+ if (refreshedUser2) setUser(refreshedUser2);
6908
+ if (!effectiveUser2?.id || !effectiveUser2?.projectId) {
6909
+ throw new Error(
6910
+ "Migration requires an active session on the target site. Please log in first."
6911
+ );
6912
+ }
6913
+ const { requestSeedFromOpener } = await import('@volr/react');
6914
+ const seed = await requestSeedFromOpener(sourceOrigin2, token2);
6915
+ await react.completeMigration({
6916
+ client,
6917
+ userId: seed.userId,
6918
+ projectId: seed.projectId,
6919
+ migrationToken: token2,
6920
+ masterSeed: seed.masterSeed,
6921
+ rpName: effectiveUser2.projectName ?? "Volr",
6922
+ userEmail: effectiveUser2.email ?? null
6923
+ });
6924
+ try {
6925
+ window.opener?.postMessage({ type: "VOLR_MIGRATION_DONE" }, "*");
6926
+ } catch {
6927
+ }
6928
+ window.close();
6929
+ return;
6930
+ }
6931
+ const targetOrigin = normalizeOrigin(migrateTo);
6932
+ const sourceOrigin = window.location.origin;
6933
+ const refreshedUser = await client.refreshSession();
6934
+ const effectiveUser = refreshedUser ?? user;
6935
+ if (refreshedUser) setUser(refreshedUser);
6936
+ if (!effectiveUser?.id || !effectiveUser?.projectId) {
6937
+ throw new Error(
6938
+ "Migration requires an active session on the source site. Please log in first."
6939
+ );
6940
+ }
6941
+ const currentRpId = getCurrentRpId();
6942
+ const sourcePasskey = effectiveUser.registeredPasskeys?.find((p) => p?.rpId === currentRpId) ?? null;
6943
+ if (!sourcePasskey?.credentialId || !sourcePasskey?.blobUrl || !sourcePasskey?.prfInput) {
6944
+ throw new Error(
6945
+ `No passkey registered for source rpId=${currentRpId}. Cannot migrate from this site.`
6946
+ );
6947
+ }
6948
+ const token = await react.requestMigration({ client, targetOrigin });
6949
+ const entropy = await react.decryptEntropyForMigration({
6950
+ client,
6951
+ userId: effectiveUser.id,
6952
+ blobUrl: sourcePasskey.blobUrl,
6953
+ prfInput: sourcePasskey.prfInput,
6954
+ credentialId: sourcePasskey.credentialId,
6955
+ rpName: effectiveUser.projectName ?? "Volr"
6956
+ });
6957
+ const cleanup = react.listenForSeedRequests(
6958
+ [targetOrigin],
6959
+ async () => entropy,
6960
+ () => ({ userId: effectiveUser.id, projectId: effectiveUser.projectId })
6961
+ );
6962
+ const url = new URL(targetOrigin);
6963
+ url.searchParams.set("migration_token", token.migrationToken);
6964
+ url.searchParams.set("source_origin", sourceOrigin);
6965
+ window.open(
6966
+ url.toString(),
6967
+ "volr_migration",
6968
+ "width=500,height=650,left=100,top=100,popup=1"
6969
+ );
6970
+ onShowOnboarding();
6971
+ setTimeout(() => cleanup(), 2 * 60 * 1e3);
6972
+ return;
6973
+ } finally {
6974
+ setRunning(false);
6975
+ }
6976
+ };
6977
+ run().catch((e) => {
6978
+ console.error("[MigrationCoordinator] failed:", e);
6979
+ setRunning(false);
6980
+ });
6981
+ }, [client, user, setUser, running, onShowOnboarding, onHideOnboarding]);
6982
+ return null;
6983
+ }
6812
6984
  function AccountModalPortal() {
6813
6985
  const { isOpen, mode, close } = useVolrModal();
6814
6986
  const [portalRoot, setPortalRoot] = React13.useState(null);
@@ -7038,6 +7210,7 @@ function OnboardingChecker({
7038
7210
  const { user, provider, isLoading } = react.useVolrContext();
7039
7211
  const { isOpen: isModalOpen } = useVolrModal();
7040
7212
  const modalWasOpened = React13__default.default.useRef(false);
7213
+ const pendingMismatchOnboarding = React13__default.default.useRef(false);
7041
7214
  React13.useEffect(() => {
7042
7215
  if (isModalOpen) {
7043
7216
  modalWasOpened.current = true;
@@ -7047,15 +7220,21 @@ function OnboardingChecker({
7047
7220
  if (isLoading) {
7048
7221
  return;
7049
7222
  }
7223
+ const passkeyDomainMismatch = isPasskeyDomainMismatch(user);
7050
7224
  if (isModalOpen) {
7225
+ if (passkeyDomainMismatch) {
7226
+ pendingMismatchOnboarding.current = true;
7227
+ }
7051
7228
  onHideOnboarding();
7052
7229
  return;
7053
7230
  }
7054
7231
  if (modalWasOpened.current) {
7055
- onHideOnboarding();
7056
- return;
7232
+ if (!passkeyDomainMismatch && !pendingMismatchOnboarding.current) {
7233
+ onHideOnboarding();
7234
+ return;
7235
+ }
7057
7236
  }
7058
- if (user?.keyStorageType) {
7237
+ if (user?.keyStorageType && !passkeyDomainMismatch && !pendingMismatchOnboarding.current) {
7059
7238
  onHideOnboarding();
7060
7239
  return;
7061
7240
  }
@@ -7063,6 +7242,15 @@ function OnboardingChecker({
7063
7242
  onHideOnboarding();
7064
7243
  return;
7065
7244
  }
7245
+ if (pendingMismatchOnboarding.current) {
7246
+ pendingMismatchOnboarding.current = false;
7247
+ onShowOnboarding();
7248
+ return;
7249
+ }
7250
+ if (passkeyDomainMismatch) {
7251
+ onShowOnboarding();
7252
+ return;
7253
+ }
7066
7254
  if (user && enforceSelection) {
7067
7255
  if (!keyStorageType) {
7068
7256
  console.error(