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
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();
|
|
});
|
|
|