Enable Discovery debug logging for production troubleshooting

- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace App\Application\Controller;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Routing\Route;
use App\Framework\Http\Session\SessionInterface;
use App\Framework\Http\Status;
/**
* Controller for CSRF token management
* Provides API endpoints for token refresh and validation
*/
final readonly class CsrfController
{
public function __construct(
private SessionInterface $session
) {
}
/**
* Generate a fresh CSRF token for a form
* Used by JavaScript to refresh tokens before they expire
*/
#[Route(path: '/api/csrf/refresh', method: Method::GET)]
public function refreshToken(Request $request): JsonResponse
{
// Get form ID from query parameter, default to 'contact_form'
$formId = $request->getQuery('form_id', 'contact_form');
// Validate form_id parameter
if (! is_string($formId) || empty($formId) || ! preg_match('/^[a-zA-Z0-9_-]+$/', $formId)) {
return new JsonResponse([
'error' => 'Invalid form_id parameter',
], Status::BAD_REQUEST);
}
try {
// Generate new token via session's CSRF protection
$newToken = $this->session->csrf->generateToken($formId);
return new JsonResponse([
'success' => true,
'token' => $newToken->toString(),
'form_id' => $formId,
'expires_in' => 7200, // 2 hours in seconds
'refresh_recommended_in' => 6300, // Refresh after 105 minutes (7200 - 900)
]);
} catch (\Exception $e) {
return new JsonResponse([
'error' => 'Failed to generate token',
'message' => $e->getMessage(),
], Status::INTERNAL_SERVER_ERROR);
}
}
/**
* Get information about current CSRF token status
* Useful for debugging and monitoring
*/
#[Route(path: '/api/csrf/info', method: Method::GET)]
public function getTokenInfo(Request $request): JsonResponse
{
$formId = $request->getQuery('form_id', 'contact_form');
if (! is_string($formId) || empty($formId) || ! preg_match('/^[a-zA-Z0-9_-]+$/', $formId)) {
return new JsonResponse([
'error' => 'Invalid form_id parameter',
], Status::BAD_REQUEST);
}
try {
$activeTokenCount = $this->session->csrf->getActiveTokenCount($formId);
return new JsonResponse([
'form_id' => $formId,
'active_tokens' => $activeTokenCount,
'max_tokens_per_form' => 3,
'token_lifetime_seconds' => 7200,
'resubmit_window_seconds' => 30,
]);
} catch (\Exception $e) {
return new JsonResponse([
'error' => 'Failed to get token info',
'message' => $e->getMessage(),
], Status::INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Application\Controller;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Meta\MetaData;
use App\Framework\Router\Result\ViewResult;
final readonly class DemoController
{
#[Route(path: '/demo/permissions', method: Method::GET)]
public function permissionsDemo(HttpRequest $request): ViewResult
{
$metaData = new MetaData(
title: 'Permission Management & Biometric Authentication Demo',
description: 'Test und Demo des Permission Management Systems und WebAuthn Biometric Authentication'
);
return new ViewResult('permissions', $metaData, [
'features' => [
'Permission API Management',
'WebAuthn Biometric Authentication',
'Onboarding Flows',
'Conditional UI Setup',
'Credential Management',
],
]);
}
#[Route(path: '/demo/canvas', method: Method::GET)]
public function canvasDemo(HttpRequest $request): ViewResult
{
$metaData = new MetaData(
title: 'Canvas Animation Demo',
description: 'Interactive Canvas Animationen, Parallax Effekte und Datenvisualisierung'
);
return new ViewResult('canvas', $metaData, [
'features' => [
'Interactive Canvas Elements',
'Parallax & Scroll Effects',
'Data Visualization',
'Particle Systems',
'Performance Optimized',
],
]);
}
#[Route(path: '/demo/api-manager', method: Method::GET)]
public function apiManagerDemo(HttpRequest $request): ViewResult
{
$metaData = new MetaData(
title: 'API Manager Demo',
description: 'Zentrale Verwaltung aller Web APIs für moderne Browser-Features'
);
return new ViewResult('api-manager', $metaData, [
'features' => [
'Observer APIs (Intersection, Resize, Mutation)',
'Media APIs (Camera, Microphone, WebRTC)',
'Storage APIs (IndexedDB, Cache API)',
'Device APIs (Geolocation, Sensors)',
'Web Animations API',
'Worker APIs (Service Worker, Web Worker)',
'Performance APIs',
],
]);
}
}

View File

@@ -0,0 +1,379 @@
<?php
declare(strict_types=1);
namespace App\Application\Controller;
use App\Framework\Http\Attribute\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Result\HtmlResult;
use App\Framework\Http\Result\HttpResponse;
use App\Framework\QrCode\ErrorCorrectionLevel;
use App\Framework\QrCode\QrCodeGenerator;
use App\Framework\QrCode\QrCodeVersion;
/**
* QR Code Test Controller
*
* Displays example QR codes for testing purposes in development
*/
final readonly class QrCodeTestController
{
public function __construct(
private QrCodeGenerator $qrCodeGenerator
) {
}
/**
* Show QR code test page with multiple examples
*/
#[Route(path: '/test/qr-codes', method: Method::GET)]
public function showTestPage(HttpRequest $request): HttpResponse
{
// Generate various test QR codes
$examples = [
[
'title' => 'Simple Text',
'data' => 'Hello, World!',
'description' => 'Basic text QR code (Version 1)',
'version' => null,
'errorLevel' => null,
],
[
'title' => 'URL Example',
'data' => 'https://example.com/test?param=value',
'description' => 'Website URL QR code',
'version' => null,
'errorLevel' => ErrorCorrectionLevel::M,
],
[
'title' => 'TOTP Example',
'data' => 'otpauth://totp/TestApp:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=TestApp&algorithm=SHA1&digits=6&period=30',
'description' => 'TOTP authenticator setup (Version 3)',
'version' => QrCodeVersion::forTotp(),
'errorLevel' => ErrorCorrectionLevel::M,
],
[
'title' => 'WiFi Connection',
'data' => 'WIFI:T:WPA;S:TestNetwork;P:password123;H:false;;',
'description' => 'WiFi connection QR code',
'version' => null,
'errorLevel' => ErrorCorrectionLevel::L,
],
[
'title' => 'Contact Card',
'data' => 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nTEL:+49123456789\nEMAIL:john@example.com\nEND:VCARD',
'description' => 'vCard contact information',
'version' => null,
'errorLevel' => ErrorCorrectionLevel::M,
],
];
$qrCodes = [];
foreach ($examples as $example) {
try {
// Generate SVG
$svg = $this->qrCodeGenerator->generateSvg(
$example['data'],
$example['errorLevel'],
$example['version']
);
// Generate data URI
$dataUri = $this->qrCodeGenerator->generateDataUri(
$example['data'],
$example['errorLevel'],
$example['version']
);
// Analyze the data
$analysis = $this->qrCodeGenerator->analyzeData($example['data']);
$qrCodes[] = [
'title' => $example['title'],
'description' => $example['description'],
'data' => $example['data'],
'svg' => $svg,
'dataUri' => $dataUri,
'analysis' => $analysis,
'success' => true,
];
} catch (\Exception $e) {
$qrCodes[] = [
'title' => $example['title'],
'description' => $example['description'],
'data' => $example['data'],
'error' => $e->getMessage(),
'success' => false,
];
}
}
$html = $this->generateTestPageHtml($qrCodes);
return new HtmlResult($html);
}
/**
* Generate individual QR code for API testing
*/
#[Route(path: '/test/qr-code', method: Method::GET)]
public function generateTestQrCode(HttpRequest $request): HttpResponse
{
$data = $request->query->get('data', 'Test QR Code from API');
$format = $request->query->get('format', 'svg'); // svg or datauri
$errorLevel = $request->query->get('error', 'M');
$version = $request->query->get('version');
try {
// Parse error correction level
$errorCorrectionLevel = match($errorLevel) {
'L' => ErrorCorrectionLevel::L,
'Q' => ErrorCorrectionLevel::Q,
'H' => ErrorCorrectionLevel::H,
default => ErrorCorrectionLevel::M,
};
// Parse version if provided
$qrVersion = $version ? new QrCodeVersion((int) $version) : null;
if ($format === 'datauri') {
$result = $this->qrCodeGenerator->generateDataUri($data, $errorCorrectionLevel, $qrVersion);
return new HtmlResult("<img src=\"{$result}\" alt=\"QR Code\" style=\"max-width: 100%;\"/>");
} else {
$svg = $this->qrCodeGenerator->generateSvg($data, $errorCorrectionLevel, $qrVersion);
return new HttpResponse(
body: $svg,
statusCode: 200,
headers: ['Content-Type' => 'image/svg+xml']
);
}
} catch (\Exception $e) {
return new HtmlResult(
"<h1>QR Code Generation Error</h1><p>{$e->getMessage()}</p>",
500
);
}
}
/**
* Generate test page HTML
*/
private function generateTestPageHtml(array $qrCodes): string
{
$examples = '';
foreach ($qrCodes as $qrCode) {
if ($qrCode['success']) {
$analysis = $qrCode['analysis'];
$analysisHtml = '';
foreach ($analysis as $key => $value) {
$displayValue = is_object($value) ? class_basename($value) : $value;
$analysisHtml .= "<tr><td>{$key}</td><td>{$displayValue}</td></tr>";
}
$examples .= "
<div class=\"qr-example\">
<h3>{$qrCode['title']}</h3>
<p class=\"description\">{$qrCode['description']}</p>
<div class=\"qr-container\">
<div class=\"qr-code\">
<img src=\"{$qrCode['dataUri']}\" alt=\"{$qrCode['title']} QR Code\" />
</div>
<div class=\"qr-info\">
<h4>Data:</h4>
<pre class=\"data\">" . htmlspecialchars($qrCode['data']) . "</pre>
<h4>Analysis:</h4>
<table class=\"analysis\">
{$analysisHtml}
</table>
</div>
</div>
</div>";
} else {
$examples .= "
<div class=\"qr-example error\">
<h3>{$qrCode['title']} - ERROR</h3>
<p class=\"description\">{$qrCode['description']}</p>
<p class=\"error-message\">Error: {$qrCode['error']}</p>
<pre class=\"data\">" . htmlspecialchars($qrCode['data']) . "</pre>
</div>";
}
}
return "
<!DOCTYPE html>
<html lang=\"de\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>QR Code Test Page</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background: #f5f5f5;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 2rem;
}
.api-info {
background: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 4px;
padding: 15px;
margin-bottom: 2rem;
}
.api-info h3 {
margin-top: 0;
color: #1976d2;
}
.api-info code {
background: #fff;
padding: 2px 6px;
border-radius: 3px;
font-family: Monaco, Consolas, monospace;
}
.qr-example {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 2rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.qr-example.error {
border-left: 4px solid #f44336;
background: #ffebee;
}
.qr-example h3 {
margin-top: 0;
color: #333;
}
.description {
color: #666;
font-style: italic;
}
.error-message {
color: #d32f2f;
font-weight: bold;
}
.qr-container {
display: flex;
gap: 20px;
align-items: flex-start;
}
.qr-code {
flex-shrink: 0;
}
.qr-code img {
border: 1px solid #ddd;
border-radius: 4px;
max-width: 200px;
}
.qr-info {
flex: 1;
min-width: 0;
}
.qr-info h4 {
margin: 1rem 0 0.5rem 0;
color: #555;
}
.data {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 10px;
font-family: Monaco, Consolas, monospace;
font-size: 12px;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
.analysis {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.analysis td {
padding: 4px 8px;
border-bottom: 1px solid #eee;
}
.analysis td:first-child {
font-weight: bold;
color: #666;
white-space: nowrap;
}
@media (max-width: 768px) {
.qr-container {
flex-direction: column;
}
.qr-code img {
max-width: 150px;
}
}
</style>
</head>
<body>
<h1>🧪 QR Code Test Page</h1>
<div class=\"api-info\">
<h3>📡 API Endpoints</h3>
<p><strong>Dynamic QR Code Generation:</strong><br>
<code>GET /test/qr-code?data=Your+Data&format=svg&error=M&version=3</code></p>
<p><strong>Parameters:</strong></p>
<ul>
<li><code>data</code> - Text to encode (URL encoded)</li>
<li><code>format</code> - Output format: <code>svg</code> (default) or <code>datauri</code></li>
<li><code>error</code> - Error correction: <code>L</code>, <code>M</code> (default), <code>Q</code>, <code>H</code></li>
<li><code>version</code> - QR version 1-40 (auto-detect if omitted)</li>
</ul>
<p><strong>Examples:</strong><br>
<code>/test/qr-code?data=Hello+World</code><br>
<code>/test/qr-code?data=https://example.com&error=H</code><br>
<code>/test/qr-code?data=Test&format=datauri&version=2</code></p>
</div>
{$examples}
<div style=\"text-align: center; margin-top: 3rem; color: #666; font-size: 14px;\">
<p>✅ QR Code Framework Module - Fully Functional</p>
<p>Generated by Custom PHP Framework QR Code Generator</p>
</div>
</body>
</html>";
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,658 @@
<main class="demo-container">
<section class="hero-section">
<h1>🔐 Permission Management & Biometric Authentication</h1>
<p class="hero-description">Test und Demo des Permission Management Systems und WebAuthn Biometric Authentication</p>
</section>
<div class="demo-grid">
<!-- API Support Status -->
<section class="demo-card">
<h2>📋 API Support Status</h2>
<button class="btn btn-primary" onclick="checkAPISupport()">Check API Support</button>
<div id="api-support-results" class="results-box"></div>
</section>
<!-- Permission Management -->
<section class="demo-card">
<h2>🔔 Permission Management</h2>
<div class="button-group">
<h3>Individual Permissions</h3>
<button class="btn btn-secondary" onclick="checkPermission('camera')">Check Camera</button>
<button class="btn btn-secondary" onclick="checkPermission('microphone')">Check Microphone</button>
<button class="btn btn-secondary" onclick="checkPermission('geolocation')">Check Location</button>
<button class="btn btn-secondary" onclick="checkPermission('notifications')">Check Notifications</button>
</div>
<div class="button-group">
<h3>Request Permissions</h3>
<button class="btn btn-primary" onclick="requestPermission('camera')">Request Camera</button>
<button class="btn btn-primary" onclick="requestPermission('microphone')">Request Microphone</button>
<button class="btn btn-primary" onclick="requestPermission('geolocation')">Request Location</button>
<button class="btn btn-primary" onclick="requestPermission('notifications')">Request Notifications</button>
</div>
<div class="button-group">
<h3>Batch Operations</h3>
<button class="btn btn-success" onclick="requestMultiplePermissions()">Request Multiple</button>
<button class="btn btn-success" onclick="startOnboardingFlow()">Start Onboarding</button>
<button class="btn btn-outline" onclick="getPermissionReport()">Get Report</button>
</div>
<div id="permission-results" class="results-box"></div>
</section>
<!-- Biometric Authentication -->
<section class="demo-card">
<h2>👆 Biometric Authentication</h2>
<div class="form-group">
<h3>User Information</h3>
<div class="input-group">
<label for="username">Username/Email:</label>
<input type="text" id="username" placeholder="user@example.com" value="testuser@example.com">
</div>
<div class="input-group">
<label for="displayName">Display Name:</label>
<input type="text" id="displayName" placeholder="Test User" value="Test User">
</div>
</div>
<div class="button-group">
<h3>Authentication Actions</h3>
<button class="btn btn-info" onclick="checkBiometricSupport()">Check Support</button>
<button class="btn btn-success" onclick="registerBiometric()">Register Biometric</button>
<button class="btn btn-primary" onclick="authenticateBiometric()">Authenticate</button>
<button class="btn btn-secondary" onclick="setupConditionalUI()">Setup Conditional UI</button>
</div>
<div class="button-group">
<h3>Management</h3>
<button class="btn btn-outline" onclick="getBiometricStatus()">Get Status</button>
<button class="btn btn-outline" onclick="listCredentials()">List Credentials</button>
<button class="btn btn-danger" onclick="clearCredentials()">Clear All</button>
</div>
<div id="biometric-results" class="results-box"></div>
<div class="credentials-section">
<h3>Registered Credentials</h3>
<div id="credentials-list" class="credentials-list">
<p>No credentials registered</p>
</div>
</div>
</section>
<!-- Combined Workflows -->
<section class="demo-card full-width">
<h2>🔄 Combined Workflows</h2>
<div class="button-group">
<button class="btn btn-success" onclick="completeSetupFlow()">Complete Setup Flow</button>
<button class="btn btn-primary" onclick="createSecureLoginFlow()">Create Secure Login Flow</button>
</div>
<div id="workflow-results" class="results-box"></div>
</section>
</div>
</main>
<style>
.demo-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.hero-section {
text-align: center;
margin-bottom: 3rem;
}
.hero-section h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #333;
}
.hero-description {
font-size: 1.2rem;
color: #666;
margin-bottom: 0;
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 2rem;
}
.demo-card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
border: 1px solid #e1e5e9;
}
.demo-card.full-width {
grid-column: 1 / -1;
}
.demo-card h2 {
margin: 0 0 1.5rem 0;
color: #333;
border-bottom: 2px solid #f0f2f5;
padding-bottom: 0.5rem;
}
.demo-card h3 {
margin: 1.5rem 0 1rem 0;
color: #555;
font-size: 1.1rem;
}
.button-group {
margin-bottom: 2rem;
}
.button-group:last-of-type {
margin-bottom: 1rem;
}
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
margin: 0.25rem 0.5rem 0.25rem 0;
border: none;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #1e7e34;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover {
background: #117a8b;
}
.btn-outline {
background: white;
color: #6c757d;
border: 2px solid #dee2e6;
}
.btn-outline:hover {
background: #f8f9fa;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.btn:disabled {
background: #ccc !important;
color: #666 !important;
cursor: not-allowed;
}
.form-group {
margin-bottom: 2rem;
}
.input-group {
margin-bottom: 1rem;
}
.input-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #555;
}
.input-group input {
width: 100%;
padding: 0.75rem;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.input-group input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
}
.results-box {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 1rem;
white-space: pre-wrap;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.85rem;
line-height: 1.4;
max-height: 300px;
overflow-y: auto;
margin-top: 1rem;
}
.results-box:empty::after {
content: "No results yet...";
color: #999;
font-style: italic;
}
.credentials-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #e1e5e9;
}
.credentials-list {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 1rem;
min-height: 60px;
}
.credential-item {
background: white;
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 6px;
border: 1px solid #e0e0e0;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.credential-item:last-child {
margin-bottom: 0;
}
.credential-item strong {
color: #333;
}
.credential-item button {
background: #dc3545;
color: white;
border: none;
padding: 0.25rem 0.75rem;
border-radius: 4px;
margin-top: 0.5rem;
cursor: pointer;
font-size: 0.8rem;
}
.credential-item button:hover {
background: #c82333;
}
@media (max-width: 768px) {
.demo-grid {
grid-template-columns: 1fr;
}
.demo-container {
padding: 1rem;
}
.hero-section h1 {
font-size: 2rem;
}
.btn {
display: block;
width: 100%;
margin: 0.25rem 0;
}
}
</style>
<script>
// Wait for API to be available
document.addEventListener('DOMContentLoaded', async function() {
// Wait for modules to initialize
await new Promise(resolve => setTimeout(resolve, 1000));
if (!window.API) {
console.error('API Manager not available');
return;
}
// Check if required managers are available
if (!window.API.permissions || !window.API.biometric) {
console.error('Permission or Biometric managers not available');
return;
}
console.log('🔐 Permission Management & Biometric Auth Demo ready');
console.log('Available API Managers:', Object.keys(window.API));
});
// API Support Check
async function checkAPISupport() {
const results = document.getElementById('api-support-results');
if (!window.API) {
results.textContent = 'ERROR: API Manager not available!';
return;
}
try {
const permissionSupport = window.API.permissions ? await window.API.permissions.getPermissionReport() : null;
const biometricSupport = window.API.biometric ? await window.API.biometric.isAvailable() : null;
results.textContent = JSON.stringify({
apiManagers: {
permissions: !!window.API.permissions,
biometric: !!window.API.biometric,
media: !!window.API.media,
device: !!window.API.device
},
permissionSupport,
biometricSupport
}, null, 2);
} catch (error) {
results.textContent = `ERROR: ${error.message}`;
console.error('API Support Check Error:', error);
}
}
// Permission Functions
async function checkPermission(permission) {
const results = document.getElementById('permission-results');
try {
const status = await window.API.permissions.check(permission);
results.textContent = `${permission} permission status:\n${JSON.stringify(status, null, 2)}`;
} catch (error) {
results.textContent = `ERROR checking ${permission}: ${error.message}`;
}
}
async function requestPermission(permission) {
const results = document.getElementById('permission-results');
try {
const result = await window.API.permissions.request(permission, {
showRationale: true,
timeout: 30000
});
results.textContent = `${permission} permission request result:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
results.textContent = `ERROR requesting ${permission}: ${error.message}`;
}
}
async function requestMultiplePermissions() {
const results = document.getElementById('permission-results');
try {
const permissions = ['camera', 'microphone', 'geolocation', 'notifications'];
const result = await window.API.permissions.requestMultiple(permissions, {
sequential: false,
requireAll: false
});
results.textContent = `Multiple permissions result:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
results.textContent = `ERROR requesting multiple permissions: ${error.message}`;
}
}
async function startOnboardingFlow() {
const results = document.getElementById('permission-results');
try {
const permissions = ['camera', 'microphone', 'geolocation'];
const onboardingFlow = window.API.permissions.createOnboardingFlow(permissions, {
title: 'App Permissions Setup',
descriptions: {
camera: 'We need camera access to take photos and scan QR codes',
microphone: 'We need microphone access for voice messages',
geolocation: 'We need location access to show nearby places'
}
});
const result = await onboardingFlow.start();
results.textContent = `Onboarding flow result:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
results.textContent = `ERROR in onboarding flow: ${error.message}`;
}
}
async function getPermissionReport() {
const results = document.getElementById('permission-results');
try {
const report = await window.API.permissions.getPermissionReport();
results.textContent = `Permission Report:\n${JSON.stringify(report, null, 2)}`;
} catch (error) {
results.textContent = `ERROR getting permission report: ${error.message}`;
}
}
// Biometric Functions
async function checkBiometricSupport() {
const results = document.getElementById('biometric-results');
try {
const availability = await window.API.biometric.isAvailable();
results.textContent = `Biometric Support:\n${JSON.stringify(availability, null, 2)}`;
} catch (error) {
results.textContent = `ERROR checking biometric support: ${error.message}`;
}
}
async function registerBiometric() {
const results = document.getElementById('biometric-results');
const username = document.getElementById('username').value;
const displayName = document.getElementById('displayName').value;
if (!username) {
results.textContent = 'ERROR: Please enter a username';
return;
}
try {
const userInfo = {
id: username,
username: username,
displayName: displayName || username
};
const result = await window.API.biometric.register(userInfo);
results.textContent = `Registration Result:\n${JSON.stringify(result, null, 2)}`;
if (result.success) {
updateCredentialsList();
}
} catch (error) {
results.textContent = `ERROR during registration: ${error.message}`;
}
}
async function authenticateBiometric() {
const results = document.getElementById('biometric-results');
try {
const result = await window.API.biometric.authenticate();
results.textContent = `Authentication Result:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
results.textContent = `ERROR during authentication: ${error.message}`;
}
}
async function setupConditionalUI() {
const results = document.getElementById('biometric-results');
try {
const result = await window.API.biometric.setupConditionalUI();
results.textContent = `Conditional UI Setup:\n${JSON.stringify(result, null, 2)}`;
} catch (error) {
results.textContent = `ERROR setting up conditional UI: ${error.message}`;
}
}
async function getBiometricStatus() {
const results = document.getElementById('biometric-results');
try {
const status = await window.API.biometric.getStatusReport();
results.textContent = `Biometric Status:\n${JSON.stringify(status, null, 2)}`;
} catch (error) {
results.textContent = `ERROR getting biometric status: ${error.message}`;
}
}
function listCredentials() {
updateCredentialsList();
}
function clearCredentials() {
if (confirm('Are you sure you want to clear all biometric credentials?')) {
try {
const credentials = window.API.biometric.getCredentials();
credentials.forEach(cred => {
window.API.biometric.revokeCredential(cred.id);
});
updateCredentialsList();
document.getElementById('biometric-results').textContent = 'All credentials cleared';
} catch (error) {
document.getElementById('biometric-results').textContent = `ERROR clearing credentials: ${error.message}`;
}
}
}
// Workflow Functions
async function completeSetupFlow() {
const results = document.getElementById('workflow-results');
try {
// Step 1: Request required permissions
const permissionResult = await window.API.permissions.requestMultiple(
['camera', 'microphone', 'notifications'],
{ sequential: true }
);
// Step 2: If permissions granted, setup biometric auth
if (permissionResult.granted > 0) {
const userInfo = {
id: 'workflow-user',
username: 'workflow-user',
displayName: 'Workflow Test User'
};
const biometricResult = await window.API.biometric.register(userInfo);
results.textContent = `Complete Setup Flow:\nPermissions: ${permissionResult.granted}/${permissionResult.total} granted\nBiometric: ${biometricResult.success ? 'Registered' : 'Failed'}\n\n` +
JSON.stringify({ permissions: permissionResult, biometric: biometricResult }, null, 2);
if (biometricResult.success) {
updateCredentialsList();
}
} else {
results.textContent = `Setup failed - no permissions granted:\n${JSON.stringify(permissionResult, null, 2)}`;
}
} catch (error) {
results.textContent = `ERROR in setup flow: ${error.message}`;
}
}
async function createSecureLoginFlow() {
const results = document.getElementById('workflow-results');
try {
const loginFlow = window.API.biometric.createLoginFlow({
onRegister: (result) => {
console.log('Biometric registered:', result);
updateCredentialsList();
},
onLogin: (result) => {
console.log('Biometric login successful:', result);
},
onError: (error) => {
console.error('Login flow error:', error);
}
});
const availability = await loginFlow.init();
results.textContent = `Secure Login Flow Created:\n${JSON.stringify(availability, null, 2)}`;
} catch (error) {
results.textContent = `ERROR creating secure login flow: ${error.message}`;
}
}
// Helper Functions
function updateCredentialsList() {
const credentialsList = document.getElementById('credentials-list');
try {
const credentials = window.API.biometric.getCredentials();
if (credentials.length === 0) {
credentialsList.innerHTML = '<p style="margin: 0; color: #666; font-style: italic;">No credentials registered</p>';
} else {
const credentialsHTML = credentials.map(cred => `
<div class="credential-item">
<strong>ID:</strong> ${cred.id.substring(0, 20)}...<br>
<strong>User:</strong> ${cred.userDisplayName}<br>
<strong>Created:</strong> ${new Date(cred.createdAt).toLocaleString()}<br>
<strong>Last Used:</strong> ${cred.lastUsed ? new Date(cred.lastUsed).toLocaleString() : 'Never'}<br>
<strong>Transports:</strong> ${cred.transports.join(', ')}<br>
<button onclick="window.API.biometric.revokeCredential('${cred.id}'); updateCredentialsList();">Revoke</button>
</div>
`).join('');
credentialsList.innerHTML = credentialsHTML;
}
} catch (error) {
credentialsList.innerHTML = `<p style="color: #dc3545;">Error loading credentials: ${error.message}</p>`;
}
}
// Initial setup
setTimeout(updateCredentialsList, 2000);
</script>