ui: tighten dashboard copy and fix layout issues
This commit is contained in:
+23
-18
@@ -36,6 +36,19 @@ async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
/** Signed-read credentials as headers (C3): keeps EIP-191 signatures out of
|
||||
* URLs, access logs, and browser history. Backend still accepts the legacy
|
||||
* ?ts=&sig= query params for old clients. */
|
||||
function signedReadInit(env: SignedEnvelope): RequestInit {
|
||||
return {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sig-Ts': String(env.timestamp),
|
||||
'X-Sig-Sig': env.signature,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export interface PostListResponse {
|
||||
items: TrumpPost[]
|
||||
total: number
|
||||
@@ -104,22 +117,16 @@ export async function getTrades(
|
||||
limit = 20,
|
||||
page = 1,
|
||||
): Promise<BotTrade[]> {
|
||||
const qs = [
|
||||
`wallet=${wallet.toLowerCase()}`,
|
||||
`ts=${env.timestamp}`,
|
||||
`sig=${encodeURIComponent(env.signature)}`,
|
||||
`limit=${limit}`,
|
||||
`page=${page}`,
|
||||
].join('&')
|
||||
return fetchJson<BotTrade[]>(`/trades?${qs}`)
|
||||
const qs = `wallet=${wallet.toLowerCase()}&limit=${limit}&page=${page}`
|
||||
return fetchJson<BotTrade[]>(`/trades?${qs}`, signedReadInit(env))
|
||||
}
|
||||
|
||||
export async function getPerformance(
|
||||
wallet: string,
|
||||
env: SignedEnvelope,
|
||||
): Promise<BotPerformance> {
|
||||
const qs = `wallet=${wallet.toLowerCase()}&ts=${env.timestamp}&sig=${encodeURIComponent(env.signature)}`
|
||||
return fetchJson<BotPerformance>(`/performance?${qs}`)
|
||||
const qs = `wallet=${wallet.toLowerCase()}`
|
||||
return fetchJson<BotPerformance>(`/performance?${qs}`, signedReadInit(env))
|
||||
}
|
||||
|
||||
export async function subscribe(
|
||||
@@ -272,16 +279,16 @@ export async function getOpenPositions(
|
||||
wallet: string,
|
||||
env: SignedEnvelope,
|
||||
): Promise<OpenPositionsResponse> {
|
||||
const qs = `wallet=${wallet.toLowerCase()}&ts=${env.timestamp}&sig=${encodeURIComponent(env.signature)}`
|
||||
return fetchJson<OpenPositionsResponse>(`/positions/open?${qs}`)
|
||||
const qs = `wallet=${wallet.toLowerCase()}`
|
||||
return fetchJson<OpenPositionsResponse>(`/positions/open?${qs}`, signedReadInit(env))
|
||||
}
|
||||
|
||||
export async function getTodayStats(
|
||||
wallet: string,
|
||||
env: SignedEnvelope,
|
||||
): Promise<TodayStats> {
|
||||
const qs = `wallet=${wallet.toLowerCase()}&ts=${env.timestamp}&sig=${encodeURIComponent(env.signature)}`
|
||||
return fetchJson<TodayStats>(`/positions/today?${qs}`)
|
||||
const qs = `wallet=${wallet.toLowerCase()}`
|
||||
return fetchJson<TodayStats>(`/positions/today?${qs}`, signedReadInit(env))
|
||||
}
|
||||
|
||||
// ─── Scanner control (module 2) ─────────────────────────────────────────────
|
||||
@@ -378,8 +385,7 @@ export async function getUser(
|
||||
wallet: string,
|
||||
env: SignedEnvelope,
|
||||
): Promise<UserData> {
|
||||
const qs = `ts=${env.timestamp}&sig=${encodeURIComponent(env.signature)}`
|
||||
return fetchJson<UserData>(`/user/${wallet.toLowerCase()}?${qs}`)
|
||||
return fetchJson<UserData>(`/user/${wallet.toLowerCase()}`, signedReadInit(env))
|
||||
}
|
||||
|
||||
export interface WindowAccuracy {
|
||||
@@ -586,8 +592,7 @@ export async function getTelegramStatus(wallet: string, env?: SignedEnvelope): P
|
||||
// Pass signature when available so the backend returns full binding details.
|
||||
// Without it the endpoint returns only `bound: boolean` to prevent third-party de-anonymisation.
|
||||
if (env) {
|
||||
const qs = new URLSearchParams({ timestamp: String(env.timestamp), signature: env.signature })
|
||||
return fetchJson<TelegramStatus>(`/telegram/${wallet}/status?${qs}`)
|
||||
return fetchJson<TelegramStatus>(`/telegram/${wallet}/status`, signedReadInit(env))
|
||||
}
|
||||
return fetchJson<TelegramStatus>(`/telegram/${wallet}/status`)
|
||||
}
|
||||
|
||||
+16
-5
@@ -18,6 +18,11 @@ interface CacheEntry<T> {
|
||||
data: T
|
||||
ts: number
|
||||
revalidating: boolean
|
||||
/** onUpdate callbacks of every caller that hit this key while a background
|
||||
* revalidation was in flight (M9 fix). All are notified when the fetch
|
||||
* resolves — previously only the caller that STARTED the revalidation got
|
||||
* fresh data; everyone else silently kept the stale copy. */
|
||||
subscribers: Array<(fresh: T) => void>
|
||||
}
|
||||
|
||||
const store = new Map<string, CacheEntry<unknown>>()
|
||||
@@ -37,16 +42,22 @@ export async function swrFetch<T>(
|
||||
// Still fresh — return immediately.
|
||||
return hit.data
|
||||
}
|
||||
// Stale — return immediately AND revalidate in background.
|
||||
// Stale — return immediately AND revalidate in background. Every caller's
|
||||
// onUpdate joins the subscriber list; the in-flight fetch notifies all.
|
||||
if (onUpdate) hit.subscribers.push(onUpdate)
|
||||
if (!hit.revalidating) {
|
||||
hit.revalidating = true
|
||||
fetcher()
|
||||
.then(fresh => {
|
||||
store.set(key, { data: fresh, ts: Date.now(), revalidating: false })
|
||||
onUpdate?.(fresh)
|
||||
const subs = hit.subscribers
|
||||
store.set(key, { data: fresh, ts: Date.now(), revalidating: false, subscribers: [] })
|
||||
for (const notify of subs) {
|
||||
try { notify(fresh) } catch { /* one bad subscriber must not starve the rest */ }
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (hit) hit.revalidating = false
|
||||
hit.revalidating = false
|
||||
hit.subscribers = []
|
||||
})
|
||||
}
|
||||
return hit.data
|
||||
@@ -54,7 +65,7 @@ export async function swrFetch<T>(
|
||||
|
||||
// Cache miss — must wait for the first fetch.
|
||||
const data = await fetcher()
|
||||
store.set(key, { data, ts: Date.now(), revalidating: false })
|
||||
store.set(key, { data, ts: Date.now(), revalidating: false, subscribers: [] })
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user