Merge branch 'master' into new-activity-mark-read

This commit is contained in:
Jose Olarte III
2025-09-19 15:14:42 +08:00
90 changed files with 4025 additions and 413 deletions

View File

@@ -27,7 +27,7 @@
need an identifier.
</p>
<router-link
:to="{ name: 'start' }"
:to="{ name: 'new-identifier' }"
class="inline-block text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
>
Create An Identifier
@@ -1051,7 +1051,11 @@ export default class AccountViewView extends Vue {
// Then get the account-specific settings
const settings: AccountSettings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || "";
this.givenName =
@@ -1446,12 +1450,11 @@ export default class AccountViewView extends Vue {
this.DEFAULT_IMAGE_API_SERVER,
);
if (imageResp.status === 200) {
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);
return;
}
const endorserResp = await fetchEndorserRateLimits(
@@ -1465,7 +1468,6 @@ export default class AccountViewView extends Vue {
} else {
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE);
return;
}
} catch (error) {
this.limitsMessage =
@@ -1482,6 +1484,7 @@ export default class AccountViewView extends Vue {
error: error instanceof Error ? error.message : String(error),
did: did,
apiServer: this.apiServer,
imageServer: this.DEFAULT_IMAGE_API_SERVER,
partnerApiServer: this.partnerApiServer,
errorCode: axiosError?.response?.data?.error?.code,
errorMessage: axiosError?.response?.data?.error?.message,
@@ -1996,7 +1999,7 @@ export default class AccountViewView extends Vue {
error: error instanceof Error ? error.message : String(error),
timestamp: new Date().toISOString(),
});
throw new Error("Failed to load profile");
return null;
}
}

View File

@@ -113,7 +113,12 @@ export default class ClaimAddRawView extends Vue {
*/
private async initializeSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
}

View File

@@ -40,7 +40,12 @@ export default class ClaimCertificateView extends Vue {
async created() {
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
const pathParams = window.location.pathname.substring(
"/claim-cert/".length,

View File

@@ -53,8 +53,13 @@ export default class ClaimReportCertificateView extends Vue {
// Initialize notification helper
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$settings();
this.activeDid = settings.activeDid || "";
const settings = await this.$accountSettings();
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
const pathParams = window.location.pathname.substring(
"/claim-cert/".length,

View File

@@ -767,7 +767,11 @@ export default class ClaimView extends Vue {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await this.$contacts();

View File

@@ -556,7 +556,12 @@ export default class ConfirmGiftView extends Vue {
*/
private async initializeSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await this.$getAllContacts();
this.isRegistered = settings.isRegistered || false;

View File

@@ -224,7 +224,12 @@ export default class ContactAmountssView extends Vue {
this.contact = contact;
const settings = await this.$getMasterSettings();
this.activeDid = settings?.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings?.apiServer || "";
if (this.activeDid && this.contact) {

View File

@@ -164,7 +164,11 @@ export default class ContactGiftingView extends Vue {
try {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.allContacts = await this.$getAllContacts();

View File

@@ -340,7 +340,12 @@ export default class ContactImportView extends Vue {
*/
private async initializeSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
}

View File

@@ -269,7 +269,12 @@ export default class ContactQRScanFull extends Vue {
try {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || "";
this.isRegistered = !!settings.isRegistered;
@@ -393,7 +398,7 @@ export default class ContactQRScanFull extends Vue {
this.isCleaningUp = true;
try {
logger.info("Cleaning up QR scanner resources");
logger.debug("Cleaning up QR scanner resources");
await this.stopScanning();
await QRScannerFactory.cleanup();
} catch (error) {
@@ -427,7 +432,7 @@ export default class ContactQRScanFull extends Vue {
rawValue === this.lastScannedValue &&
now - this.lastScanTime < this.SCAN_DEBOUNCE_MS
) {
logger.info("Ignoring duplicate scan:", rawValue);
logger.debug("Ignoring duplicate scan:", rawValue);
return;
}
@@ -435,7 +440,7 @@ export default class ContactQRScanFull extends Vue {
this.lastScannedValue = rawValue;
this.lastScanTime = now;
logger.info("Processing QR code scan result:", rawValue);
logger.debug("Processing QR code scan result:", rawValue);
let contact: Contact;
if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
@@ -448,7 +453,7 @@ export default class ContactQRScanFull extends Vue {
}
// Process JWT and contact info
logger.info("Decoding JWT payload from QR code");
logger.debug("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
if (!decodedJwt?.payload?.own) {
logger.warn("Invalid JWT payload - missing 'own' field");
@@ -487,7 +492,7 @@ export default class ContactQRScanFull extends Vue {
}
// Add contact but keep scanning
logger.info("Adding new contact to database:", {
logger.debug("Adding new contact to database:", {
did: contact.did,
name: contact.name,
});
@@ -546,7 +551,7 @@ export default class ContactQRScanFull extends Vue {
*/
async addNewContact(contact: Contact) {
try {
logger.info("Opening database connection for new contact");
logger.debug("Opening database connection for new contact");
// Check if contact already exists
const existingContact = await this.$getContact(contact.did);
@@ -560,7 +565,7 @@ export default class ContactQRScanFull extends Vue {
await this.$insertContact(contact);
if (this.activeDid) {
logger.info("Setting contact visibility", { did: contact.did });
logger.debug("Setting contact visibility", { did: contact.did });
await this.setVisibility(contact, true);
contact.seesMe = true;
}
@@ -607,7 +612,7 @@ export default class ContactQRScanFull extends Vue {
async handleAppPause() {
if (!this.isMounted) return;
logger.info("App paused, stopping scanner");
logger.debug("App paused, stopping scanner");
await this.stopScanning();
}
@@ -617,7 +622,7 @@ export default class ContactQRScanFull extends Vue {
handleAppResume() {
if (!this.isMounted) return;
logger.info("App resumed, scanner can be restarted by user");
logger.debug("App resumed, scanner can be restarted by user");
this.isScanning = false;
}

View File

@@ -288,7 +288,12 @@ export default class ContactQRScanShow extends Vue {
try {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || "";
this.hideRegisterPromptOnNewContact =
@@ -428,7 +433,7 @@ export default class ContactQRScanShow extends Vue {
this.isCleaningUp = true;
try {
logger.info("Cleaning up QR scanner resources");
logger.debug("Cleaning up QR scanner resources");
await this.stopScanning();
await QRScannerFactory.cleanup();
} catch (error) {
@@ -462,7 +467,7 @@ export default class ContactQRScanShow extends Vue {
rawValue === this.lastScannedValue &&
now - this.lastScanTime < this.SCAN_DEBOUNCE_MS
) {
logger.info("Ignoring duplicate scan:", rawValue);
logger.debug("Ignoring duplicate scan:", rawValue);
return;
}
@@ -470,7 +475,7 @@ export default class ContactQRScanShow extends Vue {
this.lastScannedValue = rawValue;
this.lastScanTime = now;
logger.info("Processing QR code scan result:", rawValue);
logger.debug("Processing QR code scan result:", rawValue);
let contact: Contact;
if (rawValue.includes(CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI)) {
@@ -480,7 +485,7 @@ export default class ContactQRScanShow extends Vue {
this.notify.error(NOTIFY_QR_INVALID_QR_CODE.message);
return;
}
logger.info("Decoding JWT payload from QR code");
logger.debug("Decoding JWT payload from QR code");
const decodedJwt = await decodeEndorserJwt(jwt);
// Process JWT and contact info
@@ -515,7 +520,7 @@ export default class ContactQRScanShow extends Vue {
}
// Add contact but keep scanning
logger.info("Adding new contact to database:", {
logger.debug("Adding new contact to database:", {
did: contact.did,
name: contact.name,
});
@@ -549,7 +554,7 @@ export default class ContactQRScanShow extends Vue {
}
async register(contact: Contact) {
logger.info("Submitting contact registration", {
logger.debug("Submitting contact registration", {
did: contact.did,
name: contact.name,
});
@@ -565,7 +570,7 @@ export default class ContactQRScanShow extends Vue {
if (regResult.success) {
contact.registered = true;
await this.$updateContact(contact.did, { registered: true });
logger.info("Contact registration successful", { did: contact.did });
logger.debug("Contact registration successful", { did: contact.did });
this.notify.success(
createQRRegistrationSuccessMessage(contact.name || ""),
@@ -691,20 +696,20 @@ export default class ContactQRScanShow extends Vue {
async handleAppPause() {
if (!this.isMounted) return;
logger.info("App paused, stopping scanner");
logger.debug("App paused, stopping scanner");
await this.stopScanning();
}
handleAppResume() {
if (!this.isMounted) return;
logger.info("App resumed, scanner can be restarted by user");
logger.debug("App resumed, scanner can be restarted by user");
this.isScanning = false;
}
async addNewContact(contact: Contact) {
try {
logger.info("Opening database connection for new contact");
logger.debug("Opening database connection for new contact");
// Check if contact already exists
const existingContact = await this.$getContact(contact.did);
@@ -731,7 +736,7 @@ export default class ContactQRScanShow extends Vue {
await this.$insertContact(contact);
if (this.activeDid) {
logger.info("Setting contact visibility", { did: contact.did });
logger.debug("Setting contact visibility", { did: contact.did });
await this.setVisibility(contact, true);
contact.seesMe = true;
}

View File

@@ -174,7 +174,7 @@ import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { isDatabaseError } from "@/interfaces/common";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { APP_SERVER } from "@/constants/app";
import { APP_SERVER, DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
import { QRNavigationService } from "@/services/QRNavigationService";
import {
NOTIFY_CONTACT_NO_INFO,
@@ -294,10 +294,19 @@ export default class ContactsView extends Vue {
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || DEFAULT_ENDORSER_API_SERVER;
this.isRegistered = !!settings.isRegistered;
logger.debug("[ContactsView] Created with settings:", {
activeDid: this.activeDid,
apiServer: this.apiServer,
isRegistered: this.isRegistered,
});
// if these detect a query parameter, they can and then redirect to this URL without a query parameter
// to avoid problems when they reload or they go forward & back and it tries to reprocess
await this.processContactJwt();
@@ -346,15 +355,34 @@ export default class ContactsView extends Vue {
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
this.notify.error(NOTIFY_BLANK_INVITE.message, TIMEOUTS.VERY_LONG);
} else if (importedInviteJwt) {
logger.debug("[ContactsView] Processing invite JWT, current activeDid:", {
activeDid: this.activeDid,
});
// Re-fetch settings after ensuring active_identity is populated
const updatedSettings = await this.$accountSettings();
this.activeDid = updatedSettings.activeDid || "";
this.apiServer = updatedSettings.apiServer || DEFAULT_ENDORSER_API_SERVER;
// Identity creation should be handled by router guard, but keep as fallback for invite processing
if (!this.activeDid) {
logger.info(
"[ContactsView] No active DID found, creating identity as fallback for invite processing",
);
this.activeDid = await generateSaveAndActivateIdentity();
logger.info("[ContactsView] Created new identity:", {
activeDid: this.activeDid,
});
}
// send invite directly to server, with auth for this user
const headers = await getHeaders(this.activeDid);
logger.debug("[ContactsView] Making API request to claim invite:", {
apiServer: this.apiServer,
activeDid: this.activeDid,
hasApiServer: !!this.apiServer,
apiServerLength: this.apiServer?.length || 0,
fullUrl: this.apiServer + "/api/v2/claim",
});
try {
const response = await this.axios.post(
this.apiServer + "/api/v2/claim",
@@ -376,6 +404,9 @@ export default class ContactsView extends Vue {
const payload: JWTPayload =
decodeEndorserJwt(importedInviteJwt).payload;
const registration = payload as VerifiableCredential;
logger.debug(
"[ContactsView] Opening ContactNameDialog for invite processing",
);
(this.$refs.contactNameDialog as ContactNameDialog).open(
"Who Invited You?",
"",
@@ -414,17 +445,28 @@ export default class ContactsView extends Vue {
this.$logAndConsole(fullError, true);
let message = "Got an error sending the invite.";
if (
error &&
typeof error === "object" &&
"response" in error &&
error.response &&
typeof error.response === "object" &&
"data" in error.response &&
error.response.data &&
error.response.data.error
typeof error.response.data === "object" &&
"error" in error.response.data
) {
if (error.response.data.error.message) {
message = error.response.data.error.message;
const responseData = error.response.data as { error: unknown };
if (
responseData.error &&
typeof responseData.error === "object" &&
"message" in responseData.error
) {
message = (responseData.error as { message: string }).message;
} else {
message = error.response.data.error;
message = String(responseData.error);
}
} else if (error.message) {
message = error.message;
} else if (error && typeof error === "object" && "message" in error) {
message = (error as { message: string }).message;
}
this.notify.error(message, TIMEOUTS.MODAL);
}

View File

@@ -376,7 +376,12 @@ export default class DIDView extends Vue {
*/
private async initializeSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
}

View File

@@ -1261,7 +1261,7 @@ export default class DatabaseMigration extends Vue {
this.comparison.differences.settings.added.length +
this.comparison.differences.accounts.added.length;
this.successMessage = `Comparison completed successfully. Found ${totalItems} items to migrate.`;
logger.info(
logger.debug(
"[DatabaseMigration] Database comparison completed successfully",
);
} catch (error) {
@@ -1313,7 +1313,7 @@ export default class DatabaseMigration extends Vue {
this.successMessage += ` ${result.warnings.length} warnings.`;
this.warning += result.warnings.join(", ");
}
logger.info(
logger.debug(
"[DatabaseMigration] Settings migration completed successfully",
result,
);
@@ -1356,7 +1356,7 @@ export default class DatabaseMigration extends Vue {
this.successMessage += ` ${result.warnings.length} warnings.`;
this.warning += result.warnings.join(", ");
}
logger.info(
logger.debug(
"[DatabaseMigration] Account migration completed successfully",
result,
);
@@ -1406,7 +1406,7 @@ export default class DatabaseMigration extends Vue {
URL.revokeObjectURL(url);
this.successMessage = "Comparison data exported successfully";
logger.info("[DatabaseMigration] Comparison data exported successfully");
logger.debug("[DatabaseMigration] Comparison data exported successfully");
} catch (error) {
this.error = `Failed to export comparison data: ${error}`;
logger.error("[DatabaseMigration] Export failed:", error);

View File

@@ -415,7 +415,11 @@ export default class DiscoverView extends Vue {
const searchPeople = !!this.$route.query["searchPeople"];
const settings = await this.$accountSettings();
this.activeDid = (settings.activeDid as string) || "";
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = (settings.apiServer as string) || "";
this.partnerApiServer =
(settings.partnerApiServer as string) || this.partnerApiServer;

View File

@@ -442,7 +442,11 @@ export default class GiftedDetails extends Vue {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
if (
(this.giverDid && !this.giverName) ||

View File

@@ -694,7 +694,10 @@ export default class HelpView extends Vue {
try {
const settings = await this.$accountSettings();
if (settings.activeDid) {
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
if (activeIdentity.activeDid) {
await this.$updateSettings({
...settings,
finishedOnboarding: false,
@@ -702,7 +705,7 @@ export default class HelpView extends Vue {
this.$log(
"[HelpView] Onboarding reset successfully for DID: " +
settings.activeDid,
activeIdentity.activeDid,
);
}

View File

@@ -238,7 +238,7 @@ Raymer * @version 1.0.0 */
<script lang="ts">
import { UAParser } from "ua-parser-js";
import { Component, Vue } from "vue-facing-decorator";
import { Component, Vue, Watch } from "vue-facing-decorator";
import { Router } from "vue-router";
//import App from "../App.vue";
@@ -283,6 +283,7 @@ import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { NOTIFY_CONTACT_LOADING_ISSUE } from "@/constants/notifications";
import * as Package from "../../package.json";
import { UNNAMED_ENTITY_NAME } from "@/constants/entities";
import { errorStringForLog } from "../libs/endorserServer";
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
interface Claim {
@@ -399,6 +400,44 @@ export default class HomeView extends Vue {
newOffersToUserProjectsHitLimit: boolean = false;
numNewOffersToUser: number = 0; // number of new offers-to-user
numNewOffersToUserProjects: number = 0; // number of new offers-to-user's-projects
/**
* CRITICAL VUE REACTIVITY BUG WORKAROUND
*
* This watcher is required for the component to render correctly.
* Without it, the newDirectOffersActivityNumber element fails to render
* even when numNewOffersToUser has the correct value.
*
* This appears to be a Vue reactivity issue where property changes
* don't trigger proper template updates.
*
* DO NOT REMOVE until the underlying Vue reactivity issue is resolved.
*
* See: doc/activeDid-migration-plan.md for details
*/
@Watch("numNewOffersToUser")
onNumNewOffersToUserChange(newValue: number, oldValue: number) {
logger.debug("[HomeView] numNewOffersToUser changed", {
oldValue,
newValue,
willRender: !!newValue,
vIfCondition: `v-if="numNewOffersToUser"`,
elementTestId: "newDirectOffersActivityNumber",
shouldShowElement: newValue > 0,
timestamp: new Date().toISOString(),
});
}
// get shouldShowNewOffersToUser() {
// const shouldShow = !!this.numNewOffersToUser;
// logger.debug("[HomeView] shouldShowNewOffersToUser computed", {
// numNewOffersToUser: this.numNewOffersToUser,
// shouldShow,
// timestamp: new Date().toISOString()
// });
// return shouldShow;
// }
searchBoxes: Array<{
name: string;
bbox: BoundingBox;
@@ -432,13 +471,44 @@ export default class HomeView extends Vue {
*/
async mounted() {
try {
logger.debug("[HomeView] mounted() - component lifecycle started", {
timestamp: new Date().toISOString(),
componentName: "HomeView",
});
await this.initializeIdentity();
// Settings already loaded in initializeIdentity()
await this.loadContacts();
// Contacts already loaded in initializeIdentity()
// Registration check already handled in initializeIdentity()
await this.loadFeedData();
logger.debug("[HomeView] mounted() - about to call loadNewOffers()", {
timestamp: new Date().toISOString(),
activeDid: this.activeDid,
hasActiveDid: !!this.activeDid,
});
await this.loadNewOffers();
logger.debug("[HomeView] mounted() - loadNewOffers() completed", {
timestamp: new Date().toISOString(),
numNewOffersToUser: this.numNewOffersToUser,
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
shouldShowElement:
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
});
await this.checkOnboarding();
logger.debug("[HomeView] mounted() - component lifecycle completed", {
timestamp: new Date().toISOString(),
finalState: {
numNewOffersToUser: this.numNewOffersToUser,
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
shouldShowElement:
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
},
});
} catch (err: unknown) {
this.handleError(err);
}
@@ -515,11 +585,22 @@ export default class HomeView extends Vue {
// **CRITICAL**: Ensure correct API server for platform
await this.ensureCorrectApiServer();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
logger.debug("[HomeView] ActiveDid migration - using new API", {
activeDid: this.activeDid,
source: "active_identity table",
hasActiveDid: !!this.activeDid,
activeIdentityResult: activeIdentity,
isRegistered: this.isRegistered,
timestamp: new Date().toISOString(),
});
// Load contacts with graceful fallback
try {
this.loadContacts();
await this.loadContacts();
} catch (error) {
this.$logAndConsole(
`[HomeView] Failed to retrieve contacts: ${error}`,
@@ -654,24 +735,103 @@ export default class HomeView extends Vue {
* @requires Active DID
*/
private async loadNewOffers() {
if (this.activeDid) {
const offersToUserData = await getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId,
);
this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
logger.debug("[HomeView] loadNewOffers() called with activeDid:", {
activeDid: this.activeDid,
hasActiveDid: !!this.activeDid,
length: this.activeDid?.length || 0,
});
const offersToUserProjects = await getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId,
if (this.activeDid) {
logger.debug(
"[HomeView] loadNewOffers() - activeDid found, calling API",
{
activeDid: this.activeDid,
apiServer: this.apiServer,
isRegistered: this.isRegistered,
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
},
);
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
try {
const offersToUserData = await getNewOffersToUser(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserJwtId,
);
logger.debug(
"[HomeView] loadNewOffers() - getNewOffersToUser successful",
{
activeDid: this.activeDid,
dataLength: offersToUserData.data.length,
hitLimit: offersToUserData.hitLimit,
},
);
this.numNewOffersToUser = offersToUserData.data.length;
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
logger.debug("[HomeView] loadNewOffers() - updated component state", {
activeDid: this.activeDid,
numNewOffersToUser: this.numNewOffersToUser,
newOffersToUserHitLimit: this.newOffersToUserHitLimit,
willRender: !!this.numNewOffersToUser,
timestamp: new Date().toISOString(),
});
const offersToUserProjects = await getNewOffersToUserProjects(
this.axios,
this.apiServer,
this.activeDid,
this.lastAckedOfferToUserProjectsJwtId,
);
logger.debug(
"[HomeView] loadNewOffers() - getNewOffersToUserProjects successful",
{
activeDid: this.activeDid,
dataLength: offersToUserProjects.data.length,
hitLimit: offersToUserProjects.hitLimit,
},
);
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
logger.debug("[HomeView] loadNewOffers() - all API calls completed", {
numNewOffersToUser: this.numNewOffersToUser,
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
shouldRenderElement: !!this.numNewOffersToUser,
elementTestId: "newDirectOffersActivityNumber",
timestamp: new Date().toISOString(),
});
// Additional logging for template rendering debugging
logger.debug("[HomeView] loadNewOffers() - template rendering check", {
numNewOffersToUser: this.numNewOffersToUser,
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
totalNewOffers:
this.numNewOffersToUser + this.numNewOffersToUserProjects,
shouldShowElement:
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
vIfCondition: `v-if="numNewOffersToUser + numNewOffersToUserProjects"`,
elementWillRender:
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
timestamp: new Date().toISOString(),
});
} catch (error) {
logger.error("[HomeView] loadNewOffers() - API call failed", {
activeDid: this.activeDid,
apiServer: this.apiServer,
isRegistered: this.isRegistered,
error: errorStringForLog(error),
errorMessage: error instanceof Error ? error.message : String(error),
});
}
} else {
logger.warn("[HomeView] loadNewOffers() - no activeDid available", {
activeDid: this.activeDid,
timestamp: new Date().toISOString(),
});
}
}

View File

@@ -200,7 +200,12 @@ export default class IdentitySwitcherView extends Vue {
async created() {
try {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || "";
@@ -222,8 +227,8 @@ export default class IdentitySwitcherView extends Vue {
}
async switchAccount(did?: string) {
// Save the new active DID to master settings
await this.$saveSettings({ activeDid: did });
// Update the active DID in the active_identity table
await this.$updateActiveDid(did);
// Check if we need to load user-specific settings for the new DID
if (did) {
@@ -267,15 +272,48 @@ export default class IdentitySwitcherView extends Vue {
this.notify.confirm(
NOTIFY_DELETE_IDENTITY_CONFIRM.text,
async () => {
await this.$exec(`DELETE FROM accounts WHERE id = ?`, [id]);
this.otherIdentities = this.otherIdentities.filter(
(ident) => ident.id !== id,
);
await this.smartDeleteAccount(id);
},
-1,
);
}
/**
* Smart deletion with atomic transaction and last account protection
* Follows the Active Pointer + Smart Deletion Pattern
*/
async smartDeleteAccount(id: string) {
await this.$withTransaction(async () => {
const total = await this.$countAccounts();
if (total <= 1) {
this.notify.warning(
"Cannot delete the last account. Keep at least one.",
);
throw new Error("blocked:last-item");
}
const accountDid = await this.$getAccountDidById(parseInt(id));
const activeDid = await this.$getActiveDid();
if (activeDid === accountDid) {
const allDids = await this.$getAllAccountDids();
const nextDid = this.$pickNextAccountDid(
allDids.filter((d) => d !== accountDid),
accountDid,
);
await this.$setActiveDid(nextDid);
this.notify.success(`Switched active to ${nextDid} before deletion.`);
}
await this.$exec("DELETE FROM accounts WHERE id = ?", [id]);
});
// Update UI
this.otherIdentities = this.otherIdentities.filter(
(ident) => ident.id !== id,
);
}
notifyCannotDelete() {
this.notify.warning(
NOTIFY_CANNOT_DELETE_ACTIVE_IDENTITY.message,

View File

@@ -224,13 +224,14 @@ export default class ImportAccountView extends Vue {
);
// Check what was actually imported
const settings = await this.$accountSettings();
// Check account-specific settings
if (settings?.activeDid) {
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
if (activeIdentity.activeDid) {
try {
await this.$query("SELECT * FROM settings WHERE accountDid = ?", [
settings.activeDid,
activeIdentity.activeDid,
]);
} catch (error) {
// Log error but don't interrupt import flow

View File

@@ -120,7 +120,12 @@ export default class InviteOneAcceptView extends Vue {
// Load or generate identity
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
// Identity creation should be handled by router guard, but keep as fallback for deep links

View File

@@ -283,7 +283,12 @@ export default class InviteOneView extends Vue {
try {
// Use PlatformServiceMixin for account settings
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.isRegistered = !!settings.isRegistered;

View File

@@ -205,7 +205,12 @@ export default class NewActivityView extends Vue {
try {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId || "";

View File

@@ -110,9 +110,9 @@ export default class NewEditAccountView extends Vue {
* @async
*/
async onClickSaveChanges() {
// Get the current active DID to save to user-specific settings
const settings = await this.$accountSettings();
const activeDid = settings.activeDid;
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (activeDid) {
// Save to user-specific settings for the current identity

View File

@@ -378,7 +378,12 @@ export default class NewEditProjectView extends Vue {
this.numAccounts = await retrieveAccountCount();
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;

View File

@@ -433,7 +433,12 @@ export default class OfferDetailsView extends Vue {
private async loadAccountSettings() {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer ?? "";
this.activeDid = settings.activeDid ?? "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid ?? "";
this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;
}

View File

@@ -780,7 +780,12 @@ export default class ProjectViewView extends Vue {
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await this.$getAllContacts();
this.isRegistered = !!settings.isRegistered;

View File

@@ -391,7 +391,12 @@ export default class ProjectsView extends Vue {
*/
private async initializeUserSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.isRegistered = !!settings.isRegistered;
this.givenName = settings.firstName || "";

View File

@@ -150,7 +150,11 @@ export default class QuickActionBvcBeginView extends Vue {
// Get account settings using PlatformServiceMixin
const settings = await this.$accountSettings();
const activeDid = settings.activeDid || "";
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid || "";
const apiServer = settings.apiServer || "";
if (!activeDid || !apiServer) {

View File

@@ -234,9 +234,13 @@ export default class QuickActionBvcEndView extends Vue {
// Initialize notification helper
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$settings();
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.allContacts = await this.$contacts();

View File

@@ -124,7 +124,12 @@ export default class RecentOffersToUserView extends Vue {
try {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId || "";

View File

@@ -116,7 +116,12 @@ export default class RecentOffersToUserView extends Vue {
try {
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
this.allContacts = await this.$getAllContacts();

View File

@@ -206,7 +206,7 @@ export default class SearchAreaView extends Vue {
this.searchBox = settings.searchBoxes?.[0] || null;
this.resetLatLong();
logger.info("[SearchAreaView] Component mounted", {
logger.debug("[SearchAreaView] Component mounted", {
hasStoredSearchBox: !!this.searchBox,
searchBoxName: this.searchBox?.name,
coordinates: this.searchBox?.bbox,
@@ -317,7 +317,7 @@ export default class SearchAreaView extends Vue {
this.searchBox = newSearchBox;
this.isChoosingSearchBox = false;
logger.info("[SearchAreaView] Search box stored successfully", {
logger.debug("[SearchAreaView] Search box stored successfully", {
searchBox: newSearchBox,
coordinates: newSearchBox.bbox,
});
@@ -360,7 +360,7 @@ export default class SearchAreaView extends Vue {
this.isChoosingSearchBox = false;
this.isNewMarkerSet = false;
logger.info("[SearchAreaView] Search box deleted successfully");
logger.debug("[SearchAreaView] Search box deleted successfully");
// Enhanced notification system with proper timeout
this.notify?.success(NOTIFY_SEARCH_AREA_DELETED.text, TIMEOUTS.STANDARD);

View File

@@ -206,8 +206,10 @@ export default class SeedBackupView extends Vue {
async created() {
try {
let activeDid = "";
const settings = await this.$accountSettings();
activeDid = settings.activeDid || "";
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
activeDid = activeIdentity.activeDid || "";
this.numAccounts = await retrieveAccountCount();
this.activeAccount = await retrieveFullyDecryptedAccount(activeDid);
@@ -238,9 +240,10 @@ export default class SeedBackupView extends Vue {
// Update the account setting to track that user has backed up their seed
try {
const settings = await this.$accountSettings();
if (settings.activeDid) {
await this.$saveUserSettings(settings.activeDid, {
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
if (activeIdentity.activeDid) {
await this.$saveUserSettings(activeIdentity.activeDid, {
hasBackedUpSeed: true,
});
}

View File

@@ -76,7 +76,7 @@ export default class ShareMyContactInfoView extends Vue {
isLoading = false;
async mounted() {
const settings = await this.$settings();
const settings = await this.$accountSettings();
const activeDid = settings?.activeDid;
if (!activeDid) {
this.$router.push({ name: "home" });
@@ -91,8 +91,8 @@ export default class ShareMyContactInfoView extends Vue {
this.isLoading = true;
try {
const settings = await this.$settings();
const account = await this.retrieveAccount(settings);
const settings = await this.$accountSettings();
const account = await this.retrieveAccount();
if (!account) {
this.showAccountError();
@@ -114,10 +114,11 @@ export default class ShareMyContactInfoView extends Vue {
/**
* Retrieve the fully decrypted account for the active DID
*/
private async retrieveAccount(
settings: Settings,
): Promise<Account | undefined> {
const activeDid = settings.activeDid || "";
private async retrieveAccount(): Promise<Account | undefined> {
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid || "";
if (!activeDid) {
return undefined;
}

View File

@@ -175,8 +175,10 @@ export default class SharedPhotoView extends Vue {
this.notify = createNotifyHelpers(this.$notify);
try {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid;
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid;
const temp = await this.$getTemp(SHARED_PHOTO_BASE64_KEY);
const imageB64 = temp?.blobB64 as string;

View File

@@ -68,10 +68,18 @@
placeholder="Enter your SQL query here..."
></textarea>
</div>
<div class="mt-4">
<div class="mt-4 flex items-center gap-4">
<button :class="primaryButtonClasses" @click="executeSql">
Execute
</button>
<label class="flex items-center gap-2">
<input
v-model="returnRawResults"
type="checkbox"
class="rounded border-gray-300"
/>
<span class="text-sm">Return Raw Results (only raw for queries)</span>
</label>
</div>
<div v-if="sqlResult" class="mt-4">
<h3 class="text-lg font-semibold mb-2">Result:</h3>
@@ -401,6 +409,7 @@ export default class Help extends Vue {
// for SQL operations
sqlQuery = "";
sqlResult: unknown = null;
returnRawResults = false;
cryptoLib = cryptoLib;
@@ -625,12 +634,12 @@ export default class Help extends Vue {
* Uses PlatformServiceMixin for database access
*/
async mounted() {
logger.info(
logger.debug(
"[TestView] 🚀 Component mounting - starting URL flow tracking",
);
// Boot-time logging for initial configuration
logger.info("[TestView] 🌍 Boot-time configuration detected:", {
logger.debug("[TestView] 🌍 Boot-time configuration detected:", {
platform: process.env.VITE_PLATFORM,
defaultEndorserApiServer: process.env.VITE_DEFAULT_ENDORSER_API_SERVER,
defaultPartnerApiServer: process.env.VITE_DEFAULT_PARTNER_API_SERVER,
@@ -643,8 +652,11 @@ export default class Help extends Vue {
logger.info("[TestView] 📥 Loading account settings...");
const settings = await this.$accountSettings();
// Get activeDid from new active_identity table (ActiveDid migration)
const activeIdentity = await this.$getActiveIdentity();
logger.info("[TestView] 📊 Settings loaded:", {
activeDid: settings.activeDid,
activeDid: activeIdentity.activeDid,
apiServer: settings.apiServer,
partnerApiServer: settings.partnerApiServer,
isRegistered: settings.isRegistered,
@@ -652,7 +664,8 @@ export default class Help extends Vue {
});
// Update component state
this.activeDid = settings.activeDid || "";
this.activeDid = activeIdentity.activeDid || "";
this.apiServer = settings.apiServer || "";
this.partnerApiServer = settings.partnerApiServer || "";
this.userName = settings.firstName;
@@ -957,15 +970,28 @@ export default class Help extends Vue {
* Supports both SELECT queries (dbQuery) and other SQL commands (dbExec)
* Provides interface for testing raw SQL operations
* Uses PlatformServiceMixin for database access and notification helpers for errors
* When returnRawResults is true, uses direct platform service methods for unparsed results
*/
async executeSql() {
try {
const isSelect = this.sqlQuery.trim().toLowerCase().startsWith("select");
if (isSelect) {
this.sqlResult = await this.$query(this.sqlQuery);
if (this.returnRawResults) {
// Use direct platform service methods for raw, unparsed results
if (isSelect) {
this.sqlResult = await this.$dbRawQuery(this.sqlQuery);
} else {
this.sqlResult = await this.$exec(this.sqlQuery);
}
} else {
this.sqlResult = await this.$exec(this.sqlQuery);
// Use methods that normalize the result objects
if (isSelect) {
this.sqlResult = await this.$query(this.sqlQuery);
} else {
this.sqlResult = await this.$exec(this.sqlQuery);
}
}
logger.log("Test SQL Result:", this.sqlResult);
} catch (error) {
logger.error("Test SQL Error:", error);
@@ -991,7 +1017,7 @@ export default class Help extends Vue {
this.urlTestResults = [];
try {
logger.info("[TestView] 🔬 Starting comprehensive URL flow test");
logger.debug("[TestView] 🔬 Starting comprehensive URL flow test");
this.addUrlTestResult("🚀 Starting URL flow test...");
// Test 1: Current state
@@ -1119,7 +1145,7 @@ export default class Help extends Vue {
);
this.addUrlTestResult(`\n✅ URL flow test completed successfully!`);
logger.info("[TestView] ✅ URL flow test completed successfully");
logger.debug("[TestView] ✅ URL flow test completed successfully");
} catch (error) {
const errorMsg = `❌ URL flow test failed: ${error instanceof Error ? error.message : String(error)}`;
this.addUrlTestResult(errorMsg);

View File

@@ -183,7 +183,12 @@ export default class UserProfileView extends Vue {
*/
private async initializeSettings() {
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
// Get activeDid from active_identity table (single source of truth)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const activeIdentity = await (this as any).$getActiveIdentity();
this.activeDid = activeIdentity.activeDid || "";
this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer;
}