Skip to content
SIP-01Public

Repository files navigation

Crawlstr

Crawlstr β€” a spider sitting in its web

Decentralized browser-based web crawler. Turn your browser into a voluntary crawl node that feeds the shared SIP-01 index on Nostr β€” the canonical Search Index Protocol v1.2. No backend. No tracking. No accounts required.

Every page you crawl becomes a kind 39697 web index observation β€” instantly searchable by 0xSearchstr, 0xPresearchstr, UNCAGED, and any future SIP-01 compatible client.

Live: https://crawlstr.shakespeare.wtf

Edit with Shakespeare


How It Works

You add a seed URL
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Crawler   β”‚  IndexedDB queue, persistent across sessions
β”‚   Engine    β”‚  Battery/WiFi/bandwidth aware
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β–Ό
   Fetch page (respects robots.txt, rate-limited per domain)
       β”‚
       β–Ό
   Parse HTML (title, description, text, links, language)
       β”‚
       β–Ό
   SHA-256 content hash (dedup across the network)
       β”‚
       β–Ό
   Sign kind 39697 event with per-device indexer key
       β”‚
       β–Ό
   Publish to Nostr relays (SIP-01)
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Shared SIP-01 Index           β”‚
β”‚                                      β”‚
β”‚  0xSearchstr reads it                β”‚
β”‚  0xPresearchstr reads it             β”‚
β”‚  UNCAGED reads it                    β”‚
β”‚  Your fork reads it                  β”‚
β”‚  Any SIP-01 client reads it          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What Makes This Different

Most "decentralized search" projects still run centralized crawlers. Crawlstr makes every browser a potential crawler β€” opt-in, transparent, resource-aware.

Feature Description
Opt-in only Nothing runs without explicitly pressing "Start Crawling"
🎲 Random Scout One button β€” picks a curated starting point you haven't scouted and goes
SIP-01 native Same protocol as 0xSearchstr, 0xPresearchstr, UNCAGED β€” one shared index
Per-device identity Anonymous indexer keypair, separate from your Nostr identity
No query leakage Events contain page metadata only β€” never what anyone searched for
Resource aware Battery, WiFi, bandwidth limits. Eco mode. Charging-only mode.
robots.txt Respected by default (configurable)
Rate limited 5–8 seconds between requests per domain
Persistent queue IndexedDB-backed, survives browser restarts
Offline capable Crawl queue persists; publishes when Nostr is reachable
Network heartbeat Kind 16919 β€” visible on the SIP-01 dashboard while running
PWA Installable, works on mobile and desktop

The Scout / Indexer Split

Crawlstr is deliberately not a small copy of Indexstr. They're two classes of node:

        THE INDEXSTR NETWORK
                  β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚                           β”‚
πŸͺΆ CRAWLSTR                🏭 INDEXSTR
Lightweight scout          Heavyweight indexer
"I found something"        "I operate indexing capacity"
    β”‚                           β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β–Ό
              NOSTR / SIP-01
                  β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β–Ό             β–Ό             β–Ό
0xSearchstr   UNCAGED      Other engines
πŸͺΆ Crawlstr (this) 🏭 Indexstr
Role Human-directed & random discovery Systematic distributed crawling
Seeds You paste a URL, or Random Scout picks Bundled curated SQLite collections
Queue Small, session-scoped Massive, sharded across 256 slots
Discovery Links, feeds, sitemaps, canonical Collection URLs at scale
Device Any browser, incl. a phone on WiFi Desktop/VPS-class contribution
Coupling None. Both speak SIP-01 to Nostr. None. Both speak SIP-01 to Nostr.

Crawlstr finds. Indexstr maintains. Nostr distributes. Searchstr searches.


Quick Start

git clone https://github.com/NostrDanish/Crwalstr.git
cd Crwalstr
npm install
npm run dev

Open the printed URL, add a seed URL, press Start Crawling.


Usage

Random Scout

The fastest way to contribute: press Explore a random corner of the web and Crawlstr shows you a starting point β€” its category and URL β€” before anything happens. Scout This starts the crawl; Another reshuffles. No account, no configuration, no URL needed.

Selection history stays in your browser (localStorage) and is never published β€” only the resulting page observations go to Nostr.

The seed corpus

The corpus lives in src/data/seeds/*.txt β€” one category per plain-text file, so improving the dataset never means touching crawler logic:

src/data/seeds/
β”œβ”€β”€ nostr.txt      ← Nostr ecosystem
β”œβ”€β”€ bitcoin.txt    ← Bitcoin & open money
β”œβ”€β”€ dev.txt        ← Dev resources & awesome lists
β”œβ”€β”€ science.txt    ← Science & education
β”œβ”€β”€ books.txt      ← Books, archives & libraries
β”œβ”€β”€ music.txt      ← Music, art & culture
β”œβ”€β”€ games.txt      ← Games
β”œβ”€β”€ opendata.txt   ← Open data & internet infrastructure
└── indie.txt      ← Indie blogs & the small web

How a seed is picked

Not plain Math.random() over a flat list. Each pick rolls a weighted strategy, tuned for long-tail coverage:

Weight Strategy Picks from
40% Fresh Seeds this device has never scouted
25% Rare Seeds this device has scouted least often
15% Category The category this device has explored least
10% Stale Seeds not scouted for the longest time
10% Random Pure randomness

Previewing a seed (the "random corner" card) does not count as scouting it β€” a dismissed preview never penalizes the seed. The selection commits only when the crawl actually starts.

Seed a URL

Enter any URL in the Seed URLs tab:

https://bitcoin.org

The crawler fetches the page, extracts content, hashes it, signs a SIP-01 observation, and publishes to Nostr. Then it follows links up to depth 3.

Crawl Modes

Mode Budget Purpose
Quick Scan ~5 pages Fast inspection of one site
Site Scan ~30 pages A useful, complete crawl
Deep Scan ~150 pages Deeper crawl of a larger site
Volunteer until stopped Continuous contribution, every limit still applies

When a mode's budget is spent, the crawler stops cleanly on its own. Random Explorer (continuous random scouting) is opt-in via startExplorer β€” never the default.

Discovery Sources

Crawlstr is a scout, so it prioritizes cheap, high-value discovery over bulk fetching:

  • RSS/Atom feeds β€” one small XML file yields current content URLs with titles and dates; entries are indexed directly and queued for full fetches
  • Sitemaps β€” Sitemap: declarations in robots.txt, plus /sitemap.xml; sampled and bounded, never a firehose
  • Canonical URLs β€” observations are filed under the page's claimed canonical identity
  • Link graph β€” same-domain links followed with per-domain rate limits; login/cart/calendar traps filtered out

Crawler Settings

Setting Default Description
WiFi Only Off Only crawl on WiFi networks
Charging Only Off Only crawl while device is charging
Respect robots.txt On Follow website crawling policies
Eco Mode On Slower crawling, less resource usage
Follow RSS/Atom feeds On Index feed entries (cheap discovery)
Read sitemaps On Use sitemap.xml for discovery (sampled)

Indexer Identity

Each browser gets its own anonymous indexer keypair (visible in the dashboard). This key signs all kind 39697 observations. It is:

  • Pseudonymous β€” not linked to your personal Nostr identity
  • Replaceable β€” regenerating creates a new indexer
  • Local β€” the secret key never leaves your browser
  • Exportable β€” for backup or migration

Protocol

Crawlstr publishes SIP-01 (Search Index Protocol) events β€” the same protocol used by the entire Searchstr ecosystem.

Kind 39697 β€” Web Index Observation

{
  "kind": 39697,
  "pubkey": "<device indexer pubkey>",
  "created_at": 1786250000,
  "content": "{\"title\":\"Example Page\",\"description\":\"A page about...\"}",
  "tags": [
    ["d", "widx:9f86d081884c7d659a2feaa0c55ad015"],
    ["u", "https://example.com/page"],
    ["l", "en"],
    ["x", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
    ["v", "1"],
    ["source", "crawlstr/1"],
    ["network", "clearnet"],
    ["type", "page"],
    ["alt", "Web index observation: Example Page"]
  ]
}
Tag Meaning
d "widx:" + sha256(normalized_url)[0:32] β€” URL identity, identical across all indexers
u Canonical URL (normalized per SIP-01 Β§7)
x Content hash: sha256(title + "\n" + description)
v Schema version "1"
l ISO 639-1 language code
source "crawlstr/1"
network Extension registry (Β§9.2) β€” always clearnet for a browser crawler
type Extension registry β€” repository for GitHub/GitLab, else page
alt Human-readable description (the alt convention, spec Β§12.3)

Full schema documentation: NIP.md Β· Canonical spec: SIP-01 v1.1

Relay Pool

Observations are published to:

SIP-01-aware index relays:

  • wss://relay-na1.metanomalist.com/
  • wss://test-sip-relay.sip-01test.workers.dev/
  • wss://sip-relay-2.sip-booster-relay.workers.dev/
  • wss://sip-relay-3.uncaged-sip.workers.dev/
  • wss://sip-relay-4.sip-relay-4.workers.dev/

Search relays (NIP-50):

  • wss://relay.nostr.band/
  • wss://relay.ditto.pub/
  • wss://search.nos.today/ (read-only β€” "blocked: writes disabled")
  • wss://relay.noswhere.com/

Write relays (for propagation):

  • wss://jskitty.cat/nostr
  • wss://relay.primal.net/
  • wss://relay.damus.io/
  • wss://nostr.hifish.org/
  • ws://acuy3mjnv26tkyaaucndlxmg2ocntz4rtebhavk57vgruozm42iaznqd.onion/ (Tor only β€” reachable from Tor Browser; clearnet browsers skip it)

Each observation is pushed to every relay in the set via targeted per-relay connections.

Custom relays: add your own in Settings β†’ Publish relays. They persist locally and merge into the publish set. The built-in set can't be removed.

Auto-discovery: Settings β†’ Discover NIP-50 / SIP-01 relays queries relay-monitor announcements (NIP-66 kind 30166), filters to relays advertising NIP-50, then verifies each candidate with a live capability probe (NIP-11 β€” checking supported_nips for 50 and the uncaged_index block for SIP-01). Verified candidates appear with one-click add; nothing is added automatically. Discovery is a hint, never a requirement β€” the built-in set keeps the app fully functional without it (NIP-66's risk guidance).

Capability testing: every relay in the list has a test button that probes its NIP-11 document and shows SIP-01 / NIP-50 badges plus latency.


Federation: One Index, Many Crawlers

Crawlstr is one more independent indexer in the SIP-01 ecosystem:

Crawlstr (browser crawler)
    β”‚
    β–Ό kind 39697, signed by device indexer key
Nostr Relays
    β”‚
    β”œβ”€β”€β†’ 0xSearchstr (search engine) reads it
    β”œβ”€β”€β†’ 0xPresearchstr (search engine) reads it
    β”œβ”€β”€β†’ UNCAGED (search engine template) reads it
    └──→ Any SIP-01 client reads it

Multiple crawlers observing the same URL produce events with the same d tag and different pubkeys β€” search nodes group by d and count distinct authors ("7 independent indexers saw this page").


Browser Limitations

Crawlstr is honest about what a browser can and cannot do:

  • CORS β€” A browser cannot read cross-origin responses unless the site sends CORS headers, and most don't. Crawlstr tries a direct fetch first, then falls back to a CORS proxy so real websites can actually be crawled. The trade-off is honest: when the proxy is used, the proxy operator sees which URL was fetched (never a search query, never a user identity). The dashboard shows the direct/proxy split per session.
  • JavaScript rendering β€” Crawlstr parses static HTML. Single-page apps that require JavaScript rendering won't have their full content extracted.
  • Background execution β€” Mobile browsers may throttle or kill background tabs. The crawler is most effective when the tab is active.
  • Rate limits β€” Per-domain rate limiting is built-in (5–8 seconds between requests). This is respectful by design.
  • Resource budgets are real β€” the bandwidth cap counts every byte (pages, robots.txt, feeds, sitemaps, proxy overhead), and the pages/hour cap is enforced on a sliding window. The page-size cap is clamped to the remaining hourly budget so a single page can't overshoot it.
  • SSRF guard at the proxy boundary β€” before any URL is handed to fetch (direct or proxied), it's checked against localhost, RFC1918, link-local (incl. cloud metadata 169.254.169.254), CGNAT, and the odd IPv4 forms browsers accept (integer, hex, octal, short), plus IPv6 loopback/ULA/link-local. Direct-path redirects are re-checked against the same list. The remaining gap β€” a public URL that 302s into private space at the proxy β€” is the proxy operator's to close, and we say so.

For unrestricted crawling, run a desktop/CLI SIP-01 crawler alongside Crawlstr.


Tech Stack

  • React 19 + TypeScript + Vite
  • TailwindCSS 4 + shadcn/ui
  • Nostrify β€” Nostr relay pool
  • nostr-tools β€” Event signing (finalizeEvent)
  • idb β€” IndexedDB wrapper for the crawl queue
  • TanStack Query β€” Data fetching + caching
  • PWA β€” Service worker, manifest, installable

Project Structure

src/
β”œβ”€β”€ crawler/
β”‚   β”œβ”€β”€ engine.ts           ← Main crawler orchestrator (crawl loop, queue, scheduling)
β”‚   β”œβ”€β”€ queue.ts            ← IndexedDB queue (ready-job scheduling) + observed/fetched split
β”‚   β”œβ”€β”€ fetcher.ts          ← HTTP fetcher (CORS, timeout, SSRF-guarded, metered)
β”‚   β”œβ”€β”€ safety.ts           ← SSRF guard β€” refuses non-public targets at the proxy boundary
β”‚   β”œβ”€β”€ meter.ts            ← Sliding-window resource accounting (every byte, every page)
β”‚   β”œβ”€β”€ parser.ts           ← HTML parser (title, description, text, links, language)
β”‚   β”œβ”€β”€ hasher.ts           ← SHA-256 content hashing for local dedup
β”‚   β”œβ”€β”€ webIndex.ts         ← SIP-01: URL normalization, event build/parse (byte-compatible)
β”‚   β”œβ”€β”€ indexerIdentity.ts  ← Per-device anonymous indexer keypair
β”‚   β”œβ”€β”€ publisher.ts        ← Signs + publishes kind 39697 via finalizeEvent
β”‚   β”œβ”€β”€ relays.ts           ← Ecosystem relay pool configuration
β”‚   β”œβ”€β”€ robots.ts           ← robots.txt parser (policies + Sitemap: discovery)
β”‚   β”œβ”€β”€ feed.ts             ← RSS/Atom detection + parsing (cheap discovery)
β”‚   β”œβ”€β”€ sitemap.ts          ← XML sitemap parsing (urlset + sitemapindex, sampled)
β”‚   β”œβ”€β”€ seeds.ts            ← Random Scout selection engine (weighted strategies)
β”‚   β”œβ”€β”€ limits.ts           ← Per-domain rate limiting
β”‚   └── types.ts            ← TypeScript interfaces (incl. crawl modes)
β”œβ”€β”€ data/
β”‚   └── seeds/              ← The seed corpus: one plain-text file per category
β”œβ”€β”€ components/
β”‚   └── crawler/
β”‚       └── CrawlerDashboard.tsx  ← Main UI (toggle, stats, seed, history, settings)
β”œβ”€β”€ hooks/
β”‚   └── useCrawler.ts       ← React hook wiring engine to Nostr
β”œβ”€β”€ pages/
β”‚   └── Index.tsx           ← Landing page + dashboard
└── NIP.md                  ← Protocol documentation

Ecosystem

Project Role URL
Crawlstr (this) Lightweight scout β†’ SIP-01 publisher crawlstr.shakespeare.wtf
Indexstr Heavyweight distributed indexer w/ collections β€”
0xSearchstr Search engine β†’ SIP-01 reader 0xsearchstr.shakespeare.wtf
0xPresearchstr Community fork with keyword staking presearchstr.shakespeare.wtf
UNCAGED-ENGINE Minimal search engine template uncaged.shakespeare.wtf

Privacy, Honestly

  • No login required to crawl. No account. No tracking.
  • Crawl observations are signed by a per-device anonymous keypair, never your personal Nostr identity.
  • Events contain page metadata only β€” never search queries, never browsing history.
  • Your crawl history stays in your browser (IndexedDB). Clearing browser data removes it.
  • Relay operators see the observation event and your IP address β€” that's how Nostr works. Key separation is guaranteed; network anonymity is not.
  • Use a VPN or Tor β€” we recommend NymVPN.

Support us: https://nym.com/pricing?ref=aYPKAFmGpJi


License

MIT


Vibed with Shakespeare

Releases

Packages

Contributors

Languages