mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-17 14:24:26 +01:00
Merge pull request #15 from MutinyWallet/connect-modal
Peer connect info modal
This commit is contained in:
8
.env
8
.env
@@ -1,3 +1,11 @@
|
|||||||
|
# LOCALHOST REGTEST
|
||||||
|
|
||||||
VITE_NETWORK="regtest"
|
VITE_NETWORK="regtest"
|
||||||
VITE_PROXY="wss://p.mutinywallet.com"
|
VITE_PROXY="wss://p.mutinywallet.com"
|
||||||
VITE_ESPLORA="http://localhost:3003"
|
VITE_ESPLORA="http://localhost:3003"
|
||||||
|
|
||||||
|
#SIGNET
|
||||||
|
|
||||||
|
# VITE_NETWORK="signet"
|
||||||
|
# VITE_PROXY="wss://p.mutinywallet.com"
|
||||||
|
# VITE_ESPLORA="https://mutinynet.com/api"
|
||||||
11
package.json
11
package.json
@@ -13,7 +13,7 @@
|
|||||||
"@typescript-eslint/parser": "^5.57.1",
|
"@typescript-eslint/parser": "^5.57.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"esbuild": "^0.14.54",
|
"esbuild": "^0.14.54",
|
||||||
"eslint": "^8.37.0",
|
"eslint": "^8.38.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"solid-start-node": "^0.2.26",
|
"solid-start-node": "^0.2.26",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
@@ -24,18 +24,19 @@
|
|||||||
"workbox-window": "^6.5.4"
|
"workbox-window": "^6.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@kobalte/core": "^0.8.2",
|
||||||
"@motionone/solid": "^10.16.0",
|
"@motionone/solid": "^10.16.0",
|
||||||
"@mutinywallet/node-manager": "^0.2.2",
|
"@mutinywallet/node-manager": "^0.2.3",
|
||||||
"@nostr-dev-kit/ndk": "^0.0.13",
|
"@nostr-dev-kit/ndk": "^0.0.13",
|
||||||
"@solidjs/meta": "^0.28.4",
|
"@solidjs/meta": "^0.28.4",
|
||||||
"@solidjs/router": "^0.8.2",
|
"@solidjs/router": "^0.8.2",
|
||||||
"class-variance-authority": "^0.4.0",
|
"class-variance-authority": "^0.4.0",
|
||||||
"nostr-tools": "^1.8.2",
|
"nostr-tools": "^1.8.3",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"solid-js": "^1.7.2",
|
"solid-js": "^1.7.3",
|
||||||
"solid-qr-code": "^0.0.8",
|
"solid-qr-code": "^0.0.8",
|
||||||
"solid-start": "^0.2.26",
|
"solid-start": "^0.2.26",
|
||||||
"undici": "^5.21.0"
|
"undici": "^5.21.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.8"
|
"node": ">=16.8"
|
||||||
|
|||||||
599
pnpm-lock.yaml
generated
599
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,11 @@
|
|||||||
import { For } from "solid-js";
|
|
||||||
import { Motion, Presence } from "@motionone/solid";
|
|
||||||
|
|
||||||
import logo from '~/assets/icons/mutiny-logo.svg';
|
import logo from '~/assets/icons/mutiny-logo.svg';
|
||||||
import send from '~/assets/icons/send.svg';
|
import BalanceBox from "~/components/BalanceBox";
|
||||||
import BalanceBox from "./BalanceBox";
|
import SafeArea from "~/components/SafeArea";
|
||||||
import SafeArea from "./SafeArea";
|
import NavBar from "~/components/NavBar";
|
||||||
import NavBar from "./NavBar";
|
|
||||||
|
|
||||||
// TODO: use this reload prompt for real
|
// TODO: use this reload prompt for real
|
||||||
// import ReloadPrompt from "./Reload";
|
import ReloadPrompt from "~/components/Reload";
|
||||||
|
import KitchenSink from './KitchenSink';
|
||||||
function ActivityItem() {
|
|
||||||
return (
|
|
||||||
<div class="flex flex-row border-b border-gray-500 gap-4 py-2">
|
|
||||||
<img src={send} class="App-logo" alt="logo" />
|
|
||||||
<div class='flex flex-col flex-1'>
|
|
||||||
<h1>Bitcoin Beefsteak</h1>
|
|
||||||
<h2>-1,441,851 SAT</h2>
|
|
||||||
<h3 class='text-sm text-gray-500'>Jul 24</h3>
|
|
||||||
</div>
|
|
||||||
<div class='text-sm font-semibold uppercase text-[#E23A5E]'>SEND</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
@@ -31,32 +14,9 @@ export default function App() {
|
|||||||
<header>
|
<header>
|
||||||
<img src={logo} class="App-logo" alt="logo" />
|
<img src={logo} class="App-logo" alt="logo" />
|
||||||
</header>
|
</header>
|
||||||
{/* <ReloadPrompt /> */}
|
|
||||||
<BalanceBox />
|
<BalanceBox />
|
||||||
<div class='rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]'>
|
<ReloadPrompt />
|
||||||
<header class='text-sm font-semibold uppercase'>
|
<KitchenSink />
|
||||||
Activity
|
|
||||||
</header>
|
|
||||||
<For each={[1, 2, 3, 4]}>
|
|
||||||
{() =>
|
|
||||||
<Presence>
|
|
||||||
<Motion
|
|
||||||
initial={{ opacity: 0, scaleY: 0 }}
|
|
||||||
animate={{ opacity: 1, scaleY: 1 }}
|
|
||||||
exit={{ opacity: 0, scaleY: 0 }}
|
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
>
|
|
||||||
<ActivityItem />
|
|
||||||
</Motion>
|
|
||||||
</Presence>
|
|
||||||
}
|
|
||||||
</For>
|
|
||||||
<div class='flex justify-end py-4'>
|
|
||||||
<a href="#" class='underline text-sm'>
|
|
||||||
MORE
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* safety div */}
|
{/* safety div */}
|
||||||
<div class="h-32" />
|
<div class="h-32" />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Motion, Presence } from "@motionone/solid";
|
|||||||
import { MutinyBalance } from "@mutinywallet/node-manager";
|
import { MutinyBalance } from "@mutinywallet/node-manager";
|
||||||
import { createResource, Show, Suspense } from "solid-js";
|
import { createResource, Show, Suspense } from "solid-js";
|
||||||
|
|
||||||
import { ButtonLink } from "./Button";
|
import { ButtonLink } from "~/components/Button";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
|
||||||
function prettyPrintAmount(n?: number | bigint): string {
|
function prettyPrintAmount(n?: number | bigint): string {
|
||||||
@@ -13,7 +13,7 @@ function prettyPrintAmount(n?: number | bigint): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prettyPrintBalance(b: MutinyBalance): string {
|
function prettyPrintBalance(b: MutinyBalance): string {
|
||||||
return prettyPrintAmount(b.confirmed.valueOf() + b.lightning.valueOf())
|
return prettyPrintAmount(b.confirmed.valueOf() + b.lightning.valueOf() + b.unconfirmed.valueOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BalanceBox() {
|
export default function BalanceBox() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cva, VariantProps } from "class-variance-authority";
|
import { cva, VariantProps } from "class-variance-authority";
|
||||||
import { children, JSX, ParentComponent, splitProps } from "solid-js";
|
import { children, JSX, ParentComponent, splitProps } from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
import { A } from "solid-start";
|
import { A } from "solid-start";
|
||||||
|
|
||||||
const button = cva(["p-4", "rounded-xl", "text-xl", "font-semibold"], {
|
const button = cva(["p-4", "rounded-xl", "text-xl", "font-semibold"], {
|
||||||
@@ -23,7 +24,6 @@ const button = cva(["p-4", "rounded-xl", "text-xl", "font-semibold"], {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Help from https://github.com/arpadgabor/credee/blob/main/packages/www/src/components/ui/button.tsx
|
// Help from https://github.com/arpadgabor/credee/blob/main/packages/www/src/components/ui/button.tsx
|
||||||
|
|
||||||
type StyleProps = VariantProps<typeof button>
|
type StyleProps = VariantProps<typeof button>
|
||||||
@@ -49,15 +49,20 @@ export const Button: ParentComponent<ButtonProps> = props => {
|
|||||||
|
|
||||||
interface ButtonLinkProps extends JSX.ButtonHTMLAttributes<HTMLAnchorElement>, StyleProps {
|
interface ButtonLinkProps extends JSX.ButtonHTMLAttributes<HTMLAnchorElement>, StyleProps {
|
||||||
href: string
|
href: string
|
||||||
|
target?: string
|
||||||
|
rel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ButtonLink: ParentComponent<ButtonLinkProps> = props => {
|
export const ButtonLink: ParentComponent<ButtonLinkProps> = props => {
|
||||||
const slot = children(() => props.children)
|
const slot = children(() => props.children)
|
||||||
const [local, attrs] = splitProps(props, ['children', 'intent', 'layout', 'class', 'href'])
|
const [local, attrs] = splitProps(props, ['children', 'intent', 'layout', 'class', 'href', 'target', 'rel'])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<A
|
<Dynamic
|
||||||
|
component={local.href?.includes('://') ? 'a' : A}
|
||||||
href={local.href}
|
href={local.href}
|
||||||
|
target={local.target}
|
||||||
|
rel={local.rel}
|
||||||
{...attrs}
|
{...attrs}
|
||||||
class={button({
|
class={button({
|
||||||
class: `flex justify-center no-underline ${local.class || ""}`,
|
class: `flex justify-center no-underline ${local.class || ""}`,
|
||||||
@@ -66,6 +71,6 @@ export const ButtonLink: ParentComponent<ButtonLinkProps> = props => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{slot()}
|
{slot()}
|
||||||
</A>
|
</Dynamic>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
13
src/components/Card.tsx
Normal file
13
src/components/Card.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ParentComponent } from "solid-js"
|
||||||
|
|
||||||
|
const Card: ParentComponent<{ title?: string }> = (props) => {
|
||||||
|
return (
|
||||||
|
<div class='rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]'>
|
||||||
|
{props.title && <header class='text-sm font-semibold uppercase'>{props.title}</header>}
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
.increment {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
padding: 1em 2em;
|
|
||||||
color: #335d92;
|
|
||||||
background-color: rgba(68, 107, 158, 0.1);
|
|
||||||
border-radius: 2em;
|
|
||||||
border: 2px solid rgba(68, 107, 158, 0);
|
|
||||||
outline: none;
|
|
||||||
width: 200px;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.increment:focus {
|
|
||||||
border: 2px solid #335d92;
|
|
||||||
}
|
|
||||||
|
|
||||||
.increment:active {
|
|
||||||
background-color: rgba(68, 107, 158, 0.2);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { createSignal } from "solid-js";
|
|
||||||
import { Button } from "./Button";
|
|
||||||
import "./Counter.css";
|
|
||||||
|
|
||||||
export default function Counter() {
|
|
||||||
const [count, setCount] = createSignal(0);
|
|
||||||
return (
|
|
||||||
<Button onClick={() => setCount(count() + 1)}>
|
|
||||||
Clicks: {count()}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
29
src/components/KitchenSink.tsx
Normal file
29
src/components/KitchenSink.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import { ButtonLink } from "./Button";
|
||||||
|
import Card from "./Card";
|
||||||
|
import PeerConnectModal from "./PeerConnectModal";
|
||||||
|
import { createResource } from "solid-js";
|
||||||
|
|
||||||
|
export default function KitchenSink() {
|
||||||
|
const [state, _] = useMegaStore()
|
||||||
|
|
||||||
|
// TODO: would be nice if this was just newest unused address
|
||||||
|
const getNewAddress = async () => {
|
||||||
|
if (state.node_manager) {
|
||||||
|
console.log("Getting new address");
|
||||||
|
const address = await state.node_manager?.get_new_address();
|
||||||
|
return address
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [address] = createResource(getNewAddress);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="Kitchen Sink">
|
||||||
|
<PeerConnectModal />
|
||||||
|
<ButtonLink target="_blank" rel="noopener noreferrer" href={`https://faucet.mutinynet.com/?address=${address()}`}>Tap the Faucet</ButtonLink>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
65
src/components/PeerConnectModal.tsx
Normal file
65
src/components/PeerConnectModal.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { QRCodeSVG } from "solid-qr-code";
|
||||||
|
import Card from "~/components/Card";
|
||||||
|
import { As, Dialog } from "@kobalte/core";
|
||||||
|
import { Button } from "~/components/Button";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import { Show, createResource } from "solid-js";
|
||||||
|
import { getExistingSettings } from "~/logic/nodeManagerSetup";
|
||||||
|
import getHostname from "~/utils/getHostname";
|
||||||
|
|
||||||
|
const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
||||||
|
const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center"
|
||||||
|
const DIALOG_CONTENT = "w-[80vw] max-w-[400px] p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
|
||||||
|
const SMALL_HEADER = "text-sm font-semibold uppercase"
|
||||||
|
|
||||||
|
export default function PeerConnectModalKobalte() {
|
||||||
|
const [state, _] = useMegaStore()
|
||||||
|
|
||||||
|
const getPeerConnectString = async () => {
|
||||||
|
if (state.node_manager) {
|
||||||
|
const { proxy } = getExistingSettings();
|
||||||
|
const nodes = await state.node_manager.list_nodes();
|
||||||
|
const firstNode = nodes[0] as string || ""
|
||||||
|
const hostName = getHostname(proxy || "")
|
||||||
|
const connectString = `mutiny:${firstNode}@${hostName}`
|
||||||
|
return connectString
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [peerConnectString] = createResource(getPeerConnectString);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: a lot of this markup is probably reusable as a "Modal" component
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger asChild>
|
||||||
|
<As component={Button}>Show Peer Connect Info</As>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay class={OVERLAY} />
|
||||||
|
<div class={DIALOG_POSITIONER}>
|
||||||
|
<Dialog.Content class={DIALOG_CONTENT}>
|
||||||
|
<div class="flex justify-between mb-2">
|
||||||
|
<Dialog.Title class={SMALL_HEADER}>Peer connect info</Dialog.Title>
|
||||||
|
<Dialog.CloseButton class="dialog__close-button">
|
||||||
|
<code>X</code>
|
||||||
|
</Dialog.CloseButton>
|
||||||
|
</div>
|
||||||
|
<Dialog.Description class="flex flex-col gap-4">
|
||||||
|
<Show when={peerConnectString()}>
|
||||||
|
<div class="w-full bg-white rounded-xl">
|
||||||
|
<QRCodeSVG value={peerConnectString() || ""} class="w-full h-full p-8 max-h-[400px]" />
|
||||||
|
</div>
|
||||||
|
<Card>
|
||||||
|
<code class="break-all">{peerConnectString() || ""}</code>
|
||||||
|
</Card>
|
||||||
|
</Show>
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Content>
|
||||||
|
</div>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root >
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { Component } from 'solid-js'
|
import type { Component } from 'solid-js'
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
|
// pwa-register doesn't have types apparently
|
||||||
|
// @ts-ignore
|
||||||
import { useRegisterSW } from 'virtual:pwa-register/solid'
|
import { useRegisterSW } from 'virtual:pwa-register/solid'
|
||||||
|
import Card from './Card'
|
||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
const ReloadPrompt: Component = () => {
|
const ReloadPrompt: Component = () => {
|
||||||
const {
|
const {
|
||||||
@@ -21,29 +25,24 @@ const ReloadPrompt: Component = () => {
|
|||||||
setNeedRefresh(false)
|
setNeedRefresh(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: for now we're just going to have it be invisible
|
return (
|
||||||
return (<></>)
|
<Show when={offlineReady() || needRefresh()}>
|
||||||
|
<Card title="PWA settings">
|
||||||
// return (
|
<div>
|
||||||
// <div>
|
<Show
|
||||||
// <Show when={offlineReady() || needRefresh()}>
|
fallback={<span>New content available, click on reload button to update.</span>}
|
||||||
// <div>
|
when={offlineReady()}
|
||||||
// <div>
|
>
|
||||||
// <Show
|
<span>App ready to work offline</span>
|
||||||
// fallback={<span>New content available, click on reload button to update.</span>}
|
</Show>
|
||||||
// when={offlineReady()}
|
</div>
|
||||||
// >
|
<Show when={needRefresh()}>
|
||||||
// <span>App ready to work offline</span>
|
<Button onClick={() => updateServiceWorker(true)}>Reload</Button>
|
||||||
// </Show>
|
</Show>
|
||||||
// </div>
|
<Button onClick={() => close()}>Close</Button>
|
||||||
// <Show when={needRefresh()}>
|
</Card>
|
||||||
// <button onClick={() => updateServiceWorker(true)}>Reload</button>
|
</Show>
|
||||||
// </Show>
|
)
|
||||||
// <button onClick={() => close()}>Close</button>
|
|
||||||
// </div>
|
|
||||||
// </Show>
|
|
||||||
// </div>
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReloadPrompt
|
export default ReloadPrompt
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, For } from "solid-js";
|
import { Component, For } from "solid-js";
|
||||||
|
|
||||||
import { Event, nip19 } from "nostr-tools"
|
import { Event, nip19 } from "nostr-tools"
|
||||||
import Linkify from "../Linkify";
|
import Linkify from "~/components/Linkify";
|
||||||
|
|
||||||
type NostrEvent = {
|
type NostrEvent = {
|
||||||
"content": string, "created_at": number, id?: string
|
"content": string, "created_at": number, id?: string
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from "solid-start";
|
} from "solid-start";
|
||||||
import "./root.css";
|
import "./root.css";
|
||||||
import { Provider as MegaStoreProvider } from "./state/megaStore";
|
import { Provider as MegaStoreProvider } from "~/state/megaStore";
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,17 +3,23 @@ import App from "~/components/App";
|
|||||||
import { Switch, Match } from "solid-js";
|
import { Switch, Match } from "solid-js";
|
||||||
import { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn";
|
import { WaitlistAlreadyIn } from "~/components/waitlist/WaitlistAlreadyIn";
|
||||||
import WaitlistForm from "~/components/waitlist/WaitlistForm";
|
import WaitlistForm from "~/components/waitlist/WaitlistForm";
|
||||||
import ReloadPrompt from "~/components/Reload";
|
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||||
|
|
||||||
|
function FullscreenLoader() {
|
||||||
|
return (
|
||||||
|
<div class="w-screen h-screen flex justify-center items-center">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [state, _] = useMegaStore();
|
const [state, _] = useMegaStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ReloadPrompt />
|
<Switch fallback={<FullscreenLoader />} >
|
||||||
|
|
||||||
<Switch fallback={<>Loading...</>} >
|
|
||||||
{/* TODO: might need this state.node_manager guard on all wallet routes */}
|
{/* TODO: might need this state.node_manager guard on all wallet routes */}
|
||||||
<Match when={state.user_status === "approved" && state.node_manager}>
|
<Match when={state.user_status === "approved" && state.node_manager}>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
13
src/utils/getHostname.ts
Normal file
13
src/utils/getHostname.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export default function getHostname(url: string): string {
|
||||||
|
// Check if the URL begins with "ws://" or "wss://"
|
||||||
|
if (url.startsWith("ws://")) {
|
||||||
|
// If it does, remove "ws://" from the URL
|
||||||
|
url = url.slice(5);
|
||||||
|
} else if (url.startsWith("wss://")) {
|
||||||
|
// If it begins with "wss://", remove "wss://" from the URL
|
||||||
|
url = url.slice(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the resulting URL
|
||||||
|
return url;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user