Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/five-rice-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@contentauth/c2pa-types': minor
'@contentauth/c2pa-wasm': minor
'@contentauth/c2pa-web': minor
---

Added reader.manifestStore() and reader.activeManifest() APIs
51 changes: 49 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
resolver = '2'
members = [
'packages/c2pa-wasm',
'packages/c2pa-types',
]

[profile.release]
Expand Down
2 changes: 2 additions & 0 deletions packages/c2pa-types/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schemas
types
9 changes: 9 additions & 0 deletions packages/c2pa-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "c2pa-types"
version = "0.1.0"
edition = "2024"

[dependencies]
c2pa = { version = "0.62.0", features = ["pdf", "rust_native_crypto", "json_schema"], default-features = false }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the pdf feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just matching the features of the V1 SDK. I don't know if clients actively need this but we do support PDFs on Verify right now and the plan is to migrate that site to use this SDK.

schemars = "0.8.21"
serde_json = "1.0.143"
16 changes: 16 additions & 0 deletions packages/c2pa-types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*
* NOTICE: Adobe permits you to use, modify, and distribute this file in
* accordance with the terms of the Adobe license agreement accompanying
* it.
*/

import type { Reader } from './types/ManifestStore.js';

// Renames the auto-generated "Reader" type to the more appropriate "ManifestStore"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ManifestStore because you only deal with the "extracted" JSON afterwards?

Copy link
Contributor Author

@emensch emensch Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type really is just a ManifestStore despite being called a "Reader," and if I don't rename it I think it will be confusing to consumers. I.e. "why does Reader.manifestStore() return a Reader?"

// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
export interface ManifestStore extends Reader {}

export type { Manifest } from './types/ManifestStore.js';
17 changes: 17 additions & 0 deletions packages/c2pa-types/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@contentauth/c2pa-types",
"description": "Typescript types generated from c2pa-rs",
"type": "module",
"version": "0.1.1",
"types": "index.d.ts",
"scripts": {
"test": "echo \"No tests to run\" && exit 0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why keep it then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of an annoying hangup with the monorepo system I'm using right now (NX). It expects a "test" command to be available for each published package and will error out if there's no command to run, so I've put this in place to get around that.

Frankly I'll be looking to get off of NX quite soon following this and would happily revisit this when I migrate.

"build": "pnpm clean && json2ts -i schemas/ -o types/",
"clean": "rimraf types"
},
"files": ["index.d.ts", "types/**/*"],
"dependencies": {
"json-schema-to-typescript": "^15.0.4",
"rimraf": "^6.0.1"
}
}
26 changes: 26 additions & 0 deletions packages/c2pa-types/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "c2pa-types",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"tags": ["lib"],
"projectType": "library",
"sourceRoot": "packages/c2pa-types/src",
"targets": {
"build": {
"cache": true,
"inputs": [
"{projectRoot}/**/*",
"!{projectRoot}/schemas/**/*",
"!{projectRoot}/types/**/*"
],
"executor": "nx:run-commands",
"options": {
"commands": [
"cargo run",
"pnpm build"
],
"parallel": false,
"cwd": "{projectRoot}"
}
}
}
}
31 changes: 31 additions & 0 deletions packages/c2pa-types/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2025 Adobe
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying
// it.

use std::{fs, path::Path};

use c2pa::Reader;
use schemars::{schema::RootSchema, schema_for};

fn main() {
let output_dir = Path::new("./schemas");

if fs::exists(output_dir).unwrap() {
fs::remove_dir_all(output_dir).expect("Could not clear existing schema directory");
}

fs::create_dir_all(output_dir).expect("Could not create schema directory");

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

fn write_schema(schema: &RootSchema, name: &str, output_dir: &Path) {
println!("Exporting JSON schema for {name}");
let output_path = output_dir.join(format!("{name}.json"));
let output = serde_json::to_string_pretty(schema).expect("Failed to serialize schema");
fs::write(&output_path, output).expect("Unable to write schema");
println!("Wrote schema to {}", output_path.display());
}
2 changes: 2 additions & 0 deletions packages/c2pa-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ wasm-bindgen-futures = "0.4.50"
c2pa = { version = "0.62.0", features = ["pdf", "rust_native_crypto", "fetch_remote_manifests"], default-features = false }
async-trait = "0.1.88"
thiserror = "2.0.12"
serde-wasm-bindgen = "0.6.5"
serde = "1.0.219"

[dependencies.web-sys]
version = "0.3.77"
Expand Down
3 changes: 3 additions & 0 deletions packages/c2pa-wasm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub enum WasmError {
#[error(transparent)]
C2pa(#[from] c2pa::Error),

#[error(transparent)]
Serde(#[from] serde_wasm_bindgen::Error),

#[error(transparent)]
Other(Box<dyn Error>),
}
Expand Down
60 changes: 44 additions & 16 deletions packages/c2pa-wasm/src/wasm_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use std::io::{Cursor, Read, Seek};

use c2pa::{identity::validator::CawgValidator, Reader};
use js_sys::{ArrayBuffer, Error as JsError, Uint8Array};
use serde::Serialize;
use serde_wasm_bindgen::Serializer;
use wasm_bindgen::prelude::*;
use web_sys::Blob;

Expand All @@ -18,6 +20,7 @@ use crate::{error::WasmError, stream::BlobStream};
#[wasm_bindgen]
pub struct WasmReader {
reader: Reader,
serializer: Serializer,
}

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

// This will be removed once CawgValidation is rolled into the reader
reader
.post_validate_async(&CawgValidator {})
.await
.map_err(WasmError::other)?;

Ok(WasmReader { reader })
Ok(WasmReader::from_reader(reader).await?)
}

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

Ok(WasmReader::from_reader(reader).await?)
}

async fn from_reader(mut reader: Reader) -> Result<WasmReader, JsError> {
let serializer = Serializer::new().serialize_maps_as_objects(true);

// This will be removed once CawgValidation is rolled into the reader
reader
.post_validate_async(&CawgValidator {})
.await
.map_err(WasmError::other)?;

Ok(WasmReader { reader })
}

/// Returns a JSON representation of the asset's manifest store.
#[wasm_bindgen]
pub fn json(&self) -> String {
self.reader.json()
Ok(WasmReader { reader, serializer })
}

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

/// Returns the asset's manifest store.
/// NOTE: at the moment, CAWG data is not decoded via this function. Use WasmReader::json() if CAWG is a requirement.
#[wasm_bindgen(js_name = manifestStore)]
pub fn manifest_store(&self) -> Result<JsValue, JsError> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what it's doing, but what is the intend? Why is the manifest store by itself needed somewhere? (Especially because I see the js below: const manifestStore = await reader.json();, so it puzzles me a bit). Why is Reader.json() not usable all the time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to get rid of json in favor of manifest_store only. The output is very similar but not exactly the same. Two things:

  1. The types generated in this PR only correspond to the output of serializing a reader, not calling Reader::json. They are close but not identical.
  2. It looks like we can only get CAWG data through json() right now, unless I'm mistaken. Serializing a reader in this way seems to sidestep the post_validate_async hook.

I'm hopeful that once @scouten-adobe is able to land contentauth/c2pa-rs#1370 and these methods return CAWG correctly, I can just get rid of json() and use these typed APIs only.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, sounds good!

let manifest_store = self
.reader
.serialize(&self.serializer)
.map_err(WasmError::from)?;

Ok(manifest_store)
}

/// Returns the asset's active manifest.
/// NOTE: at the moment, CAWG data is not decoded via this function. Use WasmReader::json() if CAWG is a requirement.
#[wasm_bindgen(js_name = activeManifest)]
pub fn active_manifest(&self) -> Result<JsValue, JsError> {
let active_manifest = self
.reader
.active_manifest()
.serialize(&self.serializer)
.map_err(WasmError::from)?;

Ok(active_manifest)
}

/// Returns a JSON representation of the asset's manifest store.
#[wasm_bindgen]
pub fn json(&self) -> String {
self.reader.json()
}

/// Accepts a URI reference to a binary object in the resource store and returns a `js_sys::ArrayBuffer` containing the resource's bytes.
#[wasm_bindgen(js_name = resourceToBuffer)]
pub fn resource_to_buffer(&self, uri: &str) -> Result<ArrayBuffer, JsError> {
Expand Down
3 changes: 2 additions & 1 deletion packages/c2pa-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
}
},
"dependencies": {
"@contentauth/c2pa-wasm": "workspace:*"
"@contentauth/c2pa-wasm": "workspace:*",
"@contentauth/c2pa-types": "workspace:*"
},
"devDependencies": {
"@playwright/test": "^1.55.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/c2pa-web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
*/

export * from './lib/c2pa.js';

export {
isSupportedReaderFormat,
READER_SUPPORTED_FORMATS,
} from './lib/supportedFormats.js';

export { type Settings } from './lib/settings.js';

// Re-export types from c2pa-types for convenience.
export type * from '@contentauth/c2pa-types';
Loading