cleanup: remove redundant and overlapping files
- Remove older capacitor integration examples and guides - Remove duplicate timesafari integration examples - Remove redundant daily notification setup guides - Remove overlapping architecture diagrams and decision guides - Keep only the clean, final versions: - capacitor-platform-service-clean-integration.ts - capacitor-platform-service-clean-changes.md - daily-notification-timesafari-setup.ts - timesafari-integration-example.ts - host-request-configuration.md This reduces file proliferation and keeps only the essential, non-overlapping documentation and examples.
This commit is contained in:
@@ -1,804 +0,0 @@
|
||||
/**
|
||||
* TimeSafari PWA - CapacitorPlatformService ActiveDid Integration Example
|
||||
*
|
||||
* This example shows how to extend the existing CapacitorPlatformService
|
||||
* to handle activeDid changes and integrate with the DailyNotification plugin.
|
||||
*
|
||||
* This represents the ACTUAL CHANGES needed to handle activeDid changes
|
||||
* in the existing TimeSafari PWA CapacitorPlatformService.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
// =================================================
|
||||
// EXISTING TIMESAFARI PWA CODE (unchanged)
|
||||
// =================================================
|
||||
|
||||
import { Filesystem, Directory, Encoding } from "@capacitor/filesystem";
|
||||
import {
|
||||
Camera,
|
||||
CameraResultType,
|
||||
CameraSource,
|
||||
CameraDirection,
|
||||
} from "@capacitor/camera";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { Share } from "@capacitor/share";
|
||||
import {
|
||||
SQLiteConnection,
|
||||
SQLiteDBConnection,
|
||||
CapacitorSQLite,
|
||||
DBSQLiteValues,
|
||||
} from "@capacitor-community/sqlite";
|
||||
|
||||
import { runMigrations } from "@/db-sql/migration";
|
||||
import { QueryExecResult } from "@/interfaces/database";
|
||||
import {
|
||||
ImageResult,
|
||||
PlatformService,
|
||||
PlatformCapabilities,
|
||||
} from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
// =================================================
|
||||
// NEW IMPORTS FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
import { DailyNotification } from '@timesafari/daily-notification-plugin';
|
||||
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
|
||||
|
||||
// =================================================
|
||||
// EXISTING INTERFACES (unchanged)
|
||||
// =================================================
|
||||
|
||||
interface QueuedOperation {
|
||||
type: "run" | "query" | "rawQuery";
|
||||
sql: string;
|
||||
params: unknown[];
|
||||
resolve: (value: unknown) => void;
|
||||
reject: (reason: unknown) => void;
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW INTERFACES FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXTENDED CapacitorPlatformService with DailyNotification and ActiveDid Integration
|
||||
*
|
||||
* This shows the ACTUAL CHANGES needed to the existing TimeSafari PWA
|
||||
* CapacitorPlatformService class to handle activeDid changes and integrate
|
||||
* with the DailyNotification plugin.
|
||||
*/
|
||||
export class CapacitorPlatformService implements PlatformService {
|
||||
// =================================================
|
||||
// EXISTING PROPERTIES (unchanged)
|
||||
// =================================================
|
||||
|
||||
/** Current camera direction */
|
||||
private currentDirection: CameraDirection = CameraDirection.Rear;
|
||||
|
||||
private sqlite: SQLiteConnection;
|
||||
private db: SQLiteDBConnection | null = null;
|
||||
private dbName = "timesafari.sqlite";
|
||||
private initialized = false;
|
||||
private initializationPromise: Promise<void> | null = null;
|
||||
private operationQueue: Array<QueuedOperation> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
|
||||
// =================================================
|
||||
// NEW PROPERTIES FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
private dailyNotificationService: DailyNotification | null = null;
|
||||
private integrationService: TimeSafariIntegrationService | null = null;
|
||||
private dailyNotificationInitialized = false;
|
||||
|
||||
// ActiveDid change tracking
|
||||
private currentActiveDid: string | null = null;
|
||||
private activeDidChangeListeners: Array<(newDid: string | null, oldDid: string | null) => void> = [];
|
||||
|
||||
// =================================================
|
||||
// EXISTING CONSTRUCTOR (unchanged)
|
||||
// =================================================
|
||||
|
||||
constructor() {
|
||||
this.sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// MODIFIED METHOD: Enhanced updateActiveDid with DailyNotification Integration
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Enhanced updateActiveDid method that handles DailyNotification plugin updates
|
||||
*
|
||||
* This method extends the existing updateActiveDid method to also update
|
||||
* the DailyNotification plugin when the activeDid changes.
|
||||
*/
|
||||
async updateActiveDid(did: string): Promise<void> {
|
||||
const oldDid = this.currentActiveDid;
|
||||
|
||||
// Update the database (existing TimeSafari pattern)
|
||||
await this.dbExec(
|
||||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||
[did],
|
||||
);
|
||||
|
||||
// Update local tracking
|
||||
this.currentActiveDid = did;
|
||||
|
||||
// Notify listeners of the change
|
||||
this.notifyActiveDidChange(did, oldDid);
|
||||
|
||||
// Update DailyNotification plugin if initialized
|
||||
if (this.dailyNotificationInitialized) {
|
||||
await this.updateDailyNotificationActiveDid(did, oldDid);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] ActiveDid updated from ${oldDid} to ${did}`
|
||||
);
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Update DailyNotification Plugin ActiveDid
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Update DailyNotification plugin when activeDid changes
|
||||
*
|
||||
* This method reconfigures the DailyNotification plugin with the new
|
||||
* activeDid and updates all related settings.
|
||||
*/
|
||||
private async updateDailyNotificationActiveDid(newDid: string, oldDid: string | null): Promise<void> {
|
||||
try {
|
||||
logger.log(`[CapacitorPlatformService] Updating DailyNotification plugin activeDid from ${oldDid} to ${newDid}`);
|
||||
|
||||
// Get new settings for the new activeDid
|
||||
const newSettings = await this.getTimeSafariSettings();
|
||||
|
||||
// Reconfigure DailyNotification plugin with new activeDid
|
||||
await DailyNotification.configure({
|
||||
timesafariConfig: {
|
||||
activeDid: newDid,
|
||||
endpoints: {
|
||||
offersToPerson: `${newSettings.apiServer}/api/v2/offers/person`,
|
||||
offersToPlans: `${newSettings.apiServer}/api/v2/offers/plans`,
|
||||
projectsLastUpdated: `${newSettings.apiServer}/api/v2/report/plansLastUpdatedBetween`
|
||||
},
|
||||
starredProjectsConfig: {
|
||||
enabled: true,
|
||||
starredPlanHandleIds: newSettings.starredPlanHandleIds || [],
|
||||
lastAckedJwtId: newSettings.lastAckedStarredPlanChangesJwtId || '',
|
||||
fetchInterval: '0 8 * * *'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update TimeSafari Integration Service
|
||||
if (this.integrationService) {
|
||||
await this.integrationService.initialize({
|
||||
activeDid: newDid,
|
||||
storageAdapter: this.getTimeSafariStorageAdapter(),
|
||||
endorserApiBaseUrl: newSettings.apiServer || 'https://endorser.ch'
|
||||
});
|
||||
}
|
||||
|
||||
logger.log(`[CapacitorPlatformService] DailyNotification plugin updated successfully for activeDid: ${newDid}`);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[CapacitorPlatformService] Failed to update DailyNotification plugin activeDid:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: ActiveDid Change Listener Management
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Add listener for activeDid changes
|
||||
*
|
||||
* This method allows components to register listeners that will be
|
||||
* notified when the activeDid changes.
|
||||
*/
|
||||
addActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void {
|
||||
this.activeDidChangeListeners.push(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listener for activeDid changes
|
||||
*/
|
||||
removeActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void {
|
||||
const index = this.activeDidChangeListeners.indexOf(listener);
|
||||
if (index > -1) {
|
||||
this.activeDidChangeListeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners of activeDid change
|
||||
*/
|
||||
private notifyActiveDidChange(newDid: string | null, oldDid: string | null): void {
|
||||
this.activeDidChangeListeners.forEach(listener => {
|
||||
try {
|
||||
listener(newDid, oldDid);
|
||||
} catch (error) {
|
||||
logger.error('[CapacitorPlatformService] Error in activeDid change listener:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Initialize DailyNotification Plugin
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Initialize DailyNotification plugin with TimeSafari configuration
|
||||
*
|
||||
* This method should be called after the database is initialized
|
||||
* to set up the DailyNotification plugin with TimeSafari-specific settings.
|
||||
*/
|
||||
async initializeDailyNotification(): Promise<void> {
|
||||
if (this.dailyNotificationInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.log("[CapacitorPlatformService] Initializing DailyNotification plugin...");
|
||||
|
||||
// Get current TimeSafari settings
|
||||
const settings = await this.getTimeSafariSettings();
|
||||
|
||||
// Get current activeDid
|
||||
const currentActiveDid = await this.getCurrentActiveDid();
|
||||
|
||||
// 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 current activeDid
|
||||
activeDid: currentActiveDid || '',
|
||||
|
||||
// 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, // Special retry for activeDid changes
|
||||
starredProjectsRetries: 3
|
||||
}
|
||||
},
|
||||
|
||||
// Network configuration using existing TimeSafari patterns
|
||||
networkConfig: {
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize TimeSafari Integration Service
|
||||
this.integrationService = TimeSafariIntegrationService.getInstance();
|
||||
await this.integrationService.initialize({
|
||||
activeDid: currentActiveDid || '',
|
||||
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.dailyNotificationInitialized = true;
|
||||
this.currentActiveDid = currentActiveDid;
|
||||
|
||||
logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
|
||||
|
||||
} catch (error) {
|
||||
logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get Current ActiveDid
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get current activeDid from the database
|
||||
*
|
||||
* This method retrieves the current activeDid from the active_identity table
|
||||
* using the existing TimeSafari pattern.
|
||||
*/
|
||||
private async getCurrentActiveDid(): Promise<string | null> {
|
||||
try {
|
||||
const result = await this.dbQuery(
|
||||
"SELECT activeDid FROM active_identity WHERE id = 1"
|
||||
);
|
||||
|
||||
if (result?.values?.length) {
|
||||
const activeDid = result.values[0][0] as string | null;
|
||||
return activeDid;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.error("[CapacitorPlatformService] Error getting current activeDid:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Enhanced loadNewStarredProjectChanges
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* 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<StarredProjectsResponse> {
|
||||
// Ensure DailyNotification is initialized
|
||||
if (!this.dailyNotificationInitialized) {
|
||||
await this.initializeDailyNotification();
|
||||
}
|
||||
|
||||
const settings = await this.getTimeSafariSettings();
|
||||
const currentActiveDid = await this.getCurrentActiveDid();
|
||||
|
||||
if (!currentActiveDid || !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(
|
||||
currentActiveDid,
|
||||
settings.starredPlanHandleIds,
|
||||
settings.lastAckedStarredPlanChangesJwtId
|
||||
);
|
||||
|
||||
// Enhanced logging (optional)
|
||||
logger.log("[CapacitorPlatformService] Starred projects loaded successfully:", {
|
||||
count: starredProjectChanges.data.length,
|
||||
hitLimit: starredProjectChanges.hitLimit,
|
||||
planIds: settings.starredPlanHandleIds.length,
|
||||
activeDid: currentActiveDid
|
||||
});
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get TimeSafari Settings
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get TimeSafari settings using existing database patterns
|
||||
*
|
||||
* This method uses the existing TimeSafari database patterns to retrieve
|
||||
* settings that are needed for the DailyNotification plugin configuration.
|
||||
*/
|
||||
private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
|
||||
try {
|
||||
// Get current activeDid
|
||||
const currentActiveDid = await this.getCurrentActiveDid();
|
||||
|
||||
if (!currentActiveDid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Use existing TimeSafari settings retrieval pattern
|
||||
const result = await this.dbQuery(
|
||||
"SELECT * FROM settings WHERE accountDid = ?",
|
||||
[currentActiveDid]
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Map database columns to values (existing TimeSafari pattern)
|
||||
const settings: TimeSafariSettings = {};
|
||||
result.columns.forEach((column, index) => {
|
||||
if (column !== 'id') {
|
||||
settings[column] = result.values[0][index];
|
||||
}
|
||||
});
|
||||
|
||||
// Set activeDid from current value
|
||||
settings.activeDid = currentActiveDid;
|
||||
|
||||
// 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) {
|
||||
logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get TimeSafari Storage Adapter
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get TimeSafari storage adapter using existing patterns
|
||||
*
|
||||
* This method creates a storage adapter that uses the existing TimeSafari
|
||||
* database patterns for the DailyNotification plugin.
|
||||
*/
|
||||
private getTimeSafariStorageAdapter(): unknown {
|
||||
// Return existing TimeSafari storage adapter
|
||||
return {
|
||||
// Use existing TimeSafari storage patterns
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHODS: Callback Handlers
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Callback handler for successful starred projects fetch
|
||||
*/
|
||||
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
|
||||
// Enhanced logging (optional)
|
||||
logger.log("[CapacitorPlatformService] Starred projects success callback:", {
|
||||
count: data.data.length,
|
||||
hitLimit: data.hitLimit,
|
||||
activeDid: this.currentActiveDid
|
||||
});
|
||||
|
||||
// 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)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for starred projects fetch errors
|
||||
*/
|
||||
private async handleStarredProjectsError(error: Error): Promise<void> {
|
||||
// Same error handling as existing TimeSafari code
|
||||
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(),
|
||||
activeDid: this.currentActiveDid
|
||||
})]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for starred projects fetch completion
|
||||
*/
|
||||
private async handleStarredProjectsComplete(result: unknown): Promise<void> {
|
||||
// Handle completion
|
||||
logger.log("[CapacitorPlatformService] Starred projects fetch completed:", {
|
||||
result,
|
||||
activeDid: this.currentActiveDid
|
||||
});
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get DailyNotification Status
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get DailyNotification plugin status for debugging
|
||||
*/
|
||||
async getDailyNotificationStatus(): Promise<{
|
||||
initialized: boolean;
|
||||
platform: string;
|
||||
capabilities: PlatformCapabilities;
|
||||
currentActiveDid: string | null;
|
||||
}> {
|
||||
return {
|
||||
initialized: this.dailyNotificationInitialized,
|
||||
platform: Capacitor.getPlatform(),
|
||||
capabilities: this.getCapabilities(),
|
||||
currentActiveDid: this.currentActiveDid
|
||||
};
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// MODIFIED METHOD: Enhanced initializeDatabase
|
||||
// =================================================
|
||||
|
||||
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;
|
||||
|
||||
// NEW: 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;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// EXISTING METHODS (unchanged - showing key ones)
|
||||
// =================================================
|
||||
|
||||
private async _initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create/Open database
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
"no-encryption",
|
||||
1,
|
||||
false,
|
||||
);
|
||||
|
||||
await this.db.open();
|
||||
|
||||
// Run migrations
|
||||
await this.runCapacitorMigrations();
|
||||
|
||||
this.initialized = true;
|
||||
logger.log(
|
||||
"[CapacitorPlatformService] SQLite database initialized successfully",
|
||||
);
|
||||
|
||||
// Start processing the queue after initialization
|
||||
this.processQueue();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[CapacitorPlatformService] Error initializing SQLite database:",
|
||||
error,
|
||||
);
|
||||
throw new Error(
|
||||
"[CapacitorPlatformService] Failed to initialize database",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
|
||||
/**
|
||||
* Gets the capabilities of the Capacitor platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
const platform = Capacitor.getPlatform();
|
||||
|
||||
return {
|
||||
hasFileSystem: true,
|
||||
hasCamera: true,
|
||||
isMobile: true, // Capacitor is always mobile
|
||||
isIOS: platform === "ios",
|
||||
hasFileDownload: false, // Mobile platforms need sharing
|
||||
needsFileHandlingInstructions: true, // Mobile needs instructions
|
||||
isNativeApp: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbQuery
|
||||
*/
|
||||
async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
|
||||
await this.waitForInitialization();
|
||||
return this.queueOperation<QueryExecResult>("query", sql, params || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbExec
|
||||
*/
|
||||
async dbExec(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<{ changes: number; lastId?: number }> {
|
||||
await this.waitForInitialization();
|
||||
return this.queueOperation<{ changes: number; lastId?: number }>(
|
||||
"run",
|
||||
sql,
|
||||
params || [],
|
||||
);
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
|
||||
/**
|
||||
* Checks if running on Capacitor platform.
|
||||
* @returns true, as this is the Capacitor implementation
|
||||
*/
|
||||
isCapacitor(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on Electron platform.
|
||||
* @returns false, as this is Capacitor, not Electron
|
||||
*/
|
||||
isElectron(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on web platform.
|
||||
* @returns false, as this is not web
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
}
|
||||
@@ -1,664 +0,0 @@
|
||||
/**
|
||||
* TimeSafari PWA - CapacitorPlatformService Extension Example
|
||||
*
|
||||
* This example shows how to extend the existing CapacitorPlatformService
|
||||
* in the TimeSafari PWA to add DailyNotification plugin functionality.
|
||||
*
|
||||
* This represents the ACTUAL CHANGES needed to the existing TimeSafari PWA code.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
// =================================================
|
||||
// EXISTING TIMESAFARI PWA CODE (unchanged)
|
||||
// =================================================
|
||||
|
||||
import { Filesystem, Directory, Encoding } from "@capacitor/filesystem";
|
||||
import {
|
||||
Camera,
|
||||
CameraResultType,
|
||||
CameraSource,
|
||||
CameraDirection,
|
||||
} from "@capacitor/camera";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { Share } from "@capacitor/share";
|
||||
import {
|
||||
SQLiteConnection,
|
||||
SQLiteDBConnection,
|
||||
CapacitorSQLite,
|
||||
DBSQLiteValues,
|
||||
} from "@capacitor-community/sqlite";
|
||||
|
||||
import { runMigrations } from "@/db-sql/migration";
|
||||
import { QueryExecResult } from "@/interfaces/database";
|
||||
import {
|
||||
ImageResult,
|
||||
PlatformService,
|
||||
PlatformCapabilities,
|
||||
} from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
// =================================================
|
||||
// NEW IMPORTS FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
import { DailyNotification } from '@timesafari/daily-notification-plugin';
|
||||
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
|
||||
|
||||
// =================================================
|
||||
// EXISTING INTERFACES (unchanged)
|
||||
// =================================================
|
||||
|
||||
interface QueuedOperation {
|
||||
type: "run" | "query" | "rawQuery";
|
||||
sql: string;
|
||||
params: unknown[];
|
||||
resolve: (value: unknown) => void;
|
||||
reject: (reason: unknown) => void;
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW INTERFACES FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXTENDED CapacitorPlatformService with DailyNotification functionality
|
||||
*
|
||||
* This shows the ACTUAL CHANGES needed to the existing TimeSafari PWA
|
||||
* CapacitorPlatformService class to add DailyNotification plugin support.
|
||||
*/
|
||||
export class CapacitorPlatformService implements PlatformService {
|
||||
// =================================================
|
||||
// EXISTING PROPERTIES (unchanged)
|
||||
// =================================================
|
||||
|
||||
/** Current camera direction */
|
||||
private currentDirection: CameraDirection = CameraDirection.Rear;
|
||||
|
||||
private sqlite: SQLiteConnection;
|
||||
private db: SQLiteDBConnection | null = null;
|
||||
private dbName = "timesafari.sqlite";
|
||||
private initialized = false;
|
||||
private initializationPromise: Promise<void> | null = null;
|
||||
private operationQueue: Array<QueuedOperation> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
|
||||
// =================================================
|
||||
// NEW PROPERTIES FOR DAILYNOTIFICATION PLUGIN
|
||||
// =================================================
|
||||
|
||||
private dailyNotificationService: DailyNotification | null = null;
|
||||
private integrationService: TimeSafariIntegrationService | null = null;
|
||||
private dailyNotificationInitialized = false;
|
||||
|
||||
// =================================================
|
||||
// EXISTING CONSTRUCTOR (unchanged)
|
||||
// =================================================
|
||||
|
||||
constructor() {
|
||||
this.sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Initialize DailyNotification Plugin
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Initialize DailyNotification plugin with TimeSafari configuration
|
||||
*
|
||||
* This method should be called after the database is initialized
|
||||
* to set up the DailyNotification plugin with TimeSafari-specific settings.
|
||||
*/
|
||||
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({
|
||||
// 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: {
|
||||
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.dailyNotificationInitialized = true;
|
||||
logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
|
||||
|
||||
} catch (error) {
|
||||
logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Enhanced loadNewStarredProjectChanges
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* This is the ACTUAL METHOD that would replace the existing TimeSafari 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
|
||||
);
|
||||
|
||||
// Enhanced logging (optional)
|
||||
logger.log("[CapacitorPlatformService] 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
|
||||
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
|
||||
return { data: [], hitLimit: false };
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get TimeSafari Settings
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get TimeSafari settings using existing database patterns
|
||||
*
|
||||
* This method uses the existing TimeSafari database patterns to retrieve
|
||||
* settings that are needed for the DailyNotification plugin configuration.
|
||||
*/
|
||||
private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
|
||||
try {
|
||||
// Use existing TimeSafari settings retrieval pattern
|
||||
const result = await this.dbQuery(
|
||||
"SELECT * FROM settings WHERE id = 1"
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Map database columns to values (existing TimeSafari pattern)
|
||||
const settings: TimeSafariSettings = {};
|
||||
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) {
|
||||
logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get TimeSafari Storage Adapter
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get TimeSafari storage adapter using existing patterns
|
||||
*
|
||||
* This method creates a storage adapter that uses the existing TimeSafari
|
||||
* database patterns for the DailyNotification plugin.
|
||||
*/
|
||||
private getTimeSafariStorageAdapter(): unknown {
|
||||
// Return existing TimeSafari storage adapter
|
||||
return {
|
||||
// Use existing TimeSafari storage patterns
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHODS: Callback Handlers
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Callback handler for successful starred projects fetch
|
||||
*
|
||||
* This method handles successful starred projects fetches and stores
|
||||
* the results in the existing TimeSafari database for UI access.
|
||||
*/
|
||||
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
|
||||
// Enhanced logging (optional)
|
||||
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)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for starred projects fetch errors
|
||||
*
|
||||
* This method handles starred projects fetch errors and stores
|
||||
* the error information in the existing TimeSafari database for UI access.
|
||||
*/
|
||||
private async handleStarredProjectsError(error: Error): Promise<void> {
|
||||
// Same error handling as existing TimeSafari code
|
||||
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() })]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler for starred projects fetch completion
|
||||
*
|
||||
* This method handles the completion of starred projects fetches
|
||||
* and provides additional logging and cleanup.
|
||||
*/
|
||||
private async handleStarredProjectsComplete(result: unknown): Promise<void> {
|
||||
// Handle completion
|
||||
logger.log("[CapacitorPlatformService] Starred projects fetch completed:", result);
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// NEW METHOD: Get DailyNotification Status
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get DailyNotification plugin status for debugging
|
||||
*
|
||||
* This method provides status information about the DailyNotification
|
||||
* plugin for debugging and monitoring purposes.
|
||||
*/
|
||||
async getDailyNotificationStatus(): Promise<{
|
||||
initialized: boolean;
|
||||
platform: string;
|
||||
capabilities: PlatformCapabilities;
|
||||
}> {
|
||||
return {
|
||||
initialized: this.dailyNotificationInitialized,
|
||||
platform: Capacitor.getPlatform(),
|
||||
capabilities: this.getCapabilities()
|
||||
};
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// EXISTING METHODS (unchanged - showing key ones)
|
||||
// =================================================
|
||||
|
||||
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;
|
||||
|
||||
// NEW: 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;
|
||||
}
|
||||
}
|
||||
|
||||
private async _initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create/Open database
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
"no-encryption",
|
||||
1,
|
||||
false,
|
||||
);
|
||||
|
||||
await this.db.open();
|
||||
|
||||
// Set journal mode to WAL for better performance
|
||||
// await this.db.execute("PRAGMA journal_mode=WAL;");
|
||||
|
||||
// Run migrations
|
||||
await this.runCapacitorMigrations();
|
||||
|
||||
this.initialized = true;
|
||||
logger.log(
|
||||
"[CapacitorPlatformService] SQLite database initialized successfully",
|
||||
);
|
||||
|
||||
// Start processing the queue after initialization
|
||||
this.processQueue();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[CapacitorPlatformService] Error initializing SQLite database:",
|
||||
error,
|
||||
);
|
||||
throw new Error(
|
||||
"[CapacitorPlatformService] Failed to initialize database",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
|
||||
/**
|
||||
* Gets the capabilities of the Capacitor platform
|
||||
* @returns Platform capabilities object
|
||||
*/
|
||||
getCapabilities(): PlatformCapabilities {
|
||||
const platform = Capacitor.getPlatform();
|
||||
|
||||
return {
|
||||
hasFileSystem: true,
|
||||
hasCamera: true,
|
||||
isMobile: true, // Capacitor is always mobile
|
||||
isIOS: platform === "ios",
|
||||
hasFileDownload: false, // Mobile platforms need sharing
|
||||
needsFileHandlingInstructions: true, // Mobile needs instructions
|
||||
isNativeApp: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbQuery
|
||||
*/
|
||||
async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
|
||||
await this.waitForInitialization();
|
||||
return this.queueOperation<QueryExecResult>("query", sql, params || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PlatformService.dbExec
|
||||
*/
|
||||
async dbExec(
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<{ changes: number; lastId?: number }> {
|
||||
await this.waitForInitialization();
|
||||
return this.queueOperation<{ changes: number; lastId?: number }>(
|
||||
"run",
|
||||
sql,
|
||||
params || [],
|
||||
);
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
|
||||
/**
|
||||
* Checks if running on Capacitor platform.
|
||||
* @returns true, as this is the Capacitor implementation
|
||||
*/
|
||||
isCapacitor(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on Electron platform.
|
||||
* @returns false, as this is Capacitor, not Electron
|
||||
*/
|
||||
isElectron(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if running on web platform.
|
||||
* @returns false, as this is not web
|
||||
*/
|
||||
isWeb(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... (all other existing methods remain unchanged)
|
||||
}
|
||||
@@ -1,594 +0,0 @@
|
||||
/**
|
||||
* 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()
|
||||
};
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
/**
|
||||
* 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