// // Rate Findr API client, run in-page via `chromeassist -a js` so the // browser's logged-in supporter session supplies auth (window.LH_RF.token). // // Synchronous XHR: chromeassist won't await a Promise. // // Reads the global ARGS; returns a JSON string, or { err } on failure. // (function () { var A = ARGS; function post(url, data) { data.rf_token = (window.LH_RF && window.LH_RF.token) || ''; var body = Object.keys(data).map(function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]); }).join('&'); var x = new XMLHttpRequest(); x.open('POST', url, false); x.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); x.send(body); if (x.status !== 200) throw 'HTTP ' + x.status + ' on ' + url; return JSON.parse(x.responseText); } function getJSON(url) { var x = new XMLHttpRequest(); x.open('GET', url, false); x.send(); if (x.status !== 200) throw 'HTTP ' + x.status + ' on ' + url; return JSON.parse(x.responseText); } function findMake(name) { var hit = (getJSON('/makes').makes || []).filter(function (m) { return m.make_name.toLowerCase() === name.toLowerCase(); })[0]; if (!hit) throw 'Unknown make: ' + name; return hit; } // One /style-data call returns every model and every trim for the make/year. function styleData(mk, year) { return post('/style-data', { make_code: mk.make_code, make_name: mk.make_name, model_year: year, country: 'us' }); } function norm(s) { return String(s).toLowerCase().replace(/\s+/g, ' ').trim(); } function incentives(prog) { var out = []; function pull(arr) { (arr || []).forEach(function (i) { out.push({ name: i.program_name || i.program_desc, value: i.inc_val }); }); } pull(prog.gen_incs); pull(prog.con_incs && prog.con_incs.cash_incs); pull(prog.dealer_incs); return out; } function rvForMiles(ld, miles) { var rvs = ld.rvs || []; var hit = rvs.filter(function (r) { return String(r.rv_mileage) === String(miles); })[0]; return hit ? hit.rv : (rvs[0] ? rvs[0].rv : null); } try { var mk = findMake(A.make); var sd = styleData(mk, A.year); if (A.action === 'models') { return JSON.stringify({ make: mk.make_name, year: A.year, models: sd.models || [] }); } var trims = (sd.trims && sd.trims[A.model]) || []; if (!trims.length) throw 'No trims for model "' + A.model + '" (try -a models)'; if (A.action === 'trims') { return JSON.stringify({ make: mk.make_name, year: A.year, model: A.model, trims: trims.map(function (t) { return { id: t.styleID, trim: t.styleDesc }; }) }); } var want = norm(A.trim); var style = trims.filter(function (t) { return norm(t.styleDesc) === want; })[0]; if (!style) throw 'No trim "' + A.trim + '" in ' + A.model + ' (try -a trims)'; var d = post('/lease-data', { vehicle_id: style.styleID, zipcode: A.zip, lease_term: A.term, miles: A.miles, country: 'us' }); var programs = (d.programs || []).map(function (p) { var ld = p.lease_data || {}; var mf = ld.mf; return { program: ld.program_name || ld.program_desc, delivery: ld.delivery_types, lender: ld.lender, lender_type: p.lender_type, mf: mf, apr: (mf != null) ? Math.round(mf * 2400 * 100) / 100 : null, rv: rvForMiles(ld, A.miles), rv_mileage: A.miles, exp_date: ld.exp_date, incentives: incentives(p) }; }); return JSON.stringify({ make: mk.make_name, year: A.year, model: A.model, trim: style.styleDesc, vehicle_id: style.styleID, term: A.term, miles: A.miles, zip: A.zip, programs: programs }); } catch (e) { return JSON.stringify({ err: String(e) }); } })()