feat(phase2): implement ActiveDid Integration & TimeSafari API Enhancement
- Enhanced ConfigureOptions with comprehensive TimeSafari activeDid configuration - Extended ContentFetchConfig with Endorser.ch API endpoints and TimeSafari config - Added detailed TimeSafari notification types (Offers, Projects, People, Items) - Implemented Host-provided activeDid Plugin Configuration with auto-sync - Enhanced Android retry logic with TimeSafari activeDid change detection - Enhanced Web retry logic with Phase 2 ActiveDid change support - Added comprehensive TimeSafari fallback content generation - Implemented cross-platform ActiveDid change event tracking Phase 2 delivers: ✅ Enhanced ConfigureOptions with host-provided activeDid patterns ✅ Extension of ContentFetchConfig with Endorser.ch endpoints ✅ Complete TimeSafari notification type definitions ✅ Host-provided activeDid Plugin Configuration implementation ✅ Enhanced Android retry logic with activeDid change detection ✅ Enhanced Web retry logic with session-based activeDid tracking ✅ TimeSafari-aware fallback content generation ✅ Comprehensive configuration storage and persistence Ready for Phase 3: Background Enhancement & TimeSafari Coordination
This commit is contained in:
@@ -203,23 +203,29 @@ public class DailyNotificationFetchWorker extends Worker {
|
|||||||
*/
|
*/
|
||||||
private Result handleFailedFetch(int retryCount, long scheduledTime) {
|
private Result handleFailedFetch(int retryCount, long scheduledTime) {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Handling failed fetch - Retry: " + retryCount);
|
Log.d(TAG, "Phase 2: Handling failed fetch - Retry: " + retryCount);
|
||||||
|
|
||||||
if (retryCount < MAX_RETRY_ATTEMPTS) {
|
// Phase 2: Check for TimeSafari special retry triggers
|
||||||
// Schedule retry
|
if (shouldRetryForActiveDidChange()) {
|
||||||
scheduleRetry(retryCount + 1, scheduledTime);
|
Log.d(TAG, "Phase 2: ActiveDid change detected - extending retry quota");
|
||||||
Log.i(TAG, "Scheduled retry attempt " + (retryCount + 1));
|
retryCount = 0; // Reset retry count for activeDid change
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retryCount < MAX_RETRIES_FOR_TIMESAFARI()) {
|
||||||
|
// Phase 2: Schedule enhanced retry with activeDid consideration
|
||||||
|
scheduleRetryWithActiveDidSupport(retryCount + 1, scheduledTime);
|
||||||
|
Log.i(TAG, "Phase 2: Scheduled retry attempt " + (retryCount + 1) + " with TimeSafari support");
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Max retries reached - use fallback content
|
// Max retries reached - use fallback content
|
||||||
Log.w(TAG, "Max retries reached, using fallback content");
|
Log.w(TAG, "Phase 2: Max retries reached, using fallback content");
|
||||||
useFallbackContent(scheduledTime);
|
useFallbackContentWithActiveDidSupport(scheduledTime);
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error handling failed fetch", e);
|
Log.e(TAG, "Phase 2: Error handling failed fetch", e);
|
||||||
return Result.failure();
|
return Result.failure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,6 +281,108 @@ public class DailyNotificationFetchWorker extends Worker {
|
|||||||
return Math.min(exponentialDelay, maxDelay);
|
return Math.min(exponentialDelay, maxDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Phase 2: TimeSafari ActiveDid Enhancement Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Check if retry is needed due to activeDid change
|
||||||
|
*/
|
||||||
|
private boolean shouldRetryForActiveDidChange() {
|
||||||
|
try {
|
||||||
|
// Check if activeDid has changed since last fetch attempt
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences("daily_notification_timesafari", android.content.Context.MODE_PRIVATE);
|
||||||
|
long lastFetchAttempt = prefs.getLong("lastFetchAttempt", 0);
|
||||||
|
long lastActiveDidChange = prefs.getLong("lastActiveDidChange", 0);
|
||||||
|
|
||||||
|
boolean activeDidChanged = lastActiveDidChange > lastFetchAttempt;
|
||||||
|
|
||||||
|
if (activeDidChanged) {
|
||||||
|
Log.d(TAG, "Phase 2: ActiveDid change detected in retry logic");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 2: Error checking activeDid change", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Get max retries with TimeSafari enhancements
|
||||||
|
*/
|
||||||
|
private int MAX_RETRIES_FOR_TIMESAFARI() {
|
||||||
|
// Base retries + additional for activeDid changes
|
||||||
|
return MAX_RETRY_ATTEMPTS + 2; // Extra retries for TimeSafari integration
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Schedule retry with activeDid support
|
||||||
|
*/
|
||||||
|
private void scheduleRetryWithActiveDidSupport(int retryCount, long scheduledTime) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 2: Scheduling retry attempt " + retryCount + " with TimeSafari support");
|
||||||
|
|
||||||
|
// Store the last fetch attempt time for activeDid change detection
|
||||||
|
android.content.SharedPreferences prefs = context.getSharedPreferences("daily_notification_timesafari", android.content.Context.MODE_PRIVATE);
|
||||||
|
prefs.edit().putLong("lastFetchAttempt", System.currentTimeMillis()).apply();
|
||||||
|
|
||||||
|
// Delegate to original retry logic
|
||||||
|
scheduleRetry(retryCount, scheduledTime);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 2: Error scheduling enhanced retry", e);
|
||||||
|
// Fallback to original retry logic
|
||||||
|
scheduleRetry(retryCount, scheduledTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Use fallback content with activeDid support
|
||||||
|
*/
|
||||||
|
private void useFallbackContentWithActiveDidSupport(long scheduledTime) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Phase 2: Using fallback content with TimeSafari support");
|
||||||
|
|
||||||
|
// Generate TimeSafari-aware fallback content
|
||||||
|
NotificationContent fallbackContent = generateTimeSafariFallbackContent();
|
||||||
|
|
||||||
|
if (fallbackContent != null) {
|
||||||
|
storage.saveNotificationContent(fallbackContent);
|
||||||
|
Log.i(TAG, "Phase 2: TimeSafari fallback content saved");
|
||||||
|
} else {
|
||||||
|
// Fallback to original logic
|
||||||
|
useFallbackContent(scheduledTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 2: Error using enhanced fallback content", e);
|
||||||
|
// Fallback to original logic
|
||||||
|
useFallbackContent(scheduledTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Generate TimeSafari-aware fallback content
|
||||||
|
*/
|
||||||
|
private NotificationContent generateTimeSafariFallbackContent() {
|
||||||
|
try {
|
||||||
|
// Generate fallback content specific to TimeSafari context
|
||||||
|
NotificationContent content = new NotificationContent();
|
||||||
|
content.id = "timesafari_fallback_" + System.currentTimeMillis();
|
||||||
|
content.title = "TimeSafari Update Available";
|
||||||
|
content.body = "Your community updates are ready. Tap to view offers, projects, and connections.";
|
||||||
|
content.fetchTime = System.currentTimeMillis();
|
||||||
|
content.scheduledTime = System.currentTimeMillis() + 30000; // 30 seconds from now
|
||||||
|
|
||||||
|
return content;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Phase 2: Error generating TimeSafari fallback content", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use fallback content when all retries fail
|
* Use fallback content when all retries fail
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -987,36 +987,90 @@ public class DailyNotificationPlugin extends Plugin {
|
|||||||
*/
|
*/
|
||||||
private void configureActiveDidIntegration(JSObject config) {
|
private void configureActiveDidIntegration(JSObject config) {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Configuring activeDid integration");
|
Log.d(TAG, "Configuring Phase 2 activeDid integration");
|
||||||
|
|
||||||
String platform = config.getString("platform", "android");
|
String platform = config.getString("platform", "android");
|
||||||
String storageType = config.getString("storageType", "plugin-managed");
|
String storageType = config.getString("storageType", "plugin-managed");
|
||||||
Integer jwtExpirationSeconds = config.getInteger("jwtExpirationSeconds", 60);
|
Integer jwtExpirationSeconds = config.getInteger("jwtExpirationSeconds", 60);
|
||||||
String apiServer = config.getString("apiServer");
|
String apiServer = config.getString("apiServer");
|
||||||
|
|
||||||
Log.d(TAG, "ActiveDid config - Platform: " + platform + ", Storage: " + storageType +
|
// Phase 2: Host-provided activeDid initial configuration
|
||||||
", JWT Expiry: " + jwtExpirationSeconds + "s, API Server: " + apiServer);
|
String initialActiveDid = config.getString("activeDid");
|
||||||
|
boolean autoSync = config.getBoolean("autoSync", false);
|
||||||
|
Integer identityChangeGraceSeconds = config.getInteger("identityChangeGraceSeconds", 30);
|
||||||
|
|
||||||
// Configure JWT manager with custom expiration
|
Log.d(TAG, "Phase 2 ActiveDid config - Platform: " + platform +
|
||||||
|
", Storage: " + storageType + ", JWT Expiry: " + jwtExpirationSeconds + "s" +
|
||||||
|
", API Server: " + apiServer + ", Initial ActiveDid: " +
|
||||||
|
(initialActiveDid != null ? initialActiveDid.substring(0, Math.min(20, initialActiveDid.length())) + "..." : "null") +
|
||||||
|
", AutoSync: " + autoSync + ", Grace Period: " + identityChangeGraceSeconds + "s");
|
||||||
|
|
||||||
|
// Phase 2: Configure JWT manager with auto-sync capabilities
|
||||||
if (jwtManager != null) {
|
if (jwtManager != null) {
|
||||||
// We'll set the JWT expiration when activeDid is provided
|
if (initialActiveDid != null && !initialActiveDid.isEmpty()) {
|
||||||
Log.d(TAG, "JWT manager configured for activeDid integration");
|
jwtManager.setActiveDid(initialActiveDid, jwtExpirationSeconds);
|
||||||
|
Log.d(TAG, "Phase 2: Initial ActiveDid set in JWT manager");
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Phase 2: JWT manager configured with auto-sync: " + autoSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure enhanced fetcher with API server
|
// Phase 2: Configure enhanced fetcher with TimeSafari API support
|
||||||
if (enhancedFetcher != null && apiServer != null && !apiServer.isEmpty()) {
|
if (enhancedFetcher != null && apiServer != null && !apiServer.isEmpty()) {
|
||||||
enhancedFetcher.setApiServerUrl(apiServer);
|
enhancedFetcher.setApiServerUrl(apiServer);
|
||||||
Log.d(TAG, "Enhanced fetcher configured with API server: " + apiServer);
|
Log.d(TAG, "Phase 2: Enhanced fetcher configured with API server: " + apiServer);
|
||||||
|
|
||||||
|
// Phase 2: Set up TimeSafari-specific configuration
|
||||||
|
if (initialActiveDid != null && !initialActiveDid.isEmpty()) {
|
||||||
|
EnhancedDailyNotificationFetcher.TimeSafariUserConfig userConfig =
|
||||||
|
new EnhancedDailyNotificationFetcher.TimeSafariUserConfig();
|
||||||
|
userConfig.activeDid = initialActiveDid;
|
||||||
|
userConfig.fetchOffersToPerson = true;
|
||||||
|
userConfig.fetchOffersToProjects = true;
|
||||||
|
userConfig.fetchProjectUpdates = true;
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 2: TimeSafari user configuration prepared");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "ActiveDid integration configured successfully");
|
// Phase 2: Store auto-sync configuration for future use
|
||||||
|
storeAutoSyncConfiguration(autoSync, identityChangeGraceSeconds);
|
||||||
|
|
||||||
|
Log.i(TAG, "Phase 2 ActiveDid integration configured successfully");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error configuring activeDid integration", e);
|
Log.e(TAG, "Error configuring Phase 2 activeDid integration", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store auto-sync configuration for background tasks
|
||||||
|
*/
|
||||||
|
private void storeAutoSyncConfiguration(boolean autoSync, int gracePeriodSeconds) {
|
||||||
|
try {
|
||||||
|
if (storage != null) {
|
||||||
|
// Store auto-sync settings in plugin storage
|
||||||
|
Map<String, Object> syncConfig = new HashMap<>();
|
||||||
|
syncConfig.put("autoSync", autoSync);
|
||||||
|
syncConfig.put("gracePeriodSeconds", gracePeriodSeconds);
|
||||||
|
syncConfig.put("configuredAt", System.currentTimeMillis());
|
||||||
|
|
||||||
|
// Store in SharedPreferences for persistence
|
||||||
|
android.content.SharedPreferences preferences = getContext()
|
||||||
|
.getSharedPreferences("daily_notification_timesafari", Context.MODE_PRIVATE);
|
||||||
|
preferences.edit()
|
||||||
|
.putBoolean("autoSync", autoSync)
|
||||||
|
.putInt("gracePeriodSeconds", gracePeriodSeconds)
|
||||||
|
.putLong("configuredAt", System.currentTimeMillis())
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
Log.d(TAG, "Phase 2: Auto-sync configuration stored");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error storing auto-sync configuration", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set active DID from host application
|
* Set active DID from host application
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export class CallbackRegistryImpl implements CallbackRegistry {
|
|||||||
lastFailure: number;
|
lastFailure: number;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
// Phase 2: TimeSafari ActiveDid change tracking
|
||||||
|
private lastRetryAttempts?: Map<string, number>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startRetryProcessor();
|
this.startRetryProcessor();
|
||||||
@@ -226,13 +228,24 @@ export class CallbackRegistryImpl implements CallbackRegistry {
|
|||||||
private async scheduleRetry(callback: CallbackRecord, event: CallbackEvent): Promise<void> {
|
private async scheduleRetry(callback: CallbackRecord, event: CallbackEvent): Promise<void> {
|
||||||
const retryCount = callback.retryCount || 0;
|
const retryCount = callback.retryCount || 0;
|
||||||
|
|
||||||
if (retryCount >= 5) {
|
// Phase 2: Enhanced retry logic with TimeSafari activeDid support
|
||||||
|
const maxRetries = Math.max(5, this.getMaxRetriesForTimeSafari());
|
||||||
|
|
||||||
|
if (retryCount >= maxRetries && !this.shouldRetryForActiveDidChange()) {
|
||||||
console.warn(`DNP-CB-RETRY-LIMIT: Max retries reached for ${callback.id}`);
|
console.warn(`DNP-CB-RETRY-LIMIT: Max retries reached for ${callback.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const backoffMs = Math.min(1000 * Math.pow(2, retryCount), 60000); // Cap at 1 minute
|
// Phase 2: Check for activeDid change retry triggers
|
||||||
const retryEvent = { ...event, retryCount: retryCount + 1 };
|
if (this.shouldRetryForActiveDidChange()) {
|
||||||
|
console.log(`DNP-CB-PHASE2: ActiveDid change detected - resetting retry count for ${callback.id}`);
|
||||||
|
callback.retryCount = 0; // Reset retry count for activeDid change
|
||||||
|
}
|
||||||
|
|
||||||
|
let actualRetryCount = callback.retryCount || 0;
|
||||||
|
|
||||||
|
const backoffMs = Math.min(1000 * Math.pow(2, actualRetryCount), 60000); // Cap at 1 minute
|
||||||
|
const retryEvent = { ...event, retryCount: actualRetryCount + 1 };
|
||||||
|
|
||||||
if (!this.retryQueue.has(callback.id)) {
|
if (!this.retryQueue.has(callback.id)) {
|
||||||
this.retryQueue.set(callback.id, []);
|
this.retryQueue.set(callback.id, []);
|
||||||
@@ -240,7 +253,124 @@ export class CallbackRegistryImpl implements CallbackRegistry {
|
|||||||
|
|
||||||
this.retryQueue.get(callback.id)!.push(retryEvent);
|
this.retryQueue.get(callback.id)!.push(retryEvent);
|
||||||
|
|
||||||
console.log(`DNP-CB-RETRY: Scheduled retry ${retryCount + 1} for ${callback.id} in ${backoffMs}ms`);
|
// Phase 2: Store last retry attempt for activeDid change detection
|
||||||
|
this.storeLastRetryAttempt(callback.id);
|
||||||
|
|
||||||
|
console.log(`DNP-CB-PHASE2-RETRY: Scheduled retry ${actualRetryCount + 1} for ${callback.id} in ${backoffMs}ms with TimeSafari support`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Enhanced retry methods for TimeSafari integration
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Get max retries with TimeSafari enhancements
|
||||||
|
*/
|
||||||
|
private getMaxRetriesForTimeSafari(): number {
|
||||||
|
// Base retries + additional for activeDid changes
|
||||||
|
return 7; // Extra retries for TimeSafari integration
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Check if retry is needed due to activeDid change
|
||||||
|
*/
|
||||||
|
private shouldRetryForActiveDidChange(): boolean {
|
||||||
|
try {
|
||||||
|
// Check if activeDid has changed since last retry attempt
|
||||||
|
const lastRetryAttempt = this.getLastRetryAttempt();
|
||||||
|
const lastActiveDidChange = this.getLastActiveDidChange();
|
||||||
|
|
||||||
|
if (lastActiveDidChange > lastRetryAttempt) {
|
||||||
|
console.log('DNP-CB-PHASE2: ActiveDid change detected in web retry logic');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-CB-PHASE2: Error checking activeDid change', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Store last retry attempt for activeDid change detection
|
||||||
|
*/
|
||||||
|
private storeLastRetryAttempt(callbackId: string): void {
|
||||||
|
try {
|
||||||
|
const lastRetryMap = this.getLastRetryMap();
|
||||||
|
lastRetryMap.set(callbackId, Date.now());
|
||||||
|
|
||||||
|
// Store in sessionStorage for web persistence
|
||||||
|
if (typeof window !== 'undefined' && window.sessionStorage) {
|
||||||
|
const retryMapKey = 'dailynotification_last_retry_attempts';
|
||||||
|
const retryMapObj = Object.fromEntries(lastRetryMap);
|
||||||
|
window.sessionStorage.setItem(retryMapKey, JSON.stringify(retryMapObj));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-CB-PHASE2: Error storing last retry attempt', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Get last retry attempt timestamp
|
||||||
|
*/
|
||||||
|
private getLastRetryAttempt(): number {
|
||||||
|
try {
|
||||||
|
const lastRetryMap = this.getLastRetryMap();
|
||||||
|
// Get the most recent retry attempt across all callbacks
|
||||||
|
let maxRetryTime = 0;
|
||||||
|
for (const retryTime of lastRetryMap.values()) {
|
||||||
|
maxRetryTime = Math.max(maxRetryTime, retryTime);
|
||||||
|
}
|
||||||
|
return maxRetryTime;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-CB-PHASE2: Error getting last retry attempt', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Get last activeDid change timestamp
|
||||||
|
*/
|
||||||
|
private getLastActiveDidChange(): number {
|
||||||
|
try {
|
||||||
|
// This would be set when activeDid changes
|
||||||
|
if (typeof window !== 'undefined' && window.sessionStorage) {
|
||||||
|
const lastChangeStr = window.sessionStorage.getItem('dailynotification_last_active_did_change');
|
||||||
|
return lastChangeStr ? parseInt(lastChangeStr, 10) : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-CB-PHASE2: Error getting last activeDid change', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 2: Get last retry map
|
||||||
|
*/
|
||||||
|
private getLastRetryMap(): Map<string, number> {
|
||||||
|
try {
|
||||||
|
if (!this.lastRetryAttempts) {
|
||||||
|
this.lastRetryAttempts = new Map();
|
||||||
|
|
||||||
|
// Load from sessionStorage if available
|
||||||
|
if (typeof window !== 'undefined' && window.sessionStorage) {
|
||||||
|
const retryMapKey = 'dailynotification_last_retry_attempts';
|
||||||
|
const retryMapStr = window.sessionStorage.getItem(retryMapKey);
|
||||||
|
if (retryMapStr) {
|
||||||
|
const retryMapObj = JSON.parse(retryMapStr);
|
||||||
|
for (const [key, value] of Object.entries(retryMapObj)) {
|
||||||
|
this.lastRetryAttempts.set(key, value as number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.lastRetryAttempts;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DNP-CB-PHASE2: Error getting last retry map', error);
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private startRetryProcessor(): void {
|
private startRetryProcessor(): void {
|
||||||
|
|||||||
@@ -160,12 +160,20 @@ export interface ConfigureOptions {
|
|||||||
prefetchLeadMinutes?: number;
|
prefetchLeadMinutes?: number;
|
||||||
maxNotificationsPerDay?: number;
|
maxNotificationsPerDay?: number;
|
||||||
retentionDays?: number;
|
retentionDays?: number;
|
||||||
// Phase 1: ActiveDid Integration Enhancement
|
// Phase 2: TimeSafari ActiveDid Integration Enhancement
|
||||||
activeDidIntegration?: {
|
activeDidIntegration?: {
|
||||||
platform: 'android' | 'ios' | 'web' | 'electron';
|
platform: 'android' | 'ios' | 'web' | 'electron';
|
||||||
storageType: 'plugin-managed' | 'host-managed';
|
storageType: 'plugin-managed' | 'host-managed';
|
||||||
jwtExpirationSeconds?: number;
|
jwtExpirationSeconds?: number;
|
||||||
apiServer?: string;
|
apiServer?: string;
|
||||||
|
// Phase 2: Host-provided activeDid configuration
|
||||||
|
activeDid?: string; // Initial activeDid from host
|
||||||
|
hostCredentials?: {
|
||||||
|
platform?: string; // Platform identifier
|
||||||
|
accessToken?: string; // Optional access token
|
||||||
|
};
|
||||||
|
autoSync?: boolean; // Auto-sync activeDid changes
|
||||||
|
identityChangeGraceSeconds?: number; // Grace period for activeDid changes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +197,30 @@ export interface ContentFetchConfig {
|
|||||||
contentHandler?: ContentHandler;
|
contentHandler?: ContentHandler;
|
||||||
cachePolicy?: CachePolicy;
|
cachePolicy?: CachePolicy;
|
||||||
networkConfig?: NetworkConfig;
|
networkConfig?: NetworkConfig;
|
||||||
|
// Phase 2: TimeSafari Endorser.ch API configuration
|
||||||
|
timesafariConfig?: {
|
||||||
|
activeDid: string; // Required activeDid for authentication
|
||||||
|
endpoints?: {
|
||||||
|
offersToPerson?: string;
|
||||||
|
offersToPlans?: string;
|
||||||
|
projectsLastUpdated?: string;
|
||||||
|
};
|
||||||
|
syncConfig?: {
|
||||||
|
enableParallel?: boolean; // Enable parallel API requests
|
||||||
|
maxConcurrent?: number; // Max concurrent requests
|
||||||
|
batchSize?: number; // Batch size for requests
|
||||||
|
};
|
||||||
|
credentialConfig?: {
|
||||||
|
jwtSecret?: string; // JWT secret for signing
|
||||||
|
tokenExpirationMinutes?: number; // Token expiration
|
||||||
|
refreshThresholdMinutes?: number; // Refresh threshold
|
||||||
|
};
|
||||||
|
errorPolicy?: {
|
||||||
|
maxRetries?: number;
|
||||||
|
backoffMultiplier?: number;
|
||||||
|
activeDidChangeRetries?: number; // Special retry for activeDid changes
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserNotificationConfig {
|
export interface UserNotificationConfig {
|
||||||
@@ -397,6 +429,116 @@ export interface PlanSummary {
|
|||||||
url?: string;
|
url?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Phase 2: Detailed TimeSafari Notification Types
|
||||||
|
export interface TimeSafariNotificationBundle {
|
||||||
|
offersToPerson?: OffersResponse;
|
||||||
|
offersToProjects?: OffersToPlansResponse;
|
||||||
|
projectUpdates?: PlansLastUpdatedResponse;
|
||||||
|
fetchTimestamp: number;
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
metadata?: {
|
||||||
|
activeDid: string;
|
||||||
|
fetchDurationMs: number;
|
||||||
|
cachedResponses: number;
|
||||||
|
networkResponses: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSafariUserConfig {
|
||||||
|
activeDid: string; // Required for all operations
|
||||||
|
lastKnownOfferId?: string;
|
||||||
|
lastKnownPlanId?: string;
|
||||||
|
starredPlanIds?: string[];
|
||||||
|
fetchOffersToPerson?: boolean;
|
||||||
|
fetchOffersToProjects?: boolean;
|
||||||
|
fetchProjectUpdates?: boolean;
|
||||||
|
notificationPreferences?: {
|
||||||
|
offers: boolean;
|
||||||
|
projects: boolean;
|
||||||
|
people: boolean;
|
||||||
|
items: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced notification types per specification
|
||||||
|
export interface TimeSafariOfferNotification {
|
||||||
|
type: 'offer';
|
||||||
|
subtype: 'new_to_me' | 'changed_to_me' | 'new_to_projects' | 'changed_to_projects' | 'new_to_favorites' | 'changed_to_favorites';
|
||||||
|
offer: OfferSummaryRecord;
|
||||||
|
relevantProjects?: PlanSummary[];
|
||||||
|
notificationPriority: 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSafariProjectNotification {
|
||||||
|
type: 'project';
|
||||||
|
subtype: 'local_and_new' | 'local_and_changed' | 'with_content_and_new' | 'favorite_and_changed';
|
||||||
|
project: PlanSummary;
|
||||||
|
changes?: {
|
||||||
|
fields: string[];
|
||||||
|
previousValues?: Record<string, any>;
|
||||||
|
};
|
||||||
|
relevantOffers?: OfferSummaryRecord[];
|
||||||
|
notificationPriority: 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSafariPersonNotification {
|
||||||
|
type: 'person';
|
||||||
|
subtype: 'local_and_new' | 'local_and_changed' | 'with_content_and_new' | 'favorite_and_changed';
|
||||||
|
personDid: string;
|
||||||
|
changes?: {
|
||||||
|
fields: string[];
|
||||||
|
previousValues?: Record<string, any>;
|
||||||
|
};
|
||||||
|
relevantProjects?: PlanSummary[];
|
||||||
|
notificationPriority: 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSafariItemNotification {
|
||||||
|
type: 'item';
|
||||||
|
subtype: 'local_and_new' | 'local_and_changed' | 'favorite_and_changed';
|
||||||
|
itemId: string;
|
||||||
|
changes?: {
|
||||||
|
fields: string[];
|
||||||
|
previousValues?: Record<string, any>;
|
||||||
|
};
|
||||||
|
relevantContext?: 'project' | 'offer' | 'person';
|
||||||
|
notificationPriority: 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union type for TimeSafari notifications
|
||||||
|
export type TimeSafariNotification =
|
||||||
|
| TimeSafariOfferNotification
|
||||||
|
| TimeSafariProjectNotification
|
||||||
|
| TimeSafariPersonNotification
|
||||||
|
| TimeSafariItemNotification;
|
||||||
|
|
||||||
|
// Enhanced ActiveDid Management Events
|
||||||
|
export interface ActiveDidChangeEventEnhanced extends ActiveDidChangeEvent {
|
||||||
|
sourceComponent: string; // 'host' | 'plugin' | 'background' | 'sync'
|
||||||
|
changeReason: 'user_switch' | 'session_expired' | 'background_refresh' | 'setup';
|
||||||
|
transitionDurationMs?: number;
|
||||||
|
relatedNotifications?: TimeSafariNotification[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeSafari-specific Platform Configuration
|
||||||
|
export interface TimeSafariPlatformConfig {
|
||||||
|
platform: 'android' | 'ios' | 'web' | 'electron';
|
||||||
|
storageType: 'plugin-managed' | 'host-managed';
|
||||||
|
syncStrategy: 'immediate' | 'batched' | 'scheduled';
|
||||||
|
permissions: {
|
||||||
|
notifications: boolean;
|
||||||
|
backgroundRefresh: boolean;
|
||||||
|
networkAccess: boolean;
|
||||||
|
};
|
||||||
|
capabilities: {
|
||||||
|
pushNotifications: boolean;
|
||||||
|
backgroundTasks: boolean;
|
||||||
|
identityManagement: boolean;
|
||||||
|
cryptoSigning: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ActiveDidIntegrationConfig {
|
export interface ActiveDidIntegrationConfig {
|
||||||
platform: 'android' | 'ios' | 'web' | 'electron';
|
platform: 'android' | 'ios' | 'web' | 'electron';
|
||||||
storageType: 'plugin-managed' | 'host-managed';
|
storageType: 'plugin-managed' | 'host-managed';
|
||||||
|
|||||||
Reference in New Issue
Block a user