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