feat(wallet): bypass WalletConnect for TP/imToken on mobile to fix China users
Some checks failed
Deploy Staging (terry-wallet-login) / deploy (push) Failing after 40s

The WalletConnect relay (wss://relay.walletconnect.org) is unreliable/blocked
in mainland China. Every wallet flow (desktop QR, mobile deeplink, mobile QR)
depends on it, so Chinese users see the login button hang forever and the
QR code never appears. When RainbowKit's render fails, the whole site goes
white because nothing catches the error.

Changes:
- Add WalletStackErrorBoundary around <RainbowWalletProvider> + modal so
  RainbowKit init failures no longer blank the entire app.
- Hoist <WalletProvider> above the boundary; it only depends on the injected
  provider, so useWallet keeps working for header / favorites / etc. even
  when the WC stack is dead.
- On mobile, the TP/imToken 'Open Wallet App' button now navigates directly
  to tpdapp://open / imtokenv2://navigate/DappView with an ?autoLogin=<kind>
  query, pulling the site into the wallet's in-app browser without ever
  touching the WC relay. MetaMask still uses the WC path (no equivalent
  deeplink).
- Add AutoInjectedLogin: when the page loads with ?autoLogin=<kind>, wait
  up to 8s for window.ethereum, then connectInjectedWallet + completeLogin.
  Strips the param via history.replaceState to avoid re-firing on reload.
- Guard against the in-app-browser disconnect/reconnect case: if
  getInjectedWallet(kind) is already truthy, skip the deeplink and let
  useWalletConnectLogin's deeplink mode take the injected fast path
  (avoids TP trying to open TP recursively).
This commit is contained in:
TerryM
2026-06-03 20:07:23 +08:00
parent b4ef5ddb61
commit 6800a8e9b6
4 changed files with 242 additions and 109 deletions

View File

@@ -4,9 +4,11 @@ import { MotionProvider } from "./motion";
import { ToastProvider } from "./components/Toast";
import { SaveToAlbumGuideProvider } from "./components/SaveToAlbumGuide";
import { FavoritesProvider } from "./favorites/FavoritesProvider";
import { AutoInjectedLogin } from "./wallet/AutoInjectedLogin";
import { RainbowWalletProvider } from "./wallet/RainbowWalletProvider";
import { WalletLoginModal } from "./wallet/WalletLoginModal";
import { WalletProvider } from "./wallet/WalletProvider";
import { WalletStackErrorBoundary } from "./wallet/WalletStackErrorBoundary";
import { PublicLayout } from "./layouts/PublicLayout";
import { LocalizedHomePage } from "./pages/LocalizedHome";
import { Browse } from "./pages/Browse";
@@ -32,115 +34,113 @@ export default function App() {
<I18nProvider>
<MotionProvider>
<ToastProvider>
<RainbowWalletProvider>
<WalletProvider>
<FavoritesProvider>
<SaveToAlbumGuideProvider>
<AdminRouterModeProvider value="absolute">
<ImageLightboxProvider>
<VideoPlayerProvider>
<PageTitleProvider>
<WalletLoginModal />
<BrowserRouter>
<ScrollToTop />
<Routes>
<Route element={<PublicLayout />}>
{/* English (root, no prefix) */}
<Route
path="/"
element={
<LocalizedHomePage targetLang="en" />
}
/>
<Route path="/browse" element={<Browse />} />
<Route
path="/categories"
element={<CategoriesPage />}
/>
<Route
path="/official-recommendations"
element={<OfficialRecommendationsPage />}
/>
<Route
path="/category/:slug"
element={<CategoryPage />}
/>
<Route
path="/search"
element={<SearchPage />}
/>
<Route
path="/resource/:id"
element={<PostRedirect />}
/>
<Route
path="/favorites"
element={<Favorites />}
/>
{/* Each non-English language gets its own nested tree. */}
{localizedHomeRoutes.map((route) => (
<Route key={route.path} path={route.path}>
<Route
index
element={
<LocalizedHomePage
targetLang={route.lang}
/>
}
/>
<Route path="browse" element={<Browse />} />
<Route
path="categories"
element={<CategoriesPage />}
/>
<Route
path="official-recommendations"
element={<OfficialRecommendationsPage />}
/>
<Route
path="category/:slug"
element={<CategoryPage />}
/>
<Route
path="search"
element={<SearchPage />}
/>
<Route
path="resource/:id"
element={<PostRedirect />}
/>
<Route
path="favorites"
element={<Favorites />}
/>
</Route>
))}
</Route>
{adminEnabled ? (
AdminRouteTree()
) : (
<Route
path={`${adminUiPrefix}/*`}
element={<Navigate to="/" replace />}
/>
)}
<WalletProvider>
<AutoInjectedLogin />
<WalletStackErrorBoundary>
<RainbowWalletProvider>
<WalletLoginModal />
</RainbowWalletProvider>
</WalletStackErrorBoundary>
<FavoritesProvider>
<SaveToAlbumGuideProvider>
<AdminRouterModeProvider value="absolute">
<ImageLightboxProvider>
<VideoPlayerProvider>
<PageTitleProvider>
<BrowserRouter>
<ScrollToTop />
<Routes>
<Route element={<PublicLayout />}>
{/* English (root, no prefix) */}
<Route
path="*"
path="/"
element={<LocalizedHomePage targetLang="en" />}
/>
<Route path="/browse" element={<Browse />} />
<Route
path="/categories"
element={<CategoriesPage />}
/>
<Route
path="/official-recommendations"
element={<OfficialRecommendationsPage />}
/>
<Route
path="/category/:slug"
element={<CategoryPage />}
/>
<Route path="/search" element={<SearchPage />} />
<Route
path="/resource/:id"
element={<PostRedirect />}
/>
<Route
path="/favorites"
element={<Favorites />}
/>
{/* Each non-English language gets its own nested tree. */}
{localizedHomeRoutes.map((route) => (
<Route key={route.path} path={route.path}>
<Route
index
element={
<LocalizedHomePage
targetLang={route.lang}
/>
}
/>
<Route path="browse" element={<Browse />} />
<Route
path="categories"
element={<CategoriesPage />}
/>
<Route
path="official-recommendations"
element={<OfficialRecommendationsPage />}
/>
<Route
path="category/:slug"
element={<CategoryPage />}
/>
<Route
path="search"
element={<SearchPage />}
/>
<Route
path="resource/:id"
element={<PostRedirect />}
/>
<Route
path="favorites"
element={<Favorites />}
/>
</Route>
))}
</Route>
{adminEnabled ? (
AdminRouteTree()
) : (
<Route
path={`${adminUiPrefix}/*`}
element={<Navigate to="/" replace />}
/>
</Routes>
</BrowserRouter>
</PageTitleProvider>
</VideoPlayerProvider>
</ImageLightboxProvider>
</AdminRouterModeProvider>
</SaveToAlbumGuideProvider>
</FavoritesProvider>
</WalletProvider>
</RainbowWalletProvider>
)}
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</BrowserRouter>
</PageTitleProvider>
</VideoPlayerProvider>
</ImageLightboxProvider>
</AdminRouterModeProvider>
</SaveToAlbumGuideProvider>
</FavoritesProvider>
</WalletProvider>
</ToastProvider>
</MotionProvider>
</I18nProvider>