Files
ebaysnipeextension/test-realtime-sync.html
Kenso Grimm 216a972fef chore: initialize project repository with core extension files
- Add .gitignore to exclude node_modules, dist, logs, and system files
- Add comprehensive project documentation including README, deployment guide, and development setup
- Add .kiro project specifications for amazon-product-bar-extension, appwrite-cloud-storage, appwrite-userid-repair, blacklist-feature, and enhanced-item-management
- Add .kiro steering documents for product, structure, styling, and tech guidelines
- Add VSCode settings configuration for consistent development environment
- Add manifest.json and babel/vite configuration for extension build setup
- Add complete source code implementation including AppWrite integration, storage managers, UI components, and services
- Add comprehensive test suite with Jest configuration and 30+ test files covering all major modules
- Add test HTML files for integration testing and validation
- Add coverage reports and build validation scripts
- Add AppWrite setup and repair documentation for database schema management
- Add migration guides and responsive accessibility implementation documentation
- Establish foundation for Amazon product bar extension with full feature set including blacklist management, enhanced item workflows, and real-time synchronization
2026-01-12 17:46:42 +01:00

723 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Real-Time Sync Service Test</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.status.success { background-color: #d4edda; color: #155724; }
.status.error { background-color: #f8d7da; color: #721c24; }
.status.info { background-color: #d1ecf1; color: #0c5460; }
.status.warning { background-color: #fff3cd; color: #856404; }
button {
background-color: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover { background-color: #0056b3; }
button:disabled { background-color: #6c757d; cursor: not-allowed; }
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.log {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
height: 200px;
overflow-y: auto;
font-family: monospace;
font-size: 12px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin: 10px 0;
}
.stat-item {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #007bff;
}
.stat-label {
font-size: 12px;
color: #6c757d;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin: 5px;
}
</style>
</head>
<body>
<h1>Real-Time Sync Service Test</h1>
<div class="container">
<h2>Service Status</h2>
<div id="serviceStatus" class="status info">Initializing...</div>
<div class="stats">
<div class="stat-item">
<div id="totalSyncs" class="stat-value">0</div>
<div class="stat-label">Total Syncs</div>
</div>
<div class="stat-item">
<div id="successfulSyncs" class="stat-value">0</div>
<div class="stat-label">Successful</div>
</div>
<div class="stat-item">
<div id="failedSyncs" class="stat-value">0</div>
<div class="stat-label">Failed</div>
</div>
<div class="stat-item">
<div id="successRate" class="stat-value">0%</div>
<div class="stat-label">Success Rate</div>
</div>
<div class="stat-item">
<div id="avgSyncTime" class="stat-value">0ms</div>
<div class="stat-label">Avg Sync Time</div>
</div>
<div class="stat-item">
<div id="networkStatus" class="stat-value">Unknown</div>
<div class="stat-label">Network Status</div>
</div>
</div>
</div>
<div class="grid">
<div class="container">
<h2>Sync Operations</h2>
<h3>Enhanced Items</h3>
<div>
<input type="text" id="itemTitle" placeholder="Item title" value="Test Enhanced Item">
<input type="text" id="itemPrice" placeholder="Price" value="29.99">
<input type="text" id="itemUrl" placeholder="Amazon URL" value="https://amazon.de/dp/B08N5WRWNW">
<br>
<button onclick="createEnhancedItem()">Create Item</button>
<button onclick="updateEnhancedItem()">Update Item</button>
<button onclick="deleteEnhancedItem()">Delete Item</button>
</div>
<h3>Blacklist</h3>
<div>
<input type="text" id="brandName" placeholder="Brand name" value="Test Brand">
<br>
<button onclick="addBrand()">Add Brand</button>
<button onclick="deleteBrand()">Delete Brand</button>
</div>
<h3>Settings</h3>
<div>
<input type="text" id="apiKey" placeholder="API Key" value="test-api-key-123">
<select id="titleSelection">
<option value="first">First</option>
<option value="second">Second</option>
<option value="third">Third</option>
</select>
<br>
<button onclick="updateSettings()">Update Settings</button>
</div>
<h3>Batch Operations</h3>
<div>
<input type="number" id="batchSize" placeholder="Batch size" value="5" min="1" max="20">
<br>
<button onclick="batchCreateItems()">Batch Create Items</button>
<button onclick="batchCreateBrands()">Batch Create Brands</button>
</div>
</div>
<div class="container">
<h2>Real-Time Events</h2>
<button onclick="clearLog()">Clear Log</button>
<button onclick="forceRefresh()">Force Refresh</button>
<button onclick="toggleSync()">Toggle Sync</button>
<div id="eventLog" class="log"></div>
</div>
</div>
<div class="container">
<h2>Collection Monitoring</h2>
<div id="monitoringStatus" class="status info">Monitoring status will appear here...</div>
<button onclick="enableAllMonitoring()">Enable All Monitoring</button>
<button onclick="disableAllMonitoring()">Disable All Monitoring</button>
</div>
<script type="module">
import { RealTimeSyncService } from './src/RealTimeSyncService.js';
import { AppWriteManager } from './src/AppWriteManager.js';
import { OfflineService } from './src/OfflineService.js';
let realTimeSyncService = null;
let appWriteManager = null;
let offlineService = null;
let isAuthenticated = false;
let syncEnabled = true;
let lastItemId = null;
let lastBrandId = null;
// Initialize services
async function initializeServices() {
try {
updateStatus('Initializing AppWrite services...', 'info');
appWriteManager = new AppWriteManager();
// Check authentication
const authService = appWriteManager.getAuthService();
const user = await authService.getCurrentUser();
if (!user) {
updateStatus('User not authenticated. Please login first.', 'error');
return;
}
isAuthenticated = true;
updateStatus(`Authenticated as: ${user.email}`, 'success');
// Initialize offline service
offlineService = new OfflineService(appWriteManager);
// Initialize real-time sync service
realTimeSyncService = new RealTimeSyncService(appWriteManager, offlineService);
// Setup event listeners
setupEventListeners();
// Enable monitoring for all collections
await enableAllMonitoring();
updateStatus('Real-time sync service initialized successfully!', 'success');
updateStats();
} catch (error) {
console.error('Failed to initialize services:', error);
updateStatus(`Initialization failed: ${error.message}`, 'error');
}
}
function setupEventListeners() {
// Real-time sync events
realTimeSyncService.addEventListener('sync:completed', (data) => {
logEvent('Sync Completed', data);
updateStats();
});
realTimeSyncService.addEventListener('sync:error', (data) => {
logEvent('Sync Error', data);
updateStats();
});
realTimeSyncService.addEventListener('data:changed', (data) => {
logEvent('Data Changed', data);
});
realTimeSyncService.addEventListener('batch:completed', (data) => {
logEvent('Batch Completed', data);
updateStats();
});
// Offline service events
if (offlineService) {
offlineService.onOnlineStatusChanged((isOnline) => {
logEvent('Network Status', { isOnline });
updateNetworkStatus(isOnline);
});
offlineService.onSyncProgress((status, processed, total) => {
logEvent('Sync Progress', { status, processed, total });
});
}
}
function updateStatus(message, type = 'info') {
const statusEl = document.getElementById('serviceStatus');
statusEl.textContent = message;
statusEl.className = `status ${type}`;
}
function updateStats() {
if (!realTimeSyncService) return;
const stats = realTimeSyncService.getSyncStats();
document.getElementById('totalSyncs').textContent = stats.totalSyncs;
document.getElementById('successfulSyncs').textContent = stats.successfulSyncs;
document.getElementById('failedSyncs').textContent = stats.failedSyncs;
document.getElementById('successRate').textContent = `${stats.successRate}%`;
document.getElementById('avgSyncTime').textContent = `${stats.averageSyncTime}ms`;
}
function updateNetworkStatus(isOnline) {
const statusEl = document.getElementById('networkStatus');
statusEl.textContent = isOnline ? 'Online' : 'Offline';
statusEl.style.color = isOnline ? '#28a745' : '#dc3545';
}
function logEvent(type, data) {
const logEl = document.getElementById('eventLog');
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${type}: ${JSON.stringify(data, null, 2)}\n`;
logEl.textContent += logEntry;
logEl.scrollTop = logEl.scrollHeight;
}
// Sync operation functions
window.createEnhancedItem = async function() {
if (!realTimeSyncService || !isAuthenticated) {
updateStatus('Service not initialized or not authenticated', 'error');
return;
}
try {
const title = document.getElementById('itemTitle').value;
const price = document.getElementById('itemPrice').value;
const url = document.getElementById('itemUrl').value;
const itemData = {
itemId: `test_${Date.now()}`,
amazonUrl: url,
originalTitle: title,
customTitle: `Enhanced: ${title}`,
price: price,
currency: 'EUR',
titleSuggestions: [`AI: ${title}`, `Smart: ${title}`, `Pro: ${title}`],
hashValue: `hash_${Date.now()}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
lastItemId = itemData.itemId;
const result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('enhancedItems'),
'create',
itemData.itemId,
itemData
);
if (result.success) {
updateStatus('Enhanced item created successfully!', 'success');
} else {
updateStatus(`Failed to create item: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error creating enhanced item:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.updateEnhancedItem = async function() {
if (!realTimeSyncService || !isAuthenticated || !lastItemId) {
updateStatus('No item to update or service not ready', 'error');
return;
}
try {
// Find the document first
const documents = await appWriteManager.getUserDocuments(
appWriteManager.getCollectionId('enhancedItems'),
[{ method: 'equal', attribute: 'itemId', values: [lastItemId] }]
);
if (documents.documents.length === 0) {
updateStatus('Item not found for update', 'error');
return;
}
const updateData = {
customTitle: `Updated: ${document.getElementById('itemTitle').value}`,
price: document.getElementById('itemPrice').value,
updatedAt: new Date().toISOString()
};
const result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('enhancedItems'),
'update',
documents.documents[0].$id,
updateData
);
if (result.success) {
updateStatus('Enhanced item updated successfully!', 'success');
} else {
updateStatus(`Failed to update item: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error updating enhanced item:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.deleteEnhancedItem = async function() {
if (!realTimeSyncService || !isAuthenticated || !lastItemId) {
updateStatus('No item to delete or service not ready', 'error');
return;
}
try {
// Find the document first
const documents = await appWriteManager.getUserDocuments(
appWriteManager.getCollectionId('enhancedItems'),
[{ method: 'equal', attribute: 'itemId', values: [lastItemId] }]
);
if (documents.documents.length === 0) {
updateStatus('Item not found for deletion', 'error');
return;
}
const result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('enhancedItems'),
'delete',
documents.documents[0].$id
);
if (result.success) {
updateStatus('Enhanced item deleted successfully!', 'success');
lastItemId = null;
} else {
updateStatus(`Failed to delete item: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error deleting enhanced item:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.addBrand = async function() {
if (!realTimeSyncService || !isAuthenticated) {
updateStatus('Service not initialized or not authenticated', 'error');
return;
}
try {
const brandName = document.getElementById('brandName').value;
const brandData = {
brandId: `bl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: brandName,
addedAt: new Date().toISOString()
};
lastBrandId = brandData.brandId;
const result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('blacklist'),
'create',
null,
brandData
);
if (result.success) {
updateStatus('Brand added successfully!', 'success');
} else {
updateStatus(`Failed to add brand: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error adding brand:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.deleteBrand = async function() {
if (!realTimeSyncService || !isAuthenticated || !lastBrandId) {
updateStatus('No brand to delete or service not ready', 'error');
return;
}
try {
// Find the document first
const documents = await appWriteManager.getUserDocuments(
appWriteManager.getCollectionId('blacklist'),
[{ method: 'equal', attribute: 'brandId', values: [lastBrandId] }]
);
if (documents.documents.length === 0) {
updateStatus('Brand not found for deletion', 'error');
return;
}
const result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('blacklist'),
'delete',
documents.documents[0].$id
);
if (result.success) {
updateStatus('Brand deleted successfully!', 'success');
lastBrandId = null;
} else {
updateStatus(`Failed to delete brand: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error deleting brand:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.updateSettings = async function() {
if (!realTimeSyncService || !isAuthenticated) {
updateStatus('Service not initialized or not authenticated', 'error');
return;
}
try {
const apiKey = document.getElementById('apiKey').value;
const titleSelection = document.getElementById('titleSelection').value;
const settingsData = {
mistralApiKey: apiKey,
defaultTitleSelection: titleSelection,
autoExtractEnabled: true,
maxRetries: 3,
timeoutSeconds: 10,
updatedAt: new Date().toISOString()
};
// Check if settings document exists
const existingSettings = await appWriteManager.getUserDocuments(
appWriteManager.getCollectionId('settings')
);
let result;
if (existingSettings.documents.length > 0) {
result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('settings'),
'update',
existingSettings.documents[0].$id,
settingsData
);
} else {
result = await realTimeSyncService.syncToCloud(
appWriteManager.getCollectionId('settings'),
'create',
null,
settingsData
);
}
if (result.success) {
updateStatus('Settings updated successfully!', 'success');
} else {
updateStatus(`Failed to update settings: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error updating settings:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.batchCreateItems = async function() {
if (!realTimeSyncService || !isAuthenticated) {
updateStatus('Service not initialized or not authenticated', 'error');
return;
}
try {
const batchSize = parseInt(document.getElementById('batchSize').value);
const operations = [];
for (let i = 0; i < batchSize; i++) {
const itemData = {
itemId: `batch_item_${Date.now()}_${i}`,
amazonUrl: `https://amazon.de/dp/BATCH${i}`,
originalTitle: `Batch Item ${i + 1}`,
customTitle: `Enhanced Batch Item ${i + 1}`,
price: `${(Math.random() * 100).toFixed(2)}`,
currency: 'EUR',
titleSuggestions: [`AI Batch ${i + 1}`, `Smart Batch ${i + 1}`],
hashValue: `batch_hash_${Date.now()}_${i}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
operations.push({
collectionId: appWriteManager.getCollectionId('enhancedItems'),
operation: 'create',
documentId: itemData.itemId,
data: itemData
});
}
updateStatus(`Creating ${batchSize} items in batch...`, 'info');
const result = await realTimeSyncService.batchSync(operations);
if (result.success) {
updateStatus(`Batch created successfully! ${result.successfulOperations}/${result.totalOperations} items created.`, 'success');
} else {
updateStatus(`Batch partially failed: ${result.successfulOperations}/${result.totalOperations} items created.`, 'warning');
}
} catch (error) {
console.error('Error in batch create:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.batchCreateBrands = async function() {
if (!realTimeSyncService || !isAuthenticated) {
updateStatus('Service not initialized or not authenticated', 'error');
return;
}
try {
const batchSize = parseInt(document.getElementById('batchSize').value);
const operations = [];
for (let i = 0; i < batchSize; i++) {
const brandData = {
brandId: `batch_brand_${Date.now()}_${i}`,
name: `Batch Brand ${i + 1}`,
addedAt: new Date().toISOString()
};
operations.push({
collectionId: appWriteManager.getCollectionId('blacklist'),
operation: 'create',
data: brandData
});
}
updateStatus(`Creating ${batchSize} brands in batch...`, 'info');
const result = await realTimeSyncService.batchSync(operations);
if (result.success) {
updateStatus(`Batch created successfully! ${result.successfulOperations}/${result.totalOperations} brands created.`, 'success');
} else {
updateStatus(`Batch partially failed: ${result.successfulOperations}/${result.totalOperations} brands created.`, 'warning');
}
} catch (error) {
console.error('Error in batch create:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.clearLog = function() {
document.getElementById('eventLog').textContent = '';
};
window.forceRefresh = async function() {
if (!realTimeSyncService) {
updateStatus('Service not initialized', 'error');
return;
}
try {
await realTimeSyncService.forceRefreshAll();
updateStatus('Force refresh completed!', 'success');
} catch (error) {
console.error('Error in force refresh:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.toggleSync = function() {
if (!realTimeSyncService) {
updateStatus('Service not initialized', 'error');
return;
}
syncEnabled = !syncEnabled;
realTimeSyncService.setSyncEnabled(syncEnabled);
updateStatus(`Sync ${syncEnabled ? 'enabled' : 'disabled'}`, 'info');
};
window.enableAllMonitoring = async function() {
if (!realTimeSyncService || !appWriteManager) {
updateStatus('Services not initialized', 'error');
return;
}
try {
const collections = ['enhancedItems', 'blacklist', 'settings'];
for (const collection of collections) {
await realTimeSyncService.enableSyncForCollection(
appWriteManager.getCollectionId(collection),
{
onDataChanged: (documents) => {
logEvent(`${collection} Data Changed`, { count: documents.length });
}
}
);
}
const stats = realTimeSyncService.getSyncStats();
document.getElementById('monitoringStatus').textContent =
`Monitoring enabled for ${stats.monitoredCollections.length} collections: ${stats.monitoredCollections.join(', ')}`;
document.getElementById('monitoringStatus').className = 'status success';
} catch (error) {
console.error('Error enabling monitoring:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
window.disableAllMonitoring = function() {
if (!realTimeSyncService || !appWriteManager) {
updateStatus('Services not initialized', 'error');
return;
}
try {
const collections = ['enhancedItems', 'blacklist', 'settings'];
for (const collection of collections) {
realTimeSyncService.disableSyncForCollection(
appWriteManager.getCollectionId(collection)
);
}
document.getElementById('monitoringStatus').textContent = 'All monitoring disabled';
document.getElementById('monitoringStatus').className = 'status warning';
} catch (error) {
console.error('Error disabling monitoring:', error);
updateStatus(`Error: ${error.message}`, 'error');
}
};
// Initialize on page load
document.addEventListener('DOMContentLoaded', initializeServices);
// Update network status on load
updateNetworkStatus(navigator.onLine);
</script>
</body>
</html>