diff --git a/packages/web3-react/package.json b/packages/web3-react/package.json index b28c0ff13..6874e8f1d 100644 --- a/packages/web3-react/package.json +++ b/packages/web3-react/package.json @@ -41,6 +41,7 @@ "@alephium/get-extension-wallet": "workspace:^", "@alephium/walletconnect-provider": "workspace:^", "@alephium/walletconnect-qrcode-modal": "^0.1.0", + "use-sync-external-store": "^1.2.2", "buffer": "^6.0.3", "detect-browser": "^5.3.0", "framer-motion": "^6.5.1", @@ -62,6 +63,7 @@ "@types/react-dom": "^18.0.11", "@types/styled-components": "^5.1.26", "eslint-plugin-react-hooks": "^4.6.0", + "@types/use-sync-external-store": "^0.0.6", "rollup": "^2.79.2", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", diff --git a/packages/web3-react/src/components/AlephiumConnect.tsx b/packages/web3-react/src/components/AlephiumConnect.tsx index ef092bf13..e2e088235 100644 --- a/packages/web3-react/src/components/AlephiumConnect.tsx +++ b/packages/web3-react/src/components/AlephiumConnect.tsx @@ -47,6 +47,7 @@ import { } from '../contexts/alephiumConnect' import { getLastConnectedAccount, removeLastConnectedAccount } from '../utils/storage' import { ConnectResult, getConnectorById } from '../utils/connector' +import { useInjectedProviders } from '../hooks/useInjectedProviders' export const ConnectSettingProvider: React.FC<{ theme?: Theme @@ -132,6 +133,7 @@ export const AlephiumConnectProvider: React.FC<{ const [_network, setNetwork] = useState(network) const [_addressGroup, setAddressGroup] = useState(addressGroup) const [_keyType, setKeyType] = useState(keyType ?? 'default') + const allInjectedProviders = useInjectedProviders() useEffect(() => setNetwork(network), [network]) useEffect(() => setAddressGroup(addressGroup), [addressGroup]) @@ -196,7 +198,14 @@ export const AlephiumConnectProvider: React.FC<{ for (const connectorId of sortedConnectorIds) { const connector = getConnectorById(connectorId) if (connector.autoConnect !== undefined) { - const result = await connector.autoConnect({ network, addressGroup, keyType, onDisconnected, onConnected }) + const result = await connector.autoConnect({ + network, + addressGroup, + keyType, + onDisconnected, + onConnected, + allInjectedProviders: connectorId === 'injected' ? allInjectedProviders : undefined + }) if (result !== undefined) { return } diff --git a/packages/web3-react/src/components/ConnectModal/ConnectWithInjector/index.tsx b/packages/web3-react/src/components/ConnectModal/ConnectWithInjector/index.tsx index 903b1eafb..bf91bd955 100644 --- a/packages/web3-react/src/components/ConnectModal/ConnectWithInjector/index.tsx +++ b/packages/web3-react/src/components/ConnectModal/ConnectWithInjector/index.tsx @@ -19,7 +19,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { AnimatePresence, Variants } from 'framer-motion' import { Container, ConnectingContainer, ConnectingAnimation, RetryButton, RetryIconContainer, Content } from './styles' -import { useAlephiumConnectContext, useConnectSettingContext } from '../../../contexts/alephiumConnect' +import { useConnectSettingContext } from '../../../contexts/alephiumConnect' import supportedConnectors from '../../../constants/supportedConnectors' import { @@ -42,9 +42,13 @@ import BrowserIcon from '../../Common/BrowserIcon' import { AlertIcon, TickIcon } from '../../../assets/icons' import { detectBrowser } from '../../../utils' import { useConnect } from '../../../hooks/useConnect' +import { AlephiumWindowObject } from '@alephium/get-extension-wallet' +import { ConnectorButton, ConnectorIcon, ConnectorLabel, ConnectorsContainer } from '../../Pages/Connectors/styles' +import { useInjectedProviders } from '../../../hooks/useInjectedProviders' const states = { CONNECTED: 'connected', + LISTING: 'listing', CONNECTING: 'connecting', EXPIRING: 'expiring', FAILED: 'failed', @@ -88,6 +92,11 @@ const ConnectWithInjector: React.FC<{ forceState?: typeof states }> = ({ connectorId, switchConnectMethod, forceState }) => { const { setOpen } = useConnectSettingContext() + const providers = useInjectedProviders() + const [injectedProvider, setInjectedProvider] = useState( + providers.length !== 0 ? providers[0] : undefined + ) + console.log(`providers size: ${providers.length}`) const { connect } = useConnect() const [id, setId] = useState(connectorId) @@ -97,7 +106,7 @@ const ConnectWithInjector: React.FC<{ const expiryDefault = 9 // Starting at 10 causes layout shifting, better to start at 9 const [expiryTimer, setExpiryTimer] = useState(expiryDefault) - const hasExtensionInstalled = connector.extensionIsInstalled && connector.extensionIsInstalled() + const hasExtensionInstalled = providers.length > 0 const browser = detectBrowser() const extensionUrl = connector.extensions ? connector.extensions[browser] : undefined @@ -111,20 +120,30 @@ const ConnectWithInjector: React.FC<{ } : undefined + const defaultState = providers.length > 1 ? states.LISTING : states.CONNECTING + const [status, setStatus] = useState( - forceState ? forceState : !hasExtensionInstalled ? states.UNAVAILABLE : states.CONNECTING + forceState ? forceState : !hasExtensionInstalled ? states.UNAVAILABLE : defaultState + ) + + const handleConnect = useCallback( + (injectedProvider) => { + setInjectedProvider(injectedProvider) + setStatus(states.CONNECTING) + }, + [setStatus, setInjectedProvider] ) const runConnect = useCallback(() => { - if (!hasExtensionInstalled) return + if (!hasExtensionInstalled || status === states.LISTING) return - connect().then((address) => { + connect(injectedProvider).then((address) => { if (!!address) { setStatus(states.CONNECTED) } setOpen(false) }) - }, [hasExtensionInstalled, setOpen, connect]) + }, [hasExtensionInstalled, setOpen, connect, status, injectedProvider]) const connectTimeoutRef = useRef>() useEffect(() => { @@ -187,270 +206,301 @@ const ConnectWithInjector: React.FC<{ return ( - - - - {(status === states.FAILED || status === states.REJECTED) && ( - - - + + {providers.map((provider) => { + const name = getProviderName(provider) + return ( + handleConnect(provider)}> + + Icon + + {name} + + ) + })} + + + )} + {status !== states.LISTING && ( + <> + + + + {(status === states.FAILED || status === states.REJECTED) && ( + - - - - - )} - - - {/* - + + + + + + )} + + + {/* + + {copy.expiring.requestWillExpiryIn}{' '} + + + + {expiryTimer} + + + s + + + } + xOffset={-2} > - {copy.expiring.requestWillExpiryIn}{' '} - - - - {expiryTimer} - - - s - - - } - xOffset={-2} - > - */} - + ) : ( + <>{connector.logos.transparent ?? connector.logos.default} + ) + } + smallLogo={connector.id === 'injected'} + connecting={status === states.CONNECTING} + unavailable={status === states.UNAVAILABLE} + countdown={status === states.EXPIRING} + /> + {/* */} + + + + + {status === states.FAILED && ( + - {connector.logos.transparent ?? connector.logos.default} - - ) : ( - <>{connector.logos.transparent ?? connector.logos.default} - ) - } - smallLogo={connector.id === 'injected'} - connecting={status === states.CONNECTING} - unavailable={status === states.UNAVAILABLE} - countdown={status === states.EXPIRING} - /> - {/* */} - - - - - {status === states.FAILED && ( - - - - - {'failed'} - - {'failed'} - - {/* Reason: Coinbase Wallet does not expose a QRURI when extension is installed */} - {connector.scannable && ( - <> - - - - )} - - )} - {status === states.REJECTED && ( - - - {'rejected'} - {'rejected'} - - - {/* Reason: Coinbase Wallet does not expose a QRURI when extension is installed */} - {connector.scannable && ( - <> - - - + + + + {'failed'} + + {'failed'} + + {/* Reason: Coinbase Wallet does not expose a QRURI when extension is installed */} + {connector.scannable && ( + <> + + + + )} + )} - - )} - {(status === states.CONNECTING || status === states.EXPIRING) && ( - - - {connector.id === 'injected' ? 'connecting' : 'rejected'} - - - )} - {status === states.CONNECTED && ( - - - - {'Connected'} - - - - )} - {status === states.NOTCONNECTED && ( - - - {'Not Connected'} - - - )} - {status === states.UNAVAILABLE && ( - - {!extensionUrl ? ( - <> - - {'Not Available'} + {status === states.REJECTED && ( + + + {'rejected'} + {'rejected'} - {/** - - - */} - {!hasExtensionInstalled && suggestedExtension && ( - + {/* Reason: Coinbase Wallet does not expose a QRURI when extension is installed */} + {connector.scannable && ( + <> + + + )} - - ) : ( - <> - - {'Install'} + + )} + {(status === states.CONNECTING || status === states.EXPIRING) && ( + + + {connector.id === 'injected' ? 'connecting' : 'rejected'} - {/** - {(connector.scannable &&| - (!hasExtensionInstalled && extensionUrl)) && } - - {connector.scannable && ( - - )} - */} - {!hasExtensionInstalled && extensionUrl && ( - + */} + {!hasExtensionInstalled && suggestedExtension && ( + + )} + + ) : ( + <> + + {'Install'} + + {/** + {(connector.scannable &&| + (!hasExtensionInstalled && extensionUrl)) && } + + {connector.scannable && ( + + )} + */} + {!hasExtensionInstalled && extensionUrl && ( + + )} + )} - + )} - - )} - - + + + + )} ) } export default ConnectWithInjector + +function getProviderName(provider: AlephiumWindowObject): string { + if (provider.icon.includes('onekey')) { + return 'OneKey' + } + return 'Alephium' +} diff --git a/packages/web3-react/src/hooks/useConnect.tsx b/packages/web3-react/src/hooks/useConnect.tsx index 2dfaa0981..835d4c66a 100644 --- a/packages/web3-react/src/hooks/useConnect.tsx +++ b/packages/web3-react/src/hooks/useConnect.tsx @@ -54,9 +54,9 @@ export function useConnect() { }, [connectorId]) const connect = useMemo(() => { - return async () => { + return async (injectedProvider?) => { setConnectionStatus('connecting') - return await connector.connect(connectOptions) + return await connector.connect({ ...connectOptions, injectedProvider }) } }, [connector, connectOptions, setConnectionStatus]) diff --git a/packages/web3-react/src/hooks/useInjectedProviders.tsx b/packages/web3-react/src/hooks/useInjectedProviders.tsx new file mode 100644 index 000000000..15b4e36c1 --- /dev/null +++ b/packages/web3-react/src/hooks/useInjectedProviders.tsx @@ -0,0 +1,27 @@ +/* +Copyright 2018 - 2022 The Alephium Authors +This file is part of the alephium project. + +The library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the library. If not, see . +*/ + +import { useSyncExternalStore } from 'use-sync-external-store/shim' +import { injectedProviderStore } from '../utils/providers' + +export const useInjectedProviders = () => + useSyncExternalStore( + injectedProviderStore.subscribe, + injectedProviderStore.getProviders, + injectedProviderStore.getProviders + ) diff --git a/packages/web3-react/src/utils/connector.ts b/packages/web3-react/src/utils/connector.ts index 128e4857c..2d70cfb33 100644 --- a/packages/web3-react/src/utils/connector.ts +++ b/packages/web3-react/src/utils/connector.ts @@ -20,7 +20,7 @@ import { Account, NetworkId, SignerProvider, KeyType } from '@alephium/web3' import { WalletConnectProvider } from '@alephium/walletconnect-provider' import QRCodeModal from '@alephium/walletconnect-qrcode-modal' import { AlephiumWindowObject, getDefaultAlephiumWallet } from '@alephium/get-extension-wallet' -import { getLastConnectedAccount, setLastConnectedAccount } from './storage' +import { setLastConnectedAccount } from './storage' import { ConnectorId } from '../types' const WALLET_CONNECT_PROJECT_ID = '6e2562e43678dd68a9070a62b6d52207' @@ -38,7 +38,17 @@ export type ConnectOptions = { onConnected: (result: ConnectResult) => Promise | void } -export type ConnectFunc = (options: ConnectOptions) => Promise +export type InjectedConnectOptions = ConnectOptions & { + injectedProvider?: AlephiumWindowObject +} + +export type InjectedAutoConnectOptions = ConnectOptions & { + allInjectedProviders?: AlephiumWindowObject[] +} + +export type ConnectFunc = ( + options: ConnectOptions | InjectedConnectOptions | InjectedAutoConnectOptions +) => Promise export type Connector = { connect: ConnectFunc @@ -118,10 +128,16 @@ const wcDisconnect = async (signerProvider: SignerProvider): Promise => { await (signerProvider as WalletConnectProvider).disconnect() } -const injectedConnect = async (options: ConnectOptions): Promise => { +const injectedConnect = async (options: InjectedConnectOptions): Promise => { try { - const windowAlephium = await getDefaultAlephiumWallet() - const enabledAccount = await windowAlephium?.enable({ ...options, networkId: options.network }) + const windowAlephium = options.injectedProvider ?? (await getDefaultAlephiumWallet()) + const enableOptions = { + addressGroup: options.addressGroup, + keyType: options.keyType, + networkId: options.network, + onDisconnected: options.onDisconnected + } + const enabledAccount = await windowAlephium?.enable(enableOptions) if (windowAlephium && enabledAccount) { await options.onConnected({ account: enabledAccount, signerProvider: windowAlephium }) @@ -139,17 +155,32 @@ const injectedDisconnect = async (signerProvider: SignerProvider): Promise return await (signerProvider as AlephiumWindowObject).disconnect() } -const injectedAutoConnect = async (options: ConnectOptions): Promise => { +const injectedAutoConnect = async (options: InjectedAutoConnectOptions): Promise => { try { - const windowAlephium = await getDefaultAlephiumWallet() - const enabledAccount = await windowAlephium?.enableIfConnected({ ...options, networkId: options.network }) - - if (windowAlephium && enabledAccount) { - await options.onConnected({ account: enabledAccount, signerProvider: windowAlephium }) - setLastConnectedAccount('injected', enabledAccount, options.network) + const allProviders = options.allInjectedProviders ?? [] + if (allProviders.length === 0) { + const windowAlephium = await getDefaultAlephiumWallet() + if (windowAlephium !== undefined) { + allProviders.push(windowAlephium) + } } - - return enabledAccount + const enableOptions = { + addressGroup: options.addressGroup, + keyType: options.keyType, + networkId: options.network, + onDisconnected: undefined as any + } + for (const provider of allProviders) { + const enabledAccount = await provider.enableIfConnected(enableOptions as any) + if (enabledAccount) { + await options.onConnected({ account: enabledAccount, signerProvider: provider }) + setLastConnectedAccount('injected', enabledAccount, options.network) + // eslint-disable-next-line + ;(provider as any)['onDisconnected'] = options.onDisconnected + return enabledAccount + } + } + return undefined } catch (error) { console.error(`Wallet auto-connect error:`, error) options.onDisconnected() diff --git a/packages/web3-react/src/utils/providers.ts b/packages/web3-react/src/utils/providers.ts new file mode 100644 index 000000000..f447e1d2e --- /dev/null +++ b/packages/web3-react/src/utils/providers.ts @@ -0,0 +1,101 @@ +/* +Copyright 2018 - 2022 The Alephium Authors +This file is part of the alephium project. + +The library is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +The library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the library. If not, see . +*/ + +import { + alephiumProvider, + AlephiumWindowObject, + getWalletObject, + isWalletObj, + providerInitializedEvent +} from '@alephium/get-extension-wallet' + +export type InjectedProviderListener = (providers: AlephiumWindowObject[]) => void + +function createProviderStore() { + const listeners: Set = new Set() + let allProviders: AlephiumWindowObject[] = [] + + const addNewProvider = (provider: AlephiumWindowObject) => { + if (allProviders.find((p) => p.icon === provider.icon) === undefined) { + allProviders.push(provider) + listeners.forEach((listener) => listener([...allProviders])) + } + } + + const detectOneKeyProvider = () => { + const oneKeyProvider = window['alephium'] + if (!!oneKeyProvider && isWalletObj(oneKeyProvider)) { + addNewProvider(oneKeyProvider) + } + } + + const detectDefaultProvider = () => { + const defaultProvider = getWalletObject(alephiumProvider.id) + if (defaultProvider !== undefined) { + addNewProvider(defaultProvider) + return + } + + window.addEventListener( + providerInitializedEvent(alephiumProvider.id), + () => { + const defaultProvider = getWalletObject(alephiumProvider.id) + if (defaultProvider !== undefined) { + addNewProvider(defaultProvider) + } + }, + { once: true } + ) + } + + const detectProviders = () => { + if (typeof window !== 'undefined') { + detectOneKeyProvider() + detectDefaultProvider() + + const handler = (event) => { + if (!!event.detail && isWalletObj(event.detail.provider)) { + addNewProvider(event.detail.provider) + } + } + window.addEventListener('announceAlephiumProvider', handler) + window.dispatchEvent(new Event('requestAlephiumProvider')) + return () => window.removeEventListener('announceAlephiumProvider', handler) + } + return undefined + } + + let cancel = detectProviders() + + return { + getProviders: () => { + return allProviders + }, + subscribe: (listener) => { + listeners.add(listener) + return () => listeners.delete(listener) + }, + reset: () => { + allProviders = [] + cancel?.() + cancel = detectProviders() + } + } +} + +export const injectedProviderStore = createProviderStore() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 602815881..200a17d9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,7 +52,7 @@ importers: version: 8.38.0 eslint-plugin-jest: specifier: ^27.2.1 - version: 27.2.1(@typescript-eslint/eslint-plugin@5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5) + version: 27.2.1(@typescript-eslint/eslint-plugin@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5) jest: specifier: ^28.1.3 version: 28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) @@ -398,7 +398,7 @@ importers: version: 5.59.0(eslint@8.38.0)(typescript@4.9.5) clean-webpack-plugin: specifier: 4.0.0 - version: 4.0.0(webpack@5.94.0) + version: 4.0.0(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)) eslint: specifier: ^8.37.0 version: 8.38.0 @@ -416,7 +416,7 @@ importers: version: 1.6.0 html-webpack-plugin: specifier: 5.5.0 - version: 5.5.0(webpack@5.94.0) + version: 5.5.0(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)) jest: specifier: ^28.1.3 version: 28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) @@ -449,7 +449,7 @@ importers: version: 12.0.3 terser-webpack-plugin: specifier: ^5.3.7 - version: 5.3.7(@swc/core@1.4.1)(webpack@5.94.0) + version: 5.3.7(@swc/core@1.4.1)(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)) ts-jest: specifier: ^28.0.8 version: 28.0.8(@babel/core@7.21.4)(@jest/types@28.1.3)(babel-jest@28.1.3(@babel/core@7.21.4))(jest@28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5) @@ -510,6 +510,9 @@ importers: styled-components: specifier: ^5.3.9 version: 5.3.9(react-dom@18.2.0(react@18.2.0))(react-is@18.2.0)(react@18.2.0) + use-sync-external-store: + specifier: ^1.2.2 + version: 1.2.2(react@18.2.0) devDependencies: '@rollup/plugin-node-resolve': specifier: ^15.0.1 @@ -529,6 +532,9 @@ importers: '@types/styled-components': specifier: ^5.1.26 version: 5.1.26 + '@types/use-sync-external-store': + specifier: ^0.0.6 + version: 0.0.6 eslint-plugin-react-hooks: specifier: ^4.6.0 version: 4.6.0(eslint@8.38.0) @@ -2083,6 +2089,9 @@ packages: '@types/swagger-schema-official@2.0.22': resolution: {integrity: sha512-7yQiX6MWSFSvc/1wW5smJMZTZ4fHOd+hqLr3qr/HONDxHEa2bnYAsOcGBOEqFIjd4yetwMOdEDdeW+udRAQnHA==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/yargs-parser@21.0.0': resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -5934,6 +5943,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -8067,6 +8081,8 @@ snapshots: '@types/swagger-schema-official@2.0.22': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/yargs-parser@21.0.0': {} '@types/yargs@17.0.24': @@ -8476,17 +8492,17 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.94.0)': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0(webpack@5.94.0))(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0))': dependencies: webpack: 5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0) webpack-cli: 4.10.0(webpack@5.94.0) - '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)': + '@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack@5.94.0))': dependencies: envinfo: 7.8.1 webpack-cli: 4.10.0(webpack@5.94.0) - '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)': + '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0(webpack@5.94.0))': dependencies: webpack-cli: 4.10.0(webpack@5.94.0) @@ -8991,7 +9007,7 @@ snapshots: clean-stack@2.2.0: {} - clean-webpack-plugin@4.0.0(webpack@5.94.0): + clean-webpack-plugin@4.0.0(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)): dependencies: del: 4.1.1 webpack: 5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0) @@ -9623,24 +9639,24 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5): + eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@29.5.0(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5): dependencies: '@typescript-eslint/utils': 5.59.0(eslint@8.38.0)(typescript@4.9.5) eslint: 8.38.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5) - jest: 28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) + jest: 29.5.0(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@29.5.0(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5): + eslint-plugin-jest@27.2.1(@typescript-eslint/eslint-plugin@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(jest@28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)))(typescript@4.9.5): dependencies: '@typescript-eslint/utils': 5.59.0(eslint@8.38.0)(typescript@4.9.5) eslint: 8.38.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 5.59.0(@typescript-eslint/parser@5.59.0(eslint@8.38.0)(typescript@4.9.5))(eslint@8.38.0)(typescript@4.9.5) - jest: 29.5.0(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) + jest: 28.1.3(@types/node@16.18.24)(ts-node@10.9.1(@swc/core@1.4.1)(@types/node@16.18.24)(typescript@4.9.5)) transitivePeerDependencies: - supports-color - typescript @@ -10227,7 +10243,7 @@ snapshots: relateurl: 0.2.7 terser: 5.17.1 - html-webpack-plugin@5.5.0(webpack@5.94.0): + html-webpack-plugin@5.5.0(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -12541,7 +12557,7 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.10(@swc/core@1.4.1)(webpack@5.94.0): + terser-webpack-plugin@5.3.10(@swc/core@1.4.1)(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -12552,7 +12568,7 @@ snapshots: optionalDependencies: '@swc/core': 1.4.1 - terser-webpack-plugin@5.3.7(@swc/core@1.4.1)(webpack@5.94.0): + terser-webpack-plugin@5.3.7(@swc/core@1.4.1)(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)): dependencies: '@jridgewell/trace-mapping': 0.3.18 jest-worker: 27.5.1 @@ -12833,6 +12849,10 @@ snapshots: dependencies: punycode: 2.3.0 + use-sync-external-store@1.2.2(react@18.2.0): + dependencies: + react: 18.2.0 + util-deprecate@1.0.2: {} utila@0.4.0: {} @@ -12872,9 +12892,9 @@ snapshots: webpack-cli@4.10.0(webpack@5.94.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.94.0) - '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0(webpack@5.94.0))(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)) + '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0(webpack@5.94.0)) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0(webpack@5.94.0)) colorette: 2.0.20 commander: 7.2.0 cross-spawn: 7.0.3 @@ -12914,7 +12934,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.4.1)(webpack@5.94.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.4.1)(webpack@5.94.0(@swc/core@1.4.1)(webpack-cli@4.10.0)) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: