
Chrome Extension Setup
Install the BMS Rate Capture extension to capture rates from Acre with one click
v1.6 — Smarter rate capture & reliable auto-parse! The extension now targets rate tables specifically (instead of grabbing the whole page), captures up to 50,000 characters (~150+ deals), and retries data injection so rates parse and save automatically without clicking any buttons.
You MUST re-download ALL 5 files (manifest.json, popup.html, popup.js, background.js, icon.png) and reload the extension in chrome://extensions.
You MUST re-download ALL 5 files (manifest.json, popup.html, popup.js, background.js, icon.png) and reload the extension in chrome://extensions.
Installation Instructions
- 1Download all 4 files below into a single folder (e.g.
bms-rate-capture). - 2Open Chrome and go to
chrome://extensions - 3Enable "Developer mode" (toggle in the top-right corner)
- 4Click "Load unpacked" and select the folder where you saved the files
- 5Pin the extension to your toolbar. When on an Acre rate page, click the extension button and hit "Capture Rates".
- 6BMS will open in a new tab, parse the rates, and save them automatically — no further action needed.
Note: The app URL is set to
https://bespokemortgagesolutions.com. If your app URL changes, re-download the popup.js file from this page.manifest.json
{
"manifest_version": 3,
"name": "BMS Rate Capture",
"version": "1.6",
"description": "Capture mortgage rates from Acre and save to BMS automatically",
"permissions": [
"activeTab",
"scripting",
"storage"
],
"host_permissions": [
"https://*.acre-platform.com/*",
"https://acre-platform.com/*",
"https://*.myac.re/*",
"https://myac.re/*",
"https://bespokemortgagesolutions.com/*"
],
"action": {
"default_popup": "popup.html",
"default_title": "Capture Rates",
"default_icon": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
}
},
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"background": {
"service_worker": "background.js"
}
}popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { width: 260px; padding: 16px; font-family: -apple-system, sans-serif; background: #1A2626; margin: 0; }
.logo { display: flex; align-items: center; gap: 9px; margin-bottom: 14px; }
.logo-icon { width: 32px; height: 32px; background: #083032; border-radius: 7px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: white; font-size: 11px; }
h2 { font-size: 16px; margin: 0; color: #ffffff; font-weight: 700; }
p { font-size: 11px; color: #a8b5b5; margin: 0 0 14px; line-height: 1.4; }
.btn-group { display: flex; flex-direction: column; gap: 9px; }
.btn { width: 100%; padding: 13px; border: none; border-radius: 9px; font-size: 15px; font-weight: 700; cursor: pointer; transition: opacity 0.15s; color: white; }
.btn:hover { opacity: 0.88; }
.btn-resi { background: #66A998; }
.btn-btl { background: #D9A948; }
.btn-ltd { background: #8B5CF6; }
.btn:disabled { opacity: 0.5; cursor: default; }
#status { margin-top: 12px; font-size: 11px; color: #a8b5b5; text-align: center; }
.success { color: #4ade80; }
.error { color: #f87171; }
</style>
</head>
<body>
<div class="logo">
<div class="logo-icon">BMS</div>
<h2>Rate Capture</h2>
</div>
<p>Click a product type to capture rates from the current Acre page.</p>
<div class="btn-group">
<button class="btn btn-resi" data-ptype="Residential">Resi</button>
<button class="btn btn-btl" data-ptype="Buy to Let">BTL</button>
<button class="btn btn-ltd" data-ptype="LTD Buy To Let">LTD</button>
</div>
<div id="status"></div>
<script src="popup.js"></script>
</body>
</html>popup.js
const MAX_CHARS = 50000;
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', async () => {
const productType = btn.dataset.ptype;
const status = document.getElementById('status');
const allBtns = document.querySelectorAll('.btn');
allBtns.forEach(b => b.disabled = true);
status.textContent = 'Reading page...';
status.className = '';
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab.url.includes('acre') && !tab.url.includes('myac')) {
status.textContent = 'Not on an Acre page. Open Acre first.';
status.className = 'error';
allBtns.forEach(b => b.disabled = false);
return;
}
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => {
// Try to find rate tables specifically — avoids capturing calculations, headers, etc.
const tables = document.querySelectorAll('table');
if (tables.length > 0) {
let content = '';
tables.forEach(t => {
t.querySelectorAll('tr').forEach(tr => {
const cells = Array.from(tr.querySelectorAll('td, th')).map(c => c.textContent.trim());
if (cells.length > 0) content += cells.join(' | ') + '\n';
});
content += '\n';
});
if (content.length > 200) return content;
}
// Fallback: try main content area
const main = document.querySelector('main, [role="main"], #main-content, .main-content');
if (main && main.innerText.length > 200) return main.innerText;
// Last resort: full page text
return document.body.innerText;
}
});
let pageContent = results[0].result || '';
if (pageContent.length < 50) {
status.textContent = 'Could not read page content.';
status.className = 'error';
allBtns.forEach(b => b.disabled = false);
return;
}
// Truncate to first 50,000 characters (~150+ deals) for parsing
if (pageContent.length > MAX_CHARS) {
pageContent = pageContent.substring(0, MAX_CHARS);
}
const appUrl = 'https://bespokemortgagesolutions.com';
const captureUrl = appUrl + '/RateCapture#fromExtension=1&autoSave=1';
const newTab = await chrome.tabs.create({ url: captureUrl });
// Send data to background service worker for injection
chrome.runtime.sendMessage({
type: 'injectRateData',
tabId: newTab.id,
data: { content: pageContent, product_type: productType }
});
status.textContent = 'Capturing & saving...';
status.className = 'success';
setTimeout(() => window.close(), 3000);
} catch (err) {
status.textContent = 'Error: ' + err.message;
status.className = 'error';
allBtns.forEach(b => b.disabled = false);
}
});
});background.js
// Background service worker — handles data injection after popup closes
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'injectRateData') {
const tabId = message.tabId;
const data = message.data;
function inject(attempt) {
chrome.scripting.executeScript({
target: { tabId: tabId },
function: (d) => {
sessionStorage.setItem('rateCaptureData', JSON.stringify(d));
window.dispatchEvent(new CustomEvent('rateCaptureDataReady'));
},
args: [data]
}).then(() => {
// Retry once after 2s in case the React app wasn't ready yet
if (attempt === 0) {
setTimeout(() => inject(1), 2000);
}
}).catch(() => {
if (attempt === 0) {
setTimeout(() => inject(1), 2000);
}
});
}
chrome.tabs.get(tabId, (tab) => {
if (tab && tab.status === 'complete') {
inject(0);
} else {
chrome.tabs.onUpdated.addListener(function listener(tid, info) {
if (tid === tabId && info.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
inject(0);
}
});
}
});
sendResponse({ success: true });
}
return true;
});icon.png

Save this as icon.png in the same folder as the other files.
