Framework-neutral viewer

BBP viewer web component

The component accepts ordinary and BBP image sources. It displays the safe base and reveals regions opened by the supplied private keys.

01 / start

Install and register

The current public release is an alpha. Install it from the next channel:

npm install @brightblur/bbp-viewer@next
<script type="module">
  import '@brightblur/bbp-viewer';
</script>

<bbp-viewer src="/photos/example.bbp.jpg"></bbp-viewer>

Importing the package registers <bbp-viewer>. The element works in plain HTML and in frameworks that support custom elements. View the package on npm ↗

02 / files and URLs

Image sources

The src attribute accepts a URL string. Remote resources such as S3 or R2 must allow the site's origin through CORS.

<bbp-viewer
  src="https://images.example.com/example.bbp.jpg">
</bbp-viewer>

Use the property for a browser File, Blob, URL, ArrayBuffer, or Uint8Array.

const viewer = document.querySelector('bbp-viewer');

input.addEventListener('change', () => {
  viewer.src = input.files?.[0] ?? null;
});
CORS matters for remote files. Loading the image in an ordinary <img> is not enough: the component fetches its bytes so it can parse the BBP payload.

03 / instance access

Instance keys

The property accepts strings, Uint8Array, or ArrayBuffer. Strings may be 64-character hexadecimal or base64. Assigning keys triggers a new local render.

const viewer = document.querySelector('bbp-viewer');

viewer.privateKeys = [
  firstPrivateKey,
  secondPrivateKey
];

viewer.clearKeys();

An explicit instance key list overrides the shared page keyring until useSharedPrivateKeys() is called.

04 / page-wide access

Shared keys

The shared keyring updates connected viewers and provides the default keys for viewers added later.

import {
  setBbpViewerPrivateKeys,
  clearBbpViewerPrivateKeys
} from '@brightblur/bbp-viewer';

setBbpViewerPrivateKeys([firstKey, secondKey]);

// Return an overridden instance to the shared keyring.
viewer.useSharedPrivateKeys();

// Remove page-wide keys.
clearBbpViewerPrivateKeys();

The keyring is module-local browser memory. It does not persist keys, synchronise tabs, or upload anything.

05 / declarative API

Attributes and properties

By default, the component shows the image recomposed with its available keys. Use compare for the draggable safe-base comparison or mask-only for the safe base alone.

NameKindPurpose
srcAttribute or propertyURL string as an attribute; URL, Blob, File, ArrayBuffer, or Uint8Array as a property.
private-keysAttributeA JSON array of hex or base64 key strings. Prefer the property when keys are already in JavaScript.
privateKeysPropertyInstance-specific private keys.
sourcePropertyAlias for src.
mask-onlyBoolean attributeShow only the undeciphered safe base.
reveal-onlyBoolean attributeExplicitly select the default recomposed view.
compareBoolean attributeOverlay the recomposed image and safe base with an accessible comparison slider.
<bbp-viewer
  src="/photos/example.bbp.jpg"
  private-keys='["base64-private-key"]'>
</bbp-viewer>

Embedding secrets in HTML can expose them to page source, logs, extensions, and analytics. The property or shared keyring is usually the safer integration.

06 / lifecycle

Rendering events

bbp-rendered fires after a successful render. For BBP files it reports the version, number of opened entities, total entity count, and accessible metadata. N-party intersections report an epochs array.

viewer.addEventListener('bbp-rendered', (event) => {
  console.log(event.detail);
  // {
  //   version: 1,
  //   openedEntities: 2,
  //   entityCount: 4,
  //   entities: [
  //     { kind: 'region', bbox, epoch: 3 },
  //     { kind: 'intersection', bbox, epochs: [3, 7, 2] }
  //   ]
  // }
});

viewer.addEventListener('bbp-error', (event) => {
  console.error(event.detail.error);
});

A normal image reports kind: 'normal' and renders identically in both panes.

07 / frameworks

Framework integration

Use the web component directly where possible. Add a wrapper only for framework-specific behaviour that the component does not provide.

React

// Set non-string properties through a ref.
const viewer = viewerRef.current;
viewer.src = file;
viewer.privateKeys = keys;

Svelte

Svelte assigns these values to the element's properties. Changes to file or keys update the viewer reactively.

<script>
  import '@brightblur/bbp-viewer';

  let { file, keys } = $props();
</script>

<bbp-viewer
  src={file}
  privateKeys={keys}
></bbp-viewer>

08 / custom carriers

Carrier adapter

The payload and cryptography do not change between carriers. An adapter recognises the base format and adds, extracts, or removes one ancillary JUMBF binding.

type CarrierAdapter = {
  detect(bytes: Uint8Array): boolean;
  embed(base: Uint8Array, jumbf: Uint8Array): Uint8Array;
  extract(file: Uint8Array): Uint8Array | null;
  strip(file: Uint8Array): Uint8Array;
};
  1. Implement structural detection from bytes.
  2. Embed the unchanged JUMBF superbox at an ancillary location.
  3. Extract only payloads with the BBP UUID.
  4. Strip only the BBP binding while preserving a decodable safe base.
  5. Add positive, negative, unrelated-metadata, and embed/extract/strip round-trip tests.
  6. Add the carrier to public detection and dispatch.

Current adapters are in packages/bbp/src/container.ts.

09 / Apple platforms

Native macOS viewer

The repository includes a native macOS example app built with SwiftUI and AppKit. It parses JPEG, WebP, PNG, and HEIF carriers in Swift and calls the Rust crypto core through its C ABI.

pnpm build:macos-app
pnpm test:macos

Open the macOS example app ↗

10 / operational boundary

Security notes

  • Private keys stay in browser memory, but the host page still controls its JavaScript environment.
  • Serve integrations over HTTPS and keep third-party scripts to a minimum.
  • Do not place private keys in URLs, storage, analytics fields, or server-rendered markup.
  • Use sensible limits before accepting large or adversarial files.
  • The protocol is still a draft and has not completed an independent security audit.