@succinctlabs/react-native-zcam1 0.4.0-alpha.5 → 0.4.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 (39) hide show
  1. package/Zcam1Sdk.podspec +3 -2
  2. package/android/src/main/java/com/succinctlabs/zcam1sdk/camera/Zcam1CameraService.kt +3 -3
  3. package/ios/Zcam1Camera.swift +177 -9
  4. package/ios/Zcam1CameraFilmStyle.swift +18 -2
  5. package/ios/Zcam1CameraViewManager.m +4 -0
  6. package/ios/Zcam1DepthData.swift +219 -286
  7. package/lib/module/NativeZcam1Capture.js.map +1 -1
  8. package/lib/module/camera.js +49 -4
  9. package/lib/module/camera.js.map +1 -1
  10. package/lib/module/capture.js +57 -54
  11. package/lib/module/capture.js.map +1 -1
  12. package/lib/module/generated/zcam1_verify_utils.js +74 -5
  13. package/lib/module/generated/zcam1_verify_utils.js.map +1 -1
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/picker.js +3 -2
  16. package/lib/module/picker.js.map +1 -1
  17. package/lib/module/verify.js +1 -0
  18. package/lib/module/verify.js.map +1 -1
  19. package/lib/typescript/src/NativeZcam1Capture.d.ts +10 -0
  20. package/lib/typescript/src/NativeZcam1Capture.d.ts.map +1 -1
  21. package/lib/typescript/src/camera.d.ts +21 -0
  22. package/lib/typescript/src/camera.d.ts.map +1 -1
  23. package/lib/typescript/src/capture.d.ts +9 -3
  24. package/lib/typescript/src/capture.d.ts.map +1 -1
  25. package/lib/typescript/src/generated/zcam1_verify_utils.d.ts +127 -4
  26. package/lib/typescript/src/generated/zcam1_verify_utils.d.ts.map +1 -1
  27. package/lib/typescript/src/index.d.ts +2 -2
  28. package/lib/typescript/src/index.d.ts.map +1 -1
  29. package/lib/typescript/src/picker.d.ts.map +1 -1
  30. package/lib/typescript/src/verify.d.ts +1 -0
  31. package/lib/typescript/src/verify.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/NativeZcam1Capture.ts +12 -0
  34. package/src/camera.tsx +75 -1
  35. package/src/capture.tsx +81 -67
  36. package/src/generated/zcam1_verify_utils.ts +86 -5
  37. package/src/index.ts +8 -1
  38. package/src/picker.tsx +3 -2
  39. package/src/verify.tsx +1 -0
package/src/capture.tsx CHANGED
@@ -62,10 +62,15 @@ export type {
62
62
  * Device registration information including keys, certificate chain, and attestation.
63
63
  */
64
64
  export type CaptureInfo = {
65
+ /** Application identifier. On iOS, formatted as `<TEAM_ID>.<BUNDLE_ID>` (e.g. `NLS5R4YCGX.com.example.myapp`). On Android, the app's package name (e.g. `com.example.myapp`). */
65
66
  appId: string;
67
+ /** Unique identifier for the device key. */
66
68
  deviceKeyId: string;
69
+ /** EC public key used to sign captured content. */
67
70
  contentPublicKey: ECKey;
71
+ /** Identifier for the content key. */
68
72
  contentKeyId: Uint8Array;
73
+ /** Device attestation blob. */
69
74
  attestation: string;
70
75
  };
71
76
 
@@ -73,7 +78,9 @@ export type CaptureInfo = {
73
78
  * Configuration settings for device initialization and backend communication.
74
79
  */
75
80
  export type Settings = {
76
- appId: string;
81
+ /** iOS only. The app identifier, formatted as `<TEAM_ID>.<BUNDLE_ID>` (e.g. `NLS5R4YCGX.com.example.myapp`). Required on iOS — omitting it will throw. Ignored on Android, where the package name is derived automatically from the app bundle. */
82
+ appId?: string;
83
+ /** Whether to use the production backend. */
77
84
  production: boolean;
78
85
  };
79
86
 
@@ -96,11 +103,11 @@ export class ZPhoto {
96
103
  * @returns Device information including keys, certificate chain, and attestation
97
104
  */
98
105
  export async function initCapture(settings: Settings): Promise<CaptureInfo> {
99
- const contentPublicKey = await getContentPublicKey();
100
106
  const isSimulator = await isEmulator();
101
-
102
- // On Android, the appId is the package name.
103
- const appId = Platform.OS == "android" ? getBundleId() : settings.appId;
107
+ const appId = getAppId(settings);
108
+ const deviceKeyId = await getAndPersistDeviceKeyId(appId, isSimulator);
109
+ const attestation = await getAndPersistAttestation(deviceKeyId, isSimulator);
110
+ const contentPublicKey = await getContentPublicKey();
104
111
 
105
112
  if (contentPublicKey.kty !== "EC") {
106
113
  throw new Error("Only EC public keys are supported");
@@ -108,74 +115,69 @@ export async function initCapture(settings: Settings): Promise<CaptureInfo> {
108
115
 
109
116
  const contentKeyId = getSecureEnclaveKeyId(contentPublicKey);
110
117
 
111
- let deviceKeyId = await EncryptedStorage.getItem(`deviceKeyId-${settings.appId}`);
112
- let attestation = deviceKeyId
113
- ? await EncryptedStorage.getItem(`attestation-${deviceKeyId}`)
114
- : null;
115
-
116
- if (deviceKeyId == null || attestation == null) {
117
- switch (Platform.OS) {
118
- case "android":
119
- // On Android, getAttestation() creates the key AND returns the attestation
120
- // certificate chain in a single call. generateHardwareKey() is iOS-only.
121
- deviceKeyId = `ZCAM1_ANDROID_DEVICE_${appId}`;
122
-
123
- if (isSimulator) {
124
- // Emulator or device without Play Integrity support — use mock attestation.
125
- console.warn(
126
- "[ZCAM] Play Integrity not available - using mock attestation. This is for development only.",
127
- );
128
- attestation = `SIMULATOR_MOCK_${deviceKeyId}_${Date.now()}`;
129
- } else {
130
- attestation = await getAttestation(deviceKeyId, deviceKeyId);
131
- }
132
- break;
133
-
134
- case "ios":
135
- case "macos":
136
- // iOS: generate key first, then attest separately
137
- if (deviceKeyId == null) {
138
- if (isSimulator) {
139
- console.warn(
140
- "[ZCAM] Running in simulator - using mock device key. This is for development only.",
141
- );
142
- deviceKeyId = `SIMULATOR_DEVICE_KEY_${Date.now()}`;
143
- } else {
144
- deviceKeyId = await generateHardwareKey();
145
- }
146
- }
147
- attestation = await updateRegistration(deviceKeyId!, settings);
148
- break;
149
-
150
- default:
151
- throw new Error(`initCapture: ${Platform.OS} not supported`);
152
- }
153
-
154
- await EncryptedStorage.setItem(`deviceKeyId-${settings.appId}`, deviceKeyId!);
155
- await EncryptedStorage.setItem(`attestation-${deviceKeyId}`, attestation!);
156
- }
157
-
158
- if (deviceKeyId == null) {
159
- throw new Error("Failed to generate a device key");
160
- }
161
-
162
118
  return {
163
- appId,
119
+ appId: appId!,
164
120
  deviceKeyId,
165
121
  contentPublicKey,
166
122
  contentKeyId,
167
- attestation: attestation!,
123
+ attestation,
168
124
  };
169
125
  }
170
126
 
171
- /**
172
- * Updates device registration by performing attestation with the backend.
173
- * @param keyId - The hardware key identifier
174
- * @param settings - Configuration settings for registration
175
- * @returns Attestation data and challenge
176
- */
177
- export async function updateRegistration(keyId: string, _settings: Settings): Promise<string> {
178
- const isSimulator = await isEmulator();
127
+ function getAppId(settings: Settings): string {
128
+ switch (Platform.OS) {
129
+ case "android":
130
+ return getBundleId();
131
+
132
+ case "ios":
133
+ case "macos":
134
+ if (settings.appId === undefined) {
135
+ throw new Error("The appId is required on iOS");
136
+ }
137
+ return settings.appId;
138
+
139
+ default:
140
+ throw new Error(`getAppId: ${Platform.OS} not supported`);
141
+ }
142
+ }
143
+
144
+ async function getAndPersistDeviceKeyId(appId: string, isSimulator: boolean): Promise<string> {
145
+ let deviceKeyId = await EncryptedStorage.getItem(`deviceKeyId-${appId}`);
146
+
147
+ if (deviceKeyId) return deviceKeyId;
148
+
149
+ if (isSimulator) {
150
+ console.warn(
151
+ "[ZCAM] Running in simulator - using mock device key. This is for development only.",
152
+ );
153
+ return `SIMULATOR_DEVICE_KEY_${Date.now()}`;
154
+ }
155
+
156
+ switch (Platform.OS) {
157
+ case "android":
158
+ // On Android, getAttestation() creates the key AND returns the attestation
159
+ // certificate chain in a single call. generateHardwareKey() is iOS-only.
160
+ deviceKeyId = `ZCAM1_ANDROID_DEVICE_${appId}`;
161
+ break;
162
+
163
+ case "ios":
164
+ case "macos":
165
+ deviceKeyId = await generateHardwareKey();
166
+ break;
167
+
168
+ default:
169
+ throw new Error(`getDeviceKeyId: ${Platform.OS} not supported`);
170
+ }
171
+
172
+ await EncryptedStorage.setItem(`deviceKeyId-${appId}`, deviceKeyId);
173
+
174
+ return deviceKeyId;
175
+ }
176
+
177
+ async function getAndPersistAttestation(keyId: string, isSimulator: boolean): Promise<string> {
178
+ let attestation = await EncryptedStorage.getItem(`attestation-${keyId}`);
179
+
180
+ if (attestation) return attestation;
179
181
 
180
182
  if (isSimulator) {
181
183
  console.warn(
@@ -184,12 +186,24 @@ export async function updateRegistration(keyId: string, _settings: Settings): Pr
184
186
  return `SIMULATOR_MOCK_${keyId}_${Date.now()}`;
185
187
  }
186
188
 
187
- const attestation = await getAttestation(keyId, keyId);
189
+ attestation = await getAttestation(keyId, keyId);
190
+
188
191
  await EncryptedStorage.setItem(`attestation-${keyId}`, attestation);
189
192
 
190
193
  return attestation;
191
194
  }
192
195
 
196
+ /**
197
+ * Updates device registration by performing attestation with the backend.
198
+ * @param keyId - The hardware key identifier
199
+ * @returns Attestation data and challenge
200
+ */
201
+ export async function updateRegistration(keyId: string, isSimulator: boolean): Promise<string> {
202
+ await EncryptedStorage.removeItem(`attestation-${keyId}`);
203
+
204
+ return getAndPersistAttestation(keyId, isSimulator);
205
+ }
206
+
193
207
  /**
194
208
  * Requests camera (and microphone) permissions on Android.
195
209
  * No-op on iOS — the system prompts automatically when the camera is accessed.
@@ -136,7 +136,10 @@ export enum VerifyError_Tags {
136
136
  Base64 = "Base64",
137
137
  Io = "Io",
138
138
  Groth16 = "Groth16",
139
+ BindingsNotFound = "BindingsNotFound",
139
140
  ProofNotFound = "ProofNotFound",
141
+ MetadataNotFound = "MetadataNotFound",
142
+ SimulatorNotAllowed = "SimulatorNotAllowed",
140
143
  PlatformNotSupported = "PlatformNotSupported",
141
144
  }
142
145
  export const VerifyError = (() => {
@@ -272,7 +275,7 @@ export const VerifyError = (() => {
272
275
  return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 6;
273
276
  }
274
277
  }
275
- class ProofNotFound extends UniffiError {
278
+ class BindingsNotFound extends UniffiError {
276
279
  /**
277
280
  * @private
278
281
  * This field is private and should not be used.
@@ -284,6 +287,28 @@ export const VerifyError = (() => {
284
287
  */
285
288
  readonly [variantOrdinalSymbol] = 7;
286
289
 
290
+ readonly tag = VerifyError_Tags.BindingsNotFound;
291
+
292
+ constructor(message: string) {
293
+ super("VerifyError", "BindingsNotFound", message);
294
+ }
295
+
296
+ static instanceOf(e: any): e is BindingsNotFound {
297
+ return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 7;
298
+ }
299
+ }
300
+ class ProofNotFound extends UniffiError {
301
+ /**
302
+ * @private
303
+ * This field is private and should not be used.
304
+ */
305
+ readonly [uniffiTypeNameSymbol]: string = "VerifyError";
306
+ /**
307
+ * @private
308
+ * This field is private and should not be used.
309
+ */
310
+ readonly [variantOrdinalSymbol] = 8;
311
+
287
312
  readonly tag = VerifyError_Tags.ProofNotFound;
288
313
 
289
314
  constructor(message: string) {
@@ -291,7 +316,51 @@ export const VerifyError = (() => {
291
316
  }
292
317
 
293
318
  static instanceOf(e: any): e is ProofNotFound {
294
- return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 7;
319
+ return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 8;
320
+ }
321
+ }
322
+ class MetadataNotFound extends UniffiError {
323
+ /**
324
+ * @private
325
+ * This field is private and should not be used.
326
+ */
327
+ readonly [uniffiTypeNameSymbol]: string = "VerifyError";
328
+ /**
329
+ * @private
330
+ * This field is private and should not be used.
331
+ */
332
+ readonly [variantOrdinalSymbol] = 9;
333
+
334
+ readonly tag = VerifyError_Tags.MetadataNotFound;
335
+
336
+ constructor(message: string) {
337
+ super("VerifyError", "MetadataNotFound", message);
338
+ }
339
+
340
+ static instanceOf(e: any): e is MetadataNotFound {
341
+ return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 9;
342
+ }
343
+ }
344
+ class SimulatorNotAllowed extends UniffiError {
345
+ /**
346
+ * @private
347
+ * This field is private and should not be used.
348
+ */
349
+ readonly [uniffiTypeNameSymbol]: string = "VerifyError";
350
+ /**
351
+ * @private
352
+ * This field is private and should not be used.
353
+ */
354
+ readonly [variantOrdinalSymbol] = 10;
355
+
356
+ readonly tag = VerifyError_Tags.SimulatorNotAllowed;
357
+
358
+ constructor(message: string) {
359
+ super("VerifyError", "SimulatorNotAllowed", message);
360
+ }
361
+
362
+ static instanceOf(e: any): e is SimulatorNotAllowed {
363
+ return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 10;
295
364
  }
296
365
  }
297
366
  class PlatformNotSupported extends UniffiError {
@@ -304,7 +373,7 @@ export const VerifyError = (() => {
304
373
  * @private
305
374
  * This field is private and should not be used.
306
375
  */
307
- readonly [variantOrdinalSymbol] = 8;
376
+ readonly [variantOrdinalSymbol] = 11;
308
377
 
309
378
  readonly tag = VerifyError_Tags.PlatformNotSupported;
310
379
 
@@ -313,7 +382,7 @@ export const VerifyError = (() => {
313
382
  }
314
383
 
315
384
  static instanceOf(e: any): e is PlatformNotSupported {
316
- return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 8;
385
+ return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 11;
317
386
  }
318
387
  }
319
388
 
@@ -328,7 +397,10 @@ export const VerifyError = (() => {
328
397
  Base64,
329
398
  Io,
330
399
  Groth16,
400
+ BindingsNotFound,
331
401
  ProofNotFound,
402
+ MetadataNotFound,
403
+ SimulatorNotAllowed,
332
404
  PlatformNotSupported,
333
405
  instanceOf,
334
406
  };
@@ -365,9 +437,18 @@ const FfiConverterTypeVerifyError = (() => {
365
437
  return new VerifyError.Groth16(FfiConverterString.read(from));
366
438
 
367
439
  case 7:
368
- return new VerifyError.ProofNotFound(FfiConverterString.read(from));
440
+ return new VerifyError.BindingsNotFound(FfiConverterString.read(from));
369
441
 
370
442
  case 8:
443
+ return new VerifyError.ProofNotFound(FfiConverterString.read(from));
444
+
445
+ case 9:
446
+ return new VerifyError.MetadataNotFound(FfiConverterString.read(from));
447
+
448
+ case 10:
449
+ return new VerifyError.SimulatorNotAllowed(FfiConverterString.read(from));
450
+
451
+ case 11:
371
452
  return new VerifyError.PlatformNotSupported(FfiConverterString.read(from));
372
453
 
373
454
  default:
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export type {
39
39
  CaptureFormat,
40
40
  FilmStyleEffect,
41
41
  FilmStyleRecipe,
42
+ HardwareShutterAction,
42
43
  HighlightShadowConfig,
43
44
  MonochromeConfig,
44
45
  TakePhotoOptions,
@@ -47,7 +48,13 @@ export type {
47
48
  } from "./camera";
48
49
  export { ZCamera } from "./camera";
49
50
  export type { CaptureInfo, DeviceOrientation } from "./capture";
50
- export { initCapture, previewFile, requestCameraPermission, requestLocationPermission, updateRegistration } from "./capture";
51
+ export {
52
+ initCapture,
53
+ previewFile,
54
+ requestCameraPermission,
55
+ requestLocationPermission,
56
+ updateRegistration,
57
+ } from "./capture";
51
58
 
52
59
  /**
53
60
  * Core cryptographic key types and secure enclave utilities.
package/src/picker.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CameraRoll } from "@react-native-camera-roll/camera-roll";
2
2
  import { FlashList, useRecyclingState } from "@shopify/flash-list";
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
- import { Dimensions, Image, StyleSheet, TouchableOpacity, View } from "react-native";
4
+ import { Dimensions, Image, Platform, StyleSheet, TouchableOpacity, View } from "react-native";
5
5
  import { createThumbnail } from "react-native-create-thumbnail";
6
6
  import { Dirs, FileSystem, Util } from "react-native-file-access";
7
7
 
@@ -141,8 +141,9 @@ const ZImageItem = ({
141
141
  const ext = Util.extname(uri)?.toLowerCase();
142
142
 
143
143
  if (ext === "mov" || ext === "mp4") {
144
+ const url = Platform.OS == "android" ? uri : stripFileProtocol(uri);
144
145
  const thumbnail = await createThumbnail({
145
- url: stripFileProtocol(uri),
146
+ url,
146
147
  });
147
148
  setThumbnail(thumbnail.path);
148
149
  }
package/src/verify.tsx CHANGED
@@ -57,6 +57,7 @@ export class VerifiableFile {
57
57
 
58
58
  /**
59
59
  * Verifies the cryptographic proof embedded in the C2PA manifest.
60
+ * @param appId - The app identifier used during capture. On iOS: `TEAM_ID.BUNDLE_ID` (e.g. `NLS5R4YCGX.com.example.myapp`). On Android: the package name (e.g. `com.example.myapp`).
60
61
  * @returns True if the proof is valid, false otherwise
61
62
  */
62
63
  verifyProof(appId: string): boolean {