tangled
alpha
login
or
join now
willdot.net
/
tangled-fork
forked from
tangled.org/core
0
fork
atom
Monorepo for Tangled
0
fork
atom
overview
issues
pulls
pipelines
jetstream: init separate package
anirudh.fi
1 year ago
eb6dd9fe
a7c75c2c
verified
This commit was signed with the committer's
known signature
.
anirudh.fi
SSH Key Fingerprint:
SHA256:FQUiBXeyBQT4WKOm7EKh6hLkHjBh9MdfkV3my0dueGE=
+152
-109
4 changed files
expand all
collapse all
unified
split
cmd
knotserver
main.go
jetstream
jetstream.go
knotserver
handler.go
jetstream.go
+11
-1
cmd/knotserver/main.go
···
4
4
"context"
5
5
"net/http"
6
6
7
7
+
"github.com/sotangled/tangled/api/tangled"
8
8
+
"github.com/sotangled/tangled/jetstream"
7
9
"github.com/sotangled/tangled/knotserver"
8
10
"github.com/sotangled/tangled/knotserver/config"
9
11
"github.com/sotangled/tangled/knotserver/db"
···
40
42
return
41
43
}
42
44
43
43
-
mux, err := knotserver.Setup(ctx, c, db, e, l)
45
45
+
jc, err := jetstream.NewJetstreamClient("knotserver", []string{
46
46
+
tangled.PublicKeyNSID,
47
47
+
tangled.KnotMemberNSID,
48
48
+
}, nil, db)
49
49
+
if err != nil {
50
50
+
l.Error("failed to setup jetstream", "error", err)
51
51
+
}
52
52
+
53
53
+
mux, err := knotserver.Setup(ctx, c, db, e, jc, l)
44
54
if err != nil {
45
55
l.Error("failed to setup server", "error", err)
46
56
return
+136
jetstream/jetstream.go
···
1
1
+
package jetstream
2
2
+
3
3
+
import (
4
4
+
"context"
5
5
+
"fmt"
6
6
+
"sync"
7
7
+
"time"
8
8
+
9
9
+
"github.com/bluesky-social/jetstream/pkg/client"
10
10
+
"github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential"
11
11
+
"github.com/bluesky-social/jetstream/pkg/models"
12
12
+
"github.com/sotangled/tangled/log"
13
13
+
)
14
14
+
15
15
+
type DB interface {
16
16
+
GetLastTimeUs() (int64, error)
17
17
+
SaveLastTimeUs(int64) error
18
18
+
}
19
19
+
20
20
+
type JetstreamClient struct {
21
21
+
cfg *client.ClientConfig
22
22
+
client *client.Client
23
23
+
ident string
24
24
+
25
25
+
db DB
26
26
+
reconnectCh chan struct{}
27
27
+
mu sync.RWMutex
28
28
+
}
29
29
+
30
30
+
func (j *JetstreamClient) AddDid(did string) {
31
31
+
j.mu.Lock()
32
32
+
j.cfg.WantedDids = append(j.cfg.WantedDids, did)
33
33
+
j.mu.Unlock()
34
34
+
j.reconnectCh <- struct{}{}
35
35
+
}
36
36
+
37
37
+
func (j *JetstreamClient) UpdateDids(dids []string) {
38
38
+
j.mu.Lock()
39
39
+
j.cfg.WantedDids = dids
40
40
+
j.mu.Unlock()
41
41
+
j.reconnectCh <- struct{}{}
42
42
+
}
43
43
+
44
44
+
func NewJetstreamClient(ident string, collections []string, cfg *client.ClientConfig, db DB) (*JetstreamClient, error) {
45
45
+
if cfg == nil {
46
46
+
cfg = client.DefaultClientConfig()
47
47
+
cfg.WebsocketURL = "wss://jetstream1.us-west.bsky.network/subscribe"
48
48
+
cfg.WantedCollections = collections
49
49
+
}
50
50
+
51
51
+
return &JetstreamClient{
52
52
+
cfg: cfg,
53
53
+
ident: ident,
54
54
+
db: db,
55
55
+
reconnectCh: make(chan struct{}, 1),
56
56
+
}, nil
57
57
+
}
58
58
+
59
59
+
func (j *JetstreamClient) StartJetstream(ctx context.Context, processFunc func(context.Context, *models.Event) error) error {
60
60
+
logger := log.FromContext(ctx)
61
61
+
62
62
+
pf := func(ctx context.Context, e *models.Event) error {
63
63
+
err := processFunc(ctx, e)
64
64
+
if err != nil {
65
65
+
return err
66
66
+
}
67
67
+
68
68
+
if err := j.db.SaveLastTimeUs(e.TimeUS); err != nil {
69
69
+
return err
70
70
+
}
71
71
+
72
72
+
return nil
73
73
+
}
74
74
+
75
75
+
sched := sequential.NewScheduler(j.ident, logger, pf)
76
76
+
77
77
+
client, err := client.NewClient(j.cfg, log.New("jetstream"), sched)
78
78
+
if err != nil {
79
79
+
return fmt.Errorf("failed to create jetstream client: %w", err)
80
80
+
}
81
81
+
j.client = client
82
82
+
83
83
+
go func() {
84
84
+
lastTimeUs := j.getLastTimeUs(ctx)
85
85
+
for len(j.cfg.WantedDids) == 0 {
86
86
+
time.Sleep(time.Second)
87
87
+
}
88
88
+
j.connectAndRead(ctx, &lastTimeUs)
89
89
+
}()
90
90
+
91
91
+
return nil
92
92
+
}
93
93
+
94
94
+
func (j *JetstreamClient) connectAndRead(ctx context.Context, cursor *int64) {
95
95
+
l := log.FromContext(ctx)
96
96
+
for {
97
97
+
select {
98
98
+
case <-j.reconnectCh:
99
99
+
l.Info("(re)connecting jetstream client")
100
100
+
j.client.Scheduler.Shutdown()
101
101
+
if err := j.client.ConnectAndRead(ctx, cursor); err != nil {
102
102
+
l.Error("error reading jetstream", "error", err)
103
103
+
}
104
104
+
default:
105
105
+
if err := j.client.ConnectAndRead(ctx, cursor); err != nil {
106
106
+
l.Error("error reading jetstream", "error", err)
107
107
+
}
108
108
+
}
109
109
+
}
110
110
+
}
111
111
+
112
112
+
func (j *JetstreamClient) getLastTimeUs(ctx context.Context) int64 {
113
113
+
l := log.FromContext(ctx)
114
114
+
lastTimeUs, err := j.db.GetLastTimeUs()
115
115
+
if err != nil {
116
116
+
l.Warn("couldn't get last time us, starting from now", "error", err)
117
117
+
lastTimeUs = time.Now().UnixMicro()
118
118
+
err = j.db.SaveLastTimeUs(lastTimeUs)
119
119
+
if err != nil {
120
120
+
l.Error("failed to save last time us")
121
121
+
}
122
122
+
}
123
123
+
124
124
+
// If last time is older than a week, start from now
125
125
+
if time.Now().UnixMicro()-lastTimeUs > 7*24*60*60*1000*1000 {
126
126
+
lastTimeUs = time.Now().UnixMicro()
127
127
+
l.Warn("last time us is older than a week. discarding that and starting from now")
128
128
+
err = j.db.SaveLastTimeUs(lastTimeUs)
129
129
+
if err != nil {
130
130
+
l.Error("failed to save last time us")
131
131
+
}
132
132
+
}
133
133
+
134
134
+
l.Info("found last time_us", "time_us", lastTimeUs)
135
135
+
return lastTimeUs
136
136
+
}
+5
-3
knotserver/handler.go
···
7
7
"net/http"
8
8
9
9
"github.com/go-chi/chi/v5"
10
10
+
"github.com/sotangled/tangled/jetstream"
10
11
"github.com/sotangled/tangled/knotserver/config"
11
12
"github.com/sotangled/tangled/knotserver/db"
12
13
"github.com/sotangled/tangled/rbac"
···
19
20
type Handle struct {
20
21
c *config.Config
21
22
db *db.DB
22
22
-
jc *JetstreamClient
23
23
+
jc *jetstream.JetstreamClient
23
24
e *rbac.Enforcer
24
25
l *slog.Logger
25
26
···
29
30
knotInitialized bool
30
31
}
31
32
32
32
-
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger) (http.Handler, error) {
33
33
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) {
33
34
r := chi.NewRouter()
34
35
35
36
h := Handle{
···
37
38
db: db,
38
39
e: e,
39
40
l: l,
41
41
+
jc: jc,
40
42
init: make(chan struct{}),
41
43
}
42
44
···
45
47
return nil, fmt.Errorf("failed to setup enforcer: %w", err)
46
48
}
47
49
48
48
-
err = h.StartJetstream(ctx)
50
50
+
err = h.jc.StartJetstream(ctx, h.processMessages)
49
51
if err != nil {
50
52
return nil, fmt.Errorf("failed to start jetstream: %w", err)
51
53
}
-105
knotserver/jetstream.go
···
8
8
"net/http"
9
9
"net/url"
10
10
"strings"
11
11
-
"sync"
12
12
-
"time"
13
11
14
14
-
"github.com/bluesky-social/jetstream/pkg/client"
15
15
-
"github.com/bluesky-social/jetstream/pkg/client/schedulers/sequential"
16
12
"github.com/bluesky-social/jetstream/pkg/models"
17
13
"github.com/sotangled/tangled/api/tangled"
18
14
"github.com/sotangled/tangled/knotserver/db"
19
15
"github.com/sotangled/tangled/log"
20
16
)
21
21
-
22
22
-
type JetstreamClient struct {
23
23
-
cfg *client.ClientConfig
24
24
-
client *client.Client
25
25
-
reconnectCh chan struct{}
26
26
-
mu sync.RWMutex
27
27
-
}
28
28
-
29
29
-
func (h *Handle) StartJetstream(ctx context.Context) error {
30
30
-
l := h.l
31
31
-
ctx = log.IntoContext(ctx, l)
32
32
-
collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID}
33
33
-
dids := []string{}
34
34
-
35
35
-
cfg := client.DefaultClientConfig()
36
36
-
cfg.WebsocketURL = "wss://jetstream1.us-west.bsky.network/subscribe"
37
37
-
cfg.WantedCollections = collections
38
38
-
cfg.WantedDids = dids
39
39
-
40
40
-
sched := sequential.NewScheduler("knotserver", l, h.processMessages)
41
41
-
42
42
-
client, err := client.NewClient(cfg, l, sched)
43
43
-
if err != nil {
44
44
-
l.Error("failed to create jetstream client", "error", err)
45
45
-
}
46
46
-
47
47
-
jc := &JetstreamClient{
48
48
-
cfg: cfg,
49
49
-
client: client,
50
50
-
reconnectCh: make(chan struct{}, 1),
51
51
-
}
52
52
-
53
53
-
h.jc = jc
54
54
-
55
55
-
go func() {
56
56
-
lastTimeUs := h.getLastTimeUs(ctx)
57
57
-
for len(h.jc.cfg.WantedDids) == 0 {
58
58
-
time.Sleep(time.Second)
59
59
-
}
60
60
-
h.connectAndRead(ctx, &lastTimeUs)
61
61
-
}()
62
62
-
return nil
63
63
-
}
64
64
-
65
65
-
func (h *Handle) connectAndRead(ctx context.Context, cursor *int64) {
66
66
-
l := log.FromContext(ctx)
67
67
-
for {
68
68
-
select {
69
69
-
case <-h.jc.reconnectCh:
70
70
-
l.Info("(re)connecting jetstream client")
71
71
-
h.jc.client.Scheduler.Shutdown()
72
72
-
if err := h.jc.client.ConnectAndRead(ctx, cursor); err != nil {
73
73
-
l.Error("error reading jetstream", "error", err)
74
74
-
}
75
75
-
default:
76
76
-
if err := h.jc.client.ConnectAndRead(ctx, cursor); err != nil {
77
77
-
l.Error("error reading jetstream", "error", err)
78
78
-
}
79
79
-
}
80
80
-
}
81
81
-
}
82
82
-
83
83
-
func (j *JetstreamClient) AddDid(did string) {
84
84
-
j.mu.Lock()
85
85
-
j.cfg.WantedDids = append(j.cfg.WantedDids, did)
86
86
-
j.mu.Unlock()
87
87
-
j.reconnectCh <- struct{}{}
88
88
-
}
89
89
-
90
90
-
func (j *JetstreamClient) UpdateDids(dids []string) {
91
91
-
j.mu.Lock()
92
92
-
j.cfg.WantedDids = dids
93
93
-
j.mu.Unlock()
94
94
-
j.reconnectCh <- struct{}{}
95
95
-
}
96
96
-
97
97
-
func (h *Handle) getLastTimeUs(ctx context.Context) int64 {
98
98
-
l := log.FromContext(ctx)
99
99
-
lastTimeUs, err := h.db.GetLastTimeUs()
100
100
-
if err != nil {
101
101
-
l.Warn("couldn't get last time us, starting from now", "error", err)
102
102
-
lastTimeUs = time.Now().UnixMicro()
103
103
-
err = h.db.SaveLastTimeUs(lastTimeUs)
104
104
-
if err != nil {
105
105
-
l.Error("failed to save last time us")
106
106
-
}
107
107
-
}
108
108
-
109
109
-
// If last time is older than a week, start from now
110
110
-
if time.Now().UnixMicro()-lastTimeUs > 7*24*60*60*1000*1000 {
111
111
-
lastTimeUs = time.Now().UnixMicro()
112
112
-
l.Warn("last time us is older than a week. discarding that and starting from now")
113
113
-
err = h.db.SaveLastTimeUs(lastTimeUs)
114
114
-
if err != nil {
115
115
-
l.Error("failed to save last time us")
116
116
-
}
117
117
-
}
118
118
-
119
119
-
l.Info("found last time_us", "time_us", lastTimeUs)
120
120
-
return lastTimeUs
121
121
-
}
122
17
123
18
func (h *Handle) processPublicKey(ctx context.Context, did string, record tangled.PublicKey) error {
124
19
l := log.FromContext(ctx)