@smileid/usesmileid_vision_document 12.1.0 → 12.2.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,10 +1,18 @@
|
|
|
1
1
|
Full release notes: [Smile ID docs](https://docs.usesmileid.com/developer-resources/sdks/mobile/release-notes/react-native-expo).
|
|
2
2
|
|
|
3
|
+
## 12.2.0
|
|
4
|
+
|
|
5
|
+
See the repository CHANGELOG for the 12.2.0 release notes.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## 12.1.1
|
|
9
|
+
|
|
10
|
+
See the repository CHANGELOG for the 12.1.1 release notes.
|
|
11
|
+
|
|
3
12
|
## 12.1.0
|
|
4
13
|
|
|
5
14
|
See the repository CHANGELOG for the 12.1.0 release notes.
|
|
6
15
|
|
|
7
|
-
|
|
8
16
|
## 12.0.2
|
|
9
17
|
|
|
10
18
|
See the repository CHANGELOG for the 12.0.2 release notes.
|
|
@@ -17,11 +17,34 @@ import UseSmileIDBridge
|
|
|
17
17
|
final class ResizingFrameStorage: FrameStorage {
|
|
18
18
|
/// Longest-edge pixel bound applied to document frames before encoding.
|
|
19
19
|
private static let documentMaxDimension = 1600
|
|
20
|
-
///
|
|
21
|
-
///
|
|
22
|
-
///
|
|
23
|
-
///
|
|
24
|
-
|
|
20
|
+
/// The hidden border kept around the guide rect: this fraction of the saved
|
|
21
|
+
/// image's short side on every edge, the rule of the native bridges'
|
|
22
|
+
/// `DocumentCropGeometry.documentBorderFraction` and the selfie's interior
|
|
23
|
+
/// crop. The border is where presentation-attack cues live; the backend reads
|
|
24
|
+
/// it, and the preview shows only the interior.
|
|
25
|
+
static let documentBorderFraction: CGFloat = 0.15
|
|
26
|
+
|
|
27
|
+
/// The uniform border, in pixels, to grow `guide` by: the b that is
|
|
28
|
+
/// `documentBorderFraction` of the grown rect's own short side,
|
|
29
|
+
/// b = f × s / (1 − 2f) for the guide's short side s. Shrinks to the most
|
|
30
|
+
/// every edge of an `imageSize` image can hold; 0 for an empty guide or one
|
|
31
|
+
/// touching an edge. Mirrors the native bridges' `DocumentCropGeometry.borderAround`.
|
|
32
|
+
static func borderAround(_ guide: CGRect, imageSize: CGSize) -> CGFloat {
|
|
33
|
+
let shortSide = min(guide.width, guide.height)
|
|
34
|
+
guard shortSide > 0 else { return 0 }
|
|
35
|
+
let wanted = documentBorderFraction * shortSide / (1 - 2 * documentBorderFraction)
|
|
36
|
+
let room = min(guide.minX, guide.minY, imageSize.width - guide.maxX, imageSize.height - guide.maxY)
|
|
37
|
+
return max(min(wanted, room), 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// A `border` around a crop of `size` as a fraction of the crop's short side,
|
|
41
|
+
/// held to `documentBorderFraction` so the crop's pixel rounding never reports
|
|
42
|
+
/// more border than was kept; 0 for an empty crop.
|
|
43
|
+
static func borderFraction(_ border: CGFloat, of size: CGSize) -> CGFloat {
|
|
44
|
+
let shortSide = min(size.width, size.height)
|
|
45
|
+
guard shortSide > 0 else { return 0 }
|
|
46
|
+
return min(max(border / shortSide, 0), documentBorderFraction)
|
|
47
|
+
}
|
|
25
48
|
|
|
26
49
|
private let sessionDirectory: URL
|
|
27
50
|
private let captureType: CaptureType
|
|
@@ -34,12 +57,18 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
34
57
|
private let livenessQuality: CGFloat
|
|
35
58
|
private let documentQuality: CGFloat
|
|
36
59
|
private let writer: Writer
|
|
60
|
+
/// The hidden border each document this storage saved kept, recorded by the
|
|
61
|
+
/// write tasks once their file is on disk and read after `flush()`.
|
|
62
|
+
private let savedBorders = SavedDocumentBorders()
|
|
37
63
|
// The analysis tick calls `setCropRegion` on the module's async-function
|
|
38
64
|
// thread; `persist` reads the value on that same serialised scan path before
|
|
39
65
|
// handing it (by value) to the background encode task. The Swift value is a
|
|
40
66
|
// `Sendable` struct captured by value into `Writer.enqueue`, so no background
|
|
41
67
|
// task ever observes a torn read of this property.
|
|
42
|
-
|
|
68
|
+
//
|
|
69
|
+
// Read back as `FrameStorage.cropRegion`, which the document transitioner judges the detected
|
|
70
|
+
// document against, so auto-capture waits for the document inside the outline (ios-v12 #370).
|
|
71
|
+
nonisolated(unsafe) private(set) var cropRegion: ViewportCropRegion?
|
|
43
72
|
|
|
44
73
|
init(
|
|
45
74
|
sessionDirectory: URL,
|
|
@@ -124,7 +153,8 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
124
153
|
resize: resize,
|
|
125
154
|
maxDimension: maxDimension,
|
|
126
155
|
quality: quality,
|
|
127
|
-
documentCropRegion: documentCropRegion
|
|
156
|
+
documentCropRegion: documentCropRegion,
|
|
157
|
+
savedBorders: savedBorders
|
|
128
158
|
)
|
|
129
159
|
}
|
|
130
160
|
}
|
|
@@ -133,6 +163,16 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
133
163
|
try await writer.flush()
|
|
134
164
|
}
|
|
135
165
|
|
|
166
|
+
/// The hidden border the document at `url` was saved with, as a fraction of its
|
|
167
|
+
/// short side on every edge, once `flush()` has returned:
|
|
168
|
+
/// `documentBorderFraction` when the frame had room for all of it, less where it
|
|
169
|
+
/// did not, and 0 for a document saved without a guide crop or a file this
|
|
170
|
+
/// storage did not write. The screens that show the document hide exactly this
|
|
171
|
+
/// much.
|
|
172
|
+
func documentBorderFraction(at url: URL) -> CGFloat {
|
|
173
|
+
savedBorders.fraction(for: url)
|
|
174
|
+
}
|
|
175
|
+
|
|
136
176
|
func clear() {
|
|
137
177
|
// Selfie and document captures share one session directory; delete
|
|
138
178
|
// only this role's own frames.
|
|
@@ -143,6 +183,7 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
143
183
|
)) ?? []
|
|
144
184
|
for fileURL in contents where ownsForClear(fileURL) {
|
|
145
185
|
try? fileManager.removeItem(at: fileURL)
|
|
186
|
+
savedBorders.record(0, for: fileURL)
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
|
|
@@ -170,17 +211,24 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
170
211
|
resize: (Int, Int)?,
|
|
171
212
|
maxDimension: Int?,
|
|
172
213
|
quality: CGFloat,
|
|
173
|
-
documentCropRegion: ViewportCropRegion
|
|
214
|
+
documentCropRegion: ViewportCropRegion?,
|
|
215
|
+
savedBorders: SavedDocumentBorders
|
|
174
216
|
) {
|
|
175
217
|
let task = Task<Void, Error>.detached(priority: .utility) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
218
|
+
do {
|
|
219
|
+
let borderFraction = try Self.encodeAndWrite(
|
|
220
|
+
image: image,
|
|
221
|
+
to: url,
|
|
222
|
+
resize: resize,
|
|
223
|
+
maxDimension: maxDimension,
|
|
224
|
+
quality: quality,
|
|
225
|
+
documentCropRegion: documentCropRegion
|
|
226
|
+
)
|
|
227
|
+
savedBorders.record(borderFraction, for: url)
|
|
228
|
+
} catch {
|
|
229
|
+
savedBorders.record(0, for: url)
|
|
230
|
+
throw error
|
|
231
|
+
}
|
|
184
232
|
}
|
|
185
233
|
inFlight.append(task)
|
|
186
234
|
}
|
|
@@ -208,7 +256,7 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
208
256
|
maxDimension: Int?,
|
|
209
257
|
quality: CGFloat,
|
|
210
258
|
documentCropRegion: ViewportCropRegion?
|
|
211
|
-
) throws {
|
|
259
|
+
) throws -> CGFloat {
|
|
212
260
|
try FileManager.default.createDirectory(
|
|
213
261
|
at: url.deletingLastPathComponent(),
|
|
214
262
|
withIntermediateDirectories: true
|
|
@@ -219,6 +267,8 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
219
267
|
// render the file sideways.
|
|
220
268
|
let upright = image.normalizingOrientation()
|
|
221
269
|
let data: Data?
|
|
270
|
+
// The border the saved document kept, as a fraction of its short side.
|
|
271
|
+
var borderFraction: CGFloat = 0
|
|
222
272
|
if let (width, height) = resize, let cgImage = upright.cgImage {
|
|
223
273
|
// Center-crop and scale to the token-resolved target size
|
|
224
274
|
// (selfie / liveness path).
|
|
@@ -236,20 +286,25 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
236
286
|
let pixelWidth = cgImage.width
|
|
237
287
|
let pixelHeight = cgImage.height
|
|
238
288
|
let box = region.toFrameBoundingBox(frameWidth: pixelWidth, frameHeight: pixelHeight)
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
let
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
289
|
+
// Grow the guide rect by the hidden border (documentBorderFraction of
|
|
290
|
+
// the saved image's short side, uniform on every edge), which the
|
|
291
|
+
// preview never shows; clamp to the image bounds.
|
|
292
|
+
let imageSize = CGSize(width: pixelWidth, height: pixelHeight)
|
|
293
|
+
let guide = CGRect(
|
|
294
|
+
x: CGFloat(box.left) * imageSize.width,
|
|
295
|
+
y: CGFloat(box.top) * imageSize.height,
|
|
296
|
+
width: CGFloat(box.width) * imageSize.width,
|
|
297
|
+
height: CGFloat(box.height) * imageSize.height
|
|
298
|
+
)
|
|
299
|
+
let border = ResizingFrameStorage.borderAround(guide, imageSize: imageSize)
|
|
300
|
+
let cropRect = guide.insetBy(dx: -border, dy: -border)
|
|
301
|
+
.intersection(CGRect(origin: .zero, size: imageSize))
|
|
251
302
|
if let cropped = cgImage.cropping(to: cropRect) {
|
|
252
303
|
cgImage = cropped
|
|
304
|
+
borderFraction = ResizingFrameStorage.borderFraction(
|
|
305
|
+
border,
|
|
306
|
+
of: CGSize(width: cropped.width, height: cropped.height)
|
|
307
|
+
)
|
|
253
308
|
}
|
|
254
309
|
}
|
|
255
310
|
let pixelWidth = cgImage.width
|
|
@@ -277,10 +332,27 @@ final class ResizingFrameStorage: FrameStorage {
|
|
|
277
332
|
throw ResizingFrameStorageError.encodeFailed(url: url)
|
|
278
333
|
}
|
|
279
334
|
try data.write(to: url, options: .atomic)
|
|
335
|
+
return borderFraction
|
|
280
336
|
}
|
|
281
337
|
}
|
|
282
338
|
}
|
|
283
339
|
|
|
340
|
+
/// The hidden border each document a `ResizingFrameStorage` saved kept, as a
|
|
341
|
+
/// fraction of its short side, by file. Shared with the detached write tasks, so it
|
|
342
|
+
/// is lock-guarded.
|
|
343
|
+
final class SavedDocumentBorders: @unchecked Sendable {
|
|
344
|
+
private let lock = NSLock()
|
|
345
|
+
private var fractions: [URL: CGFloat] = [:]
|
|
346
|
+
|
|
347
|
+
func record(_ fraction: CGFloat, for url: URL) {
|
|
348
|
+
lock.withLock { fractions[url.standardizedFileURL] = fraction > 0 ? fraction : nil }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
func fraction(for url: URL) -> CGFloat {
|
|
352
|
+
lock.withLock { fractions[url.standardizedFileURL] ?? 0 }
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
284
356
|
enum ResizingFrameStorageError: Error, Sendable {
|
|
285
357
|
case encodeFailed(url: URL)
|
|
286
358
|
}
|
|
@@ -12,6 +12,9 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
private var session: UseSmileIDDocumentSession?
|
|
15
|
+
/// The latest session's storage, kept past `close` so `documentBorderFraction` can read the
|
|
16
|
+
/// border of a document saved before the flow moved on.
|
|
17
|
+
private var storage: ResizingFrameStorage?
|
|
15
18
|
|
|
16
19
|
public func definition() -> ModuleDefinition {
|
|
17
20
|
Name("UseSmileIDVisionDocument")
|
|
@@ -43,6 +46,7 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
43
46
|
frameStorage: storage
|
|
44
47
|
)
|
|
45
48
|
self.session = newSession
|
|
49
|
+
self.storage = storage
|
|
46
50
|
// Publish the session through the holder's operation queue so the
|
|
47
51
|
// write is serialised with any concurrent scan on the FP thread.
|
|
48
52
|
UseSmileIDVisionDocumentSessionHolder.shared.setSession(newSession)
|
|
@@ -73,6 +77,14 @@ public class UseSmileIDVisionDocumentModule: Module {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
// The hidden border the document at `url` was saved with, as a fraction of its short side,
|
|
81
|
+
// once `flush` has returned; 0 for a document saved without one.
|
|
82
|
+
AsyncFunction("documentBorderFraction") { (url: String) -> Double in
|
|
83
|
+
let fileURL = url.hasPrefix("file:") ? URL(string: url) : URL(fileURLWithPath: url)
|
|
84
|
+
guard let fileURL, let storage = self.storage else { return 0 }
|
|
85
|
+
return Double(storage.documentBorderFraction(at: fileURL))
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
AsyncFunction("close") {
|
|
77
89
|
UseSmileIDVisionDocumentSessionHolder.shared.setSession(nil)
|
|
78
90
|
UseSmileIDVisionDocumentSessionHolder.shared.setStorage(nil)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smileid/usesmileid_vision_document",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.2.0",
|
|
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/
|
|
50
|
-
"@smileid/
|
|
49
|
+
"@smileid/usesmileid_bridge": "12.2.0",
|
|
50
|
+
"@smileid/usesmileid_platform_interface": "12.2.0"
|
|
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.
|
|
59
|
-
"expo": "~57.0.
|
|
60
|
-
"expo-modules-core": "^57.0.
|
|
58
|
+
"@types/node": "^26.6.2",
|
|
59
|
+
"expo": "~57.0.25",
|
|
60
|
+
"expo-modules-core": "^57.0.19",
|
|
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.
|
|
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;
|
|
@@ -145,7 +145,7 @@ begin
|
|
|
145
145
|
# @rpath/Kamera.framework/Kamera"). Xcode embeds a binary product only for an app
|
|
146
146
|
# target that depends on it, so the app target takes both products directly.
|
|
147
147
|
kamera_spm_url = 'https://github.com/smileidentity/kamera-spm'
|
|
148
|
-
kamera_spm_version = JSON.parse(File.read(kamera_package_json))['version'].sub(/-SNAPSHOT
|
|
148
|
+
kamera_spm_version = JSON.parse(File.read(kamera_package_json))['version'].sub(/-SNAPSHOT$/, '')
|
|
149
149
|
kamera_spm_app_products = %w[Kamera KameraVision]
|
|
150
150
|
# A reference an app added by hand may spell the same repository differently (.git suffix,
|
|
151
151
|
# trailing slash, case); a second reference to one repository fails Xcode's resolution.
|