@smileid/usesmileid_huawei_face 12.0.2 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ Full release notes: [Smile ID docs](https://docs.usesmileid.com/developer-resources/sdks/mobile/release-notes/react-native-expo).
2
+
3
+ ## 12.1.0
4
+
5
+ See the repository CHANGELOG for the 12.1.0 release notes.
6
+
7
+
1
8
  ## 12.0.2
2
9
 
3
10
  See the repository CHANGELOG for the 12.0.2 release notes.
@@ -17,7 +17,7 @@ plugins {
17
17
  }
18
18
 
19
19
  group = 'com.usesmileid.huawei.face'
20
- version = '12.0.2'
20
+ version = '12.1.0'
21
21
 
22
22
  // HMS ML is published only to Huawei's own Maven repository — none of the
23
23
  // com.huawei.hms:ml-computer-vision-* artifacts exist on Maven Central. Expo
@@ -56,7 +56,7 @@ android {
56
56
  // android/build.gradle and the rest of the @expo/* modules.
57
57
  defaultConfig {
58
58
  versionCode 1
59
- versionName '12.0.2'
59
+ versionName '12.1.0'
60
60
  // R8 keep-rules to bundle into partner apps that depend on this
61
61
  // module. Without these, R8 may rename the Expo module entry
62
62
  // point and the JS-bridged member functions, breaking autolinking.
@@ -66,27 +66,27 @@ android {
66
66
  }
67
67
 
68
68
  dependencies {
69
+ // kamera native core: the analyzer executor takes analysis frames from
70
+ // kamera's KameraFrameHub so pixels never cross the JS bridge. Resolves from
71
+ // Maven Central; a local publishToMavenLocal build wins (see kamera.md).
72
+ implementation 'com.usesmileid:kamera:1.0.5'
73
+
69
74
  // Existing Smile ID native AAR — the entire ML pipeline lives here.
70
75
  // Targets Huawei AppGallery / no-GMS devices via HMS ML kit. The
71
76
  // Expo Module is a thin Kotlin shim that constructs the session
72
77
  // and bridges its four methods to JS via Expo Modules.
73
- implementation 'com.usesmileid:huawei-face:12.0.2'
78
+ implementation 'com.usesmileid:huawei-face:12.1.0'
74
79
 
75
- // VisionCamera frame-processor plugin classes. The host app provides
76
- // this dependency via RN autolinking — react-native-vision-camera >= 4.7.3
77
- // must be present in the partner app's package.json.
78
- implementation project(':react-native-vision-camera')
79
80
 
80
- // CameraX types (`ImageProxy`, `ExperimentalGetImage`) referenced by the
81
- // frame processor plugin. VisionCamera declares camera-core as
82
- // `implementation`, so it is NOT on this module's compile classpath
83
- // transitively — compileOnly pins the compile-time API while the host
84
- // app's VisionCamera provides the runtime classes (its camera-core is
85
- // 1.5.0-alpha03; the `ImageProxy` surface used here is stable since 1.0).
86
- compileOnly 'androidx.camera:camera-core:1.5.0-alpha03'
81
+ }
87
82
 
88
- // `HuaweiFaceDetectorAnalyzer()` takes a defaulted `MLFaceAnalyzer` argument, so the compiler
89
- // needs that type to emit the call. The AAR above declares HMS ML at runtime
90
- // scope only — enough to execute, not to compile against.
91
- compileOnly 'com.huawei.hms:ml-computer-vision-face:3.18.1.300'
83
+ // kamera resolves from Maven Central. mavenLocal stays injected — content-filtered to
84
+ // com.usesmileid — the same way kamera's own glue does it, so a locally published kamera
85
+ // build takes precedence during the kamera dev loop without host apps editing repos.
86
+ rootProject.allprojects {
87
+ repositories {
88
+ mavenLocal {
89
+ content { includeGroup 'com.usesmileid' }
90
+ }
91
+ }
92
92
  }
@@ -0,0 +1,42 @@
1
+ package com.usesmileid.huawei.face
2
+
3
+ import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
4
+ import com.usesmileid.kamera.integration.KameraFrameHub
5
+ import com.usesmileid.rn.analyzer.huaweiface.toNativeYuvFrame
6
+ import kotlin.coroutines.cancellation.CancellationException
7
+ import kotlinx.coroutines.runBlocking
8
+ import kotlinx.coroutines.withContext
9
+
10
+ /**
11
+ * Scans the latest [KameraFrameHub] frame natively; no pending frame returns the `no_frame` wire error.
12
+ * The frame's rotationDegrees already uprights the buffer to the locked interface axis.
13
+ */
14
+ internal fun analyzeKameraFrameTick(arguments: Map<String, Any?>?): Any {
15
+ val frame = KameraFrameHub.take() ?: return mapOf("error" to "no_frame")
16
+ val yuvFrame = try {
17
+ frame.toNativeYuvFrame()
18
+ } finally {
19
+ // Closing releases the frame to the pipeline; the converted copy owns its own bytes.
20
+ frame.close()
21
+ }
22
+
23
+ // Device-tilt context (0 = upright portrait) for the validators.
24
+ val jsRotation = (arguments?.get("rotation") as? Number)?.toInt() ?: 0
25
+
26
+ return runBlocking {
27
+ withContext(UseSmileIDHuaweiFaceSessionHolder.sessionDispatcher) {
28
+ val activeSession = UseSmileIDHuaweiFaceSessionHolder.session
29
+ ?: return@withContext mapOf("error" to "session_not_configured")
30
+
31
+ try {
32
+ val scanResult = activeSession.scan(yuvFrame, jsRotation)
33
+ val events = activeSession.drainCaptureReadyEvents()
34
+ UseSmileIDHuaweiFaceModule.buildWireResult(scanResult, events)
35
+ } catch (cancellation: CancellationException) {
36
+ throw cancellation
37
+ } catch (e: Exception) {
38
+ mapOf("error" to ("scan_failed: " + (e.message ?: e.javaClass.simpleName)))
39
+ }
40
+ }
41
+ }
42
+ }
@@ -1,6 +1,5 @@
1
1
  package com.usesmileid.huawei.face
2
2
 
3
- import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
4
3
  import com.usesmileid.bridge.dsl.builder.FaceDetectorMode
5
4
  import com.usesmileid.bridge.huawei.face.HuaweiFaceDetectorAnalyzer
6
5
  import com.usesmileid.bridge.model.BoundingBox as NativeBoundingBox
@@ -27,10 +26,7 @@ import expo.modules.kotlin.modules.ModuleDefinition
27
26
  * variant targets devices without Google Mobile Services and uses
28
27
  * Huawei HMS ML kit for face detection.
29
28
  *
30
- * Per-frame work is handled by [UseSmileIDHuaweiFaceFrameProcessorPlugin],
31
- * which is registered with VisionCamera's frame-processor runtime in
32
- * [OnCreate]. Partners call `VisionCameraProxy.initFrameProcessorPlugin`
33
- * from their JS worklet instead of the old `AsyncFunction("analyze")`.
29
+ * Frames reach `analyzeKameraFrame` through kamera's native hub; pixels never cross the JS bridge.
34
30
  */
35
31
  class UseSmileIDHuaweiFaceModule : Module() {
36
32
 
@@ -105,20 +101,17 @@ class UseSmileIDHuaweiFaceModule : Module() {
105
101
  ?: throw IllegalStateException("Module not attached to a React context")
106
102
  executor.onAttach(ctx)
107
103
 
108
- // Guard against duplicate registration on dev-client hot reloads
109
- // where OnCreate can fire more than once in the same process.
110
- if (!pluginRegistered) {
111
- pluginRegistered = true
112
- FrameProcessorPluginRegistry.addFrameProcessorPlugin(
113
- FRAME_PROCESSOR_PLUGIN_NAME,
114
- ) { _, _ -> UseSmileIDHuaweiFaceFrameProcessorPlugin() }
115
- }
116
104
  }
117
105
 
118
106
  AsyncFunction("configure") { config: Map<String, Any?> ->
119
107
  executor.handleConfigure(config)
120
108
  }
121
109
 
110
+ AsyncFunction("analyzeKameraFrame") { arguments: Map<String, Any?>? ->
111
+ // Blocks this module's async queue so only one scan is in flight at a time.
112
+ analyzeKameraFrameTick(arguments)
113
+ }
114
+
122
115
  AsyncFunction("reset") {
123
116
  executor.handleReset()
124
117
  }
@@ -145,21 +138,6 @@ class UseSmileIDHuaweiFaceModule : Module() {
145
138
  /** Wire-format key partners pass via `FaceConfig.analyzerFactoryKey`. */
146
139
  const val KEY: String = "huawei-face"
147
140
 
148
- /**
149
- * Must match the JS worklet's `VisionCameraProxy.initFrameProcessorPlugin(...)`
150
- * call exactly — drift is a runtime "plugin not found" error.
151
- */
152
- const val FRAME_PROCESSOR_PLUGIN_NAME: String = "UseSmileIDHuaweiFaceFrameProcessor"
153
-
154
- /**
155
- * [FrameProcessorPluginRegistry.addFrameProcessorPlugin] asserts uniqueness
156
- * and crashes on duplicate registration; this guard prevents that on dev-client
157
- * hot reloads where [OnCreate] fires more than once per process.
158
- */
159
- @Volatile
160
- private var pluginRegistered: Boolean = false
161
-
162
- /** Wire-result builder shared with [UseSmileIDHuaweiFaceFrameProcessorPlugin]. */
163
141
  internal fun buildWireResult(
164
142
  scanResult: UseSmileIDFaceScanResult,
165
143
  events: List<NativeCaptureReadyEvent>,
@@ -10,9 +10,8 @@ import kotlinx.coroutines.asCoroutineDispatcher
10
10
  *
11
11
  * The session's state machine is not thread-safe; all operations —
12
12
  * scan+drain as one atomic unit, flush, reset, close — are serialized
13
- * through [sessionDispatcher] (a single-thread executor). The FP plugin
14
- * blocks its thread via [runBlocking]; VisionCamera drops frames that
15
- * arrive while a scan is in flight rather than queuing them.
13
+ * through [sessionDispatcher] (a single-thread executor). The analysis tick blocks its
14
+ * caller via [runBlocking]; kamera's hub keeps only the latest frame, so frames never queue.
16
15
  */
17
16
  internal object UseSmileIDHuaweiFaceSessionHolder {
18
17
 
@@ -12,6 +12,8 @@ import com.usesmileid.bridge.model.CaptureType
12
12
  import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
13
13
  import com.usesmileid.bridge.storage.DocumentSide
14
14
  import com.usesmileid.bridge.storage.FrameStorage
15
+ import com.usesmileid.kamera.frame.KameraFrame
16
+ import com.usesmileid.kamera.integration.KameraFrameHub
15
17
  import java.io.File
16
18
  import kotlinx.coroutines.CoroutineDispatcher
17
19
  import kotlinx.coroutines.CoroutineScope
@@ -171,6 +173,7 @@ abstract class UseSmileIDAnalyzerExecutor<
171
173
 
172
174
  // Shared implementation behind the Expo Module's async functions -----
173
175
 
176
+
174
177
  /**
175
178
  * Implementation behind `configure`. Closes any prior session,
176
179
  * allocates a fresh on-disk session directory, and delegates session
@@ -282,3 +285,28 @@ abstract class UseSmileIDAnalyzerExecutor<
282
285
  return dir
283
286
  }
284
287
  }
288
+
289
+ /** Copies the YUV planes so the frame can close at once; rotation stays metadata for the detector. */
290
+ internal fun KameraFrame.toNativeYuvFrame(): NativeYuvFrame {
291
+ val yPlane = planes[0]
292
+ val uPlane = planes[1]
293
+ val vPlane = planes[2]
294
+ fun copyBytes(buffer: java.nio.ByteBuffer): ByteArray {
295
+ val source = buffer.duplicate()
296
+ source.rewind()
297
+ return ByteArray(source.remaining()).also { source.get(it) }
298
+ }
299
+ return NativeYuvFrame(
300
+ planeY = copyBytes(yPlane.buffer),
301
+ planeU = copyBytes(uPlane.buffer),
302
+ planeV = copyBytes(vPlane.buffer),
303
+ yRowStride = yPlane.rowStride,
304
+ uRowStride = uPlane.rowStride,
305
+ vRowStride = vPlane.rowStride,
306
+ uPixelStride = uPlane.pixelStride,
307
+ vPixelStride = vPlane.pixelStride,
308
+ width = width,
309
+ height = height,
310
+ rotation = rotationDegrees,
311
+ )
312
+ }
package/dist/index.d.ts CHANGED
@@ -19,9 +19,10 @@ import type { FaceConfig, FaceScanResult } from "@smileid/usesmileid_bridge";
19
19
  * contract.
20
20
  */
21
21
  /**
22
- * Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` and its
23
- * VisionCamera frame-processor-plugin registration — drift is a runtime
24
- * "module/plugin not found" error, not a compile error.
22
+ * Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` — drift is a runtime
23
+ * "module not found" error, not a compile error.
24
+ *
25
+ * `frameProcessorPluginName` is inert; kept only for API compatibility.
25
26
  */
26
27
  export declare const UseSmileIDHuaweiFaceIdentifiers: {
27
28
  readonly nativeModuleName: "UseSmileIDHuaweiFace";
@@ -29,4 +30,3 @@ export declare const UseSmileIDHuaweiFaceIdentifiers: {
29
30
  };
30
31
  export declare const useSmileIDHuaweiFace: import("@smileid/usesmileid_platform_interface").FaceAnalyzer;
31
32
  export type { FaceConfig, FaceScanResult };
32
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,37 +1 @@
1
- import { AnalyzerFactoryKey } from "@smileid/usesmileid_bridge";
2
- import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
3
- /**
4
- * Huawei HMS face analyzer (Android-only, for devices without
5
- * Google Mobile Services).
6
- *
7
- * Thin shim around the existing native session
8
- * `com.usesmileid.bridge.session.UseSmileIDFaceSession(HuaweiFaceDetectorAnalyzer())`
9
- * shipped in the `com.smileidentity:huawei-face` AAR. The native side
10
- * runs the entire pipeline (analyzer + validator + transitioner +
11
- * state machine); this TS layer routes calls and validates wire
12
- * payloads at the channel boundary.
13
- *
14
- * The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
15
- * `usesmileid_platform_interface`.
16
- *
17
- * Lifecycle: `configure()` → per-frame analysis via the native frame
18
- * processor plugin → `reset()`? → `close()`. See
19
- * `usesmileid_platform_interface` `FaceAnalyzer` for the canonical
20
- * contract.
21
- */
22
- /**
23
- * Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` and its
24
- * VisionCamera frame-processor-plugin registration — drift is a runtime
25
- * "module/plugin not found" error, not a compile error.
26
- */
27
- export const UseSmileIDHuaweiFaceIdentifiers = {
28
- nativeModuleName: "UseSmileIDHuaweiFace",
29
- frameProcessorPluginName: "UseSmileIDHuaweiFaceFrameProcessor",
30
- };
31
- export const useSmileIDHuaweiFace = useSmileIDAnalyzerExecutor({
32
- nativeModuleName: UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,
33
- frameProcessorPluginName: UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
34
- kind: "face",
35
- key: AnalyzerFactoryKey.HuaweiFace,
36
- });
37
- //# sourceMappingURL=index.js.map
1
+ "use strict";import{AnalyzerFactoryKey as e}from"@smileid/usesmileid_bridge";import{useSmileIDAnalyzerExecutor as a}from"@smileid/usesmileid_platform_interface";export const UseSmileIDHuaweiFaceIdentifiers={nativeModuleName:"UseSmileIDHuaweiFace",frameProcessorPluginName:"UseSmileIDHuaweiFaceFrameProcessor"},useSmileIDHuaweiFace=a({nativeModuleName:UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,frameProcessorPluginName:UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,kind:"face",key:e.HuaweiFace});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smileid/usesmileid_huawei_face",
3
- "version": "12.0.2",
3
+ "version": "12.1.0",
4
4
  "description": "Huawei HMS face detection implementation for the UseSmileID React Native (Expo) SDK.",
5
5
  "homepage": "https://usesmileid.com",
6
6
  "repository": {
@@ -35,16 +35,17 @@
35
35
  },
36
36
  "files": [
37
37
  "dist",
38
- "src",
39
- "android",
38
+ "android/build.gradle",
39
+ "android/consumer-rules.pro",
40
+ "android/src/main",
40
41
  "expo-module.config.json",
41
42
  "README.md",
42
43
  "LICENSE",
43
44
  "CHANGELOG.md"
44
45
  ],
45
46
  "dependencies": {
46
- "@smileid/usesmileid_platform_interface": "12.0.2",
47
- "@smileid/usesmileid_bridge": "12.0.2"
47
+ "@smileid/usesmileid_bridge": "12.1.0",
48
+ "@smileid/usesmileid_platform_interface": "12.1.0"
48
49
  },
49
50
  "peerDependencies": {
50
51
  "expo": ">=56.0.0",
@@ -52,18 +53,18 @@
52
53
  "react-native": ">=0.83.0"
53
54
  },
54
55
  "devDependencies": {
55
- "expo-modules-core": "^56.0.15",
56
- "react-native": "^0.85.3",
56
+ "expo-modules-core": "^57.0.16",
57
+ "react-native": "^0.86.3",
57
58
  "typescript": "^6.0.3"
58
59
  },
59
60
  "publishConfig": {
60
61
  "access": "public"
61
62
  },
62
63
  "scripts": {
63
- "build": "tsc -p tsconfig.json",
64
+ "build": "tsc -p tsconfig.json && node ../Scripts/copy-assets.mjs",
64
65
  "typecheck": "tsc -b",
65
66
  "test": "vitest run",
66
67
  "test:watch": "vitest",
67
- "clean": "rm -rf dist .turbo coverage"
68
+ "clean": "rm -rf dist .turbo coverage tsconfig.tsbuildinfo"
68
69
  }
69
70
  }
@@ -1,79 +0,0 @@
1
- package com.usesmileid.huawei.face
2
-
3
- import androidx.camera.core.ExperimentalGetImage
4
- import com.mrousavy.camera.core.FrameInvalidError
5
- import com.mrousavy.camera.frameprocessors.Frame
6
- import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
7
- import com.usesmileid.bridge.model.YuvFrame as NativeYuvFrame
8
- import kotlin.coroutines.cancellation.CancellationException
9
- import kotlinx.coroutines.runBlocking
10
- import kotlinx.coroutines.withContext
11
-
12
- /**
13
- * VisionCamera Frame Processor Plugin for the Huawei HMS ML Kit face backend.
14
- * Registered under [UseSmileIDHuaweiFaceModule.FRAME_PROCESSOR_PLUGIN_NAME].
15
- * Reads raw [ImageProxy] planes directly (no JPEG encoding) and runs the
16
- * native session pipeline. Blocks the FP thread via [runBlocking] —
17
- * VisionCamera drops frames that arrive while a scan is in flight.
18
- */
19
- @OptIn(ExperimentalGetImage::class)
20
- internal class UseSmileIDHuaweiFaceFrameProcessorPlugin : FrameProcessorPlugin() {
21
-
22
- override fun callback(frame: Frame, arguments: Map<String, Any>?): Any? {
23
- val imageProxy = try {
24
- frame.imageProxy
25
- } catch (e: FrameInvalidError) {
26
- return mapOf("error" to "frame_conversion_failed")
27
- }
28
-
29
- val image = imageProxy.image
30
- ?: return mapOf("error" to "frame_conversion_failed")
31
-
32
- // Pixel-orientation for ML Kit — always from ImageProxy metadata, never from JS.
33
- val sensorRotation = imageProxy.imageInfo.rotationDegrees
34
-
35
- // Device-tilt context (0 = upright portrait) for head-pose math.
36
- // Must not fall back to sensorRotation — sensor angle != device tilt.
37
- val jsRotation = (arguments?.get("rotation") as? Number)?.toInt() ?: 0
38
-
39
- val planes = image.planes
40
- if (planes.size < 3) return mapOf("error" to "frame_conversion_failed")
41
-
42
- fun extractPlane(index: Int): ByteArray {
43
- val buffer = planes[index].buffer
44
- buffer.rewind()
45
- return ByteArray(buffer.remaining()).also { buffer.get(it) }
46
- }
47
-
48
- val yuvFrame = NativeYuvFrame(
49
- planeY = extractPlane(0),
50
- planeU = extractPlane(1),
51
- planeV = extractPlane(2),
52
- yRowStride = planes[0].rowStride,
53
- uRowStride = planes[1].rowStride,
54
- vRowStride = planes[2].rowStride,
55
- uPixelStride = planes[1].pixelStride,
56
- vPixelStride = planes[2].pixelStride,
57
- width = image.width,
58
- height = image.height,
59
- rotation = sensorRotation,
60
- )
61
-
62
- return runBlocking {
63
- withContext(UseSmileIDHuaweiFaceSessionHolder.sessionDispatcher) {
64
- val activeSession = UseSmileIDHuaweiFaceSessionHolder.session
65
- ?: return@withContext mapOf("error" to "session_not_configured")
66
-
67
- try {
68
- val scanResult = activeSession.scan(yuvFrame, jsRotation)
69
- val events = activeSession.drainCaptureReadyEvents()
70
- UseSmileIDHuaweiFaceModule.buildWireResult(scanResult, events)
71
- } catch (cancellation: CancellationException) {
72
- throw cancellation
73
- } catch (e: Exception) {
74
- mapOf("error" to ("scan_failed: " + (e.message ?: e.javaClass.simpleName)))
75
- }
76
- }
77
- }
78
- }
79
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG7E;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;CAGlC,CAAC;AAEX,eAAO,MAAM,oBAAoB,+DAM/B,CAAC;AAEH,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,OAAO,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAC;AAEpF;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,gBAAgB,EAAE,sBAAsB;IACxC,wBAAwB,EAAE,oCAAoC;CACtD,CAAC;AAEX,MAAM,CAAC,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;IAC7D,gBAAgB,EAAE,+BAA+B,CAAC,gBAAgB;IAClE,wBAAwB,EACtB,+BAA+B,CAAC,wBAAwB;IAC1D,IAAI,EAAE,MAAM;IACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU;CACnC,CAAC,CAAC"}
@@ -1,79 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach, type Mock } from "vitest";
2
-
3
- const native = vi.hoisted(() => ({
4
- configure: vi.fn(),
5
- reset: vi.fn(),
6
- close: vi.fn(),
7
- }));
8
-
9
- vi.mock("expo-modules-core", () => ({
10
- requireNativeModule: vi.fn(() => native),
11
- }));
12
-
13
- import { requireNativeModule } from "expo-modules-core";
14
- import {
15
- useSmileIDHuaweiFace,
16
- UseSmileIDHuaweiFaceIdentifiers,
17
- } from "../index";
18
-
19
- describe("useSmileIDHuaweiFace — TS wrapper", () => {
20
- beforeEach(() => {
21
- Object.values(native).forEach((m) => (m as Mock).mockReset());
22
- });
23
-
24
- it("looks up the native module under the contract-stable name", () => {
25
- expect(requireNativeModule).toHaveBeenCalledWith("UseSmileIDHuaweiFace");
26
- });
27
-
28
- it("exposes the lifecycle surface — no per-frame analyze method", () => {
29
- expect(typeof useSmileIDHuaweiFace.configure).toBe("function");
30
- expect(typeof useSmileIDHuaweiFace.reset).toBe("function");
31
- expect(typeof useSmileIDHuaweiFace.close).toBe("function");
32
- // Per-frame analysis lives in the native VisionCamera frame
33
- // processor plugin — raw pixels never cross the JS bridge.
34
- expect("analyze" in useSmileIDHuaweiFace).toBe(false);
35
- });
36
-
37
- it("exposes the kind discriminator and frame processor plugin name", () => {
38
- expect(useSmileIDHuaweiFace.kind).toBe("face");
39
- expect(useSmileIDHuaweiFace.frameProcessorPluginName).toBe(
40
- UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
41
- );
42
- });
43
-
44
- describe("configure", () => {
45
- it("passes the validated config through to native", async () => {
46
- native.configure.mockResolvedValueOnce(undefined);
47
- await useSmileIDHuaweiFace.configure({ selfieCaptureTimeout: 60_000 });
48
- expect(native.configure).toHaveBeenCalledTimes(1);
49
- expect(native.configure).toHaveBeenCalledWith({
50
- selfieCaptureTimeout: 60_000,
51
- });
52
- });
53
-
54
- it("rejects malformed config at the JS boundary before crossing the bridge", async () => {
55
- await expect(
56
- useSmileIDHuaweiFace.configure({
57
- // @ts-expect-error — int field with float value
58
- selfieCaptureTimeout: 60_000.5,
59
- }),
60
- ).rejects.toThrow();
61
- expect(native.configure).not.toHaveBeenCalled();
62
- });
63
- });
64
-
65
- describe("reset / close", () => {
66
- it("routes reset through to native", async () => {
67
- native.reset.mockResolvedValueOnce(undefined);
68
- await useSmileIDHuaweiFace.reset();
69
- expect(native.reset).toHaveBeenCalledTimes(1);
70
- });
71
-
72
- it("routes close through to native and is idempotent", async () => {
73
- native.close.mockResolvedValue(undefined);
74
- await useSmileIDHuaweiFace.close();
75
- await useSmileIDHuaweiFace.close();
76
- expect(native.close).toHaveBeenCalledTimes(2);
77
- });
78
- });
79
- });
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import { AnalyzerFactoryKey } from "@smileid/usesmileid_bridge";
2
- import type { FaceConfig, FaceScanResult } from "@smileid/usesmileid_bridge";
3
- import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
4
-
5
- /**
6
- * Huawei HMS face analyzer (Android-only, for devices without
7
- * Google Mobile Services).
8
- *
9
- * Thin shim around the existing native session
10
- * `com.usesmileid.bridge.session.UseSmileIDFaceSession(HuaweiFaceDetectorAnalyzer())`
11
- * shipped in the `com.smileidentity:huawei-face` AAR. The native side
12
- * runs the entire pipeline (analyzer + validator + transitioner +
13
- * state machine); this TS layer routes calls and validates wire
14
- * payloads at the channel boundary.
15
- *
16
- * The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
17
- * `usesmileid_platform_interface`.
18
- *
19
- * Lifecycle: `configure()` → per-frame analysis via the native frame
20
- * processor plugin → `reset()`? → `close()`. See
21
- * `usesmileid_platform_interface` `FaceAnalyzer` for the canonical
22
- * contract.
23
- */
24
- /**
25
- * Must match the Kotlin Expo Module's `Name("UseSmileIDHuaweiFace")` and its
26
- * VisionCamera frame-processor-plugin registration — drift is a runtime
27
- * "module/plugin not found" error, not a compile error.
28
- */
29
- export const UseSmileIDHuaweiFaceIdentifiers = {
30
- nativeModuleName: "UseSmileIDHuaweiFace",
31
- frameProcessorPluginName: "UseSmileIDHuaweiFaceFrameProcessor",
32
- } as const;
33
-
34
- export const useSmileIDHuaweiFace = useSmileIDAnalyzerExecutor({
35
- nativeModuleName: UseSmileIDHuaweiFaceIdentifiers.nativeModuleName,
36
- frameProcessorPluginName:
37
- UseSmileIDHuaweiFaceIdentifiers.frameProcessorPluginName,
38
- kind: "face",
39
- key: AnalyzerFactoryKey.HuaweiFace,
40
- });
41
-
42
- export type { FaceConfig, FaceScanResult };