aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js61
1 files changed, 60 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 38314e7..92dcb28 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -20,7 +20,9 @@ import { GroupPage } from './group-page.js';
import { SearchPage, ConnectionPool } from './search-page.js';
import { MusicPlayerBar } from './music-player.js';
import { NameModal } from './playlist-menu.js';
-import { saveQueueAsPlaylist } from './playlists.js';
+import {
+ saveQueueAsPlaylist, setPlaylistTransport, pullOnce, flushPush,
+} from './playlists.js';
import { IndexingDock } from './index-dock.js';
import { SettingsPage } from './settings-page.js';
import { ProfilePage } from './profile-page.js';
@@ -752,6 +754,63 @@ function App() {
const handleStopMusic = useCallback(() => setMusicQueue(null), []);
+ // ── Playlists reach a node through here ────────────────────────────────
+ //
+ // `playlists.js` pushes on every write and needs a transport to push
+ // through; the shell is what owns connections. Preferring the group already
+ // open costs nothing, and dialing when there is none is a change of position
+ // that is worth stating: §7 refused dialing *at sign-in for a feature nobody
+ // had asked to use*. Somebody who has just made a playlist has asked, and one
+ // dial to save their work is cheaper than losing it.
+ useEffect(() => {
+ if (!user) { setPlaylistTransport(null); return undefined; }
+ setPlaylistTransport(async () => {
+ const gt = groupTransportRef.current;
+ const live = gt && gt.transportRef && gt.transportRef.current;
+ if (live && live.connected) return live;
+ // Bounded: an account with twenty groups, all offline, must not spend
+ // twenty ten-second timeouts on a background push.
+ for (const g of groups.slice(0, 3)) {
+ try {
+ const conn = await getMusicConnection(g.id);
+ if (conn && conn.transport && conn.transport.connected) return conn.transport;
+ } catch { /* try the next one */ }
+ }
+ return null;
+ });
+ // A pending push has a second and a half to wait, and closing a laptop
+ // inside that window is not rare. `pagehide` covers a tab closing and a
+ // navigation; `visibilitychange` covers a phone being backgrounded, which
+ // on iOS is the only one of the two that reliably fires at all.
+ const flush = () => { flushPush().catch(() => {}); };
+ const onHidden = () => { if (document.visibilityState === 'hidden') flush(); };
+ window.addEventListener('pagehide', flush);
+ document.addEventListener('visibilitychange', onHidden);
+
+ return () => {
+ window.removeEventListener('pagehide', flush);
+ document.removeEventListener('visibilitychange', onHidden);
+ setPlaylistTransport(null);
+ };
+ }, [user, groups, getMusicConnection]);
+
+ // Reconcile once per sign-in, whatever this browser already holds.
+ //
+ // Nothing went looking until a group's Music tab happened to be opened, which
+ // is not where anybody looks for a playlist — and bounding this to an empty
+ // device, as the first version did, left out the ordinary case: a phone that
+ // has nine playlists and is missing the tenth.
+ //
+ // `groups.length` moves as the sidebar fills, so the ref is what makes "once"
+ // mean once rather than once per group that arrives.
+ const pulledForRef = useRef(null);
+ useEffect(() => {
+ if (!user || !groups.length) return;
+ if (pulledForRef.current === user.userId) return;
+ pulledForRef.current = user.userId;
+ pullOnce(user.userId).catch(() => {});
+ }, [user, groups.length]);
+
// Saving the queue is a shell-level action because the queue is: the player
// bar outlives every page, and the account it belongs to is here.
const [saveQueue, setSaveQueue] = useState(null);