index.html raw

   1  <!doctype html>
   2  <html lang="en">
   3  <head>
   4  <meta charset="utf-8">
   5  <title>nostrino</title>
   6  <style>
   7    :root { color-scheme: dark; }
   8    body { font: 15px/1.5 system-ui, sans-serif; max-width: 48rem; margin: 3rem auto; padding: 0 1rem; background: #121212; color: #e8e8e8; }
   9    h1 { font-size: 1.4rem; letter-spacing: .02em; }
  10    label { display: block; margin: 1rem 0 .3rem; color: #9a9a9a; font-size: .85rem; }
  11    input, select, button, textarea { font: inherit; padding: .5rem .6rem; background: #1e1e1e; color: inherit; border: 1px solid #3a3a3a; border-radius: 4px; }
  12    input, textarea { width: 100%; box-sizing: border-box; }
  13    textarea { min-height: 9rem; font-family: ui-monospace, monospace; font-size: .85rem; }
  14    .row { display: flex; gap: .5rem; align-items: end; }
  15    button { cursor: pointer; }
  16    pre { background: #1a1a1a; border: 1px solid #333; border-radius: 4px; padding: 1rem; overflow: auto; }
  17    .hint { color: #8a8a8a; font-size: .85rem; }
  18  </style>
  19  </head>
  20  <body>
  21  <h1>nostrino</h1>
  22  <p class="hint">A BIP157 light client in a dedicated worker. It syncs compact filters from a gateway over a public Nostr relay and matches scripts locally.</p>
  23  
  24  <label for="uri">gateway URI</label>
  25  <input id="uri" placeholder="nostr+chaindata://&lt;gateway-pubkey&gt;?relay=ws://127.0.0.1:34567&chains=regtest&v=1">
  26  
  27  <div class="row" style="margin-top:.6rem">
  28    <div>
  29      <label for="chain">chain</label>
  30      <select id="chain"><option>regtest</option><option>btc</option><option>testnet3</option></select>
  31    </div>
  32    <button id="sync">sync</button>
  33    <button id="cancel">cancel</button>
  34  </div>
  35  
  36  <label for="script">script hex to match (optional)</label>
  37  <div class="row">
  38    <input id="script" placeholder="626c6f636b2d37">
  39    <button id="match">match</button>
  40  </div>
  41  
  42  <details style="margin-top:1.5rem">
  43    <summary class="hint">custom chain profile (XBT or another network)</summary>
  44    <p class="hint">Paste the profile JSON. It must include the genesis header, hash, magic and proof-of-work limit.</p>
  45    <textarea id="profile" placeholder='{"name":"xbt","magic":"fabfb5da","genesis_header":"...","genesis_hash":"..."}'></textarea>
  46    <button id="setprofile" style="margin-top:.4rem">set profile</button>
  47  </details>
  48  
  49  <pre id="out">idle</pre>
  50  
  51  <script>
  52  const worker = new Worker('worker.js');
  53  const out = document.getElementById('out');
  54  worker.onmessage = (event) => {
  55    const msg = event.data;
  56    if (msg.type === 'status') out.textContent = JSON.stringify(msg.status, null, 2);
  57    else if (msg.type === 'matches') out.textContent = 'matching heights: ' + JSON.stringify(msg.matches);
  58    else if (msg.type === 'error') out.textContent = 'error: ' + msg.error;
  59  };
  60  document.getElementById('sync').onclick = () => {
  61    const uri = document.getElementById('uri').value.trim();
  62    const chain = document.getElementById('chain').value;
  63    if (!uri) { out.textContent = 'enter a gateway URI'; return; }
  64    worker.postMessage({ type: 'sync', uri, chain });
  65  };
  66  document.getElementById('cancel').onclick = () => worker.postMessage({ type: 'cancel' });
  67  document.getElementById('setprofile').onclick = () => {
  68    const json = document.getElementById('profile').value.trim();
  69    if (json) worker.postMessage({ type: 'profile', json });
  70  };
  71  document.getElementById('match').onclick = () => {
  72    const script = document.getElementById('script').value.trim();
  73    if (script) worker.postMessage({ type: 'match', script });
  74  };
  75  </script>
  76  </body>
  77  </html>
  78