Browse Source

analysis: TimeSafari host application integration patterns

- Documented active_identity table structure and access methods
- Analyzed TimeSafari's PlatformServiceMixin patterns
- Created three integration options:
  1. Host-managed activeDid (plugin receives from host)
  2. Plugin-lookup activeDid (plugin queries active_identity table)
  3. Hybrid approach (recommended combination)
- Detailed service layer integration points
- Cross-platform considerations for Android/Capacitor/Web/iOS/Electron
- Security isolation recommendations for plugin/host database access

This analysis provides clear guidance for hosting the plugin within TimeSafari applications.
research/notification-plugin-enhancement
Matthew Raymer 18 hours ago
parent
commit
1fabcf1fe9
  1. 343
      doc/TIMESAFARI_INTEGRATION_ANALYSIS.md

343
doc/TIMESAFARI_INTEGRATION_ANALYSIS.md

@ -0,0 +1,343 @@
# 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:**
```sql
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 DID
- `lastUpdated` tracks when the active identity was switched
- Single source of truth for current user identity
### Access Methods
**1. Platform Service Level:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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`
```typescript
// 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`
```typescript
// 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
```typescript
// 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` and `sub`
- 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
1. Host-managed activeDid (Option 1)
2. Service layer integration
3. Basic notification scheduling
### Phase 2: Enhanced Integration
1. Hybrid activeDid management (Option 3)
2. Background database access
3. Automatic identity synchronization
### Phase 3: Advanced Features
1. Cross-platform optimization
2. Advanced background task coordination
3. 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
Loading…
Cancel
Save