Skip to content

Commit befc9ab

Browse files
committed
feat: add typed manifestStore and activeManifest APIs
1 parent 399ac18 commit befc9ab

File tree

9 files changed

+116
-41
lines changed

9 files changed

+116
-41
lines changed

packages/c2pa-types/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7-
c2pa = { version = "0.58.0", features = ["pdf", "rust_native_crypto", "json_schema"], default-features = false }
7+
c2pa = { version = "0.62.0", features = ["pdf", "rust_native_crypto", "json_schema"], default-features = false }
88
schemars = "0.8.21"
99
serde_json = "1.0.143"

packages/c2pa-types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type { Reader } from './types/ManifestStore.js';
1111

1212
// Renames the auto-generated "Reader" type to the more appropriate "ManifestStore"
13+
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
1314
export interface ManifestStore extends Reader {}
1415

1516
export type { Manifest } from './types/ManifestStore.js';

packages/c2pa-types/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ use schemars::{schema::RootSchema, schema_for};
55

66
fn main() {
77
let output_dir = Path::new("./schemas");
8-
fs::remove_dir_all(output_dir).expect("Could not clear existing schema directory");
8+
9+
if fs::exists(output_dir).unwrap() {
10+
fs::remove_dir_all(output_dir).expect("Could not clear existing schema directory");
11+
}
12+
913
fs::create_dir_all(output_dir).expect("Could not create schema directory");
1014

1115
write_schema(&schema_for!(Reader), &"ManifestStore", output_dir);

packages/c2pa-wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ wasm-bindgen-futures = "0.4.50"
2525
c2pa = { version = "0.62.0", features = ["pdf", "rust_native_crypto", "fetch_remote_manifests"], default-features = false }
2626
async-trait = "0.1.88"
2727
thiserror = "2.0.12"
28+
serde-wasm-bindgen = "0.6.5"
29+
serde = "1.0.219"
2830

2931
[dependencies.web-sys]
3032
version = "0.3.77"

packages/c2pa-wasm/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ pub enum WasmError {
1414
#[error(transparent)]
1515
C2pa(#[from] c2pa::Error),
1616

17+
#[error(transparent)]
18+
Serde(#[from] serde_wasm_bindgen::Error),
19+
1720
#[error(transparent)]
1821
Other(Box<dyn Error>),
1922
}

packages/c2pa-wasm/src/wasm_reader.rs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::io::{Cursor, Read, Seek};
99

1010
use c2pa::{identity::validator::CawgValidator, Reader};
1111
use js_sys::{ArrayBuffer, Error as JsError, Uint8Array};
12+
use serde::Serialize;
13+
use serde_wasm_bindgen::Serializer;
1214
use wasm_bindgen::prelude::*;
1315
use web_sys::Blob;
1416

@@ -18,6 +20,7 @@ use crate::{error::WasmError, stream::BlobStream};
1820
#[wasm_bindgen]
1921
pub struct WasmReader {
2022
reader: Reader,
23+
serializer: Serializer,
2124
}
2225

2326
#[wasm_bindgen]
@@ -33,17 +36,11 @@ impl WasmReader {
3336
format: &str,
3437
stream: impl Read + Seek + Send,
3538
) -> Result<WasmReader, JsError> {
36-
let mut reader = Reader::from_stream_async(format, stream)
39+
let reader = Reader::from_stream_async(format, stream)
3740
.await
3841
.map_err(WasmError::from)?;
3942

40-
// This will be removed once CawgValidation is rolled into the reader
41-
reader
42-
.post_validate_async(&CawgValidator {})
43-
.await
44-
.map_err(WasmError::other)?;
45-
46-
Ok(WasmReader { reader })
43+
Ok(WasmReader::from_reader(reader).await?)
4744
}
4845

4946
/// Attempts to create a new `WasmReader` from an asset format, a `Blob` of the bytes of the initial segment, and a fragment `Blob`.
@@ -64,23 +61,23 @@ impl WasmReader {
6461
init: impl Read + Seek + Send,
6562
fragment: impl Read + Seek + Send,
6663
) -> Result<WasmReader, JsError> {
67-
let mut reader = Reader::from_fragment_async(format, init, fragment)
64+
let reader = Reader::from_fragment_async(format, init, fragment)
6865
.await
6966
.map_err(WasmError::other)?;
7067

68+
Ok(WasmReader::from_reader(reader).await?)
69+
}
70+
71+
async fn from_reader(mut reader: Reader) -> Result<WasmReader, JsError> {
72+
let serializer = Serializer::new().serialize_maps_as_objects(true);
73+
7174
// This will be removed once CawgValidation is rolled into the reader
7275
reader
7376
.post_validate_async(&CawgValidator {})
7477
.await
7578
.map_err(WasmError::other)?;
7679

77-
Ok(WasmReader { reader })
78-
}
79-
80-
/// Returns a JSON representation of the asset's manifest store.
81-
#[wasm_bindgen]
82-
pub fn json(&self) -> String {
83-
self.reader.json()
80+
Ok(WasmReader { reader, serializer })
8481
}
8582

8683
/// Returns the label of the asset's active manifest.
@@ -89,6 +86,37 @@ impl WasmReader {
8986
self.reader.active_label().map(|val| val.to_owned())
9087
}
9188

89+
/// Returns the asset's manifest store.
90+
/// NOTE: at the moment, CAWG data is not decoded via this function. Use WasmReader::json() if CAWG is a requirement.
91+
#[wasm_bindgen(js_name = manifestStore)]
92+
pub fn manifest_store(&self) -> Result<JsValue, JsError> {
93+
let manifest_store = self
94+
.reader
95+
.serialize(&self.serializer)
96+
.map_err(WasmError::from)?;
97+
98+
Ok(manifest_store)
99+
}
100+
101+
/// Returns the asset's active manifest.
102+
/// NOTE: at the moment, CAWG data is not decoded via this function. Use WasmReader::json() if CAWG is a requirement.
103+
#[wasm_bindgen(js_name = activeManifest)]
104+
pub fn active_manifest(&self) -> Result<JsValue, JsError> {
105+
let active_manifest = self
106+
.reader
107+
.active_manifest()
108+
.serialize(&self.serializer)
109+
.map_err(WasmError::from)?;
110+
111+
Ok(active_manifest)
112+
}
113+
114+
/// Returns a JSON representation of the asset's manifest store.
115+
#[wasm_bindgen]
116+
pub fn json(&self) -> String {
117+
self.reader.json()
118+
}
119+
92120
/// Accepts a URI reference to a binary object in the resource store and returns a `js_sys::ArrayBuffer` containing the resource's bytes.
93121
#[wasm_bindgen(js_name = resourceToBuffer)]
94122
pub fn resource_to_buffer(&self, uri: &str) -> Result<ArrayBuffer, JsError> {

packages/c2pa-web/src/lib/c2pa.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('c2pa', () => {
3535

3636
const reader = await c2pa.reader.fromBlob(blob.type, blob);
3737

38-
const manifestStore = await reader.manifestStore();
38+
const manifestStore = await reader.json();
3939

4040
expect(manifestStore).toEqual(C_with_CAWG_data_ManifestStore);
4141

@@ -49,11 +49,11 @@ describe('c2pa', () => {
4949

5050
const reader = await c2pa.reader.fromBlob(blob.type, blob);
5151

52-
const manifestStore = await reader.manifestStore();
52+
const manifestStore = await reader.json();
5353

5454
const activeManifest =
55-
manifestStore.manifests[manifestStore.active_manifest];
56-
const thumbnailId = activeManifest.thumbnail.identifier;
55+
manifestStore.manifests[manifestStore.active_manifest!];
56+
const thumbnailId = activeManifest.thumbnail!.identifier;
5757

5858
const thumbnailBuffer = await reader.resourceToBuffer(thumbnailId);
5959
const thumbnail = new Uint8Array(thumbnailBuffer!);
@@ -100,7 +100,7 @@ describe('c2pa', () => {
100100

101101
const reader = await c2pa.reader.fromBlob(blob.type, blob);
102102

103-
const manifestStore = await reader.manifestStore();
103+
const manifestStore = await reader.json();
104104

105105
expect(manifestStore).toEqual(C_with_CAWG_data_trusted_ManifestStore);
106106

@@ -123,7 +123,7 @@ describe('c2pa', () => {
123123

124124
const reader = await c2pa.reader.fromBlob(blob.type, blob);
125125

126-
const manifestStore = await reader.manifestStore();
126+
const manifestStore = await reader.json();
127127

128128
expect(manifestStore).toEqual(C_with_CAWG_data_untrusted_ManifestStore);
129129

@@ -142,7 +142,7 @@ describe('c2pa', () => {
142142
fragmentBlob
143143
);
144144

145-
const manifestStore = await reader.manifestStore();
145+
const manifestStore = await reader.json();
146146

147147
expect(manifestStore).toEqual(dashinit_ManifestStore);
148148

@@ -159,7 +159,7 @@ describe('c2pa', () => {
159159

160160
await reader.free();
161161

162-
await expect(reader.manifestStore()).rejects.toThrowError();
162+
await expect(reader.json()).rejects.toThrowError();
163163
});
164164
});
165165
});

packages/c2pa-web/src/lib/reader.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* it.
88
*/
99

10+
import { Manifest, ManifestStore } from '@contentauth/c2pa-types';
1011
import { AssetTooLargeError, UnsupportedFormatError } from './error.js';
1112
import { isSupportedReaderFormat } from './supportedFormats.js';
1213
import type { WorkerManager } from './worker/workerManager.js';
@@ -32,15 +33,29 @@ export interface ReaderFactory {
3233
}
3334

3435
export interface Reader {
36+
/**
37+
* @returns The label of the active manifest.
38+
*/
39+
activeLabel: () => Promise<string | null>;
40+
3541
/**
3642
* @returns The asset's full manifest store containing all its manifests, validation statuses, and the URI of the active manifest.
43+
*
44+
* NOTE: At the moment, the manifest store returned by this method will not include decoded CAWG data. Use Reader.json() if CAWG is a requirement.
3745
*/
38-
manifestStore: () => Promise<any>;
46+
manifestStore: () => Promise<ManifestStore>;
3947

4048
/**
41-
* @returns The label of the active manifest.
49+
* @returns The asset's active manifest.
50+
*
51+
* NOTE: At the moment, the manifest returned by this method will not include decoded CAWG data. Use Reader.json() if CAWG is a requirement.
4252
*/
43-
activeLabel: () => Promise<string | null>;
53+
activeManifest: () => Promise<Manifest>;
54+
55+
/**
56+
* @returns The asset's full manifest store, including decoded CAWG data.
57+
*/
58+
json: () => Promise<any>;
4459

4560
/**
4661
* Resolves a URI reference to a binary object (e.g. a thumbnail) in the resource store.
@@ -121,21 +136,35 @@ function createReader(
121136
onFree: () => void
122137
): Reader {
123138
return {
124-
// TODO: manifest type
125-
async manifestStore(): Promise<any> {
126-
const json = await worker.execute({ method: 'reader_json', args: [id] });
127-
128-
const manifestStore = JSON.parse(json);
129-
130-
return manifestStore;
131-
},
132139
async activeLabel(): Promise<string | null> {
133140
const label = await worker.execute({
134141
method: 'reader_activeLabel',
135142
args: [id],
136143
});
137144
return label;
138145
},
146+
async manifestStore(): Promise<ManifestStore> {
147+
const manifestStore = await worker.execute({
148+
method: 'reader_manifestStore',
149+
args: [id],
150+
});
151+
return manifestStore;
152+
},
153+
async activeManifest(): Promise<Manifest> {
154+
const activeManifest = await worker.execute({
155+
method: 'reader_activeManifest',
156+
args: [id],
157+
});
158+
159+
return activeManifest;
160+
},
161+
async json(): Promise<any> {
162+
const json = await worker.execute({ method: 'reader_json', args: [id] });
163+
164+
const manifestStore = JSON.parse(json);
165+
166+
return manifestStore;
167+
},
139168
async resourceToBuffer(uri: string): Promise<ArrayBuffer> {
140169
return worker.execute({
141170
method: 'reader_resourceToBuffer',

packages/c2pa-web/src/lib/worker.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,22 @@ const workerFunctions = {
4949
},
5050

5151
// Reader object methods
52-
reader_json(readerId: number): WorkerResponse<string> {
53-
const reader = readerMap.get(readerId);
54-
return { data: reader.json() };
55-
},
5652
reader_activeLabel(readerId: number): WorkerResponse<string | null> {
5753
const reader = readerMap.get(readerId);
5854
return { data: reader.activeLabel() ?? null };
5955
},
56+
reader_manifestStore(readerId: number): WorkerResponse<any> {
57+
const reader = readerMap.get(readerId);
58+
return { data: reader.manifestStore() };
59+
},
60+
reader_activeManifest(readerId: number): WorkerResponse<any> {
61+
const reader = readerMap.get(readerId);
62+
return { data: reader.activeManifest() };
63+
},
64+
reader_json(readerId: number): WorkerResponse<string> {
65+
const reader = readerMap.get(readerId);
66+
return { data: reader.json() };
67+
},
6068
reader_resourceToBuffer(
6169
readerId: number,
6270
uri: string

0 commit comments

Comments
 (0)