diff --git a/packages/app/src/Components/Event/Note/NoteFooter.tsx b/packages/app/src/Components/Event/Note/NoteFooter.tsx index 5f2d8b31..7af4b749 100644 --- a/packages/app/src/Components/Event/Note/NoteFooter.tsx +++ b/packages/app/src/Components/Event/Note/NoteFooter.tsx @@ -1,4 +1,4 @@ -import { normalizeReaction } from "@snort/shared"; +import { barrierQueue, normalizeReaction, processWorkQueue, WorkQueueItem } from "@snort/shared"; import { countLeadingZeros, NostrLink, TaggedNostrEvent } from "@snort/system"; import { useEventReactions, useReactions, useUserProfile } from "@snort/system-react"; import { Menu, MenuItem } from "@szhsin/react-menu"; @@ -15,7 +15,7 @@ import useEventPublisher from "@/Hooks/useEventPublisher"; import { useInteractionCache } from "@/Hooks/useInteractionCache"; import useLogin from "@/Hooks/useLogin"; import { useNoteCreator } from "@/State/NoteCreator"; -import { delay, findTag, getDisplayName } from "@/Utils"; +import { findTag, getDisplayName } from "@/Utils"; import { formatShort } from "@/Utils/Number"; import { Zapper, ZapTarget } from "@/Utils/Zapper"; import { ZapPoolController } from "@/Utils/ZapPoolController"; @@ -23,18 +23,8 @@ import { useWallet } from "@/Wallet"; import messages from "../../messages"; -let isZapperBusy = false; -const barrierZapper = async (then: () => Promise): Promise => { - while (isZapperBusy) { - await delay(100); - } - isZapperBusy = true; - try { - return await then(); - } finally { - isZapperBusy = false; - } -}; +const ZapperQueue: Array = []; +processWorkQueue(ZapperQueue); export interface NoteFooterProps { replies?: number; @@ -154,7 +144,7 @@ export default function NoteFooter(props: NoteFooterProps) { async function fastZapInner(targets: Array, amount: number) { if (wallet) { // only allow 1 invoice req/payment at a time to avoid hitting rate limits - await barrierZapper(async () => { + await barrierQueue(ZapperQueue, async () => { const zapper = new Zapper(system, publisher); const result = await zapper.send(wallet, targets, amount); const totalSent = result.reduce((acc, v) => (acc += v.sent), 0); diff --git a/packages/app/src/Pages/DeckLayout.tsx b/packages/app/src/Pages/DeckLayout.tsx index e561383f..f1c672bf 100644 --- a/packages/app/src/Pages/DeckLayout.tsx +++ b/packages/app/src/Pages/DeckLayout.tsx @@ -3,7 +3,7 @@ import "./Deck.css"; import { NostrLink, TaggedNostrEvent } from "@snort/system"; import { createContext, useCallback, useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { Link, Outlet, useNavigate } from "react-router-dom"; +import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"; import ErrorBoundary from "@/Components/ErrorBoundary"; import { LongFormText } from "@/Components/Event/LongFormText"; @@ -20,6 +20,7 @@ import { useLoginRelays } from "@/Hooks/useLoginRelays"; import { transformTextCached } from "@/Hooks/useTextTransformCache"; import { useTheme } from "@/Hooks/useTheme"; import NavSidebar from "@/Pages/Layout/NavSidebar"; +import { trackEvent } from "@/Utils"; import { getCurrentSubscription } from "@/Utils/Subscription"; import NotificationsPage from "./Notifications/Notifications"; @@ -41,7 +42,12 @@ interface DeckScope { export const DeckContext = createContext(undefined); export function SnortDeckLayout() { - const login = useLogin(s => ({ publicKey: s.publicKey, subscriptions: s.subscriptions })); + const location = useLocation(); + const login = useLogin(s => ({ + publicKey: s.publicKey, + subscriptions: s.subscriptions, + telemetry: s.appData.item.preferences.telemetry, + })); const navigate = useNavigate(); const [deckState, setDeckState] = useState({ thread: undefined, @@ -59,6 +65,12 @@ export function SnortDeckLayout() { } }, [login]); + useEffect(() => { + if (CONFIG.features.analytics && (login.telemetry ?? true)) { + trackEvent("pageview"); + } + }, [location]); + if (!login.publicKey) return null; const showDeck = CONFIG.showDeck || !(CONFIG.deckSubKind !== undefined && (sub?.type ?? -1) < CONFIG.deckSubKind); if (!showDeck) { diff --git a/packages/app/src/Pages/Layout/index.tsx b/packages/app/src/Pages/Layout/index.tsx index d6921ebe..7ce1f0ec 100644 --- a/packages/app/src/Pages/Layout/index.tsx +++ b/packages/app/src/Pages/Layout/index.tsx @@ -1,6 +1,6 @@ import "./Layout.css"; -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import { Outlet, useLocation } from "react-router-dom"; import CloseButton from "@/Components/Button/CloseButton"; @@ -15,7 +15,7 @@ import { useLoginRelays } from "@/Hooks/useLoginRelays"; import { useTheme } from "@/Hooks/useTheme"; import Footer from "@/Pages/Layout/Footer"; import { Header } from "@/Pages/Layout/Header"; -import { isFormElement } from "@/Utils"; +import { isFormElement, trackEvent } from "@/Utils"; import { LoginStore } from "@/Utils/Login"; import NavSidebar from "./NavSidebar"; @@ -23,7 +23,11 @@ import RightColumn from "./RightColumn"; export default function Index() { const location = useLocation(); - const { id, stalker } = useLogin(s => ({ id: s.id, stalker: s.stalker ?? false })); + const { id, stalker, telemetry } = useLogin(s => ({ + id: s.id, + stalker: s.stalker ?? false, + telemetry: s.appData.item.preferences.telemetry, + })); useTheme(); useLoginRelays(); @@ -34,13 +38,19 @@ export default function Index() { const shouldHideFooter = location.pathname.startsWith("/messages/"); const shouldHideHeader = hideHeaderPaths.some(path => location.pathname.startsWith(path)); - const handleKeyboardShortcut = useCallback(event => { + const handleKeyboardShortcut = useCallback((event: Event) => { if (event.target && !isFormElement(event.target as HTMLElement)) { event.preventDefault(); window.scrollTo({ top: 0, behavior: "instant" }); } }, []); + useEffect(() => { + if (CONFIG.features.analytics && (telemetry ?? true)) { + trackEvent("pageview"); + } + }, [location]); + useKeyboardShortcut(".", handleKeyboardShortcut); const isStalker = !!stalker; diff --git a/packages/app/src/Utils/index.ts b/packages/app/src/Utils/index.ts index 2928761d..fac3854d 100644 --- a/packages/app/src/Utils/index.ts +++ b/packages/app/src/Utils/index.ts @@ -23,6 +23,7 @@ import AnimalName from "@/Components/User/AnimalName"; import { Birthday, Day } from "@/Utils/Const"; import TZ from "../tz.json"; +import { LoginStore } from "./Login"; export const sha256 = (str: string | Uint8Array): u256 => { return utils.bytesToHex(hash(str)); @@ -532,8 +533,26 @@ export function getCountry() { }; } -export function trackEvent(event: string, props?: Record) { - window.plausible?.(event, props ? { props } : undefined); +export function trackEvent( + event: string, + props?: Record, + e?: { destination?: { url: string } }, +) { + if (CONFIG.features.analytics && (LoginStore.snapshot().appData.item.preferences.telemetry ?? true)) { + fetch("https://analytics.v0l.io/api/event", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + d: CONFIG.hostname, + n: event, + r: document.referrer === location.href ? null : document.referrer, + p: props, + u: e?.destination?.url ?? `${location.protocol}//${location.host}${location.pathname}`, + }), + }); + } } export function storeRefCode() { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 05dd36ce..7325a5db 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -46,12 +46,6 @@ import WalletPage from "./Pages/wallet"; import { WalletReceivePage } from "./Pages/wallet/receive"; import { WalletSendPage } from "./Pages/wallet/send"; -declare global { - interface Window { - plausible?: (tag: string, e?: object) => void; - } -} - serviceWorkerRegistration.register(); async function initSite() { @@ -77,16 +71,6 @@ async function initSite() { console.error("Failed to register protocol handler", e); } - // inject analytics script - // - if (CONFIG.features.analytics && (login.appData.item.preferences.telemetry ?? true)) { - const sc = document.createElement("script"); - sc.src = "https://analytics.v0l.io/js/script.js"; - sc.defer = true; - sc.setAttribute("data-domain", CONFIG.hostname); - document.head.appendChild(sc); - } - setupWebLNWalletConfig(Wallets); return null; }