From 14785fa079fd65fc2d91d48c6e1203ddbd1311d2 Mon Sep 17 00:00:00 2001 From: zhuguoqing Date: Sat, 28 May 2022 23:17:36 +0800 Subject: [PATCH] update buildteamplate --- .../org/cocos2dx/javascript/AppActivity.java | 358 ++++++++++-------- .../cocos2dx/javascript/ExampleApplication.kt | 11 +- .../org/cocos2dx/javascript/wc/JWCSession.kt | 303 +++++++++++++++ 3 files changed, 509 insertions(+), 163 deletions(-) create mode 100644 build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/wc/JWCSession.kt diff --git a/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java index 7ff1015e..8acc3246 100644 --- a/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java +++ b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/AppActivity.java @@ -1,60 +1,66 @@ /**************************************************************************** -Copyright (c) 2015-2016 Chukong Technologies Inc. -Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. -http://www.cocos2d-x.org + http://www.cocos2d-x.org -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -****************************************************************************/ + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ package org.cocos2dx.javascript; -import org.cocos2dx.lib.Cocos2dxActivity; -import org.cocos2dx.lib.Cocos2dxGLSurfaceView; -import org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge; -import org.jetbrains.annotations.NotNull; -import org.walletconnect.Session; - import android.content.ActivityNotFoundException; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; - import android.content.Intent; import android.content.res.Configuration; -import android.os.Looper; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import com.youme.voiceengine.MemberChange; import com.youme.voiceengine.YouMeCallBackInterface; import com.youme.voiceengine.YouMeConst; import com.youme.voiceengine.api; import com.youme.voiceengine.mgr.YouMeManager; -import java.util.Arrays; +import org.cocos2dx.javascript.wc.JWCSession; +import org.cocos2dx.lib.Cocos2dxActivity; +import org.cocos2dx.lib.Cocos2dxGLSurfaceView; +import org.cocos2dx.lib.Cocos2dxJavascriptJavaBridge; +import org.jetbrains.annotations.NotNull; +import org.walletconnect.Session; + +import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import kotlin.Unit; +import kotlin.jvm.functions.Function1; public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterface , Session.Callback { private static AppActivity appActivity = null; + private static final String TAG = "AppActivity"; private static final String appKey="YOUME1838B3633FF1410BDC9124BBD806F245B9D2E5AC"; private static final String appSecret="q6B570yTyj/00Nk4mYZtgDwyew5v05t13V1vo4mxpEuAaWUiinAyVxG41sNu3vsFe8sipOLfKfIVYGhzpQrqzvj5sId3mrBfj/s65a2gp36yDrI/nX5BnUAJB317SEosR6xLoPuhBvHU+/1DWI7nKSKaRNxnQiC46PJKFc2kX50BAAE="; - + @Override protected void onCreate(Bundle savedInstanceState) { YouMeManager.Init(this); @@ -70,175 +76,173 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf } api.SetCallback(this); api.init(appKey,appSecret,0,""); - + // DO OTHER INITIALIZATION BELOW SDKWrapper.getInstance().init(this); - + } - + @Override public Cocos2dxGLSurfaceView onCreateView() { Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this); // TestCpp should create stencil buffer glSurfaceView.setEGLConfigChooser(5, 6, 5, 0, 16, 8); SDKWrapper.getInstance().setGLSurfaceView(glSurfaceView, this); - + return glSurfaceView; } - + @Override protected void onResume() { super.onResume(); SDKWrapper.getInstance().onResume(); - + } - + @Override protected void onPause() { super.onPause(); SDKWrapper.getInstance().onPause(); - + } - + @Override protected void onDestroy() { super.onDestroy(); SDKWrapper.getInstance().onDestroy(); - + } - + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); SDKWrapper.getInstance().onActivityResult(requestCode, resultCode, data); } - + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); SDKWrapper.getInstance().onNewIntent(intent); } - + @Override protected void onRestart() { super.onRestart(); SDKWrapper.getInstance().onRestart(); } - + @Override protected void onStop() { super.onStop(); SDKWrapper.getInstance().onStop(); } - + @Override public void onBackPressed() { SDKWrapper.getInstance().onBackPressed(); super.onBackPressed(); } - + @Override public void onConfigurationChanged(Configuration newConfig) { SDKWrapper.getInstance().onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); } - + @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { SDKWrapper.getInstance().onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState); } - + @Override protected void onSaveInstanceState(Bundle outState) { SDKWrapper.getInstance().onSaveInstanceState(outState); super.onSaveInstanceState(outState); } - + @Override protected void onStart() { SDKWrapper.getInstance().onStart(); super.onStart(); } - + public static void joinRoom(String teamid,String userid){ - System.out.println("run in java joinroom"+teamid+" "+userid); - // 第四个参数是 是否检查房间存在 api.joinChannelSingleMode(userid, teamid, 1, false); api.setSpeakerMute(false); api.setMicrophoneMute(false); api.setVolume(70); } - + public static void leaveRoom(){ api.leaveChannelAll(); } - - + + @Override public void onEvent(int eventType, int errorCode, String channelID, Object param) { - + System.out.println("OnEvent:event " + eventType + ",error " + errorCode + ",channel " + channelID + ",param_" + param.toString()); - + switch (eventType) { case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_OK: //YOUME_EVENT_INIT_OK: - System.out.println("Talk 初始化成功"); - break; + System.out.println("Talk 初始化成功"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_INIT_FAILED://YOUME_EVENT_INIT_FAILED: - System.out.println("Talk 初始化失败"); - break; + System.out.println("Talk 初始化失败"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_OK://YOUME_EVENT_JOIN_OK: - System.out.println("Talk 进入频道成功,频道:" + channelID + " 用户id:" + param); - break; + System.out.println("Talk 进入频道成功,频道:" + channelID + " 用户id:" + param); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_JOIN_FAILED://YOUME_EVENT_JOIN_FAILED: - System.out.println("Talk 进入频道:" + channelID + "失败,code:" + errorCode); - break; + System.out.println("Talk 进入频道:" + channelID + "失败,code:" + errorCode); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ONE://YOUME_EVENT_LEAVED_ONE: - System.out.println("Talk 离开单个频道:" + channelID); - break; + System.out.println("Talk 离开单个频道:" + channelID); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_LEAVED_ALL://YOUME_EVENT_LEAVED_ALL: - System.out.println("Talk 离开所有频道,这个回调channel参数为空字符串"); - break; + System.out.println("Talk 离开所有频道,这个回调channel参数为空字符串"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_PAUSED://YOUME_EVENT_PAUSED: - System.out.println("Talk 暂停"); - break; + System.out.println("Talk 暂停"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_RESUMED://YOUME_EVENT_RESUMED: - System.out.println("Talk 恢复"); - break; + System.out.println("Talk 恢复"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_SUCCESS://YOUME_EVENT_SPEAK_SUCCESS:///< 切换对指定频道讲话成功(适用于多频道模式) - break; + break; case YouMeConst.YouMeEvent.YOUME_EVENT_SPEAK_FAILED://YOUME_EVENT_SPEAK_FAILED:///< 切换对指定频道讲话失败(适用于多频道模式) - break; + break; case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTING://YOUME_EVENT_RECONNECTING:///< 断网了,正在重连 - System.out.println("Talk 正在重连"); - break; + System.out.println("Talk 正在重连"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_RECONNECTED://YOUME_EVENT_RECONNECTED:///< 断网重连成功 - System.out.println("Talk 重连成功"); - break; + System.out.println("Talk 重连成功"); + break; case YouMeConst.YouMeEvent.YOUME_EVENT_REC_PERMISSION_STATUS://YOUME_EVENT_REC_FAILED:///< 通知录音启动失败(此时不管麦克风mute状态如何,都没有声音输出) - System.out.println("录音启动失败,code:" + errorCode); - break; - + System.out.println("录音启动失败,code:" + errorCode); + break; + default: - break; + break; } } - + @Override public void onRequestRestAPI(int i, int i1, String s, String s1) { - + } - + @Override public void onMemberChange(String s, MemberChange[] memberChanges, boolean b) { - + } - + @Override public void onBroadcast(int i, String s, String s1, String s2, String s3) { - + } - + public static void openSocialUrl(String uri){ Intent i = new Intent(Intent.ACTION_VIEW); i.setData(Uri.parse(uri)); @@ -249,8 +253,11 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf public static void connectwallet(String data){ ExampleApplication.Companion.resetSession(); ExampleApplication.session.addCallback(appActivity); + String url = "metamask://wc?uri=" + ExampleApplication.config.toWCUri(); + Uri uri = Uri.parse(url); + Log.i(TAG, url); Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(ExampleApplication.config.toWCUri())); + intent.setData(uri); try { appActivity.startActivity(intent); } catch (ActivityNotFoundException e) { @@ -263,10 +270,38 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf // private boolean hasSign = false; public static void signApp(String nonce){ - ExampleApplication.Companion.ethSign(nonce,ExampleApplication.session.approvedAccounts().get(0)); - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("wc://")); - appActivity.startActivity(i); + Log.i(TAG, nonce); + List paramList = new ArrayList<>(); + paramList.add(ExampleApplication.session.approvedAccounts().get(0)); + paramList.add(nonce); + long time = System.currentTimeMillis(); + + ExampleApplication.session.performMethodCall(new Session.MethodCall.Custom(time, "eth_signTypedData", paramList), new Function1() { + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Override + public Unit invoke(Session.MethodCall.Response resp) { + if (resp.getResult() != null) { + String signStr = Objects.requireNonNull(resp.getResult()).toString(); + Log.i(TAG, signStr); + signStr = signStr.substring(2); + final String finalSignStr = signStr; + appActivity.runOnGLThread(new Runnable() { + @Override + public void run() { + Cocos2dxJavascriptJavaBridge.evalString("window.signApp(\""+ finalSignStr +"\")"); + } + }); +// + } else { + Log.i(TAG, "sign is empty"); + } + return null; + } + } + ); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse("metamask://")); + appActivity.startActivity(i); } @@ -275,21 +310,21 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf System.out.println("change area"+area); switch (area){ case "0": - // japan - api.init(appKey,appSecret,9,""); - break; + // japan + api.init(appKey,appSecret,9,""); + break; case "1": - //singapore - api.init(appKey,appSecret,3,""); - break; + //singapore + api.init(appKey,appSecret,3,""); + break; case "2": - // turkey - api.init(appKey,appSecret,8,""); - break; + // turkey + api.init(appKey,appSecret,8,""); + break; case "3": - // usa - api.init(appKey,appSecret,12,""); - break; + // usa + api.init(appKey,appSecret,12,""); + break; } } @@ -297,57 +332,62 @@ public class AppActivity extends Cocos2dxActivity implements YouMeCallBackInterf @Override public void onMethodCall(@NotNull final Session.MethodCall methodCall) { - if(ExampleApplication.session.approvedAccounts()!=null){ - if(methodCall.id()==999999999){ - //签名 - appActivity.runOnGLThread(new Runnable() { - @Override - public void run() { - String tmp = ExampleApplication.Companion.getSignRes().substring(2); - System.out.println("签名无前缀"+tmp); - Cocos2dxJavascriptJavaBridge.evalString("window.signApp(\""+tmp+"\")"); - } - }); - }else{ - //连接钱包 - appActivity.runOnGLThread(new Runnable() { - @Override - public void run() { - String result = methodCall.toString(); - String[] allRes = result.split(","); - for(int i=0;i accounts = ExampleApplication.session.approvedAccounts(); + if (accounts == null || accounts.size() == 0) { + showErrMsg("Login failed, no account"); + return; + } + String account = accounts.get(0); + if ("".equals(account) || null == account || account.length() < 2) { + showErrMsg("Login failed, no account"); + return; + } + Long chainId = ((JWCSession) ExampleApplication.session).chainId(); + + if (chainId == null) { + showErrMsg("Login failed, no chainId"); + return; + } + if (chainId != 321L) { + showErrMsg("Login failed, Your wallet should support KCC chain"); + return; + } + + account = account.substring(2); + final String finalAccount = account; + appActivity.runOnGLThread(new Runnable() { + @Override + public void run() { + Cocos2dxJavascriptJavaBridge.evalString("window.connectOK(\""+ finalAccount +"\")"); + } + }); } } diff --git a/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/ExampleApplication.kt b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/ExampleApplication.kt index 166ccb73..b25fcb15 100644 --- a/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/ExampleApplication.kt +++ b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/ExampleApplication.kt @@ -1,10 +1,12 @@ package org.cocos2dx.javascript; +import android.util.Log import androidx.multidex.MultiDexApplication import com.squareup.moshi.Moshi import io.walletconnect.example.server.BridgeServer import okhttp3.OkHttpClient +import org.cocos2dx.javascript.wc.JWCSession import org.komputing.khex.extensions.toNoPrefixHexString import org.walletconnect.Session import org.walletconnect.impls.* @@ -62,16 +64,17 @@ class ExampleApplication : MultiDexApplication() { session.performMethodCall(Session.MethodCall.Custom(999999999,"eth_signTypedData",parmList),::handleResponse) } - fun handleResponse(resp: Session.MethodCall.Response) { - signRes = resp.result as String; + fun handleResponse(resp: Session.MethodCall.Response) { + signRes = resp.result as String + Log.i("Application", signRes) } - + fun resetSession() { nullOnThrow { session }?.clearCallbacks() val key = ByteArray(32).also { Random().nextBytes(it) }.toNoPrefixHexString() config = Session.Config(UUID.randomUUID().toString(), "https://bridge.walletconnect.org", key) - session = WCSession(config, + session = JWCSession(config, MoshiPayloadAdapter(moshi), storage, OkHttpTransport.Builder(client, moshi), diff --git a/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/wc/JWCSession.kt b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/wc/JWCSession.kt new file mode 100644 index 00000000..34d5bf2e --- /dev/null +++ b/build-templates/jsb-link/frameworks/runtime-src/proj.android-studio/app/src/org/cocos2dx/javascript/wc/JWCSession.kt @@ -0,0 +1,303 @@ +package org.cocos2dx.javascript.wc + +import org.walletconnect.Session +import org.walletconnect.impls.WCSessionStore +import org.walletconnect.nullOnThrow +import org.walletconnect.types.extractSessionParams +import org.walletconnect.types.intoMap +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class JWCSession( + private val config: Session.Config, + private val payloadAdapter: Session.PayloadAdapter, + private val sessionStore: WCSessionStore, + transportBuilder: Session.Transport.Builder, + clientMeta: Session.PeerMeta, + clientId: String? = null +) : Session { + + private val keyLock = Any() + + // Persisted state + private var currentKey: String + + private var approvedAccounts: List? = null + private var chainId: Long? = null + private var handshakeId: Long? = null + private var peerId: String? = null + private var peerMeta: Session.PeerMeta? = null + + private val clientData: Session.PeerData + + // Getters + private val encryptionKey: String + get() = currentKey + + private val decryptionKey: String + get() = currentKey + + // Non-persisted state + private val transport = transportBuilder.build(config.bridge, ::handleStatus, ::handleMessage) + private val requests: MutableMap Unit> = ConcurrentHashMap() + private val sessionCallbacks: MutableSet = Collections.newSetFromMap(ConcurrentHashMap()) + + init { + currentKey = config.key + clientData = sessionStore.load(config.handshakeTopic)?.let { + currentKey = it.currentKey + approvedAccounts = it.approvedAccounts + chainId = it.chainId + handshakeId = it.handshakeId + peerId = it.peerData?.id + peerMeta = it.peerData?.meta + if (clientId != null && clientId != it.clientData.id) + throw IllegalArgumentException("Provided clientId is different from stored clientId") + it.clientData + } ?: run { + Session.PeerData(clientId ?: UUID.randomUUID().toString(), clientMeta) + } + storeSession() + } + + override fun addCallback(cb: Session.Callback) { + sessionCallbacks.add(cb) + } + + override fun removeCallback(cb: Session.Callback) { + sessionCallbacks.remove(cb) + } + + override fun clearCallbacks() { + sessionCallbacks.clear() + } + + private fun propagateToCallbacks(action: Session.Callback.() -> Unit) { + sessionCallbacks.forEach { + try { it.action() } + catch (t: Throwable) { + // If error propagation fails, don't try again + nullOnThrow { it.onStatus(Session.Status.Error(t)) } + } + } + } + + override fun peerMeta(): Session.PeerMeta? = peerMeta + + override fun approvedAccounts(): List? = approvedAccounts + + fun chainId(): Long? = chainId + + override fun init() { + if (transport.connect()) { + // Register for all messages for this client + transport.send( + Session.Transport.Message( + config.handshakeTopic, "sub", "" + ) + ) + } + } + + override fun offer() { + if (transport.connect()) { + val requestId = createCallId() + send(Session.MethodCall.SessionRequest(requestId, clientData), topic = config.handshakeTopic, callback = { resp -> + // BUG in extractSessionParams, cast Double to Long error + (resp.result as? Map)?.extractSessionParams()?.let { params -> + peerId = params.peerData?.id + peerMeta = params.peerData?.meta + approvedAccounts = params.accounts +// chainId = params.chainId + + val chainObj = (resp.result as Map<*, *>).get("chainId") + var cid: Long = 0L + if (chainObj != null) { + cid = (chainObj as Double).toLong() + } + chainId = cid + + +// chainId = ((resp.result as Map<*, *>).get("chainId") as Double).toLong() + storeSession() + propagateToCallbacks { onStatus(if (params.approved) Session.Status.Approved else Session.Status.Closed) } + } + }) + handshakeId = requestId + } + } + + override fun approve(accounts: List, chainId: Long) { + val handshakeId = handshakeId ?: return + approvedAccounts = accounts + this.chainId = chainId + // We should not use classes in the Response, since this will not work with proguard + val params = Session.SessionParams(true, chainId, accounts, clientData).intoMap() + send(Session.MethodCall.Response(handshakeId, params)) + storeSession() + propagateToCallbacks { onStatus(Session.Status.Approved) } + } + + override fun update(accounts: List, chainId: Long) { + val params = Session.SessionParams(true, chainId, accounts, clientData) + send(Session.MethodCall.SessionUpdate(createCallId(), params)) + } + + override fun reject() { + handshakeId?.let { + // We should not use classes in the Response, since this will not work with proguard + val params = Session.SessionParams(false, null, null, null).intoMap() + send(Session.MethodCall.Response(it, params)) + } + endSession() + } + + override fun approveRequest(id: Long, response: Any) { + send(Session.MethodCall.Response(id, response)) + } + + override fun rejectRequest(id: Long, errorCode: Long, errorMsg: String) { + send( + Session.MethodCall.Response( + id, + result = null, + error = Session.Error(errorCode, errorMsg) + ) + ) + } + + override fun performMethodCall(call: Session.MethodCall, callback: ((Session.MethodCall.Response) -> Unit)?) { + send(call, callback = callback) + } + + private fun handleStatus(status: Session.Transport.Status) { + when (status) { + Session.Transport.Status.Connected -> { + // Register for all messages for this client + transport.send( + Session.Transport.Message( + clientData.id, "sub", "" + ) + ) + } + } + propagateToCallbacks { + onStatus(when(status) { + Session.Transport.Status.Connected -> Session.Status.Connected + Session.Transport.Status.Disconnected -> Session.Status.Disconnected + is Session.Transport.Status.Error -> Session.Status.Error(Session.TransportError(status.throwable)) + }) + } + } + + private fun handleMessage(message: Session.Transport.Message) { + if (message.type != "pub") return + val data: Session.MethodCall + synchronized(keyLock) { + try { + data = payloadAdapter.parse(message.payload, decryptionKey) + } catch (e: Exception) { + handlePayloadError(e) + return + } + } + var accountToCheck: String? = null + when (data) { + is Session.MethodCall.SessionRequest -> { + handshakeId = data.id + peerId = data.peer.id + peerMeta = data.peer.meta + storeSession() + } + is Session.MethodCall.SessionUpdate -> { + if (!data.params.approved) { + endSession() + } + // TODO handle session update -> not important for our usecase + } + is Session.MethodCall.SendTransaction -> { + accountToCheck = data.from + } + is Session.MethodCall.SignMessage -> { + accountToCheck = data.address + } + is Session.MethodCall.Response -> { + val callback = requests[data.id] ?: return + callback(data) + } + } + + if (accountToCheck?.let { accountCheck(data.id(), it) } != false) { + propagateToCallbacks { onMethodCall(data) } + } + } + + private fun accountCheck(id: Long, address: String): Boolean { + approvedAccounts?.find { it.toLowerCase() == address.toLowerCase() } ?: run { + handlePayloadError(Session.MethodCallException.InvalidAccount(id, address)) + return false + } + return true + } + + private fun handlePayloadError(e: Exception) { + propagateToCallbacks { Session.Status.Error(e) } + (e as? Session.MethodCallException)?.let { + rejectRequest(it.id, it.code, it.message ?: "Unknown error") + } + } + + private fun endSession() { + sessionStore.remove(config.handshakeTopic) + approvedAccounts = null + chainId = null + internalClose() + propagateToCallbacks { onStatus(Session.Status.Closed) } + } + + private fun storeSession() { + sessionStore.store( + config.handshakeTopic, + WCSessionStore.State( + config, + clientData, + peerId?.let { Session.PeerData(it, peerMeta) }, + handshakeId, + currentKey, + approvedAccounts, + chainId + ) + ) + } + + // Returns true if method call was handed over to transport + private fun send( + msg: Session.MethodCall, + topic: String? = peerId, + callback: ((Session.MethodCall.Response) -> Unit)? = null + ): Boolean { + topic ?: return false + + val payload: String + synchronized(keyLock) { + payload = payloadAdapter.prepare(msg, encryptionKey) + } + callback?.let { + requests[msg.id()] = callback + } + transport.send(Session.Transport.Message(topic, "pub", payload)) + return true + } + + private fun createCallId() = System.currentTimeMillis() * 1000 + Random().nextInt(999) + + private fun internalClose() { + transport.close() + } + + override fun kill() { + val params = Session.SessionParams(false, null, null, null) + send(Session.MethodCall.SessionUpdate(createCallId(), params)) + endSession() + } +}