Skip to content

Commit c6defcc

Browse files
authored
feat: add addIngredientFromReader method (#36)
* feat: add addIngredientFromReader method
1 parent c21f478 commit c6defcc

File tree

11 files changed

+126
-21
lines changed

11 files changed

+126
-21
lines changed

.changeset/odd-banks-write.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+
Add addIngredientFromReader method

js-src/Builder.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,11 +696,15 @@ describe("Builder", () => {
696696
expect(actionsAssertion).toBeDefined();
697697
if (isActionsAssertion(actionsAssertion)) {
698698
const actions = actionsAssertion.data.actions;
699-
const editedAction = actions.find((a: any) => a.action === "c2pa.edited");
699+
const editedAction = actions.find(
700+
(a: any) => a.action === "c2pa.edited",
701+
);
700702
expect(editedAction).toBeDefined();
701703
expect(editedAction?.action).toBe("c2pa.edited");
702704
} else {
703-
throw new Error("Actions assertion does not have the expected structure");
705+
throw new Error(
706+
"Actions assertion does not have the expected structure",
707+
);
704708
}
705709
});
706710

@@ -760,5 +764,48 @@ describe("Builder", () => {
760764
expect(addedIngredient?.instance_id).toBe("ingredient-12345");
761765
expect(addedIngredient?.relationship).toBe("componentOf");
762766
});
767+
768+
it("should add ingredient from reader", async () => {
769+
const builder1 = Builder.new();
770+
builder1.setIntent("edit" as any);
771+
const signer = LocalSigner.newSigner(publicKey, privateKey, "es256");
772+
const dest1: DestinationBufferAsset = {
773+
buffer: null,
774+
};
775+
builder1.sign(signer, source, dest1);
776+
777+
// Read the signed file back with Reader
778+
const reader = await Reader.fromAsset({
779+
buffer: dest1.buffer! as Buffer,
780+
mimeType: "image/jpeg",
781+
});
782+
expect(reader).not.toBeNull();
783+
784+
// Create a new builder and add ingredient from the reader
785+
const builder2 = Builder.new();
786+
const ingredient = builder2.addIngredientFromReader(reader!);
787+
expect(ingredient).toBeDefined();
788+
789+
// Verify the ingredient was added to the builder
790+
const definition = builder2.getManifestDefinition();
791+
expect(definition.ingredients).toBeDefined();
792+
expect(definition.ingredients!.length).toBeGreaterThan(0);
793+
794+
// Sign again with the new builder
795+
const dest2: DestinationBufferAsset = {
796+
buffer: null,
797+
};
798+
builder2.sign(signer, source, dest2);
799+
800+
// Verify the ingredient is in the signed manifest
801+
const reader2 = await Reader.fromAsset({
802+
buffer: dest2.buffer! as Buffer,
803+
mimeType: "image/jpeg",
804+
});
805+
expect(reader2).not.toBeNull();
806+
const activeManifest = reader2!.getActive();
807+
expect(activeManifest?.ingredients).toBeDefined();
808+
expect(activeManifest?.ingredients?.length).toBe(1);
809+
});
763810
});
764811
});

js-src/Builder.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
// specific language governing permissions and limitations under
1212
// each license.
1313

14-
import type { BuilderIntent, Manifest } from "@contentauth/c2pa-types";
14+
import type {
15+
BuilderIntent,
16+
Ingredient,
17+
Manifest,
18+
} from "@contentauth/c2pa-types";
1519

1620
import { getNeonBinary } from "./binary.js";
1721
import type {
@@ -24,6 +28,7 @@ import type {
2428
JsCallbackSignerConfig,
2529
LocalSignerInterface,
2630
ManifestAssertionKind,
31+
ReaderInterface,
2732
SourceAsset,
2833
NeonBuilderHandle,
2934
} from "./types.d.ts";
@@ -109,6 +114,15 @@ export class Builder implements BuilderInterface {
109114
}
110115
}
111116

117+
addIngredientFromReader(reader: ReaderInterface): Ingredient {
118+
const readerHandle = reader.getHandle();
119+
const result = getNeonBinary().builderAddIngredientFromReader.call(
120+
this.builder,
121+
readerHandle,
122+
);
123+
return JSON.parse(result);
124+
}
125+
112126
async toArchive(asset: DestinationAsset): Promise<void> {
113127
return getNeonBinary().builderToArchive.call(this.builder, asset);
114128
}
@@ -124,7 +138,7 @@ export class Builder implements BuilderInterface {
124138
): Buffer {
125139
return getNeonBinary().builderSign.call(
126140
this.builder,
127-
signer.signer(),
141+
signer.getHandle(),
128142
input,
129143
output,
130144
);
@@ -138,7 +152,7 @@ export class Builder implements BuilderInterface {
138152
const input: FileAsset = { path: filePath };
139153
return getNeonBinary().builderSign.call(
140154
this.builder,
141-
signer.signer(),
155+
signer.getHandle(),
142156
input,
143157
output,
144158
);
@@ -182,7 +196,7 @@ export class Builder implements BuilderInterface {
182196
input: SourceAsset,
183197
output: DestinationAsset,
184198
): Promise<Buffer> {
185-
const neonHandle = signer.signer();
199+
const neonHandle = signer.getHandle();
186200
const isIdentity = signer instanceof IdentityAssertionSigner;
187201
const neonFn = isIdentity
188202
? getNeonBinary().builderIdentitySignAsync

js-src/IdentityAssertion.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe("IdentityAssertionBuilder", () => {
171171
});
172172

173173
// Create and configure the identity assertion
174-
const iaSigner = IdentityAssertionSigner.new(c2paSigner.signer());
174+
const iaSigner = IdentityAssertionSigner.new(c2paSigner.getHandle());
175175
const iab =
176176
await IdentityAssertionBuilder.identityBuilderForCredentialHolder(
177177
cawgSigner,

js-src/IdentityAssertion.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class IdentityAssertionBuilder
3232
credentialHolder: CallbackCredentialHolderInterface,
3333
): Promise<IdentityAssertionBuilder> {
3434
const builder = getNeonBinary().identityBuilderForCredentialHolder(
35-
credentialHolder.signer(),
35+
credentialHolder.getHandle(),
3636
);
3737
return new IdentityAssertionBuilder(builder);
3838
}
@@ -72,7 +72,7 @@ export class IdentityAssertionSigner
7272
);
7373
}
7474

75-
signer(): NeonIdentityAssertionSignerHandle {
75+
getHandle(): NeonIdentityAssertionSignerHandle {
7676
return this._signer;
7777
}
7878
}
@@ -84,7 +84,7 @@ export class CallbackCredentialHolder
8484
private callbackCredentialHolder: NeonCallbackCredentialHolderHandle,
8585
) {}
8686

87-
signer(): NeonCallbackCredentialHolderHandle {
87+
getHandle(): NeonCallbackCredentialHolderHandle {
8888
return this.callbackCredentialHolder;
8989
}
9090

js-src/Reader.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,8 @@ export class Reader implements ReaderInterface {
7272

7373
return manifestStore.manifests[activeManifest];
7474
}
75+
76+
getHandle(): NeonReaderHandle {
77+
return this.reader;
78+
}
7579
}

js-src/Signer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ export class LocalSigner implements LocalSignerInterface {
5959
return getNeonBinary().localSignerTimeAuthorityUrl.call(this.localSigner);
6060
}
6161

62-
signer(): NeonLocalSignerHandle {
62+
getHandle(): NeonLocalSignerHandle {
6363
return this.localSigner;
6464
}
6565
}
6666

6767
export class CallbackSigner implements CallbackSignerInterface {
6868
constructor(private callbackSigner: NeonCallbackSignerHandle) {}
6969

70-
signer(): NeonCallbackSignerHandle {
70+
getHandle(): NeonCallbackSignerHandle {
7171
return this.callbackSigner;
7272
}
7373

js-src/types.d.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import { Buffer } from "buffer";
1616
import type {
1717
BuilderIntent,
18+
Ingredient,
1819
Manifest,
1920
ManifestStore,
2021
} from "@contentauth/c2pa-types";
@@ -110,7 +111,7 @@ export interface LocalSignerInterface {
110111
certs(): Array<Buffer>;
111112
reserveSize(): number;
112113
timeAuthorityUrl(): string | undefined;
113-
signer(): NeonLocalSignerHandle;
114+
getHandle(): NeonLocalSignerHandle;
114115
}
115116

116117
/**
@@ -123,14 +124,14 @@ export interface CallbackSignerInterface {
123124
reserveSize(): number;
124125
timeAuthorityUrl(): string | undefined;
125126
directCoseHandling(): boolean;
126-
signer(): NeonCallbackSignerHandle;
127+
getHandle(): NeonCallbackSignerHandle;
127128
}
128129

129130
export interface CallbackCredentialHolderInterface {
130131
sigType(): string;
131132
reserveSize(): number;
132133
sign(payload: SignerPayload): Promise<Buffer>;
133-
signer(): NeonCallbackCredentialHolderHandle;
134+
getHandle(): NeonCallbackCredentialHolderHandle;
134135
}
135136

136137
/**
@@ -235,6 +236,12 @@ export interface BuilderInterface {
235236
ingredient?: SourceAsset,
236237
): Promise<void>;
237238

239+
/**
240+
* Add an ingredient to the manifest from a Reader
241+
* @param reader The Reader object of the ingredient
242+
*/
243+
addIngredientFromReader(reader: ReaderInterface): Ingredient;
244+
238245
/**
239246
* Convert the Builder into a archive formatted buffer or file
240247
* @param asset The file or buffer for the archive
@@ -332,6 +339,11 @@ export interface ReaderInterface {
332339
* @param filePath The path to the file
333340
*/
334341
resourceToAsset(uri: string, output: DestinationAsset): Promise<number>;
342+
343+
/**
344+
* Get the internal handle for use with Neon bindings
345+
*/
346+
getHandle(): NeonReaderHandle;
335347
}
336348

337349
export interface IdentityAssertionSignerInterface {
@@ -345,7 +357,7 @@ export interface IdentityAssertionSignerInterface {
345357
identityAssertionBuilder: IdentityAssertionBuilderInterface,
346358
): void;
347359

348-
signer(): NeonIdentityAssertionSignerHandle;
360+
getHandle(): NeonIdentityAssertionSignerHandle;
349361
}
350362

351363
export interface IdentityAssertionBuilderInterface {

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
5555
"builderAddIngredientFromAsset",
5656
neon_builder::NeonBuilder::add_ingredient_from_asset,
5757
)?;
58+
cx.export_function(
59+
"builderAddIngredientFromReader",
60+
neon_builder::NeonBuilder::add_ingredient_from_reader,
61+
)?;
5862
cx.export_function("builderToArchive", neon_builder::NeonBuilder::to_archive)?;
5963
cx.export_function(
6064
"builderFromArchive",

src/neon_builder.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use crate::asset::parse_asset;
1515
use crate::error::{as_js_error, Error};
1616
use crate::neon_identity_assertion_signer::NeonIdentityAssertionSigner;
17+
use crate::neon_reader::NeonReader;
1718
use crate::neon_signer::{CallbackSignerConfig, NeonCallbackSigner, NeonLocalSigner};
1819
use crate::runtime::runtime;
1920
use c2pa::{Builder, BuilderIntent, Ingredient};
@@ -92,8 +93,8 @@ impl NeonBuilder {
9293
let rt = runtime();
9394
let this = cx.this::<JsBox<Self>>()?;
9495
let action_json = cx.argument::<JsString>(0)?.value(&mut cx);
95-
let action: c2pa::assertions::Action = serde_json::from_str(&action_json)
96-
.or_else(|err| cx.throw_error(err.to_string()))?;
96+
let action: c2pa::assertions::Action =
97+
serde_json::from_str(&action_json).or_else(|err| cx.throw_error(err.to_string()))?;
9798
let mut builder = rt.block_on(async { this.builder.lock().await });
9899
builder
99100
.add_action(action)
@@ -217,6 +218,21 @@ impl NeonBuilder {
217218
Ok(promise)
218219
}
219220

221+
pub fn add_ingredient_from_reader(mut cx: FunctionContext) -> JsResult<JsString> {
222+
let rt = runtime();
223+
let this = cx.this::<JsBox<Self>>()?;
224+
let reader = cx.argument::<JsBox<NeonReader>>(0)?.reader();
225+
226+
let mut builder = rt.block_on(async { this.builder.lock().await });
227+
let reader = rt.block_on(async { reader.lock().await });
228+
let ingredient = builder
229+
.add_ingredient_from_reader(&reader)
230+
.or_else(|err| cx.throw_error(err.to_string()))?;
231+
let json =
232+
serde_json::to_string(&ingredient).or_else(|err| cx.throw_error(err.to_string()))?;
233+
Ok(cx.string(json))
234+
}
235+
220236
pub fn to_archive(mut cx: FunctionContext) -> JsResult<JsPromise> {
221237
let this = cx.this::<JsBox<Self>>()?;
222238
let dest = cx

0 commit comments

Comments
 (0)