forked from trent_larson/crowd-funder-for-time-pwa
Merge branch 'master' into claimview-fullfills-offer
This commit is contained in:
@@ -60,6 +60,7 @@ import { PlanSummaryRecord } from "../interfaces/records";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
import { SOMEONE_UNNAMED } from "@/constants/entities";
|
||||
|
||||
/**
|
||||
* Standard context for schema.org data
|
||||
@@ -309,7 +310,7 @@ export function didInfoForContact(
|
||||
showDidForVisible: boolean = false,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): { known: boolean; displayName: string; profileImageUrl?: string } {
|
||||
if (!did) return { displayName: "Someone Unnamed/Unknown", known: false };
|
||||
if (!did) return { displayName: SOMEONE_UNNAMED, known: false };
|
||||
if (did === activeDid) {
|
||||
return { displayName: "You", known: true };
|
||||
} else if (contact) {
|
||||
@@ -485,6 +486,15 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
|
||||
max: 500,
|
||||
});
|
||||
|
||||
/**
|
||||
* Tracks in-flight requests to prevent duplicate API calls for the same plan
|
||||
* @constant {Map}
|
||||
*/
|
||||
const inFlightRequests = new Map<
|
||||
string,
|
||||
Promise<PlanSummaryRecord | undefined>
|
||||
>();
|
||||
|
||||
/**
|
||||
* Retrieves plan data from cache or server
|
||||
* @param {string} handleId - Plan handle ID
|
||||
@@ -504,40 +514,140 @@ export async function getPlanFromCache(
|
||||
if (!handleId) {
|
||||
return undefined;
|
||||
}
|
||||
let cred = planCache.get(handleId);
|
||||
if (!cred) {
|
||||
const url =
|
||||
apiServer +
|
||||
"/api/v2/report/plans?handleId=" +
|
||||
encodeURIComponent(handleId);
|
||||
const headers = await getHeaders(requesterDid);
|
||||
try {
|
||||
const resp = await axios.get(url, { headers });
|
||||
if (resp.status === 200 && resp.data?.data?.length > 0) {
|
||||
cred = resp.data.data[0];
|
||||
planCache.set(handleId, cred);
|
||||
} else {
|
||||
// Use debug level for development to reduce console noise
|
||||
const isDevelopment = process.env.VITE_PLATFORM === "development";
|
||||
const log = isDevelopment ? logger.debug : logger.log;
|
||||
|
||||
log(
|
||||
"[EndorserServer] Plan cache is empty for handle",
|
||||
handleId,
|
||||
" Got data:",
|
||||
JSON.stringify(resp.data),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[EndorserServer] Failed to load plan with handle",
|
||||
handleId,
|
||||
" Got error:",
|
||||
JSON.stringify(error),
|
||||
);
|
||||
}
|
||||
// Check cache first (existing behavior)
|
||||
const cred = planCache.get(handleId);
|
||||
if (cred) {
|
||||
return cred;
|
||||
}
|
||||
|
||||
// Check if request is already in flight (NEW: request deduplication)
|
||||
if (inFlightRequests.has(handleId)) {
|
||||
logger.debug(
|
||||
"[Plan Loading] 🔄 Request already in flight, reusing promise:",
|
||||
{
|
||||
handleId,
|
||||
requesterDid,
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
return inFlightRequests.get(handleId);
|
||||
}
|
||||
|
||||
// Create new request promise (NEW: request coordination)
|
||||
const requestPromise = performPlanRequest(
|
||||
handleId,
|
||||
axios,
|
||||
apiServer,
|
||||
requesterDid,
|
||||
);
|
||||
inFlightRequests.set(handleId, requestPromise);
|
||||
|
||||
try {
|
||||
const result = await requestPromise;
|
||||
return result;
|
||||
} finally {
|
||||
// Clean up in-flight request tracking (NEW: cleanup)
|
||||
inFlightRequests.delete(handleId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual plan request to the server
|
||||
* @param {string} handleId - Plan handle ID
|
||||
* @param {Axios} axios - Axios instance
|
||||
* @param {string} apiServer - API server URL
|
||||
* @param {string} [requesterDid] - Optional requester DID for private info
|
||||
* @returns {Promise<PlanSummaryRecord|undefined>} Plan data or undefined if not found
|
||||
*
|
||||
* @throws {Error} If server request fails
|
||||
*/
|
||||
async function performPlanRequest(
|
||||
handleId: string,
|
||||
axios: Axios,
|
||||
apiServer: string,
|
||||
requesterDid?: string,
|
||||
): Promise<PlanSummaryRecord | undefined> {
|
||||
const url =
|
||||
apiServer + "/api/v2/report/plans?handleId=" + encodeURIComponent(handleId);
|
||||
const headers = await getHeaders(requesterDid);
|
||||
|
||||
// Enhanced diagnostic logging for plan loading
|
||||
const requestId = `plan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
logger.debug("[Plan Loading] 🔍 Loading plan from server:", {
|
||||
requestId,
|
||||
handleId,
|
||||
apiServer,
|
||||
endpoint: url,
|
||||
requesterDid,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
try {
|
||||
const resp = await axios.get(url, { headers });
|
||||
|
||||
logger.debug("[Plan Loading] ✅ Plan loaded successfully:", {
|
||||
requestId,
|
||||
handleId,
|
||||
status: resp.status,
|
||||
hasData: !!resp.data?.data,
|
||||
dataLength: resp.data?.data?.length || 0,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (resp.status === 200 && resp.data?.data?.length > 0) {
|
||||
const cred = resp.data.data[0];
|
||||
planCache.set(handleId, cred);
|
||||
|
||||
logger.debug("[Plan Loading] 💾 Plan cached:", {
|
||||
requestId,
|
||||
handleId,
|
||||
planName: cred?.name,
|
||||
planIssuer: cred?.issuerDid,
|
||||
});
|
||||
|
||||
return cred;
|
||||
} else {
|
||||
// Use debug level for development to reduce console noise
|
||||
const isDevelopment = process.env.VITE_PLATFORM === "development";
|
||||
const log = isDevelopment ? logger.debug : logger.log;
|
||||
|
||||
log(
|
||||
"[Plan Loading] ⚠️ Plan cache is empty for handle",
|
||||
handleId,
|
||||
" Got data:",
|
||||
JSON.stringify(resp.data),
|
||||
);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
// Enhanced error logging for plan loading failures
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: unknown;
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
};
|
||||
message?: string;
|
||||
};
|
||||
|
||||
logger.error("[Plan Loading] ❌ Failed to load plan:", {
|
||||
requestId,
|
||||
handleId,
|
||||
apiServer,
|
||||
endpoint: url,
|
||||
requesterDid,
|
||||
errorStatus: axiosError.response?.status,
|
||||
errorStatusText: axiosError.response?.statusText,
|
||||
errorData: axiosError.response?.data,
|
||||
errorMessage: axiosError.message || String(error),
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
return cred;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1018,19 +1128,82 @@ export async function createAndSubmitClaim(
|
||||
|
||||
const vcJwt: string = await createEndorserJwtForDid(issuerDid, vcPayload);
|
||||
|
||||
// Enhanced diagnostic logging for claim submission
|
||||
const requestId = `claim_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
logger.info("[Claim Submission] 🚀 Starting claim submission:", {
|
||||
requestId,
|
||||
apiServer,
|
||||
requesterDid: issuerDid,
|
||||
endpoint: `${apiServer}/api/v2/claim`,
|
||||
timestamp: new Date().toISOString(),
|
||||
jwtLength: vcJwt.length,
|
||||
});
|
||||
|
||||
// Make the xhr request payload
|
||||
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||
const url = `${apiServer}/api/v2/claim`;
|
||||
|
||||
logger.debug("[Claim Submission] 📡 Making API request:", {
|
||||
requestId,
|
||||
url,
|
||||
payloadSize: payload.length,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
const response = await axios.post(url, payload, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("[Claim Submission] ✅ Claim submitted successfully:", {
|
||||
requestId,
|
||||
status: response.status,
|
||||
handleId: response.data?.handleId,
|
||||
responseSize: JSON.stringify(response.data).length,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return { success: true, handleId: response.data?.handleId };
|
||||
} catch (error: unknown) {
|
||||
logger.error("Error submitting claim:", error);
|
||||
// Enhanced error logging with comprehensive context
|
||||
const requestId = `claim_error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: { error?: { code?: string; message?: string } };
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
config?: {
|
||||
url?: string;
|
||||
method?: string;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
message?: string;
|
||||
};
|
||||
|
||||
logger.error("[Claim Submission] ❌ Claim submission failed:", {
|
||||
requestId,
|
||||
apiServer,
|
||||
requesterDid: issuerDid,
|
||||
endpoint: `${apiServer}/api/v2/claim`,
|
||||
errorCode: axiosError.response?.data?.error?.code,
|
||||
errorMessage: axiosError.response?.data?.error?.message,
|
||||
httpStatus: axiosError.response?.status,
|
||||
httpStatusText: axiosError.response?.statusText,
|
||||
responseHeaders: axiosError.response?.headers,
|
||||
requestConfig: {
|
||||
url: axiosError.config?.url,
|
||||
method: axiosError.config?.method,
|
||||
headers: axiosError.config?.headers,
|
||||
},
|
||||
originalError: axiosError.message || String(error),
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const errorMessage: string =
|
||||
serverMessageForUser(error) ||
|
||||
(error && typeof error === "object" && "message" in error
|
||||
@@ -1515,14 +1688,56 @@ export async function fetchEndorserRateLimits(
|
||||
) {
|
||||
const url = `${apiServer}/api/report/rateLimits`;
|
||||
const headers = await getHeaders(issuerDid);
|
||||
|
||||
// Enhanced diagnostic logging for user registration tracking
|
||||
logger.debug("[User Registration] Checking user status on server:", {
|
||||
did: issuerDid,
|
||||
server: apiServer,
|
||||
endpoint: url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
||||
|
||||
// Log successful registration check
|
||||
logger.debug("[User Registration] User registration check successful:", {
|
||||
did: issuerDid,
|
||||
server: apiServer,
|
||||
status: response.status,
|
||||
isRegistered: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Enhanced error logging with user registration context
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: { error?: { code?: string; message?: string } };
|
||||
status?: number;
|
||||
};
|
||||
};
|
||||
const errorCode = axiosError.response?.data?.error?.code;
|
||||
const errorMessage = axiosError.response?.data?.error?.message;
|
||||
const httpStatus = axiosError.response?.status;
|
||||
|
||||
logger.warn("[User Registration] User not registered on server:", {
|
||||
did: issuerDid,
|
||||
server: apiServer,
|
||||
errorCode: errorCode,
|
||||
errorMessage: errorMessage,
|
||||
httpStatus: httpStatus,
|
||||
needsRegistration: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Log the original error for debugging
|
||||
logger.error(
|
||||
`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`,
|
||||
errorStringForLog(error),
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1535,8 +1750,53 @@ export async function fetchEndorserRateLimits(
|
||||
* @param {string} issuerDid - The DID for which to check rate limits.
|
||||
* @returns {Promise<AxiosResponse>} The Axios response object.
|
||||
*/
|
||||
export async function fetchImageRateLimits(axios: Axios, issuerDid: string) {
|
||||
const url = DEFAULT_IMAGE_API_SERVER + "/image-limits";
|
||||
export async function fetchImageRateLimits(
|
||||
axios: Axios,
|
||||
issuerDid: string,
|
||||
imageServer?: string,
|
||||
) {
|
||||
const server = imageServer || DEFAULT_IMAGE_API_SERVER;
|
||||
const url = server + "/image-limits";
|
||||
const headers = await getHeaders(issuerDid);
|
||||
return await axios.get(url, { headers } as AxiosRequestConfig);
|
||||
|
||||
// Enhanced diagnostic logging for image server calls
|
||||
logger.debug("[Image Server] Checking image rate limits:", {
|
||||
did: issuerDid,
|
||||
server: server,
|
||||
endpoint: url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
||||
|
||||
// Log successful image server call
|
||||
logger.debug("[Image Server] Image rate limits check successful:", {
|
||||
did: issuerDid,
|
||||
server: server,
|
||||
status: response.status,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
// Enhanced error logging for image server failures
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: { error?: { code?: string; message?: string } };
|
||||
status?: number;
|
||||
};
|
||||
};
|
||||
|
||||
logger.warn("[Image Server] Image rate limits check failed:", {
|
||||
did: issuerDid,
|
||||
server: server,
|
||||
errorCode: axiosError.response?.data?.error?.code,
|
||||
errorMessage: axiosError.response?.data?.error?.message,
|
||||
httpStatus: axiosError.response?.status,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
faCameraRotate,
|
||||
faCaretDown,
|
||||
faChair,
|
||||
faChartLine,
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
faCircle,
|
||||
faCircleCheck,
|
||||
faCircleInfo,
|
||||
faCirclePlus,
|
||||
faCircleQuestion,
|
||||
faCircleRight,
|
||||
faCircleUser,
|
||||
@@ -49,6 +51,7 @@ import {
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faForward,
|
||||
faGear,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHammer,
|
||||
@@ -58,6 +61,7 @@ import {
|
||||
faHouseChimney,
|
||||
faImage,
|
||||
faImagePortrait,
|
||||
faInfo,
|
||||
faLeftRight,
|
||||
faLightbulb,
|
||||
faLink,
|
||||
@@ -72,8 +76,8 @@ import {
|
||||
faPersonCircleCheck,
|
||||
faPersonCircleQuestion,
|
||||
faPlus,
|
||||
faQuestion,
|
||||
faQrcode,
|
||||
faQuestion,
|
||||
faRightFromBracket,
|
||||
faRotate,
|
||||
faShareNodes,
|
||||
@@ -106,6 +110,7 @@ library.add(
|
||||
faCameraRotate,
|
||||
faCaretDown,
|
||||
faChair,
|
||||
faChartLine,
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronLeft,
|
||||
@@ -114,6 +119,7 @@ library.add(
|
||||
faCircle,
|
||||
faCircleCheck,
|
||||
faCircleInfo,
|
||||
faCirclePlus,
|
||||
faCircleQuestion,
|
||||
faCircleRight,
|
||||
faCircleUser,
|
||||
@@ -135,6 +141,7 @@ library.add(
|
||||
faFloppyDisk,
|
||||
faFolderOpen,
|
||||
faForward,
|
||||
faGear,
|
||||
faGift,
|
||||
faGlobe,
|
||||
faHammer,
|
||||
@@ -144,6 +151,7 @@ library.add(
|
||||
faHouseChimney,
|
||||
faImage,
|
||||
faImagePortrait,
|
||||
faInfo,
|
||||
faLeftRight,
|
||||
faLightbulb,
|
||||
faLink,
|
||||
|
||||
164
src/libs/util.ts
164
src/libs/util.ts
@@ -33,6 +33,7 @@ import { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
|
||||
import { UNNAMED_PERSON } from "@/constants/entities";
|
||||
|
||||
// Consolidate this with src/utils/PlatformServiceMixin.mapQueryResultToValues
|
||||
function mapQueryResultToValues(
|
||||
@@ -227,7 +228,7 @@ export const nameForContact = (
|
||||
): string => {
|
||||
return (
|
||||
(contact?.name as string) ||
|
||||
(capitalize ? "This" : "this") + " unnamed user"
|
||||
(capitalize ? "This" : "this") + " " + UNNAMED_PERSON
|
||||
);
|
||||
};
|
||||
|
||||
@@ -648,57 +649,64 @@ export const retrieveAllAccountsMetadata = async (): Promise<
|
||||
return result;
|
||||
};
|
||||
|
||||
export const DUPLICATE_ACCOUNT_ERROR = "Cannot import duplicate account.";
|
||||
|
||||
/**
|
||||
* Saves a new identity to both SQL and Dexie databases
|
||||
* Saves a new identity to SQL database
|
||||
*/
|
||||
export async function saveNewIdentity(
|
||||
identity: IIdentifier,
|
||||
mnemonic: string,
|
||||
derivationPath: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// add to the new sql db
|
||||
const platformService = await getPlatformService();
|
||||
// add to the new sql db
|
||||
const platformService = await getPlatformService();
|
||||
|
||||
const secrets = await platformService.dbQuery(
|
||||
`SELECT secretBase64 FROM secret`,
|
||||
);
|
||||
if (!secrets?.values?.length || !secrets.values[0]?.length) {
|
||||
throw new Error(
|
||||
"No initial encryption supported. We recommend you clear your data and start over.",
|
||||
);
|
||||
}
|
||||
// Check if account already exists before attempting to save
|
||||
const existingAccount = await platformService.dbQuery(
|
||||
"SELECT did FROM accounts WHERE did = ?",
|
||||
[identity.did],
|
||||
);
|
||||
|
||||
const secretBase64 = secrets.values[0][0] as string;
|
||||
|
||||
const secret = base64ToArrayBuffer(secretBase64);
|
||||
const identityStr = JSON.stringify(identity);
|
||||
const encryptedIdentity = await simpleEncrypt(identityStr, secret);
|
||||
const encryptedMnemonic = await simpleEncrypt(mnemonic, secret);
|
||||
const encryptedIdentityBase64 = arrayBufferToBase64(encryptedIdentity);
|
||||
const encryptedMnemonicBase64 = arrayBufferToBase64(encryptedMnemonic);
|
||||
|
||||
const sql = `INSERT INTO accounts (dateCreated, derivationPath, did, identityEncrBase64, mnemonicEncrBase64, publicKeyHex)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
const params = [
|
||||
new Date().toISOString(),
|
||||
derivationPath,
|
||||
identity.did,
|
||||
encryptedIdentityBase64,
|
||||
encryptedMnemonicBase64,
|
||||
identity.keys[0].publicKeyHex,
|
||||
];
|
||||
await platformService.dbExec(sql, params);
|
||||
|
||||
await platformService.updateDefaultSettings({ activeDid: identity.did });
|
||||
|
||||
await platformService.insertDidSpecificSettings(identity.did);
|
||||
} catch (error) {
|
||||
logger.error("Failed to update default settings:", error);
|
||||
if (existingAccount?.values?.length) {
|
||||
throw new Error(
|
||||
"Failed to set default settings. Please try again or restart the app.",
|
||||
`Account with DID ${identity.did} already exists. ${DUPLICATE_ACCOUNT_ERROR}`,
|
||||
);
|
||||
}
|
||||
|
||||
const secrets = await platformService.dbQuery(
|
||||
`SELECT secretBase64 FROM secret`,
|
||||
);
|
||||
if (!secrets?.values?.length || !secrets.values[0]?.length) {
|
||||
throw new Error(
|
||||
"No initial encryption supported. We recommend you clear your data and start over.",
|
||||
);
|
||||
}
|
||||
|
||||
const secretBase64 = secrets.values[0][0] as string;
|
||||
|
||||
const secret = base64ToArrayBuffer(secretBase64);
|
||||
const identityStr = JSON.stringify(identity);
|
||||
const encryptedIdentity = await simpleEncrypt(identityStr, secret);
|
||||
const encryptedMnemonic = await simpleEncrypt(mnemonic, secret);
|
||||
const encryptedIdentityBase64 = arrayBufferToBase64(encryptedIdentity);
|
||||
const encryptedMnemonicBase64 = arrayBufferToBase64(encryptedMnemonic);
|
||||
|
||||
const sql = `INSERT INTO accounts (dateCreated, derivationPath, did, identityEncrBase64, mnemonicEncrBase64, publicKeyHex)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`;
|
||||
const params = [
|
||||
new Date().toISOString(),
|
||||
derivationPath,
|
||||
identity.did,
|
||||
encryptedIdentityBase64,
|
||||
encryptedMnemonicBase64,
|
||||
identity.keys[0].publicKeyHex,
|
||||
];
|
||||
await platformService.dbExec(sql, params);
|
||||
|
||||
await platformService.updateDefaultSettings({ activeDid: identity.did });
|
||||
|
||||
await platformService.insertNewDidIntoSettings(identity.did);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -989,7 +997,7 @@ export async function importFromMnemonic(
|
||||
|
||||
try {
|
||||
// First, ensure the DID-specific settings record exists
|
||||
await platformService.insertDidSpecificSettings(newId.did);
|
||||
await platformService.insertNewDidIntoSettings(newId.did);
|
||||
|
||||
// Then update with Test User #0 specific settings
|
||||
await platformService.updateDidSpecificSettings(newId.did, {
|
||||
@@ -1008,13 +1016,16 @@ export async function importFromMnemonic(
|
||||
const firstName = settings[0];
|
||||
const isRegistered = settings[1];
|
||||
|
||||
logger.info("[importFromMnemonic] Test User #0 settings verification", {
|
||||
did: newId.did,
|
||||
firstName,
|
||||
isRegistered,
|
||||
expectedFirstName: "User Zero",
|
||||
expectedIsRegistered: true,
|
||||
});
|
||||
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) {
|
||||
@@ -1040,7 +1051,7 @@ export async function importFromMnemonic(
|
||||
|
||||
if (retryResult?.values?.length) {
|
||||
const retrySettings = retryResult.values[0];
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"[importFromMnemonic] Test User #0 settings after retry",
|
||||
{
|
||||
firstName: retrySettings[0],
|
||||
@@ -1063,3 +1074,58 @@ export async function importFromMnemonic(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an account with the given DID already exists in the database
|
||||
*
|
||||
* @param did - The DID to check for duplicates
|
||||
* @returns Promise<boolean> - True if account already exists, false otherwise
|
||||
* @throws Error if database query fails
|
||||
*/
|
||||
export async function checkForDuplicateAccount(did: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Checks if an account with the given DID already exists in the database
|
||||
*
|
||||
* @param mnemonic - The mnemonic phrase to derive DID from
|
||||
* @param derivationPath - The derivation path to use
|
||||
* @returns Promise<boolean> - True if account already exists, false otherwise
|
||||
* @throws Error if database query fails
|
||||
*/
|
||||
export async function checkForDuplicateAccount(
|
||||
mnemonic: string,
|
||||
derivationPath: string,
|
||||
): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Implementation of checkForDuplicateAccount with overloaded signatures
|
||||
*/
|
||||
export async function checkForDuplicateAccount(
|
||||
didOrMnemonic: string,
|
||||
derivationPath?: string,
|
||||
): Promise<boolean> {
|
||||
let didToCheck: string;
|
||||
|
||||
if (derivationPath) {
|
||||
// Derive the DID from mnemonic and derivation path
|
||||
const [address, privateHex, publicHex] = deriveAddress(
|
||||
didOrMnemonic.trim().toLowerCase(),
|
||||
derivationPath,
|
||||
);
|
||||
|
||||
const newId = newIdentifier(address, privateHex, publicHex, derivationPath);
|
||||
didToCheck = newId.did;
|
||||
} else {
|
||||
// Use the provided DID directly
|
||||
didToCheck = didOrMnemonic;
|
||||
}
|
||||
|
||||
// Check if an account with this DID already exists
|
||||
const platformService = await getPlatformService();
|
||||
const existingAccount = await platformService.dbQuery(
|
||||
"SELECT did FROM accounts WHERE did = ?",
|
||||
[didToCheck],
|
||||
);
|
||||
|
||||
return (existingAccount?.values?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user