|
|
@ -485,6 +485,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,86 +513,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); |
|
|
|
|
|
|
|
// 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:", { |
|
|
|
|
|
|
|
// 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, |
|
|
|
apiServer, |
|
|
|
endpoint: url, |
|
|
|
requesterDid, |
|
|
|
status: resp.status, |
|
|
|
hasData: !!resp.data?.data, |
|
|
|
dataLength: resp.data?.data?.length || 0, |
|
|
|
timestamp: new Date().toISOString(), |
|
|
|
}); |
|
|
|
|
|
|
|
try { |
|
|
|
const resp = await axios.get(url, { headers }); |
|
|
|
if (resp.status === 200 && resp.data?.data?.length > 0) { |
|
|
|
const cred = resp.data.data[0]; |
|
|
|
planCache.set(handleId, cred); |
|
|
|
|
|
|
|
logger.debug("[Plan Loading] ✅ Plan loaded successfully:", { |
|
|
|
logger.debug("[Plan Loading] 💾 Plan cached:", { |
|
|
|
requestId, |
|
|
|
handleId, |
|
|
|
status: resp.status, |
|
|
|
hasData: !!resp.data?.data, |
|
|
|
dataLength: resp.data?.data?.length || 0, |
|
|
|
timestamp: new Date().toISOString(), |
|
|
|
planName: cred?.name, |
|
|
|
planIssuer: cred?.issuerDid, |
|
|
|
}); |
|
|
|
|
|
|
|
if (resp.status === 200 && resp.data?.data?.length > 0) { |
|
|
|
cred = resp.data.data[0]; |
|
|
|
planCache.set(handleId, cred); |
|
|
|
|
|
|
|
logger.debug("[Plan Loading] 💾 Plan cached:", { |
|
|
|
requestId, |
|
|
|
handleId, |
|
|
|
planName: cred?.name, |
|
|
|
planIssuer: cred?.issuerDid, |
|
|
|
}); |
|
|
|
} 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), |
|
|
|
); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
// Enhanced error logging for plan loading failures
|
|
|
|
const axiosError = error as { |
|
|
|
response?: { |
|
|
|
data?: unknown; |
|
|
|
status?: number; |
|
|
|
statusText?: string; |
|
|
|
}; |
|
|
|
message?: string; |
|
|
|
}; |
|
|
|
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; |
|
|
|
|
|
|
|
logger.error("[Plan Loading] ❌ Failed to load plan:", { |
|
|
|
requestId, |
|
|
|
log( |
|
|
|
"[Plan Loading] ⚠️ Plan cache is empty for handle", |
|
|
|
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(), |
|
|
|
}); |
|
|
|
" 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; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|