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.
This commit is contained in:
343
doc/TIMESAFARI_INTEGRATION_ANALYSIS.md
Normal file
343
doc/TIMESAFARI_INTEGRATION_ANALYSIS.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user