diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue index 17524e79..737f8f2e 100644 --- a/src/components/DataExportSection.vue +++ b/src/components/DataExportSection.vue @@ -18,7 +18,7 @@ messages * - Conditional UI based on platform capabilities * * @component * > @@ -108,7 +108,7 @@ export default class DataExportSection extends Vue { * Flag indicating if the user has backed up their seed phrase * Used to control the visibility of the notification dot */ - hasBackedUpSeed = false; + showRedNotificationDot = false; /** * Notification helper for consistent notification patterns @@ -240,11 +240,12 @@ export default class DataExportSection extends Vue { private async loadSeedBackupStatus(): Promise { try { const settings = await this.$accountSettings(); - this.hasBackedUpSeed = !!settings.hasBackedUpSeed; + this.showRedNotificationDot = + !!settings.isRegistered && !settings.hasBackedUpSeed; } catch (err: unknown) { logger.error("Failed to load seed backup status:", err); // Default to false (show notification dot) if we can't load the setting - this.hasBackedUpSeed = false; + this.showRedNotificationDot = false; } } } diff --git a/src/components/TopMessage.vue b/src/components/TopMessage.vue index 9975101e..96f1428e 100644 --- a/src/components/TopMessage.vue +++ b/src/components/TopMessage.vue @@ -27,7 +27,7 @@ import { logger } from "../utils/logger"; }) export default class TopMessage extends Vue { // Enhanced PlatformServiceMixin v4.0 provides: - // - Cached database operations: this.$contacts(), this.$settings(), this.$accountSettings() + // - Cached database operations: this.$contacts(), this.$accountSettings() // - Settings shortcuts: this.$saveSettings() // - Cache management: this.$refreshSettings(), this.$clearAllCaches() // - Ultra-concise database methods: this.$db(), this.$exec(), this.$query() diff --git a/src/constants/accountView.ts b/src/constants/accountView.ts index d74c9404..3953b264 100644 --- a/src/constants/accountView.ts +++ b/src/constants/accountView.ts @@ -86,7 +86,7 @@ export const ACCOUNT_VIEW_CONSTANTS = { CANNOT_UPLOAD_IMAGES: "You cannot upload images.", BAD_SERVER_RESPONSE: "Bad server response.", ERROR_RETRIEVING_LIMITS: - "No limits were found, so no actions are allowed. You will need to get registered.", + "No limits were found, so no actions are allowed. You need to get registered.", }, // Project assignment errors diff --git a/src/db/databaseUtil.ts b/src/db/databaseUtil.ts index 18e7952b..487742c9 100644 --- a/src/db/databaseUtil.ts +++ b/src/db/databaseUtil.ts @@ -9,34 +9,6 @@ import { logger } from "@/utils/logger"; import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; import { QueryExecResult } from "@/interfaces/database"; -export async function updateDefaultSettings( - settingsChanges: Settings, -): Promise { - delete settingsChanges.accountDid; // just in case - // ensure there is no "id" that would override the key - delete settingsChanges.id; - try { - const platformService = PlatformServiceFactory.getInstance(); - const { sql, params } = generateUpdateStatement( - settingsChanges, - "settings", - "id = ?", - [MASTER_SETTINGS_KEY], - ); - const result = await platformService.dbExec(sql, params); - return result.changes === 1; - } catch (error) { - logger.error("Error updating default settings:", error); - if (error instanceof Error) { - throw error; // Re-throw if it's already an Error with a message - } else { - throw new Error( - `Failed to update settings. We recommend you try again or restart the app.`, - ); - } - } -} - export async function insertDidSpecificSettings( did: string, settings: Partial = {}, @@ -91,6 +63,7 @@ export async function updateDidSpecificSettings( ? mapColumnsToValues(postUpdateResult.columns, postUpdateResult.values)[0] : null; + // Note that we want to eliminate this check (and fix the above if it doesn't work). // Check if any of the target fields were actually changed let actuallyUpdated = false; if (currentRecord && updatedRecord) { diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index 320a6363..a0e2bf6c 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -809,6 +809,8 @@ export async function getStarredProjectsWithChanges( } if (!afterId) { + // This doesn't make sense: there should always be some previous one they've seen. + // We'll just return blank. return { data: [], hitLimit: false }; } @@ -1756,49 +1758,19 @@ export async function fetchEndorserRateLimits( timestamp: new Date().toISOString(), }); - try { - const response = await axios.get(url, { headers } as AxiosRequestConfig); - - // Log successful registration check - logger.debug("[User Registration] User registration check successful:", { - did: issuerDid, - server: apiServer, - status: response.status, - isRegistered: true, - timestamp: new Date().toISOString(), - }); - - return response; - } catch (error) { - // Enhanced error logging with user registration context - const axiosError = error as { - response?: { - data?: { error?: { code?: string; message?: string } }; - status?: number; - }; - }; - const errorCode = axiosError.response?.data?.error?.code; - const errorMessage = axiosError.response?.data?.error?.message; - const httpStatus = axiosError.response?.status; - - logger.warn("[User Registration] User not registered on server:", { - did: issuerDid, - server: apiServer, - errorCode: errorCode, - errorMessage: errorMessage, - httpStatus: httpStatus, - needsRegistration: true, - timestamp: new Date().toISOString(), - }); + // not wrapped in a 'try' because the error returned is self-explanatory + const response = await axios.get(url, { headers } as AxiosRequestConfig); - // Log the original error for debugging - logger.error( - `[fetchEndorserRateLimits] Error for DID ${issuerDid}:`, - errorStringForLog(error), - ); + // Log successful registration check + logger.debug("[User Registration] User registration check successful:", { + did: issuerDid, + server: apiServer, + status: response.status, + isRegistered: true, + timestamp: new Date().toISOString(), + }); - throw error; - } + return response; } /** @@ -1847,14 +1819,17 @@ export async function fetchImageRateLimits( }; }; - logger.error("[Image Server] Image rate limits check failed:", { - did: issuerDid, - server: server, - errorCode: axiosError.response?.data?.error?.code, - errorMessage: axiosError.response?.data?.error?.message, - httpStatus: axiosError.response?.status, - timestamp: new Date().toISOString(), - }); + logger.warn( + "[Image Server] Image rate limits check failed, which is expected for users not registered on test server (eg. when only registered on local server).", + { + did: issuerDid, + server: server, + errorCode: axiosError.response?.data?.error?.code, + errorMessage: axiosError.response?.data?.error?.message, + httpStatus: axiosError.response?.status, + timestamp: new Date().toISOString(), + }, + ); return null; } } diff --git a/src/services/api.ts b/src/services/api.ts index d7b67beb..e983f2a1 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -19,7 +19,6 @@ import { logger, safeStringify } from "../utils/logger"; * @remarks * Special handling includes: * - Enhanced logging for Capacitor platform - * - Rate limit detection and handling * - Detailed error information logging including: * - Error message * - HTTP status @@ -50,11 +49,5 @@ export const handleApiError = (error: AxiosError, endpoint: string) => { }); } - // Specific handling for rate limits - if (error.response?.status === 400) { - logger.warn(`[Rate Limit] ${endpoint}`); - return null; - } - throw error; }; diff --git a/src/utils/PlatformServiceMixin.ts b/src/utils/PlatformServiceMixin.ts index 05dc08dd..ad0b249f 100644 --- a/src/utils/PlatformServiceMixin.ts +++ b/src/utils/PlatformServiceMixin.ts @@ -353,6 +353,14 @@ export const PlatformServiceMixin = { ? JSON.stringify(settings.searchBoxes) : String(settings.searchBoxes); } + if (settings.starredPlanHandleIds !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (converted as any).starredPlanHandleIds = Array.isArray( + settings.starredPlanHandleIds, + ) + ? JSON.stringify(settings.starredPlanHandleIds) + : String(settings.starredPlanHandleIds); + } return converted; }, @@ -555,6 +563,12 @@ export const PlatformServiceMixin = { if (settings.searchBoxes) { settings.searchBoxes = this._parseJsonField(settings.searchBoxes, []); } + if (settings.starredPlanHandleIds) { + settings.starredPlanHandleIds = this._parseJsonField( + settings.starredPlanHandleIds, + [], + ); + } return settings; } catch (error) { @@ -621,6 +635,12 @@ export const PlatformServiceMixin = { [], ); } + if (mergedSettings.starredPlanHandleIds) { + mergedSettings.starredPlanHandleIds = this._parseJsonField( + mergedSettings.starredPlanHandleIds, + [], + ); + } return mergedSettings; } catch (error) { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index cf90daac..07e1566b 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -24,10 +24,28 @@ export function getMemoryLogs(): string[] { return [..._memoryLogs]; } +/** + * Stringify an object with proper handling of circular references and functions + * + * Don't use for arrays; map with this over the array. + * + * @param obj - The object to stringify + * @returns The stringified object, plus 'message' and 'stack' for Error objects + */ export function safeStringify(obj: unknown) { const seen = new WeakSet(); - return JSON.stringify(obj, (_key, value) => { + // since 'message' & 'stack' are not enumerable for errors, let's add those + let objToStringify = obj; + if (obj instanceof Error) { + objToStringify = { + ...obj, + message: obj.message, + stack: obj.stack, + }; + } + + return JSON.stringify(objToStringify, (_key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return "[Circular]"; @@ -178,7 +196,8 @@ export const logger = { } // Database logging - const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; + const argsString = + args.length > 0 ? " - " + args.map(safeStringify).join(", ") : ""; logToDatabase(message + argsString, "info"); }, @@ -189,7 +208,8 @@ export const logger = { } // Database logging - const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; + const argsString = + args.length > 0 ? " - " + args.map(safeStringify).join(", ") : ""; logToDatabase(message + argsString, "info"); }, @@ -200,7 +220,8 @@ export const logger = { } // Database logging - const argsString = args.length > 0 ? " - " + safeStringify(args) : ""; + const argsString = + args.length > 0 ? " - " + args.map(safeStringify).join(", ") : ""; logToDatabase(message + argsString, "warn"); }, @@ -211,9 +232,9 @@ export const logger = { } // Database logging - const messageString = safeStringify(message); - const argsString = args.length > 0 ? safeStringify(args) : ""; - logToDatabase(messageString + argsString, "error"); + const argsString = + args.length > 0 ? " - " + args.map(safeStringify).join(", ") : ""; + logToDatabase(message + argsString, "error"); }, // New database-focused methods (self-contained) diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index aba86705..d30f1798 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -1453,9 +1453,6 @@ export default class AccountViewView extends Vue { if (imageResp && imageResp.status === 200) { this.imageLimits = imageResp.data; - } else { - this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS; - this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES); } const endorserResp = await fetchEndorserRateLimits( @@ -1466,9 +1463,6 @@ export default class AccountViewView extends Vue { if (endorserResp.status === 200) { this.endorserLimits = endorserResp.data; - } else { - this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND; - this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE); } } catch (error) { this.limitsMessage = diff --git a/src/views/ContactAmountsView.vue b/src/views/ContactAmountsView.vue index b34b3d11..18f600c7 100644 --- a/src/views/ContactAmountsView.vue +++ b/src/views/ContactAmountsView.vue @@ -223,7 +223,7 @@ export default class ContactAmountssView extends Vue { const contact = await this.$getContact(contactDid); this.contact = contact; - const settings = await this.$settings(); + const settings = await this.$accountSettings(); // Get activeDid from active_identity table (single source of truth) // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index d7e23fc1..8fac7b7b 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -421,6 +421,18 @@ export default class HomeView extends Vue { numNewOffersToUser: number = 0; // number of new offers-to-user numNewOffersToUserProjects: number = 0; // number of new offers-to-user's-projects + numNewStarredProjectChanges: number = 0; // number of new starred project changes + starredPlanHandleIds: Array = []; // list of starred project IDs + searchBoxes: Array<{ + name: string; + bbox: BoundingBox; + }> = []; + showShortcutBvc = false; + userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html + selectedImage = ""; + isImageViewerOpen = false; + showProjectsDialog = false; + /** * CRITICAL VUE REACTIVITY BUG WORKAROUND * @@ -458,18 +470,6 @@ export default class HomeView extends Vue { // return shouldShow; // } - numNewStarredProjectChanges: number = 0; // number of new starred project changes - starredPlanHandleIds: Array = []; // list of starred project IDs - searchBoxes: Array<{ - name: string; - bbox: BoundingBox; - }> = []; - showShortcutBvc = false; - userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html - selectedImage = ""; - isImageViewerOpen = false; - showProjectsDialog = false; - /** * Initializes notification helpers */ @@ -734,7 +734,7 @@ export default class HomeView extends Vue { * Used for displaying contact info in feed and actions * * @internal - * Called by mounted() and initializeIdentity() + * Called by initializeIdentity() */ private async loadContacts() { this.allContacts = await this.$contacts(); @@ -748,7 +748,6 @@ export default class HomeView extends Vue { * Triggers updateAllFeed() to populate activity feed * * @internal - * Called by mounted() */ private async loadFeedData() { await this.updateAllFeed(); @@ -762,7 +761,6 @@ export default class HomeView extends Vue { * - Rate limit status for both * * @internal - * Called by mounted() and initializeIdentity() * @requires Active DID */ private async loadNewOffers() { @@ -873,7 +871,6 @@ export default class HomeView extends Vue { * - Rate limit status for starred project changes * * @internal - * Called by mounted() and initializeIdentity() * @requires Active DID */ private async loadNewStarredProjectChanges() { diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index c54067be..d92b05cc 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -170,7 +170,7 @@ - + @@ -303,10 +303,7 @@ {{ offer.objectDescription }}
- + = []; + /** Project JWT ID */ + jwtId = ""; /** Project location data */ latitude = 0; loadingTotals = false; @@ -910,6 +908,7 @@ export default class ProjectViewView extends Vue { this.allContacts, ); this.issuerVisibleToDids = resp.data.issuerVisibleToDids || []; + this.jwtId = resp.data.id; this.name = resp.data.claim?.name || "(no name)"; this.description = resp.data.claim?.description || ""; this.truncatedDesc = this.description.slice(0, this.truncateLength); @@ -1509,11 +1508,10 @@ export default class ProjectViewView extends Vue { if (!this.projectId) return; try { + const settings = await this.$accountSettings(); + const starredIds = settings.starredPlanHandleIds || []; if (!this.isStarred) { // Add to starred projects - const settings = await databaseUtil.retrieveSettingsForActiveAccount(); - const starredIds = settings.starredPlanHandleIds || []; - if (!starredIds.includes(this.projectId)) { const newStarredIds = [...starredIds, this.projectId]; const newIdsParam = JSON.stringify(newStarredIds); @@ -1526,20 +1524,16 @@ export default class ProjectViewView extends Vue { this.isStarred = true; } else { // eslint-disable-next-line no-console - console.log( - "Still getting a bad result from SQL update to star a project.", - ); + logger.error("Got a bad result from SQL update to star a project."); } } if (!settings.lastAckedStarredPlanChangesJwtId) { await databaseUtil.updateDidSpecificSettings(this.activeDid, { - lastAckedStarredPlanChangesJwtId: settings.lastViewedClaimId, + lastAckedStarredPlanChangesJwtId: this.jwtId, }); } } else { // Remove from starred projects - const settings = await databaseUtil.retrieveSettingsForActiveAccount(); - const starredIds = settings.starredPlanHandleIds || []; const updatedIds = starredIds.filter((id) => id !== this.projectId); const newIdsParam = JSON.stringify(updatedIds);