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.
 
 
 
 
 
 

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.ts โ†’ generateEndorserJWT()

๐ŸŸก 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: string โ†’ jwtToken: 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/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.java โ†’ getStarredPlanIds(), 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.java โ†’ fetchContentWithRetry()

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.java โ†’ parseApiResponse()

โœ… 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/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