chore: commit to move to laptop
This commit is contained in:
677
test-apps/daily-notification-test/TODO_NATIVE_FETCHER.md
Normal file
677
test-apps/daily-notification-test/TODO_NATIVE_FETCHER.md
Normal file
@@ -0,0 +1,677 @@
|
||||
# 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<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)
|
||||
|
||||
- [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<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**:
|
||||
```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 `<application>` 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user