30 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 usingdid-jwtandetherslibraries. 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 anyremoved) - Item #15: Network Security Config - โ COMPLETE (config created, AndroidManifest updated)
๐ด High Priority
1. โ ๏ธ VERIFY: Determine Correct JWT Signing Algorithm (CRITICAL)
- 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()withSimpleSigner(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:
- Remove HMAC-SHA256 implementation - Completely incorrect approach
- Remove
jwtSecretparameter - No longer needed (shared secret not used) - Implement DID-based ES256K signing - Sign with Ethereum private key
- Add DID private key retrieval - Need mechanism to get private key for
activeDid - Use Java DID library - did-jwt-java or web3j for ES256K signing
Implementation Options:
Option 1: Use did-jwt-java Library (Recommended)
// 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
// 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.mdfor 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-jwtlibrary 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 acceptjwtTokeninstead ofjwtSecret - Update Java
configureNativeFetcher()to acceptjwtTokenparameter - Remove JWT generation code from
TestNativeFetcher.generateJWTToken() - Use pre-generated token in
getAuthorizationHeader()method - Update
HomeView.vueto generate JWT using TimeSafari'screateEndorserJwtForDid() - 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-jwtlibrary (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: stringinconfigureNativeFetcher()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 usejwtTokendirectly - โ
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-jwtandetherslibraries - โ
Generate JWT before calling
configureNativeFetcher()usinggenerateEndorserJWT() - โ
Update
configureNativeFetcher()call to usejwtTokeninstead ofjwtSecret - โ
Remove
as anytype assertion after plugin rebuild
6. ES256K JWT Generation Implementation (test-apps/.../test-user-zero.ts):
- โ
Add
did-jwt@^7.0.0andethers@^6.0.0dependencies - โ
Implement
generateEndorserJWT()function:- Derives Ethereum private key from seed phrase using
ethers.HDNodeWallet - Uses
did-jwt.SimpleSignerfor ES256K signing - Creates proper ES256K signed JWTs matching TimeSafari's pattern
- Derives Ethereum private key from seed phrase using
- โ
Export
generateEndorserJWTfor 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.mdto reflect token-based approach - Document how to generate tokens in TypeScript using TimeSafari infrastructure
- Remove references to
jwtSecretand 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()withMAX_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/plansLastUpdatedBetweenhandler 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 haveplanandwrappedClaimBeforeproperties - โ
Response matches
PlanSummaryAndPreviousClaimstructure - Items haveplan.handleId,plan.jwtId, andwrappedClaimBeforeproperties
- โ
Found
-
โ Current parser handles:
- โ
Both flat structure (
jwtId,planId) and nested structure (plan.jwtId,plan.handleId) - โ ๏ธ Missing: Does NOT extract
wrappedClaimBeforefrom response (exists in API response but not used in parser)
- โ
Both flat structure (
โ 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 != nullafter 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 callinggetAsJsonArray("data")(line 490) - Could throwIllegalStateExceptionif "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.handleIdanditem.planIdformats - โ
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
planobject before accessing nested fields (line 505, 514 - currently relies on try-catch) - Log warnings for unexpected response formats (missing "data" field, wrong types)
- Extract
wrappedClaimBeforefrom 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
Applicationcontext toTestNativeFetcherconstructor - Done inTestApplication.onCreate() - โ
Store context for SharedPreferences access - Stored as
appContextand 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
PlansLastUpdatedResponseandPlanSummaryWithPreviousClaiminterfaces 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/orserver/directories for endpoint handler - Check
src/definitions.tsin 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
serverModeto"localhost"intest-user-zero.ts(line 28) for emulator testing - Test API calls to
http://10.0.2.2:3000with 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,jwtSecretparameters - โ
Method is being called in
HomeView.vue- Line 474 (onMountedhook) - โ
Currently uses
(DailyNotification as any).configureNativeFetchertype assertion - Line 454
Remaining Work:
- Rebuild plugin package to ensure TypeScript definitions are up to date in
dist/ - Remove
as anytype assertion inHomeView.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.xmldoes NOT exist inandroid/app/src/main/res/xml/ - โ
Checked: No
networkSecurityConfigattribute inAndroidManifest.xml - โ
Checked: No
usesCleartextTrafficattribute inAndroidManifest.xml
Implementation Status:
- โ
Created
android/app/src/main/res/xml/network_security_config.xmlallowing cleartext traffic for10.0.2.2,localhost, and127.0.0.1 - โ
Added
android:networkSecurityConfig="@xml/network_security_config"to<application>tag inAndroidManifest.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
afterIdparameter (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
๐ Related Documentation
docs/NATIVE_FETCHER_CONFIGURATION.md- Native fetcher configuration guidesrc/types/content-fetcher.ts- TypeScript type definitionssrc/definitions.ts-PlansLastUpdatedResponseandPlanSummaryWithPreviousClaiminterfacesexamples/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/plansLastUpdatedBetweenendpoint handler:~/projects/timesafari/endorser-ch/src/api/controllers/report-router.js
- JWT verification:
- 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()withdid-jwt.createJWT()and ES256K signing - Signs with Ethereum private key from DID identity
- Reference implementation for production JWT generation
- CONFIRMED: Uses
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.mdfor complete findings. - Context Dependency: Consider whether
TestNativeFetchershould 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:
-
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:
wrappedClaimBeforeextraction from response
-
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,onMountedhook) - โ ๏ธ Currently uses
as anytype assertion (needs plugin rebuild for proper types)
- โ
Method exists in
-
Item #15 (Network Security Config):
- โ
Confirmed:
network_security_config.xmldoes NOT exist - โ
Confirmed: No
networkSecurityConfigattribute in AndroidManifest.xml - โ
Confirmed: No
usesCleartextTrafficattribute in AndroidManifest.xml - Needs to be created for HTTP connections to
http://10.0.2.2:3000
- โ
Confirmed:
Last Updated: 2025-10-31 (Checkboxes updated - Items 4, 14, 15 verified through code inspection)
Next Review: After ES256K JWT signing implementation is complete