UNPKG

@0xfutbol/id

Version:

React component library with shared providers for 0xFutbol ID

1,388 lines 75.9 kB
'use strict';var index=require('./index-DwkZmKdT.js'),signLoginPayload=require('./sign-login-payload-BvuOpVPp.js'),eth_sendRawTransaction=require('./eth_sendRawTransaction-BernmwlE.js');require('react'),require('react/jsx-runtime'),require('@0xfutbol/id-sign'),require('react-use'),require('@0xfutbol/constants'),require('thirdweb'),require('@matchain/matchid-sdk-react'),require('@tanstack/react-query'),require('@matchain/matchid-sdk-react/index.css'),require('react-dom');/** * Gets the user's status from the backend. * * @internal */ async function getUserStatus({ authToken, client, ecosystem, }) { const clientFetch = index.g(client, ecosystem); const response = await clientFetch(`${index.a("inAppWallet")}/api/2024-05-05/accounts`, { method: "GET", headers: { "Content-Type": "application/json", "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, }); if (!response.ok) { if (response.status === 401) { // 401 response indicates there is no user logged in, so we return undefined return undefined; } const result = await response.json(); throw new Error(`Failed to get user status: ${result.message}`); } return (await response.json()); }// TODO allow overriding domain const DOMAIN_URL_2023 = index.a("inAppWallet"); const BASE_URL_2023 = `${DOMAIN_URL_2023}/`; const ROUTE_2023_10_20_API_BASE_PATH = `${BASE_URL_2023}api/2023-10-20`; const ROUTE_AUTH_JWT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-jwt`; const ROUTE_AUTH_ENDPOINT_CALLBACK = `${ROUTE_2023_10_20_API_BASE_PATH}/embedded-wallet/validate-custom-auth-endpoint`;const createErrorMessage = (message, error) => { if (error instanceof Error) { return `${message}: ${error.message}`; } return `${message}: ${index.s(error)}`; };async function authEndpoint(args) { const clientFetch = index.g(args.client, args.ecosystem); const res = await clientFetch(ROUTE_AUTH_ENDPOINT_CALLBACK, { method: "POST", headers: { "Content-Type": "application/json", }, body: index.s({ payload: args.payload, developerClientId: args.client.clientId, }), }); if (!res.ok) { const error = await res.json(); throw new Error(`Custom auth endpoint authentication error: ${error.message}`); } try { const { verifiedToken } = await res.json(); return { storedToken: verifiedToken }; } catch (e) { throw new Error(createErrorMessage("Malformed response from post auth_endpoint authentication", e)); } }/** * Authenticates via the wallet secret * @internal */ async function backendAuthenticate(args) { const clientFetch = index.g(args.client, args.ecosystem); const path = index.b({ authOption: "backend", client: args.client, ecosystem: args.ecosystem, }); const res = await clientFetch(`${path}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: index.s({ walletSecret: args.walletSecret, }), }); if (!res.ok) { throw new Error("Failed to generate backend account"); } return (await res.json()); }/** * Does no real authentication, just issues a temporary token for the user. * @internal */ async function guestAuthenticate(args) { const storage = new index.C({ storage: args.storage, clientId: args.client.clientId, ecosystem: args.ecosystem, }); let sessionId = await storage.getGuestSessionId(); if (!sessionId) { sessionId = index.r(32); storage.saveGuestSessionId(sessionId); } const clientFetch = index.g(args.client, args.ecosystem); const path = index.c({ authOption: "guest", client: args.client, ecosystem: args.ecosystem, }); const res = await clientFetch(`${path}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: index.s({ sessionId, }), }); if (!res.ok) throw new Error("Failed to generate guest account"); return (await res.json()); }async function customJwt(args) { const clientFetch = index.g(args.client, args.ecosystem); const res = await clientFetch(ROUTE_AUTH_JWT_CALLBACK, { method: "POST", headers: { "Content-Type": "application/json", }, body: index.s({ jwt: args.jwt, developerClientId: args.client.clientId, }), }); if (!res.ok) { const error = await res.json(); throw new Error(`JWT authentication error: ${error.message}`); } try { const { verifiedToken } = await res.json(); return { storedToken: verifiedToken }; } catch (e) { throw new Error(createErrorMessage("Malformed response from post jwt authentication", e)); } }/** * @description * Links a new account to the current one using an auth token. * For the public-facing API, use `wallet.linkProfile` instead. * * @internal */ async function linkAccount({ client, ecosystem, tokenToLink, storage, }) { const clientFetch = index.g(client, ecosystem); const IN_APP_URL = index.a("inAppWallet"); const currentAccountToken = await storage.getAuthCookie(); if (!currentAccountToken) { throw new Error("Failed to link account, no user logged in"); } const headers = { Authorization: `Bearer iaw-auth-token:${currentAccountToken}`, "Content-Type": "application/json", }; const linkedDetailsResp = await clientFetch(`${IN_APP_URL}/api/2024-05-05/account/connect`, { method: "POST", headers, body: index.s({ accountAuthTokenToConnect: tokenToLink, }), }); if (!linkedDetailsResp.ok) { const body = await linkedDetailsResp.json(); throw new Error(body.message || "Failed to link account."); } const { linkedAccounts } = await linkedDetailsResp.json(); return (linkedAccounts ?? []); } /** * @description * Links a new account to the current one using an auth token. * For the public-facing API, use `wallet.linkProfile` instead. * * @internal */ async function unlinkAccount({ client, ecosystem, profileToUnlink, storage, }) { const clientFetch = index.g(client, ecosystem); const IN_APP_URL = index.a("inAppWallet"); const currentAccountToken = await storage.getAuthCookie(); if (!currentAccountToken) { throw new Error("Failed to unlink account, no user logged in"); } const headers = { Authorization: `Bearer iaw-auth-token:${currentAccountToken}`, "Content-Type": "application/json", }; const linkedDetailsResp = await clientFetch(`${IN_APP_URL}/api/2024-05-05/account/disconnect`, { method: "POST", headers, body: index.s(profileToUnlink), }); if (!linkedDetailsResp.ok) { const body = await linkedDetailsResp.json(); throw new Error(body.message || "Failed to unlink account."); } const { linkedAccounts } = await linkedDetailsResp.json(); return (linkedAccounts ?? []); } /** * @description * Gets the linked accounts for the current user. * For the public-facing API, use `wallet.getProfiles` instead. * * @internal */ async function getLinkedProfilesInternal({ client, ecosystem, storage, }) { const clientFetch = index.g(client, ecosystem); const IN_APP_URL = index.a("inAppWallet"); const currentAccountToken = await storage.getAuthCookie(); if (!currentAccountToken) { throw new Error("Failed to get linked accounts, no user logged in"); } const headers = { Authorization: `Bearer iaw-auth-token:${currentAccountToken}`, "Content-Type": "application/json", }; const linkedAccountsResp = await clientFetch(`${IN_APP_URL}/api/2024-05-05/accounts`, { method: "GET", headers, }); if (!linkedAccountsResp.ok) { const body = await linkedAccountsResp.json(); throw new Error(body.message || "Failed to get linked accounts."); } const { linkedAccounts } = await linkedAccountsResp.json(); return (linkedAccounts ?? []); }function getVerificationPath() { return `${index.a("inAppWallet")}/api/2024-05-05/login/passkey/callback`; } function getChallengePath(type, username) { return `${index.a("inAppWallet")}/api/2024-05-05/login/passkey?type=${type}${username ? `&username=${username}` : ""}`; } async function registerPasskey(options) { if (!options.passkeyClient.isAvailable()) { throw new Error("Passkeys are not available on this device"); } const fetchWithId = index.g(options.client, options.ecosystem); const generatedName = options.username ?? generateUsername(options.ecosystem); // 1. request challenge from server const res = await fetchWithId(getChallengePath("sign-up", generatedName)); const challengeData = await res.json(); if (!challengeData.challenge) { throw new Error("No challenge received"); } const challenge = challengeData.challenge; // 2. initiate registration const registration = await options.passkeyClient.register({ name: generatedName, challenge, rp: options.rp, }); const customHeaders = {}; if (options.ecosystem?.partnerId) { customHeaders["x-ecosystem-partner-id"] = options.ecosystem.partnerId; } if (options.ecosystem?.id) { customHeaders["x-ecosystem-id"] = options.ecosystem.id; } // 3. send the registration object to the server const verifRes = await fetchWithId(getVerificationPath(), { method: "POST", headers: { "Content-Type": "application/json", ...customHeaders, }, body: index.s({ type: "sign-up", authenticatorData: registration.authenticatorData, credentialId: registration.credentialId, serverVerificationId: challengeData.serverVerificationId, clientData: registration.clientData, username: generatedName, credential: { publicKey: registration.credential.publicKey, algorithm: registration.credential.algorithm, }, origin: registration.origin, rpId: options.rp.id, }), }); const verifData = await verifRes.json(); if (!verifData || !verifData.storedToken) { throw new Error(`Error verifying passkey: ${verifData.message ?? "unknown error"}`); } // 4. store the credentialId in local storage await options.storage?.savePasskeyCredentialId(registration.credentialId); // 5. returns back the IAW authentication token return verifData; } async function loginWithPasskey(options) { if (!options.passkeyClient.isAvailable()) { throw new Error("Passkeys are not available on this device"); } const fetchWithId = index.g(options.client, options.ecosystem); // 1. request challenge from server/iframe const [challengeData, credentialId] = await Promise.all([ fetchWithId(getChallengePath("sign-in")).then((r) => r.json()), options.storage?.getPasskeyCredentialId(), ]); if (!challengeData.challenge) { throw new Error("No challenge received"); } const challenge = challengeData.challenge; // 2. initiate login const authentication = await options.passkeyClient.authenticate({ credentialId: credentialId ?? undefined, challenge, rp: options.rp, }); const customHeaders = {}; if (options.ecosystem?.partnerId) { customHeaders["x-ecosystem-partner-id"] = options.ecosystem.partnerId; } if (options.ecosystem?.id) { customHeaders["x-ecosystem-id"] = options.ecosystem.id; } const verifRes = await fetchWithId(getVerificationPath(), { method: "POST", headers: { "Content-Type": "application/json", ...customHeaders, }, body: index.s({ type: "sign-in", authenticatorData: authentication.authenticatorData, credentialId: authentication.credentialId, serverVerificationId: challengeData.serverVerificationId, clientData: authentication.clientData, signature: authentication.signature, origin: authentication.origin, rpId: options.rp.id, }), }); const verifData = await verifRes.json(); if (!verifData || !verifData.storedToken) { throw new Error(`Error verifying passkey: ${verifData.message ?? "unknown error"}`); } // 5. store the credentialId in local storage await options.storage?.savePasskeyCredentialId(authentication.credentialId); // 6. return the auth'd user type return verifData; } function generateUsername(ecosystem) { return `${ecosystem?.id ?? "wallet"}-${new Date().toISOString()}`; }/** * @internal */ async function siweAuthenticate(args) { const { wallet, chain, client, ecosystem } = args; // only connect if the wallet doesn't already have an account const account = wallet.getAccount() || (await wallet.connect({ client, chain })); const clientFetch = index.g(client, ecosystem); const payload = await (async () => { const path = index.b({ authOption: "wallet", client: args.client, ecosystem: args.ecosystem, }); const res = await clientFetch(`${path}&address=${account.address}&chainId=${chain.id}`); if (!res.ok) throw new Error("Failed to generate SIWE login payload"); return (await res.json()); })(); const { signature } = await signLoginPayload.signLoginPayload({ payload, account }); const authResult = await (async () => { const path = index.c({ authOption: "wallet", client: args.client, ecosystem: args.ecosystem, }); const res = await clientFetch(`${path}&signature=${signature}&payload=${encodeURIComponent(payload)}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: index.s({ signature, payload, }), }); if (!res.ok) throw new Error("Failed to verify SIWE signature"); return (await res.json()); })(); return authResult; }async function signMessage({ client, payload: { message, isRaw, originalMessage, chainId }, storage, }) { const authToken = await storage.getAuthCookie(); const ecosystem = storage.ecosystem; const clientFetch = index.g(client, ecosystem); if (!authToken) { throw new Error("No auth token found when signing message"); } const response = await clientFetch(`${index.a("inAppWallet")}/api/v1/enclave-wallet/sign-message`, { method: "POST", headers: { "Content-Type": "application/json", "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, body: index.s({ messagePayload: { message, isRaw, originalMessage, chainId, }, }), }); if (!response.ok) { throw new Error(`Failed to sign message - ${response.status} ${response.statusText}`); } const signedMessage = (await response.json()); return signedMessage; }async function signTransaction({ client, payload, storage, }) { const authToken = await storage.getAuthCookie(); const ecosystem = storage.ecosystem; const clientFetch = index.g(client, ecosystem); if (!authToken) { throw new Error("No auth token found when signing transaction"); } const response = await clientFetch(`${index.a("inAppWallet")}/api/v1/enclave-wallet/sign-transaction`, { method: "POST", headers: { "Content-Type": "application/json", "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, body: index.s({ transactionPayload: payload, }), }); if (!response.ok) { throw new Error(`Failed to sign transaction - ${response.status} ${response.statusText}`); } const signedTransaction = (await response.json()); return signedTransaction.signature; }async function signTypedData({ client, payload, storage, }) { const authToken = await storage.getAuthCookie(); const ecosystem = storage.ecosystem; const clientFetch = index.g(client, ecosystem); if (!authToken) { throw new Error("No auth token found when signing typed data"); } const response = await clientFetch(`${index.a("inAppWallet")}/api/v1/enclave-wallet/sign-typed-data`, { method: "POST", headers: { "Content-Type": "application/json", "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, body: index.s({ ...payload, }), }); if (!response.ok) { throw new Error(`Failed to sign typed data - ${response.status} ${response.statusText}`); } const signedTypedData = (await response.json()); return signedTypedData; }class EnclaveWallet { constructor({ client, ecosystem, address, storage, }) { Object.defineProperty(this, "client", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ecosystem", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "address", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "localStorage", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.client = client; this.ecosystem = ecosystem; this.address = address; this.localStorage = storage; } /** * Store the auth token for use * @returns `{walletAddress: string }` The user's wallet details * @internal */ async postWalletSetUp(authResult) { await this.localStorage.saveAuthCookie(authResult.storedToken.cookieString); } /** * Gets the current user's details * @internal */ async getUserWalletStatus() { const token = await this.localStorage.getAuthCookie(); if (!token) { return { status: "Logged Out" }; } const userStatus = await getUserStatus({ authToken: token, client: this.client, ecosystem: this.ecosystem, }); if (!userStatus) { return { status: "Logged Out" }; } const wallet = userStatus.wallets[0]; const authDetails = { email: userStatus.linkedAccounts.find((account) => account.details.email !== undefined)?.details.email, phoneNumber: userStatus.linkedAccounts.find((account) => account.details.phone !== undefined)?.details.phone, userWalletId: userStatus.id || "", recoveryShareManagement: "ENCLAVE", }; if (!wallet) { return { status: "Logged In, Wallet Uninitialized", authDetails, }; } return { status: "Logged In, Wallet Initialized", walletAddress: wallet.address, authDetails, account: await this.getAccount(), }; } /** * Returns an account to perform wallet operations * @internal */ async getAccount() { const client = this.client; const storage = this.localStorage; const address = this.address; const ecosystem = this.ecosystem; const _signTransaction = async (tx) => { const rpcRequest = index.f({ client, chain: index.h(tx.chainId), }); const transaction = { to: tx.to ? index.d(tx.to) : undefined, data: tx.data, value: hexlify(tx.value), gas: hexlify(tx.gas), nonce: hexlify(tx.nonce) || index.i(await Promise.resolve().then(function(){return require('./eth_getTransactionCount-CCly7HhE.js')}).then(({ eth_getTransactionCount }) => eth_getTransactionCount(rpcRequest, { address: index.d(this.address), blockTag: "pending", }))), chainId: index.i(tx.chainId), }; if (hexlify(tx.maxFeePerGas)) { transaction.maxFeePerGas = hexlify(tx.maxFeePerGas); transaction.maxPriorityFeePerGas = hexlify(tx.maxPriorityFeePerGas); transaction.type = 2; } else { transaction.gasPrice = hexlify(tx.gasPrice); transaction.type = 0; } return signTransaction({ client, storage, payload: transaction, }); }; return { address: index.d(address), async signTransaction(tx) { if (!tx.chainId) { throw new Error("chainId required in tx to sign"); } return _signTransaction({ chainId: tx.chainId, ...tx, }); }, async sendTransaction(tx) { const rpcRequest = index.f({ client, chain: index.h(tx.chainId), }); const signedTx = await _signTransaction(tx); const transactionHash = await eth_sendRawTransaction.e(rpcRequest, signedTx); index.t({ client, ecosystem, chainId: tx.chainId, walletAddress: address, walletType: "inApp", transactionHash, contractAddress: tx.to ?? undefined, gasPrice: tx.gasPrice, }); return { transactionHash }; }, async signMessage({ message, originalMessage, chainId }) { const messagePayload = (() => { if (typeof message === "string") { return { message, isRaw: false, originalMessage, chainId }; } return { message: typeof message.raw === "string" ? message.raw : index.e(message.raw), isRaw: true, originalMessage, chainId, }; })(); const { signature } = await signMessage({ client, payload: messagePayload, storage, }); return signature; }, async signTypedData(_typedData) { const parsedTypedData = index.p(_typedData); const { signature } = await signTypedData({ client, payload: parsedTypedData, storage, }); return signature; }, }; } } function hexlify(value) { return value === undefined || index.j(value) ? value : index.i(value); }const iframeBaseStyle = { height: "100%", width: "100%", border: "none", backgroundColor: "transparent", colorScheme: "light", position: "fixed", top: "0px", right: "0px", zIndex: "2147483646", display: "none", pointerEvents: "all", }; // Global var to help track iframe state const isIframeLoaded = new Map(); /** * @internal */ // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later class IframeCommunicator { /** * @internal */ constructor({ link, baseUrl, iframeId, container, onIframeInitialize, localStorage, clientId, ecosystem, }) { Object.defineProperty(this, "iframe", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "POLLING_INTERVAL_SECONDS", { enumerable: true, configurable: true, writable: true, value: 1.4 }); Object.defineProperty(this, "iframeBaseUrl", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "localStorage", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "clientId", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ecosystem", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.localStorage = localStorage; this.clientId = clientId; this.ecosystem = ecosystem; this.iframeBaseUrl = baseUrl; if (typeof document === "undefined") { return; } container = container ?? document.body; // Creating the IFrame element for communication let iframe = document.getElementById(iframeId); const hrefLink = new URL(link); // TODO (ew) - bring back version tracking // const sdkVersion = process.env.THIRDWEB_EWS_SDK_VERSION; // if (!sdkVersion) { // throw new Error("Missing THIRDWEB_EWS_SDK_VERSION env var"); // } // hrefLink.searchParams.set("sdkVersion", sdkVersion); if (!iframe || iframe.src !== hrefLink.href) { // ! Do not update the hrefLink here or it'll cause multiple re-renders iframe = document.createElement("iframe"); const mergedIframeStyles = { ...iframeBaseStyle, }; Object.assign(iframe.style, mergedIframeStyles); iframe.setAttribute("id", iframeId); iframe.setAttribute("fetchpriority", "high"); container.appendChild(iframe); iframe.src = hrefLink.href; // iframe.setAttribute("data-version", sdkVersion); // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later const onIframeLoaded = (event) => { if (event.data.eventType === "ewsIframeLoaded") { window.removeEventListener("message", onIframeLoaded); if (!iframe) { console.warn("thirdweb iFrame not found"); return; } this.onIframeLoadHandler(iframe, onIframeInitialize)(); } }; window.addEventListener("message", onIframeLoaded); } this.iframe = iframe; } // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later async onIframeLoadedInitVariables() { return { authCookie: await this.localStorage.getAuthCookie(), deviceShareStored: await this.localStorage.getDeviceShare(), walletUserId: await this.localStorage.getWalletUserId(), clientId: this.clientId, partnerId: this.ecosystem?.partnerId, ecosystemId: this.ecosystem?.id, }; } /** * @internal */ onIframeLoadHandler(iframe, onIframeInitialize) { return async () => { const channel = new MessageChannel(); const promise = new Promise((res, rej) => { // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later channel.port1.onmessage = (event) => { const { data } = event; channel.port1.close(); if (!data.success) { rej(new Error(data.error)); } isIframeLoaded.set(iframe.src, true); if (onIframeInitialize) { onIframeInitialize(); } res(true); }; }); iframe?.contentWindow?.postMessage({ eventType: "initIframe", data: await this.onIframeLoadedInitVariables(), }, this.iframeBaseUrl, [channel.port2]); await promise; }; } /** * @internal */ async call({ procedureName, params, showIframe = false, }) { if (!this.iframe) { throw new Error("Iframe not found. You are likely calling this from the backend where the DOM is not available."); } while (!isIframeLoaded.get(this.iframe.src)) { await index.k(this.POLLING_INTERVAL_SECONDS * 1000); } if (showIframe) { this.iframe.style.display = "block"; // magic number to let the display render before performing the animation of the modal in await index.k(0.005 * 1000); } const channel = new MessageChannel(); const promise = new Promise((res, rej) => { // biome-ignore lint/suspicious/noExplicitAny: TODO: fix later channel.port1.onmessage = async (event) => { const { data } = event; channel.port1.close(); if (showIframe) { // magic number to let modal fade out before hiding it await index.k(0.1 * 1000); if (this.iframe) { this.iframe.style.display = "none"; } } if (!data.success) { rej(new Error(data.error)); } else { res(data.data); } }; }); this.iframe.contentWindow?.postMessage({ eventType: procedureName, // Pass the initialization data on every request in case the iframe storage was reset (can happen in some environments such as iOS PWAs) data: { ...params, ...(await this.onIframeLoadedInitVariables()), }, }, this.iframeBaseUrl, [channel.port2]); return promise; } /** * This has to be called by any iframe that will be removed from the DOM. * Use to make sure that we reset the global loaded state of the particular iframe.src * @internal */ destroy() { if (this.iframe) { isIframeLoaded.delete(this.iframe.src); } } }/** * @internal */ class InAppWalletIframeCommunicator extends IframeCommunicator { /** * @internal */ constructor({ clientId, baseUrl, ecosystem, }) { super({ iframeId: IN_APP_WALLET_IFRAME_ID + (ecosystem?.id || ""), link: createInAppWalletIframeLink({ clientId, path: index.I, ecosystem, baseUrl, }).href, baseUrl, container: typeof document === "undefined" ? undefined : document.body, localStorage: new index.C({ storage: index.w, clientId, ecosystem, }), clientId, ecosystem, }); this.clientId = clientId; this.ecosystem = ecosystem; } } // This is the URL and ID tag of the iFrame that we communicate with /** * @internal */ function createInAppWalletIframeLink({ clientId, baseUrl, path, ecosystem, queryParams, }) { const inAppWalletUrl = new URL(`${path}`, baseUrl); if (queryParams) { for (const queryKey of Object.keys(queryParams)) { inAppWalletUrl.searchParams.set(queryKey, queryParams[queryKey]?.toString() || ""); } } inAppWalletUrl.searchParams.set("clientId", clientId); if (ecosystem?.partnerId !== undefined) { inAppWalletUrl.searchParams.set("partnerId", ecosystem.partnerId); } if (ecosystem?.id !== undefined) { inAppWalletUrl.searchParams.set("ecosystemId", ecosystem.id); } return inAppWalletUrl; } const IN_APP_WALLET_IFRAME_ID = "thirdweb-in-app-wallet-iframe";/** * Generate a new enclave wallet using an auth token * @internal */ async function generateWallet({ client, ecosystem, authToken, }) { const clientFetch = index.g(client, ecosystem); const response = await clientFetch(`${index.a("inAppWallet")}/api/v1/enclave-wallet/generate`, { method: "POST", headers: { "Content-Type": "application/json", "x-thirdweb-client-id": client.clientId, Authorization: `Bearer embedded-wallet-token:${authToken}`, }, }); if (!response.ok) { throw new Error(`Failed to generate wallet - ${response.status} ${response.statusText}`); } const { wallet } = (await response.json()); return wallet; }/** * @internal */ class AbstractLogin { /** * Used to manage the user's auth states. This should not be instantiated directly. * @internal */ constructor({ baseUrl, querier, preLogin, postLogin, client, ecosystem, }) { Object.defineProperty(this, "LoginQuerier", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "preLogin", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "postLogin", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "client", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "baseUrl", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ecosystem", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.baseUrl = baseUrl; this.LoginQuerier = querier; this.preLogin = preLogin; this.postLogin = postLogin; this.client = client; this.ecosystem = ecosystem; } /** * @internal */ async sendEmailLoginOtp({ email, }) { const result = await this.LoginQuerier.call({ procedureName: "sendThirdwebEmailLoginOtp", params: { email }, }); return result; } /** * * @internal */ async sendSmsLoginOtp({ phoneNumber, }) { const result = await this.LoginQuerier.call({ procedureName: "sendThirdwebSmsLoginOtp", params: { phoneNumber }, }); return result; } }/** * */ class BaseLogin extends AbstractLogin { async authenticateWithModal() { return this.LoginQuerier.call({ procedureName: "loginWithThirdwebModal", params: undefined, showIframe: true, }); } /** * @internal */ async loginWithModal() { await this.preLogin(); const result = await this.authenticateWithModal(); return this.postLogin(result); } async authenticateWithIframe({ email, }) { return this.LoginQuerier.call({ procedureName: "loginWithThirdwebModal", params: { email }, showIframe: true, }); } /** * @internal */ async loginWithIframe({ email, }) { await this.preLogin(); const result = await this.authenticateWithIframe({ email }); return this.postLogin(result); } async authenticateWithCustomJwt({ encryptionKey, jwt, }) { if (!encryptionKey || encryptionKey.length === 0) { throw new Error("Encryption key is required for custom jwt auth"); } return this.LoginQuerier.call({ procedureName: "loginWithCustomJwt", params: { encryptionKey, jwt }, }); } /** * @internal */ async loginWithCustomJwt({ encryptionKey, jwt, }) { if (!encryptionKey || encryptionKey.length === 0) { throw new Error("Encryption key is required for custom jwt auth"); } await this.preLogin(); const result = await this.authenticateWithCustomJwt({ encryptionKey, jwt }); return this.postLogin(result); } async authenticateWithCustomAuthEndpoint({ encryptionKey, payload, }) { return this.LoginQuerier.call({ procedureName: "loginWithCustomAuthEndpoint", params: { encryptionKey, payload }, }); } /** * @internal */ async loginWithCustomAuthEndpoint({ encryptionKey, payload, }) { if (!encryptionKey || encryptionKey.length === 0) { throw new Error("Encryption key is required for custom auth"); } await this.preLogin(); const result = await this.authenticateWithCustomAuthEndpoint({ encryptionKey, payload, }); return this.postLogin(result); } async authenticateWithEmailOtp({ email, otp, recoveryCode, }) { return this.LoginQuerier.call({ procedureName: "verifyThirdwebEmailLoginOtp", params: { email, otp, recoveryCode }, }); } /** * @internal */ async loginWithEmailOtp({ email, otp, recoveryCode, }) { const result = await this.authenticateWithEmailOtp({ email, otp, recoveryCode, }); return this.postLogin(result); } async authenticateWithSmsOtp({ phoneNumber, otp, recoveryCode, }) { return this.LoginQuerier.call({ procedureName: "verifyThirdwebSmsLoginOtp", params: { phoneNumber, otp, recoveryCode }, }); } /** * @internal */ async loginWithSmsOtp({ phoneNumber, otp, recoveryCode, }) { const result = await this.authenticateWithSmsOtp({ phoneNumber, otp, recoveryCode, }); return this.postLogin(result); } }/** * */ class Auth { /** * Used to manage the user's auth states. This should not be instantiated directly. * @internal */ constructor({ client, querier, onAuthSuccess, ecosystem, baseUrl, localStorage, }) { Object.defineProperty(this, "client", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ecosystem", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "AuthQuerier", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "localStorage", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "onAuthSuccess", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "BaseLogin", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.client = client; this.ecosystem = ecosystem; this.AuthQuerier = querier; this.localStorage = localStorage; this.onAuthSuccess = onAuthSuccess; this.BaseLogin = new BaseLogin({ postLogin: async (result) => { return this.postLogin(result); }, preLogin: async () => { await this.preLogin(); }, ecosystem, querier: querier, client, baseUrl, }); } async preLogin() { await this.logout(); } async postLogin({ storedToken, walletDetails, }) { if (storedToken.shouldStoreCookieString) { await this.localStorage.saveAuthCookie(storedToken.cookieString); } const initializedUser = await this.onAuthSuccess({ storedToken, walletDetails, }); return initializedUser; } async loginWithAuthToken(authToken, recoveryCode) { // We don't call logout for backend auth because that is handled on the backend where the iframe isn't available to call. Moreover, logout clears the local storage which isn't applicable for backend auth. if (authToken.storedToken.authProvider !== "Backend") { await this.preLogin(); } const user = await getUserStatus({ authToken: authToken.storedToken.cookieString, client: this.client, ecosystem: this.ecosystem, }); if (!user) { throw new Error("Cannot login, no user found for auth token"); } // If they're already an enclave wallet, proceed to login if (user.wallets.length > 0 && user.wallets[0]?.type === "enclave") { return this.postLogin({ storedToken: authToken.storedToken, walletDetails: { walletAddress: user.wallets[0].address, }, }); } if (user.wallets.length === 0) { // If this is a new ecosystem wallet without an enclave yet, we'll generate an enclave const result = await generateWallet({ authToken: authToken.storedToken.cookieString, client: this.client, ecosystem: this.ecosystem, }); return this.postLogin({ storedToken: authToken.storedToken, walletDetails: { walletAddress: result.address, }, }); } // If this is an existing sharded wallet or in-app wallet, we'll login with the sharded wallet const result = await this.AuthQuerier.call({ procedureName: "loginWithStoredTokenDetails", params: { storedToken: authToken.storedToken, recoveryCode, }, }); return this.postLogin(result); } /** * Used to log the user into their thirdweb wallet on your platform via a myriad of auth providers * @example * ```typescript * const thirdwebInAppWallet = new InAppWalletSdk({clientId: "YOUR_CLIENT_ID", chain: "Polygon"}) * try { * const user = await thirdwebInAppWallet.auth.loginWithModal(); * // user is now logged in * } catch (e) { * // User closed modal or something else went wrong during the authentication process * console.error(e) * } * ``` * @returns `{{user: InitializedUser}}` An InitializedUser object. */ async loginWithModal() { return this.BaseLogin.loginWithModal(); } async authenticateWithModal() { return this.BaseLogin.authenticateWithModal(); } /** * Used to log the user into their thirdweb wallet using email OTP * @example * ```typescript * // Basic Flow * const thirdwebInAppWallet = new InAppWalletSdk({clientId: "", chain: "Polygon"}); * try { * // prompts user to enter the code they received * const user = await thirdwebInAppWallet.auth.loginWithThirdwebEmailOtp({ email : "you@example.com" }); * // user is now logged in * } catch (e) { * // User closed the OTP modal or something else went wrong during the authentication process * console.error(e) * } * ``` * @param args - args.email: We will send the email an OTP that needs to be entered in order for them to be logged in. * @returns `{{user: InitializedUser}}` An InitializedUser object. See {@link InAppWalletSdk.getUser} for more */ async loginWithIframe(args) { return this.BaseLogin.loginWithIframe(args); } async authenticateWithIframe(args) { return this.BaseLogin.authenticateWithIframe(args); } /** * @internal */ async loginWithCustomJwt(args) { return this.BaseLogin.loginWithCustomJwt(args); } async authenticateWithCustomJwt(args) { return this.BaseLogin.authenticateWithCustomJwt(args); } /** * @internal */ async loginWithCustomAuthEndpoint(args) { return this.BaseLogin.loginWithCustomAuthEndpoint(args); } async authenticateWithCustomAuthEndpoint(args) { return this.BaseLogin.authenticateWithCustomAuthEndpoint(args); } /** * A headless way to send the users at the passed email an OTP code. * You need to then call {@link Auth.loginWithEmailOtp} in order to complete the login process * @example * @param param0.email * ```typescript * const thirdwebInAppWallet = new InAppWalletSdk({clientId: "", chain: "Polygon"}); * // sends user an OTP code * try { * await thirdwebInAppWallet.auth.sendEmailLoginOtp({ email : "you@example.com" }); * } catch(e) { * // Error Sending user's email an OTP code * console.error(e); * } * * // Then when your user is ready to verify their OTP * try { * const user = await thirdwebInAppWallet.auth.verifyEmailLoginOtp({ email: "you@example.com", otp: "6-DIGIT_CODE_HERE" }); * } catch(e) { * // Error verifying the OTP code * console.error(e) * } * ``` * @param param0 - param0.email We will send the email an OTP that needs to be entered in order for them to be logged in. * @returns `{{ isNewUser: boolean }}` IsNewUser indicates if the user is a new user to your platform * @internal */ async sendEmailLoginOtp({ email, }) { return this.BaseLogin.sendEmailLoginOtp({ email, }); } /** * @internal */ async sendSmsLoginOtp({ phoneNumber, }) { return this.BaseLogin.sendSmsLoginOtp({ phoneNumber, }); } /** * Used to verify the otp that the user receives from thirdweb * * See {@link Auth.sendEmailLoginOtp} for how the headless call flow looks like. Simply swap out the calls to `loginWithThirdwebEmailOtp` with `verifyThirdwebEmailLoginOtp` * @param args - props.email We will send the email an OTP that needs to be entered in order for them to be logged in. * props.otp The code that the user received in their email * @returns `{{user: InitializedUser}}` An InitializedUser object containing the user's status, wallet, authDetails, and more * @internal */ async loginWithEmailOtp(args) { await this.preLogin(); return this.BaseLogin.loginWithEmailOtp(args); } async authenticateWithEmailOtp(args) { return this.BaseLogin.authenticateWithEmailOtp(args); } /** * @internal */ async loginWithSmsOtp(args) { await this.preLogin(); return this.BaseLogin.loginWithSmsOtp(args); } async authenticateWithSmsOtp(args) { return this.BaseLogin.authenticateWithSmsOtp(args); } /** * Logs any existing user out of their wallet. * @returns `{{success: boolean}}` true if a user is successfully logged out. false if there's no user currently logged in. * @internal */ async logout() { const isRemoveAuthCookie = await this.localStorage.removeAuthCookie(); const isRemoveUserId = await this.localStorage.removeWalletUserId(); return { success: isRemoveAuthCookie || isRemoveUserId, }; } }/** * @internal */ const sendOtp = async (args) => { const { client, ecosystem } = args; const url = index.b({ client, ecosystem, authOption: args.strategy }); const headers = { "Content-Type": "application/json", "x-client-id": client.clientId, }; if (ecosystem?.id) { headers["x-ecosystem-id"] = ecosystem.id; } if (ecosystem?.partnerId) { headers["x-ecosystem-partner-id"] = ecosystem.partnerId; } const body = (() => { switch (args.strategy) { case "email": return { email: args.email, }; case "phone": return { phone: args.phoneNumber, }; } })(); const response = await fetch(url, { method: "POST", headers, body: index.s(body), }); if (!response.ok