@smileid/usesmileid_mlkit_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.
Files changed (29) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +7 -3
  3. package/android/build.gradle +21 -21
  4. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceKameraAnalysisTick.kt +42 -0
  5. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceModule.kt +6 -28
  6. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceSessionHolder.kt +2 -3
  7. package/android/src/main/java/com/usesmileid/rn/analyzer/mlkitface/UseSmileIDAnalyzerExecutor.kt +28 -0
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.js +1 -38
  10. package/package.json +12 -13
  11. package/plugin/build/index.js +1 -27
  12. package/plugin/build/lib/mlkit_manifest.js +1 -54
  13. package/plugin/build/lib/root_project.d.ts +11 -3
  14. package/plugin/build/lib/root_project.js +4 -109
  15. package/android/src/main/java/com/usesmileid/mlkit/face/UseSmileIDMlkitFaceFrameProcessorPlugin.kt +0 -79
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/index.js.map +0 -1
  18. package/plugin/build/index.js.map +0 -1
  19. package/plugin/build/lib/mlkit_manifest.js.map +0 -1
  20. package/plugin/build/lib/root_project.js.map +0 -1
  21. package/plugin/src/__tests__/mirrored_sources.test.ts +0 -57
  22. package/plugin/src/__tests__/mlkit_manifest.test.ts +0 -117
  23. package/plugin/src/__tests__/root_project.test.ts +0 -195
  24. package/plugin/src/index.ts +0 -47
  25. package/plugin/src/lib/mlkit_manifest.ts +0 -88
  26. package/plugin/src/lib/root_project.ts +0 -160
  27. package/plugin/tsconfig.json +0 -24
  28. package/src/__tests__/use_smile_id_mlkit_face.test.ts +0 -101
  29. package/src/index.ts +0 -46
@@ -1,160 +0,0 @@
1
- // `expo-root-project` resolves these through `extra.setIfNotExist`, so they only
2
- // take effect when declared before that plugin applies.
3
-
4
- import { type ConfigPlugin, withProjectBuildGradle } from "expo/config-plugins";
5
-
6
- export interface AndroidRootProjectPins {
7
- /**
8
- * Opt-in: only for hosts whose Kotlin predates the AARs' compiler. Current
9
- * Expo defaults read the metadata, and forcing a Kotlin that Expo ships no
10
- * compiler-plugin artifacts for breaks expo-modules-core.
11
- */
12
- kotlinVersion?: string;
13
- /** Only alongside `kotlinVersion` — Expo's own lookup table may carry no entry for it. */
14
- kspVersion?: string;
15
- /** The AARs declare it as a consumer minimum. Independent of `minSdk`, so device support is unchanged. */
16
- compileSdkVersion: number;
17
- /** `group:name:version` coordinates forced everywhere, for deps whose newest release the host toolchain cannot consume. */
18
- forcedDependencies: readonly string[];
19
- }
20
-
21
- const BEGIN_MARKER = "// === BEGIN smileid root-project pins ===";
22
- const END_MARKER = "// === END smileid root-project pins ===";
23
-
24
- const APPLY_ROOT_PROJECT = /^apply\s+plugin:\s*["']expo-root-project["']/m;
25
-
26
- // Bumping `ext.kotlinVersion` moves the dependency catalog but not the compiler, so
27
- // the classpath line needs pinning too. Version and parentheses are optional so an
28
- // already-pinned line and Groovy's space-call form still match.
29
- const KGP_CLASSPATH =
30
- /classpath\s*(?:\(\s*)?['"]org\.jetbrains\.kotlin:kotlin-gradle-plugin(?::[^'"]*)?['"](?:\s*\))?/;
31
-
32
- /** Declares the pins ahead of `apply plugin: "expo-root-project"`. */
33
- export const withAndroidRootProjectPins: ConfigPlugin<
34
- AndroidRootProjectPins
35
- > = (config, pins) =>
36
- withProjectBuildGradle(config, (config) => {
37
- if (config.modResults.language !== "groovy") {
38
- throw new Error(
39
- "[smileid-android] Expected android/build.gradle to be Groovy DSL; refusing to edit " +
40
- `a ${config.modResults.language} build script.`,
41
- );
42
- }
43
- config.modResults.contents = applyAndroidRootProjectPins(
44
- config.modResults.contents,
45
- pins,
46
- );
47
- return config;
48
- });
49
-
50
- /** Pure form of the edit. Idempotent; a re-run replaces stale values rather than appending. */
51
- export function applyAndroidRootProjectPins(
52
- contents: string,
53
- pins: AndroidRootProjectPins,
54
- ): string {
55
- if (pins.kspVersion !== undefined && pins.kotlinVersion === undefined) {
56
- throw new Error(
57
- "[smileid-android] `kspVersion` only makes sense alongside `kotlinVersion`.",
58
- );
59
- }
60
- validateGroovySafe(pins);
61
- const pinned =
62
- pins.kotlinVersion === undefined
63
- ? contents
64
- : pinKotlinGradlePlugin(contents, pins.kotlinVersion);
65
- return upsertPinBlock(pinned, renderPinBlock(pins));
66
- }
67
-
68
- // Every value lands in a single-quoted Groovy literal; reject anything that could close one.
69
- function validateGroovySafe(pins: AndroidRootProjectPins): void {
70
- const values = [
71
- pins.kotlinVersion,
72
- pins.kspVersion,
73
- ...pins.forcedDependencies,
74
- ].filter((value): value is string => value !== undefined);
75
- for (const value of values) {
76
- if (/['\\\n\r\t$]/.test(value)) {
77
- throw new Error(
78
- `[smileid-android] Refusing to edit android/build.gradle: '${value}' contains an unsafe character.`,
79
- );
80
- }
81
- }
82
- }
83
-
84
- function pinKotlinGradlePlugin(
85
- contents: string,
86
- kotlinVersion: string,
87
- ): string {
88
- const pinned = `classpath('org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}')`;
89
- if (!KGP_CLASSPATH.test(contents)) {
90
- throw new Error(
91
- "[smileid-android] Could not locate the kotlin-gradle-plugin classpath line in " +
92
- "android/build.gradle. Refusing to silently no-op — the Smile ID analyzer modules do not " +
93
- `compile unless the Kotlin compiler is pinned to ${kotlinVersion}.`,
94
- );
95
- }
96
- return contents.replace(KGP_CLASSPATH, pinned);
97
- }
98
-
99
- // Adding a further root-project pin means adding an entry here.
100
- function renderPinBlock(pins: AndroidRootProjectPins): string {
101
- const extProperties: Array<[string, string]> = [];
102
- if (pins.kotlinVersion !== undefined) {
103
- extProperties.push(["kotlinVersion", `'${pins.kotlinVersion}'`]);
104
- }
105
- if (pins.kspVersion !== undefined) {
106
- extProperties.push(["kspVersion", `'${pins.kspVersion}'`]);
107
- }
108
- extProperties.push(["compileSdkVersion", String(pins.compileSdkVersion)]);
109
- const lines = extProperties.map(([name, value]) => `ext.${name} = ${value}`);
110
- return [
111
- BEGIN_MARKER,
112
- ...lines,
113
- ...renderForcedDependencies(pins.forcedDependencies),
114
- END_MARKER,
115
- ].join("\n");
116
- }
117
-
118
- // Its own `allprojects` block, which Gradle merges — editing the template's would
119
- // break the moment its shape changes.
120
- function renderForcedDependencies(coordinates: readonly string[]): string[] {
121
- if (coordinates.length === 0) {
122
- return [];
123
- }
124
- return [
125
- "",
126
- "// Forced to the newest release that resolves under the AGP Expo generates.",
127
- "allprojects {",
128
- " configurations.all {",
129
- " resolutionStrategy {",
130
- ...coordinates.map((coordinate) => ` force '${coordinate}'`),
131
- " }",
132
- " }",
133
- "}",
134
- ];
135
- }
136
-
137
- function upsertPinBlock(contents: string, block: string): string {
138
- const existing = new RegExp(
139
- `${escapeRegExp(BEGIN_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`,
140
- "m",
141
- );
142
- if (existing.test(contents)) {
143
- return contents.replace(existing, block);
144
- }
145
- if (!APPLY_ROOT_PROJECT.test(contents)) {
146
- throw new Error(
147
- '[smileid-android] Could not locate `apply plugin: "expo-root-project"` in ' +
148
- "android/build.gradle. Refusing to silently no-op — these pins only take effect when " +
149
- "declared before that plugin applies.",
150
- );
151
- }
152
- return contents.replace(
153
- APPLY_ROOT_PROJECT,
154
- (match) => `${block}\n\n${match}`,
155
- );
156
- }
157
-
158
- function escapeRegExp(value: string): string {
159
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
160
- }
@@ -1,24 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./build",
5
- "rootDir": "./src",
6
- "module": "commonjs",
7
- "moduleResolution": "node",
8
- "ignoreDeprecations": "6.0",
9
- "target": "ES2021",
10
- "lib": ["ES2022"],
11
- "types": ["node"],
12
- "declaration": true,
13
- "declarationMap": false,
14
- "sourceMap": true,
15
- "composite": false
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": [
19
- "src/**/*.test.ts",
20
- "src/**/__tests__/**",
21
- "build",
22
- "node_modules"
23
- ]
24
- }
@@ -1,101 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach, type Mock } from "vitest";
2
-
3
- // Hoist the mock so `requireNativeModule` resolves to a stub before
4
- // the index module's top-level `requireNativeModule(...)` call runs.
5
- // Without `vi.hoisted(...)`, Vitest evaluates the import statement
6
- // before the mock is in scope and the wrapper grabs the real native
7
- // module — which doesn't exist in a Node test environment.
8
- const native = vi.hoisted(() => ({
9
- configure: vi.fn(),
10
- reset: vi.fn(),
11
- close: vi.fn(),
12
- }));
13
-
14
- vi.mock("expo-modules-core", () => ({
15
- requireNativeModule: vi.fn(() => native),
16
- }));
17
-
18
- // Imports happen *after* the mock is registered.
19
- import { requireNativeModule } from "expo-modules-core";
20
- import { useSmileIDMlkitFace, UseSmileIDMlkitFaceIdentifiers } from "../index";
21
-
22
- describe("useSmileIDMlkitFace — TS wrapper", () => {
23
- beforeEach(() => {
24
- Object.values(native).forEach((m) => (m as Mock).mockReset());
25
- });
26
-
27
- it("looks up the native module under the contract-stable name", () => {
28
- // The Kotlin Expo Module declares `Name("UseSmileIDMlkitFace")`.
29
- // If the JS wrapper passes a different string, partner apps fail
30
- // at runtime with "Cannot find native module 'X'".
31
- expect(requireNativeModule).toHaveBeenCalledWith("UseSmileIDMlkitFace");
32
- });
33
-
34
- it("exposes the lifecycle surface — no per-frame analyze method", () => {
35
- expect(typeof useSmileIDMlkitFace.configure).toBe("function");
36
- expect(typeof useSmileIDMlkitFace.reset).toBe("function");
37
- expect(typeof useSmileIDMlkitFace.close).toBe("function");
38
- // Per-frame analysis lives in the native VisionCamera frame
39
- // processor plugin — raw pixels never cross the JS bridge.
40
- expect("analyze" in useSmileIDMlkitFace).toBe(false);
41
- });
42
-
43
- it("exposes the kind discriminator and frame processor plugin name", () => {
44
- expect(useSmileIDMlkitFace.kind).toBe("face");
45
- expect(useSmileIDMlkitFace.frameProcessorPluginName).toBe(
46
- UseSmileIDMlkitFaceIdentifiers.frameProcessorPluginName,
47
- );
48
- });
49
-
50
- describe("configure", () => {
51
- it("passes the validated config through to native", async () => {
52
- native.configure.mockResolvedValueOnce(undefined);
53
- await useSmileIDMlkitFace.configure({ selfieCaptureTimeout: 60_000 });
54
- expect(native.configure).toHaveBeenCalledTimes(1);
55
- expect(native.configure).toHaveBeenCalledWith({
56
- selfieCaptureTimeout: 60_000,
57
- });
58
- });
59
-
60
- it("rejects malformed config at the JS boundary before crossing the bridge", async () => {
61
- // Concrete scenario: a partner builds a config from float math
62
- // and passes `selfieCaptureTimeout: 60_000.5`. Without the
63
- // boundary parse, the value would marshal to a Kotlin `Int`
64
- // with silent truncation. With it, the JS layer throws here
65
- // and `native.configure` is never called.
66
- await expect(
67
- useSmileIDMlkitFace.configure({
68
- // @ts-expect-error — deliberately wrong: int field with float value
69
- selfieCaptureTimeout: 60_000.5,
70
- }),
71
- ).rejects.toThrow();
72
- expect(native.configure).not.toHaveBeenCalled();
73
- });
74
- });
75
-
76
- describe("reset", () => {
77
- it("routes to native reset", async () => {
78
- native.reset.mockResolvedValueOnce(undefined);
79
- await useSmileIDMlkitFace.reset();
80
- expect(native.reset).toHaveBeenCalledTimes(1);
81
- });
82
- });
83
-
84
- describe("close", () => {
85
- it("routes to native close", async () => {
86
- native.close.mockResolvedValueOnce(undefined);
87
- await useSmileIDMlkitFace.close();
88
- expect(native.close).toHaveBeenCalledTimes(1);
89
- });
90
-
91
- it("is idempotent — partner can call it twice safely", async () => {
92
- // Idempotency contract from FaceAnalyzer doc-comment. The native
93
- // side handles the second close as a no-op; the wrapper just
94
- // needs to not throw on re-entry.
95
- native.close.mockResolvedValue(undefined);
96
- await useSmileIDMlkitFace.close();
97
- await useSmileIDMlkitFace.close();
98
- expect(native.close).toHaveBeenCalledTimes(2);
99
- });
100
- });
101
- });
package/src/index.ts DELETED
@@ -1,46 +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
- * Google ML Kit face analyzer (Android-only).
7
- *
8
- * Thin shim around the existing native session
9
- * `com.usesmileid.bridge.session.UseSmileIDFaceSession(FaceDetectorAnalyzer())`
10
- * shipped in the `com.smileidentity:mlkit-face` AAR. The native side
11
- * runs the entire pipeline (analyzer + validator + transitioner +
12
- * state machine); this TS layer only routes calls and validates wire
13
- * payloads at the channel boundary.
14
- *
15
- * The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
16
- * `usesmileid_platform_interface`. This file only supplies the
17
- * provider-specific identity so all six provider modules share one set
18
- * of correctness properties by construction.
19
- *
20
- * Lifecycle: `configure()` → per-frame analysis via the native frame
21
- * processor plugin → `reset()`? → `close()`. Calling
22
- * `close()` twice is idempotent. See `usesmileid_platform_interface`
23
- * `FaceAnalyzer` for the canonical contract.
24
- */
25
- /**
26
- * Must match the Kotlin Expo Module's `Name("UseSmileIDMlkitFace")` and its
27
- * VisionCamera frame-processor-plugin registration — drift is a runtime
28
- * "module/plugin not found" error, not a compile error.
29
- */
30
- export const UseSmileIDMlkitFaceIdentifiers = {
31
- nativeModuleName: "UseSmileIDMlkitFace",
32
- frameProcessorPluginName: "UseSmileIDMlkitFaceFrameProcessor",
33
- } as const;
34
-
35
- export const useSmileIDMlkitFace = useSmileIDAnalyzerExecutor({
36
- nativeModuleName: UseSmileIDMlkitFaceIdentifiers.nativeModuleName,
37
- frameProcessorPluginName:
38
- UseSmileIDMlkitFaceIdentifiers.frameProcessorPluginName,
39
- kind: "face",
40
- key: AnalyzerFactoryKey.MLKitFace,
41
- });
42
-
43
- // Re-export the contract types so consumers don't need a separate
44
- // import line for `FaceConfig` / `FaceScanResult` when they're using
45
- // this provider.
46
- export type { FaceConfig, FaceScanResult };