From 7bf86d618d562311690727d3be92eefdd7c83152 Mon Sep 17 00:00:00 2001 From: Juan Date: Sat, 23 Nov 2024 03:54:02 -0300 Subject: [PATCH 1/5] Fix message when error not Error --- src/loaders/compass.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/loaders/compass.ts b/src/loaders/compass.ts index fda767a..e47b479 100644 --- a/src/loaders/compass.ts +++ b/src/loaders/compass.ts @@ -52,7 +52,7 @@ export async function requestCompass(query: string, variables: Record Date: Sat, 23 Nov 2024 03:54:11 -0300 Subject: [PATCH 2/5] Change compass limit to 1000 --- src/models/compass.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/compass.ts b/src/models/compass.ts index a473245..f4c7768 100644 --- a/src/models/compass.ts +++ b/src/models/compass.ts @@ -1,4 +1,4 @@ export const COMPASS_URL = process.env.REACT_APP_COMPASS_URL ?? 'https://public.compass.poap.tech/v1/graphql' export const COMPASS_KEY = process.env.REACT_APP_COMPASS_KEY -export const DEFAULT_COMPASS_LIMIT = 100 +export const DEFAULT_COMPASS_LIMIT = 1000 From c5d39386206c2f9c7cc6fe0492747f8fd00eea7c Mon Sep 17 00:00:00 2001 From: Juan Date: Sun, 24 Nov 2024 00:58:11 -0300 Subject: [PATCH 3/5] No caching / remote force --- src/components/Status.tsx | 6 +-- src/hooks/useEventInCommon.ts | 73 ++++++++++++-------------- src/hooks/useEventsInCommon.ts | 96 +++++----------------------------- src/loaders/api.ts | 65 ++++------------------- src/models/api.ts | 8 +++ src/pages/Event.tsx | 29 ++-------- src/pages/Events.tsx | 17 ++---- src/styles/status.css | 6 --- 8 files changed, 70 insertions(+), 230 deletions(-) diff --git a/src/components/Status.tsx b/src/components/Status.tsx index 5414211..dc270d9 100644 --- a/src/components/Status.tsx +++ b/src/components/Status.tsx @@ -3,26 +3,22 @@ import 'styles/status.css' function Status({ loading, - caching, error, }: { loading?: boolean - caching?: boolean error?: boolean }) { - if (!loading && !caching && !error) { + if (!loading && !error) { return null } return (
{loading && 'Loading'} - {caching && 'Caching'} {error && 'Error'}
) diff --git a/src/hooks/useEventInCommon.ts b/src/hooks/useEventInCommon.ts index af62f1a..7202567 100644 --- a/src/hooks/useEventInCommon.ts +++ b/src/hooks/useEventInCommon.ts @@ -1,14 +1,19 @@ import { useCallback, useEffect, useState } from 'react' import { AbortedError } from 'models/error' -import { filterInCommon } from 'models/in-common' import { Drop } from 'models/drop' import { POAP } from 'models/poap' import { Progress } from 'models/http' import { InCommon } from 'models/api' -import { getInCommonEventsWithProgress, putEventInCommon } from 'loaders/api' +import { filterInCommon } from 'models/in-common' +import { getInCommonEventsWithProgress } from 'loaders/api' import { scanAddress } from 'loaders/poap' -function useEventInCommon(eventId: number, owners: string[], force: boolean = false): { +function useEventInCommon( + eventId: number, + owners: string[], + force: boolean = false, + local: boolean = false, +): { completedEventInCommon: boolean loadingEventInCommon: boolean loadedInCommonProgress: { progress: number; estimated: number | null; rate: number | null } | null @@ -16,8 +21,6 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa ownersErrors: Array<{ address: string; error: Error }> inCommon: InCommon events: Record - caching: boolean - cachingError: Error | null cachedTs: number | null fetchEventInCommon: () => () => void retryAddress: (address: string) => void @@ -29,8 +32,6 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa const [errors, setErrors] = useState>([]) const [inCommon, setInCommon] = useState({}) const [events, setEvents] = useState>({}) - const [caching, setCaching] = useState(false) - const [cachingError, setCachingError] = useState(null) const [cachedTs, setCachedTs] = useState(null) useEffect( @@ -39,22 +40,7 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa const inCommonProcessed = filterInCommon(inCommon) if (Object.keys(inCommonProcessed).length > 0) { - setCaching(true) - setCachingError(null) - putEventInCommon(eventId, inCommonProcessed).then( - () => { - setCaching(false) - setCachedTs(Math.trunc(Date.now() / 1000)) - }, - (err) => { - console.error(err) - setCaching(false) - setCachingError(new Error( - 'Could not cache drop', - { cause: err } - )) - } - ) + setCachedTs(Math.trunc(Date.now() / 1000)) } } }, @@ -72,6 +58,21 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa setErrors((prevErrors) => [...prevErrors, { address, error }]) } + function removeError(address: string): void { + setErrors((prevErrors) => { + if (prevErrors == null) { + return [] + } + const newErrors = [] + for (const { error, address: errorAddress } of prevErrors) { + if (errorAddress !== address) { + newErrors.push({ error, address: errorAddress }) + } + } + return newErrors + }) + } + const fetchAddressInCommon = useCallback( async (address: string, abortSignal: AbortSignal) => { let tokens: POAP[] @@ -136,7 +137,7 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa {} ) setCompleted(false) - if (force) { + if (local) { fetchOwnersInCommon(controllers).finally(() => { setCompleted(true) }) @@ -157,7 +158,8 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa } else { setLoadedProgress(null) } - } + }, + /*refresh*/force ).then( (result) => { setLoadedProgress(null) @@ -193,24 +195,15 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa setInCommon({}) } }, - [eventId, owners, force, fetchOwnersInCommon] + [eventId, owners, force, local, fetchOwnersInCommon] ) function retryAddress(address: string): () => void { - setErrors((prevErrors) => { - if (prevErrors == null) { - return [] - } - const newErrors = [] - for (const { error, address: errorAddress } of prevErrors) { - if (errorAddress !== address) { - newErrors.push({ error, address: errorAddress }) - } - } - return newErrors - }) + removeError(address) const controller = new AbortController() - fetchAddressInCommon(address, controller.signal) + fetchAddressInCommon(address, controller.signal).catch((err) => { + addError(address, err) + }) return () => { controller.abort() } @@ -224,8 +217,6 @@ function useEventInCommon(eventId: number, owners: string[], force: boolean = fa ownersErrors: errors, inCommon, events, - caching, - cachingError, cachedTs, fetchEventInCommon, retryAddress, diff --git a/src/hooks/useEventsInCommon.ts b/src/hooks/useEventsInCommon.ts index 8bb8c87..f4a4c57 100644 --- a/src/hooks/useEventsInCommon.ts +++ b/src/hooks/useEventsInCommon.ts @@ -1,24 +1,19 @@ import { useCallback, useEffect, useState } from 'react' -import { getInCommonEventsWithProgress, putEventInCommon } from 'loaders/api' +import { getInCommonEventsWithProgress } from 'loaders/api' import { scanAddress } from 'loaders/poap' import { AbortedError } from 'models/error' -import { filterInCommon } from 'models/in-common' import { POAP } from 'models/poap' import { Drop } from 'models/drop' import { Progress } from 'models/http' -import { InCommon } from 'models/api' - -interface EventsInCommon { - events: Record - inCommon: InCommon - ts: number | null -} +import { EventsInCommon, InCommon } from 'models/api' +import { filterInCommon } from 'models/in-common' function useEventsInCommon( eventIds: number[], eventsOwners: InCommon, all: boolean = false, force: boolean = false, + local: boolean = false, ): { completedEventsInCommon: boolean completedInCommonEvents: Record @@ -27,8 +22,6 @@ function useEventsInCommon( loadedEventsProgress: Record loadedEventsOwners: Record eventsInCommon: Record - cachingEvents: Record - cachingEventsErrors: Record fetchEventsInCommon: () => () => void retryEventAddressInCommon: (eventId: number, address: string) => () => void } { @@ -38,8 +31,6 @@ function useEventsInCommon( const [loadedProgress, setLoadedProgress] = useState>({}) const [loadedOwners, setLoadedOwners] = useState>({}) const [inCommon, setInCommon] = useState>({}) - const [caching, setCaching] = useState>({}) - const [cachingErrors, setCachingErrors] = useState>({}) useEffect( () => { @@ -48,36 +39,18 @@ function useEventsInCommon( completed[eventId] && (loadedOwners[eventId] ?? 0) === eventsOwners[eventId].length && inCommon[eventId] != null && - inCommon[eventId].ts == null && - !caching[eventId] + inCommon[eventId].ts == null ) { const inCommonProcessed = filterInCommon( inCommon[eventId].inCommon ) if (Object.keys(inCommonProcessed).length > 0) { - removeCachingError(eventId) - addCaching(eventId) - putEventInCommon(eventId, inCommonProcessed).then( - () => { - updateCachedTs(eventId) - removeCaching(eventId) - }, - (err) => { - removeCaching(eventId) - if (!(err instanceof AbortedError)) { - console.error(err) - updateCachingError( - eventId, - new Error('Could not cache drop', { cause: err }) - ) - } - } - ) + updateCachedTs(eventId) } } } }, - [eventIds, eventsOwners, loadedOwners, completed, caching, inCommon] + [eventIds, eventsOwners, loadedOwners, completed, inCommon] ) function addCompleted(eventId: number): void { @@ -323,50 +296,6 @@ function useEventsInCommon( })) } - function addCaching(eventId: number): void { - setCaching((alsoCaching) => ({ - ...alsoCaching, - [eventId]: true, - })) - } - - function removeCaching(eventId: number): void { - setCaching((alsoCaching) => { - if (alsoCaching == null) { - return {} - } - const newCaching: Record = {} - for (const [cachingEventId, caching] of Object.entries(alsoCaching)) { - if (String(eventId) !== String(cachingEventId)) { - newCaching[cachingEventId] = caching - } - } - return newCaching - }) - } - - function updateCachingError(eventId: number, err: Error): void { - setCachingErrors((prevErrors) => ({ - ...prevErrors, - [eventId]: err, - })) - } - - function removeCachingError(eventId: number): void { - setCachingErrors((alsoErrors) => { - if (alsoErrors == null) { - return {} - } - const newErrors: Record = {} - for (const [errorEventId, error] of Object.entries(alsoErrors)) { - if (String(eventId) !== String(errorEventId)) { - newErrors[errorEventId] = error - } - } - return newErrors - }) - } - function updateCachedTs(eventId: number, ts?: number): void { if (ts == null) { ts = Math.trunc(Date.now() / 1000) @@ -449,13 +378,13 @@ function useEventsInCommon( controllers: Record, controller: AbortController, ) => { - if (force) { + if (local) { await processEvent(eventId, addresses, controllers) } else { removeCompleted(eventId) addLoading(eventId) addLoadedProgress(eventId) - let result + let result: EventsInCommon | null = null try { result = await getInCommonEventsWithProgress( eventId, @@ -466,7 +395,8 @@ function useEventsInCommon( } else { removeLoadedProgress(eventId) } - } + }, + /*refresh*/force ) } catch (err: unknown) { removeLoadedProgress(eventId) @@ -491,7 +421,7 @@ function useEventsInCommon( } } }, - [force, processEvent] + [force, local, processEvent] ) const fetchEventsInCommon = useCallback( @@ -570,8 +500,6 @@ function useEventsInCommon( loadedEventsProgress: loadedProgress, loadedEventsOwners: loadedOwners, eventsInCommon: inCommon, - cachingEvents: caching, - cachingEventsErrors: cachingErrors, fetchEventsInCommon, retryEventAddressInCommon, } diff --git a/src/loaders/api.ts b/src/loaders/api.ts index 85eee7f..553c4eb 100644 --- a/src/loaders/api.ts +++ b/src/loaders/api.ts @@ -5,8 +5,8 @@ import { FAMILY_API_URL, parseInCommon, CachedEvent, - InCommon, Feedback, + EventsInCommon, } from 'models/api' import { encodeExpiryDates } from 'models/event' import { parseDrop, parseDropMetrics, parseDropOwners, Drop, DropMetrics, DropOwners } from 'models/drop' @@ -112,57 +112,11 @@ export async function getEventAndOwners( } } -export async function putEventInCommon( - eventId: number, - inCommon: InCommon, -): Promise { - if (!FAMILY_API_KEY) { - throw new Error( - `Last in common drops (${eventId}) could not be put, ` + - `configure Family API key` - ) - } - - let response: Response - - try { - response = await fetch( - `${FAMILY_API_URL}/event/${eventId}/in-common`, - { - method: 'PUT', - headers: { - 'x-api-key': FAMILY_API_KEY, - 'content-type': 'application/json', - }, - body: JSON.stringify(inCommon), - } - ) - } catch (err: unknown) { - if (err instanceof Error && err.name === 'AbortError') { - throw new AbortedError(`Put drop in common aborted`, { cause: err }) - } - throw new Error( - `Cannot put drop in common: response was not success (network error)`, - { cause: err } - ) - } - - if (response.status !== 200 && response.status !== 201) { - throw new HttpError( - `Drop ${eventId} in common save failed (status ${response.status})`, - { status: response.status } - ) - } -} - export async function getInCommonEvents( eventId: number, abortSignal: AbortSignal, -): Promise<{ - inCommon: InCommon - events: Record - ts: number -} | null> { + refresh: boolean = false, +): Promise { if (!FAMILY_API_KEY) { throw new Error( `Last in common drops (${eventId}) could not be fetched, ` + @@ -174,7 +128,8 @@ export async function getInCommonEvents( try { response = await fetch( - `${FAMILY_API_URL}/event/${eventId}/in-common`, + `${FAMILY_API_URL}/event/${eventId}` + + `/in-common${refresh ? '?refresh=true' : ''}`, { signal: abortSignal instanceof AbortSignal ? abortSignal : null, headers: { @@ -243,11 +198,8 @@ export async function getInCommonEventsWithProgress( eventId: number, abortSignal: AbortSignal, onProgress: (progressEvent: Partial) => void, -): Promise<{ - inCommon: InCommon - events: Record - ts: number -} | null> { + refresh: boolean = false, +): Promise { if (!FAMILY_API_KEY) { throw new Error( `Last in common drops (${eventId}) could not be fetched, ` + @@ -259,7 +211,8 @@ export async function getInCommonEventsWithProgress( try { response = await axios.get( - `${FAMILY_API_URL}/event/${eventId}/in-common`, + `${FAMILY_API_URL}/event/${eventId}` + + `/in-common${refresh ? '?refresh=true' : ''}`, { signal: abortSignal instanceof AbortSignal ? abortSignal : undefined, onDownloadProgress: onProgress, diff --git a/src/models/api.ts b/src/models/api.ts index f61f311..a0d01f3 100644 --- a/src/models/api.ts +++ b/src/models/api.ts @@ -1,8 +1,16 @@ +import { Drop } from './drop' + export const FAMILY_API_URL = process.env.REACT_APP_FAMILY_API_URL ?? 'https://api.poap.family' export const FAMILY_API_KEY = process.env.REACT_APP_FAMILY_API_KEY export type InCommon = Record +export interface EventsInCommon { + events: Record + inCommon: InCommon + ts: number | null +} + export function parseInCommon(inCommon: unknown): InCommon { if ( inCommon == null || diff --git a/src/pages/Event.tsx b/src/pages/Event.tsx index 94970cb..6d43908 100644 --- a/src/pages/Event.tsx +++ b/src/pages/Event.tsx @@ -19,7 +19,6 @@ import AddressErrorList from 'components/AddressErrorList' import WarningMessage from 'components/WarningMessage' import ErrorMessage from 'components/ErrorMessage' import ButtonLink from 'components/ButtonLink' -import Progress from 'components/Progress' import ButtonExportAddressCsv from 'components/ButtonExportAddressCsv' import EventButtonGroup from 'components/EventButtonGroup' import EventButtonMoments from 'components/EventButtonMoments' @@ -52,12 +51,10 @@ function Event() { ownersErrors, inCommon, events, - caching, - cachingError, cachedTs, fetchEventInCommon, retryAddress, - } = useEventInCommon(event.id, owners, force) + } = useEventInCommon(event.id, owners, force, /*local*/false) const eventIds = useMemo( () => [event.id], @@ -99,7 +96,7 @@ function Event() { useEffect( () => { - let cancelEventsCollections + let cancelEventsCollections: () => void | undefined if ( metrics && metrics.collectionsIncludes > 0 && @@ -158,31 +155,13 @@ function Event() { /> - {caching && -
- Caching{' '} -
- } - {cachingError && -
- Error - {cachingError.cause - ? ( - - {cachingError.message} - - ) - : cachingError.message - } -
- } - {cachedTs && !caching && + {cachedTs &&
Cached ,{' '} refreshCache()}>refresh.
} - {!cachedTs && !caching && metrics && metrics.ts && + {!cachedTs && metrics && metrics.ts &&
Cached ,{' '} refreshCache()}>refresh. diff --git a/src/pages/Events.tsx b/src/pages/Events.tsx index 71c355a..7400807 100644 --- a/src/pages/Events.tsx +++ b/src/pages/Events.tsx @@ -81,11 +81,9 @@ function Events() { loadedEventsProgress, loadedEventsOwners, eventsInCommon, - cachingEvents, - cachingEventsErrors, fetchEventsInCommon, retryEventAddressInCommon, - } = useEventsInCommon(eventIds, eventsOwners, all, force) + } = useEventsInCommon(eventIds, eventsOwners, all, force, /*local*/false) const { loadingCollections, @@ -123,7 +121,7 @@ function Events() { useEffect( () => { - let cancelEventsInCommon + let cancelEventsInCommon: () => void | undefined if (completedEventsOwnersAndMetrics) { cancelEventsInCommon = fetchEventsInCommon() } @@ -138,7 +136,7 @@ function Events() { useEffect( () => { - let cancelEventsCollections + let cancelEventsCollections: () => void | undefined if (completedEventsInCommon) { cancelEventsCollections = fetchEventsCollections() } @@ -338,13 +336,9 @@ function Events() { loading={ loadingInCommonEvents[event.id] } - caching={ - cachingEvents[event.id] - } error={ eventsOwnersAndMetricsErrors[event.id] != null || - eventsInCommonErrors[event.id] != null || - cachingEventsErrors[event.id] != null + eventsInCommonErrors[event.id] != null } /> {eventsOwnersAndMetricsErrors[event.id] != null && ( @@ -388,9 +382,6 @@ function Events() { rate={loadedEventsProgress[event.id].rate} /> )} - {cachingEvents[event.id] != null && ( - - )} {eventsInCommonErrors[event.id] != null && Object.entries( eventsInCommonErrors[event.id] ).map( diff --git a/src/styles/status.css b/src/styles/status.css index 0b5184f..71d6166 100644 --- a/src/styles/status.css +++ b/src/styles/status.css @@ -12,12 +12,6 @@ border: 1px dashed #D4BE74; } -.status-caching { - color: white; - background-color: slateblue; - border: 1px dashed darkslateblue; -} - .status-error { background-color: #E9A1B6; color: #F8D1D2; From d048f716b14b672240065f4e4cfc95ad45d97b95 Mon Sep 17 00:00:00 2001 From: Juan Date: Sun, 24 Nov 2024 05:38:20 -0300 Subject: [PATCH 4/5] Stream --- src/hooks/useEventInCommon.ts | 63 +++++++++----- src/hooks/useEventsInCommon.ts | 85 ++++++++++++++----- src/hooks/useEventsOwnersAndMetrics.ts | 4 +- src/loaders/api.ts | 111 +++++++++++++++++++++++-- src/models/api.ts | 21 +++-- src/models/http.ts | 9 +- src/pages/Event.tsx | 36 +++++--- src/pages/Events.tsx | 26 +++++- 8 files changed, 283 insertions(+), 72 deletions(-) diff --git a/src/hooks/useEventInCommon.ts b/src/hooks/useEventInCommon.ts index 7202567..bc8b764 100644 --- a/src/hooks/useEventInCommon.ts +++ b/src/hooks/useEventInCommon.ts @@ -2,10 +2,10 @@ import { useCallback, useEffect, useState } from 'react' import { AbortedError } from 'models/error' import { Drop } from 'models/drop' import { POAP } from 'models/poap' -import { Progress } from 'models/http' +import { CountProgress, DownloadProgress } from 'models/http' import { InCommon } from 'models/api' import { filterInCommon } from 'models/in-common' -import { getInCommonEventsWithProgress } from 'loaders/api' +import { getInCommonEventsWithEvents, getInCommonEventsWithProgress } from 'loaders/api' import { scanAddress } from 'loaders/poap' function useEventInCommon( @@ -13,10 +13,12 @@ function useEventInCommon( owners: string[], force: boolean = false, local: boolean = false, + stream: boolean = false, ): { completedEventInCommon: boolean loadingEventInCommon: boolean - loadedInCommonProgress: { progress: number; estimated: number | null; rate: number | null } | null + loadedInCommon: CountProgress | null + loadedInCommonDownload: DownloadProgress | null loadedOwners: number ownersErrors: Array<{ address: string; error: Error }> inCommon: InCommon @@ -27,7 +29,8 @@ function useEventInCommon( } { const [completed, setCompleted] = useState(false) const [loading, setLoading] = useState(false) - const [loadedProgress, setLoadedProgress] = useState(null) + const [loadedInCommon, setLoadedInCommon] = useState(null) + const [loadedProgress, setLoadedProgress] = useState(null) const [loadedOwners, setLoadedOwners] = useState(0) const [errors, setErrors] = useState>([]) const [inCommon, setInCommon] = useState({}) @@ -144,24 +147,39 @@ function useEventInCommon( } else { setLoading(true) setLoadedOwners(0) + setLoadedInCommon(null) setLoadedProgress(null) - getInCommonEventsWithProgress( - eventId, - /*abortSignal*/undefined, - /*onProgress*/({ progress, estimated, rate }) => { - if (progress != null) { - setLoadedProgress({ - progress, - estimated: estimated ?? null, - rate: rate ?? null, - }) - } else { - setLoadedProgress(null) - } - }, - /*refresh*/force + ;( + stream + ? getInCommonEventsWithEvents( + eventId, + /*refresh*/force, + /*onProgress*/(received, total) => { + setLoadedInCommon({ + count: received, + total, + }) + }, + ) + : getInCommonEventsWithProgress( + eventId, + /*abortSignal*/undefined, + /*onProgress*/({ progress, estimated, rate }) => { + if (progress != null) { + setLoadedProgress({ + progress, + estimated: estimated ?? null, + rate: rate ?? null, + }) + } else { + setLoadedProgress(null) + } + }, + /*refresh*/force + ) ).then( (result) => { + setLoadedInCommon(null) setLoadedProgress(null) if (!result) { return fetchOwnersInCommon(controllers) @@ -175,6 +193,7 @@ function useEventInCommon( setCachedTs(result.ts) }, (err) => { + setLoadedInCommon(null) setLoadedProgress(null) console.error(err) return fetchOwnersInCommon(controllers) @@ -189,13 +208,14 @@ function useEventInCommon( } setCompleted(false) setLoading(false) + setLoadedInCommon(null) setLoadedProgress(null) setLoadedOwners(0) setErrors([]) setInCommon({}) } }, - [eventId, owners, force, local, fetchOwnersInCommon] + [eventId, owners, force, local, stream, fetchOwnersInCommon] ) function retryAddress(address: string): () => void { @@ -212,7 +232,8 @@ function useEventInCommon( return { completedEventInCommon: completed, loadingEventInCommon: loading, - loadedInCommonProgress: loadedProgress, + loadedInCommon, + loadedInCommonDownload: loadedProgress, loadedOwners, ownersErrors: errors, inCommon, diff --git a/src/hooks/useEventsInCommon.ts b/src/hooks/useEventsInCommon.ts index f4a4c57..42fe01d 100644 --- a/src/hooks/useEventsInCommon.ts +++ b/src/hooks/useEventsInCommon.ts @@ -1,10 +1,10 @@ import { useCallback, useEffect, useState } from 'react' -import { getInCommonEventsWithProgress } from 'loaders/api' +import { getInCommonEventsWithEvents, getInCommonEventsWithProgress } from 'loaders/api' import { scanAddress } from 'loaders/poap' import { AbortedError } from 'models/error' import { POAP } from 'models/poap' import { Drop } from 'models/drop' -import { Progress } from 'models/http' +import { CountProgress, DownloadProgress } from 'models/http' import { EventsInCommon, InCommon } from 'models/api' import { filterInCommon } from 'models/in-common' @@ -14,12 +14,14 @@ function useEventsInCommon( all: boolean = false, force: boolean = false, local: boolean = false, + stream: boolean = false, ): { completedEventsInCommon: boolean completedInCommonEvents: Record loadingInCommonEvents: Record eventsInCommonErrors: Record> - loadedEventsProgress: Record + loadedEventsInCommon: Record + loadedEventsProgress: Record loadedEventsOwners: Record eventsInCommon: Record fetchEventsInCommon: () => () => void @@ -28,7 +30,8 @@ function useEventsInCommon( const [completed, setCompleted] = useState>({}) const [loading, setLoading] = useState>({}) const [errors, setErrors] = useState>>({}) - const [loadedProgress, setLoadedProgress] = useState>({}) + const [loadedInCommon, setLoadedInCommon] = useState>({}) + const [loadedProgress, setLoadedProgress] = useState>({}) const [loadedOwners, setLoadedOwners] = useState>({}) const [inCommon, setInCommon] = useState>({}) @@ -141,6 +144,31 @@ function useEventsInCommon( }) } + function updateLoadedInCommon( + eventId: number, + { count, total }: CountProgress, + ) { + setLoadedInCommon((prevLoadedInCommon) => ({ + ...(prevLoadedInCommon ?? {}), + [eventId]: { count, total }, + })) + } + + function removeLoadedInCommon(eventId: number): void { + setLoadedInCommon((prevLoadedInCommon) => { + if (prevLoadedInCommon == null) { + return {} + } + const newProgress: Record = {} + for (const [loadingEventId, progress] of Object.entries(prevLoadedInCommon)) { + if (String(eventId) !== String(loadingEventId)) { + newProgress[loadingEventId] = progress + } + } + return newProgress + }) + } + function addLoadedProgress(eventId: number): void { setLoadedProgress((alsoProgress) => ({ ...alsoProgress, @@ -154,7 +182,7 @@ function useEventsInCommon( function updateLoadedProgress( eventId: number, - { progress, estimated, rate }: Progress, + { progress, estimated, rate }: DownloadProgress, ): void { setLoadedProgress((alsoProgress) => { if (alsoProgress[eventId] != null) { @@ -176,7 +204,7 @@ function useEventsInCommon( if (alsoProgress == null) { return {} } - const newProgress: Record = {} + const newProgress: Record = {} for (const [loadingEventId, progress] of Object.entries(alsoProgress)) { if (String(eventId) !== String(loadingEventId)) { newProgress[loadingEventId] = progress @@ -383,22 +411,35 @@ function useEventsInCommon( } else { removeCompleted(eventId) addLoading(eventId) - addLoadedProgress(eventId) let result: EventsInCommon | null = null try { - result = await getInCommonEventsWithProgress( - eventId, - controller.signal, - /*onProgress*/({ progress, estimated, rate }) => { - if (progress != null) { - updateLoadedProgress(eventId, { progress, estimated, rate }) - } else { - removeLoadedProgress(eventId) - } - }, - /*refresh*/force - ) + if (stream) { + result = await getInCommonEventsWithEvents( + eventId, + /*refresh*/force, + /*onProgress*/(received, total) => { + updateLoadedInCommon(eventId, { count: received, total }) + }, + ) + removeLoadedInCommon(eventId) + } else { + addLoadedProgress(eventId) + result = await getInCommonEventsWithProgress( + eventId, + controller.signal, + /*onProgress*/({ progress, estimated, rate }) => { + if (progress != null) { + updateLoadedProgress(eventId, { progress, estimated, rate }) + } else { + removeLoadedProgress(eventId) + } + }, + /*refresh*/force + ) + removeLoadedProgress(eventId) + } } catch (err: unknown) { + removeLoadedInCommon(eventId) removeLoadedProgress(eventId) if (!(err instanceof AbortedError)) { console.error(err) @@ -408,6 +449,7 @@ function useEventsInCommon( } return } + removeLoadedInCommon(eventId) removeLoadedProgress(eventId) if (result == null) { await processEvent(eventId, addresses, controllers) @@ -421,7 +463,7 @@ function useEventsInCommon( } } }, - [force, local, processEvent] + [force, local, stream, processEvent] ) const fetchEventsInCommon = useCallback( @@ -430,6 +472,7 @@ function useEventsInCommon( setCompleted({}) setLoading({}) setErrors({}) + setLoadedInCommon({}) setLoadedProgress({}) setLoadedOwners({}) setInCommon({}) @@ -469,6 +512,7 @@ function useEventsInCommon( setCompleted({}) setLoading({}) setErrors({}) + setLoadedInCommon({}) setLoadedProgress({}) setLoadedOwners({}) setInCommon({}) @@ -497,6 +541,7 @@ function useEventsInCommon( completedInCommonEvents: completed, loadingInCommonEvents: loading, eventsInCommonErrors: errors, + loadedEventsInCommon: loadedInCommon, loadedEventsProgress: loadedProgress, loadedEventsOwners: loadedOwners, eventsInCommon: inCommon, diff --git a/src/hooks/useEventsOwnersAndMetrics.ts b/src/hooks/useEventsOwnersAndMetrics.ts index f2d227d..7c819a1 100644 --- a/src/hooks/useEventsOwnersAndMetrics.ts +++ b/src/hooks/useEventsOwnersAndMetrics.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react' import { filterInvalidOwners } from 'models/address' import { AbortedError } from 'models/error' -import { InCommon } from 'models/api' +import { EventAndOwners, InCommon } from 'models/api' import { fetchPOAPs } from 'loaders/poap' import { getEventAndOwners, @@ -117,7 +117,7 @@ function useEventsOwnersAndMetrics(eventIds: number[], expiryDates: Record { removeError(eventId) addLoading(eventId) - let eventAndOwners + let eventAndOwners: EventAndOwners | null = null try { eventAndOwners = await getEventAndOwners( eventId, diff --git a/src/loaders/api.ts b/src/loaders/api.ts index 553c4eb..a2e58b7 100644 --- a/src/loaders/api.ts +++ b/src/loaders/api.ts @@ -7,11 +7,12 @@ import { CachedEvent, Feedback, EventsInCommon, + EventAndOwners, } from 'models/api' import { encodeExpiryDates } from 'models/event' import { parseDrop, parseDropMetrics, parseDropOwners, Drop, DropMetrics, DropOwners } from 'models/drop' import { AbortedError, HttpError } from 'models/error' -import { Progress } from 'models/http' +import { DownloadProgress } from 'models/http' export async function getEventAndOwners( eventId: number, @@ -19,12 +20,7 @@ export async function getEventAndOwners( includeDescription: boolean = false, includeMetrics: boolean = true, refresh: boolean = false, -): Promise<{ - event: Drop - owners: string[] - ts: number - metrics: DropMetrics | null -} | null> { +): Promise { if (!FAMILY_API_KEY) { throw new Error( `Drop ${eventId} and owners could not be fetched, ` + @@ -197,7 +193,7 @@ export async function getInCommonEvents( export async function getInCommonEventsWithProgress( eventId: number, abortSignal: AbortSignal, - onProgress: (progressEvent: Partial) => void, + onProgress: (progressEvent: Partial) => void, refresh: boolean = false, ): Promise { if (!FAMILY_API_KEY) { @@ -295,6 +291,105 @@ export async function getInCommonEventsWithProgress( } } +export async function getInCommonEventsWithEvents( + eventId: number, + refresh: boolean = false, + onProgress: (received: number, total: number) => void, + onTs?: (ts: number) => void, + onEventIds?: (eventIds: number[]) => void, + onInCommon?: (eventId: number, owners: string[]) => void, +): Promise { + let total: number + let received = 0 + const inCommon: EventsInCommon = { + events: {}, + inCommon: {}, + ts: null, + } + const inCommonEvents = new EventSource( + `${FAMILY_API_URL}/event/${eventId}/in-common` + + `/stream${refresh ? '?refresh=true' : ''}` + ) + return new Promise((resolve, reject) => { + inCommonEvents.addEventListener('error', (ev) => { + reject(new Error(`Something happen: ${ev}`)) + }) + + inCommonEvents.addEventListener('message', (ev) => { + let data: unknown | undefined + try { + data = JSON.parse(ev.data) + } catch { + reject(new Error(`Cannot parse data '${ev.data}'`)) + return + } + if (!data || typeof data !== 'object') { + reject(new Error('Malformed data')) + return + } + if ('ts' in data && data.ts && typeof data.ts === 'number') { + inCommon.ts = data.ts + onTs?.(data.ts) + } + if ( + 'eventIds' in data && + data.eventIds && + Array.isArray(data.eventIds) && + data.eventIds.every( + (eventId) => eventId && typeof eventId === 'number' + ) + ) { + total = data.eventIds.length + for (const eventId of data.eventIds) { + inCommon.inCommon[eventId] = [] + } + onEventIds?.(data.eventIds) + } + if ( + 'eventId' in data && + data.eventId && + typeof data.eventId === 'number' && + 'owners' in data && + data.owners && + Array.isArray(data.owners) && + data.owners.every( + (owner) => owner && typeof owner === 'string' + ) + ) { + received++ + inCommon.inCommon[data.eventId] = data.owners + onInCommon?.(data.eventId, data.owners) + if (total) { + onProgress(received, total) + } + } + if ( + 'events' in data && + data.events && + typeof data.events === 'object' + ) { + inCommon.events = Object.fromEntries( + Object.entries(data.events).map( + ([eventIdRaw, eventData]) => [ + eventIdRaw, + parseDrop(eventData, /*includeDescription*/false), + ] + ) + ) + } + if ( + inCommon.ts != null && + received === total && + Object.values(inCommon.inCommon).every((owners) => owners.length > 0) && + Object.keys(inCommon.events).length > 0 + ) { + resolve(inCommon) + return + } + }) + }) +} + export async function getLastEvents( page: number = 1, qty: number = 3, diff --git a/src/models/api.ts b/src/models/api.ts index a0d01f3..c93cd6f 100644 --- a/src/models/api.ts +++ b/src/models/api.ts @@ -1,16 +1,10 @@ -import { Drop } from './drop' +import { Drop, DropMetrics } from 'models/drop' export const FAMILY_API_URL = process.env.REACT_APP_FAMILY_API_URL ?? 'https://api.poap.family' export const FAMILY_API_KEY = process.env.REACT_APP_FAMILY_API_KEY export type InCommon = Record -export interface EventsInCommon { - events: Record - inCommon: InCommon - ts: number | null -} - export function parseInCommon(inCommon: unknown): InCommon { if ( inCommon == null || @@ -33,6 +27,19 @@ export function parseInCommon(inCommon: unknown): InCommon { return inCommon } +export interface EventsInCommon { + events: Record + inCommon: InCommon + ts: number | null +} + +export interface EventAndOwners { + event: Drop + owners: string[] + ts: number + metrics: DropMetrics | null +} + export interface CachedEvent { id: number name: string diff --git a/src/models/http.ts b/src/models/http.ts index 6cf0e13..1169b56 100644 --- a/src/models/http.ts +++ b/src/models/http.ts @@ -1,5 +1,10 @@ -export interface Progress { +export interface DownloadProgress { progress: number rate: number | null estimated: number | null -} \ No newline at end of file +} + +export interface CountProgress { + count: number + total: number +} diff --git a/src/pages/Event.tsx b/src/pages/Event.tsx index 6d43908..110dd23 100644 --- a/src/pages/Event.tsx +++ b/src/pages/Event.tsx @@ -46,7 +46,8 @@ function Event() { const { completedEventInCommon, loadingEventInCommon, - loadedInCommonProgress, + loadedInCommon, + loadedInCommonDownload, loadedOwners, ownersErrors, inCommon, @@ -54,7 +55,13 @@ function Event() { cachedTs, fetchEventInCommon, retryAddress, - } = useEventInCommon(event.id, owners, force, /*local*/false) + } = useEventInCommon( + event.id, + owners, + /*refresh*/force, + /*local*/false, + /*stream*/true + ) const eventIds = useMemo( () => [event.id], @@ -174,15 +181,24 @@ function Event() { {loadedOwners > 0 ? : ( - loadedInCommonProgress != null + loadedInCommon != null ? ( - - ) - : + + ) + : ( + loadedInCommonDownload != null + ? ( + + ) + : + ) ) } diff --git a/src/pages/Events.tsx b/src/pages/Events.tsx index 7400807..797b7cd 100644 --- a/src/pages/Events.tsx +++ b/src/pages/Events.tsx @@ -78,12 +78,20 @@ function Events() { completedInCommonEvents, loadingInCommonEvents, eventsInCommonErrors, + loadedEventsInCommon, loadedEventsProgress, loadedEventsOwners, eventsInCommon, fetchEventsInCommon, retryEventAddressInCommon, - } = useEventsInCommon(eventIds, eventsOwners, all, force, /*local*/false) + } = useEventsInCommon( + eventIds, + eventsOwners, + all, + /*refresh*/force, + /*local*/false, + /*stream*/true + ) const { loadingCollections, @@ -363,6 +371,7 @@ function Events() { {( loadingInCommonEvents[event.id] != null && + loadedEventsInCommon[event.id] == null && loadedEventsProgress[event.id] == null && loadedEventsOwners[event.id] != null && eventsOwners[event.id] != null @@ -373,7 +382,20 @@ function Events() { showValue={loadedEventsOwners[event.id] > 0} /> )} - {loadedEventsProgress[event.id] != null && ( + {( + loadedEventsInCommon[event.id] != null && + loadedEventsProgress[event.id] == null + ) && ( + 0} + /> + )} + {( + loadedEventsInCommon[event.id] == null && + loadedEventsProgress[event.id] != null + ) && ( Date: Sun, 24 Nov 2024 05:41:50 -0300 Subject: [PATCH 5/5] Version 1.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f75b8e9..6cc8052 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@poap-xyz/poap-family", - "version": "1.16.3", + "version": "1.17.0", "author": { "name": "POAP", "url": "https://poap.xyz"