diff --git a/docs/tasks/archives.mdx b/docs/tasks/archives.mdx index ba345e16..f557edfb 100644 --- a/docs/tasks/archives.mdx +++ b/docs/tasks/archives.mdx @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import Rustarchives from './rust/_rust-archives.mdx'; import Cpparchives from './cpp/_cpp-archives.mdx'; import Pythonarchives from './python/_python-archives.mdx'; +import Nodearchives from './node/_node-archives.mdx'; _Working stores_ and _archives_ provide a standard way to save and restore the state of a `Builder`: @@ -35,5 +36,11 @@ _Working stores_ and _archives_ provide a standard way to save and restore the s + + + + + + diff --git a/docs/tasks/build.mdx b/docs/tasks/build.mdx index c5db6648..8549a03a 100644 --- a/docs/tasks/build.mdx +++ b/docs/tasks/build.mdx @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import PythonBuild from './python/_python-build.md'; import CppBuild from './cpp/_cpp-build.md'; import RustBuild from './rust/_rust-build.md'; +import NodeBuild from './node/_node-build.md'; @@ -30,5 +31,11 @@ import RustBuild from './rust/_rust-build.md'; + + + + + + diff --git a/docs/tasks/get-resources.mdx b/docs/tasks/get-resources.mdx index 9d113561..19224534 100644 --- a/docs/tasks/get-resources.mdx +++ b/docs/tasks/get-resources.mdx @@ -9,6 +9,7 @@ import TabItem from '@theme/TabItem'; import PythonGetResources from './python/_python-get-resources.md'; import CppGetResources from './cpp/_cpp-get-resources.md'; import RustGetResources from './rust/_rust-get-resources.md'; +import NodeGetResources from './node/_node-get-resources.md'; Manifest data can include binary resources such as thumbnail and icon images which are referenced by JUMBF URIs in manifest data. @@ -32,4 +33,10 @@ Manifest data can include binary resources such as thumbnail and icon images whi + + + + + + diff --git a/docs/tasks/intents.mdx b/docs/tasks/intents.mdx index 752a1714..7b222216 100644 --- a/docs/tasks/intents.mdx +++ b/docs/tasks/intents.mdx @@ -9,6 +9,7 @@ import TabItem from '@theme/TabItem'; import Rustintents from './rust/_rust-intents.md'; import Cppintents from './cpp/_cpp-intents.md'; import Pythonintents from './python/_python-intents.md'; +import Nodeintents from './node/_node-intents.md'; _Intents_ tell the `Builder` what kind of manifest you are creating. They enable validation, add required default actions, and help prevent invalid operations. @@ -41,5 +42,11 @@ There are three types of intents, shown here: + + + + + + diff --git a/docs/tasks/node/_node-archives.mdx b/docs/tasks/node/_node-archives.mdx new file mode 100644 index 00000000..c3813441 --- /dev/null +++ b/docs/tasks/node/_node-archives.mdx @@ -0,0 +1,114 @@ +import TOCInline from '@theme/TOCInline'; + + + +### Saving a working store to an archive + +Use `toArchive` to serialize the current `Builder` state (working store) to a file or to an in-memory buffer. The binary uses the standard C2PA JUMBF `application/c2pa` archive format (often stored with a `.c2pa` extension). + +```typescript +import { Builder } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const builder = Builder.new(); + +await builder.addIngredient( + JSON.stringify({ + title: 'source.jpg', + relationship: 'parentOf', + format: 'image/jpeg', + }), + { buffer: await readFile('source.jpg'), mimeType: 'image/jpeg' }, +); + +await builder.toArchive({ path: 'manifest.c2pa' }); +``` + +Write the archive to a buffer: + +```typescript +const archive: { buffer: Buffer | null } = { buffer: null }; +await builder.toArchive(archive); +// archive.buffer is populated with the archive bytes +``` + +### Restoring a working store from an archive + +Use `Builder.fromArchive` to construct a new `Builder` from archive bytes or a file path. Pass optional [settings](../settings.mdx) as the second argument when you need custom verification or builder behavior. + +```typescript +import { Builder, LocalSigner } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const restored = await Builder.fromArchive({ path: 'manifest.c2pa' }); + +const signer = LocalSigner.newSigner( + await readFile('signer.pem'), + await readFile('signer.key'), + 'es256', +); + +restored.sign( + signer, + { path: 'asset.jpg' }, + { path: 'signed-asset.jpg' }, +); +``` + +### Two-phase workflow + +**Phase 1 — prepare** a manifest and ingredients, then save an archive: + +```typescript +import { Builder } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const builder = Builder.new(); +await builder.addIngredient( + JSON.stringify({ title: 'sketch.png', relationship: 'componentOf' }), + { buffer: await readFile('sketch.png'), mimeType: 'image/png' }, +); +await builder.toArchive({ path: 'draft.c2pa' }); +``` + +**Phase 2 — sign** after loading the archive: + +```typescript +import { Builder, LocalSigner } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const builder = await Builder.fromArchive({ path: 'draft.c2pa' }); +const signer = LocalSigner.newSigner( + await readFile('signer.pem'), + await readFile('signer.key'), + 'es256', +); + +builder.sign( + signer, + { path: 'artwork.jpg' }, + { path: 'signed-artwork.jpg' }, +); +``` + +### Reading an archive with `Reader` + +Archives are read like other assets by setting `mimeType` to `application/c2pa`. You can list ingredients and copy binary resources into a new `Builder` with `addResource` (see [Getting resources from a manifest](../get-resources.mdx)). + +```typescript +import { Reader } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const archiveBuffer = await readFile('ingredients.c2pa'); +const reader = await Reader.fromAsset( + { buffer: archiveBuffer, mimeType: 'application/c2pa' }, + { verify: { verify_after_reading: false } }, +); + +const active = reader?.getActive(); +const ingredients = active?.ingredients ?? []; +``` + +### Ingredient archives + +To build a reusable ingredient catalog, add ingredients (stable `instance_id` values help later lookup), then call `toArchive`. Consumers read the archive with `Reader` and merge selected ingredients into a signing `Builder`, transferring thumbnails and `manifest_data` URIs with `resourceToAsset` / `addResource` as in the [@contentauth/c2pa-node README](https://github.com/contentauth/c2pa-node-v2#adding-ingredients-from-archives-c2pa-files). diff --git a/docs/tasks/node/_node-build.md b/docs/tasks/node/_node-build.md index 6271fe8d..b1fb5335 100644 --- a/docs/tasks/node/_node-build.md +++ b/docs/tasks/node/_node-build.md @@ -1,171 +1,106 @@ -This is an example of how to assign a manifest to an asset and sign the claim using Node.js: +Use the `Builder` and `LocalSigner` classes from `@contentauth/c2pa-node` to assemble manifest data and sign an asset. -```ts +### Create a builder + +```typescript import { Builder } from '@contentauth/c2pa-node'; -// Create a new builder +// Default settings const builder = Builder.new(); -// Create with custom settings -const settings = { - builder: { - generate_c2pa_archive: true - } -}; -const builder = Builder.new(settings); +// With settings (JSON object or string; see [Settings](../settings.mdx)) +const withSettings = Builder.new({ + builder: { generate_c2pa_archive: true }, +}); + +// From an existing manifest definition (see manifest JSON reference) +const fromDefinition = Builder.withJson({ + claim_generator_info: [{ name: 'my-app', version: '1.0.0' }], + title: 'My image', + format: 'image/jpeg', + assertions: [], + resources: { resources: {} }, +}); +``` -// Or create from an existing manifest definition -const builder = Builder.withJson(manifestDefinition); +### Add assertions and resources -// Or create with both manifest and settings -const builder = Builder.withJson(manifestDefinition, settings); +```typescript +builder.addAssertion('c2pa.actions', { + actions: [{ action: 'c2pa.created' }], +}); -// Add assertions to the manifest -builder.addAssertion('c2pa.actions', actionsAssertion); +await builder.addResource('resource://example/thumb', { + buffer: thumbnailBytes, + mimeType: 'image/jpeg', +}); +``` -// Add resources -await builder.addResource('resource://example', resourceAsset); +### Sign with a local certificate and key -// Sign the manifest -const manifest = builder.sign(signer, inputAsset, outputAsset); -``` +`LocalSigner.newSigner` takes the signing certificate (PEM), private key (PEM), algorithm (`es256`, `ps256`, `ed25519`, etc.), and an optional RFC 3161 timestamp URL. -Use the `c2pa.sign()` method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API. +```typescript +import { Builder, LocalSigner } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; -## Signing a stream +const cert = await readFile('signer.pem'); +const key = await readFile('signer.key'); +const signer = LocalSigner.newSigner(cert, key, 'es256'); -If you have an asset file's data loaded into a stream, you can use it to sign the asset +const builder = Builder.withJson({ + claim_generator_info: [{ name: 'my-app', version: '1.0.0' }], + title: 'output.jpg', + format: 'image/jpeg', + assertions: [], + resources: { resources: {} }, +}); -**NOTE**: Signing using a stream is currently supported only for `image/jpeg` and `image/png` data. For all other file types, use the [file-based approach](#signing-files) . +builder.setIntent('edit'); -```ts -import { readFile } from 'node:fs/promises'; -import { createC2pa, createTestSigner } from 'c2pa-node'; +// Output to a file +builder.sign(signer, { path: 'input.jpg' }, { path: 'signed.jpg' }); +``` -// read an asset into a buffer -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; +### Sign to an in-memory buffer -// build a manifest to use for signing -const manifest = new ManifestBuilder( - { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], - }, - { vendor: 'cai' }, -); +Use a destination object with `buffer: null`; after `sign`, the signed asset bytes are written into `dest.buffer`. -// create a signing function -async function sign(asset, manifest) { - const signer = await createTestSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -// sign -await sign(asset, manifest); +```typescript +const dest: { buffer: Buffer | null } = { buffer: null }; +builder.sign(signer, { path: 'input.jpg' }, dest); +const signedBytes = dest.buffer; ``` -**Remote signing** +### Callback signing (`signAsync`) -If you have access to a web service that performs signing, you can use it to sign remotely; for example: +For signing in hardware, a remote service, or other custom flows, use `CallbackSigner` and `signAsync`: -```ts +```typescript +import { Builder, CallbackSigner } from '@contentauth/c2pa-node'; import { readFile } from 'node:fs/promises'; -import { fetch, Headers } from 'node-fetch'; -import { createC2pa, SigningAlgorithm } from 'c2pa-node'; - -function createRemoteSigner() { - return { - type: 'remote', - async reserveSize() { - const url = `https://my.signing.service/box-size`; - const res = await fetch(url); - const data = (await res.json()) as { boxSize: number }; - return data.boxSize; - }, - async sign({ reserveSize, toBeSigned }) { - const url = `https://my.signing.service/sign?boxSize=${reserveSize}`; - const res = await fetch(url, { - method: 'POST', - headers: new Headers({ - 'Content-Type': 'application/octet-stream', - }), - body: toBeSigned, - }); - return res.buffer(); - }, - }; -} - -async function sign(asset, manifest) { - const signer = createRemoteSigner(); - const c2pa = createC2pa({ - signer, - }); - - const { signedAsset, signedManifest } = await c2pa.sign({ - asset, - manifest, - }); -} - -const buffer = await readFile('to-be-signed.jpg'); -const asset: Asset = { buffer, mimeType: 'image/jpeg' }; - -const manifest = new ManifestBuilder( + +const cert = await readFile('signer.pem'); + +const callbackSigner = CallbackSigner.newSigner( { - claim_generator: 'my-app/1.0.0', - format: 'image/jpeg', - title: 'buffer_signer.jpg', - assertions: [ - { - label: 'c2pa.actions', - data: { - actions: [ - { - action: 'c2pa.created', - }, - ], - }, - }, - { - label: 'com.custom.my-assertion', - data: { - description: 'My custom test assertion', - version: '1.0.0', - }, - }, - ], + alg: 'es256', + certs: [cert], + reserveSize: 1024, + }, + async (data: Buffer) => { + return customSign(data); }, - { vendor: 'cai' }, ); -await sign(asset, manifest); -``` \ No newline at end of file +const builder = Builder.new(); +await builder.signAsync( + callbackSigner, + { path: 'input.jpg' }, + { path: 'signed.jpg' }, +); +``` + +Replace `customSign` with your implementation that returns the detached signature bytes for the C2PA claim. + +For identity assertions (CAWG), see `IdentityAssertionBuilder` and `IdentityAssertionSigner` in the [c2pa-node-v2 README](https://github.com/contentauth/c2pa-node-v2#identity-assertion-components). diff --git a/docs/tasks/node/_node-get-resources.md b/docs/tasks/node/_node-get-resources.md index 68006e78..4f5a0e54 100644 --- a/docs/tasks/node/_node-get-resources.md +++ b/docs/tasks/node/_node-get-resources.md @@ -1,10 +1,74 @@ -The example below shows how to get resources from manifest data using the Node.js library. +Binary resources (thumbnails, icons, linked manifest blobs) are referenced by URI strings in the active manifest. Use `Reader.resourceToAsset` to copy a resource to a file path or to a buffer. -```js -import { createC2pa } from 'c2pa-node'; -import { readFile } from 'node:fs/promises'; +### Write a resource to a file -const c2pa = createC2pa(); +```typescript +import { Reader } from '@contentauth/c2pa-node'; +import path from 'node:path'; -// TBD -``` \ No newline at end of file +async function writeThumbnail( + assetPath: string, + outputPath: string, +): Promise { + const reader = await Reader.fromAsset({ path: assetPath }); + if (!reader) { + throw new Error('No C2PA manifest found for this asset.'); + } + + const manifest = reader.getActive(); + const uri = manifest?.thumbnail?.identifier; + if (!uri) { + throw new Error('Active manifest has no thumbnail.'); + } + + await reader.resourceToAsset(uri, { path: path.resolve(outputPath) }); +} +``` + +### Read a resource into a buffer + +Pass a destination object with `buffer: null`. The implementation fills `buffer` after the call. + +```typescript +import { Reader } from '@contentauth/c2pa-node'; + +async function readResourceToBuffer( + assetPath: string, + uri: string, +): Promise { + const reader = await Reader.fromAsset({ path: assetPath }); + if (!reader) { + throw new Error('No C2PA manifest found.'); + } + + const dest: { buffer: Buffer | null } = { buffer: null }; + await reader.resourceToAsset(uri, dest); + if (!dest.buffer) { + throw new Error('Resource could not be read.'); + } + return dest.buffer; +} +``` + +### Discover URIs from JSON + +You can inspect `reader.json()` for the full manifest store, or use `getActive()` for the active manifest only. Ingredient thumbnails use the same `identifier` pattern as the claim thumbnail. + +```typescript +import { Reader } from '@contentauth/c2pa-node'; + +const reader = await Reader.fromAsset({ path: 'signed.jpg' }); +if (!reader) { + process.exit(0); +} + +const store = reader.json(); +console.log(JSON.stringify(store, null, 2)); + +const active = reader.getActive(); +for (const ing of active?.ingredients ?? []) { + if (ing.thumbnail?.identifier) { + console.log('Ingredient thumbnail URI:', ing.thumbnail.identifier); + } +} +``` diff --git a/docs/tasks/node/_node-intents.md b/docs/tasks/node/_node-intents.md new file mode 100644 index 00000000..fdc9e6e5 --- /dev/null +++ b/docs/tasks/node/_node-intents.md @@ -0,0 +1,94 @@ +### Setting the intent + +In Node.js, call `setIntent` on a `Builder` from `@contentauth/c2pa-node`. The intent shapes validation, default actions, and whether a parent ingredient is required. + +```typescript +import { Builder } from '@contentauth/c2pa-node'; + +const builder = Builder.new(); + +// Runtime intent (Create, Edit, or Update) +builder.setIntent({ + create: + 'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia', +}); +``` + +You can also set `edit` or `update` with a string: + +```typescript +builder.setIntent('edit'); +builder.setIntent('update'); +``` + +### Create intent + +Use a `create` intent for new digital creations. You must supply a [digital source type](https://c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_digital_source_type) URI. The manifest must not have a parent ingredient; the SDK can add a `c2pa.created` action when appropriate. + +```typescript +import { Builder, LocalSigner } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const builder = Builder.new(); +builder.setIntent({ + create: + 'http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture', +}); + +const signer = LocalSigner.newSigner( + await readFile('signer.pem'), + await readFile('signer.key'), + 'es256', +); + +builder.sign( + signer, + { path: 'source.jpg' }, + { path: 'signed.jpg' }, +); +``` + +### Edit intent + +Use `edit` when modifying existing content. If you do not add a parent ingredient, one can be created from the source asset you pass to `sign`. + +```typescript +import { Builder } from '@contentauth/c2pa-node'; + +const builder = Builder.new(); +builder.setIntent('edit'); +``` + +To supply the parent explicitly, add an ingredient JSON string and optional asset buffer (see [Adding manifest data](../build.mdx)): + +```typescript +import { Builder } from '@contentauth/c2pa-node'; +import { readFile } from 'node:fs/promises'; + +const builder = Builder.new(); +builder.setIntent('edit'); + +const parentJson = JSON.stringify({ + title: 'Original Photo', + relationship: 'parentOf', + format: 'image/jpeg', +}); + +await builder.addIngredient(parentJson, { + buffer: await readFile('original.jpg'), + mimeType: 'image/jpeg', +}); +``` + +### Update intent + +Use `update` for restricted, metadata-oriented changes: typically a single parent ingredient and no changes to the parent’s hashed content. + +```typescript +import { Builder } from '@contentauth/c2pa-node'; + +const builder = Builder.new(); +builder.setIntent('update'); +``` + +For more detail on intent semantics, see the [c2pa-rs `Builder` documentation](https://docs.rs/c2pa/latest/c2pa/struct.Builder.html). diff --git a/docs/tasks/node/_node-read.md b/docs/tasks/node/_node-read.md index 3c2e2485..06b90c38 100644 --- a/docs/tasks/node/_node-read.md +++ b/docs/tasks/node/_node-read.md @@ -42,7 +42,10 @@ import { Reader } from '@contentauth/c2pa-node'; async function readFromBuffer(filePath: string): Promise { const buffer = await fs.readFile(filePath); - const reader = await Reader.fromAsset({ buffer, mimeType: 'jpeg' }); // adjust mimeType as needed + const reader = await Reader.fromAsset({ + buffer, + mimeType: 'image/jpeg', + }); if (!reader) { console.log('No C2PA manifest found.'); return; diff --git a/docs/tasks/node/_node-settings.md b/docs/tasks/node/_node-settings.md index 459689c1..04b71f3d 100644 --- a/docs/tasks/node/_node-settings.md +++ b/docs/tasks/node/_node-settings.md @@ -1,29 +1,61 @@ -```ts +The Node.js library does not expose a `Context` type. Instead, you pass **per-instance** settings: a JavaScript object, a JSON string, or file contents (JSON or TOML) from `loadSettingsFromFile`, as the second argument to `Reader.fromAsset`, `Reader.fromManifestDataAndAsset`, `Builder.new`, `Builder.withJson`, or `Builder.fromArchive`. + +For the full settings schema, see [SDK object reference — Settings](../../manifest/json-ref/settings-schema). + +### Inline settings + +```typescript +import { Reader } from '@contentauth/c2pa-node'; + +const settings = { + verify: { + verify_after_reading: false, + verify_trust: true, + }, +}; + +const reader = await Reader.fromAsset({ path: 'image.jpg' }, settings); +``` + +`Builder` accepts the same shape (or a JSON string): + +```typescript +import { Builder } from '@contentauth/c2pa-node'; + +const builder = Builder.new({ + builder: { generate_c2pa_archive: true }, +}); +``` + +### Helper functions + +Use the helpers from `@contentauth/c2pa-node` to build typed fragments, merge them, and serialize to JSON (camelCase keys are converted to snake_case for the Rust-backed SDK): + +```typescript import { + Builder, + Reader, createTrustSettings, createCawgTrustSettings, createVerifySettings, mergeSettings, settingsToJson, loadSettingsFromFile, - loadSettingsFromUrl + loadSettingsFromUrl, } from '@contentauth/c2pa-node'; -// Create trust settings const trustSettings = createTrustSettings({ verifyTrustList: true, - userAnchors: "path/to/user-anchors.pem", - trustAnchors: "path/to/trust-anchors.pem", - allowedList: "path/to/allowed-list.pem" + userAnchors: 'path/to/user-anchors.pem', + trustAnchors: 'path/to/trust-anchors.pem', + allowedList: 'path/to/allowed-list.pem', }); -// Create CAWG trust settings const cawgTrustSettings = createCawgTrustSettings({ verifyTrustList: true, - trustAnchors: "path/to/cawg-anchors.pem" + trustAnchors: 'path/to/cawg-anchors.pem', }); -// Create verify settings const verifySettings = createVerifySettings({ verifyAfterReading: false, verifyAfterSign: false, @@ -32,20 +64,23 @@ const verifySettings = createVerifySettings({ ocspFetch: true, remoteManifestFetch: true, skipIngredientConflictResolution: false, - strictV1Validation: false + strictV1Validation: false, }); -// Merge multiple settings -const combinedSettings = mergeSettings(trustSettings, verifySettings); +const combined = mergeSettings(trustSettings, verifySettings, cawgTrustSettings); +const asJson = settingsToJson(combined); +``` + +### Load settings from a file or URL -// Convert settings to JSON string -const jsonString = settingsToJson(combinedSettings); +```typescript +import { Reader, Builder, loadSettingsFromFile, loadSettingsFromUrl } from '@contentauth/c2pa-node'; -// Load settings from file (JSON or TOML) const fileSettings = await loadSettingsFromFile('./c2pa-settings.toml'); -const reader = await Reader.fromAsset(inputAsset, fileSettings); +const reader = await Reader.fromAsset({ path: 'image.jpg' }, fileSettings); -// Load settings from URL const urlSettings = await loadSettingsFromUrl('https://example.com/c2pa-settings.json'); const builder = Builder.new(urlSettings); -``` \ No newline at end of file +``` + +Only use HTTPS URLs you trust for `loadSettingsFromUrl`. diff --git a/docs/tasks/node/_node-wip.md b/docs/tasks/node/_node-wip.md deleted file mode 100644 index ca2cd717..00000000 --- a/docs/tasks/node/_node-wip.md +++ /dev/null @@ -1,3 +0,0 @@ -:::note -The Node.js library is being revised. The documentation will be updated as soon as possible with the latest changes. -::: \ No newline at end of file diff --git a/docs/tasks/read.mdx b/docs/tasks/read.mdx index fe80cc31..43b55d3d 100644 --- a/docs/tasks/read.mdx +++ b/docs/tasks/read.mdx @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import PythonRead from './python/_python-read.md'; import CppRead from './cpp/_cpp-read.md'; import RustRead from './rust/_rust-read.md'; +import NodeRead from './node/_node-read.md'; @@ -31,4 +32,10 @@ import RustRead from './rust/_rust-read.md'; + + + + + + diff --git a/docs/tasks/settings.mdx b/docs/tasks/settings.mdx index acd23431..b0d3d5c5 100644 --- a/docs/tasks/settings.mdx +++ b/docs/tasks/settings.mdx @@ -10,6 +10,7 @@ import TabItem from '@theme/TabItem'; import PythonRead from './python/_python-settings.md'; import CppRead from './cpp/_cpp-settings.md'; import RustRead from './rust/_rust-settings.md'; +import NodeSettings from './node/_node-settings.md'; Regardless of which language you're working in, you use the `Context` and `Settings` classes to control SDK behavior including verification, trust anchors, thumbnails, signing, and more. @@ -92,4 +93,10 @@ For Boolean values, use JSON `true` and `false`, not the strings `"true"` and `" + + + + + +