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.
 
 
 
 
 
 

403 lines
13 KiB

/**
* TimeSafari PWA Integration Example
*
* This example shows how to directly adopt the existing TimeSafari PWA
* request patterns (like loadNewStarredProjectChanges) into the Daily
* Notification Plugin configuration.
*
* @author Matthew Raymer
* @version 1.0.0
*/
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
import { AxiosInstance } from 'axios';
// TimeSafari PWA existing interfaces (for reference)
interface PlanSummaryAndPreviousClaim {
id: string;
title: string;
description: string;
lastUpdated: string;
previousClaim?: unknown;
}
interface StarredProjectsResponse {
data: Array<PlanSummaryAndPreviousClaim>;
hitLimit: boolean;
}
// TimeSafari PWA existing class (for reference)
class TimeSafariHomeView {
// Existing TimeSafari PWA properties
activeDid: string = '';
starredPlanHandleIds: string[] = [];
lastAckedStarredPlanChangesJwtId: string = '';
numNewStarredProjectChanges: number = 0;
newStarredProjectChangesHitLimit: boolean = false;
apiServer: string = 'https://endorser.ch';
axios: AxiosInstance;
// Plugin integration properties
private dailyNotificationService: DailyNotification | null = null;
private integrationService: TimeSafariIntegrationService | null = null;
constructor(axiosInstance: AxiosInstance) {
this.axios = axiosInstance;
}
/**
* Initialize Daily Notification Plugin with existing TimeSafari patterns
*/
async initializeDailyNotifications(): Promise<void> {
try {
// Configure plugin with existing TimeSafari configuration
await DailyNotification.configure({
// TimeSafari-specific configuration
timesafariConfig: {
activeDid: this.activeDid,
// Use existing TimeSafari API endpoints
endpoints: {
offersToPerson: `${this.apiServer}/api/v2/offers/person`,
offersToPlans: `${this.apiServer}/api/v2/offers/plans`,
projectsLastUpdated: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween`
},
// Configure starred projects fetching (matches existing TimeSafari pattern)
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: this.starredPlanHandleIds,
lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId,
fetchInterval: '0 8 * * *', // Daily at 8 AM
maxResults: 50,
hitLimitHandling: 'warn' // 'warn' | 'error' | 'ignore'
},
// Sync configuration matching TimeSafari patterns
syncConfig: {
enableParallel: true,
maxConcurrent: 3,
batchSize: 10,
timeout: 30000,
retryAttempts: 3
}
},
// Network configuration using existing TimeSafari patterns
networkConfig: {
// Use existing axios instance
httpClient: this.axios,
baseURL: this.apiServer,
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000,
// Headers matching TimeSafari pattern
defaultHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
},
// Content fetch configuration
contentFetch: {
enabled: true,
schedule: '0 8 * * *', // Daily at 8 AM
// Use existing TimeSafari request pattern
requestConfig: {
method: 'POST',
url: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween`,
headers: {
'Authorization': 'Bearer ${jwt}',
'X-User-DID': '${activeDid}'
},
body: {
planIds: '${starredPlanHandleIds}',
afterId: '${lastAckedJwtId}'
}
},
// Callbacks matching TimeSafari error handling
callbacks: {
onSuccess: this.handleStarredProjectsSuccess.bind(this),
onError: this.handleStarredProjectsError.bind(this),
onComplete: this.handleStarredProjectsComplete.bind(this)
}
}
});
// Initialize TimeSafari integration service
this.integrationService = TimeSafariIntegrationService.getInstance();
await this.integrationService.initialize({
activeDid: this.activeDid,
storageAdapter: this.getTimeSafariStorageAdapter(),
endorserApiBaseUrl: this.apiServer,
// Use existing TimeSafari request patterns
requestConfig: {
httpClient: this.axios,
baseURL: this.apiServer,
timeout: 30000,
retryAttempts: 3
},
// Configure starred projects fetching
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: this.starredPlanHandleIds,
lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId,
fetchInterval: '0 8 * * *',
maxResults: 50
}
});
console.log('Daily notifications initialized successfully with TimeSafari patterns');
} catch (error) {
console.error('Failed to initialize daily notifications:', error);
throw error;
}
}
/**
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
*
* This method replaces the existing TimeSafari PWA method with plugin-enhanced
* functionality while maintaining the same interface and behavior.
*/
async loadNewStarredProjectChanges(): Promise<void> {
if (this.activeDid && this.starredPlanHandleIds.length > 0) {
try {
// Use plugin's enhanced fetching with same interface as TimeSafari PWA
const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges(
this.activeDid,
this.starredPlanHandleIds,
this.lastAckedStarredPlanChangesJwtId
);
// Same handling as original TimeSafari PWA
this.numNewStarredProjectChanges = starredProjectChanges.data.length;
this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit;
// Enhanced logging with structured observability
console.log('Starred projects loaded successfully:', {
count: this.numNewStarredProjectChanges,
hitLimit: this.newStarredProjectChangesHitLimit,
planIds: this.starredPlanHandleIds.length
});
} catch (error) {
// Same error handling as original TimeSafari PWA
console.warn('[HomeView] Failed to load starred project changes:', error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
}
} else {
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
}
}
/**
* Callback handlers matching TimeSafari patterns
*/
async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
// Same handling as TimeSafari PWA
this.numNewStarredProjectChanges = data.data.length;
this.newStarredProjectChangesHitLimit = data.hitLimit;
// Update UI (existing TimeSafari method)
this.updateStarredProjectsUI(data);
// Enhanced logging
console.log('Starred projects success callback:', {
count: data.data.length,
hitLimit: data.hitLimit
});
}
async handleStarredProjectsError(error: Error): Promise<void> {
// Same error handling as TimeSafari PWA
console.warn('[HomeView] Failed to load starred project changes:', error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
// Enhanced error handling with structured logging
console.error('Starred projects error callback:', {
error: error.message,
activeDid: this.activeDid,
planCount: this.starredPlanHandleIds.length
});
}
async handleStarredProjectsComplete(result: unknown): Promise<void> {
// Handle completion
console.log('Starred projects fetch completed:', result);
}
/**
* Existing TimeSafari methods (unchanged)
*/
private updateStarredProjectsUI(data: StarredProjectsResponse): void {
// Existing TimeSafari UI update logic
console.log('Updating UI with starred projects data:', data);
}
private getTimeSafariStorageAdapter(): unknown {
// Return existing TimeSafari storage adapter
return {
// TimeSafari storage adapter implementation
};
}
}
// Usage example
export async function initializeTimeSafariWithPlugin(axiosInstance: AxiosInstance): Promise<TimeSafariHomeView> {
const homeView = new TimeSafariHomeView(axiosInstance);
// Set up TimeSafari data (existing pattern)
homeView.activeDid = 'did:example:123';
homeView.starredPlanHandleIds = ['plan1', 'plan2', 'plan3'];
homeView.lastAckedStarredPlanChangesJwtId = 'jwt123';
// Initialize plugin with TimeSafari patterns
await homeView.initializeDailyNotifications();
// Test the enhanced method
await homeView.loadNewStarredProjectChanges();
return homeView;
}
// Comparison example: Original vs Plugin-enhanced
export class TimeSafariComparisonExample {
private axios: AxiosInstance;
constructor(axiosInstance: AxiosInstance) {
this.axios = axiosInstance;
}
/**
* Original TimeSafari PWA method (for comparison)
*/
async loadNewStarredProjectChangesOriginal(
activeDid: string,
starredPlanHandleIds: string[],
lastAckedJwtId: string
): Promise<StarredProjectsResponse> {
if (!starredPlanHandleIds || starredPlanHandleIds.length === 0) {
return { data: [], hitLimit: false };
}
if (!lastAckedJwtId) {
return { data: [], hitLimit: false };
}
const url = `https://endorser.ch/api/v2/report/plansLastUpdatedBetween`;
const headers = await this.getHeaders(activeDid);
const requestBody = {
planIds: starredPlanHandleIds,
afterId: lastAckedJwtId,
};
const response = await this.axios.post(url, requestBody, { headers });
return response.data;
}
/**
* Plugin-enhanced version (same interface, enhanced functionality)
*/
async loadNewStarredProjectChangesEnhanced(
activeDid: string,
starredPlanHandleIds: string[],
lastAckedJwtId: string
): Promise<StarredProjectsResponse> {
// Use plugin's enhanced integration service
const integrationService = TimeSafariIntegrationService.getInstance();
return await integrationService.getStarredProjectsWithChanges(
activeDid,
starredPlanHandleIds,
lastAckedJwtId
);
}
/**
* Test both implementations in parallel
*/
async testBothImplementations(
activeDid: string,
starredPlanHandleIds: string[],
lastAckedJwtId: string
): Promise<void> {
console.log('Testing both implementations in parallel...');
const start = Date.now();
// Test original implementation
const originalResult = await this.loadNewStarredProjectChangesOriginal(
activeDid,
starredPlanHandleIds,
lastAckedJwtId
);
const originalTime = Date.now() - start;
const pluginStart = Date.now();
// Test plugin-enhanced implementation
const pluginResult = await this.loadNewStarredProjectChangesEnhanced(
activeDid,
starredPlanHandleIds,
lastAckedJwtId
);
const pluginTime = Date.now() - pluginStart;
// Compare results
console.log('Comparison Results:', {
original: {
time: originalTime,
count: originalResult.data.length,
hitLimit: originalResult.hitLimit
},
plugin: {
time: pluginTime,
count: pluginResult.data.length,
hitLimit: pluginResult.hitLimit
},
performance: {
improvement: ((originalTime - pluginTime) / originalTime * 100).toFixed(2) + '%'
}
});
// Verify results match
if (JSON.stringify(originalResult) === JSON.stringify(pluginResult)) {
console.log('✅ Results match perfectly!');
} else {
console.log('❌ Results differ:', {
original: originalResult,
plugin: pluginResult
});
}
}
private async getHeaders(activeDid: string): Promise<Record<string, string>> {
// Existing TimeSafari header generation
return {
'Authorization': `Bearer ${await this.getJWTToken(activeDid)}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-User-DID': activeDid
};
}
private async getJWTToken(activeDid: string): Promise<string> {
// Existing TimeSafari JWT token generation
return 'jwt-token-placeholder';
}
}
// Export for use in TimeSafari PWA
export { TimeSafariHomeView, TimeSafariComparisonExample };