diff --git a/src/components/SetupErrorDisplay.tsx b/src/components/SetupErrorDisplay.tsx index 82a1b08..619bed3 100644 --- a/src/components/SetupErrorDisplay.tsx +++ b/src/components/SetupErrorDisplay.tsx @@ -1,47 +1,78 @@ import { Title } from "solid-start"; import { DefaultMain, LargeHeader, NiceP, SafeArea } from "~/components/layout"; import { ExternalLink } from "./layout/ExternalLink"; +import { Match, Switch } from "solid-js"; export default function SetupErrorDisplay(props: { error: Error }) { return ( - Incompatible browser - - Incompatible browser detected -

- {props.error.name}:{" "} - {props.error.message} -

- - Mutiny requires a modern browser that supports WebAssembly, - LocalStorage, and IndexedDB. Some browsers disable these - features in private mode. - - - 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. - - - (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.) - - - Supported Browsers - - -
-

- Bugs? Feedback?{" "} - - - Create an issue + + + Multiple tabs detected + + Multiple tabs detected +

+ {props.error.name}:{" "} + {props.error.message} +

+ + 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. + +
+

+ Bugs? Feedback?{" "} + + + Create an issue + + +

+ + + + Incompatible browser + + Incompatible browser detected +

+ {props.error.name}:{" "} + {props.error.message} +

+ + Mutiny requires a modern browser that supports + WebAssembly, LocalStorage, and IndexedDB. Some + browsers disable these features in private mode. + + + 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. + + + (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.) + + + Supported Browsers - -

-
+ +
+

+ Bugs? Feedback?{" "} + + + Create an issue + + +

+ + + ); } diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index d3d67fa..6643152 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -23,6 +23,7 @@ import { ParsedParams } from "~/routes/Scanner"; import { MutinyTagItem } from "~/utils/tags"; import { checkBrowserCompatibility } from "~/logic/browserCompatibility"; import eify from "~/utils/eify"; +import { timeout } from "~/utils/timeout"; const MegaStoreContext = createContext(); @@ -47,6 +48,7 @@ export type MegaStore = [ activity: MutinyActivity[]; setup_error?: Error; is_pwa: boolean; + existing_tab_detected: boolean; }, { fetchUserStatus(): Promise; @@ -85,7 +87,8 @@ export const Provider: ParentComponent = (props) => { nwc_enabled: localStorage.getItem("nwc_enabled") === "true", activity: [] as MutinyActivity[], 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 = { @@ -236,21 +239,35 @@ export const Provider: ParentComponent = (props) => { console.log("checking for browser compatibility..."); actions.checkBrowserCompat().then((browserIsGood) => { if (browserIsGood) { - 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) }); - }); + 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..."); + 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 - window.onunload = async (_e) => { - console.log("stopping mutiny_wallet"); - await state.mutiny_wallet?.stop(); - console.log("mutiny_wallet stopped"); - }; + // Setup an event listener to stop the mutiny wallet when the page unloads + window.onunload = async (_e) => { + console.log("stopping mutiny_wallet"); + await state.mutiny_wallet?.stop(); + 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; return ( diff --git a/src/utils/timeout.ts b/src/utils/timeout.ts new file mode 100644 index 0000000..88afa8b --- /dev/null +++ b/src/utils/timeout.ts @@ -0,0 +1,3 @@ +// A promise version of timeout +export const timeout = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms));