@jaak.ai/stamps
v2.3.0
Published
Jaak Document Identifier
Keywords
Readme
Jaak Stamps Web Component
Description
Jaak Stamps is a powerful web component for document scanning and capture using camera input with AI-powered document detection. The component provides a complete solution for capturing high-quality document images with real-time detection feedback and automated cropping capabilities.
Key Features
- AI-Powered Document Detection: Automatically detects and frames documents in the camera view
- Dual-Side Capture: Supports both front and back document capture workflow
- Real-time Preview: Live camera feed with detection overlays and guidance
- Multi-Camera Support: Automatic camera selection with manual override options
- Smart Cropping: Automatic document boundary detection and cropping
- Mobile Optimized: Responsive design that works across all device sizes
- Web Standards Compliant: Built as a standard Web Component for maximum compatibility
Typical Use Cases
- Identity Verification: Capture ID cards, passports, and driver's licenses
- Document Digitization: Scan contracts, certificates, and legal documents
- KYC/AML Processes: Streamlined identity document capture for compliance
- Form Processing: Digitize paper forms and applications
- Insurance Claims: Capture damage reports and supporting documentation
Installation
NPM/Yarn
# Using npm
npm install @jaak.ai/stamps
# Using yarn
yarn add @jaak.ai/stampsCDN
<!-- ES Module -->
<script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
<!-- With Loader -->
<script type="module">
import { defineCustomElements } from 'https://unpkg.com/@jaak.ai/stamps/loader/index.js';
defineCustomElements();
</script>API Reference
Properties/Attributes
License Properties (Required)
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| license | string | undefined | [REQUIRED] License key for component authentication |
| license-environment | 'dev' \| 'qa' \| 'sandbox' \| 'prod' | 'prod' | API environment for license validation |
| app-id | string | 'jaak-stamps-web' | Application identifier for analytics and tracking |
| trace-id | string | undefined | Optional trace ID for distributed tracing (auto-generated if not provided) |
Base Configuration Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| debug | boolean | false | Enable debug mode with additional logging and overlays |
| mask-size | number | 90 | Size percentage of the document detection mask (50-100) |
| alignment-tolerance | number | 15 | Tolerance level in pixels for document alignment detection |
| capture-delay | number | 1500 | Delay in milliseconds before automatic capture (0-10000) |
| crop-margin | number | 20 | Margin in pixels around detected document for cropping (0-100) |
| preferred-camera | 'auto' \| 'front' \| 'back' | 'auto' | Preferred camera selection |
| use-document-classification | boolean | false | Enable AI document type classification |
| use-document-detector | boolean | true | Enable/disable AI document detection model |
| enable-back-document-timer | boolean | false | Enable timer for back document capture |
| back-document-timer-duration | number | 20 | Duration in seconds for back document capture timer |
Telemetry and OpenTelemetry Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| telemetry-collector-url | string | 'https://collector.jaak.ai/v1/traces' | OTLP collector URL for distributed traces |
| metrics-collector-url | string | 'https://collector.jaak.ai/v1/metrics' | OTLP collector URL for metrics |
| enable-telemetry | boolean | true | Enable distributed tracing with OpenTelemetry |
| enable-metrics | boolean | true | Enable metrics export to OpenTelemetry |
| metrics-export-interval-millis | number | 60000 | Metrics export interval in milliseconds |
| propagate-trace-header-cors-urls | string | undefined | Comma-separated URLs for W3C Trace Context header propagation |
Context Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| customer-id | string | undefined | Customer ID for telemetry and analytics |
| tenant-id | string | undefined | Tenant ID for multi-tenancy support |
| environment | string | 'production' | Execution environment (development/staging/production) |
Methods
Camera Control
// Get information about available cameras
getCameraInfo(): Promise<CameraInfoResponse>
// Set preferred camera
setPreferredCamera(camera: "auto" | "front" | "back"): Promise<{
success: boolean;
selectedCamera: string;
availableCameras: number;
}>
// Control device torch/flashlight
setTorchEnabled(enabled: boolean): Promise<{
success: boolean;
enabled: boolean;
}>
// Focus camera at specific point
focusAtPoint(x: number, y: number): Promise<{
success: boolean;
coordinates: { x: number; y: number };
}>Capture Control
// Start the capture process
startCapture(): Promise<void>
// Stop the capture process
stopCapture(): Promise<void>
// Reset capture state
resetCapture(): Promise<void>
// Skip back document capture
skipBackCapture(): Promise<void>Configuration
// Set capture delay
setCaptureDelay(delay: number): Promise<{
success: boolean;
captureDelay: number;
}>
// Get current capture delay
getCaptureDelay(): Promise<number>Status & Data
// Get current component status
getStatus(): Promise<StatusResponse>
// Get captured images
getCapturedImages(): Promise<CapturedImagesResponse>
// Check if process is completed
isProcessCompleted(): Promise<boolean>
// Preload AI model
preloadModel(): Promise<{
success: boolean;
message?: string;
error?: any;
}>Events
| Event | Detail Type | Description |
|-------|-------------|-------------|
| isReady | boolean | Fired when component is fully initialized and ready |
| captureCompleted | object | Fired when document capture process is completed |
| traceIdGenerated | { traceId: string } | Fired after license validation with the generated or provided trace ID for request tracing |
Type Definitions
interface CameraInfoResponse {
availableCameras: Array<{
id: string;
label: string;
selected: boolean;
}>;
selectedCameraId: string | null;
deviceType: string;
isMultipleCamerasAvailable: boolean;
preferredFacing: 'environment' | 'user' | null;
}
interface CapturedImagesResponse {
front: {
fullFrame: string | null;
cropped: string | null;
};
back: {
fullFrame: string | null;
cropped: string | null;
};
metadata: {
totalImages: number;
processCompleted: boolean;
backCaptureSkipped: boolean;
};
}
interface StatusResponse {
isVideoActive: boolean;
captureStep: 'front' | 'back' | 'completed';
hasImages: boolean;
isProcessCompleted: boolean;
isModelPreloaded: boolean;
}Implementation Examples
Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
</head>
<body>
<jaak-stamps
id="documentScanner"
license="your-license-key-here"
environment="prod"
app-id="my-custom-app"
preferred-camera="auto"
capture-delay="1500"
use-document-detector="true"
debug="false">
</jaak-stamps>
<script>
const scanner = document.getElementById('documentScanner');
// Handle trace ID generation
scanner.addEventListener('traceIdGenerated', (event) => {
console.log('Trace ID for this session:', event.detail.traceId);
// Use this trace ID for correlating requests across your system
});
// Wait for component to be ready
scanner.addEventListener('isReady', () => {
console.log('Document scanner is ready');
// Start the capture process
scanner.startCapture();
});
// Handle capture completion
scanner.addEventListener('captureCompleted', async (event) => {
console.log('Capture completed:', event.detail);
// Get captured images
const images = await scanner.getCapturedImages();
console.log('Captured images:', images);
// Process the images as needed
if (images.front.cropped) {
// Display or upload the cropped front image
displayImage(images.front.cropped);
}
});
function displayImage(base64Image) {
const img = document.createElement('img');
img.src = `data:image/jpeg;base64,${base64Image}`;
document.body.appendChild(img);
}
</script>
</body>
</html>React
import React, { useEffect, useRef, useState } from 'react';
import { defineCustomElements } from '@jaak.ai/stamps/loader';
// Define custom elements
defineCustomElements();
// Extend HTMLElement for TypeScript
declare global {
namespace JSX {
interface IntrinsicElements {
'jaak-stamps': any;
}
}
}
interface CapturedImages {
front: { fullFrame: string | null; cropped: string | null };
back: { fullFrame: string | null; cropped: string | null };
metadata: {
totalImages: number;
processCompleted: boolean;
backCaptureSkipped: boolean;
};
}
const DocumentScanner: React.FC = () => {
const scannerRef = useRef<HTMLElement>(null);
const [isReady, setIsReady] = useState(false);
const [capturedImages, setCapturedImages] = useState<CapturedImages | null>(null);
useEffect(() => {
const scanner = scannerRef.current;
if (!scanner) return;
// Trace ID generation handler
const handleTraceIdGenerated = (event: CustomEvent) => {
console.log('Trace ID:', event.detail.traceId);
};
// Component ready handler
const handleReady = () => {
setIsReady(true);
};
// Capture completion handler
const handleCaptureCompleted = async () => {
const images = await (scanner as any).getCapturedImages();
setCapturedImages(images);
};
// Add event listeners
scanner.addEventListener('traceIdGenerated', handleTraceIdGenerated);
scanner.addEventListener('isReady', handleReady);
scanner.addEventListener('captureCompleted', handleCaptureCompleted);
return () => {
scanner.removeEventListener('traceIdGenerated', handleTraceIdGenerated);
scanner.removeEventListener('isReady', handleReady);
scanner.removeEventListener('captureCompleted', handleCaptureCompleted);
};
}, []);
const startScan = async () => {
if (scannerRef.current) {
await (scannerRef.current as any).startCapture();
}
};
return (
<div>
<h2>Document Scanner</h2>
<jaak-stamps
ref={scannerRef}
license="your-license-key-here"
environment="prod"
app-id="my-react-app"
trace-id="" // Leave empty to auto-generate
preferred-camera="auto"
capture-delay={1500}
use-document-classification={true}
use-document-detector={true}
/>
<div style={{ marginTop: '20px' }}>
<button onClick={startScan} disabled={!isReady}>
{isReady ? 'Start Scanning' : 'Loading...'}
</button>
</div>
{capturedImages && (
<div style={{ marginTop: '20px' }}>
<h3>Captured Images</h3>
{capturedImages.front.cropped && (
<div>
<h4>Front Document</h4>
<img
src={`data:image/jpeg;base64,${capturedImages.front.cropped}`}
alt="Front document"
style={{ maxWidth: '300px' }}
/>
</div>
)}
{capturedImages.back.cropped && (
<div>
<h4>Back Document</h4>
<img
src={`data:image/jpeg;base64,${capturedImages.back.cropped}`}
alt="Back document"
style={{ maxWidth: '300px' }}
/>
</div>
)}
</div>
)}
</div>
);
};
export default DocumentScanner;Angular
// document-scanner.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { defineCustomElements } from '@jaak.ai/stamps/loader';
// Define custom elements
defineCustomElements();
interface CapturedImages {
front: { fullFrame: string | null; cropped: string | null };
back: { fullFrame: string | null; cropped: string | null };
metadata: {
totalImages: number;
processCompleted: boolean;
backCaptureSkipped: boolean;
};
}
@Component({
selector: 'app-document-scanner',
template: `
<div class="scanner-container">
<h2>Document Scanner</h2>
<jaak-stamps
#scanner
license="your-license-key-here"
environment="prod"
app-id="my-angular-app"
preferred-camera="auto"
[capture-delay]="1500"
[use-document-classification]="true"
[use-document-detector]="true">
</jaak-stamps>
<div class="controls">
<button
(click)="startScan()"
[disabled]="!isReady"
class="scan-button">
{{ isReady ? 'Start Scanning' : 'Loading...' }}
</button>
<button
(click)="resetScan()"
[disabled]="!isReady"
class="reset-button">
Reset
</button>
</div>
<div *ngIf="capturedImages" class="results">
<h3>Captured Images</h3>
<div *ngIf="capturedImages.front.cropped" class="image-result">
<h4>Front Document</h4>
<img
[src]="'data:image/jpeg;base64,' + capturedImages.front.cropped"
alt="Front document"
class="captured-image">
</div>
<div *ngIf="capturedImages.back.cropped" class="image-result">
<h4>Back Document</h4>
<img
[src]="'data:image/jpeg;base64,' + capturedImages.back.cropped"
alt="Back document"
class="captured-image">
</div>
<div class="metadata">
<p>Total Images: {{ capturedImages.metadata.totalImages }}</p>
<p>Process Completed: {{ capturedImages.metadata.processCompleted }}</p>
</div>
</div>
</div>
`,
styles: [`
.scanner-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.controls {
margin: 20px 0;
display: flex;
gap: 10px;
}
.scan-button, .reset-button {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.scan-button {
background-color: #007bff;
color: white;
}
.reset-button {
background-color: #6c757d;
color: white;
}
.scan-button:disabled, .reset-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.results {
margin-top: 20px;
}
.image-result {
margin: 20px 0;
}
.captured-image {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
border-radius: 4px;
}
.metadata {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
margin-top: 20px;
}
`],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class DocumentScannerComponent implements AfterViewInit {
@ViewChild('scanner', { static: false }) scannerRef!: ElementRef;
isReady = false;
capturedImages: CapturedImages | null = null;
ngAfterViewInit() {
const scanner = this.scannerRef.nativeElement;
// Trace ID generation handler
scanner.addEventListener('traceIdGenerated', (event: CustomEvent) => {
console.log('Trace ID:', event.detail.traceId);
});
// Component ready handler
scanner.addEventListener('isReady', () => {
this.isReady = true;
});
// Capture completion handler
scanner.addEventListener('captureCompleted', async () => {
this.capturedImages = await scanner.getCapturedImages();
});
}
async startScan() {
if (this.scannerRef?.nativeElement) {
await this.scannerRef.nativeElement.startCapture();
}
}
async resetScan() {
if (this.scannerRef?.nativeElement) {
await this.scannerRef.nativeElement.resetCapture();
this.capturedImages = null;
}
}
}// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { DocumentScannerComponent } from './document-scanner.component';
@NgModule({
declarations: [DocumentScannerComponent],
imports: [BrowserModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for custom elements
exports: [DocumentScannerComponent]
})
export class DocumentScannerModule { }Telemetry and Observability (OpenTelemetry)
Jaak Stamps includes full OpenTelemetry integration for distributed tracing, metrics, and performance monitoring in production environments.
Distributed Tracing
The component implements distributed tracing following the W3C Trace Context standard, providing:
- Complete flow tracking of document capture processes
- Request correlation between frontend and backend
- Performance bottleneck identification
- Context propagation via
traceparentheaders
Automatically Recorded Spans:
component.initialize- Component initializationcapture.start- Capture process startmodel.preload- AI model preloadingmodel.detection.load- Detection model loadingmodel.classification.load- Classification model loading
Performance Metrics
The component exports detailed metrics to OpenTelemetry:
Counters:
capture.counter- Number of completed captureserror.counter- Error count by typemodel.load.counter- Model load countuser.interaction.counter- User interactions
Histograms (Latencies):
capture.latency- Total capture timemodel.load.latency- Model loading timedetection.latency- Per-frame detection timeimage.size- Captured image sizes
Gauges (Current State):
active.sessions- Active sessionsmemory.usage- Memory usage
Telemetry Configuration
<jaak-stamps
license="your-license-key"
enable-telemetry="true"
enable-metrics="true"
telemetry-collector-url="https://collector.jaak.ai/v1/traces"
metrics-collector-url="https://collector.jaak.ai/v1/metrics"
metrics-export-interval-millis="60000"
propagate-trace-header-cors-urls="https://api.example.com,https://backend.example.com"
customer-id="customer123"
tenant-id="tenant456"
environment="production"
trace-id="optional-custom-trace-id">
</jaak-stamps>Using Trace ID
The component automatically generates a unique trace ID for each session, or accepts a custom one:
const scanner = document.getElementById('documentScanner');
// Listen for trace ID generation
scanner.addEventListener('traceIdGenerated', (event) => {
const traceId = event.detail.traceId;
console.log('Trace ID for this session:', traceId);
// Use this traceId to correlate requests in your backend
fetch('/api/process-document', {
method: 'POST',
headers: {
'X-Trace-Id': traceId,
'Content-Type': 'application/json'
},
body: JSON.stringify({ /* data */ })
});
});Enriched Attributes
Traces and metrics automatically include:
- User Agent: Browser, version, operating system
- Geolocation: Country, region, city (when available)
- Device Memory: Device memory capacity
- Customer Context:
customerId,tenantId,environment - Service Info: Service name, component version
Context Propagation (CORS)
To propagate traces to your backend services:
<jaak-stamps
propagate-trace-header-cors-urls="https://api.example.com,https://services.example.com">
</jaak-stamps>This automatically configures fetch and XMLHttpRequest interceptors to include the traceparent header in requests to those URLs.
Visualization with Jaeger/Zipkin
Exported traces are compatible with:
- Jaeger - Distributed tracing system
- Zipkin - Alternative tracing system
- Grafana Tempo - Grafana trace backend
- Any OTLP-compatible backend (OpenTelemetry Protocol)
License Validation
The component requires a valid license to function. Validation occurs automatically when the component loads.
License Configuration
<jaak-stamps
license="your-license-key-here"
license-environment="prod"
app-id="my-application">
</jaak-stamps>Available Environments
| Environment | Validation URL | Use Case |
|-------------|---------------|----------|
| dev | https://api.dev.jaak.ai | Local development |
| qa | https://api.qa.jaak.ai | Quality assurance testing |
| sandbox | https://api.sandbox.jaak.ai | Integration testing |
| prod | https://services.api.jaak.ai | Production |
License Error Handling
If the license is invalid, the component will display an error and not function:
const scanner = document.getElementById('documentScanner');
scanner.addEventListener('isReady', (event) => {
if (!event.detail) {
console.error('Error: Invalid license or component not initialized');
}
});Common error messages:
"License key is required"- No license provided"Invalid license key"- Invalid or expired license"License validation failed"- Validation error
Style Guide
CSS Custom Properties
The component supports customization through CSS custom properties:
jaak-stamps {
/* Camera view dimensions */
--camera-width: 100%;
--camera-height: 400px;
/* Detection overlay colors */
--detection-overlay-color: rgba(0, 123, 255, 0.3);
--detection-border-color: #007bff;
--detection-border-width: 2px;
/* UI element colors */
--button-background: #007bff;
--button-text-color: #ffffff;
--button-border-radius: 4px;
/* Status indicator colors */
--status-success-color: #28a745;
--status-warning-color: #ffc107;
--status-error-color: #dc3545;
}Custom CSS Classes
You can style the component container and add custom overlays:
/* Component container styling */
.document-scanner-container {
position: relative;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: #000;
}
/* Custom loading overlay */
.scanner-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 8px;
z-index: 1000;
}
/* Responsive design */
@media (max-width: 768px) {
jaak-stamps {
--camera-height: 300px;
}
}Theme Support
The component automatically adapts to system dark/light mode preferences:
/* Light theme (default) */
jaak-stamps {
--background-color: #ffffff;
--text-color: #333333;
--border-color: #dddddd;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
jaak-stamps {
--background-color: #1a1a1a;
--text-color: #ffffff;
--border-color: #444444;
}
}Compatibility
Browser Support
- Chrome/Edge: 80+
- Firefox: 75+
- Safari: 13+
- iOS Safari: 13+
- Chrome Android: 80+
Framework Compatibility
- Vanilla JavaScript: Full support
- React: 16.8+
- Angular: 12+
- Vue.js: 3+
- Svelte: 3+
Required Dependencies
- Modern browser with WebRTC support
- Camera access permission
- HTTPS context (required for camera access)
Optional Dependencies
- Web Workers support for enhanced performance
- WebAssembly support for AI model acceleration
Troubleshooting
Common Issues
Camera Not Working
Problem: Camera preview is black or not showing
Solutions:
// Check camera permissions
const scanner = document.querySelector('jaak-stamps');
const cameraInfo = await scanner.getCameraInfo();
console.log('Available cameras:', cameraInfo.availableCameras);
// Try switching cameras
await scanner.setPreferredCamera('back');Slow Performance
Problem: Component is slow or laggy
Solutions:
// Preload the AI model
const scanner = document.querySelector('jaak-stamps');
await scanner.preloadModel();
// Reduce capture delay for faster response
await scanner.setCaptureDelay(1000);
// Disable debug mode in production
scanner.setAttribute('debug', 'false');No Images Captured
Problem: Capture completes but no images returned
Solutions:
// Check component status
const status = await scanner.getStatus();
console.log('Component status:', status);
// Verify capture process completion
const isCompleted = await scanner.isProcessCompleted();
if (isCompleted) {
const images = await scanner.getCapturedImages();
console.log('Images:', images);
}Memory Issues on Mobile
Problem: Component crashes on mobile devices
Solutions:
<!-- Reduce memory usage -->
<jaak-stamps
mask-size="60"
crop-margin="15"
capture-delay="2000">
</jaak-stamps>Frequently Asked Questions
Q: Can I use this component without HTTPS?
A: No, camera access requires HTTPS in production. Use localhost for development.
Q: How do I handle multiple document types? A: Enable document classification:
<jaak-stamps use-document-classification="true"></jaak-stamps>Q: Can I customize the detection area?
A: Yes, use the mask-size property to adjust the detection frame size.
Q: Can I disable automatic document detection?
A: Yes, set use-document-detector="false" to disable AI detection and enable manual capture mode:
<jaak-stamps use-document-detector="false"></jaak-stamps>Q: How do I get only the cropped images?
A: Access the cropped property from the captured images:
const images = await scanner.getCapturedImages();
const croppedFront = images.front.cropped; // Base64 stringQ: Is the component accessible? A: Yes, the component includes ARIA labels and keyboard navigation support.
Q: Can I use this in a Progressive Web App (PWA)? A: Yes, the component is fully compatible with PWAs and works offline after initial load.
License
MIT © Jaak AI
