@smileid/usesmileid_vision_document 12.0.0-beta01
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 +3 -0
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/app.plugin.js +3 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/ios/UseSmileIDVisionDocumentExpo.podspec +53 -0
- package/ios/UseSmileIDVisionDocumentModule/ResizingFrameStorage.swift +181 -0
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDAnalyzerExecutor.swift +119 -0
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentFrameProcessorPlugin.swift +97 -0
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentModule.swift +221 -0
- package/ios/UseSmileIDVisionDocumentModule/UseSmileIDVisionDocumentSessionHolder.swift +105 -0
- package/package.json +79 -0
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +107 -0
- package/plugin/build/index.js.map +1 -0
- package/plugin/build/lib/podfile.d.ts +17 -0
- package/plugin/build/lib/podfile.js +180 -0
- package/plugin/build/lib/podfile.js.map +1 -0
- package/plugin/build/lib/spm.d.ts +33 -0
- package/plugin/build/lib/spm.js +196 -0
- package/plugin/build/lib/spm.js.map +1 -0
- package/plugin/src/index.ts +88 -0
- package/plugin/src/lib/podfile.ts +209 -0
- package/plugin/src/lib/spm.ts +314 -0
- package/plugin/tsconfig.json +19 -0
- package/src/__tests__/use_smile_id_vision_document.test.ts +79 -0
- package/src/index.ts +42 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
import UseSmileIDBridge
|
|
4
|
+
import UseSmileIDVisionDocument
|
|
5
|
+
import VisionCamera
|
|
6
|
+
|
|
7
|
+
/// Expo Module bridging the existing native session
|
|
8
|
+
/// `UseSmileIDDocumentSession(analyzeFrame:)` (shipped in the
|
|
9
|
+
/// `UseSmileIDVisionDocument` Swift Package) to the JS side.
|
|
10
|
+
///
|
|
11
|
+
/// No ML logic lives here — the native session owns the entire
|
|
12
|
+
/// pipeline. This module adapts the four contract methods (`configure /
|
|
13
|
+
/// reset / flush / close`) and publishes the active session to
|
|
14
|
+
/// `UseSmileIDVisionDocumentSessionHolder` so the frame processor plugin
|
|
15
|
+
/// can feed camera frames directly without crossing the JS thread.
|
|
16
|
+
///
|
|
17
|
+
/// All session operations (configure, reset, flush, close, per-frame scan)
|
|
18
|
+
/// are serialised through `UseSmileIDVisionDocumentSessionHolder.shared`,
|
|
19
|
+
/// which owns the single `operationQueue` serialisation point.
|
|
20
|
+
///
|
|
21
|
+
/// Lifecycle: Expo instantiates exactly one `Module` per app process;
|
|
22
|
+
/// `session` is owned for the module's lifetime.
|
|
23
|
+
public class UseSmileIDVisionDocumentModule: Module {
|
|
24
|
+
private lazy var analyzer: DocumentDetectorAnalyzer = DocumentDetectorAnalyzer.Factory().newInstance()!
|
|
25
|
+
|
|
26
|
+
/// Prevents double-registration of the frame processor plugin when
|
|
27
|
+
/// Expo Go / dev-client fast-refreshes the JS bundle and `OnCreate`
|
|
28
|
+
/// runs again in the same native process.
|
|
29
|
+
private static var isFrameProcessorRegistered = false
|
|
30
|
+
|
|
31
|
+
private var session: UseSmileIDDocumentSession?
|
|
32
|
+
|
|
33
|
+
public func definition() -> ModuleDefinition {
|
|
34
|
+
Name("UseSmileIDVisionDocument")
|
|
35
|
+
|
|
36
|
+
OnCreate {
|
|
37
|
+
// Guard prevents double-registration on dev-client fast-refresh reloads.
|
|
38
|
+
if !Self.isFrameProcessorRegistered {
|
|
39
|
+
FrameProcessorPluginRegistry.addFrameProcessorPlugin("UseSmileIDVisionDocumentFrameProcessor") { proxy, options in
|
|
40
|
+
UseSmileIDVisionDocumentFrameProcessorPlugin(proxy: proxy, options: options)
|
|
41
|
+
}
|
|
42
|
+
Self.isFrameProcessorRegistered = true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
AsyncFunction("configure") { (config: [String: Any?]) in
|
|
47
|
+
// The active side selects the `document_front.jpg` /
|
|
48
|
+
// `document_back.jpg` filename; two-sided flows re-`configure` with
|
|
49
|
+
// `back` so the back image doesn't overwrite the front.
|
|
50
|
+
let documentSide: DocumentSide =
|
|
51
|
+
(config["documentSide"] as? String) == "back" ? .back : .front
|
|
52
|
+
// iOS quality is CGFloat 0..1; the wire key carries an integer 1..100.
|
|
53
|
+
let documentQuality = ((config["documentJpegQuality"] as? NSNumber)?.intValue)
|
|
54
|
+
.map { CGFloat($0) / 100.0 } ?? 0.85
|
|
55
|
+
let storage = ResizingFrameStorage(
|
|
56
|
+
sessionDirectory: Self.freshSessionDirectory(),
|
|
57
|
+
documentSide: documentSide,
|
|
58
|
+
documentQuality: documentQuality
|
|
59
|
+
)
|
|
60
|
+
let captured = self.analyzer
|
|
61
|
+
let newSession = UseSmileIDDocumentSession(
|
|
62
|
+
analyzeFrame: { input, state in
|
|
63
|
+
await captured.analyze(data: input, state: state)
|
|
64
|
+
},
|
|
65
|
+
config: config.toDocumentScanConfig(),
|
|
66
|
+
frameStorage: storage
|
|
67
|
+
)
|
|
68
|
+
self.session = newSession
|
|
69
|
+
// Publish the session through the holder's operation queue so the
|
|
70
|
+
// write is serialised with any concurrent scan on the FP thread.
|
|
71
|
+
UseSmileIDVisionDocumentSessionHolder.shared.setSession(newSession)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AsyncFunction("reset") {
|
|
75
|
+
UseSmileIDVisionDocumentSessionHolder.shared.reset()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
AsyncFunction("flush") { (promise: Promise) in
|
|
79
|
+
// Hop onto a detached task so the Expo queue is not blocked while
|
|
80
|
+
// flush waits behind any in-flight scan on the operation queue.
|
|
81
|
+
Task.detached(priority: .userInitiated) {
|
|
82
|
+
do {
|
|
83
|
+
try UseSmileIDVisionDocumentSessionHolder.shared.flush()
|
|
84
|
+
promise.resolve(nil)
|
|
85
|
+
} catch {
|
|
86
|
+
promise.reject("FLUSH_FAILED", error.localizedDescription)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
AsyncFunction("close") {
|
|
92
|
+
UseSmileIDVisionDocumentSessionHolder.shared.setSession(nil)
|
|
93
|
+
self.session = nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
OnDestroy {
|
|
97
|
+
UseSmileIDVisionDocumentSessionHolder.shared.setSession(nil)
|
|
98
|
+
self.session = nil
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static func freshSessionDirectory() -> URL {
|
|
103
|
+
// Application Support — not Caches — so the OS cannot evict
|
|
104
|
+
// in-flight capture frames between capture and submission under
|
|
105
|
+
// memory pressure. The root is flagged out of iCloud / iTunes
|
|
106
|
+
// backup so biometric frames never leave the device.
|
|
107
|
+
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
|
108
|
+
?? FileManager.default.temporaryDirectory
|
|
109
|
+
var root = support.appendingPathComponent("UseSmileID", isDirectory: true)
|
|
110
|
+
try? FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
|
111
|
+
var resourceValues = URLResourceValues()
|
|
112
|
+
resourceValues.isExcludedFromBackup = true
|
|
113
|
+
try? root.setResourceValues(resourceValues)
|
|
114
|
+
let dir = root.appendingPathComponent(String(Int(Date().timeIntervalSince1970 * 1000)), isDirectory: true)
|
|
115
|
+
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
116
|
+
return dir
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private extension Dictionary where Key == String, Value == Any? {
|
|
121
|
+
// JS numbers cross the Expo bridge as `NSNumber`; direct `Int`/`Double` casts
|
|
122
|
+
// silently fail when the bit pattern doesn't match — funnel through `NSNumber`.
|
|
123
|
+
func toDocumentScanConfig() -> DocumentScanConfig {
|
|
124
|
+
DocumentScanConfig(
|
|
125
|
+
documentDetectorConfig: DocumentDetectorConfig(
|
|
126
|
+
timeout: (self["timeout"] as? NSNumber)?.intValue ?? 15_000,
|
|
127
|
+
iouThreshold: (self["iouThreshold"] as? NSNumber)?.floatValue ?? 0.85,
|
|
128
|
+
timeRequired: (self["timeRequired"] as? NSNumber)?.intValue ?? 500,
|
|
129
|
+
minCoverageThreshold: (self["minCoverageThreshold"] as? NSNumber)?.floatValue ?? 0.18,
|
|
130
|
+
maxCoverageThreshold: (self["maxCoverageThreshold"] as? NSNumber)?.floatValue ?? 0.78,
|
|
131
|
+
centerToleranceX: (self["centerToleranceX"] as? NSNumber)?.floatValue ?? 0.05,
|
|
132
|
+
centerToleranceY: (self["centerToleranceY"] as? NSNumber)?.floatValue ?? 0.05
|
|
133
|
+
),
|
|
134
|
+
stayInFoundDuration: (self["stayInFoundDuration"] as? NSNumber)?.intValue ?? 1_000
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Wire-format converters for the native session result types. Marked
|
|
140
|
+
// `internal` (no access modifier) so both this file and
|
|
141
|
+
// `UseSmileIDVisionDocumentSessionHolder` — which builds the same wire dict
|
|
142
|
+
// inside `scanAndDrain` — share one canonical set. The plugin file uses
|
|
143
|
+
// the holder's `scanAndDrain` return value and never calls these directly.
|
|
144
|
+
|
|
145
|
+
extension UseSmileIDBridge.DocumentDetectorOutput {
|
|
146
|
+
func toWireMap() -> [String: Any] {
|
|
147
|
+
[
|
|
148
|
+
"documents": documents.map { $0.toWireMap() },
|
|
149
|
+
"brightness": Double(brightness)
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
extension UseSmileIDBridge.BoundingBox {
|
|
155
|
+
func toWireMap() -> [String: Any] {
|
|
156
|
+
[
|
|
157
|
+
"left": Double(left),
|
|
158
|
+
"top": Double(top),
|
|
159
|
+
"width": Double(width),
|
|
160
|
+
"height": Double(height),
|
|
161
|
+
"score": Double(score)
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
extension UseSmileIDBridge.CaptureReadyEvent {
|
|
167
|
+
func toWireMap() -> [String: Any] {
|
|
168
|
+
let kindString: String
|
|
169
|
+
let captureIndex: Int
|
|
170
|
+
let urlString: String
|
|
171
|
+
switch self {
|
|
172
|
+
case .selfieFrame(let url):
|
|
173
|
+
kindString = "selfie"
|
|
174
|
+
captureIndex = 0
|
|
175
|
+
urlString = url.absoluteString
|
|
176
|
+
case .livenessFrame(let index, let url):
|
|
177
|
+
kindString = "liveness"
|
|
178
|
+
captureIndex = index
|
|
179
|
+
urlString = url.absoluteString
|
|
180
|
+
case .documentFrame(let url):
|
|
181
|
+
kindString = "document"
|
|
182
|
+
captureIndex = 0
|
|
183
|
+
urlString = url.absoluteString
|
|
184
|
+
}
|
|
185
|
+
return [
|
|
186
|
+
"kind": kindString,
|
|
187
|
+
"captureIndex": captureIndex,
|
|
188
|
+
"url": urlString
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Map the native state-machine type into the wire-string the JS-side
|
|
194
|
+
/// `scanStateTypeSchema` enum accepts. Keep in lock-step with the
|
|
195
|
+
/// `ScanStateType` enum order so cross-platform analytics/dashboards
|
|
196
|
+
/// see consistent values.
|
|
197
|
+
extension UseSmileIDBridge.UseSmileIDScanState {
|
|
198
|
+
func toWireMap() -> [String: Any] {
|
|
199
|
+
let typeString: String
|
|
200
|
+
switch type {
|
|
201
|
+
case .initial: typeString = "initial"
|
|
202
|
+
case .found: typeString = "found"
|
|
203
|
+
case .satisfied: typeString = "satisfied"
|
|
204
|
+
case .unsatisfied: typeString = "unsatisfied"
|
|
205
|
+
case .finished: typeString = "finished"
|
|
206
|
+
case .timeout: typeString = "timeout"
|
|
207
|
+
case .headTurn: typeString = "headTurn"
|
|
208
|
+
}
|
|
209
|
+
var map: [String: Any] = ["type": typeString]
|
|
210
|
+
if let feedbackName {
|
|
211
|
+
map["feedback"] = feedbackName
|
|
212
|
+
}
|
|
213
|
+
if let unsatisfiedReason {
|
|
214
|
+
map["reason"] = unsatisfiedReason
|
|
215
|
+
}
|
|
216
|
+
if let captureProgress {
|
|
217
|
+
map["captureProgress"] = Double(captureProgress)
|
|
218
|
+
}
|
|
219
|
+
return map
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import UseSmileIDBridge
|
|
4
|
+
import UseSmileIDVisionDocument
|
|
5
|
+
|
|
6
|
+
/// Process-wide holder for the active `UseSmileIDDocumentSession`.
|
|
7
|
+
///
|
|
8
|
+
/// All session ops (scan+drain, reset, flush, setSession) serialise through one
|
|
9
|
+
/// `operationQueue`. Deadlock invariant: `Task.detached` bodies inside
|
|
10
|
+
/// `scanAndDrain`/`flush` run on the cooperative pool and never re-enter the holder.
|
|
11
|
+
final class UseSmileIDVisionDocumentSessionHolder {
|
|
12
|
+
static let shared = UseSmileIDVisionDocumentSessionHolder()
|
|
13
|
+
|
|
14
|
+
/// Single serial queue serialising every session operation.
|
|
15
|
+
private let operationQueue = DispatchQueue(
|
|
16
|
+
label: "com.usesmileid.vision.document.session.operations",
|
|
17
|
+
qos: .userInitiated
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
private var session: UseSmileIDDocumentSession?
|
|
21
|
+
|
|
22
|
+
private init() {}
|
|
23
|
+
|
|
24
|
+
/// Replace or clear the active session. Called from the Expo module's
|
|
25
|
+
/// `configure`, `close`, and `OnDestroy` paths.
|
|
26
|
+
func setSession(_ newSession: UseSmileIDDocumentSession?) {
|
|
27
|
+
operationQueue.sync {
|
|
28
|
+
session = newSession
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Run `session.reset()` serialised with any in-flight scan. Called from
|
|
33
|
+
/// the Expo module's `reset` AsyncFunction.
|
|
34
|
+
func reset() {
|
|
35
|
+
operationQueue.sync {
|
|
36
|
+
session?.reset()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Flush the active session, blocking the caller until completion.
|
|
41
|
+
/// Semaphore wait runs inside `operationQueue.sync` — blocks concurrent
|
|
42
|
+
/// scans for the flush duration. Task body runs on cooperative pool; no deadlock.
|
|
43
|
+
func flush() throws {
|
|
44
|
+
var flushError: Error?
|
|
45
|
+
operationQueue.sync {
|
|
46
|
+
guard let currentSession = session else {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
let errorBox = FlushErrorBox()
|
|
50
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
51
|
+
Task.detached(priority: .userInitiated) {
|
|
52
|
+
do {
|
|
53
|
+
try await currentSession.flush()
|
|
54
|
+
} catch {
|
|
55
|
+
errorBox.error = error
|
|
56
|
+
}
|
|
57
|
+
semaphore.signal()
|
|
58
|
+
}
|
|
59
|
+
semaphore.wait()
|
|
60
|
+
flushError = errorBox.error
|
|
61
|
+
}
|
|
62
|
+
if let error = flushError {
|
|
63
|
+
throw error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Run `session.scan` + `drainCaptureReadyEvents` as one atomic unit, blocking the
|
|
68
|
+
/// caller. Returns `nil` when no session is configured; never throws.
|
|
69
|
+
func scanAndDrain(image: UIImage, rotation: Int) -> [String: Any]? {
|
|
70
|
+
var result: [String: Any]?
|
|
71
|
+
operationQueue.sync {
|
|
72
|
+
guard let currentSession = session else {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
let resultBox = ScanResultBox()
|
|
76
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
77
|
+
Task.detached(priority: .userInitiated) {
|
|
78
|
+
let scanResult = await currentSession.scan(image: image, rotation: rotation)
|
|
79
|
+
let nativeOutput = scanResult.output as? UseSmileIDBridge.DocumentDetectorOutput
|
|
80
|
+
let state = scanResult.state
|
|
81
|
+
let events = currentSession.drainCaptureReadyEvents()
|
|
82
|
+
resultBox.value = [
|
|
83
|
+
"output": nativeOutput?.toWireMap() ?? (["documents": [], "brightness": 0.0] as [String: Any]),
|
|
84
|
+
"state": state.toWireMap(),
|
|
85
|
+
"capturedEvents": events.map { $0.toWireMap() }
|
|
86
|
+
]
|
|
87
|
+
semaphore.signal()
|
|
88
|
+
}
|
|
89
|
+
semaphore.wait()
|
|
90
|
+
result = resultBox.value
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Shuttles a scan result from the cooperative-pool Task to the semaphore-blocked caller.
|
|
97
|
+
private final class ScanResultBox: @unchecked Sendable {
|
|
98
|
+
var value: [String: Any] = [:]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Reference-type container used to propagate a flush error across the
|
|
102
|
+
/// same semaphore boundary as `ScanResultBox`.
|
|
103
|
+
private final class FlushErrorBox: @unchecked Sendable {
|
|
104
|
+
var error: Error?
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smileid/usesmileid_vision_document",
|
|
3
|
+
"version": "12.0.0-beta01",
|
|
4
|
+
"description": "UseSmileID v12 — Apple Vision document provider (iOS-only). Thin Expo Module shim around the existing Swift Package `UseSmileIDVisionDocument`.",
|
|
5
|
+
"homepage": "https://usesmileid.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/smileidentity/react-native-expo.git",
|
|
9
|
+
"directory": "usesmileid_vision_document"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/smileidentity/react-native-expo/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Smile Identity <hello@usesmileid.com>",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"expo",
|
|
19
|
+
"expo-module",
|
|
20
|
+
"biometric",
|
|
21
|
+
"kyc",
|
|
22
|
+
"identity-verification",
|
|
23
|
+
"face-detection",
|
|
24
|
+
"document-capture",
|
|
25
|
+
"smile-id"
|
|
26
|
+
],
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./app.plugin.js": "./app.plugin.js"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src",
|
|
40
|
+
"ios",
|
|
41
|
+
"plugin/build",
|
|
42
|
+
"plugin/src",
|
|
43
|
+
"plugin/tsconfig.json",
|
|
44
|
+
"app.plugin.js",
|
|
45
|
+
"expo-module.config.json",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@smileid/usesmileid_bridge": "12.0.0-beta01",
|
|
52
|
+
"@smileid/usesmileid_platform_interface": "12.0.0-beta01"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"expo": ">=56.0.0",
|
|
56
|
+
"expo-modules-core": ">=56.0.0",
|
|
57
|
+
"react-native": ">=0.83.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@expo/config-plugins": "^56.0.8",
|
|
61
|
+
"@types/node": "^25.9.1",
|
|
62
|
+
"expo-modules-core": "^56.0.15",
|
|
63
|
+
"react-native": "^0.85.3",
|
|
64
|
+
"typescript": "^6.0.3"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public",
|
|
68
|
+
"provenance": false
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "pnpm run build:lib && pnpm run build:plugin",
|
|
72
|
+
"build:lib": "tsc -p tsconfig.json",
|
|
73
|
+
"build:plugin": "tsc -p plugin/tsconfig.json",
|
|
74
|
+
"typecheck": "tsc -b && tsc -p plugin/tsconfig.json --noEmit",
|
|
75
|
+
"test": "vitest run",
|
|
76
|
+
"test:watch": "vitest",
|
|
77
|
+
"clean": "rm -rf dist plugin/build .turbo coverage"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Expo config plugin that wires the Smile Identity Swift Package
|
|
3
|
+
// (`https://github.com/smileidentity/ios-spm`) into both:
|
|
4
|
+
// 1. The consuming app's Xcode project — so the .xcframeworks ship
|
|
5
|
+
// and embed in the final .app bundle.
|
|
6
|
+
// 2. The autogenerated Pods.xcodeproj at `pod install` time — so
|
|
7
|
+
// the Expo Module bridge (`UseSmileIDVisionDocumentExpo`) compiles
|
|
8
|
+
// with `import UseSmileIDVisionDocument` / `import UseSmileIDBridge`.
|
|
9
|
+
//
|
|
10
|
+
// Why a plugin: `expo-modules-autolinking@55.0.x`'s
|
|
11
|
+
// `RawModuleConfigApple` schema has no `dependencies` field, so the
|
|
12
|
+
// `apple.dependencies` block in `expo-module.config.json` is silently
|
|
13
|
+
// ignored. Without this plugin, neither the bridge nor the host app
|
|
14
|
+
// know about the SPM package.
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const podfile_1 = require("./lib/podfile");
|
|
53
|
+
const spm_1 = require("./lib/spm");
|
|
54
|
+
// Pinned to the exact `12.0.0-beta01` release tag — `from: 12.0.0`
|
|
55
|
+
// would not resolve because `ios-spm` publishes pre-release tags (e.g.
|
|
56
|
+
// `12.0.0-beta01`), and SPM excludes pre-release tags from open-ended
|
|
57
|
+
// major-version requirements.
|
|
58
|
+
const SMILE_ID_PACKAGE = {
|
|
59
|
+
url: "https://github.com/smileidentity/ios-spm",
|
|
60
|
+
requirement: {
|
|
61
|
+
kind: "exactVersion",
|
|
62
|
+
version: "12.0.0-beta01",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const POD_TARGET = "UseSmileIDVisionDocumentExpo";
|
|
66
|
+
const SPM_PRODUCTS = ["UseSmileIDVisionDocument", "UseSmileIDBridge"];
|
|
67
|
+
const PODFILE_MARKER = "usesmileid_vision_document";
|
|
68
|
+
const withVisionDocumentSpm = (config) => {
|
|
69
|
+
// 1. Add the package + products to the host app's Xcode project so
|
|
70
|
+
// Xcode resolves the binary xcframeworks and embeds them in the
|
|
71
|
+
// .app at link time.
|
|
72
|
+
config = (0, config_plugins_1.withXcodeProject)(config, (config) => {
|
|
73
|
+
const appTarget = config.modRequest.projectName;
|
|
74
|
+
if (!appTarget) {
|
|
75
|
+
throw new Error("[usesmileid_vision_document] Unable to resolve host app native target name from Expo config.");
|
|
76
|
+
}
|
|
77
|
+
(0, spm_1.linkSpmPackage)(config.modResults, SMILE_ID_PACKAGE, {
|
|
78
|
+
targetName: appTarget,
|
|
79
|
+
products: [...SPM_PRODUCTS],
|
|
80
|
+
});
|
|
81
|
+
return config;
|
|
82
|
+
});
|
|
83
|
+
// 2. Inject a Ruby block into the Podfile's post_install hook that
|
|
84
|
+
// re-applies the same wiring to Pods.xcodeproj when CocoaPods
|
|
85
|
+
// materializes it. The bridge pod target needs the products
|
|
86
|
+
// visible at compile time; the host-app step alone isn't enough.
|
|
87
|
+
config = (0, config_plugins_1.withDangerousMod)(config, [
|
|
88
|
+
"ios",
|
|
89
|
+
(config) => {
|
|
90
|
+
const podfilePath = path.join(config.modRequest.platformProjectRoot, "Podfile");
|
|
91
|
+
const original = fs.readFileSync(podfilePath, "utf-8");
|
|
92
|
+
const next = (0, podfile_1.injectPodfileSpmBlock)(original, {
|
|
93
|
+
marker: PODFILE_MARKER,
|
|
94
|
+
package: SMILE_ID_PACKAGE,
|
|
95
|
+
podTarget: POD_TARGET,
|
|
96
|
+
products: [...SPM_PRODUCTS],
|
|
97
|
+
});
|
|
98
|
+
if (next !== original) {
|
|
99
|
+
fs.writeFileSync(podfilePath, next);
|
|
100
|
+
}
|
|
101
|
+
return config;
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
return config;
|
|
105
|
+
};
|
|
106
|
+
exports.default = withVisionDocumentSpm;
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iEAAiE;AACjE,0DAA0D;AAC1D,qEAAqE;AACrE,2CAA2C;AAC3C,mEAAmE;AACnE,wEAAwE;AACxE,2EAA2E;AAC3E,EAAE;AACF,oDAAoD;AACpD,oEAAoE;AACpE,sEAAsE;AACtE,oEAAoE;AACpE,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE9B,yDAI8B;AAC9B,uCAAyB;AACzB,2CAA6B;AAE7B,2CAAsD;AACtD,mCAAuD;AAEvD,mEAAmE;AACnE,uEAAuE;AACvE,sEAAsE;AACtE,8BAA8B;AAC9B,MAAM,gBAAgB,GAAe;IACnC,GAAG,EAAE,0CAA0C;IAC/C,WAAW,EAAE;QACX,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,eAAe;KACzB;CACF,CAAC;AAEF,MAAM,UAAU,GAAG,8BAA8B,CAAC;AAClD,MAAM,YAAY,GAAG,CAAC,0BAA0B,EAAE,kBAAkB,CAAU,CAAC;AAC/E,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAEpD,MAAM,qBAAqB,GAAiB,CAAC,MAAM,EAAE,EAAE;IACrD,mEAAmE;IACnE,mEAAmE;IACnE,wBAAwB;IACxB,MAAM,GAAG,IAAA,iCAAgB,EAAC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;QAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC;QAChD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;QACJ,CAAC;QACD,IAAA,oBAAc,EAAC,MAAM,CAAC,UAAU,EAAE,gBAAgB,EAAE;YAClD,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,CAAC,GAAG,YAAY,CAAC;SAC5B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,mEAAmE;IACnE,iEAAiE;IACjE,+DAA+D;IAC/D,oEAAoE;IACpE,MAAM,GAAG,IAAA,iCAAgB,EAAC,MAAM,EAAE;QAChC,KAAK;QACL,CAAC,MAAM,EAAE,EAAE;YACT,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAC3B,MAAM,CAAC,UAAU,CAAC,mBAAmB,EACrC,SAAS,CACV,CAAC;YACF,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,IAAA,+BAAqB,EAAC,QAAQ,EAAE;gBAC3C,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,gBAAgB;gBACzB,SAAS,EAAE,UAAU;gBACrB,QAAQ,EAAE,CAAC,GAAG,YAAY,CAAC;aAC5B,CAAC,CAAC;YACH,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,kBAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SpmPackage } from "./spm";
|
|
2
|
+
export interface PodfileLinkOptions {
|
|
3
|
+
/** Marker key — distinct per bridge so each plugin owns its own block. */
|
|
4
|
+
marker: string;
|
|
5
|
+
package: SpmPackage;
|
|
6
|
+
/** Pod target name (e.g. `UseSmileIDVisionFaceExpo`). */
|
|
7
|
+
podTarget: string;
|
|
8
|
+
/** SPM library product names to link to the pod target. */
|
|
9
|
+
products: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Idempotently injects an SPM-linking Ruby block into the Podfile's
|
|
13
|
+
* top-level `post_install` block. Re-running on an already-injected
|
|
14
|
+
* Podfile rewrites the same marker block in place (so version /
|
|
15
|
+
* product changes flow through cleanly on the next prebuild).
|
|
16
|
+
*/
|
|
17
|
+
export declare function injectPodfileSpmBlock(podfile: string, options: PodfileLinkOptions): string;
|