Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [6.0.0](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.2.0...v6.0.0) (2025-11-20)

### [5.0.2-rc.3](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.0.2-rc.2...v5.0.2-rc.3) (2025-07-22)

### [5.3.2](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.3.1...v5.3.2) (2025-11-13)

### [5.3.1](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.2.0...v5.3.1) (2025-10-28)


### Bug Fixes

* update loans SDK dependency to beta version for compatibility ([453385c](https://github.com/smallcase/react-native-smallcase-gateway/commit/453385c736ad57a85f255227dc87ef716e15c49a))

### [5.0.2-rc.3](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.0.2-rc.2...v5.0.2-rc.3) (2025-07-22)

## [5.3.0](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.2.0...v5.3.0) (2025-10-01)

### [5.0.2-rc.3](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.0.2-rc.2...v5.0.2-rc.3) (2025-07-22)
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ def kotlin_version = getExtOrDefault('kotlinVersion')
dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation 'com.smallcase.gateway:sdk:4.4.0'
implementation 'com.smallcase.loans:sdk:3.1.1'
implementation 'com.smallcase.gateway:sdk:5.0.0'
implementation 'com.smallcase.loans:sdk:4.0.0'
implementation "androidx.core:core-ktx:1.3.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@

// SCGatewayBridgeEmitter.kt
package com.reactnativesmallcasegateway

import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.smallcase.gateway.data.listeners.Notification
import com.smallcase.gateway.data.listeners.NotificationCenter
import com.smallcase.gateway.portal.ScgNotification
import com.smallcase.gateway.portal.SmallcaseGatewaySdk

class SCGatewayBridgeEmitter(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

companion object {
const val TAG = "SCGatewayBridgeEmitter"
}

private var notificationObserver: ((Notification) -> Unit)? = null

private val isListening: Boolean
get() = notificationObserver != null

init {
UiThreadUtil.runOnUiThread { startListening() }
}

override fun getName(): String = "SCGatewayBridgeEmitter"

override fun onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy()
if (isListening) {
notificationObserver?.let { observer ->
NotificationCenter.removeObserver(observer)
}
notificationObserver = null
Log.d(TAG, "Successfully cleaned up notification observer")
}
}

@ReactMethod
fun startListening(promise: Promise? = null) {
Log.d(TAG, "startListening called")

if (isListening) {
Log.d(TAG, "Already listening to events")
promise?.resolve("Already listening")
return
}

UiThreadUtil.runOnUiThread {
Log.d(TAG, "Starting listener on thread: ${Thread.currentThread().name}")

notificationObserver = { notification ->
Log.d(TAG, "Received notification: ${notification.name}")
processScgNotification(notification)
}

notificationObserver?.let { observer ->
NotificationCenter.addObserver(observer)
Log.d(TAG, "Successfully started listening for notifications")
promise?.resolve("Started listening successfully")
} ?: run {
promise?.reject("START_LISTENING_ERROR", "Failed to create observer")
}
}
}

@ReactMethod
fun stopListening(promise: Promise) {
if (!isListening) {
promise.resolve("Not listening")
return
}

UiThreadUtil.runOnUiThread {
notificationObserver?.let { observer ->
NotificationCenter.removeObserver(observer)
notificationObserver = null
promise.resolve("Stopped listening successfully")
} ?: run {
promise.resolve("No observer to remove")
}
}
}

private fun processScgNotification(notification: Notification) {
val jsonString = notification.userInfo?.get(ScgNotification.STRINGIFIED_PAYLOAD_KEY) as? String
if (jsonString == null) {
Log.e(TAG, "SCGatewayBridgeEmitter: Invalid notification object - expected JSON string")
return
}

sendEvent(SmallcaseGatewaySdk.SCG_NOTIFICATION_NAME, jsonString)
}

private fun sendEvent(eventName: String, jsonString: String) {
try {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, jsonString)
} else {
Log.w(TAG, "React context not active, cannot send event: $eventName")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to send event: $eventName", e)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SCLoansBridgeEmitter.kt
package com.reactnativesmallcasegateway

import android.util.Log
import com.facebook.react.bridge.*
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.smallcase.loans.core.external.ScLoan
import com.smallcase.loans.core.external.ScLoanNotification
import com.smallcase.loans.data.listeners.Notification
import com.smallcase.loans.data.listeners.NotificationCenter

class SCLoansBridgeEmitter(private val reactContext: ReactApplicationContext):
ReactContextBaseJavaModule(reactContext) {

companion object {
const val TAG = "SCLoansBridgeEmitter"
}

private var notificationObserver: ((Notification) -> Unit)? = null

private val isListening: Boolean
get() = notificationObserver != null

init {
UiThreadUtil.runOnUiThread {
startListening()
}
}

override fun getName(): String = "SCLoansBridgeEmitter"

override fun onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy()
if (isListening) {
notificationObserver?.let { observer ->
NotificationCenter.removeObserver(observer)
}
notificationObserver = null
Log.d(TAG, "Successfully cleaned up notification observer")
}
}

@ReactMethod
fun startListening(promise: Promise ? = null) {
Log.d(TAG, "startListening called")

if (isListening) {
Log.d(TAG, "Already listening to events")
promise?.resolve("Already listening")
return
}

UiThreadUtil.runOnUiThread {
Log.d(TAG, "Starting listener on thread: ${Thread.currentThread().name}")

notificationObserver = { notification ->
Log.d(TAG, "Received notification: ${notification.name}")
processScLoansNotification(notification)
}

notificationObserver?.let { observer ->
NotificationCenter.addObserver(observer)
Log.d(TAG, "Successfully started listening for notifications")
promise?.resolve("Started listening successfully")
} ?: run {
promise?.reject("START_LISTENING_ERROR", "Failed to create observer")
}
}
}

@ReactMethod
fun stopListening(promise: Promise) {
if (!isListening) {
promise.resolve("Not listening")
return
}

UiThreadUtil.runOnUiThread {
notificationObserver?.let { observer ->
NotificationCenter.removeObserver(observer)
notificationObserver = null
promise.resolve("Stopped listening successfully")
} ?: run {
promise.resolve("No observer to remove")
}
}
}

private fun processScLoansNotification(notification: Notification) {
val jsonString =
notification.userInfo?.get(ScLoanNotification.STRINGIFIED_PAYLOAD_KEY) as? String
if (jsonString == null) {
Log.e(
TAG,
"SCLoansBridgeEmitter: Invalid notification object - expected JSON string"
)
return
}

sendEvent(ScLoan.SCLOANS_NOTIFICATION_NAME, jsonString)
}

private fun sendEvent(eventName: String, jsonString: String) {
try {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, jsonString)
} else {
Log.w(TAG, "React context not active, cannot send event: $eventName")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to send event: $eventName", e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte

@ReactMethod
fun init(sdkToken: String, promise: Promise) {
Log.d(TAG, "init: start")

val initReq = InitRequest(sdkToken)
SmallcaseGatewaySdk.init(authRequest = initReq, gatewayInitialisationListener = object : DataListener<InitialisationResponse> {
Expand All @@ -85,7 +84,6 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte

@ReactMethod
fun triggerTransaction(transactionId: String, utmParams: ReadableMap?, brokerList: ReadableArray?, promise: Promise) {
Log.d(TAG, "triggerTransaction: start")

var safeBrokerList = listOf<String>()

Expand Down Expand Up @@ -161,7 +159,6 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte

@ReactMethod
fun launchSmallplug(targetEndpoint: String, params: String, promise: Promise) {
Log.d(TAG, "launchSmallplug: start")

SmallcaseGatewaySdk.launchSmallPlug(currentActivity!!, SmallplugData(targetEndpoint, params), object : SmallPlugResponseListener {
override fun onFailure(errorCode: Int, errorMessage: String) {
Expand All @@ -179,7 +176,14 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte
}

@ReactMethod
fun launchSmallplugWithBranding(targetEndpoint: String, params: String, readableMap: ReadableMap?, promise: Promise) {
fun launchSmallplugWithBranding(targetEndpoint: String, params: String, headerColor: String?, headerOpacity: Double?, backIconColor: String?, backIconOpacity: Double?, promise: Promise
) {
val readableMap = Arguments.createMap().apply {
headerColor?.let { putString("headerColor", it) }
headerOpacity?.let { putDouble("headerOpacity", it) }
backIconColor?.let { putString("backIconColor", it) }
backIconOpacity?.let { putDouble("backIconOpacity", it) }
}

fun getColorValue(value: Any?, defaultValue: String): String {
return when (value) {
Expand All @@ -191,12 +195,11 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte
}
}
}
Log.d(TAG, "launchSmallplugWithBranding: start")

var partnerProps: SmallplugPartnerProps? = SmallplugPartnerProps(headerColor = "#2F363F", backIconColor = "ffffff")

try {
partnerProps = readableMap?.toHashMap()?.let { map ->
partnerProps = readableMap.toHashMap().let { map ->
val hc = getColorValue(map["headerColor"], "#2F363F")
val ho = map["headerOpacity"]?.let { if (it is Double) it else 1.0 } ?: 1.0
val bc = getColorValue(map["backIconColor"], "#ffffff")
Expand All @@ -206,25 +209,21 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte
} catch (e: Throwable) {
}


SmallcaseGatewaySdk.launchSmallPlug(currentActivity!!, SmallplugData(targetEndpoint, params), object : SmallPlugResponseListener {
override fun onFailure(errorCode: Int, errorMessage: String) {
val err = createErrorJSON(errorCode, errorMessage, null)

promise.reject("error", err)
}

override fun onSuccess(smallPlugResult: SmallPlugResult) {
val res = resultToWritableMap(smallPlugResult)
promise.resolve(res)
}

}, partnerProps)
}

@ReactMethod
fun archiveSmallcase(iscid: String, promise: Promise) {
Log.d(TAG, "markSmallcaseArchive: start")

SmallcaseGatewaySdk.markSmallcaseArchived(iscid, object : DataListener<SmallcaseGatewayDataResponse> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import com.facebook.react.uimanager.ViewManager

class SmallcaseGatewayPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(SmallcaseGatewayModule(reactContext))
return listOf(
SmallcaseGatewayModule(reactContext),
SCGatewayBridgeEmitter(reactContext),
SCLoansBridgeEmitter(reactContext)
)
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
Expand Down
20 changes: 20 additions & 0 deletions ios/SCGatewayBridgeEmitter.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// SCGatewayBridgeEmitter.m
// SCGateway
//
// Created by Dhruv Porwal
// Copyright © 2025 smallcase. All rights reserved.
//

#import <React/RCTBridgeModule.h>
#import "SmallcaseGateway-Bridging-Header.h"

@interface RCT_EXTERN_REMAP_MODULE(SCGatewayBridgeEmitter, SCGatewayEmitter, NSObject)

RCT_EXTERN_METHOD(startListening:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(stopListening:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)

@end
Loading