Files

12 KiB

nostr:// URI Scheme + JSON Viewer — Implementation Plan

Overview

Two complementary features that work together:

  1. JSON Viewer (Phase 1) — A rich JSON viewer with syntax highlighting and collapsible tree view, injected into all application/json responses. Useful everywhere, not just for nostr://.

  2. nostr:// URL Handling (Phase 2) — Smart URL bar parsing for Nostr bech32 entities. The browser fetches the event(s) from relays and displays the raw JSON (using the Phase 1 viewer). At the top of the page, a toolbar shows clickable links to configured external Nostr clients (helper applications). The browser is NOT a Nostr client — it shows raw data and lets the user open it in a client of their choice.


Phase 1: JSON Viewer

Goal

When the browser loads any application/json content (from any URL scheme — http, https, file, sovereign, nostr), instead of WebKit's default plain <pre> display, inject a rich JSON viewer with:

  • Syntax highlighting — colored keys, strings, numbers, booleans, null
  • Collapsible tree view — click to expand/collapse objects and arrays
  • Copy buttons — copy the raw JSON or a specific path
  • Dark mode — respects prefers-color-scheme
  • Search/filter — filter keys in large JSON documents

Architecture

WebKitGTK's built-in JSON display wraps raw JSON in a <pre> tag. We intercept this by injecting a user script that:

  1. Detects when the page content type is application/json
  2. Parses the <pre> content as JSON
  3. Replaces the <pre> with a styled tree view

Implementation approach: Use webkit_web_view_add_user_content_filter or a WebKitUserScript injected via WebKitUserContentManager. The script runs on all pages but only activates when document.contentType === 'application/json'.

Alternatively, intercept at the resource-load-started or load-changed level and inject the viewer script after load.

Files

www/
├── json-viewer/
│   ├── json-viewer.js    # Tree view renderer + syntax highlighting
│   └── json-viewer.css   # Tree view styles (light + dark)
src/
├── nostr_bridge.c        # Extended: serve json-viewer.js/css via sovereign://
├── tab_manager.c         # Extended: inject json-viewer script on JSON pages

JSON Viewer JS Design

// Pseudo-code for the viewer
(function() {
  if (document.contentType !== 'application/json') return;
  
  var raw = document.body.textContent;
  var data;
  try { data = JSON.parse(raw); } catch(e) { return; } // not valid JSON, leave as-is
  
  var viewer = createTreeView(data);
  document.body.innerHTML = '';
  document.body.appendChild(viewer);
  
  function createTreeView(obj, path) {
    // Recursively create collapsible nodes:
    // - Objects: { key: value, ... } with expand/collapse
    // - Arrays: [ item, ... ] with expand/collapse  
    // - Strings: green, with quotes
    // - Numbers: blue
    // - Booleans: orange
    // - Null: gray
    // - Nested objects/arrays: collapsible with item count
  }
})();

Styling

  • Light mode: white background, dark text, colored values
  • Dark mode: dark background, light text, adjusted colors
  • Monospace font for all JSON content
  • Indentation guides (vertical lines for nesting depth)
  • Hover highlight on rows
  • Expand/collapse arrows (▶ ▼)

Settings

  • json_viewer_enabled (default: true) — toggle in Settings dialog
  • Can be disabled per-page via a keyboard shortcut (e.g., Ctrl+Shift+J)

Phase 2: nostr:// URL Handling

Goal

The browser recognizes Nostr bech32 entities in the URL bar, fetches the event(s) from relays, and displays the raw JSON using the Phase 1 JSON viewer. At the top of the page, a toolbar shows clickable links to configured external Nostr clients (helper applications). The browser is NOT a Nostr client — it shows raw data and lets the user choose to open it in an external client.

User Flow

  1. User pastes npub1abc... or nostr:npub1abc... in the URL bar
  2. Browser fetches the relevant event(s) from relays
  3. Browser displays the raw JSON with the nice tree viewer (Phase 1)
  4. At the top of the page, a toolbar shows: "Open in: [Jumble] [Snort] [njump.me]"
  5. Clicking a helper app link opens that client's URL for the same entity in a new tab

URL Bar Smart Parsing

When the user types or pastes something in the URL bar, the browser should recognize these formats and normalize them:

Input Recognized as Normalized to
npub1abc... NIP-19 npub nostr:npub1abc...
nsec1abc... NIP-19 nsec (rejected — private key, don't navigate)
note1abc... NIP-19 note nostr:note1abc...
nevent1abc... NIP-19 nevent nostr:nevent1abc...
naddr1abc... NIP-19 naddr nostr:naddr1abc...
nprofile1abc... NIP-19 nprofile nostr:nprofile1abc...
nrelay1abc... NIP-19 nrelay nostr:nrelay1abc...
nostr:npub1abc... NIP-21 URI (as-is)
nostr://npub1abc... Our scheme (as-is)

NIP-21 defines nostr: as the standard prefix for bech32 entities in links. We support both nostr:npub1... (NIP-21 standard) and nostr://npub1... (our scheme variant).

nostr:// URI Scheme Handler

Register nostr:// (and handle nostr:) as a WebKit URI scheme. When navigated to:

  1. Parse the bech32 entity from the URL
  2. Determine what events to fetch based on entity type:
    • npub/nprofile: kind 0 (metadata), recent kind 1 (notes), kind 3 (contacts), kind 10002 (relays)
    • note/nevent: the specific event by ID
    • naddr: the event by kind + pubkey + d-tag
    • nrelay: NIP-11 relay info (HTTP GET)
  3. Fetch events from relays (using relay hints from the entity, or user's configured relays)
  4. Return the event(s) as JSON with Content-Type: application/json
  5. The Phase 1 JSON viewer renders it with the tree view
  6. The JSON viewer also injects a helper-app toolbar at the top of the page

Helper App Toolbar

When the JSON viewer detects that the page is a nostr:// response, it adds a toolbar above the JSON tree:

┌─────────────────────────────────────────────────────────────┐
│ Nostr entity: npub1abc...  │ Open in: [Jumble] [Snort] [njump] │
└─────────────────────────────────────────────────────────────┘

Each button is a link that opens the helper app's URL for this entity in a new tab. The toolbar is styled to match the JSON viewer's theme.

Helper App Configuration

In the Settings dialog, users configure a list of helper applications:

Helper applications for Nostr entities:
  Name: Jumble       URL: https://jumble.social/{entity}
  Name: Snort        URL: https://snort.social/{entity}
  Name: njump.me     URL: https://njump.me/{entity}
  Name: habla.news   URL: https://habla.news/a/{naddr}
  [+ Add helper app]

The {entity} placeholder is replaced with the full bech32 string (e.g., npub1abc...). The {naddr} placeholder is replaced with the naddr string (for article-specific clients). The {id} placeholder is replaced with the hex event ID.

Default helper apps (if user hasn't configured):

  • njump.me — https://njump.me/{entity} (universal, handles all entity types)
  • Snort — https://snort.social/{entity}
  • Jumble — https://jumble.social/{entity}

Settings Storage

[nostr]
helper_apps=Jumble|https://jumble.social/{entity},Snort|https://snort.social/{entity},njump.me|https://njump.me/{entity}

Stored as a comma-separated list of Name|URL pairs. Parsed on startup and passed to the JSON viewer via the sovereign:// bridge.

Files

src/
├── nostr_url.c           # URL parsing + entity detection (new)
├── nostr_url.h           # Public API (new)
├── nostr_scheme.c        # nostr:// URI scheme handler (new)
├── nostr_scheme.h        # Scheme handler API (new)
├── tab_manager.c         # Extended: URL bar smart parsing
├── settings.c            # Extended: helper app config
├── nostr_bridge.c        # Extended: settings page + helper app API
└── relay_fetch.c         # Extended: ad-hoc event fetch by entity
www/
├── json-viewer/
│   └── json-viewer.js    # Extended: helper app toolbar for nostr:// pages
├── settings.html         # Extended: helper app config UI
└── settings.js           # Extended: save/load helper app config

URL Bar Integration

In tab_manager.c, the normalize_url() function (or the URL entry activate handler) is extended:

/* Before trying to parse as a URL, check if it's a Nostr bech32 entity */
if (nostr_url_detect(input) != NOSTR_ENTITY_NONE) {
    char *nostr_url = nostr_url_normalize(input);
    /* Navigate to the nostr:// scheme — the scheme handler will
     * fetch events and return JSON */
    webkit_web_view_load_uri(webview, nostr_url);
    g_free(nostr_url);
    return;
}

When a page contains nostr:npub1... links (NIP-21 format), the on_decide_policy handler intercepts the navigation:

if (strncmp(uri, "nostr:", 6) == 0) {
    /* Normalize and navigate via our nostr:// scheme handler */
    char *nostr_url = nostr_url_normalize(uri);
    webkit_web_view_load_uri(webview, nostr_url);
    g_free(nostr_url);
    return TRUE; /* ignore the original navigation */
}

NIP-19 / NIP-21 Reference

NIP-19: bech32-encoded entities

Prefix Description Data
npub1 Public key 32-byte pubkey
nsec1 Private key 32-byte privkey
note1 Event ID 32-byte event ID
nprofile1 Profile TLV: pubkey + relay hints
nevent1 Event TLV: event ID + relay hints + author
naddr1 Address TLV: kind + pubkey + d-tag + relay hints
nrelay1 Relay TLV: relay URL

NIP-21: URI scheme

NIP-21 defines nostr: as the standard URI prefix:

  • nostr:npub1abc... — link to a profile
  • nostr:note1abc... — link to a note
  • nostr:nevent1abc... — link to an event
  • nostr:naddr1abc... — link to an addressable event
  • nostr:nprofile1abc... — link to a profile (with relay hints)
  • nostr:nrelay1abc... — link to a relay

We support both nostr:entity (NIP-21) and nostr://entity (our variant) — they're treated identically.


Implementation Order

Phase 1: JSON Viewer

  1. Create www/json-viewer/json-viewer.js — tree view + syntax highlighting
  2. Create www/json-viewer/json-viewer.css — styling (light + dark)
  3. Embed via embed_web_files.sh
  4. Inject into webviews via WebKitUserScript or content filter
  5. Add json_viewer_enabled setting
  6. Test with local .json files, API responses, and sovereign:// endpoints

Phase 2: nostr:// URL Handling

  1. Create nostr_url.c — bech32 entity detection and parsing
  2. Extend nip019 in nostr_core_lib to decode TLV entities (nprofile, nevent, naddr, nrelay)
  3. Create nostr_scheme.c — register nostr:// URI scheme handler
  4. Extend relay_fetch.c — ad-hoc event fetch by entity type
  5. Add helper app configuration to settings (list of Name|URL pairs)
  6. Extend URL bar normalize_url() to detect bare bech32 entities
  7. Extend on_decide_policy to intercept nostr: links
  8. Extend JSON viewer to show helper app toolbar on nostr:// pages
  9. Add settings UI for configuring helper apps
  10. Test with various bech32 entities pasted in URL bar