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.
 
 
 
 
 
 

594 lines
18 KiB

/**
* TimeSafari PWA - DailyNotification Plugin Integration Example
*
* This example shows how to integrate the DailyNotification plugin with the existing
* TimeSafari PWA architecture, specifically the CapacitorPlatformService and
* PlatformServiceMixin patterns.
*
* @author Matthew Raymer
* @version 1.0.0
*/
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory';
import { Capacitor } from '@capacitor/core';
// TimeSafari PWA existing interfaces (from the actual project)
interface PlanSummaryAndPreviousClaim {
id: string;
title: string;
description: string;
lastUpdated: string;
previousClaim?: unknown;
}
interface StarredProjectsResponse {
data: Array<PlanSummaryAndPreviousClaim>;
hitLimit: boolean;
}
interface Settings {
accountDid?: string;
activeDid?: string;
apiServer?: string;
starredPlanHandleIds?: string[];
lastAckedStarredPlanChangesJwtId?: string;
[key: string]: unknown;
}
/**
* Enhanced CapacitorPlatformService with DailyNotification integration
*
* This extends the existing TimeSafari PWA CapacitorPlatformService to include
* DailyNotification plugin functionality while maintaining the same interface.
*/
export class EnhancedCapacitorPlatformService {
private platformService = PlatformServiceFactory.getInstance();
private dailyNotificationService: DailyNotification | null = null;
private integrationService: TimeSafariIntegrationService | null = null;
private initialized = false;
/**
* Initialize DailyNotification plugin with TimeSafari PWA configuration
*/
async initializeDailyNotification(): Promise<void> {
if (this.initialized) {
return;
}
try {
// Get current TimeSafari settings
const settings = await this.getTimeSafariSettings();
// Configure DailyNotification plugin with TimeSafari data
await DailyNotification.configure({
// Basic plugin configuration
storage: 'tiered',
ttlSeconds: 1800,
enableETagSupport: true,
enableErrorHandling: true,
enablePerformanceOptimization: true,
// TimeSafari-specific configuration
timesafariConfig: {
// Use existing TimeSafari activeDid
activeDid: settings.activeDid || '',
// Use existing TimeSafari API endpoints
endpoints: {
offersToPerson: `${settings.apiServer}/api/v2/offers/person`,
offersToPlans: `${settings.apiServer}/api/v2/offers/plans`,
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
},
// Configure starred projects fetching (matches existing TimeSafari pattern)
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: settings.starredPlanHandleIds || [],
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
fetchInterval: '0 8 * * *', // Daily at 8 AM
maxResults: 50,
hitLimitHandling: 'warn' // Same as existing TimeSafari error handling
},
// Sync configuration (optimized for TimeSafari use case)
syncConfig: {
enableParallel: true,
maxConcurrent: 3,
batchSize: 10,
timeout: 30000,
retryAttempts: 3
},
// Error policy (matches existing TimeSafari error handling)
errorPolicy: {
maxRetries: 3,
backoffMultiplier: 2,
activeDidChangeRetries: 5,
starredProjectsRetries: 3
}
},
// Network configuration using existing TimeSafari patterns
networkConfig: {
// Use existing TimeSafari HTTP client (if available)
baseURL: settings.apiServer || 'https://endorser.ch',
timeout: 30000,
retryAttempts: 3,
retryDelay: 1000,
maxConcurrent: 5,
// Headers matching TimeSafari pattern
defaultHeaders: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'TimeSafari-PWA/1.0.0'
}
},
// Content fetch configuration (replaces existing loadNewStarredProjectChanges)
contentFetch: {
enabled: true,
schedule: '0 8 * * *', // Daily at 8 AM
// Use existing TimeSafari request pattern
requestConfig: {
method: 'POST',
url: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`,
headers: {
'Authorization': 'Bearer ${jwt}',
'X-User-DID': '${activeDid}',
'Content-Type': 'application/json'
},
body: {
planIds: '${starredPlanHandleIds}',
afterId: '${lastAckedJwtId}'
}
},
// Callbacks that match TimeSafari error handling
callbacks: {
onSuccess: this.handleStarredProjectsSuccess.bind(this),
onError: this.handleStarredProjectsError.bind(this),
onComplete: this.handleStarredProjectsComplete.bind(this)
}
},
// Authentication configuration
authentication: {
jwt: {
secret: process.env.JWT_SECRET || 'timesafari-jwt-secret',
algorithm: 'HS256',
expirationMinutes: 60,
refreshThresholdMinutes: 10
}
},
// Observability configuration
logging: {
level: 'INFO',
enableRequestLogging: true,
enableResponseLogging: true,
enableErrorLogging: true,
redactSensitiveData: true
},
// Security configuration
security: {
certificatePinning: {
enabled: true,
pins: [
{
hostname: 'endorser.ch',
pins: ['sha256/YOUR_PIN_HERE']
}
]
}
}
});
// Initialize TimeSafari Integration Service
this.integrationService = TimeSafariIntegrationService.getInstance();
await this.integrationService.initialize({
activeDid: settings.activeDid || '',
storageAdapter: this.getTimeSafariStorageAdapter(),
endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch',
// Use existing TimeSafari request patterns
requestConfig: {
baseURL: settings.apiServer || 'https://endorser.ch',
timeout: 30000,
retryAttempts: 3
},
// Configure starred projects fetching
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: settings.starredPlanHandleIds || [],
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
fetchInterval: '0 8 * * *',
maxResults: 50
}
});
// Schedule daily notifications
await DailyNotification.scheduleDailyNotification({
title: 'TimeSafari Community Update',
body: 'You have new offers and project updates',
time: '09:00',
channel: 'timesafari_community_updates'
});
this.initialized = true;
console.log('DailyNotification plugin initialized successfully with TimeSafari PWA');
} catch (error) {
console.error('Failed to initialize DailyNotification plugin:', error);
throw error;
}
}
/**
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
*
* This replaces the existing method with plugin-enhanced functionality
* while maintaining the same interface and behavior.
*/
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
if (!this.initialized) {
await this.initializeDailyNotification();
}
const settings = await this.getTimeSafariSettings();
if (!settings.activeDid || !settings.starredPlanHandleIds?.length) {
return { data: [], hitLimit: false };
}
try {
// Use plugin's enhanced fetching with same interface as existing TimeSafari code
const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges(
settings.activeDid,
settings.starredPlanHandleIds,
settings.lastAckedStarredPlanChangesJwtId
);
// Enhanced logging (optional)
console.log('Starred projects loaded successfully:', {
count: starredProjectChanges.data.length,
hitLimit: starredProjectChanges.hitLimit,
planIds: settings.starredPlanHandleIds.length
});
return starredProjectChanges;
} catch (error) {
// Same error handling as existing TimeSafari code
console.warn('[TimeSafari] Failed to load starred project changes:', error);
return { data: [], hitLimit: false };
}
}
/**
* Get TimeSafari settings using existing PlatformServiceMixin pattern
*/
private async getTimeSafariSettings(): Promise<Settings> {
try {
// Use existing TimeSafari settings retrieval pattern
const result = await this.platformService.dbQuery(
"SELECT * FROM settings WHERE id = 1"
);
if (!result?.values?.length) {
return {};
}
// Map database columns to values (existing TimeSafari pattern)
const settings: Settings = {};
result.columns.forEach((column, index) => {
if (column !== 'id') {
settings[column] = result.values[0][index];
}
});
// Handle JSON field parsing (existing TimeSafari pattern)
if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') {
try {
settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds);
} catch {
settings.starredPlanHandleIds = [];
}
}
return settings;
} catch (error) {
console.error('Error getting TimeSafari settings:', error);
return {};
}
}
/**
* Get TimeSafari storage adapter using existing patterns
*/
private getTimeSafariStorageAdapter(): unknown {
// Return existing TimeSafari storage adapter
return {
// Use existing TimeSafari storage patterns
store: async (key: string, value: unknown) => {
await this.platformService.dbExec(
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
[key, JSON.stringify(value)]
);
},
retrieve: async (key: string) => {
const result = await this.platformService.dbQuery(
"SELECT data FROM temp WHERE id = ?",
[key]
);
if (result?.values?.length) {
try {
return JSON.parse(result.values[0][0] as string);
} catch {
return null;
}
}
return null;
}
};
}
/**
* Callback handlers that match existing TimeSafari error handling patterns
*/
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
// Enhanced logging (optional)
console.log('Starred projects success callback:', {
count: data.data.length,
hitLimit: data.hitLimit
});
// Store results in TimeSafari temp table for UI access
await this.platformService.dbExec(
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
['starred_projects_latest', JSON.stringify(data)]
);
}
private async handleStarredProjectsError(error: Error): Promise<void> {
// Same error handling as existing TimeSafari code
console.warn('[TimeSafari] Failed to load starred project changes:', error);
// Store error in TimeSafari temp table for UI access
await this.platformService.dbExec(
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
['starred_projects_error', JSON.stringify({ error: error.message, timestamp: Date.now() })]
);
}
private async handleStarredProjectsComplete(result: unknown): Promise<void> {
// Handle completion
console.log('Starred projects fetch completed:', result);
}
/**
* Get plugin status for debugging
*/
async getDailyNotificationStatus(): Promise<{
initialized: boolean;
platform: string;
capabilities: unknown;
}> {
return {
initialized: this.initialized,
platform: Capacitor.getPlatform(),
capabilities: this.platformService.getCapabilities()
};
}
}
/**
* Vue.js Mixin for DailyNotification integration with TimeSafari PWA
*
* This mixin extends the existing PlatformServiceMixin to include
* DailyNotification functionality while maintaining the same patterns.
*/
export const TimeSafariDailyNotificationMixin = {
data() {
return {
// Existing TimeSafari data
activeDid: '',
starredPlanHandleIds: [] as string[],
lastAckedStarredPlanChangesJwtId: '',
numNewStarredProjectChanges: 0,
newStarredProjectChangesHitLimit: false,
// Plugin integration
dailyNotificationService: null as DailyNotification | null,
integrationService: null as TimeSafariIntegrationService | null,
enhancedPlatformService: null as EnhancedCapacitorPlatformService | null
};
},
computed: {
// Existing TimeSafari computed properties
isCapacitor(): boolean {
return this.platformService.isCapacitor();
},
isWeb(): boolean {
return this.platformService.isWeb();
},
isElectron(): boolean {
return this.platformService.isElectron();
}
},
async mounted() {
// Initialize DailyNotification when component mounts (only on Capacitor)
if (this.isCapacitor) {
await this.initializeDailyNotification();
}
},
methods: {
/**
* Initialize DailyNotification plugin with TimeSafari configuration
*/
async initializeDailyNotification(): Promise<void> {
try {
// Create enhanced platform service
this.enhancedPlatformService = new EnhancedCapacitorPlatformService();
// Initialize the plugin
await this.enhancedPlatformService.initializeDailyNotification();
console.log('DailyNotification initialized successfully in TimeSafari PWA');
} catch (error) {
console.error('Failed to initialize DailyNotification in TimeSafari PWA:', error);
}
},
/**
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
*/
async loadNewStarredProjectChanges(): Promise<void> {
if (this.isCapacitor && this.enhancedPlatformService) {
// Use plugin-enhanced method on Capacitor
const result = await this.enhancedPlatformService.loadNewStarredProjectChanges();
this.numNewStarredProjectChanges = result.data.length;
this.newStarredProjectChangesHitLimit = result.hitLimit;
} else {
// Use existing web method in browser
await this.loadNewStarredProjectChangesWeb();
}
},
/**
* Existing web-only method (unchanged)
*/
async loadNewStarredProjectChangesWeb(): Promise<void> {
// Your existing web-only implementation
console.log('Using web-only method for starred projects');
},
/**
* Get DailyNotification status for debugging
*/
async getDailyNotificationStatus(): Promise<unknown> {
if (this.enhancedPlatformService) {
return await this.enhancedPlatformService.getDailyNotificationStatus();
}
return { initialized: false, platform: 'web' };
}
}
};
/**
* Usage example in a TimeSafari PWA Vue component
*/
export const TimeSafariHomeViewExample = {
name: 'TimeSafariHomeView',
mixins: [TimeSafariDailyNotificationMixin],
data() {
return {
// Existing TimeSafari data
activeDid: '',
starredPlanHandleIds: [] as string[],
lastAckedStarredPlanChangesJwtId: '',
numNewStarredProjectChanges: 0,
newStarredProjectChangesHitLimit: false
};
},
async mounted() {
// Load existing TimeSafari data
await this.loadTimeSafariData();
// Initialize DailyNotification (only on Capacitor)
if (this.isCapacitor) {
await this.initializeDailyNotification();
}
},
methods: {
/**
* Load existing TimeSafari data using PlatformServiceMixin
*/
async loadTimeSafariData(): Promise<void> {
try {
// Use existing TimeSafari settings pattern
const settings = await this.$settings();
this.activeDid = settings.activeDid || '';
this.starredPlanHandleIds = settings.starredPlanHandleIds || [];
this.lastAckedStarredPlanChangesJwtId = settings.lastAckedStarredPlanChangesJwtId || '';
} catch (error) {
console.error('Error loading TimeSafari data:', error);
}
},
/**
* Enhanced loadNewStarredProjectChanges method
*/
async loadNewStarredProjectChanges(): Promise<void> {
if (this.isCapacitor && this.enhancedPlatformService) {
// Use plugin-enhanced method on Capacitor
const result = await this.enhancedPlatformService.loadNewStarredProjectChanges();
this.numNewStarredProjectChanges = result.data.length;
this.newStarredProjectChangesHitLimit = result.hitLimit;
} else {
// Use existing web method in browser
await this.loadNewStarredProjectChangesWeb();
}
},
/**
* Existing web-only method (unchanged)
*/
async loadNewStarredProjectChangesWeb(): Promise<void> {
// Your existing web-only implementation
console.log('Using web-only method for starred projects');
}
}
};
/**
* Factory function to create enhanced platform service
*/
export function createEnhancedPlatformService(): EnhancedCapacitorPlatformService {
return new EnhancedCapacitorPlatformService();
}
/**
* Utility function to check if DailyNotification should be used
*/
export function shouldUseDailyNotification(): boolean {
return Capacitor.isNativePlatform();
}
/**
* Utility function to get platform-specific configuration
*/
export function getPlatformConfig(): {
usePlugin: boolean;
platform: string;
capabilities: unknown;
} {
const platform = Capacitor.getPlatform();
const platformService = PlatformServiceFactory.getInstance();
return {
usePlugin: Capacitor.isNativePlatform(),
platform,
capabilities: platformService.getCapabilities()
};
}