Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106// JSON-RPC-style wrapper for BroadcastChannelclass RpcChannel { #name; #channel; #handlers = new Map(); #requestTimeoutMs;
constructor(channel, { requestTimeoutMs }) { this.#name = channel.name; this.#requestTimeoutMs = requestTimeoutMs; this.#channel = channel; channel.addEventListener("message", (event) => this.#handleRequest(event)); }
onRequest(method, handler) { this.#handlers.set(method, handler); }
// Resolves with the first result another tab returns, or null if none does. request(method, params = null) { const channel = this.#channel; if (!channel) return Promise.resolve(null); // closed const id = crypto.randomUUID(); return new Promise((resolve) => { const onMessage = (event) => { if (event.data?.id !== id) return; cleanup(); resolve(event.data.result); }; const cleanup = () => { clearTimeout(timer); channel.removeEventListener("message", onMessage); }; const timer = setTimeout(() => { cleanup(); resolve(null); }, this.#requestTimeoutMs); channel.addEventListener("message", onMessage); channel.postMessage({ method, params, id }); }); }
close() { this.#channel?.close(); this.#channel = null; this.#handlers.clear(); }
async #handleRequest(event) { const { method, params = null, id = null } = event.data ?? {}; const handler = this.#handlers.get(method); if (!handler) return; let result = null; try { result = await handler(params); } catch (error) { console.error(`[rpc:${this.#name}] ${method} handler failed`, error); return; } // Every tab receives every request, so null means "no response" if (id === null || result === null) return; this.#channel?.postMessage({ id, result }); }}
const CHANNEL_NAME = "active-tab-monitor";const REPLY_TIMEOUT_MS = 100;
function isThisTabFocused() { return document.visibilityState === "visible" && document.hasFocus();}
// Queries for active tabs over a broadcast channel. Every tab starts one and// answers the others' queries, so it runs whether or not this tab reads it.export function startActiveTabMonitor({ replyTimeoutMs = REPLY_TIMEOUT_MS,} = {}) { if (typeof BroadcastChannel === "undefined") { console.warn("[activeTabMonitor] BroadcastChannel unavailable"); return { async isAnyTabActive() { return isThisTabFocused(); }, stop() {}, }; }
const rpc = new RpcChannel(new BroadcastChannel(CHANNEL_NAME), { requestTimeoutMs: replyTimeoutMs, });
rpc.onRequest("isTabActive", () => isThisTabFocused() || null);
return { async isAnyTabActive() { if (isThisTabFocused()) return true; const res = await rpc.request("isTabActive"); return res === true; },
stop() { rpc.close(); }, };}