diff --git a/src/app/provider/view.nim b/src/app/provider/view.nim index 4991f148e1..7e03328de7 100644 --- a/src/app/provider/view.nim +++ b/src/app/provider/view.nim @@ -249,6 +249,9 @@ QtObject: proc hasPermission*(self: Web3ProviderView, hostname: string, permission: string): bool {.slot.} = result = self.status.permissions.hasPermission(hostname, permission.toPermission()) + proc disconnect*(self: Web3ProviderView) {.slot.} = + self.status.permissions.revoke("web3".toPermission()) + proc postMessage*(self: Web3ProviderView, message: string): string {.slot.} = case message.requestType(): of RequestTypes.Web3SendAsyncReadOnly: message.toWeb3SendAsyncReadOnly().process(self.status) diff --git a/src/status/permissions.nim b/src/status/permissions.nim index 6e0c447a6e..0d67ecc5dd 100644 --- a/src/status/permissions.nim +++ b/src/status/permissions.nim @@ -59,6 +59,21 @@ proc getPermissions*(self: PermissionsModel, dapp: string): HashSet[Permission] for permission in dappPermission["permissions"].getElems(): result.incl(permission.getStr().toPermission()) +proc revoke*(self: PermissionsModel, permission: Permission) = + let response = callPrivateRPC("permissions_getDappPermissions") + var permissions = initHashSet[Permission]() + + for dapps in response.parseJson["result"].getElems(): + for currPerm in dapps["permissions"].getElems(): + let p = currPerm.getStr().toPermission() + if p != permission: + permissions.incl(p) + + discard callPrivateRPC("permissions_addDappPermissions", %*[{ + "dapp": dapps["dapp"].getStr(), + "permissions": permissions.toSeq() + }]) + proc hasPermission*(self: PermissionsModel, dapp: string, permission: Permission): bool = result = self.getPermissions(dapp).contains(permission) diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index 41f97c05f7..d5f28ac610 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -266,6 +266,8 @@ Rectangle { signDialog.open(); + } else if (request.type === Constants.web3DisconnectAccount) { + web3Response(data); } else { web3Response(_web3Provider.postMessage(data)); } diff --git a/ui/app/AppLayouts/Browser/BrowserWalletMenu.qml b/ui/app/AppLayouts/Browser/BrowserWalletMenu.qml index 92974c23f4..9deeb20f71 100644 --- a/ui/app/AppLayouts/Browser/BrowserWalletMenu.qml +++ b/ui/app/AppLayouts/Browser/BrowserWalletMenu.qml @@ -82,8 +82,11 @@ Popup { MouseArea { cursorShape: Qt.PointingHandCursor anchors.fill: parent - onClicked: console.log('TODO Disconnect') - + onClicked: { + _web3Provider.disconnect(); + provider.postMessage(`{"type":"web3-disconnect-account"}`); + popup.close(); + } } } } diff --git a/ui/app/AppLayouts/Browser/provider.js b/ui/app/AppLayouts/Browser/provider.js index a83e6d0cfe..99ae8e43d6 100644 --- a/ui/app/AppLayouts/Browser/provider.js +++ b/ui/app/AppLayouts/Browser/provider.js @@ -805,6 +805,12 @@ You may add additional accurate notices of copyright ownership. const id = data.messageId; const callback = callbacks[id]; + if(data.type === "web3-disconnect-account") { + window.statusAppcurrentAccountAddress = ""; + window.ethereum.emit("accountsChanged", []); + return; + } + if (callback) { if (data.type === "api-response") { if (data.permission == "qr-code") { @@ -846,6 +852,9 @@ You may add additional accurate notices of copyright ownership. new QWebChannel(qt.webChannelTransport, function(channel) { backend = channel.objects.backend; backend.web3Response.connect(onMessage); + + window.ethereum.on("connected", () => {}); // TODO: Dummy event. Will need to be replaced once connecte/disconnected provider logic is implemented in status-go + window.ethereum.emit("connected", {"chainId": backend.networkId.toString()}); }); const bridgeSend = data => { @@ -918,6 +927,35 @@ You may add additional accurate notices of copyright ownership. } UserRejectedRequest.prototype = Object.create(Error.prototype); + function UnsupportedMethod() { + this.name = "Unsupported Method"; + this.id = 4200; + this.code = 4200; + this.message = "The Provider does not support the requested method."; + } + UnsupportedMethod.prototype = Object.create(Error.prototype); + + function Disconnected() { + this.name = "Disconnected"; + this.id = 4900; + this.code = 4900; + this.message = "The Provider is disconnected from all chains."; + } + Disconnected.prototype = Object.create(Error.prototype); + + // + function ChainDisconnected() { + this.name = "Chain Disconnected"; + this.id = 4901; + this.code = 4901; + this.message = "The Provider is not connected to the requested chain."; + } + ChainDisconnected.prototype = Object.create(ChainDisconnected.prototype); + + + // NOTE: chainChanged is not implemented because we do not support switching networks without disconnecting from Status + // Provider.on('chainChanged', listener: (chainId: string) => void): Provider; + function web3Response (payload, result){ return { id: payload.id, @@ -951,6 +989,25 @@ You may add additional accurate notices of copyright ownership. EthereumProvider.prototype.isStatus = true; EthereumProvider.prototype.status = new StatusAPI(); EthereumProvider.prototype.isConnected = function () { return true; }; + EthereumProvider.prototype._events = {}; + + EthereumProvider.prototype.on = function(name, listener) { + if (!this._events[name]) { + this._events[name] = []; + } + this._events[name].push(listener); + } + + EthereumProvider.prototype.removeListener = function (name, listenerToRemove) { + if (!this._events[name]) throw new Error(`event "${name}" does not exist`); + const filterListeners = (listener) => listener !== listenerToRemove; + this._events[name] = this._events[name].filter(filterListeners); + } + + EthereumProvider.prototype.emit = function (name, data) { + if (!this._events[name]) throw new Error(`event "${name}" does not exist`); + this._events[name].forEach(cb => cb(data)); + } EthereumProvider.prototype.enable = function () { return sendAPIrequest('web3'); @@ -1053,4 +1110,7 @@ You may add additional accurate notices of copyright ownership. } window.ethereum = new EthereumProvider(); + + // TODO: connected/disconnected must be emitted if we lose connection to infura. Verify if status-go emits events for that + })(); \ No newline at end of file diff --git a/ui/imports/Constants.qml b/ui/imports/Constants.qml index c8dc5ff67f..01d3242f9d 100644 --- a/ui/imports/Constants.qml +++ b/ui/imports/Constants.qml @@ -87,6 +87,7 @@ QtObject { readonly property string api_request: "api-request" readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only" + readonly property string web3DisconnectAccount: "web3-disconnect-account" readonly property string permission_web3: "web3" readonly property string permission_contactCode: "contact-code"