mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-26 01:24:59 +02:00
* fix(cors): use ACAO: * for bootstrap to fix CF cache origin pinning CF ignores Vary: Origin and pins the first request's ACAO header on the cached response. Preview deployments from *.vercel.app got ACAO: worldmonitor.app from CF's cache, blocking CORS. Bootstrap data is fully public (world events, market prices, seismic data) so ACAO: * is safe and allows CF to cache one entry valid for all origins. isDisallowedOrigin() still gates non-cache paths. * chore: finish security triage * fix(aviation): update isArray callback signature for fast-xml-parser 5.5.x fast-xml-parser bumped from 5.4.2 to 5.5.7 changed the isArray callback's second parameter type from string to unknown. Guard with typeof check before calling .test() to satisfy the new type contract. * docs: fix MD032 blank lines around lists in tradingview-screener-integration * fix(security): address code review findings from PR #1903 - api/_json-response.js: add recursion depth limit (20) to sanitizeJsonValue and strip Error.cause chain alongside stack/stackTrace - scripts/ais-relay.cjs: extract WORLD_BANK_COUNTRY_ALLOWLIST to module level to eliminate duplicate; clamp years param to [1,30] to prevent unbounded World Bank date ranges - src-tauri/sidecar/local-api-server.mjs: use JSON.stringify for vq value in inline JS, consistent with safeVideoId/safeOrigin handling - src/services/story-share.ts: simplify sanitizeStoryType to use typed array instead of repeated as-casts * fix(desktop): use parent window origin for YouTube embed postMessage Sidecar youtube-embed route was targeting the iframe's own localhost origin for all window.parent.postMessage calls, so browsers dropped yt-ready/ yt-state/yt-error on Tauri builds where the parent is tauri://localhost or asset://localhost. LiveNewsPanel and LiveWebcamsPanel already pass parentOrigin=window.location.origin in the embed URL; the sidecar now reads, validates, and uses it as the postMessage target for all player event messages. The YT API playerVars origin/widget_referrer continue to use the sidecar's own localhost origin which YouTube requires. Also restore World Bank relay to a generic proxy: replace TECH_INDICATORS membership check with a format-only regex so any valid indicator code (NY.GDP.MKTP.CD etc.) is accepted, not just the 16 tech-sector codes.
81 lines
2.7 KiB
JavaScript
81 lines
2.7 KiB
JavaScript
/**
|
|
* Pure parse helpers for trade-data seed scripts.
|
|
* Extracted so test files can import directly without new Function() hacks.
|
|
*/
|
|
|
|
export const BUDGET_LAB_TARIFFS_URL = 'https://budgetlab.yale.edu/research/tracking-economic-effects-tariffs';
|
|
|
|
const MONTH_MAP = {
|
|
january: '01', february: '02', march: '03', april: '04',
|
|
may: '05', june: '06', july: '07', august: '08',
|
|
september: '09', october: '10', november: '11', december: '12',
|
|
};
|
|
|
|
export function htmlToPlainText(html) {
|
|
return String(html ?? '')
|
|
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
.replace(/<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s[^<>]*?)?>/g, ' ')
|
|
.replace(/ /gi, ' ')
|
|
.replace(/&/gi, '&')
|
|
.replace(/"/gi, '"')
|
|
.replace(/'/gi, '\'')
|
|
.replace(/\s+/g, ' ')
|
|
.trim();
|
|
}
|
|
|
|
/**
|
|
* Convert a human-readable date string like "March 2, 2026" to ISO "2026-03-02".
|
|
* Falls back to '' on failure.
|
|
*/
|
|
export function toIsoDate(value) {
|
|
const text = String(value ?? '').trim();
|
|
if (!text) return '';
|
|
if (/^\d{4}-\d{2}-\d{2}/.test(text)) return text.slice(0, 10);
|
|
const m = text.match(/^([A-Za-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
|
|
if (m) {
|
|
const mm = MONTH_MAP[m[1].toLowerCase()];
|
|
if (mm) return `${m[3]}-${mm}-${m[2].padStart(2, '0')}`;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Parse the Yale Budget Lab tariff-tracking page and extract effective tariff rate.
|
|
*
|
|
* Tries three patterns in priority order:
|
|
* 1. "effective tariff rate reaching X% in [month year]"
|
|
* 2. "average effective [U.S.] tariff rate ... to X% ... in/by [month year]"
|
|
* 3. Same as 2 but no period capture
|
|
*
|
|
* Returns null when no recognisable rate is found.
|
|
*/
|
|
export function parseBudgetLabEffectiveTariffHtml(html) {
|
|
const text = htmlToPlainText(html);
|
|
if (!text) return null;
|
|
|
|
const updatedAt = toIsoDate(text.match(/\bUpdated:\s*([A-Za-z]+\s+\d{1,2},\s+\d{4})/i)?.[1] ?? '');
|
|
const patterns = [
|
|
/effective tariff rate reaching\s+(\d+(?:\.\d+)?)%\s+in\s+([A-Za-z]+\s+\d{4})/i,
|
|
/average effective (?:u\.s\.\s*)?tariff rate[^.]{0,180}?\bto\s+(\d+(?:\.\d+)?)%[^.]{0,180}?\b(?:in|by)\s+([A-Za-z]+\s+\d{4})/i,
|
|
/average effective (?:u\.s\.\s*)?tariff rate[^.]{0,180}?\bto\s+(\d+(?:\.\d+)?)%/i,
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = text.match(pattern);
|
|
if (!match) continue;
|
|
const tariffRate = parseFloat(match[1]);
|
|
if (!Number.isFinite(tariffRate)) continue;
|
|
return {
|
|
sourceName: 'Yale Budget Lab',
|
|
sourceUrl: BUDGET_LAB_TARIFFS_URL,
|
|
observationPeriod: match[2] ?? '',
|
|
updatedAt,
|
|
tariffRate: Math.round(tariffRate * 100) / 100,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|