Files
daily-notification-plugin/test-apps/daily-notification-test/TODO_NATIVE_FETCHER.md
Jose Olarte III 6ad7ff5fe1 docs: reorganize docs into subdirs and fix links
- Keep only index, getting-started, invariants, performance,
  troubleshooting, and file-organization-summary in docs/ root
- Add docs/architecture/ (storage, database interfaces, native fetcher)
- Add docs/deployment/ (deployment-guide, DEPLOYMENT_CHECKLIST)
- Add docs/compliance/ (accessibility, legal, observability)
- Move integration guides and host-app docs to docs/integration/
- Move design/planning and prefetch docs to docs/design/
- Move Android consuming-app and comparison docs to docs/platform/android/
- Move DEPLOYMENT_SUMMARY and TODO-CLASSIFICATION to docs/progress/
- Archive deprecated platform-capability-reference to docs/_archive/
- Point platform-capability links to alarms/01-platform-capability-reference.md
- Update docs/00-INDEX.md with new sections and paths
- Fix cross-references in README, deployment, progress, design, testing,
  and test-app docs
- Remove one-off COMMIT_MESSAGE.txt
2026-03-06 19:51:13 +08:00

32 KiB

TODO: Test App Native Fetcher Improvements

Created: 2025-10-31
Updated: 2025-10-31 (implementation complete: ES256K JWT generation and network security config)
Context: Review of TestNativeFetcher.java implementation for endorser API integration
Status: IMPLEMENTATION COMPLETE - All high-priority items complete. Item #1 (ES256K JWT generation) implemented using did-jwt and ethers. Item #15 (Network Security Config) created. All TypeScript/Java interfaces updated. Plugin rebuilt with updated definitions.


📊 Progress Summary

Completed High-Priority Items

  • Item #1: JWT Algorithm Investigation - COMPLETE (ES256K requirement confirmed, implementation pending)
  • Item #2: SharedPreferences Persistence - COMPLETE
  • Item #3: Error Handling and Retry Logic - COMPLETE
  • Item #6: Context Management - COMPLETE

Critical Implementation Complete (Item #1)

  • ES256K JWT Token Generation - COMPLETE: Implemented generateEndorserJWT() function using did-jwt and ethers libraries. Generates proper ES256K signed JWTs from test user zero's seed phrase. Native fetcher now receives pre-generated tokens, avoiding Java DID library complexity.

🟡 Medium Priority Status

  • Item #4: API Response Structure Validation - VERIFIED (structure verified, validation improvements needed - see details below)
  • Item #5: Comprehensive Logging - Partial (basic logging exists, more comprehensive needed)

🟢 Low Priority

  • Items #7-11: All pending

📝 Configuration Tasks

  • Item #13: Real API Calls - Ready (can now enable localhost mode, ES256K JWT generation complete)
  • Item #14: TypeScript Types - COMPLETE (plugin rebuilt, types updated, as any removed)
  • Item #15: Network Security Config - COMPLETE (config created, AndroidManifest updated)

🔴 High Priority

1. ⚠️ JWT Generation and Verification Status

  • JWT Generation: COMPLETE - TypeScript generates ES256K signed JWTs correctly
  • JWT Verification: 🟡 PARTIAL - Generation works, but server verification fails

Current Status (2025-10-31):

JWT Generation (TypeScript):

  • Using did-jwt library with ES256K algorithm ✓
  • Signing with Ethereum private key from seed phrase ✓
  • Correct payload structure (iat, exp, iss, sub, aud) ✓
  • Location: test-user-zero.tsgenerateEndorserJWT()

🟡 JWT Verification Issue:

  • Error: JWT failed verification: Error: invalid_signature: no matching public key found
  • Root Cause: Server cannot resolve DID to get public key for signature verification
  • Server uses: didJwt.verifyJWT(jwt, {resolver}) which requires DID resolution
  • The JWT signature is cryptographically valid, but server can't verify it

Problem: DID did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F may not be:

  1. Registered on the resolver/blockchain that the server uses
  2. Accessible by the server's DID resolver
  3. Configured correctly in the test API server

Next Steps (to be verified):

  • Verify DID registration: Ensure User Zero's DID is registered on resolver server uses
  • Test with known DID: Try a DID that definitely exists in server's system
  • Check server resolver config: Verify server's DID resolver can resolve did:ethr: DIDs
  • Check test API server: If using localhost:3000, confirm it supports DID-based JWT verification

Verification Status Table:

Component Status Notes
JWT Algorithm Correct ES256K (matches server expectation)
JWT Format Correct Proper structure and claims
JWT Signature Valid Signature is cryptographically valid
DID Resolution Failing Server can't resolve DID to public key
Overall Status 🟡 Partial Generation works; verification fails due to DID resolution

Investigation Results (See INVESTIGATION_JWT_ALGORITHM_RESULTS.md for full details):

CONFIRMED: Endorser-ch API uses DID-based JWTs (ES256K), NOT HMAC-SHA256.

TimeSafari Implementation (~/projects/timesafari/crowd-funder-for-time-pwa/src/libs/crypto/vc/index.ts):

  • Uses did-jwt.createJWT() with SimpleSigner(privateKeyHex)
  • Signs with Ethereum private key from DID identity
  • Algorithm: ES256K (default for did-jwt library)

endorser-ch Verification (~/projects/timesafari/endorser-ch/src/api/services/vc/index.js):

  • Uses did-jwt.verifyJWT(jwt, {resolver}) for verification
  • Verifies signature using DID resolver (resolves DID to public key)
  • NO shared secret used - authentication is DID-based

IMPLEMENTATION COMPLETE:

  • TypeScript generates ES256K signed JWTs using generateEndorserJWT()
  • Native fetcher receives pre-generated JWT token via configureNativeFetcher()
  • No Java DID library needed - token generated in TypeScript with did-jwt library

Current Architecture:

  1. TypeScript: generateEndorserJWT() → Creates ES256K signed JWT
  2. TypeScript: DailyNotification.configureNativeFetcher({ jwtToken }) → Passes token to native
  3. Android: TestNativeFetcher.configure() → Stores JWT token
  4. Android: Background worker uses stored JWT token for API requests

Remaining Issue: DID resolution on server side (see verification status above)

Previous Implementation (REMOVED):

  • HMAC-SHA256 with shared secret → Replaced with ES256K DID-based
  • Java DID library implementation → Not needed (TypeScript generates token)

Implementation Options (NO LONGER NEEDED - TypeScript handles it):

Option 1: Use did-jwt-java Library (Recommended)

// DEPRECATED - TypeScript generates JWT now
// No longer needed in Java code

Option 2: Use web3j for Ethereum Signing

// DEPRECATED - TypeScript generates JWT now
// No longer needed in Java code
    keyPair
);
// Then encode signature according to ES256K format for JWT

References:

  • Investigation Results: See INVESTIGATION_JWT_ALGORITHM_RESULTS.md for complete findings
  • TimeSafari Implementation: ~/projects/timesafari/crowd-funder-for-time-pwa/src/libs/crypto/vc/index.ts - createEndorserJwtForKey() function
  • endorser-ch Verification: ~/projects/timesafari/endorser-ch/src/api/services/vc/index.js - decodeAndVerifyJwt() function
  • did-jwt library - DID-based JWT library (JavaScript reference)
  • did-jwt-java - Java implementation (if available) or alternative Java DID libraries

Impact: CRITICAL - Current HMAC-SHA256 implementation will be rejected by API server. All authentication will fail until DID-based ES256K signing is implemented.

ARCHITECTURAL DECISION: Generate JWT in TypeScript (host app), pass token to native fetcher

Rationale:

  • TimeSafari already has did-jwt library available in TypeScript
  • TimeSafari can access DID private keys via Capacitor SQLite (retrieveFullyDecryptedAccount())
  • Plugin SPI pattern is designed for host app to provide implementations
  • Avoids complexity of finding/implementing Java DID libraries
  • Keeps cryptography in TypeScript where libraries are mature

Updated Implementation Approach:

Option A: Generate JWT in TypeScript, Pass Token (RECOMMENDED)

// In HomeView.vue or TimeSafari app
import { createEndorserJwtForDid } from '@/libs/endorserServer';

// Generate JWT in TypeScript using existing TimeSafari infrastructure
const account = await retrieveFullyDecryptedAccount(activeDid);
const jwtToken = await createEndorserJwtForDid(activeDid, {
  exp: Math.floor(Date.now() / 1000) + 60,
  iat: Math.floor(Date.now() / 1000),
  iss: activeDid
});

// Pass JWT token to native fetcher (no private key needed)
await DailyNotification.configureNativeFetcher({
  apiBaseUrl: 'http://10.0.2.2:3000',
  activeDid: activeDid,
  jwtToken: jwtToken  // Pre-generated token, not secret
});
// In TestNativeFetcher.java - Just use the token, don't generate it
private volatile String jwtToken; // Pre-generated token from TypeScript

public void configure(String apiBaseUrl, String activeDid, String jwtToken) {
    this.apiBaseUrl = apiBaseUrl;
    this.activeDid = activeDid;
    this.jwtToken = jwtToken; // Use pre-generated token
}

private String getAuthorizationHeader() {
    return "Bearer " + jwtToken;
}

Benefits:

  • No Java DID library needed
  • Uses existing TimeSafari infrastructure (did-jwt, retrieveFullyDecryptedAccount)
  • Keeps cryptography in TypeScript (where it's proven to work)
  • Native fetcher just uses token (simple, no crypto complexity)
  • Token can be refreshed in TypeScript when needed

Next Actions:

  • Update configureNativeFetcher() TypeScript signature to accept jwtToken instead of jwtSecret
  • Update Java configureNativeFetcher() to accept jwtToken parameter
  • Remove JWT generation code from TestNativeFetcher.generateJWTToken()
  • Use pre-generated token in getAuthorizationHeader() method
  • Update HomeView.vue to generate JWT using TimeSafari's createEndorserJwtForDid()
  • For TimeSafari integration: Generate JWT in host app using account data from SQLite
  • Test with real endorser-ch API to verify tokens are accepted

Testing:

  • Verified algorithm with endorser-ch source code - ES256K confirmed
  • Generate JWT in TypeScript using did-jwt library (ES256K signing)
  • Pass pre-generated JWT token to native fetcher via configureNativeFetcher()
  • ⚠️ Verify tokens are accepted by the endorser API endpoint (pending real API testing)
  • Implement token refresh mechanism in TypeScript when tokens expire (60 seconds)

Implementation Checklist:

1. Update TypeScript Interface (src/definitions.ts):

  • Change jwtSecret: stringjwtToken: string in configureNativeFetcher() signature
  • Update JSDoc to reflect that token is pre-generated, not a secret
  • Rebuild plugin to update dist/definitions.d.ts

2. Update Java Plugin (android/plugin/.../DailyNotificationPlugin.java):

  • Change String jwtSecret = call.getString("jwtSecret")String jwtToken = call.getString("jwtToken")
  • Update validation message: "Missing required parameters: apiBaseUrl, activeDid, and jwtToken are required"
  • Update fetcher.configure(apiBaseUrl, activeDid, jwtToken) call
  • Update Javadoc to reflect token-based approach

3. Update Native Interface (android/plugin/.../NativeNotificationContentFetcher.java):

  • Change default void configure(String apiBaseUrl, String activeDid, String jwtSecret)default void configure(String apiBaseUrl, String activeDid, String jwtToken)
  • Update Javadoc to explain token is pre-generated by TypeScript

4. Update TestNativeFetcher (test-apps/.../TestNativeFetcher.java):

  • Change private volatile String jwtSecret;private volatile String jwtToken;
  • Update configure() method signature and implementation
  • Remove entire generateJWTToken() method (HMAC-SHA256 implementation removed)
  • Update fetchContentWithRetry() to use jwtToken directly
  • Remove JWT-related constants (JWT_EXPIRATION_MINUTES) - no longer needed
  • Remove HMAC-SHA256 imports (Mac, SecretKeySpec, Base64, MessageDigest) - not used elsewhere

5. Update HomeView.vue (test-apps/.../HomeView.vue):

  • Implement ES256K JWT generation using did-jwt and ethers libraries
  • Generate JWT before calling configureNativeFetcher() using generateEndorserJWT()
  • Update configureNativeFetcher() call to use jwtToken instead of jwtSecret
  • Remove as any type assertion after plugin rebuild

6. ES256K JWT Generation Implementation (test-apps/.../test-user-zero.ts):

  • Add did-jwt@^7.0.0 and ethers@^6.0.0 dependencies
  • Implement generateEndorserJWT() function:
    • Derives Ethereum private key from seed phrase using ethers.HDNodeWallet
    • Uses did-jwt.SimpleSigner for ES256K signing
    • Creates proper ES256K signed JWTs matching TimeSafari's pattern
  • Export generateEndorserJWT for use in HomeView.vue

6. For TimeSafari Production Integration:

  • In TimeSafari app, generate JWT using createEndorserJwtForKey() with account from SQLite
  • Call DailyNotification.configureNativeFetcher() with generated token
  • Implement token refresh logic when tokens expire (60 seconds)
  • Handle token expiration errors and regenerate tokens

7. Documentation Updates:

  • Update docs/architecture/NATIVE_FETCHER_CONFIGURATION.md to reflect token-based approach
  • Document how to generate tokens in TypeScript using TimeSafari infrastructure
  • Remove references to jwtSecret and HMAC-SHA256

2. Implement SharedPreferences Persistence for Configuration

  • Status: COMPLETE

Current State: Implemented - getStarredPlanIds() and getLastAcknowledgedJwtId() use SharedPreferences

Location: TestNativeFetcher.javagetStarredPlanIds(), getLastAcknowledgedJwtId()

Implementation Status: COMPLETE

  • SharedPreferences initialized in constructor with PREFS_NAME = "DailyNotificationPrefs"
  • getStarredPlanIds() - Loads JSON array from SharedPreferences
  • getLastAcknowledgedJwtId() - Loads JWT ID from SharedPreferences
  • updateStarredPlanIds() - Saves plan IDs to SharedPreferences
  • updateLastAckedJwtId() - Saves JWT ID to SharedPreferences
  • Context passed to constructor and stored as appContext

Impact: Proper state management and pagination support enabled


3. Add Error Handling and Retry Logic

  • Status: COMPLETE

Current State: Implemented - fetchContentWithRetry() with exponential backoff

Location: TestNativeFetcher.javafetchContentWithRetry()

Implementation Status:

  • Retry logic for network failures (transient errors) - fetchContentWithRetry() with MAX_RETRIES = 3
  • Distinguish retryable vs non-retryable errors - shouldRetry() method checks response codes and retry count
  • Log error details for debugging - Detailed logging with retry counts and error messages
  • Exponential backoff for retries - RETRY_DELAY_MS * (1 << retryCount) implemented

Suggested Implementation:

private static final int MAX_RETRIES = 3;
private static final int RETRY_DELAY_MS = 1000;

private CompletableFuture<List<NotificationContent>> fetchContentWithRetry(
    FetchContext context, int retryCount) {
    // ... existing fetch logic ...
    
    if (responseCode == 401 || responseCode == 403) {
        // Non-retryable - authentication issue
        Log.e(TAG, "Authentication failed - check credentials");
        return CompletableFuture.completedFuture(Collections.emptyList());
    }
    
    if (responseCode >= 500 && retryCount < MAX_RETRIES) {
        // Retryable - server error
        Log.w(TAG, "Server error " + responseCode + ", retrying... (" + retryCount + "/" + MAX_RETRIES + ")");
        Thread.sleep(RETRY_DELAY_MS * (1 << retryCount)); // Exponential backoff
        return fetchContentWithRetry(context, retryCount + 1);
    }
}

Impact: Improves reliability for transient network issues


🟡 Medium Priority

4. Validate API Response Structure (Need to Verify Actual Structure)

  • Status: VERIFIED - Endpoint structure verified, parser validation needs improvement

Current State: Parser handles both flat and nested structures, but could add more validation

Location: TestNativeFetcher.javaparseApiResponse()

Endpoint Structure Verified:

  • Check endorser-ch endpoint implementation:

    • Found /api/v2/report/plansLastUpdatedBetween handler in endorser-ch source: ~/projects/timesafari/endorser-ch/src/api/controllers/report-router.js
    • Verified actual response structure - Returns { data: [...], hitLimit: boolean } where data items have plan and wrappedClaimBefore properties
    • Response matches PlanSummaryAndPreviousClaim structure - Items have plan.handleId, plan.jwtId, and wrappedClaimBefore properties
  • Current parser handles:

    • Both flat structure (jwtId, planId) and nested structure (plan.jwtId, plan.handleId)
    • ⚠️ Missing: Does NOT extract wrappedClaimBefore from response (exists in API response but not used in parser)

Verification Results:

Null Checks Status:

  • Uses .has() checks before accessing fields (planId, jwtId) - Lines 502, 511
  • Checks item.has("plan") before accessing plan object - Lines 504, 513
  • Checks dataArray != null after getting it - Line 491
  • Handles null values safely (planId and jwtId can be null) - Lines 499-518
  • Wrapped in try-catch for error handling - Line 485, 570
  • ⚠️ Missing: No check for root.has("data") before calling getAsJsonArray("data") (line 490) - Could throw IllegalStateException if "data" field missing or wrong type
  • ⚠️ Potential Issue: getAsJsonObject("plan") (lines 505, 514) could throw if "plan" exists but is not a JsonObject (currently caught by try-catch)

Structure Validation Status:

  • ⚠️ Missing: No validation that root has "data" field before accessing (line 490)
  • ⚠️ Missing: No warning logged if "data" field is missing or wrong type (would be caught by try-catch, but no specific warning)
  • Handles missing/null data gracefully (creates default notification if contents empty) - Line 554-568
  • Handles parse errors gracefully (returns empty list on exception) - Line 570-573

Nested Structure Handling:

  • Handle nested structures correctly - Parser handles both item.plan.handleId and item.planId formats
  • Handles both flat (jwtId, planId) and nested (plan.jwtId, plan.handleId) structures

Remaining Work:

  • Add root.has("data") check before accessing data array (line 490 - currently could throw if "data" missing)
  • Add null check for plan object before accessing nested fields (line 505, 514 - currently relies on try-catch)
  • Log warnings for unexpected response formats (missing "data" field, wrong types)
  • Extract wrappedClaimBefore from response items (API provides this but parser doesn't use it)

Suggested Improvements:

private List<NotificationContent> parseApiResponse(String responseBody, FetchContext context) {
    try {
        JsonParser parser = new JsonParser();
        JsonObject root = parser.parse(responseBody).getAsJsonObject();
        
        // Validate response structure
        if (!root.has("data")) {
            Log.w(TAG, "API response missing 'data' field");
            return Collections.emptyList();
        }
        
        JsonArray dataArray = root.getAsJsonArray("data");
        if (dataArray == null) {
            Log.w(TAG, "API response 'data' field is not an array");
            return Collections.emptyList();
        }
        
        // ... rest of parsing with null checks ...
    }
}

Impact: Prevents crashes on unexpected API responses


5. Add Comprehensive Logging for Debugging

  • Status: Partial - Basic logging exists, more comprehensive logging needed

Current State: Basic logging present (Log.d/i/e/w), but could be more detailed and structured

Location: Throughout TestNativeFetcher.java

Current Implementation:

  • Uses Log.d/i/e/w for different severity levels
  • Some structured tags (e.g., "TestNativeFetcher")
  • Logs error details and retry information

Required Changes:

  • Add more comprehensive log levels (DEBUG, INFO, WARN, ERROR) - Basic levels exist but could be more systematic
  • Log request/response details (truncated for sensitive data) - Some logging exists, could be more comprehensive
  • Log timing information for performance monitoring - Not currently implemented (timing would be helpful)
  • Add structured logging tags for filtering - Basic tags exist, could be more structured with consistent prefixes

Suggested Logging Points:

  • Configuration received
  • Fetch start/end with timing
  • HTTP request details (URL, method, headers - sanitized)
  • Response code and size
  • Parse success/failure
  • Number of notifications created

Example:

Log.d(TAG, String.format("FETCH_START trigger=%s scheduledTime=%d fetchTime=%d", 
    context.trigger, context.scheduledTime, context.fetchTime));
    
long startTime = System.currentTimeMillis();
// ... radius logic ...
long duration = System.currentTimeMillis() - startTime;

Log.i(TAG, String.format("FETCH_COMPLETE duration=%dms items=%d", 
    duration, contents.size()));

6. Implement Proper Context Management

  • Status: COMPLETE

Current State: Context passed to constructor and stored

Location: TestNativeFetcher.java constructor, TestApplication.java

Implementation Status:

  • Pass Application context to TestNativeFetcher constructor - Done in TestApplication.onCreate()
  • Store context for SharedPreferences access - Stored as appContext and used for SharedPreferences
  • Using application context (no leaks) - context.getApplicationContext() used

Example:

// In TestNativeFetcher:
private final Context appContext;

public TestNativeFetcher(Context context) {
    this.appContext = context.getApplicationContext(); // Use app context
    // ... initialize SharedPreferences ...
}

// In TestApplication.onCreate():
Context context = getApplicationContext();
TestNativeFetcher fetcher = new TestNativeFetcher(context);

🟢 Low Priority / Nice to Have

7. Add Unit Tests

  • Status: Not started

Location: Create TestNativeFetcher/CONTENT_FETCHERTest.java

Coverage Needed:

  • JWT token generation (verify signature format)
  • API response parsing (valid and invalid responses)
  • Error handling (network failures, invalid responses)
  • Configuration updates
  • SharedPreferences integration

Test Framework: JUnit 4/5 + Mockito


8. Extract JWT Generation to Utility Class

  • Status: Not started

Current State: JWT generation code embedded in fetcher

Benefit: Reusable across other parts of the app, easier to test

Location: Create test-apps/daily-notification-test/android/app/src/main/java/com/timesafari/dailynotification/test/JwtUtils.java


9. Add Request/Response Interceptors for Development

  • Status: Not started

Purpose: Log full request/response for API debugging

Implementation:

  • Add methods to log full HTTP requests/responses (with data sanitization)

Note: Only enable in debug builds for security


10. Support API Response Caching

  • Status: Not started

Current State: Always fetches from API

Enhancement:

  • Cache successful responses with TTL
  • Use cached data if available and fresh

Use Case: Reduce API calls when multiple notifications scheduled close together


11. Add Metrics/Telemetry

  • Status: Not started

Purpose: Track fetch success rates, latency, error types

Implementation:

  • Record metrics to SharedPreferences or analytics service
  • Track: success/failure rate, average response time, error distribution
  • Log summary periodically

12. MOVED TO HIGH PRIORITY: Verify API Response Structure

This item has been moved to High Priority item #4 - Validate API Response Structure

Action Required (covered in item #4 above):

  • Check endorser-ch source code for actual endpoint response structure
  • Compare with PlansLastUpdatedResponse and PlanSummaryWithPreviousClaim interfaces in codebase
  • Update parser if structure differs - Parser handles both flat and nested structures
  • Add validation for required fields - Additional validation still needed
  • Test with real API responses from endorser-ch server - Pending real API testing

Location:

  • Check endorser-ch source: src/ or server/ directories for endpoint handler
  • Check src/definitions.ts in this codebase for expected interface structure
  • Test with real responses from running endorser-ch server

📝 Configuration & Setup Tasks

13. Enable Real API Calls in Test Config

  • Status: Ready to enable - ES256K JWT generation complete, network config ready

Current State: TEST_USER_ZERO_CONFIG.api.serverMode = "mock"

Ready to Enable: ES256K JWT generation is complete, network security config is in place

Action:

  • Change serverMode to "localhost" in test-user-zero.ts (line 28) for emulator testing
  • Test API calls to http://10.0.2.2:3000 with real ES256K signed tokens
  • Verify tokens are accepted by endorser-ch API
  • Update to "staging" or "production" for real environment testing when ready

Location: test-apps/daily-notification-test/src/config/test-user-zero.ts (line 28)

Note: JWT generation now uses generateEndorserJWT() which creates proper ES256K signed tokens from the seed phrase.


14. Update TypeScript Type Definitions

  • Status: VERIFIED - Type definitions exist and are being used, may need updates after ES256K changes

Current State: Using as any type assertion for configureNativeFetcher in HomeView.vue

Verification Results:

  • configureNativeFetcher() method added to plugin interface (src/definitions.ts) - Verified: Line 376
  • Method signature exists with apiBaseUrl, activeDid, jwtSecret parameters
  • Method is being called in HomeView.vue - Line 474 (onMounted hook)
  • Currently uses (DailyNotification as any).configureNativeFetcher type assertion - Line 454

Remaining Work:

  • Rebuild plugin package to ensure TypeScript definitions are up to date in dist/
  • Remove as any type assertion in HomeView.vue (after confirming types are available)
  • ⚠️ CRITICAL: Update method signature after ES256K implementation (remove jwtSecret, add private key mechanism)

Location: test-apps/daily-notification-test/src/views/HomeView.vue - Currently uses (DailyNotification as any).configureNativeFetcher


15. Add Network Security Configuration

  • Status: COMPLETE - Network security config created and AndroidManifest.xml updated

Purpose: Allow HTTP connections in Android emulator (for localhost:3000)

Verification Results:

  • Checked: network_security_config.xml does NOT exist in android/app/src/main/res/xml/
  • Checked: No networkSecurityConfig attribute in AndroidManifest.xml
  • Checked: No usesCleartextTraffic attribute in AndroidManifest.xml

Implementation Status:

  • Created android/app/src/main/res/xml/network_security_config.xml allowing cleartext traffic for 10.0.2.2, localhost, and 127.0.0.1
  • Added android:networkSecurityConfig="@xml/network_security_config" to <application> tag in AndroidManifest.xml
  • Documented that this is safe for development (emulator-only access)

Files Modified:

  • test-apps/daily-notification-test/android/app/src/main/res/xml/network_security_config.xml (created)
  • test-apps/daily-notification-test/android/app/src/main/AndroidManifest.xml (updated line 10)

Note: Required for HTTP connections (not HTTPS) to http://10.0.2.2:3000 from Android emulator


🧪 Testing Checklist

Before considering this implementation complete:

  • ⚠️ CRITICAL: JWT tokens are accepted by endorser API (pending ES256K implementation - current HMAC-SHA256 will fail)
  • API responses are correctly parsed into notifications
  • Notifications appear at scheduled times
  • Prefetch occurs 5 minutes before notification
  • Error handling works for network failures - Retry logic implemented
  • SharedPreferences persists starred plan IDs - Implementation complete
  • Pagination works with afterId parameter (SharedPreferences ready, needs API testing)
  • Multiple notifications can be scheduled and fetched
  • Background fetches work when app is closed
  • App recovers gracefully from API errors - Error handling and retry logic implemented

  • docs/architecture/NATIVE_FETCHER_CONFIGURATION.md - Native fetcher configuration guide
  • src/types/content-fetcher.ts - TypeScript type definitions
  • src/definitions.ts - PlansLastUpdatedResponse and PlanSummaryWithPreviousClaim interfaces
  • examples/native-fetcher-android.kt - Example Android implementation
  • Investigation Results: INVESTIGATION_JWT_ALGORITHM_RESULTS.md - Complete investigation findings
  • endorser-ch Repository - CONFIRMED: Uses did-jwt.verifyJWT() with DID resolver for ES256K verification
    • JWT verification: ~/projects/timesafari/endorser-ch/src/api/services/vc/index.js - decodeAndVerifyJwt()
    • /api/v2/report/plansLastUpdatedBetween endpoint handler: ~/projects/timesafari/endorser-ch/src/api/controllers/report-router.js
  • did-jwt Library - DID-based JWT implementation (JavaScript reference)
  • TimeSafari Repository: ~/projects/timesafari/crowd-funder-for-time-pwa/src/libs/crypto/vc/index.ts
    • CONFIRMED: Uses createEndorserJwtForKey() with did-jwt.createJWT() and ES256K signing
    • Signs with Ethereum private key from DID identity
    • Reference implementation for production JWT generation

Notes

  • JWT Implementation: CONFIRMED - TimeSafari repository investigation complete. Endorser-ch API uses DID-based JWTs (ES256K) for API authentication. Current HMAC-SHA256 implementation must be replaced. See INVESTIGATION_JWT_ALGORITHM_RESULTS.md for complete findings.
  • Context Dependency: Consider whether TestNativeFetcher should be a singleton or if context should be injected differently
  • Error Strategy: Decide on error handling strategy - should fetches fail silently (current), or propagate errors for user notification?
  • State Management: Consider whether starred plan IDs should be managed by the app or fetched from API user profile
  • Investigation Status: Item #1 (JWT algorithm) investigation COMPLETE. Confirmed ES256K requirement. Implementation changes required before proceeding with other items.

Verification Summary (2025-10-31)

Items Verified Through Code Inspection:

  1. Item #4 (API Response Structure):

    • Endpoint structure confirmed in endorser-ch source
    • Parser handles both flat and nested structures
    • Null checks exist for most fields (uses .has() checks)
    • ⚠️ Missing: root.has("data") check before accessing data array
    • ⚠️ Missing: wrappedClaimBefore extraction from response
  2. Item #14 (TypeScript Types):

    • Method exists in src/definitions.ts (Line 376)
    • Method signature includes apiBaseUrl, activeDid, jwtSecret
    • Method being called in HomeView.vue (Line 474, onMounted hook)
    • ⚠️ Currently uses as any type assertion (needs plugin rebuild for proper types)
  3. Item #15 (Network Security Config):

    • Confirmed: network_security_config.xml does NOT exist
    • Confirmed: No networkSecurityConfig attribute in AndroidManifest.xml
    • Confirmed: No usesCleartextTraffic attribute in AndroidManifest.xml
    • Needs to be created for HTTP connections to http://10.0.2.2:3000

Last Updated: 2025-10-31 (Checkboxes updated - Items 4, 14, 15 verified through code inspection)
Next Review: After ES256K JWT signing implementation is complete