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.
 
 
 
 
 
 

19 KiB

Integration Point Refactor - Implementation Context

Author: Matthew Raymer
Date: 2025-10-29
Status: ๐ŸŽฏ CONTEXT - Pre-implementation analysis and mapping

Purpose

This document maps the current codebase to the Integration Point Refactor plan, identifies what exists, what needs to be created, and where gaps exist before starting implementation (PR1).


Current State Analysis

โœ… What Already Exists

1. TypeScript Type Definitions (src/types/content-fetcher.ts)

  • โœ… NotificationContent interface (matches directive spec)
  • โœ… FetchContext interface (matches directive spec)
  • โœ… JsNotificationContentFetcher interface (matches directive spec)
  • โœ… FetchTrigger type (matches directive spec)
  • โœ… SchedulingPolicy interface (matches directive spec)

Status: Complete and ready for PR1. No changes needed.

2. Example Implementations

  • โœ… examples/native-fetcher-android.kt - Kotlin native fetcher skeleton
  • โœ… examples/js-fetcher-typescript.ts - TypeScript JS fetcher skeleton

Status: Examples exist as reference. These are for host app, not plugin.

3. Test Contract (tests/fixtures/test-contract.json)

  • โœ… Golden contract fixture with expected outputs
  • โœ… Failure scenarios defined

Status: Ready for contract tests. Will be used in PR2+ for validation.

4. Current TimeSafari Integration (To Be Replaced)

Location: android/plugin/src/main/java/com/timesafari/dailynotification/

  • โœ… TimeSafariIntegrationManager.java - Current TimeSafari orchestration
  • โœ… EnhancedDailyNotificationFetcher.java - TimeSafari API client
  • โœ… DailyNotificationJWTManager.java - JWT generation for TimeSafari
  • โœ… DailyNotificationETagManager.java - ETag caching for TimeSafari

Status: These will be moved to host app OR deprecated behind feature flag (PR7).

Current Integration Flow:

DailyNotificationPlugin.load()
  โ†’ TimeSafariIntegrationManager (created in plugin)
    โ†’ EnhancedDailyNotificationFetcher
      โ†’ JWTManager + ETagManager
        โ†’ TimeSafari API endpoints

Target Flow (after refactor):

DailyNotificationPlugin.load()
  โ†’ NativeNotificationContentFetcher (registered by host app)
    โ†’ Host app's TimeSafari integration
      โ†’ TimeSafari API endpoints

What Needs to Be Created

PR1: SPI Shell (First Implementation)

1. Android Native SPI Interface

Location: android/plugin/src/main/java/com/timesafari/dailynotification/

File to Create: NativeNotificationContentFetcher.java

package com.timesafari.dailynotification;

public interface NativeNotificationContentFetcher {
    java.util.concurrent.CompletableFuture<java.util.List<NotificationContent>> 
        fetchContent(FetchContext context);
}

public class FetchContext {
    public final String trigger; // "background_work" | "prefetch" | "manual" | "scheduled"
    public final Long scheduledTime; // Optional: epoch ms
    public final long fetchTime; // Required: epoch ms
    public final Map<String, Object> metadata; // Optional
    
    // Constructor, getters...
}

Status: โŒ Not created yet - PR1 task

2. Fetcher Registry in Plugin

Location: android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java

What to Add:

  • Field: private NativeNotificationContentFetcher nativeFetcher;
  • Method: public void setNativeFetcher(NativeNotificationContentFetcher fetcher)
  • Storage: Store in static registry or application-level singleton

Status: โŒ Not implemented yet - PR1 task

3. SchedulingPolicy Implementation (Android)

Location: android/plugin/src/main/java/com/timesafari/dailynotification/

File to Create: SchedulingPolicy.java

public class SchedulingPolicy {
    public Long prefetchWindowMs;
    public RetryBackoff retryBackoff;
    public Integer maxBatchSize;
    public Long dedupeHorizonMs;
    public Integer cacheTtlSeconds;
    public Boolean exactAlarmsAllowed;
    
    public static class RetryBackoff {
        public long minMs;
        public long maxMs;
        public double factor;
        public int jitterPct;
    }
}

Status: โŒ Not created yet - PR1 task

Where Used:

  • DailyNotificationFetchWorker - backoff policy
  • DailyNotificationScheduler - prefetch timing
  • Deduplication logic (new, PR4)
  • TTL enforcement (existing DailyNotificationTTLEnforcer)

4. Plugin API Methods (TypeScript Bridge)

Location: android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java

Methods to Add:

@PluginMethod
public void setJsContentFetcher(PluginCall call) {
    // Store JS fetcher for foreground use
    // Will be called from TypeScript
}

@PluginMethod  
public void enableNativeFetcher(PluginCall call) {
    // Toggle native fetcher on/off
    // Default: true (native required for background)
}

@PluginMethod
public void setPolicy(PluginCall call) {
    // Update SchedulingPolicy
    // Parse JSON from call, create SchedulingPolicy instance
}

Status: โŒ Not implemented yet - PR1 task

5. TypeScript Plugin Interface Extensions

Location: src/definitions.ts

What to Add:

export interface DailyNotificationPlugin {
  // ... existing methods ...
  
  setJsContentFetcher(fetcher: JsNotificationContentFetcher): void;
  enableNativeFetcher(enable: boolean): Promise<void>;
  setPolicy(policy: SchedulingPolicy): Promise<void>;
}

Status: โŒ Partially exists in src/types/content-fetcher.ts but not wired to DailyNotificationPlugin interface in src/definitions.ts - PR1 task


PR2: Background Workers

Current Worker: DailyNotificationFetchWorker.java

Location: android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java

Current Behavior:

  • Uses DailyNotificationFetcher (generic, no TimeSafari)
  • Called from DailyNotificationFetcher.scheduleFetch()
  • Runs via WorkManager

What Needs to Change:

  1. Remove TimeSafari-Specific Coordination:

    // Remove these lines (79-92):
    boolean timesafariCoordination = inputData.getBoolean("timesafari_coordination", false);
    // ... coordination checks ...
    
  2. Add Native Fetcher Call:

    private NotificationContent fetchContentWithTimeout() {
        if (nativeFetcher == null) {
            Log.w(TAG, "Native fetcher not registered, falling back to cache");
            return getCachedContent();
        }
    
        FetchContext context = new FetchContext(
            "background_work",
            scheduledTime,
            System.currentTimeMillis(),
            metadata
        );
    
        try {
            List<NotificationContent> results = 
                nativeFetcher.fetchContent(context)
                    .get(30, TimeUnit.SECONDS);
            return results.isEmpty() ? null : results.get(0);
        } catch (Exception e) {
            // Handle timeout, errors
            return null;
        }
    }
    
  3. Add SchedulingPolicy Backoff:

    • Use SchedulingPolicy.retryBackoff for retry delays
    • Replace hardcoded retry logic

Status: โŒ Not modified yet - PR2 task


PR3: JS Fetcher Path

Plugin Side: Bridge JS Fetcher to Workers

Location: android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java

What Needs to Change:

  1. Store JS Fetcher Reference:

    private JsNotificationContentFetcher jsFetcher; // Wrapper object needed
    
    @PluginMethod
    public void setJsContentFetcher(PluginCall call) {
        // Extract callback from JS
        // Store for foreground use only
        // Log warning if used in background worker
    }
    
  2. Foreground Fetch Method:

    @PluginMethod
    public void refreshNow(PluginCall call) {
        if (jsFetcher != null) {
            // Call JS fetcher (foreground only)
            // Must bridge JS callback to Java
        } else if (nativeFetcher != null) {
            // Fall back to native fetcher
        }
    }
    

Challenge: JavaScript โ†’ Java callback bridge. Options:

  • Option A: Use Capacitor's bridge to call JS function
  • Option B: Use event system (plugin emits event, JS responds)
  • Option C: Store fetcher config, not callback (requires HTTP endpoint)

Status: โŒ Not implemented yet - PR3 task


PR4: Deduplication

New Component: Deduplication Store

Location: android/plugin/src/main/java/com/timesafari/dailynotification/

File to Create: NotificationDeduplicationStore.java

Responsibilities:

  • Maintain bloom filter or LRU cache of recent dedupeKey values
  • Check dedupeHorizonMs window
  • Prevent duplicate notifications within horizon
  • Use dedupeKey (fallback to id)

Integration Points:

  • DailyNotificationScheduler.scheduleNotification() - check before scheduling
  • DailyNotificationFetchWorker.doWork() - check after fetch

Status: โŒ Not created yet - PR4 task


PR5: Failure Policy

Enhance Existing Components

Location: Multiple files

1. Retry Logic (DailyNotificationFetchWorker.java):

  • Replace hardcoded retry with SchedulingPolicy.retryBackoff
  • Implement exponential backoff with jitter
  • Distinguish retryable vs unretryable errors

2. Cache Fallback (new or enhance DailyNotificationStorage.java):

  • Check TTL using SchedulingPolicy.cacheTtlSeconds or item ttlSeconds
  • Fallback to cached content if fetch fails and cache valid

3. Error Classification:

public enum FetchErrorType {
    RETRYABLE,    // 5xx, network, timeout
    UNRETRYABLE,  // 4xx auth, schema error
    PARTIAL,      // Some items bad
    OVER_QUOTA    // 429 with Retry-After
}

Status: โŒ Partially implemented (basic retry exists), needs enhancement - PR5 task


Component Dependency Map

Current Dependencies

DailyNotificationPlugin
  โ”œโ”€โ”€ TimeSafariIntegrationManager (TO BE REMOVED in PR7)
  โ”‚   โ”œโ”€โ”€ EnhancedDailyNotificationFetcher (TO BE MOVED to host app)
  โ”‚   โ”œโ”€โ”€ DailyNotificationJWTManager (TO BE MOVED to host app)
  โ”‚   โ””โ”€โ”€ DailyNotificationETagManager (TO BE MOVED to host app)
  โ”œโ”€โ”€ DailyNotificationScheduler
  โ”‚   โ”œโ”€โ”€ DailyNotificationTTLEnforcer
  โ”‚   โ””โ”€โ”€ PendingIntentManager
  โ”œโ”€โ”€ DailyNotificationFetchWorker
  โ”‚   โ””โ”€โ”€ DailyNotificationFetcher (generic, keep)
  โ””โ”€โ”€ DailyNotificationStorage

Target Dependencies (After Refactor)

DailyNotificationPlugin
  โ”œโ”€โ”€ NativeNotificationContentFetcher (REGISTERED by host app)
  โ”œโ”€โ”€ JsNotificationContentFetcher (REGISTERED by host app, foreground only)
  โ”œโ”€โ”€ SchedulingPolicy (CONFIGURED by host app)
  โ”œโ”€โ”€ DailyNotificationScheduler
  โ”‚   โ”œโ”€โ”€ DailyNotificationTTLEnforcer
  โ”‚   โ”œโ”€โ”€ PendingIntentManager
  โ”‚   โ””โ”€โ”€ NotificationDeduplicationStore (NEW in PR4)
  โ”œโ”€โ”€ DailyNotificationFetchWorker
  โ”‚   โ”œโ”€โ”€ Calls NativeNotificationContentFetcher (native path)
  โ”‚   โ””โ”€โ”€ Uses SchedulingPolicy (backoff, TTL)
  โ””โ”€โ”€ DailyNotificationStorage

Integration Points to Identify

1. Worker Enqueue Points

Where Workers Are Scheduled:

  1. DailyNotificationFetcher.scheduleFetch() (line ~77-116):

    • Enqueues DailyNotificationFetchWorker
    • Currently generic (no TimeSafari coupling)
  2. TimeSafariIntegrationManager.fetchAndScheduleFromServer() (line ~258):

    • May enqueue workers with TimeSafari coordination flags
    • Needs to switch to native fetcher path
  3. DailyNotificationPlugin.scheduleCoordinatedBackgroundJobs():

    • Phase 3 coordination code
    • Adds TimeSafari-specific flags to WorkManager Data
    • Needs refactor to remove TimeSafari flags

Action for PR2: Modify worker enqueue to use native fetcher, remove TimeSafari coordination flags.


2. Notification Content Flow

Current Flow:

EnhancedDailyNotificationFetcher.fetch()
  โ†’ Returns TimeSafariNotificationBundle
    โ†’ TimeSafariIntegrationManager.convertBundleToNotificationContent()
      โ†’ Returns List<NotificationContent>
        โ†’ DailyNotificationScheduler.scheduleNotification()

Target Flow:

NativeNotificationContentFetcher.fetchContent(context)
  โ†’ Returns List<NotificationContent> (already generic)
    โ†’ DailyNotificationScheduler.scheduleNotification()

Gap: convertBundleToNotificationContent() logic must move to host app (PR7).


3. TTL Enforcement Points

Current TTL Flow:

  1. Hard-Fail at Arming: ->DailyNotificationScheduler.scheduleNotification()

    • Calls ttlEnforcer.validateBeforeArming(content)
    • โœ… Already generic (works with any NotificationContent)
  2. Soft-Check at Fire: DailyNotificationReceiver.onReceive()

    • Checks freshness, may trigger refresh
    • โœ… Already generic

Status: โœ… TTL enforcement is already generic - no changes needed for SPI.


4. Metrics Collection Points

Current Metrics:

  • src/observability.ts - TypeScript observability
  • packages/polling-contracts/src/telemetry.ts - Telemetry manager

What to Add:

  • fetch_duration_ms - Time for native fetcher to complete
  • fetch_success - Boolean success/failure
  • fetch_fail_class - Error classification (retryable/unretryable/partial/over-quota)
  • items_fetched - Number of items from fetcher
  • items_enqueued - Number successfully scheduled
  • deduped_count - Items filtered by deduplication (PR4)
  • cache_hits - Cached content used when fetch fails (PR5)

Action for PR2: Add metrics calls in DailyNotificationFetchWorker.doWork().


Migration Path Mapping

Feature Flag Implementation (PR7)

Location: DailyNotificationPlugin.java

What to Add:

private static final boolean USE_LEGACY_INTEGRATION = false; // Default false in new minor

private void fetchContent() {
    if (USE_LEGACY_INTEGRATION && timeSafariIntegration != null) {
        // Legacy path
        timeSafariIntegration.fetchAndScheduleFromServer(false);
    } else if (nativeFetcher != null) {
        // New SPI path
        // Call native fetcher
    } else {
        Log.w(TAG, "No fetcher available");
    }
}

Files to Guard:

  • TimeSafariIntegrationManager.java - Keep for one minor release
  • EnhancedDailyNotificationFetcher.java - Keep for one minor release
  • Legacy @PluginMethod wrappers - Deprecate, keep for compatibility

File Creation Checklist

PR1: SPI Shell

  • android/plugin/src/main/java/com/timesafari/dailynotification/NativeNotificationContentFetcher.java
  • android/plugin/src/main/java/com/timesafari/dailynotification/FetchContext.java (or inner class)
  • android/plugin/src/main/java/com/timesafari/dailynotification/SchedulingPolicy.java
  • Update DailyNotificationPlugin.java:
    • Add setNativeFetcher() method
    • Add setJsContentFetcher() method (stub, full in PR3)
    • Add enableNativeFetcher() method
    • Add setPolicy() method
  • Update src/definitions.ts:
    • Wire SchedulingPolicy from content-fetcher.ts
    • Add new methods to DailyNotificationPlugin interface

PR2: Background Workers

  • Modify DailyNotificationFetchWorker.java:
    • Remove TimeSafari coordination checks
    • Add native fetcher call
    • Add SchedulingPolicy backoff
    • Add metrics recording

PR3: JS Fetcher Path

  • Modify DailyNotificationPlugin.java:
    • Implement setJsContentFetcher() (JS bridge)
    • Add foreground refresh method
    • Add event bus for JS communication
  • Create TypeScript bridge utilities

PR4: Deduplication

  • android/plugin/src/main/java/com/timesafari/dailynotification/NotificationDeduplicationStore.java
  • Integrate into DailyNotificationScheduler
  • Add metrics for deduped count

PR5: Failure Policy

  • Create FetchErrorType enum
  • Enhance retry logic with SchedulingPolicy
  • Add cache fallback mechanism
  • Add error classification helpers

PR6: Docs & Samples

  • INTEGRATION_GUIDE.md - When to use native vs JS fetcher
  • SCHEDULING_POLICY.md - All configuration knobs
  • Update examples with real registration code

PR7: Feature Flag Legacy

  • Add USE_LEGACY_INTEGRATION flag
  • Guard all TimeSafari code paths
  • Update migration guide
  • Regression tests

Open Questions to Resolve

Before PR1

  1. Fetcher Registration Pattern:

    • Should setNativeFetcher() be called from host app's Application.onCreate()?
    • Or should it be a static registry accessible from host app's native code?
    • Recommendation: Static registry in plugin, host app registers in Application.onCreate()
  2. FetchContext Metadata:

    • What should plugin populate in metadata map?
    • Current activeDid? App state? Network state?
    • Recommendation: Start minimal (empty map), extend later

Before PR2

  1. Background Worker Timeout:

    • Current: 30 seconds for fetch
    • Should this be configurable in SchedulingPolicy?
    • Recommendation: Add fetchTimeoutMs to SchedulingPolicy in PR1
  2. Multiple NotificationContent Handling:

    • If native fetcher returns multiple items, should all be scheduled?
    • Should maxBatchSize apply here?
    • Recommendation: Schedule all valid items, maxBatchSize limits fetcher input/API calls

Before PR3

  1. JS Fetcher Bridge Mechanism:

    • Which approach: Capacitor bridge callback, event system, or HTTP endpoint?
    • Recommendation: Start with event system (plugin emits fetch_needed, JS responds via setJsContentFetcher callback)
  2. Foreground-Only Enforcement:

    • How to prevent JS fetcher from being called in background worker?
    • Recommendation: Runtime guard + lint rule (document in PR3)

Testing Strategy

Contract Tests (After PR2)

  • Use tests/fixtures/test-contract.json
  • Run native fetcher with fixture context
  • Compare normalized output to expected output
  • Location: Create android/plugin/src/test/java/com/timesafari/dailynotification/NativeFetcherContractTest.java

Integration Tests (After PR3)

  • Test JS fetcher in foreground
  • Test native fetcher in background worker
  • Verify both produce identical results for same input

Regression Tests (Before PR7)

  • Ensure legacy TimeSafari path still works behind feature flag
  • Verify new SPI path works independently
  • Test migration from legacy to SPI path

Next Steps

  1. Review this context document - Verify completeness
  2. Resolve open questions - Make decisions before PR1
  3. Create PR1 branch - Start with SPI shell implementation
  4. Reference examples - Use examples/native-fetcher-android.kt as pattern
  5. Follow 7-PR plan - Sequential implementation with tests after each PR

Status: Context building complete - ready for PR1 implementation
Last Updated: 2025-10-29
Maintainer: Plugin development team