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