useCachedFetch hook for trending api calls

This commit is contained in:
Martti Malmi
2023-12-26 20:15:26 +02:00
parent 6b88df96ab
commit f70d752fae
9 changed files with 167 additions and 265 deletions

View File

@ -1,105 +1,20 @@
import { throwIfOffline } from "@snort/shared";
import { NostrEvent } from "@snort/system";
export interface TrendingUser {
pubkey: string;
}
export interface TrendingUserResponse {
profiles: Array<TrendingUser>;
}
export interface TrendingNote {
event: NostrEvent;
author: NostrEvent; // kind0 event
}
export interface TrendingNoteResponse {
notes: Array<TrendingNote>;
}
export interface TrendingHashtagsResponse {
hashtags: Array<{
hashtag: string;
posts: number;
}>;
}
export interface SuggestedFollow {
pubkey: string;
}
export interface SuggestedFollowsResponse {
profiles: Array<SuggestedFollow>;
}
export class NostrBandError extends Error {
body: string;
statusCode: number;
constructor(message: string, body: string, status: number) {
super(message);
this.body = body;
this.statusCode = status;
}
}
export default class NostrBandApi {
readonly #url = "https://api.nostr.band";
readonly #supportedLangs = ["en", "de", "ja", "zh", "th", "pt", "es", "fr"];
async trendingProfiles() {
return await this.#json<TrendingUserResponse>("GET", "/v0/trending/profiles");
trendingProfilesUrl() {
return `${this.#url}/v0/trending/profiles`;
}
async trendingNotes(lang?: string) {
if (lang && this.#supportedLangs.includes(lang)) {
return await this.#json<TrendingNoteResponse>("GET", `/v0/trending/notes?lang=${lang}`);
}
return await this.#json<TrendingNoteResponse>("GET", "/v0/trending/notes");
trendingNotesUrl(lang?: string) {
return `${this.#url}/v0/trending/notes${lang && this.#supportedLangs.includes(lang) ? `?lang=${lang}` : ""}`;
}
async sugguestedFollows(pubkey: string) {
return await this.#json<SuggestedFollowsResponse>("GET", `/v0/suggested/profiles/${pubkey}`);
suggestedFollowsUrl(pubkey: string) {
return `${this.#url}/v0/suggested/profiles/${pubkey}`;
}
async trendingHashtags(lang?: string) {
if (lang && this.#supportedLangs.includes(lang)) {
return await this.#json<TrendingHashtagsResponse>("GET", `/v0/trending/hashtags?lang=${lang}`);
}
return await this.#json<TrendingHashtagsResponse>("GET", "/v0/trending/hashtags");
}
async #json<T>(method: string, path: string) {
throwIfOffline();
const storageKey = `nostr-band-${path}`;
const cachedData = localStorage.getItem(storageKey);
if (cachedData) {
const parsedData = JSON.parse(cachedData);
const { timestamp, data } = parsedData;
const ageInMinutes = (new Date().getTime() - timestamp) / 1000 / 60;
if (ageInMinutes < 15) {
return data as T;
}
}
try {
const res = await fetch(`${this.#url}${path}`, { method: method ?? "GET" });
if (res.ok) {
const data = (await res.json()) as T;
// Cache the new data with a timestamp
localStorage.setItem(storageKey, JSON.stringify({ data, timestamp: new Date().getTime() }));
return data;
} else {
throw new NostrBandError("Failed to load content from nostr.band", await res.text(), res.status);
}
} catch (error) {
if (cachedData) {
return JSON.parse(cachedData).data as T;
} else {
throw error;
}
}
trendingHashtagsUrl(lang?: string) {
return `${this.#url}/v0/trending/hashtags${lang && this.#supportedLangs.includes(lang) ? `?lang=${lang}` : ""}`;
}
}

View File

@ -1,46 +1,8 @@
import { throwIfOffline } from "@snort/shared";
export interface RecommendedProfilesResponse {
quality: number;
recommendations: Array<[pubkey: string, score: number]>;
}
export class SemisolDevApiError extends Error {
body: string;
statusCode: number;
constructor(message: string, body: string, status: number) {
super(message);
this.body = body;
this.statusCode = status;
}
}
export default class SemisolDevApi {
readonly #url = "https://api.semisol.dev";
async sugguestedFollows(pubkey: string, follows: Array<string>) {
return await this.#json<RecommendedProfilesResponse>("POST", "/nosgraph/v1/recommend", {
pubkey,
exclude: [],
following: follows,
});
}
async #json<T>(method: string, path: string, body?: unknown) {
throwIfOffline();
const url = `${this.#url}${path}`;
const res = await fetch(url, {
method: method ?? "GET",
body: body ? JSON.stringify(body) : undefined,
headers: {
...(body ? { "content-type": "application/json" } : {}),
},
});
if (res.ok) {
return (await res.json()) as T;
} else {
throw new SemisolDevApiError(`Failed to load content from ${url}`, await res.text(), res.status);
}
suggestedFollowsUrl(pubkey: string, follows: Array<string>) {
const query = new URLSearchParams({ pubkey, follows: JSON.stringify(follows) });
return `${this.#url}/nosgraph/v1/recommend?${query.toString()}`;
}
}