feat(wallet) run WebEngineView as a service in background

This is required to control the resource consumption in case of no
usage of wallet connect
Hence we load the WebEngineView only if we have active pairings and
such that SDK events are expected from the paired dapps.

Also:

- Moved the generic WebEngineView communication bridge to StatusQ
- Added basic tests for WebEngineLoader
- Add a way to know when wallet is loaded (`walletReady`)
- Add storybook support for mock of nim sections as context properties

Updates: #12639
This commit is contained in:
Stefan 2023-11-15 23:26:12 +02:00 committed by Stefan Dunca
parent b30c2992a5
commit 5b9e4faa8a
38 changed files with 2769 additions and 498 deletions

View File

@ -312,6 +312,7 @@ proc checkIfModuleDidLoad(self: Module) =
self.notifyFilterChanged()
self.moduleLoaded = true
self.delegate.walletSectionDidLoad()
self.view.setWalletReady()
method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()

View File

@ -23,6 +23,7 @@ QtObject:
isNonArchivalNode: bool
keypairOperabilityForObservedAccount: string
wcController: wcc.Controller
walletReady: bool
proc setup(self: View) =
self.QObject.setup
@ -212,3 +213,17 @@ QtObject:
return newQVariant(self.wcController)
QtProperty[QVariant] walletConnectController:
read = getWalletConnectController
proc walletReadyChanged*(self: View) {.signal.}
proc getWalletReady*(self: View): bool {.slot.} =
return self.walletReady
proc setWalletReady*(self: View) =
if not self.walletReady:
self.walletReady = true
self.walletReadyChanged()
QtProperty[bool] walletReady:
read = getWalletReady
notify = walletReadyChanged

View File

@ -1,4 +1,4 @@
import NimQml, strutils, logging, json
import NimQml, strutils, logging, json, options
import backend/wallet_connect as backend
@ -20,6 +20,7 @@ QtObject:
Controller* = ref object of QObject
events: EventEmitter
sessionRequestJson: JsonNode
hasActivePairings: Option[bool]
## Forward declarations
proc onDataSigned(self: Controller, keyUid: string, path: string, r: string, s: string, v: string, pin: string)
@ -68,6 +69,19 @@ QtObject:
let supportedNamespacesJson = if res.hasKey("supportedNamespaces"): $res["supportedNamespaces"] else: ""
self.proposeUserPair(sessionProposalJson, supportedNamespacesJson)
proc recordSuccessfulPairing(self: Controller, sessionProposalJson: string) {.slot.} =
if backend.recordSuccessfulPairing(sessionProposalJson):
if not self.hasActivePairings.get(false):
self.hasActivePairings = some(true)
proc getHasActivePairings*(self: Controller): bool {.slot.} =
if self.hasActivePairings.isNone:
self.hasActivePairings = some(backend.hasActivePairings())
return self.hasActivePairings.get(false)
QtProperty[bool] hasActivePairings:
read = getHasActivePairings
proc respondSessionRequest*(self: Controller, sessionRequestJson: string, signedJson: string, error: bool) {.signal.}
proc sendTransactionAndRespond(self: Controller, signature: string) =
@ -140,4 +154,4 @@ QtObject:
proc getProjectId*(self: Controller): string {.slot.} =
return constants.WALLET_CONNECT_PROJECT_ID
QtProperty[string] projectId:
read = getProjectId
read = getProjectId

View File

@ -6,7 +6,7 @@ from gen import rpc
import backend
# Declared in services/wallet/walletconnect/walletconnect.go
#const eventWCTODO*: string = "wallet-wc-todo"
const eventWCProposeUserPair*: string = "WalletConnectProposeUserPair"
# Declared in services/wallet/walletconnect/walletconnect.go
const ErrorChainsNotSupported*: string = "chains not supported"
@ -28,11 +28,23 @@ rpc(wCSendTransactionWithSignature, "wallet"):
rpc(wCPairSessionProposal, "wallet"):
sessionProposalJson: string
rpc(wCRecordSuccessfulPairing, "wallet"):
sessionProposalJson: string
rpc(wCHasActivePairings, "wallet"):
discard
rpc(wCSessionRequest, "wallet"):
sessionRequestJson: string
proc prepareResponse(res: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string =
proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool =
if not rpcResponse.error.isNil:
return true
return false
proc prepareResponse(res: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string =
if isErrorResponse(rpcResponse):
return rpcResponse.error.message
if rpcResponse.result.isNil:
return "no result"
@ -79,10 +91,28 @@ proc pair*(res: var JsonNode, sessionProposalJson: string): string =
warn e.msg
return e.msg
proc recordSuccessfulPairing*(sessionProposalJson: string): bool =
try:
let response = wCRecordSuccessfulPairing(sessionProposalJson)
return not isErrorResponse(response)
except Exception as e:
warn e.msg
return false
proc hasActivePairings*(): bool =
try:
let response = wCHasActivePairings()
if isErrorResponse(response):
return false
return response.result.getBool()
except Exception as e:
warn e.msg
return false
proc sessionRequest*(res: var JsonNode, sessionRequestJson: string): string =
try:
let response = wCSessionRequest(sessionRequestJson)
return prepareResponse(res, response)
except Exception as e:
warn e.msg
return e.msg
return e.msg

View File

@ -1,9 +1,12 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlComponent>
#include <QDirIterator>
#include <QtWebView>
#include "cachecleaner.h"
#include "directorieswatcher.h"
#include "figmalinks.h"
@ -12,11 +15,15 @@
#include "testsrunner.h"
#include "systemutils.h"
#include <memory>
struct PagesModelInitialized : public PagesModel {
explicit PagesModelInitialized(QObject *parent = nullptr)
: PagesModel(QML_IMPORT_ROOT + QStringLiteral("/pages"), parent) {}
};
void loadContextPropertiesMocks(const char* storybookRoot, QQmlApplicationEngine& engine);
int main(int argc, char *argv[])
{
// Required by the WalletConnectSDK view
@ -90,6 +97,7 @@ int main(int argc, char *argv[])
return new SystemUtils;
});
loadContextPropertiesMocks(QML_IMPORT_ROOT, engine);
#ifdef Q_OS_WIN
const QUrl url(QUrl::fromLocalFile(QML_IMPORT_ROOT + QStringLiteral("/main.qml")));
#else
@ -104,3 +112,34 @@ int main(int argc, char *argv[])
return QGuiApplication::exec();
}
void loadContextPropertiesMocks(const char* storybookRoot, QQmlApplicationEngine& engine) {
QDirIterator it(QML_IMPORT_ROOT + QStringLiteral("/stubs/nim/sectionmocks"), QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
if (it.fileInfo().isFile() && it.fileInfo().suffix() == QStringLiteral("qml")) {
auto component = std::make_unique<QQmlComponent>(&engine, QUrl::fromLocalFile(it.filePath()));
if (component->status() != QQmlComponent::Ready) {
qWarning() << "Failed to load mock for" << it.filePath() << component->errorString();
continue;
}
auto objPtr = std::unique_ptr<QObject>(component->create());
if(!objPtr) {
qWarning() << "Failed to create mock for" << it.filePath();
continue;
}
if(!objPtr->property("contextPropertyName").isValid()) {
qInfo() << "Not a mock, missing property name \"contextPropertyName\"";
continue;
}
auto contextPropertyName = objPtr->property("contextPropertyName").toString();
auto obj = objPtr.release();
obj->setParent(&engine);
engine.rootContext()->setContextProperty(contextPropertyName, obj);
}
}
}

View File

@ -323,12 +323,12 @@ ApplicationWindow {
modal: true
contentItem: Label {
text: `
Tips:
For inline components use naming convention of adding
"Custom" at the begining (like Custom${root.currentPage})
For popups set closePolicy to "Popup.NoAutoClose""
`
text: '
Tips:\n\
For inline components use naming convention of adding\n\
"Custom" at the begining (like Custom'+root.currentPage+')\n\
For popups set closePolicy to "Popup.NoAutoClose"\n\
'
}
}

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import Qt.labs.settings 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
@ -24,55 +25,85 @@ import nim 1.0
Item {
id: root
WalletConnect {
id: wc
anchors.top: parent.bottom
anchors.left: parent.left
url: `${pagesFolder}/../stubs/AppLayouts/Wallet/views/walletconnect/src/index.html`
controller: WalletConnectController {
pairSessionProposal: function(sessionProposalJson) {
proposeUserPair(sessionProposalJson, `{"eip155":{"methods":["eth_sendTransaction","personal_sign"],"chains":["eip155:5"],"events":["accountsChanged","chainChanged"],"accounts":["eip155:5:0x53780d79E83876dAA21beB8AFa87fd64CC29990b","eip155:5:0xBd54A96c0Ae19a220C8E1234f54c940DFAB34639","eip155:5:0x5D7905390b77A937Ae8c444aA8BF7Fa9a6A7DBA0"]}}`)
}
sessionRequest: function(sessionRequestJson, password) {
const signedJson = "0x1234567890"
this.respondSessionRequest(sessionRequestJson, signedJson, respondError.checked)
}
hasActivePairings: settings.hasActivePairings
projectId: "87815d72a81d739d2a7ce15c2cfdefb3"
}
clip: true
}
// qml Splitter
SplitView {
anchors.fill: parent
WalletConnect {
id: walletConnect
ColumnLayout {
SplitView.fillWidth: true
backgroundColor: Theme.palette.statusAppLayout.backgroundColor
controller: WalletConnectController {
pairSessionProposal: function(sessionProposalJson) {
proposeUserPair(sessionProposalJson, `{"eip155":{"methods":["eth_sendTransaction","personal_sign"],"chains":["eip155:5"],"events":["accountsChanged","chainChanged"],"accounts":["eip155:5:0x53780d79E83876dAA21beB8AFa87fd64CC29990b","eip155:5:0xBd54A96c0Ae19a220C8E1234f54c940DFAB34639","eip155:5:0x5D7905390b77A937Ae8c444aA8BF7Fa9a6A7DBA0"]}}`)
StatusButton {
id: openModalButton
text: "OpenModal"
onClicked: {
wc.modal.open()
}
sessionRequest: function(sessionRequestJson, password) {
const signedJson = "0x1234567890"
this.respondSessionRequest(sessionRequestJson, signedJson, respondError.checked)
}
projectId: "87815d72a81d739d2a7ce15c2cfdefb3"
}
clip: true
ColumnLayout {}
}
ColumnLayout {
id: optionsSpace
StatusRadioButton {
text: "WebEngine running"
checked: wc.sdk.webEngineLoader.active
enabled: false
}
RowLayout {
id: optionsHeader
Text { text: "projectId" }
Text {
readonly property string projectId: walletConnect.controller.projectId
readonly property string projectId: wc.controller.projectId
text: projectId.substring(0, 3) + "..." + projectId.substring(projectId.length - 3)
font.bold: true
}
}
CheckBox {
StatusCheckBox {
id: hasActivePairingsCheckBox
text: "Has active pairings"
checked: settings.hasActivePairings
}
StatusCheckBox {
id: respondError
text: "Respond Error"
checked: false
}
// spacer
ColumnLayout {}
}
}
Settings {
id: settings
property bool hasActivePairings: hasActivePairingsCheckBox.checked
}
}
// category: Popups

View File

@ -0,0 +1,10 @@
<!-- Keep in sync with mock of: ui/app/AppLayouts/Wallet/views/walletconnect/sdk/src/index.html to avoid trying to load from app qrc -->
<!DOCTYPE html>
<html>
<head>
<script src="qrc:/StatusQ/Components/private/qwebchannel/qwebchannel.js" defer></script>
<script src="qrc:/StatusQ/Components/private/qwebchannel/helpers.js" defer></script>
<script src="../../../../../../../ui/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js" defer></script>
</head>
<body></body>
</html>

View File

@ -17,5 +17,6 @@ Item {
required property var sessionRequest
required property bool hasActivePairings
required property string projectId
}

View File

@ -0,0 +1,13 @@
// Mock of src/app/modules/main/wallet_section/networks/view.nim
import QtQuick 2.15
QtObject {
readonly property string contextPropertyName: "networksModule"
//
// Silence warnings
readonly property ListModel layer1: ListModel {}
readonly property ListModel layer2: ListModel {}
readonly property ListModel enabled: ListModel {}
readonly property ListModel all: ListModel {}
}

View File

@ -0,0 +1,20 @@
import QtQuick 2.15
// Required mock of: src/app/modules/main/wallet_section/view.nim
Item {
readonly property string contextPropertyName: "walletSection"
// Required
//
readonly property bool walletReady: true
//
// Silence warnings
readonly property QtObject overview: QtObject {
readonly property string mixedcaseAddress: ""
}
readonly property ListModel mixedcaseAddress: ListModel {}
signal walletAccountRemoved(string address)
}

View File

@ -0,0 +1,9 @@
// Mock of src/app/modules/main/wallet_section/buy_sell_crypto/view.nim
import QtQuick 2.15
QtObject {
readonly property string contextPropertyName: "walletSectionBuySellCrypto"
// Silence warnings
readonly property ListModel model: ListModel {}
}

View File

@ -0,0 +1,11 @@
import QtQuick 2.15
// Required mock of: src/app/modules/main/wallet_section/overview/view.nim
Item {
readonly property string contextPropertyName: "walletSectionOverview"
//
// Silence warnings
readonly property string mixedcaseAddress: ""
}

View File

@ -0,0 +1,12 @@
import QtQuick 2.15
// Required mock of: src/app/modules/main/wallet_section/send/view.nim
Item {
readonly property string contextPropertyName: "walletSectionSend"
// Silence warnings
readonly property ListModel accounts: ListModel {}
readonly property QtObject selectedReceiveAccount: QtObject {}
}

View File

@ -0,0 +1,68 @@
import QtQuick 2.15
import QtWebEngine 1.10
import QtWebChannel 1.15
// Helper to load and setup an instance of \c WebEngineView
//
// The \c webChannelObjects property is used to register specific objects
//
// Loading qrc:/StatusQ/Components/private/qwebchannel/qwebchannel.js and
// qrc:/StatusQ/Components/private/qwebchannel/helpers.js will provide
// access to window.statusq APIs used to exchange data between the internal
// web engine and the QML application
Item {
id: root
required property url url
required property var webChannelObjects
property alias active: loader.active
property alias instance: loader.item
signal engineLoaded(WebEngineView instance)
signal engineUnloaded()
signal pageLoaded()
signal pageLoadingError(string errorString)
Loader {
id: loader
active: false
onStatusChanged: function() {
if (status === Loader.Ready) {
root.engineLoaded(loader.item)
} else if (status === Loader.Null) {
root.engineUnloaded()
}
}
sourceComponent: WebEngineView {
id: webEngineView
anchors.fill: parent
visible: false
url: root.url
webChannel: statusChannel
onLoadingChanged: function(loadRequest) {
switch(loadRequest.status) {
case WebEngineView.LoadSucceededStatus:
root.pageLoaded()
break
case WebEngineView.LoadFailedStatus:
root.pageLoadingError(loadRequest.errorString)
break
}
}
WebChannel {
id: statusChannel
registeredObjects: root.webChannelObjects
}
}
}
}

View File

@ -0,0 +1,19 @@
// Helper functions for instantiating QWebChannel
// Requires loading of qwebchannel.js first
function initializeWebChannel() {
if (window.statusq && window.statusq.channel) {
console.error("WebChannel already initialized");
window.statusq.error = "WebChannel already initialized";
return;
}
window.statusq = {error: ""}
try {
window.statusq.channel = new QWebChannel(qt.webChannelTransport);
} catch (e) {
console.error("Unable to initialize WebChannel", e);
window.statusq.error = "initialize WebChannel fail: " + e.message;
}
}
initializeWebChannel();

View File

@ -67,3 +67,4 @@ StatusToastMessage 0.1 StatusToastMessage.qml
StatusToolBar 0.1 StatusToolBar.qml
StatusVideo 0.1 StatusVideo.qml
StatusWizardStepper 0.1 StatusWizardStepper.qml
WebEngineLoader 0.1 WebEngineLoader.qml

View File

@ -228,5 +228,8 @@
<file>StatusQ/Controls/StatusWarningBox.qml</file>
<file>StatusQ/Core/Utils/Subscription.qml</file>
<file>StatusQ/Core/Utils/SubscriptionBroker.qml</file>
<file>StatusQ/Components/WebEngineLoader.qml</file>
<file>StatusQ/Components/private/qwebchannel/qwebchannel.js</file>
<file>StatusQ/Components/private/qwebchannel/helpers.js</file>
</qresource>
</RCC>

View File

@ -6,8 +6,8 @@ enable_testing()
set(CMAKE_AUTOMOC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml Quick REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick REQUIRED)
find_package(QT NAMES Qt6 Qt5 COMPONENTS QuickTest Qml Quick WebChannel WebEngine REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick WebChannel WebEngine REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -31,6 +31,8 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::QuickTest
Qt${QT_VERSION_MAJOR}::Qml
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::WebChannel
Qt${QT_VERSION_MAJOR}::WebEngine
StatusQ
)

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
<script src="../../../src/StatusQ/Components/private/qwebchannel/qwebchannel.js" defer></script>
<script src="../../../src/StatusQ/Components/private/qwebchannel/helpers.js" defer></script>
</head>
<body>
<h1>Test Page</h1>
</body>
</html>

View File

@ -0,0 +1,110 @@
import QtQuick 2.15
import QtTest 1.0
import QtWebEngine 1.10
import QtWebChannel 1.15
import StatusQ 0.1 // https://github.com/status-im/status-desktop/issues/10218
import StatusQ.Components 0.1
import StatusQ.TestHelpers 0.1
TestCase {
id: root
name: "TestWebEngineLoader"
QtObject {
id: testObject
WebChannel.id: "testObject"
signal webChannelInitOk()
signal webChannelError()
function signalWebChannelInitResult(error) {
if(error) {
webChannelError()
} else {
webChannelInitOk()
}
}
}
Loader {
id: loader
active: false
sourceComponent: WebEngineLoader {
url: "./WebEngineLoader/test.html"
webChannelObjects: [testObject]
}
}
SignalSpy { id: loadedSpy; target: loader; signalName: "loaded" }
SignalSpy { id: webEngineLoadedSpy; target: loader.item; signalName: "engineLoaded" }
SignalSpy { id: pageLoadedSpy; target: loader.item; signalName: "pageLoaded" }
SignalSpy { id: engineUnloadedSpy; target: loader.item; signalName: "engineUnloaded" }
SignalSpy { id: pageLoadingErrorSpy; target: loader.item; signalName: "onPageLoadingError" }
function init() {
for (var i = 0; i < root.children.length; i++) {
const child = root.children[i]
if(child.hasOwnProperty("signalName")) {
child.clear()
}
}
loader.active = true
loadedSpy.wait(1000);
}
function cleanup() {
loader.active = false
}
function test_loadUnload() {
const webEngine = loader.item
compare(webEngine.instance, null, "By default the engine is not loaded")
webEngine.active = true
webEngineLoadedSpy.wait(1000);
verify(webEngine.instance !== null , "The WebEngineView should be available")
if (Qt.platform.os === "linux") {
skip("fails to load page on linux")
}
pageLoadedSpy.wait(1000);
webEngine.active = false
engineUnloadedSpy.wait(1000);
verify(webEngine.instance === null , "The WebEngineView should be unavailable")
}
SignalSpy { id: wcInitOkSpy; target: testObject; signalName: "webChannelInitOk" }
SignalSpy { id: wcInitErrorSpy; target: testObject; signalName: "webChannelError" }
function test_executeCode() {
if (Qt.platform.os === "linux") {
skip("fails to load page on linux")
}
const webEngine = loader.item
webEngine.active = true
pageLoadedSpy.wait(1000);
let errorResult = null
webEngine.instance.runJavaScript(`
window.testError = window.statusq.error;
try {
window.statusq.channel.objects.testObject.signalWebChannelInitResult("");
} catch (e) {
window.testError = e.message;
}
window.testError
`, function(result) {
errorResult = result
})
wcInitOkSpy.wait(1000);
compare(errorResult, "", "Expected empty error string if all good")
}
}

View File

@ -1,3 +0,0 @@
Start testing: Apr 18 18:49 CEST
----------------------------------------------------------
End testing: Apr 18 18:49 CEST

View File

@ -1,8 +1,20 @@
#include <QtQuickTest/quicktest.h>
#include <QQmlEngine>
#include <QtWebEngine>
#include "TestHelpers/MonitorQtOutput.h"
class RunBeforeQApplicationIsInitialized {
public:
RunBeforeQApplicationIsInitialized()
{
QtWebEngine::initialize();
}
};
static RunBeforeQApplicationIsInitialized runBeforeQApplicationIsInitialized;
class TestSetup : public QObject
{
Q_OBJECT

View File

@ -163,45 +163,7 @@ SettingsContentBase {
visible: root.advancedStore.isDebugEnabled
onClicked: {
wcLoader.active = true
}
Component {
id: wcDialogComponent
StatusDialog {
id: wcDialog
onOpenedChanged: {
if (!opened) {
wcLoader.active = false
}
}
WalletConnect {
SplitView.preferredWidth: 400
SplitView.preferredHeight: 600
backgroundColor: wcDialog.backgroundColor
controller: WalletStores.RootStore.walletConnectController
}
clip: true
}
}
Loader {
id: wcLoader
active: false
onStatusChanged: {
if (status === Loader.Ready) {
wcLoader.item.open()
}
}
sourceComponent: wcDialogComponent
Global.popupWalletConnect()
}
}

View File

@ -1,23 +0,0 @@
import QtQuick 2.15
QtObject {
enum RequestCodes {
SdkInitSuccess,
SdkInitError,
PairSuccess,
PairError,
ApprovePairSuccess,
ApprovePairError,
RejectPairSuccess,
RejectPairError,
AcceptSessionSuccess,
AcceptSessionError,
RejectSessionSuccess,
RejectSessionError,
GetPairings,
GetPairingsError
}
}

View File

@ -1,295 +1,32 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import AppLayouts.Wallet.stores 1.0 as WalletStores
Item {
id: root
implicitWidth: Math.min(mainLayout.implicitWidth, 400)
implicitHeight: Math.min(mainLayout.implicitHeight, 700)
required property color backgroundColor
property bool sdkReady: state === d.sdkReadyState
// wallet_connect.Controller \see wallet_section/wallet_connect/controller.nim
required property var controller
ColumnLayout {
id: mainLayout
property alias modal: modal
property alias sdk: sdk
property alias url: sdk.url
anchors.fill: parent
WalletConnectModal {
id: modal
StatusBaseText {
text: qsTr("Debugging UX until design is ready")
}
StatusInput {
id: pairLinkInput
Layout.fillWidth: true
placeholderText: "Insert pair link"
}
RowLayout {
Layout.fillWidth: true
StatusButton {
text: "Pair"
onClicked: {
statusText.text = "Pairing..."
sdkView.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && sdkView.sdkReady
}
StatusButton {
text: "Auth"
onClicked: {
statusText.text = "Authenticating..."
sdkView.auth()
}
enabled: false && pairLinkInput.text.length > 0 && sdkView.sdkReady
}
StatusButton {
text: "Accept"
onClicked: {
sdkView.approvePairSession(d.sessionProposal, d.supportedNamespaces)
}
visible: root.state === d.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
sdkView.rejectPairSession(d.sessionProposal.id)
}
visible: root.state === d.waitingPairState
}
}
ColumnLayout {
StatusBaseText {
id: statusText
text: "-"
}
StatusBaseText {
text: "Pairings"
visible: sdkView.pairingsModel.count > 0
}
StatusListView {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 200
model: sdkView.pairingsModel
delegate: StatusBaseText {
text: `${SQUtils.Utils.elideText(topic, 6, 6)} - ${new Date(expiry * 1000).toLocaleString()}`
color: active ? "green" : "orange"
}
}
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: 200
Layout.maximumHeight: 400
contentWidth: detailsText.width
contentHeight: detailsText.height
StatusBaseText {
id: detailsText
text: ""
visible: text.length > 0
color: "#FF00FF"
}
ScrollBar.vertical: ScrollBar {}
clip: true
}
RowLayout {
StatusButton {
text: "Accept"
onClicked: {
root.controller.sessionRequest(JSON.stringify(d.sessionRequest), passwordInput.text)
}
visible: root.state === d.waitingUserResponseToSessionRequest
}
StatusButton {
text: "Reject"
onClicked: {
sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, false)
}
visible: root.state === d.waitingUserResponseToSessionRequest
}
StatusInput {
id: passwordInput
text: "1234567890"
placeholderText: "Insert account password"
visible: root.state === d.waitingUserResponseToSessionRequest
}
}
ColumnLayout { /* spacer */ }
}
// Separator
ColumnLayout {}
controller: root.controller
sdk: sdk
}
WalletConnectSDK {
id: sdkView
// SDK runs fine if WebEngineView is not visible
visible: false
anchors.top: parent.bottom
anchors.left: parent.left
width: 100
height: 100
id: sdk
projectId: controller.projectId
onSdkInit: function(success, info) {
d.setDetailsText(info)
if (success) {
d.setStatusText("Ready to pair or auth")
root.state = d.sdkReadyState
} else {
d.setStatusText("SDK Error", "red")
root.state = ""
}
}
active: WalletStores.RootStore.walletSectionInst.walletReady && (controller.hasActivePairings || modal.opened)
onPairSessionProposal: function(success, sessionProposal) {
d.setDetailsText(sessionProposal)
if (success) {
d.setStatusText("Pair ID: " + sessionProposal.id + "; Topic: " + sessionProposal.params.pairingTopic)
root.controller.pairSessionProposal(JSON.stringify(sessionProposal))
// Expecting signal onProposeUserPair from controller
} else {
d.setStatusText("Pairing error", "red")
}
}
onPairAcceptedResult: function(success, result) {
d.setDetailsText(result)
if (success) {
d.setStatusText("Pairing OK")
root.state = d.pairedState
} else {
d.setStatusText("Pairing error", "red")
root.state = d.sdkReadyState
}
}
onPairRejectedResult: function(success, result) {
d.setDetailsText(result)
root.state = d.sdkReadyState
if (success) {
d.setStatusText("Pairing rejected")
} else {
d.setStatusText("Rejecting pairing error", "red")
}
}
onSessionRequestUserAnswerResult: function(accept, error) {
if (error) {
d.setStatusText(`Session Request ${accept ? "Accept" : "Reject"} error`, "red")
return
}
root.state = d.pairedState
if (accept) {
d.setStatusText(`Session Request accepted`)
} else {
d.setStatusText(`Session Request rejected`)
}
}
onSessionRequestEvent: function(sessionRequest) {
d.setStatusText("Approve session request")
d.setDetailsText(JSON.stringify(sessionRequest, null, 2))
d.sessionRequest = sessionRequest
root.state = d.waitingUserResponseToSessionRequest
}
onPairSessionProposalExpired: {
d.setStatusText(`Timeout waiting for response. Reusing URI?`, "red")
}
onStatusChanged: function(message) {
statusText.text = message
}
}
QtObject {
id: d
property var sessionProposal: null
property var supportedNamespaces: null
property var sessionRequest: null
property var signedData: null
readonly property string sdkReadyState: "sdk_ready"
readonly property string waitingPairState: "waiting_pairing"
readonly property string waitingUserResponseToSessionRequest: "waiting_user_response_to_session_request"
readonly property string pairedState: "paired"
function setStatusText(message, textColor) {
statusText.text = message
if (textColor === undefined) {
textColor = "green"
}
statusText.color = textColor
}
function setDetailsText(message) {
if (message === undefined) {
message = "undefined"
} else if (typeof message !== "string") {
message = JSON.stringify(message, null, 2)
}
detailsText.text = message
}
}
Connections {
target: root.controller
function onProposeUserPair(sessionProposalJson, supportedNamespacesJson) {
d.setStatusText("Waiting user accept")
d.sessionProposal = JSON.parse(sessionProposalJson)
d.supportedNamespaces = JSON.parse(supportedNamespacesJson)
d.setDetailsText(JSON.stringify(d.supportedNamespaces, null, 2))
root.state = d.waitingPairState
}
function onRespondSessionRequest(sessionRequestJson, signedData, error) {
console.log("@dd respondSessionRequest", sessionRequestJson, " signedData", signedData, " error: ", error)
if (error) {
d.setStatusText("Session Request error", "red")
sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)
return
}
d.sessionRequest = JSON.parse(sessionRequestJson)
d.signedData = signedData
sdkView.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData)
d.setStatusText("Session Request accepted")
d.setDetailsText(d.signedData)
onSessionRequestEvent: (details) => {
modal.openWithSessionRequestEvent(details)
}
}
}

View File

@ -0,0 +1,308 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Popups 0.1
Popup {
id: root
implicitWidth: Math.min(mainLayout.implicitWidth, 400)
implicitHeight: Math.min(mainLayout.implicitHeight, 700)
required property WalletConnectSDK sdk
parent: Overlay.overlay
anchors.centerIn: parent
clip: true
property bool sdkReady: d.state === d.sdkReadyState
// wallet_connect.Controller \see wallet_section/wallet_connect/controller.nim
required property var controller
function openWithSessionRequestEvent(sessionRequest) {
d.setStatusText("Approve session request")
d.setDetailsText(JSON.stringify(sessionRequest, null, 2))
d.sessionRequest = sessionRequest
d.state = d.waitingUserResponseToSessionRequest
root.open()
}
Flickable {
id: flickable
anchors.fill: parent
contentWidth: mainLayout.width
contentHeight: mainLayout.height
ColumnLayout {
id: mainLayout
StatusBaseText {
text: qsTr("Debugging UX until design is ready")
}
StatusInput {
id: pairLinkInput
Layout.fillWidth: true
placeholderText: "Insert pair link"
}
RowLayout {
Layout.fillWidth: true
StatusButton {
text: "Pair"
onClicked: {
statusText.text = "Pairing..."
sdk.pair(pairLinkInput.text)
}
enabled: pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Auth"
onClicked: {
statusText.text = "Authenticating..."
sdk.auth()
}
enabled: false && pairLinkInput.text.length > 0 && sdk.sdkReady
}
StatusButton {
text: "Accept"
onClicked: {
sdk.approvePairSession(d.sessionProposal, d.supportedNamespaces)
}
visible: d.state === d.waitingPairState
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectPairSession(d.sessionProposal.id)
}
visible: d.state === d.waitingPairState
}
}
ColumnLayout {
StatusBaseText {
id: statusText
text: "-"
}
StatusBaseText {
text: "Pairings"
visible: sdk.pairingsModel.count > 0
}
StatusListView {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Layout.maximumHeight: 200
model: sdk.pairingsModel
delegate: StatusBaseText {
text: `${SQUtils.Utils.elideText(topic, 6, 6)} - ${new Date(expiry * 1000).toLocaleString()}`
color: active ? "green" : "orange"
}
}
Flickable {
Layout.fillWidth: true
Layout.preferredHeight: 200
Layout.maximumHeight: 400
contentWidth: detailsText.width
contentHeight: detailsText.height
StatusBaseText {
id: detailsText
text: ""
visible: text.length > 0
color: "#FF00FF"
}
ScrollBar.vertical: ScrollBar {}
clip: true
}
RowLayout {
StatusButton {
text: "Accept"
onClicked: {
root.controller.sessionRequest(JSON.stringify(d.sessionRequest), passwordInput.text)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusButton {
text: "Reject"
onClicked: {
sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, false)
}
visible: d.state === d.waitingUserResponseToSessionRequest
}
StatusInput {
id: passwordInput
text: "1234567890"
placeholderText: "Insert account password"
visible: d.state === d.waitingUserResponseToSessionRequest
}
}
ColumnLayout { /* spacer */ }
}
// Separator
ColumnLayout {}
}
ScrollBar.vertical: ScrollBar {}
clip: true
}
Connections {
target: root.sdk
function onSdkInit(success, info) {
d.setDetailsText(info)
if (success) {
d.setStatusText("Ready to pair or auth")
d.state = d.sdkReadyState
} else {
d.setStatusText("SDK Error", "red")
d.state = ""
}
}
function onPairSessionProposal(success, sessionProposal) {
d.setDetailsText(sessionProposal)
if (success) {
d.setStatusText("Pair ID: " + sessionProposal.id + "; Topic: " + sessionProposal.params.pairingTopic)
root.controller.pairSessionProposal(JSON.stringify(sessionProposal))
// Expecting signal onProposeUserPair from controller
} else {
d.setStatusText("Pairing error", "red")
}
}
function onPairAcceptedResult(sessionProposal, success, result) {
d.setDetailsText(result)
if (success) {
d.setStatusText("Pairing OK")
d.state = d.pairedState
root.controller.recordSuccessfulPairing(JSON.stringify(sessionProposal))
} else {
d.setStatusText("Pairing error", "red")
d.state = d.sdkReadyState
}
}
function onPairRejectedResult(success, result) {
d.setDetailsText(result)
d.state = d.sdkReadyState
if (success) {
d.setStatusText("Pairing rejected")
} else {
d.setStatusText("Rejecting pairing error", "red")
}
}
function onSessionRequestUserAnswerResult(accept, error) {
if (error) {
d.setStatusText(`Session Request ${accept ? "Accept" : "Reject"} error`, "red")
return
}
d.state = d.pairedState
if (accept) {
d.setStatusText(`Session Request accepted`)
} else {
d.setStatusText(`Session Request rejected`)
}
}
function onPairSessionProposalExpired() {
d.setStatusText(`Timeout waiting for response. Reusing URI?`, "red")
}
function onStatusChanged(message) {
statusText.text = message
}
}
QtObject {
id: d
property var sessionProposal: null
property var supportedNamespaces: null
property var sessionRequest: null
property var signedData: null
property string state: ""
readonly property string sdkReadyState: "sdk_ready"
readonly property string waitingPairState: "waiting_pairing"
readonly property string waitingUserResponseToSessionRequest: "waiting_user_response_to_session_request"
readonly property string pairedState: "paired"
function setStatusText(message, textColor) {
statusText.text = message
if (textColor === undefined) {
textColor = "green"
}
statusText.color = textColor
}
function setDetailsText(message) {
if (message === undefined) {
message = "undefined"
} else if (typeof message !== "string") {
message = JSON.stringify(message, null, 2)
}
detailsText.text = message
}
}
Connections {
target: root.controller
function onProposeUserPair(sessionProposalJson, supportedNamespacesJson) {
d.setStatusText("Waiting user accept")
d.sessionProposal = JSON.parse(sessionProposalJson)
d.supportedNamespaces = JSON.parse(supportedNamespacesJson)
d.setDetailsText(JSON.stringify(d.supportedNamespaces, null, 2))
d.state = d.waitingPairState
}
function onRespondSessionRequest(sessionRequestJson, signedData, error) {
console.log("WC respondSessionRequest", sessionRequestJson, " signedData", signedData, " error: ", error)
if (error) {
d.setStatusText("Session Request error", "red")
sdk.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)
return
}
d.sessionRequest = JSON.parse(sessionRequestJson)
d.signedData = signedData
sdk.acceptSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, d.signedData)
d.state = d.pairedState
d.setStatusText("Session Request accepted")
d.setDetailsText(d.signedData)
}
}
}

View File

@ -1,10 +1,12 @@
import QtQuick 2.15
import QtWebEngine 1.10
import QtWebChannel 1.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtWebEngine 1.10
import QtWebChannel 1.15
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
Item {
id: root
@ -12,6 +14,10 @@ Item {
required property string projectId
readonly property alias sdkReady: d.sdkReady
readonly property alias pairingsModel: d.pairingsModel
readonly property alias webEngineLoader: loader
property alias active: loader.active
property alias url: loader.url
implicitWidth: 1
implicitHeight: 1
@ -20,7 +26,7 @@ Item {
signal sdkInit(bool success, var result)
signal pairSessionProposal(bool success, var sessionProposal)
signal pairSessionProposalExpired()
signal pairAcceptedResult(bool success, var sessionType)
signal pairAcceptedResult(var sessionProposal, bool success, var sessionType)
signal pairRejectedResult(bool success, var result)
signal sessionRequestEvent(var sessionRequest)
signal sessionRequestUserAnswerResult(bool accept, string error)
@ -54,6 +60,8 @@ Item {
property bool sdkReady: false
property ListModel pairingsModel: pairings
property WebEngineView engine: loader.instance
onSdkReadyChanged: {
if (sdkReady)
{
@ -61,19 +69,22 @@ Item {
}
}
function resetPairingsModel()
function resetPairingsModel(entryCallback)
{
pairings.clear();
wcCalls.getPairings((pairList) => {
for (let i = 0; i < pairList.length; i++) {
pairings.append({
active: pairList[i].active,
topic: pairList[i].topic,
expiry: pairList[i].expiry
});
}
})
for (let i = 0; i < pairList.length; i++) {
pairings.append({
active: pairList[i].active,
topic: pairList[i].topic,
expiry: pairList[i].expiry
});
if (entryCallback) {
entryCallback(pairList[i])
}
}
})
}
function getPairingTopicFromPairingUrl(url)
@ -95,11 +106,11 @@ Item {
id: wcCalls
function init() {
console.debug(`@dd WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
console.debug(`WC WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
webEngineView.runJavaScript(`wc.init("${root.projectId}")`, function(result) {
d.engine.runJavaScript(`wc.init("${root.projectId}")`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result, null, 2)}`)
if (result && !!result.error)
{
@ -109,11 +120,11 @@ Item {
}
function getPairings(callback) {
console.debug(`@dd WalletConnectSDK.wcCall.getPairings;`)
console.debug(`WC WalletConnectSDK.wcCall.getPairings;`)
webEngineView.runJavaScript(`wc.getPairings()`, function(result) {
d.engine.runJavaScript(`wc.getPairings()`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.getPairings; response: ${JSON.stringify(result, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.getPairings; response: ${JSON.stringify(result, null, 2)}`)
if (result)
{
@ -129,11 +140,11 @@ Item {
}
function pair(pairLink) {
console.debug(`@dd WalletConnectSDK.wcCall.pair; pairLink: ${pairLink}`)
console.debug(`WC WalletConnectSDK.wcCall.pair; pairLink: ${pairLink}`)
wcCalls.getPairings((allPairings) => {
console.debug(`@dd WalletConnectSDK.wcCall.pair; response: ${JSON.stringify(allPairings, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.pair; response: ${JSON.stringify(allPairings, null, 2)}`)
let pairingTopic = d.getPairingTopicFromPairingUrl(pairLink);
@ -147,7 +158,7 @@ Item {
}
}
webEngineView.runJavaScript(`wc.pair("${pairLink}")`, function(result) {
d.engine.runJavaScript(`wc.pair("${pairLink}")`, function(result) {
if (result && !!result.error)
{
console.error("pair: ", result.error)
@ -158,32 +169,38 @@ Item {
}
function approvePairSession(sessionProposal, supportedNamespaces) {
console.debug(`@dd WalletConnectSDK.wcCall.approvePairSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
console.debug(`WC WalletConnectSDK.wcCall.approvePairSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
webEngineView.runJavaScript(`wc.approvePairSession(${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)})`, function(result) {
d.engine.runJavaScript(`wc.approvePairSession(${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)})`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.approvePairSession; response: ${JSON.stringify(result, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.approvePairSession; response: ${JSON.stringify(result, null, 2)}`)
if (result) {
if (!!result.error)
{
console.error("approvePairSession: ", result.error)
root.pairAcceptedResult(false, result.error)
root.pairAcceptedResult(sessionProposal, false, result.error)
return
}
root.pairAcceptedResult(true, result.error)
// Update the temporary expiry with the one from the pairing
d.resetPairingsModel((pairing) => {
if (pairing.topic === sessionProposal.params.pairingTopic) {
sessionProposal.params.expiry = pairing.expiry
root.pairAcceptedResult(sessionProposal, true, result.error)
}
})
}
d.resetPairingsModel()
})
}
function rejectPairSession(id) {
console.debug(`@dd WalletConnectSDK.wcCall.rejectPairSession; id: ${id}`)
console.debug(`WC WalletConnectSDK.wcCall.rejectPairSession; id: ${id}`)
webEngineView.runJavaScript(`wc.rejectPairSession(${id})`, function(result) {
d.engine.runJavaScript(`wc.rejectPairSession(${id})`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.rejectPairSession; response: ${JSON.stringify(result, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.rejectPairSession; response: ${JSON.stringify(result, null, 2)}`)
d.resetPairingsModel()
if (result) {
if (!!result.error)
{
@ -193,16 +210,15 @@ Item {
}
root.pairRejectedResult(true, result.error)
}
d.resetPairingsModel()
})
}
function acceptSessionRequest(topic, id, signature) {
console.debug(`@dd WalletConnectSDK.wcCall.acceptSessionRequest; topic: "${topic}", id: ${id}, signature: "${signature}"`)
console.debug(`WC WalletConnectSDK.wcCall.acceptSessionRequest; topic: "${topic}", id: ${id}, signature: "${signature}"`)
webEngineView.runJavaScript(`wc.respondSessionRequest("${topic}", ${id}, "${signature}")`, function(result) {
d.engine.runJavaScript(`wc.respondSessionRequest("${topic}", ${id}, "${signature}")`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.acceptSessionRequest; response: ${JSON.stringify(allPairings, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.acceptSessionRequest; response: ${JSON.stringify(allPairings, null, 2)}`)
if (result) {
if (!!result.error)
@ -218,11 +234,11 @@ Item {
}
function rejectSessionRequest(topic, id, error) {
console.debug(`@dd WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
console.debug(`WC WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
webEngineView.runJavaScript(`wc.rejectSessionRequest("${topic}", ${id}, "${error}")`, function(result) {
d.engine.runJavaScript(`wc.rejectSessionRequest("${topic}", ${id}, "${error}")`, function(result) {
console.debug(`@dd WalletConnectSDK.wcCall.rejectSessionRequest; response: ${JSON.stringify(result, null, 2)}`)
console.debug(`WC WalletConnectSDK.wcCall.rejectSessionRequest; response: ${JSON.stringify(result, null, 2)}`)
if (result) {
if (!!result.error)
@ -263,54 +279,54 @@ Item {
function onSessionProposal(details)
{
console.debug(`@dd WalletConnectSDK.onSessionProposal; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC WalletConnectSDK.onSessionProposal; details: ${JSON.stringify(details, null, 2)}`)
root.pairSessionProposal(true, details)
}
function onSessionUpdate(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionUpdate; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionUpdate; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionExtend(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionExtend; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionExtend; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionPing(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionPing; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionPing; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionDelete(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionExpire(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionExpire; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionExpire; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionRequest(details)
{
console.debug(`@dd WalletConnectSDK.onSessionRequest; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC WalletConnectSDK.onSessionRequest; details: ${JSON.stringify(details, null, 2)}`)
root.sessionRequestEvent(details)
}
function onSessionRequestSent(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionRequestSent; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionRequestSent; details: ${JSON.stringify(details, null, 2)}`)
}
function onSessionEvent(details)
{
console.debug(`@dd TODO WalletConnectSDK.onSessionEvent; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC TODO WalletConnectSDK.onSessionEvent; details: ${JSON.stringify(details, null, 2)}`)
}
function onProposalExpire(details)
{
console.debug(`@dd WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details, null, 2)}`)
console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details, null, 2)}`)
root.pairSessionProposalExpired()
}
}
@ -319,36 +335,19 @@ Item {
id: pairings
}
WebChannel {
id: statusChannel
registeredObjects: [statusObject]
}
WebEngineView {
id: webEngineView
WebEngineLoader {
id: loader
anchors.fill: parent
Component.onCompleted: {
console.debug(`@dd WalletConnectSDK.WebEngineView.onCompleted; url: ${url}; debug? ${SQUtils.isDebug()};`)
}
url: "qrc:/app/AppLayouts/Wallet/views/walletconnect/sdk/src/index.html"
webChannel: statusChannel
webChannelObjects: [ statusObject ]
onLoadingChanged: function(loadRequest) {
console.debug(`@dd WalletConnectSDK.onLoadingChanged; status: ${loadRequest.status}; error: ${loadRequest.errorString}`)
switch(loadRequest.status) {
case WebEngineView.LoadSucceededStatus:
wcCalls.init()
break
case WebEngineView.LoadFailedStatus:
root.statusChanged(`<font color="red">Failed loading SDK JS code; error: "${loadRequest.errorString}"</font>`)
break
case WebEngineView.LoadStartedStatus:
root.statusChanged(`<font color="blue">Loading SDK JS code</font>`)
break
}
onPageLoaded: function() {
wcCalls.init()
}
onPageLoadingError: function(error) {
console.error("WebEngineLoader.onPageLoadingError: ", error)
}
}
}

View File

@ -1 +1,3 @@
WalletConnect 1.0 WalletConnect.qml
WalletConnect 1.0 WalletConnect.qml
WalletConnectModal 1.0 WalletConnectModal.qml
WalletConnectSDK 1.0 WalletConnectSDK.qml

View File

@ -28,33 +28,6 @@ Install dependencies steps by executing commands in this directory:
Use the web demo test client https://react-app.walletconnect.com/ for wallet pairing and https://react-auth-dapp.walletconnect.com/ for authentication
## Dev - to be removed
To test SDK loading add the following to `ui/app/mainui/AppMain.qml`
```qml
import AppLayouts.Wallet.stores 1.0 as WalletStores
import AppLayouts.Wallet.views.walletconnect 1.0
// ...
StatusDialog {
id: wcHelperDialog
visible: true
WalletConnect {
SplitView.preferredWidth: 400
SplitView.preferredHeight: 600
backgroundColor: wcHelperDialog.backgroundColor
controller: WalletStores.RootStore.walletConnectController
}
clip: true
}
```
## Log
Initial setup

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<script src="qrc:/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js"></script>
<script src="qrc:/StatusQ/Components/private/qwebchannel/qwebchannel.js" defer></script>
<script src="qrc:/StatusQ/Components/private/qwebchannel/helpers.js" defer></script>
<script src="qrc:/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js" defer></script>
</head>
<body>
</body>

View File

@ -3,8 +3,6 @@ import { Web3Wallet } from "@walletconnect/web3wallet";
import AuthClient from '@walletconnect/auth-client'
import { QWebChannel } from './qwebchannel';
// import the builder util
import { buildApprovedNamespaces, getSdkError } from "@walletconnect/utils";
import { formatJsonRpcResult, formatJsonRpcError } from "@walletconnect/jsonrpc-utils";
@ -17,11 +15,20 @@ window.wc = {
init: function (projectId) {
(async () => {
try {
await createWebChannel();
} catch (error) {
wc.statusObject.sdkInitialized(error);
return
if (!window.statusq) {
console.error('missing window.statusq! Forgot to execute "ui/StatusQ/src/StatusQ/Components/private/qwebchannel/helpers.js" first?');
return;
}
if (window.statusq.error) {
console.error("Failed initializing WebChannel: " + window.statusq.error);
return;
}
wc.statusObject = window.statusq.channel.objects.statusObject;
if (!wc.statusObject) {
console.error("Failed initializing WebChannel or initialization not run");
return;
}
window.wc.core = new Core({
@ -208,18 +215,3 @@ window.wc = {
};
},
};
function createWebChannel(projectId) {
return new Promise((resolve, reject) => {
window.wc.channel = new QWebChannel(qt.webChannelTransport, function (channel) {
let statusObject = channel.objects.statusObject;
if (!statusObject) {
reject(new Error("Unable to resolve statusObject"));
} else {
window.wc.statusObject = statusObject;
resolve();
}
});
});
}

View File

@ -40,6 +40,7 @@ import AppLayouts.stores 1.0
import AppLayouts.Chat.stores 1.0 as ChatStores
import AppLayouts.Communities.stores 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
import AppLayouts.Wallet.views.walletconnect 1.0
import mainui.activitycenter.stores 1.0
import mainui.activitycenter.popups 1.0
@ -1674,4 +1675,20 @@ Item {
onClosed: userAgreementLoader.active = false
}
}
WalletConnect {
id: walletConnect
anchors.top: parent.bottom
width: 100
height: 100
controller: WalletStore.RootStore.walletConnectController
Connections {
target: Global
function onPopupWalletConnect() {
walletConnect.modal.open()
}
}
}
}

View File

@ -79,6 +79,8 @@ QtObject {
signal openTestnetPopup()
signal popupWalletConnect()
function openProfilePopup(publicKey, parentPopup, cb) {
root.openProfilePopupRequested(publicKey, parentPopup, cb)
}

View File

@ -22,6 +22,7 @@ continueUserActivity:(NSUserActivity *)userActivity
if (!url)
return FALSE;
QUrl deeplink = QUrl::fromNSURL(url);
// TODO #12434: Check if WalletConnect link and redirect the workflow to Pair or Authenticate
// TODO #12245: set it to nim