diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e8addacc..648c8747 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,17 +2,19 @@ name: publish to npm on: [workflow_dispatch] +permissions: + id-token: write # Required for OIDC + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v6 with: - node-version: "14.x" + node-version: "25.x" registry-url: "https://registry.npmjs.org" - run: npm install - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f8092b79..adfe812f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ 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. +## [7.0.0](https://github.com/smallcase/react-native-smallcase-gateway/compare/v6.0.0...v7.0.0) (2026-01-05) + + +### Features + +* standardize smallplug response structure across iOS and Android ([0d82229](https://github.com/smallcase/react-native-smallcase-gateway/commit/0d82229b10fdd4eb67bd2866ca14b1972942e46d)) + ### [6.0.1](https://github.com/smallcase/react-native-smallcase-gateway/compare/v6.0.0...v6.0.1) (2025-12-06) ## [6.0.0](https://github.com/smallcase/react-native-smallcase-gateway/compare/v5.2.0...v6.0.0) (2025-11-20) diff --git a/android/build.gradle b/android/build.gradle index 1438442a..8e4e4dc2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -149,7 +149,7 @@ def kotlin_version = getExtOrDefault('kotlinVersion') dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation 'com.smallcase.gateway:sdk:5.0.1' + implementation 'com.smallcase.gateway:sdk:6.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" diff --git a/android/src/main/java/com/reactnativesmallcasegateway/SmallcaseGatewayModule.kt b/android/src/main/java/com/reactnativesmallcasegateway/SmallcaseGatewayModule.kt index da28ad8b..a2fa9e9e 100644 --- a/android/src/main/java/com/reactnativesmallcasegateway/SmallcaseGatewayModule.kt +++ b/android/src/main/java/com/reactnativesmallcasegateway/SmallcaseGatewayModule.kt @@ -161,9 +161,8 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte fun launchSmallplug(targetEndpoint: String, params: String, promise: Promise) { SmallcaseGatewaySdk.launchSmallPlug(currentActivity!!, SmallplugData(targetEndpoint, params), object : SmallPlugResponseListener { - override fun onFailure(errorCode: Int, errorMessage: String) { + override fun onFailure(errorCode: Int, errorMessage: String) { val err = createErrorJSON(errorCode, errorMessage, null) - promise.reject("error", err) } @@ -469,15 +468,36 @@ class SmallcaseGatewayModule(reactContext: ReactApplicationContext) : ReactConte writableMap.putBoolean("success", result.success) writableMap.putString("smallcaseAuthToken", result.smallcaseAuthToken) + val dataMap = Arguments.createMap() + userInfoToWritableMap(result.userInfo)?.let { + dataMap.putMap("userInfo", it) + } + + if (dataMap.keySetIterator().hasNextKey()) { + writableMap.putMap("data", dataMap) + } + return writableMap } - private fun createErrorJSON(errorCode: Int?, errorMessage: String?, data: String?): WritableMap { + private fun userInfoToWritableMap(userInfo: UserInfo?): WritableMap? { + if (userInfo == null) return null + + val map = Arguments.createMap() + map.putString("number", userInfo.number) + map.putString("countryCode", userInfo.countryCode) + return map + } + + private fun createErrorJSON(errorCode: Int?, errorMessage: String?, data: Any?): WritableMap { val errObj = Arguments.createMap() errorCode?.let { errObj.putInt("errorCode", it) } errorMessage?.let { errObj.putString("errorMessage", it) } - data?.let { errObj.putString("data", it) } + when (data) { + is String -> errObj.putString("data", data) + is WritableMap -> errObj.putMap("data", data) + } return errObj } diff --git a/ios/SmallcaseGateway.m b/ios/SmallcaseGateway.m index 89413165..554efaf3 100644 --- a/ios/SmallcaseGateway.m +++ b/ios/SmallcaseGateway.m @@ -333,40 +333,51 @@ @interface RCT_EXTERN_MODULE(SmallcaseGateway, NSObject) [SCGateway.shared launchSmallPlugWithPresentingController:[[[UIApplication sharedApplication] keyWindow] rootViewController] smallplugData:smallplugData completion:^(id smallplugResponse, NSError * error) { - NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; - if (error != nil) { NSLog(@"%@", error.domain); double delayInSeconds = 0.5; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; - [responseDict setValue:[NSNumber numberWithBool:false] forKey:@"success"]; - [responseDict setValue:[NSNumber numberWithInteger:error.code] forKey:@"errorCode"]; - [responseDict setValue:error.domain forKey:@"error"]; - - resolve(responseDict); - return; + NSMutableDictionary *errorDict = [[NSMutableDictionary alloc] init]; + [errorDict setValue:[NSNumber numberWithInteger:error.code] forKey:@"errorCode"]; + [errorDict setValue:error.domain forKey:@"errorMessage"]; + + reject(@"error", error.domain, error); }); } else { - - if ([smallplugResponse isKindOfClass: [NSString class]]) { - NSLog(@"%@", smallplugResponse); - - [responseDict setValue:[NSNumber numberWithBool: true] forKey:@"success"]; - [responseDict setValue:smallplugResponse forKey:@"smallcaseAuthToken"]; + if ([smallplugResponse isKindOfClass:[SmallPlugResult class]]) { + SmallPlugResult *result = (SmallPlugResult *)smallplugResponse; + + NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; + [responseDict setValue:[NSNumber numberWithBool:true] forKey:@"success"]; + + if (result.smallcaseAuthToken) { + [responseDict setValue:result.smallcaseAuthToken forKey:@"smallcaseAuthToken"]; + } + + // Add userInfo inside data object if available + if (result.userInfo) { + NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *userInfoDict = [[NSMutableDictionary alloc] init]; + + if (result.userInfo.number) { + [userInfoDict setValue:result.userInfo.number forKey:@"number"]; + } + if (result.userInfo.countryCode) { + [userInfoDict setValue:result.userInfo.countryCode forKey:@"countryCode"]; + } + + [dataDict setValue:userInfoDict forKey:@"userInfo"]; + [responseDict setValue:dataDict forKey:@"data"]; + } double delayInSeconds = 0.5; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - resolve(responseDict); - return; - }); } } - }]; }); } @@ -405,40 +416,51 @@ @interface RCT_EXTERN_MODULE(SmallcaseGateway, NSObject) [SCGateway.shared launchSmallPlugWithPresentingController:[[[UIApplication sharedApplication] keyWindow] rootViewController] smallplugData:smallplugData smallplugUiConfig:smallplugUiConfig completion:^(id smallplugResponse, NSError * error) { - NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; - if (error != nil) { NSLog(@"%@", error.domain); double delayInSeconds = 0.5; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; - [responseDict setValue:[NSNumber numberWithBool:false] forKey:@"success"]; - [responseDict setValue:[NSNumber numberWithInteger:error.code] forKey:@"errorCode"]; - [responseDict setValue:error.domain forKey:@"error"]; - - resolve(responseDict); - return; + NSMutableDictionary *errorDict = [[NSMutableDictionary alloc] init]; + [errorDict setValue:[NSNumber numberWithInteger:error.code] forKey:@"errorCode"]; + [errorDict setValue:error.domain forKey:@"errorMessage"]; + + reject(@"error", error.domain, error); }); } else { - - if ([smallplugResponse isKindOfClass: [NSString class]]) { - NSLog(@"%@", smallplugResponse); - - [responseDict setValue:[NSNumber numberWithBool: true] forKey:@"success"]; - [responseDict setValue:smallplugResponse forKey:@"smallcaseAuthToken"]; + if ([smallplugResponse isKindOfClass:[SmallPlugResult class]]) { + SmallPlugResult *result = (SmallPlugResult *)smallplugResponse; + + NSMutableDictionary *responseDict = [[NSMutableDictionary alloc] init]; + [responseDict setValue:[NSNumber numberWithBool:true] forKey:@"success"]; + + if (result.smallcaseAuthToken) { + [responseDict setValue:result.smallcaseAuthToken forKey:@"smallcaseAuthToken"]; + } + + // Add userInfo inside data object if available + if (result.userInfo) { + NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *userInfoDict = [[NSMutableDictionary alloc] init]; + + if (result.userInfo.number) { + [userInfoDict setValue:result.userInfo.number forKey:@"number"]; + } + if (result.userInfo.countryCode) { + [userInfoDict setValue:result.userInfo.countryCode forKey:@"countryCode"]; + } + + [dataDict setValue:userInfoDict forKey:@"userInfo"]; + [responseDict setValue:dataDict forKey:@"data"]; + } double delayInSeconds = 0.5; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - resolve(responseDict); - return; - }); } } - }]; }); } diff --git a/package-lock.json b/package-lock.json index 71f21e5c..513b3153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-native-smallcase-gateway", - "version": "6.0.1", + "version": "7.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-native-smallcase-gateway", - "version": "6.0.1", + "version": "7.0.0", "license": "MIT", "dependencies": { "standard-version": "^9.5.0" diff --git a/package.json b/package.json index 5f3d6010..5733ea4e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-smallcase-gateway", "title": "React Native Smallcase Gateway", - "version": "6.0.1", + "version": "7.0.0", "description": "smallcase gateway bindings for react native", "main": "src/index.js", "files": [ @@ -67,8 +67,6 @@ "jest": "^28.1.1", "pod-install": "^0.1.0", "prettier": "^2.0.5", - "react": "17.0.2", - "react-native": "0.68.2", "react-native-builder-bob": "^0.18.3", "release-it": "^15.0.0", "standard-version": "^9.5.0", diff --git a/react-native-smallcase-gateway.podspec b/react-native-smallcase-gateway.podspec index 1dc584d6..97040b27 100644 --- a/react-native-smallcase-gateway.podspec +++ b/react-native-smallcase-gateway.podspec @@ -33,6 +33,6 @@ Pod::Spec.new do |s| s.dependency "ReactCommon/turbomodule/core" end - s.dependency 'SCGateway', '6.1.1' + s.dependency 'SCGateway', '7.0.0' s.dependency 'SCLoans', '6.0.2' end diff --git a/smart_investing_react_native/android/gradle.properties b/smart_investing_react_native/android/gradle.properties index e0b6dc18..92675fe8 100644 --- a/smart_investing_react_native/android/gradle.properties +++ b/smart_investing_react_native/android/gradle.properties @@ -32,7 +32,8 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # your application. You should enable this flag either if you want # to write custom TurboModules/Fabric components OR use libraries that # are providing them. -newArchEnabled=true +newArchEnabled=false +fabricEnabled=false # Use this property to enable or disable the Hermes JS engine. # If set to false, you will be using JSC instead. diff --git a/smart_investing_react_native/app/apis/Functions.tsx b/smart_investing_react_native/app/apis/Functions.tsx index 186efaab..159d6a2a 100644 --- a/smart_investing_react_native/app/apis/Functions.tsx +++ b/smart_investing_react_native/app/apis/Functions.tsx @@ -422,14 +422,35 @@ interface UIConfig { backIconOpacity: number; } +export interface UserInfo { + phoneNumber: string; + phoneCountryCode: string; +} + +export interface SmallplugRes { + success: boolean; + smallcaseAuthToken: string; + data?: { + userInfo?: UserInfo; + }; +} + +export interface SmallplugError { + errorCode: number; + errorMessage: string; + data?: { + userInfo?: UserInfo; + }; +} + async function launchSmallPlug( targetEndpoint: any, params: any, uiConfig: UIConfig, -) { +): Promise { try { console.log(`launchSmallPlug start ${JSON.stringify(uiConfig)}`); - const res = await SmallcaseGateway.launchSmallplugWithBranding( + const res: any = await SmallcaseGateway.launchSmallplugWithBranding( targetEndpoint, params, uiConfig.headerColor ?? '', @@ -438,12 +459,14 @@ async function launchSmallPlug( uiConfig.backIconOpacity, ); console.log(`launch dm res -> ${JSON.stringify(res)}`); - alert('Launch Smallplug Success', JSON.stringify(res)); + await alert('Launch Smallplug Success', JSON.stringify(res)); + return res; } catch (error) { console.log( 'Launch Smallplug error stringified - ' + JSON.stringify(error), ); - alert('Launch Smallplug Error', getErrorString(error)); + await alert('Launch Smallplug Error', getErrorString(error)); + throw error; } } diff --git a/smart_investing_react_native/app/screens/SmtScreen.tsx b/smart_investing_react_native/app/screens/SmtScreen.tsx index f4ddafae..dee1fe83 100644 --- a/smart_investing_react_native/app/screens/SmtScreen.tsx +++ b/smart_investing_react_native/app/screens/SmtScreen.tsx @@ -3,8 +3,9 @@ import {Button, TextInput, View} from 'react-native'; import {launchSmallPlug} from '../apis/Functions'; const SmtScreen = () => { - const [targetEndpoint, onChangeTargetEndpoint] = - React.useState(null); + const [targetEndpoint, onChangeTargetEndpoint] = React.useState< + string | null + >(null); const [params, onChangeParams] = React.useState(null); const [headerColor, onChangeHeaderColor] = React.useState( null, @@ -15,8 +16,9 @@ const SmtScreen = () => { const [backIconColor, onChangeBackIconColor] = React.useState( null, ); - const [backIconOpacity, onChangeBackIconOpacity] = - React.useState(null); + const [backIconOpacity, onChangeBackIconOpacity] = React.useState< + string | null + >(null); return ( { }} />