React Native 캡처
네이티브 모듈을 사용해 React Native 앱에서 캡처를 엽니다.
백엔드가 Journey 플로우와 앱의 URL 스킴을 사용하는 callbackUri로 Human Plugin에서 프로세스를 생성합니다. 네이티브 모듈이 응답의 link를 iOS에서는 ASWebAuthenticationSession, Android에서는 Custom Tabs로 엽니다. 캡처가 끝나면 모듈이 promise를 resolve하고 앱이 제어를 이어받습니다.
생성 응답의 Human Plugin id를 보관하세요. 백엔드가 이 값으로 결과를 읽습니다.
사전 요구 사항
- React Native 0.71 이상
- iOS용 Xcode 14 이상
- Android용 Android Studio
- 앱 고유의 URL 스킴(예:
myapp)
1. 스킴을 지정해 프로세스 생성
백엔드가 스킴을 callbackUri로 전송합니다.
{
"flow": "humantoken",
"clientId": "client-a",
"correlationId": "order-1001",
"credential": { "type": "DUI_TYPE_NG_BVN", "value": "<document number>" },
"callbackUri": "myapp://callback"
}
그리고 id와 link를 앱에 반환합니다.
2. TypeScript 브리지 생성
// src/modules/CaptureModule.ts
import { NativeModules } from 'react-native';
const { CaptureModule } = NativeModules;
if (!CaptureModule) {
throw new Error('CaptureModule not found. Make sure the native module is linked correctly.');
}
export const Capture = {
openSession: (url: string): Promise<string> => CaptureModule.openSession(url),
};
3. iOS 모듈 구현
- Objective-C 브리지
- Swift 구현
// ios/CaptureModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CaptureModule, NSObject)
RCT_EXTERN_METHOD(openSession:(NSString *)url
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
@end
// ios/CaptureModule.swift
import AuthenticationServices
@objc(CaptureModule)
class CaptureModule: NSObject, ASWebAuthenticationPresentationContextProviding {
private var session: ASWebAuthenticationSession?
@objc func openSession(_ url: String,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
guard let sessionURL = URL(string: url) else {
reject("INVALID_URL", "Invalid URL", nil)
return
}
DispatchQueue.main.async {
let newSession = ASWebAuthenticationSession(
url: sessionURL,
callbackURLScheme: "myapp"
) { callbackURL, error in
self.session = nil
if let callbackURL = callbackURL {
resolve(callbackURL.absoluteString)
} else {
reject("SESSION_ERROR", error?.localizedDescription ?? "Session cancelled", error)
}
}
newSession.presentationContextProvider = self
newSession.prefersEphemeralWebBrowserSession = true
newSession.start()
self.session = newSession
}
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else {
return ASPresentationAnchor()
}
return window
}
@objc static func requiresMainQueueSetup() -> Bool { return true }
}
4. Android 모듈 구현
android/app/build.gradle에 의존성을 추가합니다.
implementation("androidx.browser:browser:1.5.0")
// android/app/src/main/java/<your.package>/CaptureModule.kt
package com.yourapp
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import com.facebook.react.bridge.*
class CaptureModule(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private var pendingPromise: Promise? = null
override fun getName() = "CaptureModule"
@ReactMethod
fun openSession(url: String, promise: Promise) {
pendingPromise = promise
currentActivity?.runOnUiThread {
CustomTabsIntent.Builder().build()
.launchUrl(currentActivity!!, Uri.parse(url))
} ?: promise.reject("NO_ACTIVITY", "No current activity")
}
fun handleRedirectUrl(uri: Uri) {
if (uri.scheme == "myapp") {
pendingPromise?.resolve(uri.toString())
pendingPromise = null
}
}
}
// CapturePackage.kt
package com.yourapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class CapturePackage : ReactPackage {
override fun createNativeModules(ctx: ReactApplicationContext): List<NativeModule> =
listOf(CaptureModule(ctx))
override fun createViewManagers(ctx: ReactApplicationContext): List<ViewManager<*, *>> =
emptyList()
}
MainApplication.kt에 등록합니다.
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(CapturePackage())
}
MainActivity.kt에서 복귀를 처리합니다.
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
if (uri.scheme == "myapp") {
val module = reactInstanceManager
.currentReactContext
?.getNativeModule(CaptureModule::class.java)
module?.handleRedirectUrl(uri)
}
}
}
5. Android에 스킴 등록
AndroidManifest.xml에 추가합니다.
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<activity android:name=".MainActivity" ...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="callback" />
</intent-filter>
</activity>
6. 캡처 시작
import { Capture } from './modules/CaptureModule';
try {
await Capture.openSession(link);
// 캡처가 끝났습니다. 백엔드에 Human Plugin id로
// GET /v1/processes/{id}를 호출해 결과를 읽도록 요청하세요.
} catch {
// 사용자가 세션을 닫았습니다.
}
요약
| 파일 | 용도 |
|---|---|
src/modules/CaptureModule.ts | TypeScript 브리지 |
ios/CaptureModule.m | Objective-C 선언 |
ios/CaptureModule.swift | iOS: ASWebAuthenticationSession |
android/.../CaptureModule.kt | Android: Custom Tabs |
android/.../CapturePackage.kt | 패키지 등록 |
android/.../MainApplication.kt | 패키지 추가 |
android/.../MainActivity.kt | 복귀 처리 |
AndroidManifest.xml | 인텐트 필터 |