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.
 
 
 
 
 
 

1124 lines
38 KiB

import { Capacitor } from '@capacitor/core';
import { DailyNotificationPlugin } from '@timesafari/daily-notification-plugin';
// Phase 4: Import TimeSafari components
import { EndorserAPIClient, TIMESAFARI_ENDSORER_CONFIG } from '../../../src/typescript/EndorserAPIClient';
import { SecurityManager, TIMESAFARI_SECURITY_CONFIG } from '../../../src/typescript/SecurityManager';
import { TimeSafariNotificationManager, DEFAULT_TIMESAFARI_PREFERENCES } from '../../../src/typescript/TimeSafariNotificationManager';
import {
TimeSafariUser,
TimeSafariPreferences,
EnhancedTimeSafariNotification,
TimeSafariNotificationType
} from '../../../src/definitions';
// Enhanced ConfigLoader for Phase 4
class ConfigLoader {
private static instance: ConfigLoader;
private config: any;
static getInstance(): ConfigLoader {
if (!this.instance) {
this.instance = new ConfigLoader();
}
return this.instance;
}
async loadConfig(): Promise<void> {
this.config = {
timesafari: {
appId: 'timesafari-android-test',
appName: 'TimeSafari Android Test',
version: '1.0.0'
},
scheduling: {
contentFetch: { schedule: '0 8 * * *' },
userNotification: { schedule: '0 9 * * *' }
},
endorser: {
baseUrl: 'http://10.0.2.2:3001/api/v2/report',
apiKey: 'test-api-key',
// Phase 4: Enhanced EndorserAPI configuration
timeoutMs: 15000,
maxRetries: 3,
enableParallel: true,
maxConcurrent: 3
},
security: {
// Phase 4: Security configuration
enableCryptoSigning: true,
enableJWTGeneration: true,
jwtExpirationMinutes: 60,
signatureAlgorithm: 'ES256K',
credentialStorage: 'secure_element'
},
testData: {
userDid: 'did:example:android-test-user',
lastKnownOfferId: '0',
lastKnownPlanId: '0',
starredPlanIds: ['plan:test-1', 'plan:test-2'],
favoritePersonIds: ['person:test-1'],
favoriteItemIds: ['item:test-1']
}
};
}
getConfig(): any {
return this.config;
}
getEndorserUrl(endpoint: string): string {
const endpoints: { [key: string]: string } = {
notificationsBundle: '/notifications/bundle',
offers: '/offersToPerson',
offersToPlans: '/offersToPlansOwnedByMe',
plansLastUpdated: '/plansLastUpdatedBetween'
};
return `${this.config.endorser.baseUrl}${endpoints[endpoint] || endpoint}`;
}
// Phase 4: Enhanced configuration methods
getEndorserAPIConfig() {
return {
baseUrl: this.config.endorser.baseUrl.replace('/api/v2/report', ''),
timeoutMs: this.config.endorser.timeoutMs,
maxRetries: this.config.endorser.maxRetries,
enableParallel: this.config.endorser.enableParallel,
maxConcurrent: this.config.endorser.maxConcurrent
};
}
getSecurityConfig() {
return {
enableCryptoSigning: this.config.security.enableCryptoSigning,
enableJWTGeneration: this.config.security.enableJWTGeneration,
jwtExpirationMinutes: this.config.security.jwtExpirationMinutes,
signatureAlgorithm: this.config.security.signatureAlgorithm,
credentialStorage: this.config.security.credentialStorage
};
}
getTimeSafariUser(): TimeSafariUser {
return {
activeDid: this.config.testData.userDid,
preferences: DEFAULT_TIMESAFARI_PREFERENCES,
starredPlanIds: this.config.testData.starredPlanIds,
favoritePersonIds: this.config.testData.favoritePersonIds,
favoriteItemIds: this.config.testData.favoriteItemIds,
lastKnownOfferId: this.config.testData.lastKnownOfferId,
lastKnownPlanId: this.config.testData.lastKnownPlanId
};
}
getAuthHeaders(): any {
return {
'Authorization': `Bearer ${this.config.endorser.apiKey}`,
'Content-Type': 'application/json'
};
}
}
class MockDailyNotificationService {
constructor(config: any) {
this.config = config;
}
private config: any;
async initialize(): Promise<void> {
console.log('Mock notification service initialized');
}
async scheduleDualNotification(config: any): Promise<void> {
console.log('Mock dual notification scheduled:', config);
}
async registerCallback(name: string, callback: Function): Promise<void> {
console.log(`Mock callback registered: ${name}`);
}
async getDualScheduleStatus(): Promise<any> {
return {
contentFetch: { enabled: true },
userNotification: { enabled: true }
};
}
}
class TestLogger {
constructor(level: string) {
console.log('Mock logger initialized with level:', level);
}
info(message: string, data?: any) {
console.log(`[INFO] ${message}`, data);
}
error(message: string, data?: any) {
console.error(`[ERROR] ${message}`, data);
}
debug(message: string, data?: any) {
console.log(`[DEBUG] ${message}`, data);
}
}
// Enhanced UI components for comprehensive 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 testing
const mockStatus = {
granted: true,
notifications: 'granted' as const,
backgroundRefresh: 'granted' as const
};
this.renderStatus(mockStatus);
}
private renderStatus(status: any): 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>Background Refresh:</span>
<span class="${status.backgroundRefresh === 'granted' ? 'granted' : 'denied'}">
${status.backgroundRefresh}
</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 Android integration with Phase 4 components
class TimeSafariAndroidTestApp {
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 Android 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('check-status')?.addEventListener('click', () => this.testStatus());
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('refresh-status')?.addEventListener('click', () => this.refreshStatus());
document.getElementById('battery-status')?.addEventListener('click', () => this.checkBatteryStatus());
document.getElementById('exact-alarm-status')?.addEventListener('click', () => this.checkExactAlarmStatus());
document.getElementById('reboot-recovery')?.addEventListener('click', () => this.checkRebootRecovery());
// 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());
}
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 checkBatteryStatus(): Promise<void> {
try {
this.log('Checking battery optimization status...');
// Mock battery status check
this.log('✅ Battery optimization: Not optimized (mock)');
} catch (error) {
this.log(`❌ Battery status check failed: ${error}`);
this.errorDisplay.showError(error as Error);
}
}
private async checkExactAlarmStatus(): Promise<void> {
try {
this.log('Checking exact alarm permission...');
// Mock exact alarm status
this.log('✅ Exact alarm permission: Granted (mock)');
} catch (error) {
this.log(`❌ Exact alarm check failed: ${error}`);
this.errorDisplay.showError(error as Error);
}
}
private async checkRebootRecovery(): Promise<void> {
try {
this.log('Checking reboot recovery status...');
// Mock reboot recovery status
this.log('✅ Reboot recovery: Active (mock)');
} catch (error) {
this.log(`❌ Reboot recovery check failed: ${error}`);
this.errorDisplay.showError(error as Error);
}
}
private async testConfigure() {
try {
this.log('Testing TimeSafari configuration...');
await this.configLoader.loadConfig();
const config = this.configLoader.getConfig();
await this.notificationService.initialize();
this.log('✅ TimeSafari 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() {
try {
this.log('Testing TimeSafari 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: any) => {
this.log('✅ Content fetch successful', data);
await this.processEndorserNotificationBundle(data);
},
onError: async (error: any) => {
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('✅ Community notification scheduled successfully');
this.updateStatus('Scheduled');
} catch (error) {
this.log(`❌ Scheduling failed: ${error}`);
}
}
private async testEndorserAPI() {
try {
this.log('Testing Endorser.ch API integration...');
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', {
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() {
try {
this.log('Testing TimeSafari notification callbacks...');
const config = this.configLoader.getConfig();
// Register offers callback
await this.notificationService.registerCallback('offers', async (event: any) => {
this.log('📨 Offers callback triggered', event);
await this.handleOffersNotification(event);
});
// Register projects callback
await this.notificationService.registerCallback('projects', async (event: any) => {
this.log('📨 Projects callback triggered', event);
await this.handleProjectsNotification(event);
});
// Register people callback
await this.notificationService.registerCallback('people', async (event: any) => {
this.log('📨 People callback triggered', event);
await this.handlePeopleNotification(event);
});
// Register items callback
await this.notificationService.registerCallback('items', async (event: any) => {
this.log('📨 Items callback triggered', event);
await this.handleItemsNotification(event);
});
this.log('✅ All callbacks registered successfully');
this.updateStatus('Callbacks Registered');
} catch (error) {
this.log(`❌ Callback registration failed: ${error}`);
}
}
private async testStatus() {
try {
this.log('Testing notification status...');
const status = await this.notificationService.getDualScheduleStatus();
this.log('📊 Notification Status:', status);
this.updateStatus(`Status: ${status.contentFetch.enabled ? 'Active' : 'Inactive'}`);
} catch (error) {
this.log(`❌ Status check failed: ${error}`);
}
}
private async testPerformance() {
try {
this.log('Testing Android performance metrics...');
const metrics = {
overallScore: 85,
databasePerformance: 90,
memoryEfficiency: 80,
batteryEfficiency: 85,
objectPoolEfficiency: 90,
totalDatabaseQueries: 150,
averageMemoryUsage: 25.5,
objectPoolHits: 45,
backgroundCpuUsage: 2.3,
totalNetworkRequests: 12,
recommendations: ['Enable ETag support', 'Optimize memory usage']
};
this.log('📊 Android 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: any): Promise<void> {
try {
this.log('Processing Endorser.ch notification bundle...');
// 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('✅ Notification bundle processed successfully');
} catch (error) {
this.log(`❌ Bundle processing failed: ${error}`);
}
}
/**
* Handle offers notification events from Endorser.ch API
*/
private async handleOffersNotification(event: any): Promise<void> {
this.log('Handling offers notification:', event);
if (event.data && event.data.length > 0) {
// Process OfferSummaryArrayMaybeMoreBody format
event.data.forEach((offer: any) => {
this.log('Processing 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: any): Promise<void> {
this.log('Handling projects notification:', event);
if (event.data && event.data.length > 0) {
// Process PlanSummaryAndPreviousClaimArrayMaybeMore format
event.data.forEach((planData: any) => {
const { plan, wrappedClaimBefore } = planData;
this.log('Processing 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: any): Promise<void> {
this.log('Handling people notification:', event);
// Implementation would process people data and update local state
}
/**
* Handle items notification events
*/
private async handleItemsNotification(event: any): Promise<void> {
this.log('Handling items notification:', event);
// Implementation would process items data and update local state
}
private log(message: string, data?: any) {
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() {
this.logElement.innerHTML = '';
this.log('Log cleared');
}
private updateStatus(status: string) {
this.statusElement.textContent = status;
}
// Phase 4: TimeSafari component test methods
private async testSecurityManager(): Promise<void> {
try {
this.log('🔐 Testing SecurityManager...');
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...');
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...');
// 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...');
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);
}
}
}
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new TimeSafariAndroidTestApp();
});