@succinctlabs/react-native-zcam1 0.2.7 → 0.3.13
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/ios/Zcam1Camera.swift +177 -9
- package/ios/Zcam1CameraFilmStyle.swift +18 -2
- package/ios/Zcam1CameraViewManager.m +4 -0
- package/ios/Zcam1DepthData.swift +219 -286
- package/lib/module/NativeZcam1Capture.js.map +1 -1
- package/lib/module/camera.js +113 -15
- package/lib/module/camera.js.map +1 -1
- package/lib/module/capture.js +21 -3
- package/lib/module/capture.js.map +1 -1
- package/lib/module/common.js +3 -2
- package/lib/module/common.js.map +1 -1
- package/lib/module/generated/zcam1_c2pa_utils.js +85 -6
- package/lib/module/generated/zcam1_c2pa_utils.js.map +1 -1
- package/lib/module/generated/zcam1_verify_utils.js +80 -3
- package/lib/module/generated/zcam1_verify_utils.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils.js +5 -4
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/src/NativeZcam1Capture.d.ts +10 -0
- package/lib/typescript/src/NativeZcam1Capture.d.ts.map +1 -1
- package/lib/typescript/src/camera.d.ts +36 -0
- package/lib/typescript/src/camera.d.ts.map +1 -1
- package/lib/typescript/src/capture.d.ts +8 -1
- package/lib/typescript/src/capture.d.ts.map +1 -1
- package/lib/typescript/src/common.d.ts.map +1 -1
- package/lib/typescript/src/generated/zcam1_c2pa_utils.d.ts +60 -0
- package/lib/typescript/src/generated/zcam1_c2pa_utils.d.ts.map +1 -1
- package/lib/typescript/src/generated/zcam1_verify_utils.d.ts +134 -3
- package/lib/typescript/src/generated/zcam1_verify_utils.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts +1 -1
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/NativeZcam1Capture.ts +12 -0
- package/src/camera.tsx +179 -9
- package/src/capture.tsx +30 -3
- package/src/common.tsx +3 -2
- package/src/generated/zcam1_c2pa_utils.ts +126 -3
- package/src/generated/zcam1_verify_utils.ts +92 -3
- package/src/index.ts +2 -1
- package/src/utils.ts +7 -3
package/src/camera.tsx
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import Geolocation from "@react-native-community/geolocation";
|
|
1
2
|
import JailMonkey from "jail-monkey";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import { requireNativeComponent, type StyleProp, type ViewStyle } from "react-native";
|
|
4
|
-
import { Dirs, Util } from "react-native-file-access";
|
|
5
|
+
import { Dirs, FileSystem, Util } from "react-native-file-access";
|
|
5
6
|
|
|
6
7
|
import {
|
|
8
|
+
AuthenticityData,
|
|
7
9
|
buildSelfSignedCertificate,
|
|
8
10
|
computeHash,
|
|
9
11
|
DepthData,
|
|
10
12
|
ExistingCertChain,
|
|
11
13
|
formatFromPath,
|
|
14
|
+
LocationInfo,
|
|
12
15
|
ManifestEditor,
|
|
13
16
|
type PhotoMetadataInfo,
|
|
14
17
|
SelfSignedCertChain,
|
|
@@ -43,6 +46,13 @@ export type CaptureFormat = "jpeg" | "dng";
|
|
|
43
46
|
*/
|
|
44
47
|
export type CameraFilmStyle = "normal" | "mellow" | "nostalgic" | "bw";
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Hardware shutter action type.
|
|
51
|
+
* - "photo": Full press on volume button or Camera Control. Trigger capture.
|
|
52
|
+
* - "focus": Light press on Camera Control. Trigger focus/zoom (optional).
|
|
53
|
+
*/
|
|
54
|
+
export type HardwareShutterAction = "photo" | "focus";
|
|
55
|
+
|
|
46
56
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
57
|
// Custom Film Style Recipe Types
|
|
48
58
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -153,6 +163,22 @@ export interface ZCameraProps {
|
|
|
153
163
|
* Use with `filmStyle` prop by casting the custom name: `filmStyle={"myStyle" as CameraFilmStyle}`.
|
|
154
164
|
*/
|
|
155
165
|
customFilmStyles?: Record<string, FilmStyleRecipe>;
|
|
166
|
+
/**
|
|
167
|
+
* When true, embeds a trusted GPS timestamp in the C2PA manifest at capture time.
|
|
168
|
+
* Requires location permission. The timestamp is sourced from GPS rather than device clock,
|
|
169
|
+
* making it tamper-evident. Stored as `trustedTimestamp` in photo/video metadata.
|
|
170
|
+
* @default false
|
|
171
|
+
*/
|
|
172
|
+
captureTimestampEnabled?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* When true, embeds GPS coordinates in the C2PA manifest at capture time.
|
|
175
|
+
* Requires location permission. Stored as `location` in photo/video metadata.
|
|
176
|
+
* If location retrieval fails, `isLocationAvailable` is set to `false` and
|
|
177
|
+
* `locationRetrievalStatus` contains the error reason.
|
|
178
|
+
* @default false
|
|
179
|
+
*/
|
|
180
|
+
captureLocationEnabled?: boolean;
|
|
181
|
+
|
|
156
182
|
/**
|
|
157
183
|
* Enable depth data capture at session level.
|
|
158
184
|
* When true, depth data can be captured but zoom may be restricted on dual-camera devices.
|
|
@@ -167,6 +193,21 @@ export interface ZCameraProps {
|
|
|
167
193
|
* @param orientation The new physical orientation of the device.
|
|
168
194
|
*/
|
|
169
195
|
onOrientationChange?: (orientation: DeviceOrientation) => void;
|
|
196
|
+
/**
|
|
197
|
+
* Whether hardware buttons (volume buttons, Camera Control on iPhone 16)
|
|
198
|
+
* should trigger capture events via onHardwareShutter.
|
|
199
|
+
* When enabled, the system volume HUD is automatically suppressed while
|
|
200
|
+
* the camera is active. Requires iOS 17.2+; ignored on older versions.
|
|
201
|
+
* @default true
|
|
202
|
+
*/
|
|
203
|
+
hardwareShutterEnabled?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Callback fired when a hardware capture button is pressed.
|
|
206
|
+
* Volume buttons and Camera Control (iPhone 16) trigger "photo" action.
|
|
207
|
+
* Light press on Camera Control triggers "focus" action.
|
|
208
|
+
* @param action The type of hardware shutter event.
|
|
209
|
+
*/
|
|
210
|
+
onHardwareShutter?: (action: HardwareShutterAction) => void;
|
|
170
211
|
/** Capture information used to generate C2PA bindings for each photo. */
|
|
171
212
|
captureInfo: CaptureInfo;
|
|
172
213
|
/** Optional certificate chain used to sign the C2PA manifest. */
|
|
@@ -206,6 +247,8 @@ type NativeCameraViewProps = {
|
|
|
206
247
|
customFilmStyles?: Record<string, FilmStyleEffect[]>;
|
|
207
248
|
depthEnabled?: boolean;
|
|
208
249
|
onOrientationChange?: (event: { nativeEvent: { orientation: string } }) => void;
|
|
250
|
+
hardwareShutterEnabled?: boolean;
|
|
251
|
+
onHardwareShutter?: (event: { nativeEvent: { action: string } }) => void;
|
|
209
252
|
};
|
|
210
253
|
|
|
211
254
|
/**
|
|
@@ -472,6 +515,16 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
472
515
|
const when = new Date().toISOString().replace("T", " ").split(".")[0]!;
|
|
473
516
|
const isJailBroken = JailMonkey.isJailBroken();
|
|
474
517
|
const isLocationSpoofingAvailable = JailMonkey.canMockLocation();
|
|
518
|
+
const location = await retrieveLocationData(
|
|
519
|
+
this.props.captureTimestampEnabled,
|
|
520
|
+
this.props.captureLocationEnabled,
|
|
521
|
+
);
|
|
522
|
+
const authenticityData: AuthenticityData = {
|
|
523
|
+
isJailBroken,
|
|
524
|
+
isLocationSpoofingAvailable,
|
|
525
|
+
isLocationAvailable: location.isLocationAvailable,
|
|
526
|
+
locationRetrievalStatus: location.locationRetrievalStatus,
|
|
527
|
+
};
|
|
475
528
|
|
|
476
529
|
result.filePath = await embedBindings(
|
|
477
530
|
result.filePath,
|
|
@@ -492,11 +545,10 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
492
545
|
audioCodec: result.audioCodec,
|
|
493
546
|
audioSampleRate: result.audioSampleRate,
|
|
494
547
|
audioChannels: result.audioChannels,
|
|
495
|
-
authenticityData
|
|
496
|
-
isJailBroken,
|
|
497
|
-
isLocationSpoofingAvailable,
|
|
498
|
-
},
|
|
548
|
+
authenticityData,
|
|
499
549
|
filmStyle: this.resolveFilmStyleInfo(),
|
|
550
|
+
trustedTimestamp: location.trustedTimestamp,
|
|
551
|
+
location: location.coords,
|
|
500
552
|
},
|
|
501
553
|
this.props.captureInfo,
|
|
502
554
|
this.certChainPem,
|
|
@@ -543,6 +595,15 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
543
595
|
}
|
|
544
596
|
|
|
545
597
|
const originalPath = result.filePath;
|
|
598
|
+
const depthHeatMapPath = result.depthHeatMapPath as string | undefined;
|
|
599
|
+
const depthRawHash = result.depthRawHash as string | undefined;
|
|
600
|
+
|
|
601
|
+
// Log depth diagnostics for troubleshooting.
|
|
602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
603
|
+
const depthDiag = (result as any)._depthDiag;
|
|
604
|
+
if (depthDiag) {
|
|
605
|
+
console.log("[DEPTH_DIAG]", JSON.stringify(depthDiag, null, 2));
|
|
606
|
+
}
|
|
546
607
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
547
608
|
const metadata = (result.metadata as any) ?? {};
|
|
548
609
|
|
|
@@ -555,6 +616,16 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
555
616
|
const softwareVersion = tiff.Software || "Unknown";
|
|
556
617
|
const isJailBroken = JailMonkey.isJailBroken();
|
|
557
618
|
const isLocationSpoofingAvailable = JailMonkey.canMockLocation();
|
|
619
|
+
const location = await retrieveLocationData(
|
|
620
|
+
this.props.captureTimestampEnabled,
|
|
621
|
+
this.props.captureLocationEnabled,
|
|
622
|
+
);
|
|
623
|
+
const authenticityData: AuthenticityData = {
|
|
624
|
+
isJailBroken,
|
|
625
|
+
isLocationSpoofingAvailable,
|
|
626
|
+
isLocationAvailable: location.isLocationAvailable,
|
|
627
|
+
locationRetrievalStatus: location.locationRetrievalStatus,
|
|
628
|
+
};
|
|
558
629
|
|
|
559
630
|
const destinationPath = await embedBindings(
|
|
560
631
|
originalPath,
|
|
@@ -570,15 +641,16 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
570
641
|
exposureTime: exif.ExposureTime,
|
|
571
642
|
depthOfField: exif.FNumber,
|
|
572
643
|
focalLength: exif.FocalLength,
|
|
573
|
-
authenticityData
|
|
574
|
-
isJailBroken,
|
|
575
|
-
isLocationSpoofingAvailable,
|
|
576
|
-
},
|
|
644
|
+
authenticityData,
|
|
577
645
|
depthData: result.depthData as DepthData | undefined,
|
|
578
646
|
filmStyle: this.resolveFilmStyleInfo(),
|
|
647
|
+
trustedTimestamp: location.trustedTimestamp,
|
|
648
|
+
location: location.coords,
|
|
579
649
|
},
|
|
580
650
|
this.props.captureInfo,
|
|
581
651
|
this.certChainPem,
|
|
652
|
+
depthHeatMapPath,
|
|
653
|
+
depthRawHash,
|
|
582
654
|
);
|
|
583
655
|
|
|
584
656
|
return new ZPhoto(originalPath, destinationPath);
|
|
@@ -601,6 +673,8 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
601
673
|
customFilmStyles,
|
|
602
674
|
depthEnabled = false,
|
|
603
675
|
onOrientationChange,
|
|
676
|
+
hardwareShutterEnabled = true,
|
|
677
|
+
onHardwareShutter,
|
|
604
678
|
style,
|
|
605
679
|
} = this.props;
|
|
606
680
|
|
|
@@ -629,6 +703,12 @@ export class ZCamera extends React.PureComponent<ZCameraProps> {
|
|
|
629
703
|
? (event) => onOrientationChange(event.nativeEvent.orientation as DeviceOrientation)
|
|
630
704
|
: undefined
|
|
631
705
|
}
|
|
706
|
+
hardwareShutterEnabled={hardwareShutterEnabled}
|
|
707
|
+
onHardwareShutter={
|
|
708
|
+
onHardwareShutter
|
|
709
|
+
? (event) => onHardwareShutter(event.nativeEvent.action as HardwareShutterAction)
|
|
710
|
+
: undefined
|
|
711
|
+
}
|
|
632
712
|
/>
|
|
633
713
|
);
|
|
634
714
|
}
|
|
@@ -643,6 +723,8 @@ async function embedBindings(
|
|
|
643
723
|
metadata: PhotoMetadataInfo | VideoMetadataInfo,
|
|
644
724
|
captureInfo: CaptureInfo,
|
|
645
725
|
certChainPem: string,
|
|
726
|
+
depthHeatMapPath?: string,
|
|
727
|
+
depthRawHash?: string,
|
|
646
728
|
): Promise<string> {
|
|
647
729
|
originalPath = stripFileProtocol(originalPath);
|
|
648
730
|
const dataHash = computeHash(originalPath);
|
|
@@ -670,10 +752,40 @@ async function embedBindings(
|
|
|
670
752
|
normalizedMetadata = manifestEditor.addVideoMetadataAction(metadata as VideoMetadataInfo, when);
|
|
671
753
|
}
|
|
672
754
|
|
|
755
|
+
// Embed depth heat map as a C2PA assertion if available.
|
|
756
|
+
// The heat map JPEG is read from disk (written by Swift during capture) — only the
|
|
757
|
+
// file path string crosses the RN bridge, not the binary data.
|
|
758
|
+
if (depthHeatMapPath && format.indexOf("video") < 0) {
|
|
759
|
+
try {
|
|
760
|
+
const heatMapBase64 = await FileSystem.readFile(depthHeatMapPath, "base64");
|
|
761
|
+
const depthData = (metadata as PhotoMetadataInfo).depthData;
|
|
762
|
+
manifestEditor.addAssertion(
|
|
763
|
+
"succinct.depth-heatmap",
|
|
764
|
+
JSON.stringify({
|
|
765
|
+
format: "image/jpeg",
|
|
766
|
+
width: depthData?.width,
|
|
767
|
+
height: depthData?.height,
|
|
768
|
+
colormap: "turbo",
|
|
769
|
+
depth_accuracy: depthData?.accuracy,
|
|
770
|
+
depth_range_min: depthData?.statistics?.min,
|
|
771
|
+
depth_range_max: depthData?.statistics?.max,
|
|
772
|
+
sensor_type: depthData?.pixelFormat,
|
|
773
|
+
raw_depth_hash: depthRawHash,
|
|
774
|
+
image_data: heatMapBase64,
|
|
775
|
+
}),
|
|
776
|
+
);
|
|
777
|
+
} catch (e) {
|
|
778
|
+
console.warn("[embedBindings] Failed to embed depth heat map:", e);
|
|
779
|
+
} finally {
|
|
780
|
+
FileSystem.unlink(depthHeatMapPath).catch(() => {});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
673
784
|
const assertion = await generateAppAttestAssertion(
|
|
674
785
|
dataHash,
|
|
675
786
|
normalizedMetadata,
|
|
676
787
|
captureInfo.deviceKeyId,
|
|
788
|
+
captureInfo.production,
|
|
677
789
|
);
|
|
678
790
|
|
|
679
791
|
// Add an assertion containing all data needed to later generate a proof
|
|
@@ -692,3 +804,61 @@ async function embedBindings(
|
|
|
692
804
|
|
|
693
805
|
return destinationPath;
|
|
694
806
|
}
|
|
807
|
+
|
|
808
|
+
type LocationData = {
|
|
809
|
+
coords: LocationInfo | undefined;
|
|
810
|
+
trustedTimestamp: bigint | undefined;
|
|
811
|
+
isLocationAvailable: boolean | undefined;
|
|
812
|
+
locationRetrievalStatus: string | undefined;
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
function retrieveLocationData(
|
|
816
|
+
captureTimestampEnabled: boolean | undefined,
|
|
817
|
+
captureLocationEnabled: boolean | undefined,
|
|
818
|
+
): Promise<LocationData> {
|
|
819
|
+
if (!captureTimestampEnabled && !captureLocationEnabled) {
|
|
820
|
+
return Promise.resolve({
|
|
821
|
+
coords: undefined,
|
|
822
|
+
trustedTimestamp: undefined,
|
|
823
|
+
isLocationAvailable: undefined,
|
|
824
|
+
locationRetrievalStatus: undefined,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return new Promise((resolve) => {
|
|
829
|
+
Geolocation.getCurrentPosition(
|
|
830
|
+
(position) => {
|
|
831
|
+
resolve({
|
|
832
|
+
coords: captureLocationEnabled
|
|
833
|
+
? {
|
|
834
|
+
latitude: position.coords.latitude.toFixed(6),
|
|
835
|
+
longitude: position.coords.longitude.toFixed(6),
|
|
836
|
+
altitude: position.coords.altitude?.toFixed(6),
|
|
837
|
+
accuracy: position.coords.accuracy.toFixed(6),
|
|
838
|
+
altitudeAccuracy: position.coords.altitudeAccuracy?.toFixed(6),
|
|
839
|
+
}
|
|
840
|
+
: undefined,
|
|
841
|
+
trustedTimestamp: captureTimestampEnabled
|
|
842
|
+
? BigInt(Math.trunc(position.timestamp))
|
|
843
|
+
: undefined,
|
|
844
|
+
isLocationAvailable: true,
|
|
845
|
+
locationRetrievalStatus: "success",
|
|
846
|
+
});
|
|
847
|
+
},
|
|
848
|
+
(error) => {
|
|
849
|
+
console.warn(`[ZCAM1] failed to retrieve GPS location data: ${error.message}`);
|
|
850
|
+
resolve({
|
|
851
|
+
coords: undefined,
|
|
852
|
+
trustedTimestamp: undefined,
|
|
853
|
+
isLocationAvailable: false,
|
|
854
|
+
locationRetrievalStatus: error.message,
|
|
855
|
+
});
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
timeout: 1000, // 1 second
|
|
859
|
+
maximumAge: 60 * 1000, // 1 minute
|
|
860
|
+
enableHighAccuracy: true,
|
|
861
|
+
},
|
|
862
|
+
);
|
|
863
|
+
});
|
|
864
|
+
}
|
package/src/capture.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateHardwareKey, getAttestation } from "@pagopa/io-react-native-integrity";
|
|
2
|
+
import Geolocation from "@react-native-community/geolocation";
|
|
2
3
|
import EncryptedStorage from "react-native-encrypted-storage";
|
|
3
4
|
|
|
4
5
|
import { type ECKey, getContentPublicKey, getSecureEnclaveKeyId } from "./common";
|
|
@@ -51,6 +52,7 @@ export type {
|
|
|
51
52
|
*/
|
|
52
53
|
export type CaptureInfo = {
|
|
53
54
|
appId: string;
|
|
55
|
+
production: boolean;
|
|
54
56
|
deviceKeyId: string;
|
|
55
57
|
contentPublicKey: ECKey;
|
|
56
58
|
contentKeyId: Uint8Array;
|
|
@@ -102,6 +104,12 @@ export async function initCapture(settings: Settings): Promise<CaptureInfo> {
|
|
|
102
104
|
// If running in simulator, hardware key generation is not supported
|
|
103
105
|
const err = error as { code?: string; message?: string } | undefined;
|
|
104
106
|
if (err?.code === "-1" || err?.message?.includes("UNSUPPORTED_SERVICE")) {
|
|
107
|
+
if (settings.production) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"ZCAM: Simulator is not supported in production mode. Set production: false for development.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
console.warn(
|
|
106
114
|
"[ZCAM] Running in simulator - using mock device key. This is for development only.",
|
|
107
115
|
);
|
|
@@ -126,6 +134,7 @@ export async function initCapture(settings: Settings): Promise<CaptureInfo> {
|
|
|
126
134
|
|
|
127
135
|
return {
|
|
128
136
|
appId: settings.appId,
|
|
137
|
+
production: settings.production,
|
|
129
138
|
deviceKeyId,
|
|
130
139
|
contentPublicKey,
|
|
131
140
|
contentKeyId,
|
|
@@ -139,8 +148,8 @@ export async function initCapture(settings: Settings): Promise<CaptureInfo> {
|
|
|
139
148
|
* @param settings - Configuration settings for registration
|
|
140
149
|
* @returns Attestation data and challenge
|
|
141
150
|
*/
|
|
142
|
-
export async function updateRegistration(keyId: string,
|
|
143
|
-
// Try to get real attestation, but fall back to mock for simulator
|
|
151
|
+
export async function updateRegistration(keyId: string, settings: Settings): Promise<string> {
|
|
152
|
+
// Try to get real attestation, but fall back to mock for simulator in dev mode
|
|
144
153
|
let attestation: string;
|
|
145
154
|
try {
|
|
146
155
|
attestation = await getAttestation(keyId, keyId);
|
|
@@ -148,11 +157,15 @@ export async function updateRegistration(keyId: string, _settings: Settings): Pr
|
|
|
148
157
|
// If running in simulator, App Attest is not supported
|
|
149
158
|
const err = error as { code?: string; message?: string } | undefined;
|
|
150
159
|
if (err?.code === "-1" || err?.message?.includes("UNSUPPORTED_SERVICE")) {
|
|
160
|
+
if (settings.production) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
"ZCAM: Simulator is not supported in production mode. Set production: false for development.",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
151
165
|
console.warn(
|
|
152
166
|
"[ZCAM] Running in simulator - using mock attestation. This is for development only.",
|
|
153
167
|
);
|
|
154
168
|
// Use a mock attestation for simulator testing
|
|
155
|
-
// In production, this would need to be rejected by the backend
|
|
156
169
|
return `SIMULATOR_MOCK_${keyId}_${Date.now()}`;
|
|
157
170
|
} else {
|
|
158
171
|
throw error;
|
|
@@ -163,3 +176,17 @@ export async function updateRegistration(keyId: string, _settings: Settings): Pr
|
|
|
163
176
|
|
|
164
177
|
return attestation;
|
|
165
178
|
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Requests location permission from the user.
|
|
182
|
+
* This function triggers the native location authorization prompt on the device.
|
|
183
|
+
* @throws {string} Error message if permission request fails
|
|
184
|
+
*/
|
|
185
|
+
export function requestLocationPermission() {
|
|
186
|
+
Geolocation.requestAuthorization(
|
|
187
|
+
() => {},
|
|
188
|
+
(error) => {
|
|
189
|
+
throw error.message;
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
}
|
package/src/common.tsx
CHANGED
|
@@ -24,8 +24,9 @@ function flexibleBase64Decode(str: string): Uint8Array {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export async function getContentPublicKey(): Promise<PublicKey> {
|
|
27
|
-
return await getPublicKeyFixed(CONTENT_KEY_TAG).catch(() => {
|
|
28
|
-
|
|
27
|
+
return await getPublicKeyFixed(CONTENT_KEY_TAG).catch(async () => {
|
|
28
|
+
await generate(CONTENT_KEY_TAG);
|
|
29
|
+
return getPublicKeyFixed(CONTENT_KEY_TAG);
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -169,6 +169,8 @@ export function formatFromPath(path: string): string | undefined {
|
|
|
169
169
|
export type AuthenticityData = {
|
|
170
170
|
isJailBroken: boolean;
|
|
171
171
|
isLocationSpoofingAvailable: boolean;
|
|
172
|
+
isLocationAvailable: boolean | undefined;
|
|
173
|
+
locationRetrievalStatus: string | undefined;
|
|
172
174
|
};
|
|
173
175
|
|
|
174
176
|
/**
|
|
@@ -206,16 +208,22 @@ const FfiConverterTypeAuthenticityData = (() => {
|
|
|
206
208
|
return {
|
|
207
209
|
isJailBroken: FfiConverterBool.read(from),
|
|
208
210
|
isLocationSpoofingAvailable: FfiConverterBool.read(from),
|
|
211
|
+
isLocationAvailable: FfiConverterOptionalBool.read(from),
|
|
212
|
+
locationRetrievalStatus: FfiConverterOptionalString.read(from),
|
|
209
213
|
};
|
|
210
214
|
}
|
|
211
215
|
write(value: TypeName, into: RustBuffer): void {
|
|
212
216
|
FfiConverterBool.write(value.isJailBroken, into);
|
|
213
217
|
FfiConverterBool.write(value.isLocationSpoofingAvailable, into);
|
|
218
|
+
FfiConverterOptionalBool.write(value.isLocationAvailable, into);
|
|
219
|
+
FfiConverterOptionalString.write(value.locationRetrievalStatus, into);
|
|
214
220
|
}
|
|
215
221
|
allocationSize(value: TypeName): number {
|
|
216
222
|
return (
|
|
217
223
|
FfiConverterBool.allocationSize(value.isJailBroken) +
|
|
218
|
-
FfiConverterBool.allocationSize(value.isLocationSpoofingAvailable)
|
|
224
|
+
FfiConverterBool.allocationSize(value.isLocationSpoofingAvailable) +
|
|
225
|
+
FfiConverterOptionalBool.allocationSize(value.isLocationAvailable) +
|
|
226
|
+
FfiConverterOptionalString.allocationSize(value.locationRetrievalStatus)
|
|
219
227
|
);
|
|
220
228
|
}
|
|
221
229
|
}
|
|
@@ -605,6 +613,95 @@ const FfiConverterTypeFilmStyleInfo = (() => {
|
|
|
605
613
|
return new FFIConverter();
|
|
606
614
|
})();
|
|
607
615
|
|
|
616
|
+
/**
|
|
617
|
+
* GPS location captured at the time of photo/video creation.
|
|
618
|
+
*
|
|
619
|
+
* Coordinates and accuracy are stored as strings to preserve the original
|
|
620
|
+
* precision from the device without floating-point rounding.
|
|
621
|
+
*/
|
|
622
|
+
export type LocationInfo = {
|
|
623
|
+
/**
|
|
624
|
+
* Latitude in decimal degrees (e.g. "37.7749").
|
|
625
|
+
*/
|
|
626
|
+
latitude: string;
|
|
627
|
+
/**
|
|
628
|
+
* Longitude in decimal degrees (e.g. "-122.4194").
|
|
629
|
+
*/
|
|
630
|
+
longitude: string;
|
|
631
|
+
/**
|
|
632
|
+
* Altitude in meters above sea level, if available.
|
|
633
|
+
*/
|
|
634
|
+
altitude: string | undefined;
|
|
635
|
+
/**
|
|
636
|
+
* Horizontal accuracy radius in meters.
|
|
637
|
+
*/
|
|
638
|
+
accuracy: string;
|
|
639
|
+
/**
|
|
640
|
+
* Vertical accuracy in meters, if available.
|
|
641
|
+
*/
|
|
642
|
+
altitudeAccuracy: string | undefined;
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Generated factory for {@link LocationInfo} record objects.
|
|
647
|
+
*/
|
|
648
|
+
export const LocationInfo = (() => {
|
|
649
|
+
const defaults = () => ({});
|
|
650
|
+
const create = (() => {
|
|
651
|
+
return uniffiCreateRecord<LocationInfo, ReturnType<typeof defaults>>(defaults);
|
|
652
|
+
})();
|
|
653
|
+
return Object.freeze({
|
|
654
|
+
/**
|
|
655
|
+
* Create a frozen instance of {@link LocationInfo}, with defaults specified
|
|
656
|
+
* in Rust, in the {@link zcam1_c2pa_utils} crate.
|
|
657
|
+
*/
|
|
658
|
+
create,
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Create a frozen instance of {@link LocationInfo}, with defaults specified
|
|
662
|
+
* in Rust, in the {@link zcam1_c2pa_utils} crate.
|
|
663
|
+
*/
|
|
664
|
+
new: create,
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Defaults specified in the {@link zcam1_c2pa_utils} crate.
|
|
668
|
+
*/
|
|
669
|
+
defaults: () => Object.freeze(defaults()) as Partial<LocationInfo>,
|
|
670
|
+
});
|
|
671
|
+
})();
|
|
672
|
+
|
|
673
|
+
const FfiConverterTypeLocationInfo = (() => {
|
|
674
|
+
type TypeName = LocationInfo;
|
|
675
|
+
class FFIConverter extends AbstractFfiConverterByteArray<TypeName> {
|
|
676
|
+
read(from: RustBuffer): TypeName {
|
|
677
|
+
return {
|
|
678
|
+
latitude: FfiConverterString.read(from),
|
|
679
|
+
longitude: FfiConverterString.read(from),
|
|
680
|
+
altitude: FfiConverterOptionalString.read(from),
|
|
681
|
+
accuracy: FfiConverterString.read(from),
|
|
682
|
+
altitudeAccuracy: FfiConverterOptionalString.read(from),
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
write(value: TypeName, into: RustBuffer): void {
|
|
686
|
+
FfiConverterString.write(value.latitude, into);
|
|
687
|
+
FfiConverterString.write(value.longitude, into);
|
|
688
|
+
FfiConverterOptionalString.write(value.altitude, into);
|
|
689
|
+
FfiConverterString.write(value.accuracy, into);
|
|
690
|
+
FfiConverterOptionalString.write(value.altitudeAccuracy, into);
|
|
691
|
+
}
|
|
692
|
+
allocationSize(value: TypeName): number {
|
|
693
|
+
return (
|
|
694
|
+
FfiConverterString.allocationSize(value.latitude) +
|
|
695
|
+
FfiConverterString.allocationSize(value.longitude) +
|
|
696
|
+
FfiConverterOptionalString.allocationSize(value.altitude) +
|
|
697
|
+
FfiConverterString.allocationSize(value.accuracy) +
|
|
698
|
+
FfiConverterOptionalString.allocationSize(value.altitudeAccuracy)
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return new FFIConverter();
|
|
703
|
+
})();
|
|
704
|
+
|
|
608
705
|
export type PhotoMetadataInfo = {
|
|
609
706
|
deviceMake: string;
|
|
610
707
|
deviceModel: string;
|
|
@@ -619,6 +716,8 @@ export type PhotoMetadataInfo = {
|
|
|
619
716
|
authenticityData: AuthenticityData;
|
|
620
717
|
depthData: DepthData | undefined;
|
|
621
718
|
filmStyle: FilmStyleInfo | undefined;
|
|
719
|
+
trustedTimestamp: /*u64*/ bigint | undefined;
|
|
720
|
+
location: LocationInfo | undefined;
|
|
622
721
|
};
|
|
623
722
|
|
|
624
723
|
/**
|
|
@@ -667,6 +766,8 @@ const FfiConverterTypePhotoMetadataInfo = (() => {
|
|
|
667
766
|
authenticityData: FfiConverterTypeAuthenticityData.read(from),
|
|
668
767
|
depthData: FfiConverterOptionalTypeDepthData.read(from),
|
|
669
768
|
filmStyle: FfiConverterOptionalTypeFilmStyleInfo.read(from),
|
|
769
|
+
trustedTimestamp: FfiConverterOptionalUInt64.read(from),
|
|
770
|
+
location: FfiConverterOptionalTypeLocationInfo.read(from),
|
|
670
771
|
};
|
|
671
772
|
}
|
|
672
773
|
write(value: TypeName, into: RustBuffer): void {
|
|
@@ -683,6 +784,8 @@ const FfiConverterTypePhotoMetadataInfo = (() => {
|
|
|
683
784
|
FfiConverterTypeAuthenticityData.write(value.authenticityData, into);
|
|
684
785
|
FfiConverterOptionalTypeDepthData.write(value.depthData, into);
|
|
685
786
|
FfiConverterOptionalTypeFilmStyleInfo.write(value.filmStyle, into);
|
|
787
|
+
FfiConverterOptionalUInt64.write(value.trustedTimestamp, into);
|
|
788
|
+
FfiConverterOptionalTypeLocationInfo.write(value.location, into);
|
|
686
789
|
}
|
|
687
790
|
allocationSize(value: TypeName): number {
|
|
688
791
|
return (
|
|
@@ -698,7 +801,9 @@ const FfiConverterTypePhotoMetadataInfo = (() => {
|
|
|
698
801
|
FfiConverterUInt32.allocationSize(value.focalLength) +
|
|
699
802
|
FfiConverterTypeAuthenticityData.allocationSize(value.authenticityData) +
|
|
700
803
|
FfiConverterOptionalTypeDepthData.allocationSize(value.depthData) +
|
|
701
|
-
FfiConverterOptionalTypeFilmStyleInfo.allocationSize(value.filmStyle)
|
|
804
|
+
FfiConverterOptionalTypeFilmStyleInfo.allocationSize(value.filmStyle) +
|
|
805
|
+
FfiConverterOptionalUInt64.allocationSize(value.trustedTimestamp) +
|
|
806
|
+
FfiConverterOptionalTypeLocationInfo.allocationSize(value.location)
|
|
702
807
|
);
|
|
703
808
|
}
|
|
704
809
|
}
|
|
@@ -779,6 +884,8 @@ export type VideoMetadataInfo = {
|
|
|
779
884
|
audioChannels: /*u32*/ number | undefined;
|
|
780
885
|
authenticityData: AuthenticityData;
|
|
781
886
|
filmStyle: FilmStyleInfo | undefined;
|
|
887
|
+
trustedTimestamp: /*u64*/ bigint | undefined;
|
|
888
|
+
location: LocationInfo | undefined;
|
|
782
889
|
};
|
|
783
890
|
|
|
784
891
|
/**
|
|
@@ -831,6 +938,8 @@ const FfiConverterTypeVideoMetadataInfo = (() => {
|
|
|
831
938
|
audioChannels: FfiConverterOptionalUInt32.read(from),
|
|
832
939
|
authenticityData: FfiConverterTypeAuthenticityData.read(from),
|
|
833
940
|
filmStyle: FfiConverterOptionalTypeFilmStyleInfo.read(from),
|
|
941
|
+
trustedTimestamp: FfiConverterOptionalUInt64.read(from),
|
|
942
|
+
location: FfiConverterOptionalTypeLocationInfo.read(from),
|
|
834
943
|
};
|
|
835
944
|
}
|
|
836
945
|
write(value: TypeName, into: RustBuffer): void {
|
|
@@ -851,6 +960,8 @@ const FfiConverterTypeVideoMetadataInfo = (() => {
|
|
|
851
960
|
FfiConverterOptionalUInt32.write(value.audioChannels, into);
|
|
852
961
|
FfiConverterTypeAuthenticityData.write(value.authenticityData, into);
|
|
853
962
|
FfiConverterOptionalTypeFilmStyleInfo.write(value.filmStyle, into);
|
|
963
|
+
FfiConverterOptionalUInt64.write(value.trustedTimestamp, into);
|
|
964
|
+
FfiConverterOptionalTypeLocationInfo.write(value.location, into);
|
|
854
965
|
}
|
|
855
966
|
allocationSize(value: TypeName): number {
|
|
856
967
|
return (
|
|
@@ -870,7 +981,9 @@ const FfiConverterTypeVideoMetadataInfo = (() => {
|
|
|
870
981
|
FfiConverterOptionalUInt32.allocationSize(value.audioSampleRate) +
|
|
871
982
|
FfiConverterOptionalUInt32.allocationSize(value.audioChannels) +
|
|
872
983
|
FfiConverterTypeAuthenticityData.allocationSize(value.authenticityData) +
|
|
873
|
-
FfiConverterOptionalTypeFilmStyleInfo.allocationSize(value.filmStyle)
|
|
984
|
+
FfiConverterOptionalTypeFilmStyleInfo.allocationSize(value.filmStyle) +
|
|
985
|
+
FfiConverterOptionalUInt64.allocationSize(value.trustedTimestamp) +
|
|
986
|
+
FfiConverterOptionalTypeLocationInfo.allocationSize(value.location)
|
|
874
987
|
);
|
|
875
988
|
}
|
|
876
989
|
}
|
|
@@ -1683,6 +1796,9 @@ const uniffiTypeManifestStoreObjectFactory: UniffiObjectFactory<ManifestStoreInt
|
|
|
1683
1796
|
// FfiConverter for ManifestStoreInterface
|
|
1684
1797
|
const FfiConverterTypeManifestStore = new FfiConverterObject(uniffiTypeManifestStoreObjectFactory);
|
|
1685
1798
|
|
|
1799
|
+
// FfiConverter for boolean | undefined
|
|
1800
|
+
const FfiConverterOptionalBool = new FfiConverterOptional(FfiConverterBool);
|
|
1801
|
+
|
|
1686
1802
|
// FfiConverter for DepthData | undefined
|
|
1687
1803
|
const FfiConverterOptionalTypeDepthData = new FfiConverterOptional(FfiConverterTypeDepthData);
|
|
1688
1804
|
|
|
@@ -1696,6 +1812,9 @@ const FfiConverterOptionalTypeFilmStyleInfo = new FfiConverterOptional(
|
|
|
1696
1812
|
FfiConverterTypeFilmStyleInfo,
|
|
1697
1813
|
);
|
|
1698
1814
|
|
|
1815
|
+
// FfiConverter for LocationInfo | undefined
|
|
1816
|
+
const FfiConverterOptionalTypeLocationInfo = new FfiConverterOptional(FfiConverterTypeLocationInfo);
|
|
1817
|
+
|
|
1699
1818
|
// FfiConverter for Proof | undefined
|
|
1700
1819
|
const FfiConverterOptionalTypeProof = new FfiConverterOptional(FfiConverterTypeProof);
|
|
1701
1820
|
|
|
@@ -1705,6 +1824,9 @@ const FfiConverterOptionalString = new FfiConverterOptional(FfiConverterString);
|
|
|
1705
1824
|
// FfiConverter for /*u32*/number | undefined
|
|
1706
1825
|
const FfiConverterOptionalUInt32 = new FfiConverterOptional(FfiConverterUInt32);
|
|
1707
1826
|
|
|
1827
|
+
// FfiConverter for /*u64*/bigint | undefined
|
|
1828
|
+
const FfiConverterOptionalUInt64 = new FfiConverterOptional(FfiConverterUInt64);
|
|
1829
|
+
|
|
1708
1830
|
/**
|
|
1709
1831
|
* This should be called before anything else.
|
|
1710
1832
|
*
|
|
@@ -1856,6 +1978,7 @@ export default Object.freeze({
|
|
|
1856
1978
|
FfiConverterTypeDeviceBindings,
|
|
1857
1979
|
FfiConverterTypeExclusion,
|
|
1858
1980
|
FfiConverterTypeFilmStyleInfo,
|
|
1981
|
+
FfiConverterTypeLocationInfo,
|
|
1859
1982
|
FfiConverterTypeManifest,
|
|
1860
1983
|
FfiConverterTypeManifestEditor,
|
|
1861
1984
|
FfiConverterTypeManifestStore,
|