WIP: Fix DB disconnects #204

Open
trentlarson wants to merge 8 commits from clean-db-disconnects into master
  1. 8
      src/libs/endorserServer.ts
  2. 89
      src/libs/util.ts
  3. 37
      src/services/platforms/CapacitorPlatformService.ts
  4. 13
      src/services/platforms/WebPlatformService.ts
  5. 74
      src/views/AccountViewView.vue
  6. 2
      src/views/HomeView.vue

8
src/libs/endorserServer.ts

@ -57,7 +57,7 @@ import {
KeyMetaMaybeWithPrivate, KeyMetaMaybeWithPrivate,
} from "../interfaces/common"; } from "../interfaces/common";
import { PlanSummaryRecord } from "../interfaces/records"; import { PlanSummaryRecord } from "../interfaces/records";
import { logger } from "../utils/logger"; import { logger, safeStringify } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { APP_SERVER } from "@/constants/app"; import { APP_SERVER } from "@/constants/app";
import { SOMEONE_UNNAMED } from "@/constants/entities"; import { SOMEONE_UNNAMED } from "@/constants/entities";
@ -685,7 +685,7 @@ export function serverMessageForUser(error: unknown): string | undefined {
export function errorStringForLog(error: unknown) { export function errorStringForLog(error: unknown) {
let stringifiedError = "" + error; let stringifiedError = "" + error;
try { try {
stringifiedError = JSON.stringify(error); stringifiedError = safeStringify(error);
} catch (e) { } catch (e) {
// can happen with Dexie, eg: // can happen with Dexie, eg:
// TypeError: Converting circular structure to JSON // TypeError: Converting circular structure to JSON
@ -697,7 +697,7 @@ export function errorStringForLog(error: unknown) {
if (error && typeof error === "object" && "response" in error) { if (error && typeof error === "object" && "response" in error) {
const err = error as AxiosErrorResponse; const err = error as AxiosErrorResponse;
const errorResponseText = JSON.stringify(err.response); const errorResponseText = safeStringify(err.response);
// for some reason, error.response is not included in stringify result (eg. for 400 errors on invite redemptions) // for some reason, error.response is not included in stringify result (eg. for 400 errors on invite redemptions)
if (!R.empty(errorResponseText) && !fullError.includes(errorResponseText)) { if (!R.empty(errorResponseText) && !fullError.includes(errorResponseText)) {
// add error.response stuff // add error.response stuff
@ -707,7 +707,7 @@ export function errorStringForLog(error: unknown) {
R.equals(err.config, err.response.config) R.equals(err.config, err.response.config)
) { ) {
// but exclude "config" because it's already in there // but exclude "config" because it's already in there
const newErrorResponseText = JSON.stringify( const newErrorResponseText = safeStringify(
R.omit(["config"] as never[], err.response), R.omit(["config"] as never[], err.response),
); );
fullError += fullError +=

89
src/libs/util.ts

@ -988,11 +988,6 @@ export async function importFromMnemonic(
): Promise<void> { ): Promise<void> {
const mne: string = mnemonic.trim().toLowerCase(); const mne: string = mnemonic.trim().toLowerCase();
// Check if this is Test User #0
const TEST_USER_0_MNEMONIC =
"rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage";
const isTestUser0 = mne === TEST_USER_0_MNEMONIC;
// Derive address and keys from mnemonic // Derive address and keys from mnemonic
const [address, privateHex, publicHex] = deriveAddress(mne, derivationPath); const [address, privateHex, publicHex] = deriveAddress(mne, derivationPath);
@ -1007,90 +1002,6 @@ export async function importFromMnemonic(
// Save the new identity // Save the new identity
await saveNewIdentity(newId, mne, derivationPath); await saveNewIdentity(newId, mne, derivationPath);
// Set up Test User #0 specific settings
if (isTestUser0) {
// Set up Test User #0 specific settings with enhanced error handling
const platformService = await getPlatformService();
try {
// First, ensure the DID-specific settings record exists
await platformService.insertNewDidIntoSettings(newId.did);
// Then update with Test User #0 specific settings
await platformService.updateDidSpecificSettings(newId.did, {
firstName: "User Zero",
isRegistered: true,
});
// Verify the settings were saved correctly
const verificationResult = await platformService.dbQuery(
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
[newId.did],
);
if (verificationResult?.values?.length) {
const settings = verificationResult.values[0];
const firstName = settings[0];
const isRegistered = settings[1];
logger.debug(
"[importFromMnemonic] Test User #0 settings verification",
{
did: newId.did,
firstName,
isRegistered,
expectedFirstName: "User Zero",
expectedIsRegistered: true,
},
);
// If settings weren't saved correctly, try individual updates
if (firstName !== "User Zero" || isRegistered !== 1) {
logger.warn(
"[importFromMnemonic] Test User #0 settings not saved correctly, retrying with individual updates",
);
await platformService.dbExec(
"UPDATE settings SET firstName = ? WHERE accountDid = ?",
["User Zero", newId.did],
);
await platformService.dbExec(
"UPDATE settings SET isRegistered = ? WHERE accountDid = ?",
[1, newId.did],
);
// Verify again
const retryResult = await platformService.dbQuery(
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
[newId.did],
);
if (retryResult?.values?.length) {
const retrySettings = retryResult.values[0];
logger.debug(
"[importFromMnemonic] Test User #0 settings after retry",
{
firstName: retrySettings[0],
isRegistered: retrySettings[1],
},
);
}
}
} else {
logger.error(
"[importFromMnemonic] Failed to verify Test User #0 settings - no record found",
);
}
} catch (error) {
logger.error(
"[importFromMnemonic] Error setting up Test User #0 settings:",
error,
);
// Don't throw - allow the import to continue even if settings fail
}
}
} }
/** /**

37
src/services/platforms/CapacitorPlatformService.ts

@ -1343,10 +1343,21 @@ export class CapacitorPlatformService implements PlatformService {
async updateDefaultSettings( async updateDefaultSettings(
settings: Record<string, unknown>, settings: Record<string, unknown>,
): Promise<void> { ): Promise<void> {
// Get current active DID and update that identity's settings
const activeIdentity = await this.getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (!activeDid) {
logger.warn(
"[CapacitorPlatformService] No active DID found, cannot update default settings",
);
return;
}
const keys = Object.keys(settings); const keys = Object.keys(settings);
const setClause = keys.map((key) => `${key} = ?`).join(", "); const setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE id = 1`; const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
const params = keys.map((key) => settings[key]); const params = [...keys.map((key) => settings[key]), activeDid];
await this.dbExec(sql, params); await this.dbExec(sql, params);
} }
@ -1357,6 +1368,15 @@ export class CapacitorPlatformService implements PlatformService {
); );
} }
async getActiveIdentity(): Promise<{ activeDid: string }> {
const result = await this.dbQuery(
"SELECT activeDid FROM active_identity WHERE id = 1",
);
return {
activeDid: (result?.values?.[0]?.[0] as string) || "",
};
}
async insertNewDidIntoSettings(did: string): Promise<void> { async insertNewDidIntoSettings(did: string): Promise<void> {
// Import constants dynamically to avoid circular dependencies // Import constants dynamically to avoid circular dependencies
const { DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER } = const { DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER } =
@ -1385,7 +1405,18 @@ export class CapacitorPlatformService implements PlatformService {
string, string,
unknown unknown
> | null> { > | null> {
const result = await this.dbQuery("SELECT * FROM settings WHERE id = 1"); // Get current active DID from active_identity table
const activeIdentity = await this.getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (!activeDid) {
return null;
}
const result = await this.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[activeDid],
);
if (result?.values?.[0]) { if (result?.values?.[0]) {
// Convert the row to an object // Convert the row to an object
const row = result.values[0]; const row = result.values[0];

13
src/services/platforms/WebPlatformService.ts

@ -753,7 +753,18 @@ export class WebPlatformService implements PlatformService {
string, string,
unknown unknown
> | null> { > | null> {
const result = await this.dbQuery("SELECT * FROM settings WHERE id = 1"); // Get current active DID from active_identity table
const activeIdentity = await this.getActiveIdentity();
const activeDid = activeIdentity.activeDid;
if (!activeDid) {
return null;
}
const result = await this.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[activeDid],
);
if (result?.values?.[0]) { if (result?.values?.[0]) {
// Convert the row to an object // Convert the row to an object
const row = result.values[0]; const row = result.values[0];

74
src/views/AccountViewView.vue

@ -1064,6 +1064,53 @@ export default class AccountViewView extends Vue {
this.hideRegisterPromptOnNewContact = this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact; !!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
// If settings show unregistered but user has activeDid, verify registration status
if (!this.isRegistered && this.activeDid) {
logger.debug(
"[AccountViewView] Settings show unregistered, verifying with server:",
{
activeDid: this.activeDid,
apiServer: this.apiServer,
},
);
try {
const { fetchEndorserRateLimits } = await import(
"@/libs/endorserServer"
);
const resp = await fetchEndorserRateLimits(
this.apiServer,
this.axios,
this.activeDid,
);
if (resp.status === 200) {
logger.debug(
"[AccountViewView] Server confirms user IS registered, updating settings:",
{
activeDid: this.activeDid,
wasRegistered: false,
nowRegistered: true,
},
);
// Update settings and state
await this.$saveUserSettings(this.activeDid, {
isRegistered: true,
});
this.isRegistered = true;
}
} catch (error) {
logger.debug(
"[AccountViewView] Registration check failed (expected for unregistered users):",
{
activeDid: this.activeDid,
error: error instanceof Error ? error.message : String(error),
},
);
}
}
this.isSearchAreasSet = !!settings.searchBoxes; this.isSearchAreasSet = !!settings.searchBoxes;
this.searchBox = settings.searchBoxes?.[0] || null; this.searchBox = settings.searchBoxes?.[0] || null;
this.notifyingNewActivity = !!settings.notifyingNewActivityTime; this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
@ -1479,18 +1526,21 @@ export default class AccountViewView extends Vue {
status?: number; status?: number;
}; };
}; };
logger.error("[Server Limits] Error retrieving limits:", { logger.warn(
error: error instanceof Error ? error.message : String(error), "[Server Limits] Error retrieving limits, expected for unregistered users:",
did: did, {
apiServer: this.apiServer, error: error instanceof Error ? error.message : String(error),
imageServer: this.DEFAULT_IMAGE_API_SERVER, did: did,
partnerApiServer: this.partnerApiServer, apiServer: this.apiServer,
errorCode: axiosError?.response?.data?.error?.code, imageServer: this.DEFAULT_IMAGE_API_SERVER,
errorMessage: axiosError?.response?.data?.error?.message, partnerApiServer: this.partnerApiServer,
httpStatus: axiosError?.response?.status, errorCode: axiosError?.response?.data?.error?.code,
needsUserMigration: true, errorMessage: axiosError?.response?.data?.error?.message,
timestamp: new Date().toISOString(), httpStatus: axiosError?.response?.status,
}); needsUserMigration: true,
timestamp: new Date().toISOString(),
},
);
// this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD); // this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
} finally { } finally {

2
src/views/HomeView.vue

@ -662,7 +662,7 @@ export default class HomeView extends Vue {
}; };
logger.warn( logger.warn(
"[HomeView Settings Trace] ⚠️ Registration check failed", "[HomeView Settings Trace] ⚠️ Registration check failed, expected for unregistered users.",
{ {
error: errorMessage, error: errorMessage,
did: this.activeDid, did: this.activeDid,

Loading…
Cancel
Save