main.go raw
1 package main
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/url"
7 "os"
8 "time"
9
10 "github.com/gorilla/websocket"
11 "git.smesh.lol/orly/pkg/nostr/encoders/hex"
12 "git.smesh.lol/orly/pkg/nostr/interfaces/signer/p8k"
13 )
14
15 type mReq struct {
16 Method string `json:"method"`
17 Nsec string `json:"nsec,omitempty"`
18 Pubkey string `json:"pubkey,omitempty"`
19 Sig string `json:"sig,omitempty"`
20 Recipient string `json:"recipient,omitempty"`
21 Content string `json:"content,omitempty"`
22 Relays []string `json:"relays,omitempty"`
23 Alias string `json:"alias,omitempty"`
24 }
25
26 type mResp struct {
27 Method string `json:"method"`
28 OK bool `json:"ok,omitempty"`
29 Error string `json:"error,omitempty"`
30 Peer string `json:"peer,omitempty"`
31 Content string `json:"content,omitempty"`
32 Ts int64 `json:"ts,omitempty"`
33 Groups []string `json:"groups,omitempty"`
34 Pubkey string `json:"pubkey,omitempty"`
35 Subscribed bool `json:"subscribed,omitempty"`
36 Relay string `json:"relay,omitempty"`
37 NumGroups int `json:"num_groups,omitempty"`
38 }
39
40 func main() {
41 base := "ws://127.0.0.1:8090"
42 if len(os.Args) > 1 {
43 base = os.Args[1]
44 }
45
46 alice, _ := p8k.New()
47 alice.Generate()
48 bob, _ := p8k.New()
49 bob.Generate()
50
51 aliceSec := hex.Enc(alice.Sec())
52 bobSec := hex.Enc(bob.Sec())
53 alicePub := hex.Enc(alice.Pub())
54 bobPub := hex.Enc(bob.Pub())
55
56 fmt.Printf("alice: %s\nbob: %s\n\n", alicePub, bobPub)
57
58 // --- Phase 1: Basic DM flow ---
59
60 ac := dial(base)
61 defer ac.Close()
62 step("alice auth", func() {
63 send(ac, mReq{Method: "auth", Nsec: aliceSec})
64 r := expect(ac, "auth", 15)
65 check(r.OK, "auth", r.Error)
66 })
67
68 bc := dial(base)
69 defer bc.Close()
70 step("bob auth", func() {
71 send(bc, mReq{Method: "auth", Nsec: bobSec})
72 r := expect(bc, "auth", 15)
73 check(r.OK, "auth", r.Error)
74 })
75
76 step("alice publish_kp", func() {
77 send(ac, mReq{Method: "publish_kp"})
78 r := expect(ac, "publish_kp", 15)
79 check(r.OK, "publish_kp", r.Error)
80 })
81 step("bob publish_kp", func() {
82 send(bc, mReq{Method: "publish_kp"})
83 r := expect(bc, "publish_kp", 15)
84 check(r.OK, "publish_kp", r.Error)
85 })
86
87 step("alice subscribe", func() {
88 send(ac, mReq{Method: "subscribe"})
89 r := expect(ac, "subscribe", 10)
90 check(r.OK, "subscribe", r.Error)
91 })
92 step("bob subscribe", func() {
93 send(bc, mReq{Method: "subscribe"})
94 r := expect(bc, "subscribe", 10)
95 check(r.OK, "subscribe", r.Error)
96 })
97
98 fmt.Println(" waiting 3s for KP propagation...")
99 time.Sleep(3 * time.Second)
100
101 msg1 := "hello-" + time.Now().Format("150405")
102 step("alice send_dm → bob", func() {
103 send(ac, mReq{Method: "send_dm", Recipient: bobPub, Content: msg1})
104 r := expect(ac, "send_dm", 30)
105 check(r.OK, "send_dm", r.Error)
106 })
107
108 step("bob receive dm", func() {
109 r := expectDM(bc, 30)
110 if r.Content != msg1 {
111 fail("content mismatch: got %q want %q", r.Content, msg1)
112 }
113 fmt.Printf("content=%q ", r.Content)
114 })
115
116 // --- Phase 2: Status ---
117
118 step("alice status", func() {
119 send(ac, mReq{Method: "status"})
120 r := expect(ac, "status", 5)
121 check(r.OK, "status", r.Error)
122 if r.Pubkey != alicePub {
123 fail("wrong pubkey: %s", r.Pubkey)
124 }
125 if !r.Subscribed {
126 fail("not subscribed")
127 }
128 if r.NumGroups < 1 {
129 fail("no groups")
130 }
131 fmt.Printf("sub=%v groups=%d relay=%s ", r.Subscribed, r.NumGroups, r.Relay)
132 })
133
134 step("bob status", func() {
135 send(bc, mReq{Method: "status"})
136 r := expect(bc, "status", 5)
137 check(r.OK, "status", r.Error)
138 if !r.Subscribed {
139 fail("not subscribed")
140 }
141 fmt.Printf("sub=%v groups=%d ", r.Subscribed, r.NumGroups)
142 })
143
144 // --- Phase 3: Reset both + re-establish ---
145 // Both sides reset → clean slate → re-establish group from scratch.
146
147 step("alice reset", func() {
148 send(ac, mReq{Method: "reset"})
149 r := expect(ac, "reset", 15)
150 check(r.OK, "reset", r.Error)
151 })
152 step("bob reset", func() {
153 send(bc, mReq{Method: "reset"})
154 r := expect(bc, "reset", 15)
155 check(r.OK, "reset", r.Error)
156 })
157
158 step("bob status after reset", func() {
159 send(bc, mReq{Method: "status"})
160 r := expect(bc, "status", 5)
161 check(r.OK, "status", r.Error)
162 if r.Subscribed {
163 fail("should not be subscribed after reset")
164 }
165 if r.NumGroups != 0 {
166 fail("groups should be 0 after reset, got %d", r.NumGroups)
167 }
168 fmt.Printf("sub=%v groups=%d ", r.Subscribed, r.NumGroups)
169 })
170
171 step("alice re-publish_kp", func() {
172 send(ac, mReq{Method: "publish_kp"})
173 r := expect(ac, "publish_kp", 15)
174 check(r.OK, "publish_kp", r.Error)
175 })
176 step("bob re-publish_kp", func() {
177 send(bc, mReq{Method: "publish_kp"})
178 r := expect(bc, "publish_kp", 15)
179 check(r.OK, "publish_kp", r.Error)
180 })
181
182 step("alice re-subscribe", func() {
183 send(ac, mReq{Method: "subscribe"})
184 r := expect(ac, "subscribe", 10)
185 check(r.OK, "subscribe", r.Error)
186 })
187 step("bob re-subscribe", func() {
188 send(bc, mReq{Method: "subscribe"})
189 r := expect(bc, "subscribe", 10)
190 check(r.OK, "subscribe", r.Error)
191 })
192
193 time.Sleep(2 * time.Second)
194 msg2 := "after-reset-" + time.Now().Format("150405")
195 step("alice send_dm after reset", func() {
196 send(ac, mReq{Method: "send_dm", Recipient: bobPub, Content: msg2})
197 r := expect(ac, "send_dm", 30)
198 check(r.OK, "send_dm", r.Error)
199 })
200
201 step("bob receive after reset", func() {
202 r := expectDM(bc, 30)
203 if r.Content != msg2 {
204 fail("content mismatch: got %q want %q", r.Content, msg2)
205 }
206 fmt.Printf("content=%q ", r.Content)
207 })
208
209 // --- Phase 4: Alias resolution ---
210
211 step("resolve_alias jb55@damus.io", func() {
212 send(ac, mReq{Method: "resolve_alias", Alias: "jb55@damus.io"})
213 r := expect(ac, "resolve_alias", 15)
214 check(r.OK, "resolve_alias", r.Error)
215 if len(r.Pubkey) != 64 {
216 fail("invalid pubkey: %s", r.Pubkey)
217 }
218 // jb55's known pubkey
219 if r.Pubkey != "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" {
220 fail("wrong pubkey: %s", r.Pubkey)
221 }
222 fmt.Printf("pubkey=%s..%s ", r.Pubkey[:8], r.Pubkey[56:])
223 })
224
225 step("list_groups final", func() {
226 send(ac, mReq{Method: "list_groups"})
227 r := expect(ac, "list_groups", 5)
228 check(r.OK, "list_groups", r.Error)
229 fmt.Printf("groups=%d ", len(r.Groups))
230 })
231
232 fmt.Println("\nALL PASS")
233 }
234
235 func dial(base string) *websocket.Conn {
236 u, _ := url.Parse(base + "/__marmot")
237 c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
238 if err != nil {
239 fail("dial %s: %v", u, err)
240 }
241 return c
242 }
243
244 func send(c *websocket.Conn, req mReq) {
245 data, _ := json.Marshal(req)
246 c.WriteMessage(websocket.TextMessage, data)
247 }
248
249 func expect(c *websocket.Conn, method string, timeoutSec int) mResp {
250 c.SetReadDeadline(time.Now().Add(time.Duration(timeoutSec) * time.Second))
251 for {
252 _, raw, err := c.ReadMessage()
253 if err != nil {
254 fail("expect %s: %v", method, err)
255 }
256 var r mResp
257 json.Unmarshal(raw, &r)
258 if r.Method == method {
259 return r
260 }
261 }
262 }
263
264 func expectDM(c *websocket.Conn, timeoutSec int) mResp {
265 c.SetReadDeadline(time.Now().Add(time.Duration(timeoutSec) * time.Second))
266 for {
267 _, raw, err := c.ReadMessage()
268 if err != nil {
269 fail("expect dm_received: %v", err)
270 }
271 var r mResp
272 json.Unmarshal(raw, &r)
273 if r.Method == "dm_received" {
274 return r
275 }
276 }
277 }
278
279 func step(name string, fn func()) {
280 fmt.Printf(" %s... ", name)
281 fn()
282 fmt.Println("OK")
283 }
284
285 func check(ok bool, what, errMsg string) {
286 if !ok {
287 fail("%s: %s", what, errMsg)
288 }
289 }
290
291 func fail(format string, args ...any) {
292 fmt.Printf("FAIL: "+format+"\n", args...)
293 os.Exit(1)
294 }
295