mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 02:11:40 +00:00
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user