Client Integration
The Capsule client is a lightweight browser library that handles key management and content decryption using the Web Crypto API.
Installation
npm install @sesamy/capsuleBasic Usage
import { CapsuleClient } from '@sesamy/capsule';
// Initialize client
const client = new CapsuleClient({
keyId: 'user-keys',
keySize: 2048, // or 4096
});
// Check if keys exist, generate if needed
const hasKeys = await client.hasKeyPair();
if(!hasKeys) {
await client.generateKeyPair();
}
// Get public key to send to server
const publicKey = await client.getPublicKey(); // Base64 SPKI
// Decrypt content
const decrypted = await client.decryptArticle({
encryptedContent: 'base64...',
iv: 'base64...',
encryptedDek: 'base64...'
});API Reference
Constructor Options
interface CapsuleClientOptions {
keySize?: 2048 | 4096; // RSA key size(default: 2048)
dbName?: string; // IndexedDB database name
storeName?: string; // IndexedDB store name
keyId?: string; // Key identifier(default: 'default')
}Methods
generateKeyPair()
Generates a new RSA-OAEP key pair and stores it in IndexedDB.
const publicKeyB64 = await client.generateKeyPair();
// Returns: Base64-encoded SPKI public keyhasKeyPair()
Checks if a key pair exists in storage.
const exists = await client.hasKeyPair();
// Returns: booleangetPublicKey()
Retrieves the stored public key in Base64 SPKI format.
const publicKey = await client.getPublicKey();
// Returns: Base64-encoded SPKI public keydecryptArticle(payload)
Decrypts an encrypted article payload.
const content = await client.decryptArticle({
encryptedContent: 'base64-ciphertext',
iv: 'base64-iv',
encryptedDek: 'base64-wrapped-dek'
});
// Returns: Decrypted content as stringdecryptContent(payload)
Decrypts content and returns raw ArrayBuffer.
const buffer = await client.decryptContent(payload);
// Returns: ArrayBufferclearKeys()
Deletes all stored keys from IndexedDB.
await client.clearKeys();React Integration
import { useState, useEffect } from 'react';
import { CapsuleClient } from '@sesamy/capsule';
export function useCapsule() {
const [client, setClient] = useState(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
async function init() {
const capsule = new CapsuleClient({ keyId: 'demo-key' });
const hasKeys = await capsule.hasKeyPair();
if(!hasKeys) {
await capsule.generateKeyPair();
}
setClient(capsule);
setIsReady(true);
}
init();
}, []);
return { client, isReady };
}
// Usage
function Article({ encryptedData }) {
const { client, isReady } = useCapsule();
const [content, setContent] = useState(null);
const unlock = async() => {
const publicKey = await client.getPublicKey();
// Get wrapped DEK from server
const res = await fetch('/api/unlock', {
method: 'POST',
body: JSON.stringify({ tier: 'premium', publicKey })
});
const { encryptedDek } = await res.json();
// Decrypt
const decrypted = await client.decryptArticle({
...encryptedData,
encryptedDek
});
setContent(decrypted);
};
return(
<div>
{content ? (
<div>{content}</div>
) : (
<button onClick={unlock} disabled={!isReady}>
Unlock
</button>
)}
</div>
);
}Advanced: DEK Caching
For subscription-based models, cache unwrapped DEKs to avoid repeated server requests:
const dekCache = new Map(); // tier → CryptoKey
async function unlockTier(tier, client) {
// Check cache first
if(dekCache.has(tier)) {
return dekCache.get(tier);
}
// Get public key
const publicKey = await client.getPublicKey();
// Request wrapped DEK from server
const res = await fetch('/api/unlock', {
method: 'POST',
body: JSON.stringify({ tier, publicKey })
});
const { encryptedDek } = await res.json();
// Unwrap DEK
const dek = await crypto.subtle.unwrapKey(
'raw',
base64ToArrayBuffer(encryptedDek),
client.privateKey, // Access via client internals
{ name: 'RSA-OAEP' },
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
);
// Cache for session
dekCache.set(tier, dek);
return dek;
}Browser Compatibility
Capsule requires the Web Crypto API, which is available in:
- ✅ Chrome 37+
- ✅ Firefox 34+
- ✅ Safari 11+
- ✅ Edge 79+
Note: Web Crypto API is only available in secure contexts (HTTPS or localhost).