envelopes.go raw
1 package nostr
2
3 import (
4 "encoding/hex"
5 "errors"
6 "fmt"
7 "strconv"
8 "strings"
9 "unsafe"
10
11 "github.com/mailru/easyjson"
12 jwriter "github.com/mailru/easyjson/jwriter"
13 "github.com/tidwall/gjson"
14 )
15
16 var UnknownLabel = errors.New("unknown envelope label")
17
18 type MessageParser interface {
19 // ParseMessage parses a message into an Envelope.
20 ParseMessage(string) (Envelope, error)
21 }
22
23 // Deprecated: use NewMessageParser instead
24 func ParseMessage(message string) Envelope {
25 firstQuote := strings.IndexRune(message, '"')
26 if firstQuote == -1 {
27 return nil
28 }
29 secondQuote := strings.IndexRune(message[firstQuote+1:], '"')
30 if secondQuote == -1 {
31 return nil
32 }
33 label := message[firstQuote+1 : firstQuote+1+secondQuote]
34
35 var v Envelope
36 switch label {
37 case "EVENT":
38 v = &EventEnvelope{}
39 case "REQ":
40 v = &ReqEnvelope{}
41 case "COUNT":
42 v = &CountEnvelope{}
43 case "NOTICE":
44 x := NoticeEnvelope("")
45 v = &x
46 case "EOSE":
47 x := EOSEEnvelope("")
48 v = &x
49 case "OK":
50 v = &OKEnvelope{}
51 case "AUTH":
52 v = &AuthEnvelope{}
53 case "CLOSED":
54 v = &ClosedEnvelope{}
55 case "CLOSE":
56 x := CloseEnvelope("")
57 v = &x
58 default:
59 return nil
60 }
61
62 if err := v.FromJSON(message); err != nil {
63 return nil
64 }
65
66 return v
67 }
68
69 // Envelope is the interface for all nostr message envelopes.
70 type Envelope interface {
71 Label() string
72 FromJSON(string) error
73 MarshalJSON() ([]byte, error)
74 String() string
75 }
76
77 var (
78 _ Envelope = (*EventEnvelope)(nil)
79 _ Envelope = (*ReqEnvelope)(nil)
80 _ Envelope = (*CountEnvelope)(nil)
81 _ Envelope = (*NoticeEnvelope)(nil)
82 _ Envelope = (*EOSEEnvelope)(nil)
83 _ Envelope = (*CloseEnvelope)(nil)
84 _ Envelope = (*OKEnvelope)(nil)
85 _ Envelope = (*AuthEnvelope)(nil)
86 )
87
88 // EventEnvelope represents an EVENT message.
89 type EventEnvelope struct {
90 SubscriptionID *string
91 Event
92 }
93
94 func (_ EventEnvelope) Label() string { return "EVENT" }
95
96 func (v *EventEnvelope) FromJSON(data string) error {
97 r := gjson.Parse(data)
98 arr := r.Array()
99 switch len(arr) {
100 case 2:
101 return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
102 case 3:
103 subid := string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
104 v.SubscriptionID = &subid
105 return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Event)
106 default:
107 return fmt.Errorf("failed to decode EVENT envelope")
108 }
109 }
110
111 func (v EventEnvelope) MarshalJSON() ([]byte, error) {
112 w := jwriter.Writer{NoEscapeHTML: true}
113 w.RawString(`["EVENT",`)
114 if v.SubscriptionID != nil {
115 w.RawString(`"`)
116 w.RawString(*v.SubscriptionID)
117 w.RawString(`",`)
118 }
119 v.Event.MarshalEasyJSON(&w)
120 w.RawString(`]`)
121 return w.BuildBytes()
122 }
123
124 // ReqEnvelope represents a REQ message.
125 type ReqEnvelope struct {
126 SubscriptionID string
127 Filters
128 }
129
130 func (_ ReqEnvelope) Label() string { return "REQ" }
131
132 func (v *ReqEnvelope) FromJSON(data string) error {
133 r := gjson.Parse(data)
134 arr := r.Array()
135 if len(arr) < 3 {
136 return fmt.Errorf("failed to decode REQ envelope: missing filters")
137 }
138 v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
139 v.Filters = make(Filters, len(arr)-2)
140 f := 0
141 for i := 2; i < len(arr); i++ {
142 if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw)), &v.Filters[f]); err != nil {
143 return fmt.Errorf("%w -- on filter %d", err, f)
144 }
145 f++
146 }
147
148 return nil
149 }
150
151 func (v ReqEnvelope) MarshalJSON() ([]byte, error) {
152 w := jwriter.Writer{NoEscapeHTML: true}
153 w.RawString(`["REQ","`)
154 w.RawString(v.SubscriptionID)
155 w.RawString(`"`)
156 for _, filter := range v.Filters {
157 w.RawString(`,`)
158 filter.MarshalEasyJSON(&w)
159 }
160 w.RawString(`]`)
161 return w.BuildBytes()
162 }
163
164 // CountEnvelope represents a COUNT message.
165 type CountEnvelope struct {
166 SubscriptionID string
167 Filter
168 Count *int64
169 HyperLogLog []byte
170 }
171
172 func (_ CountEnvelope) Label() string { return "COUNT" }
173 func (c CountEnvelope) String() string {
174 v, _ := json.Marshal(c)
175 return string(v)
176 }
177
178 func (v *CountEnvelope) FromJSON(data string) error {
179 r := gjson.Parse(data)
180 arr := r.Array()
181 if len(arr) < 3 {
182 return fmt.Errorf("failed to decode COUNT envelope: missing filters")
183 }
184 v.SubscriptionID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
185
186 var countResult struct {
187 Count *int64 `json:"count"`
188 HLL string `json:"hll"`
189 }
190 if err := json.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &countResult); err == nil && countResult.Count != nil {
191 v.Count = countResult.Count
192 if len(countResult.HLL) == 512 {
193 v.HyperLogLog, err = hex.DecodeString(countResult.HLL)
194 if err != nil {
195 return fmt.Errorf("invalid \"hll\" value in COUNT message: %w", err)
196 }
197 }
198 return nil
199 }
200
201 f := 0
202 for i := 2; i < len(arr); i++ {
203 item := unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw))
204
205 if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
206 return fmt.Errorf("%w -- on filter %d", err, f)
207 }
208
209 f++
210 }
211
212 return nil
213 }
214
215 func (v CountEnvelope) MarshalJSON() ([]byte, error) {
216 w := jwriter.Writer{NoEscapeHTML: true}
217 w.RawString(`["COUNT","`)
218 w.RawString(v.SubscriptionID)
219 w.RawString(`",`)
220 if v.Count != nil {
221 w.RawString(`{"count":`)
222 w.RawString(strconv.FormatInt(*v.Count, 10))
223 if v.HyperLogLog != nil {
224 w.RawString(`,"hll":"`)
225 hllHex := make([]byte, 512)
226 hex.Encode(hllHex, v.HyperLogLog)
227 w.Buffer.AppendBytes(hllHex)
228 w.RawString(`"`)
229 }
230 w.RawString(`}`)
231 } else {
232 v.Filter.MarshalEasyJSON(&w)
233 }
234 w.RawString(`]`)
235 return w.BuildBytes()
236 }
237
238 // NoticeEnvelope represents a NOTICE message.
239 type NoticeEnvelope string
240
241 func (_ NoticeEnvelope) Label() string { return "NOTICE" }
242 func (n NoticeEnvelope) String() string {
243 v, _ := json.Marshal(n)
244 return string(v)
245 }
246
247 func (v *NoticeEnvelope) FromJSON(data string) error {
248 r := gjson.Parse(data)
249 arr := r.Array()
250 if len(arr) < 2 {
251 return fmt.Errorf("failed to decode NOTICE envelope")
252 }
253 *v = NoticeEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
254 return nil
255 }
256
257 func (v NoticeEnvelope) MarshalJSON() ([]byte, error) {
258 w := jwriter.Writer{NoEscapeHTML: true}
259 w.RawString(`["NOTICE",`)
260 w.Raw(json.Marshal(string(v)))
261 w.RawString(`]`)
262 return w.BuildBytes()
263 }
264
265 // EOSEEnvelope represents an EOSE (End of Stored Events) message.
266 type EOSEEnvelope string
267
268 func (_ EOSEEnvelope) Label() string { return "EOSE" }
269 func (e EOSEEnvelope) String() string {
270 v, _ := json.Marshal(e)
271 return string(v)
272 }
273
274 func (v *EOSEEnvelope) FromJSON(data string) error {
275 r := gjson.Parse(data)
276 arr := r.Array()
277 if len(arr) < 2 {
278 return fmt.Errorf("failed to decode EOSE envelope")
279 }
280 *v = EOSEEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
281 return nil
282 }
283
284 func (v EOSEEnvelope) MarshalJSON() ([]byte, error) {
285 w := jwriter.Writer{NoEscapeHTML: true}
286 w.RawString(`["EOSE",`)
287 w.Raw(json.Marshal(string(v)))
288 w.RawString(`]`)
289 return w.BuildBytes()
290 }
291
292 // CloseEnvelope represents a CLOSE message.
293 type CloseEnvelope string
294
295 func (_ CloseEnvelope) Label() string { return "CLOSE" }
296 func (c CloseEnvelope) String() string {
297 v, _ := json.Marshal(c)
298 return string(v)
299 }
300
301 func (v *CloseEnvelope) FromJSON(data string) error {
302 r := gjson.Parse(data)
303 arr := r.Array()
304 switch len(arr) {
305 case 2:
306 *v = CloseEnvelope(string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))))
307 return nil
308 default:
309 return fmt.Errorf("failed to decode CLOSE envelope")
310 }
311 }
312
313 func (v CloseEnvelope) MarshalJSON() ([]byte, error) {
314 w := jwriter.Writer{NoEscapeHTML: true}
315 w.RawString(`["CLOSE",`)
316 w.Raw(json.Marshal(string(v)))
317 w.RawString(`]`)
318 return w.BuildBytes()
319 }
320
321 // ClosedEnvelope represents a CLOSED message.
322 type ClosedEnvelope struct {
323 SubscriptionID string
324 Reason string
325 }
326
327 func (_ ClosedEnvelope) Label() string { return "CLOSED" }
328 func (c ClosedEnvelope) String() string {
329 v, _ := json.Marshal(c)
330 return string(v)
331 }
332
333 func (v *ClosedEnvelope) FromJSON(data string) error {
334 r := gjson.Parse(data)
335 arr := r.Array()
336 switch len(arr) {
337 case 3:
338 *v = ClosedEnvelope{
339 string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str))),
340 string(unsafe.Slice(unsafe.StringData(arr[2].Str), len(arr[2].Str))),
341 }
342 return nil
343 default:
344 return fmt.Errorf("failed to decode CLOSED envelope")
345 }
346 }
347
348 func (v ClosedEnvelope) MarshalJSON() ([]byte, error) {
349 w := jwriter.Writer{NoEscapeHTML: true}
350 w.RawString(`["CLOSED",`)
351 w.Raw(json.Marshal(string(v.SubscriptionID)))
352 w.RawString(`,`)
353 w.Raw(json.Marshal(v.Reason))
354 w.RawString(`]`)
355 return w.BuildBytes()
356 }
357
358 // OKEnvelope represents an OK message.
359 type OKEnvelope struct {
360 EventID string
361 OK bool
362 Reason string
363 }
364
365 func (_ OKEnvelope) Label() string { return "OK" }
366 func (o OKEnvelope) String() string {
367 v, _ := json.Marshal(o)
368 return string(v)
369 }
370
371 func (v *OKEnvelope) FromJSON(data string) error {
372 r := gjson.Parse(data)
373 arr := r.Array()
374 if len(arr) < 4 {
375 return fmt.Errorf("failed to decode OK envelope: missing fields")
376 }
377 v.EventID = string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
378 v.OK = arr[2].Raw == "true"
379 v.Reason = string(unsafe.Slice(unsafe.StringData(arr[3].Str), len(arr[3].Str)))
380
381 return nil
382 }
383
384 func (v OKEnvelope) MarshalJSON() ([]byte, error) {
385 w := jwriter.Writer{NoEscapeHTML: true}
386 w.RawString(`["OK","`)
387 w.RawString(v.EventID)
388 w.RawString(`",`)
389 ok := "false"
390 if v.OK {
391 ok = "true"
392 }
393 w.RawString(ok)
394 w.RawString(`,`)
395 w.Raw(json.Marshal(v.Reason))
396 w.RawString(`]`)
397 return w.BuildBytes()
398 }
399
400 // AuthEnvelope represents an AUTH message.
401 type AuthEnvelope struct {
402 Challenge *string
403 Event Event
404 }
405
406 func (_ AuthEnvelope) Label() string { return "AUTH" }
407 func (a AuthEnvelope) String() string {
408 v, _ := json.Marshal(a)
409 return string(v)
410 }
411
412 func (v *AuthEnvelope) FromJSON(data string) error {
413 r := gjson.Parse(data)
414 arr := r.Array()
415 if len(arr) < 2 {
416 return fmt.Errorf("failed to decode Auth envelope: missing fields")
417 }
418 if arr[1].IsObject() {
419 return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
420 } else {
421 challenge := string(unsafe.Slice(unsafe.StringData(arr[1].Str), len(arr[1].Str)))
422 v.Challenge = &challenge
423 }
424 return nil
425 }
426
427 func (v AuthEnvelope) MarshalJSON() ([]byte, error) {
428 w := jwriter.Writer{NoEscapeHTML: true}
429 w.RawString(`["AUTH",`)
430 if v.Challenge != nil {
431 w.Raw(json.Marshal(*v.Challenge))
432 } else {
433 v.Event.MarshalEasyJSON(&w)
434 }
435 w.RawString(`]`)
436 return w.BuildBytes()
437 }
438