feat(db): improve settings retrieval resilience and logging

Enhance retrieveSettingsForActiveAccount with better error handling and logging
while maintaining core functionality. Changes focus on making the system more
debuggable and resilient without overcomplicating the logic.

Key improvements:
- Add structured error handling with specific try-catch blocks
- Implement detailed logging with [databaseUtil] prefix for easy filtering
- Add graceful fallbacks for searchBoxes parsing and missing settings
- Improve error recovery paths with safe defaults
- Maintain existing security model and data integrity

Security:
- No sensitive data in logs
- Safe JSON parsing with fallbacks
- Proper error boundaries
- Consistent state management
- Clear fallback paths

Testing:
- Verify settings retrieval works with/without active DID
- Check error handling for invalid searchBoxes
- Confirm logging provides clear debugging context
- Validate fallback to default settings works
This commit is contained in:
Matthew Raymer
2025-06-06 09:22:35 +00:00
parent 7547e7a5f7
commit ce9c193a30
7 changed files with 254 additions and 109 deletions

View File

@@ -515,49 +515,85 @@ export default class HomeView extends Vue {
*/
private async initializeIdentity() {
try {
this.allMyDids = await retrieveAccountDids();
if (this.allMyDids.length === 0) {
this.isCreatingIdentifier = true;
const newDid = await generateSaveAndActivateIdentity();
this.isCreatingIdentifier = false;
this.allMyDids = [newDid];
// Retrieve DIDs with better error handling
try {
this.allMyDids = await retrieveAccountDids();
logConsoleAndDb(`[HomeView] Retrieved ${this.allMyDids.length} DIDs`);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve DIDs: ${error}`, true);
throw new Error("Failed to load existing identities. Please try restarting the app.");
}
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
if (USE_DEXIE_DB) {
settings = await retrieveSettingsForActiveAccount();
// Create new DID if needed
if (this.allMyDids.length === 0) {
try {
this.isCreatingIdentifier = true;
const newDid = await generateSaveAndActivateIdentity();
this.isCreatingIdentifier = false;
this.allMyDids = [newDid];
logConsoleAndDb(`[HomeView] Created new identity: ${newDid}`);
} catch (error) {
this.isCreatingIdentifier = false;
logConsoleAndDb(`[HomeView] Failed to create new identity: ${error}`, true);
throw new Error("Failed to create new identity. Please try again.");
}
}
// Load settings with better error context
let settings;
try {
settings = await databaseUtil.retrieveSettingsForActiveAccount();
if (USE_DEXIE_DB) {
settings = await retrieveSettingsForActiveAccount();
}
logConsoleAndDb(`[HomeView] Retrieved settings for ${settings.activeDid || 'no active DID'}`);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve settings: ${error}`, true);
throw new Error("Failed to load user settings. Some features may be limited.");
}
// Update component state
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
const platformService = PlatformServiceFactory.getInstance();
const dbContacts = await platformService.dbQuery(
"SELECT * FROM contacts",
);
this.allContacts = databaseUtil.mapQueryResultToValues(
dbContacts,
) as unknown as Contact[];
if (USE_DEXIE_DB) {
this.allContacts = await db.contacts.toArray();
// Load contacts with graceful fallback
try {
const platformService = PlatformServiceFactory.getInstance();
const dbContacts = await platformService.dbQuery("SELECT * FROM contacts");
this.allContacts = databaseUtil.mapQueryResultToValues(dbContacts) as Contact[];
if (USE_DEXIE_DB) {
this.allContacts = await db.contacts.toArray();
}
logConsoleAndDb(`[HomeView] Retrieved ${this.allContacts.length} contacts`);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve contacts: ${error}`, true);
this.allContacts = []; // Ensure we have a valid empty array
this.$notify({
group: "alert",
type: "warning",
title: "Contact Loading Issue",
text: "Some contact information may be unavailable.",
}, 5000);
}
// Update remaining settings
this.feedLastViewedClaimId = settings.lastViewedClaimId;
this.givenName = settings.firstName || "";
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId;
this.lastAckedOfferToUserProjectsJwtId = settings.lastAckedOfferToUserProjectsJwtId;
this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
// Check onboarding status
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Home,
);
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
}
// someone may have have registered after sharing contact info, so recheck
// Check registration status if needed
if (!this.isRegistered && this.activeDid) {
try {
const resp = await fetchEndorserRateLimits(
@@ -577,51 +613,62 @@ export default class HomeView extends Vue {
});
}
this.isRegistered = true;
logConsoleAndDb(`[HomeView] User ${this.activeDid} is now registered`);
}
} catch (e) {
// ignore the error... just keep us unregistered
} catch (error) {
logConsoleAndDb(`[HomeView] Registration check failed: ${error}`, true);
// Continue as unregistered - this is expected for new users
}
}
// this returns a Promise but we don't need to wait for it
this.updateAllFeed();
// Initialize feed and offers
try {
// Start feed update in background
this.updateAllFeed().catch(error => {
logConsoleAndDb(`[HomeView] Background feed update failed: ${error}`, true);
});
if (this.activeDid) {
const offersToUserData = await getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId,
);
this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
}
// Load new offers if we have an active DID
if (this.activeDid) {
const [offersToUser, offersToProjects] = await Promise.all([
getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId,
),
getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId,
),
]);
if (this.activeDid) {
const offersToUserProjects = await getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId,
);
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
this.$notify(
{
this.numNewOffersToUser = offersToUser.data.length;
this.newOffersToUserHitLimit = offersToUser.hitLimit;
this.numNewOffersToUserProjects = offersToProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToProjects.hitLimit;
logConsoleAndDb(
`[HomeView] Retrieved ${this.numNewOffersToUser} user offers and ` +
`${this.numNewOffersToUserProjects} project offers`
);
}
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to initialize feed/offers: ${error}`, true);
// Don't throw - we can continue with empty feed
this.$notify({
group: "alert",
type: "danger",
title: "Error",
text:
(err as { userMessage?: string })?.userMessage ||
"There was an error retrieving your settings or the latest activity.",
},
5000,
);
type: "warning",
title: "Feed Loading Issue",
text: "Some feed data may be unavailable. Pull to refresh.",
}, 5000);
}
} catch (error) {
this.handleError(error);
throw error; // Re-throw to be caught by mounted()
}
}
@@ -784,19 +831,24 @@ export default class HomeView extends Vue {
* - Displays user notification
*
* @internal
* Called by mounted()
* Called by mounted() and initializeIdentity()
* @param err Error object with optional userMessage
*/
private handleError(err: unknown) {
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
const errorMessage = err instanceof Error ? err.message : String(err);
const userMessage = (err as { userMessage?: string })?.userMessage;
logConsoleAndDb(
`[HomeView] Initialization error: ${errorMessage}${userMessage ? ` (${userMessage})` : ''}`,
true
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
(err as { userMessage?: string })?.userMessage ||
"There was an error retrieving your settings or the latest activity.",
text: userMessage || "There was an error loading your data. Please try refreshing the page.",
},
5000,
);