Skip to content

Commit 52273a0

Browse files
committed
wip: add WEBPUSH extension
References: ircv3/ircv3-specifications#471
1 parent ae68c82 commit 52273a0

File tree

6 files changed

+140
-1
lines changed

6 files changed

+140
-1
lines changed

downstream.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414
"time"
1515

16+
"github.com/SherClockHolmes/webpush-go"
1617
"github.com/emersion/go-sasl"
1718
"golang.org/x/crypto/bcrypt"
1819
"gopkg.in/irc.v3"
@@ -232,6 +233,7 @@ var permanentDownstreamCaps = map[string]string{
232233
"soju.im/bouncer-networks": "",
233234
"soju.im/bouncer-networks-notify": "",
234235
"soju.im/read": "",
236+
"soju.im/webpush": "",
235237
}
236238

237239
// needAllDownstreamCaps is the list of downstream capabilities that
@@ -313,7 +315,8 @@ type downstreamConn struct {
313315

314316
lastBatchRef uint64
315317

316-
monitored casemapMap
318+
monitored casemapMap
319+
webPushSubscriptions []webpush.Subscription
317320
}
318321

319322
func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
@@ -2997,6 +3000,83 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
29973000
Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
29983001
}}
29993002
}
3003+
case "WEBPUSH":
3004+
var subcommand string
3005+
if err := parseMessageParams(msg, &subcommand); err != nil {
3006+
return err
3007+
}
3008+
3009+
switch subcommand {
3010+
case "VAPIDPUBKEY":
3011+
dc.SendMessage(&irc.Message{
3012+
Prefix: dc.srv.prefix(),
3013+
Command: "WEBPUSH",
3014+
Params: []string{"VAPIDPUBKEY", dc.srv.vapidKeys.pub},
3015+
})
3016+
case "REGISTER":
3017+
var endpoint, keysStr string
3018+
if err := parseMessageParams(msg, nil, &endpoint, &keysStr); err != nil {
3019+
return err
3020+
}
3021+
3022+
// TODO: validate endpoint URL
3023+
rawKeys := irc.ParseTags(keysStr)
3024+
authKey, hasAuthKey := rawKeys["auth"]
3025+
p256dhKey, hasP256dh := rawKeys["p256dh"]
3026+
if !hasAuthKey || !hasP256dh {
3027+
return ircError{&irc.Message{
3028+
Command: "FAIL",
3029+
Params: []string{"WEBPUSH", "INVALID_PARAMS", subcommand, "Missing auth or p256dh key"},
3030+
}}
3031+
}
3032+
3033+
keys := webpush.Keys{
3034+
Auth: string(authKey),
3035+
P256dh: string(p256dhKey),
3036+
}
3037+
3038+
for i, sub := range dc.user.webPushSubscriptions {
3039+
if sub.Endpoint == endpoint && sub.network == dc.network {
3040+
dc.user.webPushSubscriptions = append(dc.user.webPushSubscriptions[:i], dc.user.webPushSubscriptions[i+1:]...)
3041+
break
3042+
}
3043+
}
3044+
3045+
// TODO: limit max number of subscriptions, prune old ones
3046+
// TODO: save the subscription in the DB
3047+
3048+
sub := &webPushSubscription{
3049+
Subscription: webpush.Subscription{
3050+
Endpoint: endpoint,
3051+
Keys: keys,
3052+
},
3053+
network: dc.network,
3054+
}
3055+
dc.user.webPushSubscriptions = append(dc.user.webPushSubscriptions, sub)
3056+
3057+
dc.srv.sendWebPush(&sub.Subscription, &irc.Message{
3058+
Prefix: dc.srv.prefix(),
3059+
Command: "NOTE",
3060+
Params: []string{"WEBPUSH", "REGISTERED", "Web Push subscription created"},
3061+
})
3062+
case "UNREGISTER":
3063+
var endpoint string
3064+
if err := parseMessageParams(msg, nil, &endpoint); err != nil {
3065+
return err
3066+
}
3067+
3068+
for i, sub := range dc.user.webPushSubscriptions {
3069+
if sub.Endpoint == endpoint && sub.network == dc.network {
3070+
dc.user.webPushSubscriptions = append(dc.user.webPushSubscriptions[:i], dc.user.webPushSubscriptions[i+1:]...)
3071+
break
3072+
}
3073+
}
3074+
default:
3075+
return ircError{&irc.Message{
3076+
Command: "FAIL",
3077+
Params: []string{"WEBPUSH", "INVALID_PARAMS", subcommand, "Unknown command"},
3078+
}}
3079+
}
30003080
default:
30013081
dc.logger.Printf("unhandled message: %v", msg)
30023082

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.15
55
require (
66
git.sr.ht/~emersion/go-scfg v0.0.0-20211215104734-c2c7a15d6c99
77
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9
8+
github.com/SherClockHolmes/webpush-go v1.2.0 // indirect
89
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac
910
github.com/klauspost/compress v1.14.4 // indirect
1011
github.com/lib/pq v1.10.4

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 h1:Ahny8Ud1LjVMMA
3838
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA=
3939
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4040
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41+
github.com/SherClockHolmes/webpush-go v1.2.0 h1:sGv0/ZWCvb1HUH+izLqrb2i68HuqD/0Y+AmGQfyqKJA=
42+
github.com/SherClockHolmes/webpush-go v1.2.0/go.mod h1:w6X47YApe/B9wUz2Wh8xukxlyupaxSSEbu6yKJcHN2w=
4143
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
4244
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
4345
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -94,6 +96,8 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
9496
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
9597
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
9698
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
99+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
100+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
97101
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
98102
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
99103
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -249,6 +253,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
249253
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
250254
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
251255
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
256+
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
252257
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
253258
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
254259
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

server.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sync/atomic"
1515
"time"
1616

17+
"github.com/SherClockHolmes/webpush-go"
1718
"github.com/prometheus/client_golang/prometheus"
1819
"github.com/prometheus/client_golang/prometheus/promauto"
1920
"gopkg.in/irc.v3"
@@ -159,6 +160,10 @@ type Server struct {
159160

160161
upstreamConnectErrorsTotal prometheus.Counter
161162
}
163+
164+
vapidKeys struct {
165+
priv, pub string
166+
}
162167
}
163168

164169
func NewServer(db Database) *Server {
@@ -191,6 +196,12 @@ func (s *Server) SetConfig(cfg *Config) {
191196
func (s *Server) Start() error {
192197
s.registerMetrics()
193198

199+
priv, pub, err := webpush.GenerateVAPIDKeys()
200+
if err != nil {
201+
return err
202+
}
203+
s.vapidKeys.priv, s.vapidKeys.pub = priv, pub
204+
194205
users, err := s.db.ListUsers(context.TODO())
195206
if err != nil {
196207
return err
@@ -254,6 +265,28 @@ func (s *Server) registerMetrics() {
254265
})
255266
}
256267

268+
func (s *Server) sendWebPush(sub *webpush.Subscription, msg *irc.Message) {
269+
options := webpush.Options{
270+
VAPIDPublicKey: s.vapidKeys.pub,
271+
VAPIDPrivateKey: s.vapidKeys.priv,
272+
Subscriber: "https://soju.im",
273+
TTL: 7 * 24 * 60 * 60, // seconds
274+
Urgency: webpush.UrgencyHigh,
275+
RecordSize: 2048,
276+
}
277+
278+
resp, err := webpush.SendNotification([]byte(msg.String()), sub, &options)
279+
if err != nil {
280+
s.Logger.Printf("failed to push notification: %v", err)
281+
return
282+
}
283+
284+
resp.Body.Close()
285+
if resp.StatusCode != http.StatusCreated {
286+
s.Logger.Printf("failed to push notification: HTTP error: %v", resp.Status)
287+
}
288+
}
289+
257290
func (s *Server) Shutdown() {
258291
s.lock.Lock()
259292
for ln := range s.listeners {

upstream.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
498498
if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
499499
uc.updateChannelAutoDetach(target)
500500
}
501+
if highlight {
502+
uc.broadcastWebPush(msg)
503+
}
501504
}
502505

503506
uc.produce(target, msg, nil)
@@ -2183,3 +2186,12 @@ func (uc *upstreamConn) updateMonitor() {
21832186
uc.monitored.Delete(target)
21842187
}
21852188
}
2189+
2190+
func (uc *upstreamConn) broadcastWebPush(msg *irc.Message) {
2191+
for _, sub := range uc.user.webPushSubscriptions {
2192+
if sub.network != uc.network {
2193+
continue
2194+
}
2195+
uc.srv.sendWebPush(&sub.Subscription, msg)
2196+
}
2197+
}

user.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
"github.com/SherClockHolmes/webpush-go"
1516
"gopkg.in/irc.v3"
1617
)
1718

@@ -433,6 +434,11 @@ func (net *network) autoSaveSASLPlain(ctx context.Context, username, password st
433434
}
434435
}
435436

437+
type webPushSubscription struct {
438+
webpush.Subscription
439+
network *network
440+
}
441+
436442
type user struct {
437443
User
438444
srv *Server
@@ -444,6 +450,8 @@ type user struct {
444450
networks []*network
445451
downstreamConns []*downstreamConn
446452
msgStore messageStore
453+
454+
webPushSubscriptions []*webPushSubscription
447455
}
448456

449457
func newUser(srv *Server, record *User) *user {

0 commit comments

Comments
 (0)