Skip to content

Commit 8a87c6d

Browse files
authored
feat: Change the exception about No JUMBF Found to null (#34)
* feat: update c2pa version Add builder methods addIngredient and addAction Breaking change: old addIngredient renamed to addIngredientFromAsset * feat: change the exception about No JUMBF Found to null * fix: Make asset optional for addIngredient * chore: add changeset
1 parent b7b66cc commit 8a87c6d

File tree

15 files changed

+283
-97
lines changed

15 files changed

+283
-97
lines changed

.changeset/hot-dancers-open.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@contentauth/c2pa-node": patch
3+
---
4+
5+
Make asset in Builder.addIngredient optional. Add Builder.addAction

Cargo.lock

Lines changed: 11 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ crate-type = ["cdylib"]
1111
[dependencies]
1212
async-trait = "0.1.77"
1313
ciborium = "0.2.2"
14-
c2pa = { version = "0.70.0", default-features = false, features = ["file_io", "pdf", "fetch_remote_manifests", "add_thumbnails", "rust_native_crypto", "default_http"] }
14+
c2pa = { version = "0.72.0", default-features = false, features = ["file_io", "pdf", "fetch_remote_manifests", "add_thumbnails", "rust_native_crypto", "default_http"] }
1515
futures = "0.3"
1616
image = "0.25.6"
1717
neon = { version = "1.0.0", default-features = false, features = [

js-src/Builder.spec.ts

Lines changed: 129 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ describe("Builder", () => {
210210
expect(bytes.length).toBeGreaterThan(0);
211211

212212
const reader = await Reader.fromAsset(dest);
213-
const manifestStore = reader.json();
214-
const activeManifest = reader.getActive();
213+
expect(reader).not.toBeNull();
214+
const manifestStore = reader!.json();
215+
const activeManifest = reader!.getActive();
215216
expect(manifestStore.validation_status![0].code).toBe(
216217
"signingCredential.untrusted",
217218
);
@@ -237,7 +238,8 @@ describe("Builder", () => {
237238

238239
// Read and verify the assertion in the signed manifest
239240
const reader = await Reader.fromAsset(dest);
240-
const activeManifest = reader.getActive();
241+
expect(reader).not.toBeNull();
242+
const activeManifest = reader!.getActive();
241243
const cborAssertion = activeManifest?.assertions?.find(
242244
(a: any) => a.label === "c2pa.actions.v2",
243245
);
@@ -265,8 +267,9 @@ describe("Builder", () => {
265267
buffer: dest.buffer! as Buffer,
266268
mimeType: "jpeg",
267269
});
268-
const manifestStore = reader.json();
269-
const activeManifest = reader.getActive();
270+
expect(reader).not.toBeNull();
271+
const manifestStore = reader!.json();
272+
const activeManifest = reader!.getActive();
270273
expect(manifestStore.validation_status![0].code).toBe(
271274
"signingCredential.untrusted",
272275
);
@@ -296,8 +299,9 @@ describe("Builder", () => {
296299
expect(bytes.length).toBeGreaterThan(0);
297300

298301
const reader = await Reader.fromAsset(dest);
299-
const manifestStore = reader.json();
300-
const activeManifest = reader.getActive();
302+
expect(reader).not.toBeNull();
303+
const manifestStore = reader!.json();
304+
const activeManifest = reader!.getActive();
301305
expect(manifestStore.validation_status![0].code).toBe(
302306
"signingCredential.untrusted",
303307
);
@@ -330,8 +334,9 @@ describe("Builder", () => {
330334
buffer: dest.buffer! as Buffer,
331335
mimeType: "jpeg",
332336
});
333-
const manifestStore = reader.json();
334-
const activeManifest = reader.getActive();
337+
expect(reader).not.toBeNull();
338+
const manifestStore = reader!.json();
339+
const activeManifest = reader!.getActive();
335340
expect(manifestStore.validation_status![0].code).toBe(
336341
"signingCredential.untrusted",
337342
);
@@ -360,8 +365,9 @@ describe("Builder", () => {
360365
buffer: dest.buffer! as Buffer,
361366
mimeType: "jpeg",
362367
});
363-
const manifestStore = reader.json();
364-
const activeManifest = reader.getActive();
368+
expect(reader).not.toBeNull();
369+
const manifestStore = reader!.json();
370+
const activeManifest = reader!.getActive();
365371
expect(manifestStore.validation_status![0].code).toBe(
366372
"signingCredential.untrusted",
367373
);
@@ -390,7 +396,8 @@ describe("Builder", () => {
390396
builder.sign(signer, source, dest);
391397

392398
const reader = await Reader.fromAsset(dest);
393-
const manifest = reader.json();
399+
expect(reader).not.toBeNull();
400+
const manifest = reader!.json();
394401

395402
// Check that our specific JSON assertion doesn't have escaped characters
396403
const activeManifest = manifest.manifests[manifest.active_manifest!];
@@ -475,7 +482,8 @@ describe("Builder", () => {
475482
buffer: dest.buffer! as Buffer,
476483
mimeType: "jpeg",
477484
});
478-
const manifestStore = reader.json();
485+
expect(reader).not.toBeNull();
486+
const manifestStore = reader!.json();
479487
expect(JSON.stringify(manifestStore)).toContain("Test Ingredient");
480488
expect(JSON.stringify(manifestStore)).toContain("thumbnail.ingredient");
481489
});
@@ -485,7 +493,8 @@ describe("Builder", () => {
485493

486494
// Create a reader to get the parent manifest label from the existing source
487495
const reader = await Reader.fromAsset(source);
488-
const parentManifestLabel = reader.activeLabel();
496+
expect(reader).not.toBeNull();
497+
const parentManifestLabel = reader!.activeLabel();
489498
expect(parentManifestLabel).toBeDefined();
490499

491500
// Create a redacted URI for the assertion we are going to redact
@@ -550,10 +559,11 @@ describe("Builder", () => {
550559
buffer: dest.buffer! as Buffer,
551560
mimeType: "image/jpeg",
552561
});
562+
expect(signedReader).not.toBeNull();
553563
expect(signedReader).toBeDefined();
554564

555565
// Check that the manifest was created successfully
556-
const activeManifest = signedReader.getActive();
566+
const activeManifest = signedReader!.getActive();
557567
expect(activeManifest).toBeDefined();
558568

559569
// Verify the redacted action was added
@@ -643,9 +653,112 @@ describe("Builder", () => {
643653
});
644654

645655
// Check if the manifest has the expected structure
646-
const activeManifest = reader.getActive();
656+
const activeManifest = reader!.getActive();
647657
expect(activeManifest).toBeDefined();
648658
expect(activeManifest?.title).toBe("Test_Manifest");
649659
});
660+
661+
it("should add action using addAction method", async () => {
662+
const simpleManifestDefinition = {
663+
claim_generator_info: [
664+
{
665+
name: "c2pa_test",
666+
version: "1.0.0",
667+
},
668+
],
669+
title: "Test_AddAction",
670+
format: "image/jpeg",
671+
assertions: [],
672+
resources: { resources: {} },
673+
};
674+
675+
const builder = Builder.withJson(simpleManifestDefinition);
676+
677+
// Add an action using addAction method
678+
// The action needs to be a structured object matching the c2pa Action type
679+
const actionJson = JSON.stringify({
680+
action: "c2pa.edited",
681+
});
682+
builder.addAction(actionJson);
683+
684+
// Sign the manifest
685+
const dest = { path: path.join(tempDir, "add_action_test.jpg") };
686+
const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
687+
builder.sign(signer, source, dest);
688+
689+
// Verify the action was added
690+
const reader = await Reader.fromAsset(dest);
691+
expect(reader).not.toBeNull();
692+
const activeManifest = reader!.getActive();
693+
const actionsAssertion = activeManifest?.assertions?.find(
694+
(a: any) => a.label === "c2pa.actions.v2",
695+
);
696+
expect(actionsAssertion).toBeDefined();
697+
if (isActionsAssertion(actionsAssertion)) {
698+
const actions = actionsAssertion.data.actions;
699+
const editedAction = actions.find((a: any) => a.action === "c2pa.edited");
700+
expect(editedAction).toBeDefined();
701+
expect(editedAction?.action).toBe("c2pa.edited");
702+
} else {
703+
throw new Error("Actions assertion does not have the expected structure");
704+
}
705+
});
706+
707+
it("should add ingredient using addIngredient method", async () => {
708+
const simpleManifestDefinition = {
709+
claim_generator_info: [
710+
{
711+
name: "c2pa_test",
712+
version: "1.0.0",
713+
},
714+
],
715+
title: "Test_AddIngredient",
716+
format: "image/jpeg",
717+
ingredients: [],
718+
assertions: [
719+
{
720+
label: "c2pa.actions",
721+
data: {
722+
actions: [
723+
{
724+
action: "c2pa.created",
725+
digitalSourceType: "http://c2pa.org/digitalsourcetype/empty",
726+
},
727+
],
728+
},
729+
},
730+
],
731+
resources: { resources: {} },
732+
};
733+
734+
const builder = Builder.withJson(simpleManifestDefinition);
735+
736+
// Add an ingredient using addIngredient method
737+
const ingredientJson = JSON.stringify({
738+
title: "Test Ingredient via addIngredient",
739+
format: "image/jpeg",
740+
instance_id: "ingredient-12345",
741+
relationship: "componentOf",
742+
});
743+
builder.addIngredient(ingredientJson);
744+
745+
// Sign the manifest
746+
const dest = { path: path.join(tempDir, "add_ingredient_test.jpg") };
747+
const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
748+
builder.sign(signer, source, dest);
749+
750+
// Verify the ingredient was added
751+
const reader = await Reader.fromAsset(dest);
752+
expect(reader).not.toBeNull();
753+
const activeManifest = reader!.getActive();
754+
expect(activeManifest?.ingredients).toBeDefined();
755+
expect(activeManifest?.ingredients?.length).toBeGreaterThan(0);
756+
const addedIngredient = activeManifest?.ingredients?.find(
757+
(ing: any) => ing.title === "Test Ingredient via addIngredient",
758+
);
759+
expect(addedIngredient).toBeDefined();
760+
expect(addedIngredient?.instance_id).toBe("ingredient-12345");
761+
expect(addedIngredient?.relationship).toBe("componentOf");
762+
});
650763
});
651764
});

js-src/Builder.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export class Builder implements BuilderInterface {
7070
getNeonBinary().builderSetRemoteUrl.call(this.builder, remoteUrl);
7171
}
7272

73+
addAction(actionJson: string): void {
74+
return getNeonBinary().builderAddAction.call(this.builder, actionJson);
75+
}
76+
7377
addAssertion(
7478
label: string,
7579
assertion: unknown,
@@ -89,13 +93,20 @@ export class Builder implements BuilderInterface {
8993

9094
async addIngredient(
9195
ingredientJson: string,
92-
ingredient: SourceAsset,
96+
ingredient?: SourceAsset,
9397
): Promise<void> {
94-
return getNeonBinary().builderAddIngredient.call(
95-
this.builder,
96-
ingredientJson,
97-
ingredient,
98-
);
98+
if (ingredient) {
99+
return getNeonBinary().builderAddIngredientFromAsset.call(
100+
this.builder,
101+
ingredientJson,
102+
ingredient,
103+
);
104+
} else {
105+
return getNeonBinary().builderAddIngredient.call(
106+
this.builder,
107+
ingredientJson,
108+
);
109+
}
99110
}
100111

101112
async toArchive(asset: DestinationAsset): Promise<void> {

0 commit comments

Comments
 (0)