Elie Habib 9b07fc8d8a feat(yahoo): _yahoo-fetch helper with curl-only Decodo proxy fallback + 4 seeder migrations (#3120)
* feat(_yahoo-fetch): curl-only Decodo proxy fallback helper

Yahoo Finance throttles Railway egress IPs aggressively. 4 seeders
(seed-commodity-quotes, seed-etf-flows, seed-gulf-quotes, seed-market-quotes)
duplicated the same fetchYahooWithRetry block with no proxy fallback.
This helper consolidates them and adds the proxy fallback.

Yahoo-specific: CURL-ONLY proxy strategy. Probed 2026-04-16:
  query1.finance.yahoo.com via CONNECT (httpsProxyFetchRaw): HTTP 404
  query1.finance.yahoo.com via curl    (curlFetch):          HTTP 200
Yahoo's edge blocks Decodo's CONNECT egress IPs but accepts the curl
egress IPs. Helper deliberately omits the CONNECT leg — adding it
would burn time on guaranteed-404 attempts. Production defaults expose
ONLY curlProxyResolver + curlFetcher.

All learnings from PR #3118 + #3119 reviews baked in:
- lastDirectError accumulator across the loop, embedded in final throw +
  Error.cause chain
- catch block uses break (NOT throw) so thrown errors also reach proxy
- DI seams (_curlProxyResolver, _proxyCurlFetcher) for hermetic tests
- _PROXY_DEFAULTS exported for production-default lock tests
- Sync curlFetch wrapped with await Promise.resolve() to future-proof
  against an async refactor (Greptile P2 from #3119)

Tests (tests/yahoo-fetch.test.mjs, 11 cases):
- Production defaults: curl resolver/fetcher reference equality
- Production defaults: NO CONNECT leg present (regression guard)
- 200 OK passthrough, never touches proxy
- 429 with no proxy → throws exhausted with HTTP 429 in message
- Retry-After header parsed correctly
- 429 + curl proxy succeeds → returns proxy data
- Thrown fetch error on final retry → proxy fallback runs (P1 guard)
- 429 + proxy ALSO fails → both errors visible in message + cause chain
- Proxy malformed JSON → throws exhausted
- Non-retryable 500 → no extra direct retry, falls to proxy
- parseRetryAfterMs unit (exported sanity check)

Verification: 11/11 helper tests pass. node --check clean.

Phase 1 of 2 — seeder migrations follow.

* feat(yahoo-seeders): migrate 4 seeders to _yahoo-fetch helper

Removes the duplicated fetchYahooWithRetry function (4 byte-identical
copies across seed-commodity-quotes, seed-etf-flows, seed-gulf-quotes,
seed-market-quotes) and routes all Yahoo Finance fetches through the
new scripts/_yahoo-fetch.mjs helper. Each seeder gains the curl-only
Decodo proxy fallback baked into the helper.

Per-seeder changes (mechanical):
- import { fetchYahooJson } from './_yahoo-fetch.mjs'
- delete the local fetchYahooWithRetry function
- replace 'const resp = await fetchYahooWithRetry(url, label); if (!resp)
  return X; const json = await resp.json()' with
  'let json; try { json = await fetchYahooJson(url, { label }); }
  catch { return X; }'
- prune now-unused CHROME_UA/sleep imports where applicable

Latent bugs fixed in passing:
- seed-etf-flows.mjs:23 and seed-market-quotes.mjs:38 referenced
  CHROME_UA without importing it (would throw ReferenceError at
  runtime if the helper were called). Now the call site is gone in
  etf-flows; in market-quotes CHROME_UA is properly imported because
  Finnhub call still uses it.

seed-commodity-quotes also has fetchYahooChart1y (separate non-retry
function for gold history). Migrated to use fetchYahooJson under the
hood — preserves return shape, adds proxy fallback automatically.

Verification:
- node --check clean on all 4 modified seeders
- npm run typecheck:all clean
- npm run test:data: 5374/5374 pass

Phase 2 of 2.

* fix(_yahoo-fetch): log success AFTER parse + add _sleep DI seam for honest Retry-After test

Greptile P2: "[YAHOO] proxy (curl) succeeded" was logged BEFORE
JSON.parse(text). On malformed proxy JSON, Railway logs would show:

  [YAHOO] proxy (curl) succeeded for AAPL
  throw: Yahoo retries exhausted ...

Contradictory + breaks the post-deploy log-grep verification this PR
relies on ("look for [YAHOO] proxy (curl) succeeded"). Fix: parse
first; success log only fires when parse succeeds AND the value is
about to be returned.

Greptile P3: 'Retry-After header parsed correctly' test used header
value '0', but parseRetryAfterMs() treats non-positive seconds as null
→ helper falls through to default linear backoff. So the test was
exercising the wrong branch despite its name.

Fix: added _sleep DI opt seam to the helper. New test injects a sleep
spy and asserts the captured duration:

  Retry-After: '7' → captured sleep == [7000]   (Retry-After branch)
  no Retry-After  → captured sleep == [10]      (default backoff = retryBaseMs * 1)

Two paired tests lock both branches separately so a future regression
that collapses them is caught.

Also added a log-ordering regression test: malformed proxy JSON must
NOT emit the 'succeeded' log. Captures console.log into an array and
asserts no 'proxy (curl) succeeded' line appeared before the throw.

Verification:
- tests/yahoo-fetch.test.mjs: 13/13 (was 11, +2)
- npm run test:data: 5376/5376 (+2)
- npm run typecheck:all: clean

Followup commits on PR #3120.
2026-04-16 09:25:06 +04:00

World Monitor

Real-time global intelligence dashboard — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.

GitHub stars GitHub forks Discord License: AGPL v3 TypeScript Last commit Latest release

Web App  Tech Variant  Finance Variant  Commodity Variant  Happy Variant

Download Windows  Download macOS ARM  Download macOS Intel  Download Linux

Documentation  ·  Releases  ·  Contributing

World Monitor Dashboard


What It Does

  • 435+ curated news feeds across 15 categories, AI-synthesized into briefs
  • Dual map engine — 3D globe (globe.gl) and WebGL flat map (deck.gl) with 45 data layers
  • Cross-stream correlation — military, economic, disaster, and escalation signal convergence
  • Country Intelligence Index — composite risk scoring across 12 signal categories
  • Finance radar — 92 stock exchanges, commodities, crypto, and 7-signal market composite
  • Local AI — run everything with Ollama, no API keys required
  • 5 site variants from a single codebase (world, tech, finance, commodity, happy)
  • Native desktop app (Tauri 2) for macOS, Windows, and Linux
  • 21 languages with native-language feeds and RTL support

For the full feature list, architecture, data sources, and algorithms, see the documentation.


Quick Start

git clone https://github.com/koala73/worldmonitor.git
cd worldmonitor
npm install
npm run dev

Open localhost:5173. No environment variables required for basic operation.

For variant-specific development:

npm run dev:tech       # tech.worldmonitor.app
npm run dev:finance    # finance.worldmonitor.app
npm run dev:commodity  # commodity.worldmonitor.app
npm run dev:happy      # happy.worldmonitor.app

See the self-hosting guide for deployment options (Vercel, Docker, static).


Tech Stack

Category Technologies
Frontend Vanilla TypeScript, Vite, globe.gl + Three.js, deck.gl + MapLibre GL
Desktop Tauri 2 (Rust) with Node.js sidecar
AI/ML Ollama / Groq / OpenRouter, Transformers.js (browser-side)
API Contracts Protocol Buffers (92 protos, 22 services), sebuf HTTP annotations
Deployment Vercel Edge Functions (60+), Railway relay, Tauri, PWA
Caching Redis (Upstash), 3-tier cache, CDN, service worker

Full stack details in the architecture docs.


Flight Data

Flight data provided gracefully by Wingbits, the most advanced ADS-B flight data solution.


Data Sources

WorldMonitor aggregates 65+ external data sources across geopolitics, finance, energy, climate, aviation, cyber, military, infrastructure, and news intelligence. See the full data sources catalog for providers, feed tiers, and collection methods.


Contributing

Contributions welcome! See CONTRIBUTING.md for guidelines.

npm run typecheck        # Type checking
npm run build:full       # Production build

License

AGPL-3.0 for non-commercial use. Commercial license required for any commercial use.

Use Case Allowed?
Personal / research / educational Yes
Self-hosted (non-commercial) Yes, with attribution
Fork and modify (non-commercial) Yes, share source under AGPL-3.0
Commercial use / SaaS / rebranding Requires commercial license

See LICENSE for full terms. For commercial licensing, contact the maintainer.

Copyright (C) 2024-2026 Elie Habib. All rights reserved.


Author

Elie HabibGitHub

Contributors

Security Acknowledgments

We thank the following researchers for responsibly disclosing security issues:

  • Cody Richard — Disclosed three security findings covering IPC command exposure, renderer-to-sidecar trust boundary analysis, and fetch patch credential injection architecture (2026)

See our Security Policy for responsible disclosure guidelines.


worldmonitor.app  ·  docs.worldmonitor.app  ·  finance.worldmonitor.app  ·  commodity.worldmonitor.app

Star History

Star History Chart
Description
Mirrored from GitHub
Readme AGPL-3.0 382 MiB
Languages
TypeScript 49.1%
JavaScript 47%
CSS 2.9%
HTML 0.4%
Rust 0.3%
Other 0.1%