clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / scripts / qa / buzz-chat-fixture.mjs
buzz-chat-fixture.mjs 3.7K
1#!/usr/bin/env node2// Disposable local corpus for Buzz-style replies, task cards and decisions.3import {finalizeEvent,getPublicKey} from 'nostr-tools/pure';4import {createHash} from 'node:crypto';5import {mkdir,writeFile} from 'node:fs/promises';6const base=process.env.TINY_QA_URL||'http://127.0.0.1:18468';7if(!['127.0.0.1','localhost'].includes(new URL(base).hostname)) throw Error('This fixture only runs locally.');8export const keys=Object.fromEntries(['owner','alice','agent'].map((name,i)=>[name,Uint8Array.from(Buffer.from((i+1).toString(16).padStart(64,'0'),'hex'))]));9const pub=Object.fromEntries(Object.entries(keys).map(([name,key])=>[name,getPublicKey(key)]));10export async function signed(path,method,value,who='owner') {11 const body=JSON.stringify(value),auth=finalizeEvent({kind:27235,created_at:Math.floor(Date.now()/1000),tags:[['u',base+path],['method',method],['payload',createHash('sha256').update(body).digest('hex')]],content:''},keys[who]);12 const response=await fetch(base+path,{method,headers:{'content-type':'application/json',authorization:'Nostr '+Buffer.from(JSON.stringify(auth)).toString('base64')},body});13 const result=await response.json();if(!response.ok||result.error||result.accepted===false&&!String(result.message).startsWith('duplicate:'))throw Error(JSON.stringify(result));return result;14}15export async function publish(who,kind,content,tags=[]) {const e=finalizeEvent({kind,content,tags,created_at:Math.floor(Date.now()/1000)},keys[who]);await signed('/events','POST',e,who);return e;}16if(process.argv[1]?.endsWith('buzz-chat-fixture.mjs')) {17 await mkdir('output/playwright/buzz',{recursive:true});18 for(const name of ['owner','alice']){await signed('/manage/rpc','POST',{method:'setmember',params:[pub[name],{role:'member'}]});await publish(name,0,JSON.stringify({name:name==='owner'?'Morgan':'Alice'}));}19 await publish('owner',30392,'',[['d',pub.agent],['p',pub.agent],['name','Build agent'],['room','work'],['k','0'],['k','9'],['k','11'],['k','12'],['k','7'],['k','1111'],['k','20001'],['k','20002'],['jobs','both'],['expiration',String(Math.floor(Date.now()/1000)+86400)]]);20 await publish('agent',0,JSON.stringify({name:'Build agent'}));21 await publish('owner',9007,'',[['h','work'],['name','Workshop'],['about','Agent tasks, decisions and chat'],['visibility','open']]);22 await publish('owner',9000,'',[['h','work'],['p',pub.agent,'member']]);23 const root=await publish('alice',9,'Could you prepare a preview?',[['h','work']]);24 const reply=await publish('owner',9,'Keep the existing color palette.',[['h','work'],['e',root.id,'','reply',pub.alice]]);25 await publish('alice',9,'And check the layout on mobile.',[['h','work'],['e',reply.id,'','reply',pub.owner]]);26 const task=await publish('owner',5000,'Prepare the site preview',[['h','work'],['e',root.id],['p',pub.agent],['subject','Prepare the site preview']]);27 await publish('agent',7000,'Desktop checks passed. Checking mobile navigation next.',[['h','work'],['e',task.id],['p',pub.owner],['status','processing','2 of 3 checks complete']]);28 const approval=await publish('agent',9,'Publish the preview with the existing palette and updated mobile navigation?',[['h','work'],['e',root.id,'','root'],['request','approve'],['p',pub.owner],['subject','Publish preview']]);29 const question=await publish('agent',9,'Which title should the preview use?',[['h','work'],['request','question'],['p',pub.owner],['subject','Preview title']]);30 await publish('owner',9,'The task card holds progress updates. Replies stay in their thread.',[['h','work']]);31 const fixture={base,pub,root:root.id,reply:reply.id,task:task.id,approval:approval.id,question:question.id};32 await writeFile('output/playwright/buzz/fixture.json',JSON.stringify(fixture,null,2));console.log(JSON.stringify(fixture));33}