Browse Source

fix(platform): remove auto-fix identity selection and fix feed loading race condition

- Remove problematic $ensureActiveIdentityPopulated() that auto-selected identities
- Add user-friendly $needsActiveIdentitySelection() and $getAvailableAccountDids() methods
- Fix missing updateActiveDid implementation in CapacitorPlatformService
- Resolve race condition in HomeView initialization causing feed loading failures
- Improve TypeScript error handling in ContactsView invite processing

Addresses team concerns about data consistency and user control for identity selection.
pull/188/head
Matthew Raymer 2 weeks ago
parent
commit
10a1f435ed
  1. 7
      src/services/platforms/CapacitorPlatformService.ts
  2. 168
      src/utils/PlatformServiceMixin.ts
  3. 26
      src/views/ContactsView.vue
  4. 4
      src/views/HomeView.vue

7
src/services/platforms/CapacitorPlatformService.ts

@ -1319,6 +1319,13 @@ export class CapacitorPlatformService implements PlatformService {
await this.dbExec(sql, params);
}
async updateActiveDid(did: string): Promise<void> {
await this.dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[did],
);
}
async insertNewDidIntoSettings(did: string): Promise<void> {
await this.dbExec("INSERT INTO settings (accountDid) VALUES (?)", [did]);
}

168
src/utils/PlatformServiceMixin.ts

@ -195,80 +195,6 @@ export const PlatformServiceMixin = {
// SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
// =================================================
/**
* Ensure active_identity table is populated with data from settings
* This is a one-time fix for the migration gap
*/
async $ensureActiveIdentityPopulated(): Promise<void> {
try {
logger.info(
"[PlatformServiceMixin] $ensureActiveIdentityPopulated() called",
);
// Check if active_identity has data
const activeIdentity = await this.$dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1",
);
const currentActiveDid = activeIdentity?.values?.[0]?.[0] as string;
logger.info(
"[PlatformServiceMixin] Current active_identity table state:",
{ currentActiveDid, hasData: !!currentActiveDid },
);
if (!currentActiveDid) {
logger.info(
"[PlatformServiceMixin] Active identity table empty, populating from settings",
);
// Get activeDid from settings (any row with accountDid)
const settings = await this.$dbQuery(
"SELECT accountDid FROM settings WHERE accountDid IS NOT NULL LIMIT 1",
);
const settingsAccountDid = settings?.values?.[0]?.[0] as string;
logger.info("[PlatformServiceMixin] Found settings accountDid:", {
settingsAccountDid,
});
if (settingsAccountDid) {
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[settingsAccountDid],
);
logger.info(
`[PlatformServiceMixin] Populated active_identity with: ${settingsAccountDid}`,
);
} else {
// If no settings found, try to get any account DID
const accounts = await this.$dbQuery(
"SELECT did FROM accounts LIMIT 1",
);
const accountDid = accounts?.values?.[0]?.[0] as string;
if (accountDid) {
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[accountDid],
);
logger.info(
`[PlatformServiceMixin] Populated active_identity with account DID: ${accountDid}`,
);
} else {
logger.warn(
"[PlatformServiceMixin] No accountDid found in settings or accounts table",
);
}
}
}
} catch (error) {
logger.warn(
"[PlatformServiceMixin] Failed to populate active_identity:",
error,
);
}
},
/**
* Update the current activeDid and trigger change detection
* This method should be called when the user switches identities
@ -306,6 +232,69 @@ export const PlatformServiceMixin = {
}
},
/**
* Check if active identity needs user intervention
* Returns true if active_identity table is empty but accounts exist
* This allows components to show appropriate UI for user selection
*/
async $needsActiveIdentitySelection(): Promise<boolean> {
try {
// Check if active_identity table has a valid activeDid
const activeIdentity = await this.$dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1",
);
const currentActiveDid = activeIdentity?.values?.[0]?.[0] as string;
// If we have an activeDid, validate it exists in accounts
if (currentActiveDid) {
const accountExists = await this.$dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[currentActiveDid],
);
return !accountExists?.values?.length;
}
// If no activeDid, check if there are any accounts available
const availableAccounts = await this.$dbQuery(
"SELECT COUNT(*) FROM accounts",
);
const accountCount = availableAccounts?.values?.[0]?.[0] as number;
return accountCount > 0;
} catch (error) {
logger.error(
"[PlatformServiceMixin] Error checking if active identity selection needed:",
error,
);
return false;
}
},
/**
* Get available account DIDs for user selection
* Returns array of DIDs that can be set as active identity
*/
async $getAvailableAccountDids(): Promise<string[]> {
try {
const result = await this.$dbQuery(
"SELECT did FROM accounts ORDER BY did",
);
if (!result?.values?.length) {
return [];
}
return result.values.map((row: unknown[]) => row[0] as string);
} catch (error) {
logger.error(
"[PlatformServiceMixin] Error getting available account DIDs:",
error,
);
return [];
}
},
/**
* Map database columns to values with proper type conversion
* Handles boolean conversion from SQLite integers (0/1) to boolean values
@ -655,9 +644,6 @@ export const PlatformServiceMixin = {
"[PlatformServiceMixin] $getActiveIdentity() called - API layer verification",
);
// Ensure the table is populated before reading
await this.$ensureActiveIdentityPopulated();
logger.debug(
"[PlatformServiceMixin] Getting active identity from active_identity table",
);
@ -700,6 +686,29 @@ export const PlatformServiceMixin = {
}
}
// Handle empty active_identity table - this indicates a migration issue
// Instead of auto-fixing, we log the issue for user awareness
logger.warn(
"[PlatformServiceMixin] Active identity table is empty - this may indicate a migration issue",
);
// Check if there are any accounts available for user selection
const availableAccounts = await this.$dbQuery(
"SELECT did FROM accounts ORDER BY did LIMIT 5",
);
if (availableAccounts?.values?.length) {
const accountDids = availableAccounts.values.map(
(row: unknown[]) => row[0] as string,
);
logger.info(
"[PlatformServiceMixin] Available accounts for user selection:",
{ accountDids },
);
} else {
logger.warn("[PlatformServiceMixin] No accounts found in database");
}
logger.debug(
"[PlatformServiceMixin] No active identity found, returning empty",
);
@ -1837,6 +1846,8 @@ export interface IPlatformServiceMixin {
): Promise<Settings>;
$getActiveIdentity(): Promise<{ activeDid: string }>;
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
$needsActiveIdentitySelection(): Promise<boolean>;
$getAvailableAccountDids(): Promise<string[]>;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
@ -1958,7 +1969,10 @@ declare module "@vue/runtime-core" {
did?: string,
defaults?: Settings,
): Promise<Settings>;
$getActiveIdentity(): Promise<{ activeDid: string }>;
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
$needsActiveIdentitySelection(): Promise<boolean>;
$getAvailableAccountDids(): Promise<string[]>;
// Specialized shortcuts - contacts cached, settings fresh
$contacts(): Promise<Contact[]>;

26
src/views/ContactsView.vue

@ -359,9 +359,6 @@ export default class ContactsView extends Vue {
activeDid: this.activeDid,
});
// Ensure active_identity is populated before processing invite
await this.$ensureActiveIdentityPopulated();
// Re-fetch settings after ensuring active_identity is populated
const updatedSettings = await this.$accountSettings();
this.activeDid = updatedSettings.activeDid || "";
@ -448,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);
}

4
src/views/HomeView.vue

@ -478,7 +478,7 @@ export default class HomeView extends Vue {
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();
@ -600,7 +600,7 @@ export default class HomeView extends Vue {
// Load contacts with graceful fallback
try {
this.loadContacts();
await this.loadContacts();
} catch (error) {
this.$logAndConsole(
`[HomeView] Failed to retrieve contacts: ${error}`,

Loading…
Cancel
Save