@smileid/usesmileid_vision_document 12.2.0 → 12.2.1-SNAPSHOT.44
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 +0 -1
- package/README.md +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/ios/UseSmileIDVisionDocumentModule/ResizingFrameStorage.swift +4 -2
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentKameraAnalysisTick.swift +27 -41
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentModule.swift +31 -11
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentSessionHolder.swift +45 -2
- package/package.json +6 -6
- package/plugin/build/index.js +1 -1
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDAnalyzerExecutor.swift +0 -119
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ your own and other vendors'.
|
|
|
62
62
|
```ts
|
|
63
63
|
import { useSmileIDVisionDocument } from "@smileid/usesmileid_vision_document";
|
|
64
64
|
|
|
65
|
-
await useSmileIDVisionDocument.configure({
|
|
65
|
+
await useSmileIDVisionDocument.configure({ timeRequired: 500 });
|
|
66
66
|
const result = await useSmileIDVisionDocument.analyze(bytes, rotation);
|
|
67
67
|
await useSmileIDVisionDocument.close();
|
|
68
68
|
```
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{
|
|
1
|
+
"use strict";import{DocumentAnalyzerKey as e}from"@smileid/usesmileid_bridge";import{useSmileIDAnalyzerExecutor as o}from"@smileid/usesmileid_platform_interface";export const UseSmileIDVisionDocumentIdentifiers={nativeModuleName:"UseSmileIDVisionDocument",frameProcessorPluginName:"UseSmileIDVisionDocumentFrameProcessor"},useSmileIDVisionDocument=o({nativeModuleName:UseSmileIDVisionDocumentIdentifiers.nativeModuleName,frameProcessorPluginName:UseSmileIDVisionDocumentIdentifiers.frameProcessorPluginName,kind:"document",key:e.VisionDocument});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// MIRRORED FILE — duplicated in both RN Expo iOS vision modules
|
|
2
|
-
// (usesmileid_vision_face / usesmileid_vision_document)
|
|
3
|
-
// targets can't share sources
|
|
2
|
+
// (usesmileid_vision_face / usesmileid_vision_document). The per-module pod
|
|
3
|
+
// targets can't share sources, and the bridge pod that both depend on doesn't
|
|
4
|
+
// link the native UseSmileIDBridge package this conforms to. Apply any edit to
|
|
5
|
+
// BOTH copies in the same commit.
|
|
4
6
|
|
|
5
7
|
import Foundation
|
|
6
8
|
import UIKit
|
|
@@ -1,70 +1,56 @@
|
|
|
1
|
-
import CoreMedia
|
|
2
1
|
import Kamera
|
|
3
|
-
import CoreImage
|
|
4
2
|
import UIKit
|
|
5
3
|
import UseSmileIDBridge
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
private extension CGImagePropertyOrientation {
|
|
9
|
-
/// Kamera rotation + mirror metadata → the preview's view of the frame. The rotation already
|
|
10
|
-
/// follows the locked interface axis, so no further fold onto the viewport is needed.
|
|
11
|
-
init(rotationDegrees: Int, isMirrored: Bool) {
|
|
12
|
-
let normalised = ((rotationDegrees % 360) + 360) % 360
|
|
13
|
-
switch (normalised, isMirrored) {
|
|
14
|
-
case (90, false): self = .right
|
|
15
|
-
case (90, true): self = .leftMirrored
|
|
16
|
-
case (180, false): self = .down
|
|
17
|
-
case (180, true): self = .downMirrored
|
|
18
|
-
case (270, false): self = .left
|
|
19
|
-
case (270, true): self = .rightMirrored
|
|
20
|
-
case (_, true): self = .upMirrored
|
|
21
|
-
default: self = .up
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
5
|
/// Scans the latest `KameraFrameHub` frame; no pending frame returns the `no_frame` wire error.
|
|
27
6
|
enum UseSmileIDVisionDocumentKameraTick {
|
|
28
|
-
/// Reused across ticks — per-tick creation recompiles the GPU pipeline.
|
|
29
|
-
private static let ciContext = CIContext(options: [.useSoftwareRenderer: false])
|
|
30
|
-
|
|
31
7
|
static func analyze(arguments: [String: Any?]?) -> Any {
|
|
32
8
|
guard let frame = KameraFrameHub.take() else {
|
|
33
9
|
return ["error": "no_frame"]
|
|
34
10
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
let orientedImage = CIImage(cvPixelBuffer: concrete.pixelBuffer)
|
|
41
|
-
.oriented(CGImagePropertyOrientation(
|
|
42
|
-
rotationDegrees: frame.rotationDegrees,
|
|
43
|
-
isMirrored: frame.isMirrored
|
|
44
|
-
))
|
|
45
|
-
let cgImage = ciContext.createCGImage(orientedImage, from: orientedImage.extent)
|
|
46
|
-
frame.close()
|
|
47
|
-
guard let cgImage else {
|
|
11
|
+
// The session reads the live frame's readings as the scan runs; closing it afterwards releases
|
|
12
|
+
// it to the pipeline for the next one.
|
|
13
|
+
defer { frame.close() }
|
|
14
|
+
guard frame is CVPixelBufferKameraFrame else {
|
|
48
15
|
return ["error": "frame_conversion_failed"]
|
|
49
16
|
}
|
|
50
17
|
|
|
51
18
|
let rotation = (arguments?["rotation"] as? NSNumber)?.intValue ?? 0
|
|
52
19
|
|
|
53
20
|
// Crop the saved capture to the guide rectangle the user framed.
|
|
54
|
-
|
|
21
|
+
let cropRegion = kameraCropRegionFrom(arguments)
|
|
22
|
+
UseSmileIDVisionDocumentSessionHolder.shared.setCropRegion(cropRegion)
|
|
23
|
+
// The guide follows the overlay's crop and the upright frame's size, for the camera's
|
|
24
|
+
// close-focus floor.
|
|
25
|
+
let guideTracker = UseSmileIDVisionDocumentSessionHolder.shared.guideTracker
|
|
26
|
+
guideTracker?.setCropRegion(cropRegion)
|
|
27
|
+
// The frame is sensor-native; its rotation turns it upright.
|
|
28
|
+
let turned = frame.rotationDegrees % 180 != 0
|
|
29
|
+
guideTracker?.setUprightFrameSize(
|
|
30
|
+
width: turned ? frame.height : frame.width,
|
|
31
|
+
height: turned ? frame.width : frame.height
|
|
32
|
+
)
|
|
55
33
|
|
|
56
|
-
guard
|
|
57
|
-
|
|
34
|
+
guard var result = UseSmileIDVisionDocumentSessionHolder.shared.scanAndDrain(
|
|
35
|
+
frame: frame,
|
|
58
36
|
rotation: rotation
|
|
59
37
|
) else {
|
|
60
38
|
return ["error": "session_not_configured"]
|
|
61
39
|
}
|
|
40
|
+
if let guide = guideTracker?.guide {
|
|
41
|
+
result["documentGuide"] = documentGuideWire(guide)
|
|
42
|
+
}
|
|
62
43
|
return result
|
|
63
44
|
}
|
|
64
45
|
}
|
|
65
46
|
|
|
47
|
+
/// A guide as the JS `documentGuideSchema` reads it.
|
|
48
|
+
func documentGuideWire(_ guide: DocumentGuide) -> [String: Any] {
|
|
49
|
+
["widthMm": guide.widthMm, "frameWidthFraction": guide.frameWidthFraction]
|
|
50
|
+
}
|
|
51
|
+
|
|
66
52
|
/// Crop region from the flat crop args; nil when `cropWidth <= 0` means no region.
|
|
67
|
-
|
|
53
|
+
func kameraCropRegionFrom(_ arguments: [String: Any?]?) -> ViewportCropRegion? {
|
|
68
54
|
guard let width = (arguments?["cropWidth"] as? NSNumber)?.floatValue, width > 0 else {
|
|
69
55
|
return nil
|
|
70
56
|
}
|
|
@@ -23,6 +23,7 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
AsyncFunction("configure") { (config: [String: Any?]) in
|
|
26
|
+
UseSmileIDVisionDocumentSessionHolder.shared.configureGuide(config)
|
|
26
27
|
// The active side selects the `document_front.jpg` /
|
|
27
28
|
// `document_back.jpg` filename; two-sided flows re-`configure` with
|
|
28
29
|
// `back` so the back image doesn't overwrite the front.
|
|
@@ -60,6 +61,17 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
60
61
|
UseSmileIDVisionDocumentKameraTick.analyze(arguments: arguments)
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
// The overlay's guide rectangle, as soon as it lays out: crops the saved capture to it and
|
|
65
|
+
// resolves what it frames. The tracker measures against the session's frame size until the first
|
|
66
|
+
// frame, so the camera gets its zoom before it starts.
|
|
67
|
+
AsyncFunction("setCropRegion") { (arguments: [String: Any?]?) -> [String: Any]? in
|
|
68
|
+
let region = kameraCropRegionFrom(arguments)
|
|
69
|
+
UseSmileIDVisionDocumentSessionHolder.shared.setCropRegion(region)
|
|
70
|
+
guard let tracker = UseSmileIDVisionDocumentSessionHolder.shared.guideTracker else { return nil }
|
|
71
|
+
tracker.setCropRegion(region)
|
|
72
|
+
return tracker.guide.map(documentGuideWire)
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
AsyncFunction("reset") {
|
|
64
76
|
UseSmileIDVisionDocumentSessionHolder.shared.reset()
|
|
65
77
|
}
|
|
@@ -119,20 +131,28 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
119
131
|
private extension Dictionary where Key == String, Value == Any? {
|
|
120
132
|
// JS numbers cross the Expo bridge as `NSNumber`; direct `Int`/`Double` casts
|
|
121
133
|
// silently fail when the bit pattern doesn't match — funnel through `NSNumber`.
|
|
134
|
+
// Unset fields take the native capture screen's defaults (DocumentDetectorConfig), so the document is
|
|
135
|
+
// held as long and judged as the iOS SDK does. Its coverage floor sits at 0.12 because coverage is measured
|
|
136
|
+
// against the full analysis frame, which is wider than the preview shows.
|
|
122
137
|
func toDocumentScanConfig() -> DocumentScanConfig {
|
|
123
|
-
|
|
138
|
+
let screen = DocumentDetectorConfig()
|
|
139
|
+
let expectedAspectRatio = DocumentKind.expectedAspectRatio(
|
|
140
|
+
wireToken: self["documentKind"] as? String,
|
|
141
|
+
knownAspectRatio: (self["knownAspectRatio"] as? NSNumber)?.floatValue
|
|
142
|
+
)
|
|
143
|
+
return DocumentScanConfig(
|
|
124
144
|
documentDetectorConfig: DocumentDetectorConfig(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
145
|
+
iouThreshold: (self["iouThreshold"] as? NSNumber)?.floatValue ?? screen.iouThreshold,
|
|
146
|
+
timeRequired: (self["timeRequired"] as? NSNumber)?.intValue ?? screen.timeRequired,
|
|
147
|
+
minCoverageThreshold: (self["minCoverageThreshold"] as? NSNumber)?.floatValue ?? screen.minCoverageThreshold,
|
|
148
|
+
maxCoverageThreshold: (self["maxCoverageThreshold"] as? NSNumber)?.floatValue ?? screen.maxCoverageThreshold,
|
|
149
|
+
centerToleranceX: (self["centerToleranceX"] as? NSNumber)?.floatValue ?? screen.centerToleranceX,
|
|
150
|
+
centerToleranceY: (self["centerToleranceY"] as? NSNumber)?.floatValue ?? screen.centerToleranceY,
|
|
151
|
+
aspectRatioTolerance: screen.aspectRatioTolerance,
|
|
152
|
+
// The shape gate judges the document's own shape, by the bridge's rule for its kind.
|
|
153
|
+
expectedAspectRatio: expectedAspectRatio
|
|
134
154
|
),
|
|
135
|
-
stayInFoundDuration: (self["stayInFoundDuration"] as? NSNumber)?.intValue ??
|
|
155
|
+
stayInFoundDuration: (self["stayInFoundDuration"] as? NSNumber)?.intValue ?? DocumentScanConfig().stayInFoundDuration
|
|
136
156
|
)
|
|
137
157
|
}
|
|
138
158
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import Kamera
|
|
2
3
|
import UIKit
|
|
3
4
|
import UseSmileIDBridge
|
|
4
5
|
import UseSmileIDVisionDocument
|
|
@@ -27,6 +28,28 @@ final class UseSmileIDVisionDocumentSessionHolder {
|
|
|
27
28
|
|
|
28
29
|
private init() {}
|
|
29
30
|
|
|
31
|
+
private let guideLock = NSLock()
|
|
32
|
+
private var currentGuideTracker: DocumentGuideTracker?
|
|
33
|
+
|
|
34
|
+
/// Keeps what the document guide frames, for the camera's close-focus floor; built at configure
|
|
35
|
+
/// from the document's kind and the session's analysis size, and nil without them.
|
|
36
|
+
var guideTracker: DocumentGuideTracker? {
|
|
37
|
+
guideLock.withLock { currentGuideTracker }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Builds `guideTracker` for the document kind and session size `config` carries.
|
|
41
|
+
func configureGuide(_ config: [String: Any?]) {
|
|
42
|
+
let next = DocumentKind.fromWireToken(config["documentKind"] as? String).flatMap { kind in
|
|
43
|
+
DocumentGuideTracker(
|
|
44
|
+
kind: kind,
|
|
45
|
+
sessionFrameWidth: (config["sessionFrameWidth"] as? NSNumber)?.intValue ?? 0,
|
|
46
|
+
sessionFrameHeight: (config["sessionFrameHeight"] as? NSNumber)?.intValue ?? 0,
|
|
47
|
+
onGuideChanged: { _ in }
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
guideLock.withLock { currentGuideTracker = next }
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/// Replace or clear the active session. Called from the Expo module's
|
|
31
54
|
/// `configure`, `close`, and `OnDestroy` paths.
|
|
32
55
|
func setSession(_ newSession: UseSmileIDDocumentSession?) {
|
|
@@ -90,7 +113,11 @@ final class UseSmileIDVisionDocumentSessionHolder {
|
|
|
90
113
|
|
|
91
114
|
/// Run `session.scan` + `drainCaptureReadyEvents` as one atomic unit, blocking the
|
|
92
115
|
/// caller. Returns `nil` when no session is configured; never throws.
|
|
93
|
-
|
|
116
|
+
///
|
|
117
|
+
/// The session analyses the kamera `frame`'s pixel buffer as the native capture screens do, with
|
|
118
|
+
/// the readings kamera took on it (device motion, tracked face, live-frame quality), and draws an
|
|
119
|
+
/// image only for a frame a capture keeps. The caller keeps `frame` open until this returns.
|
|
120
|
+
func scanAndDrain(frame: any KameraFrame, rotation: Int) -> [String: Any]? {
|
|
94
121
|
var result: [String: Any]?
|
|
95
122
|
operationQueue.sync {
|
|
96
123
|
guard let currentSession = session else {
|
|
@@ -98,8 +125,14 @@ final class UseSmileIDVisionDocumentSessionHolder {
|
|
|
98
125
|
}
|
|
99
126
|
let resultBox = ScanResultBox()
|
|
100
127
|
let semaphore = DispatchSemaphore(value: 0)
|
|
128
|
+
let frameBox = KameraFrameBox(frame)
|
|
101
129
|
Task.detached(priority: .userInitiated) {
|
|
102
|
-
let scanResult = await currentSession.scan(
|
|
130
|
+
guard let scanResult = await currentSession.scan(kameraFrame: frameBox.frame, rotation: rotation) else {
|
|
131
|
+
// A frame with no pixel buffer; the tick only hands over pixel-buffer frames.
|
|
132
|
+
resultBox.value = ["error": "frame_conversion_failed"]
|
|
133
|
+
semaphore.signal()
|
|
134
|
+
return
|
|
135
|
+
}
|
|
103
136
|
let nativeOutput = scanResult.output as? UseSmileIDBridge.DocumentDetectorOutput
|
|
104
137
|
let state = scanResult.state
|
|
105
138
|
let events = currentSession.drainCaptureReadyEvents()
|
|
@@ -117,6 +150,16 @@ final class UseSmileIDVisionDocumentSessionHolder {
|
|
|
117
150
|
}
|
|
118
151
|
}
|
|
119
152
|
|
|
153
|
+
/// Carries the kamera frame into the scan Task. Kamera frames guard their planes with their own
|
|
154
|
+
/// lock, and the caller blocks on the Task while it reads the frame.
|
|
155
|
+
private final class KameraFrameBox: @unchecked Sendable {
|
|
156
|
+
let frame: any KameraFrame
|
|
157
|
+
|
|
158
|
+
init(_ frame: any KameraFrame) {
|
|
159
|
+
self.frame = frame
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
/// Shuttles a scan result from the cooperative-pool Task to the semaphore-blocked caller.
|
|
121
164
|
private final class ScanResultBox: @unchecked Sendable {
|
|
122
165
|
var value: [String: Any] = [:]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/usesmileid_vision_document",
|
|
3
|
-
"version": "12.2.
|
|
3
|
+
"version": "12.2.1-SNAPSHOT.44",
|
|
4
4
|
"description": "Apple Vision document detection implementation for the UseSmileID React Native (Expo) SDK.",
|
|
5
5
|
"homepage": "https://usesmileid.com",
|
|
6
6
|
"repository": {
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"CHANGELOG.md"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@smileid/usesmileid_bridge": "12.2.
|
|
50
|
-
"@smileid/usesmileid_platform_interface": "12.2.
|
|
49
|
+
"@smileid/usesmileid_bridge": "12.2.1-SNAPSHOT.44",
|
|
50
|
+
"@smileid/usesmileid_platform_interface": "12.2.1-SNAPSHOT.44"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"expo": ">=56.0.0",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"react-native": ">=0.83.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@types/node": "^26.6.
|
|
59
|
-
"expo": "~57.0.
|
|
60
|
-
"expo-modules-core": "^57.0.
|
|
58
|
+
"@types/node": "^26.6.4",
|
|
59
|
+
"expo": "~57.0.26",
|
|
60
|
+
"expo-modules-core": "^57.0.20",
|
|
61
61
|
"react-native": "^0.86.3",
|
|
62
62
|
"typescript": "^6.0.3",
|
|
63
63
|
"xcode": "^3.0.1"
|
package/plugin/build/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(i,r,t,e){e===void 0&&(e=t);var o=Object.getOwnPropertyDescriptor(r,t);(!o||("get"in o?!r.__esModule:o.writable||o.configurable))&&(o={enumerable:!0,get:function(){return r[t]}}),Object.defineProperty(i,e,o)}):(function(i,r,t,e){e===void 0&&(e=t),i[e]=r[t]})),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(i,r){Object.defineProperty(i,"default",{enumerable:!0,value:r})}):function(i,r){i.default=r}),__importStar=this&&this.__importStar||(function(){var i=function(r){return i=Object.getOwnPropertyNames||function(t){var e=[];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[e.length]=o);return e},i(r)};return function(r){if(r&&r.__esModule)return r;var t={};if(r!=null)for(var e=i(r),o=0;o<e.length;o++)e[o]!=="default"&&__createBinding(t,r,e[o]);return __setModuleDefault(t,r),t}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.SPM_PRODUCTS=void 0;const config_plugins_1=require("expo/config-plugins"),fs=__importStar(require("fs")),path=__importStar(require("path")),deployment_target_1=require("./lib/deployment_target"),podfile_1=require("./lib/podfile"),signature_dedup_1=require("./lib/signature_dedup"),spm_1=require("./lib/spm"),IOS_DEPLOYMENT_TARGET="16.4",SMILE_ID_PACKAGE={url:"https://github.com/smileidentity/ios-spm",requirement:{kind:"exactVersion",version:"12.2.0"}},POD_TARGET="UseSmileIDVisionDocumentExpo";exports.SPM_PRODUCTS=["UseSmileIDVisionDocument"];const PODFILE_MARKER="usesmileid_vision_document",BRIDGE_POD_TARGET="UseSmileIDBridgeExpo",BRIDGE_SPM_PRODUCTS=["UseSmileIDBridge"],BRIDGE_PODFILE_MARKER="usesmileid_bridge",withVisionDocumentSpm=(i,r)=>(i=(0,deployment_target_1.withIosDeploymentTargetFloor)(i,IOS_DEPLOYMENT_TARGET),i=(0,config_plugins_1.withXcodeProject)(i,t=>{const e=t.modRequest.projectName;if(!e)throw new Error("[usesmileid_vision_document] Unable to resolve host app native target name from Expo config.");return(0,spm_1.linkSpmPackage)(t.modResults,SMILE_ID_PACKAGE,{targetName:e,products:[...exports.SPM_PRODUCTS]}),r?.iosSignatureAggregation==="disabled"?(0,signature_dedup_1.disableSignatureAggregation)(t.modResults,e):(0,signature_dedup_1.ensureSignatureDedupPhase)(t.modResults,e),t}),i=(0,config_plugins_1.withDangerousMod)(i,["ios",t=>{const e=path.join(t.modRequest.platformProjectRoot,"Podfile"),o=fs.readFileSync(e,"utf-8");let n=(0,podfile_1.injectPodfileSpmBlock)(o,{marker:PODFILE_MARKER,package:SMILE_ID_PACKAGE,podTarget:POD_TARGET,products:[...exports.SPM_PRODUCTS]});return n=(0,podfile_1.injectPodfileSpmBlock)(n,{marker:BRIDGE_PODFILE_MARKER,package:SMILE_ID_PACKAGE,podTarget:BRIDGE_POD_TARGET,products:[...BRIDGE_SPM_PRODUCTS],required:!0}),n=(0,podfile_1.injectPodfileKameraLinkBlock)(n),n=(0,podfile_1.injectPodfileSignatureDedupBlock)(n),n!==o&&fs.writeFileSync(e,n),t}]),i);exports.default=withVisionDocumentSpm;
|
|
1
|
+
"use strict";var __createBinding=this&&this.__createBinding||(Object.create?(function(i,r,t,e){e===void 0&&(e=t);var o=Object.getOwnPropertyDescriptor(r,t);(!o||("get"in o?!r.__esModule:o.writable||o.configurable))&&(o={enumerable:!0,get:function(){return r[t]}}),Object.defineProperty(i,e,o)}):(function(i,r,t,e){e===void 0&&(e=t),i[e]=r[t]})),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?(function(i,r){Object.defineProperty(i,"default",{enumerable:!0,value:r})}):function(i,r){i.default=r}),__importStar=this&&this.__importStar||(function(){var i=function(r){return i=Object.getOwnPropertyNames||function(t){var e=[];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[e.length]=o);return e},i(r)};return function(r){if(r&&r.__esModule)return r;var t={};if(r!=null)for(var e=i(r),o=0;o<e.length;o++)e[o]!=="default"&&__createBinding(t,r,e[o]);return __setModuleDefault(t,r),t}})();Object.defineProperty(exports,"__esModule",{value:!0}),exports.SPM_PRODUCTS=void 0;const config_plugins_1=require("expo/config-plugins"),fs=__importStar(require("fs")),path=__importStar(require("path")),deployment_target_1=require("./lib/deployment_target"),podfile_1=require("./lib/podfile"),signature_dedup_1=require("./lib/signature_dedup"),spm_1=require("./lib/spm"),IOS_DEPLOYMENT_TARGET="16.4",SMILE_ID_PACKAGE={url:"https://github.com/smileidentity/ios-spm",requirement:{kind:"exactVersion",version:"12.2.0-SNAPSHOT.20261011230808.38182841286"}},POD_TARGET="UseSmileIDVisionDocumentExpo";exports.SPM_PRODUCTS=["UseSmileIDVisionDocument"];const PODFILE_MARKER="usesmileid_vision_document",BRIDGE_POD_TARGET="UseSmileIDBridgeExpo",BRIDGE_SPM_PRODUCTS=["UseSmileIDBridge"],BRIDGE_PODFILE_MARKER="usesmileid_bridge",withVisionDocumentSpm=(i,r)=>(i=(0,deployment_target_1.withIosDeploymentTargetFloor)(i,IOS_DEPLOYMENT_TARGET),i=(0,config_plugins_1.withXcodeProject)(i,t=>{const e=t.modRequest.projectName;if(!e)throw new Error("[usesmileid_vision_document] Unable to resolve host app native target name from Expo config.");return(0,spm_1.linkSpmPackage)(t.modResults,SMILE_ID_PACKAGE,{targetName:e,products:[...exports.SPM_PRODUCTS]}),r?.iosSignatureAggregation==="disabled"?(0,signature_dedup_1.disableSignatureAggregation)(t.modResults,e):(0,signature_dedup_1.ensureSignatureDedupPhase)(t.modResults,e),t}),i=(0,config_plugins_1.withDangerousMod)(i,["ios",t=>{const e=path.join(t.modRequest.platformProjectRoot,"Podfile"),o=fs.readFileSync(e,"utf-8");let n=(0,podfile_1.injectPodfileSpmBlock)(o,{marker:PODFILE_MARKER,package:SMILE_ID_PACKAGE,podTarget:POD_TARGET,products:[...exports.SPM_PRODUCTS]});return n=(0,podfile_1.injectPodfileSpmBlock)(n,{marker:BRIDGE_PODFILE_MARKER,package:SMILE_ID_PACKAGE,podTarget:BRIDGE_POD_TARGET,products:[...BRIDGE_SPM_PRODUCTS],required:!0}),n=(0,podfile_1.injectPodfileKameraLinkBlock)(n),n=(0,podfile_1.injectPodfileSignatureDedupBlock)(n),n!==o&&fs.writeFileSync(e,n),t}]),i);exports.default=withVisionDocumentSpm;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// MIRRORED FILE — this file is duplicated byte-identical across the two
|
|
2
|
-
// RN Expo iOS bridge modules. Canonical location:
|
|
3
|
-
// usesmileid_vision_face/ios/UseSmileIDVisionFaceModule/
|
|
4
|
-
// UseSmileIDAnalyzerExecutor.swift
|
|
5
|
-
// Mirror location:
|
|
6
|
-
// usesmileid_vision_document/ios/UseSmileIDVisionDocumentModule/
|
|
7
|
-
// UseSmileIDAnalyzerExecutor.swift
|
|
8
|
-
//
|
|
9
|
-
// Each RN module is its own CocoaPods bridge target with its own
|
|
10
|
-
// `source_files` glob; source cannot be shared across them without
|
|
11
|
-
// inventing a new pod. The helper is duplicated verbatim into both
|
|
12
|
-
// vision modules. Any edit here MUST be copied to the other location in
|
|
13
|
-
// the same commit.
|
|
14
|
-
//
|
|
15
|
-
// Purpose: collapse the analyze / flush plumbing of the two RN Expo
|
|
16
|
-
// vision modules into a single generic executor whose Task closures
|
|
17
|
-
// capture only @Sendable values (closures + locals) — no module-class
|
|
18
|
-
// state, no implicit `self`. This forces both modules to share one set
|
|
19
|
-
// of Sendable correctness properties by construction: if one compiles
|
|
20
|
-
// cleanly under strict concurrency, the other must too.
|
|
21
|
-
//
|
|
22
|
-
// RN-specific differences from the Flutter twin:
|
|
23
|
-
// • RN modules receive pre-decoded JPEG/PNG bytes (`UIImage(data:)`),
|
|
24
|
-
// so the executor accepts `UIImage` directly rather than Pigeon's
|
|
25
|
-
// `YuvImage` and an associated decoder. JPEG decoding happens in
|
|
26
|
-
// the per-module wrapper before the executor is invoked.
|
|
27
|
-
// • The completion signature is `(Result<PigeonResult, Error>) -> Void`
|
|
28
|
-
// identical to Flutter — the per-module wrapper translates that
|
|
29
|
-
// into Expo's `Promise.resolve`/`reject`. Keeping the executor's
|
|
30
|
-
// public surface a `Result` consumer means both bridges share the
|
|
31
|
-
// same Task discipline.
|
|
32
|
-
|
|
33
|
-
import ExpoModulesCore
|
|
34
|
-
import UIKit
|
|
35
|
-
import UseSmileIDBridge
|
|
36
|
-
|
|
37
|
-
/// Bridges an Expo Modules @escaping completion handler into Swift 6
|
|
38
|
-
/// Task captures. Expo's `Promise` is itself reference-typed but not
|
|
39
|
-
/// `Sendable`; the executor never captures a `Promise` — it captures a
|
|
40
|
-
/// `Result` consumer closure that the per-module wrapper translates
|
|
41
|
-
/// into `resolve`/`reject`. This box mirrors the Flutter PigeonCompletion
|
|
42
|
-
/// pattern.
|
|
43
|
-
final class ExecutorCompletion<T>: @unchecked Sendable {
|
|
44
|
-
let call: (T) -> Void
|
|
45
|
-
init(_ fn: @escaping (T) -> Void) { self.call = fn }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Generic helper that owns the analyze / flush bridging between the
|
|
49
|
-
/// RN Expo Module's completion-based API and the native session's async
|
|
50
|
-
/// API.
|
|
51
|
-
///
|
|
52
|
-
/// The class itself is `@unchecked Sendable` because it stores only
|
|
53
|
-
/// `@Sendable` closures (which are themselves Sendable). The downstream
|
|
54
|
-
/// `Task.detached` bodies capture those closures — never an instance of a
|
|
55
|
-
/// concrete module class — so the compiler can prove the Task body is
|
|
56
|
-
/// Sendable-by-construction.
|
|
57
|
-
///
|
|
58
|
-
/// `NativeOutput` is the concrete native analyzer output type (e.g.
|
|
59
|
-
/// `FaceDetectorOutput`, `DocumentDetectorOutput`); `Wire` is the
|
|
60
|
-
/// JS-side wire dictionary the module returns to JS (typed as
|
|
61
|
-
/// `[String: Any?]` after Expo bridge marshalling).
|
|
62
|
-
final class UseSmileIDAnalyzerExecutor<NativeOutput: AnalyzerOutput, Wire>: @unchecked Sendable {
|
|
63
|
-
|
|
64
|
-
private let scan: @Sendable (UIImage, Int) async -> (NativeOutput?, UseSmileIDScanState)
|
|
65
|
-
private let drainEvents: @Sendable () -> [UseSmileIDBridge.CaptureReadyEvent]
|
|
66
|
-
private let flushFn: @Sendable () async throws -> Void
|
|
67
|
-
private let buildResult: @Sendable (NativeOutput?, UseSmileIDScanState, [UseSmileIDBridge.CaptureReadyEvent]) -> Wire
|
|
68
|
-
|
|
69
|
-
init(
|
|
70
|
-
scan: @escaping @Sendable (UIImage, Int) async -> (NativeOutput?, UseSmileIDScanState),
|
|
71
|
-
drainEvents: @escaping @Sendable () -> [UseSmileIDBridge.CaptureReadyEvent],
|
|
72
|
-
flush: @escaping @Sendable () async throws -> Void,
|
|
73
|
-
buildResult: @escaping @Sendable (NativeOutput?, UseSmileIDScanState, [UseSmileIDBridge.CaptureReadyEvent]) -> Wire
|
|
74
|
-
) {
|
|
75
|
-
self.scan = scan
|
|
76
|
-
self.drainEvents = drainEvents
|
|
77
|
-
self.flushFn = flush
|
|
78
|
-
self.buildResult = buildResult
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/// Hands the already-decoded `UIImage` to the captured `scan` closure
|
|
82
|
-
/// on a detached task. The Task's capture list explicitly lists every
|
|
83
|
-
/// value it consumes so the compiler can verify the closure is
|
|
84
|
-
/// Sendable without falling back on implicit `self` capture.
|
|
85
|
-
///
|
|
86
|
-
/// The `completion` consumer is a `Result<Wire, Error>` — the
|
|
87
|
-
/// per-module wrapper translates that into `Promise.resolve` /
|
|
88
|
-
/// `Promise.reject`.
|
|
89
|
-
func analyze(
|
|
90
|
-
image: UIImage,
|
|
91
|
-
rotation: Int,
|
|
92
|
-
completion: @escaping (Result<Wire, Error>) -> Void
|
|
93
|
-
) {
|
|
94
|
-
let scan = self.scan
|
|
95
|
-
let drainEvents = self.drainEvents
|
|
96
|
-
let buildResult = self.buildResult
|
|
97
|
-
let completionBox = ExecutorCompletion(completion)
|
|
98
|
-
Task.detached(priority: .userInitiated) {
|
|
99
|
-
let (output, state) = await scan(image, rotation)
|
|
100
|
-
let events = drainEvents()
|
|
101
|
-
completionBox.call(.success(buildResult(output, state, events)))
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/// Awaits the captured `flushFn` on a detached task. Mirrors `analyze`'s
|
|
106
|
-
/// capture discipline — no `self`, no class state.
|
|
107
|
-
func flush(completion: @escaping (Result<Void, Error>) -> Void) {
|
|
108
|
-
let flushFn = self.flushFn
|
|
109
|
-
let completionBox = ExecutorCompletion(completion)
|
|
110
|
-
Task.detached(priority: .userInitiated) {
|
|
111
|
-
do {
|
|
112
|
-
try await flushFn()
|
|
113
|
-
completionBox.call(.success(()))
|
|
114
|
-
} catch {
|
|
115
|
-
completionBox.call(.failure(error))
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|