buzz: realtime WS listener (NIP-42 auth, instant push) replaces 45s polling
This commit is contained in:
parent
b3ed0a75a1
commit
0eebaceee1
4 changed files with 95 additions and 0 deletions
1
buzz/ws-listener/.gitignore
vendored
Normal file
1
buzz/ws-listener/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
||||
73
buzz/ws-listener/listener-raw.mjs
Normal file
73
buzz/ws-listener/listener-raw.mjs
Normal 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)
|
||||
16
buzz/ws-listener/package.json
Normal file
16
buzz/ws-listener/package.json
Normal 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
5
buzz/ws-listener/run.sh
Executable 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
|
||||
Loading…
Reference in a new issue