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,10 +1,39 @@
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>
<Switch>
<Match when={props.error.message.startsWith("Existing tab")}>
<Title>Multiple tabs detected</Title>
<DefaultMain>
<LargeHeader>Multiple tabs detected</LargeHeader>
<p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{props.error.name}</span>:{" "}
{props.error.message}
</p>
<NiceP>
Mutiny currently only supports use in one tab at a
time. It looks like you have another tab open with
Mutiny running. Please close that tab and refresh
this page, or close this tab and refresh the other
one.
</NiceP>
<div class="h-full" />
<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>
<Match when={true}>
<Title>Incompatible browser</Title> <Title>Incompatible browser</Title>
<DefaultMain> <DefaultMain>
<LargeHeader>Incompatible browser detected</LargeHeader> <LargeHeader>Incompatible browser detected</LargeHeader>
@@ -13,20 +42,20 @@ export default function SetupErrorDisplay(props: { error: Error }) {
{props.error.message} {props.error.message}
</p> </p>
<NiceP> <NiceP>
Mutiny requires a modern browser that supports WebAssembly, Mutiny requires a modern browser that supports
LocalStorage, and IndexedDB. Some browsers disable these WebAssembly, LocalStorage, and IndexedDB. Some
features in private mode. browsers disable these features in private mode.
</NiceP> </NiceP>
<NiceP> <NiceP>
Please make sure your browser supports all these features, Please make sure your browser supports all these
or consider trying another browser. You might also try features, or consider trying another browser. You
disabling certain extensions or "shields" that block these might also try disabling certain extensions or
features. "shields" that block these features.
</NiceP> </NiceP>
<NiceP> <NiceP>
(We'd love to support more private browsers, but we have to (We'd love to support more private browsers, but we
save your wallet data to browser storage or else you will have to save your wallet data to browser storage or
lose funds.) else you will lose funds.)
</NiceP> </NiceP>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Browser-Compatibility"> <ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Browser-Compatibility">
Supported Browsers Supported Browsers
@@ -42,6 +71,8 @@ export default function SetupErrorDisplay(props: { error: Error }) {
</span> </span>
</p> </p>
</DefaultMain> </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,10 +239,22 @@ 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("checking if any other tabs are open");
// 500ms should hopefully be enough time for any tabs to reply
timeout(500).then(() => {
if (state.existing_tab_detected) {
setState({
setup_error: new Error(
"Existing tab detected, aborting setup"
)
});
} else {
console.log("running setup node manager..."); console.log("running setup node manager...");
actions actions
.setupMutinyWallet() .setupMutinyWallet()
.then(() => console.log("node manager setup done")) .then(() =>
console.log("node manager setup done")
)
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
setState({ setup_error: eify(e) }); setState({ setup_error: eify(e) });
@@ -255,6 +270,8 @@ export const Provider: ParentComponent = (props) => {
}); });
} }
}); });
}
});
}); });
// Be reactive to changes in waitlist_id // Be reactive to changes in waitlist_id
@@ -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));