forked from jsnbuchanan/crowd-funder-for-time-pwa
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.
This commit is contained in:
@@ -1319,6 +1319,13 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
await this.dbExec(sql, params);
|
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> {
|
async insertNewDidIntoSettings(did: string): Promise<void> {
|
||||||
await this.dbExec("INSERT INTO settings (accountDid) VALUES (?)", [did]);
|
await this.dbExec("INSERT INTO settings (accountDid) VALUES (?)", [did]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,80 +195,6 @@ export const PlatformServiceMixin = {
|
|||||||
// SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
|
// 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
|
* Update the current activeDid and trigger change detection
|
||||||
* This method should be called when the user switches identities
|
* 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
|
* Map database columns to values with proper type conversion
|
||||||
* Handles boolean conversion from SQLite integers (0/1) to boolean values
|
* Handles boolean conversion from SQLite integers (0/1) to boolean values
|
||||||
@@ -655,9 +644,6 @@ export const PlatformServiceMixin = {
|
|||||||
"[PlatformServiceMixin] $getActiveIdentity() called - API layer verification",
|
"[PlatformServiceMixin] $getActiveIdentity() called - API layer verification",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure the table is populated before reading
|
|
||||||
await this.$ensureActiveIdentityPopulated();
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[PlatformServiceMixin] Getting active identity from active_identity table",
|
"[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(
|
logger.debug(
|
||||||
"[PlatformServiceMixin] No active identity found, returning empty",
|
"[PlatformServiceMixin] No active identity found, returning empty",
|
||||||
);
|
);
|
||||||
@@ -1837,6 +1846,8 @@ export interface IPlatformServiceMixin {
|
|||||||
): Promise<Settings>;
|
): Promise<Settings>;
|
||||||
$getActiveIdentity(): Promise<{ activeDid: string }>;
|
$getActiveIdentity(): Promise<{ activeDid: string }>;
|
||||||
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
||||||
|
$needsActiveIdentitySelection(): Promise<boolean>;
|
||||||
|
$getAvailableAccountDids(): Promise<string[]>;
|
||||||
isCapacitor: boolean;
|
isCapacitor: boolean;
|
||||||
isWeb: boolean;
|
isWeb: boolean;
|
||||||
isElectron: boolean;
|
isElectron: boolean;
|
||||||
@@ -1958,7 +1969,10 @@ declare module "@vue/runtime-core" {
|
|||||||
did?: string,
|
did?: string,
|
||||||
defaults?: Settings,
|
defaults?: Settings,
|
||||||
): Promise<Settings>;
|
): Promise<Settings>;
|
||||||
|
$getActiveIdentity(): Promise<{ activeDid: string }>;
|
||||||
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
|
||||||
|
$needsActiveIdentitySelection(): Promise<boolean>;
|
||||||
|
$getAvailableAccountDids(): Promise<string[]>;
|
||||||
|
|
||||||
// Specialized shortcuts - contacts cached, settings fresh
|
// Specialized shortcuts - contacts cached, settings fresh
|
||||||
$contacts(): Promise<Contact[]>;
|
$contacts(): Promise<Contact[]>;
|
||||||
|
|||||||
@@ -359,9 +359,6 @@ export default class ContactsView extends Vue {
|
|||||||
activeDid: this.activeDid,
|
activeDid: this.activeDid,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure active_identity is populated before processing invite
|
|
||||||
await this.$ensureActiveIdentityPopulated();
|
|
||||||
|
|
||||||
// Re-fetch settings after ensuring active_identity is populated
|
// Re-fetch settings after ensuring active_identity is populated
|
||||||
const updatedSettings = await this.$accountSettings();
|
const updatedSettings = await this.$accountSettings();
|
||||||
this.activeDid = updatedSettings.activeDid || "";
|
this.activeDid = updatedSettings.activeDid || "";
|
||||||
@@ -448,17 +445,28 @@ export default class ContactsView extends Vue {
|
|||||||
this.$logAndConsole(fullError, true);
|
this.$logAndConsole(fullError, true);
|
||||||
let message = "Got an error sending the invite.";
|
let message = "Got an error sending the invite.";
|
||||||
if (
|
if (
|
||||||
|
error &&
|
||||||
|
typeof error === "object" &&
|
||||||
|
"response" in error &&
|
||||||
error.response &&
|
error.response &&
|
||||||
|
typeof error.response === "object" &&
|
||||||
|
"data" in error.response &&
|
||||||
error.response.data &&
|
error.response.data &&
|
||||||
error.response.data.error
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data
|
||||||
) {
|
) {
|
||||||
if (error.response.data.error.message) {
|
const responseData = error.response.data as { error: unknown };
|
||||||
message = error.response.data.error.message;
|
if (
|
||||||
|
responseData.error &&
|
||||||
|
typeof responseData.error === "object" &&
|
||||||
|
"message" in responseData.error
|
||||||
|
) {
|
||||||
|
message = (responseData.error as { message: string }).message;
|
||||||
} else {
|
} else {
|
||||||
message = error.response.data.error;
|
message = String(responseData.error);
|
||||||
}
|
}
|
||||||
} else if (error.message) {
|
} else if (error && typeof error === "object" && "message" in error) {
|
||||||
message = error.message;
|
message = (error as { message: string }).message;
|
||||||
}
|
}
|
||||||
this.notify.error(message, TIMEOUTS.MODAL);
|
this.notify.error(message, TIMEOUTS.MODAL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
await this.initializeIdentity();
|
await this.initializeIdentity();
|
||||||
// Settings already loaded in initializeIdentity()
|
// Settings already loaded in initializeIdentity()
|
||||||
await this.loadContacts();
|
// Contacts already loaded in initializeIdentity()
|
||||||
// Registration check already handled in initializeIdentity()
|
// Registration check already handled in initializeIdentity()
|
||||||
await this.loadFeedData();
|
await this.loadFeedData();
|
||||||
|
|
||||||
@@ -600,7 +600,7 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// Load contacts with graceful fallback
|
// Load contacts with graceful fallback
|
||||||
try {
|
try {
|
||||||
this.loadContacts();
|
await this.loadContacts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$logAndConsole(
|
this.$logAndConsole(
|
||||||
`[HomeView] Failed to retrieve contacts: ${error}`,
|
`[HomeView] Failed to retrieve contacts: ${error}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user