buzz: realtime WS listener (NIP-42 auth, instant push) replaces 45s polling

This commit is contained in:
zai-agent 2026-09-16 20:24:06 +00:00
parent b3ed0a75a1
commit 0eebaceee1
4 changed files with 95 additions and 0 deletions

1
buzz/ws-listener/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

View file

@ -0,0 +1,73 @@
// Raw WebSocket listener for Buzz #general — no relay abstraction.
// Handles NIP-42 auth explicitly: on ["AUTH", challenge] we sign kind 22242.
// On a matching kind-9 event from another pubkey: spool to inbox, exit 0.
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
import fs from 'node:fs'
const RELAY = 'wss://ch4t.buzz'
const CH = 'cc2aac17-af28-47ba-8563-c2cbb8633084'
const ME = '4abe5fcdf9695be34bdfc8fe82297aaaf0cf65b5ca92b6c789f6f8783b5ee197'
const INBOX = '/home/opc/.buzz-inbox'
const MAX_LIFETIME = 4 * 3600 * 1000
const keyText = fs.readFileSync('/home/opc/buzz-agent-key.txt', 'utf8')
const SK = Uint8Array.from(keyText.match(/SECRET:\s*(\S+)/)[1].match(/.{2}/g).map((h) => parseInt(h, 16)))
const MY_PUBKEY = getPublicKey(SK)
const ws = new WebSocket(RELAY)
const seen = new Set()
let authEventId = null
let subscribed = false
function subscribe() {
if (subscribed) return
subscribed = true
ws.send(JSON.stringify(['REQ', 'general', { kinds: [9], '#h': [CH], since: Math.floor(Date.now() / 1000) }]))
console.error('subscribed')
}
function sendAuth(challenge) {
const ev = finalizeEvent(
{ kind: 22242, created_at: Math.floor(Date.now() / 1000), tags: [['relay', RELAY], ['challenge', challenge]], content: '' },
SK
)
authEventId = ev.id
ws.send(JSON.stringify(['AUTH', ev]))
console.error('sent NIP-42 auth response')
}
ws.onopen = () => {
console.error('ws open — waiting for auth challenge')
}
ws.onmessage = (msg) => {
let data
try { data = JSON.parse(msg.data) } catch { return }
const [type, ...rest] = data
if (type === 'AUTH') {
console.error('auth challenge received')
sendAuth(rest[0])
return
}
if (type === 'OK') {
if (rest[0] === authEventId && rest[1] === true) {
console.error('auth OK — subscribing')
subscribe()
}
return
}
if (type === 'NOTICE') { console.error('NOTICE:', rest[0]); return }
if (type === 'EVENT') {
const [, subid, evt] = data
if (subid !== 'general') return
if (evt.pubkey === ME || seen.has(evt.id)) return
seen.add(evt.id)
fs.appendFileSync(INBOX, JSON.stringify(evt) + '\n')
console.error('message received from', evt.pubkey.slice(0, 10))
process.exit(0)
}
if (type === 'EOSE') console.error('EOSE — waiting for live events')
}
ws.onclose = () => { console.error('ws closed'); process.exit(2) }
ws.onerror = () => {}
setTimeout(() => { console.error('lifetime elapsed'); process.exit(3) }, MAX_LIFETIME)

View file

@ -0,0 +1,16 @@
{
"name": "ws-listener",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"nostr-tools": "^2.25.2"
}
}

5
buzz/ws-listener/run.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# ZCode presence: WS listener exits when a message arrives -> agent wakes.
cd "$(dirname "$0")"
[ -d node_modules ] || npm install nostr-tools >/dev/null 2>&1
exec node listener-raw.mjs