docs: add TimeSafari PWA request pattern adoption guide and example

- Add comprehensive guide for adopting existing TimeSafari PWA request patterns
- Show direct integration of loadNewStarredProjectChanges() and getStarredProjectsWithChanges()
- Provide Vue.js component integration examples
- Include migration strategy and testing approach
- Add practical example showing exact configuration needed
- Demonstrate parallel testing and performance comparison
- Show how to maintain existing TimeSafari interfaces while adding plugin features

This enables seamless adoption of the plugin into existing TimeSafari PWA codebase
while maintaining the same developer experience and user interface.
This commit is contained in:
Matthew Raymer
2025-10-08 07:19:46 +00:00
parent eaaa980167
commit ff166560df
2 changed files with 1085 additions and 0 deletions

View File

@@ -0,0 +1,403 @@
/**
* 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 };