feature flags config / typed app config
This commit is contained in:
parent
c023a89271
commit
9d33abbf1e
@ -6,5 +6,8 @@
|
|||||||
"favicon": "public/favicon.ico",
|
"favicon": "public/favicon.ico",
|
||||||
"appleTouchIconUrl": "/nostrich_512.png",
|
"appleTouchIconUrl": "/nostrich_512.png",
|
||||||
"httpCache": "",
|
"httpCache": "",
|
||||||
"animalNamePlaceholders": false
|
"animalNamePlaceholders": false,
|
||||||
|
"features": {
|
||||||
|
"subscriptions": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,8 @@
|
|||||||
"favicon": "public/iris/favicon.ico",
|
"favicon": "public/iris/favicon.ico",
|
||||||
"appleTouchIconUrl": "/img/apple-touch-icon.png",
|
"appleTouchIconUrl": "/img/apple-touch-icon.png",
|
||||||
"httpCache": "https://api.iris.to",
|
"httpCache": "https://api.iris.to",
|
||||||
"animalNamePlaceholders": true
|
"animalNamePlaceholders": true,
|
||||||
|
"features": {
|
||||||
|
"subscriptions": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
14
packages/app/custom.d.ts
vendored
14
packages/app/custom.d.ts
vendored
@ -34,3 +34,17 @@ declare module "emojilib" {
|
|||||||
const value: Record<string, string>;
|
const value: Record<string, string>;
|
||||||
export default value;
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -12,7 +12,7 @@ const ExtendedFormattedMessage: FC<ExtendedProps> = props => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const translatedMessage = formatMessage({ id, defaultMessage }, values);
|
const translatedMessage = formatMessage({ id, defaultMessage }, values);
|
||||||
if (typeof translatedMessage === "string") {
|
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]);
|
}, [id, defaultMessage, values, formatMessage]);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const Logo = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<h1 className="logo" onClick={() => navigate("/")}>
|
<h1 className="logo" onClick={() => navigate("/")}>
|
||||||
{process.env.APP_NAME}
|
{CONFIG.appNameCapitalized}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -145,7 +145,7 @@ export function LoginUnlock() {
|
|||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open {site}."
|
defaultMessage="Enter a pin to encrypt your private key, you must enter this pin every time you open {site}."
|
||||||
values={{
|
values={{
|
||||||
site: process.env.APP_NAME_CAPITALIZED,
|
site: CONFIG.appNameCapitalized,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./DisplayName.css";
|
import "./DisplayName.css";
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { HexKey, UserMetadata, NostrPrefix } from "@snort/system";
|
import { HexKey, UserMetadata, NostrPrefix } from "@snort/system";
|
||||||
import AnimalName from "Element/User/AnimalName";
|
import AnimalName from "Element/User/AnimalName";
|
||||||
import { hexToBech32 } from "SnortUtils";
|
import { hexToBech32 } from "SnortUtils";
|
||||||
@ -22,7 +22,7 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
|
|||||||
name = user.display_name;
|
name = user.display_name;
|
||||||
} else if (typeof user?.name === "string" && user.name.length > 0) {
|
} else if (typeof user?.name === "string" && user.name.length > 0) {
|
||||||
name = user.name;
|
name = user.name;
|
||||||
} else if (pubkey && process.env.ANIMAL_NAME_PLACEHOLDERS) {
|
} else if (pubkey && CONFIG.animalNamePlaceholders) {
|
||||||
name = AnimalName(pubkey);
|
name = AnimalName(pubkey);
|
||||||
isPlaceHolder = true;
|
isPlaceHolder = true;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ export default function useLoginFeed() {
|
|||||||
leaveOpen: true,
|
leaveOpen: true,
|
||||||
});
|
});
|
||||||
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]);
|
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().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]);
|
||||||
b.withFilter()
|
b.withFilter()
|
||||||
.relay("wss://relay.snort.social")
|
.relay("wss://relay.snort.social")
|
||||||
|
@ -94,13 +94,13 @@ const DonatePage = () => {
|
|||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Help fund the development of {site}"
|
defaultMessage="Help fund the development of {site}"
|
||||||
values={{ site: process.env.APP_NAME_CAPITALIZED }}
|
values={{ site: CONFIG.appNameCapitalized }}
|
||||||
/>
|
/>
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="{site} is an open source project built by passionate people in their free time"
|
defaultMessage="{site} is an open source project built by passionate people in their free time"
|
||||||
values={{ site: process.env.APP_NAME_CAPITALIZED }}
|
values={{ site: CONFIG.appNameCapitalized }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -209,7 +209,7 @@ function LogoHeader() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to="/" className="logo">
|
<Link to="/" className="logo">
|
||||||
<h1>{process.env.APP_NAME}</h1>
|
<h1>{CONFIG.appName}</h1>
|
||||||
{currentSubscription && (
|
{currentSubscription && (
|
||||||
<small className="flex">
|
<small className="flex">
|
||||||
<Icon name="diamond" size={10} className="mr5" />
|
<Icon name="diamond" size={10} className="mr5" />
|
||||||
|
@ -143,7 +143,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
function generateNip46() {
|
function generateNip46() {
|
||||||
const meta = {
|
const meta = {
|
||||||
name: process.env.APP_NAME_CAPITALIZED,
|
name: CONFIG.appNameCapitalized,
|
||||||
url: window.location.href,
|
url: window.location.href,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ export default function LoginPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="login-container">
|
<div className="login-container">
|
||||||
<h1 className="logo" onClick={() => navigate("/")}>
|
<h1 className="logo" onClick={() => navigate("/")}>
|
||||||
{process.env.APP_NAME}
|
{CONFIG.appName}
|
||||||
</h1>
|
</h1>
|
||||||
<h1 dir="auto">
|
<h1 dir="auto">
|
||||||
<FormattedMessage defaultMessage="Login" description="Login header" />
|
<FormattedMessage defaultMessage="Login" description="Login header" />
|
||||||
@ -342,7 +342,7 @@ export default function LoginPage() {
|
|||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Secure your private key with a PIN, ensuring enhanced protection on {site}. You'll be prompted to enter this PIN each time you access the site."
|
defaultMessage="Secure your private key with a PIN, ensuring enhanced protection on {site}. You'll be prompted to enter this PIN each time you access the site."
|
||||||
values={{
|
values={{
|
||||||
site: process.env.APP_NAME_CAPITALIZED,
|
site: CONFIG.appNameCapitalized,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
@ -25,7 +25,7 @@ export default function NostrLinkHandler() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const pubkey = await getNip05PubKey(`${link}@${process.env.NIP05_DOMAIN}`);
|
const pubkey = await getNip05PubKey(`${link}@${CONFIG.nip05Domain}`);
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
setRenderComponent(<ProfilePage id={pubkey} />); // Directly render ProfilePage
|
setRenderComponent(<ProfilePage id={pubkey} />); // Directly render ProfilePage
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,8 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.nip05 && user?.isNostrAddressValid) {
|
if (user?.nip05 && user?.isNostrAddressValid) {
|
||||||
if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) {
|
if (user.nip05.endsWith(`@${CONFIG.nip05Domain}`)) {
|
||||||
const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, "");
|
const username = user.nip05?.replace(`@${CONFIG.nip05Domain}`, "");
|
||||||
navigate(`/${username}`, { replace: true });
|
navigate(`/${username}`, { replace: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,11 @@ export default defineMessages({
|
|||||||
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
|
KeysSaved: { defaultMessage: "I have saved my keys, continue" },
|
||||||
WhatIsSnort: {
|
WhatIsSnort: {
|
||||||
defaultMessage: "What is {site} and how does it work?",
|
defaultMessage: "What is {site} and how does it work?",
|
||||||
values: { site: process.env.APP_NAME_CAPITALIZED },
|
values: { site: CONFIG.appNameCapitalized },
|
||||||
},
|
},
|
||||||
WhatIsSnortIntro: {
|
WhatIsSnortIntro: {
|
||||||
defaultMessage: `{site} is a Nostr UI, nostr is a decentralised protocol for saving and distributing "notes".`,
|
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: {
|
WhatIsSnortNotes: {
|
||||||
defaultMessage: `Notes hold text content, the most popular usage of these notes is to store "tweet like" messages.`,
|
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: {
|
WhatIsSnortExperience: {
|
||||||
defaultMessage: "{site} is designed to have a similar experience to Twitter.",
|
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?" },
|
HowKeysWork: { defaultMessage: "How do keys work?" },
|
||||||
DigitalSignatures: {
|
DigitalSignatures: {
|
||||||
@ -70,9 +70,9 @@ export default defineMessages({
|
|||||||
NameSquatting: {
|
NameSquatting: {
|
||||||
defaultMessage:
|
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.",
|
"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" },
|
GetSnortId: { defaultMessage: "Get a Snort identifier" },
|
||||||
GetSnortIdHelp: {
|
GetSnortIdHelp: {
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
|
@ -6,9 +6,9 @@ import Icon from "Icons/Icon";
|
|||||||
import { LoginStore, logout } from "Login";
|
import { LoginStore, logout } from "Login";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { getCurrentSubscription } from "Subscription";
|
import { getCurrentSubscription } from "Subscription";
|
||||||
|
import usePageWidth from "Hooks/usePageWidth";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import usePageWidth from "Hooks/usePageWidth";
|
|
||||||
|
|
||||||
const SettingsIndex = () => {
|
const SettingsIndex = () => {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
@ -61,11 +61,13 @@ const SettingsIndex = () => {
|
|||||||
<FormattedMessage defaultMessage="Nostr Adddress" />
|
<FormattedMessage defaultMessage="Nostr Adddress" />
|
||||||
<Icon name="arrowFront" size={16} />
|
<Icon name="arrowFront" size={16} />
|
||||||
</div>
|
</div>
|
||||||
<div className="settings-row" onClick={() => navigate("/subscribe/manage")}>
|
{CONFIG.features.subscriptions && (
|
||||||
<Icon name="diamond" size={24} />
|
<div className="settings-row" onClick={() => navigate("/subscribe/manage")}>
|
||||||
<FormattedMessage defaultMessage="Subscription" />
|
<Icon name="diamond" size={24} />
|
||||||
<Icon name="arrowFront" size={16} />
|
<FormattedMessage defaultMessage="Subscription" />
|
||||||
</div>
|
<Icon name="arrowFront" size={16} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{sub && (
|
{sub && (
|
||||||
<div className="settings-row" onClick={() => navigate("accounts")}>
|
<div className="settings-row" onClick={() => navigate("accounts")}>
|
||||||
<Icon name="code-circle" size={24} />
|
<Icon name="code-circle" size={24} />
|
||||||
|
@ -15,7 +15,7 @@ export class DonateTask extends BaseUITask {
|
|||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Thanks for using {site}, please consider donating if you can."
|
defaultMessage="Thanks for using {site}, please consider donating if you can."
|
||||||
values={{ site: process.env.APP_NAME_CAPITALIZED }}
|
values={{ site: CONFIG.appNameCapitalized }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<Link to="/donate">
|
<Link to="/donate">
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
ReqFilter,
|
ReqFilter,
|
||||||
PowMiner,
|
PowMiner,
|
||||||
NostrEvent,
|
NostrEvent,
|
||||||
|
mapEventToProfile,
|
||||||
} from "@snort/system";
|
} from "@snort/system";
|
||||||
import { SnortContext } from "@snort/system-react";
|
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<string>) => {
|
||||||
|
return (await Promise.all(keys.map(a => fetchProfile(a)))).filter(a => a !== undefined).map(a => unwrap(a));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton user profile loader
|
* Singleton user profile loader
|
||||||
*/
|
*/
|
||||||
@ -191,7 +215,7 @@ export const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
...NewUserRoutes,
|
...NewUserRoutes,
|
||||||
...WalletRoutes,
|
...WalletRoutes,
|
||||||
...SubscribeRoutes,
|
...(CONFIG.features.subscriptions ? SubscribeRoutes : []),
|
||||||
{
|
{
|
||||||
path: "/debug",
|
path: "/debug",
|
||||||
element: <DebugPage />,
|
element: <DebugPage />,
|
||||||
|
@ -88,11 +88,7 @@ const config = {
|
|||||||
})
|
})
|
||||||
: false,
|
: false,
|
||||||
new DefinePlugin({
|
new DefinePlugin({
|
||||||
"process.env.APP_NAME": JSON.stringify(appConfig.get("appName")),
|
CONFIG: JSON.stringify(appConfig),
|
||||||
"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")),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
|
@ -11,24 +11,6 @@ export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined {
|
|||||||
h => {
|
h => {
|
||||||
if (pubKey) {
|
if (pubKey) {
|
||||||
system.ProfileLoader.TrackMetadata(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);
|
const release = system.ProfileLoader.Cache.hook(h, pubKey);
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import { unixNowMs, FeedCache } from "@snort/shared";
|
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 { ProfileCacheExpire } from "./const";
|
||||||
import { mapEventToProfile, MetadataCache } from "./cache";
|
import { mapEventToProfile, MetadataCache } from "./cache";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
const MetadataRelays = ["wss://purplepag.es"];
|
const MetadataRelays = ["wss://purplepag.es"];
|
||||||
|
|
||||||
@ -23,6 +24,11 @@ export class ProfileLoaderService {
|
|||||||
|
|
||||||
readonly #log = debug("ProfileCache");
|
readonly #log = debug("ProfileCache");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom loader function for fetching profiles from alternative sources
|
||||||
|
*/
|
||||||
|
loaderFn?: (pubkeys: Array<string>) => Promise<Array<MetadataCache>>;
|
||||||
|
|
||||||
constructor(system: SystemInterface, cache: FeedCache<MetadataCache>) {
|
constructor(system: SystemInterface, cache: FeedCache<MetadataCache>) {
|
||||||
this.#system = system;
|
this.#system = system;
|
||||||
this.#cache = cache;
|
this.#cache = cache;
|
||||||
@ -92,50 +98,8 @@ export class ProfileLoaderService {
|
|||||||
if (missing.size > 0) {
|
if (missing.size > 0) {
|
||||||
this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length);
|
this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length);
|
||||||
|
|
||||||
const sub = new RequestBuilder("profiles");
|
const results = await this.#loadProfiles([...missing]);
|
||||||
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 newProfiles = new Set<string>();
|
|
||||||
const q = this.#system.Query<NoteCollection>(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<Readonly<Array<TaggedNostrEvent>>>(resolve => {
|
|
||||||
let timeout: ReturnType<typeof setTimeout> | 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));
|
const couldNotFetch = [...missing].filter(a => !results.some(b => b.pubkey === a));
|
||||||
this.#missingLastRun = new Set(couldNotFetch);
|
this.#missingLastRun = new Set(couldNotFetch);
|
||||||
if (couldNotFetch.length > 0) {
|
if (couldNotFetch.length > 0) {
|
||||||
@ -150,12 +114,43 @@ export class ProfileLoaderService {
|
|||||||
await Promise.all(empty);
|
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
|
// onEvent is not fired and the loaded timestamp never gets updated
|
||||||
const expiredSame = results.filter(a => !newProfiles.has(a.id) && expired.includes(a.pubkey));
|
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);
|
setTimeout(() => this.#FetchMetadata(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #loadProfiles(missing: Array<string>) {
|
||||||
|
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<TaggedNostrEvent>;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user