diff --git a/docs/pages/wallets/auth/google-oauth.mdx b/docs/pages/wallets/auth/google-oauth.mdx index 9ea3f25..9866737 100644 --- a/docs/pages/wallets/auth/google-oauth.mdx +++ b/docs/pages/wallets/auth/google-oauth.mdx @@ -8,12 +8,14 @@ This page covers the web flow. For Expo / React Native, see [Google OAuth on React Native](/wallets/react-native/google-oauth). ::: -Google OAuth lets users sign in with their Google account using a popup-based flow. The SDK handles the popup, redirect back to your app, and session creation automatically. +Google OAuth lets users sign in with their Google account using a popup-based flow. The SDK handles the popup, returns it to the same route that started auth, and completes session creation automatically. By default, the Google OAuth flow uses a Zerodev-managed Google account for authentication. This allows you to get started quickly without any additional configuration. You can customize the OAuth flow by providing your own Google OAuth client ID and secret in the developer dashboard. > **Important:** Using custom credentials requires creating a new project with no wallets already created. You cannot switch an existing project from the default Zerodev-managed OAuth setup to a custom client configuration. +In the standard web flow, you do **not** need a dedicated `/oauth-callback` page. Render the auth button on the page that should receive the popup back, and keep the ZeroDev provider mounted there. + ## Hook - [`useAuthenticateOAuth`](/wallets/hooks/use-authenticate-oauth) — Trigger the OAuth flow @@ -72,7 +74,7 @@ function GoogleAuth() { 3. **Open popup**: The SDK opens a popup to that verified Google login URL and polls the popup while the user signs in with Google. -4. **Popup redirect**: After a successful sign-in, the popup returns to your app's origin with `?oauth_success=true&session_id=...`. +4. **Popup redirect**: After a successful sign-in, the popup returns to the same origin and pathname that started auth, with `?oauth_success=true&session_id=...`. 5. **Complete auth**: The SDK reads `session_id` from the popup URL, completes wallet authentication with it, and then connects the Wagmi connector. diff --git a/docs/pages/wallets/hooks/use-authenticate-oauth.mdx b/docs/pages/wallets/hooks/use-authenticate-oauth.mdx index a09ccc6..1a0aea2 100644 --- a/docs/pages/wallets/hooks/use-authenticate-oauth.mdx +++ b/docs/pages/wallets/hooks/use-authenticate-oauth.mdx @@ -6,6 +6,8 @@ import MutationResult from '../shared/mutation-result.mdx' **USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time. ::: +On web this hook opens a popup and returns it to the current page. Unlike the React Native Expo hook, the common web flow does not require a separate callback route or a `redirectUri` parameter. + ## Import ```tsx @@ -44,7 +46,15 @@ function OAuthLogin() { } ``` -## Parameters +## Hook Parameters + +### timeoutMs + +`number | undefined` + +Optional timeout for the popup polling flow, in milliseconds. Defaults to `300000` (5 minutes). + +## Mutation Parameters ### provider @@ -62,7 +72,7 @@ function OAuthLogin() { `(variables: { provider: OAuthProvider }) => void` -The mutation function to start the OAuth flow. Opens a popup window for the user to sign in. +The mutation function to start the OAuth flow. Opens a popup window for the user to sign in, then waits for that popup to return to the current page's origin. ### mutateAsync diff --git a/docs/pages/wallets/react-native/configuration.mdx b/docs/pages/wallets/react-native/configuration.mdx index 823b5f8..59a2a61 100644 --- a/docs/pages/wallets/react-native/configuration.mdx +++ b/docs/pages/wallets/react-native/configuration.mdx @@ -168,3 +168,5 @@ bunx expo install @react-native-async-storage/async-storage - **Crypto polyfill** — import `react-native-get-random-values` at the very top of your app's entry file (see the [quickstart](/wallets/react-native/quickstart)). - **Wallet export is a component, not a hook** — [`useExportWallet`](/wallets/hooks/use-export-wallet) and [`useExportPrivateKey`](/wallets/hooks/use-export-private-key) are not available on React Native. Use [`ZeroDevExportWebView`](/wallets/react-native/export-wallet) instead. - **OAuth uses deep links** — instead of the web popup flow, use [`useAuthenticateOAuthWithExpoWebBrowser`](/wallets/hooks/use-authenticate-oauth-with-expo-web-browser). + +Running the same Expo app on the web too? See [React Native Web](/wallets/react-native/web) — the web build auto-defaults the stampers and storage, so the connector is just `zeroDevWallet({ projectId, chains })`. diff --git a/docs/pages/wallets/react-native/google-oauth.mdx b/docs/pages/wallets/react-native/google-oauth.mdx index ef19569..922a7ed 100644 --- a/docs/pages/wallets/react-native/google-oauth.mdx +++ b/docs/pages/wallets/react-native/google-oauth.mdx @@ -100,6 +100,8 @@ export default function OAuthCallback() { } ``` +This route is for the **native** OAuth flow. If the same Expo app also runs on web, the `.web` variant should use [`useAuthenticateOAuth`](/wallets/hooks/use-authenticate-oauth) and return to the current page instead of relying on `app/oauth-callback.tsx`. + ## 5. Allowlist the redirect URL On the [ZeroDev Dashboard](https://dashboard.zerodev.app/), **allowlist the redirect URL**. After a successful sign-in the browser redirects back into the app. diff --git a/docs/pages/wallets/react-native/passkeys.mdx b/docs/pages/wallets/react-native/passkeys.mdx index f24c6da..516b2bd 100644 --- a/docs/pages/wallets/react-native/passkeys.mdx +++ b/docs/pages/wallets/react-native/passkeys.mdx @@ -79,16 +79,16 @@ export const wagmiConfig = createConfig({ ```tsx import { useLoginPasskey, useRegisterPasskey } from "@zerodev/wallet-react"; -import { Button, Platform, Text, View } from "react-native"; +import { Button, Text, View } from "react-native"; import { useAccount } from "wagmi"; -/** Renders nothing once connected; native-only (RN passkey stamper). */ +/** Renders nothing once connected. The same component can be reused on web. */ export function PasskeyFlow() { const { status } = useAccount(); const register = useRegisterPasskey(); const login = useLoginPasskey(); - if (status === "connected" || Platform.OS === "web") return null; + if (status === "connected") return null; return ( @@ -115,3 +115,5 @@ export function PasskeyFlow() { ``` The OS handles the passkey UI (biometric/PIN); the hooks auto-connect the wallet on success. The device needs Google Play services and an enrolled screen lock. + +If the same Expo app also runs on web, keep this component shared. The [React Native Web guide](/wallets/react-native/web) uses the same `PasskeyFlow` and lets the web build auto-default its WebAuthn stamper, so you do not need a separate `.web` variant for the passkey UI. diff --git a/docs/pages/wallets/react-native/quickstart.mdx b/docs/pages/wallets/react-native/quickstart.mdx index 176f23b..e5b72a5 100644 --- a/docs/pages/wallets/react-native/quickstart.mdx +++ b/docs/pages/wallets/react-native/quickstart.mdx @@ -401,3 +401,4 @@ These guides demonstrate the native flows on Android because a debug build runs - [Magic Link](/wallets/react-native/magic-link) — Sign in with a link sent by email - [Passkeys](/wallets/react-native/passkeys) — Native WebAuthn - [Export Wallet](/wallets/react-native/export-wallet) — Reveal the seed phrase or private key via WebView +- [React Native Web](/wallets/react-native/web) — Run the same app on the web with react-native-web diff --git a/docs/pages/wallets/react-native/web.mdx b/docs/pages/wallets/react-native/web.mdx new file mode 100644 index 0000000..2bc5c7e --- /dev/null +++ b/docs/pages/wallets/react-native/web.mdx @@ -0,0 +1,290 @@ +# React Native Web [Run your Expo app on the web] + +:::danger[IMPORTANT] +**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time. +::: + +The Expo app from the [quickstart](/wallets/react-native/quickstart) also runs on the web via [`react-native-web`](https://necolas.github.io/react-native-web/). The only work is swapping a few React-Native-only integrations for web variants, then adjusting routing so the auth routes that still matter on web can mount. + +:::info[The web build auto-defaults everything] +On web the SDK fills in every platform piece for you — `apiKeyStamper → IndexedDB`, `passkeyStamper → WebAuthn`, `sessionStorage → localStorage`, `rpId → window.location.hostname`. So the web connector is just `zeroDevWallet({ projectId, chains })`, and **passkeys work on web for free**. See [Connector Options](/wallets/connector-options). +::: + +## Prerequisites + +The default Expo starter already ships everything web needs — confirm it's there: + +- `react-dom` and `react-native-web` in `package.json` +- a `"web": "expo start --web"` script +- `app.json` → `"web": { "output": "static", "favicon": … }` + +## What changes from quickstart + +The quickstart already gives you the native OTP flow. To make the same Expo app work on web: + +- add `.web` siblings for files that call React-Native-only wallet helpers +- keep the passkey UI shared, but swap the native OAuth and export implementations for web ones +- move tabs under a root `Stack` so `app/verify-email.tsx` can mount +- type-check `.web` files separately and allowlist your web origin on the Dashboard + +## 1. Add web variants of the native-only files + +The `@zerodev/wallet-core/react-native/*` and `@zerodev/wallet-react/react-native/*` subpaths resolve to throw-on-use stubs on web. If a universal file calls one of those helpers during startup — for example, `wagmi.config.ts` creating native stampers at module load — the app fails before React renders. The fix is Metro's platform resolution: a `foo.web.tsx` file is used on web, `foo.tsx` everywhere else (the starter already does this for `animated-icon` and `app-tabs`). Add a `.web` sibling for each file that touches an RN-only module; the base file stays native. + +### `wagmi.config.web.ts` + +The web connector needs only `projectId` and `chains`: + +```ts +import { zeroDevWallet } from "@zerodev/wallet-react"; +import { createConfig, http } from "wagmi"; +import { arbitrumSepolia, sepolia } from "wagmi/chains"; + +const ZERODEV_PROJECT_ID = process.env.EXPO_PUBLIC_ZERODEV_PROJECT_ID ?? ""; +export const RP_ID = "zdwalletdemo.vercel.app"; // kept for parity; unused on web + +const chains = [sepolia, arbitrumSepolia] as const; + +export const wagmiConfig = createConfig({ + chains, + connectors: [zeroDevWallet({ projectId: ZERODEV_PROJECT_ID, chains })], + transports: { [sepolia.id]: http(), [arbitrumSepolia.id]: http() }, + multiInjectedProviderDiscovery: false, +}); + +declare module "wagmi" { + interface Register { + config: typeof wagmiConfig; + } +} +``` + +Leave `rpId` unset so WebAuthn matches the serving origin (localhost in dev, your https domain in prod), and omit Wagmi's `storage` (it defaults to `localStorage`). + +### `magic-link-pending.web.ts` + +The same async API as the native AsyncStorage helper, backed by `localStorage`: + +```ts +const KEY = "magic-link-pending"; +type Pending = { otpId: string; otpEncryptionTargetBundle: string }; + +export const savePendingMagicLink = async (p: Pending) => + localStorage.setItem(KEY, JSON.stringify(p)); + +export const loadPendingMagicLink = async (): Promise => { + const raw = localStorage.getItem(KEY); + return raw ? JSON.parse(raw) : null; +}; +``` + +### `google-oauth-flow.web.tsx` + +The web [`useAuthenticateOAuth`](/wallets/hooks/use-authenticate-oauth) runs a popup instead of a deep link. It returns to the same page that started auth, so it takes no arguments and does not need a dedicated `/oauth-callback` route on web: + +```tsx +import { OAUTH_PROVIDERS, useAuthenticateOAuth } from "@zerodev/wallet-react"; +import { Button, Text, View } from "react-native"; +import { useAccount } from "wagmi"; + +export function GoogleOauthFlow() { + const { status } = useAccount(); + const auth = useAuthenticateOAuth(); + + if (status === "connected") return null; + + return ( + +