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