Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions web/src/flow/tabs/broadcast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ export interface BroadcastMessage {
[key: string]: unknown;
}

export class Broadcast extends BroadcastChannel {
export class Broadcast extends BroadcastChannel implements Disposable {
static shared = new Broadcast();

private discoveredTabIds = new Set<string>();
exitedTabIds: string[] = [];
protected discoveredTabIDs = new Set<string>();
public exitedTabIDs: string[] = [];

#logger: Logger;
protected logger: Logger;

#onMessage = (ev: MessageEvent<BroadcastMessage>) => {
this.#logger.debug("broadcast event", ev.data);
protected messageListener = (ev: MessageEvent<BroadcastMessage>) => {
this.logger.debug("broadcast event", ev.data);
switch (ev.data.type) {
case BroadcastMessageType.discover:
if (ev.data.sender === TabID.shared.current) {
Expand All @@ -38,40 +38,50 @@ export class Broadcast extends BroadcastChannel {
});
return;
case BroadcastMessageType.discoverReply:
this.discoveredTabIds.add(ev.data.sender as string);
this.discoveredTabIDs.add(ev.data.sender as string);
return;
case BroadcastMessageType.exit:
this.exitedTabIds.push(ev.data.sender);
this.exitedTabIDs.push(ev.data.sender);
return;
case BroadcastMessageType.continue:
if (ev.data.target === TabID.shared.current) {
this.#logger.debug("Continuing upon event");
this.logger.debug("Continuing upon event");
window.dispatchEvent(new CustomEvent("ak-multitab-continue"));
}
return;
}
};

protected pageHideListener = () => {
this.akExitTab();
};

constructor() {
super(BROADCAST_CHANNEL_NAME);
this.addEventListener("message", this.#onMessage);
this.#logger = ConsoleLogger.prefix("mtab/broadcast");

this.addEventListener("message", this.messageListener);
window.addEventListener("pagehide", this.pageHideListener);

this.logger = ConsoleLogger.prefix("mtab/broadcast");
}

[Symbol.dispose]() {
this.removeEventListener("message", this.#onMessage);
this.removeEventListener("message", this.messageListener);
}

async akTabDiscover(): Promise<Set<string>> {
this.discoveredTabIds.clear();
this.discoveredTabIDs.clear();

this.postMessage({
type: BroadcastMessageType.discover,
sender: TabID.shared.current,
});

await new Promise<void>((r) => {
setTimeout(r, 20);
});
return this.discoveredTabIds;

return this.discoveredTabIDs;
}

akResumeTab(tabId: string) {
Expand Down
38 changes: 28 additions & 10 deletions web/src/flow/tabs/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { ConsoleLogger } from "#logger/browser";
const lockKey = "authentik-tab-locked";
const logger = ConsoleLogger.prefix("mtab/orchestrate");

const TAB_EXIT_TIMEOUT_MS = 3000;

export function multiTabOrchestrateLeave() {
if (!globalAK().brand.flags.flowsContinuousLogin) {
return;
}
Broadcast.shared.akExitTab();
TabID.shared.clear();
}
Expand All @@ -20,35 +19,54 @@ export async function multiTabOrchestrateResume() {
if (!globalAK().brand.flags.flowsContinuousLogin) {
return;
}
const lockTabId = localStorage.getItem(lockKey);

const lockTabID = localStorage.getItem(lockKey);
const tabs = await Broadcast.shared.akTabDiscover();

logger.debug("Got list of tabs", tabs);

if (lockTabId && tabs.has(lockTabId)) {
if (lockTabID && tabs.has(lockTabID)) {
logger.debug("Tabs locked, leaving.");
multiTabOrchestrateLeave();
return;
}

logger.debug("Locking tabs");
localStorage.setItem(lockKey, TabID.shared.current);

for (const tab of tabs) {
logger.debug("Telling tab to continue", tab);
Broadcast.shared.akResumeTab(tab);

const done = Promise.withResolvers<void>();
const checker = setInterval(() => {
if (Broadcast.shared.exitedTabIds.includes(tab)) {

let timeout = -1;

const checker = requestAnimationFrame(() => {
if (Broadcast.shared.exitedTabIDs.includes(tab)) {
logger.debug("tab exited", tab);
setTimeout(() => {
self.clearTimeout(timeout);

self.setTimeout(() => {
logger.debug("continue exited", tab);
done.resolve();
}, 1000);
clearInterval(checker);

cancelAnimationFrame(checker);
}
}, 1);
});

timeout = self.setTimeout(() => {
logger.warn("Timed out waiting for tab to exit, moving on", tab);
cancelAnimationFrame(checker);
done.resolve();
}, TAB_EXIT_TIMEOUT_MS);

await done.promise;

logger.debug("Tab done, continuing", tab);
}

logger.debug("All tabs done.");
localStorage.removeItem(lockKey);
}
Loading