// Copyright 2019-2020 The Hush developers // Released under the GPLv3 package org.myhush.silentdragon import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.res.Resources import android.util.Log import com.beust.klaxon.Json import com.beust.klaxon.JsonObject import com.beust.klaxon.json import okhttp3.* import okio.ByteString import org.json.JSONObject import java.lang.Exception import java.net.ConnectException import java.util.* import java.util.concurrent.TimeUnit import kotlin.collections.HashMap object ConnectionManager { val DATA_SIGNAL: String = "ConnectionManager_NewData_Signal" /** * Refresh all data, including attempting a connection if none exists */ fun refreshAllData() { // First, try to make a connection. makeConnection() initCurrencies() } // Attempt a connection to the server. If there is no saved connection, we'll set the connection status // to None private fun makeConnection(directConn : Boolean = true) { val connString = DataModel.getConnString(SilentDragonApp.appContext!!) if (connString.isNullOrBlank()) { // The user might have just disconnected, so make sure we are disconnected DataModel.ws?.close(1000, "disconnected") sendUpdateDataSignal(true) return } println("MakeConnection") // If still connecting, this is a duplicate call, so do nothing but wait. if (DataModel.connStatus == DataModel.ConnectionStatus.CONNECTING) { return } // If already connected, then refresh data if (DataModel.connStatus == DataModel.ConnectionStatus.CONNECTED) { DataModel.makeAPICalls() return } println("Attempting new connection ${DataModel.connStatus}") sendRefreshSignal(false) // If direct connection, then connect to the URL in connection string if (directConn) { // Update status to connecting, so we can update the UI DataModel.connStatus = DataModel.ConnectionStatus.CONNECTING println("Connstatus = connecting") val client = OkHttpClient.Builder().connectTimeout(2, TimeUnit.SECONDS).build() val request = Request.Builder().url(connString).build() val listener = WebsocketClient(true) DataModel.ws = client.newWebSocket(request, listener) } else { // Connect to the wormhole DataModel.connStatus = DataModel.ConnectionStatus.CONNECTING println("Connstatus = connecting") val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build() val request = Request.Builder().url("wss://wormhole.myhush.org:443").build() //val request = Request.Builder().url("ws://192.168.5.187:7070").build() val listener = WebsocketClient(false) DataModel.ws = client.newWebSocket(request, listener) } } fun closeConnection() { DataModel.ws?.close(1000, "Close requested") } fun sendRefreshSignal(finished: Boolean) { val i = Intent(DATA_SIGNAL) i.putExtra("action", "refresh") i.putExtra("finished", finished) SilentDragonApp.appContext?.sendBroadcast(i) } fun sendUpdateDataSignal(updateTxns: Boolean = false) { val i = Intent(DATA_SIGNAL) i.putExtra("action", "newdata") i.putExtra("updateTxns", updateTxns) SilentDragonApp.appContext?.sendBroadcast(i) } fun sendErrorSignal(msg: String? = null, doDisconnect: Boolean = false) { val i = Intent(DATA_SIGNAL) i.putExtra("action", "error") i.putExtra("msg", msg) i.putExtra("doDisconnect", doDisconnect) SilentDragonApp.appContext?.sendBroadcast(i) } fun initCurrencies(){ try { DataModel.currencySymbols["AUD"] = "$" DataModel.currencySymbols["BTC"] = "BTC" DataModel.currencySymbols["CAD"] = "$" DataModel.currencySymbols["CNY"] = "¥" DataModel.currencySymbols["EUR"] = "€" DataModel.currencySymbols["GBP"] = "£" DataModel.currencySymbols["JPY"] = "¥" DataModel.currencySymbols["KRW"] = "₩" DataModel.currencySymbols["MXN"] = "$" DataModel.currencySymbols["MYR"] = "RM" DataModel.currencySymbols["PHP"] = "₱" DataModel.currencySymbols["PKR"] = "₨" DataModel.currencySymbols["RUB"] = "₽" DataModel.currencySymbols["SGD"] = "$" DataModel.currencySymbols["THB"] = "฿" DataModel.currencySymbols["USD"] = "$" DataModel.currencySymbols["VEF"] = "Bs" DataModel.currencySymbols["VND"] = "₫" DataModel.currencySymbols["XAG"] = "XAG" DataModel.currencySymbols["XAU"] = "XAU" DataModel.currencySymbols["ZAR"] = "R" Thread { val client = OkHttpClient() val currencies = "usd,eur,jpy,btc,cny,rub,cad,sgd,chf,inr,gbp,aud,pkr,mxn,php,vnd,thb,zar,krw,myr,vef,xau,xag" val request: Request = Request.Builder() .url("https://api.coingecko.com/api/v3/simple/price?ids=hush&vs_currencies=${currencies}") .build() val response: Response = client.newCall(request).execute() val json: JSONObject = JSONObject(response.body()?.string() as @NonNull String)["hush"] as JSONObject if (json.length() > 0){ for (cur: String in json.keys()){ DataModel.currencyValues[cur.toUpperCase()] = json.getDouble(cur) if(!DataModel.currencySymbols.containsKey(cur.toUpperCase())) DataModel.currencySymbols[cur.toUpperCase()] = cur.toUpperCase() } } }.start() }catch (e: Exception){ e.printStackTrace() } } private class WebsocketClient (directConn: Boolean) : WebSocketListener() { val m_directConn = directConn val TAG = "WebsocketClient" override fun onOpen(webSocket: WebSocket, response: Response) { Log.d(TAG, "Opened Websocket") DataModel.connStatus = DataModel.ConnectionStatus.CONNECTED println("Connstatus = connected") // If direct connection, start making API calls to get data. if (m_directConn) { DataModel.makeAPICalls() } else { // If this is a connection to wormhole, we have to register ourselves before we make any API calls if (!DataModel.getWormholeCode().isNullOrBlank()) { webSocket.send( json { obj( "register" to DataModel.getWormholeCode()) }.toJsonString()) // Delay sending the API calls a bit to let the register call finish Timer().schedule(object : TimerTask() { override fun run() { DataModel.makeAPICalls() }}, 100) } } sendUpdateDataSignal() } override fun onMessage(webSocket: WebSocket?, text: String?) { Log.i(TAG, "Receiving $text") val r = DataModel.parseResponse(text!!) if (r.displayMsg != null) { sendErrorSignal( r.displayMsg, r.doDisconnect ) if (r.doDisconnect) { // We don't pass a reason here, because we already sent the error signal above webSocket?.close(1000, null) } } else { sendUpdateDataSignal(r.updateTxns) sendRefreshSignal(r.updateTxns) } } override fun onMessage(webSocket: WebSocket?, bytes: ByteString) { Log.i(TAG, "Receiving bytes : " + bytes.hex()) } override fun onClosing(webSocket: WebSocket, code: Int, reason: String?) { DataModel.connStatus = DataModel.ConnectionStatus.DISCONNECTED println("Connstatus = disconnected") Log.i(TAG,"Closing : $code / $reason") //if (!reason.isNullOrEmpty()) { // sendErrorSignal(reason, true) //} sendRefreshSignal(true) } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { Log.e(TAG,"Failed $t") DataModel.connStatus = DataModel.ConnectionStatus.DISCONNECTED val allowInternet = DataModel.getAllowInternet() && DataModel.getGlobalAllowInternet() // If the connection is direct, and there is no need to further connect, so just error out if (t is ConnectException && (m_directConn && !allowInternet)) { var mesg = t.localizedMessage if (!DataModel.getAllowInternet()) { mesg += ": " + SilentDragonApp.appContext!!.getString(R.string.Connecting_over_internet_not_enabled_in_desktop_node) } else if (!DataModel.getGlobalAllowInternet()) { mesg += ": " + SilentDragonApp.appContext!!.getString(R.string.Connecting_over_internet_is_disabled_in_settings) } sendErrorSignal(mesg, true) sendRefreshSignal(true) return } // If this was a direct connection and there was a failure to connect, retry connecting // without the direct connection (i.e., through wormhole) if (m_directConn && allowInternet) { makeConnection(false) } else { // Not a direct connection (or we're not allowed to connect to internet) and there was a failure. sendErrorSignal( t.localizedMessage, true ) sendRefreshSignal(true) } } } }