# 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. โš ๏ธ VERIFY: Determine Correct JWT Signing Algorithm (CRITICAL) - [x] **Status**: โœ… **INVESTIGATION COMPLETE** - See `INVESTIGATION_JWT_ALGORITHM_RESULTS.md` **CRITICAL FINDING**: Endorser-ch API expects **DID-based JWTs (ES256K)**, NOT HMAC-SHA256. Current implementation is WRONG and must be replaced. **Current State**: Uses SHA-256 hash with `jwtSecret:unsignedToken` format, header claims `"alg": "HS256"` **Location**: `TestNativeFetcher.java` โ†’ `generateJWTToken()` **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 **Current Implementation Problem**: - Currently uses HMAC-SHA256 with shared secret (`jwtSecret`) - Server will **reject** these tokens as invalid - Must be replaced with DID-based ES256K signing **Required Implementation Changes:** 1. **Remove HMAC-SHA256 implementation** - Completely incorrect approach 2. **Remove `jwtSecret` parameter** - No longer needed (shared secret not used) 3. **Implement DID-based ES256K signing** - Sign with Ethereum private key 4. **Add DID private key retrieval** - Need mechanism to get private key for `activeDid` 5. **Use Java DID library** - did-jwt-java or web3j for ES256K signing **Implementation Options:** **Option 1: Use did-jwt-java Library (Recommended)** ```java // Add dependency to build.gradle implementation 'io.uport:uport-did-jwt:3.1.0' // or equivalent // Sign with DID private key import io.uport.sdk.did.jwt.DIDJWT; import io.uport.sdk.did.jwt.SimpleSigner; String privateKeyHex = getPrivateKeyForDid(activeDid); // Need to implement SimpleSigner signer = new SimpleSigner(privateKeyHex); String jwt = DIDJWT.createJWT(payload, signer, issuer: activeDid); ``` **Option 2: Use web3j for Ethereum Signing** ```java // Add dependency implementation 'org.web3j:core:4.9.8' import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Sign; ECKeyPair keyPair = getKeyPairForDid(activeDid); // Need to implement Sign.SignatureData signature = Sign.signMessage( unsignedToken.getBytes(StandardCharsets.UTF_8), 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](https://github.com/decentralized-identity/did-jwt) - DID-based JWT library (JavaScript reference) - [did-jwt-java](https://github.com/uport-project/uport-did-jwt) - 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)** ```typescript // 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 }); ``` ```java // 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**: - [x] โœ… Verified algorithm with endorser-ch source code - ES256K confirmed - [x] โœ… Generate JWT in TypeScript using `did-jwt` library (ES256K signing) - [x] โœ… 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`)**: - [x] โœ… Change `jwtSecret: string` โ†’ `jwtToken: string` in `configureNativeFetcher()` signature - [x] โœ… Update JSDoc to reflect that token is pre-generated, not a secret - [x] โœ… Rebuild plugin to update `dist/definitions.d.ts` **2. Update Java Plugin (`android/plugin/.../DailyNotificationPlugin.java`)**: - [x] โœ… Change `String jwtSecret = call.getString("jwtSecret")` โ†’ `String jwtToken = call.getString("jwtToken")` - [x] โœ… Update validation message: "Missing required parameters: apiBaseUrl, activeDid, and jwtToken are required" - [x] โœ… Update `fetcher.configure(apiBaseUrl, activeDid, jwtToken)` call - [x] โœ… Update Javadoc to reflect token-based approach **3. Update Native Interface (`android/plugin/.../NativeNotificationContentFetcher.java`)**: - [x] โœ… Change `default void configure(String apiBaseUrl, String activeDid, String jwtSecret)` โ†’ `default void configure(String apiBaseUrl, String activeDid, String jwtToken)` - [x] โœ… Update Javadoc to explain token is pre-generated by TypeScript **4. Update TestNativeFetcher (`test-apps/.../TestNativeFetcher.java`)**: - [x] โœ… Change `private volatile String jwtSecret;` โ†’ `private volatile String jwtToken;` - [x] โœ… Update `configure()` method signature and implementation - [x] โœ… Remove entire `generateJWTToken()` method (HMAC-SHA256 implementation removed) - [x] โœ… Update `fetchContentWithRetry()` to use `jwtToken` directly - [x] โœ… Remove JWT-related constants (`JWT_EXPIRATION_MINUTES`) - no longer needed - [x] โœ… Remove HMAC-SHA256 imports (`Mac`, `SecretKeySpec`, `Base64`, `MessageDigest`) - not used elsewhere **5. Update HomeView.vue (`test-apps/.../HomeView.vue`)**: - [x] โœ… Implement ES256K JWT generation using `did-jwt` and `ethers` libraries - [x] โœ… Generate JWT before calling `configureNativeFetcher()` using `generateEndorserJWT()` - [x] โœ… Update `configureNativeFetcher()` call to use `jwtToken` instead of `jwtSecret` - [x] โœ… Remove `as any` type assertion after plugin rebuild **6. ES256K JWT Generation Implementation (`test-apps/.../test-user-zero.ts`)**: - [x] โœ… Add `did-jwt@^7.0.0` and `ethers@^6.0.0` dependencies - [x] โœ… 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 - [x] โœ… 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 - [x] **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 - [x] **Status**: โœ… **COMPLETE** **Current State**: โœ… Implemented - `fetchContentWithRetry()` with exponential backoff **Location**: `TestNativeFetcher.java` โ†’ `fetchContentWithRetry()` **Implementation Status**: - [x] โœ… Retry logic for network failures (transient errors) - `fetchContentWithRetry()` with `MAX_RETRIES = 3` - [x] โœ… Distinguish retryable vs non-retryable errors - `shouldRetry()` method checks response codes and retry count - [x] โœ… Log error details for debugging - Detailed logging with retry counts and error messages - [x] โœ… Exponential backoff for retries - `RETRY_DELAY_MS * (1 << retryCount)` implemented **Suggested Implementation**: ```java private static final int MAX_RETRIES = 3; private static final int RETRY_DELAY_MS = 1000; private CompletableFuture> 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) - [x] **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:** - [x] โœ… **Check endorser-ch endpoint implementation:** - [x] โœ… Found `/api/v2/report/plansLastUpdatedBetween` handler in endorser-ch source: `~/projects/timesafari/endorser-ch/src/api/controllers/report-router.js` - [x] โœ… Verified actual response structure - Returns `{ data: [...], hitLimit: boolean }` where data items have `plan` and `wrappedClaimBefore` properties - [x] โœ… Response matches `PlanSummaryAndPreviousClaim` structure - Items have `plan.handleId`, `plan.jwtId`, and `wrappedClaimBefore` properties - [x] โœ… **Current parser handles:** - [x] โœ… 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:** - [x] โœ… Uses `.has()` checks before accessing fields (planId, jwtId) - Lines 502, 511 - [x] โœ… Checks `item.has("plan")` before accessing plan object - Lines 504, 513 - [x] โœ… Checks `dataArray != null` after getting it - Line 491 - [x] โœ… Handles null values safely (planId and jwtId can be null) - Lines 499-518 - [x] โœ… 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) - [x] โœ… Handles missing/null data gracefully (creates default notification if contents empty) - Line 554-568 - [x] โœ… Handles parse errors gracefully (returns empty list on exception) - Line 570-573 **Nested Structure Handling:** - [x] โœ… Handle nested structures correctly - Parser handles both `item.plan.handleId` and `item.planId` formats - [x] โœ… 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**: ```java private List 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**: ```java 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 - [x] **Status**: โœ… **COMPLETE** **Current State**: โœ… Context passed to constructor and stored **Location**: `TestNativeFetcher.java` constructor, `TestApplication.java` **Implementation Status**: - [x] โœ… Pass `Application` context to `TestNativeFetcher` constructor - Done in `TestApplication.onCreate()` - [x] โœ… Store context for SharedPreferences access - Stored as `appContext` and used for SharedPreferences - [x] โœ… Using application context (no leaks) - `context.getApplicationContext()` used **Example**: ```java // 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): - [x] โœ… Check endorser-ch source code for actual endpoint response structure - [x] โœ… Compare with `PlansLastUpdatedResponse` and `PlanSummaryWithPreviousClaim` interfaces in codebase - [x] โœ… 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 - [x] **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:** - [x] โœ… `configureNativeFetcher()` method added to plugin interface (`src/definitions.ts`) - Verified: Line 376 - [x] โœ… Method signature exists with `apiBaseUrl`, `activeDid`, `jwtSecret` parameters - [x] โœ… Method is being called in `HomeView.vue` - Line 474 (`onMounted` hook) - [x] โœ… 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 - [x] **Status**: โœ… **COMPLETE** - Network security config created and AndroidManifest.xml updated **Purpose**: Allow HTTP connections in Android emulator (for localhost:3000) **Verification Results:** - [x] โœ… Checked: `network_security_config.xml` does NOT exist in `android/app/src/main/res/xml/` - [x] โœ… Checked: No `networkSecurityConfig` attribute in `AndroidManifest.xml` - [x] โœ… Checked: No `usesCleartextTraffic` attribute in `AndroidManifest.xml` **Implementation Status**: - [x] โœ… 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` - [x] โœ… Added `android:networkSecurityConfig="@xml/network_security_config"` to `` tag in `AndroidManifest.xml` - [x] โœ… 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 - [x] โœ… Error handling works for network failures - Retry logic implemented - [x] โœ… 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 - [x] โœ… App recovers gracefully from API errors - Error handling and retry logic implemented --- ## ๐Ÿ“š Related Documentation - `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](https://github.com/trentlarson/endorser-ch/tree/master) - **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](https://github.com/decentralized-identity/did-jwt) - 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