-
-
Notifications
You must be signed in to change notification settings - Fork 454
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
RFC: Cross-Tab Synchronization Exchange #1062
Comments
Here's a nice video about the different ways to synchronize data across documents: https://www.youtube.com/watch?v=9UNwHmagedE Just posting it in case it can help to decide on what API to use. They do speak about the |
A use case to consider is how this would work with the subscription exchange and with subscriptions that tabs may have open:
|
I've been wondering if we could handle this a bit more naive, in the sense that we could do something like combining the Let's say a user has a list of todos on tab 1, and the same window open on tab2. When the user switched to tab2, the refocusExchange will trigger and our cache should trigger the promise to readFromStorage, this means that the queries on-screen will be refetched. This hits the cache which will in-turn buffer these queries since we have a pending readFromStorage, when the cache sees that it's in lock-state it should poll for the lock to be removed, when it's removed it can rehydrate the cache and respond to the in-flight queries. The concern I have here is that currently we don't use our optimistic results in storage, so this could mean that we inherit the pending mutations from the storage which could possibly introduce us dispatching them twice. This should be a case to take in account. |
I think that'd require us to make the assumption that only one tab is "active" at a time, which isn't necessarily the case with multiple windows, background tasks, timers, etc 😅 |
What's the progress on this one? |
Yeah. This is really needed. |
@kitten do you actually need a cross-tab synchronization exchange, considering that "data stored in IndexedDB is available to all tabs from within the same origin" (ref)? Switching to a tab could refresh its state based on a pull from IndexedDB. If a background push to all tabs would be too complex. |
This is how I implemented syncExchange with the help of IndexedDB. It's probably not super efficient, but good enough for my needs: import { Exchange, OperationResult } from 'urql';
import { makeSubject, merge, pipe, tap } from 'wonka';
function pageVisible(callback: (visible: boolean) => void) {
if (typeof window === 'undefined') {
return () => undefined;
}
const focusHandler = () => callback(true);
// Blur handler gives us a few false positives, but we can live with that
const blurHandler = () => callback(false);
const visibilityChangeHandler = () => {
callback(document.visibilityState === 'visible');
};
window.addEventListener('focus', focusHandler, false);
window.addEventListener('blur', blurHandler, false);
window.addEventListener('visibilitychange', visibilityChangeHandler, false);
// Returns unsubscribe
return () => {
window.removeEventListener('focus', focusHandler);
window.removeEventListener('blur', blurHandler);
window.removeEventListener('visibilitychange', visibilityChangeHandler);
};
}
export function syncExchange(): Exchange {
let leader = true;
pageVisible((visible) => (leader = visible));
return ({ forward }) =>
(operations$) => {
if (typeof window === 'undefined') {
return forward(operations$);
}
const { source, next } = makeSubject<OperationResult>();
const channel = new BroadcastChannel('syncExchange');
channel.addEventListener('message', (event) => {
if (leader) {
return;
}
next(event.data as OperationResult);
});
const processOutgoingOperation = (operation: OperationResult) => {
if (!leader) {
return;
}
// Right now we're forwarding everything, but since it's only on already
// handled graphql operations, it shouldn't matter for our use case
channel.postMessage(operation);
};
return pipe(
merge([forward(operations$), source]),
tap(processOutgoingOperation)
);
};
} |
BroadcastChannel has full support in Safari now, as of Mar 15 2022. |
Last comment here was a couple years back — curious what the latest thinking is. 🙏 |
Summary
Especially with Graphcache we've discussed cases where a sufficiently complex app may want to synchronize what's happening across apps. As part of this one may have multiple tabs of a Graphcache application opened.
We can make the assumption that it doesn't matter whether the cache itself is 100% in sync, but we hypothesize that two tabs of Graphcache can already coexist (Needs Testing)
A cross-tab synchronization exchange can therefore focus on distributing results across all apps.
Proposed Solution
Create an exchange that deduplicates fetching results and distributes results to other tabs, maybe using the
BroadcastChannel
API.Related Conversations
The text was updated successfully, but these errors were encountered: