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

89
src/libs/util.ts

@ -988,11 +988,6 @@ export async function importFromMnemonic(
): Promise<void> {
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
const [address, privateHex, publicHex] = deriveAddress(mne, derivationPath);
@ -1007,90 +1002,6 @@ export async function importFromMnemonic(
// Save the new identity
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(
settings: Record<string, unknown>,
): 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 setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE id = 1`;
const params = keys.map((key) => settings[key]);
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
const params = [...keys.map((key) => settings[key]), activeDid];
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> {
// Import constants dynamically to avoid circular dependencies
const { DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER } =
@ -1385,7 +1405,18 @@ export class CapacitorPlatformService implements PlatformService {
string,
unknown
> | 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]) {
// Convert the row to an object
const row = result.values[0];

13
src/services/platforms/WebPlatformService.ts

@ -753,7 +753,18 @@ export class WebPlatformService implements PlatformService {
string,
unknown
> | 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]) {
// Convert the row to an object
const row = result.values[0];

74
src/views/AccountViewView.vue

@ -1064,6 +1064,53 @@ export default class AccountViewView extends Vue {
this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact;
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.searchBox = settings.searchBoxes?.[0] || null;
this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
@ -1479,18 +1526,21 @@ export default class AccountViewView extends Vue {
status?: number;
};
};
logger.error("[Server Limits] Error retrieving limits:", {
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,
httpStatus: axiosError?.response?.status,
needsUserMigration: true,
timestamp: new Date().toISOString(),
});
logger.warn(
"[Server Limits] Error retrieving limits, expected for unregistered users:",
{
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,
httpStatus: axiosError?.response?.status,
needsUserMigration: true,
timestamp: new Date().toISOString(),
},
);
// this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
} finally {

2
src/views/HomeView.vue

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

Loading…
Cancel
Save