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
25 changes: 13 additions & 12 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ export {
SelfAppBuilder,
bigIntToString,
brutforceSignatureAlgorithmDsc,
buildSMT,
calculateUserIdentifierHash,
findStartPubKeyIndex,
formatEndpoint,
formatMrz,
genAndInitMockPassportData,
genMockIdDoc,
genMockIdDocAndInitDataParsing,
generateCircuitInputsDSC,
generateCircuitInputsRegister,
generateCircuitInputsVCandDisclose,
Expand All @@ -69,28 +73,17 @@ export {
getLeafCscaTree,
getLeafDscTree,
getSKIPEM,
getSolidityPackedUserContextData,
getUniversalLink,
hashEndpointWithScope,
initElliptic,
initPassportDataParsing,
parseCertificateSimple,
parseDscCertificateData,
genMockIdDoc,
genMockIdDocAndInitDataParsing,
buildSMT,
calculateUserIdentifierHash,
getSolidityPackedUserContextData,
stringToBigInt,
} from './src/utils/index.js';

export {
prepareAadhaarRegisterTestData,
prepareAadhaarDiscloseTestData,
prepareAadhaarRegisterData,
} from './src/utils/aadhaar/mockData.js';
export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';
export { createSelector } from './src/utils/aadhaar/constants.js';

// Hash utilities
export {
customHasher,
Expand All @@ -99,3 +92,11 @@ export {
hash,
packBytesAndPoseidon,
} from './src/utils/hash.js';

export { generateTestData, testCustomData } from './src/utils/aadhaar/utils.js';

export {
prepareAadhaarDiscloseTestData,
prepareAadhaarRegisterData,
prepareAadhaarRegisterTestData,
} from './src/utils/aadhaar/mockData.js';
5 changes: 5 additions & 0 deletions packages/mobile-sdk-alpha/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ android {
exclude 'META-INF/androidx.exifinterface_exifinterface.version'
pickFirst '**/libc++_shared.so'
pickFirst '**/libjsc.so'
pickFirst 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
}
}

Expand Down Expand Up @@ -155,4 +156,8 @@ dependencies {
implementation 'androidx.core:core-ktx:1.12.0'

implementation 'com.github.mhshams:jnbis:2.0.2'

// QR Code scanning dependencies
implementation 'com.google.zxing:core:3.5.2'
implementation 'com.google.zxing:android-core:3.3.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ class RNSelfPassportReaderPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(
RNSelfPassportReaderModule(reactContext),
SelfMRZScannerModule(reactContext)
SelfMRZScannerModule(reactContext),
SelfQRScannerModule(reactContext)
)
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(SelfOCRViewManager(reactContext))
return listOf(
SelfOCRViewManager(reactContext),
SelfQRScannerViewManager(reactContext)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

package com.selfxyz.selfSDK

import androidx.fragment.app.FragmentActivity
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import android.view.ViewGroup
import android.view.View
import android.widget.FrameLayout
import com.selfxyz.selfSDK.ui.QrCodeScannerFragment

class SelfQRScannerModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext), QrCodeScannerFragment.QRCodeScannerCallback {
override fun getName() = "SelfQRScannerModule"

private var scanPromise: Promise? = null
private var currentContainer: FrameLayout? = null
private var currentFragment: QrCodeScannerFragment? = null

@ReactMethod
fun startScanning(promise: Promise) {
scanPromise = promise
val activity = reactApplicationContext.currentActivity as? FragmentActivity
if (activity == null) {
promise.reject("E_NO_ACTIVITY", "No FragmentActivity found")
return
}

activity.runOnUiThread {
try {
val container = FrameLayout(activity)
// just using view.generateViewId() doesn't work.
val containerId = generateUnusedId(activity.window.decorView as ViewGroup)
container.id = containerId

container.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)

container.isFocusable = true
container.isFocusableInTouchMode = true
container.setBackgroundColor(android.graphics.Color.BLACK)

activity.addContentView(container, ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
))

val fragment = QrCodeScannerFragment(this@SelfQRScannerModule)

// Store references for cleanup
currentContainer = container
currentFragment = fragment

activity.supportFragmentManager
.beginTransaction()
.replace(containerId, fragment)
.commitNow()

} catch (e: Exception) {
android.util.Log.e("SelfQRScannerModule", "Error in startScanning", e)
promise.reject("E_SCANNING_ERROR", e.message, e)
}
}
}

override fun onQRData(data: String) {
scanPromise?.resolve(data)
scanPromise = null
cleanup()
}

override fun onError(e: Exception) {
scanPromise?.reject("E_QR_SCAN_ERROR", e.message, e)
scanPromise = null
cleanup()
}

private fun cleanup() {
val activity = reactApplicationContext.currentActivity as? FragmentActivity
activity?.runOnUiThread {
currentFragment?.let { fragment ->
activity.supportFragmentManager
.beginTransaction()
.remove(fragment)
.commit()
}
currentContainer?.let { container ->
(container.parent as? ViewGroup)?.removeView(container)
}
currentContainer = null
currentFragment = null
}
}

private fun generateUnusedId(parent: ViewGroup): Int {
var id = View.generateViewId()
while (parent.findViewById<View>(id) != null) {
id = View.generateViewId()
}
return id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// SPDX-FileCopyrightText: 2025 Social Connect Labs, Inc.
// SPDX-License-Identifier: BUSL-1.1
// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE.

package com.selfxyz.selfSDK

import android.view.Choreographer
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.FragmentActivity
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactPropGroup
import com.facebook.react.uimanager.events.RCTEventEmitter
import com.selfxyz.selfSDK.ui.QrCodeScannerFragment

class SelfQRScannerViewManager(
open val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>(), QrCodeScannerFragment.QRCodeScannerCallback {
private var propWidth: Int? = null
private var propHeight: Int? = null
private var reactNativeViewId: Int? = null

override fun getName() = REACT_CLASS

/**
* Return a FrameLayout which will later hold the Fragment
*/
override fun createViewInstance(reactContext: ThemedReactContext) =
FrameLayout(reactContext)

/**
* Map the "create" command to an integer
*/
override fun getCommandsMap() = mapOf(
"create" to COMMAND_CREATE,
"destroy" to COMMAND_DESTROY
)

/**
* Handle "create" command (called from JS) and call createFragment method
*/
override fun receiveCommand(
root: FrameLayout,
commandId: String,
args: ReadableArray?
) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)

when (commandId.toInt()) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
COMMAND_DESTROY -> destroyFragment(root, reactNativeViewId)
}
}

override fun receiveCommand(
root: FrameLayout,
commandId: Int,
args: ReadableArray?
) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)

when (commandId) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
COMMAND_DESTROY -> destroyFragment(root, reactNativeViewId)
}
}

@ReactPropGroup(names = ["width", "height"], customType = "Style")
fun setStyle(view: FrameLayout, index: Int, value: Int) {
if (index == 0) propWidth = value
if (index == 1) propHeight = value
}

/**
* Replace your React Native view with a custom fragment
*/
private fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
this.reactNativeViewId = reactNativeViewId
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)

val qrScannerFragment = QrCodeScannerFragment(this)
val activity = reactContext.currentActivity as FragmentActivity
activity.supportFragmentManager
.beginTransaction()
.replace(reactNativeViewId, qrScannerFragment, reactNativeViewId.toString())
.commit()
}

private fun destroyFragment(root: FrameLayout, reactNativeViewId: Int) {
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)

val activity = reactContext.currentActivity as FragmentActivity
val qrScannerFragment = activity.supportFragmentManager.findFragmentByTag(reactNativeViewId.toString())
qrScannerFragment?.let {
activity.supportFragmentManager
.beginTransaction()
.remove(it)
.commit()
}
}


private fun setupLayout(view: View) {
Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}

/**
* Layout all children properly
*/
private fun manuallyLayoutChildren(view: View) {
// propWidth and propHeight coming from react-native props
val width = requireNotNull(propWidth)
val height = requireNotNull(propHeight)

view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))

view.layout(0, 0, width, height)
}

companion object {
private const val REACT_CLASS = "SelfQRScannerViewManager"
private const val COMMAND_CREATE = 1
private const val COMMAND_DESTROY = 2
private const val SUCCESS_EVENT = "onQRCodeReadResult"
private const val FAILURE_EVENT = "onQRCodeReadError"
}


override fun onQRData(data: String) {
val event = Arguments.createMap()
event.putString("data", data)
reactContext
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(this.reactNativeViewId!!, SUCCESS_EVENT, event)
}

override fun onError(e: Exception) {
val event = Arguments.createMap()
event.putString("errorMessage", "Something went wrong scanning the QR Code")
event.putString("error", e.toString())
event.putString("stackTrace", e.stackTraceToString())
reactContext
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(this.reactNativeViewId!!, FAILURE_EVENT, event)
}

override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
SUCCESS_EVENT to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onQRData"
)
),
FAILURE_EVENT to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onError"
)
)
)
}
}
Loading
Loading