feat(wallet) quick show on how to make WebView-QML bridge robust

Updates: #12639
This commit is contained in:
Stefan 2023-11-15 23:26:12 +02:00
parent 783a755230
commit 1c2c95288a
15 changed files with 535 additions and 300 deletions

View File

@ -312,6 +312,7 @@ statusq-tests:
-DSTATUSQ_BUILD_SANDBOX=OFF \
-DSTATUSQ_BUILD_SANITY_CHECKER=OFF \
-DSTATUSQ_BUILD_TESTS=ON \
-DSTATUSQ_SHADOW_BUILD=OFF \
-B $(STATUSQ_BUILD_PATH) \
-S $(STATUSQ_SOURCE_PATH) \
$(HANDLE_OUTPUT)

View File

@ -0,0 +1,191 @@
import QtQuick 2.15
import QtWebView 1.15
// Specialization of \c WebView that provides a bridge between QML and JS code in the web page
// The bridge uses a polling mechanism for handing async responses
// TODO: stop timer when there is nothing to poll to
// TODO: simpler events
WebView {
id: root
// object name under the window object that will be used to cache internal runtime state
property string globalObjectName: "statusq"
signal contentReady();
signal contentFailedLoading(string errorString);
function asyncCall(callName, paramsStr) {
return d.call(callName, paramsStr, d.callTypeAsync)
}
function call(callName, paramsStr) {
return d.call(callName, paramsStr, d.callTypeSync)
}
// callback of type (result, error) => {}
function onCallback(callName, callback) {
d.call(callName, null, d.callTypeCallback).then(function(result) {
callback(result, false)
}).error(function(error) {
callback(error, true)
});
}
Timer {
id: timer
interval: 100
repeat: true
running: false
onTriggered: {
root.runJavaScript(`${d.ctx}.popCalls = ${d.ctx}.calls; ${d.ctx}.calls = null; ${d.ctx}.popCalls`, function(results) {
if (!results) {
return;
}
d.pendingResults = d.pendingResults.concat(results);
d.processPendingResults();
});
}
}
QtObject {
id: d
readonly property int successState: 1
readonly property int errorState: 2
readonly property int exceptionState: 3
readonly property int callTypeAsync: 1
readonly property int callTypeSync: 2
readonly property int callTypeCallback: 3
readonly property string ctx: `window.${root.globalObjectName}`
property int nextCallIndex: 0
property var callbacks: ({})
property var pendingResults: []
function call(callName, paramsStr, callType) {
const currentCallIndex = d.nextCallIndex++;
var jsCode = `
if (!${d.ctx}) {
${d.ctx} = {};
}
function reportCallResult(callIndex, state, result) {
if (!${d.ctx}.calls) {
${d.ctx}.calls = [];
}
const callRes = {state: state, result: result, callIndex: callIndex};
${d.ctx}.calls.push(callRes);
}
try {
switch(${callType}) {
case ${d.callTypeAsync}:
${callName}(${paramsStr}).then((callRes) => {
reportCallResult(${currentCallIndex}, ${d.successState}, callRes);
}).catch((error) => {
reportCallResult(${currentCallIndex}, ${d.errorState}, error);
});
break;
case ${d.callTypeSync}:
const callRes = ${callName}(${paramsStr});
reportCallResult(${currentCallIndex}, ${d.successState}, callRes);
break;
case ${d.callTypeCallback}:
${callName}(${paramsStr}, function(callRes) {
reportCallResult(${currentCallIndex}, ${d.successState}, callRes);
});
}
} catch (e) {
${d.ctx}.errorRes = {state: ${d.exceptionState}, result: e.message, callIndex: ${currentCallIndex}};
}
${d.ctx}.errorRes
`;
let promise = promiseComponent.createObject(null, {callIndex: currentCallIndex})
root.runJavaScript(jsCode, function(result) {
d.callbacks[currentCallIndex] = promise;
if (!result) {
timer.restart();
return;
}
// Process it now
d.pendingResults = d.pendingResults.concat(result);
d.processPendingResults();
});
return promise;
}
function processPendingResults() {
while(pendingResults.length != 0) {
const res = pendingResults[0]
if(d.callbacks[res.callIndex]) {
const callback = d.callbacks[res.callIndex]
if (res.state === d.successState && callback.hasSuccess()) {
callback.successCallback(res.result)
} else if (res.state !== d.successState && callback.hasError()) {
callback.errorCallback(res.result)
} else {
callback.result = res
}
d.callbacks[res.callIndex] = null
}
pendingResults.splice(0, 1)
}
}
}
Component {
id: promiseComponent
QtObject {
id: callbackObj
function then(callback) {
successCallback = callback;
// If the callback is set after the result is available
if (result && result.state === d.successState) {
successCallback(result.result);
}
return this;
}
function error(callback) {
errorCallback = callback;
// If the callback is set after the result is available
if (result && result.state !== d.successState) {
successCallback(result.result);
}
return this;
}
function hasSuccess() {
return successCallback !== null
}
function hasError() {
return errorCallback !== null
}
property var successCallback: null
property var errorCallback: null
property int callIndex: -1
property var result: null
}
}
onLoadingChanged: function(loadRequest) {
switch(loadRequest.status) {
case WebView.LoadSucceededStatus:
root.contentReady();
break
case WebView.LoadFailedStatus:
root.contentFailedLoading(loadRequest.errorString);
break
}
}
}

View File

@ -66,3 +66,4 @@ StatusToastMessage 0.1 StatusToastMessage.qml
StatusToolBar 0.1 StatusToolBar.qml
StatusVideo 0.1 StatusVideo.qml
StatusWizardStepper 0.1 StatusWizardStepper.qml
StatusWebView 0.1 StatusWebView.qml

View File

@ -60,6 +60,7 @@
<file>StatusQ/Components/StatusVideo.qml</file>
<file>StatusQ/Components/StatusWizardStepper.qml</file>
<file>StatusQ/Components/StatusSortableColumnHeader.qml</file>
<file>StatusQ/Components/StatusWebView.qml</file>
<file>StatusQ/Controls/Validators/qmldir</file>
<file>StatusQ/Controls/Validators/StatusAddressOrEnsValidator.qml</file>
<file>StatusQ/Controls/Validators/StatusAddressValidator.qml</file>

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 WebView REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick WebView REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -34,6 +34,7 @@ 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}::WebView
)
target_compile_definitions(${PROJECT_NAME} PRIVATE

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
<script>
window.asyncFunction = function (param) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(param), 10);
});
}
window.asyncRejectFunction = function(param) {
return new Promise((_, reject) => {
setTimeout(() => reject(param), 1);
});
}
window.syncFunction = function(param) {
return param;
}
window.syncFunction = function(param) {
return param;
}
window.syncFunctionThrows = function(param) {
throw new Error("Test exception");
return param;
}
window.sendEvent = function(param) {
document.dispatchEvent(new CustomEvent("testEvent", { detail: param }));
}
</script>
</head>
<body>
<h1>Test Page</h1>
</body>
</html>

View File

@ -0,0 +1,210 @@
import QtQuick 2.15
import QtTest 1.0
//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: "StatusWebView"
Component {
id: webViewComponent
StatusWebView {
id: webView
url: "StatusWebView/test.html"
anchors.fill: parent
}
}
SignalSpy {
id: spyLoaded
target: webView
signalName: "contentReady"
}
Component {
id: promiseResultComponent
QtObject {
property var result: null
property bool success: false
property bool completed: false
}
}
property StatusWebView webView: null
function init() {
webView = webViewComponent.createObject(root);
}
function cleanup() {
webView.destroy();
}
function test_asyncFunction() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.asyncCall("window.asyncFunction", "'asyncFunctionParam'");
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true;
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
});
tryCompare(callbackInfo, "completed", true, 1000, "The promise should complete");
compare(callbackInfo.result, "asyncFunctionParam", "The promise should return the success result")
compare(callbackInfo.success, true, "The promise should succeed")
}
function test_rejectAsyncFunction() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.asyncCall("window.asyncRejectFunction", "'asyncRejectFunctionParam'");
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true;
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
});
tryCompare(callbackInfo, "completed", true, 1000, "The promise should complete");
compare(callbackInfo.result, "asyncRejectFunctionParam", "The promise should return the success result")
compare(callbackInfo.success, false, "The promise should fail")
}
function test_asyncCallMissing() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.asyncCall("window.missingFunction", "'exceptionAsyncFunctionParam'");
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true;
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
});
tryCompare(callbackInfo, "completed", true, 1000, "The promise should complete");
compare(callbackInfo.result, "window.missingFunction is not a function", "The promise should return a specific error message")
compare(callbackInfo.success, false, "The promise should fail")
}
function test_registerLate() {
spyLoaded.wait(1000);
let asyncFinalized = {done: false}
var promise = webView.call("window.syncFunction", "'syncFunctionParam'");
var asyncPromise = webView.asyncCall("window.asyncFunction", "'asyncFunctionParam'");
asyncPromise.then(function(result) {
asyncFinalized.done = true;
});
tryCompare(asyncFinalized, "done", true, 1000, "The promise should complete");
const callbackInfo = promiseResultComponent.createObject(null);
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
})
compare(callbackInfo.completed, true, "The synchronous promise should complete serially");
compare(callbackInfo.result, "syncFunctionParam", ".then should report the passed param")
compare(callbackInfo.success, true, "The promise should succeed")
}
function test_synchronousCall() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.call("window.syncFunction", "'syncFunctionParam'");
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
})
tryCompare(callbackInfo, "completed", true, 1000, "The synchronous promise should complete");
compare(callbackInfo.result, "syncFunctionParam", ".then should report the passed param")
compare(callbackInfo.success, true, "The promise should succeed")
}
function test_synchronousCallThrows() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.call("window.syncFunctionThrows", "'syncFunctionThrowsParam'");
promise.then(function(result) {
callbackInfo.result = result;
callbackInfo.completed = true;
callbackInfo.success = true
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
})
tryCompare(callbackInfo, "completed", true, 1000, "The synchronous promise should complete");
compare(callbackInfo.result, "Test exception", ".error should report a specific exception message")
compare(callbackInfo.success, false, "The promise should succeed")
}
function test_synchronousCallMissing() {
spyLoaded.wait(1000);
const callbackInfo = promiseResultComponent.createObject(null);
var promise = webView.call("window.missingFunction", "'missingFunctionParam'");
promise.then(function(result) {
callbackInfo.completed = true;
callbackInfo.success = true
}).error(function(error) {
callbackInfo.result = error;
callbackInfo.completed = true;
callbackInfo.success = false;
})
tryCompare(callbackInfo, "completed", true, 1000, "The synchronous promise should complete");
compare(callbackInfo.success, false, "The promise should succeed")
compare(callbackInfo.result, "window.missingFunction is not a function", ".error should report a specific error message")
}
// function test_onCallback() {
// spyLoaded.wait(1000);
// const callbackInfo = promiseResultComponent.createObject(null);
// let testInfo = {counter: 0}
// webView.onCallback("document.addEventListener", `"testEvent"`, function(payload, error) {
// compare(payload, "testDetail", "The payload should be passed to the callback")
// testInfo.counter++;
// })
// webView.call("window.sendEvent", "'testDetail'");
// tryCompare(testInfo, "counter", 2, 1000, "Expects a specific number of calls");
// }
}

View File

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

View File

@ -3,6 +3,18 @@
#include "TestHelpers/MonitorQtOutput.h"
#include <QtWebView>
class RunBeforeQApplicationIsInitialized {
public:
RunBeforeQApplicationIsInitialized()
{
QtWebView::initialize();
}
};
static RunBeforeQApplicationIsInitialized runBeforeQApplicationIsInitialized;
class TestSetup : public QObject
{
Q_OBJECT

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

@ -148,46 +148,6 @@ Item {
ColumnLayout { /* spacer */ }
}
// TODO: DEBUG JS Loading in DMG
// RowLayout {
// TextField {
// id: urlInput
// Layout.fillWidth: true
// placeholderText: "Insert URL here"
// }
// Button {
// text: "Set URL"
// onClicked: {
// d.sdkView.url = urlInput.text
// }
// }
// }
// Button {
// text: "Set HTML"
// onClicked: {
// d.sdkView.loadHtml(htmlContent.text, "http://status.im")
// }
// }
// StatusInput {
// id: htmlContent
// Layout.fillWidth: true
// Layout.minimumHeight: 200
// Layout.maximumHeight: 300
// text: `<!DOCTYPE html><html><head><title>TODO: Test</title>\n<!--<script src="http://127.0.0.1:8080/bundle.js" defer></script>-->\n<script type='text/javascript'>\n console.log("@dd loaded dummy script!")\n</script>\n</head><body style='background-color: ${root.backgroundColor.toString()};'></body></html>`
// multiline: true
// minimumHeight: Layout.minimumHeight
// maximumHeight: Layout.maximumHeight
// }
// END DEBUGGING
// Separator
ColumnLayout {}
@ -332,7 +292,6 @@ Item {
target: root.controller
function onRespondSessionRequest(sessionRequestJson, signedJson, error) {
console.log("@dd respondSessionRequest", sessionRequestJson, signedJson, error)
if (error) {
d.setStatusText("Session Request error", "red")
d.sdkView.rejectSessionRequest(d.sessionRequest.topic, d.sessionRequest.id, true)

View File

@ -1,19 +1,16 @@
import QtQuick 2.15
import QtWebView 1.15
// TODO #12434: remove debugging WebEngineView code
// import QtWebEngine 1.10
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
// Control used to instantiate and run the the WalletConnect web SDK
// The view is not used to draw anything, but has to be visible to be able to run JS code
// Use the \c backgroundColor property to blend in with the background
// \warning A too smaller height might cause rendering errors
// TODO #12434: remove debugging WebEngineView code
WebView {
//WebEngineView {
StatusWebView {
id: root
implicitWidth: 1
@ -33,59 +30,68 @@ WebView {
signal sessionRequestUserAnswerResult(bool accept, string error)
signal responseTimeout()
// TODO: proper report
signal statusChanged(string message)
function pair(pairLink) {
let callStr = d.generateSdkCall("pair", `"${pairLink}"`, RequestCodes.PairSuccess, RequestCodes.PairError)
d.requestSdkAsync(callStr)
root.asyncCall("wc.pair", `"${pairLink}"`).then((result) => {
root.pairSessionProposal(true, result)
d.getPairings()
}).error((error) => {
root.pairSessionProposal(false, error)
})
}
function approvePairSession(sessionProposal, supportedNamespaces) {
let callStr = d.generateSdkCall("approvePairSession", `${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)}`, RequestCodes.ApprovePairSuccess, RequestCodes.ApprovePairSuccess)
d.requestSdkAsync(callStr)
root.asyncCall("wc.approvePairSession", `${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)}`).then((result) => {
root.pairAcceptedResult(true, "")
d.getPairings()
}).error((error) => {
root.pairAcceptedResult(false, error)
d.getPairings()
})
}
function rejectPairSession(id) {
let callStr = d.generateSdkCall("rejectPairSession", id, RequestCodes.RejectPairSuccess, RequestCodes.RejectPairError)
d.requestSdkAsync(callStr)
root.asyncCall("wc.rejectPairSession", id).then((result) => {
root.pairRejectedResult(true, "")
}).error((error) => {
root.pairRejectedResult(false, error)
})
}
function acceptSessionRequest(topic, id, signature) {
let callStr = d.generateSdkCall("respondSessionRequest", `"${topic}", ${id}, "${signature}"`, RequestCodes.AcceptSessionSuccess, RequestCodes.AcceptSessionError)
d.requestSdkAsync(callStr)
root.asyncCall("wc.respondSessionRequest", `"${topic}", ${id}, "${signature}"`).then((result) => {
root.sessionRequestUserAnswerResult(true, "")
}).error((error) => {
root.sessionRequestUserAnswerResult(true, error)
})
}
function rejectSessionRequest(topic, id, error) {
let callStr = d.generateSdkCall("rejectSessionRequest", `"${topic}", ${id}, ${error}`, RequestCodes.RejectSessionSuccess, RequestCodes.RejectSessionError)
d.requestSdkAsync(callStr)
root.asyncCall("wc.rejectSessionRequest", `"${topic}", ${id}, ${error}`).then((result) => {
root.sessionRequestUserAnswerResult(false, "")
}).error((error) => {
root.sessionRequestUserAnswerResult(false, error)
})
}
// TODO #12434: remove debugging WebEngineView code
onLoadingChanged: function(loadRequest) {
console.debug(`@dd WalletConnectSDK.onLoadingChanged; status: ${loadRequest.status}; error: ${loadRequest.errorString}`)
switch(loadRequest.status) {
case WebView.LoadSucceededStatus:
// case WebEngineView.LoadSucceededStatus:
d.init(root.projectId)
break
case WebView.LoadFailedStatus:
// case WebEngineView.LoadFailedStatus:
root.statusChanged(`<font color="red">Failed loading SDK JS code; error: "${loadRequest.errorString}"</font>`)
break
case WebView.LoadStartedStatus:
// case WebEngineView.LoadStartedStatus:
root.statusChanged(`<font color="blue">Loading SDK JS code</font>`)
break
}
onContentReady: {
root.asyncCall("wc.init", `"${projectId}"`).then((result) => {
d.sdkReady = true
root.sdkInit(true, "")
d.startListeningForEvents()
d.getPairings()
}).error((error) => {
d.sdkReady = false
root.sdkInit(false, error)
})
}
onContentFailedLoading: (errorString) => {
root.statusChanged(`<font color="red">Failed loading SDK JS code; error: "${errorString}"</font>`)
}
Component.onCompleted: {
console.debug(`@dd WalletConnectSDK onCompleted`)
var scriptSrc = SQUtils.StringUtils.readTextFile(":/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
// Load bundle from disk if not found in resources (Storybook)
if (scriptSrc === "") {
@ -96,142 +102,11 @@ WebView {
}
}
let htmlSrc = `<!DOCTYPE html><html><head><!--<title>TODO: Test</title>--><script type='text/javascript'>${scriptSrc}</script></head><body style='background-color: ${root.backgroundColor.toString()};'></body></html>`
let htmlSrc = `<!DOCTYPE html><html><head><script type='text/javascript'>${scriptSrc}</script></head><body style='background-color: ${root.backgroundColor.toString()};'></body></html>`
console.debug(`@dd WalletConnectSDK.loadHtml; htmlSrc len: ${htmlSrc.length}`)
root.loadHtml(htmlSrc, "https://status.app")
}
Timer {
id: timer
interval: 100
repeat: true
running: false
triggeredOnStart: true
property int errorCount: 0
onTriggered: {
root.runJavaScript(
"wcResult",
function(wcResult) {
if (!wcResult) {
return
}
let done = false
if (wcResult.error) {
console.debug(`WC JS error response - ${JSON.stringify(wcResult)}`)
done = true
if (!d.sdkReady) {
root.statusChanged(`<font color="red">[${timer.errorCount++}] Failed SDK init; error: ${wcResult.error}</font>`)
} else {
root.statusChanged(`<font color="red">[${timer.errorCount++}] Operation error: ${wcResult.error}</font>`)
}
}
if (wcResult.state !== undefined) {
switch (wcResult.state) {
case RequestCodes.SdkInitSuccess:
d.sdkReady = true
root.sdkInit(true, "")
d.startListeningForEvents()
break
case RequestCodes.SdkInitError:
d.sdkReady = false
root.sdkInit(false, wcResult.error)
break
case RequestCodes.PairSuccess:
root.pairSessionProposal(true, wcResult.result)
d.getPairings()
break
case RequestCodes.PairError:
root.pairSessionProposal(false, wcResult.error)
break
case RequestCodes.ApprovePairSuccess:
root.pairAcceptedResult(true, "")
d.getPairings()
break
case RequestCodes.ApprovePairError:
root.pairAcceptedResult(false, wcResult.error)
d.getPairings()
break
case RequestCodes.RejectPairSuccess:
root.pairRejectedResult(true, "")
break
case RequestCodes.RejectPairError:
root.pairRejectedResult(false, wcResult.error)
break
case RequestCodes.AcceptSessionSuccess:
root.sessionRequestUserAnswerResult(true, "")
break
case RequestCodes.AcceptSessionError:
root.sessionRequestUserAnswerResult(true, wcResult.error)
break
case RequestCodes.RejectSessionSuccess:
root.sessionRequestUserAnswerResult(false, "")
break
case RequestCodes.RejectSessionError:
root.sessionRequestUserAnswerResult(false, wcResult.error)
break
case RequestCodes.GetPairings:
d.populatePairingsModel(wcResult.result)
break
case RequestCodes.GetPairingsError:
console.error(`WalletConnectSDK - getPairings error: ${wcResult.error}`)
break
default: {
root.statusChanged(`<font color="red">[${timer.errorCount++}] Unknown state: ${wcResult.state}</font>`)
}
}
done = true
}
if (done) {
timer.stop()
}
}
)
}
}
Timer {
id: responseTimeoutTimer
interval: 10000
repeat: false
running: timer.running
onTriggered: {
timer.stop()
root.responseTimeout()
}
}
Timer {
id: eventsTimer
interval: 100
repeat: true
running: false
onTriggered: {
root.runJavaScript("window.wcEventResult ? window.wcEventResult.shift() : null", function(event) {
if (event) {
switch(event.name) {
case "session_request":
root.sessionRequestEvent(event.payload)
break
default:
console.error("WC unknown event type: ", event.type)
break
}
}
})
}
}
QtObject {
id: d
@ -242,12 +117,6 @@ WebView {
property ListModel pairingsModel: pairings
onSdkReadyChanged: {
if (sdkReady) {
d.getPairings()
}
}
function populatePairingsModel(pairList) {
pairings.clear();
for (let i = 0; i < pairList.length; i++) {
@ -259,31 +128,6 @@ WebView {
}
}
function isWaitingForSdk() {
return timer.running
}
function generateSdkCall(methodName, paramsStr, successState, errorState) {
return "wcResult = {}; try { wc." + methodName + "(" + paramsStr + ").then((callRes) => { wcResult = {state: " + successState + ", error: null, result: callRes}; }).catch((error) => { wcResult = {state: " + errorState + ", error: error}; }); } catch (e) { wcResult = {state: " + errorState + ", error: \"Exception: \" + e.message}; }; wcResult"
}
function requestSdkAsync(jsCode) {
root.runJavaScript(jsCode,
function(result) {
timer.restart()
}
)
}
function requestSdk(methodName, paramsStr, successState, errorState) {
const jsCode = "wcResult = {}; try { const callRes = wc." + methodName + "(" + (paramsStr ? (paramsStr) : "") + "); wcResult = {state: " + successState + ", error: null, result: callRes}; } catch (e) { wcResult = {state: " + errorState + ", error: \"Exception: \" + e.message}; }; wcResult"
root.runJavaScript(jsCode,
function(result) {
timer.restart()
}
)
}
function startListeningForEvents() {
const jsCode = "
try {
@ -308,15 +152,14 @@ WebView {
}
}
)
eventsTimer.start()
}
function init(projectId) {
d.requestSdkAsync(generateSdkCall("init", `"${projectId}"`, RequestCodes.SdkInitSuccess, RequestCodes.SdkInitError))
}
function getPairings(projectId) {
d.requestSdk("getPairings", `null`, RequestCodes.GetPairings, RequestCodes.GetPairingsError)
root.call("wc.getPairings", "").then((result) => {
d.populatePairingsModel(result)
}).error((error) => {
console.error(`WalletConnectSDK - getPairings error: ${error}`)
})
}
}

View File

@ -6,16 +6,7 @@
### Design questions
- [ ] Do we report all chains and all accounts combination or let user select?
- Wallet Connect require to report all chainIDs that were requested
- Show error to user workflow.
- [ ] Can't respond to sign messages if the wallet-connect dialog/view is closed (app is minimized)
- Only apps that use deep links are expected to work seamlessly
- [ ] Do we report **disabled chains**? **Update session** in case of enabled/disabled chains?
- [ ] Allow user to **disconnect session**? Manage sessions?
- [ ] Support update session if one account is added/removed?
- [ ] User awareness of session expiration?
- Support extend session?
- [ ] User error workflow: retry?
- [ ] Check the `Auth` request for verifyContext <https://docs.walletconnect.com/web3wallet/verify>
- [ ] What `description` and `icons` to use for the app? See `metadata` parameter in `Web3Wallet.init` call
@ -37,18 +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
## Log
Initial setup
```sh
npm init -y
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save @walletconnect/web3wallet
npm run build
# npm run build:dev # for development
```
## Dev - to be removed
To test SDK loading add the following to `ui/app/mainui/AppMain.qml`
@ -75,3 +54,29 @@ StatusDialog {
clip: true
}
```
## Log
Initial setup
```sh
npm init -y
npm install --save-dev webpack webpack-cli webpack-dev-server
npm install --save @walletconnect/web3wallet
npm run build
# npm run build:dev # for development
```
- [x] Do we report all chains and all accounts combination or let user select?
- Wallet Connect require to report all chainIDs that were requested
- Answer: We only report the available chains for the current account. We will look into adding others to he same session instead of requiring a new link
- [x] Can't respond to sign messages if the wallet-connect dialog/view is closed (app is minimized)
- Only apps that use deep links are expected to work seamlessly
- Also the main workflow will be driven by user
- [x] Allow user to **disconnect session**? Manage sessions?
- Yes, in settings
- [x] Support update session if one account is added/removed?
- Not at first
- [X] User awareness of session expiration?
- Support extend session?
- Yes

View File

@ -22,8 +22,7 @@ continueUserActivity:(NSUserActivity *)userActivity
if (!url)
return FALSE;
QUrl deeplink = QUrl::fromNSURL(url);
// TODO #12434: Check if WalletConnect link and redirect the workflow
qDebug() << "@dd deeplink " << deeplink;
// TODO #12434: Check if WalletConnect link and redirect the workflow
// TODO #12245: set it to nim
return TRUE;