From 9d33abbf1ef3df27fba7199699d192d96e0c29a5 Mon Sep 17 00:00:00 2001
From: Kieran
Date: Tue, 10 Oct 2023 10:37:53 +0100
Subject: [PATCH] feature flags config / typed app config
---
packages/app/config/default.json | 5 +-
packages/app/config/iris.json | 5 +-
packages/app/custom.d.ts | 14 +++
packages/app/src/Element/FormattedMessage.tsx | 2 +-
packages/app/src/Element/Logo.tsx | 2 +-
packages/app/src/Element/PinPrompt.tsx | 2 +-
packages/app/src/Element/User/DisplayName.tsx | 4 +-
packages/app/src/Feed/LoginFeed.ts | 2 +-
packages/app/src/Pages/DonatePage.tsx | 4 +-
packages/app/src/Pages/Layout.tsx | 2 +-
packages/app/src/Pages/LoginPage.tsx | 6 +-
packages/app/src/Pages/NostrLinkHandler.tsx | 2 +-
.../app/src/Pages/Profile/ProfilePage.tsx | 4 +-
packages/app/src/Pages/new/messages.ts | 10 +--
packages/app/src/Pages/settings/Root.tsx | 14 +--
packages/app/src/Tasks/DonateTask.tsx | 2 +-
packages/app/src/index.tsx | 26 +++++-
packages/app/webpack.config.js | 6 +-
packages/system-react/src/useUserProfile.ts | 18 ----
packages/system/src/profile-cache.ts | 87 +++++++++----------
20 files changed, 118 insertions(+), 99 deletions(-)
diff --git a/packages/app/config/default.json b/packages/app/config/default.json
index 59de76938..8c5714d26 100644
--- a/packages/app/config/default.json
+++ b/packages/app/config/default.json
@@ -6,5 +6,8 @@
"favicon": "public/favicon.ico",
"appleTouchIconUrl": "/nostrich_512.png",
"httpCache": "",
- "animalNamePlaceholders": false
+ "animalNamePlaceholders": false,
+ "features": {
+ "subscriptions": true
+ }
}
diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json
index 823281ed3..b1f59e8c0 100644
--- a/packages/app/config/iris.json
+++ b/packages/app/config/iris.json
@@ -6,5 +6,8 @@
"favicon": "public/iris/favicon.ico",
"appleTouchIconUrl": "/img/apple-touch-icon.png",
"httpCache": "https://api.iris.to",
- "animalNamePlaceholders": true
+ "animalNamePlaceholders": true,
+ "features": {
+ "subscriptions": false
+ }
}
diff --git a/packages/app/custom.d.ts b/packages/app/custom.d.ts
index 8e871bb89..17267e740 100644
--- a/packages/app/custom.d.ts
+++ b/packages/app/custom.d.ts
@@ -34,3 +34,17 @@ declare module "emojilib" {
const value: Record;
export default value;
}
+
+declare const CONFIG: {
+ appName: string;
+ appNameCapitalized: string;
+ appTitle: string;
+ nip05Domain: string;
+ favicon: string;
+ appleTouchIconUrl: string;
+ httpCache: string;
+ animalNamePlaceholders: boolean;
+ features: {
+ subscriptions: boolean;
+ };
+};
diff --git a/packages/app/src/Element/FormattedMessage.tsx b/packages/app/src/Element/FormattedMessage.tsx
index e29784825..77141d390 100644
--- a/packages/app/src/Element/FormattedMessage.tsx
+++ b/packages/app/src/Element/FormattedMessage.tsx
@@ -12,7 +12,7 @@ const ExtendedFormattedMessage: FC = props => {
useEffect(() => {
const translatedMessage = formatMessage({ id, defaultMessage }, values);
if (typeof translatedMessage === "string") {
- setProcessedMessage(translatedMessage.replace("Snort", process.env.APP_NAME_CAPITALIZED || "Snort"));
+ setProcessedMessage(translatedMessage.replace("Snort", CONFIG.appNameCapitalized || "Snort"));
}
}, [id, defaultMessage, values, formatMessage]);
diff --git a/packages/app/src/Element/Logo.tsx b/packages/app/src/Element/Logo.tsx
index d33e3f953..7647e2ab4 100644
--- a/packages/app/src/Element/Logo.tsx
+++ b/packages/app/src/Element/Logo.tsx
@@ -4,7 +4,7 @@ const Logo = () => {
const navigate = useNavigate();
return (
navigate("/")}>
- {process.env.APP_NAME}
+ {CONFIG.appNameCapitalized}
);
};
diff --git a/packages/app/src/Element/PinPrompt.tsx b/packages/app/src/Element/PinPrompt.tsx
index a94129319..fa8d5e2ef 100644
--- a/packages/app/src/Element/PinPrompt.tsx
+++ b/packages/app/src/Element/PinPrompt.tsx
@@ -145,7 +145,7 @@ export function LoginUnlock() {
diff --git a/packages/app/src/Element/User/DisplayName.tsx b/packages/app/src/Element/User/DisplayName.tsx
index b7e58f8fe..e3fe57fa6 100644
--- a/packages/app/src/Element/User/DisplayName.tsx
+++ b/packages/app/src/Element/User/DisplayName.tsx
@@ -1,6 +1,6 @@
import "./DisplayName.css";
-import React, { useMemo } from "react";
+import { useMemo } from "react";
import { HexKey, UserMetadata, NostrPrefix } from "@snort/system";
import AnimalName from "Element/User/AnimalName";
import { hexToBech32 } from "SnortUtils";
@@ -22,7 +22,7 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
name = user.display_name;
} else if (typeof user?.name === "string" && user.name.length > 0) {
name = user.name;
- } else if (pubkey && process.env.ANIMAL_NAME_PLACEHOLDERS) {
+ } else if (pubkey && CONFIG.animalNamePlaceholders) {
name = AnimalName(pubkey);
isPlaceHolder = true;
}
diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts
index 14f065394..400bb6dce 100644
--- a/packages/app/src/Feed/LoginFeed.ts
+++ b/packages/app/src/Feed/LoginFeed.ts
@@ -49,7 +49,7 @@ export default function useLoginFeed() {
leaveOpen: true,
});
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]);
- if (!login.readonly) {
+ if (CONFIG.features.subscriptions && !login.readonly) {
b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]);
b.withFilter()
.relay("wss://relay.snort.social")
diff --git a/packages/app/src/Pages/DonatePage.tsx b/packages/app/src/Pages/DonatePage.tsx
index f1df62c83..7617286b0 100644
--- a/packages/app/src/Pages/DonatePage.tsx
+++ b/packages/app/src/Pages/DonatePage.tsx
@@ -94,13 +94,13 @@ const DonatePage = () => {
diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx
index a02150fdf..001d28223 100644
--- a/packages/app/src/Pages/Layout.tsx
+++ b/packages/app/src/Pages/Layout.tsx
@@ -209,7 +209,7 @@ function LogoHeader() {
return (
-
{process.env.APP_NAME}
+ {CONFIG.appName}
{currentSubscription && (
diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx
index ba068cc3f..c8e0a8997 100644
--- a/packages/app/src/Pages/LoginPage.tsx
+++ b/packages/app/src/Pages/LoginPage.tsx
@@ -143,7 +143,7 @@ export default function LoginPage() {
function generateNip46() {
const meta = {
- name: process.env.APP_NAME_CAPITALIZED,
+ name: CONFIG.appNameCapitalized,
url: window.location.href,
};
@@ -287,7 +287,7 @@ export default function LoginPage() {
navigate("/")}>
- {process.env.APP_NAME}
+ {CONFIG.appName}
@@ -342,7 +342,7 @@ export default function LoginPage() {
diff --git a/packages/app/src/Pages/NostrLinkHandler.tsx b/packages/app/src/Pages/NostrLinkHandler.tsx
index 801ab7943..d716bfca6 100644
--- a/packages/app/src/Pages/NostrLinkHandler.tsx
+++ b/packages/app/src/Pages/NostrLinkHandler.tsx
@@ -25,7 +25,7 @@ export default function NostrLinkHandler() {
}
} else {
try {
- const pubkey = await getNip05PubKey(`${link}@${process.env.NIP05_DOMAIN}`);
+ const pubkey = await getNip05PubKey(`${link}@${CONFIG.nip05Domain}`);
if (pubkey) {
setRenderComponent(); // Directly render ProfilePage
}
diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx
index 81b15b6ee..fb515ddca 100644
--- a/packages/app/src/Pages/Profile/ProfilePage.tsx
+++ b/packages/app/src/Pages/Profile/ProfilePage.tsx
@@ -139,8 +139,8 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
useEffect(() => {
if (user?.nip05 && user?.isNostrAddressValid) {
- if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) {
- const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, "");
+ if (user.nip05.endsWith(`@${CONFIG.nip05Domain}`)) {
+ const username = user.nip05?.replace(`@${CONFIG.nip05Domain}`, "");
navigate(`/${username}`, { replace: true });
}
}
diff --git a/packages/app/src/Pages/new/messages.ts b/packages/app/src/Pages/new/messages.ts
index 5e7d04528..2a86b565e 100644
--- a/packages/app/src/Pages/new/messages.ts
+++ b/packages/app/src/Pages/new/messages.ts
@@ -14,11 +14,11 @@ export default defineMessages({
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
WhatIsSnort: {
defaultMessage: "What is {site} and how does it work?",
- values: { site: process.env.APP_NAME_CAPITALIZED },
+ values: { site: CONFIG.appNameCapitalized },
},
WhatIsSnortIntro: {
defaultMessage: `{site} is a Nostr UI, nostr is a decentralised protocol for saving and distributing "notes".`,
- values: { site: process.env.APP_NAME_CAPITALIZED },
+ values: { site: CONFIG.appNameCapitalized },
},
WhatIsSnortNotes: {
defaultMessage: `Notes hold text content, the most popular usage of these notes is to store "tweet like" messages.`,
@@ -26,7 +26,7 @@ export default defineMessages({
WhatIsSnortExperience: {
defaultMessage: "{site} is designed to have a similar experience to Twitter.",
- values: { site: process.env.APP_NAME_CAPITALIZED },
+ values: { site: CONFIG.appNameCapitalized },
},
HowKeysWork: { defaultMessage: "How do keys work?" },
DigitalSignatures: {
@@ -70,9 +70,9 @@ export default defineMessages({
NameSquatting: {
defaultMessage:
"Name-squatting and impersonation is not allowed. {site} and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.",
- values: { site: process.env.APP_NAME_CAPITALIZED },
+ values: { site: CONFIG.appNameCapitalized },
},
- PreviewOnSnort: { defaultMessage: "Preview on {site}", values: { site: process.env.APP_NAME_CAPITALIZED } },
+ PreviewOnSnort: { defaultMessage: "Preview on {site}", values: { site: CONFIG.appNameCapitalized } },
GetSnortId: { defaultMessage: "Get a Snort identifier" },
GetSnortIdHelp: {
defaultMessage:
diff --git a/packages/app/src/Pages/settings/Root.tsx b/packages/app/src/Pages/settings/Root.tsx
index 8edab2b8f..3383b886e 100644
--- a/packages/app/src/Pages/settings/Root.tsx
+++ b/packages/app/src/Pages/settings/Root.tsx
@@ -6,9 +6,9 @@ import Icon from "Icons/Icon";
import { LoginStore, logout } from "Login";
import useLogin from "Hooks/useLogin";
import { getCurrentSubscription } from "Subscription";
+import usePageWidth from "Hooks/usePageWidth";
import messages from "./messages";
-import usePageWidth from "Hooks/usePageWidth";
const SettingsIndex = () => {
const login = useLogin();
@@ -61,11 +61,13 @@ const SettingsIndex = () => {
-
navigate("/subscribe/manage")}>
-
-
-
-
+ {CONFIG.features.subscriptions && (
+
navigate("/subscribe/manage")}>
+
+
+
+
+ )}
{sub && (
navigate("accounts")}>
diff --git a/packages/app/src/Tasks/DonateTask.tsx b/packages/app/src/Tasks/DonateTask.tsx
index f6431f490..041e18df1 100644
--- a/packages/app/src/Tasks/DonateTask.tsx
+++ b/packages/app/src/Tasks/DonateTask.tsx
@@ -15,7 +15,7 @@ export class DonateTask extends BaseUITask {
diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx
index 417a93959..2a52a23ce 100644
--- a/packages/app/src/index.tsx
+++ b/packages/app/src/index.tsx
@@ -16,6 +16,7 @@ import {
ReqFilter,
PowMiner,
NostrEvent,
+ mapEventToProfile,
} from "@snort/system";
import { SnortContext } from "@snort/system-react";
@@ -87,6 +88,29 @@ export const System = new NostrSystem({
},
});
+async function fetchProfile(key: string) {
+ const rsp = await fetch(`${CONFIG.httpCache}/profile/${key}`);
+ if (rsp.ok) {
+ try {
+ 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 (await Promise.all(keys.map(a => fetchProfile(a)))).filter(a => a !== undefined).map(a => unwrap(a));
+ };
+}
+
/**
* Singleton user profile loader
*/
@@ -191,7 +215,7 @@ export const router = createBrowserRouter([
},
...NewUserRoutes,
...WalletRoutes,
- ...SubscribeRoutes,
+ ...(CONFIG.features.subscriptions ? SubscribeRoutes : []),
{
path: "/debug",
element: ,
diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js
index c20183fa6..4f40b87f9 100644
--- a/packages/app/webpack.config.js
+++ b/packages/app/webpack.config.js
@@ -88,11 +88,7 @@ const config = {
})
: false,
new DefinePlugin({
- "process.env.APP_NAME": JSON.stringify(appConfig.get("appName")),
- "process.env.APP_NAME_CAPITALIZED": JSON.stringify(appConfig.get("appNameCapitalized")),
- "process.env.NIP05_DOMAIN": JSON.stringify(appConfig.get("nip05Domain")),
- "process.env.HTTP_CACHE": JSON.stringify(appConfig.get("httpCache")),
- "process.env.ANIMAL_NAME_PLACEHOLDERS": JSON.stringify(appConfig.get("animalNamePlaceholders")),
+ CONFIG: JSON.stringify(appConfig),
}),
],
module: {
diff --git a/packages/system-react/src/useUserProfile.ts b/packages/system-react/src/useUserProfile.ts
index d6b125855..5d7187619 100644
--- a/packages/system-react/src/useUserProfile.ts
+++ b/packages/system-react/src/useUserProfile.ts
@@ -11,24 +11,6 @@ export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined {
h => {
if (pubKey) {
system.ProfileLoader.TrackMetadata(pubKey);
- if (process.env.HTTP_CACHE && !system.ProfileLoader.Cache.getFromCache(pubKey)) {
- fetch(`${process.env.HTTP_CACHE}/profile/${pubKey}`)
- .then(async r => {
- if (r.ok) {
- try {
- const data = await r.json();
- if (data) {
- system.ProfileLoader.onProfileEvent(data);
- }
- } catch (e) {
- console.error(e);
- }
- }
- })
- .catch(e => {
- console.error(e);
- });
- }
}
const release = system.ProfileLoader.Cache.hook(h, pubKey);
return () => {
diff --git a/packages/system/src/profile-cache.ts b/packages/system/src/profile-cache.ts
index 6118a8ae9..ee5646c8d 100644
--- a/packages/system/src/profile-cache.ts
+++ b/packages/system/src/profile-cache.ts
@@ -1,8 +1,9 @@
import debug from "debug";
import { unixNowMs, FeedCache } from "@snort/shared";
-import { EventKind, HexKey, SystemInterface, TaggedNostrEvent, NoteCollection, RequestBuilder } from ".";
+import { EventKind, HexKey, SystemInterface, TaggedNostrEvent, RequestBuilder } from ".";
import { ProfileCacheExpire } from "./const";
import { mapEventToProfile, MetadataCache } from "./cache";
+import { v4 as uuid } from "uuid";
const MetadataRelays = ["wss://purplepag.es"];
@@ -23,6 +24,11 @@ export class ProfileLoaderService {
readonly #log = debug("ProfileCache");
+ /**
+ * Custom loader function for fetching profiles from alternative sources
+ */
+ loaderFn?: (pubkeys: Array) => Promise>;
+
constructor(system: SystemInterface, cache: FeedCache) {
this.#system = system;
this.#cache = cache;
@@ -92,50 +98,8 @@ export class ProfileLoaderService {
if (missing.size > 0) {
this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length);
- const sub = new RequestBuilder("profiles");
- sub
- .withOptions({
- skipDiff: true,
- })
- .withFilter()
- .kinds([EventKind.SetMetadata])
- .authors([...missing]);
+ const results = await this.#loadProfiles([...missing]);
- if (this.#missingLastRun.size > 0) {
- const fMissing = sub
- .withFilter()
- .kinds([EventKind.SetMetadata])
- .authors([...this.#missingLastRun]);
- MetadataRelays.forEach(r => fMissing.relay(r));
- }
- const newProfiles = new Set();
- const q = this.#system.Query(NoteCollection, sub);
- const feed = (q?.feed as NoteCollection) ?? new NoteCollection();
- // never release this callback, it will stop firing anyway after eose
- const releaseOnEvent = feed.onEvent(async e => {
- for (const pe of e) {
- newProfiles.add(pe.id);
- await this.onProfileEvent(pe);
- }
- });
- const results = await new Promise>>(resolve => {
- let timeout: ReturnType | undefined = undefined;
- const release = feed.hook(() => {
- if (!feed.loading) {
- clearTimeout(timeout);
- resolve(feed.getSnapshotData() ?? []);
- this.#log("Profiles finished: %s", sub.id);
- release();
- }
- });
- timeout = setTimeout(() => {
- release();
- resolve(feed.getSnapshotData() ?? []);
- this.#log("Profiles timeout: %s", sub.id);
- }, 5_000);
- });
-
- releaseOnEvent();
const couldNotFetch = [...missing].filter(a => !results.some(b => b.pubkey === a));
this.#missingLastRun = new Set(couldNotFetch);
if (couldNotFetch.length > 0) {
@@ -150,12 +114,43 @@ export class ProfileLoaderService {
await Promise.all(empty);
}
- // When we fetch an expired profile and its the same as what we already have
+ /* When we fetch an expired profile and its the same as what we already have
// onEvent is not fired and the loaded timestamp never gets updated
const expiredSame = results.filter(a => !newProfiles.has(a.id) && expired.includes(a.pubkey));
- await Promise.all(expiredSame.map(v => this.onProfileEvent(v)));
+ await Promise.all(expiredSame.map(v => this.onProfileEvent(v)));*/
}
setTimeout(() => this.#FetchMetadata(), 500);
}
+
+ async #loadProfiles(missing: Array) {
+ if (this.loaderFn) {
+ const results = await this.loaderFn(missing);
+ await Promise.all(results.map(a => this.#cache.update(a)));
+ return results;
+ } else {
+ const sub = new RequestBuilder(`profiles-${uuid()}`);
+ sub
+ .withOptions({
+ skipDiff: true,
+ })
+ .withFilter()
+ .kinds([EventKind.SetMetadata])
+ .authors(missing);
+
+ if (this.#missingLastRun.size > 0) {
+ const fMissing = sub
+ .withFilter()
+ .kinds([EventKind.SetMetadata])
+ .authors([...this.#missingLastRun]);
+ MetadataRelays.forEach(r => fMissing.relay(r));
+ }
+ const results = (await this.#system.Fetch(sub, async e => {
+ for (const pe of e) {
+ await this.onProfileEvent(pe);
+ }
+ })) as ReadonlyArray;
+ return results;
+ }
+ }
}