SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%

web: playground — initialisation depuis l'URL avant toute réécriture (course au montage qui perdait les liens profonds) ; listes de clés tolérantes aux enveloppes inattendues

Simon-Pierre Boucher committed 20 days ago (Sep 4, 2026) parent e7991bb

4 changed files +18 −8

modified hfmarketdata/web/src/pages/dashboard/Keys.jsx +6 −1
@@ -21,7 +21,12 @@ export default function Keys() {
21 21 const navigate = useNavigate()
22 22
23 23 const load = useCallback(async () => {
24 − try { const r = await api('/v1/me/keys'); setKeys(r.data?.data || r.data || []); setErr(null) } catch (e) { setErr(e); setKeys([]) }
24 + try {
25 + const r = await api('/v1/me/keys')
26 + const d = r.data
27 + setKeys(Array.isArray(d) ? d : Array.isArray(d?.data) ? d.data : Array.isArray(d?.keys) ? d.keys : [])
28 + setErr(null)
29 + } catch (e) { setErr(e); setKeys([]) }
25 30 }, [])
26 31 useEffect(() => { load() }, [load])
27 32
modified hfmarketdata/web/src/pages/dashboard/Overview.jsx +1 −1
@@ -22,7 +22,7 @@ export default function Overview() {
22 22 useEffect(() => {
23 23 let alive = true
24 24 api('/v1/me/usage?range=24h').then(r => alive && setUsage(r.data?.data || r.data)).catch(e => alive && setErr(e))
25 − api('/v1/me/keys').then(r => alive && setKeys(r.data?.data || r.data || [])).catch(() => alive && setKeys([]))
25 + api('/v1/me/keys').then(r => alive && setKeys(Array.isArray(r.data) ? r.data : Array.isArray(r.data?.data) ? r.data.data : [])).catch(() => alive && setKeys([]))
26 26 return () => { alive = false }
27 27 }, [])
28 28 const tier = TIERS.find(t => t.id === (user.tier || 'free')) || TIERS[1]
modified hfmarketdata/web/src/pages/dashboard/PlaygroundTab.jsx +1 −1
@@ -12,7 +12,7 @@ export default function PlaygroundTab() {
12 12 const [keys, setKeys] = useState(null)
13 13 const [busy, setBusy] = useState(false)
14 14 const [err, setErr] = useState(null)
15 − useEffect(() => { let alive = true; api('/v1/me/keys').then(r => alive && setKeys(r.data?.data || r.data || [])).catch(() => alive && setKeys([])); return () => { alive = false } }, [])
15 + useEffect(() => { let alive = true; api('/v1/me/keys').then(r => alive && setKeys(Array.isArray(r.data) ? r.data : Array.isArray(r.data?.data) ? r.data.data : [])).catch(() => alive && setKeys([])); return () => { alive = false } }, [])
16 16
17 17 const usePasted = e => {
18 18 e.preventDefault()
modified hfmarketdata/web/src/playground/Playground.jsx +10 −5
@@ -89,6 +89,7 @@ export default function Playground({ apiKey, embedded = false }) {
89 89 const [lastRate, setLastRate] = useState(null)
90 90 const [filter, setFilter] = useState('')
91 91 const lastWritten = useRef(null)
92 + const ready = useRef(false) // URL → state initialisation done; only then do we write state → URL
92 93 const abortRef = useRef(null)
93 94 const resultRef = useRef(null)
94 95
@@ -99,9 +100,7 @@ export default function Playground({ apiKey, embedded = false }) {
99 100 const ref = sp.get('ep')
100 101 const urlValues = {}
101 102 sp.forEach((v, k) => { if (k !== 'ep') urlValues[k] = v })
102 − let cancelled = false
103 − resolveEntry(ref, urlValues).then(({ entry: e, generic, unknown }) => {
104 − if (cancelled) return
103 + const apply = ({ entry: e, generic, unknown }) => {
105 104 setEntry(e)
106 105 const dv = defaultValues(e)
107 106 for (const [k, v] of Object.entries(urlValues)) if (k in dv || e.fromSpec) dv[k] = v
@@ -110,13 +109,19 @@ export default function Playground({ apiKey, embedded = false }) {
110 109 if (unknown) setNotice({ kind: 'warn', text: `Unknown request type "${unknown}" — showing ${e.title} instead.` })
111 110 else if (generic) setNotice({ kind: 'info', text: `${e.title}: form generated from the OpenAPI spec (no curated preset yet).` })
112 111 else setNotice(null)
113 − })
112 + ready.current = true
113 + }
114 + // synchronous fast path (catalog id / known operationId) avoids a flash of the default form
115 + const direct = ref ? findEntry(ref, urlValues) : byId(DEFAULT_EP)
116 + if (direct) { apply({ entry: direct }); return undefined }
117 + let cancelled = false
118 + resolveEntry(ref, urlValues).then(res => { if (!cancelled) apply(res) })
114 119 return () => { cancelled = true }
115 120 }, [location.search])
116 121
117 122 // ---- write the URL from the state (replace, no history spam)
118 123 useEffect(() => {
119 − if (!entry) return
124 + if (!entry || !ready.current) return
120 125 const sp = new URLSearchParams()
121 126 sp.set('ep', entry.fromSpec ? entry.operationIds[0] : entry.id)
122 127 const dv = defaultValues(entry)
123 128