@particle-network/authkit
Version:
Auth Core provides MPC (Multi-Party Computation)-based threshold signatures.
4 lines • 214 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/common/icon/circle_close.svg", "../../src/api/model/authError.ts", "../../src/api/model/bundle.ts", "../../src/utils/index.ts", "../../src/context/types.ts", "../../src/context/hooks/useLogin.ts", "../../src/context/index.tsx", "../../src/components/customRouter/index.tsx", "../../src/components/loading/index.tsx", "../../src/components/focusTrap/index.tsx", "../../src/context/hooks/useFocusTrap.ts", "../../src/components/particle-modal/index.tsx", "../../src/components/particle-drawer/index.tsx", "../../src/utils/isTelegramWebApp.ts", "../../src/components/icon/circle-close.tsx", "../../src/components/particle-modal/authCoreModalContainer.tsx", "../../src/locales/index.ts", "../../src/utils/authorizeUtils.ts", "../../src/api/getPublicAddress.ts", "../../src/utils/chain-utils.ts", "../../src/utils/common-utils.ts", "../../src/utils/sendAnalyticsActive.ts", "../../src/utils/version.ts", "../../src/context/providerInject.tsx", "../../src/utils/isSocialLogin.ts", "../../src/context/hooks/useAuthCore.ts", "../../src/common/config/index.ts", "../../src/context/hooks/useMessage.ts", "../../src/context/hooks/useConnect.ts", "../../src/context/hooks/useCustomize.ts", "../../src/context/hooks/useEthereum.ts", "../../src/utils/evmSendTransaction.ts", "../../src/utils/number-utils.ts", "../../src/utils/transaction-utils.ts", "../../src/context/evmProvider.ts", "../../src/context/hooks/useUserInfo.ts", "../../src/context/hooks/useIsMounted.ts", "../../src/context/hooks/useSolana.ts", "../../src/context/solanaWallet.ts", "../../src/context/web3ModalProvider.tsx", "../../src/repository/index.ts", "../../src/api/master-password.ts"],
"sourcesContent": ["import * as React from \"react\";\nfunction MyComponent(props) {\n return <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 28 28\" {...props}><circle cx={14} cy={14} r={14} opacity={0.08} /><rect width={1.368} height={12.992} rx={0.684} transform=\"rotate(-45 16.924 -5.61)\" /><rect width={1.368} height={12.992} rx={0.684} transform=\"rotate(45 -2.24 26.54)\" /></svg>;\n}\nexport default MyComponent;", "export class AuthError {\n constructor(public code: number, public message: string) {\n this.code = code;\n this.message = message;\n }\n\n public static feeError() {\n return new AuthError(8101, 'maxFeePerGas cannot be less than maxPriorityFeePerGas');\n }\n\n public static userRejectedRequest() {\n return new AuthError(4001, 'The user rejected the request');\n }\n\n public static userCancelOperation() {\n return new AuthError(4011, 'The user canceled operation.');\n }\n\n public static unauthorized() {\n return new AuthError(4100, 'The requested method and/or account has not been authorized by the user');\n }\n\n public static systemError() {\n return new AuthError(8001, 'System Error');\n }\n\n public static paramsError() {\n return new AuthError(8002, 'Param error, see doc for more info');\n }\n\n public static notLogin() {\n return new AuthError(8005, 'Please connect first!');\n }\n\n public static walletNotCreated() {\n return new AuthError(8006, 'Wallet not created');\n }\n\n public static authorizeError() {\n return new AuthError(8007, 'Authorize error');\n }\n\n public static network() {\n return new AuthError(8011, 'Network error');\n }\n\n public static unknown(message: string) {\n return new AuthError(9000, message);\n }\n\n public static pending(method: string) {\n return new AuthError(-32002, `Request of type ${method} already pending, please wait.`);\n }\n}\n", "export interface AuthorizationOptions {\n message?: string;\n uniq?: boolean;\n}\n\nexport enum PromptSettingType {\n /**\n * \u4E0D\u63D0\u9192\n */\n none = 0,\n /**\n * \u9996\u6B21\u63D0\u9192\n */\n first = 1,\n /**\n * \u6BCF\u6B21\u90FD\u63D0\u9192\n */\n every = 2,\n /**\n * \u6BCF\u6B21\u90FD\u63D0\u9192\uFF0C\u4E14\u4E0D\u53EF\u8DF3\u8FC7\n */\n everyAndNotSkip = 3,\n}\n\nexport interface PromptSettingConfig {\n promptPaymentPasswordSettingWhenSign?: PromptSettingType | boolean; // 0: \u4E0D\u63D0\u9192\uFF0C 1: \u9996\u6B21\u63D0\u9192\uFF0C 2: \u6BCF\u6B21\u90FD\u63D0\u9192, 3: \u6BCF\u6B21\u90FD\u63D0\u9192\uFF0C\u4E14\u4E0D\u53EF\u8DF3\u8FC7\n promptMasterPasswordSettingWhenLogin?: PromptSettingType | boolean; // 0: \u4E0D\u63D0\u9192\uFF0C 1: \u9996\u6B21\u63D0\u9192\uFF0C 2: \u6BCF\u6B21\u90FD\u63D0\u9192\uFF0C 3: \u6BCF\u6B21\u90FD\u63D0\u9192\uFF0C\u4E14\u4E0D\u53EF\u8DF3\u8FC7\n}\n", "import type { CountryCode } from 'libphonenumber-js/max';\nimport { isValidPhoneNumber, parsePhoneNumberWithError } from 'libphonenumber-js/max';\nimport darkApple from '../common/images/apple_dark_icon.png';\nimport apple from '../common/images/apple_icon.png';\nimport discord from '../common/images/discord_icon.png';\nimport email from '../common/images/email_icon.png';\nimport facebook from '../common/images/facebook_icon.png';\nimport darkGithub from '../common/images/github_dark_icon.png';\nimport github from '../common/images/github_icon.png';\nimport google from '../common/images/google_icon.png';\nimport jwt from '../common/images/jwt_icon.png';\nimport linkedin from '../common/images/linkedin_icon.png';\nimport microsoft from '../common/images/microsoft_icon.png';\nimport passkeys from '../common/images/passkeys_icon.png';\nimport phone from '../common/images/phone_icon.png';\nimport twitch from '../common/images/twitch_icon.png';\nimport darkTwitter from '../common/images/twitter_dark_icon.png';\nimport twitter from '../common/images/twitter_icon.png';\nimport type { Theme } from '../types';\n\nexport const ipfsToSrc = (ipfs: string) => {\n if (!ipfs || !ipfs.startsWith('ipfs://')) {\n return ipfs || '';\n }\n\n return `https://ipfs.io/ipfs/${encodeURI(ipfs.slice(7))}`;\n};\n\nexport const EmailRegExp = /^\\w+([-+.]\\w+)*@[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*\\.[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*$/;\n\nexport const PhoneRegExp = /^[0-9]{4,16}$/;\n\nexport const PhoneE164Exp = /^\\+[1-9]\\d{4,14}$/;\n\nexport function isValidEmail(email?: string) {\n if (email && EmailRegExp.test(email)) {\n return email;\n }\n}\n\nexport function isPhoneValid(phone: string, regionCode: string): boolean {\n if (!phone || phone.length < 5) {\n return false;\n }\n const countryCode = regionCode?.toUpperCase() as CountryCode;\n const result = isValidPhoneNumber(phone, countryCode);\n return result;\n}\n\nexport const isValidE164PhoneNumber = (phone: string | undefined) => {\n if (phone && PhoneE164Exp.test(phone)) {\n try {\n if (isValidPhoneNumber(phone)) {\n const e164Phone = parsePhoneNumberWithError(phone).format('E.164');\n return e164Phone;\n }\n } catch (e) {\n // ignore\n }\n }\n};\n\nexport function isValidCaptcha(code?: string) {\n return code && code.match(/^\\d{6}$/);\n}\n\nexport const isAutoFocusSupported = () => {\n return true;\n};\n\nexport const isMobile = () => {\n if (isServer()) return false;\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n};\n\nexport const isServer = () => typeof window === 'undefined';\n\nexport const isClient = () => typeof window !== 'undefined';\n\nexport const getLoginLogos = (theme?: Theme): Record<string, string> => {\n const twitterIcon = theme === 'dark' ? darkTwitter : twitter;\n const githubIcon = theme === 'dark' ? darkGithub : github;\n const appleIcon = theme === 'dark' ? darkApple : apple;\n return {\n email,\n phone,\n google,\n facebook,\n twitter: twitterIcon,\n linkedin,\n github: githubIcon,\n microsoft,\n apple: appleIcon,\n discord,\n twitch,\n jwt,\n passkeys,\n };\n};\n", "import type { AuthType, ERC4337Options, ServerError, UserInfo } from '@particle-network/auth-core';\nimport type { WalletCustomStyle, WalletOption } from '@particle-network/wallet';\nimport type { Chain as ViemChain } from 'viem/chains';\nimport type { PromptSettingConfig } from '../api/model/bundle';\nimport type { CurrencyUnit, Theme } from '../types';\n\nexport type ViewType = 'desktop' | 'mobile';\n\nexport interface NavigateOptions {\n replace?: boolean;\n back?: boolean;\n state?: any;\n}\n\nexport interface IAuthCoreModal {\n rootModalContainer: HTMLDivElement | null;\n rootBody: HTMLDivElement | null;\n particleModalVisible: boolean;\n viewType: ViewType;\n closable?: boolean;\n}\n\nexport interface ICustomRouter {\n path: string;\n replace?: boolean;\n state?: any;\n history?: any[];\n children?: React.ReactNode;\n}\n\nexport enum AuthCoreModalEvent {\n Login = 'login', // \u767B\u5F55\n LoginSuccess = 'loginSuccess', // \u767B\u5F55\u6210\u529F\n LoginFail = 'loginFail', // \u767B\u5F55\u5931\u8D25\n SignResponse = 'signResponse', // \u7B7E\u540D\u54CD\u5E94\n}\n\nexport type Language = 'en' | 'zh-cn' | 'ja' | 'ko' | 'zh-tw';\n\nexport interface ThemeStyle {\n primaryBtnColor?: string;\n primaryBtnBackgroundColor?: string;\n secondaryBtnColor?: string;\n secondaryBtnBackgroundColor?: string;\n textColor?: string;\n secondaryTextColor?: string;\n themeBackgroundColor?: string;\n iconBorderColor?: string;\n accentColor?: string;\n inputBackgroundColor?: string;\n inputBorderColor?: string;\n inputPlaceholderColor?: string;\n cardBorderColor?: string;\n cardUnclickableBackgroundColor?: string;\n cardUnclickableBorderColor?: string;\n cardDividerColor?: string;\n tagBackgroundColor?: string;\n modalBackgroundColor?: string;\n tipsBackgroundColor?: string;\n}\n\nexport interface CustomStyle {\n logo?: string;\n projectName?: string;\n subtitle?: string;\n modalWidth?: number;\n modalHeight?: number;\n zIndex?: number;\n primaryBtnBorderRadius?: number | string;\n modalBorderRadius?: number | string;\n cardBorderRadius?: number | string;\n fontFamily?: string;\n theme?: {\n dark?: ThemeStyle;\n light?: ThemeStyle;\n };\n}\n\nexport interface AuthCoreModalOptions {\n projectId: string;\n clientKey: string;\n appId: string;\n authTypes?: AuthType[];\n themeType?: Theme;\n fiatCoin?: CurrencyUnit;\n erc4337?: ERC4337Options;\n language?: Language;\n promptSettingConfig?: PromptSettingConfig;\n customStyle?: CustomStyle;\n chains: readonly [ViemChain, ...ViemChain[]];\n wallet?:\n | (Omit<WalletOption, 'language' | 'erc4337' | 'customStyle'> & {\n customStyle?: Omit<WalletCustomStyle, 'supportChains'>;\n })\n | false;\n supportEIP6963?: boolean; // defalut true\n web3Modal?: any; // optional: add to support @web3modal/ethers\n}\n\nexport type ConnectionStatus = 'loading' | 'connecting' | 'connected' | 'disconnected';\n\nexport type SocialConnectCallback = {\n onSuccess?: (info: UserInfo) => void;\n onError?: (error: ServerError | Error) => void;\n};\n\nexport interface OpenBuyOptions {\n network?: string;\n fiatCoin?: string;\n cryptoCoin?: string;\n fiatAmt?: number;\n fixFiatCoin?: boolean;\n fixCryptoCoin?: boolean;\n fixFiatAmt?: boolean;\n walletAddress?: string;\n}\n\nexport type OpenBuyOptionsKeys = keyof OpenBuyOptions;\n", "import { RecordType } from '@particle-network/analytics';\nimport type {\n Authorization,\n Chain,\n ConnectParam,\n ConnectWithSocialParam,\n SocialAuthType,\n} from '@particle-network/auth-core';\nimport { analyticsRecord, connect, thirdpartyAuth } from '@particle-network/auth-core';\nimport base64url from 'base64url';\nimport { useCallback } from 'react';\nimport { useCustomNavigate, useParticleAuth } from '..';\nimport { isPromptSetMasterPassword } from '../../api/master-password';\nimport { AuthPage } from '../../components/customRouter';\nimport { sendAnalyticsActive } from '../../utils/sendAnalyticsActive';\nimport { AuthCoreModalEvent } from '../types';\n\nconst useLogin = () => {\n const navigate = useCustomNavigate();\n const { loginSuccessRedirectToApp, modalOptions, events } = useParticleAuth();\n const loginHandler = useCallback(\n async (data: ConnectParam, emitError: boolean = true) => {\n try {\n if (!data.chain) {\n data.chain = modalOptions.chains[0];\n }\n const userInfo = await connect(data);\n const { token = '', ...restInfo } = userInfo;\n sendAnalyticsActive('login', restInfo);\n\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_LOGIN_BUTTON_CLICK_SUCCESS, // \u767B\u5F55\u6210\u529F\n });\n console.log('login successful, check master password');\n\n if (userInfo.security_account?.has_set_master_password) {\n console.log('login successful, start verify master password');\n navigate(AuthPage.MasterPasswordVerify, {\n replace: true,\n state: {\n loginVerifyMasterPassword: true,\n },\n });\n } else {\n if (isPromptSetMasterPassword(modalOptions.promptSettingConfig?.promptMasterPasswordSettingWhenLogin)) {\n console.log('login successful, popup set master password');\n navigate(AuthPage.MasterPasswordPrompt, {\n replace: true,\n });\n } else {\n loginSuccessRedirectToApp();\n }\n }\n } catch (error) {\n analyticsRecord({\n // eslint-disable-next-line camelcase\n record_type: RecordType.PAGE_LOGIN_BUTTON_CLICK_FAILURE, // \u767B\u5F55\u5931\u8D25\n });\n if (emitError) {\n console.log('login failed, emit LoginFail event', error);\n events.emit(AuthCoreModalEvent.LoginFail, error);\n } else {\n throw error;\n }\n }\n },\n [modalOptions.promptSettingConfig, navigate, loginSuccessRedirectToApp, modalOptions?.wallet]\n );\n\n const socialAuthLogin = useCallback(\n async (\n options: ConnectWithSocialParam & {\n authorization?: Authorization;\n chain?: Chain;\n }\n ) => {\n try {\n const appState = base64url(\n JSON.stringify({\n chain: options.chain,\n authorization: options.authorization,\n socialType: options.socialType,\n })\n );\n await thirdpartyAuth({\n authType: options.socialType as SocialAuthType,\n appState,\n prompt: options.prompt,\n });\n } catch (error) {\n events.emit(AuthCoreModalEvent.LoginFail, error);\n }\n },\n [events]\n );\n\n return { loginHandler, socialAuthLogin };\n};\n\nexport default useLogin;\n", "import type { LoginAuthorization, SolanaAuthorization, UserInfo } from '@particle-network/auth-core';\nimport {\n AuthCoreEvent,\n EvmRpcMethod,\n SolanaRpcMethod,\n connect,\n getConnectCaptcha,\n getUserInfo,\n hasPaymentPassword,\n isConnected,\n particleAuth,\n syncUserInfo,\n} from '@particle-network/auth-core';\nimport type { WalletEntryPlugin } from '@particle-network/wallet';\nimport { useAsyncEffect, useSetState, useSize } from 'ahooks';\nimport base58 from 'bs58';\nimport { EventEmitter } from 'events';\nimport get from 'lodash/get';\nimport React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport root from 'react-shadow';\nimport { AuthError } from '../api/model/authError';\nimport CustomRouter, { AuthPage } from '../components/customRouter';\nimport FocusTrap from '../components/focusTrap';\nimport ParticleModal from '../components/particle-modal';\nimport AuthCoreModalContainer from '../components/particle-modal/authCoreModalContainer';\nimport { dictionaries } from '../locales';\nimport type { AccountTipModalProps } from '../pages/account/accountTipModal';\nimport type { PaymentVerify } from '../pages/account/paymentVerifyModal';\nimport type { WrongPasswordInfo } from '../pages/account/wrongPasswordModal';\nimport { isServer } from '../utils';\nimport authorizeUtils from '../utils/authorizeUtils';\nimport { isSolana } from '../utils/chain-utils';\nimport { toHexPrefixString } from '../utils/common-utils';\nimport { sendAnalyticsActive } from '../utils/sendAnalyticsActive';\nimport { getVersion } from '../utils/version';\nimport ProviderInject from './providerInject';\nimport type {\n AuthCoreModalOptions,\n ConnectionStatus,\n IAuthCoreModal,\n ICustomRouter,\n Language,\n NavigateOptions,\n SocialConnectCallback,\n ViewType,\n} from './types';\nimport { AuthCoreModalEvent } from './types';\nimport Web3ModalProvider from './web3ModalProvider';\n\nconst IframeWalletPlugin = React.lazy(() => import('./iframeWalletPlugin'));\nconst AccountTipModal = React.lazy(() => import('../pages/account/accountTipModal'));\nconst PaymentVerifyModal = React.lazy(() => import('../pages/account/paymentVerifyModal'));\nconst WrongPasswordModal = React.lazy(() => import('../pages/account/wrongPasswordModal'));\nconst SelectSecurityAccount = React.lazy(() => import('../pages/account/selectSecurityAccount'));\n\nexport const defaultModalOptions: Partial<AuthCoreModalOptions> = {\n themeType: 'light',\n language: 'en',\n fiatCoin: 'USD',\n promptSettingConfig: {\n promptPaymentPasswordSettingWhenSign: 1,\n promptMasterPasswordSettingWhenLogin: 0,\n },\n customStyle: {\n modalWidth: 400,\n modalHeight: 650,\n },\n wallet: { visible: true, themeType: 'light' },\n};\n\ninterface GlobalState {\n connectionStatus: ConnectionStatus;\n setConnectionStatus: (status: ConnectionStatus) => void;\n userInfo?: UserInfo;\n modalOptions: AuthCoreModalOptions;\n setModalOptions: (options: any) => void;\n showSelectSecurityAccount: (display: boolean, state?: any) => void;\n setWrongPassword: (info: WrongPasswordInfo) => void;\n setPaymentVerify: (props: PaymentVerify) => void;\n showAccountTipModal: (props: AccountTipModalProps) => void;\n setPaymentPassword: () => void;\n loginSuccessRedirectToApp: () => void;\n events: EventEmitter;\n authCoreModal: IAuthCoreModal;\n setAuthCoreModal: (params: any) => void;\n customRouter: ICustomRouter;\n setCustomRouter: (params: any) => void;\n customNavigate: (url: string | number, options?: NavigateOptions) => void;\n setLoginAuthorization: (loginAuthorization?: LoginAuthorization) => void;\n authCoreModalClose: () => void;\n socialConnectCallback?: SocialConnectCallback;\n setSocialConnectCallback: (callback?: SocialConnectCallback) => void;\n walletEntryPlugin?: WalletEntryPlugin;\n lang: string;\n setLang: (value: Language) => void;\n}\n\nexport const GlobalContext = createContext<GlobalState | null>(null);\n\nconst events = new EventEmitter();\nevents.setMaxListeners(100);\n\nexport const AuthCoreContextProvider = (props: { options: AuthCoreModalOptions; children: React.ReactNode }) => {\n const [userInfo, _setUserInfo] = useState<UserInfo | undefined>();\n const userInfoRef = useRef<UserInfo | undefined>(userInfo);\n\n const setUserInfo = (info: UserInfo | undefined) => {\n _setUserInfo(info);\n userInfoRef.current = info;\n };\n\n const [lang, setLang] = useState<Language>('en');\n const rootRef = React.useRef<HTMLDivElement>(null);\n\n const [styles, setStyles] = useState<any>({\n styleList: [\n {\n name: 'antStyle',\n loadStyle: () => import('../../../../node_modules/antd/dist/antd.css'),\n styleContent: '',\n },\n {\n name: 'globalStyle',\n loadStyle: () => import('../common/style/global.scss'),\n styleContent: '',\n },\n {\n name: 'scrollStyle',\n loadStyle: () => import('../common/style/scroll.scss'),\n styleContent: '',\n },\n {\n name: 'themeStyle',\n loadStyle: () => import('../common/style/theme.scss'),\n styleContent: '',\n },\n ],\n });\n\n const [modalOptions, setModalOptions] = useSetState<AuthCoreModalOptions>(props.options);\n\n const [selectSecurityAccount, setSelectSecurityAccount] = useState(false);\n\n const [verifySecurityAccountState, setVerifySecurityAccountState] = useState<any>();\n\n const [wrongPassword, setWrongPassword] = useState<WrongPasswordInfo>({ visible: false });\n\n const [accountTipModal, setAccountTipModal] = useState<AccountTipModalProps>({\n visible: false,\n });\n\n const initParticleAuthRef = React.useRef<any>(false);\n\n const [paymentVerify, setPaymentVerify] = useState<PaymentVerify>({ visible: false });\n\n const [loginAuthorization, _setLoginAuthorization] = useState<LoginAuthorization>();\n\n const loginAuthorizationRef = useRef<any>(null);\n\n const setLoginAuthorization = (data?: LoginAuthorization) => {\n _setLoginAuthorization(data);\n loginAuthorizationRef.current = data;\n };\n\n const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('loading');\n\n const showSelectSecurityAccount = (display: boolean, state?: any) => {\n setVerifySecurityAccountState(state);\n setSelectSecurityAccount(display);\n };\n\n const showAccountTipModal = (props: AccountTipModalProps) => {\n setAccountTipModal(props);\n };\n\n const loadStyles = () => {\n const fns = styles.styleList.map((item: any) => {\n return item?.loadStyle?.();\n });\n Promise.all(fns).then((styles: any) => {\n const styleList = styles.map((item: any) => {\n return {\n styleContent: item.default,\n };\n });\n setStyles({\n styleList,\n });\n });\n };\n\n const [customRouter, setCustomRouter] = useSetState<ICustomRouter>({ path: '/' });\n const [authCoreModal, setAuthCoreModal] = useSetState<IAuthCoreModal>({\n viewType: 'mobile' as ViewType,\n particleModalVisible: false,\n rootModalContainer: null,\n rootBody: null,\n closable: true,\n });\n\n const customNavigate = useCallback(\n (url: string | number, options?: NavigateOptions) => {\n if (typeof url === 'string' && url !== '/') {\n url = url.replace(/^\\//, '');\n }\n\n console.log('customNavigate', url, options);\n\n let { history } = customRouter;\n\n if (!history) {\n history = [];\n }\n\n if (options?.back === true) {\n if (\n typeof url === 'string' &&\n !!history.find((item: any) => item.path.replace(/^\\//, '') === (url as string).replace(/^\\//, ''))\n ) {\n url = history.findIndex((item) => item.path === url) - history.length + 1;\n }\n }\n\n if (typeof url === 'string') {\n if (url !== AuthPage.Login && url !== AuthPage.Index && !isConnected()) {\n throw AuthError.notLogin();\n }\n\n const { replace = false, state = {} } = options || {};\n\n if (history.length && replace) {\n history.pop();\n }\n\n history.push({\n path: url,\n state,\n updateAt: new Date().getTime(),\n });\n\n setCustomRouter({\n path: url,\n replace,\n state,\n history,\n });\n } else if (typeof url === 'number' && history.length > 0) {\n for (let i = 0; i < Math.abs(url); i++) {\n history.pop();\n }\n\n if (history.length === 0) {\n setAuthCoreModal({\n particleModalVisible: false,\n });\n return;\n }\n\n const route = history[history.length - 1];\n setCustomRouter({\n path: route?.path,\n replace: false,\n state: route?.state,\n history,\n });\n }\n },\n [customRouter, setAuthCoreModal, setCustomRouter]\n );\n\n const setPaymentPassword = useCallback(() => {\n if (userInfo?.security_account?.has_set_payment_password) {\n return;\n }\n if (!userInfo?.security_account?.email && !userInfo?.security_account?.phone) {\n customNavigate('/account/bind', {\n state: {\n accountType: userInfo?.email ? 'phone' : 'email',\n showSwitch: true,\n redirectUrl: '/account/set-password',\n },\n });\n return;\n }\n if (userInfo?.security_account?.email && userInfo?.security_account?.phone) {\n showSelectSecurityAccount(true);\n } else {\n customNavigate('/account/set-password', {\n state: {\n account: userInfo?.security_account?.email || userInfo?.security_account?.phone,\n },\n });\n }\n }, [userInfo, customNavigate]);\n\n const loginSuccessRedirectToApp = useCallback(() => {\n console.log('login success', 'redirectToApp');\n\n const userInfo = userInfoRef.current;\n const loginAuthorization = loginAuthorizationRef.current;\n\n let authorizeResult;\n if (loginAuthorization && authorizeUtils.isNeedAuthorize(loginAuthorization)) {\n if (hasPaymentPassword()) {\n if (isSolana(loginAuthorization.chain)) {\n const message = (loginAuthorization.authorization as SolanaAuthorization).message;\n customNavigate(AuthPage.SolanaSign, {\n state: {\n method: SolanaRpcMethod.signMessage,\n param: base58.decode(message),\n loginAuthorizationSign: true,\n },\n });\n } else {\n authorizeUtils\n .generateMessage(loginAuthorization.chain, modalOptions.erc4337)\n .then((message) => {\n customNavigate(AuthPage.EvmSign, {\n state: {\n method: loginAuthorization.authorization.uniq\n ? EvmRpcMethod.personalSignUniq\n : EvmRpcMethod.personalSign,\n param: toHexPrefixString(message),\n loginAuthorizationSign: true,\n },\n });\n })\n .catch((error) => {\n events.emit(AuthCoreModalEvent.LoginFail, error);\n });\n }\n return;\n } else {\n authorizeUtils\n .authorize(loginAuthorization, modalOptions.erc4337)\n .then((result) => {\n events.emit(AuthCoreModalEvent.LoginSuccess, {\n ...userInfo,\n authorization: result,\n });\n })\n .catch((error) => {\n events.emit(AuthCoreModalEvent.LoginFail, error);\n });\n }\n } else {\n events.emit(AuthCoreModalEvent.LoginSuccess, {\n ...(userInfo ?? getUserInfo()),\n authorization: authorizeResult,\n });\n }\n }, [customNavigate, userInfo, modalOptions.erc4337, loginAuthorization]);\n\n const authCoreModalClose = () => {\n if (events.listenerCount(AuthCoreModalEvent.SignResponse) > 0) {\n events.emit(AuthCoreModalEvent.SignResponse, {\n error: AuthError.userRejectedRequest(),\n });\n }\n\n if (events.listenerCount(AuthCoreModalEvent.LoginSuccess)) {\n if (customRouter?.path === AuthPage.Login || !userInfo) {\n events.emit(AuthCoreModalEvent.LoginFail, AuthError.userCancelOperation());\n } else {\n events.emit(AuthCoreModalEvent.LoginSuccess, userInfo);\n }\n }\n\n setAuthCoreModal({\n particleModalVisible: false,\n });\n };\n\n const getPageHTML = () => {\n let htmlContent = rootRef.current?.outerHTML || '';\n const path = rootRef.current?.getAttribute('data-path');\n htmlContent = Buffer.from(htmlContent).toString('base64');\n console.log('getPageHTML', path, htmlContent.substring(0, 30) + '...');\n return htmlContent;\n };\n\n const fontStyle = useMemo(() => {\n let style = '';\n if (!isServer()) {\n const bodyFontFamily = window.getComputedStyle(window.document.body).fontFamily;\n const fontFamily =\n props.options?.customStyle?.fontFamily ||\n bodyFontFamily ||\n '\"PingFang SC\", \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif';\n\n style = ` \n .particle-auth-core-shadow-root {\n --auth-core-font-family: ${fontFamily};\n }\n `;\n }\n\n return style;\n }, [props.options?.customStyle?.fontFamily]);\n\n // \u81EA\u5B9A\u4E49 \u4E3B\u9898\n const customThemeStyle = useMemo(() => {\n const themeType = modalOptions.themeType;\n const customStyle = modalOptions.customStyle || {};\n\n const theme = {\n ...Object.fromEntries(\n Object.keys(customStyle || {})\n .filter((key) => key !== 'theme')\n .map((key) => {\n return [key, (customStyle as any)?.[key] || ''];\n })\n ),\n\n ...(customStyle?.theme?.[themeType === 'light' ? 'light' : 'dark'] || {}),\n };\n\n const styleContent = Object.keys(theme)\n .filter((key) => key !== 'fontFamily')\n .map((key) => {\n const name = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n let value = theme[key as keyof typeof theme];\n if (!isNaN(Number(value))) {\n value = value + 'px';\n }\n return `--${name}:${value};`;\n })\n .join('\\n');\n\n if (themeType === 'dark') {\n return `.particle-auth-core-shadow-root.dark{\n ${styleContent}\n }`;\n }\n\n return `.particle-auth-core-shadow-root{\n ${styleContent}\n }`;\n }, [modalOptions.themeType, modalOptions.customStyle]);\n\n useEffect(() => {\n setModalOptions({\n ...defaultModalOptions,\n ...props.options,\n promptSettingConfig: {\n ...defaultModalOptions.promptSettingConfig,\n ...props.options.promptSettingConfig,\n },\n customStyle: {\n ...defaultModalOptions.customStyle,\n ...props.options.customStyle,\n },\n });\n }, [props.options, setModalOptions]);\n\n useEffect(() => {\n // \u5F39\u51FA\u7A97\u53E3\u540E \u9501\u5B9Abody \u6EDA\u52A8\n if (!document.querySelector('.particle-auth-core-body-style')) {\n const style = document.createElement('style');\n style.classList.add('particle-auth-core-body-style');\n const styleContent = `.particle-modal-lock {\n overflow: hidden !important;\n }`;\n style.innerHTML = styleContent;\n document.head.appendChild(style);\n }\n\n if (authCoreModal.particleModalVisible) {\n document.body.classList.add('particle-modal-lock');\n const rootBody = authCoreModal.rootModalContainer?.querySelector(\n '.root-particle-modal-container .ant-drawer-body,.root-particle-modal-container .ant-modal-body'\n ) as HTMLDivElement;\n setAuthCoreModal({\n rootBody,\n });\n } else {\n // \u5173\u95EDrootModal\u5F39\u7A97\u540E\uFF0C\u5EF6\u65F6\u6E05\u7A7A\u8DEF\u7531\n setTimeout(() => {\n setCustomRouter({\n path: '/',\n history: [],\n });\n }, 100);\n document.body.classList.remove('particle-modal-lock');\n }\n }, [authCoreModal.particleModalVisible, authCoreModal.rootModalContainer]);\n\n const bodySze = useSize(isServer() ? null : document.body);\n\n useEffect(() => {\n // \u8BBE\u7F6EviewType \u4E3A\u5168\u5C40\u53D8\u91CF desktop | mobile\n let viewType: ViewType = 'mobile';\n if (document.body.clientWidth >= 600) {\n viewType = 'desktop';\n }\n setAuthCoreModal({\n viewType,\n });\n }, [bodySze]);\n\n useEffect(() => {\n if (customRouter?.path && customRouter?.path != '/') {\n // \u8DEF\u7531 path \u4E0D\u4E3A / \u65F6\uFF0C\u6253\u5F00rootModal\u5F39\u7A97\n setAuthCoreModal({\n particleModalVisible: true,\n });\n }\n }, [customRouter?.path]);\n\n useEffect(() => {\n // \u8BBE\u7F6E\u6A21\u6001\u6846\u7684\u5BBD\u5EA6\u548C\u9AD8\u5EA6\uFF0C\u54CD\u5E94\u79FB\u52A8\u7AEF\u89C6\u56FE\n if (rootRef.current) {\n const minWidth = 300;\n const minHeight = 500;\n let modalWidth = Math.max(minWidth, Number(modalOptions.customStyle?.modalWidth));\n let modalHeight = Math.max(minHeight, Number(modalOptions.customStyle?.modalHeight));\n\n if (authCoreModal.viewType == 'mobile') {\n modalWidth = document.body.clientWidth;\n modalHeight = document.body.clientHeight * 0.86;\n }\n const vm = modalWidth / 100;\n rootRef.current.style.setProperty('--vw', vm + 'px');\n rootRef.current.style.setProperty('--doc-height', modalHeight + 'px');\n rootRef.current.style.setProperty('--doc-width', modalWidth + 'px');\n }\n }, [\n rootRef.current,\n authCoreModal.viewType,\n modalOptions.customStyle?.modalWidth,\n modalOptions.customStyle?.modalHeight,\n ]);\n\n useEffect(() => {\n setTimeout(() => {\n setLang(modalOptions.language || 'en');\n });\n }, [modalOptions.language]);\n\n useEffect(() => {\n const onMasterPasswordChange = () => {\n customNavigate(AuthPage.MasterPasswordVerify);\n };\n window?.particleAuth?.on(AuthCoreEvent.MasterPasswordChanged, onMasterPasswordChange);\n return () => {\n window?.particleAuth?.off(AuthCoreEvent.MasterPasswordChanged, onMasterPasswordChange);\n };\n }, [customRouter.history, customNavigate]);\n\n useEffect(() => {\n const userInfoChanged = (userInfo: UserInfo) => {\n setUserInfo(userInfo);\n };\n const onDisconnect = (error?: Error) => {\n setUserInfo(undefined);\n if (events.listenerCount(AuthCoreModalEvent.SignResponse) > 0) {\n // \u5982\u679C\u6709\u76D1\u542CsignResponse\u4E8B\u4EF6\uFF0C\u8BF4\u660E\u662F\u5728\u7B7E\u540D\u4E2D\u65AD\uFF0C\u9700\u8981\u89E6\u53D1signResponse\u4E8B\u4EF6\n events.emit(AuthCoreModalEvent.SignResponse, {\n error: error || AuthError.notLogin(),\n });\n }\n setAuthCoreModal({\n particleModalVisible: false,\n });\n };\n\n window?.particleAuth?.on(AuthCoreEvent.UserInfoChanged, userInfoChanged);\n window?.particleAuth?.on(AuthCoreEvent.ParticleAuthDisconnect, onDisconnect);\n return () => {\n window?.particleAuth?.off(AuthCoreEvent.UserInfoChanged, userInfoChanged);\n window?.particleAuth?.off(AuthCoreEvent.ParticleAuthDisconnect, onDisconnect);\n };\n }, []);\n\n if (!initParticleAuthRef.current || particleAuth.chains != modalOptions.chains) {\n // authCore \u521D\u59CB\u5316\n particleAuth.init({\n projectId: modalOptions.projectId || props.options.projectId,\n clientKey: modalOptions.clientKey || props.options.clientKey,\n appId: modalOptions.appId || props.options.appId,\n chains: modalOptions.chains,\n });\n const info = getUserInfo();\n setUserInfo(info);\n initParticleAuthRef.current = true;\n\n if (info) {\n const { token = '', ...restInfo } = info;\n sendAnalyticsActive('open', restInfo);\n }\n }\n\n useEffect(() => {\n if (isConnected()) {\n syncUserInfo().catch((e) => console.error('syncUserInfo', e));\n }\n loadStyles();\n\n if (!props?.options?.chains.length) {\n throw new Error('Auth Core: Please set chains in options');\n }\n }, []);\n\n if (!isServer()) {\n // @ts-ignore\n window.__getConnectCaptcha = getConnectCaptcha;\n // @ts-ignore\n window.__connect = connect;\n // @ts-ignore\n window.__getUserInfo = getUserInfo;\n // @ts-ignore\n window.__getPageHTML = getPageHTML;\n // @ts-ignore\n window.__navigate = customNavigate;\n // @ts-ignore\n window.__particleAuthCoreModal__ = rootRef?.current;\n }\n\n useEffect(() => {\n console.log('\uD83D\uDE80 ~ AuthCoreContextProvider ~ connectionStatus:', connectionStatus);\n }, [connectionStatus]);\n\n const [socialConnectCallback, setSocialConnectCallback] = useState<SocialConnectCallback>();\n const [walletEntryPlugin, setWalletEntryPlugin] = useState<WalletEntryPlugin>();\n\n useAsyncEffect(async () => {\n if (modalOptions.wallet !== false) {\n const { walletEntryPlugin } = await import('@particle-network/wallet');\n setWalletEntryPlugin(walletEntryPlugin);\n }\n }, [modalOptions.wallet]);\n\n return (\n <GlobalContext.Provider\n value={{\n connectionStatus,\n setConnectionStatus,\n userInfo,\n modalOptions,\n setModalOptions,\n showSelectSecurityAccount,\n setWrongPassword,\n setPaymentVerify,\n showAccountTipModal,\n setPaymentPassword,\n loginSuccessRedirectToApp,\n events,\n authCoreModal,\n setAuthCoreModal,\n customRouter,\n setCustomRouter,\n customNavigate,\n setLoginAuthorization,\n authCoreModalClose,\n socialConnectCallback,\n setSocialConnectCallback,\n walletEntryPlugin,\n lang,\n setLang,\n }}\n >\n <ProviderInject>{props.children}</ProviderInject>\n\n {isServer() ? (\n <div></div>\n ) : (\n <root.div mode='closed'>\n <div\n id='particle-auth-core-modal'\n className={`particle-auth-core-shadow-root ${modalOptions.themeType}`}\n data-path={customRouter?.path}\n data-auth-core-modal-version={getVersion()}\n data-auth-core-version={particleAuth.version}\n ref={rootRef}\n onClick={(event) => {\n event.stopPropagation();\n }}\n >\n {/* <KeyboardProvider container={rootRef.current as HTMLElement}> */}\n <FocusTrap>\n <style data-name='fontStyle'>{fontStyle}</style>\n {!!styles.styleList[0].styleContent &&\n styles.styleList.map((item: any, index: number) => {\n return (\n <style data-name={item.name} data-index={index} key={index}>\n {item.styleContent}\n </style>\n );\n })}\n <style data-name='customTheme'>{customThemeStyle as unknown as string}</style>\n\n {authCoreModal.rootModalContainer && (\n <ParticleModal\n visible={authCoreModal.particleModalVisible}\n closable={authCoreModal.closable}\n onClose={() => {\n authCoreModalClose();\n }}\n className='root-particle-modal-container'\n modalProps={{\n footer: null,\n width: parseInt(modalOptions.customStyle?.modalWidth as unknown as string),\n height: parseInt(modalOptions.customStyle?.modalHeight as unknown as string),\n forceRender: true,\n destroyOnClose: true,\n style: {\n maxWidth: modalOptions.customStyle?.modalWidth,\n },\n zIndex: modalOptions.customStyle?.zIndex || 99999,\n }}\n drawerProps={{\n height: '86%',\n closable: false,\n forceRender: true,\n destroyOnClose: true,\n push: false,\n zIndex: modalOptions.customStyle?.zIndex || 99999,\n }}\n >\n <CustomRouter\n path={customRouter?.path}\n state={customRouter?.state}\n replace={customRouter?.replace}\n children={customRouter?.children}\n />\n </ParticleModal>\n )}\n\n <React.Suspense>\n <IframeWalletPlugin />\n <SelectSecurityAccount visible={selectSecurityAccount} state={verifySecurityAccountState} />\n <AccountTipModal modal={accountTipModal} />\n <PaymentVerifyModal props={paymentVerify} />\n <WrongPasswordModal info={wrongPassword} />\n </React.Suspense>\n <AuthCoreModalContainer data-ref='rootModalContainer' setAuthCoreModal={setAuthCoreModal} />\n {props.options.web3Modal && <Web3ModalProvider web3Modal={props.options.web3Modal} />}\n </FocusTrap>\n {/* </KeyboardProvider> */}\n </div>\n </root.div>\n )}\n </GlobalContext.Provider>\n );\n};\n\nexport const useParticleAuth = () => {\n const context = useContext(GlobalContext);\n if (!context) {\n throw new Error('Auth Core hooks must used in AuthCoreContextProvider');\n }\n return context;\n};\n\nexport const useAuthCoreModal = () => {\n const { authCoreModal, setAuthCoreModal, authCoreModalClose } = useParticleAuth();\n return {\n authCoreModalClose,\n authCoreModal,\n setAuthCoreModal,\n };\n};\n\nexport const useCustomRouter = () => {\n const { customRouter, setCustomRouter } = useParticleAuth();\n return {\n customRouter,\n setCustomRouter,\n };\n};\n\nexport const useCustomNavigate = () => {\n const { customNavigate } = useParticleAuth();\n\n return customNavigate;\n};\n\nexport const useSupportedChains = () => {\n const { modalOptions } = useParticleAuth();\n return modalOptions?.chains || [];\n};\n\nexport const useModalOptions = () => {\n const { modalOptions, setModalOptions } = useParticleAuth();\n return {\n modalOptions,\n setModalOptions,\n };\n};\n\nexport const useEvents = () => {\n const { events } = useParticleAuth();\n return { events };\n};\n\nexport const useTranslation = () => {\n const { lang } = useParticleAuth();\n const tempData = (dictionaries[lang.toLowerCase() as keyof typeof dictionaries] || dictionaries.en)();\n type ValueType = Awaited<typeof tempData>;\n const [data, setData] = useState<ValueType>({} as ValueType);\n\n useEffect(() => {\n async function getTranslations() {\n const targetTranslations = await tempData;\n\n setData(targetTranslations);\n }\n\n getTranslations();\n }, [lang]);\n\n const getTranslation = useCallback(\n (key: string) => {\n const keyItems = key.split('.');\n return get(data, keyItems, '');\n },\n [data]\n );\n\n return { ...data, t: getTranslation };\n};\n", "import { isConnected } from '@particle-network/auth-core';\nimport qs from 'qs';\nimport React, { useMemo } from 'react';\nimport type { ICustomRouter } from '../../context/types';\nimport ParticleLoading from '../loading';\n\nexport enum AuthPage {\n Index = 'index',\n Login = 'login',\n EvmSign = 'evm-chain/sign',\n SolanaSign = 'solana/sign',\n LoginAccount = 'login-account',\n AccountSecurity = 'account/security',\n MasterPasswordVerify = 'account/master-password/verify',\n MasterPasswordPrompt = 'account/master-password/prompt',\n}\n\ninterface IPage {\n name?: AuthPage;\n path?: string;\n component: React.LazyExoticComponent<any>;\n}\n\nconst pages: IPage[] = [\n {\n name: AuthPage.Index,\n component: React.lazy(() => import('../../pages/index/index')),\n },\n {\n name: AuthPage.Login,\n component: React.lazy(() => import('../../pages/login')),\n },\n {\n path: 'account/security',\n component: React.lazy(() => import('../../pages/account/security')),\n },\n {\n name: AuthPage.LoginAccount,\n path: 'login-account',\n component: React.lazy(() => import('../../pages/loginAccount/index')),\n },\n {\n path: 'manageDevices/deviceList',\n component: React.lazy(() => import('../../pages/manageDevices/deviceList/index')),\n },\n {\n path: 'manageDevices/deviceDetails',\n component: React.lazy(() => import('../../pages/manageDevices/deviceDetails/index')),\n },\n {\n name: AuthPage.EvmSign,\n component: React.lazy(() => import('../../pages/sign/index')),\n },\n {\n name: AuthPage.SolanaSign,\n component: React.lazy(() => import('../../pages/sign/components/info-sign')),\n },\n {\n path: 'account/bind',\n component: React.lazy(() => import('../../pages/account/accountBind/index')),\n },\n {\n path: 'account/verify',\n component: React.lazy(() => import('../../pages/account/accountVerify/index')),\n },\n {\n path: 'login-account',\n component: React.lazy(() => import('../../pages/loginAccount/index')),\n },\n {\n path: 'manageDevices/deviceDetails',\n component: React.lazy(() => import('../../pages/manageDevices/deviceDetails/index')),\n },\n {\n path: 'account/master-password',\n component: React.lazy(() => import('../../pages/account/setMasterPassword/index')),\n },\n {\n path: 'account/master-password/description',\n component: React.lazy(() => import('../../pages/account/masterPasswordDescription/index')),\n },\n {\n path: AuthPage.MasterPasswordVerify,\n component: React.lazy(() => import('../../pages/account/masterPasswordVerify/index')),\n },\n {\n path: AuthPage.MasterPasswordPrompt,\n component: React.lazy(() => import('../../pages/account/masterPasswordModal/index')),\n },\n {\n path: 'account/master-password/change',\n component: React.lazy(() => import('../../pages/account/masterPasswordChange/index')),\n },\n {\n path: 'account/set-password',\n component: React.lazy(() => import('../../pages/account/setPaymentPassword/index')),\n },\n {\n path: 'account/change-password',\n component: React.lazy(() => import('../../pages/account/changePaymentPassword/index')),\n },\n {\n path: 'login-account/bind',\n component: React.lazy(() => import('../../pages/loginAccountBind/index')),\n },\n {\n path: 'login-account/bind-loading',\n component: React.lazy(() => import('../../pages/account/loginAccountBindLoading/index')),\n },\n];\n\nconst CustomRouter = (props: ICustomRouter) => {\n const path = useMemo(() => {\n let path = props.path?.split('?')?.[0].replace(/^\\//, '');\n if (path == '' || path == '/') {\n path = AuthPage.Index;\n }\n if (path !== AuthPage.Login && !isConnected() && path !== AuthPage.Index) {\n path = AuthPage.Login;\n }\n return path;\n }, [props.path]);\n\n const queryString = useMemo(() => {\n let queryString = props.path?.split('?')?.[1];\n if (path !== AuthPage.Login && !isConnected() && path !== AuthPage.Index) {\n queryString = '';\n }\n return queryString;\n }, [props.path, path]);\n\n const state = useMemo(() => {\n return {\n ...(props.state || {}),\n ...qs.parse(queryString),\n };\n }, [props.state, queryString]);\n\n const Comp = useMemo(() => {\n let Comp = pages.find((item: IPage) => item.name === path || item.path === path);\n if (!Comp) {\n if (path) {\n console.log('Not Found', path);\n }\n Comp = pages[0];\n }\n return Comp;\n }, [path]);\n\n return (\n <div data-path={path} data-state={qs.stringify(state)} key={path}>\n <React.Suspense\n fallback={\n <div className='lazy-loaing-container'>\n <ParticleLoading />\n </div>\n }\n >\n <Comp.component {...state} children={props.children} />\n </React.Suspense>\n </div>\n );\n};\n\nexport default CustomRouter;\n", "import React from 'react';\nimport styles from './index.less';\n\nconst ParticleLoading = () => {\n return (\n <>\n <style>{styles as unknown as string}</style>\n <div className='loading-content'>\n <div className='lds-default'>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n </div>\n <div className='power-text'>Powered by</div>\n <div className='logo-text'>Particle Network</div>\n </div>\n </>\n );\n};\n\nexport default ParticleLoading;\n", "import React, { useEffect } from 'react';\nimport useFocusTrap from '../../context/hooks/useFocusTrap';\n\nexport default function FocusTrap(props: any) {\n const elRef = useFocusTrap();\n\n useEffect(() => {\n if (!elRef.current) return;\n elRef.current.focus({ preventScroll: true });\n }, []);\n\n return (\n <div ref={elRef} tabIndex={0}>\n {props.children}\n </div>\n );\n}\n", "import { useEffect, useRef } from 'react';\n\nconst KEYCODE_TAB = 9;\n\nexport default function useFocusTrap() {\n const elRef = useRef<any>(null);\n\n function handleFocus(e: any) {\n if (!elRef.current) return;\n const focusableEls = elRef.current.querySelectorAll(`\n a[href]:not(:disabled),\n button:not(:disabled),\n textarea:not(:disabled),\n input[type=\"text\"]:not(:disabled),\n input[type=\"radio\"]:not(:disabled),\n input[type=\"checkbox\"]:not(:disabled),\n select:not(:disabled)\n `),\n firstFocusableEl = focusableEls[0],\n lastFocusableEl = focusableEls[focusableEls.length - 1];\n\n const isTabPressed = e.key === 'Tab' || e.keyCode === KEYCODE_TAB;\n\n if (!isTabPressed) {\n return;\n }\n\n if (e.shiftKey) {\n /* shift + tab */ if (document.activeElement === firstFocusableEl) {\n lastFocusableEl.focus();\n e.preventDefault();\n }\n } /* tab */ else {\n if (document.activeElement === lastFocusableEl) {\n firstFocusableEl.focus();\n e.preventDefault();\n }\n }\n }\n\n useEffect(() => {\n if (elRef.current) {\n elRef.current.addEventListener('keydown', handleFocus);\n elRef.current.focus({ preventScroll: true });\n }\n return () => {\n if (elRef.current) {\n elRef.current.removeEventListener('keydown', handleFocus);\n }\n };\n }, []);\n\n return elRef;\n}\n", "import { Modal } from 'antd';\nimport React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';\n\nimport { useAuthCoreModal } from '../../context';\nimport ParticleDrawer from '..