Skip to content

Commit 256201c

Browse files
sshaderConvex, Inc.
authored andcommitted
Add configuration for how eagerly to refresh auth tokens (#30588)
I would like this to be configurable so some things like Next.js + Convex Auth can refresh more eagerly than the default. I made it so that if the configured period is shorter than the token lifetime, we'll just refetch immediately, but we'll log a warning since repeatedly fetching immediately is probably a misconfiguration. GitOrigin-RevId: 10a9efbeaac3f91ef75f42923ce126e8730eeb41
1 parent fc60bff commit 256201c

File tree

2 files changed

+58
-38
lines changed

2 files changed

+58
-38
lines changed

src/browser/sync/authentication_manager.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -91,35 +91,31 @@ export class AuthenticationManager {
9191
// Passed down by BaseClient, sends a message to the server
9292
private readonly clearAuth: () => void;
9393
private readonly logger: Logger;
94-
94+
private readonly refreshTokenLeewaySeconds: number;
9595
constructor(
9696
syncState: LocalSyncState,
97-
{
98-
authenticate,
99-
stopSocket,
100-
restartSocket,
101-
pauseSocket,
102-
resumeSocket,
103-
clearAuth,
104-
logger,
105-
}: {
97+
callbacks: {
10698
authenticate: (token: string) => void;
10799
stopSocket: () => Promise<void>;
108100
restartSocket: () => void;
109101
pauseSocket: () => void;
110102
resumeSocket: () => void;
111103
clearAuth: () => void;
104+
},
105+
config: {
106+
refreshTokenLeewaySeconds: number;
112107
logger: Logger;
113108
},
114109
) {
115110
this.syncState = syncState;
116-
this.authenticate = authenticate;
117-
this.stopSocket = stopSocket;
118-
this.restartSocket = restartSocket;
119-
this.pauseSocket = pauseSocket;
120-
this.resumeSocket = resumeSocket;
121-
this.clearAuth = clearAuth;
122-
this.logger = logger;
111+
this.authenticate = callbacks.authenticate;
112+
this.stopSocket = callbacks.stopSocket;
113+
this.restartSocket = callbacks.restartSocket;
114+
this.pauseSocket = callbacks.pauseSocket;
115+
this.resumeSocket = callbacks.resumeSocket;
116+
this.clearAuth = callbacks.clearAuth;
117+
this.logger = config.logger;
118+
this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;
123119
}
124120

125121
async setConfig(
@@ -331,21 +327,31 @@ export class AuthenticationManager {
331327
);
332328
return;
333329
}
334-
const leewaySeconds = 2;
335330
// Because the client and server clocks may be out of sync,
336331
// we only know that the token will expire after `exp - iat`,
337332
// and since we just fetched a fresh one we know when that
338333
// will happen.
339-
const delay = Math.min(
340-
MAXIMUM_REFRESH_DELAY,
341-
(exp - iat - leewaySeconds) * 1000,
342-
);
343-
if (delay <= 0) {
334+
const tokenValiditySeconds = exp - iat;
335+
if (tokenValiditySeconds <= 2) {
344336
this.logger.error(
345337
"Auth token does not live long enough, cannot refetch the token",
346338
);
347339
return;
348340
}
341+
// Attempt to refresh the token `refreshTokenLeewaySeconds` before it expires,
342+
// or immediately if the token is already expiring soon.
343+
let delay = Math.min(
344+
MAXIMUM_REFRESH_DELAY,
345+
(tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1000,
346+
);
347+
if (delay <= 0) {
348+
// Refetch immediately, but this might be due to configuring a `refreshTokenLeewaySeconds`
349+
// that is too large compared to the token's actual lifetime.
350+
this.logger.warn(
351+
`Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`,
352+
);
353+
delay = 0;
354+
}
349355
const refetchTokenTimeoutId = setTimeout(() => {
350356
void this.refetchToken();
351357
}, delay);

src/browser/sync/client.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ export interface BaseConvexClientOptions {
9292
* The default value is `false`
9393
*/
9494
skipConvexDeploymentUrlCheck?: boolean;
95+
/**
96+
* If using auth, the number of seconds before a token expires that we should refresh it.
97+
*
98+
* The default value is `2`.
99+
*/
100+
authRefreshTokenLeewaySeconds?: number;
95101
}
96102

97103
/**
@@ -187,6 +193,8 @@ export class BaseConvexClient {
187193
validateDeploymentUrl(address);
188194
}
189195
options = { ...options };
196+
const authRefreshTokenLeewaySeconds =
197+
options.authRefreshTokenLeewaySeconds ?? 2;
190198
let webSocketConstructor = options.webSocketConstructor;
191199
if (!webSocketConstructor && typeof WebSocket === "undefined") {
192200
throw new Error(
@@ -222,23 +230,29 @@ export class BaseConvexClient {
222230
this.logger,
223231
);
224232
this.requestManager = new RequestManager(this.logger);
225-
this.authenticationManager = new AuthenticationManager(this.state, {
226-
authenticate: (token) => {
227-
const message = this.state.setAuth(token);
228-
this.webSocketManager.sendMessage(message);
229-
},
230-
stopSocket: () => this.webSocketManager.stop(),
231-
restartSocket: () => this.webSocketManager.restart(),
232-
pauseSocket: () => {
233-
this.webSocketManager.pause();
234-
this.state.pause();
233+
this.authenticationManager = new AuthenticationManager(
234+
this.state,
235+
{
236+
authenticate: (token) => {
237+
const message = this.state.setAuth(token);
238+
this.webSocketManager.sendMessage(message);
239+
},
240+
stopSocket: () => this.webSocketManager.stop(),
241+
restartSocket: () => this.webSocketManager.restart(),
242+
pauseSocket: () => {
243+
this.webSocketManager.pause();
244+
this.state.pause();
245+
},
246+
resumeSocket: () => this.webSocketManager.resume(),
247+
clearAuth: () => {
248+
this.clearAuth();
249+
},
235250
},
236-
resumeSocket: () => this.webSocketManager.resume(),
237-
clearAuth: () => {
238-
this.clearAuth();
251+
{
252+
logger: this.logger,
253+
refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds,
239254
},
240-
logger: this.logger,
241-
});
255+
);
242256
this.optimisticQueryResults = new OptimisticQueryResults();
243257
this.onTransition = onTransition;
244258
this._nextRequestId = 0;

0 commit comments

Comments
 (0)