chore(@desktop/walletconnect): communication between qt desktop app and loaded wallet connect sdk is made via web channel
This commit is contained in:
parent
98207381da
commit
48c9e372dc
2
Makefile
2
Makefile
|
@ -186,7 +186,7 @@ ifneq ($(detected_OS),Windows)
|
|||
endif
|
||||
DOTHERSIDE_LIBFILE := vendor/DOtherSide/build/lib/libDOtherSideStatic.a
|
||||
# order matters here, due to "-Wl,-as-needed"
|
||||
NIM_PARAMS += --passL:"$(DOTHERSIDE_LIBFILE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia Qt5WebView)"
|
||||
NIM_PARAMS += --passL:"$(DOTHERSIDE_LIBFILE)" --passL:"$(shell PKG_CONFIG_PATH="$(QT5_PCFILEDIR)" pkg-config --libs Qt5Core Qt5Qml Qt5Gui Qt5Quick Qt5QuickControls2 Qt5Widgets Qt5Svg Qt5Multimedia Qt5WebView Qt5WebChannel)"
|
||||
else
|
||||
NIM_EXTRA_PARAMS := --passL:"-lsetupapi -lhid"
|
||||
endif
|
||||
|
|
|
@ -17,11 +17,6 @@ Item {
|
|||
|
||||
property bool sdkReady: state === d.sdkReadyState
|
||||
|
||||
function setUriAndPair(wcUri) {
|
||||
pairLinkInput.text = wcUri
|
||||
d.sdkView.pair(wcUri)
|
||||
}
|
||||
|
||||
// wallet_connect.Controller \see wallet_section/wallet_connect/controller.nim
|
||||
required property var controller
|
||||
|
||||
|
@ -205,7 +200,6 @@ Item {
|
|||
|
||||
component SdkViewComponent: WalletConnectSDK {
|
||||
projectId: controller.projectId
|
||||
backgroundColor: root.backgroundColor
|
||||
|
||||
onSdkInit: function(success, info) {
|
||||
d.setDetailsText(info)
|
||||
|
@ -270,14 +264,13 @@ Item {
|
|||
root.state = d.waitingUserResponseToSessionRequest
|
||||
}
|
||||
|
||||
onResponseTimeout: {
|
||||
onPairSessionProposalExpired: {
|
||||
d.setStatusText(`Timeout waiting for response. Reusing URI?`, "red")
|
||||
}
|
||||
|
||||
onStatusChanged: function(message) {
|
||||
statusText.text = message
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QtObject {
|
||||
|
@ -326,10 +319,6 @@ Item {
|
|||
|
||||
root.state = d.waitingPairState
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.controller
|
||||
|
||||
function onRespondSessionRequest(sessionRequestJson, signedJson, error) {
|
||||
console.log("@dd respondSessionRequest", sessionRequestJson, signedJson, error)
|
||||
|
|
|
@ -1,326 +1,338 @@
|
|||
import QtQuick 2.15
|
||||
import QtWebView 1.15
|
||||
// TODO #12434: remove debugging WebEngineView code
|
||||
// import QtWebEngine 1.10
|
||||
import QtWebEngine 1.10
|
||||
import QtWebChannel 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
// 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 {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property string projectId
|
||||
readonly property alias sdkReady: d.sdkReady
|
||||
readonly property alias pairingsModel: d.pairingsModel
|
||||
|
||||
implicitWidth: 1
|
||||
implicitHeight: 1
|
||||
|
||||
required property string projectId
|
||||
required property color backgroundColor
|
||||
|
||||
readonly property alias sdkReady: d.sdkReady
|
||||
readonly property alias pairingsModel: d.pairingsModel
|
||||
|
||||
signal statusChanged(string message)
|
||||
signal sdkInit(bool success, var result)
|
||||
signal pairSessionProposal(bool success, var sessionProposal)
|
||||
signal pairSessionProposalExpired()
|
||||
signal pairAcceptedResult(bool success, var sessionType)
|
||||
signal pairRejectedResult(bool success, var result)
|
||||
signal sessionRequestEvent(var sessionRequest)
|
||||
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)
|
||||
function pair(pairLink)
|
||||
{
|
||||
wcCalls.pair(pairLink)
|
||||
}
|
||||
|
||||
function approvePairSession(sessionProposal, supportedNamespaces) {
|
||||
let callStr = d.generateSdkCall("approvePairSession", `${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)}`, RequestCodes.ApprovePairSuccess, RequestCodes.ApprovePairSuccess)
|
||||
|
||||
d.requestSdkAsync(callStr)
|
||||
function approvePairSession(sessionProposal, supportedNamespaces)
|
||||
{
|
||||
wcCalls.approvePairSession(sessionProposal, supportedNamespaces)
|
||||
}
|
||||
|
||||
function rejectPairSession(id) {
|
||||
let callStr = d.generateSdkCall("rejectPairSession", id, RequestCodes.RejectPairSuccess, RequestCodes.RejectPairError)
|
||||
|
||||
d.requestSdkAsync(callStr)
|
||||
function rejectPairSession(id)
|
||||
{
|
||||
wcCalls.rejectPairSession(id)
|
||||
}
|
||||
|
||||
function acceptSessionRequest(topic, id, signature) {
|
||||
let callStr = d.generateSdkCall("respondSessionRequest", `"${topic}", ${id}, "${signature}"`, RequestCodes.AcceptSessionSuccess, RequestCodes.AcceptSessionError)
|
||||
|
||||
d.requestSdkAsync(callStr)
|
||||
wcCalls.acceptSessionRequest(topic, id, signature)
|
||||
}
|
||||
|
||||
function rejectSessionRequest(topic, id, error) {
|
||||
let callStr = d.generateSdkCall("rejectSessionRequest", `"${topic}", ${id}, ${error}`, RequestCodes.RejectSessionSuccess, RequestCodes.RejectSessionError)
|
||||
|
||||
d.requestSdkAsync(callStr)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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 === "") {
|
||||
scriptSrc = SQUtils.StringUtils.readTextFile("./AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
|
||||
if (scriptSrc === "") {
|
||||
console.error("Failed to read WalletConnect SDK bundle")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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>`
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
wcCalls.rejectSessionRequest(topic, id, error)
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property var sessionProposal: null
|
||||
property var sessionType: null
|
||||
property bool sdkReady: false
|
||||
|
||||
property ListModel pairingsModel: pairings
|
||||
|
||||
onSdkReadyChanged: {
|
||||
if (sdkReady) {
|
||||
d.getPairings()
|
||||
if (sdkReady)
|
||||
{
|
||||
d.resetPairingsModel()
|
||||
}
|
||||
}
|
||||
|
||||
function populatePairingsModel(pairList) {
|
||||
function resetPairingsModel()
|
||||
{
|
||||
pairings.clear();
|
||||
for (let i = 0; i < pairList.length; i++) {
|
||||
pairings.append({
|
||||
active: pairList[i].active,
|
||||
topic: pairList[i].topic,
|
||||
expiry: pairList[i].expiry
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getPairingTopicFromPairingUrl(url)
|
||||
{
|
||||
if (!url.startsWith("wc:"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
const atIndex = url.indexOf("@");
|
||||
if (atIndex < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return url.slice(3, atIndex);
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: wcCalls
|
||||
|
||||
function isWaitingForSdk() {
|
||||
return timer.running
|
||||
}
|
||||
function init() {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
|
||||
|
||||
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()
|
||||
webEngineView.runJavaScript(`wc.init("${root.projectId}")`, function(result) {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
if (result && !!result.error)
|
||||
{
|
||||
console.error("init: ", result.error)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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 getPairings(callback) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.getPairings;`)
|
||||
|
||||
function startListeningForEvents() {
|
||||
const jsCode = "
|
||||
try {
|
||||
function processWCEvents() {
|
||||
window.wcEventResult = [];
|
||||
window.wcEventError = null
|
||||
window.wc.registerForSessionRequest((event) => {
|
||||
window.wcEventResult.push({name: 'session_request', payload: event});
|
||||
});
|
||||
}
|
||||
processWCEvents();
|
||||
} catch (e) {
|
||||
window.wcEventError = e
|
||||
}
|
||||
window.wcEventError"
|
||||
webEngineView.runJavaScript(`wc.getPairings()`, function(result) {
|
||||
|
||||
root.runJavaScript(jsCode,
|
||||
function(result) {
|
||||
if (result) {
|
||||
console.error("startListeningForEvents: processWCEvents error", result)
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.getPairings; response: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (!!result.error) {
|
||||
console.error("getPairings: ", result.error)
|
||||
return
|
||||
}
|
||||
|
||||
callback(result.result)
|
||||
return
|
||||
}
|
||||
)
|
||||
eventsTimer.start()
|
||||
})
|
||||
}
|
||||
|
||||
function init(projectId) {
|
||||
d.requestSdkAsync(generateSdkCall("init", `"${projectId}"`, RequestCodes.SdkInitSuccess, RequestCodes.SdkInitError))
|
||||
function pair(pairLink) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.pair; pairLink: ${pairLink}`)
|
||||
|
||||
wcCalls.getPairings((allPairings) => {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.pair; response: ${JSON.stringify(allPairings, null, 2)}`)
|
||||
|
||||
let pairingTopic = d.getPairingTopicFromPairingUrl(pairLink);
|
||||
|
||||
// Find pairing by topic
|
||||
const pairing = allPairings.find((p) => p.topic === pairingTopic);
|
||||
if (pairing)
|
||||
{
|
||||
if (pairing.active) {
|
||||
console.warn("pair: already paired")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
webEngineView.runJavaScript(`wc.pair("${pairLink}")`, function(result) {
|
||||
if (result && !!result.error)
|
||||
{
|
||||
console.error("pair: ", result.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function getPairings(projectId) {
|
||||
d.requestSdk("getPairings", `null`, RequestCodes.GetPairings, RequestCodes.GetPairingsError)
|
||||
function approvePairSession(sessionProposal, supportedNamespaces) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.approvePairSession; sessionProposal: ${JSON.stringify(sessionProposal)}, supportedNamespaces: ${JSON.stringify(supportedNamespaces)}`)
|
||||
|
||||
webEngineView.runJavaScript(`wc.approvePairSession(${JSON.stringify(sessionProposal)}, ${JSON.stringify(supportedNamespaces)})`, function(result) {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.approvePairSession; response: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
if (result) {
|
||||
if (!!result.error)
|
||||
{
|
||||
console.error("approvePairSession: ", result.error)
|
||||
root.pairAcceptedResult(false, result.error)
|
||||
return
|
||||
}
|
||||
root.pairAcceptedResult(true, result.error)
|
||||
}
|
||||
d.resetPairingsModel()
|
||||
})
|
||||
}
|
||||
|
||||
function rejectPairSession(id) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.rejectPairSession; id: ${id}`)
|
||||
|
||||
webEngineView.runJavaScript(`wc.rejectPairSession(${id})`, function(result) {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.rejectPairSession; response: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
if (result) {
|
||||
if (!!result.error)
|
||||
{
|
||||
console.error("rejectPairSession: ", result.error)
|
||||
root.pairRejectedResult(false, result.error)
|
||||
return
|
||||
}
|
||||
root.pairRejectedResult(true, result.error)
|
||||
}
|
||||
d.resetPairingsModel()
|
||||
})
|
||||
}
|
||||
|
||||
function acceptSessionRequest(topic, id, signature) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.acceptSessionRequest; topic: "${topic}", id: ${id}, signature: "${signature}"`)
|
||||
|
||||
webEngineView.runJavaScript(`wc.respondSessionRequest("${topic}", ${id}, "${signature}")`, function(result) {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.acceptSessionRequest; response: ${JSON.stringify(allPairings, null, 2)}`)
|
||||
|
||||
if (result) {
|
||||
if (!!result.error)
|
||||
{
|
||||
console.error("respondSessionRequest: ", result.error)
|
||||
root.sessionRequestUserAnswerResult(true, result.error)
|
||||
return
|
||||
}
|
||||
root.sessionRequestUserAnswerResult(true, result.error)
|
||||
}
|
||||
d.resetPairingsModel()
|
||||
})
|
||||
}
|
||||
|
||||
function rejectSessionRequest(topic, id, error) {
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
|
||||
|
||||
webEngineView.runJavaScript(`wc.rejectSessionRequest("${topic}", ${id}, "${error}")`, function(result) {
|
||||
|
||||
console.debug(`@dd WalletConnectSDK.wcCall.rejectSessionRequest; response: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
if (result) {
|
||||
if (!!result.error)
|
||||
{
|
||||
console.error("rejectSessionRequest: ", result.error)
|
||||
root.sessionRequestUserAnswerResult(false, result.error)
|
||||
return
|
||||
}
|
||||
root.sessionRequestUserAnswerResult(false, result.error)
|
||||
}
|
||||
d.resetPairingsModel()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: statusObject
|
||||
|
||||
WebChannel.id: "statusObject"
|
||||
|
||||
function sdkInitialized(error)
|
||||
{
|
||||
d.sdkReady = !error
|
||||
root.sdkInit(d.sdkReady, error)
|
||||
}
|
||||
|
||||
function onSessionProposal(details)
|
||||
{
|
||||
console.debug(`@dd 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)}`)
|
||||
}
|
||||
|
||||
function onSessionExtend(details)
|
||||
{
|
||||
console.debug(`@dd TODO WalletConnectSDK.onSessionExtend; details: ${JSON.stringify(details, null, 2)}`)
|
||||
}
|
||||
|
||||
function onSessionPing(details)
|
||||
{
|
||||
console.debug(`@dd TODO WalletConnectSDK.onSessionPing; details: ${JSON.stringify(details, null, 2)}`)
|
||||
}
|
||||
|
||||
function onSessionDelete(details)
|
||||
{
|
||||
console.debug(`@dd TODO WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`)
|
||||
}
|
||||
|
||||
function onSessionExpire(details)
|
||||
{
|
||||
console.debug(`@dd TODO WalletConnectSDK.onSessionExpire; details: ${JSON.stringify(details, null, 2)}`)
|
||||
}
|
||||
|
||||
function onSessionRequest(details)
|
||||
{
|
||||
console.debug(`@dd 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)}`)
|
||||
}
|
||||
|
||||
function onSessionEvent(details)
|
||||
{
|
||||
console.debug(`@dd TODO WalletConnectSDK.onSessionEvent; details: ${JSON.stringify(details, null, 2)}`)
|
||||
}
|
||||
|
||||
function onProposalExpire(details)
|
||||
{
|
||||
console.debug(`@dd WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details, null, 2)}`)
|
||||
root.pairSessionProposalExpired()
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: pairings
|
||||
}
|
||||
|
||||
WebChannel {
|
||||
id: statusChannel
|
||||
registeredObjects: [statusObject]
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
id: webEngineView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
url: "qrc:/app/AppLayouts/Wallet/views/walletconnect/sdk/src/index.html"
|
||||
webChannel: statusChannel
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="qrc:/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -3,19 +3,27 @@ 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";
|
||||
|
||||
// "Export" API to window
|
||||
// Workaround, tried using export via output.module: true in webpack.config.js, but it didn't work
|
||||
window.wc = {
|
||||
core: null,
|
||||
web3wallet: null,
|
||||
authClient: null,
|
||||
statusObject: null,
|
||||
|
||||
init: function (projectId) {
|
||||
return new Promise(async (resolve) => {
|
||||
(async () => {
|
||||
try {
|
||||
await createWebChannel();
|
||||
} catch (error) {
|
||||
wc.statusObject.sdkInitialized(error);
|
||||
return
|
||||
}
|
||||
|
||||
window.wc.core = new Core({
|
||||
projectId: projectId,
|
||||
});
|
||||
|
@ -33,59 +41,75 @@ window.wc = {
|
|||
window.wc.authClient = await AuthClient.init({
|
||||
projectId: projectId,
|
||||
metadata: window.wc.web3wallet.metadata,
|
||||
})
|
||||
});
|
||||
|
||||
resolve()
|
||||
})
|
||||
// connect session responses https://specs.walletconnect.com/2.0/specs/clients/sign/session-events#events
|
||||
window.wc.web3wallet.on("session_proposal", async (details) => {
|
||||
wc.statusObject.onSessionProposal(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_update", async (details) => {
|
||||
wc.statusObject.onSessionUpdate(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_extend", async (details) => {
|
||||
wc.statusObject.onSessionExtend(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_ping", async (details) => {
|
||||
wc.statusObject.onSessionPing(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_delete", async (details) => {
|
||||
wc.statusObject.onSessionDelete(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_expire", async (details) => {
|
||||
wc.statusObject.onSessionExpire(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_request", async (details) => {
|
||||
wc.statusObject.onSessionRequest(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_request_sent", async (details) => {
|
||||
wc.statusObject.onSessionRequestSent(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("session_event", async (details) => {
|
||||
wc.statusObject.onSessionEvent(details)
|
||||
});
|
||||
|
||||
window.wc.web3wallet.on("proposal_expire", async (details) => {
|
||||
wc.statusObject.onProposalExpire(details)
|
||||
});
|
||||
|
||||
wc.statusObject.sdkInitialized("");
|
||||
})();
|
||||
|
||||
return { result: "ok", error: "" };
|
||||
},
|
||||
|
||||
alreadyPaired: new Error("Already paired"),
|
||||
waitingForApproval: new Error("Waiting for approval"),
|
||||
// TODO: there is a corner case when attempting to pair with a link that is already paired or was rejected won't trigger any event back
|
||||
pair: function (uri) {
|
||||
let pairingTopic = getPairingTopicFromPairingUrl(uri);
|
||||
|
||||
const pairings = window.wc.core.pairing.getPairings();
|
||||
// Find pairing by topic
|
||||
const pairing = pairings.find((p) => p.topic === pairingTopic);
|
||||
if (pairing) {
|
||||
if (pairing.active) {
|
||||
return new Promise((_, reject) => {
|
||||
reject(window.wc.alreadyPaired);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let pairPromise = window.wc.web3wallet
|
||||
.pair({ uri: uri })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pairPromise
|
||||
.then(() => {
|
||||
window.wc.web3wallet.on("session_proposal", async (sessionProposal) => {
|
||||
resolve(sessionProposal);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return {
|
||||
result: window.wc.web3wallet.pair({ uri }),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
getPairings: function () {
|
||||
return window.wc.core.pairing.getPairings();
|
||||
return {
|
||||
result: window.wc.core.pairing.getPairings(),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function (topic) {
|
||||
return window.wc.core.pairing.disconnect({ topic: topic});
|
||||
},
|
||||
|
||||
registerForSessionRequest: function (callback) {
|
||||
window.wc.web3wallet.on("session_request", callback);
|
||||
},
|
||||
|
||||
registerForSessionDelete: function (callback) {
|
||||
window.wc.web3wallet.on("session_delete", callback);
|
||||
return {
|
||||
result: window.wc.core.pairing.disconnect({ topic: topic }),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
approvePairSession: function (sessionProposal, supportedNamespaces) {
|
||||
|
@ -96,47 +120,29 @@ window.wc = {
|
|||
supportedNamespaces: supportedNamespaces,
|
||||
});
|
||||
|
||||
return window.wc.web3wallet.approveSession({
|
||||
id,
|
||||
namespaces: approvedNamespaces,
|
||||
});
|
||||
return {
|
||||
result: window.wc.web3wallet.approveSession({
|
||||
id,
|
||||
namespaces: approvedNamespaces,
|
||||
}),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
rejectPairSession: function (id) {
|
||||
return window.wc.web3wallet.rejectSession({
|
||||
id: id,
|
||||
reason: getSdkError("USER_REJECTED"), // TODO USER_REJECTED_METHODS, USER_REJECTED_CHAINS, USER_REJECTED_EVENTS
|
||||
});
|
||||
return {
|
||||
result: window.wc.web3wallet.rejectSession({
|
||||
id: id,
|
||||
reason: getSdkError("USER_REJECTED"), // TODO USER_REJECTED_METHODS, USER_REJECTED_CHAINS, USER_REJECTED_EVENTS
|
||||
}),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
auth: function (uri) {
|
||||
let pairingTopic = getPairingTopicFromPairingUrl(uri);
|
||||
|
||||
const pairings = window.wc.core.pairing.getPairings();
|
||||
// Find pairing by topic
|
||||
const pairing = pairings.find((p) => p.topic === pairingTopic);
|
||||
if (pairing) {
|
||||
if (pairing.active) {
|
||||
return new Promise((_, reject) => {
|
||||
reject(window.wc.alreadyPaired);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let pairPromise = window.wc.authClient.core.pairing
|
||||
.pair({ uri })
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
pairPromise
|
||||
.then(() => {
|
||||
// TODO: check if we can separate using the URI info
|
||||
window.wc.authClient.on("auth_request", async (authProposal) => {
|
||||
resolve(authProposal);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return {
|
||||
result: window.wc.authClient.core.pairing.pair({ uri }),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
approveAuth: function (authProposal) {
|
||||
|
@ -151,7 +157,8 @@ window.wc = {
|
|||
// TODO: signature
|
||||
const signature = "0x123456789"
|
||||
|
||||
return window.wc.authClient.respond(
|
||||
return {
|
||||
result: window.wc.authClient.respond(
|
||||
{
|
||||
id: id,
|
||||
signature: {
|
||||
|
@ -159,43 +166,49 @@ window.wc = {
|
|||
t: "eip191",
|
||||
},
|
||||
},
|
||||
iss
|
||||
);
|
||||
iss),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
rejectAuth: function (id) {
|
||||
return window.wc.authClient.reject(id);
|
||||
return {
|
||||
result: window.wc.authClient.reject(id),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
respondSessionRequest: function (topic, id, signature) {
|
||||
const response = formatJsonRpcResult(id, signature)
|
||||
return window.wc.web3wallet.respondSessionRequest({ topic, response });
|
||||
return {
|
||||
result: window.wc.web3wallet.respondSessionRequest({ topic, response }),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
|
||||
rejectSessionRequest: function (topic, id, error = false) {
|
||||
const errorType = error ? "SESSION_SETTLEMENT_FAILED" : "USER_REJECTED";
|
||||
return window.wc.web3wallet.respondSessionRequest({
|
||||
topic: topic,
|
||||
response: formatJsonRpcError(id, getSdkError(errorType)),
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
disconnectAll: function () {
|
||||
const pairings = window.wc.core.pairing.getPairings();
|
||||
pairings.forEach((p) => {
|
||||
window.wc.core.pairing.disconnect({ topic: p.topic });
|
||||
});
|
||||
return {
|
||||
result: window.wc.web3wallet.respondSessionRequest({
|
||||
topic: topic,
|
||||
response: formatJsonRpcError(id, getSdkError(errorType)),
|
||||
}),
|
||||
error: ""
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Returns null if not a pairing url
|
||||
function getPairingTopicFromPairingUrl(url) {
|
||||
if (!url.startsWith("wc:")) {
|
||||
return null;
|
||||
}
|
||||
const atIndex = url.indexOf("@");
|
||||
if (atIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
return url.slice(3, atIndex);
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,448 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtWebChannel module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
"use strict";
|
||||
|
||||
var QWebChannelMessageTypes = {
|
||||
signal: 1,
|
||||
propertyUpdate: 2,
|
||||
init: 3,
|
||||
idle: 4,
|
||||
debug: 5,
|
||||
invokeMethod: 6,
|
||||
connectToSignal: 7,
|
||||
disconnectFromSignal: 8,
|
||||
setProperty: 9,
|
||||
response: 10,
|
||||
};
|
||||
|
||||
var QWebChannel = function(transport, initCallback)
|
||||
{
|
||||
if (typeof transport !== "object" || typeof transport.send !== "function") {
|
||||
console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
|
||||
" Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = this;
|
||||
this.transport = transport;
|
||||
|
||||
this.send = function(data)
|
||||
{
|
||||
if (typeof(data) !== "string") {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
channel.transport.send(data);
|
||||
}
|
||||
|
||||
this.transport.onmessage = function(message)
|
||||
{
|
||||
var data = message.data;
|
||||
if (typeof data === "string") {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
switch (data.type) {
|
||||
case QWebChannelMessageTypes.signal:
|
||||
channel.handleSignal(data);
|
||||
break;
|
||||
case QWebChannelMessageTypes.response:
|
||||
channel.handleResponse(data);
|
||||
break;
|
||||
case QWebChannelMessageTypes.propertyUpdate:
|
||||
channel.handlePropertyUpdate(data);
|
||||
break;
|
||||
default:
|
||||
console.error("invalid message received:", message.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.execCallbacks = {};
|
||||
this.execId = 0;
|
||||
this.exec = function(data, callback)
|
||||
{
|
||||
if (!callback) {
|
||||
// if no callback is given, send directly
|
||||
channel.send(data);
|
||||
return;
|
||||
}
|
||||
if (channel.execId === Number.MAX_VALUE) {
|
||||
// wrap
|
||||
channel.execId = Number.MIN_VALUE;
|
||||
}
|
||||
if (data.hasOwnProperty("id")) {
|
||||
console.error("Cannot exec message with property id: " + JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
data.id = channel.execId++;
|
||||
channel.execCallbacks[data.id] = callback;
|
||||
channel.send(data);
|
||||
};
|
||||
|
||||
this.objects = {};
|
||||
|
||||
this.handleSignal = function(message)
|
||||
{
|
||||
var object = channel.objects[message.object];
|
||||
if (object) {
|
||||
object.signalEmitted(message.signal, message.args);
|
||||
} else {
|
||||
console.warn("Unhandled signal: " + message.object + "::" + message.signal);
|
||||
}
|
||||
}
|
||||
|
||||
this.handleResponse = function(message)
|
||||
{
|
||||
if (!message.hasOwnProperty("id")) {
|
||||
console.error("Invalid response message received: ", JSON.stringify(message));
|
||||
return;
|
||||
}
|
||||
channel.execCallbacks[message.id](message.data);
|
||||
delete channel.execCallbacks[message.id];
|
||||
}
|
||||
|
||||
this.handlePropertyUpdate = function(message)
|
||||
{
|
||||
message.data.forEach(data => {
|
||||
var object = channel.objects[data.object];
|
||||
if (object) {
|
||||
object.propertyUpdate(data.signals, data.properties);
|
||||
} else {
|
||||
console.warn("Unhandled property update: " + data.object + "::" + data.signal);
|
||||
}
|
||||
});
|
||||
channel.exec({type: QWebChannelMessageTypes.idle});
|
||||
}
|
||||
|
||||
this.debug = function(message)
|
||||
{
|
||||
channel.send({type: QWebChannelMessageTypes.debug, data: message});
|
||||
};
|
||||
|
||||
channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
|
||||
for (const objectName of Object.keys(data)) {
|
||||
new QObject(objectName, data[objectName], channel);
|
||||
}
|
||||
|
||||
// now unwrap properties, which might reference other registered objects
|
||||
for (const objectName of Object.keys(channel.objects)) {
|
||||
channel.objects[objectName].unwrapProperties();
|
||||
}
|
||||
|
||||
if (initCallback) {
|
||||
initCallback(channel);
|
||||
}
|
||||
channel.exec({type: QWebChannelMessageTypes.idle});
|
||||
});
|
||||
};
|
||||
|
||||
function QObject(name, data, webChannel)
|
||||
{
|
||||
this.__id__ = name;
|
||||
webChannel.objects[name] = this;
|
||||
|
||||
// List of callbacks that get invoked upon signal emission
|
||||
this.__objectSignals__ = {};
|
||||
|
||||
// Cache of all properties, updated when a notify signal is emitted
|
||||
this.__propertyCache__ = {};
|
||||
|
||||
var object = this;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
this.unwrapQObject = function(response)
|
||||
{
|
||||
if (response instanceof Array) {
|
||||
// support list of objects
|
||||
return response.map(qobj => object.unwrapQObject(qobj))
|
||||
}
|
||||
if (!(response instanceof Object))
|
||||
return response;
|
||||
|
||||
if (!response["__QObject*__"] || response.id === undefined) {
|
||||
var jObj = {};
|
||||
for (const propName of Object.keys(response)) {
|
||||
jObj[propName] = object.unwrapQObject(response[propName]);
|
||||
}
|
||||
return jObj;
|
||||
}
|
||||
|
||||
var objectId = response.id;
|
||||
if (webChannel.objects[objectId])
|
||||
return webChannel.objects[objectId];
|
||||
|
||||
if (!response.data) {
|
||||
console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
|
||||
return;
|
||||
}
|
||||
|
||||
var qObject = new QObject( objectId, response.data, webChannel );
|
||||
qObject.destroyed.connect(function() {
|
||||
if (webChannel.objects[objectId] === qObject) {
|
||||
delete webChannel.objects[objectId];
|
||||
// reset the now deleted QObject to an empty {} object
|
||||
// just assigning {} though would not have the desired effect, but the
|
||||
// below also ensures all external references will see the empty map
|
||||
// NOTE: this detour is necessary to workaround QTBUG-40021
|
||||
Object.keys(qObject).forEach(name => delete qObject[name]);
|
||||
}
|
||||
});
|
||||
// here we are already initialized, and thus must directly unwrap the properties
|
||||
qObject.unwrapProperties();
|
||||
return qObject;
|
||||
}
|
||||
|
||||
this.unwrapProperties = function()
|
||||
{
|
||||
for (const propertyIdx of Object.keys(object.__propertyCache__)) {
|
||||
object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
function addSignal(signalData, isPropertyNotifySignal)
|
||||
{
|
||||
var signalName = signalData[0];
|
||||
var signalIndex = signalData[1];
|
||||
object[signalName] = {
|
||||
connect: function(callback) {
|
||||
if (typeof(callback) !== "function") {
|
||||
console.error("Bad callback given to connect to signal " + signalName);
|
||||
return;
|
||||
}
|
||||
|
||||
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
|
||||
object.__objectSignals__[signalIndex].push(callback);
|
||||
|
||||
// only required for "pure" signals, handled separately for properties in propertyUpdate
|
||||
if (isPropertyNotifySignal)
|
||||
return;
|
||||
|
||||
// also note that we always get notified about the destroyed signal
|
||||
if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)")
|
||||
return;
|
||||
|
||||
// and otherwise we only need to be connected only once
|
||||
if (object.__objectSignals__[signalIndex].length == 1) {
|
||||
webChannel.exec({
|
||||
type: QWebChannelMessageTypes.connectToSignal,
|
||||
object: object.__id__,
|
||||
signal: signalIndex
|
||||
});
|
||||
}
|
||||
},
|
||||
disconnect: function(callback) {
|
||||
if (typeof(callback) !== "function") {
|
||||
console.error("Bad callback given to disconnect from signal " + signalName);
|
||||
return;
|
||||
}
|
||||
object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
|
||||
var idx = object.__objectSignals__[signalIndex].indexOf(callback);
|
||||
if (idx === -1) {
|
||||
console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
|
||||
return;
|
||||
}
|
||||
object.__objectSignals__[signalIndex].splice(idx, 1);
|
||||
if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
|
||||
// only required for "pure" signals, handled separately for properties in propertyUpdate
|
||||
webChannel.exec({
|
||||
type: QWebChannelMessageTypes.disconnectFromSignal,
|
||||
object: object.__id__,
|
||||
signal: signalIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes all callbacks for the given signalname. Also works for property notify callbacks.
|
||||
*/
|
||||
function invokeSignalCallbacks(signalName, signalArgs)
|
||||
{
|
||||
var connections = object.__objectSignals__[signalName];
|
||||
if (connections) {
|
||||
connections.forEach(function(callback) {
|
||||
callback.apply(callback, signalArgs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.propertyUpdate = function(signals, propertyMap)
|
||||
{
|
||||
// update property cache
|
||||
for (const propertyIndex of Object.keys(propertyMap)) {
|
||||
var propertyValue = propertyMap[propertyIndex];
|
||||
object.__propertyCache__[propertyIndex] = this.unwrapQObject(propertyValue);
|
||||
}
|
||||
|
||||
for (const signalName of Object.keys(signals)) {
|
||||
// Invoke all callbacks, as signalEmitted() does not. This ensures the
|
||||
// property cache is updated before the callbacks are invoked.
|
||||
invokeSignalCallbacks(signalName, signals[signalName]);
|
||||
}
|
||||
}
|
||||
|
||||
this.signalEmitted = function(signalName, signalArgs)
|
||||
{
|
||||
invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));
|
||||
}
|
||||
|
||||
function addMethod(methodData)
|
||||
{
|
||||
var methodName = methodData[0];
|
||||
var methodIdx = methodData[1];
|
||||
|
||||
// Fully specified methods are invoked by id, others by name for host-side overload resolution
|
||||
var invokedMethod = methodName[methodName.length - 1] === ')' ? methodIdx : methodName
|
||||
|
||||
object[methodName] = function() {
|
||||
var args = [];
|
||||
var callback;
|
||||
var errCallback;
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
var argument = arguments[i];
|
||||
if (typeof argument === "function")
|
||||
callback = argument;
|
||||
else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)
|
||||
args.push({
|
||||
"id": argument.__id__
|
||||
});
|
||||
else
|
||||
args.push(argument);
|
||||
}
|
||||
|
||||
var result;
|
||||
// during test, webChannel.exec synchronously calls the callback
|
||||
// therefore, the promise must be constucted before calling
|
||||
// webChannel.exec to ensure the callback is set up
|
||||
if (!callback && (typeof(Promise) === 'function')) {
|
||||
result = new Promise(function(resolve, reject) {
|
||||
callback = resolve;
|
||||
errCallback = reject;
|
||||
});
|
||||
}
|
||||
|
||||
webChannel.exec({
|
||||
"type": QWebChannelMessageTypes.invokeMethod,
|
||||
"object": object.__id__,
|
||||
"method": invokedMethod,
|
||||
"args": args
|
||||
}, function(response) {
|
||||
if (response !== undefined) {
|
||||
var result = object.unwrapQObject(response);
|
||||
if (callback) {
|
||||
(callback)(result);
|
||||
}
|
||||
} else if (errCallback) {
|
||||
(errCallback)();
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function bindGetterSetter(propertyInfo)
|
||||
{
|
||||
var propertyIndex = propertyInfo[0];
|
||||
var propertyName = propertyInfo[1];
|
||||
var notifySignalData = propertyInfo[2];
|
||||
// initialize property cache with current value
|
||||
// NOTE: if this is an object, it is not directly unwrapped as it might
|
||||
// reference other QObject that we do not know yet
|
||||
object.__propertyCache__[propertyIndex] = propertyInfo[3];
|
||||
|
||||
if (notifySignalData) {
|
||||
if (notifySignalData[0] === 1) {
|
||||
// signal name is optimized away, reconstruct the actual name
|
||||
notifySignalData[0] = propertyName + "Changed";
|
||||
}
|
||||
addSignal(notifySignalData, true);
|
||||
}
|
||||
|
||||
Object.defineProperty(object, propertyName, {
|
||||
configurable: true,
|
||||
get: function () {
|
||||
var propertyValue = object.__propertyCache__[propertyIndex];
|
||||
if (propertyValue === undefined) {
|
||||
// This shouldn't happen
|
||||
console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
},
|
||||
set: function(value) {
|
||||
if (value === undefined) {
|
||||
console.warn("Property setter for " + propertyName + " called with undefined value!");
|
||||
return;
|
||||
}
|
||||
object.__propertyCache__[propertyIndex] = value;
|
||||
var valueToSend = value;
|
||||
if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)
|
||||
valueToSend = { "id": valueToSend.__id__ };
|
||||
webChannel.exec({
|
||||
"type": QWebChannelMessageTypes.setProperty,
|
||||
"object": object.__id__,
|
||||
"property": propertyIndex,
|
||||
"value": valueToSend
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
data.methods.forEach(addMethod);
|
||||
|
||||
data.properties.forEach(bindGetterSetter);
|
||||
|
||||
data.signals.forEach(function(signal) { addSignal(signal, false); });
|
||||
|
||||
Object.assign(object, data.enums);
|
||||
}
|
||||
|
||||
//required for use with nodejs
|
||||
if (typeof module === 'object') {
|
||||
module.exports = {
|
||||
QWebChannel: QWebChannel
|
||||
};
|
||||
}
|
|
@ -26,6 +26,7 @@ var qrcExtensions = map[string]bool{
|
|||
".gif": true,
|
||||
".json": true,
|
||||
".mdwn": true,
|
||||
".html": true,
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
Loading…
Reference in New Issue