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