Skip to content

Commit

Permalink
ecosystem refactor part 4: react native edition (#4783)
Browse files Browse the repository at this point in the history
## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing wallet functionalities and authentication processes in the `thirdweb` library, particularly for ecosystem wallets in React Native. It introduces new wallet interfaces, updates existing methods, and improves error handling for user authentication.

### Detailed summary
- Removed obsolete `web-wallet.ts.changeset/chilled-dancers-cheer.md` file.
- Added support for ecosystem wallets in `useAutoConnect` hook.
- Updated `createInAppWallet` to include new `createOptions` structure.
- Defined `IWebWallet` interface with methods for wallet setup and user status.
- Expanded `EcosystemWalletCreationOptions` to include authentication options.
- Modified `getUserStatus` to handle user status retrieval with better error logging.
- Updated `InAppWebConnector` to utilize the new wallet interfaces and methods.
- Implemented `ShardedWallet` class with user wallet status and account retrieval.
- Enhanced error handling in the authentication processes across various modules.
- Removed deprecated methods from `native-auth.ts`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
joaquim-verges committed Sep 25, 2024
1 parent 1f1f99d commit 944d56f
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 716 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-dancers-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": minor
---

Enable ecosystem wallets in React Native
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useQuery } from "@tanstack/react-query";
import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js";
import { createWallet } from "../../../../wallets/create-wallet.js";
import { getUrlToken } from "../../../../wallets/in-app/web/lib/get-url-token.js";
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
import {
getLastConnectedChain,
getStoredActiveWalletId,
getStoredConnectedWalletIds,
} from "../../../../wallets/manager/index.js";
import type { WalletId } from "../../../../wallets/wallet-types.js";
import { useConnectionManagerCtx } from "../../providers/connection-manager.js";
import { setLastAuthProvider } from "../../utils/storage.js";
import { timeoutPromise } from "../../utils/timeoutPromise.js";
Expand All @@ -18,6 +18,7 @@ import { useSetActiveWalletConnectionStatus } from "./useSetActiveWalletConnecti
export function useAutoConnectCore(
storage: AsyncStorage,
props: AutoConnectProps & { wallets: Wallet[] },
createWalletFn: (id: WalletId) => Wallet,
getInstalledWallets?: () => Wallet[],
) {
const manager = useConnectionManagerCtx("useAutoConnect");
Expand Down Expand Up @@ -73,7 +74,7 @@ export function useAutoConnectCore(
const activeWallet =
lastActiveWalletId &&
(availableWallets.find((w) => w.id === lastActiveWalletId) ||
createWallet(lastActiveWalletId));
createWalletFn(lastActiveWalletId));

Check warning on line 77 in packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts#L77

Added line #L77 was not covered by tests

if (activeWallet) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { nativeLocalStorage } from "../../../../utils/storage/nativeStorage.js";
import { createWallet } from "../../../../wallets/native/create-wallet.js";
import type { AutoConnectProps } from "../../../core/hooks/connection/types.js";
import { useAutoConnectCore } from "../../../core/hooks/wallets/useAutoConnect.js";
import { getDefaultWallets } from "../../wallets/defaultWallets.js";
Expand All @@ -23,8 +24,12 @@ import { getDefaultWallets } from "../../wallets/defaultWallets.js";
* @returns whether the auto connect was successful.
*/
export function useAutoConnect(props: AutoConnectProps) {
return useAutoConnectCore(nativeLocalStorage, {
...props,
wallets: props.wallets || getDefaultWallets(props),
});
return useAutoConnectCore(
nativeLocalStorage,
{
...props,
wallets: props.wallets || getDefaultWallets(props),
},
createWallet,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function useAutoConnect(props: AutoConnectProps) {
...props,
wallets,
},
createWallet,
() => {
const specifiedWalletIds = new Set(wallets.map((x) => x.id));

Expand Down
16 changes: 14 additions & 2 deletions packages/thirdweb/src/wallets/ecosystem/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import type {
InAppWalletAutoConnectOptions,
InAppWalletConnectionOptions,
InAppWalletCreationOptions,
} from "../in-app/core/wallet/types.js";

export type EcosystemWalletCreationOptions = InAppWalletCreationOptions & {
export type EcosystemWalletCreationOptions = {
auth?: {
/**
* Whether to display the social auth prompt in a popup or redirect
*/
mode?: "popup" | "redirect" | "window";
/**
* Optional url to redirect to after authentication
*/
redirectUrl?: string;
};
/**
* The partnerId of the ecosystem wallet to connect to
*/
partnerId?: string;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { ThirdwebClient } from "../../../../client/client.js";
import { getSessionHeaders } from "../../native/helpers/api/fetchers.js";
import { ROUTE_AUTH_ENDPOINT_CALLBACK } from "../../native/helpers/constants.js";
import { createErrorMessage } from "../../native/helpers/errors.js";
import type { ClientScopedStorage } from "./client-scoped-storage.js";
import type { AuthStoredTokenWithCookieReturnType } from "./types.js";

export async function authEndpoint(args: {
payload: string;
client: ThirdwebClient;
storage: ClientScopedStorage;
}): Promise<AuthStoredTokenWithCookieReturnType> {
const resp = await fetch(ROUTE_AUTH_ENDPOINT_CALLBACK, {
method: "POST",
headers: {
...getSessionHeaders(),
},
body: JSON.stringify({
payload: args.payload,
developerClientId: args.client.clientId,
}),
});
if (!resp.ok) {
const error = await resp.json();
throw new Error(
`Custom auth endpoint authentication error: ${error.message}`,
);
}

try {
const { verifiedToken } = await resp.json();

return { storedToken: verifiedToken };
} catch (e) {
throw new Error(
createErrorMessage(
"Malformed response from post auth_endpoint authentication",
e,
),
);
}
}
37 changes: 37 additions & 0 deletions packages/thirdweb/src/wallets/in-app/core/authentication/jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ThirdwebClient } from "../../../../client/client.js";
import { getSessionHeaders } from "../../native/helpers/api/fetchers.js";
import { ROUTE_AUTH_JWT_CALLBACK } from "../../native/helpers/constants.js";
import { createErrorMessage } from "../../native/helpers/errors.js";
import type { ClientScopedStorage } from "./client-scoped-storage.js";
import type { AuthStoredTokenWithCookieReturnType } from "./types.js";

export async function customJwt(args: {
jwt: string;
client: ThirdwebClient;
storage: ClientScopedStorage;
}): Promise<AuthStoredTokenWithCookieReturnType> {
const resp = await fetch(ROUTE_AUTH_JWT_CALLBACK, {
method: "POST",
headers: {
...getSessionHeaders(),
},
body: JSON.stringify({
jwt: args.jwt,
developerClientId: args.client.clientId,
}),
});

if (!resp.ok) {
const error = await resp.json();
throw new Error(`JWT authentication error: ${error.message}`);
}

try {
const { verifiedToken } = await resp.json();
return { storedToken: verifiedToken };
} catch (e) {
throw new Error(
createErrorMessage("Malformed response from post jwt authentication", e),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ export type AuthAndWalletRpcReturnType = AuthStoredTokenWithCookieReturnType & {
walletDetails: SetUpWalletRpcReturnType | WalletAddressObjectType;
};

export type AuthResultAndRecoveryCode = AuthStoredTokenWithCookieReturnType & {
deviceShareStored?: string;
encryptionKey?: string;
};

export type AuthLoginReturnType = { user: InitializedUser };

// Auth Types
Expand Down Expand Up @@ -156,7 +161,7 @@ type InitializedUser = {

// In App Wallet Types

export type WalletAddressObjectType = {
type WalletAddressObjectType = {
/**
* User's wallet address
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ import type {
Account,
SendTransactionOption,
} from "../../../interfaces/wallet.js";
import type { ClientScopedStorage } from "../../core/authentication/client-scoped-storage.js";
import { getUserStatus } from "../../web/lib/actions/get-enclave-user-status.js";
import { signMessage as signEnclaveMessage } from "../../web/lib/actions/sign-message.enclave.js";
import { signTransaction as signEnclaveTransaction } from "../../web/lib/actions/sign-transaction.enclave.js";
import { signTypedData as signEnclaveTypedData } from "../../web/lib/actions/sign-typed-data.enclave.js";
import type { ClientScopedStorage } from "../authentication/client-scoped-storage.js";
import type {
AuthDetails,
AuthResultAndRecoveryCode,
GetUser,
WalletAddressObjectType,
} from "../../core/authentication/types.js";
import type { Ecosystem } from "../../core/wallet/types.js";
import { getUserStatus } from "./actions/get-enclave-user-status.js";
import { signMessage as signEnclaveMessage } from "./actions/sign-message.enclave.js";
import { signTransaction as signEnclaveTransaction } from "./actions/sign-transaction.enclave.js";
import { signTypedData as signEnclaveTypedData } from "./actions/sign-typed-data.enclave.js";
import type { IWebWallet, PostWalletSetup } from "./web-wallet.js";
} from "../authentication/types.js";
import type { Ecosystem } from "./types.js";
import type { IWebWallet } from "./web-wallet.js";

export type UserStatus = {
linkedAccounts: {
Expand Down Expand Up @@ -73,12 +73,8 @@ export class EnclaveWallet implements IWebWallet {
* @returns `{walletAddress: string }` The user's wallet details
* @internal
*/
async postWalletSetUp({
walletAddress,
authToken,
}: PostWalletSetup): Promise<WalletAddressObjectType> {
await this.localStorage.saveAuthCookie(authToken);
return { walletAddress };
async postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise<void> {
await this.localStorage.saveAuthCookie(authResult.storedToken.cookieString);
}

/**
Expand Down
14 changes: 14 additions & 0 deletions packages/thirdweb/src/wallets/in-app/core/wallet/web-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Account } from "../../../interfaces/wallet.js";
import type {
AuthResultAndRecoveryCode,
GetUser,
} from "../authentication/types.js";

/**
*
*/
export interface IWebWallet {
postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise<void>;
getUserWalletStatus(): Promise<GetUser>;
getAccount(): Promise<Account>;
}
Loading

0 comments on commit 944d56f

Please sign in to comment.