Browse Source

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.
master
Matthew Raymer 3 days ago
parent
commit
ff166560df
  1. 682
      docs/timesafari-request-adoption-guide.md
  2. 403
      examples/timesafari-pwa-integration-example.ts

682
docs/timesafari-request-adoption-guide.md

@ -0,0 +1,682 @@
# TimeSafari Daily Notification Plugin - Request Pattern Adoption Guide
**Author**: Matthew Raymer
**Version**: 1.0.0
**Created**: 2025-10-08 06:24:57 UTC
## Overview
This guide shows how to directly adopt the existing TimeSafari PWA request patterns (like `loadNewStarredProjectChanges()` and `getStarredProjectsWithChanges()`) into the Daily Notification Plugin configuration. The plugin is designed to work seamlessly with TimeSafari's existing axios-based request architecture.
## Current TimeSafari PWA Pattern Analysis
### Original TimeSafari PWA Code
```typescript
// TimeSafari PWA - HomeView.vue
private async loadNewStarredProjectChanges() {
if (this.activeDid && this.starredPlanHandleIds.length > 0) {
try {
const starredProjectChanges = await getStarredProjectsWithChanges(
this.axios,
this.apiServer,
this.activeDid,
this.starredPlanHandleIds,
this.lastAckedStarredPlanChangesJwtId,
);
this.numNewStarredProjectChanges = starredProjectChanges.data.length;
this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit;
} catch (error) {
logger.warn("[HomeView] Failed to load starred project changes:", error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
}
}
}
// TimeSafari PWA - API function
export async function getStarredProjectsWithChanges(
axios: Axios,
apiServer: string,
activeDid: string,
starredPlanHandleIds: string[],
afterId?: string,
): Promise<{ data: Array<PlanSummaryAndPreviousClaim>; hitLimit: boolean }> {
const url = `${apiServer}/api/v2/report/plansLastUpdatedBetween`;
const headers = await getHeaders(activeDid);
const requestBody = {
planIds: starredPlanHandleIds,
afterId: afterId,
};
const response = await axios.post(url, requestBody, { headers });
return response.data;
}
```
## Plugin Configuration Adoption
### 1. Direct Axios Integration Pattern
The plugin can be configured to use the host's existing axios instance and request patterns:
```typescript
// In TimeSafari PWA - Plugin Configuration
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
// Configure plugin with existing TimeSafari patterns
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
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 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: '${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: async (data: { data: Array<PlanSummaryAndPreviousClaim>; hitLimit: boolean }) => {
// Handle successful fetch - same as TimeSafari PWA
this.numNewStarredProjectChanges = data.data.length;
this.newStarredProjectChangesHitLimit = data.hitLimit;
// Update UI
this.updateStarredProjectsUI(data);
},
onError: async (error: Error) => {
// Handle error - same as TimeSafari PWA
logger.warn("[DailyNotification] Failed to load starred project changes:", error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
},
onComplete: async (result: ContentFetchResult) => {
// Handle completion
logger.info("[DailyNotification] Starred projects fetch completed", {
success: result.success,
dataCount: result.data?.data?.length || 0,
hitLimit: result.data?.hitLimit || false
});
}
}
}
});
```
### 2. Enhanced TimeSafari Integration Service
The plugin provides an enhanced integration service that mirrors TimeSafari's patterns:
```typescript
// In TimeSafari PWA - Enhanced Integration
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
// Initialize with existing TimeSafari configuration
const integrationService = TimeSafariIntegrationService.getInstance();
await integrationService.initialize({
activeDid: this.activeDid,
storageAdapter: this.timeSafariStorageAdapter,
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
}
});
// Use the service to fetch starred projects (same pattern as TimeSafari PWA)
const fetchStarredProjects = async () => {
try {
const starredProjectChanges = await integrationService.getStarredProjectsWithChanges(
this.activeDid,
this.starredPlanHandleIds,
this.lastAckedStarredPlanChangesJwtId
);
// Same handling as TimeSafari PWA
this.numNewStarredProjectChanges = starredProjectChanges.data.length;
this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit;
} catch (error) {
// Same error handling as TimeSafari PWA
logger.warn("[DailyNotification] Failed to load starred project changes:", error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
}
};
```
### 3. Vue.js Component Integration
Direct integration into existing TimeSafari Vue components:
```typescript
// In TimeSafari PWA - HomeView.vue (enhanced)
import { defineComponent } from 'vue';
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
export default defineComponent({
name: 'HomeView',
data() {
return {
// Existing TimeSafari data
activeDid: '',
starredPlanHandleIds: [] as string[],
lastAckedStarredPlanChangesJwtId: '',
numNewStarredProjectChanges: 0,
newStarredProjectChangesHitLimit: false,
// Plugin integration
dailyNotificationService: null as DailyNotificationService | null,
integrationService: null as TimeSafariIntegrationService | null
};
},
async mounted() {
// Initialize plugin with existing TimeSafari configuration
await this.initializeDailyNotifications();
},
methods: {
async initializeDailyNotifications() {
try {
// Configure plugin with existing TimeSafari patterns
await DailyNotification.configure({
timesafariConfig: {
activeDid: this.activeDid,
endpoints: {
projectsLastUpdated: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween`
},
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: this.starredPlanHandleIds,
lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId,
fetchInterval: '0 8 * * *'
}
},
networkConfig: {
httpClient: this.axios,
baseURL: this.apiServer,
timeout: 30000
},
contentFetch: {
enabled: true,
schedule: '0 8 * * *',
callbacks: {
onSuccess: this.handleStarredProjectsSuccess,
onError: this.handleStarredProjectsError
}
}
});
// Initialize integration service
this.integrationService = TimeSafariIntegrationService.getInstance();
await this.integrationService.initialize({
activeDid: this.activeDid,
storageAdapter: this.timeSafariStorageAdapter,
endorserApiBaseUrl: this.apiServer
});
// Replace existing method with plugin-enhanced version
this.loadNewStarredProjectChanges = this.loadNewStarredProjectChangesEnhanced;
} catch (error) {
logger.error("[HomeView] Failed to initialize daily notifications:", error);
}
},
// Enhanced version of existing method
async loadNewStarredProjectChangesEnhanced() {
if (this.activeDid && this.starredPlanHandleIds.length > 0) {
try {
// Use plugin's enhanced fetching with same interface
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;
} catch (error) {
// Same error handling as original TimeSafari PWA
logger.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: { data: Array<PlanSummaryAndPreviousClaim>; hitLimit: boolean }) {
// Same handling as TimeSafari PWA
this.numNewStarredProjectChanges = data.data.length;
this.newStarredProjectChangesHitLimit = data.hitLimit;
// Update UI
this.updateStarredProjectsUI(data);
},
async handleStarredProjectsError(error: Error) {
// Same error handling as TimeSafari PWA
logger.warn("[HomeView] Failed to load starred project changes:", error);
this.numNewStarredProjectChanges = 0;
this.newStarredProjectChangesHitLimit = false;
},
// Existing TimeSafari methods (unchanged)
updateStarredProjectsUI(data: { data: Array<PlanSummaryAndPreviousClaim>; hitLimit: boolean }) {
// Existing TimeSafari UI update logic
this.$emit('starred-projects-updated', data);
}
}
});
```
## Plugin Implementation Details
### 1. Enhanced TimeSafari Integration Service
The plugin provides an enhanced version of TimeSafari's request patterns:
```typescript
// Plugin implementation - Enhanced TimeSafari Integration Service
export class TimeSafariIntegrationService {
private static instance: TimeSafariIntegrationService;
private httpClient: AxiosInstance | null = null;
private baseURL: string = '';
private activeDid: string = '';
static getInstance(): TimeSafariIntegrationService {
if (!TimeSafariIntegrationService.instance) {
TimeSafariIntegrationService.instance = new TimeSafariIntegrationService();
}
return TimeSafariIntegrationService.instance;
}
async initialize(config: {
activeDid: string;
httpClient: AxiosInstance;
baseURL: string;
storageAdapter: TimeSafariStorageAdapter;
endorserApiBaseUrl: string;
}): Promise<void> {
this.activeDid = config.activeDid;
this.httpClient = config.httpClient;
this.baseURL = config.baseURL;
// Initialize with existing TimeSafari patterns
await this.initializeStorage(config.storageAdapter);
await this.initializeAuthentication(config.endorserApiBaseUrl);
}
// Enhanced version of TimeSafari's getStarredProjectsWithChanges
async getStarredProjectsWithChanges(
activeDid: string,
starredPlanHandleIds: string[],
afterId?: string
): Promise<{ data: Array<PlanSummaryAndPreviousClaim>; hitLimit: boolean }> {
if (!starredPlanHandleIds || starredPlanHandleIds.length === 0) {
return { data: [], hitLimit: false };
}
if (!afterId) {
return { data: [], hitLimit: false };
}
try {
// Use existing TimeSafari request pattern
const url = `${this.baseURL}/api/v2/report/plansLastUpdatedBetween`;
const headers = await this.getHeaders(activeDid);
const requestBody = {
planIds: starredPlanHandleIds,
afterId: afterId,
};
// Use host's axios instance
const response = await this.httpClient!.post(url, requestBody, { headers });
// Log with structured logging
observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Starred projects fetched successfully', {
activeDid,
planCount: starredPlanHandleIds.length,
resultCount: response.data.data.length,
hitLimit: response.data.hitLimit
});
return response.data;
} catch (error) {
// Enhanced error handling with structured logging
observability.logEvent('ERROR', EVENT_CODES.FETCH_FAILURE, 'Failed to fetch starred projects', {
activeDid,
planCount: starredPlanHandleIds.length,
error: (error as Error).message
});
throw error;
}
}
// Enhanced version of TimeSafari's getHeaders
private async getHeaders(activeDid: string): Promise<Record<string, string>> {
// Use existing TimeSafari authentication pattern
const jwt = await this.getJWTToken(activeDid);
return {
'Authorization': `Bearer ${jwt}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-User-DID': activeDid
};
}
// Enhanced JWT token management
private async getJWTToken(activeDid: string): Promise<string> {
// Use existing TimeSafari JWT pattern
const cachedToken = await this.getCachedToken(activeDid);
if (cachedToken && !this.isTokenExpired(cachedToken)) {
return cachedToken;
}
// Generate new token using existing TimeSafari pattern
const newToken = await this.generateJWTToken(activeDid);
await this.cacheToken(activeDid, newToken);
return newToken;
}
}
```
### 2. Plugin Configuration Extensions
The plugin extends the configuration to support TimeSafari's existing patterns:
```typescript
// Plugin configuration extensions
export interface TimeSafariConfig {
activeDid: string;
// Existing TimeSafari endpoints
endpoints?: {
offersToPerson?: string;
offersToPlans?: string;
projectsLastUpdated?: string;
};
// Enhanced starred projects configuration
starredProjectsConfig?: {
enabled: boolean;
starredPlanHandleIds: string[];
lastAckedJwtId: string;
fetchInterval: string; // Cron expression
maxResults?: number;
hitLimitHandling?: 'warn' | 'error' | 'ignore';
};
// Existing TimeSafari sync configuration
syncConfig?: {
enableParallel?: boolean;
maxConcurrent?: number;
batchSize?: number;
timeout?: number;
retryAttempts?: number;
};
// Enhanced error policy
errorPolicy?: {
maxRetries?: number;
backoffMultiplier?: number;
activeDidChangeRetries?: number;
starredProjectsRetries?: number; // New
};
}
export interface NetworkConfig {
// Use existing TimeSafari axios instance
httpClient?: AxiosInstance;
baseURL?: string;
timeout?: number;
retryAttempts?: number;
retryDelay?: number;
// Existing TimeSafari headers
defaultHeaders?: Record<string, string>;
// Enhanced request configuration
requestConfig?: {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
headers?: Record<string, string>;
body?: Record<string, unknown>;
params?: Record<string, string>;
};
}
```
## Migration Strategy
### Phase 1: Parallel Implementation
1. **Keep existing TimeSafari PWA code unchanged**
2. **Add plugin configuration alongside existing code**
3. **Test plugin functionality in parallel**
4. **Compare results between existing and plugin implementations**
### Phase 2: Gradual Migration
1. **Replace individual request methods one by one**
2. **Use plugin's enhanced error handling and logging**
3. **Maintain existing UI and user experience**
4. **Add plugin-specific features (background fetching, etc.)**
### Phase 3: Full Integration
1. **Replace all TimeSafari request patterns with plugin**
2. **Remove duplicate code**
3. **Leverage plugin's advanced features**
4. **Optimize performance with plugin's caching and batching**
## Benefits of Plugin Adoption
### 1. Enhanced Error Handling
```typescript
// Existing TimeSafari PWA
catch (error) {
logger.warn("[HomeView] Failed to load starred project changes:", error);
this.numNewStarredProjectChanges = 0;
}
// Plugin-enhanced version
catch (error) {
// Structured logging with event IDs
observability.logEvent('WARN', EVENT_CODES.FETCH_FAILURE, 'Failed to load starred project changes', {
eventId: this.generateEventId(),
activeDid: this.activeDid,
planCount: this.starredPlanHandleIds.length,
error: error.message,
retryCount: this.retryCount
});
// Enhanced fallback handling
await this.handleStarredProjectsFallback(error);
}
```
### 2. Background Fetching
```typescript
// Plugin provides background fetching
await DailyNotification.configure({
contentFetch: {
enabled: true,
schedule: '0 8 * * *', // Daily at 8 AM
backgroundFetch: true, // Fetch in background
cachePolicy: {
maxAge: 3600, // 1 hour cache
staleWhileRevalidate: 1800 // 30 minutes stale
}
}
});
```
### 3. Enhanced Observability
```typescript
// Plugin provides comprehensive metrics
const metrics = await DailyNotification.getMetrics();
console.log('Starred Projects Metrics:', {
fetchSuccessRate: metrics.starredProjects.fetchSuccessRate,
averageResponseTime: metrics.starredProjects.averageResponseTime,
cacheHitRate: metrics.starredProjects.cacheHitRate,
errorRate: metrics.starredProjects.errorRate
});
```
## Testing Strategy
### 1. Parallel Testing
```typescript
// Test both implementations in parallel
const testStarredProjectsFetch = async () => {
// Existing TimeSafari PWA implementation
const existingResult = await getStarredProjectsWithChanges(
this.axios,
this.apiServer,
this.activeDid,
this.starredPlanHandleIds,
this.lastAckedStarredPlanChangesJwtId
);
// Plugin implementation
const pluginResult = await this.integrationService.getStarredProjectsWithChanges(
this.activeDid,
this.starredPlanHandleIds,
this.lastAckedStarredPlanChangesJwtId
);
// Compare results
assert.deepEqual(existingResult, pluginResult);
};
```
### 2. Performance Testing
```typescript
// Compare performance
const performanceTest = async () => {
const start = Date.now();
// Existing implementation
await getStarredProjectsWithChanges(...);
const existingTime = Date.now() - start;
const pluginStart = Date.now();
// Plugin implementation
await this.integrationService.getStarredProjectsWithChanges(...);
const pluginTime = Date.now() - pluginStart;
console.log('Performance Comparison:', {
existing: existingTime,
plugin: pluginTime,
improvement: ((existingTime - pluginTime) / existingTime * 100).toFixed(2) + '%'
});
};
```
## Conclusion
The Daily Notification Plugin is designed to seamlessly adopt TimeSafari's existing request patterns while providing enhanced functionality:
- **Same Interface**: Plugin methods match existing TimeSafari patterns
- **Enhanced Features**: Background fetching, structured logging, metrics
- **Gradual Migration**: Can be adopted incrementally
- **Backward Compatibility**: Existing code continues to work
- **Performance Improvements**: Caching, batching, and optimization
The plugin transforms TimeSafari's existing `loadNewStarredProjectChanges()` pattern into a more robust, observable, and efficient system while maintaining the same developer experience and user interface.
---
**Next Steps**:
1. Implement parallel testing with existing TimeSafari PWA code
2. Gradually migrate individual request methods
3. Leverage plugin's advanced features for enhanced user experience

403
examples/timesafari-pwa-integration-example.ts

@ -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 };
Loading…
Cancel
Save