feat: Update test-apps for TimeSafari integration with Endorser.ch API patterns

- Add comprehensive configuration system with timesafari-config.json
- Create shared config-loader.ts with TypeScript interfaces and mock services
- Update Android test app to use TimeSafari community notification patterns
- Update iOS test app with rolling window and community features
- Update Electron test app with desktop-specific TimeSafari integration
- Enhance test API server to simulate Endorser.ch API endpoints
- Add pagination support with afterId/beforeId parameters
- Implement parallel API requests pattern for offers, projects, people, items
- Add community analytics and notification bundle endpoints
- Update all test app UIs for TimeSafari-specific functionality
- Update README with comprehensive TimeSafari testing guide

All test apps now demonstrate:
- Real Endorser.ch API integration patterns
- TimeSafari community-building features
- Platform-specific optimizations (Android/iOS/Electron)
- Comprehensive error handling and performance monitoring
- Configuration-driven testing with type safety
This commit is contained in:
Matthew Raymer
2025-09-24 07:38:53 +00:00
parent 999b824a36
commit fe82fd2147
11 changed files with 3432 additions and 389 deletions

View File

@@ -89,15 +89,16 @@
</head>
<body>
<div class="container">
<h1>🍎 Daily Notification Plugin - iOS Test</h1>
<h1>🍎 TimeSafari Daily Notification - iOS Test</h1>
<div class="status" id="status">Ready</div>
<div class="button-grid">
<button id="configure">Configure Plugin</button>
<button id="schedule">Schedule Notification</button>
<button id="rolling-window">Maintain Window</button>
<button id="window-stats">Window Stats</button>
<button id="configure">Configure TimeSafari</button>
<button id="schedule">Schedule Community Notifications</button>
<button id="rolling-window">Maintain Rolling Window</button>
<button id="endorser-api">Test Endorser.ch API</button>
<button id="callbacks">Register Callbacks</button>
<button id="performance">Performance Metrics</button>
</div>

View File

@@ -1,76 +1,47 @@
import { Capacitor } from '@capacitor/core';
import { ConfigLoader, MockDailyNotificationService, TestLogger } from '../shared/config-loader';
// Mock plugin for development
const DailyNotification = {
async configure(options: any) {
console.log('Configure called:', options);
return Promise.resolve();
},
async scheduleDailyNotification(options: any) {
console.log('Schedule called:', options);
return Promise.resolve();
},
async maintainRollingWindow() {
console.log('Maintain rolling window called');
return Promise.resolve();
},
async getRollingWindowStats() {
return Promise.resolve({
stats: '64 pending notifications, 20 daily limit',
maintenanceNeeded: false,
timeUntilNextMaintenance: 900000
});
},
async getPerformanceMetrics() {
return Promise.resolve({
overallScore: 88,
databasePerformance: 92,
memoryEfficiency: 85,
batteryEfficiency: 90,
objectPoolEfficiency: 88,
totalDatabaseQueries: 120,
averageMemoryUsage: 22.3,
objectPoolHits: 38,
backgroundCpuUsage: 1.8,
totalNetworkRequests: 8,
recommendations: ['Enable background tasks', 'Optimize memory usage']
});
}
};
// Test interface
class TestApp {
// Test interface for TimeSafari iOS integration
class TimeSafariIOSTestApp {
private statusElement: HTMLElement;
private logElement: HTMLElement;
private configLoader: ConfigLoader;
private notificationService: MockDailyNotificationService;
private logger: TestLogger;
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());
this.setupEventListeners();
this.log('iOS Test app initialized');
this.log('TimeSafari iOS Test app initialized');
}
private setupEventListeners() {
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure());
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule());
document.getElementById('rolling-window')?.addEventListener('click', () => this.testRollingWindow());
document.getElementById('window-stats')?.addEventListener('click', () => this.testWindowStats());
document.getElementById('endorser-api')?.addEventListener('click', () => this.testEndorserAPI());
document.getElementById('callbacks')?.addEventListener('click', () => this.testCallbacks());
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance());
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog());
}
private async testConfigure() {
try {
this.log('Testing iOS configuration...');
await DailyNotification.configure({
storage: 'shared',
ttlSeconds: 1800,
prefetchLeadMinutes: 15,
enableETagSupport: true,
enableErrorHandling: true,
enablePerformanceOptimization: true
this.log('Testing TimeSafari iOS configuration...');
await this.configLoader.loadConfig();
const config = this.configLoader.getConfig();
await this.notificationService.initialize();
this.log('✅ TimeSafari iOS configuration successful', {
appId: config.timesafari.appId,
appName: config.timesafari.appName,
version: config.timesafari.version
});
this.log('✅ iOS Configuration successful');
this.updateStatus('Configured');
} catch (error) {
this.log(`❌ Configuration failed: ${error}`);
@@ -79,53 +50,277 @@ class TestApp {
private async testSchedule() {
try {
this.log('Testing iOS notification scheduling...');
await DailyNotification.scheduleDailyNotification({
url: 'https://api.example.com/daily-content',
time: '09:00',
title: 'Daily iOS Test Notification',
body: 'This is a test notification from the iOS test app'
});
this.log('✅ iOS Notification scheduled successfully');
this.log('Testing TimeSafari iOS 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('✅ iOS community notification scheduled successfully');
this.updateStatus('Scheduled');
} catch (error) {
this.log(`❌ iOS Scheduling failed: ${error}`);
this.log(`❌ iOS scheduling failed: ${error}`);
}
}
private async testRollingWindow() {
try {
this.log('Testing iOS rolling window maintenance...');
await DailyNotification.maintainRollingWindow();
this.log('✅ Rolling window maintenance completed');
// Simulate rolling window maintenance
const stats = {
stats: '64 pending notifications, 20 daily limit',
maintenanceNeeded: false,
timeUntilNextMaintenance: 900000
};
this.log('✅ Rolling window maintenance completed', stats);
this.updateStatus('Rolling Window Maintained');
} catch (error) {
this.log(`❌ Rolling window maintenance failed: ${error}`);
}
}
private async testWindowStats() {
private async testEndorserAPI() {
try {
this.log('Testing iOS rolling window stats...');
const stats = await DailyNotification.getRollingWindowStats();
this.log(`📊 Rolling Window Stats:`, stats);
this.updateStatus(`Window: ${stats.maintenanceNeeded ? 'Needs Maintenance' : 'OK'}`);
this.log('Testing Endorser.ch API integration on iOS...');
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 iOS', {
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(`Window stats check failed: ${error}`);
this.log(`Endorser.ch API test failed: ${error}`);
}
}
private async testCallbacks() {
try {
this.log('Testing TimeSafari iOS notification callbacks...');
const config = this.configLoader.getConfig();
// Register offers callback
await this.notificationService.registerCallback('offers', async (event: any) => {
this.log('📨 iOS Offers callback triggered', event);
await this.handleOffersNotification(event);
});
// Register projects callback
await this.notificationService.registerCallback('projects', async (event: any) => {
this.log('📨 iOS Projects callback triggered', event);
await this.handleProjectsNotification(event);
});
// Register people callback
await this.notificationService.registerCallback('people', async (event: any) => {
this.log('📨 iOS People callback triggered', event);
await this.handlePeopleNotification(event);
});
// Register items callback
await this.notificationService.registerCallback('items', async (event: any) => {
this.log('📨 iOS Items callback triggered', event);
await this.handleItemsNotification(event);
});
this.log('✅ All iOS callbacks registered successfully');
this.updateStatus('Callbacks Registered');
} catch (error) {
this.log(`❌ iOS callback registration failed: ${error}`);
}
}
private async testPerformance() {
try {
this.log('Testing iOS performance metrics...');
const metrics = await DailyNotification.getPerformanceMetrics();
this.log(`📊 iOS Performance Metrics:`, metrics);
const metrics = {
overallScore: 88,
databasePerformance: 92,
memoryEfficiency: 85,
batteryEfficiency: 90,
objectPoolEfficiency: 88,
totalDatabaseQueries: 120,
averageMemoryUsage: 22.3,
objectPoolHits: 38,
backgroundCpuUsage: 1.8,
totalNetworkRequests: 8,
recommendations: ['Enable background tasks', 'Optimize memory usage']
};
this.log('📊 iOS 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 on iOS...');
// 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('✅ iOS notification bundle processed successfully');
} catch (error) {
this.log(`❌ iOS bundle processing failed: ${error}`);
}
}
/**
* Handle offers notification events from Endorser.ch API
*/
private async handleOffersNotification(event: any): Promise<void> {
this.log('Handling iOS offers notification:', event);
if (event.data && event.data.length > 0) {
// Process OfferSummaryArrayMaybeMoreBody format
event.data.forEach((offer: any) => {
this.log('Processing iOS 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 iOS 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 iOS 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 iOS 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 iOS 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');
@@ -149,5 +344,5 @@ class TestApp {
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new TestApp();
new TimeSafariIOSTestApp();
});