tinyrelayfile
tinyrelay/internal/records/notifications.go/

internal/records/notifications.go raw
1package records
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "strings"
8
9 "fiatjaf.com/nostr"
10 "fiatjaf.com/nostr/nip44"
11 "github.com/FelineStateMachine/tinyrelay/internal/event"
12)
13
14func (s *Service) notify(ctx context.Context, typ, owner, target string, now int64) error {
15 if !s.policy().Notify.Succession && (typ == "succession" || typ == "succession-transfer") {
16 return nil
17 }
18 content, _ := json.Marshal(map[string]string{"type": typ, "target": target})
19 wrapped, err := s.giftWrap(owner, string(content), "relay", now)
20 if err != nil {
21 return err
22 }
23 if s.deliverNotification != nil {
24 if err := s.deliverNotification(ctx, wrapped, target); err != nil {
25 return err
26 }
27 return s.push(ctx, target, typ, "relay", "")
28 }
29 if s.onGenerated != nil {
30 return s.onGenerated(ctx, wrapped)
31 }
32 return err
33}
34
35func (s *Service) push(ctx context.Context, recipient, kind, subject, text string) error {
36 if s.pushNotification == nil {
37 return nil
38 }
39 return s.pushNotification(ctx, recipient, kind, subject, text)
40}
41
42// Notify applies notification preferences and emits a durable gift wrap.
43func (s *Service) Notify(ctx context.Context, kind, text, subject, target string, now int64) error {
44 p := s.policy()
45 if kind != "test" {
46 enabled := map[string]bool{"reports": p.Notify.Reports, "jobs": p.Notify.Jobs, "succession": p.Notify.Succession, "digest": p.Notify.Digest}[kind]
47 if !enabled {
48 return nil
49 }
50 }
51 if target == "" {
52 target = p.Owner
53 }
54 return s.notifyText(ctx, kind, text, subject, target, now)
55}
56
57func (s *Service) notifyText(ctx context.Context, kind, text, subject, target string, now int64) error {
58 if target == "" {
59 return errors.New("notify: empty recipient")
60 }
61 if s.deliverNotification == nil && s.onGenerated == nil {
62 return nil
63 }
64 wrapped, err := s.giftWrap(target, text, subject, now)
65 if err != nil {
66 return err
67 }
68 if s.deliverNotification != nil {
69 if err := s.deliverNotification(ctx, wrapped, target); err != nil {
70 return err
71 }
72 return s.push(ctx, target, kind, subject, text)
73 }
74 if s.onGenerated != nil {
75 return s.onGenerated(ctx, wrapped)
76 }
77 return nil
78}
79
80// giftWrap creates the NIP-17 and NIP-59 records.
81func (s *Service) giftWrap(recipient, content, subject string, now int64) (event.Event, error) {
82 rpk, err := nostr.PubKeyFromHex(recipient)
83 if err != nil {
84 return event.Event{}, err
85 }
86 sk, err := nostr.SecretKeyFromHex(s.secret)
87 if err != nil {
88 return event.Event{}, err
89 }
90 // Build the three NIP-59 records with the project's canonical event signer.
91 // This avoids the upstream nostr JSON encoder's unsafe fast path under -race.
92 tags := [][]string{{"p", recipient}}
93 if subject != "" {
94 tags = append(tags, []string{"subject", subject})
95 }
96 rumor := event.Event{CreatedAt: now, Kind: event.KIND_DM, Tags: tags, Content: content}
97 if err := event.Sign(&rumor, s.secret); err != nil {
98 return event.Event{}, err
99 }
100 rumor.Sig = strings.Repeat("0", 128)
101 rumorRaw, err := json.Marshal(rumor)
102 if err != nil {
103 return event.Event{}, err
104 }
105 key, err := nip44.GenerateConversationKey(rpk, sk)
106 if err != nil {
107 return event.Event{}, err
108 }
109 rumorCipher, err := nip44.Encrypt(string(rumorRaw), key)
110 if err != nil {
111 return event.Event{}, err
112 }
113 seal := event.Event{CreatedAt: now, Kind: 13, Tags: [][]string{}, Content: rumorCipher}
114 if err := event.Sign(&seal, s.secret); err != nil {
115 return event.Event{}, err
116 }
117 nonce := nostr.Generate()
118 outerKey, err := nip44.GenerateConversationKey(rpk, nonce)
119 if err != nil {
120 return event.Event{}, err
121 }
122 sealRaw, err := json.Marshal(seal)
123 if err != nil {
124 return event.Event{}, err
125 }
126 outerCipher, err := nip44.Encrypt(string(sealRaw), outerKey)
127 if err != nil {
128 return event.Event{}, err
129 }
130 wrapped := event.Event{CreatedAt: now, Kind: event.KIND_WRAP, Tags: [][]string{{"p", recipient}}, Content: outerCipher}
131 if err := event.Sign(&wrapped, nonce.Hex()); err != nil {
132 return event.Event{}, err
133 }
134 return wrapped, nil
135}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs