detect if multiple tabs are open

This commit is contained in:
Paul Miller
2023-06-12 16:51:04 -05:00
parent ed5cddc1b4
commit c1b024baf6
3 changed files with 127 additions and 51 deletions

View File

@@ -1,47 +1,78 @@
import { Title } from "solid-start"; import { Title } from "solid-start";
import { DefaultMain, LargeHeader, NiceP, SafeArea } from "~/components/layout"; import { DefaultMain, LargeHeader, NiceP, SafeArea } from "~/components/layout";
import { ExternalLink } from "./layout/ExternalLink"; import { ExternalLink } from "./layout/ExternalLink";
import { Match, Switch } from "solid-js";
export default function SetupErrorDisplay(props: { error: Error }) { export default function SetupErrorDisplay(props: { error: Error }) {
return ( return (
<SafeArea> <SafeArea>
<Title>Incompatible browser</Title> <Switch>
<DefaultMain> <Match when={props.error.message.startsWith("Existing tab")}>
<LargeHeader>Incompatible browser detected</LargeHeader> <Title>Multiple tabs detected</Title>
<p class="bg-white/10 rounded-xl p-4 font-mono"> <DefaultMain>
<span class="font-bold">{props.error.name}</span>:{" "} <LargeHeader>Multiple tabs detected</LargeHeader>
{props.error.message} <p class="bg-white/10 rounded-xl p-4 font-mono">
</p> <span class="font-bold">{props.error.name}</span>:{" "}
<NiceP> {props.error.message}
Mutiny requires a modern browser that supports WebAssembly, </p>
LocalStorage, and IndexedDB. Some browsers disable these <NiceP>
features in private mode. Mutiny currently only supports use in one tab at a
</NiceP> time. It looks like you have another tab open with
<NiceP> Mutiny running. Please close that tab and refresh
Please make sure your browser supports all these features, this page, or close this tab and refresh the other
or consider trying another browser. You might also try one.
disabling certain extensions or "shields" that block these </NiceP>
features. <div class="h-full" />
</NiceP> <p class="self-center text-neutral-500 mt-4">
<NiceP> Bugs? Feedback?{" "}
(We'd love to support more private browsers, but we have to <span class="text-neutral-400">
save your wallet data to browser storage or else you will <ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
lose funds.) Create an issue
</NiceP> </ExternalLink>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Browser-Compatibility"> </span>
Supported Browsers </p>
</ExternalLink> </DefaultMain>
</Match>
<div class="h-full" /> <Match when={true}>
<p class="self-center text-neutral-500 mt-4"> <Title>Incompatible browser</Title>
Bugs? Feedback?{" "} <DefaultMain>
<span class="text-neutral-400"> <LargeHeader>Incompatible browser detected</LargeHeader>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues"> <p class="bg-white/10 rounded-xl p-4 font-mono">
Create an issue <span class="font-bold">{props.error.name}</span>:{" "}
{props.error.message}
</p>
<NiceP>
Mutiny requires a modern browser that supports
WebAssembly, LocalStorage, and IndexedDB. Some
browsers disable these features in private mode.
</NiceP>
<NiceP>
Please make sure your browser supports all these
features, or consider trying another browser. You
might also try disabling certain extensions or
"shields" that block these features.
</NiceP>
<NiceP>
(We'd love to support more private browsers, but we
have to save your wallet data to browser storage or
else you will lose funds.)
</NiceP>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Browser-Compatibility">
Supported Browsers
</ExternalLink> </ExternalLink>
</span>
</p> <div class="h-full" />
</DefaultMain> <p class="self-center text-neutral-500 mt-4">
Bugs? Feedback?{" "}
<span class="text-neutral-400">
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
Create an issue
</ExternalLink>
</span>
</p>
</DefaultMain>
</Match>
</Switch>
</SafeArea> </SafeArea>
); );
} }

View File

@@ -23,6 +23,7 @@ import { ParsedParams } from "~/routes/Scanner";
import { MutinyTagItem } from "~/utils/tags"; import { MutinyTagItem } from "~/utils/tags";
import { checkBrowserCompatibility } from "~/logic/browserCompatibility"; import { checkBrowserCompatibility } from "~/logic/browserCompatibility";
import eify from "~/utils/eify"; import eify from "~/utils/eify";
import { timeout } from "~/utils/timeout";
const MegaStoreContext = createContext<MegaStore>(); const MegaStoreContext = createContext<MegaStore>();
@@ -47,6 +48,7 @@ export type MegaStore = [
activity: MutinyActivity[]; activity: MutinyActivity[];
setup_error?: Error; setup_error?: Error;
is_pwa: boolean; is_pwa: boolean;
existing_tab_detected: boolean;
}, },
{ {
fetchUserStatus(): Promise<UserStatus>; fetchUserStatus(): Promise<UserStatus>;
@@ -85,7 +87,8 @@ export const Provider: ParentComponent = (props) => {
nwc_enabled: localStorage.getItem("nwc_enabled") === "true", nwc_enabled: localStorage.getItem("nwc_enabled") === "true",
activity: [] as MutinyActivity[], activity: [] as MutinyActivity[],
setup_error: undefined as Error | undefined, setup_error: undefined as Error | undefined,
is_pwa: window.matchMedia("(display-mode: standalone)").matches is_pwa: window.matchMedia("(display-mode: standalone)").matches,
existing_tab_detected: false
}); });
const actions = { const actions = {
@@ -236,21 +239,35 @@ export const Provider: ParentComponent = (props) => {
console.log("checking for browser compatibility..."); console.log("checking for browser compatibility...");
actions.checkBrowserCompat().then((browserIsGood) => { actions.checkBrowserCompat().then((browserIsGood) => {
if (browserIsGood) { if (browserIsGood) {
console.log("running setup node manager..."); console.log("checking if any other tabs are open");
actions // 500ms should hopefully be enough time for any tabs to reply
.setupMutinyWallet() timeout(500).then(() => {
.then(() => console.log("node manager setup done")) if (state.existing_tab_detected) {
.catch((e) => { setState({
console.error(e); setup_error: new Error(
setState({ setup_error: eify(e) }); "Existing tab detected, aborting setup"
}); )
});
} else {
console.log("running setup node manager...");
actions
.setupMutinyWallet()
.then(() =>
console.log("node manager setup done")
)
.catch((e) => {
console.error(e);
setState({ setup_error: eify(e) });
});
// Setup an event listener to stop the mutiny wallet when the page unloads // Setup an event listener to stop the mutiny wallet when the page unloads
window.onunload = async (_e) => { window.onunload = async (_e) => {
console.log("stopping mutiny_wallet"); console.log("stopping mutiny_wallet");
await state.mutiny_wallet?.stop(); await state.mutiny_wallet?.stop();
console.log("mutiny_wallet stopped"); console.log("mutiny_wallet stopped");
}; };
}
});
} }
}); });
} }
@@ -274,6 +291,31 @@ export const Provider: ParentComponent = (props) => {
}); });
}); });
onMount(() => {
const channel = new BroadcastChannel("tab-detector");
// First we let everyone know we exist
channel.postMessage({ type: "NEW_TAB" });
channel.onmessage = (e) => {
// If any tabs reply, we know there's an existing tab so abort setup
if (e.data.type === "EXISTING_TAB") {
console.debug("there's an existing tab");
setState({ existing_tab_detected: true });
}
// If we get notified of a new tab, we let it know we exist
if (e.data.type === "NEW_TAB") {
console.debug("a new tab just came online");
channel.postMessage({ type: "EXISTING_TAB" });
}
};
onCleanup(() => {
channel.close();
});
});
const store = [state, actions] as MegaStore; const store = [state, actions] as MegaStore;
return ( return (

3
src/utils/timeout.ts Normal file
View File

@@ -0,0 +1,3 @@
// A promise version of timeout
export const timeout = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));