10 KiB
TimeSafari Daily Notification Plugin Integration Analysis
Author: Matthew Raymer
Version: 1.0.0
Created: 2025-10-02 11:00:00 UTC
Last Updated: 2025-10-02 11:00:00 UTC
Overview
This document analyzes how the Daily Notification Plugin would integrate with TimeSafari, focusing on activeDid
access and plugin hosting architecture.
ActiveDid Management in TimeSafari
Database Schema: active_identity
Table
Table Structure:
CREATE TABLE active_identity (
id INTEGER PRIMARY KEY DEFAULT 1,
activeDid TEXT NOT NULL,
lastUpdated DATETIME DEFAULT (datetime('now'))
);
Key Characteristics:
- Single row table with
id = 1
always activeDid
contains the currently active user's DIDlastUpdated
tracks when the active identity was switched- Single source of truth for current user identity
Access Methods
1. Platform Service Level:
// In CapacitorPlatformService, WebPlatformService, ElectronPlatformService
async updateActiveDid(did: string): Promise<void> {
await this.dbExec(
"INSERT OR REPLACE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, ?, ?)",
[did, new Date().toISOString()]
);
}
async getActiveIdentity(): Promise<{ activeDid: string }> {
const result = await this.dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
return {
activeDid: (result?.values?.[0]?.[0] as string) || ""
};
}
2. Vue Mixin Level:
// In PlatformServiceMixin.ts
async $updateActiveDid(newDid: string | null): Promise<void> {
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[newDid || ""]
);
}
async $getActiveIdentity(): Promise<{ activeDid: string }> {
const result = await this.$dbExec(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
return { activeDid: result.activeDid || "" };
}
3. Component Level Usage:
// In Vue components
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid || "";
Plugin Integration Options
Option 1: Host Application Manages activeDid
Pros:
- Simple integration
- Plugin doesn't need database access
- Clear separation of concerns
Implementation:
// In TimeSafari application
import { DailyNotification } from '@timesafari/daily-notification-plugin';
// On app initialization or user switch
async function initializeNotifications() {
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (activeDid) {
await DailyNotification.setActiveDid(activeDid);
await DailyNotification.configure({
apiServer: 'https://endorser.ch',
activeDid: activeDid,
// ... other config
});
}
}
When to Use:
- Host application already has database access
- Plugin should be stateless regarding identity
- Simple notification scheduling only
Option 2: Plugin Looks Up activeDid
Pros:
- Plugin manages its own identity context
- Automatic activeDid synchronization
- Background tasks have direct access
Implementation:
// Plugin has database access via @capacitor-community/sqlite
interface EnhancedDailyNotificationPlugin {
async initializeFromTimeSafari(): Promise<void> {
// Plugin accesses TimeSafari's active_identity table
const result = await this.dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1"
);
const activeDid = result?.values?.[0]?.[0] as string;
await this.setActiveDid(activeDid);
await this.scheduleBackgroundTasks();
}
}
When to Use:
- Plugin needs autonomous background operation
- Host application integration should be minimal
- Plugin operates independently of app lifecycle
Option 3: Hybrid Approach (Recommended)
Implementation:
// Host provides activeDid when app is active
// Plugin looks up activeDid for background operations
interface HybridDailyNotificationPlugin {
// Host-initiated activeDid setting
async setActiveDidFromHost(activeDid: string): Promise<void>;
// Plugin-initiated activeDid lookup for background
async refreshActiveDidFromDatabase(): Promise<string>;
// Automatic mode that uses both approaches
async enableAutoActiveDidMode(): Promise<void>;
}
// In TimeSafari app
async function initializeNotifications() {
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (activeDid) {
await DailyNotification.setActiveDidFromHost(activeDid);
await DailyNotification.enableAutoActiveDidMode();
}
}
Recommended Plugin Hosting Architecture
Service Layer Integration
Location: src/services/DailyNotificationService.ts
// src/services/DailyNotificationService.ts
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { PlatformServiceFactory } from './PlatformServiceFactory';
export class DailyNotificationService {
private plugin: DailyNotification;
private platform = PlatformServiceFactory.getInstance();
private currentActiveDid: string | null = null;
async initialize(): Promise<void> {
// Initialize plugin with current active identity
const activeIdentity = await this.platform.getActiveIdentity();
this.currentActiveDid = activeIdentity.activeDid;
if (this.currentActiveDid) {
await this.plugin.setActiveDid(this.currentActiveDid);
await this.plugin.configure({
apiServer: import.meta.env.VITE_ENDORSER_API_SERVER,
storageType: 'plugin-managed',
notifications: {
offers: true,
projects: true,
people: true,
items: true
}
});
// Listen for identity changes
this.setupIdentityChangeListener();
}
}
private setupIdentityChangeListener(): void {
// Subscribe to identity changes in TimeSafari
document.addEventListener('activeDidChanged', (event: CustomEvent) => {
this.handleActiveDidChange(event.detail.activeDid);
});
}
private async handleActiveDidChange(newActiveDid: string): Promise<void> {
if (newActiveDid !== this.currentActiveDid) {
this.currentActiveDid = newActiveDid;
await this.plugin.setActiveDid(newActiveDid);
logger.info(`[DailyNotificationService] ActiveDid updated to: ${newActiveDid}`);
PlatformServiceMixin Integration
Location: src/utils/PlatformServiceMixin.ts
// Add to PlatformServiceMixin.ts
async $notifyDailyNotificationService(): Promise<void> {
// Trigger notification service update when activeDid changes
const event = new CustomEvent('activeDidChanged', {
detail: { activeDid: this.currentActiveDid }
});
document.dispatchEvent(event);
}
// Modify existing $updateActiveDid method
async $updateActiveDid(newDid: string | null): Promise<void> {
// ... existing logic ...
// Notify DailyNotification service
await this.$notifyDailyNotificationService();
}
App Initialization Integration
Location: src/main.ts
or App component
// src/main.ts
import { DailyNotificationService } from '@/services/DailyNotificationService';
async function initializeNotifications() {
try {
const notificationService = new DailyNotificationService();
await notificationService.initialize();
logger.info('[App] Daily notification service initialized successfully');
} catch (error) {
logger.error('[App] Failed to initialize notification service:', error);
}
}
// Call after platform service initialization
initializePlatform().then(async () => {
await initializeNotifications();
// ... rest of app initialization
});
Cross-Platform Considerations
Android (Capacitor)
- Database Access: Plugin uses
@capacitor-community/sqlite
- identity Access: Can read TimeSafari's active_identity table directly
- Background Tasks: WorkManager can query active_identity independently
Web (absurd-sql)
- Database Access: Plugin delegates to host application
- identity Access: Host provides activeDid, plugin doesn't access database
- Background Tasks: Service Worker coordination with host database
iOS (Capacitor)
- Database Access: Plugin uses CapacitorSQLite
- identity Access: Can read TimeSafari's active_identity table
- Background Tasks: BGTaskScheduler coordination
Electron
- Database Access: Plugin uses
@capacitor-community/sqlite
+ filesystem - identity Access: Direct file-based database access
- Background Tasks: Electron main process coordination
Security Considerations
activeDid Isolation
- Plugin should not modify active_identity table
- Plugin should only read current activeDid
- Identity changes should be initiated by TimeSafari host application
Token Management
- JWT tokens should use activeDid for both
iss
andsub
- Token generation should happen in plugin context
- Host application should not handle authentication tokens
Background Execution
- Background tasks should validate activeDid hasn't changed
- Implement cache invalidation when activeDid changes
- Handle edge cases where activeDid becomes empty/invalid
Implementation Priority
Phase 1: Basic Integration
- Host-managed activeDid (Option 1)
- Service layer integration
- Basic notification scheduling
Phase 2: Enhanced Integration
- Hybrid activeDid management (Option 3)
- Background database access
- Automatic identity synchronization
Phase 3: Advanced Features
- Cross-platform optimization
- Advanced background task coordination
- Performance monitoring and analytics
Success Criteria
- Plugin correctly receives activeDid from TimeSafari host
- Background notifications work across identity changes
- [] Cross-platform consistency maintained
- Minimal impact on TimeSafari performance
- Secure isolation between plugin and host database operations
Status: Architectural analysis complete - Ready for implementation planning
Next Steps: Choose integration approach and begin Phase 1 implementation
Dependencies: TimeSafari active_identity table access, PlatformServiceMixin integration