feat(BC): Unify dapp sessions between WalletConnect and BrowserConnect

New component introduced (DAppsModel) to provide a common model for WC and BC. The WCDappsProvider and BCDappsProvider components are responsible to fill the model from different sources
This commit is contained in:
Alex Jbanca 2024-10-30 17:18:24 +02:00
parent 4636f80260
commit 1ecd960cb2
No known key found for this signature in database
GPG Key ID: 6004079575C21C5D
14 changed files with 305 additions and 152 deletions

View File

@ -21,6 +21,8 @@ type ConnectorGrantDAppPermissionSignal* = ref object of Signal
url*: string
name*: string
iconUrl*: string
chains*: string
sharedAccount*: string
type ConnectorRevokeDAppPermissionSignal* = ref object of Signal
url*: string
@ -48,6 +50,8 @@ proc fromEvent*(T: type ConnectorGrantDAppPermissionSignal, event: JsonNode): Co
result.url = event["event"]{"url"}.getStr()
result.name = event["event"]{"name"}.getStr()
result.iconUrl = event["event"]{"iconUrl"}.getStr()
result.chains = $(event["event"]{"chains"})
result.sharedAccount = event["event"]{"sharedAccount"}.getStr()
proc fromEvent*(T: type ConnectorRevokeDAppPermissionSignal, event: JsonNode): ConnectorRevokeDAppPermissionSignal =
result = ConnectorRevokeDAppPermissionSignal()

View File

@ -26,10 +26,16 @@ QtObject:
proc delete*(self: Controller) =
self.QObject.delete
proc dappRequestsToConnect*(self: Controller, requestId: string, payload: string) {.signal.}
proc dappValidatesTransaction*(self: Controller, requestId: string, payload: string) {.signal.}
proc dappGrantDAppPermission*(self: Controller, payload: string) {.signal.}
proc dappRevokeDAppPermission*(self: Controller, payload: string) {.signal.}
proc connectRequested*(self: Controller, requestId: string, payload: string) {.signal.}
proc connected*(self: Controller, payload: string) {.signal.}
proc disconnected*(self: Controller, payload: string) {.signal.}
proc signRequested*(self: Controller, requestId: string, payload: string) {.signal.}
proc approveConnectResponse*(self: Controller, payload: string, error: bool) {.signal.}
proc rejectConnectResponse*(self: Controller, payload: string, error: bool) {.signal.}
proc approveTransactionResponse*(self: Controller, requestId: string, error: bool) {.signal.}
proc rejectTransactionResponse*(self: Controller, requestId: string, error: bool) {.signal.}
proc newController*(service: connector_service.Service, events: EventEmitter): Controller =
new(result, delete)
@ -51,7 +57,7 @@ QtObject:
"url": params.url,
}
controller.dappRequestsToConnect(params.requestId, dappInfo.toJson())
controller.connectRequested(params.requestId, dappInfo.toJson())
result.events.on(SIGNAL_CONNECTOR_EVENT_CONNECTOR_SEND_TRANSACTION) do(e: Args):
let params = ConnectorSendTransactionSignal(e)
@ -63,7 +69,7 @@ QtObject:
"txArgs": params.txArgs,
}
controller.dappValidatesTransaction(params.requestId, dappInfo.toJson())
controller.signRequested(params.requestId, dappInfo.toJson())
result.events.on(SIGNAL_CONNECTOR_GRANT_DAPP_PERMISSION) do(e: Args):
let params = ConnectorGrantDAppPermissionSignal(e)
@ -71,9 +77,11 @@ QtObject:
"icon": params.iconUrl,
"name": params.name,
"url": params.url,
"chains": params.chains,
"sharedAccount": params.sharedAccount,
}
controller.dappGrantDAppPermission(dappInfo.toJson())
controller.connected(dappInfo.toJson())
result.events.on(SIGNAL_CONNECTOR_REVOKE_DAPP_PERMISSION) do(e: Args):
let params = ConnectorRevokeDAppPermissionSignal(e)
@ -83,7 +91,7 @@ QtObject:
"url": params.url,
}
controller.dappRevokeDAppPermission(dappInfo.toJson())
controller.disconnected(dappInfo.toJson())
result.QObject.setup
@ -97,20 +105,27 @@ QtObject:
except JsonParsingError:
raise newException(ValueError, "Failed to parse JSON")
proc approveDappConnectRequest*(self: Controller, requestId: string, account: string, chainIDString: string): bool {.slot.} =
proc approveConnection*(self: Controller, requestId: string, account: string, chainIDString: string): bool {.slot.} =
let chainId = parseSingleUInt(chainIDString)
return self.service.approveDappConnect(requestId, account, chainId)
result = self.service.approveDappConnect(requestId, account, chainId)
self.approveConnectResponse(requestId, not result)
proc rejectDappConnectRequest*(self: Controller, requestId: string): bool {.slot.} =
return self.service.rejectDappConnect(requestId)
proc rejectConnection*(self: Controller, requestId: string): bool {.slot.} =
result = self.service.rejectDappConnect(requestId)
self.rejectConnectResponse(requestId, not result)
proc approveTransactionRequest*(self: Controller, requestId: string, signature: string): bool {.slot.} =
proc approveTransaction*(self: Controller, requestId: string, signature: string): bool {.slot.} =
let hash = utils.createHash(signature)
return self.service.approveTransactionRequest(requestId, hash)
result = self.service.approveTransactionRequest(requestId, hash)
self.approveTransactionResponse(requestId, not result)
proc rejectTransactionSigning*(self: Controller, requestId: string): bool {.slot.} =
return self.service.rejectTransactionSigning(requestId)
proc rejectTransaction*(self: Controller, requestId: string): bool {.slot.} =
result = self.service.rejectTransactionSigning(requestId)
self.rejectTransactionResponse(requestId, not result)
proc recallDAppPermission*(self: Controller, dAppUrl: string): bool {.slot.} =
return self.service.recallDAppPermission(dAppUrl)
proc disconnect*(self: Controller, dAppUrl: string): bool {.slot.} =
result = self.service.recallDAppPermission(dAppUrl)
proc getDApps*(self: Controller): string {.slot.} =
return self.service.getDApps()

View File

@ -138,3 +138,16 @@ QtObject:
except Exception as e:
error "recallDAppPermissionFinishedRpc failed: ", err=e.msg
return false
proc getDApps*(self: Service): string =
try:
let response = status_go.getPermittedDAppsList()
if not response.error.isNil:
raise newException(Exception, "Error getting connector dapp list: " & response.error.message)
# Expect nil golang array to be valid empty array
let jsonArray = $response.result
return if jsonArray != "null": jsonArray else: "[]"
except Exception as e:
error "getDApps failed: ", err=e.msg
return "[]"

View File

@ -38,6 +38,9 @@ rpc(requestAccountsRejected, "connector"):
rpc(recallDAppPermission, "connector"):
dAppUrl: string
rpc(getPermittedDAppsList, "connector"):
discard
proc isSuccessResponse(rpcResponse: RpcResponse[JsonNode]): bool =
return rpcResponse.error.isNil

View File

@ -0,0 +1,72 @@
import QtQuick 2.15
import AppLayouts.Wallet.services.dapps 1.0
import StatusQ.Core.Utils 0.1
import shared.stores 1.0
import utils 1.0
DAppsModel {
id: root
required property BrowserConnectStore store
readonly property int connectorId: Constants.StatusConnect
property bool enabled: true
Connections {
target: root.store
enabled: root.enabled
function onConnected(dappJson) {
const dapp = JSON.parse(dappJson)
const { url, name, icon, sharedAccount } = dapp
if (!url) {
console.warn(invalidDAppUrlError)
return
}
root.append({
name,
url,
iconUrl: icon,
topic: url,
connectorId: root.connectorId,
accountAddresses: [{address: sharedAccount}],
rawSessions: [dapp]
})
}
function onDisconnected(dappJson) {
const dapp = JSON.parse(dappJson)
const { url } = dapp
if (!url) {
console.warn(invalidDAppUrlError)
return
}
root.remove(dapp.url)
}
}
Component.onCompleted: {
if (root.enabled) {
const dappsStr = root.store.getDApps()
if (dappsStr) {
const dapps = JSON.parse(dappsStr)
dapps.forEach(dapp => {
const { url, name, iconUrl, sharedAccount } = dapp
root.append({
name,
url,
iconUrl,
topic: url,
connectorId: root.connectorId,
accountAddresses: [{address: sharedAccount}],
rawSessions: [dapp]
})
})
}
}
}
}

View File

@ -1,106 +0,0 @@
import QtQuick 2.15
import AppLayouts.Wallet.services.dapps 1.0
import StatusQ.Core.Utils 0.1
import shared.stores 1.0
import utils 1.0
QObject {
id: root
readonly property alias dappsModel: d.dappsModel
readonly property int connectorId: Constants.StatusConnect
property bool enabled: true
function addSession(url, name, iconUrl, accountAddress) {
if (!enabled) {
return
}
if (!url || !name || !iconUrl || !accountAddress) {
console.error("addSession: missing required parameters")
return
}
const topic = url
const activeSession = getActiveSession(topic)
if (!activeSession) {
d.addSession({
url,
name,
iconUrl,
topic,
connectorId: root.connectorId,
accountAddresses: [{address: accountAddress}]
})
return
}
if (!ModelUtils.contains(activeSession.accountAddresses, "address", accountAddress, Qt.CaseInsensitive)) {
activeSession.accountAddresses.append({address: accountAddress})
}
}
function revokeSession(topic) {
if (!enabled) {
return
}
d.revokeSession(topic)
}
function getActiveSession(topic) {
if (!enabled) {
return
}
return d.getActiveSession(topic)
}
QObject {
id: d
property ListModel dappsModel: ListModel {
id: dapps
}
function addSession(dappItem) {
dapps.append(dappItem)
}
function revokeSession(topic) {
for (let i = 0; i < dapps.count; i++) {
let existingDapp = dapps.get(i)
if (existingDapp.topic === topic) {
dapps.remove(i)
break
}
}
}
function revokeAllSessions() {
for (let i = 0; i < dapps.count; i++) {
dapps.remove(i)
}
}
function getActiveSession(topic) {
for (let i = 0; i < dapps.count; i++) {
const existingDapp = dapps.get(i)
if (existingDapp.topic === topic) {
return {
name: existingDapp.name,
url: existingDapp.url,
icon: existingDapp.iconUrl,
topic: existingDapp.topic,
connectorId: existingDapp.connectorId,
accountAddresses: existingDapp.accountAddresses
};
}
}
return null
}
}
}

View File

@ -0,0 +1,71 @@
import QtQuick 2.15
import StatusQ.Core.Utils 0.1
QObject {
id: root
// RoleNames
// name: string
// url: string
// iconUrl: string
// topic: string
// connectorId: int
// accountAddressses: [{address: string}]
// chains: string
// rawSessions: [{session: object}]
readonly property ListModel model: ListModel {}
function append(dapp) {
try {
const {name, url, iconUrl, topic, accountAddresses, connectorId, rawSessions } = dapp
if (!name || !url || !iconUrl || !topic || !connectorId || !accountAddresses || !rawSessions) {
console.warn("DAppsModel - Failed to append dapp, missing required fields", JSON.stringify(dapp))
return
}
root.model.append({
name,
url,
iconUrl,
topic,
connectorId,
accountAddresses,
rawSessions
})
} catch (e) {
console.warn("DAppsModel - Failed to append dapp", e)
}
}
function remove(topic) {
for (let i = 0; i < root.model.count; i++) {
const dapp = root.model.get(i)
if (dapp.topic == topic) {
root.model.remove(i)
break
}
}
}
function clear() {
root.model.clear()
}
function getByTopic(topic) {
for (let i = 0; i < root.model.count; i++) {
const dapp = root.model.get(i)
if (dapp.topic == topic) {
return {
name: dapp.name,
url: dapp.url,
iconUrl: dapp.iconUrl,
topic: dapp.topic,
connectorId: dapp.connectorId,
accountAddresses: dapp.accountAddresses,
rawSessions: dapp.rawSessions
}
}
}
return null
}
}

View File

@ -9,14 +9,15 @@ import shared.stores 1.0
import utils 1.0
QObject {
DAppsModel {
id: root
// Input
required property WalletConnectSDKBase sdk
required property DAppsStore store
required property var supportedAccountsModel
readonly property int connectorId: Constants.WalletConnect
readonly property var dappsModel: d.dappsModel
property bool enabled: true
@ -39,11 +40,6 @@ QObject {
QObject {
id: d
property ListModel dappsModel: ListModel {
id: dapps
objectName: "DAppsModel"
}
property Connections sdkConnections: Connections {
target: root.sdk
enabled: root.enabled
@ -75,23 +71,22 @@ QObject {
{
dappsListReceivedFn = (dappsJson) => {
root.store.dappsListReceived.disconnect(dappsListReceivedFn);
dapps.clear();
root.clear();
let dappsList = JSON.parse(dappsJson);
for (let i = 0; i < dappsList.length; i++) {
const cachedEntry = dappsList[i];
// TODO #15075: on SDK dApps refresh update the model that has data source from persistence instead of using reset
const dappEntryWithRequiredRoles = {
description: "",
url: cachedEntry.url,
name: cachedEntry.name,
iconUrl: cachedEntry.iconUrl,
accountAddresses: [],
topic: "",
connectorId: root.connectorId,
sessions: []
rawSessions: []
}
dapps.append(dappEntryWithRequiredRoles);
root.append(dappEntryWithRequiredRoles);
}
}
root.store.dappsListReceived.connect(dappsListReceivedFn);
@ -125,18 +120,18 @@ QObject {
// more modern syntax (ES-6) is not available yet
const combinedAddresses = new Set(existingDApp.accountAddresses.concat(accounts));
existingDApp.accountAddresses = Array.from(combinedAddresses);
dapp.sessions = [...existingDApp.sessions, session]
dapp.rawSessions = [...existingDApp.rawSessions, session]
} else {
dapp.accountAddresses = accounts
dapp.topic = sessionID
dapp.sessions = [session]
dapp.rawSessions = [session]
dAppsMap[dapp.url] = dapp
}
topics.push(sessionID)
}
// TODO #15075: on SDK dApps refresh update the model that has data source from persistence instead of using reset
dapps.clear();
root.clear();
// Iterate dAppsMap and fill dapps
for (const uri in dAppsMap) {
@ -145,7 +140,7 @@ QObject {
// having array of key value pair fixes the problem
dAppEntry.accountAddresses = dAppEntry.accountAddresses.filter(account => (!!account)).map(account => ({address: account}));
dAppEntry.connectorId = root.connectorId;
dapps.append(dAppEntry);
root.append(dAppEntry);
}
root.store.updateWalletConnectSessions(JSON.stringify(topics))

View File

@ -30,6 +30,7 @@ QObject {
//input properties
required property WalletConnectSDKBase wcSDK
required property BrowserConnectStore bcStore
required property DAppsStore store
required property var walletRootStore
// // Array[chainId] of the networks that are down
@ -51,8 +52,6 @@ QObject {
/// Default value: true
readonly property bool serviceAvailableToCurrentAddress: !root.walletRootStore.selectedAddress ||
ModelUtils.contains(root.validAccounts, "address", root.walletRootStore.selectedAddress, Qt.CaseInsensitive)
/// TODO: refactor
readonly property alias connectorDAppsProvider: connectorDAppsProvider
readonly property bool isServiceOnline: requestHandler.isServiceOnline
@ -130,11 +129,11 @@ QObject {
sources: [
SourceModel {
model: dappsProvider.dappsModel
model: dappsProvider.model
markerRoleValue: "walletConnect"
},
SourceModel {
model: connectorDAppsProvider.dappsModel
model: connectorDAppsProvider.model
markerRoleValue: "statusConnect"
}
]
@ -308,6 +307,7 @@ QObject {
ConnectorDAppsListProvider {
id: connectorDAppsProvider
enabled: root.connectorFeatureEnabled
store: root.bcStore
}
// Timeout for the corner case where the URL was already dismissed and the SDK doesn't respond with an error nor advances with the proposal

View File

@ -1,10 +1,9 @@
BCDappsProvider 1.0 BCDappsProvider.qml
DappsConnectorSDK 1.0 DappsConnectorSDK.qml
DAppsHelpers 1.0 helpers.js
DAppsModel 1.0 DAppsModel.qml
DAppsRequestHandler 1.0 DAppsRequestHandler.qml
WalletConnectSDKBase 1.0 WalletConnectSDKBase.qml
WalletConnectSDK 1.0 WalletConnectSDK.qml
WalletConnectService 1.0 WalletConnectService.qml
DappsConnectorSDK 1.0 DappsConnectorSDK.qml
DAppsListProvider 1.0 DAppsListProvider.qml
DAppsRequestHandler 1.0 DAppsRequestHandler.qml
ConnectorDAppsListProvider 1.0 ConnectorDAppsListProvider.qml
DAppsHelpers 1.0 helpers.js
WCDappsProvider 1.0 WCDappsProvider.qml

View File

@ -2206,6 +2206,9 @@ Item {
store: SharedStores.DAppsStore {
controller: WalletStores.RootStore.walletConnectController
}
bcStore: SharedStores.BrowserConnectStore {
controller: WalletStores.RootStore.dappsConnectorController
}
walletRootStore: WalletStores.RootStore
blockchainNetworksDown: appMain.networkConnectionStore.blockchainNetworksDown

View File

@ -0,0 +1,83 @@
import QtQuick 2.15
import StatusQ.Core.Utils 0.1 as SQUtils
SQUtils.QObject {
id: root
required property var controller
// Signals driven by the dApp
signal connectRequested(string requestId, string dappJson)
signal signRequested(string requestId, string requestJson)
signal connected(string dappJson)
signal disconnected(string dappJson)
// Responses to user actions
signal approveConnectResponse(string id, bool error)
signal rejectConnectResponse(string id, bool error)
signal approveTransactionResponse(string requestId, bool error)
signal rejectTransactionResponse(string requestId, bool error)
function approveConnection(id, account, chainId) {
return controller.approveConnection(id, account, chainId)
}
function rejectConnection(id, error) {
return controller.rejectConnection(id, error)
}
function approveTransaction(requestId, signature) {
return controller.approveTransaction(requestId, signature)
}
function rejectTransaction(requestId, error) {
return controller.rejectTransaction(requestId, error)
}
function disconnect(id) {
return controller.disconnect(id)
}
function getDApps() {
return controller.getDApps()
}
Connections {
target: controller
function onConnectRequested(requestId, dappJson) {
root.connectRequested(requestId, dappJson)
}
function onSignRequested(requestId, requestJson) {
root.signRequested(requestId, requestJson)
}
function onConnected(dappJson) {
root.connected(dappJson)
}
function onDisconnected(dappJson) {
root.disconnected(dappJson)
}
function onApproveConnectResponse(id, error) {
root.approveConnectResponse(id, error)
}
function onRejectConnectResponse(id, error) {
root.rejectConnectResponse(id, error)
}
function onApproveTransactionResponse(requestId, error) {
root.approveTransactionResponse(requestId, error)
}
function onRejectTransactionResponse(requestId, error) {
root.rejectTransactionResponse(requestId, error)
}
}
}

View File

@ -1,4 +1,5 @@
BIP39_en 1.0 BIP39_en.qml
BrowserConnectStore 1.0 BrowserConnectStore.qml
CommunityTokensStore 1.0 CommunityTokensStore.qml
CurrenciesStore 1.0 CurrenciesStore.qml
DAppsStore 1.0 DAppsStore.qml

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit b329b158c8f62f5bab03be596cf4f7d7b07c96f7
Subproject commit ba2baec26fac64fe074b485639e95a7b11e81ae5