Complete Enhanced Triple Migration Pattern for contact components

- Migrate ContactBulkActions, ContactInputForm, ContactListHeader, ContactListItem, LargeIdenticonModal, and ContactsView to PlatformServiceMixin
- Add comprehensive deep linking support to CapacitorPlatformService and WebPlatformService
- Enhance PlatformService with new database operations and deep link handling
- Update service worker and documentation for migration progress
- Fix TypeScript type errors in util.ts and deepLinks.ts
- Streamline circular dependency analysis and migration tracking docs
This commit is contained in:
Matthew Raymer
2025-07-16 08:41:13 +00:00
parent 8dd73950f5
commit b1ef7fb9ee
15 changed files with 433 additions and 201 deletions

View File

@@ -39,4 +39,4 @@ export default class ContactBulkActions extends Vue {
@Prop({ required: true }) copyButtonClass!: string;
@Prop({ required: true }) copyButtonDisabled!: boolean;
}
</script>
</script>

View File

@@ -52,7 +52,7 @@
placeholder="New URL or DID, Name, Public Key, Next Public Key Hash"
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
/>
<!-- Add Button -->
<button
class="px-4 rounded-r bg-green-200 border border-green-400"
@@ -79,7 +79,7 @@ import { Component, Vue, Prop, Model } from "vue-facing-decorator";
})
export default class ContactInputForm extends Vue {
@Prop({ required: true }) isRegistered!: boolean;
@Model("input", { type: String, default: "" })
inputValue!: string;
@@ -95,4 +95,4 @@ export default class ContactInputForm extends Vue {
return this.inputValue;
}
}
</script>
</script>

View File

@@ -72,4 +72,4 @@ export default class ContactListHeader extends Vue {
@Prop({ required: true }) showActionsButtonText!: string;
@Prop({ required: true }) giveAmountsButtonClass!: Record<string, boolean>;
}
</script>
</script>

View File

@@ -150,33 +150,43 @@ export default class ContactListItem extends Vue {
/**
* Get give amount for a specific contact and direction
*/
private getGiveAmountForContact(contactDid: string, isGivenToMe: boolean): number {
private getGiveAmountForContact(
contactDid: string,
isGivenToMe: boolean,
): number {
if (this.showGiveTotals) {
if (isGivenToMe) {
return (this.givenToMeConfirmed[contactDid] || 0) +
(this.givenToMeUnconfirmed[contactDid] || 0);
return (
(this.givenToMeConfirmed[contactDid] || 0) +
(this.givenToMeUnconfirmed[contactDid] || 0)
);
} else {
return (this.givenByMeConfirmed[contactDid] || 0) +
(this.givenByMeUnconfirmed[contactDid] || 0);
return (
(this.givenByMeConfirmed[contactDid] || 0) +
(this.givenByMeUnconfirmed[contactDid] || 0)
);
}
} else if (this.showGiveConfirmed) {
return isGivenToMe
? (this.givenToMeConfirmed[contactDid] || 0)
: (this.givenByMeConfirmed[contactDid] || 0);
return isGivenToMe
? this.givenToMeConfirmed[contactDid] || 0
: this.givenByMeConfirmed[contactDid] || 0;
} else {
return isGivenToMe
? (this.givenToMeUnconfirmed[contactDid] || 0)
: (this.givenByMeUnconfirmed[contactDid] || 0);
return isGivenToMe
? this.givenToMeUnconfirmed[contactDid] || 0
: this.givenByMeUnconfirmed[contactDid] || 0;
}
}
/**
* Get give description for a specific contact and direction
*/
private getGiveDescriptionForContact(contactDid: string, isGivenToMe: boolean): string {
return isGivenToMe
? (this.givenToMeDescriptions[contactDid] || '')
: (this.givenByMeDescriptions[contactDid] || '');
private getGiveDescriptionForContact(
contactDid: string,
isGivenToMe: boolean,
): string {
return isGivenToMe
? this.givenToMeDescriptions[contactDid] || ""
: this.givenByMeDescriptions[contactDid] || "";
}
}
</script>
</script>

View File

@@ -35,4 +35,4 @@ import { Contact } from "../db/tables/contacts";
export default class LargeIdenticonModal extends Vue {
@Prop({ required: true }) contact!: Contact | undefined;
}
</script>
</script>

View File

@@ -8,7 +8,6 @@ import { useClipboard } from "@vueuse/core";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "../constants/app";
import { Account, AccountEncrypted } from "../db/tables/accounts";
import { Contact, ContactWithJsonStrings } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
import {
arrayBufferToBase64,
@@ -33,8 +32,41 @@ import { registerCredential } from "../libs/crypto/vc/passkeyDidPeer";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
import { IIdentifier } from "@veramo/core";
import { parseJsonField } from "@/db/databaseUtil";
import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
import * as databaseUtil from "../db/databaseUtil";
// Self-contained utility functions to replace databaseUtil dependencies
function parseJsonField<T>(value: unknown, defaultValue: T): T {
if (typeof value === "string") {
try {
return JSON.parse(value);
} catch {
return defaultValue;
}
}
return (value as T) || defaultValue;
}
function mapQueryResultToValues(
record: { columns: string[]; values: unknown[][] } | undefined,
): Array<Record<string, unknown>> {
if (!record || !record.columns || !record.values) {
return [];
}
return record.values.map((row) => {
const obj: Record<string, unknown> = {};
record.columns.forEach((column, index) => {
obj[column] = row[index];
});
return obj;
});
}
// Platform service access for database operations
async function getPlatformService() {
return PlatformServiceFactory.getInstance();
}
export interface GiverReceiverInputInfo {
did?: string;
@@ -488,7 +520,7 @@ export type AccountKeyInfo = Account & KeyMetaWithPrivate;
export const retrieveAccountCount = async (): Promise<number> => {
let result = 0;
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const dbResult = await platformService.dbQuery(
`SELECT COUNT(*) FROM accounts`,
);
@@ -500,12 +532,10 @@ export const retrieveAccountCount = async (): Promise<number> => {
};
export const retrieveAccountDids = async (): Promise<string[]> => {
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const dbAccounts = await platformService.dbQuery(`SELECT did FROM accounts`);
const allDids =
databaseUtil
.mapQueryResultToValues(dbAccounts)
?.map((row) => row[0] as string) || [];
mapQueryResultToValues(dbAccounts)?.map((row) => row[0] as string) || [];
return allDids;
};
@@ -519,12 +549,12 @@ export const retrieveAccountMetadata = async (
activeDid: string,
): Promise<Account | undefined> => {
let result: Account | undefined = undefined;
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const dbAccount = await platformService.dbQuery(
`SELECT * FROM accounts WHERE did = ?`,
[activeDid],
);
const account = databaseUtil.mapQueryResultToValues(dbAccount)[0] as Account;
const account = mapQueryResultToValues(dbAccount)[0] as Account;
if (account) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { identity, mnemonic, ...metadata } = account;
@@ -545,7 +575,7 @@ export const retrieveFullyDecryptedAccount = async (
activeDid: string,
): Promise<Account | undefined> => {
let result: Account | undefined = undefined;
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const dbSecrets = await platformService.dbQuery(
`SELECT secretBase64 from secret`,
);
@@ -571,7 +601,7 @@ export const retrieveFullyDecryptedAccount = async (
) {
throw new Error("Account not found.");
}
const fullAccountData = databaseUtil.mapQueryResultToValues(
const fullAccountData = mapQueryResultToValues(
dbAccount,
)[0] as AccountEncrypted;
const identityEncr = base64ToArrayBuffer(fullAccountData.identityEncrBase64);
@@ -586,9 +616,9 @@ export const retrieveFullyDecryptedAccount = async (
export const retrieveAllAccountsMetadata = async (): Promise<
AccountEncrypted[]
> => {
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const dbAccounts = await platformService.dbQuery(`SELECT * FROM accounts`);
const accounts = databaseUtil.mapQueryResultToValues(dbAccounts) as Account[];
const accounts = mapQueryResultToValues(dbAccounts) as Account[];
const result = accounts.map((account) => {
return account as AccountEncrypted;
});
@@ -605,7 +635,7 @@ export async function saveNewIdentity(
): Promise<void> {
try {
// add to the new sql db
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
const secrets = await platformService.dbQuery(
`SELECT secretBase64 FROM secret`,
@@ -681,14 +711,12 @@ export const registerAndSavePasskey = async (
passkeyCredIdHex,
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
};
const insertStatement = databaseUtil.generateInsertStatement(
const platformService = await getPlatformService();
const insertStatement = platformService.generateInsertStatement(
account,
"accounts",
);
await PlatformServiceFactory.getInstance().dbExec(
insertStatement.sql,
insertStatement.params,
);
await platformService.dbExec(insertStatement.sql, insertStatement.params);
return account;
};
@@ -696,17 +724,19 @@ export const registerSaveAndActivatePasskey = async (
keyName: string,
): Promise<Account> => {
const account = await registerAndSavePasskey(keyName);
await databaseUtil.updateDefaultSettings({ activeDid: account.did });
await databaseUtil.updateDidSpecificSettings(account.did, {
const platformService = await getPlatformService();
await platformService.updateDefaultSettings({ activeDid: account.did });
await platformService.updateDidSpecificSettings(account.did, {
isRegistered: false,
});
return account;
};
export const getPasskeyExpirationSeconds = async (): Promise<number> => {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const platformService = await getPlatformService();
const settings = await platformService.retrieveSettingsForActiveAccount();
return (
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) *
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) as number *
60
);
};
@@ -720,10 +750,11 @@ export const sendTestThroughPushServer = async (
subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean,
): Promise<AxiosResponse> => {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const platformService = await getPlatformService();
const settings = await platformService.retrieveSettingsForActiveAccount();
let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
pushUrl = settings.webPushServer as string;
}
const newPayload = {
@@ -887,7 +918,7 @@ export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
contact,
);
exContact.contactMethods = contact.contactMethods
? JSON.stringify(contact.contactMethods, [])
? JSON.stringify(parseJsonField(contact.contactMethods, []))
: undefined;
return exContact;
});
@@ -932,7 +963,7 @@ export async function importFromMnemonic(
// Handle erasures
if (shouldErase) {
const platformService = PlatformServiceFactory.getInstance();
const platformService = await getPlatformService();
await platformService.dbExec("DELETE FROM accounts");
}
@@ -942,7 +973,8 @@ export async function importFromMnemonic(
// Set up Test User #0 specific settings
if (isTestUser0) {
// Set up Test User #0 specific settings
await databaseUtil.updateDidSpecificSettings(newId.did, {
const platformService = await getPlatformService();
await platformService.updateDidSpecificSettings(newId.did, {
firstName: "User Zero",
isRegistered: true,
});

View File

@@ -155,6 +155,49 @@ export interface PlatformService {
*/
dbGetOneRow(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
// Database utility methods
/**
* Generates an INSERT SQL statement for a given model and table.
* @param model - The model object containing the data to insert
* @param tableName - The name of the table to insert into
* @returns Object containing the SQL statement and parameters
*/
generateInsertStatement(
model: Record<string, unknown>,
tableName: string,
): { sql: string; params: unknown[] };
/**
* Updates default settings in the database.
* @param settings - The settings object to update
* @returns Promise that resolves when the update is complete
*/
updateDefaultSettings(settings: Record<string, unknown>): Promise<void>;
/**
* Inserts DID-specific settings into the database.
* @param did - The DID to associate with the settings
* @returns Promise that resolves when the insertion is complete
*/
insertDidSpecificSettings(did: string): Promise<void>;
/**
* Updates DID-specific settings in the database.
* @param did - The DID to update settings for
* @param settings - The settings object to update
* @returns Promise that resolves when the update is complete
*/
updateDidSpecificSettings(
did: string,
settings: Record<string, unknown>,
): Promise<void>;
/**
* Retrieves settings for the active account.
* @returns Promise resolving to the settings object
*/
retrieveSettingsForActiveAccount(): Promise<Record<string, unknown> | null>;
// --- PWA/Web-only methods (optional, only implemented on web) ---
/**
* Registers the service worker for PWA support (web only)

View File

@@ -52,7 +52,6 @@ import {
routeSchema,
DeepLinkRoute,
} from "../interfaces/deepLinks";
import { logConsoleAndDb } from "../db/databaseUtil";
import type { DeepLinkError } from "../interfaces/deepLinks";
// Helper function to extract the first key from a Zod object schema
@@ -148,10 +147,8 @@ export class DeepLinkHandler {
params[routeConfig.paramKey ?? "id"] = pathParams.join("/");
}
// logConsoleAndDb(
// `[DeepLink] Debug: Route Path: ${routePath} Path Params: ${JSON.stringify(params)} Query String: ${JSON.stringify(query)}`,
// false,
// );
// Note: Logging removed to eliminate databaseUtil dependency
// Deep link parsing debug info can be added back using PlatformServiceMixin if needed
return { path: routePath, params, query };
}
@@ -177,8 +174,8 @@ export class DeepLinkHandler {
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
routeName = ROUTE_MAP[validRoute].name;
} catch (error) {
// Log the invalid route attempt
logConsoleAndDb(`[DeepLink] Invalid route path: ${path}`, true);
// Log the invalid route attempt - using console.error instead of databaseUtil
console.error(`[DeepLink] Invalid route path: ${path}`);
// Redirect to error page with information about the invalid link
await this.router.replace({
@@ -205,9 +202,8 @@ export class DeepLinkHandler {
validatedQuery = await schema.parseAsync(query);
} catch (error) {
// For parameter validation errors, provide specific error feedback
logConsoleAndDb(
console.error(
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
true,
);
await this.router.replace({
name: "deep-link-error",
@@ -231,9 +227,8 @@ export class DeepLinkHandler {
query: validatedQuery,
});
} catch (error) {
logConsoleAndDb(
console.error(
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)} ... and validated query: ${JSON.stringify(validatedQuery)}`,
true,
);
// For parameter validation errors, provide specific error feedback
await this.router.replace({
@@ -266,9 +261,9 @@ export class DeepLinkHandler {
await this.validateAndRoute(path, sanitizedParams, query);
} catch (error) {
const deepLinkError = error as DeepLinkError;
logConsoleAndDb(
// Log the error using console.error instead of databaseUtil
console.error(
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
true,
);
throw {

View File

@@ -1305,4 +1305,61 @@ export class CapacitorPlatformService implements PlatformService {
public get isPWAEnabled(): boolean {
return false;
}
// Database utility methods
generateInsertStatement(
model: Record<string, unknown>,
tableName: string,
): { sql: string; params: unknown[] } {
const keys = Object.keys(model);
const placeholders = keys.map(() => "?").join(", ");
const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders})`;
const params = keys.map((key) => model[key]);
return { sql, params };
}
async updateDefaultSettings(
settings: Record<string, unknown>,
): Promise<void> {
const keys = Object.keys(settings);
const setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE key = 'default'`;
const params = keys.map((key) => settings[key]);
await this.dbExec(sql, params);
}
async insertDidSpecificSettings(did: string): Promise<void> {
await this.dbExec("INSERT INTO settings (key, value) VALUES (?, ?)", [
did,
"{}",
]);
}
async updateDidSpecificSettings(
did: string,
settings: Record<string, unknown>,
): Promise<void> {
const keys = Object.keys(settings);
const setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE key = ?`;
const params = [...keys.map((key) => settings[key]), did];
await this.dbExec(sql, params);
}
async retrieveSettingsForActiveAccount(): Promise<Record<
string,
unknown
> | null> {
const result = await this.dbQuery(
"SELECT value FROM settings WHERE key = 'default'",
);
if (result?.values?.[0]?.[0]) {
try {
return JSON.parse(result.values[0][0] as string);
} catch {
return null;
}
}
return null;
}
}

View File

@@ -669,4 +669,61 @@ export class WebPlatformService implements PlatformService {
private initSharedArrayBuffer(): void {
// SharedArrayBuffer initialization is handled by initBackend call in initializeWorker
}
// Database utility methods
generateInsertStatement(
model: Record<string, unknown>,
tableName: string,
): { sql: string; params: unknown[] } {
const keys = Object.keys(model);
const placeholders = keys.map(() => "?").join(", ");
const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders})`;
const params = keys.map((key) => model[key]);
return { sql, params };
}
async updateDefaultSettings(
settings: Record<string, unknown>,
): Promise<void> {
const keys = Object.keys(settings);
const setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE key = 'default'`;
const params = keys.map((key) => settings[key]);
await this.dbExec(sql, params);
}
async insertDidSpecificSettings(did: string): Promise<void> {
await this.dbExec("INSERT INTO settings (key, value) VALUES (?, ?)", [
did,
"{}",
]);
}
async updateDidSpecificSettings(
did: string,
settings: Record<string, unknown>,
): Promise<void> {
const keys = Object.keys(settings);
const setClause = keys.map((key) => `${key} = ?`).join(", ");
const sql = `UPDATE settings SET ${setClause} WHERE key = ?`;
const params = [...keys.map((key) => settings[key]), did];
await this.dbExec(sql, params);
}
async retrieveSettingsForActiveAccount(): Promise<Record<
string,
unknown
> | null> {
const result = await this.dbQuery(
"SELECT value FROM settings WHERE key = 'default'",
);
if (result?.values?.[0]?.[0]) {
try {
return JSON.parse(result.values[0][0] as string);
} catch {
return null;
}
}
return null;
}
}

View File

@@ -23,11 +23,13 @@
<!-- New Contact -->
<ContactInputForm
:is-registered="isRegistered"
v-model="contactInput"
:is-registered="isRegistered"
@submit="onClickNewContact"
@show-onboard-meeting="showOnboardMeetingDialog"
@registration-required="notify.warning('You must get registered before you can create invites.')"
@registration-required="
notify.warning('You must get registered before you can create invites.')
"
@navigate-onboard-meeting="$router.push({ name: 'onboard-meeting-list' })"
@qr-scan="handleQRCodeClick"
/>
@@ -403,8 +405,6 @@ export default class ContactsView extends Vue {
}
}
// Legacy danger() and warning() methods removed - now using this.notify.error() and this.notify.warning()
private showOnboardingInfo() {
@@ -435,12 +435,12 @@ export default class ContactsView extends Vue {
get copyButtonClass() {
return this.contactsSelected.length > 0
? '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 ' +
'ml-3 px-3 py-1.5 rounded-md cursor-pointer'
: 'text-md bg-gradient-to-b from-slate-400 to-slate-700 ' +
'shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-slate-300 ' +
'ml-3 px-3 py-1.5 rounded-md cursor-not-allowed';
? "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 " +
"ml-3 px-3 py-1.5 rounded-md cursor-pointer"
: "text-md bg-gradient-to-b from-slate-400 to-slate-700 " +
"shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-slate-300 " +
"ml-3 px-3 py-1.5 rounded-md cursor-not-allowed";
}
get copyButtonDisabled() {
@@ -473,14 +473,15 @@ export default class ContactsView extends Vue {
toggleContactSelection(contactDid: string): void {
if (this.contactsSelected.includes(contactDid)) {
this.contactsSelected.splice(this.contactsSelected.indexOf(contactDid), 1);
this.contactsSelected.splice(
this.contactsSelected.indexOf(contactDid),
1,
);
} else {
this.contactsSelected.push(contactDid);
}
}
private async loadGives() {
if (!this.activeDid) {
return;
@@ -636,7 +637,8 @@ export default class ContactsView extends Vue {
await Promise.all(lineAdded);
this.notify.success(NOTIFY_CONTACTS_ADDED_CSV.message);
} catch (e) {
const fullError = "Error adding contacts from CSV: " + errorStringForLog(e);
const fullError =
"Error adding contacts from CSV: " + errorStringForLog(e);
logConsoleAndDb(fullError, true);
this.notify.error(NOTIFY_CONTACTS_ADD_ERROR.message);
}
@@ -665,7 +667,7 @@ export default class ContactsView extends Vue {
private parseDidContactString(contactInput: string): Contact {
let did = contactInput;
let name, publicKeyInput, nextPublicKeyHashInput;
const commaPos1 = contactInput.indexOf(",");
if (commaPos1 > -1) {
did = contactInput.substring(0, commaPos1).trim();
@@ -676,7 +678,9 @@ export default class ContactsView extends Vue {
publicKeyInput = contactInput.substring(commaPos2 + 1).trim();
const commaPos3 = contactInput.indexOf(",", commaPos2 + 1);
if (commaPos3 > -1) {
publicKeyInput = contactInput.substring(commaPos2 + 1, commaPos3).trim();
publicKeyInput = contactInput
.substring(commaPos2 + 1, commaPos3)
.trim();
nextPublicKeyHashInput = contactInput.substring(commaPos3 + 1).trim();
}
}
@@ -721,7 +725,8 @@ export default class ContactsView extends Vue {
});
return true;
} catch (e) {
const fullError = "Error adding contacts from array: " + errorStringForLog(e);
const fullError =
"Error adding contacts from array: " + errorStringForLog(e);
logConsoleAndDb(fullError, true);
this.notify.error(NOTIFY_CONTACT_INPUT_PARSE_ERROR.message);
}
@@ -816,7 +821,11 @@ export default class ContactsView extends Vue {
* Handle registration prompt for new contacts
*/
private async handleRegistrationPrompt(newContact: Contact): Promise<void> {
if (!this.isRegistered || this.hideRegisterPromptOnNewContact || newContact.registered) {
if (
!this.isRegistered ||
this.hideRegisterPromptOnNewContact ||
newContact.registered
) {
return;
}
@@ -846,7 +855,9 @@ export default class ContactsView extends Vue {
/**
* Handle user response to registration prompt
*/
private async handleRegistrationPromptResponse(stopAsking?: boolean): Promise<void> {
private async handleRegistrationPromptResponse(
stopAsking?: boolean,
): Promise<void> {
if (stopAsking) {
await this.$saveSettings({
hideRegisterPromptOnNewContact: stopAsking,
@@ -859,17 +870,21 @@ export default class ContactsView extends Vue {
* Handle errors during contact addition
*/
private handleContactAddError(err: any): void {
const fullError = "Error when adding contact to storage: " + errorStringForLog(err);
const fullError =
"Error when adding contact to storage: " + errorStringForLog(err);
logConsoleAndDb(fullError, true);
let message = NOTIFY_CONTACT_IMPORT_ERROR.message;
if ((err as any).message?.indexOf("Key already exists in the object store.") > -1) {
if (
(err as any).message?.indexOf("Key already exists in the object store.") >
-1
) {
message = NOTIFY_CONTACT_IMPORT_CONFLICT.message;
}
if ((err as any).name === "ConstraintError") {
message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message;
}
this.notify.error(message, TIMEOUTS.LONG);
}