MCPcopy Create free account
hub / github.com/Noumena-Network/code / createTokenRefreshScheduler

Function createTokenRefreshScheduler

src/bridge/jwtUtils.ts:73–257  ·  view source on GitHub ↗
({
  getAccessToken,
  onRefresh,
  label,
  refreshBufferMs = TOKEN_REFRESH_BUFFER_MS,
}: {
  getAccessToken: () => string | undefined | Promise<string | undefined>
  onRefresh: (sessionId: string) => void | Promise<void>
  label: string
  /** How long before expiry to fire refresh. Defaults to 5 min. */
  refreshBufferMs?: number
})

Source from the content-addressed store, hash-verified

71 * standalone bridge, WebSocket reconnect for the REPL bridge).
72 */
73export function createTokenRefreshScheduler({
74 getAccessToken,
75 onRefresh,
76 label,
77 refreshBufferMs = TOKEN_REFRESH_BUFFER_MS,
78}: {
79 getAccessToken: () => string | undefined | Promise<string | undefined>
80 onRefresh: (sessionId: string) => void | Promise<void>
81 label: string
82 /** How long before expiry to fire refresh. Defaults to 5 min. */
83 refreshBufferMs?: number
84}): {
85 schedule: (sessionId: string, token: string) => void
86 scheduleFromExpiresIn: (sessionId: string, expiresInSeconds: number) => void
87 cancel: (sessionId: string) => void
88 cancelAll: () => void
89} {
90 const timers = new Map<string, ReturnType<typeof setTimeout>>()
91 const failureCounts = new Map<string, number>()
92 // Generation counter per session — incremented by schedule() and cancel()
93 // so that in-flight async doRefresh() calls can detect when they've been
94 // superseded and should skip setting follow-up timers.
95 const generations = new Map<string, number>()
96
97 function nextGeneration(sessionId: string): number {
98 const gen = (generations.get(sessionId) ?? 0) + 1
99 generations.set(sessionId, gen)
100 return gen
101 }
102
103 function schedule(sessionId: string, token: string): void {
104 const expiry = decodeJwtExpiry(token)
105 if (!expiry) {
106 // Token is not a decodable JWT (e.g. an OAuth token passed from the
107 // REPL bridge WebSocket open handler). Preserve any existing timer
108 // (such as the follow-up refresh set by doRefresh) so the refresh
109 // chain is not broken.
110 logForDebugging(
111 `[${label}:token] Could not decode JWT expiry for sessionId=${sessionId}, token prefix=${token.slice(0, 15)}…, keeping existing timer`,
112 )
113 return
114 }
115
116 // Clear any existing refresh timer — we have a concrete expiry to replace it.
117 const existing = timers.get(sessionId)
118 if (existing) {
119 clearTimeout(existing)
120 }
121
122 // Bump generation to invalidate any in-flight async doRefresh.
123 const gen = nextGeneration(sessionId)
124
125 const expiryDate = new Date(expiry * 1000).toISOString()
126 const delayMs = expiry * 1000 - Date.now() - refreshBufferMs
127 if (delayMs <= 0) {
128 logForDebugging(
129 `[${label}:token] Token for sessionId=${sessionId} expires=${expiryDate} (past or within buffer), refreshing immediately`,
130 )

Callers 3

jwtUtils.test.tsFile · 0.85
initEnvLessBridgeCoreFunction · 0.85
runBridgeLoopFunction · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected