2023-02-07 20:04:50 +00:00
|
|
|
import "./index.css";
|
|
|
|
import "@szhsin/react-menu/dist/index.css";
|
2023-08-25 22:24:47 +00:00
|
|
|
import "./fonts/inter.css";
|
2022-12-18 14:51:47 +00:00
|
|
|
|
2023-09-24 20:28:39 +00:00
|
|
|
import { compress, expand_filter, flat_merge, get_diff, pow, default as wasmInit } from "@snort/system-wasm";
|
2023-11-20 19:28:22 +00:00
|
|
|
import WasmPath from "@snort/system-wasm/pkg/system_wasm_bg.wasm";
|
2023-09-06 12:45:25 +00:00
|
|
|
|
2023-02-07 20:04:50 +00:00
|
|
|
import { StrictMode } from "react";
|
|
|
|
import * as ReactDOM from "react-dom/client";
|
2023-10-17 09:54:34 +00:00
|
|
|
import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-dom";
|
2023-09-24 20:28:39 +00:00
|
|
|
import {
|
|
|
|
NostrSystem,
|
|
|
|
ProfileLoaderService,
|
|
|
|
QueryOptimizer,
|
|
|
|
FlatReqFilter,
|
|
|
|
ReqFilter,
|
|
|
|
PowMiner,
|
|
|
|
NostrEvent,
|
2023-10-10 09:37:53 +00:00
|
|
|
mapEventToProfile,
|
2023-10-12 14:12:06 +00:00
|
|
|
PowWorker,
|
2023-11-10 13:58:27 +00:00
|
|
|
encodeTLVEntries,
|
2023-11-17 10:02:08 +00:00
|
|
|
socialGraphInstance,
|
2023-09-24 20:28:39 +00:00
|
|
|
} from "@snort/system";
|
2023-11-20 19:16:47 +00:00
|
|
|
import PowWorkerURL from "@snort/system/src/pow-worker.ts?worker&url";
|
2023-08-26 21:48:15 +00:00
|
|
|
import { SnortContext } from "@snort/system-react";
|
2023-10-31 15:40:12 +00:00
|
|
|
import { removeUndefined, throwIfOffline } from "@snort/shared";
|
2022-12-18 14:51:47 +00:00
|
|
|
|
2023-11-20 19:28:22 +00:00
|
|
|
import { lazy, Suspense } from "react";
|
2023-11-16 12:37:48 +00:00
|
|
|
|
2023-11-17 11:52:10 +00:00
|
|
|
const NetworkGraph = lazy(() => import("@/Pages/NetworkGraph"));
|
2023-11-16 12:37:48 +00:00
|
|
|
|
2023-11-17 11:52:10 +00:00
|
|
|
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";
|
2023-11-22 11:32:18 +00:00
|
|
|
import NotificationsPage from "@/Pages/Notifications";
|
2023-11-17 11:52:10 +00:00
|
|
|
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";
|
2023-11-08 14:47:35 +00:00
|
|
|
|
|
|
|
declare global {
|
|
|
|
interface Window {
|
|
|
|
plausible?: (tag: string) => void;
|
|
|
|
}
|
|
|
|
}
|
2023-05-30 13:48:38 +00:00
|
|
|
|
2023-09-11 14:33:16 +00:00
|
|
|
const WasmQueryOptimizer = {
|
|
|
|
expandFilter: (f: ReqFilter) => {
|
|
|
|
return expand_filter(f) as Array<FlatReqFilter>;
|
|
|
|
},
|
|
|
|
getDiff: (prev: Array<ReqFilter>, next: Array<ReqFilter>) => {
|
|
|
|
return get_diff(prev, next) as Array<FlatReqFilter>;
|
|
|
|
},
|
|
|
|
flatMerge: (all: Array<FlatReqFilter>) => {
|
|
|
|
return flat_merge(all) as Array<ReqFilter>;
|
|
|
|
},
|
|
|
|
compress: (all: Array<ReqFilter>) => {
|
|
|
|
return compress(all) as Array<ReqFilter>;
|
2023-09-12 14:02:16 +00:00
|
|
|
},
|
2023-09-11 14:33:16 +00:00
|
|
|
} as QueryOptimizer;
|
|
|
|
|
2023-09-24 20:28:39 +00:00
|
|
|
export class WasmPowWorker implements PowMiner {
|
|
|
|
minePow(ev: NostrEvent, target: number): Promise<NostrEvent> {
|
|
|
|
const res = pow(ev, target);
|
|
|
|
return Promise.resolve(res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-20 19:28:22 +00:00
|
|
|
const hasWasm = "WebAssembly" in globalThis;
|
2023-11-17 11:52:10 +00:00
|
|
|
const DefaultPowWorker = hasWasm ? undefined : new PowWorker(PowWorkerURL);
|
2023-10-12 14:12:06 +00:00
|
|
|
export const GetPowWorker = () => (hasWasm ? new WasmPowWorker() : unwrap(DefaultPowWorker));
|
|
|
|
|
2023-05-30 13:48:38 +00:00
|
|
|
/**
|
|
|
|
* Singleton nostr system
|
|
|
|
*/
|
2023-10-13 15:34:31 +00:00
|
|
|
const System = new NostrSystem({
|
2023-06-15 11:03:05 +00:00
|
|
|
relayCache: UserRelays,
|
2023-06-17 18:42:09 +00:00
|
|
|
profileCache: UserCache,
|
|
|
|
relayMetrics: RelayMetrics,
|
2023-10-12 14:12:06 +00:00
|
|
|
queryOptimizer: hasWasm ? WasmQueryOptimizer : undefined,
|
2023-10-09 13:35:21 +00:00
|
|
|
db: SystemDb,
|
2023-11-07 13:28:01 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
System.on("auth", async (c, r, cb) => {
|
|
|
|
const { id } = LoginStore.snapshot();
|
|
|
|
const pub = LoginStore.getPublisher(id);
|
|
|
|
if (pub) {
|
|
|
|
cb(await pub.nip42Auth(c, r));
|
|
|
|
}
|
2023-05-30 13:48:38 +00:00
|
|
|
});
|
|
|
|
|
2023-11-17 10:02:08 +00:00
|
|
|
System.on("event", ev => {
|
|
|
|
if (ev.kind === 3) {
|
|
|
|
socialGraphInstance.handleFollowEvent(ev);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-10 09:37:53 +00:00
|
|
|
async function fetchProfile(key: string) {
|
2023-10-15 16:28:07 +00:00
|
|
|
try {
|
2023-10-31 15:40:12 +00:00
|
|
|
throwIfOffline();
|
2023-10-15 16:28:07 +00:00
|
|
|
const rsp = await fetch(`${CONFIG.httpCache}/profile/${key}`);
|
|
|
|
if (rsp.ok) {
|
2023-10-10 09:37:53 +00:00
|
|
|
const data = (await rsp.json()) as NostrEvent;
|
|
|
|
if (data) {
|
|
|
|
return mapEventToProfile(data);
|
|
|
|
}
|
|
|
|
}
|
2023-10-15 16:28:07 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
2023-10-10 09:37:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add profile loader fn
|
|
|
|
*/
|
|
|
|
if (CONFIG.httpCache) {
|
|
|
|
System.ProfileLoader.loaderFn = async (keys: Array<string>) => {
|
2023-10-11 14:41:36 +00:00
|
|
|
return removeUndefined(await Promise.all(keys.map(a => fetchProfile(a))));
|
2023-10-10 09:37:53 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-05-30 13:48:38 +00:00
|
|
|
/**
|
|
|
|
* Singleton user profile loader
|
|
|
|
*/
|
2023-06-08 10:45:23 +00:00
|
|
|
export const ProfileLoader = new ProfileLoaderService(System, UserCache);
|
2022-12-18 14:51:47 +00:00
|
|
|
|
2023-01-19 11:03:51 +00:00
|
|
|
serviceWorkerRegistration.register();
|
|
|
|
|
2023-09-05 13:57:50 +00:00
|
|
|
async function initSite() {
|
2023-11-06 13:34:09 +00:00
|
|
|
console.debug(getCountry());
|
2023-10-12 14:12:06 +00:00
|
|
|
if (hasWasm) {
|
|
|
|
await wasmInit(WasmPath);
|
|
|
|
}
|
2023-09-05 13:57:50 +00:00
|
|
|
const login = LoginStore.takeSnapshot();
|
|
|
|
db.ready = await db.isAvailable();
|
|
|
|
if (db.ready) {
|
|
|
|
await preload(login.follows.item);
|
|
|
|
}
|
|
|
|
|
2023-10-31 15:40:57 +00:00
|
|
|
updateRelayConnections(System, login.relays.item).catch(console.error);
|
2023-10-31 15:40:12 +00:00
|
|
|
|
2023-09-05 13:57:50 +00:00
|
|
|
try {
|
|
|
|
if ("registerProtocolHandler" in window.navigator) {
|
2023-09-05 13:59:44 +00:00
|
|
|
window.navigator.registerProtocolHandler("web+nostr", `${window.location.protocol}//${window.location.host}/%s`);
|
2023-09-05 13:57:50 +00:00
|
|
|
console.info("Registered protocol handler for 'web+nostr'");
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.error("Failed to register protocol handler", e);
|
|
|
|
}
|
2023-09-12 21:53:32 +00:00
|
|
|
|
|
|
|
// inject analytics script
|
|
|
|
// <script defer data-domain="snort.social" src="http://analytics.v0l.io/js/script.js"></script>
|
2023-11-13 16:51:29 +00:00
|
|
|
if (CONFIG.features.analytics && (login.appData.item.preferences.telemetry ?? true)) {
|
2023-09-12 21:53:32 +00:00
|
|
|
const sc = document.createElement("script");
|
2023-09-12 22:02:51 +00:00
|
|
|
sc.src = "https://analytics.v0l.io/js/script.js";
|
2023-09-12 21:53:32 +00:00
|
|
|
sc.defer = true;
|
2023-10-12 13:45:30 +00:00
|
|
|
sc.setAttribute("data-domain", CONFIG.hostname);
|
2023-09-12 21:53:32 +00:00
|
|
|
document.head.appendChild(sc);
|
|
|
|
}
|
2023-11-10 10:13:41 +00:00
|
|
|
|
|
|
|
setupWebLNWalletConfig(Wallets);
|
2023-09-05 13:57:50 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let didInit = false;
|
2023-10-17 09:54:34 +00:00
|
|
|
const mainRoutes = [
|
|
|
|
...RootRoutes,
|
|
|
|
{
|
|
|
|
path: "/help",
|
|
|
|
element: <HelpPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/e/:id",
|
|
|
|
element: <ThreadRoute />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/p/:id",
|
|
|
|
element: <ProfilePage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/notifications",
|
|
|
|
element: <NotificationsPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/settings",
|
|
|
|
element: <SettingsPage />,
|
|
|
|
children: SettingsRoutes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/free-nostr-address",
|
|
|
|
element: <FreeNostrAddressPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/nostr-address",
|
|
|
|
element: <NostrAddressPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/messages/:id?",
|
|
|
|
element: <MessagesPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/donate",
|
|
|
|
element: <DonatePage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/search/:keyword?",
|
|
|
|
element: <SearchPage />,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: "/list-feed/:id",
|
|
|
|
element: <ListFeedPage />,
|
|
|
|
},
|
2023-11-02 17:39:42 +00:00
|
|
|
{
|
|
|
|
path: "/about",
|
|
|
|
element: <AboutPage />,
|
|
|
|
},
|
2023-11-16 12:37:48 +00:00
|
|
|
{
|
|
|
|
path: "/graph",
|
|
|
|
element: <NetworkGraph />,
|
|
|
|
},
|
2023-11-08 14:47:35 +00:00
|
|
|
...OnboardingRoutes,
|
2023-10-17 09:54:34 +00:00
|
|
|
...WalletRoutes,
|
|
|
|
] as Array<RouteObject>;
|
|
|
|
|
|
|
|
if (CONFIG.features.zapPool) {
|
|
|
|
mainRoutes.push({
|
|
|
|
path: "/zap-pool",
|
|
|
|
element: <ZapPoolPage />,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (CONFIG.features.subscriptions) {
|
|
|
|
mainRoutes.push(...SubscribeRoutes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// add catch all route
|
|
|
|
mainRoutes.push({
|
|
|
|
path: "/*",
|
|
|
|
element: <NostrLinkHandler />,
|
|
|
|
});
|
|
|
|
|
|
|
|
const routes = [
|
2023-01-12 12:00:44 +00:00
|
|
|
{
|
|
|
|
element: <Layout />,
|
|
|
|
errorElement: <ErrorPage />,
|
2023-05-25 09:31:47 +00:00
|
|
|
loader: async () => {
|
2023-09-05 13:57:50 +00:00
|
|
|
if (!didInit) {
|
|
|
|
didInit = true;
|
2023-09-05 13:59:44 +00:00
|
|
|
return await initSite();
|
2023-05-25 09:31:47 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
2023-10-17 09:54:34 +00:00
|
|
|
children: mainRoutes,
|
2023-02-07 20:04:50 +00:00
|
|
|
},
|
2023-10-17 09:54:34 +00:00
|
|
|
] as Array<RouteObject>;
|
|
|
|
|
|
|
|
if (CONFIG.features.deck) {
|
|
|
|
routes.push({
|
2023-09-07 14:54:25 +00:00
|
|
|
path: "/deck",
|
|
|
|
element: <SnortDeckLayout />,
|
|
|
|
loader: async () => {
|
|
|
|
if (!didInit) {
|
|
|
|
didInit = true;
|
|
|
|
return await initSite();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
2023-09-14 18:45:29 +00:00
|
|
|
children: RootTabRoutes,
|
2023-10-17 09:54:34 +00:00
|
|
|
} as RouteObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const router = createBrowserRouter(routes);
|
2023-01-12 12:00:44 +00:00
|
|
|
|
2023-02-07 19:47:57 +00:00
|
|
|
const root = ReactDOM.createRoot(unwrap(document.getElementById("root")));
|
2022-12-18 14:51:47 +00:00
|
|
|
root.render(
|
2023-01-18 23:39:50 +00:00
|
|
|
<StrictMode>
|
2023-09-21 20:02:59 +00:00
|
|
|
<IntlProvider>
|
|
|
|
<SnortContext.Provider value={System}>
|
2023-11-16 12:37:48 +00:00
|
|
|
<Suspense fallback={<div>Loading...</div>}>
|
|
|
|
<RouterProvider router={router} />
|
|
|
|
</Suspense>
|
2023-09-21 20:02:59 +00:00
|
|
|
</SnortContext.Provider>
|
|
|
|
</IntlProvider>
|
2023-09-12 21:58:37 +00:00
|
|
|
</StrictMode>,
|
2022-12-18 14:51:47 +00:00
|
|
|
);
|
2023-11-10 13:58:27 +00:00
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
//@ts-ignore
|
|
|
|
window.encodeTLV = encodeTLVEntries;
|
2023-11-17 11:52:10 +00:00
|
|
|
|
|
|
|
// Use react-helmet instead?
|
|
|
|
document.title = CONFIG.appTitle;
|
2023-11-20 19:16:47 +00:00
|
|
|
document.querySelector('link[rel="apple-touch-icon"]')?.setAttribute("href", CONFIG.appleTouchIconUrl);
|