You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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 DID
  • lastUpdated 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

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();
  }
}

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 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