Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

retry fetch token on network error #163

Merged
merged 5 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"arctic": "^1.2.0",
"cookie": "^1.0.1",
"cspell": "^8.17.2",
"is-network-error": "^1.1.0",
"jose": "^5.2.2",
"jwt-decode": "^4.0.0",
"lucia": "^3.2.0",
Expand Down
49 changes: 43 additions & 6 deletions src/react/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import type {
ConvexAuthActionsContext as ConvexAuthActionsContextType,
TokenStorage,
} from "./index.js";
import isNetworkError from "is-network-error";

// Retry after this much time, based on the retry number.
const RETRY_BACKOFF = [500, 2000]; // In ms
const RETRY_JITTER = 100; // In ms

export const ConvexAuthActionsContext =
createContext<ConvexAuthActionsContextType>(undefined as any);
Expand Down Expand Up @@ -72,6 +77,7 @@ export function AuthProvider({
(message: string) => {
if (verbose) {
console.debug(`${new Date().toISOString()} ${message}`);
client.logger?.logVerbose(message);
}
},
[verbose],
Expand Down Expand Up @@ -164,16 +170,47 @@ export function AuthProvider({
return () => browserRemoveEventListener("storage", listener);
}, [setToken]);

const verifyCode = useCallback(
async (
args: { code: string; verifier?: string } | { refreshToken: string },
) => {
let lastError;
// Retry the call if it fails due to a network error.
// This is especially common in mobile apps where an app is backgrounded
// while making a call and hits a network error, but will succeed with a
// retry once the app is brought to the foreground.
let retry = 0;
while (retry < RETRY_BACKOFF.length) {
try {
return await client.unauthenticatedCall(
"auth:signIn" as unknown as SignInAction,
"code" in args
? { params: { code: args.code }, verifier: args.verifier }
: args,
);
} catch (e) {
lastError = e;
if (!isNetworkError(e)) {
break;
}
const wait = RETRY_BACKOFF[retry] + RETRY_JITTER * Math.random();
retry++;
logVerbose(
`verifyCode failed with network error, retry ${retry} of ${RETRY_BACKOFF.length} in ${wait}ms`,
);
await new Promise((resolve) => setTimeout(resolve, wait));
}
}
throw lastError;
},
[client],
);

const verifyCodeAndSetToken = useCallback(
async (
args: { code: string; verifier?: string } | { refreshToken: string },
) => {
const { tokens } = await client.unauthenticatedCall(
"auth:signIn" as unknown as SignInAction,
"code" in args
? { params: { code: args.code }, verifier: args.verifier }
: args,
);
const { tokens } = await verifyCode(args);
logVerbose(`retrieved tokens, is null: ${tokens === null}`);
await setToken({ shouldStore: true, tokens: tokens ?? null });
return tokens !== null;
Expand Down
2 changes: 2 additions & 0 deletions src/react/clientType.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ConvexReactClient } from "convex/react";
import { FunctionReference, OptionalRestArgs } from "convex/server";

export type AuthClient = {
Expand All @@ -12,4 +13,5 @@ export type AuthClient = {
...args: OptionalRestArgs<Action>
): Promise<Action["_returnType"]>;
verbose: boolean | undefined;
logger?: ConvexReactClient["logger"];
};
8 changes: 4 additions & 4 deletions src/react/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ export function ConvexAuthProvider(props: {
return client.action(action, args);
},
unauthenticatedCall(action, args) {
return new ConvexHttpClient((client as any).address).action(
action,
args,
);
return new ConvexHttpClient((client as any).address, {
logger: client.logger,
}).action(action, args);
},
verbose: (client as any).options?.verbose,
logger: client.logger,
}) satisfies AuthClient,
[client],
);
Expand Down