diff --git a/apps/remix-ide/src/app.ts b/apps/remix-ide/src/app.ts index 86eb8697a28..3fc63542e89 100644 --- a/apps/remix-ide/src/app.ts +++ b/apps/remix-ide/src/app.ts @@ -69,6 +69,8 @@ import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin' import { circomPlugin } from './app/plugins/electron/circomElectronPlugin' import { GitPlugin } from './app/plugins/git' import { Matomo } from './app/plugins/matomo' +import { DesktopClient } from './app/plugins/desktop-client' +import { DesktopHost } from './app/plugins/electron/desktopHostPlugin' import { TemplatesSelectionPlugin } from './app/plugins/templates-selection/templates-selection-plugin' @@ -149,6 +151,8 @@ class AppComponent { popupPanel: PopupPanel statusBar: StatusBar settings: SettingsTab + params: any + desktopClientMode: boolean constructor() { const PlatFormAPi = new platformApi() Registry.getInstance().put({ @@ -157,6 +161,8 @@ class AppComponent { }) this.appManager = new RemixAppManager() this.queryParams = new QueryParams() + this.params = this.queryParams.get() + this.desktopClientMode = this.params && this.params.activate && this.params.activate.split(',').includes('desktopClient') this._components = {} as Components // setup storage const configStorage = new Storage('config-v0.8:') @@ -457,6 +463,12 @@ class AppComponent { this.engine.register([appUpdater]) const remixAIDesktop = new remixAIDesktopPlugin() this.engine.register([remixAIDesktop]) + const desktopHost = new DesktopHost() + this.engine.register([desktopHost]) + } else{ + //---- desktop client + const desktopClient = new DesktopClient(blockchain) + this.engine.register([desktopClient]) } const compilerloader = isElectron() ? new compilerLoaderPluginDesktop() : new compilerLoaderPlugin() @@ -545,8 +557,6 @@ class AppComponent { } async activate() { - const queryParams = new QueryParams() - const params: any = queryParams.get() try { this.engine.register(await this.appManager.registeredPlugins()) @@ -623,13 +633,13 @@ class AppComponent { .activatePlugin(this.workspace) .then(async () => { try { - if (params.deactivate) { - await this.appManager.deactivatePlugin(params.deactivate.split(',')) + if (this.params.deactivate) { + await this.appManager.deactivatePlugin(this.params.deactivate.split(',')) } } catch (e) { console.log(e) } - if (params.code && (!params.activate || params.activate.split(',').includes('solidity'))) { + if (this.params.code && (!this.params.activate || this.params.activate.split(',').includes('solidity'))) { // if code is given in url we focus on solidity plugin this.menuicons.select('solidity') } else { @@ -641,8 +651,8 @@ class AppComponent { } } - if (params.call) { - const callDetails: any = params.call.split('//') + if (this.params.call) { + const callDetails = this.params.call.split('//') if (callDetails.length > 1) { this.appManager.call('notification', 'toast', `initiating ${callDetails[0]} and calling "${callDetails[1]}" ...`) // @todo(remove the timeout when activatePlugin is on 0.3.0) @@ -651,8 +661,8 @@ class AppComponent { } } - if (params.calls) { - const calls = params.calls.split('///') + if (this.params.calls) { + const calls = this.params.calls.split('///') // call all functions in the list, one after the other for (const call of calls) { @@ -694,6 +704,10 @@ class AppComponent { // activate solidity plugin this.appManager.activatePlugin(['solidity', 'udapp', 'deploy-libraries', 'link-libraries', 'openzeppelin-proxy', 'scriptRunnerBridge']) + + if(isElectron()){ + this.appManager.activatePlugin(['desktopHost']) + } } } diff --git a/apps/remix-ide/src/app/components/DesktopClientUI.tsx b/apps/remix-ide/src/app/components/DesktopClientUI.tsx new file mode 100644 index 00000000000..cd4450db244 --- /dev/null +++ b/apps/remix-ide/src/app/components/DesktopClientUI.tsx @@ -0,0 +1,115 @@ +import React, { useContext, useEffect } from 'react' +import { AppContext, appActionTypes } from '@remix-ui/app' +import { Provider } from '../../blockchain/blockchain' +import { providerLogos } from '../udapp/run-tab' +import { desktopConnection } from '@remix-api' +import { set } from 'lodash' + +interface DesktopClientState { + connected: desktopConnection + providers: Provider[] + disableconnect: boolean + currentContext: string +} + +const DesktopClientUI = (props: DesktopClientState & { openDesktopApp: () => void } & { onConnect: (providerName: Provider) => void }) => { + const appContext = useContext(AppContext) + const { connected, providers, onConnect, disableconnect, currentContext } = props + const [title, setTitle] = React.useState('Connecting...') + const [disabled, setDisabled] = React.useState(false) + const [hasMetamask, setHasMetamask] = React.useState(false) + const [hasBrave, setHasBrave] = React.useState(false) + + useEffect(() => { + console.log('connected', props.connected) + appContext.appStateDispatch({ + type: appActionTypes.setConnectedToDesktop, + payload: props.connected, + }) + appContext.appStateDispatch({ + type: appActionTypes.setShowPopupPanel, + payload: false, + }) + }, [props.connected]) + + useEffect(() => { + console.log('providers', props.providers) + const metamaskProvider = providers.find((provider) => provider.name.toLowerCase().includes('metamask')) + const braveProvider = providers.find((provider) => provider.name.toLowerCase().includes('brave')) + setHasMetamask(!!metamaskProvider) + setHasBrave(!!braveProvider) + + }, [providers]) + + useEffect(() => { + if (hasMetamask) { + setTitle('Connect to MetaMask') + setDisabled(false) + } else if (hasBrave && !hasMetamask) { + setTitle('Brave Wallet is not supported') + setDisabled(true) + } else { + setTitle('Connecting...') + } + },[hasMetamask, hasBrave]) + + if (disabled) { + return ( +
+
+

{title}

+

+ The Brave Wallet is not supported at this time. +

+
+
+ ) + } + + return ( +
+
+

{title}

+

+ 1. Connect to your favorite Ethereum wallet provider +

2. Go back to the Remix Desktop application +

3. Deploy using 'MetaMask Wallet' + {hasBrave &&

+ Note: Brave Wallet is not supported. +
} +

+
+ +
+
+ {providers && providers.length > 0 ? ( + providers + .filter((provider) => provider.isInjected && provider.name.toLocaleLowerCase().includes('metamask')) + .map((provider, index) => ( +
+
+
+
{providerLogos[provider.name] && providerLogos[provider.name].map((logo, index) => )}
+
{provider.displayName}
+

{provider.description}

+ +
+
+
+ )) + ) : ( +
+
+ No injected providers found. Please install MetaMask or another browser wallet. +
+
+ )} +
+
+
+ ) +} + +export default DesktopClientUI diff --git a/apps/remix-ide/src/app/components/status-bar.tsx b/apps/remix-ide/src/app/components/status-bar.tsx index c264fdd4341..adbb937009f 100644 --- a/apps/remix-ide/src/app/components/status-bar.tsx +++ b/apps/remix-ide/src/app/components/status-bar.tsx @@ -8,6 +8,7 @@ import { RemixUIStatusBar } from '@remix-ui/statusbar' import { FilePanelType } from '@remix-ui/workspace' import { VerticalIcons } from './vertical-icons' import { CustomRemixApi } from '@remix-api' +import { AppAction } from '@remix-ui/app' const statusBarProfile: PluginProfile = { name: 'statusBar', @@ -22,7 +23,8 @@ export class StatusBar extends Plugin implements StatusBarI events: EventEmitter filePanelPlugin: FilePanelType verticalIcons: VerticalIcons - dispatch: React.Dispatch = () => {} + dispatch: React.Dispatch = () => { } + appStateDispatch: React.Dispatch = () => { } currentWorkspaceName: string = '' isGitRepo: boolean = false isAiActive: boolean = false @@ -86,6 +88,10 @@ export class StatusBar extends Plugin implements StatusBarI this.dispatch = dispatch } + setAppStateDispatch(appStateDispatch: React.Dispatch) { + this.appStateDispatch = appStateDispatch + } + renderComponent() { this.dispatch({ plugins: this, @@ -99,7 +105,7 @@ export class StatusBar extends Plugin implements StatusBarI render() { return (
- +
) } diff --git a/apps/remix-ide/src/app/panels/layout.ts b/apps/remix-ide/src/app/panels/layout.ts index 2ed6541d9eb..924402197c5 100644 --- a/apps/remix-ide/src/app/panels/layout.ts +++ b/apps/remix-ide/src/app/panels/layout.ts @@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib' const profile: Profile = { name: 'layout', description: 'layout', - methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal', 'maximisePinnedPanel', 'resetPinnedPanel'] + methods: ['minimize', 'minimizeSidePanel', 'maximiseSidePanel', 'resetSidePanel', 'maximizeTerminal', 'maximisePinnedPanel', 'resetPinnedPanel'] } interface panelState { @@ -141,6 +141,10 @@ export class Layout extends Plugin { this.emit('change', this.panels) } + async minimizeSidePanel () { + this.event.emit('minimizesidepanel') + } + async maximiseSidePanel () { const current = await this.call('sidePanel', 'currentFocus') this.maximized[current] = true diff --git a/apps/remix-ide/src/app/plugins/desktop-client.tsx b/apps/remix-ide/src/app/plugins/desktop-client.tsx new file mode 100644 index 00000000000..4455e081d48 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/desktop-client.tsx @@ -0,0 +1,306 @@ +/* eslint-disable prefer-const */ +import React from 'react' +import { desktopConnection, desktopConnectionType } from '@remix-api' +import { Blockchain, Provider } from '../../blockchain/blockchain' +import { AppAction, AppModal, ModalTypes } from '@remix-ui/app' +import { ViewPlugin } from '@remixproject/engine-web' +import { PluginViewWrapper } from '@remix-ui/helper' +import { QueryParams } from '@remix-project/remix-lib' +import cbor from 'cbor' +import isElectron from 'is-electron' +import DesktopClientUI from '../components/DesktopClientUI' // Import the UI component +import JSONbig from 'json-bigint' + +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'desktopClient', + displayName: 'desktopClient', + description: '', + methods: ['init', 'sendAsync'], + events: ['connected'], + maintainedBy: 'Remix', + location: 'mainPanel', +} + +interface DesktopClientState { + connected: desktopConnection + providers: Provider[] + disableconnect: boolean + currentContext: string +} + +export class DesktopClient extends ViewPlugin { + blockchain: Blockchain + ws: WebSocket + dispatch: React.Dispatch = () => {} + state: DesktopClientState + appStateDispatch: React.Dispatch + queryParams: QueryParams + params: any + + constructor(blockchain: Blockchain) { + super(profile) + this.blockchain = blockchain + this.state = { + connected: desktopConnectionType.disconnected, + providers: [], + disableconnect: false, + currentContext: '', + } + this.queryParams = new QueryParams() + + this.params = this.queryParams.get() + console.log('DesktopClient params', this.params) + } + + onActivation() { + console.log('DesktopClient activated') + _paq.push(['trackEvent', 'plugin', 'activated', 'DesktopClient']) + + this.connectToWebSocket() + + const updateProviders = async () => { + const providersObj: { [key: string]: Provider } = await this.call('blockchain', 'getAllProviders') + const providers: Provider[] = Object.values(providersObj) + this.state.providers = providers + this.renderComponent() + console.log('providers', providers) + } + + this.on('udapp', 'providerAdded', updateProviders) + window.addEventListener('eip6963:announceProvider', (event: CustomEvent) => updateProviders()) + if (!isElectron()) window.dispatchEvent(new Event('eip6963:requestProvider')) + this.call('layout', 'minimizeSidePanel') + this.blockchain.event.register('networkStatus', this.handleNetworkStatus.bind(this)) + this.blockchain.event.register('contextChanged', this.handleContextChanged.bind(this)) + } + + handleContextChanged(context: any) { + console.log('contextChanged handled', context) + } + + onDeactivation() {} + + setDispatch(dispatch: React.Dispatch): void { + this.dispatch = dispatch + this.renderComponent() + } + + setAppStateDispatch(appStateDispatch: React.Dispatch) { + console.log('setAppStateDispatch', appStateDispatch) + this.appStateDispatch = appStateDispatch + } + + renderComponent() { + this.dispatch({ + ...this.state, + }) + } + + async handleProviderConnect(provider: Provider) { + console.log('handleProviderConnect', provider) + this.state.disableconnect = true + this.renderComponent() + this.blockchain.changeExecutionContext({ context: provider.name, fork: '' }, null, null, () => { + console.log('setFinalContext') + this.state.disableconnect = false + this.renderComponent() + }) + } + + setConnectionState = (state: desktopConnection) => { + this.state.connected = state + + this.renderComponent() + } + + async handleNetworkStatus(context: any) { + console.log('networkStatus handled', context) + this.state.currentContext = this.blockchain.executionContext.executionContext + this.renderComponent() + this.debouncedSendContextChanged() + } + + // Debounced function to send context changed event + debouncedSendContextChanged = debounce(() => { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + console.log('Sending context changed event to server') + this.ws.send(stringifyWithBigInt({ type: 'contextChanged', payload: null })) + } + }, 500) // Adjust the debounce wait time as needed + + async checkConnection() { + console.log('Checking connection', this.ws) + if (this.ws && this.ws.readyState === this.ws.OPEN) { + console.log('OK Connected to server') + } else { + console.log('NOT Connected to server') + this.connectToWebSocket() + } + } + + async isInjected() { + const executionContext = this.blockchain.executionContext.executionContext + const currentProvider = this.state.providers.find((provider) => provider.name === executionContext) + console.log('isInjected', currentProvider) + this.ws.send(stringifyWithBigInt({ type: 'isInjected', payload: currentProvider.isInjected })) + } + + async openDesktopApp() { + console.log('openDesktopApp') + this.ws.send(stringifyWithBigInt({ type: 'focus', payload: null })) + } + + updateComponent(state: DesktopClientState) { + return ( + <> + + + ) + } + + render() { + return ( +
+ +
+ ) + } + + async connectToWebSocket() { + console.log('Connecting to server') + try { + this.ws = new WebSocket(`ws://localhost:${this.params.desktopClientPort}`) + this.ws.binaryType = 'arraybuffer' + } catch (e) { + console.error('CATCH WebSocket error:', e) + return + } + this.ws.onopen = () => { + console.log('Connected to server') + this.emit('connected', true) + this.setConnectionState(desktopConnectionType.connected) + this.call('terminal', 'log', { + value: 'Connected to the desktop application.', + type: 'info', + }) + //this.blockchain.event.register('contextChanged', this.handleNetworkStatus.bind(this)); + } + + this.ws.onmessage = async (event) => { + const parsed = JSON.parse(event.data) + console.log('Message from server:', parsed.method, parsed.id) + if (parsed && parsed.type === 'error') { + if (parsed.payload === 'ALREADY_CONNECTED') { + console.log('ALREADY_CONNECTED') + this.setConnectionState(desktopConnectionType.alreadyConnected) + const modalContent: AppModal = { + id: this.profile.name, + title: 'Another tab or window is already connected.', + message: 'Another tab or window is already connected to the desktop application. Please close this tab or window.', + modalType: ModalTypes.fixed, + okLabel: null, + } + + this.call('notification', 'modal' as any, modalContent) + return + } + } + if (parsed.method === 'eth_sendTransaction' || parsed.method === 'eth_getTransactionReceipt') { + this.call('terminal', 'log', { + value: 'Transaction from desktop client: ' + event.data, + type: 'info', + }) + } + if (parsed.method === 'eth_sendTransaction' || parsed.method === 'eth_getTransactionReceipt') { + console.log('Sending message to web3:', parsed) + } + + if (parsed.method === 'eth_getTransactionReceipt') { + console.log('Getting receipt for', parsed.params) + let receipt = await this.tryTillReceiptAvailable(parsed.params[0]) + console.log('Receipt:', receipt) + console.log('Sending receipt back to server', parsed.params[0], receipt, stringifyWithBigInt({ + jsonrpc: '2.0', + result: receipt, + id: parsed.id, + })) + this.ws.send( + stringifyWithBigInt({ + jsonrpc: '2.0', + result: receipt, + id: parsed.id, + }) + ) + } else { + const provider = this.blockchain.web3().currentProvider + await this.isInjected() + let result = await provider.sendAsync(parsed) + if (parsed.method === 'eth_sendTransaction') { + console.log('Sending result back to server', result) + } + // if (parsed.method === 'net_version' && result.result === 1337) { + // console.log('incoming net_version', result) + // console.log('Sending result back to server', result, this.blockchain.executionContext) + // console.log(this.state.providers) + // if (this.state.providers.length === 0) { // if no providers are available, send the VM context + // this.ws.send(stringifyWithBigInt(result)) + // } + + // } else { + console.log('Sending result back to server', result) + this.ws.send(stringifyWithBigInt(result)) + //} + + } + } + + this.ws.onclose = () => { + console.log('Disconnected from server') + this.ws = null + + this.emit('connected', false) + if (this.state.connected !== desktopConnectionType.alreadyConnected) { + this.setConnectionState(desktopConnectionType.disconnected) + + setTimeout(() => { + this.connectToWebSocket() + }, 5000) + } + } + } + + async init() {} + + async sendAsync(payload: any) {} + + async tryTillReceiptAvailable(txhash) { + try { + const receipt = await this.call('blockchain', 'getTransactionReceipt', txhash) + if (receipt) return receipt + } catch (e) { + // do nothing + } + await this.pause() + return await this.tryTillReceiptAvailable(txhash) + } + async pause() { + return new Promise((resolve, reject) => { + setTimeout(resolve, 500) + }) + } +} + +function stringifyWithBigInt(obj) { + const r = JSON.stringify(obj, (key, value) => (typeof value === 'bigint' ? value.toString() + 'n' : value)) + return r +} + +function debounce(func: (...args: any[]) => void, wait: number) { + let timeout: NodeJS.Timeout + return function (...args: any[]) { + clearTimeout(timeout) + timeout = setTimeout(() => func.apply(this, args), wait) + } +} diff --git a/apps/remix-ide/src/app/plugins/electron/desktopHostPlugin.ts b/apps/remix-ide/src/app/plugins/electron/desktopHostPlugin.ts new file mode 100644 index 00000000000..7dc008d0981 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/electron/desktopHostPlugin.ts @@ -0,0 +1,28 @@ +/* eslint-disable prefer-const */ +import React from 'react' +import { Plugin } from '@remixproject/engine' +import { ElectronPlugin } from '@remixproject/engine-electron' + +const _paq = (window._paq = window._paq || []) + +const profile = { + name: 'desktopHost', + displayName: '', + description: '', + methods: [], + events: ['connected'], + maintainedBy: 'Remix' +} + +export class DesktopHost extends ElectronPlugin { + + constructor() { + super(profile) + } + + onActivation() { + console.log('DesktopHost activated') + _paq.push(['trackEvent', 'plugin', 'activated', 'DesktopHost']) + } + +} \ No newline at end of file diff --git a/apps/remix-ide/src/app/udapp/run-tab.tsx b/apps/remix-ide/src/app/udapp/run-tab.tsx index f63b81e55c5..03472a6a1e9 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.tsx +++ b/apps/remix-ide/src/app/udapp/run-tab.tsx @@ -13,6 +13,47 @@ import { ForkedVMStateProvider } from '../providers/vm-provider' import { Recorder } from '../tabs/runTab/model/recorder' const _paq = (window._paq = window._paq || []) +export const providerDescriptions = { + 'vm-cancun': 'Deploy to the in-browser virtual machine running the Cancun fork.', + 'vm-shanghai': 'Deploy to the in-browser virtual machine running the Shanghai fork.', + 'vm-paris': 'Deploy to the in-browser virtual machine running the Paris fork.', + 'vm-london': 'Deploy to the in-browser virtual machine running the London fork.', + 'vm-berlin': 'Deploy to the in-browser virtual machine running the Berlin fork.', + 'vm-mainnet-fork': 'Deploy to a fork of the Ethereum mainnet in the in-browser virtual machine.', + 'vm-sepolia-fork': 'Deploy to a fork of the Sepolia testnet in the in-browser virtual machine.', + 'vm-custom-fork': 'Deploy to a fork of a custom network in the in-browser virtual machine.', + 'walletconnect': 'Deploy using WalletConnect.', + 'desktopHost': 'Deploy using web metamask.', + 'basic-http-provider': 'Deploy to a Custom local network.', + 'hardhat-provider': 'Deploy to the local Hardhat dev chain.', + 'ganache-provider': 'Deploy to the local Ganache dev chain.', + 'foundry-provider': 'Deploy to the local Foundry dev chain.', + 'injected-MetaMask': 'Deploy through the Metamask browser extension.', + 'injected-Brave Wallet': 'Deploy through the Brave Wallet extension.', + 'injected-Brave': 'Deploy through the Brave browser extension.', + 'injected-metamask-optimism': 'Deploy to Optimism through the Metamask browser extension.', + 'injected-metamask-gnosis': 'Deploy to Gnosis through the Metamask browser extension.', + 'injected-metamask-arbitrum': 'Deploy to Arbitrum through the Metamask browser extension.', + 'injected-metamask-sepolia': 'Deploy to the Sepolia testnet through the Metamask browser extension.', + 'injected-metamask-ephemery': 'Deploy to the Ephemery testnet through the Metamask browser extension.', + 'injected-metamask-linea': 'Deploy to Linea through the Metamask browser extension.' +} + +export const providerLogos = { + 'injected-metamask-optimism': ['assets/img/optimism-ethereum-op-logo.png', 'assets/img/metamask.png'], + 'injected-metamask-arbitrum': ['assets/img/arbitrum-arb-logo.png', 'assets/img/metamask.png'], + 'injected-metamask-gnosis': ['assets/img/gnosis_chain.png', 'assets/img/metamask.png'], + 'injected-metamask-linea': ['assets/img/linea_chain.png', 'assets/img/metamask.png'], + 'injected-metamask-sepolia': ['assets/img/metamask.png'], + 'injected-metamask-ephemery': ['assets/img/metamask.png'], + 'injected-MetaMask': ['assets/img/metamask.png'], + 'injected-Brave Wallet': ['assets/img/brave.png'], + 'injected-Trust Wallet': ['assets/img/trust-wallet.png'], + 'hardhat-provider': ['assets/img/hardhat.png'], + 'walletconnect': ['assets/img/Walletconnect-logo.png'], + 'foundry-provider': ['assets/img/foundry.png'] +} + const profile = { name: 'udapp', displayName: 'Deploy & run transactions', @@ -35,7 +76,8 @@ const profile = { 'clearAllInstances', 'addInstance', 'resolveContractAndAddInstance', - 'showPluginDetails' + 'showPluginDetails', + 'getProviders', ] } @@ -139,46 +181,6 @@ export class RunTab extends ViewPlugin { async onInitDone() { const udapp = this // eslint-disable-line - const descriptions = { - 'vm-cancun': 'Deploy to the in-browser virtual machine running the Cancun fork.', - 'vm-shanghai': 'Deploy to the in-browser virtual machine running the Shanghai fork.', - 'vm-paris': 'Deploy to the in-browser virtual machine running the Paris fork.', - 'vm-london': 'Deploy to the in-browser virtual machine running the London fork.', - 'vm-berlin': 'Deploy to the in-browser virtual machine running the Berlin fork.', - 'vm-mainnet-fork': 'Deploy to a fork of the Ethereum mainnet in the in-browser virtual machine.', - 'vm-sepolia-fork': 'Deploy to a fork of the Sepolia testnet in the in-browser virtual machine.', - 'vm-custom-fork': 'Deploy to a fork of a custom network in the in-browser virtual machine.', - 'walletconnect': 'Deploy using WalletConnect.', - 'basic-http-provider': 'Deploy to a Custom local network.', - 'hardhat-provider': 'Deploy to the local Hardhat dev chain.', - 'ganache-provider': 'Deploy to the local Ganache dev chain.', - 'foundry-provider': 'Deploy to the local Foundry dev chain.', - 'injected-MetaMask': 'Deploy through the Metamask browser extension.', - 'injected-Brave Wallet': 'Deploy through the Brave Wallet extension.', - 'injected-Brave': 'Deploy through the Brave browser extension.', - 'injected-metamask-optimism': 'Deploy to Optimism through the Metamask browser extension.', - 'injected-metamask-gnosis': 'Deploy to Gnosis through the Metamask browser extension.', - 'injected-metamask-arbitrum': 'Deploy to Arbitrum through the Metamask browser extension.', - 'injected-metamask-sepolia': 'Deploy to the Sepolia testnet through the Metamask browser extension.', - 'injected-metamask-ephemery': 'Deploy to the Ephemery testnet through the Metamask browser extension.', - 'injected-metamask-linea': 'Deploy to Linea through the Metamask browser extension.' - } - - const logos = { - 'injected-metamask-optimism': ['assets/img/optimism-ethereum-op-logo.png', 'assets/img/metamask.png'], - 'injected-metamask-arbitrum': ['assets/img/arbitrum-arb-logo.png', 'assets/img/metamask.png'], - 'injected-metamask-gnosis': ['assets/img/gnosis_chain.png', 'assets/img/metamask.png'], - 'injected-metamask-linea': ['assets/img/linea_chain.png', 'assets/img/metamask.png'], - 'injected-metamask-sepolia': ['assets/img/metamask.png'], - 'injected-metamask-ephemery': ['assets/img/metamask.png'], - 'injected-MetaMask': ['assets/img/metamask.png'], - 'injected-Brave Wallet': ['assets/img/brave.png'], - 'injected-Trust Wallet': ['assets/img/trust-wallet.png'], - 'hardhat-provider': ['assets/img/hardhat.png'], - 'walletconnect': ['assets/img/Walletconnect-logo.png'], - 'foundry-provider': ['assets/img/foundry.png'] - } - const addProvider = async (position, name, displayName, isInjected, isVM, isForkedState, fork = '', dataId = '', title = '', forkedVM = false) => { await this.call('blockchain', 'addProvider', { position, @@ -186,8 +188,8 @@ export class RunTab extends ViewPlugin { dataId, name, displayName, - description: descriptions[name] || displayName, - logos: logos[name], + description: providerDescriptions[name] || displayName, + logos: providerLogos[name], fork, isInjected, isForkedVM: forkedVM, @@ -203,10 +205,20 @@ export class RunTab extends ViewPlugin { }, provider: new Provider(udapp, name) }) + this.emit('providerAdded', { + name, + displayName, + description: providerDescriptions[name] || displayName, + logos: providerLogos[name], + fork, + isInjected, + isVM, + isForkedState, + }) } const addCustomInjectedProvider = async (position, event, name, displayName, networkId, urls, nativeCurrency?) => { - // name = `${name} through ${event.detail.info.name}` + console.log(`${name} through ${event.detail.info.name}`) await this.engine.register([new InjectedCustomProvider(event.detail.provider, name, displayName, networkId, urls, nativeCurrency)]) await addProvider(position, name, displayName + ' - ' + event.detail.info.name, true, false, false) } @@ -265,7 +277,7 @@ export class RunTab extends ViewPlugin { let stateDetail = await this.call('fileManager', 'readFile', stateFilePath) stateDetail = JSON.parse(stateDetail) const providerName = 'vm-fs-' + stateDetail.stateName - descriptions[providerName] = JSON.stringify({ + providerDescriptions[providerName] = JSON.stringify({ name: providerName, latestBlock: stateDetail.latestBlockNumber, timestamp: stateDetail.savingTimestamp @@ -275,7 +287,7 @@ export class RunTab extends ViewPlugin { name: providerName, displayName: stateDetail.stateName, kind: 'provider', - description: descriptions[providerName], + description: providerDescriptions[providerName], methods: ['sendAsync', 'init'], version: packageJson.version }, this.blockchain, stateDetail.forkName) @@ -302,7 +314,12 @@ export class RunTab extends ViewPlugin { }) // wallet connect - await addProvider(6, 'walletconnect', 'WalletConnect', false, false, false) + await addProvider(20, 'walletconnect', 'WalletConnect', false, false, false) + + if (isElectron()) { + // desktop host + await addProvider(5, 'desktopHost', 'Metamask Wallet', false, false, false) + } // external provider await addProvider(10, 'basic-http-provider', 'Custom - External Http Provider', false, false, false) diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index ffca9e766b4..267c1e78d59 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -115,7 +115,7 @@ export class Blockchain extends Plugin { this.networkcallid = 0 this.networkStatus = { network: { name: ' - ', id: ' - ' } } this.registeredPluginEvents = [] - this.defaultPinnedProviders = ['vm-cancun', 'vm-mainnet-fork', 'walletconnect', 'injected-MetaMask', 'basic-http-provider', 'hardhat-provider', 'foundry-provider'] + this.defaultPinnedProviders = ['desktopHost', 'vm-cancun', 'vm-mainnet-fork', 'walletconnect', 'injected-MetaMask', 'hardhat-provider', 'foundry-provider'] this.pinnedProviders = [] this.setupEvents() this.setupProviders() @@ -130,13 +130,15 @@ export class Blockchain extends Plugin { onActivation() { this.active = true this.on('manager', 'pluginActivated', (plugin) => { - if (plugin && plugin.name && (plugin.name.startsWith('injected') || plugin.name === 'walletconnect')) { + if ((plugin && plugin.name && (plugin.name.startsWith('injected') || plugin.name === 'walletconnect')) || plugin.name === 'desktopHost') { this.registeredPluginEvents.push(plugin.name) this.on(plugin.name, 'chainChanged', () => { - this.detectNetwork((error, network) => { - this.networkStatus = { network, error } - this._triggerEvent('networkStatus', [this.networkStatus]) - }) + if (plugin.name === this.executionContext.executionContext) { + this.detectNetwork((error, network) => { + this.networkStatus = { network, error } + this._triggerEvent('networkStatus', [this.networkStatus]) + }) + } }) } }) diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 04179529601..fd725453199 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -150,7 +150,8 @@ export function isNative(name) { 'walletconnect', 'contract-verification', 'popupPanel', - 'LearnEth', + 'desktopClient', + 'LearnEth' ] return nativePlugins.includes(name) || requiredModules.includes(name) || isInjectedProvider(name) || isVM(name) || isScriptRunner(name) } diff --git a/apps/remixdesktop/package.json b/apps/remixdesktop/package.json index 9020b130fcb..2c2be24a2b8 100644 --- a/apps/remixdesktop/package.json +++ b/apps/remixdesktop/package.json @@ -42,6 +42,7 @@ "@electron/notarize": "^2.3.0", "@types/byline": "^4.2.35", "@types/express": "^4.17.21", + "@types/json-bigint": "^1.0.4", "@types/nightwatch": "^2.3.23", "chromedriver": "116", "cross-env": "^7.0.3", @@ -65,23 +66,28 @@ }, "dependencies": { "@remix-project/remix-url-resolver": "^0.0.65", - "@remixproject/engine": "0.3.43", - "@remixproject/engine-electron": "0.3.43", - "@remixproject/plugin": "0.3.43", - "@remixproject/plugin-api": "^0.3.43", - "@remixproject/plugin-electron": "0.3.43", + "@remixproject/engine": "0.3.44", + "@remixproject/engine-electron": "0.3.44", + "@remixproject/plugin": "0.3.44", + "@remixproject/plugin-api": "^0.3.44", + "@remixproject/plugin-electron": "0.3.44", + "@types/ws": "^8.5.13", "@vscode/ripgrep": "^1.15.6", "add": "^2.0.6", "axios": "^1.7.4", "byline": "^5.0.0", + "cbor": "^10.0.3", "chokidar": "^3.5.3", + "cors": "^2.8.5", "electron-updater": "^6.1.8", - "express": "^4.20.0", + "express": "^4.21.2", "isomorphic-git": "^1.24.2", + "json-bigint": "^1.0.0", "matomo-tracker": "^2.2.4", "node-pty": "^1.0.0", "octokit": "^3.1.2", - "semver": "^7.5.4" + "semver": "^7.5.4", + "ws": "^8.18.0" }, "optionalDependencies": { "@remix-project/remix-ws-templates": "^1.0.27" diff --git a/apps/remixdesktop/src/engine.ts b/apps/remixdesktop/src/engine.ts index 7c573a752fb..1549241b919 100644 --- a/apps/remixdesktop/src/engine.ts +++ b/apps/remixdesktop/src/engine.ts @@ -16,6 +16,7 @@ import { FoundryPlugin } from './plugins/foundryPlugin'; import { HardhatPlugin } from './plugins/hardhatPlugin'; import { CircomElectronPlugin } from './plugins/circomElectronBasePlugin'; import { isE2E } from './main'; +import { DesktopHostPlugin } from './plugins/desktopHost'; const engine = new Engine() const appManager = new PluginManager() @@ -32,6 +33,7 @@ const foundryPlugin = new FoundryPlugin() const hardhatPlugin = new HardhatPlugin() const remixAIDesktopPlugin = new RemixAIDesktopPlugin() const circomPlugin = new CircomElectronPlugin() +const desktopHostPlugin = new DesktopHostPlugin() engine.register(appManager) engine.register(fsPlugin) @@ -47,6 +49,7 @@ engine.register(appUpdaterPlugin) engine.register(hardhatPlugin) engine.register(remixAIDesktopPlugin) engine.register(circomPlugin) +engine.register(desktopHostPlugin) appManager.activatePlugin('electronconfig') appManager.activatePlugin('fs') diff --git a/apps/remixdesktop/src/lib/server.ts b/apps/remixdesktop/src/lib/server.ts new file mode 100644 index 00000000000..358dc2b0da2 --- /dev/null +++ b/apps/remixdesktop/src/lib/server.ts @@ -0,0 +1,233 @@ +import * as http from 'http' +import { WebSocketServer, WebSocket } from 'ws' +import EventEmitter from 'events' +import { RequestArguments } from '../types' +import path from 'path' +import express from 'express' +import cbor from 'cbor' + +import { findAvailablePort } from '../utils/portFinder' +import { isPackaged } from '../main' +import { isE2ELocal } from '../main' + +// We will hold onto the pending requests here. +// Key: The request ID; Value: an object containing { resolve, reject, method } +const pendingRequests: Record< + number | string, + { + resolve: (value: any) => void + reject: (reason: any) => void + method?: string + } +> = {} + +let connectedWebSocket: WebSocket | null = null + +// ------------------------- +// Single top-level message handler +// ------------------------- +function setupMessageHandler(ws: WebSocket, eventEmitter: EventEmitter) { + ws.on('message', (data: Buffer | string) => { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8') + } + + let parsed: { id: any; error: any; result: any; type: string | symbol; payload: any } + try { + parsed = parseWithBigInt(data) + } catch (err) { + console.error('Could not parse incoming WebSocket message:', err) + return + } + + // If the message has an 'id', try to find a pending request + if (typeof parsed?.id !== 'undefined') { + const requestId = parsed.id + const pendingReq = pendingRequests[requestId] + if (pendingReq) { + // Found a matching pending request. + // Clear it from the queue to avoid memory leak + delete pendingRequests[requestId] + + // If there's an error in the response + if (parsed.error) { + const errorObj = { data: parsed.error } + // Your same logic as before + if (errorObj.data && errorObj.data.originalError) { + pendingReq.resolve({ + jsonrpc: '2.0', + error: errorObj.data.originalError, + id: parsed.id, + }) + } else if (errorObj.data && errorObj.data.message) { + pendingReq.resolve({ + jsonrpc: '2.0', + error: errorObj.data, + id: parsed.id, + }) + } else { + pendingReq.resolve({ + jsonrpc: '2.0', + error: errorObj, + id: parsed.id, + }) + } + } else { + // No error; resolve with result + pendingReq.resolve(parsed.result) + } + } else { + // If there's no matching pending request, you can decide to ignore or handle differently + // console.log('No pending request matches id', requestId, parsed) + } + } else if (parsed?.type) { + // Possibly a "notification" or event-based message + // that doesn't match a pending JSON-RPC request + eventEmitter.emit(parsed.type, parsed.payload) + } + }) +} + +// ------------------------- +// The request forwarder +// ------------------------- +export const handleRequest = async ( + jsonRpcPayload: RequestArguments, + eventEmitter: EventEmitter +): Promise => { + if (!connectedWebSocket || connectedWebSocket.readyState !== WebSocket.OPEN) { + throw new Error('No active WebSocket connection to forward request') + } + + const requestId = jsonRpcPayload.id + + return new Promise((resolve, reject) => { + // Store references in our pendingRequests map + pendingRequests[requestId] = { resolve, reject, method: jsonRpcPayload.method } + + // Optional: You can start a request-specific timeout here + // to reject the request if it doesn't resolve in time. + const timeout = setTimeout(() => { + // If it times out, remove from pendingRequests. + delete pendingRequests[requestId] + console.error('Timeout waiting for WebSocket response', jsonRpcPayload) + reject(new Error('Timeout waiting for WebSocket response')) + }, 240000) // 4-min timeout or whatever you prefer + + connectedWebSocket.send(JSON.stringify(jsonRpcPayload), (err) => { + if (err) { + delete pendingRequests[requestId] + clearTimeout(timeout) + reject(err) + } else { + // If you want to log for specific methods: + if (jsonRpcPayload.method === 'eth_sendTransaction' || jsonRpcPayload.method === 'eth_getTransactionReceipt') { + console.log('Sent message to WebSocket client:', jsonRpcPayload) + } + } + }) + }) +} + +export const startHostServer = async (eventEmitter: EventEmitter) => { + let http_port = await findAvailablePort([49589]) + const websocket_port = await findAvailablePort([49588]) + + // Create an Express server + const startServer = () => { + const server = express() + const remixPath = path.join(__dirname, 'remix-ide') + server.use(express.static(remixPath)) + console.log('remixPath', remixPath) + server.get('/', (req, res) => { + res.sendFile(path.join(remixPath, 'index.html')) + }) + + const httpServer = http.createServer(server) + httpServer.listen(http_port, () => { + const address = httpServer.address() + if (typeof address === 'string') { + console.log(`Server started at ${address}`) + } else if (address && address.port) { + console.log(`Server started at http://localhost:${address.port}`) + } + }) + + return httpServer + } + + // Create the WebSocket server + const wsServer = new WebSocketServer({ port: websocket_port }) + wsServer.on('connection', (ws) => { + console.log('WebSocket client connected') + + // If we already have a connected client, close the new one + if (connectedWebSocket && connectedWebSocket.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'error', payload: 'ALREADY_CONNECTED' })) + ws.close(1000, 'Another client connected') + return + } else if (connectedWebSocket) { + try { + // Clean up any leftover listeners + connectedWebSocket.removeAllListeners() + } catch (_) {} + } + + connectedWebSocket = ws + eventEmitter.emit('connected', true) + + // Important: Use a single on('message') listener for all requests + setupMessageHandler(ws, eventEmitter) + + connectedWebSocket.on('close', () => { + console.log('WebSocket client disconnected') + connectedWebSocket = null + eventEmitter.emit('connected', false) + // Optionally clean up pendingRequests or handle them as errors + // for (const requestId in pendingRequests) { + // pendingRequests[requestId].reject(new Error('WebSocket closed')) + // delete pendingRequests[requestId] + // } + }) + + connectedWebSocket.on('error', (error) => { + console.error('WebSocket error:', error) + connectedWebSocket = null + eventEmitter.emit('connected', false) + }) + }) + + console.log(`WebSocket server running on ws://localhost:${(wsServer.address() as any).port}`) + if ((process.env.NODE_ENV === 'production' || isPackaged) && !isE2ELocal) { + startServer() + } else { + // For local dev, maybe keep using port 8080 + http_port = 8080 + } + + return { + http_port, + websocket_port, + } +} + +function parseWithBigInt(json: string) { + // You can unify your approach here, either JSON.parse or try cbor first: + try { + // Attempt JSON parse with BigInt + return JSON.parse(json, (key, value) => { + if (typeof value === 'string' && /^\d+n?$/.test(value)) { + return BigInt(value.endsWith('n') ? value.slice(0, -1) : value) + } + return value + }) + } catch (jsonErr) { + // fallback to cbor if you like: + try { + return cbor.decode(json) + } catch (cborErr) { + console.log('parseWithBigInt error', cborErr, json) + return {} + } + } +} \ No newline at end of file diff --git a/apps/remixdesktop/src/main.ts b/apps/remixdesktop/src/main.ts index 5763d874c83..2610f772fb7 100644 --- a/apps/remixdesktop/src/main.ts +++ b/apps/remixdesktop/src/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, Menu, MenuItem, shell, utilityProcess, screen, ipcMain } from 'electron'; +import { app, BrowserWindow, dialog, Menu, MenuItem, shell, utilityProcess, screen, ipcMain, protocol } from 'electron'; import path from 'path'; @@ -37,7 +37,7 @@ export const createWindow = async (dir?: string): Promise => { // reize factor let resizeFactor = 0.8 // if the window is too small the size is 100% - if( screen.getPrimaryDisplay().size.width < 2560 || screen.getPrimaryDisplay().size.height < 1440) { + if (screen.getPrimaryDisplay().size.width < 2560 || screen.getPrimaryDisplay().size.height < 1440) { resizeFactor = 1 } const width = screen.getPrimaryDisplay().size.width * resizeFactor @@ -175,5 +175,16 @@ ipcMain.handle('matomo:trackEvent', async (event, data) => { } }) - - +ipcMain.on('focus-window', (windowId: any) => { + console.log('focus-window', windowId) + windowSet.forEach((win: BrowserWindow) => { + console.log('win', win.webContents.id) + if (win.webContents.id === windowId) { + if (win.isMinimized()) { + win.restore() + } + win.show() + win.focus() + } + }) +}) \ No newline at end of file diff --git a/apps/remixdesktop/src/plugins/desktopHost.ts b/apps/remixdesktop/src/plugins/desktopHost.ts new file mode 100644 index 00000000000..022f47a92fe --- /dev/null +++ b/apps/remixdesktop/src/plugins/desktopHost.ts @@ -0,0 +1,153 @@ +import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" +import { Profile } from "@remixproject/plugin-utils" +import { handleRequest, startHostServer } from "../lib/server" +import EventEmitter from "events" +import { ipcMain, shell } from "electron" +import { RequestArguments } from "../types" +import fs from 'fs' +import path from 'path' +import os from 'os' +import { isPackaged } from "../main" + +const logFilePath = isPackaged ? path.join(os.tmpdir(), 'desktopHost.log') : path.join(__dirname, 'desktopHost.log') + +const profile = { + displayName: 'desktopHost', + name: 'desktopHost', + description: 'desktopHost', +} + +const eventEmitter = new EventEmitter() +let isConnected = false + +let ports: { + http_port: number + websocket_port: number +} + +export class DesktopHostPlugin extends ElectronBasePlugin { + clients: DesktopHostPluginClient[] = [] + constructor() { + super(profile, clientProfile, DesktopHostPluginClient) + this.methods = [...super.methods] + this.startServer() + eventEmitter.on('connected', (payload) => { + console.log('connected', payload) + isConnected = payload + }) + eventEmitter.on('isInjected', (isInjected: boolean) => { + console.log('isInjected', isInjected) + for (const client of this.clients) { + client.setIsInjected(isInjected) + } + }) + eventEmitter.on('focus', () => { + console.log('focus') + ipcMain.emit('focus-window', 0) + }) + eventEmitter.on('contextChanged', (context) => { + console.log('contextChanged', context) + for (const client of this.clients) { + client.emit('chainChanged', context) + } + }) + } + + async startServer(): Promise { + console.log('desktopHost activated') + ports = await startHostServer(eventEmitter) + console.log('desktopHost server started', ports) + } +} + +const clientProfile: Profile = { + name: 'desktopHost', + displayName: 'desktopHost', + description: 'desktopHost', + methods: ['getIsConnected', 'sendAsync', 'init'], + kind: 'provider', +} + +export class DesktopHostPluginClient extends ElectronBasePluginClient { + + isInjected: boolean + constructor(webContentsId: number, profile: Profile) { + super(webContentsId, profile) + this.isInjected = null + eventEmitter.on('connected', async (payload) => { + console.log('SERVER connected', payload) + isConnected = payload + await this.sendConnectionStatus() + }) + } + + async setIsInjected(isInjected: boolean) { + if (this.isInjected !== isInjected && isConnected) { + this.isInjected = isInjected + + await this.sendConnectionStatus() + if (isInjected) { + this.call('notification' as any, 'toast', 'You are now connected to Metamask!') + } else { + this.call('notification' as any, 'toast', 'You are not yet connected to Metamask. Please connect to Metamask in the browser') + } + } + } + + async sendConnectionStatus() { + const provider = await this.call('blockchain' as any, 'getProvider') + console.log('provider', provider) + if (provider === 'desktopHost' && !this.isInjected) { + this.emit('connectedToBrowser') + } else if (provider === 'desktopHost' && this.isInjected) { + this.emit('connectedToMetamask') + } else { + this.emit('disconnected') + } + } + + + getIsConnected() { + console.log('getIsConnected', isConnected) + return isConnected + } + + async init() { + console.log('SETTING UP REMOTE WEBSOCKET...', this.webContentsId) + + if (!isConnected) + await shell.openExternal(`http://localhost:${ports.http_port}/?activate=udapp,desktopClient&desktopClientPort=${ports.websocket_port}`) + // wait for the connection + while (!isConnected) { + await new Promise(resolve => setTimeout(resolve, 1000)) + } + console.log('CONNECTED TO REMOTE WEBSOCKET', this.webContentsId) + } + + + + async sendAsync(data: RequestArguments) { + if (!isConnected) { + console.log('NOT CONNECTED', this.webContentsId) + return { error: 'Not connected to the remote websocket' } + } + console.log('SEND ASYNC', data, this.webContentsId) + + + if (data.method === 'eth_getTransactionReceipt') { + ipcMain.emit('focus-window', this.webContentsId) + } + const result = await handleRequest(data, eventEmitter) + + if (!isPackaged) { + const logEntry = ` + webContentsId: ${this.webContentsId} + Request: ${JSON.stringify(data, (key, value) => typeof value === 'bigint' ? value.toString() : value, 2)} + Result: ${JSON.stringify(result, (key, value) => typeof value === 'bigint' ? value.toString() : value, 2)} + ` + + fs.appendFileSync(logFilePath, logEntry) + } + return result + } +} \ No newline at end of file diff --git a/apps/remixdesktop/src/preload.ts b/apps/remixdesktop/src/preload.ts index 6eb9d717441..366352b7f3d 100644 --- a/apps/remixdesktop/src/preload.ts +++ b/apps/remixdesktop/src/preload.ts @@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString()) /* preload script needs statically defined API for each plugin */ -const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID', 'circom'] +const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID', 'circom', 'desktopHost'] let webContentsId: number | undefined diff --git a/apps/remixdesktop/src/types/index.ts b/apps/remixdesktop/src/types/index.ts index e69de29bb2d..e696f99ed4e 100644 --- a/apps/remixdesktop/src/types/index.ts +++ b/apps/remixdesktop/src/types/index.ts @@ -0,0 +1,15 @@ + +export interface RequestArguments { + jsonrpc: '2.0' + readonly method: string + readonly params?: readonly unknown[] | object + readonly id?: string +} + +export type Chain = { + chainId: number + name: string + currency: string + explorerUrl: string + rpcUrl: string +} \ No newline at end of file diff --git a/apps/remixdesktop/src/utils/portFinder.ts b/apps/remixdesktop/src/utils/portFinder.ts new file mode 100644 index 00000000000..2f14a12658a --- /dev/null +++ b/apps/remixdesktop/src/utils/portFinder.ts @@ -0,0 +1,52 @@ +import * as net from 'net'; + +/** + * Check if a specific port is available. + * @param port - The port to check. + * @returns Promise - True if available, false otherwise. + */ +const isPortAvailable = (port: number): Promise => { + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', () => { + resolve(false); // Port is not available + }); + + server.once('listening', () => { + server.close(); // Close the server immediately + resolve(true); // Port is available + }); + + server.listen(port); // Try to bind to the port + }); +}; + +/** + * Find an available port from the provided list, or return a random one. + * @param ports - Array of ports to check. + * @returns Promise - Available port number. + */ +export const findAvailablePort = async (ports: number[]): Promise => { + for (const port of ports) { + const available = await isPortAvailable(port); + if (available) { + console.log(`Port ${port} is available.`); + return port; + } + } + + // Fallback: find a random available port + return new Promise((resolve) => { + const server = net.createServer(); + server.listen(0, () => { + const address = server.address(); + if (typeof address === 'object' && address?.port) { + console.log(`No specified ports available. Using random port ${address.port}.`); + resolve(address.port); + } + server.close(); + }); + }); +}; + diff --git a/apps/remixdesktop/test/tests/app/metamask.test.ts b/apps/remixdesktop/test/tests/app/metamask.test.ts new file mode 100644 index 00000000000..bcc0777690d --- /dev/null +++ b/apps/remixdesktop/test/tests/app/metamask.test.ts @@ -0,0 +1,63 @@ +import { NightwatchBrowser } from 'nightwatch' + +const tests = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + done() + }, + 'open default template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .waitForElementVisible('button[data-id="landingPageImportFromTemplate"]') + .click('button[data-id="landingPageImportFromTemplate"]') + .waitForElementPresent('*[data-id="create-remixDefault"]') + .scrollAndClick('*[data-id="create-remixDefault"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementPresent('[data-id="TemplatesSelectionModalDialogContainer-react"] .modal-ok') + .click('[data-id="TemplatesSelectionModalDialogContainer-react"] .modal-ok') + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) + browser + .switchWindow(result.value[1]) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .openFile('contracts/1_Storage.sol') + .waitForElementVisible('*[id="editorView"]', 10000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) + }) + }) + }, + 'connect to Wallet': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('udapp') + .waitForElementVisible('[data-id="settingsSelectEnvOptions"]') + .click('[data-id="settingsSelectEnvOptions"] button') + .waitForElementVisible(`[data-id="dropdown-item-desktopHost"]`) + .click('[data-id="dropdown-item-desktopHost"]') // close the dropdown + .waitForElementVisible({ + locateStrategy: 'xpath', + selector: '//*[@data-id="settingsNetworkEnv" and contains(.,"1337")]', + }) + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') + .pause(2000) + .clickLaunchIcon('udapp') + .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]', 45000) // wait for the contract to compile + .pause(2000) + .click('*[data-id="Deploy - transact (not payable)"]') + .waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000) + .clickInstance(0) + .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '10' }) + .clickFunction('retrieve - call') + .waitForElementContainsText('[data-id="treeViewLi0"]', 'uint256: 10') + .pause() + }, +} + +module.exports = { + ...tests, +} diff --git a/apps/remixdesktop/yarn.lock b/apps/remixdesktop/yarn.lock index a8b2472ae03..6d559d01b2d 100644 --- a/apps/remixdesktop/yarn.lock +++ b/apps/remixdesktop/yarn.lock @@ -982,46 +982,46 @@ ethers "^5.4.2" web3 "^1.5.1" -"@remixproject/engine-electron@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.43.tgz#37fe98c6ef2deb6de80db60882608714d6e25274" - integrity sha512-k1Lcg67tlPiBFJwKhBRT9bDMyZuYWDdUXZOHKX8BGPZ0lS1ZQDrH4uN2QE2EFGIiCxeArcNzQ7MZ5iEo763eUA== +"@remixproject/engine-electron@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.44.tgz#fd01d74b8ce1f637db4096168382bd4f34917b09" + integrity sha512-dqLh1Cg1Sx4hfrHShfAHVyAainL4iHzskOd29+gmvPGtX5J5uf5WyIuEkpj14FH40P8szxECYNIXH1XRQPJRrQ== dependencies: - "@remixproject/engine" "0.3.43" - "@remixproject/plugin-api" "0.3.43" - "@remixproject/plugin-utils" "0.3.43" + "@remixproject/engine" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" -"@remixproject/engine@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.43.tgz#deceb8398a034d33f741f9de38b233ab616720a4" - integrity sha512-BKmLVdtkPUQ56yZuRsU7CxS0TgJe4b7P9RoqdBW0Udy1w8oUJsWmlmKchCLXD+/t+12jPyk4sulRN8N9YOFBAw== +"@remixproject/engine@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.44.tgz#fafe15bae1f02feed731f406ece131547b07c4f9" + integrity sha512-mwq4wBN+wE5Bdjm0nXOpIm/810dSPYEPlQEn6K2buV5DfzPaRewSgxCuAlLTeoITk5Vg1O/NX+VAesLixv79/g== dependencies: - "@remixproject/plugin-api" "0.3.43" - "@remixproject/plugin-utils" "0.3.43" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" -"@remixproject/plugin-api@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.43.tgz#68ce6799a8e6e22961b82f46a7a52b1d7a4a765c" - integrity sha512-qgwq3UQAW9JKsFv47m0E7plNNEWL4CFbqoLGbfsfwuZTd/V7HhaMc1my5dxOObW+RKExyvFNFSEvEp7HoHFsWg== +"@remixproject/plugin-api@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.44.tgz#fcc2dcae5140c781e0b916b563d650968c7cb78d" + integrity sha512-Sx/8AhwlfSl/JA2DTIF1SPDkEz+iwDqfTLNBT6zUVSSkCwqM7SbO7UZZZA8YJK1mP8+t6M0ZDDCwDQmCtGOPnw== dependencies: - "@remixproject/plugin-utils" "0.3.43" + "@remixproject/plugin-utils" "0.3.44" -"@remixproject/plugin-api@^0.3.43": +"@remixproject/plugin-api@^0.3.44": version "0.3.208" resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.208.tgz#28505b84ec06c84e04ca1bf7cfa51109c178c06b" integrity sha512-11hFxABBrEzE4fgcDblWqxLAh5ARH2tBADgh9KNk+y7LUV7aQ7OZf4KiZ2US+uKiSC6497iu/uLHbWBTeRqlVA== dependencies: "@remixproject/plugin-utils" "0.3.208" -"@remixproject/plugin-electron@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.43.tgz#6c621c413745ce785f9973baea109debe3def00a" - integrity sha512-uv44xjmkTsC/o4xnMEBml6NxrMeq95aOR3FFY8MnZkKvnWOKC94SE5AYuHOAvt+FBrnar2f58+IYpBJAIkYyaQ== +"@remixproject/plugin-electron@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.44.tgz#050378779aa2a8979c0e77497633ec0922b29703" + integrity sha512-3ofuttt7xsPKe3royKuEaN0PgKIS8zMY5vHQTx3+8PqAx3DCcCRn9Xd7qQl/mFNEDxjjOgLrRw2Q2P8NQs7DEQ== dependencies: - "@remixproject/engine" "0.3.43" - "@remixproject/plugin" "0.3.43" - "@remixproject/plugin-api" "0.3.43" - "@remixproject/plugin-utils" "0.3.43" + "@remixproject/engine" "0.3.44" + "@remixproject/plugin" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" "@remixproject/plugin-utils@0.3.208": version "0.3.208" @@ -1030,20 +1030,20 @@ dependencies: tslib "2.0.1" -"@remixproject/plugin-utils@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.43.tgz#53206666135a360c88bfde11568c31341c9d961f" - integrity sha512-FB2Dz0/+TQ+D9AdINfsu38qHEsUVIDpaDCaXY76suDkSUudoHcGrC5TbpaV/xMUbMMma2dcLp629vBNnA5Cd0w== +"@remixproject/plugin-utils@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.44.tgz#fdaa978f1c10331b68e67bef9e636c7d19069c27" + integrity sha512-DX4vcbEplMFLE6SZ10wBMBQ6ntT9rEsPfajlhSnmZYyMpmuP0RHQLopNHsL+DxuHpOJM+hXFNEdORhWrgIQ3Kw== dependencies: tslib "2.0.1" -"@remixproject/plugin@0.3.43": - version "0.3.43" - resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.43.tgz#c7397b8e44ab6627a290c3716985439482eb4f4a" - integrity sha512-uO0wQ9kP982QTJIlGUXXeOjLG1qG64UN5kDopTcMbplzT5vXlMRV64FY8zEqSXtl+sdqKBFLXrwmb+AUNU0MTA== +"@remixproject/plugin@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.44.tgz#9a82e93641c7627b2f9d0ee23754d78870adc91d" + integrity sha512-pkGCLfvcZwItHD4xA1ZnQtKsgffiYymqx0cSDcPAHm4i5x7S80vJ3BVjJm/CT/gyBXP8qF5SFGP6RjveFm7haQ== dependencies: - "@remixproject/plugin-api" "0.3.43" - "@remixproject/plugin-utils" "0.3.43" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" events "3.2.0" "@scure/base@~1.1.0": @@ -1208,6 +1208,11 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== +"@types/json-bigint@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3" + integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag== + "@types/json-schema@^7.0.8": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1348,6 +1353,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.13": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz" @@ -2194,6 +2206,13 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== +cbor@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-10.0.3.tgz#202d79cd696f408700af51b0c9771577048a860e" + integrity sha512-72Jnj81xMsqepqdcSdf2+fflz/UDsThOHy5hj2MW5F5xzHL8Oa0KQ6I6V9CwVUPxg5pf+W9xp6W2KilaRXWWtw== + dependencies: + nofilter "^3.0.2" + chai-nightwatch@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz#980ecf63dde5a04e7f3524370682c7ff01178ffb" @@ -2473,6 +2492,11 @@ cookie@0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" @@ -2483,7 +2507,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@^2.8.1: +cors@^2.8.1, cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -3416,7 +3440,7 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -express@^4.14.0, express@^4.20.0: +express@^4.14.0: version "4.20.0" resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== @@ -3453,6 +3477,43 @@ express@^4.14.0, express@^4.20.0: utils-merge "1.0.1" vary "~1.1.2" +express@^4.21.2: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.12" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + ext@^1.1.2: version "1.7.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" @@ -3540,6 +3601,19 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-process@^1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/find-process/-/find-process-1.4.7.tgz#8c76962259216c381ef1099371465b5b439ea121" @@ -4591,6 +4665,13 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" @@ -5384,6 +5465,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +nofilter@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" + integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" @@ -5616,6 +5702,11 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + pathval@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -6214,6 +6305,16 @@ serve-static@1.16.0: parseurl "~1.3.3" send "0.18.0" +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + servify@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" @@ -6441,16 +6542,7 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6509,14 +6601,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7464,16 +7549,7 @@ workerpool@6.2.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7515,6 +7591,11 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" diff --git a/libs/remix-api/src/index.ts b/libs/remix-api/src/index.ts index 96515e169f0..bd9f5012fe4 100644 --- a/libs/remix-api/src/index.ts +++ b/libs/remix-api/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/remix-api' -export * from './lib/types/git' \ No newline at end of file +export * from './lib/types/git' +export * from './lib/types/desktopConnection' \ No newline at end of file diff --git a/libs/remix-api/src/lib/plugins/desktop-client.ts b/libs/remix-api/src/lib/plugins/desktop-client.ts new file mode 100644 index 00000000000..78a4284209e --- /dev/null +++ b/libs/remix-api/src/lib/plugins/desktop-client.ts @@ -0,0 +1,13 @@ +import { StatusEvents } from '@remixproject/plugin-utils' +import { JsonDataRequest, JsonDataResult } from '../types/rpc' + +export interface IDesktopClient { + events: { + connected: (connected: boolean) => void, + } & StatusEvents + methods: { + init(): void + sendAsync(payload: JsonDataRequest): Promise + } + +} diff --git a/libs/remix-api/src/lib/remix-api.ts b/libs/remix-api/src/lib/remix-api.ts index 36d7fbf6a04..4fd77629391 100644 --- a/libs/remix-api/src/lib/remix-api.ts +++ b/libs/remix-api/src/lib/remix-api.ts @@ -18,6 +18,7 @@ import { IRemixAID } from "./plugins/remixAIDesktop-api" import { IMenuIconsApi } from "./plugins/menuicons-api" import { IDgitPlugin } from "./plugins/dgitplugin-api" import { IPopupPanelAPI } from "./plugins/popuppanel-api" +import { IDesktopClient } from "./plugins/desktop-client" export interface ICustomRemixApi extends IRemixApi { popupPanel: IPopupPanelAPI @@ -39,6 +40,7 @@ export interface ICustomRemixApi extends IRemixApi { menuicons: IMenuIconsApi remixAI: IRemixAI, remixAID: IRemixAID + desktopClient: IDesktopClient } diff --git a/libs/remix-api/src/lib/types/desktopConnection.ts b/libs/remix-api/src/lib/types/desktopConnection.ts new file mode 100644 index 00000000000..bf6c725ca57 --- /dev/null +++ b/libs/remix-api/src/lib/types/desktopConnection.ts @@ -0,0 +1,9 @@ +export const desktopConnectionType = { + connected: 'connected', + disconnected: 'disconnected', + disabled: 'disabled', + alreadyConnected: 'alreadyConnected', + connectedToInjected: 'connectedToInjected' +} + +export type desktopConnection = typeof desktopConnectionType [keyof typeof desktopConnectionType ] \ No newline at end of file diff --git a/libs/remix-api/src/lib/types/rpc.ts b/libs/remix-api/src/lib/types/rpc.ts new file mode 100644 index 00000000000..3b22420408e --- /dev/null +++ b/libs/remix-api/src/lib/types/rpc.ts @@ -0,0 +1,18 @@ +export type JsonDataRequest = { + id: number + jsonrpc: string // version + method: string + params: Array + } + + export type JsonDataResult = { + id: number + jsonrpc: string // version + result?: any + error?: { + code: number, + message: string + data?: string + } + errorData?: any + } \ No newline at end of file diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts index b2c46feff45..03d70f37a88 100644 --- a/libs/remix-lib/src/execution/txRunnerWeb3.ts +++ b/libs/remix-lib/src/execution/txRunnerWeb3.ts @@ -39,6 +39,7 @@ export class TxRunnerWeb3 { let currentDateTime = new Date(); const start = currentDateTime.getTime() / 1000 const cb = (err, resp) => { + console.log('transactionBroadcasted', resp, err) if (err) { return callback(err, resp) } @@ -46,6 +47,7 @@ export class TxRunnerWeb3 { const listenOnResponse = () => { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { + console.log('waiting for receipt from IDE') const receipt = await tryTillReceiptAvailable(resp, this.getWeb3()) tx = await tryTillTxAvailable(resp, this.getWeb3()) currentDateTime = new Date(); @@ -79,7 +81,9 @@ export class TxRunnerWeb3 { ) } else { try { + console.log('sending transaction') const res = await this.getWeb3().eth.sendTransaction(tx, null, { checkRevertBeforeSending: false, ignoreGasPricing: true }) + console.log('transaction success', res) cb(null, res.transactionHash) } catch (e) { if (!e.message) e.message = '' diff --git a/libs/remix-ui/app/src/lib/remix-app/actions/app.ts b/libs/remix-ui/app/src/lib/remix-app/actions/app.ts index 246b10cad09..6cb33532c92 100644 --- a/libs/remix-ui/app/src/lib/remix-app/actions/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/actions/app.ts @@ -1,5 +1,4 @@ -import { branch, GitHubUser } from '@remix-api'; -import { AppModal } from '../interface' +import { branch, desktopConnection, GitHubUser } from '@remix-api'; type ActionMap = { [Key in keyof M]: M[Key] extends undefined @@ -18,6 +17,7 @@ export const enum appActionTypes { setNeedsGitInit = 'SET_NEEDS_GIT_INIT', setCanUseGit = 'SET_CAN_USE_GIT', setShowPopupPanel = 'SET_SHOW_POPUP_PANEL', + setConnectedToDesktop = 'SET_CONNECTED_TO_DESKTOP', } type AppPayload = { @@ -26,6 +26,7 @@ type AppPayload = { [appActionTypes.setNeedsGitInit]: boolean, [appActionTypes.setCanUseGit]: boolean, [appActionTypes.setShowPopupPanel]: boolean, + [appActionTypes.setConnectedToDesktop]: desktopConnection } export type AppAction = ActionMap[keyof ActionMap< diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx index 5349eb2eed3..214b4dd2500 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx @@ -117,6 +117,17 @@ const ModalWrapper = (props: ModalWrapperProps) => { message: createForm({ valid: true }) }) break + case ModalTypes.fixed: + setState({ + ...props, + okFn: null, + cancelFn: null, + okLabel: null, + cancelLabel: null, + preventBlur: true, + hideCancelIcon: true, + }) + break default: setState({ ...props, diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts index 531c2764d0a..72fdfc2f0ef 100644 --- a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts @@ -1,4 +1,4 @@ -import { branch, GitHubUser } from '@remix-api' +import { branch, desktopConnection, GitHubUser } from '@remix-api' import { AppModalCancelTypes, ModalTypes } from '../types' export type ValidationResult = { @@ -53,5 +53,7 @@ export interface AppState { needsGitInit: boolean canUseGit: boolean showPopupPanel: boolean + connectedToDesktop: desktopConnection + desktopClientConnected: desktopConnection } diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts index 39d61cab75c..2110a638997 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts @@ -34,5 +34,13 @@ export const appReducer = (state: AppState, action: AppAction): AppState => { showPopupPanel: action.payload } } + + case appActionTypes.setConnectedToDesktop: { + console.log('setConnectedToDesktop', action.payload) + return { + ...state, + connectedToDesktop: action.payload + } + } } } \ No newline at end of file diff --git a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx index 0c4a58ffa3f..ea9aacb8904 100644 --- a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx @@ -14,6 +14,7 @@ import { UsageTypes } from './types' import { appReducer } from './reducer/app' import { appInitialState } from './state/app' import isElectron from 'is-electron' +import { desktopConnectionType } from '@remix-api' declare global { interface Window { @@ -44,12 +45,18 @@ const RemixApp = (props: IRemixAppUi) => { const sidePanelRef = useRef(null) const pinnedPanelRef = useRef(null) + //console.log('RemixApp props', props) + const [appState, appStateDispatch] = useReducer(appReducer, { ...appInitialState, - showPopupPanel: !window.localStorage.getItem('did_show_popup_panel') && !isElectron() + showPopupPanel: !window.localStorage.getItem('did_show_popup_panel') && !isElectron(), + connectedToDesktop: props.app.desktopClientMode ? desktopConnectionType .disconnected : desktopConnectionType .disabled }) useEffect(() => { + if (props.app.params && props.app.params.activate && props.app.params.activate.split(',').includes('desktopClient')){ + setHideSidePanel(true) + } async function activateApp() { props.app.themeModule.initTheme(() => { setAppReady(true) @@ -112,27 +119,29 @@ const RemixApp = (props: IRemixAppUi) => { },[appState.showPopupPanel]) function setListeners() { - props.app.sidePanel.events.on('toggle', () => { - setHideSidePanel((prev) => { - return !prev + if (!props.app.desktopClientMode){ + props.app.sidePanel.events.on('toggle', () => { + setHideSidePanel((prev) => { + return !prev + }) + }) + props.app.sidePanel.events.on('showing', () => { + setHideSidePanel(false) }) - }) - props.app.sidePanel.events.on('showing', () => { - setHideSidePanel(false) - }) - props.app.layout.event.on('minimizesidepanel', () => { - // the 'showing' event always fires from sidepanel, so delay this a bit - setTimeout(() => { - setHideSidePanel(true) - }, 1000) - }) + props.app.layout.event.on('minimizesidepanel', () => { + // the 'showing' event always fires from sidepanel, so delay this a bit + setTimeout(() => { + setHideSidePanel(true) + }, 1000) + }) - props.app.layout.event.on('maximisesidepanel', () => { - setMaximiseLeftTrigger((prev) => { - return prev + 1 + props.app.layout.event.on('maximisesidepanel', () => { + setMaximiseLeftTrigger((prev) => { + return prev + 1 + }) }) - }) + } props.app.layout.event.on('enhancesidepanel', () => { setEnhanceLeftTrigger((prev) => { diff --git a/libs/remix-ui/app/src/lib/remix-app/state/app.ts b/libs/remix-ui/app/src/lib/remix-app/state/app.ts index 708b22301ac..4c0eae622ee 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/app.ts @@ -1,4 +1,4 @@ -import { GitHubUser } from "@remix-api"; +import { desktopConnectionType , GitHubUser } from "@remix-api"; import { AppState } from "../interface"; export const appInitialState: AppState = { @@ -6,5 +6,7 @@ export const appInitialState: AppState = { currentBranch: null, needsGitInit: true, canUseGit: false, - showPopupPanel: false + showPopupPanel: false, + connectedToDesktop: desktopConnectionType.disabled, + desktopClientConnected: desktopConnectionType.disabled } \ No newline at end of file diff --git a/libs/remix-ui/app/src/lib/remix-app/types/index.ts b/libs/remix-ui/app/src/lib/remix-app/types/index.ts index 598d5d96f01..08e44f654a1 100644 --- a/libs/remix-ui/app/src/lib/remix-app/types/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/types/index.ts @@ -5,7 +5,8 @@ export const enum ModalTypes { password = 'password', default = 'default', form = 'form', - forceChoice = 'forceChoice' + forceChoice = 'forceChoice', + fixed = 'fixed' } export const enum AppModalCancelTypes { diff --git a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx index 89d8cbca8c2..30c96bef773 100644 --- a/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx +++ b/libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.tsx @@ -9,9 +9,10 @@ import HomeTabScamAlert from './components/homeTabScamAlert' import HomeTabGetStarted from './components/homeTabGetStarted' import HomeTabFeatured from './components/homeTabFeatured' import HomeTabFeaturedPlugins from './components/homeTabFeaturedPlugins' -import { appPlatformTypes, platformContext } from '@remix-ui/app' +import { AppContext, appPlatformTypes, platformContext } from '@remix-ui/app' import { HomeTabFileElectron } from './components/homeTabFileElectron' import { LanguageOptions } from './components/homeTablangOptions' +import { desktopConnectionType } from '@remix-api' declare global { interface Window { @@ -25,6 +26,7 @@ export interface RemixUiHomeTabProps { export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { const platform = useContext(platformContext) + const appContext = useContext(AppContext) const { plugin } = props const [state, setState] = useState<{ @@ -71,6 +73,10 @@ export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { } }, []) + if (appContext.appState.connectedToDesktop != desktopConnectionType .disabled) { + return (<>) + } + // border-right return (
diff --git a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx index ee891c3abdb..e7ab0c0907d 100644 --- a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx +++ b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx @@ -101,7 +101,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
{props.title && props.title}
- {!props.showCancelIcon && ( + {!props.hideCancelIcon && ( handleHide()}> diff --git a/libs/remix-ui/modal-dialog/src/lib/types/index.ts b/libs/remix-ui/modal-dialog/src/lib/types/index.ts index 90c18ad2fe2..ee436e42b93 100644 --- a/libs/remix-ui/modal-dialog/src/lib/types/index.ts +++ b/libs/remix-ui/modal-dialog/src/lib/types/index.ts @@ -20,7 +20,7 @@ export interface ModalDialogProps { cancelFn?: (reason?: AppModalCancelTypes) => void, modalClass?: string, modalParentClass?: string - showCancelIcon?: boolean, + hideCancelIcon?: boolean, hide?: boolean, handleHide: (hideState?: boolean) => void, children?: React.ReactNode, diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index 2e06963e11a..ee0fff5897e 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -98,6 +98,12 @@ export const setupEvents = (plugin: RunTab) => { plugin.on('truffle', 'compilationFinished', (file, source, languageVersion, data) => broadcastCompilationResult('truffle', plugin, dispatch, file, source, languageVersion, data)) + plugin.on('desktopHost', 'chainChanged', (context) => { + //console.log('desktopHost chainChanged', context) + fillAccountsList(plugin, dispatch) + updateInstanceBalance(plugin, dispatch) + }) + plugin.on('udapp', 'setEnvironmentModeReducer', (env: { context: string, fork: string }, from: string) => { plugin.call('notification', 'toast', envChangeNotification(env, from)) setExecutionContext(plugin, dispatch, env) diff --git a/libs/remix-ui/statusbar/src/index.ts b/libs/remix-ui/statusbar/src/index.ts index 9c8a70edde5..b8b0cc661e7 100644 --- a/libs/remix-ui/statusbar/src/index.ts +++ b/libs/remix-ui/statusbar/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/remixui-statusbar-panel' +export { DesktopStatus } from './lib/components/desktopStatus' export { StatusBarInterface } from './lib/types' diff --git a/libs/remix-ui/statusbar/src/lib/components/desktopStatus.tsx b/libs/remix-ui/statusbar/src/lib/components/desktopStatus.tsx new file mode 100644 index 00000000000..ec9bffc799f --- /dev/null +++ b/libs/remix-ui/statusbar/src/lib/components/desktopStatus.tsx @@ -0,0 +1,53 @@ +import React, { useContext } from 'react' +import '../../css/statusbar.css' +import { CustomTooltip } from '@remix-ui/helper' +import { AppContext } from '@remix-ui/app' +import { desktopConnectionType } from '@remix-api' + +export const DesktopStatus = () => { + const appContext = useContext(AppContext) + + return ( +
+ {appContext.appState.connectedToDesktop === desktopConnectionType.connected ? ( + <> + + Connected to the desktop application + + ) : null} + {appContext.appState.desktopClientConnected === desktopConnectionType.connected ? ( + <> + + + Connected to browser + + + ) : null} + {appContext.appState.desktopClientConnected === desktopConnectionType.connectedToInjected ? ( + <> + + + Connected to Metamask + + + ) : null} + {appContext.appState.connectedToDesktop === desktopConnectionType.alreadyConnected ? ( + <> + Error: you are already connected to the desktop application in another tab or window + + ) : null} + {appContext.appState.connectedToDesktop === desktopConnectionType.disconnected ? ( + <> + + Waiting for the desktop application to connect... + + ) : null} +
+ ) +} diff --git a/libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx b/libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx index 22b1dac3907..ee5b1fbfd84 100644 --- a/libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx +++ b/libs/remix-ui/statusbar/src/lib/remixui-statusbar-panel.tsx @@ -9,7 +9,9 @@ import axios from 'axios' import { StatusBar } from 'apps/remix-ide/src/app/components/status-bar' import { StatusBarContextProvider } from '../contexts/statusbarcontext' import DidYouKnow from './components/didYouKnow' -import { appPlatformTypes, platformContext } from '@remix-ui/app' +import { AppContext, appPlatformTypes, platformContext } from '@remix-ui/app' +import { DesktopStatus } from './components/desktopStatus' +import { desktopConnectionType } from '@remix-api' export interface RemixUIStatusBarProps { statusBarPlugin: StatusBar @@ -45,6 +47,7 @@ export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) { const dismiss = useDismiss(context) const role = useRole(context) const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]) + const appContext = useContext(AppContext) useEffect(() => { const abortController = new AbortController() @@ -67,6 +70,11 @@ export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) { return aiActive } + if (platform !== appPlatformTypes.desktop && appContext.appState.connectedToDesktop !== desktopConnectionType.disabled) { + return (<>
+
) + } + return ( <> @@ -88,18 +96,24 @@ export function RemixUIStatusBar({ statusBarPlugin }: RemixUIStatusBarProps) { )}
- { (platform !== appPlatformTypes.desktop) &&
+ {(platform !== appPlatformTypes.desktop) &&
-
} +
} + {(platform === appPlatformTypes.desktop) &&
+
} +
+
+
+
diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 0d279961a33..75b2df5ae5d 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -1,11 +1,13 @@ import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' import { CustomTooltip } from '@remix-ui/helper' import { Plugin } from '@remixproject/engine' -import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line +import React, { useState, useRef, useEffect, useReducer, useContext } from 'react' // eslint-disable-line import { FormattedMessage } from 'react-intl' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import './remix-ui-tabs.css' import { values } from 'lodash' +import { AppContext } from '@remix-ui/app' +import { desktopConnectionType } from '@remix-api' const _paq = (window._paq = window._paq || []) /* eslint-disable-next-line */ @@ -76,6 +78,7 @@ export const TabsUI = (props: TabsUIProps) => { const [ai_switch, setAI_switch] = useState(true) const tabs = useRef(props.tabs) tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks + const appContext = useContext(AppContext) useEffect(() => { if (props.tabs[tabsState.selectedIndex]) { @@ -187,7 +190,12 @@ export const TabsUI = (props: TabsUIProps) => { } return ( -
+
{ - if (tx.status === 1 || tx.status === '0x1' || tx.status === true) { + if (tx.status === 1 || tx.status === '0x1' || tx.status === true || tx.status === '1' || tx.status === BigInt(1)) { return } if (type === 'call' || type === 'unknownCall' || type === 'unknown') { diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx b/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx index 5a3e9e1789e..ea15633204d 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx +++ b/libs/remix-ui/vertical-icons-panel/src/lib/components/Home.tsx @@ -2,23 +2,43 @@ import { CustomTooltip } from '@remix-ui/helper' import React from 'react' import { FormattedMessage } from 'react-intl' import BasicLogo from './BasicLogo' + interface HomeProps { verticalIconPlugin: any + disableClick?: boolean } -function Home({ verticalIconPlugin }: HomeProps) { +function Home({ verticalIconPlugin, disableClick }: HomeProps) { + const handleClick = async () => { + if (!disableClick) { + await verticalIconPlugin.activateHome() + } + } + return ( - }> -
await verticalIconPlugin.activateHome()} - {...{ plugin: 'home' }} - data-id="verticalIconsHomeIcon" - id="verticalIconsHomeIcon" - > - -
-
+ <> + {disableClick ? ( +
+ +
+ ) : ( + }> +
+ +
+
+ )} + ) } diff --git a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx index 108052d4824..f2ef812a834 100644 --- a/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx +++ b/libs/remix-ui/vertical-icons-panel/src/lib/remix-ui-vertical-icons-panel.tsx @@ -6,9 +6,10 @@ import Home from './components/Home' import { verticalScrollReducer } from './reducers/verticalScrollReducer' import { Chevron } from './components/Chevron' import { IconRecord } from './types' -import { onLineContext } from '@remix-ui/app' +import { AppContext, onLineContext } from '@remix-ui/app' import { CustomTooltip } from '@remix-ui/helper' import { Registry } from '@remix-project/remix-lib' +import { desktopConnectionType } from '@remix-api' export interface RemixUiVerticalIconsPanelProps { verticalIconsPlugin: Plugin @@ -18,7 +19,7 @@ export interface RemixUiVerticalIconsPanelProps { const initialState = { scrollHeight: 0, clientHeight: 0, - scrollState: false + scrollState: false, } const RemixUiVerticalIconsPanel = ({ verticalIconsPlugin, icons }: RemixUiVerticalIconsPanelProps) => { @@ -27,6 +28,7 @@ const RemixUiVerticalIconsPanel = ({ verticalIconsPlugin, icons }: RemixUiVertic const [activateScroll, dispatchScrollAction] = useReducer(verticalScrollReducer, initialState) const [theme, setTheme] = useState('dark') const online = useContext(onLineContext) + const appContext = useContext(AppContext) const evaluateScrollability = () => { dispatchScrollAction({ @@ -34,8 +36,8 @@ const RemixUiVerticalIconsPanel = ({ verticalIconsPlugin, icons }: RemixUiVertic payload: { scrollHeight: scrollableRef.current?.scrollHeight, clientHeight: scrollableRef.current?.clientHeight, - scrollState: false - } + scrollState: false, + }, }) } @@ -67,40 +69,27 @@ const RemixUiVerticalIconsPanel = ({ verticalIconsPlugin, icons }: RemixUiVertic verticalIconsPlugin.call('manager', 'deactivatePlugin', name) } + if (appContext.appState.connectedToDesktop !== desktopConnectionType.disabled) { + return ( + <> +
+
+ +
+
+ + ) + } + return (
-
scrollableRef.current.clientHeight - ? 'remixui_default-icons-container remixui_requiredSection' - : activateScroll && activateScroll.scrollState - ? 'remixui_default-icons-container remixui_requiredSection' - : 'remixui_requiredSection' - } - > - p.isRequired && p.profile.name !== 'pluginManager')} - verticalIconsPlugin={verticalIconsPlugin} - itemContextAction={itemContextAction} - /> - {scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? ( - - ) : null} +
scrollableRef.current.clientHeight ? 'remixui_default-icons-container remixui_requiredSection' : activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_requiredSection' : 'remixui_requiredSection'}> + p.isRequired && p.profile.name !== 'pluginManager')} verticalIconsPlugin={verticalIconsPlugin} itemContextAction={itemContextAction} /> + {scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? : null}
-
scrollableRef.current.clientHeight - ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' - : activateScroll && activateScroll.scrollState - ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' - : 'remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' - } - ref={scrollableRef} - > +
scrollableRef.current.clientHeight ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' : activateScroll && activateScroll.scrollState ? 'remixui_default-icons-container remixui_scrollable-container remixui_scrollbar remixui_hide-scroll' : 'remixui_scrollable-container remixui_scrollbar remixui_hide-scroll'} ref={scrollableRef}> { @@ -111,31 +100,19 @@ const RemixUiVerticalIconsPanel = ({ verticalIconsPlugin, icons }: RemixUiVertic />
- { scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? ( - - ) : null } - p.profile.name === 'settings' || p.profile.name === 'pluginManager')} - verticalIconsPlugin={verticalIconsPlugin} - itemContextAction={itemContextAction} - /> - { Registry.getInstance().get('platform').api.isDesktop() ? ( + {scrollableRef.current && scrollableRef.current.scrollHeight > scrollableRef.current.clientHeight ? : null} + p.profile.name === 'settings' || p.profile.name === 'pluginManager')} verticalIconsPlugin={verticalIconsPlugin} itemContextAction={itemContextAction} /> + {Registry.getInstance().get('platform').api.isDesktop() ? ( online ? ( - + - ) - : - ( + + ) : ( + - ) - ) : null } + + ) + ) : null}
diff --git a/package.json b/package.json index b34d1021179..16af79719a8 100644 --- a/package.json +++ b/package.json @@ -114,15 +114,15 @@ "@openzeppelin/upgrades-core": "^1.30.0", "@openzeppelin/wizard": "0.4.0", "@reduxjs/toolkit": "^2.0.1", - "@remixproject/engine": "0.3.42", - "@remixproject/engine-electron": "0.3.42", - "@remixproject/engine-web": "0.3.42", - "@remixproject/plugin": "0.3.42", - "@remixproject/plugin-api": "0.3.42", - "@remixproject/plugin-electron": "0.3.42", - "@remixproject/plugin-utils": "0.3.42", - "@remixproject/plugin-webview": "0.3.42", - "@remixproject/plugin-ws": "0.3.42", + "@remixproject/engine": "0.3.44", + "@remixproject/engine-electron": "0.3.44", + "@remixproject/engine-web": "0.3.44", + "@remixproject/plugin": "0.3.44", + "@remixproject/plugin-api": "0.3.44", + "@remixproject/plugin-electron": "0.3.44", + "@remixproject/plugin-utils": "0.3.44", + "@remixproject/plugin-webview": "0.3.44", + "@remixproject/plugin-ws": "0.3.44", "@ricarso/react-image-magnifiers": "^1.9.0", "@types/nightwatch": "^2.3.1", "@web3modal/ethers5": "^4.0.1", @@ -133,6 +133,7 @@ "bn.js": "^5.1.2", "bootstrap": "^5.2.2", "brace": "^0.8.0", + "cbor": "^10.0.3", "change-case": "^4.1.1", "chokidar": "^2.1.8", "circom_wasm": "https://github.com/remix-project-org/circom_wasm.git", @@ -167,6 +168,7 @@ "isomorphic-git": "^1.25.7", "jquery": "^3.3.1", "js-yaml": "^4.1.0", + "json-bigint": "^1.0.0", "jspdf": "^2.5.1", "jszip": "^3.6.0", "just-once": "^2.2.0", @@ -217,7 +219,7 @@ "tslib": "^2.3.0", "web3": "^4.1.0", "winston": "^3.3.3", - "ws": "^8.17.1", + "ws": "^8.18.0", "xterm": "^5.2.1", "xterm-addon-search": "^0.12.0" }, @@ -262,6 +264,7 @@ "@types/express-ws": "^3.0.1", "@types/fs-extra": "^9.0.1", "@types/isomorphic-git__lightning-fs": "^4.4.2", + "@types/json-bigint": "^1.0.4", "@types/lodash": "^4.14.172", "@types/mocha": "^9.1.1", "@types/node": "18.16.1", @@ -275,7 +278,7 @@ "@types/request": "^2.48.7", "@types/semver": "^7.3.10", "@types/tape": "^4.13.0", - "@types/ws": "^7.2.4", + "@types/ws": "^8.5.13", "@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/parser": "^5.40.1", "@uniswap/v2-core": "^1.0.1", diff --git a/yarn.lock b/yarn.lock index 89c2ba8cd6a..e71734fc30c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6557,82 +6557,82 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.0.tgz#9bc39a5a3a71b81bdb310eba6def5bc3966695b7" integrity sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A== -"@remixproject/engine-electron@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.42.tgz#12328f762f3a2969a55abda58c9f3307143bb03b" - integrity sha512-rtvHtNPfAUUxDEFeHd2Wm6M7ee6Tx8uDhprgA9PRxiL3WjJg39PVTCm6X4Y3hpPdiqFvC56cTW30q5Fsg8X8dQ== - dependencies: - "@remixproject/engine" "0.3.42" - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" - -"@remixproject/engine-web@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.42.tgz#1221666a43d33c73119df97e6f65ab908fc2e80d" - integrity sha512-FtKfXOLY3mxHtkgdEKargup17N4w9Qqniiwxec5193zO6nR0wlBz5fB8ClKUp3V1boiQAg7fEOJv01QZ4UUTgw== - dependencies: - "@remixproject/engine" "0.3.42" - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" - -"@remixproject/engine@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.42.tgz#30606ea9c6b6d16a14eb71480ad450e82bac18dd" - integrity sha512-5t0H7SLxzyy03FAm7OSVLaiufrOYRinICAY3LRZDIqTu2wWeNQ/MTuBsfzBv0erzq8F/07rRyksbaPQdeQKoOw== - dependencies: - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" - -"@remixproject/plugin-api@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.42.tgz#c64d8b75a139d4e5cc342861d288d638818a8081" - integrity sha512-q1YLSAeluuTaLZDO2iwn3k3wW4Ii/FFVMt/5IoB8bId7pWLyVe4C+QtntrIpoRnfa8D1Hi6XEE3Sq0l5W2knMQ== - dependencies: - "@remixproject/plugin-utils" "0.3.42" - -"@remixproject/plugin-electron@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.42.tgz#e3a230593c5ec5d9f88ef6e6bb5a8a0d0c52e6ed" - integrity sha512-Raa/A6raZ79VRAh4GYPxD74ZXy4W1JqocnR23AV/WVVjefF/5EsmjBFyCGJLFW9cN53+QeB968T3VFNBjHtaUQ== - dependencies: - "@remixproject/engine" "0.3.42" - "@remixproject/plugin" "0.3.42" - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" - -"@remixproject/plugin-utils@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.42.tgz#4ac4b4aaa15e14f1a905236645a4813a63b00c9c" - integrity sha512-4SkvanwKTxZ7iffTkjwxFqqXo9XLl1swc4SIyW02WrzXAOYPKZ8Vj48N1CIcTA+HqZtnoKAHlQklYcegDtL2sw== +"@remixproject/engine-electron@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/engine-electron/-/engine-electron-0.3.44.tgz#fd01d74b8ce1f637db4096168382bd4f34917b09" + integrity sha512-dqLh1Cg1Sx4hfrHShfAHVyAainL4iHzskOd29+gmvPGtX5J5uf5WyIuEkpj14FH40P8szxECYNIXH1XRQPJRrQ== + dependencies: + "@remixproject/engine" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" + +"@remixproject/engine-web@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/engine-web/-/engine-web-0.3.44.tgz#7722a7bc5ac8863f919dcd80c37259431bf33331" + integrity sha512-vTOPHa5pqpllqJ67ONp/RK43xGAeocZ23RAe7zZ+YIEEC6cGs2WjTBB++MKVB/AK5icps9AvFuI26nlqGkSk9A== + dependencies: + "@remixproject/engine" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" + +"@remixproject/engine@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/engine/-/engine-0.3.44.tgz#fafe15bae1f02feed731f406ece131547b07c4f9" + integrity sha512-mwq4wBN+wE5Bdjm0nXOpIm/810dSPYEPlQEn6K2buV5DfzPaRewSgxCuAlLTeoITk5Vg1O/NX+VAesLixv79/g== + dependencies: + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" + +"@remixproject/plugin-api@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-api/-/plugin-api-0.3.44.tgz#fcc2dcae5140c781e0b916b563d650968c7cb78d" + integrity sha512-Sx/8AhwlfSl/JA2DTIF1SPDkEz+iwDqfTLNBT6zUVSSkCwqM7SbO7UZZZA8YJK1mP8+t6M0ZDDCwDQmCtGOPnw== + dependencies: + "@remixproject/plugin-utils" "0.3.44" + +"@remixproject/plugin-electron@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-electron/-/plugin-electron-0.3.44.tgz#050378779aa2a8979c0e77497633ec0922b29703" + integrity sha512-3ofuttt7xsPKe3royKuEaN0PgKIS8zMY5vHQTx3+8PqAx3DCcCRn9Xd7qQl/mFNEDxjjOgLrRw2Q2P8NQs7DEQ== + dependencies: + "@remixproject/engine" "0.3.44" + "@remixproject/plugin" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" + +"@remixproject/plugin-utils@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-utils/-/plugin-utils-0.3.44.tgz#fdaa978f1c10331b68e67bef9e636c7d19069c27" + integrity sha512-DX4vcbEplMFLE6SZ10wBMBQ6ntT9rEsPfajlhSnmZYyMpmuP0RHQLopNHsL+DxuHpOJM+hXFNEdORhWrgIQ3Kw== dependencies: tslib "2.0.1" -"@remixproject/plugin-webview@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.42.tgz#08febdcd1b61e8296128fdb4130d99415e8a63dc" - integrity sha512-Do+TmHDMirXXzbuneVRWdsfUax3z4R3d+dAu2AVMO2eRkiElRUgxO9A4bcwMZNP2ETW2A5Dq9jL0qDtjyaYiXg== +"@remixproject/plugin-webview@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-webview/-/plugin-webview-0.3.44.tgz#6d923efc84ebfb7da1fbe0fcb12d6dc55bc9a9bd" + integrity sha512-QZ6GQI5mJrz+VTQjFTocTkprd6Epoytdskfo9LenSgXPvcf+zvzi66+qZSvK0xHKTLvvJWutuk1FVreGEdEVSw== dependencies: - "@remixproject/plugin" "0.3.42" - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" + "@remixproject/plugin" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" axios "^0.21.1" -"@remixproject/plugin-ws@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.42.tgz#5c93112445de3bfbaddd3ce04e177d66e2ebd08a" - integrity sha512-CifgiuaHWHQVTjH4O5tMBGd9ov4LW1ENBObez1xTLYEGW90EQjFHnBXBgyyMbR9vrpMtBim7unTBUZokRdT9bg== +"@remixproject/plugin-ws@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin-ws/-/plugin-ws-0.3.44.tgz#37c05093163a59a90458b46de9e0c7defc7d0f58" + integrity sha512-ZSat1Xs+FfYTcRiyHCiGc8jv6r0yHX0guTZAYp/9MZHHUHwMF2Rpo4i4LfSBIJowWRfUADJEuLg/ZZsJDteQrA== dependencies: - "@remixproject/plugin" "0.3.42" - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" + "@remixproject/plugin" "0.3.44" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" -"@remixproject/plugin@0.3.42": - version "0.3.42" - resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.42.tgz#26709eedf53a7fe13717fa909eebebfd757f74bf" - integrity sha512-QhpKCnxlxuwGWxaJHpO7yW3p+GqEEA7451BMT2zeFfUs///RrkmJdG7Z56RHAmH8GaGlALrMemsg68O3K55eAQ== +"@remixproject/plugin@0.3.44": + version "0.3.44" + resolved "https://registry.yarnpkg.com/@remixproject/plugin/-/plugin-0.3.44.tgz#9a82e93641c7627b2f9d0ee23754d78870adc91d" + integrity sha512-pkGCLfvcZwItHD4xA1ZnQtKsgffiYymqx0cSDcPAHm4i5x7S80vJ3BVjJm/CT/gyBXP8qF5SFGP6RjveFm7haQ== dependencies: - "@remixproject/plugin-api" "0.3.42" - "@remixproject/plugin-utils" "0.3.42" + "@remixproject/plugin-api" "0.3.44" + "@remixproject/plugin-utils" "0.3.44" events "3.2.0" "@repeaterjs/repeater@^3.0.4": @@ -7520,6 +7520,11 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== +"@types/json-bigint@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3" + integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag== + "@types/json-schema@^7.0.8": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -7901,13 +7906,6 @@ dependencies: "@types/node" "*" -"@types/ws@^7.2.4": - version "7.4.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" - integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== - dependencies: - "@types/node" "*" - "@types/ws@^8.0.0": version "8.5.10" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" @@ -7915,6 +7913,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.13": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -11601,6 +11606,13 @@ catering@^2.0.0, catering@^2.1.0, catering@^2.1.1: resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== +cbor@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-10.0.3.tgz#202d79cd696f408700af51b0c9771577048a860e" + integrity sha512-72Jnj81xMsqepqdcSdf2+fflz/UDsThOHy5hj2MW5F5xzHL8Oa0KQ6I6V9CwVUPxg5pf+W9xp6W2KilaRXWWtw== + dependencies: + nofilter "^3.0.2" + cbor@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" @@ -20046,6 +20058,13 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -23328,7 +23347,7 @@ nodent@>=2.6.12: nodent-runtime "^3.2.1" resolve "^1.5.0" -nofilter@^3.1.0: +nofilter@^3.0.2, nofilter@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== @@ -31982,7 +32001,7 @@ ws@8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== -ws@>=8.7.0, ws@^8.12.0, ws@^8.13.0, ws@^8.15.0, ws@^8.17.1, ws@^8.4.2, ws@^8.7.0, ws@^8.8.1: +ws@>=8.7.0, ws@^8.12.0, ws@^8.13.0, ws@^8.15.0, ws@^8.18.0, ws@^8.4.2, ws@^8.7.0, ws@^8.8.1: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==