docs: add actual changes needed to existing CapacitorPlatformService

- Add example showing exact changes to existing TimeSafari PWA CapacitorPlatformService
- Show how to extend the existing class rather than creating a new one
- Provide summary of actual code changes needed (imports, properties, methods)
- Include modifications to PlatformServiceMixin and Vue components
- Show how to integrate with existing database and settings patterns
- Provide migration strategy for gradual adoption

This gives an accurate representation of the changes needed to the actual
TimeSafari PWA codebase rather than creating a separate class.
This commit is contained in:
Matthew Raymer
2025-10-08 08:20:00 +00:00
parent a3c92ec45e
commit f72bba23b5
2 changed files with 1091 additions and 0 deletions

View File

@@ -0,0 +1,427 @@
# TimeSafari PWA - CapacitorPlatformService Changes Summary
**Author**: Matthew Raymer
**Version**: 1.0.0
**Created**: 2025-10-08 06:24:57 UTC
## Overview
This document summarizes the **exact changes** needed to the existing TimeSafari PWA `CapacitorPlatformService` to add DailyNotification plugin functionality. These are the actual modifications required to the existing codebase.
## Required Changes to Existing TimeSafari PWA Code
### File: `src/services/platforms/CapacitorPlatformService.ts`
#### 1. Add New Imports (at the top of the file)
```typescript
// ADD THESE IMPORTS
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
```
#### 2. Add New Interfaces (after existing interfaces)
```typescript
// ADD THESE INTERFACES
interface PlanSummaryAndPreviousClaim {
id: string;
title: string;
description: string;
lastUpdated: string;
previousClaim?: unknown;
}
interface StarredProjectsResponse {
data: Array<PlanSummaryAndPreviousClaim>;
hitLimit: boolean;
}
interface TimeSafariSettings {
accountDid?: string;
activeDid?: string;
apiServer?: string;
starredPlanHandleIds?: string[];
lastAckedStarredPlanChangesJwtId?: string;
[key: string]: unknown;
}
```
#### 3. Add New Properties (in the class, after existing properties)
```typescript
export class CapacitorPlatformService implements PlatformService {
// ... existing properties ...
// ADD THESE NEW PROPERTIES
private dailyNotificationService: DailyNotification | null = null;
private integrationService: TimeSafariIntegrationService | null = null;
private dailyNotificationInitialized = false;
}
```
#### 4. Add New Methods (in the class, after existing methods)
```typescript
/**
* Initialize DailyNotification plugin with TimeSafari configuration
*/
async initializeDailyNotification(): Promise<void> {
if (this.dailyNotificationInitialized) {
return;
}
try {
logger.log("[CapacitorPlatformService] Initializing DailyNotification plugin...");
// Get current TimeSafari settings
const settings = await this.getTimeSafariSettings();
// Configure DailyNotification plugin with TimeSafari data
await DailyNotification.configure({
timesafariConfig: {
activeDid: settings.activeDid || '',
endpoints: {
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
},
starredProjectsConfig: {
enabled: true,
starredPlanHandleIds: settings.starredPlanHandleIds || [],
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
fetchInterval: '0 8 * * *'
}
},
networkConfig: {
baseURL: settings.apiServer || 'https://endorser.ch',
timeout: 30000
}
});
// Initialize TimeSafari Integration Service
this.integrationService = TimeSafariIntegrationService.getInstance();
await this.integrationService.initialize({
activeDid: settings.activeDid || '',
storageAdapter: this.getTimeSafariStorageAdapter(),
endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch'
});
this.dailyNotificationInitialized = true;
logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
} catch (error) {
logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
throw error;
}
}
/**
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
*/
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
// Ensure DailyNotification is initialized
if (!this.dailyNotificationInitialized) {
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
);
return starredProjectChanges;
} catch (error) {
// Same error handling as existing TimeSafari code
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
return { data: [], hitLimit: false };
}
}
/**
* Get TimeSafari settings using existing database patterns
*/
private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
try {
const result = await this.dbQuery("SELECT * FROM settings WHERE id = 1");
if (!result?.values?.length) {
return {};
}
const settings: TimeSafariSettings = {};
result.columns.forEach((column, index) => {
if (column !== 'id') {
settings[column] = result.values[0][index];
}
});
// Handle JSON field parsing
if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') {
try {
settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds);
} catch {
settings.starredPlanHandleIds = [];
}
}
return settings;
} catch (error) {
logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
return {};
}
}
/**
* Get TimeSafari storage adapter using existing patterns
*/
private getTimeSafariStorageAdapter(): unknown {
return {
store: async (key: string, value: unknown) => {
await this.dbExec(
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
[key, JSON.stringify(value)]
);
},
retrieve: async (key: string) => {
const result = await this.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 for DailyNotification plugin
*/
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
logger.log("[CapacitorPlatformService] Starred projects success callback:", {
count: data.data.length,
hitLimit: data.hitLimit
});
// Store results in TimeSafari temp table for UI access
await this.dbExec(
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
['starred_projects_latest', JSON.stringify(data)]
);
}
private async handleStarredProjectsError(error: Error): Promise<void> {
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
// Store error in TimeSafari temp table for UI access
await this.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> {
logger.log("[CapacitorPlatformService] Starred projects fetch completed:", result);
}
/**
* Get DailyNotification plugin status for debugging
*/
async getDailyNotificationStatus(): Promise<{
initialized: boolean;
platform: string;
capabilities: PlatformCapabilities;
}> {
return {
initialized: this.dailyNotificationInitialized,
platform: Capacitor.getPlatform(),
capabilities: this.getCapabilities()
};
}
```
#### 5. Modify Existing Method (update the `initializeDatabase` method)
```typescript
private async initializeDatabase(): Promise<void> {
// If already initialized, return immediately
if (this.initialized) {
return;
}
// If initialization is in progress, wait for it
if (this.initializationPromise) {
return this.initializationPromise;
}
try {
// Start initialization
this.initializationPromise = this._initialize();
await this.initializationPromise;
// ADD THIS LINE: Initialize DailyNotification after database is ready
await this.initializeDailyNotification();
} catch (error) {
logger.error(
"[CapacitorPlatformService] Initialize database method failed:",
error,
);
this.initializationPromise = null; // Reset on failure
throw error;
}
}
```
## Required Changes to PlatformServiceMixin
### File: `src/utils/PlatformServiceMixin.ts`
#### 1. Add New Methods (in the methods section)
```typescript
methods: {
// ... existing methods ...
/**
* Initialize DailyNotification plugin (Capacitor only)
*/
async $initializeDailyNotification(): Promise<void> {
if (this.isCapacitor) {
await this.platformService.initializeDailyNotification();
}
},
/**
* Enhanced loadNewStarredProjectChanges method
*/
async $loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
if (this.isCapacitor) {
return await this.platformService.loadNewStarredProjectChanges();
} else {
// Fall back to existing web method
return await this.$loadNewStarredProjectChangesWeb();
}
},
/**
* Get DailyNotification status for debugging
*/
async $getDailyNotificationStatus(): Promise<unknown> {
if (this.isCapacitor) {
return await this.platformService.getDailyNotificationStatus();
}
return { initialized: false, platform: 'web' };
}
}
```
## Required Changes to Vue Components
### File: `src/views/HomeView.vue` (or similar)
#### 1. Add DailyNotification Initialization (in mounted lifecycle)
```typescript
export default defineComponent({
name: 'HomeView',
mixins: [PlatformServiceMixin],
async mounted() {
// ADD THIS: Initialize DailyNotification (only on Capacitor)
if (this.isCapacitor) {
await this.$initializeDailyNotification();
}
// ... existing mounted code ...
},
methods: {
// ... existing methods ...
/**
* Enhanced loadNewStarredProjectChanges method
*/
async loadNewStarredProjectChanges(): Promise<void> {
if (this.isCapacitor) {
// Use plugin-enhanced method on Capacitor
const result = await this.$loadNewStarredProjectChanges();
this.numNewStarredProjectChanges = result.data.length;
this.newStarredProjectChangesHitLimit = result.hitLimit;
} else {
// Use existing web method in browser
await this.loadNewStarredProjectChangesWeb();
}
}
}
});
```
## Package.json Changes
### Add DailyNotification Plugin Dependency
```json
{
"dependencies": {
"@timesafari/daily-notification-plugin": "ssh://git@173.199.124.46:222/trent_larson/daily-notification-plugin.git"
}
}
```
## Summary of Changes
### Files Modified:
1. **`src/services/platforms/CapacitorPlatformService.ts`**
- Add imports for DailyNotification plugin
- Add new interfaces for plugin integration
- Add new properties for plugin state
- Add new methods for plugin functionality
- Modify existing `initializeDatabase` method
2. **`src/utils/PlatformServiceMixin.ts`**
- Add new methods for plugin integration
- Add platform detection for plugin usage
3. **Vue Components (e.g., `src/views/HomeView.vue`)**
- Add plugin initialization in mounted lifecycle
- Add enhanced methods for plugin functionality
4. **`package.json`**
- Add DailyNotification plugin dependency
### Key Benefits:
- **Same Interface**: Existing methods work exactly the same
- **Enhanced Functionality**: Background fetching, structured logging, error handling
- **Platform Detection**: Only initializes on Capacitor platforms
- **Gradual Migration**: Can be adopted incrementally
- **No Breaking Changes**: Existing code continues to work
### Migration Strategy:
1. **Add the changes** to existing TimeSafari PWA code
2. **Test on Capacitor platforms** (Android, iOS)
3. **Verify web fallback** works correctly
4. **Gradually migrate** individual methods to use plugin features
5. **Leverage advanced features** like background fetching and observability
---
**These are the exact changes needed to integrate the DailyNotification plugin with the existing TimeSafari PWA CapacitorPlatformService architecture.**