From 16d581f0ed49be6f3eb2b894ad6c030938e82459 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Sat, 12 Oct 2024 11:19:53 -0500 Subject: [PATCH 1/2] Broadcast cache writes to all tabs/windows This allows fresh data from one tab to be applied to all tabs. Each tab still has its own cache, changes are just synced. So refreshing the page will still initialize empty, so if some data point became stale (now in all tabs), the user can refresh one page, and the fresh data will refresh all tabs. --- src/api/client/createCache.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/api/client/createCache.ts b/src/api/client/createCache.ts index e9d21a039f..c7a231f0a0 100644 --- a/src/api/client/createCache.ts +++ b/src/api/client/createCache.ts @@ -1,4 +1,5 @@ import { InMemoryCache, PossibleTypesMap, TypePolicies } from '@apollo/client'; +import { Cache } from '@apollo/client/cache'; import { possibleTypes } from '../schema/fragmentMatcher'; import { typePolicies } from '../schema/typePolicies'; @@ -26,5 +27,16 @@ export const createCache = () => { } } + if (typeof BroadcastChannel !== 'undefined') { + const writeChannel = new BroadcastChannel('apollo::write'); + const orig = cache.write.bind(cache); + const ourWrite = (options: Cache.WriteOptions, sendToOthers = true) => { + sendToOthers && writeChannel.postMessage(options); + return orig(options); + }; + writeChannel.onmessage = (event) => ourWrite(event.data, false); + cache.write = ourWrite; + } + return cache; }; From ebd038c077e85937a16c8c445e33fc00cc9b26cb Mon Sep 17 00:00:00 2001 From: Carson Full Date: Sat, 12 Oct 2024 15:07:22 -0500 Subject: [PATCH 2/2] Broadcast impersonation changes --- src/api/client/ImpersonationContext.tsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/api/client/ImpersonationContext.tsx b/src/api/client/ImpersonationContext.tsx index 3663ebca20..510b57350a 100644 --- a/src/api/client/ImpersonationContext.tsx +++ b/src/api/client/ImpersonationContext.tsx @@ -28,6 +28,11 @@ export const ImpersonationProvider = ({ children, initial, }: ChildrenProp & { initial?: Impersonation | null }) => { + const [broadcast] = useState(() => + typeof BroadcastChannel !== 'undefined' + ? new BroadcastChannel('impersonation') + : undefined + ); const [impersonation, setImpersonation] = useState(() => { if (initial !== undefined) { return initial ?? undefined; @@ -41,6 +46,7 @@ export const ImpersonationProvider = ({ : undefined; next = next && Object.keys(next).length === 0 ? undefined : next; setImpersonation(next); + broadcast?.postMessage(next); if (!next) { Cookies.remove('impersonation'); return; @@ -50,7 +56,7 @@ export const ImpersonationProvider = ({ sameSite: 'strict', }); }, - [setImpersonation] + [setImpersonation, broadcast] ); const value = useMemo( () => ({ @@ -61,6 +67,21 @@ export const ImpersonationProvider = ({ [impersonation, set] ); + useEffect(() => { + if (!broadcast) { + return; + } + const listener = (event: MessageEvent) => { + setImpersonation(event.data); + }; + broadcast.addEventListener('message', listener); + return () => { + broadcast.removeEventListener('message', listener); + broadcast.close(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // Expose devtools API const latest = useLatest(value); useEffect(() => {