@smileid/usesmileid_vision_document 12.0.1 → 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.
@@ -1,314 +0,0 @@
1
- // Shared plumbing for wiring a remote Swift Package into both the
2
- // consuming Expo app's Xcode project and the autogenerated CocoaPods
3
- // project for a specific bridge pod target.
4
- //
5
- // Why this exists: `expo-modules-autolinking@55.0.x`'s
6
- // `RawModuleConfigApple` schema does not surface a `dependencies`
7
- // field, so listing SPM products in `expo-module.config.json` is a
8
- // silent no-op. This module fills that gap.
9
-
10
- import { XcodeProject } from "@expo/config-plugins";
11
-
12
- export interface SpmPackage {
13
- /** Remote git URL of the Swift Package, e.g. `https://github.com/smileidentity/ios-spm`. */
14
- url: string;
15
- /** Version requirement; mirrors Xcode's package resolution UI. */
16
- requirement: SpmRequirement;
17
- }
18
-
19
- export type SpmRequirement =
20
- | { kind: "upToNextMajorVersion"; minimumVersion: string }
21
- | { kind: "upToNextMinorVersion"; minimumVersion: string }
22
- | { kind: "exactVersion"; version: string }
23
- | { kind: "branch"; branch: string };
24
-
25
- export interface SpmTargetLink {
26
- /** Native target name in the project (app target or pod target). */
27
- targetName: string;
28
- /** SPM library product names to attach to the target. */
29
- products: string[];
30
- }
31
-
32
- /**
33
- * Adds (or reuses) a remote Swift Package reference on the given
34
- * Xcode project and links the requested products to the target.
35
- * Idempotent: existing package refs and product deps are reused so
36
- * multiple plugins can compose without conflict.
37
- */
38
- export function linkSpmPackage(
39
- project: XcodeProject,
40
- pkg: SpmPackage,
41
- link: SpmTargetLink,
42
- ): void {
43
- const pkgRef = upsertPackageReference(project, pkg);
44
- const target = findNativeTarget(project, link.targetName);
45
- if (!target) {
46
- throw new Error(
47
- `[smileid-spm] Native target '${link.targetName}' not found in Xcode project.`,
48
- );
49
- }
50
- for (const product of link.products) {
51
- upsertProductDependency(project, target.uuid, pkgRef, product);
52
- }
53
- }
54
-
55
- // Internals — pbxproj surgery. xcode.js doesn't expose SPM helpers,
56
- // so we manipulate the underlying object graph directly.
57
-
58
- interface PackageRef {
59
- uuid: string;
60
- }
61
-
62
- interface NativeTarget {
63
- uuid: string;
64
- obj: PbxNativeTarget;
65
- }
66
-
67
- interface PbxNativeTarget {
68
- isa: "PBXNativeTarget";
69
- name: string;
70
- packageProductDependencies?: Array<{ value: string; comment?: string }>;
71
- buildPhases: Array<{ value: string; comment?: string }>;
72
- }
73
-
74
- interface PbxProject {
75
- isa: "PBXProject";
76
- packageReferences?: Array<{ value: string; comment?: string }>;
77
- }
78
-
79
- function objects(project: XcodeProject): Record<string, unknown> {
80
- const hash = (
81
- project as unknown as {
82
- hash: { project: { objects: Record<string, unknown> } };
83
- }
84
- ).hash.project.objects;
85
- return hash;
86
- }
87
-
88
- function section<T>(project: XcodeProject, isa: string): Record<string, T> {
89
- const objs = objects(project);
90
- if (!objs[isa]) {
91
- objs[isa] = {};
92
- }
93
- return objs[isa] as Record<string, T>;
94
- }
95
-
96
- function generateUuid(project: XcodeProject): string {
97
- return (project as unknown as { generateUuid: () => string }).generateUuid();
98
- }
99
-
100
- function upsertPackageReference(
101
- project: XcodeProject,
102
- pkg: SpmPackage,
103
- ): PackageRef {
104
- const refs = section<{
105
- isa: string;
106
- repositoryURL?: string;
107
- requirement?: object;
108
- }>(project, "XCRemoteSwiftPackageReference");
109
-
110
- for (const [uuid, ref] of Object.entries(refs)) {
111
- if (uuid.endsWith("_comment")) continue;
112
- if (typeof ref !== "object" || ref === null) continue;
113
- if (ref.repositoryURL?.replace(/^"|"$/g, "") === pkg.url) {
114
- return { uuid };
115
- }
116
- }
117
-
118
- const uuid = generateUuid(project);
119
- refs[uuid] = {
120
- isa: "XCRemoteSwiftPackageReference",
121
- repositoryURL: `"${pkg.url}"`,
122
- requirement: serializeRequirement(pkg.requirement),
123
- };
124
- refs[`${uuid}_comment`] =
125
- `XCRemoteSwiftPackageReference "${repoName(pkg.url)}"` as never;
126
-
127
- // Attach to the PBXProject's packageReferences list.
128
- const projectSection = section<PbxProject>(project, "PBXProject");
129
- for (const [projUuid, proj] of Object.entries(projectSection)) {
130
- if (projUuid.endsWith("_comment")) continue;
131
- if (typeof proj !== "object" || proj === null) continue;
132
- if (proj.isa !== "PBXProject") continue;
133
- proj.packageReferences ??= [];
134
- if (!proj.packageReferences.some((r) => r.value === uuid)) {
135
- proj.packageReferences.push({
136
- value: uuid,
137
- comment: `XCRemoteSwiftPackageReference "${repoName(pkg.url)}"`,
138
- });
139
- }
140
- }
141
-
142
- return { uuid };
143
- }
144
-
145
- function upsertProductDependency(
146
- project: XcodeProject,
147
- targetUuid: string,
148
- pkgRef: PackageRef,
149
- productName: string,
150
- ): void {
151
- const targetSection = section<PbxNativeTarget>(project, "PBXNativeTarget");
152
- const target = targetSection[targetUuid];
153
- if (!target || typeof target !== "object") {
154
- throw new Error(
155
- `[smileid-spm] Target ${targetUuid} disappeared between lookups.`,
156
- );
157
- }
158
-
159
- const productDeps = section<{
160
- isa: string;
161
- package?: string;
162
- productName?: string;
163
- }>(project, "XCSwiftPackageProductDependency");
164
-
165
- // Each target owns its own XCSwiftPackageProductDependency entry —
166
- // sharing one across targets is not how Xcode writes pbxproj and
167
- // would cause downstream tools (and re-runs of this plugin) to
168
- // silently drop the second target's binding. Look up the dep
169
- // *within this target's* packageProductDependencies, never globally.
170
- target.packageProductDependencies ??= [];
171
- let depUuid = target.packageProductDependencies.find((d) => {
172
- const entry = productDeps[d.value];
173
- return (
174
- entry !== undefined &&
175
- typeof entry === "object" &&
176
- entry !== null &&
177
- entry.package === pkgRef.uuid &&
178
- entry.productName === productName
179
- );
180
- })?.value;
181
-
182
- if (!depUuid) {
183
- depUuid = generateUuid(project);
184
- productDeps[depUuid] = {
185
- isa: "XCSwiftPackageProductDependency",
186
- package: pkgRef.uuid,
187
- productName,
188
- };
189
- productDeps[`${depUuid}_comment`] = productName as never;
190
- target.packageProductDependencies.push({
191
- value: depUuid,
192
- comment: productName,
193
- });
194
- }
195
-
196
- ensureFrameworkLink(project, target, depUuid, productName);
197
- }
198
-
199
- function ensureFrameworkLink(
200
- project: XcodeProject,
201
- target: PbxNativeTarget,
202
- productDepUuid: string,
203
- productName: string,
204
- ): void {
205
- const frameworksPhase = findFrameworksBuildPhase(project, target);
206
- if (!frameworksPhase) {
207
- throw new Error(
208
- `[smileid-spm] Target '${target.name}' has no PBXFrameworksBuildPhase; cannot link ${productName}.`,
209
- );
210
- }
211
-
212
- const buildFiles = section<{ isa: string; productRef?: string }>(
213
- project,
214
- "PBXBuildFile",
215
- );
216
-
217
- // Scope the existence check to *this target's* frameworks phase.
218
- // A globally-scoped check would early-return when a sibling target
219
- // already linked the same product, leaving this target unlinked.
220
- frameworksPhase.obj.files ??= [];
221
- for (const fileRef of frameworksPhase.obj.files) {
222
- const bf = buildFiles[fileRef.value];
223
- if (
224
- bf !== undefined &&
225
- typeof bf === "object" &&
226
- bf !== null &&
227
- bf.productRef === productDepUuid
228
- ) {
229
- return;
230
- }
231
- }
232
-
233
- const buildFileUuid = generateUuid(project);
234
- buildFiles[buildFileUuid] = {
235
- isa: "PBXBuildFile",
236
- productRef: productDepUuid,
237
- };
238
- buildFiles[`${buildFileUuid}_comment`] =
239
- `${productName} in Frameworks` as never;
240
-
241
- frameworksPhase.obj.files.push({
242
- value: buildFileUuid,
243
- comment: `${productName} in Frameworks`,
244
- });
245
- }
246
-
247
- interface FrameworksPhase {
248
- uuid: string;
249
- obj: {
250
- isa: "PBXFrameworksBuildPhase";
251
- files?: Array<{ value: string; comment?: string }>;
252
- };
253
- }
254
-
255
- function findFrameworksBuildPhase(
256
- project: XcodeProject,
257
- target: PbxNativeTarget,
258
- ): FrameworksPhase | undefined {
259
- const phaseSection = section<{
260
- isa: string;
261
- files?: Array<{ value: string; comment?: string }>;
262
- }>(project, "PBXFrameworksBuildPhase");
263
- for (const phaseRef of target.buildPhases) {
264
- const phase = phaseSection[phaseRef.value];
265
- if (
266
- phase &&
267
- typeof phase === "object" &&
268
- phase.isa === "PBXFrameworksBuildPhase"
269
- ) {
270
- return { uuid: phaseRef.value, obj: phase as FrameworksPhase["obj"] };
271
- }
272
- }
273
- return undefined;
274
- }
275
-
276
- function findNativeTarget(
277
- project: XcodeProject,
278
- name: string,
279
- ): NativeTarget | undefined {
280
- const targets = section<PbxNativeTarget>(project, "PBXNativeTarget");
281
- for (const [uuid, t] of Object.entries(targets)) {
282
- if (uuid.endsWith("_comment")) continue;
283
- if (typeof t !== "object" || t === null) continue;
284
- if (t.isa !== "PBXNativeTarget") continue;
285
- if (t.name === name || t.name === `"${name}"`) {
286
- return { uuid, obj: t };
287
- }
288
- }
289
- return undefined;
290
- }
291
-
292
- function serializeRequirement(req: SpmRequirement): object {
293
- switch (req.kind) {
294
- case "upToNextMajorVersion":
295
- return {
296
- kind: "upToNextMajorVersion",
297
- minimumVersion: req.minimumVersion,
298
- };
299
- case "upToNextMinorVersion":
300
- return {
301
- kind: "upToNextMinorVersion",
302
- minimumVersion: req.minimumVersion,
303
- };
304
- case "exactVersion":
305
- return { kind: "exactVersion", version: req.version };
306
- case "branch":
307
- return { kind: "branch", branch: req.branch };
308
- }
309
- }
310
-
311
- function repoName(url: string): string {
312
- const match = url.replace(/\.git$/, "").match(/\/([^/]+)$/);
313
- return match?.[1] ?? url;
314
- }
@@ -1,19 +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": ["build", "node_modules"]
19
- }
@@ -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
- useSmileIDVisionDocument,
16
- UseSmileIDVisionDocumentIdentifiers,
17
- } from "../index";
18
-
19
- describe("useSmileIDVisionDocument — 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(
26
- "UseSmileIDVisionDocument",
27
- );
28
- });
29
-
30
- it("exposes the lifecycle surface — no per-frame analyze method", () => {
31
- expect(typeof useSmileIDVisionDocument.configure).toBe("function");
32
- expect(typeof useSmileIDVisionDocument.reset).toBe("function");
33
- expect(typeof useSmileIDVisionDocument.close).toBe("function");
34
- // Per-frame analysis lives in the native VisionCamera frame
35
- // processor plugin — raw pixels never cross the JS bridge.
36
- expect("analyze" in useSmileIDVisionDocument).toBe(false);
37
- });
38
-
39
- it("exposes the kind discriminator and frame processor plugin name", () => {
40
- expect(useSmileIDVisionDocument.kind).toBe("document");
41
- expect(useSmileIDVisionDocument.frameProcessorPluginName).toBe(
42
- UseSmileIDVisionDocumentIdentifiers.frameProcessorPluginName,
43
- );
44
- });
45
-
46
- describe("configure", () => {
47
- it("passes the validated config through to native", async () => {
48
- native.configure.mockResolvedValueOnce(undefined);
49
- await useSmileIDVisionDocument.configure({ timeout: 8_000 });
50
- expect(native.configure).toHaveBeenCalledTimes(1);
51
- expect(native.configure).toHaveBeenCalledWith({ timeout: 8_000 });
52
- });
53
-
54
- it("rejects malformed config at the JS boundary before crossing the bridge", async () => {
55
- await expect(
56
- useSmileIDVisionDocument.configure({
57
- // @ts-expect-error — int field with float value
58
- timeout: 8_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 useSmileIDVisionDocument.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 useSmileIDVisionDocument.close();
75
- await useSmileIDVisionDocument.close();
76
- expect(native.close).toHaveBeenCalledTimes(2);
77
- });
78
- });
79
- });
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import type {
2
- DocumentConfig,
3
- DocumentScanResult,
4
- } from "@smileid/usesmileid_bridge";
5
- import { useSmileIDAnalyzerExecutor } from "@smileid/usesmileid_platform_interface";
6
-
7
- /**
8
- * Apple Vision document analyzer (iOS-only).
9
- *
10
- * Thin shim around the existing native session
11
- * `UseSmileID.UseSmileIDDocumentSession(DocumentDetectorAnalyzer())` shipped
12
- * in the `UseSmileIDVisionDocument` Swift Package. The native side runs the
13
- * entire pipeline (analyzer + validator + transitioner + state machine);
14
- * this TS layer routes calls and validates wire payloads at the channel
15
- * boundary.
16
- *
17
- * The canonical wiring lives in [useSmileIDAnalyzerExecutor] in
18
- * `usesmileid_platform_interface`.
19
- *
20
- * Lifecycle: `configure()` → per-frame analysis via the native frame
21
- * processor plugin → `reset()`? → `close()`. See
22
- * `usesmileid_platform_interface` `DocumentAnalyzer` for the canonical
23
- * contract.
24
- */
25
- /**
26
- * Must match the Swift Expo Module's `Name("UseSmileIDVisionDocument")` and
27
- * its VisionCamera frame-processor-plugin registration — drift is a runtime
28
- * "module/plugin not found" error, not a compile error.
29
- */
30
- export const UseSmileIDVisionDocumentIdentifiers = {
31
- nativeModuleName: "UseSmileIDVisionDocument",
32
- frameProcessorPluginName: "UseSmileIDVisionDocumentFrameProcessor",
33
- } as const;
34
-
35
- export const useSmileIDVisionDocument = useSmileIDAnalyzerExecutor({
36
- nativeModuleName: UseSmileIDVisionDocumentIdentifiers.nativeModuleName,
37
- frameProcessorPluginName:
38
- UseSmileIDVisionDocumentIdentifiers.frameProcessorPluginName,
39
- kind: "document",
40
- });
41
-
42
- export type { DocumentConfig, DocumentScanResult };