Native Module iOS

How to Create a Native Module in iOS

This detailed guide explains how to create a native module for the iOS platform within a React Native application. The process follows the step-by-step instructions from the official documentation.

1. Open the iOS Project in Xcode

You can include this in your package.json under the scripts section:

"scripts": {
    "open:ios": "xed ios"
},

This script ensures that the Expo module opens correctly in Xcode.

2. Create the Native Module

Create a file named CafSmartAuthBridgeModule.swift in the directory modules/caf-smart-auth-react-native/ios/. This file will contain the class that implements the native module.

Key Functions of the CafSmartAuthBridgeModule Class

  • build: Creates and configures an instance of the CafSmartAuthSdk using the provided parameters.

  • setupListener: Sets up a listener to monitor the status of authentication operations.

  • startSmartAuth: A method exposed to React Native to initiate smart authentication.

Example Implementation of the CafSmartAuthBridgeModule.swift File

import ExpoModulesCore
import CafSmartAuth

private struct CafSmartAuthBridgeConstants {
    static let moduleName: String = "CafSmartAuthBridgeModule"
    static let startSmartAuth: String = "startSmartAuth"
    
    static let cafSmartAuthSuccessEvent: String = "CafSmartAuth_Success"
    static let cafSmartAuthPendingEvent: String = "CafSmartAuth_Pending"
    static let cafSmartAuthErrorEvent: String = "CafSmartAuth_Error"
    static let cafSmartAuthCancelEvent: String = "CafSmartAuth_Cancel"
    static let cafSmartAuthLoadingEvent: String = "CafSmartAuth_Loading"
    static let cafSmartAuthLoadedEvent: String = "CafSmartAuth_Loaded"
    
    static let isAuthorized: String = "isAuthorized"
    static let attestation: String = "attestation"
    static let errorMessage: String = "message"
    static let isCancelled: String = "isCancelled"
    static let isLoading: String = "isLoading"
    static let isLoaded: String = "isLoaded"

    static let cafFilterNaturalIndex: Int = 0
}

public class CafSmartAuthBridgeModule: Module {
    // Each module class must implement the definition function. The definition consists of components
    // that describes the module's functionality and behavior.
    // See https://docs.expo.dev/modules/module-api for more details about available components.
    
    private var smartAuth: CafSmartAuthSdk?
    
    public func definition() -> ModuleDefinition {
        // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
        // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
        // The module will be accessible from `requireNativeModule('CafSmartAuthBridgeModule')` in JavaScript.
        Name(CafSmartAuthBridgeConstants.moduleName)
        
        // Defines event names that the module can send to JavaScript.
        Events(
            CafSmartAuthBridgeConstants.cafSmartAuthSuccessEvent,
            CafSmartAuthBridgeConstants.cafSmartAuthPendingEvent,
            CafSmartAuthBridgeConstants.cafSmartAuthErrorEvent,
            CafSmartAuthBridgeConstants.cafSmartAuthCancelEvent,
            CafSmartAuthBridgeConstants.cafSmartAuthLoadingEvent,
            CafSmartAuthBridgeConstants.cafSmartAuthLoadedEvent
        )
        
        // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
        Function(CafSmartAuthBridgeConstants.startSmartAuth) { (mfaToken: String, faceAuthToken: String, personId: String, policyId: String, settings: String?) -> Void in
            DispatchQueue.main.async {
               
                self.smartAuth = self.build(
                    mfaToken: mfaToken, faceAuthToken: faceAuthToken, settings: CafSmartAuthBridgeSettings().parseJson(settings: settings)
                )
                
                self.smartAuth?.verifyPolicy(personID: personId, policyId: policyId, listener: self.setupListener())
            }
        }
    }
    
    private func build(
        mfaToken: String,
        faceAuthToken: String,
        settings: CafSmartAuthBridgeSettingsModel?
    ) -> CafSmartAuthSdk {
        let builder = CafSmartAuthSdk.CafBuilder(mobileToken: mfaToken)
        
        if let stage = settings?.stage, let cafStage = CAFStage(rawValue: stage) {
            _ = builder.setStage(cafStage)
        }
        
        let filter: CafFilterStyle = {
            if let faceSettings = settings?.faceAuthenticationSettings, faceSettings.filter == CafSmartAuthBridgeConstants.cafFilterNaturalIndex {
                return .natural
            }
            return .lineDrawing
        }()
        
        _ = builder.setLivenessSettings(
            CafFaceLivenessSettings(
                faceLivenessToken: faceAuthToken,
                useLoadingScreen: settings?.faceAuthenticationSettings?.loadingScreen ?? false,
                filter: filter
            )
        )
        
        return builder.build()
    }
    
    
    private func setupListener() -> CafVerifyPolicyListener {
        return { result in
            switch result {
            case .onSuccess(let response):
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthSuccessEvent, [
                    CafSmartAuthBridgeConstants.isAuthorized: response.isAuthorized,
                    CafSmartAuthBridgeConstants.attestation: response.attestation
                ])
                self.smartAuth = nil
                
            case .onPending(let response):
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthPendingEvent, [
                    CafSmartAuthBridgeConstants.isAuthorized: response.isAuthorized,
                    CafSmartAuthBridgeConstants.attestation: response.attestation
                ])
                
            case .onError(let error):
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthErrorEvent, [
                    CafSmartAuthBridgeConstants.errorMessage: error.localizedDescription
                ])
                self.smartAuth = nil
                
            case .onCanceled(_):
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthCancelEvent, [
                    CafSmartAuthBridgeConstants.isCancelled: true
                ])
                self.smartAuth = nil
                
            case .onLoading:
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthLoadingEvent, [
                    CafSmartAuthBridgeConstants.isLoading: true
                ])
                
            case .onLoaded:
                self.sendEvent(CafSmartAuthBridgeConstants.cafSmartAuthLoadedEvent, [
                    CafSmartAuthBridgeConstants.isLoaded: true
                ])
            }
        }
    }
}

3. Create the CafSmartAuthBridgeSettings.swift File

This file will handle the interpretation of data sent from React Native to the native module.

Example Implementation:

import CafSmartAuth

internal struct CafFaceAuthenticationSettingsModel: Decodable {
    let loadingScreen: Bool?
    let filter: Int?
}

internal struct CafSmartAuthBridgeSettingsModel: Decodable {
    let stage: Int?
    let faceAuthenticationSettings: CafFaceAuthenticationSettingsModel?
}

internal class CafSmartAuthBridgeSettings {
  internal func parseJson(settings: String) -> CafSmartAuthBridgeSettingsModel? {
    guard let data = settings.data(using: .utf8) else {
      return nil
    }
    
    do {
      let decoder = JSONDecoder()
      let parsedSettings = try decoder.decode(CafSmartAuthBridgeSettingsModel.self, from: data)
      
      return parsedSettings
    } catch {
      return nil
    }
  }
}

Last updated

Was this helpful?