* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 deletions
+263
View File
@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sonr ES Autoloader Example</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
color: #333;
border-bottom: 2px solid #4CAF50;
padding-bottom: 10px;
}
.section {
background: white;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin: 5px;
}
button:hover {
background: #45a049;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
code {
font-family: 'Courier New', Courier, monospace;
}
</style>
</head>
<body>
<h1>Sonr ES Autoloader Example</h1>
<div class="section">
<h2>Library Status</h2>
<div id="loadStatus" class="status info">Loading Sonr ES library...</div>
<div id="environment" class="status info" style="display: none;"></div>
</div>
<div class="section">
<h2>WebAuthn Demo</h2>
<div id="webauthnStatus" class="status info">Checking WebAuthn availability...</div>
<div id="webauthnDemo" style="display: none;">
<h3>Register with Passkey</h3>
<input type="text" id="username" placeholder="Enter username" style="padding: 8px; margin: 5px;">
<button id="registerBtn" onclick="registerUser()">Register</button>
<h3>Login with Passkey</h3>
<button id="loginBtn" onclick="loginUser()">Login</button>
<div id="webauthnResult" style="margin-top: 20px;"></div>
</div>
</div>
<div class="section">
<h2>Available Modules</h2>
<div id="modules"></div>
</div>
<div class="section">
<h2>Console Output</h2>
<pre id="console"></pre>
</div>
<!-- Load the Sonr ES autoloader -->
<script type="module">
// Import from local build (change to CDN URL in production)
import '../dist/autoloader.js';
// Custom console logger
const consoleEl = document.getElementById('console');
const originalLog = console.log;
console.log = function(...args) {
originalLog.apply(console, args);
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg
).join(' ');
consoleEl.textContent += message + '\n';
};
// Wait for Sonr to be ready
window.addEventListener('sonr:ready', (event) => {
const Sonr = event.detail;
// Update load status
document.getElementById('loadStatus').className = 'status success';
document.getElementById('loadStatus').textContent = 'Sonr ES library loaded successfully!';
// Show environment info
const env = Sonr.getEnvironment();
document.getElementById('environment').style.display = 'block';
document.getElementById('environment').innerHTML = `
<strong>Environment:</strong> ${env.type}<br>
<strong>WebAuthn:</strong> ${env.webauthn ? 'Supported' : 'Not supported'}<br>
<strong>Service Worker:</strong> ${env.serviceWorker ? 'Supported' : 'Not supported'}
`;
// Check WebAuthn
checkWebAuthn();
// List available modules
listModules();
});
async function checkWebAuthn() {
if (window.Sonr && window.Sonr.webauthn) {
const isAvailable = await window.Sonr.webauthn.isAvailable();
const isConditional = await window.Sonr.webauthn.isConditionalAvailable();
const statusEl = document.getElementById('webauthnStatus');
if (isAvailable) {
statusEl.className = 'status success';
statusEl.innerHTML = `
WebAuthn is available!<br>
Conditional mediation (autofill): ${isConditional ? 'Supported' : 'Not supported'}
`;
document.getElementById('webauthnDemo').style.display = 'block';
} else {
statusEl.className = 'status error';
statusEl.textContent = 'WebAuthn is not available in this browser';
}
}
}
function listModules() {
const modulesEl = document.getElementById('modules');
if (window.Sonr) {
const modules = [
'auth - Authentication utilities',
'client - Blockchain client',
'codec - Encoding/decoding utilities',
'wallet - Wallet management',
'registry - Chain registry',
'plugins - WASM plugins (motor, vault)',
'webauthn - WebAuthn shortcuts'
];
modulesEl.innerHTML = '<ul>' +
modules.map(m => `<li><code>window.Sonr.${m.split(' - ')[0]}</code> - ${m.split(' - ')[1]}</li>`).join('') +
'</ul>';
}
}
// Make functions available globally
window.registerUser = async function() {
const username = document.getElementById('username').value;
if (!username) {
alert('Please enter a username');
return;
}
const resultEl = document.getElementById('webauthnResult');
resultEl.className = 'status info';
resultEl.textContent = 'Starting registration...';
try {
const result = await window.Sonr.webauthn.register({
username,
displayName: username,
// Add your RP info here
rpId: window.location.hostname,
rpName: 'Sonr ES Demo'
});
console.log('Registration successful:', result);
resultEl.className = 'status success';
resultEl.innerHTML = `
<strong>Registration successful!</strong><br>
Credential ID: ${result.credentialId}<br>
User verified: ${result.userVerified}
`;
} catch (error) {
console.error('Registration failed:', error);
resultEl.className = 'status error';
resultEl.textContent = `Registration failed: ${error.message}`;
}
};
window.loginUser = async function() {
const resultEl = document.getElementById('webauthnResult');
resultEl.className = 'status info';
resultEl.textContent = 'Starting login...';
try {
const result = await window.Sonr.webauthn.login({
rpId: window.location.hostname
});
console.log('Login successful:', result);
resultEl.className = 'status success';
resultEl.innerHTML = `
<strong>Login successful!</strong><br>
Credential ID: ${result.credentialId}<br>
User verified: ${result.userVerified}
`;
} catch (error) {
console.error('Login failed:', error);
resultEl.className = 'status error';
resultEl.textContent = `Login failed: ${error.message}`;
}
};
</script>
<!-- Alternative: Load from CDN -->
<!-- <script type="module" src="https://unpkg.com/@sonr.io/es@latest/dist/autoloader.js"></script> -->
<!-- Alternative: Load with regular script tag (non-module) -->
<!--
<script>
// The library will be available as window.Sonr after loading
window.addEventListener('sonr:ready', function(event) {
console.log('Sonr is ready!', window.Sonr);
});
</script>
<script src="https://unpkg.com/@sonr.io/es@latest/dist/autoloader.js"></script>
-->
</body>
</html>
+489
View File
@@ -0,0 +1,489 @@
/**
* Usage examples for IPFS/Helia integration with MPC enclave data
*
* This file demonstrates how to use the IPFS integration for
* storing and retrieving MPC enclave data in the Sonr ecosystem.
*/
import {
// IPFS client
IPFSClient,
createIPFSClient,
type IPFSClientConfig,
// Enclave manager
EnclaveIPFSManager,
createEnclaveIPFSManager,
type EnclaveDataWithCID,
// Enhanced vault client
VaultClientWithIPFS,
createVaultClientWithIPFS,
// Caching
IPFSCache,
createIPFSCache,
// DWN query service
DWNIPFSQueryService,
createDWNIPFSQueryService,
} from '@sonr.io/es';
// ============================================
// Example 1: Basic IPFS Client Usage
// ============================================
async function basicIPFSExample() {
console.log('🌐 Basic IPFS Client Example');
// Create and initialize IPFS client
const ipfsClient = await createIPFSClient({
gateways: ['https://gateway.pinata.cloud', 'https://ipfs.io'],
enablePersistence: true,
});
try {
// Store data
const data = new TextEncoder().encode('Hello from Sonr!');
const { cid, size, timestamp } = await ipfsClient.addEnclaveData(data);
console.log(`✅ Stored data with CID: ${cid} (${size} bytes)`);
// Retrieve data
const retrieved = await ipfsClient.getEnclaveData(cid);
const text = new TextDecoder().decode(retrieved);
console.log(`✅ Retrieved: "${text}"`);
// Get node status
const status = await ipfsClient.getNodeStatus();
console.log(`📊 Node Status:`, status);
// Pin important data
await ipfsClient.pin(cid);
console.log(`📌 Pinned CID: ${cid}`);
// List all pins
const pins = await ipfsClient.listPins();
console.log(`📋 Total pinned items: ${pins.length}`);
} finally {
await ipfsClient.cleanup();
}
}
// ============================================
// Example 2: MPC Enclave Storage
// ============================================
async function mpcEnclaveExample() {
console.log('🔐 MPC Enclave Storage Example');
const ipfsClient = await createIPFSClient();
const enclaveManager = await createEnclaveIPFSManager(ipfsClient, {
encryptionRequired: true,
pinningEnabled: true,
maxRetries: 3,
});
try {
// Create MPC enclave data
const enclaveData: EnclaveDataWithCID = {
publicKey: 'ed25519:8FYH...publickey...ZKpQ',
privateKeyShares: [
'share1_encrypted_base64...',
'share2_encrypted_base64...',
'share3_encrypted_base64...',
],
threshold: 2, // Need 2 out of 3 shares to reconstruct
parties: 3,
encryptionMetadata: {
algorithm: 'AES-256-GCM',
keyVersion: 1,
consensusHeight: 12345,
nonce: crypto.randomUUID(),
},
};
// Encrypt the payload (in production, use consensus keys)
const payload = JSON.stringify({
...enclaveData,
timestamp: Date.now(),
chainId: 'sonr-mainnet-1',
});
const encryptedPayload = new TextEncoder().encode(payload);
// Store enclave data
const result = await enclaveManager.storeEnclaveData(
enclaveData,
encryptedPayload
);
console.log(`✅ Stored enclave with CID: ${result.cid}`);
console.log(` - Size: ${result.size} bytes`);
console.log(` - Pinned: ${result.isPinned}`);
// Retrieve and verify
const retrieved = await enclaveManager.retrieveEnclaveData(result.cid);
const isValid = await enclaveManager.verifyEnclaveDataIntegrity(
result.cid,
encryptedPayload
);
console.log(`✅ Retrieved enclave data (integrity: ${isValid})`);
// Check status
const status = await enclaveManager.getEnclaveStatus(result.cid);
console.log(`📊 Enclave Status:`, status);
} finally {
await ipfsClient.cleanup();
}
}
// ============================================
// Example 3: Vault Client with IPFS
// ============================================
async function vaultWithIPFSExample() {
console.log('🔒 Vault Client with IPFS Example');
const vaultClient = createVaultClientWithIPFS({
chainId: 'sonr-testnet-1',
enableIPFSPersistence: true,
ipfsGateways: ['https://gateway.pinata.cloud'],
enclave: {
publicKey: 'test-public-key',
privateKeyShares: ['share1', 'share2', 'share3'],
threshold: 2,
parties: 3,
},
});
try {
// Initialize vault with IPFS
await vaultClient.initializeWithIPFS(
'/path/to/vault.wasm',
'sonr1abc...xyz'
);
// Store vault enclave
const cid = await vaultClient.storeVaultEnclave([
'encrypted_share1',
'encrypted_share2',
'encrypted_share3',
]);
console.log(`✅ Stored vault enclave: ${cid}`);
// Retrieve vault enclave
const enclave = await vaultClient.retrieveVaultEnclave(cid);
console.log(`✅ Retrieved enclave for ${enclave.parties} parties`);
// List pinned enclaves
const pinnedEnclaves = await vaultClient.listPinnedEnclaves();
console.log(`📌 Pinned enclaves: ${pinnedEnclaves.length}`);
// Sync with IPFS network
await vaultClient.syncWithIPFS();
console.log('✅ Synced with IPFS network');
// Get IPFS status
const ipfsStatus = await vaultClient.getIPFSStatus();
console.log(`📊 IPFS Status:`, ipfsStatus);
} catch (error) {
// Handle WASM errors gracefully
if (error.message.includes('WASM')) {
console.log('⚠️ WASM not available, using IPFS features only');
} else {
throw error;
}
} finally {
await vaultClient.cleanup();
}
}
// ============================================
// Example 4: Caching Layer
// ============================================
async function cachingExample() {
console.log('⚡ IPFS Caching Example');
const ipfsClient = await createIPFSClient();
const cache = createIPFSCache({
maxSize: 50,
ttl: 60000, // 1 minute TTL
enablePersistence: true,
});
try {
// Store some data in IPFS
const data1 = new TextEncoder().encode('Data item 1');
const data2 = new TextEncoder().encode('Data item 2');
const data3 = new TextEncoder().encode('Data item 3');
const { cid: cid1 } = await ipfsClient.addEnclaveData(data1);
const { cid: cid2 } = await ipfsClient.addEnclaveData(data2);
const { cid: cid3 } = await ipfsClient.addEnclaveData(data3);
// Preload into cache
console.log('📥 Preloading data into cache...');
await cache.preload(
[cid1, cid2, cid3],
async (cid) => await ipfsClient.getEnclaveData(cid)
);
// Measure cache performance
console.time('Cache retrieval');
const cached1 = await cache.get(cid1);
const cached2 = await cache.get(cid2);
const cached3 = await cache.get(cid3);
console.timeEnd('Cache retrieval');
// Get cache statistics
const stats = cache.getStats();
console.log('📊 Cache Statistics:');
console.log(` - Size: ${stats.size} items`);
console.log(` - Total bytes: ${stats.totalBytes}`);
console.log(` - Hit rate: ${stats.hitRate.toFixed(2)}%`);
console.log(` - Avg access time: ${stats.avgAccessTime.toFixed(2)}ms`);
// Clean up expired entries
const removed = await cache.cleanup();
console.log(`🧹 Cleaned up ${removed} expired entries`);
} finally {
await cache.destroy();
await ipfsClient.cleanup();
}
}
// ============================================
// Example 5: DWN IPFS Query Service
// ============================================
async function dwnQueryServiceExample() {
console.log('🔍 DWN IPFS Query Service Example');
const queryService = createDWNIPFSQueryService({
rpcEndpoint: 'http://localhost:1317',
defaultStaleTime: 30000,
});
try {
// Query IPFS status from backend
const ipfsStatus = await queryService.queryIPFSStatus();
console.log('📊 Backend IPFS Status:');
console.log(` - Enabled: ${ipfsStatus.enabled}`);
console.log(` - Peer ID: ${ipfsStatus.peerId}`);
console.log(` - Connected peers: ${ipfsStatus.connectedPeers}`);
// Query specific CID content
const cidResponse = await queryService.queryCIDContent(
'QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG',
false // Don't decrypt
);
if (cidResponse.error) {
console.log(`⚠️ CID not found: ${cidResponse.error}`);
} else {
console.log(`✅ Found CID content (${cidResponse.size} bytes)`);
}
// Query enclave data for a vault
const enclaveResponse = await queryService.queryEnclaveData(
'did:sonr:vault123',
false // Don't include private shares
);
if (enclaveResponse.enclaveCid) {
console.log(`✅ Found enclave for vault: ${enclaveResponse.enclaveCid}`);
}
// Store new enclave data via backend
const newEnclaveData = new TextEncoder().encode('New enclave data');
const storeResult = await queryService.storeEnclaveData(
'did:sonr:newvault',
newEnclaveData
);
console.log(`✅ Stored via backend: ${storeResult.cid}`);
} finally {
await queryService.cleanup();
}
}
// ============================================
// Example 6: Error Handling and Recovery
// ============================================
async function errorHandlingExample() {
console.log('🛡️ Error Handling Example');
const ipfsClient = await createIPFSClient({
gateways: [
'https://gateway.pinata.cloud',
'https://ipfs.io',
'http://localhost:8080', // Local fallback
],
});
const enclaveManager = new EnclaveIPFSManager(ipfsClient, {
encryptionRequired: true,
pinningEnabled: true,
maxRetries: 3,
operationTimeout: 10000,
});
try {
// Handle invalid CID
try {
await ipfsClient.getEnclaveData('invalid-cid-format');
} catch (error) {
console.log('✅ Caught invalid CID error:', error.message);
}
// Handle missing encryption metadata
try {
const invalidEnclave: EnclaveDataWithCID = {
publicKey: 'test',
privateKeyShares: ['share1'],
threshold: 1,
parties: 1,
// Missing encryptionMetadata when encryptionRequired is true
};
await enclaveManager.storeEnclaveData(
invalidEnclave,
new Uint8Array()
);
} catch (error) {
console.log('✅ Caught missing encryption metadata:', error.message);
}
// Handle network timeout with retry
console.log('🔄 Testing retry logic...');
const enclaveData: EnclaveDataWithCID = {
publicKey: 'retry-test',
privateKeyShares: ['share1'],
threshold: 1,
parties: 1,
encryptionMetadata: {
algorithm: 'AES-256-GCM',
keyVersion: 1,
consensusHeight: 1,
nonce: 'test',
},
};
const result = await enclaveManager.storeEnclaveData(
enclaveData,
new Uint8Array([1, 2, 3])
);
console.log('✅ Succeeded with retry logic');
} finally {
await ipfsClient.cleanup();
}
}
// ============================================
// Example 7: Performance Best Practices
// ============================================
async function performanceExample() {
console.log('🚀 Performance Best Practices Example');
// 1. Use connection pooling
const ipfsClient = await createIPFSClient({
gateways: ['https://gateway.pinata.cloud'],
libp2pConfig: {
connectionManager: {
maxConnections: 100,
minConnections: 10,
},
},
});
// 2. Use caching aggressively
const cache = createIPFSCache({
maxSize: 200,
ttl: 300000, // 5 minutes
enablePersistence: true,
});
// 3. Batch operations
const enclaveManager = new EnclaveIPFSManager(ipfsClient, {
encryptionRequired: false,
pinningEnabled: true,
redundancy: 3,
maxRetries: 2,
});
try {
// Batch store multiple items
console.log('📦 Batch storing enclaves...');
const enclaves = Array.from({ length: 10 }, (_, i) => ({
data: {
publicKey: `key-${i}`,
privateKeyShares: [`share-${i}`],
threshold: 1,
parties: 1,
} as EnclaveDataWithCID,
payload: new Uint8Array([i, i, i]),
}));
console.time('Batch store');
const results = await enclaveManager.batchStoreEnclaves(enclaves);
console.timeEnd('Batch store');
console.log(`✅ Stored ${results.length} enclaves in batch`);
// Preload frequently accessed data
const cids = results.map((r) => r.cid);
console.time('Preload cache');
await cache.preload(cids.slice(0, 5), async (cid) =>
enclaveManager.retrieveEnclaveData(cid)
);
console.timeEnd('Preload cache');
// Use cache for fast retrieval
console.time('Cached retrieval');
for (const cid of cids.slice(0, 5)) {
await cache.get(cid);
}
console.timeEnd('Cached retrieval');
const stats = cache.getStats();
console.log(`📊 Cache hit rate: ${stats.hitRate.toFixed(2)}%`);
} finally {
await cache.destroy();
await ipfsClient.cleanup();
}
}
// ============================================
// Main: Run all examples
// ============================================
async function main() {
console.log('🌟 Sonr IPFS/Helia Integration Examples\n');
try {
await basicIPFSExample();
console.log('\n---\n');
await mpcEnclaveExample();
console.log('\n---\n');
await vaultWithIPFSExample();
console.log('\n---\n');
await cachingExample();
console.log('\n---\n');
await dwnQueryServiceExample();
console.log('\n---\n');
await errorHandlingExample();
console.log('\n---\n');
await performanceExample();
console.log('\n✅ All examples completed successfully!');
} catch (error) {
console.error('❌ Example failed:', error);
process.exit(1);
}
}
// Run if executed directly
if (require.main === module) {
main();
}
// Export examples for use in other modules
export {
basicIPFSExample,
mpcEnclaveExample,
vaultWithIPFSExample,
cachingExample,
dwnQueryServiceExample,
errorHandlingExample,
performanceExample,
};
@@ -0,0 +1,442 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Motor WASM Service Worker Test</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
}
h2 {
color: #555;
margin-top: 30px;
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-family: 'Courier New', monospace;
}
.success {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.info {
background: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.warning {
background: #fff3cd;
border: 1px solid #ffeeba;
color: #856404;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
font-size: 14px;
}
button:hover {
background: #0056b3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.test-section {
margin: 20px 0;
padding: 15px;
border-left: 4px solid #007bff;
background: #f8f9fa;
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.log-entry {
font-family: 'Courier New', monospace;
font-size: 12px;
padding: 2px 0;
}
#logs {
max-height: 300px;
overflow-y: auto;
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Motor WASM Service Worker Test</h1>
<div class="test-section">
<h2>Service Worker Status</h2>
<div id="worker-status" class="status info">Checking service worker support...</div>
<button id="register-worker">Register Service Worker</button>
<button id="unregister-worker" disabled>Unregister Service Worker</button>
</div>
<div class="test-section">
<h2>Plugin Initialization</h2>
<div id="plugin-status" class="status info">Plugin not initialized</div>
<button id="init-plugin" disabled>Initialize Motor Plugin</button>
</div>
<div class="test-section">
<h2>API Tests</h2>
<div id="test-results"></div>
<h3>Identity Operations</h3>
<button id="test-issuer-did" disabled>Test Get Issuer DID</button>
<h3>UCAN Token Operations</h3>
<button id="test-origin-token" disabled>Test Create Origin Token</button>
<button id="test-attenuated-token" disabled>Test Create Attenuated Token</button>
<h3>Cryptographic Operations</h3>
<button id="test-sign-verify" disabled>Test Sign & Verify</button>
<h3>DWN Operations</h3>
<button id="test-dwn-crud" disabled>Test DWN CRUD</button>
</div>
<div class="test-section">
<h2>Console Logs</h2>
<div id="logs"></div>
<button id="clear-logs">Clear Logs</button>
</div>
</div>
<script type="module">
// Logging utility
const logContainer = document.getElementById('logs');
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `<span style="color: ${
type === 'error' ? '#dc3545' :
type === 'success' ? '#28a745' :
type === 'warning' ? '#ffc107' : '#007bff'
}">[${timestamp}]</span> ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
console.log(`[${type}]`, message);
}
// Clear logs button
document.getElementById('clear-logs').addEventListener('click', () => {
logContainer.innerHTML = '';
log('Logs cleared', 'info');
});
// Check service worker support
const workerStatusEl = document.getElementById('worker-status');
const registerBtn = document.getElementById('register-worker');
const unregisterBtn = document.getElementById('unregister-worker');
const initPluginBtn = document.getElementById('init-plugin');
const pluginStatusEl = document.getElementById('plugin-status');
let motorPlugin = null;
let serviceWorkerRegistration = null;
// Check if service workers are supported
if ('serviceWorker' in navigator) {
workerStatusEl.className = 'status success';
workerStatusEl.textContent = '✅ Service Workers are supported in this browser';
log('Service Workers supported', 'success');
// Check if already registered
navigator.serviceWorker.getRegistrations().then(registrations => {
if (registrations.length > 0) {
const motorWorker = registrations.find(r => r.active?.scriptURL.includes('motor'));
if (motorWorker) {
serviceWorkerRegistration = motorWorker;
workerStatusEl.textContent = '✅ Motor Service Worker already registered';
unregisterBtn.disabled = false;
initPluginBtn.disabled = false;
log('Found existing Motor service worker', 'info');
}
}
});
} else {
workerStatusEl.className = 'status error';
workerStatusEl.textContent = '❌ Service Workers are not supported in this browser';
registerBtn.disabled = true;
log('Service Workers not supported', 'error');
}
// Register service worker
registerBtn.addEventListener('click', async () => {
try {
log('Registering Motor service worker...', 'info');
registerBtn.disabled = true;
// Check if Motor service worker file exists
const workerUrl = '/dist/wasm/motr-sw.js';
// Try to fetch the worker file first
try {
const response = await fetch(workerUrl);
if (!response.ok) {
throw new Error(`Worker file not found at ${workerUrl}. Status: ${response.status}`);
}
log(`Worker file found at ${workerUrl}`, 'success');
} catch (fetchError) {
throw new Error(`Cannot access worker file: ${fetchError.message}. Make sure you're serving the dist/wasm directory.`);
}
// Register the service worker
serviceWorkerRegistration = await navigator.serviceWorker.register(workerUrl, {
scope: '/',
updateViaCache: 'none'
});
log('Service worker registered successfully', 'success');
// Wait for activation
await navigator.serviceWorker.ready;
workerStatusEl.className = 'status success';
workerStatusEl.textContent = '✅ Motor Service Worker registered and active';
unregisterBtn.disabled = false;
initPluginBtn.disabled = false;
log('Service worker is ready', 'success');
} catch (error) {
workerStatusEl.className = 'status error';
workerStatusEl.textContent = `❌ Registration failed: ${error.message}`;
log(`Registration failed: ${error.message}`, 'error');
registerBtn.disabled = false;
}
});
// Unregister service worker
unregisterBtn.addEventListener('click', async () => {
try {
log('Unregistering service worker...', 'info');
unregisterBtn.disabled = true;
if (serviceWorkerRegistration) {
await serviceWorkerRegistration.unregister();
serviceWorkerRegistration = null;
}
workerStatusEl.className = 'status info';
workerStatusEl.textContent = 'Service Worker unregistered';
registerBtn.disabled = false;
initPluginBtn.disabled = true;
motorPlugin = null;
pluginStatusEl.className = 'status info';
pluginStatusEl.textContent = 'Plugin not initialized';
log('Service worker unregistered', 'success');
// Disable all test buttons
document.querySelectorAll('button[id^="test-"]').forEach(btn => {
btn.disabled = true;
});
} catch (error) {
log(`Unregistration failed: ${error.message}`, 'error');
unregisterBtn.disabled = false;
}
});
// Initialize Motor plugin
initPluginBtn.addEventListener('click', async () => {
try {
log('Initializing Motor plugin...', 'info');
initPluginBtn.disabled = true;
// Dynamically import the Motor plugin
// In a real application, this would be imported from @sonr.io/es/client/motor
const { createMotorPluginForBrowser } = await import('/dist/client/motor/index.js');
motorPlugin = await createMotorPluginForBrowser('/api/motor', {
auto_register_worker: false, // We already registered it
debug: true
});
pluginStatusEl.className = 'status success';
pluginStatusEl.textContent = '✅ Motor Plugin initialized successfully';
log('Motor plugin initialized', 'success');
// Enable test buttons
document.querySelectorAll('button[id^="test-"]').forEach(btn => {
btn.disabled = false;
});
} catch (error) {
pluginStatusEl.className = 'status error';
pluginStatusEl.textContent = `❌ Initialization failed: ${error.message}`;
log(`Plugin initialization failed: ${error.message}`, 'error');
initPluginBtn.disabled = false;
}
});
// Test functions
function addTestResult(testName, success, message) {
const resultsContainer = document.getElementById('test-results');
const result = document.createElement('div');
result.className = `status ${success ? 'success' : 'error'}`;
result.textContent = `${success ? '✅' : '❌'} ${testName}: ${message}`;
resultsContainer.appendChild(result);
log(`Test "${testName}": ${message}`, success ? 'success' : 'error');
}
// Test Get Issuer DID
document.getElementById('test-issuer-did').addEventListener('click', async () => {
try {
log('Testing Get Issuer DID...', 'info');
const result = await motorPlugin.getIssuerDID();
addTestResult('Get Issuer DID', true, `DID: ${result.issuer_did}, Address: ${result.address}`);
} catch (error) {
addTestResult('Get Issuer DID', false, error.message);
}
});
// Test Create Origin Token
document.getElementById('test-origin-token').addEventListener('click', async () => {
try {
log('Testing Create Origin Token...', 'info');
const result = await motorPlugin.newOriginToken({
audience_did: 'did:sonr:test_audience',
attenuations: [{ can: ['sign', 'verify'], with: 'vault://test' }],
facts: ['test_fact'],
expires_at: Date.now() + 3600000
});
addTestResult('Create Origin Token', true, `Token created, Issuer: ${result.issuer}`);
} catch (error) {
addTestResult('Create Origin Token', false, error.message);
}
});
// Test Create Attenuated Token
document.getElementById('test-attenuated-token').addEventListener('click', async () => {
try {
log('Testing Create Attenuated Token...', 'info');
// First create an origin token
const originToken = await motorPlugin.newOriginToken({
audience_did: 'did:sonr:test',
attenuations: [{ can: ['sign', 'verify'], with: 'vault://test' }]
});
// Then create attenuated token
const result = await motorPlugin.newAttenuatedToken({
parent_token: originToken.token,
audience_did: 'did:sonr:delegated',
attenuations: [{ can: ['sign'], with: 'vault://restricted' }]
});
addTestResult('Create Attenuated Token', true, `Delegated token created`);
} catch (error) {
addTestResult('Create Attenuated Token', false, error.message);
}
});
// Test Sign & Verify
document.getElementById('test-sign-verify').addEventListener('click', async () => {
try {
log('Testing Sign & Verify...', 'info');
const message = new TextEncoder().encode('Test message for signing');
// Sign the message
const signResult = await motorPlugin.signData({ data: message });
log(`Signed message, signature length: ${signResult.signature.length} bytes`, 'info');
// Verify the signature
const verifyResult = await motorPlugin.verifyData({
data: message,
signature: signResult.signature
});
addTestResult('Sign & Verify', verifyResult.valid,
verifyResult.valid ? 'Signature verified successfully' : 'Signature verification failed');
} catch (error) {
addTestResult('Sign & Verify', false, error.message);
}
});
// Test DWN CRUD
document.getElementById('test-dwn-crud').addEventListener('click', async () => {
try {
log('Testing DWN CRUD operations...', 'info');
// Create a record
const createResult = await motorPlugin.createRecord({
schema: 'https://schema.org/Person',
data: new TextEncoder().encode(JSON.stringify({
name: 'Test User',
email: 'test@example.com'
})),
is_encrypted: false
});
log(`Created record: ${createResult.record_id}`, 'success');
// Read the record
const readResult = await motorPlugin.readRecord({
record_id: createResult.record_id
});
log(`Read record: ${readResult.record_id}`, 'success');
// Update the record
const updateResult = await motorPlugin.updateRecord({
record_id: createResult.record_id,
data: new TextEncoder().encode(JSON.stringify({
name: 'Updated User',
email: 'updated@example.com'
}))
});
log(`Updated record: ${updateResult.record_id}`, 'success');
// Delete the record
const deleteResult = await motorPlugin.deleteRecord({
record_id: createResult.record_id
});
log(`Deleted record: ${deleteResult.success}`, 'success');
addTestResult('DWN CRUD', true, 'All CRUD operations completed successfully');
} catch (error) {
addTestResult('DWN CRUD', false, error.message);
}
});
// Initial log
log('Motor WASM Service Worker Test Page loaded', 'info');
log('Make sure to serve this page over HTTP (not file://) for service workers to work', 'warning');
log('Example: python3 -m http.server 8080', 'info');
</script>
</body>
</html>
+310
View File
@@ -0,0 +1,310 @@
/**
* Example usage of the Motor WASM service worker integration.
* This demonstrates how to use the Motor plugin for both DWN and Wallet operations.
*/
import {
createMotorPlugin,
createMotorPluginForNode,
createMotorPluginForBrowser,
isMotorSupported,
getMotorEnvironment,
type MotorPlugin,
type NewOriginTokenRequest,
type CreateRecordRequest,
} from '@sonr.io/es/client/motor';
// ╭─────────────────────────────────────────────────────────╮
// │ Environment Detection │
// ╰─────────────────────────────────────────────────────────╯
async function detectEnvironment(): Promise<void> {
console.log('🔍 Detecting environment capabilities...');
const env = getMotorEnvironment();
console.log('Environment info:', {
browser: env.is_browser,
node: env.is_node,
serviceWorker: env.supports_service_worker,
wasm: env.supports_wasm,
});
const supported = isMotorSupported();
console.log('Motor supported:', supported);
}
// ╭─────────────────────────────────────────────────────────╮
// │ Auto Plugin Creation │
// ╰─────────────────────────────────────────────────────────╯
async function createPlugin(): Promise<MotorPlugin> {
console.log('🚀 Creating Motor plugin...');
// Auto-detects environment and creates appropriate plugin
const plugin = await createMotorPlugin({
debug: true,
timeout: 30000,
max_retries: 3,
});
console.log('✅ Plugin created successfully');
return plugin;
}
// ╭─────────────────────────────────────────────────────────╮
// │ Environment-Specific Creation │
// ╰─────────────────────────────────────────────────────────╯
async function createBrowserPlugin(): Promise<MotorPlugin> {
console.log('🌐 Creating browser-specific Motor plugin...');
const plugin = await createMotorPluginForBrowser('/motor-worker', {
auto_register_worker: true,
prefer_service_worker: true,
debug: true,
});
console.log('✅ Browser plugin created with service worker support');
return plugin;
}
async function createNodePlugin(): Promise<MotorPlugin> {
console.log('🖥️ Creating Node.js-specific Motor plugin...');
const plugin = await createMotorPluginForNode('http://localhost:8080', {
timeout: 15000,
max_retries: 2,
debug: true,
});
console.log('✅ Node.js plugin created with HTTP fallback');
return plugin;
}
// ╭─────────────────────────────────────────────────────────╮
// │ Wallet Operations │
// ╰─────────────────────────────────────────────────────────╯
async function demonstrateWalletOperations(plugin: MotorPlugin): Promise<void> {
console.log('💼 Demonstrating wallet operations...');
try {
// Get issuer DID
console.log('📋 Getting issuer DID...');
const issuerResponse = await plugin.getIssuerDID();
console.log('Issuer DID:', issuerResponse.issuer_did);
console.log('Address:', issuerResponse.address);
// Create origin token
console.log('🎫 Creating UCAN origin token...');
const tokenRequest: NewOriginTokenRequest = {
audience_did: 'did:sonr:example-audience',
attenuations: [
{
can: ['sign', 'encrypt'],
with: 'vault://example-vault',
},
],
facts: ['motor-wasm-demo'],
expires_at: Date.now() + (24 * 60 * 60 * 1000), // 24 hours
};
const tokenResponse = await plugin.newOriginToken(tokenRequest);
console.log('✅ Origin token created:', tokenResponse.token.substring(0, 50) + '...');
// Create attenuated token
console.log('🔗 Creating attenuated token...');
const attenuatedResponse = await plugin.newAttenuatedToken({
parent_token: tokenResponse.token,
audience_did: 'did:sonr:delegated-audience',
attenuations: [
{
can: ['sign'],
with: 'vault://limited-access',
},
],
expires_at: Date.now() + (2 * 60 * 60 * 1000), // 2 hours
});
console.log('✅ Attenuated token created:', attenuatedResponse.token.substring(0, 50) + '...');
// Sign data
console.log('✍️ Signing data...');
const dataToSign = new TextEncoder().encode('Hello, Motor WASM!');
const signResponse = await plugin.signData({ data: dataToSign });
console.log('✅ Data signed, signature length:', signResponse.signature.length);
// Verify signature
console.log('🔍 Verifying signature...');
const verifyResponse = await plugin.verifyData({
data: dataToSign,
signature: signResponse.signature,
});
console.log('✅ Signature valid:', verifyResponse.valid);
} catch (error) {
console.error('❌ Wallet operation failed:', error);
}
}
// ╭─────────────────────────────────────────────────────────╮
// │ DWN Operations │
// ╰─────────────────────────────────────────────────────────╯
async function demonstrateDWNOperations(plugin: MotorPlugin): Promise<void> {
console.log('🌐 Demonstrating DWN operations...');
try {
// Create a record
console.log('📝 Creating DWN record...');
const recordData = new TextEncoder().encode(JSON.stringify({
message: 'Hello from Motor DWN!',
timestamp: new Date().toISOString(),
version: '1.0.0',
}));
const createRequest: CreateRecordRequest = {
target: 'did:sonr:alice',
data: recordData,
schema: 'https://schema.org/Message',
protocol: 'https://protocol.example.com/messaging',
published: true,
encrypt: true, // Encrypt the data
};
const createResponse = await plugin.createRecord?.(createRequest);
if (!createResponse) {
console.log('️ DWN operations not available in this plugin instance');
return;
}
console.log('✅ Record created:', createResponse.record_id);
console.log('📅 Created at:', new Date(createResponse.created_at * 1000).toISOString());
console.log('🔒 Encrypted:', createResponse.is_encrypted);
// Read the record
console.log('📖 Reading DWN record...');
const readResponse = await plugin.readRecord?.(createResponse.record_id, createRequest.target);
if (readResponse) {
const decodedData = new TextDecoder().decode(readResponse.data);
console.log('✅ Record data:', JSON.parse(decodedData));
console.log('🔓 Decrypted successfully:', !readResponse.is_encrypted || readResponse.data.length > 0);
}
// Update the record
console.log('✏️ Updating DWN record...');
const updatedData = new TextEncoder().encode(JSON.stringify({
message: 'Updated message from Motor DWN!',
timestamp: new Date().toISOString(),
version: '1.1.0',
updated: true,
}));
const updateResponse = await plugin.updateRecord?.({
record_id: createResponse.record_id,
target: createRequest.target,
data: updatedData,
published: true,
});
if (updateResponse) {
console.log('✅ Record updated at:', new Date(updateResponse.updated_at * 1000).toISOString());
}
// Delete the record
console.log('🗑️ Deleting DWN record...');
const deleteResponse = await plugin.deleteRecord?.(createResponse.record_id, createRequest.target);
if (deleteResponse) {
console.log('✅ Record deleted:', deleteResponse.status);
}
} catch (error) {
console.error('❌ DWN operation failed:', error);
}
}
// ╭─────────────────────────────────────────────────────────╮
// │ Health Monitoring │
// ╰─────────────────────────────────────────────────────────╯
async function checkServiceHealth(plugin: MotorPlugin): Promise<void> {
console.log('🏥 Checking service health...');
try {
// Test connection
const connected = await plugin.testConnection();
console.log('🔗 Connected:', connected);
if (connected) {
// Get health status
const health = await plugin.getHealth();
console.log('💓 Health status:', health.status);
console.log('🏷️ Service:', health.service);
console.log('📦 Version:', health.version);
// Get service info
const info = await plugin.getServiceInfo();
console.log('📋 Service info:', {
description: info.description,
endpoints: Object.keys(info.endpoints),
});
}
} catch (error) {
console.error('❌ Health check failed:', error);
}
}
// ╭─────────────────────────────────────────────────────────╮
// │ Main Demo │
// ╰─────────────────────────────────────────────────────────╯
async function runDemo(): Promise<void> {
console.log('🎬 Starting Motor WASM Service Worker Demo');
console.log('==========================================');
try {
// Detect environment
await detectEnvironment();
console.log();
// Create plugin (auto-detection)
const plugin = await createPlugin();
console.log();
// Check service health
await checkServiceHealth(plugin);
console.log();
// Demonstrate wallet operations
await demonstrateWalletOperations(plugin);
console.log();
// Demonstrate DWN operations
await demonstrateDWNOperations(plugin);
console.log();
// Cleanup
console.log('🧹 Cleaning up...');
await plugin.cleanup?.();
console.log('✅ Demo completed successfully!');
} catch (error) {
console.error('❌ Demo failed:', error);
}
}
// Export for use in other modules
export {
detectEnvironment,
createPlugin,
createBrowserPlugin,
createNodePlugin,
demonstrateWalletOperations,
demonstrateDWNOperations,
checkServiceHealth,
runDemo,
};
// Run demo if this file is executed directly
if (typeof require !== 'undefined' && require.main === module) {
runDemo().catch(console.error);
}
+136
View File
@@ -0,0 +1,136 @@
/**
* Example demonstrating the usage of plugins from @sonr.io/es
*/
// Import plugins module
import { plugins } from '@sonr.io/es';
// Or import specific plugins
import { createVaultClient, createMotorPlugin } from '@sonr.io/es/plugins';
// Or import plugins directly
import { VaultClient } from '@sonr.io/es/plugins/vault';
import { MotorPluginImpl } from '@sonr.io/es/plugins/motor';
async function demonstrateVaultPlugin() {
console.log('=== Vault Plugin Demo ===');
// Create a vault client
const vault = createVaultClient({
chainId: 'sonr-testnet-1',
// enclave configuration would go here
});
// Initialize the vault
await vault.initialize();
// Get issuer DID
const issuerInfo = await vault.getIssuerDID();
console.log('Issuer DID:', issuerInfo.issuer_did);
console.log('Address:', issuerInfo.address);
// Create a UCAN token
const tokenResponse = await vault.newOriginToken({
audience_did: 'did:sonr:example123',
attenuations: [
{ can: ['sign', 'verify'], with: 'vault://keys/*' }
],
expires_at: Date.now() + 3600000, // 1 hour from now
});
console.log('UCAN Token created:', tokenResponse.token.substring(0, 50) + '...');
// Sign some data
const dataToSign = new TextEncoder().encode('Hello, Sonr!');
const signature = await vault.signData({ data: dataToSign });
console.log('Signature created');
// Verify the signature
const verification = await vault.verifyData({
data: dataToSign,
signature: signature.signature,
});
console.log('Signature valid:', verification.valid);
// Clean up
await vault.cleanup();
}
async function demonstrateMotorPlugin() {
console.log('\n=== Motor Plugin Demo ===');
// Create a motor plugin (auto-detects environment)
const motor = await createMotorPlugin({
debug: true,
timeout: 30000,
});
// Check if motor is ready
const isReady = await motor.isReady();
console.log('Motor ready:', isReady);
// Get service info
const serviceInfo = await motor.getServiceInfo();
console.log('Service version:', serviceInfo.version);
console.log('Service status:', serviceInfo.status);
// Create a DWN record
const record = await motor.createRecord({
data: { message: 'Hello from Motor!' },
published: false,
schema: 'https://schema.org/Message',
dataFormat: 'application/json',
});
console.log('Record created:', record.record_id);
// Read the record back
const readResult = await motor.readRecord(record.record_id);
console.log('Record data:', readResult.data);
// Create a UCAN token using motor
const tokenResponse = await motor.newOriginToken({
audience_did: 'did:sonr:motor123',
attenuations: [
{ can: ['create', 'read', 'update', 'delete'], with: 'dwn://records/*' }
],
});
console.log('Motor UCAN Token:', tokenResponse.token.substring(0, 50) + '...');
// Clean up
await motor.cleanup();
}
async function demonstratePluginNamespaces() {
console.log('\n=== Using Plugin Namespaces ===');
// Access plugins through namespace
const vaultClient = plugins.vault.createVaultClient();
const motorPlugin = await plugins.motor.createMotorPlugin();
console.log('Vault client created via namespace');
console.log('Motor plugin created via namespace');
// Use the plugins...
// ...
// Clean up
await vaultClient.cleanup();
await motorPlugin.cleanup();
}
// Main execution
async function main() {
try {
await demonstrateVaultPlugin();
await demonstrateMotorPlugin();
await demonstratePluginNamespaces();
console.log('\n✅ All plugin demonstrations completed successfully!');
} catch (error) {
console.error('❌ Error during plugin demonstration:', error);
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
+382
View File
@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced WebAuthn Example - Sonr ES</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 100%;
padding: 40px;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 14px;
}
.preset-selector {
margin-bottom: 30px;
}
.preset-selector label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
}
.preset-selector select {
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
background: white;
cursor: pointer;
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
}
.input-group input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
}
.button-group {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.webauthn-button {
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
color: white;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.webauthn-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.webauthn-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.webauthn-button.webauthn-success {
background: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
}
.webauthn-button.webauthn-error {
background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
}
.webauthn-button.webauthn-warning {
background: linear-gradient(135deg, #f7971e 0%, #ffd200 100%);
}
.status-box {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
min-height: 60px;
}
.status-box h3 {
color: #333;
font-size: 14px;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 1px;
}
#status {
color: #666;
font-size: 14px;
line-height: 1.5;
}
.callback-log {
background: #2d3748;
color: #a0aec0;
border-radius: 8px;
padding: 15px;
font-family: 'Courier New', monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
}
.callback-log .log-entry {
margin-bottom: 5px;
padding: 5px;
border-left: 3px solid #4a5568;
padding-left: 10px;
}
.callback-log .log-entry.info {
border-left-color: #667eea;
}
.callback-log .log-entry.success {
border-left-color: #56ab2f;
}
.callback-log .log-entry.error {
border-left-color: #ff416c;
}
.callback-log .log-entry.warning {
border-left-color: #f7971e;
}
.support-info {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 20px;
}
.support-item {
text-align: center;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
}
.support-item.supported {
background: #d4edda;
color: #155724;
}
.support-item.unsupported {
background: #f8d7da;
color: #721c24;
}
</style>
</head>
<body>
<div class="container">
<h1>🔐 Enhanced WebAuthn Demo</h1>
<p class="subtitle">Sonr ES Package - Advanced Passkey Features</p>
<div class="support-info" id="supportInfo">
<div class="support-item" id="webauthnSupport">WebAuthn: Checking...</div>
<div class="support-item" id="platformSupport">Platform: Checking...</div>
<div class="support-item" id="autofillSupport">Autofill: Checking...</div>
</div>
<div class="preset-selector">
<label for="presetSelect">Configuration Preset:</label>
<select id="presetSelect">
<option value="BROAD_COMPATIBILITY">Broad Compatibility (Default)</option>
<option value="PLATFORM_ONLY">Platform Only (Biometrics)</option>
<option value="SECURITY_KEY">Security Key Focus</option>
<option value="MOBILE_FRIENDLY">Mobile Friendly (QR)</option>
<option value="HIGH_SECURITY">High Security (Enterprise)</option>
</select>
</div>
<div class="input-group">
<label for="username">Username:</label>
<input type="text" id="username" placeholder="Enter your username" value="demo@example.com">
</div>
<div class="button-group">
<button id="registerBtn" class="webauthn-button">Register with Passkey</button>
<button id="loginBtn" class="webauthn-button">Login with Passkey</button>
</div>
<div class="status-box">
<h3>Status</h3>
<div id="status">Ready to start...</div>
</div>
<div class="callback-log" id="callbackLog">
<div class="log-entry info">Console initialized. Callbacks will appear here...</div>
</div>
</div>
<!-- Load the Sonr ES autoloader -->
<script type="module">
import '../dist/autoloader.js';
// Wait for Sonr to be ready
window.addEventListener('sonr:ready', async (event) => {
const Sonr = event.detail;
console.log('Sonr ES loaded:', Sonr);
// Check support
const support = await Sonr.webauthn.checkSupport();
// Update support indicators
document.getElementById('webauthnSupport').textContent = `WebAuthn: ${support.supported ? '✓' : '✗'}`;
document.getElementById('webauthnSupport').className = `support-item ${support.supported ? 'supported' : 'unsupported'}`;
document.getElementById('platformSupport').textContent = `Platform: ${support.platformAuthenticator ? '✓' : '✗'}`;
document.getElementById('platformSupport').className = `support-item ${support.platformAuthenticator ? 'supported' : 'unsupported'}`;
document.getElementById('autofillSupport').textContent = `Autofill: ${support.available ? '✓' : '✗'}`;
document.getElementById('autofillSupport').className = `support-item ${support.available ? 'supported' : 'unsupported'}`;
// Log function
const addLog = (message, type = 'info') => {
const log = document.getElementById('callbackLog');
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
};
// Get selected preset
const getSelectedPreset = () => {
const presetName = document.getElementById('presetSelect').value;
return Sonr.webauthn.presets[presetName];
};
// Register button
document.getElementById('registerBtn').addEventListener('click', async () => {
const username = document.getElementById('username').value;
if (!username) {
alert('Please enter a username');
return;
}
const preset = getSelectedPreset();
addLog(`Using preset: ${document.getElementById('presetSelect').value}`, 'info');
const result = await Sonr.webauthn.register('http://localhost:8080', {
username,
displayName: username,
config: {
...preset,
onRegistrationStart: (options) => {
addLog('Registration started', 'info');
addLog(`Challenge: ${options.challenge.substring(0, 20)}...`, 'info');
},
onRegistrationComplete: (credential) => {
addLog('Registration completed!', 'success');
addLog(`Credential ID: ${credential.id.substring(0, 30)}...`, 'success');
},
onRegistrationError: (error) => {
addLog(`Registration error: ${error.message}`, 'error');
},
onStatusUpdate: (status, type) => {
document.getElementById('status').textContent = status;
addLog(status, type);
}
}
});
if (result.success) {
addLog('✅ Registration successful!', 'success');
if (result.details) {
addLog(`Authenticator: ${result.details.authenticatorAttachment || 'cross-platform'}`, 'info');
}
} else {
addLog(`❌ Registration failed: ${result.error}`, 'error');
}
});
// Login button
document.getElementById('loginBtn').addEventListener('click', async () => {
const username = document.getElementById('username').value;
const preset = getSelectedPreset();
addLog(`Using preset: ${document.getElementById('presetSelect').value}`, 'info');
const result = await Sonr.webauthn.login('http://localhost:8080', {
username: username || undefined,
config: {
...preset,
onAuthenticationStart: (options) => {
addLog('Authentication started', 'info');
addLog(`Challenge: ${options.challenge.substring(0, 20)}...`, 'info');
},
onAuthenticationComplete: (credential) => {
addLog('Authentication completed!', 'success');
addLog(`Credential ID: ${credential.id.substring(0, 30)}...`, 'success');
},
onAuthenticationError: (error) => {
addLog(`Authentication error: ${error.message}`, 'error');
},
onStatusUpdate: (status, type) => {
document.getElementById('status').textContent = status;
addLog(status, type);
}
}
});
if (result.success) {
addLog('✅ Login successful!', 'success');
if (result.details) {
addLog(`Authenticator: ${result.details.authenticatorAttachment || 'cross-platform'}`, 'info');
}
} else {
addLog(`❌ Login failed: ${result.error}`, 'error');
}
});
// Initial log
addLog('Sonr ES WebAuthn Enhanced loaded and ready', 'success');
});
</script>
</body>
</html>