cover TODOs + cross-tab cache

This commit is contained in:
Igor Loskutov
2025-09-03 07:57:11 -04:00
parent 048ebbd654
commit cff662709d
7 changed files with 138 additions and 59 deletions

View File

@@ -1,16 +1,16 @@
"use client"; "use client";
import { Flex, Spinner } from "@chakra-ui/react"; import { Flex, Spinner } from "@chakra-ui/react";
import useAuthReady from "../lib/useAuthReady"; import { useAuth } from "../lib/AuthProvider";
export default function AuthWrapper({ export default function AuthWrapper({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { isLoading } = useAuthReady(); const auth = useAuth();
if (isLoading) { if (auth.status === "loading") {
return ( return (
<Flex <Flex
flexDir="column" flexDir="column"

View File

@@ -4,23 +4,16 @@ import { $api } from "./apiClient";
import { useError } from "../(errors)/errorContext"; import { useError } from "../(errors)/errorContext";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import type { components, paths } from "../reflector-api"; import type { components, paths } from "../reflector-api";
import useAuthReady from "./useAuthReady"; import { useAuth } from "./AuthProvider";
// FIXME: React Query caching issues with cross-tab synchronization const useAuthReady = () => {
// const auth = useAuth();
// The default React Query behavior caches data indefinitely until invalidated,
// which should work well in theory. However, we're experiencing two problems: return {
// isAuthenticated: auth.status === "authenticated",
// 1. Navigation between pages doesn't refresh data as expected by users isLoading: auth.status === "loading",
// 2. Query invalidation doesn't work properly across browser tabs - changes };
// made in one tab (like updating room settings or deleting transcripts) };
// aren't reflected when navigating in another tab without a full page refresh
//
// As a temporary workaround, we're setting a short staleTime to force data
// reloading, similar to our previous implementation. This should be revisited
// once we can resolve the underlying invalidation and cross-tab sync issues.
// 500ms is arbitrary.
const STALE_TIME = 500;
export function useRoomsList(page: number = 1) { export function useRoomsList(page: number = 1) {
const { isAuthenticated } = useAuthReady(); const { isAuthenticated } = useAuthReady();
@@ -35,7 +28,6 @@ export function useRoomsList(page: number = 1) {
}, },
{ {
enabled: isAuthenticated, enabled: isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -69,7 +61,6 @@ export function useTranscriptsSearch(
}, },
{ {
enabled: isAuthenticated, enabled: isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -115,7 +106,6 @@ export function useTranscriptGet(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -133,7 +123,6 @@ export function useRoomGet(roomId: string | null) {
}, },
{ {
enabled: !!roomId && isAuthenticated, enabled: !!roomId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -205,7 +194,6 @@ export function useZulipStreams() {
{}, {},
{ {
enabled: isAuthenticated, enabled: isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -225,7 +213,6 @@ export function useZulipTopics(streamId: number | null) {
}, },
{ {
enabled, enabled,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -302,7 +289,6 @@ export function useTranscriptWaveform(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -320,7 +306,6 @@ export function useTranscriptMP3(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -338,7 +323,6 @@ export function useTranscriptTopics(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -356,7 +340,6 @@ export function useTranscriptTopicsWithWords(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -380,7 +363,6 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
}, },
{ {
enabled: !!transcriptId && !!topicId && isAuthenticated, enabled: !!transcriptId && !!topicId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }
@@ -398,7 +380,6 @@ export function useTranscriptParticipants(transcriptId: string | null) {
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId && isAuthenticated,
staleTime: STALE_TIME,
}, },
); );
} }

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { QueryClient } from "@tanstack/react-query"; import { QueryClient } from "@tanstack/react-query";
import { broadcastQueryClient } from "@tanstack/query-broadcast-client-experimental";
export const queryClient = new QueryClient({ export const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -15,3 +16,8 @@ export const queryClient = new QueryClient({
}, },
}, },
}); });
broadcastQueryClient({
queryClient,
broadcastChannel: "reflector-query-client",
});

View File

@@ -18,7 +18,8 @@ export interface CustomSession extends Session {
}; };
} }
// assumption that JWT is JWTWithAccessToken - not ideal, TODO find a reason we have to do that // assumption that JWT is JWTWithAccessToken - we set it in jwt callback of auth; typing isn't strong around there
// but the assumption is crucial to auth working
export const assertExtendedToken = <T>( export const assertExtendedToken = <T>(
t: T, t: T,
): T & { ): T & {

View File

@@ -1,13 +0,0 @@
"use client";
import { useAuth } from "./AuthProvider";
// TODO
export default function useAuthReady() {
const auth = useAuth();
return {
isAuthenticated: auth.status === "authenticated",
isLoading: auth.status === "loading",
};
}

View File

@@ -17,7 +17,8 @@
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@sentry/nextjs": "^7.77.0", "@sentry/nextjs": "^7.77.0",
"@tanstack/react-query": "^5.85.5", "@tanstack/query-broadcast-client-experimental": "^5.85.9",
"@tanstack/react-query": "^5.85.9",
"@vercel/edge-config": "^0.4.1", "@vercel/edge-config": "^0.4.1",
"@whereby.com/browser-sdk": "^3.3.4", "@whereby.com/browser-sdk": "^3.3.4",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",

129
www/pnpm-lock.yaml generated
View File

@@ -25,9 +25,12 @@ importers:
"@sentry/nextjs": "@sentry/nextjs":
specifier: ^7.77.0 specifier: ^7.77.0
version: 7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) version: 7.120.4(next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)
"@tanstack/query-broadcast-client-experimental":
specifier: ^5.85.9
version: 5.85.9
"@tanstack/react-query": "@tanstack/react-query":
specifier: ^5.85.5 specifier: ^5.85.9
version: 5.85.5(react@18.3.1) version: 5.85.9(react@18.3.1)
"@vercel/edge-config": "@vercel/edge-config":
specifier: ^0.4.1 specifier: ^0.4.1
version: 0.4.1 version: 0.4.1
@@ -72,7 +75,7 @@ importers:
version: 0.14.0 version: 0.14.0
openapi-react-query: openapi-react-query:
specifier: ^0.5.0 specifier: ^0.5.0
version: 0.5.0(@tanstack/react-query@5.85.5(react@18.3.1))(openapi-fetch@0.14.0) version: 0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0)
postcss: postcss:
specifier: 8.4.31 specifier: 8.4.31
version: 8.4.31 version: 8.4.31
@@ -196,6 +199,13 @@ packages:
engines: { node: ">=6.0.0" } engines: { node: ">=6.0.0" }
hasBin: true hasBin: true
"@babel/runtime@7.27.0":
resolution:
{
integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==,
}
engines: { node: ">=6.9.0" }
"@babel/runtime@7.28.2": "@babel/runtime@7.28.2":
resolution: resolution:
{ {
@@ -1408,16 +1418,22 @@ packages:
integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==, integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==,
} }
"@tanstack/query-core@5.85.5": "@tanstack/query-broadcast-client-experimental@5.85.9":
resolution: resolution:
{ {
integrity: sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==, integrity: sha512-8UOwzQ16BAJwoTo584ZciRcT2sOw5DPsCRwQVDvST3ri8stDCj2CRQ9BxL9BIjkwICMuyNFQDc6Xgh1Coh9uyw==,
} }
"@tanstack/react-query@5.85.5": "@tanstack/query-core@5.85.9":
resolution: resolution:
{ {
integrity: sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==, integrity: sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ==,
}
"@tanstack/react-query@5.85.9":
resolution:
{
integrity: sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ==,
} }
peerDependencies: peerDependencies:
react: ^18 || ^19 react: ^18 || ^19
@@ -2820,6 +2836,12 @@ packages:
} }
engines: { node: ">=8" } engines: { node: ">=8" }
broadcast-channel@7.1.0:
resolution:
{
integrity: sha512-InJljddsYWbEL8LBnopnCg+qMQp9KcowvYWOt4YWrjD5HmxzDYKdVbDS1w/ji5rFZdRD58V5UxJPtBdpEbEJYw==,
}
browserslist@4.25.2: browserslist@4.25.2:
resolution: resolution:
{ {
@@ -3893,6 +3915,12 @@ packages:
} }
engines: { node: ">=10.13.0" } engines: { node: ">=10.13.0" }
eventemitter3@4.0.7:
resolution:
{
integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==,
}
events-intercept@2.0.0: events-intercept@2.0.0:
resolution: resolution:
{ {
@@ -5669,6 +5697,13 @@ packages:
} }
engines: { node: ">= 0.4" } engines: { node: ">= 0.4" }
oblivious-set@1.4.0:
resolution:
{
integrity: sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg==,
}
engines: { node: ">=16" }
oidc-token-hash@5.1.1: oidc-token-hash@5.1.1:
resolution: resolution:
{ {
@@ -5752,6 +5787,13 @@ packages:
} }
engines: { node: ">= 0.4" } engines: { node: ">= 0.4" }
p-finally@1.0.0:
resolution:
{
integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==,
}
engines: { node: ">=4" }
p-finally@2.0.1: p-finally@2.0.1:
resolution: resolution:
{ {
@@ -5773,6 +5815,20 @@ packages:
} }
engines: { node: ">=10" } engines: { node: ">=10" }
p-queue@6.6.2:
resolution:
{
integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==,
}
engines: { node: ">=8" }
p-timeout@3.2.0:
resolution:
{
integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==,
}
engines: { node: ">=8" }
package-json-from-dist@1.0.1: package-json-from-dist@1.0.1:
resolution: resolution:
{ {
@@ -6302,6 +6358,12 @@ packages:
} }
engines: { node: ">= 0.4" } engines: { node: ">= 0.4" }
regenerator-runtime@0.14.1:
resolution:
{
integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==,
}
regexp.prototype.flags@1.5.4: regexp.prototype.flags@1.5.4:
resolution: resolution:
{ {
@@ -7215,6 +7277,12 @@ packages:
} }
engines: { node: ">= 10.0.0" } engines: { node: ">= 10.0.0" }
unload@2.4.1:
resolution:
{
integrity: sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw==,
}
unpipe@1.0.0: unpipe@1.0.0:
resolution: resolution:
{ {
@@ -7668,6 +7736,10 @@ snapshots:
dependencies: dependencies:
"@babel/types": 7.28.2 "@babel/types": 7.28.2
"@babel/runtime@7.27.0":
dependencies:
regenerator-runtime: 0.14.1
"@babel/runtime@7.28.2": {} "@babel/runtime@7.28.2": {}
"@babel/template@7.27.2": "@babel/template@7.27.2":
@@ -8473,11 +8545,16 @@ snapshots:
"@swc/counter": 0.1.3 "@swc/counter": 0.1.3
tslib: 2.8.1 tslib: 2.8.1
"@tanstack/query-core@5.85.5": {} "@tanstack/query-broadcast-client-experimental@5.85.9":
"@tanstack/react-query@5.85.5(react@18.3.1)":
dependencies: dependencies:
"@tanstack/query-core": 5.85.5 "@tanstack/query-core": 5.85.9
broadcast-channel: 7.1.0
"@tanstack/query-core@5.85.9": {}
"@tanstack/react-query@5.85.9(react@18.3.1)":
dependencies:
"@tanstack/query-core": 5.85.9
react: 18.3.1 react: 18.3.1
"@tootallnate/once@2.0.0": {} "@tootallnate/once@2.0.0": {}
@@ -9677,6 +9754,13 @@ snapshots:
dependencies: dependencies:
fill-range: 7.1.1 fill-range: 7.1.1
broadcast-channel@7.1.0:
dependencies:
"@babel/runtime": 7.27.0
oblivious-set: 1.4.0
p-queue: 6.6.2
unload: 2.4.1
browserslist@4.25.2: browserslist@4.25.2:
dependencies: dependencies:
caniuse-lite: 1.0.30001734 caniuse-lite: 1.0.30001734
@@ -10402,6 +10486,8 @@ snapshots:
event-target-shim@6.0.2: {} event-target-shim@6.0.2: {}
eventemitter3@4.0.7: {}
events-intercept@2.0.0: {} events-intercept@2.0.0: {}
events@3.3.0: {} events@3.3.0: {}
@@ -11570,6 +11656,8 @@ snapshots:
define-properties: 1.2.1 define-properties: 1.2.1
es-object-atoms: 1.1.1 es-object-atoms: 1.1.1
oblivious-set@1.4.0: {}
oidc-token-hash@5.1.1: {} oidc-token-hash@5.1.1: {}
once@1.3.3: once@1.3.3:
@@ -11588,9 +11676,9 @@ snapshots:
dependencies: dependencies:
openapi-typescript-helpers: 0.0.15 openapi-typescript-helpers: 0.0.15
openapi-react-query@0.5.0(@tanstack/react-query@5.85.5(react@18.3.1))(openapi-fetch@0.14.0): openapi-react-query@0.5.0(@tanstack/react-query@5.85.9(react@18.3.1))(openapi-fetch@0.14.0):
dependencies: dependencies:
"@tanstack/react-query": 5.85.5(react@18.3.1) "@tanstack/react-query": 5.85.9(react@18.3.1)
openapi-fetch: 0.14.0 openapi-fetch: 0.14.0
openapi-typescript-helpers: 0.0.15 openapi-typescript-helpers: 0.0.15
@@ -11630,6 +11718,8 @@ snapshots:
object-keys: 1.1.1 object-keys: 1.1.1
safe-push-apply: 1.0.0 safe-push-apply: 1.0.0
p-finally@1.0.0: {}
p-finally@2.0.1: {} p-finally@2.0.1: {}
p-limit@3.1.0: p-limit@3.1.0:
@@ -11640,6 +11730,15 @@ snapshots:
dependencies: dependencies:
p-limit: 3.1.0 p-limit: 3.1.0
p-queue@6.6.2:
dependencies:
eventemitter3: 4.0.7
p-timeout: 3.2.0
p-timeout@3.2.0:
dependencies:
p-finally: 1.0.0
package-json-from-dist@1.0.1: {} package-json-from-dist@1.0.1: {}
parent-module@1.0.1: parent-module@1.0.1:
@@ -11940,6 +12039,8 @@ snapshots:
get-proto: 1.0.1 get-proto: 1.0.1
which-builtin-type: 1.2.1 which-builtin-type: 1.2.1
regenerator-runtime@0.14.1: {}
regexp.prototype.flags@1.5.4: regexp.prototype.flags@1.5.4:
dependencies: dependencies:
call-bind: 1.0.8 call-bind: 1.0.8
@@ -12576,6 +12677,8 @@ snapshots:
universalify@2.0.1: {} universalify@2.0.1: {}
unload@2.4.1: {}
unpipe@1.0.0: {} unpipe@1.0.0: {}
unrs-resolver@1.11.1: unrs-resolver@1.11.1: