Skip to content

Commit dba23fa

Browse files
committed
check tab count
1 parent d299d85 commit dba23fa

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

frontend/src/utils/AuthService.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { APIError } from "./api";
22
import { urlForName } from "./router";
33
import appState, { AppStateService } from "./state";
4+
import TabSyncService from "./TabSyncService";
45

56
import type { APIUser } from "@/index";
67
import type { Auth } from "@/types/auth";
@@ -64,7 +65,10 @@ export default class AuthService {
6465
static logOutEvent: keyof AuthEventMap = "btrix-log-out";
6566
static needLoginEvent: keyof AuthEventMap = "btrix-need-login";
6667

67-
static broadcastChannel = new BroadcastChannel(AuthService.storageKey);
68+
static tabSync = new TabSyncService(AuthService.storageKey);
69+
static get broadcastChannel() {
70+
return AuthService.tabSync.channel;
71+
}
6872
static storage = {
6973
getItem() {
7074
return window.sessionStorage.getItem(AuthService.storageKey);
@@ -245,12 +249,17 @@ export default class AuthService {
245249
};
246250
AuthService.broadcastChannel.addEventListener("message", cb);
247251
});
252+
248253
// Ensure that `getSharedSessionAuth` is resolved within a reasonable
249254
// timeframe, even if another window/tab doesn't respond:
250255
const timeoutPromise = new Promise<null>((resolve) => {
256+
if (!this.tabSync.tabCount || this.tabSync.tabCount === 1) {
257+
resolve(null);
258+
return;
259+
}
251260
window.setTimeout(() => {
252261
resolve(null);
253-
}, 10);
262+
}, 500);
254263
});
255264

256265
return Promise.race([broadcastPromise, timeoutPromise]).then(
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { nanoid } from "nanoid";
2+
3+
type TabSyncConfig = { tabIds: string[] };
4+
5+
/**
6+
* Service for syncing data across tabs using `BroadcastChannel`
7+
*/
8+
export default class TabSyncService {
9+
static storageKey = "btrix.tabSync";
10+
11+
public tabId = nanoid();
12+
public channel: BroadcastChannel;
13+
public get tabCount() {
14+
return this.getStoredSyncConfig()?.tabIds.length;
15+
}
16+
17+
constructor(channelName: string) {
18+
// Open channel
19+
this.channel = new BroadcastChannel(channelName);
20+
21+
// Update number of open tabs
22+
const syncConfig = this.getStoredSyncConfig() || { tabIds: [] };
23+
24+
syncConfig.tabIds.push(this.tabId);
25+
26+
window.localStorage.setItem(
27+
TabSyncService.storageKey,
28+
JSON.stringify({
29+
...syncConfig,
30+
// Somewhat arbitrary, but only store latest 20 tabs to keep list managable
31+
tabIds: syncConfig.tabIds.slice(-20),
32+
}),
33+
);
34+
35+
// Remove tab ID on page unload
36+
window.addEventListener("unload", () => {
37+
const syncConfig = this.getStoredSyncConfig();
38+
39+
if (syncConfig) {
40+
const tabIds = syncConfig.tabIds.filter((id) => id === this.tabId);
41+
42+
window.localStorage.setItem(
43+
TabSyncService.storageKey,
44+
JSON.stringify({
45+
...syncConfig,
46+
tabIds,
47+
}),
48+
);
49+
}
50+
});
51+
}
52+
53+
private getStoredSyncConfig(): TabSyncConfig | null {
54+
const storedSyncConfig = window.localStorage.getItem(
55+
TabSyncService.storageKey,
56+
);
57+
58+
if (storedSyncConfig) {
59+
return JSON.parse(storedSyncConfig) as TabSyncConfig;
60+
}
61+
62+
return null;
63+
}
64+
}

0 commit comments

Comments
 (0)