+213
auth/auth.go
+213
auth/auth.go
···
1
+
package auth
2
+
3
+
// taken from https://github.com/jazware/go-bsky-feed-generator
4
+
// which doesnt seem to be published?
5
+
import (
6
+
"context"
7
+
"fmt"
8
+
"net/http"
9
+
"strings"
10
+
"time"
11
+
12
+
"github.com/bluesky-social/indigo/atproto/identity"
13
+
"github.com/bluesky-social/indigo/atproto/syntax"
14
+
es256k "github.com/ericvolp12/jwt-go-secp256k1"
15
+
"github.com/gin-gonic/gin"
16
+
"github.com/golang-jwt/jwt"
17
+
lru "github.com/hashicorp/golang-lru/arc/v2"
18
+
"github.com/prometheus/client_golang/prometheus"
19
+
"github.com/prometheus/client_golang/prometheus/promauto"
20
+
"gitlab.com/yawning/secp256k1-voi/secec"
21
+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
22
+
"go.opentelemetry.io/otel"
23
+
"go.opentelemetry.io/otel/attribute"
24
+
"golang.org/x/time/rate"
25
+
)
26
+
27
+
type KeyCacheEntry struct {
28
+
UserDID string
29
+
Key any
30
+
ExpiresAt time.Time
31
+
}
32
+
33
+
// Initialize Prometheus Metrics for cache hits and misses
34
+
var cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{
35
+
Name: "feedgen_auth_cache_hits_total",
36
+
Help: "The total number of cache hits",
37
+
}, []string{"cache_type"})
38
+
39
+
var cacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{
40
+
Name: "feedgen_auth_cache_misses_total",
41
+
Help: "The total number of cache misses",
42
+
}, []string{"cache_type"})
43
+
44
+
var cacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
45
+
Name: "feedgen_auth_cache_size_bytes",
46
+
Help: "The size of the cache in bytes",
47
+
}, []string{"cache_type"})
48
+
49
+
type Auth struct {
50
+
KeyCache *lru.ARCCache[string, KeyCacheEntry]
51
+
KeyCacheTTL time.Duration
52
+
ServiceDID string
53
+
Dir *identity.CacheDirectory
54
+
}
55
+
56
+
// NewAuth creates a new Auth instance with the given key cache size and TTL
57
+
// The PLC Directory URL is also required, as well as the DID of the service
58
+
// for JWT audience validation
59
+
// The key cache is used to cache the public keys of users for a given TTL
60
+
// The PLC Directory URL is used to fetch the public keys of users
61
+
// The service DID is used to validate the audience of JWTs
62
+
// The HTTP client is used to make requests to the PLC Directory
63
+
// A rate limiter is used to limit the number of requests to the PLC Directory
64
+
func NewAuth(
65
+
keyCacheSize int,
66
+
keyCacheTTL time.Duration,
67
+
requestsPerSecond int,
68
+
serviceDID string,
69
+
) (*Auth, error) {
70
+
keyCache, err := lru.NewARC[string, KeyCacheEntry](keyCacheSize)
71
+
if err != nil {
72
+
return nil, fmt.Errorf("Failed to create key cache: %v", err)
73
+
}
74
+
75
+
// Initialize the HTTP client with OpenTelemetry instrumentation
76
+
client := http.Client{
77
+
Transport: otelhttp.NewTransport(http.DefaultTransport),
78
+
}
79
+
80
+
baseDir := identity.BaseDirectory{
81
+
PLCURL: identity.DefaultPLCURL,
82
+
PLCLimiter: rate.NewLimiter(rate.Limit(float64(requestsPerSecond)), 1),
83
+
HTTPClient: client,
84
+
TryAuthoritativeDNS: true,
85
+
// primary Bluesky PDS instance only supports HTTP resolution method
86
+
SkipDNSDomainSuffixes: []string{".bsky.social"},
87
+
}
88
+
dir := identity.NewCacheDirectory(&baseDir, keyCacheSize, keyCacheTTL, time.Minute*2, keyCacheTTL)
89
+
90
+
return &Auth{
91
+
KeyCache: keyCache,
92
+
KeyCacheTTL: keyCacheTTL,
93
+
ServiceDID: serviceDID,
94
+
Dir: &dir,
95
+
}, nil
96
+
}
97
+
98
+
func (auth *Auth) GetClaimsFromAuthHeader(ctx context.Context, authHeader string, claims jwt.Claims) error {
99
+
tracer := otel.Tracer("auth")
100
+
ctx, span := tracer.Start(ctx, "Auth:GetClaimsFromAuthHeader")
101
+
defer span.End()
102
+
103
+
if authHeader == "" {
104
+
span.End()
105
+
return fmt.Errorf("No Authorization header provided")
106
+
}
107
+
108
+
authHeaderParts := strings.Split(authHeader, " ")
109
+
if len(authHeaderParts) != 2 {
110
+
return fmt.Errorf("Invalid Authorization header")
111
+
}
112
+
113
+
if authHeaderParts[0] != "Bearer" {
114
+
return fmt.Errorf("Invalid Authorization header (expected Bearer)")
115
+
}
116
+
117
+
accessToken := authHeaderParts[1]
118
+
119
+
parser := jwt.Parser{
120
+
ValidMethods: []string{es256k.SigningMethodES256K.Alg()},
121
+
}
122
+
123
+
token, err := parser.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) {
124
+
if claims, ok := token.Claims.(*jwt.StandardClaims); ok {
125
+
// Get the user's key from PLC Directory
126
+
userDID := claims.Issuer
127
+
entry, ok := auth.KeyCache.Get(userDID)
128
+
if ok && entry.ExpiresAt.After(time.Now()) {
129
+
cacheHits.WithLabelValues("key").Inc()
130
+
span.SetAttributes(attribute.Bool("caches.keys.hit", true))
131
+
return entry.Key, nil
132
+
}
133
+
134
+
cacheMisses.WithLabelValues("key").Inc()
135
+
span.SetAttributes(attribute.Bool("caches.keys.hit", false))
136
+
137
+
did, err := syntax.ParseDID(userDID)
138
+
if err != nil {
139
+
return nil, fmt.Errorf("Failed to parse user DID: %v", err)
140
+
}
141
+
142
+
// Get the user's key from PLC Directory
143
+
id, err := auth.Dir.LookupDID(ctx, did)
144
+
if err != nil {
145
+
return nil, fmt.Errorf("Failed to lookup user DID: %v", err)
146
+
}
147
+
148
+
key, err := id.GetPublicKey("atproto")
149
+
if err != nil {
150
+
return nil, fmt.Errorf("Failed to get user public key: %v", err)
151
+
}
152
+
153
+
parsedPubkey, err := secec.NewPublicKey(key.UncompressedBytes())
154
+
if err != nil {
155
+
return nil, fmt.Errorf("Failed to parse user public key: %v", err)
156
+
}
157
+
158
+
// Add the ECDSA key to the cache
159
+
auth.KeyCache.Add(userDID, KeyCacheEntry{
160
+
Key: parsedPubkey,
161
+
ExpiresAt: time.Now().Add(auth.KeyCacheTTL),
162
+
})
163
+
164
+
return parsedPubkey, nil
165
+
}
166
+
167
+
return nil, fmt.Errorf("Invalid authorization token (failed to parse claims)")
168
+
})
169
+
170
+
if err != nil {
171
+
return fmt.Errorf("Failed to parse authorization token: %v", err)
172
+
}
173
+
174
+
if !token.Valid {
175
+
return fmt.Errorf("Invalid authorization token")
176
+
}
177
+
178
+
return nil
179
+
}
180
+
181
+
func (auth *Auth) AuthenticateGinRequestViaJWT(c *gin.Context) {
182
+
tracer := otel.Tracer("auth")
183
+
ctx, span := tracer.Start(c.Request.Context(), "Auth:AuthenticateGinRequestViaJWT")
184
+
185
+
authHeader := c.GetHeader("Authorization")
186
+
if authHeader == "" {
187
+
span.End()
188
+
c.Next()
189
+
return
190
+
}
191
+
192
+
claims := jwt.StandardClaims{}
193
+
194
+
err := auth.GetClaimsFromAuthHeader(ctx, authHeader, &claims)
195
+
if err != nil {
196
+
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Errorf("Failed to get claims from auth header: %v", err).Error()})
197
+
span.End()
198
+
c.Abort()
199
+
return
200
+
}
201
+
202
+
if claims.Audience != auth.ServiceDID {
203
+
c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("Invalid audience (expected %s)", auth.ServiceDID)})
204
+
c.Abort()
205
+
return
206
+
}
207
+
208
+
// Set claims Issuer to context as user DID
209
+
c.Set("user_did", claims.Issuer)
210
+
span.SetAttributes(attribute.String("user.did", claims.Issuer))
211
+
span.End()
212
+
c.Next()
213
+
}
+80
go.mod
+80
go.mod
···
1
+
module tangled.org/whey.party/rdcs
2
+
3
+
go 1.25.4
4
+
5
+
require (
6
+
github.com/bluesky-social/indigo v0.0.0-20251202051123-81f317e322bc
7
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2
8
+
github.com/gin-gonic/gin v1.11.0
9
+
github.com/golang-jwt/jwt v3.2.2+incompatible
10
+
github.com/google/uuid v1.6.0
11
+
github.com/gorilla/websocket v1.5.3
12
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7
13
+
github.com/prometheus/client_golang v1.23.2
14
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
15
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
16
+
go.opentelemetry.io/otel v1.38.0
17
+
golang.org/x/time v0.14.0
18
+
)
19
+
20
+
require (
21
+
github.com/beorn7/perks v1.0.1 // indirect
22
+
github.com/bytedance/sonic v1.14.0 // indirect
23
+
github.com/bytedance/sonic/loader v0.3.0 // indirect
24
+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
25
+
github.com/cloudwego/base64x v0.1.6 // indirect
26
+
github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect
27
+
github.com/felixge/httpsnoop v1.0.4 // indirect
28
+
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
29
+
github.com/gin-contrib/sse v1.1.0 // indirect
30
+
github.com/go-logr/logr v1.4.3 // indirect
31
+
github.com/go-logr/stdr v1.2.2 // indirect
32
+
github.com/go-playground/locales v0.14.1 // indirect
33
+
github.com/go-playground/universal-translator v0.18.1 // indirect
34
+
github.com/go-playground/validator/v10 v10.27.0 // indirect
35
+
github.com/goccy/go-json v0.10.2 // indirect
36
+
github.com/goccy/go-yaml v1.18.0 // indirect
37
+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
38
+
github.com/ipfs/go-cid v0.4.1 // indirect
39
+
github.com/json-iterator/go v1.1.12 // indirect
40
+
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
41
+
github.com/leodido/go-urn v1.4.0 // indirect
42
+
github.com/mattn/go-isatty v0.0.20 // indirect
43
+
github.com/minio/sha256-simd v1.0.1 // indirect
44
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
45
+
github.com/modern-go/reflect2 v1.0.2 // indirect
46
+
github.com/mr-tron/base58 v1.2.0 // indirect
47
+
github.com/multiformats/go-base32 v0.1.0 // indirect
48
+
github.com/multiformats/go-base36 v0.2.0 // indirect
49
+
github.com/multiformats/go-multibase v0.2.0 // indirect
50
+
github.com/multiformats/go-multihash v0.2.3 // indirect
51
+
github.com/multiformats/go-varint v0.0.7 // indirect
52
+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
53
+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
54
+
github.com/prometheus/client_model v0.6.2 // indirect
55
+
github.com/prometheus/common v0.66.1 // indirect
56
+
github.com/prometheus/procfs v0.16.1 // indirect
57
+
github.com/quic-go/qpack v0.5.1 // indirect
58
+
github.com/quic-go/quic-go v0.54.0 // indirect
59
+
github.com/spaolacci/murmur3 v1.1.0 // indirect
60
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
61
+
github.com/ugorji/go/codec v1.3.0 // indirect
62
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect
63
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
64
+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
65
+
go.opentelemetry.io/otel/metric v1.38.0 // indirect
66
+
go.opentelemetry.io/otel/trace v1.38.0 // indirect
67
+
go.uber.org/mock v0.5.0 // indirect
68
+
go.yaml.in/yaml/v2 v2.4.2 // indirect
69
+
golang.org/x/arch v0.20.0 // indirect
70
+
golang.org/x/crypto v0.41.0 // indirect
71
+
golang.org/x/mod v0.26.0 // indirect
72
+
golang.org/x/net v0.43.0 // indirect
73
+
golang.org/x/sync v0.16.0 // indirect
74
+
golang.org/x/sys v0.35.0 // indirect
75
+
golang.org/x/text v0.28.0 // indirect
76
+
golang.org/x/tools v0.35.0 // indirect
77
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
78
+
google.golang.org/protobuf v1.36.9 // indirect
79
+
lukechampine.com/blake3 v1.2.1 // indirect
80
+
)
+182
go.sum
+182
go.sum
···
1
+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2
+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3
+
github.com/bluesky-social/indigo v0.0.0-20251202051123-81f317e322bc h1:2t+uAvfzJiCsTMwn5fW85t/IGa0+2I7BXS2ORastK4o=
4
+
github.com/bluesky-social/indigo v0.0.0-20251202051123-81f317e322bc/go.mod h1:Pm2I1+iDXn/hLbF7XCg/DsZi6uDCiOo7hZGWprSM7k0=
5
+
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
6
+
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
7
+
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
8
+
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
9
+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
10
+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
11
+
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
12
+
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
13
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
15
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16
+
github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg=
17
+
github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw=
18
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 h1:puGwrNTY2vCt8eakkSEq2yeNxUD3zb2kPhv1OsF1hPs=
19
+
github.com/ericvolp12/jwt-go-secp256k1 v0.0.2/go.mod h1:ntxzdN7EhBp8h+N78AtN2hjbVKHa7mijryYd9nPMyMo=
20
+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
21
+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
22
+
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
23
+
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
24
+
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
25
+
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
26
+
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
27
+
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
28
+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
29
+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
30
+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
31
+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
32
+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
33
+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
34
+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
35
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
36
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
37
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
38
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
39
+
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
40
+
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
41
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
42
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
43
+
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
44
+
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
45
+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
46
+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
47
+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
48
+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
49
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
50
+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
51
+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
52
+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
53
+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
54
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ=
55
+
github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc=
56
+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
57
+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
58
+
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
59
+
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
60
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
61
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
62
+
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
63
+
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
64
+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
65
+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
66
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
67
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
68
+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
69
+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
70
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
71
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
72
+
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
73
+
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
74
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
75
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
76
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
77
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
78
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
79
+
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
80
+
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
81
+
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
82
+
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
83
+
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
84
+
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
85
+
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
86
+
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
87
+
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
88
+
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
89
+
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
90
+
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
91
+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
92
+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
93
+
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
94
+
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
95
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
96
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
97
+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
98
+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
99
+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
100
+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
101
+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
102
+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
103
+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
104
+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
105
+
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
106
+
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
107
+
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
108
+
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
109
+
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
110
+
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
111
+
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
112
+
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
113
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
114
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
115
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
116
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
117
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
118
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
119
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
120
+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
121
+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
122
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
123
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
124
+
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
125
+
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
126
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4=
127
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
128
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
129
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
130
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
131
+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
132
+
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
133
+
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
134
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
135
+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
136
+
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
137
+
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
138
+
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
139
+
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
140
+
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
141
+
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
142
+
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
143
+
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
144
+
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
145
+
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
146
+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
147
+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
148
+
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
149
+
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
150
+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
151
+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
152
+
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
153
+
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
154
+
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
155
+
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
156
+
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
157
+
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
158
+
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
159
+
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
160
+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
161
+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
162
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
163
+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
164
+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
165
+
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
166
+
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
167
+
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
168
+
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
169
+
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
170
+
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
171
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
172
+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
173
+
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
174
+
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
175
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
176
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
177
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
178
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
179
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
180
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
181
+
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
182
+
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+67
main.go
+67
main.go
···
1
+
package main
2
+
3
+
import (
4
+
"fmt"
5
+
"log"
6
+
"net/http"
7
+
"os"
8
+
"time"
9
+
10
+
"tangled.org/whey.party/rdcs/sticket"
11
+
// "github.com/bluesky-social/indigo/atproto/atclient"
12
+
// comatproto "github.com/bluesky-social/indigo/api/atproto"
13
+
// appbsky "github.com/bluesky-social/indigo/api/bsky"
14
+
// "github.com/bluesky-social/indigo/atproto/atclient"
15
+
// "github.com/bluesky-social/indigo/atproto/identity"
16
+
// "github.com/bluesky-social/indigo/atproto/syntax"
17
+
// "github.com/bluesky-social/jetstream/pkg/models"
18
+
)
19
+
20
+
const (
21
+
JETSTREAM_URL = "ws://localhost:6008/subscribe"
22
+
SPACEDUST_URL = "ws://localhost:9998/subscribe"
23
+
SLINGSHOT_URL = "http://localhost:7729"
24
+
CONSTELLATION_URL = "http://localhost:7728"
25
+
)
26
+
27
+
func main() {
28
+
fmt.Fprintf(os.Stdout, "RDCS started")
29
+
30
+
mailbox := sticket.New()
31
+
32
+
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
33
+
mailbox.HandleWS(&w, r)
34
+
})
35
+
36
+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
37
+
fmt.Fprintf(w, "hello worldio !")
38
+
clientUUID := sticket.GetUUIDFromRequest(r)
39
+
hasSticket := clientUUID != ""
40
+
if hasSticket {
41
+
go func(targetUUID string) {
42
+
// simulated heavy processing
43
+
time.Sleep(2 * time.Second)
44
+
45
+
lateData := map[string]any{
46
+
"postId": 101,
47
+
"newComments": []string{
48
+
"Wow great tutorial!",
49
+
"I am stuck on step 1.",
50
+
},
51
+
}
52
+
53
+
success := mailbox.SendToClient(targetUUID, "post_thread_update", lateData)
54
+
if success {
55
+
log.Println("Successfully sent late data via Sticket")
56
+
} else {
57
+
log.Println("Failed to send late data (client disconnected?)")
58
+
}
59
+
}(clientUUID)
60
+
}
61
+
})
62
+
http.ListenAndServe(":7152", nil)
63
+
}
64
+
65
+
func getPostThreadV2(w http.ResponseWriter, r *http.Request) {
66
+
fmt.Fprintf(w, "hello worldio !")
67
+
}
+130
sticket/sticket.go
+130
sticket/sticket.go
···
1
+
package sticket
2
+
3
+
import (
4
+
"log"
5
+
"net/http"
6
+
"sync"
7
+
8
+
"github.com/google/uuid"
9
+
"github.com/gorilla/websocket"
10
+
)
11
+
12
+
const HeaderKey = "X-STICKET"
13
+
14
+
type Event struct {
15
+
Type string `json:"type"`
16
+
Payload any `json:"payload"`
17
+
}
18
+
19
+
type Manager struct {
20
+
clients map[string]*websocket.Conn
21
+
mu sync.RWMutex
22
+
upgrader websocket.Upgrader
23
+
}
24
+
25
+
func New() *Manager {
26
+
return &Manager{
27
+
clients: make(map[string]*websocket.Conn),
28
+
upgrader: websocket.Upgrader{
29
+
CheckOrigin: func(r *http.Request) bool { return true },
30
+
},
31
+
}
32
+
}
33
+
34
+
func (m *Manager) HandleWS(w *http.ResponseWriter, r *http.Request) {
35
+
conn, err := m.upgrader.Upgrade(*w, r, nil)
36
+
if err != nil {
37
+
log.Printf("Sticket: Failed to upgrade: %v", err)
38
+
return
39
+
}
40
+
41
+
clientUUID := r.URL.Query().Get("uuid")
42
+
//isNew := false
43
+
44
+
if clientUUID == "" {
45
+
//isNew = true
46
+
clientUUID = uuid.New().String()
47
+
} else {
48
+
if _, err := uuid.Parse(clientUUID); err != nil {
49
+
//isNew = true
50
+
clientUUID = uuid.New().String()
51
+
}
52
+
}
53
+
54
+
m.register(clientUUID, conn)
55
+
56
+
defer m.unregister(clientUUID)
57
+
58
+
initEvent := Event{
59
+
Type: "init",
60
+
Payload: map[string]string{"uuid": clientUUID},
61
+
}
62
+
if err := m.sendJson(conn, initEvent); err != nil {
63
+
return
64
+
}
65
+
66
+
for {
67
+
if _, _, err := conn.NextReader(); err != nil {
68
+
break
69
+
}
70
+
}
71
+
}
72
+
73
+
func (m *Manager) SendToClient(uuid string, eventType string, data any) bool {
74
+
m.mu.RLock()
75
+
conn, ok := m.clients[uuid]
76
+
m.mu.RUnlock()
77
+
78
+
if !ok {
79
+
return false
80
+
}
81
+
82
+
evt := Event{
83
+
Type: eventType,
84
+
Payload: data,
85
+
}
86
+
87
+
m.mu.Lock()
88
+
defer m.mu.Unlock()
89
+
90
+
if conn, ok = m.clients[uuid]; !ok {
91
+
return false
92
+
}
93
+
94
+
if err := m.sendJson(conn, evt); err != nil {
95
+
conn.Close()
96
+
delete(m.clients, uuid)
97
+
return false
98
+
}
99
+
100
+
return true
101
+
}
102
+
103
+
func GetUUIDFromRequest(r *http.Request) string {
104
+
return r.Header.Get(HeaderKey)
105
+
}
106
+
107
+
func (m *Manager) register(id string, conn *websocket.Conn) {
108
+
m.mu.Lock()
109
+
defer m.mu.Unlock()
110
+
111
+
if existing, ok := m.clients[id]; ok {
112
+
existing.Close()
113
+
}
114
+
m.clients[id] = conn
115
+
log.Printf("Sticket: Client connected [%s]", id)
116
+
}
117
+
118
+
func (m *Manager) unregister(id string) {
119
+
m.mu.Lock()
120
+
defer m.mu.Unlock()
121
+
if conn, ok := m.clients[id]; ok {
122
+
conn.Close()
123
+
delete(m.clients, id)
124
+
log.Printf("Sticket: Client disconnected [%s]", id)
125
+
}
126
+
}
127
+
128
+
func (m *Manager) sendJson(conn *websocket.Conn, v any) error {
129
+
return conn.WriteJSON(v)
130
+
}