Skip to content

Commit

Permalink
EW unified implentation (#2334)
Browse files Browse the repository at this point in the history
Co-authored-by: Winston Yeo <[email protected]>
  • Loading branch information
joaquim-verges and ElasticBottle authored Feb 21, 2024
1 parent 1335852 commit 06e4f34
Show file tree
Hide file tree
Showing 22 changed files with 1,910 additions and 20 deletions.
118 changes: 118 additions & 0 deletions packages/thirdweb/src/wallets/embedded/core/authentication/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import type { ThirdwebClient } from "../../../../index.js";
import type { AuthArgsType, PreAuthArgsType } from "./type.js";
import { AuthProvider } from "../../implementations/interfaces/auth.js";
import { UserWalletStatus } from "../../implementations/interfaces/embedded-wallets/embedded-wallets.js";
import type { EmbeddedWalletSdk } from "../../implementations/lib/embedded-wallet.js";

const ewsSDKCache = new Map<ThirdwebClient, EmbeddedWalletSdk>();

/**
* @internal
*/
async function getEmbeddedWalletSDK(client: ThirdwebClient) {
if (ewsSDKCache.has(client)) {
return ewsSDKCache.get(client) as EmbeddedWalletSdk;
}
const { EmbeddedWalletSdk } = await import(
"../../implementations/lib/embedded-wallet.js"
);
// TODO (ew) cache this
const ewSDK = new EmbeddedWalletSdk({
client: client,
});
ewsSDKCache.set(client, ewSDK);
return ewSDK;
}

/**
* @internal
*/
export async function getAuthenticatedUser(args: { client: ThirdwebClient }) {
const { client } = args;
const ewSDK = await getEmbeddedWalletSDK(client);
const user = await ewSDK.getUser();
switch (user.status) {
case UserWalletStatus.LOGGED_IN_WALLET_INITIALIZED: {
return user;
}
}
return undefined;
}

/**
* @internal
*/
export async function preAuthenticate(args: PreAuthArgsType) {
const ewSDK = await getEmbeddedWalletSDK(args.client);
const strategy = args.strategy;
switch (strategy) {
case "email": {
return ewSDK.auth.sendEmailLoginOtp({ email: args.email });
}
default:
throw new Error(
`Provider: ${strategy} doesnt require pre-authentication`,
);
}
}

/**
* @internal
*/
export async function authenticate(args: AuthArgsType) {
const ewSDK = await getEmbeddedWalletSDK(args.client);
const strategy = args.strategy;
switch (strategy) {
case "email": {
return await ewSDK.auth.verifyEmailLoginOtp({
email: args.email,
otp: args.verificationCode,
});
}
case "apple":
case "facebook":
case "google": {
const oauthProvider = oauthStrategyToAuthProvider[strategy];
return ewSDK.auth.loginWithOauth({
oauthProvider,
closeOpenedWindow: args.closeOpenedWindow,
openedWindow: args.openedWindow,
});
}
case "jwt": {
return ewSDK.auth.loginWithCustomJwt({
jwt: args.jwt,
encryptionKey: args.encryptionKey,
});
}
case "auth_endpoint": {
return ewSDK.auth.loginWithCustomAuthEndpoint({
payload: args.payload,
encryptionKey: args.encryptionKey,
});
}
case "iframe_email_verification": {
return ewSDK.auth.loginWithEmailOtp({
email: args.email,
});
}
case "iframe": {
return ewSDK.auth.loginWithModal();
}
default:
assertUnreachable(strategy);
}
}

function assertUnreachable(x: never): never {
throw new Error("Invalid param: " + x);
}

const oauthStrategyToAuthProvider: Record<
"google" | "facebook" | "apple",
AuthProvider
> = {
google: AuthProvider.GOOGLE,
facebook: AuthProvider.FACEBOOK,
apple: AuthProvider.APPLE,
};
37 changes: 37 additions & 0 deletions packages/thirdweb/src/wallets/embedded/core/authentication/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ThirdwebClient } from "../../../../client/client.js";

export type MultiStepAuthProviderType = {
strategy: "email";
email: string;
};
export type PreAuthArgsType = MultiStepAuthProviderType & {
client: ThirdwebClient;
};

export type MultiStepAuthArgsType = MultiStepAuthProviderType & {
verificationCode: string;
};
export type SingleStepAuthArgsType =
| {
strategy: "google";
openedWindow?: Window;
closeOpenedWindow?: (window: Window) => void;
}
| {
strategy: "apple";
openedWindow?: Window;
closeOpenedWindow?: (window: Window) => void;
}
| {
strategy: "facebook";
openedWindow?: Window;
closeOpenedWindow?: (window: Window) => void;
}
| { strategy: "jwt"; jwt: string; encryptionKey: string }
| { strategy: "auth_endpoint"; payload: string; encryptionKey: string }
| { strategy: "iframe_email_verification"; email: string }
| { strategy: "iframe" };

export type AuthArgsType = (MultiStepAuthArgsType | SingleStepAuthArgsType) & {
client: ThirdwebClient;
};
100 changes: 100 additions & 0 deletions packages/thirdweb/src/wallets/embedded/core/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
defineChain,
type Chain,
type ThirdwebClient,
} from "../../../../index.js";
import type { Account, Wallet } from "../../../interfaces/wallet.js";
import type { WalletMetadata } from "../../../types.js";
import type {
MultiStepAuthArgsType,
PreAuthArgsType,
SingleStepAuthArgsType,
} from "../authentication/type.js";
import {
authenticate,
getAuthenticatedUser,
preAuthenticate,
} from "../authentication/index.js";
import type { EmbeddedWalletConfig } from "./types.js";

/**
* Embedded Wallet
* @param args - The args to use for the wallet
* @param args.client - The ThirdwebClient to use for the wallet
* @returns The embedded wallet
* @example
* ```ts
* import { embeddedWallet } from "thirdweb/wallets";
*
* const wallet = embeddedWallet({
* client,
* });
* await wallet.connect({
* strategy: "google",
* });
* ```
*/
export function embeddedWallet(args: EmbeddedWalletConfig) {
return new EmbeddedWallet(args);
}

class EmbeddedWallet implements Wallet {
metadata: WalletMetadata = {
id: "embedded-wallet",
name: "Embedded Wallet",
iconUrl: "", // TODO (ew)
};
client: ThirdwebClient;
account?: Account;
chain: Chain;

constructor(args: EmbeddedWalletConfig) {
this.client = args.client;
this.chain = args.defaultChain ?? defineChain(1);
}

async preAuthenticate(options: Omit<PreAuthArgsType, "client">) {
return preAuthenticate({
client: this.client,
...options,
});
}

async connect(
options: MultiStepAuthArgsType | SingleStepAuthArgsType,
): Promise<Account> {
const authResult = await authenticate({
client: this.client,
...options,
});
const authAccount = await authResult.user.wallet.getAccount();
this.account = authAccount;
return authAccount;
}

async autoConnect(): Promise<Account> {
const user = await getAuthenticatedUser({ client: this.client });
if (!user) {
throw new Error("not authenticated");
}
const authAccount = await user.wallet.getAccount();
this.account = authAccount;
return authAccount;
}

async disconnect(): Promise<void> {
this.account = undefined;
}

getAccount(): Account | undefined {
return this.account;
}

getChain() {
return this.chain;
}

async switchChain(newChain: Chain) {
this.chain = newChain;
}
}
7 changes: 7 additions & 0 deletions packages/thirdweb/src/wallets/embedded/core/wallet/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Chain } from "../../../../chains/types.js";
import type { ThirdwebClient } from "../../../../client/client.js";

export type EmbeddedWalletConfig = {
client: ThirdwebClient;
defaultChain?: Chain;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @internal
*/
export const EMBEDDED_WALLET_PATH = "/sdk/2022-08-12/embedded-wallet";

/**
* @internal
*/
export const HEADLESS_GOOGLE_OAUTH_ROUTE = `/auth/headless-google-login-managed`;
/**
* @internal
*/
export const GET_IFRAME_BASE_URL = () => {
if (
!!(
typeof window !== "undefined" &&
localStorage.getItem("IS_THIRDWEB_DEV") === "true"
)
) {
return (
window.localStorage.getItem("THIRDWEB_DEV_URL") ?? "http://localhost:3000"
);
}

return `https://embedded-wallet.thirdweb.com`;
};
/**
* @internal
*/
export const WALLET_USER_DETAILS_LOCAL_STORAGE_NAME = (clientId: string) =>
`thirdwebEwsWalletUserDetails-${clientId}`;

/**
* @internal
*/
export const WALLET_USER_ID_LOCAL_STORAGE_NAME = (clientId: string) =>
`thirdwebEwsWalletUserId-${clientId}`;

/**
* @internal
*/
const AUTH_TOKEN_LOCAL_STORAGE_PREFIX = "walletToken";

/**
* @internal
*/
export const AUTH_TOKEN_LOCAL_STORAGE_NAME = (clientId: string) => {
return `${AUTH_TOKEN_LOCAL_STORAGE_PREFIX}-${clientId}`;
};

/**
* @internal
*/
const DEVICE_SHARE_LOCAL_STORAGE_PREFIX = "a";

/**
* @internal
*/
export const DEVICE_SHARE_LOCAL_STORAGE_NAME = (
clientId: string,
userId: string,
) => `${DEVICE_SHARE_LOCAL_STORAGE_PREFIX}-${clientId}-${userId}`;

/**
* @internal
*/
export const DEVICE_SHARE_LOCAL_STORAGE_NAME_DEPRECATED = (clientId: string) =>
`${DEVICE_SHARE_LOCAL_STORAGE_PREFIX}-${clientId}`;
34 changes: 34 additions & 0 deletions packages/thirdweb/src/wallets/embedded/implementations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export {
AUTH_TOKEN_LOCAL_STORAGE_NAME,
DEVICE_SHARE_LOCAL_STORAGE_NAME,
DEVICE_SHARE_LOCAL_STORAGE_NAME_DEPRECATED,
WALLET_USER_DETAILS_LOCAL_STORAGE_NAME,
WALLET_USER_ID_LOCAL_STORAGE_NAME,
} from "./constants/settings.js";
export { AuthProvider, RecoveryShareManagement } from "./interfaces/auth.js";
export type {
AuthAndWalletRpcReturnType,
AuthLoginReturnType,
AuthStoredTokenWithCookieReturnType,
StoredTokenType,
GetHeadlessLoginLinkReturnType,
} from "./interfaces/auth.js";
export { UserWalletStatus } from "./interfaces/embedded-wallets/embedded-wallets.js";
export type {
AuthDetails,
EmbeddedWalletConstructorType,
GetAuthDetailsReturnType,
GetUser,
GetUserWalletStatusRpcReturnType,
InitializedUser,
LogoutReturnType,
SendEmailOtpReturnType,
SetUpWalletRpcReturnType,
} from "./interfaces/embedded-wallets/embedded-wallets.js";
export type {
GetAddressReturnType,
SignMessageReturnType,
SignTransactionReturnType,
SignedTypedDataReturnType,
} from "./interfaces/embedded-wallets/signer.js";
export { EmbeddedWalletSdk } from "./lib/embedded-wallet.js";
Loading

0 comments on commit 06e4f34

Please sign in to comment.