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:
403
examples/timesafari-pwa-integration-example.ts
Normal file
403
examples/timesafari-pwa-integration-example.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user