@affirmfirst/react
v1.0.0
Published
React components for aFFirmFirst secure image delivery. Drop-in replacement for <img> tags with encrypted canvas rendering, watermarking, and right-click protection.
Maintainers
Readme
@affirmfirst/react
React components for aFFirmFirst secure image delivery. Drop-in replacement for <img> tags with encrypted canvas rendering, watermarking, and right-click protection.
Installation
npm install @affirmfirst/reactQuick Start
1. Wrap your app with <AffirmProvider>
// app/layout.tsx (Next.js) or App.tsx (React)
import { AffirmProvider } from '@affirmfirst/react';
export default function Layout({ children }) {
return (
<AffirmProvider>
{children}
</AffirmProvider>
);
}2. Use <SecureImage> anywhere
import { SecureImage } from '@affirmfirst/react';
export function ProductImage({ imageId }) {
return (
<SecureImage
imageId={imageId}
width={600}
height={400}
alt="Product photo"
/>
);
}That's it. The image is rendered via encrypted canvas — no <img> tag, no downloadable URL.
Components
<AffirmProvider>
Auto-loads the aFFirmFirst SDK script. Place in your root layout. No configuration needed.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| autoLoad | boolean | true | Auto-inject the SDK <script> tag |
<SecureImage>
Renders a single protected image.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| imageId | string | required | aFFirmFirst vault image ID |
| width | string \| number | '100%' | Container width |
| height | string \| number | 'auto' | Container height |
| lazy | boolean | true | Lazy load via IntersectionObserver |
| className | string | — | CSS class for wrapper |
| style | CSSProperties | — | Inline styles for wrapper |
| alt | string | — | Accessibility label |
| onLoad | () => void | — | Called when image loads |
| onError | (error: string) => void | — | Called on load error |
<SecureGallery>
Renders a grid or masonry layout of protected images.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| imageIds | string[] | required | Array of vault image IDs |
| layout | 'grid' \| 'masonry' | 'grid' | Layout mode |
| columns | number | 3 | Number of columns |
| gap | number | 8 | Gap between images (px) |
| imageHeight | string | '250px' | Image height (grid mode) |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline styles |
Hooks
useAffirmFirst()
Access the SDK instance for programmatic control.
import { useAffirmFirst } from '@affirmfirst/react';
function MyComponent() {
const { ready, sdk } = useAffirmFirst();
const handleEmbed = () => {
if (ready && sdk) {
sdk.embed({
imageId: 'clx123abc',
container: document.getElementById('my-container'),
width: '100%',
height: 'auto',
});
}
};
return <div id="my-container" />;
}Next.js Utilities
Import from @affirmfirst/react/next:
affirmImageLoader
Custom image loader for next/image. Uses thumbnail URLs for standard <Image> rendering (previews, admin panels). For maximum protection, use <SecureImage> instead.
// lib/affirm-loader.js
import { affirmImageLoader } from '@affirmfirst/react/next';
export default affirmImageLoader;// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './lib/affirm-loader.js',
},
};SDK_SCRIPT_URL
Pre-configured SDK script URL. Use with Next.js <Script> if you prefer manual loading over <AffirmProvider>.
import Script from 'next/script';
import { SDK_SCRIPT_URL } from '@affirmfirst/react/next';
<Script src={SDK_SCRIPT_URL} strategy="afterInteractive" />Usage Examples
Next.js Ecommerce — Product Page
import { SecureImage } from '@affirmfirst/react';
export default function ProductPage({ product }) {
return (
<div className="grid grid-cols-2 gap-8">
<SecureImage
imageId={product.affirmImageId}
width="100%"
height={500}
alt={product.name}
/>
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
</div>
</div>
);
}
// SEO: Use signed URL for og:image (crawlers can't run JS)
export async function generateMetadata({ params }) {
const product = await getProduct(params.id);
return {
openGraph: {
images: [product.signedImageUrl],
},
};
}Product Gallery
import { SecureGallery } from '@affirmfirst/react';
export default function ProductGallery({ imageIds }) {
return (
<SecureGallery
imageIds={imageIds}
layout="grid"
columns={4}
gap={4}
imageHeight="200px"
/>
);
}Portfolio Masonry
import { SecureGallery } from '@affirmfirst/react';
export default function Portfolio({ imageIds }) {
return (
<SecureGallery
imageIds={imageIds}
layout="masonry"
columns={3}
gap={8}
/>
);
}How It Works
<AffirmProvider>loads the SDK script (/sdk/affirm.js) into the page<SecureImage>creates a container div and callsAffirmFirst.embed()- The SDK fetches the image via encrypted streaming (
/stream/secure/:imageId) - A Web Worker decrypts the AES-256-CBC encrypted bytes in memory
- The decrypted image is painted onto a
<canvas>element — no<img>tag exists - Watermark overlay, right-click blocking, and screen capture detection are applied
Security features active on every image:
- AES-256-CBC encryption in transit
- Canvas rendering (no downloadable URL)
- Imperceptible pixel noise (anti-scraping)
- Right-click and drag protection
- Watermark overlay
- Screen capture detection
- Domain locking (referer validation)
License
MIT
