/* Company Stock Price — market store: live API only, paginated stock loading. */ const REFRESH_MS = (window.FinnhubSymbols && window.FinnhubSymbols.REFRESH_MS) || 5 * 60 * 1000; const MarketDataCtx = React.createContext(null); function useMarketData() { return React.useContext(MarketDataCtx); } function mergeStocks(existing, incoming) { const map = new Map((existing || []).map((s) => [s.symbol || s.ticker, s])); (incoming || []).forEach((s) => map.set(s.symbol || s.ticker, s)); return [...map.values()]; } function MarketDataProvider({ children }) { const L = useLive(); const [state, setState] = useState({ loading: false, ready: false, stocks: [], stocksTotal: 0, stocksHasMore: false, stocksLoadingMore: false, stockPageSize: 15, indices: [], globalIndices: [], globalStocks: [], globalLoading: false, news: [], status: null, error: null, lastUpdated: null, }); const loadRemainingStocks = React.useCallback(async (startOffset, pageSize, opts = {}) => { let offset = startOffset; let hasMore = true; let merged = []; while (hasMore) { const page = await L.market.getIndiaStocksPage({ offset, limit: pageSize, ...opts }); merged = mergeStocks(merged, page.stocks); setState((s) => ({ ...s, stocks: mergeStocks(s.stocks, page.stocks), stocksHasMore: page.hasMore, stocksTotal: page.total, })); hasMore = page.hasMore; offset += pageSize; } return merged; }, [L.market]); const loadIndia = React.useCallback(async (opts = {}) => { setState((s) => ({ ...s, loading: !s.ready, error: null })); try { const overview = await L.market.getIndiaOverview(opts); const pageSize = overview.stockPageSize || 15; setState((s) => ({ loading: false, ready: true, stocks: overview.stocks, stocksTotal: overview.stocksTotal, stocksHasMore: overview.stocksHasMore, stocksLoadingMore: overview.stocksHasMore, stockPageSize: pageSize, indices: overview.indices, globalIndices: s.globalIndices, globalStocks: s.globalStocks, globalLoading: false, news: (() => { if (window.NewsCache && overview.news) window.NewsCache.putMany(overview.news); return overview.news; })(), funds: [], status: overview.status, error: null, lastUpdated: Date.now(), })); if (overview.stocksHasMore) { await loadRemainingStocks(pageSize, pageSize, opts); setState((s) => ({ ...s, stocksLoadingMore: false })); } } catch (e) { setState((s) => ({ ...s, loading: false, ready: false, stocksLoadingMore: false, error: apiErrorMessage(e), })); } }, [L.market, loadRemainingStocks]); const loadMoreStocks = React.useCallback(async () => { if (state.stocksLoadingMore || !state.stocksHasMore) return; setState((s) => ({ ...s, stocksLoadingMore: true })); try { const page = await L.market.getIndiaStocksPage({ offset: state.stocks.length, limit: state.stockPageSize, }); setState((s) => ({ ...s, stocks: mergeStocks(s.stocks, page.stocks), stocksHasMore: page.hasMore, stocksTotal: page.total, stocksLoadingMore: false, })); } catch (e) { setState((s) => ({ ...s, stocksLoadingMore: false })); } }, [L.market, state.stocks.length, state.stocksHasMore, state.stocksLoadingMore, state.stockPageSize]); const loadGlobal = React.useCallback(async () => { if (!L.hasKey) return; setState((s) => ({ ...s, globalLoading: true })); try { const [globalIndices, globalStocks] = await Promise.all([ L.market.getGlobalIndices(), L.market.getGlobalStocks(L.market.globalWatchlist()), ]); L.refreshRateLimit(); setState((s) => ({ ...s, globalIndices, globalStocks, globalLoading: false, })); } catch (e) { setState((s) => ({ ...s, globalLoading: false })); } }, [L]); const load = React.useCallback((opts) => loadIndia(opts), [loadIndia]); useEffect(() => { loadIndia(); const id = setInterval(() => loadIndia({ noCache: true }), REFRESH_MS); return () => clearInterval(id); }, [loadIndia]); useEffect(() => { if (!L.hasKey) return; const id = setInterval(() => { if (state.globalIndices.length) loadGlobal(); }, REFRESH_MS); return () => clearInterval(id); }, [L.hasKey, state.globalIndices.length, loadGlobal]); const value = { ...state, reload: load, loadGlobal, loadMoreStocks, refreshMs: REFRESH_MS, indexMeta: Object.fromEntries( (state.indices || []).map((ix) => [ix.label, { desc: ix.desc, members: (window.FinnhubSymbols.IN.INDEX_MEMBERS[ix.label] || []), }]) ), globalIndexMeta: Object.fromEntries( (state.globalIndices || []).map((ix) => [ix.label, { desc: ix.desc, members: (window.FinnhubSymbols.US.INDEX_MEMBERS[ix.label] || []), }]) ), indexMembers: (label, region = 'IN') => { const map = region === 'US' ? window.FinnhubSymbols.US.INDEX_MEMBERS : window.FinnhubSymbols.IN.INDEX_MEMBERS; const syms = map[label]; if (!syms) return []; const pool = region === 'US' ? state.globalStocks : state.stocks; return pool.filter((s) => syms.includes(s.symbol || (s.ticker + '.NS'))); }, }; return {children}; } Object.assign(window, { MarketDataCtx, useMarketData, MarketDataProvider });