/* Recallroll page. Talks to NHTSA directly from the browser (CORS-enabled public APIs). © 2026 CyberMax */ (function () { 'use strict'; var VPIC = 'https://vpic.nhtsa.dot.gov/api/vehicles/'; var RECALLS = 'https://api.nhtsa.gov/recalls/recallsByVehicle'; var $ = function (id) { return document.getElementById(id); }; var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); }; var cache = {}; function getJson(url, opts) { if (!opts && cache[url]) return cache[url]; var p = fetch(url, opts).then(function (r) { if (!r.ok) throw new Error('NHTSA returned ' + r.status); return r.json(); }); if (!opts) { cache[url] = p; p.catch(function () { delete cache[url]; }); } return p; } // ---------- tabs ---------- var tabs = { one: 'p-one', bulk: 'p-bulk', 'new': 'p-new' }; function show(t) { Object.keys(tabs).forEach(function (k) { $('tab-' + k).classList.toggle('on', k === t); $(tabs[k]).hidden = k !== t; }); if (t === 'new') loadSnapshot(); try { history.replaceState(null, '', '#' + t); } catch (e) { /* file:// */ } } Object.keys(tabs).forEach(function (k) { $('tab-' + k).onclick = function () { show(k); }; }); // ---------- recalls for make/model/year ---------- // NHTSA's Recalls API only matches its own model spellings ("MUSTANG MACH E", "F-350 SD"); models/.json // (built daily from NHTSA's recall file) maps each make to those names. It answers 400 when nothing matches. function modelIndex(year) { return getJson('models/' + encodeURIComponent(year) + '.json').catch(function () { return {}; }); } function recallsFor(make, model, year) { var q = function (m) { return fetch(RECALLS + '?make=' + encodeURIComponent(make) + '&model=' + encodeURIComponent(m) + '&modelYear=' + encodeURIComponent(year)) .then(function (r) { if (r.status === 400) return { results: [] }; if (!r.ok) throw new Error('NHTSA returned ' + r.status); return r.json(); }) .then(function (d) { return d.results || []; }); }; return modelIndex(year).then(function (idx) { var names = RR.matchModels(model, idx[String(make).toUpperCase()] || []); if (!names.length) names = [model]; // older campaigns (before 2010) aren't in the index: try vPIC's name return Promise.all(names.map(q)).then(function (all) { return { rows: RR.normalizeRecalls([].concat.apply([], all)), models: names }; }); }); } function recallCard(r) { var flags = (r.doNotDrive ? 'Do Not Drive ' : '') + (r.parkOutside ? 'Park Outside ' : '') + (r.ota ? 'Over-the-air fix ' : ''); return '
' + flags + '' + esc(r.component) + '
' + '
Campaign ' + esc(r.campaign) + ' · reported ' + esc(r.date) + (r.manufacturer ? ' · ' + esc(r.manufacturer) : '') + '
' + (r.summary ? '

' + esc(r.summary) + '

' : '') + (r.consequence ? '

Risk: ' + esc(r.consequence) + '

' : '') + (r.remedy ? '

Fix: ' + esc(r.remedy) + '

' : '') + '
'; } function recallBlock(v, res) { var n = res.rows.length, hot = res.rows.filter(function (r) { return r.doNotDrive || r.parkOutside; }).length; var head = '

' + (n ? n + ' recall' + (n > 1 ? 's' : '') : 'No recalls') + ' listed for the ' + esc([v.year, v.make, res.models.join(' / ')].join(' ')) + '

'; if (hot) head += '

' + hot + ' with a Do Not Drive or Park Outside advisory. Check your exact VIN at nhtsa.gov/recalls or call a dealer now.

'; head += '

Recalls apply to the model year; your exact vehicle may not be included, or may already be fixed. Confirm your VIN at nhtsa.gov/recalls. Recall repairs are free at authorized dealers.

'; var sorted = res.rows.slice().sort(function (a, b) { return (b.doNotDrive || b.parkOutside) - (a.doNotDrive || a.parkOutside); }); return head + '
' + sorted.map(recallCard).join('') + '
' + NEXT; } // Right after a free result: the next step for people with more than one vehicle (counted links, checkout opens directly). var BUY = 'https://data.cybermax-tools.workers.dev/buy/'; var NEXT = ''; function vehicleBlock(v) { var rows = [['Vehicle', [v.year, v.make, v.model, v.trim].filter(Boolean).join(' ')], ['Series', v.series], ['Body', v.body], ['Type', v.type], ['Engine', v.engine], ['Drive', v.drive], ['GVWR', v.gvwr], ['Built in', v.plant], ['Manufacturer', v.manufacturer]]; return '
' + esc(v.vin) + '
' + rows.filter(function (r) { return r[1]; }).map(function (r) { return '
' + r[0] + '
' + esc(r[1]) + '
'; }).join('') + '
'; } function uniq(a) { var s = {}; return a.filter(function (x) { if (!x || s[x]) return false; s[x] = 1; return true; }).sort(); } function msg(el, text, kind) { el.hidden = !text; el.className = 'msg' + (kind ? ' ' + kind : ''); el.innerHTML = text || ''; } // ---------- one VIN ---------- $('f-one').onsubmit = function (e) { e.preventDefault(); var c = RR.checkVin($('vin').value), out = $('one-out'), m = $('one-msg'); out.innerHTML = ''; if (!c.ok) return msg(m, esc(c.error), 'bad'); msg(m, c.warning ? esc(c.warning) + ' Decoding anyway…' : 'Decoding…', c.warning ? 'warn' : ''); try { history.replaceState(null, '', '?vin=' + c.vin + '#one'); } catch (err) { /* ignore */ } getJson(VPIC + 'DecodeVinValues/' + c.vin + '?format=json').then(function (d) { var v = RR.vehicleFromVpic((d.Results || [])[0] || {}); v.vin = c.vin; if (!v.decoded) { msg(m, 'NHTSA could not decode this VIN' + (v.errorText ? ': ' + esc(v.errorText) : '.') + ' Check it for typos.', 'bad'); return; } msg(m, c.warning ? esc(c.warning) : '', c.warning ? 'warn' : ''); out.innerHTML = vehicleBlock(v) + '

Loading recalls…

'; return recallsFor(v.make, v.model, v.year).then(function (res) { $('one-rec').innerHTML = recallBlock(v, res); }); }).catch(function (err) { msg(m, 'Could not reach NHTSA (' + esc(err.message) + '). Try again in a minute.', 'bad'); }); }; // ---------- search by year/make/model (only vehicles that have had a recall since 2010) ---------- (function () { var yy = $('yy'), mk = $('mk'), md = $('md'); getJson('models/years.json').then(function (d) { yy.innerHTML = '' + d.years.slice().reverse().map(function (y) { return ''; }).join(''); }); yy.onchange = function () { mk.disabled = md.disabled = true; mk.innerHTML = ''; md.innerHTML = ''; if (!yy.value) return; modelIndex(yy.value).then(function (idx) { mk.innerHTML = '' + Object.keys(idx).sort().map(function (m) { return ''; }).join(''); mk.disabled = false; }); }; mk.onchange = function () { md.disabled = true; md.innerHTML = ''; if (!mk.value) return; modelIndex(yy.value).then(function (idx) { md.innerHTML = '' + uniq(idx[mk.value] || []).map(function (m) { return ''; }).join(''); md.disabled = false; }); }; md.onchange = function () { if (!md.value) return; var v = { year: yy.value, make: mk.value, model: md.value }; $('one-out').innerHTML = '

Loading recalls…

'; msg($('one-msg'), ''); recallsFor(v.make, v.model, v.year).then(function (res) { $('one-out').innerHTML = recallBlock(v, res); }) .catch(function (err) { msg($('one-msg'), 'Could not reach NHTSA (' + esc(err.message) + ').', 'bad'); }); }; })(); // ---------- bulk ---------- var bulkRows = []; function pool(items, n, fn) { var i = 0, out = new Array(items.length); function next() { if (i >= items.length) return Promise.resolve(); var k = i++; return fn(items[k], k).then(function (r) { out[k] = r; }, function () { out[k] = null; }).then(next); } return Promise.all(Array.from({ length: Math.min(n, items.length) }, next)).then(function () { return out; }); } $('bulk-go').onclick = function () { var vins = RR.extractVins($('vins').value, 50), st = $('bulk-status'), tb = document.querySelector('#bt tbody'); if (!vins.length) { st.textContent = 'No 17-character VINs found.'; return; } st.textContent = 'Decoding ' + vins.length + ' VINs…'; $('bulk-csv').disabled = true; tb.innerHTML = ''; $('bulk-wrap').hidden = false; var body = new URLSearchParams({ format: 'json', data: vins.join(';') }); getJson(VPIC + 'DecodeVINValuesBatch/', { method: 'POST', body: body }).then(function (d) { var vs = (d.Results || []).map(RR.vehicleFromVpic); var groups = {}; vs.forEach(function (v) { if (v.decoded) groups[[v.year, v.make, v.model].join('|')] = v; }); var keys = Object.keys(groups); st.textContent = 'Checking recalls for ' + keys.length + ' model years…'; return pool(keys, 4, function (k) { var v = groups[k]; return recallsFor(v.make, v.model, v.year); }).then(function (res) { var byKey = {}; keys.forEach(function (k, i) { byKey[k] = res[i]; }); bulkRows = vs.map(function (v) { var r = byKey[[v.year, v.make, v.model].join('|')], rows = r ? r.rows : []; var dnd = rows.filter(function (x) { return x.doNotDrive; }).length, po = rows.filter(function (x) { return x.parkOutside; }).length; return { vin: v.vin, year: v.year, make: v.make, model: v.model, trim: v.trim, decoded: v.decoded, recalls: v.decoded && r ? rows.length : '', do_not_drive: dnd, park_outside: po, newest_campaign: rows[0] ? rows[0].campaign : '', newest_date: rows[0] ? rows[0].date : '', newest_component: rows[0] ? rows[0].component : '', campaigns: rows.map(function (x) { return x.campaign; }).join(' '), note: !v.decoded ? (v.errorText || 'not decoded') : (r ? '' : 'recall lookup failed') }; }); bulkRows.sort(function (a, b) { return (b.do_not_drive + b.park_outside) - (a.do_not_drive + a.park_outside) || (+b.recalls || 0) - (+a.recalls || 0); }); tb.innerHTML = bulkRows.map(function (r) { var warn = (r.do_not_drive ? 'Do Not Drive ×' + r.do_not_drive + ' ' : '') + (r.park_outside ? 'Park Outside ×' + r.park_outside + '' : ''); return '' + (r.decoded ? esc([r.year, r.make, r.model, r.trim].filter(Boolean).join(' ')) : '' + esc(r.note) + '') + '' + esc(r.recalls) + '' + warn + '' + esc(r.vin) + '' + (r.newest_campaign ? esc(r.newest_date) + ' ' + esc(r.newest_component) + '' : '') + ''; }).join(''); var hot = bulkRows.filter(function (r) { return r.do_not_drive || r.park_outside; }).length; st.textContent = bulkRows.length + ' VINs checked · ' + bulkRows.filter(function (r) { return +r.recalls > 0; }).length + ' with recalls listed for their model year · ' + hot + ' with a Do Not Drive / Park Outside advisory. Confirm each VIN at nhtsa.gov/recalls.'; $('bulk-csv').disabled = false; }); }).catch(function (err) { st.textContent = 'Could not reach NHTSA (' + err.message + '). Try again.'; }); }; $('bulk-csv').onclick = function () { var csv = RR.toCsv(bulkRows, ['vin', 'year', 'make', 'model', 'trim', 'recalls', 'do_not_drive', 'park_outside', 'newest_campaign', 'newest_date', 'newest_component', 'campaigns', 'note']); var a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' })); a.download = 'recallroll-' + new Date().toISOString().slice(0, 10) + '.csv'; document.body.appendChild(a); a.click(); a.remove(); }; // ---------- new recalls (snapshot) ---------- var snap = null; function loadSnapshot() { if (snap) return; snap = {}; fetch('data.json').then(function (r) { return r.json(); }).then(function (d) { snap = d; var m = d.meta, fmt = function (n) { return Number(n).toLocaleString('en-US'); }; $('stats').innerHTML = [[fmt(m.campaigns_30d), 'recall campaigns in the last 30 days'], [fmt(m.campaigns), 'in the last 12 months'], [fmt(m.units_affected), 'units potentially affected (12 mo.)'], [fmt(m.do_not_drive + m.park_outside), 'Do Not Drive / Park Outside (12 mo.)']] .map(function (s) { return '
' + s[0] + '' + s[1] + '
'; }).join(''); var makes = {}; d.recent.forEach(function (c) { c.makes.split(', ').forEach(function (k) { makes[k] = 1; }); }); $('nmake').innerHTML = '' + Object.keys(makes).sort().map(function (k) { return ''; }).join(''); renderNew(); }).catch(function () { $('ncount').textContent = 'Could not load the recall snapshot.'; snap = null; }); } function renderNew() { if (!snap || !snap.recent) return; var flag = $('nflag').checked; var list = flag ? RR.filterRecent(snap.advisories, $('nq').value, $('nmake').value) : RR.filterRecent(snap.recent, $('nq').value, $('nmake').value); $('ncount').textContent = (flag ? list.length + ' Do Not Drive / Park Outside campaigns in the last 5 years' : list.length + ' campaigns received in the last 60 days') + ' · NHTSA data as of ' + snap.meta.latest_received + ' (built ' + snap.meta.built + ')'; $('nlist').innerHTML = list.slice(0, 200).map(function (c) { return recallCard({ campaign: c.campaign, date: c.report_received, component: c.vehicles, manufacturer: c.manufacturer + (c.units_affected ? ' · ' + Number(c.units_affected).toLocaleString('en-US') + ' affected' : ''), summary: c.component + (c.summary ? ': ' + c.summary : ''), consequence: c.consequence, remedy: c.remedy, doNotDrive: c.do_not_drive, parkOutside: c.park_outside }); }).join(''); } ['nq', 'nmake', 'nflag'].forEach(function (id) { $(id).addEventListener('input', renderNew); $(id).addEventListener('change', renderNew); }); // ---------- deep links: ?vin=... #bulk #new ---------- var qv = new URLSearchParams(location.search).get('vin'); var h = (location.hash || '').slice(1); if (tabs[h]) show(h); if (qv) { $('vin').value = qv; show('one'); $('f-one').requestSubmit ? $('f-one').requestSubmit() : $('f-one').onsubmit(new Event('submit')); } })();