- Light/dark/system theme (Settings > General) via nativeTheme.themeSource driving prefers-color-scheme across chrome, settings, popover, and home. - Search box redesigned: a magnifier icon opens the engine dropdown, the rest is a wider typing field; styled to match the browser (was a raw <select>). - Trimmed built-in engines to DuckDuckGo/Google/Brave/Bing/Startpage/Yandex; custom engines still add/remove via "Add / edit engines…". - WebRTC: the "WebRTC Network Limiter" extension can't run in Electron (chrome.privacy API is unavailable), so its function is now a native 4-mode WebRTC IP policy in Settings > Privacy. - Address-bar placeholder: "Ask a search engine or enter web address".
98 lines
4.6 KiB
JavaScript
98 lines
4.6 KiB
JavaScript
// Electrum-sourced BNS indexer — NO node, NO Fulcrum, NO disk, NO sync.
|
|
//
|
|
// Instead of scanning blocks from a BCHN node (see RpcSource in bns-indexer.js),
|
|
// this polls PUBLIC electrum/Fulcrum servers for just the two calls BNS needs —
|
|
// blockchain.scripthash.get_history (the beacon) + blockchain.transaction.get —
|
|
// caches the beacon set, and serves the same BNS electrum-over-WebSocket endpoint.
|
|
//
|
|
// Result: a featherweight, pinned-IP, DNS-independent caching relay you can run on
|
|
// a tiny VPS. Tradeoff vs. RpcSource: it trusts the upstream public servers (no own
|
|
// chain), but it needs no node, no Fulcrum, no storage.
|
|
//
|
|
// BNS_ELECTRUM_SERVERS=wss://a:50004,wss://b:50004 (comma-separated, tried in order)
|
|
// BNS_WS_BIND=127.0.0.1 BNS_WS_PORT=50010 BNS_POLL_MS=30000
|
|
// node electrum-source.js
|
|
import WebSocket from "ws";
|
|
import { pathToFileURL } from "node:url";
|
|
import { BeaconIndex, startElectrumWS } from "./bns-indexer.js";
|
|
|
|
const CONFIG = {
|
|
servers: (process.env.BNS_ELECTRUM_SERVERS ||
|
|
"wss://chipnet.bch.ninja:50004,wss://chipnet.imaginary.cash:50004,wss://chipnet.c3-soft.com:64004")
|
|
.split(",").map((s) => s.trim()).filter(Boolean),
|
|
wsBind: process.env.BNS_WS_BIND || "127.0.0.1",
|
|
wsPort: Number(process.env.BNS_WS_PORT || 50010),
|
|
beacon: process.env.BNS_BEACON_SCRIPTHASH ||
|
|
"e1cdbba719436f49acb4bf245b44df875c6d6b405eda46a9d3e736d138102f3b",
|
|
pollMs: Number(process.env.BNS_POLL_MS || 30000),
|
|
};
|
|
const log = (...a) => console.log(new Date().toISOString(), ...a);
|
|
|
|
// minimal newline-delimited electrum WS client (tolerates a missing trailing newline).
|
|
function elClient(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const ws = new WebSocket(url);
|
|
let id = 0, buf = ""; const pending = new Map();
|
|
const t = setTimeout(() => { try { ws.close(); } catch {} reject(new Error("connect timeout " + url)); }, 15000);
|
|
ws.on("open", () => { clearTimeout(t); resolve({
|
|
call(method, params = []) {
|
|
return new Promise((res, rej) => {
|
|
const i = ++id; pending.set(i, { res, rej });
|
|
ws.send(JSON.stringify({ id: i, method, params }) + "\n");
|
|
setTimeout(() => { if (pending.has(i)) { pending.delete(i); rej(new Error("timeout " + method)); } }, 20000);
|
|
});
|
|
},
|
|
close() { try { ws.close(); } catch {} },
|
|
}); });
|
|
ws.on("error", (e) => { clearTimeout(t); reject(e); });
|
|
const settle = (line) => { try { const m = JSON.parse(line); const p = pending.get(m.id); if (p) { pending.delete(m.id); m.error ? p.rej(new Error(JSON.stringify(m.error))) : p.res(m.result); } } catch {} };
|
|
ws.on("message", (d) => {
|
|
buf += d.toString(); let nl;
|
|
while ((nl = buf.indexOf("\n")) >= 0) { const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1); if (line) settle(line); }
|
|
const rest = buf.trim();
|
|
if (rest) { try { JSON.parse(rest); settle(rest); buf = ""; } catch {} }
|
|
});
|
|
});
|
|
}
|
|
|
|
async function connectAny(servers) {
|
|
let lastErr;
|
|
for (const url of servers) {
|
|
try { const cl = await elClient(url); await cl.call("server.version", ["BNS-Relay/0.1", "1.4.3"]); return { cl, url }; }
|
|
catch (e) { lastErr = e; log("upstream unavailable:", url, "-", e.message); }
|
|
}
|
|
throw lastErr || new Error("no upstream servers configured");
|
|
}
|
|
|
|
// Pull the beacon history + any not-yet-cached txs from an upstream, into the index.
|
|
async function sync(index, servers) {
|
|
const { cl, url } = await connectAny(servers);
|
|
try {
|
|
const history = await cl.call("blockchain.scripthash.get_history", [CONFIG.beacon]);
|
|
if (!Array.isArray(history)) throw new Error("bad get_history response");
|
|
let fetched = 0;
|
|
for (const { tx_hash, height } of history) {
|
|
index.history.set(tx_hash, height);
|
|
if (!index.txCache.has(tx_hash)) {
|
|
index.txCache.set(tx_hash, await cl.call("blockchain.transaction.get", [tx_hash, true]));
|
|
fetched++;
|
|
}
|
|
}
|
|
log(`synced via ${url}: ${index.history.size} beacon txs (${fetched} new)`);
|
|
} finally { cl.close(); }
|
|
}
|
|
|
|
async function main() {
|
|
log("electrum-sourced BNS indexer starting — upstreams:", CONFIG.servers.join(", "));
|
|
const index = new BeaconIndex(CONFIG.beacon);
|
|
startElectrumWS(index, { bind: CONFIG.wsBind, port: CONFIG.wsPort }, null); // cache-only: beacon txs are all cached
|
|
for (;;) {
|
|
try { await sync(index, CONFIG.servers); }
|
|
catch (e) { log("sync error (will retry):", e.message); }
|
|
await new Promise((r) => setTimeout(r, CONFIG.pollMs));
|
|
}
|
|
}
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) {
|
|
main().catch((e) => { console.error("fatal:", e.message); process.exit(1); });
|
|
}
|