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