snort/packages/app/src/index.tsx

294 lines
7.5 KiB
TypeScript
Raw Normal View History

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-09-22 19:28:56 +00:00
import WasmPath from "@snort/system-wasm/pkg/system_wasm_bg.wasm";
2023-09-06 12:45:25 +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,
mapEventToProfile,
2023-10-12 14:12:06 +00:00
PowWorker,
2023-11-10 13:58:27 +00:00
encodeTLVEntries,
2023-09-24 20:28:39 +00:00
} from "@snort/system";
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
import * as serviceWorkerRegistration from "serviceWorkerRegistration";
2023-01-31 18:56:31 +00:00
import { IntlProvider } from "IntlProvider";
2023-11-05 04:48:33 +00:00
import { getCountry, unwrap } from "SnortUtils";
import Layout from "Pages/Layout";
2023-10-06 10:13:49 +00:00
import ProfilePage from "Pages/Profile/ProfilePage";
2023-09-07 14:54:25 +00:00
import { RootRoutes, RootTabRoutes } from "Pages/Root";
import NotificationsPage from "Pages/Notifications";
import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage";
import ErrorPage from "Pages/ErrorPage";
2023-08-21 13:58:57 +00:00
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";
2023-02-13 15:29:25 +00:00
import { WalletRoutes } from "Pages/WalletPage";
2023-02-14 11:08:25 +00:00
import NostrLinkHandler from "Pages/NostrLinkHandler";
2023-09-28 09:26:10 +00:00
import { ThreadRoute } from "Element/Event/Thread";
2023-04-13 18:43:43 +00:00
import { SubscribeRoutes } from "Pages/subscribe";
2023-05-16 21:30:52 +00:00
import ZapPoolPage from "Pages/ZapPool";
import { db } from "Db";
2023-10-09 13:35:21 +00:00
import { preload, RelayMetrics, SystemDb, UserCache, UserRelays } from "Cache";
import { LoginStore } from "Login";
2023-09-07 14:54:25 +00:00
import { SnortDeckLayout } from "Pages/DeckLayout";
2023-09-27 08:41:26 +00:00
import FreeNostrAddressPage from "./Pages/FreeNostrAddressPage";
2023-10-12 15:28:30 +00:00
import { ListFeedPage } from "Pages/ListFeedPage";
2023-10-31 15:40:12 +00:00
import { updateRelayConnections } from "Hooks/useLoginRelays";
2023-11-02 17:39:42 +00:00
import { AboutPage } from "Pages/About";
2023-11-08 14:47:35 +00:00
import { OnboardingRoutes } from "Pages/onboarding";
2023-11-10 10:13:41 +00:00
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-10-12 14:12:06 +00:00
const hasWasm = "WebAssembly" in globalThis;
const DefaultPowWorker = hasWasm ? undefined : new PowWorker("/pow.js");
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
});
async function fetchProfile(key: string) {
try {
2023-10-31 15:40:12 +00:00
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<string>) => {
2023-10-11 14:41:36 +00:00
return removeUndefined(await Promise.all(keys.map(a => fetchProfile(a))));
};
}
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-10-17 09:54:34 +00:00
if (CONFIG.features.analytics && (login.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;
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-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 />,
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();
}
return null;
},
2023-10-17 09:54:34 +00:00
children: mainRoutes,
},
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}>
<RouterProvider router={router} />
</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;