/* Copyright 2020 Appboxo pte. ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.appboxo.react import android.app.Application import android.os.Handler import android.os.Looper import android.graphics.Color import androidx.fragment.app.FragmentActivity import io.boxo.data.models.MiniappData import io.boxo.data.models.PageAnimation import io.boxo.js.params.CustomEvent import io.boxo.js.params.PaymentData import io.boxo.sdk.Boxo import io.boxo.sdk.Config import io.boxo.sdk.Miniapp import io.boxo.sdk.MiniappConfig import io.boxo.sdk.MiniappListCallback import io.boxo.ui.main.BoxoFragment import com.appboxo.react.utils.MapUtil import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap import com.facebook.react.modules.core.DeviceEventManagerModule class RnappboxosdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), Miniapp.LifecycleListener, Miniapp.CustomEventListener, Miniapp.AuthListener, Miniapp.PaymentEventListener { private val reactContext: ReactApplicationContext private val handler: Handler init { this.reactContext = reactContext Boxo.init(reactContext.getBaseContext() as Application) handler = Handler(Looper.getMainLooper()) } override fun getName(): String = "Rnappboxosdk" @ReactMethod fun setConfig( clientId: String, userId: String, language: String, sandboxMode: Boolean, theme: String?, isDebug: Boolean, showPermissionsPage: Boolean, showClearCache: Boolean, showAboutPage: Boolean, splashScreenOptions: ReadableMap? ) { val globalTheme: Config.Theme = when (theme) { "light" -> Config.Theme.LIGHT "dark" -> Config.Theme.DARK else -> Config.Theme.SYSTEM } Boxo .setConfig( Config.Builder() .setClientId(clientId) .setUserId(userId) .sandboxMode(sandboxMode) .setTheme(globalTheme) .setLanguage(language) .permissionsPage(showPermissionsPage) .showClearCache(showClearCache) .showAboutPage(showAboutPage) .debug(isDebug) .apply { splashScreenOptions?.also { options -> setProgressBarColors( lightIndicator = Color.parseColor(options.getString("lightProgressIndicator")), lightTrack = Color.parseColor(options.getString("lightProgressTrack")), darkIndicator = Color.parseColor(options.getString("darkProgressIndicator")), darkTrack = Color.parseColor(options.getString("darkProgressTrack")) ) setSplashBackgroundColors( light = Color.parseColor(options.getString("lightBackground")), dark = Color.parseColor(options.getString("darkBackground")) ) } } .build() ) } @ReactMethod fun openMiniapp( appId: String, data: ReadableMap?, theme: String?, extraUrlParams: ReadableMap?, urlSuffix: String?, colors: ReadableMap?, enableSplash: Boolean, saveState: Boolean, pageAnimation: String? ) { handler.post { val miniapp: Miniapp = Boxo.getMiniapp(appId) .setCustomEventListener(this) .setPaymentEventListener(this) .setAuthListener(this) .setLifecycleListener(this) if (data != null) miniapp.setData(MapUtil.toMap(data)) val configBuilder = MiniappConfig.Builder() if (theme != null) { val miniappTheme: Config.Theme? = when (theme) { "light" -> Config.Theme.LIGHT "dark" -> Config.Theme.DARK "system" -> Config.Theme.SYSTEM else -> null } if (miniappTheme != null) { configBuilder.setTheme(miniappTheme) } } if (extraUrlParams != null) { val map: Map = MapUtil.toMap(extraUrlParams) val stringMap: MutableMap = HashMap() for ((key, value) in map) stringMap[key] = value.toString() configBuilder.setExtraUrlParams(stringMap) } urlSuffix?.also { suffix -> configBuilder.setUrlSuffix(suffix) } if (colors != null) { configBuilder.setColors( colors.getString("primaryColor") ?: "", colors.getString("secondaryColor") ?: "", colors.getString("tertiaryColor") ?: "", ) } val animation = runCatching { PageAnimation.valueOf(pageAnimation ?: "") } .getOrDefault(PageAnimation.BOTTOM_TO_TOP) configBuilder.pageAnimation(animation) configBuilder.enableSplash(enableSplash) configBuilder.saveState(saveState) miniapp.setConfig(configBuilder.build()) miniapp.open(reactContext.currentActivity as FragmentActivity) } } @ReactMethod fun setAuthCode(appId: String, authCode: String) { Boxo.getMiniapp(appId) .setAuthCode(authCode) } @ReactMethod fun setAuthTokens(appId: String, tokens: ReadableMap) { Boxo.getMiniapp(appId) .setAuthTokens(MapUtil.toMap(tokens) as Map) } @ReactMethod fun closeMiniapp(appId: String) { handler.post { Boxo.getExistingMiniapp(appId)?.close() } } override fun handle( fragment: BoxoFragment, miniapp: Miniapp, customEvent: CustomEvent ) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) val event: WritableMap = Arguments.createMap() try { event.putInt("request_id", customEvent.requestId ?: 0) event.putString("type", customEvent.type) event.putString("error_type", customEvent.errorType) event.putMap( "payload", MapUtil.toWritableMap(customEvent.payload) ) params.putMap("custom_event", event) sendEvent(CUSTOM_EVENTS_EVENT_NAME, params) } catch (e: Exception) { e.printStackTrace() } } override fun onLaunch(miniapp: Miniapp) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onLaunch") sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onResume(miniapp: Miniapp) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onResume") sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onPause(miniapp: Miniapp) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onPause") sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onClose(miniapp: Miniapp) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onClose") sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onError(miniapp: Miniapp, message: String) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onError") params.putString("error", message) sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onUserInteraction(miniapp: Miniapp) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onUserInteraction") sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } override fun onAuth(fragment: BoxoFragment, miniapp: Miniapp, requiredFields: List) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) params.putString("lifecycle", "onAuth") val fields = Arguments.createArray() requiredFields.forEach { item -> fields.pushString(item) } params.putArray("required_fields", fields) sendEvent(MINIAPP_LIFECYCLE_EVENT_NAME, params) } @ReactMethod fun sendCustomEvent(map: ReadableMap) { try { val appId: String = map.getString("app_id")!! val customEventMap: ReadableMap = map.getMap("custom_event")!! val event = CustomEvent( customEventMap.getInt("request_id"), customEventMap.getString("type")!!, if (customEventMap.hasKey("error_type")) customEventMap.getString("error_type") else null, MapUtil.toMap(customEventMap.getMap("payload")) ) handler.post { Boxo.getExistingMiniapp(appId)?.sendEvent(event) } } catch (e: Exception) { e.printStackTrace() } } @ReactMethod fun sendPaymentEvent(map: ReadableMap) { try { val appId: String = map.getString("app_id")!! val paymentEvent: ReadableMap = map.getMap("payment_event")!! val data = PaymentData( paymentEvent.getString("transaction_token") ?: "", paymentEvent.getString("order_payment_id") ?: "", paymentEvent.getString("miniapp_order_id") ?: "", paymentEvent.getDouble("amount"), paymentEvent.getString("currency") ?: "", paymentEvent.getString("status") ?: "", paymentEvent.getString("hostapp_order_id") ?: "", paymentEvent.getMap("extra_params") ?.let { MapUtil.toMap(paymentEvent.getMap("extra_params")) } ?: mapOf() ) handler.post { Boxo.getExistingMiniapp(appId)?.sendPaymentResult(data) } } catch (e: Exception) { e.printStackTrace() } } @ReactMethod fun getMiniapps() { Boxo.getMiniapps(object : MiniappListCallback { override fun onSuccess(miniapps: List) { try { val array: Array?> = arrayOfNulls(miniapps.size) for (i in miniapps.indices) { val miniappData: MiniappData = miniapps[i] val data: MutableMap = HashMap() data["app_id"] = miniappData.appId data["name"] = miniappData.name data["category"] = miniappData.category data["logo"] = miniappData.logo data["description"] = miniappData.description array[i] = data } val params: MutableMap = HashMap() params["miniapps"] = array sendEvent( MINIAPP_LIST_EVENT_NAME, MapUtil.toWritableMap(params as Map) ) } catch (e: Exception) { e.printStackTrace() } } override fun onFailure(e: Exception) { val result: WritableMap = Arguments.createMap() result.putString("error", e.message) sendEvent(MINIAPP_LIST_EVENT_NAME, result) } }) } @ReactMethod fun hideMiniapps() { Boxo.hideMiniapps() } @ReactMethod fun logout() { Boxo.logout() } @ReactMethod fun addListener(eventName: String) { } @ReactMethod fun removeListeners(count: Int) { } private fun sendEvent(name: String, params: WritableMap) { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(name, params) } override fun handle( fragment: BoxoFragment, miniapp: Miniapp, paymentData: PaymentData ) { val params: WritableMap = Arguments.createMap() params.putString("app_id", miniapp.appId) val event: WritableMap = Arguments.createMap() try { event.putString("transaction_token", paymentData.transactionToken) event.putString("order_payment_id", paymentData.orderPaymentId) event.putString("miniapp_order_id", paymentData.miniappOrderId) event.putDouble("amount", paymentData.amount) event.putString("currency", paymentData.currency) event.putString("status", paymentData.status) event.putString("hostapp_order_id", paymentData.hostappOrderId) if (paymentData.extraParams != null) { event.putMap("extra_params", MapUtil.toWritableMap(paymentData.extraParams)) } params.putMap("payment_event", event) sendEvent(PAYMENT_EVENTS_EVENT_NAME, params) } catch (e: Exception) { e.printStackTrace() } } companion object { private const val CUSTOM_EVENTS_EVENT_NAME = "appboxo_custom_events" private const val PAYMENT_EVENTS_EVENT_NAME = "appboxo_payment_events" private const val MINIAPP_LIST_EVENT_NAME = "appboxo_miniapp_list" private const val MINIAPP_LIFECYCLE_EVENT_NAME = "appboxo_miniapp_lifecycle" } }