tinyrelaycommit

Commit 974ca1d9aef3

browse files at this commit | history

1diff --git a/scripts/qa/nip46-bunker.mjs b/scripts/qa/nip46-bunker.mjs
2index 558a038..e8e4f14 100644
3--- a/scripts/qa/nip46-bunker.mjs
4+++ b/scripts/qa/nip46-bunker.mjs
5@@ -1,9 +1,98 @@
6-const path=require('path');const {createRequire}=require('module');const deps=createRequire(path.resolve(__dirname,'../../../bindws/node_modules/'));const {WebSocketServer,WebSocket}=deps('ws'); global.WebSocket=WebSocket;
7-const {generateSecretKey,getPublicKey,finalizeEvent}=deps('nostr-tools'); const nip44=require(path.join(path.dirname(deps.resolve('nostr-tools')),'nip44.js'));
8-const wss=new WebSocketServer({port:8789}); const clients=new Map(),events=[]; const bunkerSK=generateSecretKey(), bunkerPK=getPublicKey(bunkerSK), userSK=generateSecretKey(), userPK=getPublicKey(userSK);
9-function match(ev,f){if(f.kinds&&!f.kinds.includes(ev.kind))return false;if(f.authors&&!f.authors.includes(ev.pubkey))return false;for(const [k,vals] of Object.entries(f))if(k[0]==='#'&&!ev.tags.some(t=>t[0]===k.slice(1)&&vals.includes(t[1])))return false;return true}
10-wss.on('connection',ws=>{const subs=new Map();clients.set(ws,subs);ws.on('message',raw=>{let m;try{m=JSON.parse(raw)}catch{return}if(m[0]==='REQ'){const [id,...fs]=m.slice(1);subs.set(id,fs);for(const ev of events)if(fs.some(f=>match(ev,f)))ws.send(JSON.stringify(['EVENT',id,ev]));ws.send(JSON.stringify(['EOSE',id]));}else if(m[0]==='EVENT'){const ev=m[1];events.push(ev);for(const [peer,ps] of clients)for(const [id,fs] of ps)if(fs.some(f=>match(ev,f)))peer.send(JSON.stringify(['EVENT',id,ev]));ws.send(JSON.stringify(['OK',ev.id,true,'']))}});ws.on('close',()=>clients.delete(ws));});
11-function publish(obj,clientPK){const conv=nip44.getConversationKey(bunkerSK,clientPK);const ev=finalizeEvent({kind:24133,tags:[['p',clientPK]],content:nip44.encrypt(JSON.stringify(obj),conv),created_at:Math.floor(Date.now()/1000)},bunkerSK);events.push(ev);for(const [peer,ps] of clients)for(const [id,fs] of ps)if(fs.some(f=>match(ev,f)))peer.send(JSON.stringify(['EVENT',id,ev]));}
12-setInterval(()=>{for(const ev of events){if(ev.kind!==24133||ev.pubkey===bunkerPK||ev._handled)continue;ev._handled=true;try{const clientPK=ev.pubkey,conv=nip44.getConversationKey(bunkerSK,clientPK),req=JSON.parse(nip44.decrypt(ev.content,conv));let result='';if(req.method==='connect')result='ack';else if(req.method==='ping')result='pong';else if(req.method==='get_public_key')result=userPK;else if(req.method==='sign_event'){const x=JSON.parse(req.params[0]);result=JSON.stringify(finalizeEvent(x,userSK));}publish({id:req.id,result},clientPK);}catch(e){console.error('handler',e.message)}}},50);
13-console.log(`bunker://?relay=ws%3A%2F%2F127.0.0.1%3A8789&pubkey=${bunkerPK}`); // UI parser expects pubkey in host; print corrected below
14-console.log(`bunker://${bunkerPK}?relay=ws%3A%2F%2F127.0.0.1%3A8789`);
15+#!/usr/bin/env node
16+// A throwaway NIP-46 bunker for trying the Bunker URL sign-in flow locally.
17+// It runs a minimal in-memory relay on 127.0.0.1:8789, answers connect,
18+// ping, get_public_key and sign_event requests for a fresh user key, and
19+// prints the bunker URL to paste into the sign-in page.
20+import { WebSocketServer, WebSocket } from "ws";
21+import { finalizeEvent, generateSecretKey, getPublicKey } from "nostr-tools/pure";
22+import * as nip44 from "nostr-tools/nip44";
23+
24+globalThis.WebSocket = WebSocket;
25+
26+const port = Number(process.env.BUNKER_PORT ?? 8789);
27+const bunkerSecret = generateSecretKey();
28+const bunkerPubkey = getPublicKey(bunkerSecret);
29+const userSecret = generateSecretKey();
30+const userPubkey = getPublicKey(userSecret);
31+const clients = new Map();
32+const events = [];
33+
34+const matches = (event, filter) => {
35+ if (filter.kinds && !filter.kinds.includes(event.kind)) return false;
36+ if (filter.authors && !filter.authors.includes(event.pubkey)) return false;
37+ for (const [key, values] of Object.entries(filter)) {
38+ if (key[0] === "#" && !event.tags.some(tag => tag[0] === key.slice(1) && values.includes(tag[1]))) return false;
39+ }
40+ return true;
41+};
42+
43+const broadcast = event => {
44+ events.push(event);
45+ for (const [peer, subscriptions] of clients) {
46+ for (const [id, filters] of subscriptions) {
47+ if (filters.some(filter => matches(event, filter))) peer.send(JSON.stringify(["EVENT", id, event]));
48+ }
49+ }
50+};
51+
52+const server = new WebSocketServer({ host: "127.0.0.1", port });
53+server.on("connection", socket => {
54+ const subscriptions = new Map();
55+ clients.set(socket, subscriptions);
56+ socket.on("message", raw => {
57+ let message;
58+ try { message = JSON.parse(raw); } catch { return; }
59+ if (message[0] === "REQ") {
60+ const [id, ...filters] = message.slice(1);
61+ subscriptions.set(id, filters);
62+ for (const event of events) {
63+ if (filters.some(filter => matches(event, filter))) socket.send(JSON.stringify(["EVENT", id, event]));
64+ }
65+ socket.send(JSON.stringify(["EOSE", id]));
66+ } else if (message[0] === "EVENT") {
67+ broadcast(message[1]);
68+ socket.send(JSON.stringify(["OK", message[1].id, true, ""]));
69+ } else if (message[0] === "CLOSE") {
70+ subscriptions.delete(message[1]);
71+ }
72+ });
73+ socket.on("close", () => clients.delete(socket));
74+});
75+
76+const reply = (clientPubkey, payload) => {
77+ const conversation = nip44.getConversationKey(bunkerSecret, clientPubkey);
78+ broadcast(finalizeEvent({
79+ kind: 24133,
80+ tags: [["p", clientPubkey]],
81+ content: nip44.encrypt(JSON.stringify(payload), conversation),
82+ created_at: Math.floor(Date.now() / 1000)
83+ }, bunkerSecret));
84+};
85+
86+const answer = request => {
87+ switch (request.method) {
88+ case "connect": return "ack";
89+ case "ping": return "pong";
90+ case "get_public_key": return userPubkey;
91+ case "sign_event": return JSON.stringify(finalizeEvent(JSON.parse(request.params[0]), userSecret));
92+ default: throw Error("unsupported method " + request.method);
93+ }
94+};
95+
96+const handled = new WeakSet();
97+setInterval(() => {
98+ for (const event of events) {
99+ if (event.kind !== 24133 || event.pubkey === bunkerPubkey || handled.has(event)) continue;
100+ handled.add(event);
101+ try {
102+ const conversation = nip44.getConversationKey(bunkerSecret, event.pubkey);
103+ const request = JSON.parse(nip44.decrypt(event.content, conversation));
104+ reply(event.pubkey, { id: request.id, result: answer(request) });
105+ } catch (err) {
106+ console.error("bunker request failed:", err.message);
107+ }
108+ }
109+}, 50);
110+
111+console.log("user pubkey: " + userPubkey);
112+console.log(`bunker://${bunkerPubkey}?relay=${encodeURIComponent("ws://127.0.0.1:" + port)}`);

About

ownerc01503e8035f
visibilitypublic
head974ca1d9aef3

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs