cross_tab

Coordinate the browser tabs of a Flutter Web app.

final tabs = CrossTab.open('my-app');

tabs.presence.listen((p) {
  if (p.isLeader) {
    startPolling();
  } else {
    stopPolling();
  }
});

tabs.messages.listen((m) => print('${m.from} said ${m.data}'));
tabs.send({'signedOut': true});

The problem

Open your app in two tabs and things start going wrong quietly. Both poll the server. Both hold a socket. Both raise the same notification. The user signs out in one and the other carries on as though nothing happened.

The browser gives you BroadcastChannel and stops there. What is missing is everything around it.

what you need what the platform gives you
messages with a sender untyped structured clones, no identity
how many tabs are open nothing — you maintain it by heartbeat
exactly one tab doing the work nothing at all

A leader

This is the part worth the package. Exactly one tab should poll, hold the socket, or raise the notification — and when that tab closes, another must pick it up without being told.

tabs.presence.listen((p) {
  if (p.isLeader) {
    socket.connect();
  } else {
    socket.disconnect();
  }
});

The oldest tab leads, with the id as a tiebreak. That second part matters more than it looks: two tabs opened in the same microsecond would otherwise each conclude the other should lead, or both lead. Comparing ids makes the ordering total, so every tab reaches the same answer without further negotiation.

A tab that closes says goodbye, so the others react at once rather than waiting out a timeout. A tab that crashes says nothing, so it is dropped after three missed heartbeats.

Presence

final p = tabs.current;
p.count;     // how many tabs are open
p.tabs;      // their ids, including this one
p.leader;    // whose turn it is, or null while an election settles
p.isLeader;  // whether that is you

presence emits the current state on subscription, so a listener knows where it stands immediately rather than after the next change.

Messages

tabs.send({'cart': items.length});
tabs.messages.listen((m) => print('from ${m.from}: ${m.data}'));

A tab never receives its own messages — the sender already knows what it sent.

Off the web

The package compiles everywhere. On mobile and desktop there is exactly one instance of your app, so it reports one tab and is always the leader.

That is the correct answer rather than a degraded one, and it is deliberate: it means the code above runs unchanged on every platform, with no if (kIsWeb) wrapped around your leader logic.

Verified

Two BroadcastChannel objects with the same name see each other even within one document, so the multi-tab behaviour is exercised for real rather than mocked. flutter test --platform chrome covers four tabs agreeing on a single leader, the oldest tab winning, leadership passing when the leader closes, tabs discovering and losing each other, and a tab not hearing its own messages. The single-instance path is tested on the VM.

License

MIT © K M Shahriar Hossain

Libraries

cross_tab
Coordinate the browser tabs of a Flutter Web app.