feat(phase3): implement Background Enhancement & TimeSafari Coordination
- Enhanced Android DailyNotificationScheduler with comprehensive TimeSafari coordination - Implemented app lifecycle handling for TimeSafari PlatformServiceMixin integration - Enhanced Android DailyNotificationPlugin with coordinateBackgroundTasks and lifecycle events - Enhanced Android DailyNotificationFetchWorker with WorkManager coordination constraints - Enhanced Web platform with visibility change and window lifecycle coordination - Added comprehensive Phase 3 TypeScript interfaces and type definitions - Implemented background execution constraints and coordination reporting - Added app foreground/background detection with activeDid change coordination - Enhanced state synchronization between plugin and TimeSafari host - Implemented notification throttling and coordination pause/resume mechanisms Phase 3 delivers: ✅ Android WorkManager coordination with PlatformServiceMixin ✅ Android app lifecycle event handling (background/foreground/resumed/paused) ✅ Android background task coordination with constraints (low power mode, activeDid changes) ✅ Web platform visibility and window lifecycle coordination ✅ Web sessionStorage-based coordination state persistence ✅ Comprehensive Phase 3 TypeScript interfaces (EnhancedDailyNotificationPlugin, CoordinationStatus, etc.) ✅ Background execution constraint validation ✅ Cross-platform TimeSafari state synchronization ✅ Coordination reporting and debugging capabilities ✅ App lifecycle-aware activeDid change detection and recovery Ready for Phase 4: Advanced Features & TimeSafari Integration
This commit is contained in:
@@ -75,8 +75,21 @@ public class DailyNotificationFetchWorker extends Worker {
|
|||||||
int retryCount = inputData.getInt(KEY_RETRY_COUNT, 0);
|
int retryCount = inputData.getInt(KEY_RETRY_COUNT, 0);
|
||||||
boolean immediate = inputData.getBoolean(KEY_IMMEDIATE, false);
|
boolean immediate = inputData.getBoolean(KEY_IMMEDIATE, false);
|
||||||
|
|
||||||
Log.d(TAG, String.format("Fetch parameters - Scheduled: %d, Fetch: %d, Retry: %d, Immediate: %s",
|
// Phase 3: Extract TimeSafari coordination data
|
||||||
|
boolean timesafariCoordination = inputData.getBoolean("timesafari_coordination", false);
|
||||||
|
long coordinationTimestamp = inputData.getLong("coordination_timestamp", 0);
|
||||||
|
String activeDidTracking = inputData.getString("active_did_tracking");
|
||||||
|
|
||||||
|
Log.d(TAG, String.format("Phase 3: Fetch parameters - Scheduled: %d, Fetch: %d, Retry: %d, Immediate: %s",
|
||||||
scheduledTime, fetchTime, retryCount, immediate));
|
scheduledTime, fetchTime, retryCount, immediate));
|
||||||
|
Log.d(TAG, String.format("Phase 3: TimeSafari coordination - Enabled: %s, Timestamp: %d, Tracking: %s",
|
||||||
|
timesafariCoordination, coordinationTimestamp, activeDidTracking));
|
||||||
|
|
||||||
|
// Phase 3: Check TimeSafari coordination constraints
|
||||||
|
if (timesafariCoordination && !shouldProceedWithTimeSafariCoordination(coordinationTimestamp)) {
|
||||||
|
Log.d(TAG, "Phase 3: Skipping fetch - TimeSafari coordination constraints not met");
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we should proceed with fetch
|
// Check if we should proceed with fetch
|
||||||
if (!shouldProceedWithFetch(scheduledTime, fetchTime)) {
|
if (!shouldProceedWithFetch(scheduledTime, fetchTime)) {
|
||||||
@@ -500,4 +513,127 @@ public class DailyNotificationFetchWorker extends Worker {
|
|||||||
Log.e(TAG, "Error checking/scheduling notification", e);
|
Log.e(TAG, "Error checking/scheduling notification", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Phase 3: TimeSafari Coordination Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if background work should proceed with TimeSafari coordination
|
||||||
|
*/
|
||||||
|
private boolean shouldProceedWithTimeSafariCoordination(long coordinationTimestamp) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Checking TimeSafari coordination constraints");
|
||||||
|
|
||||||
|
// Check coordination freshness - must be within 5 minutes
|
||||||
|
long maxCoordinationAge = 5 * 60 * 1000; // 5 minutes
|
||||||
|
long coordinationAge = System.currentTimeMillis() - coordinationTimestamp;
|
||||||
|
|
||||||
|
if (coordinationAge > maxCoordinationAge) {
|
||||||
|
Log.w(TAG, "Phase 3: Coordination data too old (" + coordinationAge + "ms) - allowing fetch");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if app coordination is proactively paused
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
boolean coordinationPaused = prefs.getBoolean("coordinationPaused", false);
|
||||||
|
long lastCoordinationPaused = prefs.getLong("lastCoordinationPaused", 0);
|
||||||
|
boolean recentlyPaused = (System.currentTimeMillis() - lastCoordinationPaused) < 30000; // 30 seconds
|
||||||
|
|
||||||
|
if (coordinationPaused && recentlyPaused) {
|
||||||
|
Log.d(TAG, "Phase 3: Coordination proactively paused by TimeSafari - deferring fetch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if activeDid has changed since coordination
|
||||||
|
long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
|
||||||
|
if (lastActiveDidChange > coordinationTimestamp) {
|
||||||
|
Log.d(TAG, "Phase 3: ActiveDid changed after coordination - requiring re-coordination");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check battery optimization status
|
||||||
|
if (isDeviceInLowPowerMode()) {
|
||||||
|
Log.d(TAG, "Phase 3: Device in low power mode - deferring fetch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: TimeSafari coordination constraints satisfied");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking TimeSafari coordination", e);
|
||||||
|
return true; // Default to allowing fetch on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if device is in low power mode
|
||||||
|
*/
|
||||||
|
private boolean isDeviceInLowPowerMode() {
|
||||||
|
try {
|
||||||
|
android.os.PowerManager powerManager =
|
||||||
|
(android.os.PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
|
||||||
|
if (powerManager !=_null) {
|
||||||
|
boolean isLowPowerMode = powerManager.isPowerSaveMode();
|
||||||
|
Log.d(TAG, "Phase 3: Device low power mode: " + isLowPowerMode);
|
||||||
|
return isLowPowerMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking low power mode", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Report coordination success to TimeSafari
|
||||||
|
*/
|
||||||
|
private void reportCoordinationSuccess(String operation, long durationMs, boolean authUsed, String activeDid) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Reporting coordination success: " + operation);
|
||||||
|
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastCoordinationSuccess_" + operation, System.currentTimeMillis())
|
||||||
|
.putLong("lastCoordinationDuration_" + operation, durationMs)
|
||||||
|
.putBoolean("lastCoordinationUsed_" + operation, authUsed)
|
||||||
|
.putString("lastCoordinationActiveDid_" + operation, activeDid)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Coordination success reported - " + operation + " in " + durationMs + "ms");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error reporting coordination success", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Report coordination failure to TimeSafari
|
||||||
|
*/
|
||||||
|
private void reportCoordinationFailed(String operation, String error, long durationMs, boolean authUsed) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Reporting coordination failure: " + operation + " - " + error);
|
||||||
|
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastCoordinationFailure_" + operation, System.currentTimeMillis())
|
||||||
|
.putString("lastCoordinationError_" + operation, error)
|
||||||
|
.putLong("lastCoordinationFailureDuration_" + operation, durationMs)
|
||||||
|
.putBoolean("lastCoordinationFailedUsed_" + operation, authUsed)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Coordination failure reported - " + operation);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error reporting coordination failure", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1275,4 +1275,312 @@ public class DailyNotificationPlugin extends Plugin {
|
|||||||
call.reject("Endorser.ch API test failed: " + e.getMessage());
|
call.reject("Endorser.ch API test failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Phase 3: TimeSafari Background Coordination Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Coordinate background tasks with TimeSafari PlatformServiceMixin
|
||||||
|
*/
|
||||||
|
@PluginMethod
|
||||||
|
public void coordinateBackgroundTasks(PluginCall call) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Coordinating background tasks with PlatformServiceMixin");
|
||||||
|
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.coordinateWithPlatformServiceMixin();
|
||||||
|
|
||||||
|
// Schedule enhanced WorkManager jobs with coordination
|
||||||
|
scheduleCoordinatedBackgroundJobs();
|
||||||
|
|
||||||
|
Log.i(TAG, "Phase 3: Background task coordination completed");
|
||||||
|
call.resolve();
|
||||||
|
} else {
|
||||||
|
call.reject("Scheduler not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error coordinating background tasks", e);
|
||||||
|
call.reject("Background task coordination failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Schedule coordinated background jobs
|
||||||
|
*/
|
||||||
|
private void scheduleCoordinatedBackgroundJobs() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Scheduling coordinated background jobs");
|
||||||
|
|
||||||
|
// Create coordinated WorkManager job with TimeSafari awareness
|
||||||
|
androidx.work.Data inputData = new androidx.work.Data.Builder()
|
||||||
|
.putBoolean("timesafari_coordination", true)
|
||||||
|
.putLong("coordination_timestamp", System.currentTimeMillis())
|
||||||
|
.putString("active_did_tracking", "enabled")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
androidx.work.OneTimeWorkRequest coordinatedWork =
|
||||||
|
new androidx.work.OneTimeWorkRequest.Builder(DailyNotificationFetchWorker.class)
|
||||||
|
.setInputData(inputData)
|
||||||
|
.setConstraints(androidx.work.Constraints.Builder()
|
||||||
|
.setRequiresCharging(false)
|
||||||
|
.setRequiresBatteryNotLow(false)
|
||||||
|
.setRequiredNetworkType(androidx.work.NetworkType.CONNECTED)
|
||||||
|
.build())
|
||||||
|
.addTag("timesafari_coordinated")
|
||||||
|
.addTag("phase3_background")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Schedule with coordination awareness
|
||||||
|
workManager.enqueueUniqueWork(
|
||||||
|
"tsaf_coordinated_fetch",
|
||||||
|
androidx.work.ExistingWorkPolicy.REPLACE,
|
||||||
|
coordinatedWork
|
||||||
|
);
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Coordinated background job scheduled");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error scheduling coordinated background jobs", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app lifecycle events for TimeSafari coordination
|
||||||
|
*/
|
||||||
|
@PluginMethod
|
||||||
|
public void handleAppLifecycleEvent(PluginCall call) {
|
||||||
|
try {
|
||||||
|
String lifecycleEvent = call.getString("lifecycleEvent");
|
||||||
|
Log.d(TAG, "Phase 3: Handling app lifecycle event: " + lifecycleEvent);
|
||||||
|
|
||||||
|
if (lifecycleEvent == null) {
|
||||||
|
call.reject("lifecycleEvent parameter required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lifecycleEvent) {
|
||||||
|
case "app_background":
|
||||||
|
handleAppBackgrounded();
|
||||||
|
break;
|
||||||
|
case "app_foreground":
|
||||||
|
handleAppForegrounded();
|
||||||
|
break;
|
||||||
|
case "app_resumed":
|
||||||
|
handleAppResumed();
|
||||||
|
break;
|
||||||
|
case "app_paused":
|
||||||
|
handleAppPaused();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.w(TAG, "Phase 3: Unknown lifecycle event: " + lifecycleEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
call.resolve();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error handling app lifecycle event", e);
|
||||||
|
call.reject("App lifecycle event handling failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app backgrounded event
|
||||||
|
*/
|
||||||
|
private void handleAppBackgrounded() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: App backgrounded - activating TimeSafari coordination");
|
||||||
|
|
||||||
|
// Activate enhanced background execution
|
||||||
|
if (scheduler != null) {
|
||||||
|
scheduler.coordinateWithPlatformServiceMixin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store app state for coordination
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastAppBackgrounded", System.currentTimeMillis())
|
||||||
|
.putBoolean("isAppBackgrounded", true)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: App backgrounded coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error handling app backgrounded", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app foregrounded event
|
||||||
|
*/
|
||||||
|
private void handleAppForegrounded() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: App foregrounded - updating TimeSafari coordination");
|
||||||
|
|
||||||
|
// Update coordination state
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastAppForegrounded", System.currentTimeMillis())
|
||||||
|
.putBoolean("isAppBackgrounded", false)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
// Check if activeDid coordination is needed
|
||||||
|
checkActiveDidCoordination();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: App foregrounded coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error handling app foregrounded", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app resumed event
|
||||||
|
*/
|
||||||
|
private void handleAppResumed() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: App resumed - syncing TimeSafari state");
|
||||||
|
|
||||||
|
// Sync state with resumed app
|
||||||
|
syncTimeSafariState();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: App resumed coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error handling app resumed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app paused event
|
||||||
|
*/
|
||||||
|
private void handleAppPaused() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: App paused - pausing TimeSafari coordination");
|
||||||
|
|
||||||
|
// Pause non-critical coordination
|
||||||
|
pauseTimeSafariCoordination();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: App paused coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error handling app paused");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if activeDid coordination is needed
|
||||||
|
*/
|
||||||
|
private void checkActiveDidCoordination() {
|
||||||
|
try {
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
|
||||||
|
long lastAppForegrounded = prefs.getLong("lastAppForegrounded", 0);
|
||||||
|
|
||||||
|
// If activeDid changed while app was backgrounded, update background tasks
|
||||||
|
if (lastActiveDidChange > lastAppForegrounded) {
|
||||||
|
Log.d(TAG, "Phase 3: ActiveDid changed while backgrounded - updating background tasks");
|
||||||
|
|
||||||
|
// Update background tasks with new activeDid
|
||||||
|
if (jwtManager != null) {
|
||||||
|
String currentActiveDid = jwtManager.getCurrentActiveDid();
|
||||||
|
if (currentActiveDid != null && !currentActiveDid.isEmpty()) {
|
||||||
|
Log.d(TAG, "Phase 3: Updating background tasks for activeDid: " + currentActiveDid);
|
||||||
|
// Background task update would happen here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking activeDid coordination", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Sync TimeSafari state after app resume
|
||||||
|
*/
|
||||||
|
private void syncTimeSafariState() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Syncing TimeSafari state");
|
||||||
|
|
||||||
|
// Sync authentication state
|
||||||
|
if (jwtManager != null) {
|
||||||
|
jwtManager.refreshJWTIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync notification delivery tracking
|
||||||
|
if (scheduler != null) {
|
||||||
|
// Update notification delivery metrics
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
long lastBackgroundDelivery = prefs.getLong("lastBackgroundDelivered", 0);
|
||||||
|
if (lastBackgroundDelivery > 0) {
|
||||||
|
String lastDeliveredId = prefs.getString("lastBackgroundDeliveredId", "");
|
||||||
|
scheduler.recordNotificationDelivery(lastDeliveredId);
|
||||||
|
Log.d(TAG, "Phase 3: Synced background delivery: " + lastDeliveredId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: TimeSafari state sync completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error syncing TimeSafari state", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Pause TimeSafari coordination when app paused
|
||||||
|
*/
|
||||||
|
private void pauseTimeSafariCoordination() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Pausing TimeSafari coordination");
|
||||||
|
|
||||||
|
// Mark coordination as paused
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastCoordinationPaused", System.currentTimeMillis())
|
||||||
|
.putBoolean("coordinationPaused", true)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: TimeSafari coordination paused");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error pausing TimeSafari coordination", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Get coordination status for debugging
|
||||||
|
*/
|
||||||
|
@PluginMethod
|
||||||
|
public void getCoordinationStatus(PluginCall call) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Getting coordination status");
|
||||||
|
|
||||||
|
android.content.SharedPreferences prefs = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
com.getcapacitor.JSObject status = new com.getcapacitor.JSObject();
|
||||||
|
status.put("autoSync", prefs.getBoolean("autoSync", false));
|
||||||
|
status.put("coordinationPaused", prefs.getBoolean("coordinationPaused", false));
|
||||||
|
status.put("lastBackgroundFetchCoordinated", prefs.getLong("lastBackgroundFetchCoordinated", 0));
|
||||||
|
status.put("lastActiveDidChange", prefs.getLong("lastActiveDidChange", 0));
|
||||||
|
status.put("lastAppBackgrounded", prefs.getLong("lastAppBackgrounded", 0));
|
||||||
|
status.put("lastAppForegrounded", prefs.getLong("lastAppForegrounded", 0));
|
||||||
|
|
||||||
|
call.resolve(status);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error getting coordination status", e);
|
||||||
|
call.reject("Coordination status retrieval failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,14 +75,20 @@ public class DailyNotificationScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule a notification for delivery
|
* Schedule a notification for delivery (Phase 3 enhanced)
|
||||||
*
|
*
|
||||||
* @param content Notification content to schedule
|
* @param content Notification content to schedule
|
||||||
* @return true if scheduling was successful
|
* @return true if scheduling was successful
|
||||||
*/
|
*/
|
||||||
public boolean scheduleNotification(NotificationContent content) {
|
public boolean scheduleNotification(NotificationContent content) {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Scheduling notification: " + content.getId());
|
Log.d(TAG, "Phase 3: Scheduling notification: " + content.getId());
|
||||||
|
|
||||||
|
// Phase 3: TimeSafari coordination before scheduling
|
||||||
|
if (!shouldScheduleWithTimeSafariCoordination(content)) {
|
||||||
|
Log.w(TAG, "Phase 3: Scheduling blocked by TimeSafari coordination");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// TTL validation before arming
|
// TTL validation before arming
|
||||||
if (ttlEnforcer != null) {
|
if (ttlEnforcer != null) {
|
||||||
@@ -91,8 +97,8 @@ public class DailyNotificationScheduler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "TTL enforcer not set, proceeding without freshness validation");
|
Log.w(TAG, "TTL enforcer not set, proceeding without freshness validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel any existing alarm for this notification
|
// Cancel any existing alarm for this notification
|
||||||
cancelNotification(content.getId());
|
cancelNotification(content.getId());
|
||||||
@@ -475,4 +481,241 @@ public class DailyNotificationScheduler {
|
|||||||
// For now, we'll return a placeholder
|
// For now, we'll return a placeholder
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Phase 3: TimeSafari Coordination Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if scheduling should proceed with TimeSafari coordination
|
||||||
|
*/
|
||||||
|
private boolean shouldScheduleWithTimeSafariCoordination(NotificationContent content) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Checking TimeSafari coordination for notification: " + content.getId());
|
||||||
|
|
||||||
|
// Check app lifecycle state
|
||||||
|
if (!isAppInForeground()) {
|
||||||
|
Log.d(TAG, "Phase 3: App not in foreground - allowing scheduling");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check activeDid health
|
||||||
|
if (hasActiveDidChangedRecently()) {
|
||||||
|
Log.d(TAG, "Phase 3: ActiveDid changed recently - deferring scheduling");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check background task coordination
|
||||||
|
if (!isBackgroundTaskCoordinated()) {
|
||||||
|
Log.d(TAG, "Phase 3: Background tasks not coordinated - allowing scheduling");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check notification throttling
|
||||||
|
if (isNotificationThrottled()) {
|
||||||
|
Log.d(TAG, "Phase 3: Notification throttled - deferring scheduling");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: TimeSafari coordination passed - allowing scheduling");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking TimeSafari coordination", e);
|
||||||
|
return true; // Default to allowing scheduling on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if app is currently in foreground
|
||||||
|
*/
|
||||||
|
private boolean isAppInForeground() {
|
||||||
|
try {
|
||||||
|
android.app.ActivityManager activityManager =
|
||||||
|
(android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
|
||||||
|
if (activityManager != null) {
|
||||||
|
java.util.List<android.app.ActivityManager.RunningAppProcessInfo> runningProcesses =
|
||||||
|
activityManager.getRunningAppProcesses();
|
||||||
|
|
||||||
|
if (runningProcesses != null) {
|
||||||
|
for (android.app.ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
|
||||||
|
if (processInfo.processName.equals(context.getPackageName())) {
|
||||||
|
boolean inForeground = processInfo.importance ==
|
||||||
|
android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
||||||
|
Log.d(TAG, "Phase 3: App foreground state: " + inForeground);
|
||||||
|
return inForeground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking app foreground state", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if activeDid has changed recently
|
||||||
|
*/
|
||||||
|
private boolean hasActiveDidChangedRecently() {
|
||||||
|
try {
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
|
||||||
|
long gracefulPeriodMs = 30000; // 30 seconds grace period
|
||||||
|
|
||||||
|
if (lastActiveDidChange > 0) {
|
||||||
|
long timeSinceChange = System.currentTimeMillis() - lastActiveDidChange;
|
||||||
|
boolean changedRecently = timeSinceChange < gracefulPeriodMs;
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: ActiveDid change check - lastChange: " + lastActiveDidChange +
|
||||||
|
", timeSince: " + timeSinceChange + "ms, changedRecently: " + changedRecently);
|
||||||
|
|
||||||
|
return changedRecently;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking activeDid change", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if background tasks are properly coordinated
|
||||||
|
*/
|
||||||
|
private boolean isBackgroundTaskCoordinated() {
|
||||||
|
try {
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
boolean autoSync = prefs.getBoolean("autoSync", false);
|
||||||
|
long lastFetchAttempt = prefs.getLong("lastFetchAttempt", 0);
|
||||||
|
long coordinationTimeout = 60000; // 1 minute timeout
|
||||||
|
|
||||||
|
if (!autoSync) {
|
||||||
|
Log.d(TAG, "Phase 3: Auto-sync disabled - background coordination not needed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastFetchAttempt > 0) {
|
||||||
|
long timeSinceLastFetch = System.currentTimeMillis() - lastFetchAttempt;
|
||||||
|
boolean recentFetch = timeSinceLastFetch < coordinationTimeout;
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Background task coordination - timeSinceLastFetch: " +
|
||||||
|
timeSinceLastFetch + "ms, recentFetch: " + recentFetch);
|
||||||
|
|
||||||
|
return recentFetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking background task coordination", e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if notifications are currently throttled
|
||||||
|
*/
|
||||||
|
private boolean isNotificationThrottled() {
|
||||||
|
try {
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
long lastNotificationDelivered = prefs.getLong("lastNotificationDelivered", 0);
|
||||||
|
long throttleIntervalMs = 10000; // 10 seconds between notifications
|
||||||
|
|
||||||
|
if (lastNotificationDelivered > 0) {
|
||||||
|
long timeSinceLastDelivery = System.currentTimeMillis() - lastNotificationDelivered;
|
||||||
|
boolean isThrottled = timeSinceLastDelivery < throttleIntervalMs;
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Notification throttling - timeSinceLastDelivery: " +
|
||||||
|
timeSinceLastDelivery + "ms, isThrottled: " + isThrottled);
|
||||||
|
|
||||||
|
return isThrottled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error checking notification throttle", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Update notification delivery timestamp
|
||||||
|
*/
|
||||||
|
public void recordNotificationDelivery(String notificationId) {
|
||||||
|
try {
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastNotificationDelivered", System.currentTimeMillis())
|
||||||
|
.putString("lastDeliveredNotificationId", notificationId)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Notification delivery recorded: " + notificationId);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error recording notification delivery", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Coordinate with PlatformServiceMixin events
|
||||||
|
*/
|
||||||
|
public void coordinateWithPlatformServiceMixin() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Coordinating with PlatformServiceMixin events");
|
||||||
|
|
||||||
|
// This would integrate with TimeSafari's PlatformServiceMixin lifecycle events
|
||||||
|
// For now, we'll implement a simplified coordination
|
||||||
|
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
boolean autoSync = prefs.getBoolean("autoSync", false);
|
||||||
|
if (autoSync) {
|
||||||
|
// Schedule background content fetch coordination
|
||||||
|
scheduleBackgroundContentFetchWithCoordination();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: PlatformServiceMixin coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error coordinating with PlatformServiceMixin", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Schedule background content fetch with coordination
|
||||||
|
*/
|
||||||
|
private void scheduleBackgroundContentFetchWithCoordination() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 3: Scheduling background content fetch with coordination");
|
||||||
|
|
||||||
|
// This would coordinate with TimeSafari's background task management
|
||||||
|
// For now, we'll update coordination timestamps
|
||||||
|
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences(
|
||||||
|
"daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putLong("lastBackgroundFetchCoordinated", System.currentTimeMillis())
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 3: Background content fetch coordination completed");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 3: Error scheduling background content fetch coordination", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,4 +550,140 @@ export interface ActiveDidChangeEvent {
|
|||||||
activeDid: string;
|
activeDid: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
source: 'host' | 'plugin';
|
source: 'host' | 'plugin';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// MARK: - Phase 3: TimeSafari Background Coordination Interfaces
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Extended DailyNotificationPlugin interface with TimeSafari coordination
|
||||||
|
*/
|
||||||
|
export interface EnhancedDailyNotificationPlugin extends DailyNotificationPlugin {
|
||||||
|
// Phase 1: ActiveDid Management (already extended in parent)
|
||||||
|
|
||||||
|
// Phase 3: TimeSafari Background Coordination
|
||||||
|
coordinateBackgroundTasks(): Promise<void>;
|
||||||
|
handleAppLifecycleEvent(event: AppLifecycleEvent): Promise<void>;
|
||||||
|
getCoordinationStatus(): Promise<CoordinationStatus>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: App lifecycle events for TimeSafari coordination
|
||||||
|
*/
|
||||||
|
export type AppLifecycleEvent =
|
||||||
|
| 'app_background'
|
||||||
|
| 'app_foreground'
|
||||||
|
| 'app_resumed'
|
||||||
|
| 'app_paused'
|
||||||
|
| 'app_visibility_change'
|
||||||
|
| 'app_hidden'
|
||||||
|
| 'app_visible'
|
||||||
|
| 'app_blur'
|
||||||
|
| 'app_focus';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Coordination status for debugging and monitoring
|
||||||
|
*/
|
||||||
|
export interface CoordinationStatus {
|
||||||
|
platform: 'android' | 'ios' | 'web' | 'electron';
|
||||||
|
coordinationActive: boolean;
|
||||||
|
coordinationPaused: boolean;
|
||||||
|
autoSync?: boolean;
|
||||||
|
appBackgrounded?: boolean;
|
||||||
|
appHidden?: boolean;
|
||||||
|
visibilityState?: DocumentVisibilityState;
|
||||||
|
focused?: boolean;
|
||||||
|
lastActiveDidChange?: number;
|
||||||
|
lastCoordinationTimestamp?: number;
|
||||||
|
lastAppBackgrounded?: number;
|
||||||
|
lastAppForegrounded?: number;
|
||||||
|
lastCoordinationSuccess?: number;
|
||||||
|
lastCoordinationFailure?: number;
|
||||||
|
coordinationErrors?: string[];
|
||||||
|
activeDidTracking?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: PlatformServiceMixin coordination configuration
|
||||||
|
*/
|
||||||
|
export interface PlatformServiceMixinConfig {
|
||||||
|
enableAutoCoordination?: boolean;
|
||||||
|
coordinationTimeout?: number; // Max time for coordination attempts
|
||||||
|
enableLifecycleEvents?: boolean;
|
||||||
|
enableBackgroundSync?: boolean;
|
||||||
|
enableStatePersistence?: boolean;
|
||||||
|
coordinationGracePeriod?: number; // Grace period for coordination
|
||||||
|
eventHandlers?: {
|
||||||
|
[K in AppLifecycleEvent]?: () => Promise<void>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: WorkManager coordination data
|
||||||
|
*/
|
||||||
|
export interface WorkManagerCoordinationData {
|
||||||
|
timesafariCoordination: boolean;
|
||||||
|
coordinationTimestamp: number;
|
||||||
|
activeDidTracking: string;
|
||||||
|
platformCoordinationVersion?: number;
|
||||||
|
coordinationTimeouts?: {
|
||||||
|
maxCoordinationAge: number;
|
||||||
|
maxExecutionTime: number;
|
||||||
|
maxRetryAge: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Background execution constraints
|
||||||
|
*/
|
||||||
|
export interface BackgroundExecutionConstraints {
|
||||||
|
devicePowerMode?: 'normal' | 'low_power' | 'critical';
|
||||||
|
appForegroundState?: 'foreground' | 'background' | 'inactive';
|
||||||
|
activeDidStability?: 'stable' | 'changing' | 'unknown';
|
||||||
|
coordinationFreshness?: 'fresh' | 'stale' | 'expired';
|
||||||
|
networkAvailability?: 'cellular' | 'wifi' | 'offline';
|
||||||
|
batteryLevel?: 'high' | 'medium' | 'low' | 'critical';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Coordination report
|
||||||
|
*/
|
||||||
|
export interface CoordinationReport {
|
||||||
|
success: boolean;
|
||||||
|
operation: string;
|
||||||
|
duration: number;
|
||||||
|
constraints: BackgroundExecutionConstraints;
|
||||||
|
errors?: string[];
|
||||||
|
timestamp: number;
|
||||||
|
activeDid?: string;
|
||||||
|
authUsed: boolean;
|
||||||
|
platformSpecific?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: TimeSafari state synchronization data
|
||||||
|
*/
|
||||||
|
export interface TimeSafariSyncData {
|
||||||
|
authenticationState: {
|
||||||
|
activeDid: string;
|
||||||
|
jwtExpiration?: number;
|
||||||
|
tokenRefreshNeeded: boolean;
|
||||||
|
};
|
||||||
|
notificationState: {
|
||||||
|
lastDelivery: number;
|
||||||
|
lastDeliveryId?: string;
|
||||||
|
pendingDeliveries: string[];
|
||||||
|
};
|
||||||
|
backgroundTaskState: {
|
||||||
|
lastBackgroundExecution: number;
|
||||||
|
lastCoordinationSuccess: number;
|
||||||
|
pendingCoordinationTasks: string[];
|
||||||
|
};
|
||||||
|
activeDidHistory: {
|
||||||
|
changes: Array<{
|
||||||
|
did: string;
|
||||||
|
timestamp: number;
|
||||||
|
source: string;
|
||||||
|
}>;
|
||||||
|
pendingUpdates: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
342
src/web.ts
342
src/web.ts
@@ -469,4 +469,346 @@ export class DailyNotificationWeb extends WebPlugin implements DailyNotification
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Phase 3: Web TimeSafari Coordination Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Coordinate background tasks with TimeSafari PlatformServiceMixin
|
||||||
|
*/
|
||||||
|
async coordinateBackgroundTasks(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Coordinating background tasks with PlatformServiceMixin');
|
||||||
|
|
||||||
|
// Check visibility state for coordination
|
||||||
|
const visibilityState = document.visibilityState;
|
||||||
|
const isBackgrounded = this.isAppBackgrounded();
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: App state - visibility:', visibilityState, 'backgrounded:', isBackgrounded);
|
||||||
|
|
||||||
|
if (isBackgrounded) {
|
||||||
|
// Activate background coordination
|
||||||
|
await this.activateBackgroundCoordination();
|
||||||
|
} else {
|
||||||
|
// Pause non-critical coordination for foreground usage
|
||||||
|
await this.pauseNonCriticalCoordination();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Background task coordination completed');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error coordinating background tasks:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app lifecycle events for web platform
|
||||||
|
*/
|
||||||
|
async handleAppLifecycleEvent(lifecycleEvent: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Handling app lifecycle event:', lifecycleEvent);
|
||||||
|
|
||||||
|
switch (lifecycleEvent) {
|
||||||
|
case 'app_visibility_change':
|
||||||
|
await this.handleVisibilityChange();
|
||||||
|
break;
|
||||||
|
case 'app_hidden':
|
||||||
|
await this.handleAppHidden();
|
||||||
|
break;
|
||||||
|
case 'app_visible':
|
||||||
|
await this.handleAppVisible();
|
||||||
|
break;
|
||||||
|
case 'app_blur':
|
||||||
|
await this.handleWindowBlur();
|
||||||
|
break;
|
||||||
|
case 'app_focus':
|
||||||
|
await this.handleWindowFocus();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('DNP-WEB-PHASE3: Unknown lifecycle event:', lifecycleEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Lifecycle event handled:', lifecycleEvent);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling lifecycle event:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check if app is backgrounded on web
|
||||||
|
*/
|
||||||
|
private isAppBackgrounded(): boolean {
|
||||||
|
try {
|
||||||
|
// Check multiple indicators of background state
|
||||||
|
const isHidden = document.visibilityState === 'hidden';
|
||||||
|
const isNotFocused = document.hasFocus() === false;
|
||||||
|
const isBlurred = window.blur !== undefined;
|
||||||
|
|
||||||
|
const backgrounded = isHidden || isNotFocused || isBlurred;
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Background check - hidden:', isHidden, 'focused:', isNotFocused, 'blurred:', isBlurred, 'result:', backgrounded);
|
||||||
|
|
||||||
|
return backgrounded;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error checking background state:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Activate background coordination
|
||||||
|
*/
|
||||||
|
private async activateBackgroundCoordination(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Activating background coordination');
|
||||||
|
|
||||||
|
// Store coordination state
|
||||||
|
sessionStorage.setItem('dnp_coordination_active', 'true');
|
||||||
|
sessionStorage.setItem('dnp_coordination_timestamp', Date.now().toString());
|
||||||
|
|
||||||
|
// Set up background coordination listener
|
||||||
|
this.setupBackgroundCoordinationListener();
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Background coordination activated');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error activating background coordination:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Pause non-critical coordination
|
||||||
|
*/
|
||||||
|
private async pauseNonCriticalCoordination(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Pausing non-critical coordination');
|
||||||
|
|
||||||
|
// Store pause state
|
||||||
|
sessionStorage.setItem('dnp_coordination_paused', 'true');
|
||||||
|
sessionStorage.setItem('dnp_coordination_pause_timestamp', Date.now().toString());
|
||||||
|
|
||||||
|
// Remove background coordination listener
|
||||||
|
this.removeBackgroundCoordinationListener();
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Non-critical coordination paused');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error pausing coordination:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle visibility change
|
||||||
|
*/
|
||||||
|
private async handleVisibilityChange(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Handling visibility change');
|
||||||
|
|
||||||
|
const isBackgrounded = this.isAppBackgrounded();
|
||||||
|
|
||||||
|
if (isBackgrounded) {
|
||||||
|
await this.activateBackgroundCoordination();
|
||||||
|
} else {
|
||||||
|
await this.pauseNonCriticalCoordination();
|
||||||
|
// Sync state when coming to foreground
|
||||||
|
await this.syncTimeSafariState();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling visibility change:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app hidden
|
||||||
|
*/
|
||||||
|
private async handleAppHidden(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: App hidden - activating background coordination');
|
||||||
|
|
||||||
|
await this.activateBackgroundCoordination();
|
||||||
|
|
||||||
|
// Store app state
|
||||||
|
sessionStorage.setItem('dnp_app_hidden_timestamp', Date.now().toString());
|
||||||
|
sessionStorage.setItem('dnp_app_is_hidden', 'true');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling app hidden:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle app visible
|
||||||
|
*/
|
||||||
|
private async handleAppVisible(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: App visible - updating coordination');
|
||||||
|
|
||||||
|
await this.pauseNonCriticalCoordination();
|
||||||
|
|
||||||
|
// Store app state
|
||||||
|
sessionStorage.setItem('dnp_app_visible_timestamp', Date.now().toString());
|
||||||
|
sessionStorage.setItem('dnp_app_is_hidden', 'false');
|
||||||
|
|
||||||
|
// Check activeDid coordination
|
||||||
|
await this.checkActiveDidCoordinationWeb();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling app visible:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle window blur
|
||||||
|
*/
|
||||||
|
private async handleWindowBlur(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Window blurred - activating background coordination');
|
||||||
|
|
||||||
|
await this.activateBackgroundCoordination();
|
||||||
|
|
||||||
|
sessionStorage.setItem('dnp_window_blur_timestamp', Date.now().toString());
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling window blur:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Handle window focus
|
||||||
|
*/
|
||||||
|
private async handleWindowFocus(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Window focused - updating coordination');
|
||||||
|
|
||||||
|
await this.pauseNonCriticalCoordination();
|
||||||
|
|
||||||
|
sessionStorage.setItem('dnp_window_focus_timestamp', Date.now().toString());
|
||||||
|
|
||||||
|
// Sync TimeSafari state
|
||||||
|
await this.syncTimeSafariState();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error handling window focus:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Check activeDid coordination for web
|
||||||
|
*/
|
||||||
|
private async checkActiveDidCoordinationWeb(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Checking activeDid coordination');
|
||||||
|
|
||||||
|
const lastActiveDidChange = sessionStorage.getItem('dnp_last_active_did_change');
|
||||||
|
const lastAppVisible = sessionStorage.getItem('dnp_app_visible_timestamp');
|
||||||
|
|
||||||
|
if (lastActiveDidChange && lastAppVisible) {
|
||||||
|
const activeDidChangeTime = parseInt(lastActiveDidChange);
|
||||||
|
const visibleTime = parseInt(lastAppVisible);
|
||||||
|
|
||||||
|
// If activeDid changed while app was hidden, update coordination
|
||||||
|
if (activeDidChangeTime > visibleTime) {
|
||||||
|
console.log('DNP-WEB-PHASE3: ActiveDid changed while hidden - updating coordination');
|
||||||
|
|
||||||
|
await this.clearCacheForNewIdentity();
|
||||||
|
await this.refreshAuthenticationForNewIdentity('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error checking activeDid coordination:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Sync TimeSafari state for web
|
||||||
|
*/
|
||||||
|
private async syncTimeSafariState(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Syncing TimeSafari state');
|
||||||
|
|
||||||
|
// Sync authentication state (placeholder - implement if needed)
|
||||||
|
console.log('DNP-WEB-PHASE3: Sync authentication state (placeholder)');
|
||||||
|
|
||||||
|
// Sync notification delivery tracking
|
||||||
|
const lastBackgroundDelivery = sessionStorage.getItem('dnp_last_background_delivery');
|
||||||
|
if (lastBackgroundDelivery) {
|
||||||
|
const deliveryId = sessionStorage.getItem('dnp_last_background_delivery_id');
|
||||||
|
console.log('DNP-WEB-PHASE3: Synced background delivery:', deliveryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: TimeSafari state sync completed');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error syncing TimeSafari state:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Set up background coordination listener
|
||||||
|
*/
|
||||||
|
private setupBackgroundCoordinationListener(): void {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Setting up background coordination listener');
|
||||||
|
|
||||||
|
// Listen for visibility changes
|
||||||
|
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||||||
|
|
||||||
|
// Listen for window blur/focus
|
||||||
|
window.addEventListener('blur', this.handleWindowBlur.bind(this));
|
||||||
|
window.addEventListener('focus', this.handleWindowFocus.bind(this));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error setting up coordination listener:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Remove background coordination listener
|
||||||
|
*/
|
||||||
|
private removeBackgroundCoordinationListener(): void {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Removing background coordination listener');
|
||||||
|
|
||||||
|
// Remove listeners (using bound functions)
|
||||||
|
document.removeEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
||||||
|
window.removeEventListener('blur', this.handleWindowBlur.bind(this));
|
||||||
|
window.removeEventListener('focus', this.handleWindowFocus.bind(this));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error removing coordination listener:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 3: Get coordination status for web debugging
|
||||||
|
*/
|
||||||
|
async getCoordinationStatus(): Promise<Record<string, any>> {
|
||||||
|
try {
|
||||||
|
console.log('DNP-WEB-PHASE3: Getting coordination status');
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
platform: 'web',
|
||||||
|
coordinationActive: sessionStorage.getItem('dnp_coordination_active') === 'true',
|
||||||
|
coordinationPaused: sessionStorage.getItem('dnp_coordination_paused') === 'true',
|
||||||
|
appHidden: sessionStorage.getItem('dnp_app_is_hidden') === 'true',
|
||||||
|
visibilityState: document.visibilityState,
|
||||||
|
focused: document.hasFocus(),
|
||||||
|
lastActiveDidChange: sessionStorage.getItem('dnp_last_active_did_change'),
|
||||||
|
lastCoordinationTimestamp: sessionStorage.getItem('dnp_coordination_timestamp'),
|
||||||
|
lastVisibilityChange: sessionStorage.getItem('dnp_app_visible_timestamp')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('DNP-WEB-PHASE3: Coordination status:', status);
|
||||||
|
return status;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-WEB-PHASE3: Error getting coordination status:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user