MCPcopy
hub / github.com/claude-code-best/claude-code / createTokenRefreshScheduler

Function createTokenRefreshScheduler

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

Source from the content-addressed store, hash-verified

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

Callers 2

initEnvLessBridgeCoreFunction · 0.85
runBridgeLoopFunction · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected