Skip to content
Open
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
33 changes: 21 additions & 12 deletions components/ConvexClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
'use client';

import { ReactNode, useCallback, useState } from 'react';
import { ReactNode, useCallback, useState, useRef } from 'react';
import { ConvexReactClient } from 'convex/react';
import { ConvexProviderWithAuth } from 'convex/react';
import { AuthKitProvider, useAuth, useAccessToken } from '@workos-inc/authkit-nextjs/components';

const noop = () => {};

export function ConvexClientProvider({ children }: { children: ReactNode }) {
const [convex] = useState(() => {
return new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
const client = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
return client;
});

return (
<AuthKitProvider>
// Prevent AuthKit's default window.location.reload() on session expiration.
// We handle auth state gracefully via Convex token refresh and middleware checks.
<AuthKitProvider onSessionExpired={noop}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do, disable some default onSessionExpired behavior? a comment would be helpful

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment - this prevents authkit-nextjs from doing a window.location.reload()

<ConvexProviderWithAuth client={convex} useAuth={useAuthFromAuthKit}>
{children}
</ConvexProviderWithAuth>
Expand All @@ -20,7 +26,9 @@ export function ConvexClientProvider({ children }: { children: ReactNode }) {

function useAuthFromAuthKit() {
const { user, loading: isLoading } = useAuth();
const { getAccessToken, refresh } = useAccessToken();
const { getAccessToken, accessToken, refresh } = useAccessToken();
const accessTokenRef = useRef<string | undefined>(undefined);
accessTokenRef.current = accessToken;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
accessTokenRef.current = accessToken;
useEffect(() => {
accessTokenRef.current = accessToken;
}, [accessToken]);


const isAuthenticated = !!user;

Expand All @@ -31,17 +39,18 @@ function useAuthFromAuthKit() {
}

try {
if (forceRefreshToken) {
return (await refresh()) ?? null;
}

return (await getAccessToken()) ?? null;
// If Convex requests a forced refresh (e.g., token was rejected by server),
// always get a fresh token. Otherwise, return cached token if still valid.
return forceRefreshToken ? ((await refresh()) ?? null) : ((await getAccessToken()) ?? null);
} catch (error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (error) {
} catch {

console.error('Failed to get access token:', error);
return null;
// On network errors during laptop wake, fall back to cached token.
// Even if expired, Convex will treat it like null and clear auth.
// AuthKit's tokenStore schedules automatic retries in the background.
console.log('[Convex Auth] Using cached token during network issues');
return accessTokenRef.current ?? null;
}
},
[user, refresh, getAccessToken],
[user, getAccessToken, refresh],
);

return {
Expand Down
11 changes: 8 additions & 3 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
* @module
*/

import type * as myFunctions from "../myFunctions.js";

import type {
ApiFromModules,
FilterApi,
FunctionReference,
} from "convex/server";
import type * as myFunctions from "../myFunctions.js";

/**
* A utility for referencing Convex functions in your app's API.
Expand All @@ -26,11 +27,15 @@ import type * as myFunctions from "../myFunctions.js";
declare const fullApi: ApiFromModules<{
myFunctions: typeof myFunctions;
}>;
declare const fullApiWithMounts: typeof fullApi;

export declare const api: FilterApi<
typeof fullApi,
typeof fullApiWithMounts,
FunctionReference<any, "public">
>;
export declare const internal: FilterApi<
typeof fullApi,
typeof fullApiWithMounts,
FunctionReference<any, "internal">
>;

export declare const components: {};
3 changes: 2 additions & 1 deletion convex/_generated/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @module
*/

import { anyApi } from "convex/server";
import { anyApi, componentsGeneric } from "convex/server";

/**
* A utility for referencing Convex functions in your app's API.
Expand All @@ -20,3 +20,4 @@ import { anyApi } from "convex/server";
*/
export const api = anyApi;
export const internal = anyApi;
export const components = componentsGeneric();
7 changes: 7 additions & 0 deletions convex/_generated/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import {
ActionBuilder,
AnyComponents,
HttpActionBuilder,
MutationBuilder,
QueryBuilder,
Expand All @@ -18,9 +19,15 @@ import {
GenericQueryCtx,
GenericDatabaseReader,
GenericDatabaseWriter,
FunctionReference,
} from "convex/server";
import type { DataModel } from "./dataModel.js";

type GenericCtx =
| GenericActionCtx<DataModel>
| GenericMutationCtx<DataModel>
| GenericQueryCtx<DataModel>;

/**
* Define a query in this Convex app's public API.
*
Expand Down
1 change: 1 addition & 0 deletions convex/_generated/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
internalActionGeneric,
internalMutationGeneric,
internalQueryGeneric,
componentsGeneric,
} from "convex/server";

/**
Expand Down