import "./index.css"; import "@szhsin/react-menu/dist/index.css"; import "./fonts/inter.css"; import { compress, expand_filter, flat_merge, get_diff, pow, default as wasmInit } from "@snort/system-wasm"; import WasmPath from "@snort/system-wasm/pkg/system_wasm_bg.wasm"; import { StrictMode } from "react"; import * as ReactDOM from "react-dom/client"; import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-dom"; import { NostrSystem, ProfileLoaderService, QueryOptimizer, FlatReqFilter, ReqFilter, PowMiner, NostrEvent, mapEventToProfile, PowWorker, encodeTLVEntries, } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import { removeUndefined, throwIfOffline } from "@snort/shared"; import * as serviceWorkerRegistration from "serviceWorkerRegistration"; import { IntlProvider } from "IntlProvider"; import { getCountry, unwrap } from "SnortUtils"; import Layout from "Pages/Layout"; import ProfilePage from "Pages/Profile/ProfilePage"; import { RootRoutes, RootTabRoutes } from "Pages/Root"; import NotificationsPage from "Pages/Notifications"; import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage"; import ErrorPage from "Pages/ErrorPage"; import NostrAddressPage from "Pages/NostrAddressPage"; import MessagesPage from "Pages/MessagesPage"; import DonatePage from "Pages/DonatePage"; import SearchPage from "Pages/SearchPage"; import HelpPage from "Pages/HelpPage"; import { WalletRoutes } from "Pages/WalletPage"; import NostrLinkHandler from "Pages/NostrLinkHandler"; import { ThreadRoute } from "Element/Event/Thread"; import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; import { db } from "Db"; import { preload, RelayMetrics, SystemDb, UserCache, UserRelays } from "Cache"; import { LoginStore } from "Login"; import { SnortDeckLayout } from "Pages/DeckLayout"; import FreeNostrAddressPage from "./Pages/FreeNostrAddressPage"; import { ListFeedPage } from "Pages/ListFeedPage"; import { updateRelayConnections } from "Hooks/useLoginRelays"; import { AboutPage } from "Pages/About"; import { OnboardingRoutes } from "Pages/onboarding"; import { setupWebLNWalletConfig } from "Wallet/WebLN"; import { Wallets } from "Wallet"; declare global { interface Window { plausible?: (tag: string) => void; } } const WasmQueryOptimizer = { expandFilter: (f: ReqFilter) => { return expand_filter(f) as Array; }, getDiff: (prev: Array, next: Array) => { return get_diff(prev, next) as Array; }, flatMerge: (all: Array) => { return flat_merge(all) as Array; }, compress: (all: Array) => { return compress(all) as Array; }, } as QueryOptimizer; export class WasmPowWorker implements PowMiner { minePow(ev: NostrEvent, target: number): Promise { const res = pow(ev, target); return Promise.resolve(res); } } const hasWasm = "WebAssembly" in globalThis; const DefaultPowWorker = hasWasm ? undefined : new PowWorker("/pow.js"); export const GetPowWorker = () => (hasWasm ? new WasmPowWorker() : unwrap(DefaultPowWorker)); /** * Singleton nostr system */ const System = new NostrSystem({ relayCache: UserRelays, profileCache: UserCache, relayMetrics: RelayMetrics, queryOptimizer: hasWasm ? WasmQueryOptimizer : undefined, db: SystemDb, }); System.on("auth", async (c, r, cb) => { const { id } = LoginStore.snapshot(); const pub = LoginStore.getPublisher(id); if (pub) { cb(await pub.nip42Auth(c, r)); } }); async function fetchProfile(key: string) { try { throwIfOffline(); const rsp = await fetch(`${CONFIG.httpCache}/profile/${key}`); if (rsp.ok) { const data = (await rsp.json()) as NostrEvent; if (data) { return mapEventToProfile(data); } } } catch (e) { console.error(e); } } /** * Add profile loader fn */ if (CONFIG.httpCache) { System.ProfileLoader.loaderFn = async (keys: Array) => { return removeUndefined(await Promise.all(keys.map(a => fetchProfile(a)))); }; } /** * Singleton user profile loader */ export const ProfileLoader = new ProfileLoaderService(System, UserCache); serviceWorkerRegistration.register(); async function initSite() { console.debug(getCountry()); if (hasWasm) { await wasmInit(WasmPath); } const login = LoginStore.takeSnapshot(); db.ready = await db.isAvailable(); if (db.ready) { await preload(login.follows.item); } updateRelayConnections(System, login.relays.item).catch(console.error); try { if ("registerProtocolHandler" in window.navigator) { window.navigator.registerProtocolHandler("web+nostr", `${window.location.protocol}//${window.location.host}/%s`); console.info("Registered protocol handler for 'web+nostr'"); } } catch (e) { console.error("Failed to register protocol handler", e); } // inject analytics script // if (CONFIG.features.analytics && (login.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; } let didInit = false; const mainRoutes = [ ...RootRoutes, { path: "/help", element: , }, { path: "/e/:id", element: , }, { path: "/p/:id", element: , }, { path: "/notifications", element: , }, { path: "/settings", element: , children: SettingsRoutes, }, { path: "/free-nostr-address", element: , }, { path: "/nostr-address", element: , }, { path: "/messages/:id?", element: , }, { path: "/donate", element: , }, { path: "/search/:keyword?", element: , }, { path: "/list-feed/:id", element: , }, { path: "/about", element: , }, ...OnboardingRoutes, ...WalletRoutes, ] as Array; if (CONFIG.features.zapPool) { mainRoutes.push({ path: "/zap-pool", element: , }); } if (CONFIG.features.subscriptions) { mainRoutes.push(...SubscribeRoutes); } // add catch all route mainRoutes.push({ path: "/*", element: , }); const routes = [ { element: , errorElement: , loader: async () => { if (!didInit) { didInit = true; return await initSite(); } return null; }, children: mainRoutes, }, ] as Array; if (CONFIG.features.deck) { routes.push({ path: "/deck", element: , loader: async () => { if (!didInit) { didInit = true; return await initSite(); } return null; }, children: RootTabRoutes, } as RouteObject); } export const router = createBrowserRouter(routes); const root = ReactDOM.createRoot(unwrap(document.getElementById("root"))); root.render( , ); // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore window.encodeTLV = encodeTLVEntries;