You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1070 lines
38 KiB
1070 lines
38 KiB
import { ConfigLoader, MockDailyNotificationService, TestLogger } from '../shared/config-loader';
|
|
|
|
// Phase 4: Import TimeSafari components
|
|
import { EndorserAPIClient } from '../shared/typescript/EndorserAPIClient';
|
|
import { SecurityManager } from '../shared/typescript/SecurityManager';
|
|
import { TimeSafariNotificationManager } from '../shared/typescript/TimeSafariNotificationManager';
|
|
// import {
|
|
// TimeSafariUser,
|
|
// TimeSafariPreferences,
|
|
// EnhancedTimeSafariNotification,
|
|
// TimeSafariNotificationType
|
|
// } from '../../../src/definitions';
|
|
|
|
// Enhanced UI components for Electron testing
|
|
class PermissionManager {
|
|
private container: HTMLElement;
|
|
private dialogContainer: HTMLElement;
|
|
|
|
constructor(container: HTMLElement, dialogContainer: HTMLElement) {
|
|
this.container = container;
|
|
this.dialogContainer = dialogContainer;
|
|
}
|
|
|
|
async updateStatus(): Promise<void> {
|
|
// Mock permission status for Electron testing
|
|
const mockStatus = {
|
|
granted: true,
|
|
notifications: 'granted' as const,
|
|
serviceWorker: 'active' as const
|
|
};
|
|
|
|
this.renderStatus(mockStatus);
|
|
}
|
|
|
|
private renderStatus(status: Record<string, unknown>): void {
|
|
const statusClass = status.granted ? 'status-granted' : 'status-denied';
|
|
const statusText = status.granted ? 'Granted' : 'Denied';
|
|
|
|
this.container.innerHTML = `
|
|
<div class="permission-status ${statusClass}">
|
|
<div class="status-indicator">
|
|
<span class="status-icon">${status.granted ? '✓' : '✗'}</span>
|
|
<span class="status-text">${statusText}</span>
|
|
</div>
|
|
<div class="permission-details">
|
|
<div class="permission-item">
|
|
<span>Notifications:</span>
|
|
<span class="${status.notifications === 'granted' ? 'granted' : 'denied'}">
|
|
${status.notifications}
|
|
</span>
|
|
</div>
|
|
<div class="permission-item">
|
|
<span>Service Worker:</span>
|
|
<span class="${status.serviceWorker === 'active' ? 'granted' : 'denied'}">
|
|
${status.serviceWorker}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
showPermissionDialog(): void {
|
|
const dialog = document.createElement('div');
|
|
dialog.className = 'dialog-overlay';
|
|
dialog.innerHTML = `
|
|
<div class="dialog-content">
|
|
<h2>Enable Daily Notifications</h2>
|
|
<p>Get notified about new offers, projects, people, and items in your TimeSafari community.</p>
|
|
<ul>
|
|
<li>New offers directed to you</li>
|
|
<li>Changes to your projects</li>
|
|
<li>Updates from favorited people</li>
|
|
<li>New items of interest</li>
|
|
</ul>
|
|
<div class="dialog-actions">
|
|
<button id="allow-permissions" class="btn-primary">Allow Notifications</button>
|
|
<button id="deny-permissions" class="btn-secondary">Not Now</button>
|
|
<button id="never-permissions" class="btn-tertiary">Never Ask Again</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
this.dialogContainer.appendChild(dialog);
|
|
|
|
dialog.querySelector('#allow-permissions')?.addEventListener('click', () => {
|
|
this.hideDialog();
|
|
this.updateStatus();
|
|
});
|
|
|
|
dialog.querySelector('#deny-permissions')?.addEventListener('click', () => {
|
|
this.hideDialog();
|
|
});
|
|
|
|
dialog.querySelector('#never-permissions')?.addEventListener('click', () => {
|
|
this.hideDialog();
|
|
});
|
|
}
|
|
|
|
private hideDialog(): void {
|
|
const dialog = this.dialogContainer.querySelector('.dialog-overlay');
|
|
if (dialog) {
|
|
dialog.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
class SettingsPanel {
|
|
private container: HTMLElement;
|
|
|
|
constructor(container: HTMLElement) {
|
|
this.container = container;
|
|
}
|
|
|
|
render(): void {
|
|
this.container.innerHTML = `
|
|
<div class="settings-panel">
|
|
<div class="setting-group">
|
|
<label class="setting-label">
|
|
<input type="checkbox" id="enable-notifications" checked>
|
|
Enable Daily Notifications
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label for="notification-time">Notification Time:</label>
|
|
<input type="time" id="notification-time" value="09:00">
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Content Types:</label>
|
|
<div class="checkbox-group">
|
|
<label><input type="checkbox" id="content-offers" checked> Offers</label>
|
|
<label><input type="checkbox" id="content-projects" checked> Projects</label>
|
|
<label><input type="checkbox" id="content-people" checked> People</label>
|
|
<label><input type="checkbox" id="content-items" checked> Items</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Notification Preferences:</label>
|
|
<div class="preference-grid">
|
|
<label>
|
|
<input type="checkbox" id="sound-enabled" checked>
|
|
Sound
|
|
</label>
|
|
<label>
|
|
<input type="checkbox" id="vibration-enabled" checked>
|
|
Vibration
|
|
</label>
|
|
<label>
|
|
<select id="priority-level">
|
|
<option value="low">Low</option>
|
|
<option value="normal" selected>Normal</option>
|
|
<option value="high">High</option>
|
|
</select>
|
|
Priority
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-actions">
|
|
<button id="save-settings" class="btn-primary">Save Settings</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class StatusDashboard {
|
|
private container: HTMLElement;
|
|
|
|
constructor(container: HTMLElement) {
|
|
this.container = container;
|
|
}
|
|
|
|
render(): void {
|
|
this.container.innerHTML = `
|
|
<div class="status-dashboard">
|
|
<div class="status-grid">
|
|
<div class="status-item">
|
|
<div class="status-label">Overall Status</div>
|
|
<div class="status-value active">Active</div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-label">Next Notification</div>
|
|
<div class="status-value">2h 15m</div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-label">Last Outcome</div>
|
|
<div class="status-value active">Success</div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-label">Cache Age</div>
|
|
<div class="status-value">1h 23m</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="performance-metrics">
|
|
<h4>Performance</h4>
|
|
<div class="metrics-grid">
|
|
<div class="metric-item">
|
|
<span class="metric-label">Success Rate:</span>
|
|
<span class="metric-value">95%</span>
|
|
</div>
|
|
<div class="metric-item">
|
|
<span class="metric-label">Error Count:</span>
|
|
<span class="metric-value">2</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
class ErrorDisplay {
|
|
private container: HTMLElement;
|
|
|
|
constructor(container: HTMLElement) {
|
|
this.container = container;
|
|
}
|
|
|
|
showError(error: Error): void {
|
|
this.container.innerHTML = `
|
|
<div class="error-display">
|
|
<div class="error-icon">⚠️</div>
|
|
<div class="error-content">
|
|
<h3>Something went wrong</h3>
|
|
<p class="error-message">${error.message}</p>
|
|
<p class="error-code">Error Code: ${error.name}</p>
|
|
</div>
|
|
<div class="error-actions">
|
|
<button id="retry-action" class="btn-primary">Retry</button>
|
|
<button id="reset-action" class="btn-secondary">Reset Configuration</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
hide(): void {
|
|
this.container.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
// Enhanced test interface for TimeSafari Electron integration
|
|
// Enhanced test interface for TimeSafari Electron integration with Phase 4 components
|
|
class TimeSafariElectronTestApp {
|
|
private statusElement: HTMLElement;
|
|
private logElement: HTMLElement;
|
|
private configLoader: ConfigLoader;
|
|
private notificationService: MockDailyNotificationService;
|
|
private logger: TestLogger;
|
|
|
|
// Phase 4: TimeSafari components
|
|
private endorserAPIClient: EndorserAPIClient;
|
|
private securityManager: SecurityManager;
|
|
private timeSafariNotificationManager: TimeSafariNotificationManager;
|
|
|
|
// UI Components
|
|
private permissionManager: PermissionManager;
|
|
private settingsPanel: SettingsPanel;
|
|
private statusDashboard: StatusDashboard;
|
|
private errorDisplay: ErrorDisplay;
|
|
|
|
constructor() {
|
|
this.statusElement = document.getElementById('status')!;
|
|
this.logElement = document.getElementById('log')!;
|
|
this.configLoader = ConfigLoader.getInstance();
|
|
this.logger = new TestLogger('debug');
|
|
this.notificationService = new MockDailyNotificationService(this.configLoader.getConfig());
|
|
|
|
// Phase 4: Initialize TimeSafari components
|
|
this.endorserAPIClient = new EndorserAPIClient(this.configLoader.getEndorserAPIConfig());
|
|
this.securityManager = new SecurityManager(this.configLoader.getSecurityConfig());
|
|
this.timeSafariNotificationManager = new TimeSafariNotificationManager();
|
|
|
|
// Initialize UI components
|
|
this.permissionManager = new PermissionManager(
|
|
document.getElementById('permission-status-container')!,
|
|
document.getElementById('permission-dialog-container')!
|
|
);
|
|
this.settingsPanel = new SettingsPanel(document.getElementById('settings-container')!);
|
|
this.statusDashboard = new StatusDashboard(document.getElementById('status-container')!);
|
|
this.errorDisplay = new ErrorDisplay(document.getElementById('error-container')!);
|
|
|
|
this.setupEventListeners();
|
|
this.initializeUI();
|
|
this.initializePhase4Components();
|
|
this.log('TimeSafari Electron Test app initialized with Phase 4 components');
|
|
}
|
|
|
|
private setupEventListeners() {
|
|
// Original test functionality
|
|
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure());
|
|
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule());
|
|
document.getElementById('endorser-api')?.addEventListener('click', () => this.testEndorserAPI());
|
|
document.getElementById('callbacks')?.addEventListener('click', () => this.testCallbacks());
|
|
document.getElementById('debug-info')?.addEventListener('click', () => this.testDebugInfo());
|
|
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance());
|
|
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog());
|
|
|
|
// Enhanced UI functionality
|
|
document.getElementById('check-permissions')?.addEventListener('click', () => this.checkPermissions());
|
|
document.getElementById('request-permissions')?.addEventListener('click', () => this.requestPermissions());
|
|
document.getElementById('open-settings')?.addEventListener('click', () => this.openSettings());
|
|
document.getElementById('test-notification')?.addEventListener('click', () => this.testNotification());
|
|
document.getElementById('check-status')?.addEventListener('click', () => this.testStatus());
|
|
document.getElementById('refresh-status')?.addEventListener('click', () => this.refreshStatus());
|
|
document.getElementById('service-worker-status')?.addEventListener('click', () => this.checkServiceWorker());
|
|
document.getElementById('push-notification-status')?.addEventListener('click', () => this.checkPushNotifications());
|
|
|
|
// Phase 4: TimeSafari component testing
|
|
document.getElementById('test-security-manager')?.addEventListener('click', () => this.testSecurityManager());
|
|
document.getElementById('test-endorser-api-client')?.addEventListener('click', () => this.testEndorserAPIClient());
|
|
document.getElementById('test-notification-manager')?.addEventListener('click', () => this.testTimeSafariNotificationManager());
|
|
document.getElementById('test-phase4-integration')?.addEventListener('click', () => this.testPhase4Integration());
|
|
|
|
// Static Daily Reminder event listeners
|
|
document.getElementById('schedule-reminder')?.addEventListener('click', () => this.scheduleDailyReminder());
|
|
document.getElementById('cancel-reminder')?.addEventListener('click', () => this.cancelDailyReminder());
|
|
document.getElementById('get-reminders')?.addEventListener('click', () => this.getScheduledReminders());
|
|
document.getElementById('update-reminder')?.addEventListener('click', () => this.updateDailyReminder());
|
|
}
|
|
|
|
private async initializeUI(): Promise<void> {
|
|
try {
|
|
// Initialize UI components
|
|
await this.permissionManager.updateStatus();
|
|
this.settingsPanel.render();
|
|
this.statusDashboard.render();
|
|
|
|
this.log('✅ Enhanced UI components initialized');
|
|
} catch (error) {
|
|
this.log(`❌ UI initialization failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
// Phase 4: Initialize TimeSafari components
|
|
private async initializePhase4Components(): Promise<void> {
|
|
try {
|
|
this.log('Initializing Phase 4 TimeSafari components...');
|
|
|
|
// Initialize SecurityManager with test DID
|
|
const timeSafariUser = this.configLoader.getTimeSafariUser();
|
|
const securityInitialized = await this.securityManager.initialize(timeSafariUser.activeDid);
|
|
|
|
if (securityInitialized) {
|
|
this.log('✅ SecurityManager initialized successfully');
|
|
|
|
// Generate JWT token for API authentication
|
|
const jwt = await this.securityManager.generateJWT({
|
|
scope: 'notifications',
|
|
audience: 'endorser-api'
|
|
});
|
|
|
|
if (jwt) {
|
|
this.endorserAPIClient.setAuthToken(jwt);
|
|
this.log('✅ JWT token generated and set for EndorserAPI');
|
|
}
|
|
} else {
|
|
this.log('❌ SecurityManager initialization failed');
|
|
}
|
|
|
|
// Initialize TimeSafariNotificationManager
|
|
const managerInitialized = await this.timeSafariNotificationManager.initialize(timeSafariUser);
|
|
|
|
if (managerInitialized) {
|
|
this.log('✅ TimeSafariNotificationManager initialized successfully');
|
|
} else {
|
|
this.log('❌ TimeSafariNotificationManager initialization failed');
|
|
}
|
|
|
|
this.log('Phase 4 components initialization completed');
|
|
|
|
} catch (error) {
|
|
this.log('Error initializing Phase 4 components:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
// Enhanced UI methods
|
|
private async checkPermissions(): Promise<void> {
|
|
try {
|
|
this.log('Checking permissions...');
|
|
await this.permissionManager.updateStatus();
|
|
this.log('✅ Permission status updated');
|
|
} catch (error) {
|
|
this.log(`❌ Permission check failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async requestPermissions(): Promise<void> {
|
|
try {
|
|
this.log('Requesting permissions...');
|
|
this.permissionManager.showPermissionDialog();
|
|
this.log('✅ Permission dialog shown');
|
|
} catch (error) {
|
|
this.log(`❌ Permission request failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async openSettings(): Promise<void> {
|
|
try {
|
|
this.log('Opening settings...');
|
|
// Mock settings opening
|
|
this.log('✅ Settings opened (mock)');
|
|
} catch (error) {
|
|
this.log(`❌ Settings open failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async testNotification(): Promise<void> {
|
|
try {
|
|
this.log('Sending test notification...');
|
|
// Mock test notification
|
|
this.log('✅ Test notification sent (mock)');
|
|
} catch (error) {
|
|
this.log(`❌ Test notification failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async refreshStatus(): Promise<void> {
|
|
try {
|
|
this.log('Refreshing status...');
|
|
this.statusDashboard.render();
|
|
this.log('✅ Status refreshed');
|
|
} catch (error) {
|
|
this.log(`❌ Status refresh failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async checkServiceWorker(): Promise<void> {
|
|
try {
|
|
this.log('Checking service worker status...');
|
|
// Mock service worker status
|
|
this.log('✅ Service worker: Active (mock)');
|
|
} catch (error) {
|
|
this.log(`❌ Service worker check failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async checkPushNotifications(): Promise<void> {
|
|
try {
|
|
this.log('Checking push notification status...');
|
|
// Mock push notification status
|
|
this.log('✅ Push notifications: Enabled (mock)');
|
|
} catch (error) {
|
|
this.log(`❌ Push notification check failed: ${error}`);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async testConfigure() {
|
|
try {
|
|
this.log('Testing TimeSafari Electron configuration...');
|
|
await this.configLoader.loadConfig();
|
|
const config = this.configLoader.getConfig();
|
|
|
|
await this.notificationService.initialize();
|
|
|
|
this.log('✅ TimeSafari Electron configuration successful', {
|
|
appId: config.timesafari.appId,
|
|
appName: config.timesafari.appName,
|
|
version: config.timesafari.version
|
|
});
|
|
this.updateStatus('Configured');
|
|
} catch (error) {
|
|
this.log(`❌ Configuration failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
private async testSchedule(): Promise<void> {
|
|
try {
|
|
this.log('Testing TimeSafari Electron community notification scheduling...');
|
|
const config = this.configLoader.getConfig();
|
|
|
|
const dualConfig = {
|
|
contentFetch: {
|
|
enabled: true,
|
|
schedule: config.scheduling.contentFetch.schedule,
|
|
url: this.configLoader.getEndorserUrl('notificationsBundle'),
|
|
headers: this.configLoader.getAuthHeaders(),
|
|
ttlSeconds: 3600,
|
|
timeout: 30000,
|
|
retryAttempts: 3,
|
|
retryDelay: 5000,
|
|
callbacks: {
|
|
onSuccess: async (data: Record<string, unknown>): Promise<void> => {
|
|
this.log('✅ Content fetch successful', data);
|
|
await this.processEndorserNotificationBundle(data);
|
|
},
|
|
onError: async (error: Record<string, unknown>): Promise<void> => {
|
|
this.log('❌ Content fetch failed', error);
|
|
}
|
|
}
|
|
},
|
|
userNotification: {
|
|
enabled: true,
|
|
schedule: config.scheduling.userNotification.schedule,
|
|
title: 'TimeSafari Community Update',
|
|
body: 'New offers, projects, people, and items await your attention!',
|
|
sound: true,
|
|
vibration: true,
|
|
priority: 'high',
|
|
actions: [
|
|
{ id: 'view_offers', title: 'View Offers' },
|
|
{ id: 'view_projects', title: 'See Projects' },
|
|
{ id: 'view_people', title: 'Check People' },
|
|
{ id: 'view_items', title: 'Browse Items' },
|
|
{ id: 'dismiss', title: 'Dismiss' }
|
|
]
|
|
},
|
|
relationship: {
|
|
autoLink: true,
|
|
contentTimeout: 300000,
|
|
fallbackBehavior: 'show_default'
|
|
}
|
|
};
|
|
|
|
await this.notificationService.scheduleDualNotification(dualConfig);
|
|
this.log('✅ Electron community notification scheduled successfully');
|
|
this.updateStatus('Scheduled');
|
|
} catch (error) {
|
|
this.log(`❌ Electron scheduling failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
private async testEndorserAPI(): Promise<void> {
|
|
try {
|
|
this.log('Testing Endorser.ch API integration on Electron...');
|
|
const config = this.configLoader.getConfig();
|
|
const testData = config.testData;
|
|
|
|
// Test parallel API requests pattern
|
|
const requests = [
|
|
// Offers to person
|
|
fetch(`${this.configLoader.getEndorserUrl('offers')}?recipientId=${testData.userDid}&afterId=${testData.lastKnownOfferId}`, {
|
|
headers: this.configLoader.getAuthHeaders()
|
|
}),
|
|
|
|
// Offers to user's projects
|
|
fetch(`${this.configLoader.getEndorserUrl('offersToPlans')}?afterId=${testData.lastKnownOfferId}`, {
|
|
headers: this.configLoader.getAuthHeaders()
|
|
}),
|
|
|
|
// Changes to starred projects
|
|
fetch(this.configLoader.getEndorserUrl('plansLastUpdated'), {
|
|
method: 'POST',
|
|
headers: this.configLoader.getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
planIds: testData.starredPlanIds,
|
|
afterId: testData.lastKnownPlanId
|
|
})
|
|
})
|
|
];
|
|
|
|
const [offersToPerson, offersToProjects, starredChanges] = await Promise.all(requests);
|
|
|
|
const notificationData = {
|
|
offersToPerson: await offersToPerson.json(),
|
|
offersToProjects: await offersToProjects.json(),
|
|
starredChanges: await starredChanges.json()
|
|
};
|
|
|
|
this.log('✅ Endorser.ch API integration successful on Electron', {
|
|
offersToPerson: notificationData.offersToPerson.data?.length || 0,
|
|
offersToProjects: notificationData.offersToProjects.data?.length || 0,
|
|
starredChanges: notificationData.starredChanges.data?.length || 0
|
|
});
|
|
|
|
this.updateStatus('API Connected');
|
|
} catch (error) {
|
|
this.log(`❌ Endorser.ch API test failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
private async testCallbacks(): Promise<void> {
|
|
try {
|
|
this.log('Testing TimeSafari Electron notification callbacks...');
|
|
// const config = this.configLoader.getConfig();
|
|
|
|
// Register offers callback
|
|
await this.notificationService.registerCallback('offers', async (event: Record<string, unknown>) => {
|
|
this.log('📨 Electron Offers callback triggered', event);
|
|
await this.handleOffersNotification(event);
|
|
});
|
|
|
|
// Register projects callback
|
|
await this.notificationService.registerCallback('projects', async (event: Record<string, unknown>) => {
|
|
this.log('📨 Electron Projects callback triggered', event);
|
|
await this.handleProjectsNotification(event);
|
|
});
|
|
|
|
// Register people callback
|
|
await this.notificationService.registerCallback('people', async (event: Record<string, unknown>) => {
|
|
this.log('📨 Electron People callback triggered', event);
|
|
await this.handlePeopleNotification(event);
|
|
});
|
|
|
|
// Register items callback
|
|
await this.notificationService.registerCallback('items', async (event: Record<string, unknown>) => {
|
|
this.log('📨 Electron Items callback triggered', event);
|
|
await this.handleItemsNotification(event);
|
|
});
|
|
|
|
this.log('✅ All Electron callbacks registered successfully');
|
|
this.updateStatus('Callbacks Registered');
|
|
} catch (error) {
|
|
this.log(`❌ Electron callback registration failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
private async testDebugInfo(): Promise<void> {
|
|
try {
|
|
this.log('Testing Electron debug info...');
|
|
const debugInfo = {
|
|
platform: 'electron',
|
|
nodeVersion: process.versions.node,
|
|
electronVersion: process.versions.electron,
|
|
chromeVersion: process.versions.chrome,
|
|
status: 'running',
|
|
config: this.configLoader.getConfig().timesafari,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
this.log('🔍 Electron Debug Info:', debugInfo);
|
|
this.updateStatus(`Debug: ${debugInfo.status}`);
|
|
} catch (error) {
|
|
this.log(`❌ Debug info failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
private async testPerformance() {
|
|
try {
|
|
this.log('Testing Electron performance metrics...');
|
|
const metrics = {
|
|
overallScore: 82,
|
|
databasePerformance: 85,
|
|
memoryEfficiency: 78,
|
|
batteryEfficiency: 80,
|
|
objectPoolEfficiency: 85,
|
|
totalDatabaseQueries: 100,
|
|
averageMemoryUsage: 30.2,
|
|
objectPoolHits: 25,
|
|
backgroundCpuUsage: 3.1,
|
|
totalNetworkRequests: 15,
|
|
recommendations: ['Optimize IPC communication', 'Reduce memory usage']
|
|
};
|
|
|
|
this.log('📊 Electron Performance Metrics:', metrics);
|
|
this.updateStatus(`Performance: ${metrics.overallScore}/100`);
|
|
} catch (error) {
|
|
this.log(`❌ Performance check failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process Endorser.ch notification bundle using parallel API requests
|
|
*/
|
|
private async processEndorserNotificationBundle(data: Record<string, unknown>): Promise<void> {
|
|
try {
|
|
this.log('Processing Endorser.ch notification bundle on Electron...');
|
|
|
|
// Process each notification type
|
|
if (data.offersToPerson?.data?.length > 0) {
|
|
await this.handleOffersNotification(data.offersToPerson);
|
|
}
|
|
|
|
if (data.starredChanges?.data?.length > 0) {
|
|
await this.handleProjectsNotification(data.starredChanges);
|
|
}
|
|
|
|
this.log('✅ Electron notification bundle processed successfully');
|
|
} catch (error) {
|
|
this.log(`❌ Electron bundle processing failed: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle offers notification events from Endorser.ch API
|
|
*/
|
|
private async handleOffersNotification(event: Record<string, unknown>): Promise<void> {
|
|
this.log('Handling Electron offers notification:', event);
|
|
|
|
if (event.data && event.data.length > 0) {
|
|
// Process OfferSummaryArrayMaybeMoreBody format
|
|
event.data.forEach((offer: Record<string, unknown>) => {
|
|
this.log('Processing Electron offer:', {
|
|
jwtId: offer.jwtId,
|
|
handleId: offer.handleId,
|
|
offeredByDid: offer.offeredByDid,
|
|
recipientDid: offer.recipientDid,
|
|
objectDescription: offer.objectDescription
|
|
});
|
|
});
|
|
|
|
// Check if there are more offers to fetch
|
|
if (event.hitLimit) {
|
|
const lastOffer = event.data[event.data.length - 1];
|
|
this.log('More offers available, last JWT ID:', lastOffer.jwtId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle projects notification events from Endorser.ch API
|
|
*/
|
|
private async handleProjectsNotification(event: Record<string, unknown>): Promise<void> {
|
|
this.log('Handling Electron projects notification:', event);
|
|
|
|
if (event.data && event.data.length > 0) {
|
|
// Process PlanSummaryAndPreviousClaimArrayMaybeMore format
|
|
event.data.forEach((planData: Record<string, unknown>) => {
|
|
const { plan, wrappedClaimBefore } = planData;
|
|
this.log('Processing Electron project change:', {
|
|
jwtId: plan.jwtId,
|
|
handleId: plan.handleId,
|
|
name: plan.name,
|
|
issuerDid: plan.issuerDid,
|
|
hasPreviousClaim: !!wrappedClaimBefore
|
|
});
|
|
});
|
|
|
|
// Check if there are more project changes to fetch
|
|
if (event.hitLimit) {
|
|
const lastPlan = event.data[event.data.length - 1];
|
|
this.log('More project changes available, last JWT ID:', lastPlan.plan.jwtId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle people notification events
|
|
*/
|
|
private async handlePeopleNotification(event: Record<string, unknown>): Promise<void> {
|
|
this.log('Handling Electron people notification:', event);
|
|
// Implementation would process people data and update local state
|
|
}
|
|
|
|
/**
|
|
* Handle items notification events
|
|
*/
|
|
private async handleItemsNotification(event: Record<string, unknown>): Promise<void> {
|
|
this.log('Handling Electron items notification:', event);
|
|
// Implementation would process items data and update local state
|
|
}
|
|
|
|
private log(message: string, data?: Record<string, unknown>): void {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const logEntry = document.createElement('div');
|
|
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
|
|
if (data) {
|
|
logEntry.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
|
}
|
|
this.logElement.appendChild(logEntry);
|
|
this.logElement.scrollTop = this.logElement.scrollHeight;
|
|
}
|
|
|
|
private clearLog(): void {
|
|
this.logElement.innerHTML = '';
|
|
this.log('Log cleared');
|
|
}
|
|
|
|
private updateStatus(status: string): void {
|
|
this.statusElement.textContent = status;
|
|
}
|
|
|
|
// Phase 4: TimeSafari component test methods
|
|
private async testSecurityManager(): Promise<void> {
|
|
try {
|
|
this.log('🔐 Testing SecurityManager (Electron)...');
|
|
|
|
// const timeSafariUser = this.configLoader.getTimeSafariUser();
|
|
|
|
// Test JWT generation
|
|
const jwt = await this.securityManager.generateJWT({
|
|
scope: 'test',
|
|
audience: 'test-api'
|
|
});
|
|
|
|
if (jwt) {
|
|
this.log('✅ JWT generation successful');
|
|
this.log('JWT token:', jwt.substring(0, 50) + '...');
|
|
|
|
// Test JWT verification
|
|
const claims = await this.securityManager.verifyJWT(jwt);
|
|
if (claims) {
|
|
this.log('✅ JWT verification successful');
|
|
this.log('Claims:', claims);
|
|
} else {
|
|
this.log('❌ JWT verification failed');
|
|
}
|
|
} else {
|
|
this.log('❌ JWT generation failed');
|
|
}
|
|
|
|
// Test operation history
|
|
const history = this.securityManager.getOperationHistory();
|
|
this.log(`Security operations performed: ${history.length}`);
|
|
|
|
this.log('SecurityManager test completed');
|
|
|
|
} catch (error) {
|
|
this.log('SecurityManager test failed:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async testEndorserAPIClient(): Promise<void> {
|
|
try {
|
|
this.log('🌐 Testing EndorserAPIClient (Electron)...');
|
|
|
|
const timeSafariUser = this.configLoader.getTimeSafariUser();
|
|
|
|
// Test offers to person
|
|
this.log('Testing offers to person...');
|
|
const offersResponse = await this.endorserAPIClient.fetchOffersToPerson(
|
|
timeSafariUser.activeDid,
|
|
timeSafariUser.lastKnownOfferId,
|
|
undefined
|
|
);
|
|
|
|
this.log(`✅ Offers fetched: ${offersResponse.data.length} offers`);
|
|
if (offersResponse.data.length > 0) {
|
|
this.log('Sample offer:', offersResponse.data[0]);
|
|
}
|
|
|
|
// Test offers to projects
|
|
this.log('Testing offers to projects...');
|
|
const projectsResponse = await this.endorserAPIClient.fetchOffersToProjectsOwnedByMe(
|
|
timeSafariUser.lastKnownOfferId
|
|
);
|
|
|
|
this.log(`✅ Project offers fetched: ${projectsResponse.data.length} offers`);
|
|
|
|
// Test project updates
|
|
if (timeSafariUser.starredPlanIds && timeSafariUser.starredPlanIds.length > 0) {
|
|
this.log('Testing project updates...');
|
|
const updatesResponse = await this.endorserAPIClient.fetchProjectsLastUpdated(
|
|
timeSafariUser.starredPlanIds,
|
|
timeSafariUser.lastKnownPlanId,
|
|
undefined
|
|
);
|
|
|
|
this.log(`✅ Project updates fetched: ${updatesResponse.data.length} updates`);
|
|
}
|
|
|
|
this.log('EndorserAPIClient test completed');
|
|
|
|
} catch (error) {
|
|
this.log('EndorserAPIClient test failed:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async testTimeSafariNotificationManager(): Promise<void> {
|
|
try {
|
|
this.log('📱 Testing TimeSafariNotificationManager (Electron)...');
|
|
|
|
// Test notification generation
|
|
this.log('Generating TimeSafari notifications...');
|
|
const notifications = await this.timeSafariNotificationManager.generateNotifications({
|
|
forceFetch: false,
|
|
includeMetadata: true,
|
|
filterByPriority: true,
|
|
maxNotifications: 10,
|
|
cacheTtl: 300000
|
|
});
|
|
|
|
this.log(`✅ Generated ${notifications.length} notifications`);
|
|
|
|
// Display notification details
|
|
notifications.forEach((notification, index) => {
|
|
this.log(`Notification ${index + 1}:`, {
|
|
type: notification.type,
|
|
subtype: notification.subtype,
|
|
priority: notification.notificationPriority,
|
|
timestamp: new Date(notification.timestamp).toISOString(),
|
|
disabled: notification.disabled,
|
|
sound: notification.sound,
|
|
vibration: notification.vibration
|
|
});
|
|
});
|
|
|
|
// Test statistics
|
|
const stats = this.timeSafariNotificationManager.getStatistics();
|
|
this.log('Manager statistics:', stats);
|
|
|
|
this.log('TimeSafariNotificationManager test completed');
|
|
|
|
} catch (error) {
|
|
this.log('TimeSafariNotificationManager test failed:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async testPhase4Integration(): Promise<void> {
|
|
try {
|
|
this.log('🚀 Testing complete Phase 4 integration (Electron)...');
|
|
|
|
const timeSafariUser = this.configLoader.getTimeSafariUser();
|
|
|
|
// Test complete workflow
|
|
this.log('Step 1: Security authentication...');
|
|
const jwt = await this.securityManager.generateJWT({
|
|
scope: 'notifications',
|
|
audience: 'endorser-api'
|
|
});
|
|
|
|
if (!jwt) {
|
|
throw new Error('JWT generation failed');
|
|
}
|
|
|
|
this.endorserAPIClient.setAuthToken(jwt);
|
|
this.log('✅ Authentication successful');
|
|
|
|
this.log('Step 2: Fetching TimeSafari data...');
|
|
const bundle = await this.endorserAPIClient.fetchAllTimeSafariNotifications({
|
|
activeDid: timeSafariUser.activeDid,
|
|
starredPlanIds: timeSafariUser.starredPlanIds || [],
|
|
lastKnownOfferId: timeSafariUser.lastKnownOfferId,
|
|
lastKnownPlanId: timeSafariUser.lastKnownPlanId,
|
|
fetchOffersToPerson: true,
|
|
fetchOffersToProjects: true,
|
|
fetchProjectUpdates: true,
|
|
notificationPreferences: {
|
|
offers: true,
|
|
projects: true,
|
|
people: false,
|
|
items: true
|
|
}
|
|
});
|
|
|
|
this.log(`✅ Data fetched successfully: ${bundle.success}`);
|
|
this.log('Bundle metadata:', bundle.metadata);
|
|
|
|
this.log('Step 3: Generating notifications...');
|
|
const notifications = await this.timeSafariNotificationManager.generateNotifications({
|
|
forceFetch: true,
|
|
includeMetadata: true,
|
|
maxNotifications: 20
|
|
});
|
|
|
|
this.log(`✅ Generated ${notifications.length} notifications`);
|
|
|
|
// Summary
|
|
this.log('🎉 Phase 4 integration test completed successfully!');
|
|
this.log('Summary:', {
|
|
securityInitialized: this.securityManager.isInitialized(),
|
|
apiClientReady: !!this.endorserAPIClient,
|
|
managerInitialized: this.timeSafariNotificationManager.isInitialized(),
|
|
notificationsGenerated: notifications.length,
|
|
bundleSuccess: bundle.success
|
|
});
|
|
|
|
} catch (error) {
|
|
this.log('Phase 4 integration test failed:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
// Static Daily Reminder Methods
|
|
private async scheduleDailyReminder(): Promise<void> {
|
|
try {
|
|
this.log('Scheduling daily reminder...');
|
|
|
|
const reminderOptions = {
|
|
id: (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin',
|
|
title: (document.getElementById('reminder-title') as HTMLInputElement)?.value || 'Good Morning!',
|
|
body: (document.getElementById('reminder-body') as HTMLInputElement)?.value || 'Time to check your TimeSafari community updates',
|
|
time: (document.getElementById('reminder-time') as HTMLInputElement)?.value || '09:00',
|
|
sound: (document.getElementById('reminder-sound') as HTMLInputElement)?.checked ?? true,
|
|
vibration: (document.getElementById('reminder-vibration') as HTMLInputElement)?.checked ?? true,
|
|
priority: (document.getElementById('reminder-priority') as HTMLSelectElement)?.value || 'normal',
|
|
repeatDaily: (document.getElementById('reminder-repeat') as HTMLInputElement)?.checked ?? true
|
|
};
|
|
|
|
this.log('Reminder options:', reminderOptions);
|
|
|
|
await this.plugin.scheduleDailyReminder(reminderOptions);
|
|
this.log('✅ Daily reminder scheduled successfully');
|
|
this.errorDisplay.showSuccess('Daily reminder scheduled successfully!');
|
|
|
|
} catch (error) {
|
|
this.log('❌ Failed to schedule daily reminder:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async cancelDailyReminder(): Promise<void> {
|
|
try {
|
|
this.log('Cancelling daily reminder...');
|
|
|
|
const reminderId = (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin';
|
|
|
|
await this.plugin.cancelDailyReminder(reminderId);
|
|
this.log('✅ Daily reminder cancelled successfully');
|
|
this.errorDisplay.showSuccess('Daily reminder cancelled successfully!');
|
|
|
|
} catch (error) {
|
|
this.log('❌ Failed to cancel daily reminder:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async getScheduledReminders(): Promise<void> {
|
|
try {
|
|
this.log('Getting scheduled reminders...');
|
|
|
|
const result = await this.plugin.getScheduledReminders();
|
|
this.log('✅ Scheduled reminders retrieved:', result);
|
|
|
|
if (result.reminders && result.reminders.length > 0) {
|
|
this.errorDisplay.showSuccess(`Found ${result.reminders.length} scheduled reminders`);
|
|
// eslint-disable-next-line no-console
|
|
console.table(result.reminders);
|
|
} else {
|
|
this.errorDisplay.showInfo('No scheduled reminders found');
|
|
}
|
|
|
|
} catch (error) {
|
|
this.log('❌ Failed to get scheduled reminders:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
|
|
private async updateDailyReminder(): Promise<void> {
|
|
try {
|
|
this.log('Updating daily reminder...');
|
|
|
|
const reminderId = (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin';
|
|
|
|
const updateOptions = {
|
|
title: (document.getElementById('reminder-title') as HTMLInputElement)?.value,
|
|
body: (document.getElementById('reminder-body') as HTMLInputElement)?.value,
|
|
time: (document.getElementById('reminder-time') as HTMLInputElement)?.value,
|
|
sound: (document.getElementById('reminder-sound') as HTMLInputElement)?.checked,
|
|
vibration: (document.getElementById('reminder-vibration') as HTMLInputElement)?.checked,
|
|
priority: (document.getElementById('reminder-priority') as HTMLSelectElement)?.value,
|
|
repeatDaily: (document.getElementById('reminder-repeat') as HTMLInputElement)?.checked
|
|
};
|
|
|
|
this.log('Update options:', updateOptions);
|
|
|
|
await this.plugin.updateDailyReminder(reminderId, updateOptions);
|
|
this.log('✅ Daily reminder updated successfully');
|
|
this.errorDisplay.showSuccess('Daily reminder updated successfully!');
|
|
|
|
} catch (error) {
|
|
this.log('❌ Failed to update daily reminder:', error);
|
|
this.errorDisplay.showError(error as Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize app when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new TimeSafariElectronTestApp();
|
|
});
|
|
|