| @@ -113,6 +113,10 @@ export interface CachedRemoteJWKSet extends JWTVerifyGetKey { · 112 unmodified lines |
| 113 | 113 | blockingFetchCount: number; |
| 114 | 114 | storeHitCount: number; |
| 115 | 115 | lastFetchDurationMs: number | null; |
| 116 | + /** Wall-clock of the last cross-isolate store read (I/O). */ |
| 117 | + lastStoreReadMs: number | null; |
| 118 | + /** Wall-clock of the last key resolution — WebCrypto `importKey`. */ |
| 119 | + lastResolveMs: number | null; |
| 116 | 120 | }; |
| 117 | 121 | } |
| 118 | 122 | |
| @@ -272,6 +276,8 @@ export const createCachedRemoteJWKSet = ( · 153 unmodified lines |
| 272 | 276 | let blockingFetchCount = 0; |
| 273 | 277 | let storeHitCount = 0; |
| 274 | 278 | let lastFetchDurationMs: number | null = null; |
| 279 | + let lastStoreReadMs: number | null = null; |
| 280 | + let lastResolveMs: number | null = null; |
| 275 | 281 | |
| 276 | 282 | const isFresh = (candidate: CacheEntry): boolean => Date.now() - candidate.fetchedAt < ttlMs; |
| 277 | 283 | const isUsable = (candidate: CacheEntry): boolean => |
| @@ -314,7 +320,9 @@ export const createCachedRemoteJWKSet = ( · 36 unmodified lines |
| 314 | 320 | if (!store) return null; |
| 315 | 321 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: the L2 store is an optimization; any failure degrades to an upstream fetch |
| 316 | 322 | try { |
| 323 | + const storeReadStartedAt = Date.now(); |
| 317 | 324 | const stored = await store.get(url); |
| 325 | + lastStoreReadMs = Date.now() - storeReadStartedAt; |
| 318 | 326 | if (!stored) return null; |
| 319 | 327 | const candidate = entryFrom(stored); |
| 320 | 328 | if (!isUsable(candidate)) return null; |
| @@ -364,7 +372,10 @@ export const createCachedRemoteJWKSet = ( · 43 unmodified lines |
| 364 | 372 | const current = await ensureFresh(false); |
| 365 | 373 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: jose JWTVerifyGetKey retry path is defined by thrown resolver failures |
| 366 | 374 | try { |
| 367 | | − return await current.resolver(protectedHeader, token); |
| 375 | + const resolveStartedAt = Date.now(); |
| 376 | + const key = await current.resolver(protectedHeader, token); |
| 377 | + lastResolveMs = Date.now() - resolveStartedAt; |
| 378 | + return key; |
| 368 | 379 | } catch (error) { |
| 369 | 380 | // Likely cause: keys rotated upstream after our TTL window started, or |
| 370 | 381 | // we answered from a stale entry. Refetch once and try again. Anything |
| @@ -394,6 +405,8 @@ export const createCachedRemoteJWKSet = ( · 23 unmodified lines |
| 394 | 405 | blockingFetchCount, |
| 395 | 406 | storeHitCount, |
| 396 | 407 | lastFetchDurationMs, |
| 408 | + lastStoreReadMs, |
| 409 | + lastResolveMs, |
| 397 | 410 | }), |
| 398 | 411 | }); |
| 399 | 412 | return result; |