diff --git a/autopush/client.go b/autopush/client.go index b27d4bf..07bbaef 100644 --- a/autopush/client.go +++ b/autopush/client.go @@ -1,6 +1,7 @@ package autopush import ( + "context" "crypto/ecdh" "crypto/sha256" "encoding/base64" @@ -18,6 +19,7 @@ const MOZILLA_PUSH_SERVICE = "wss://push.services.mozilla.com" type AutoPushClient struct { *websocket.WebSocketClient + awaitingPong bool ece *rfc8291.RFC8291 helloChan chan HelloResponse notificationChan chan Notification @@ -49,8 +51,13 @@ func unmarshaler[T any](payload json.RawMessage, label MessageType) (data *T) { return data } +func (c *AutoPushClient) Ping() error { + c.awaitingPong = true + return c.SendJSON(struct{}{}) +} + func (c *AutoPushClient) Hello(uaid string, channelIDs []string) (HelloResponse, error) { - return request(c, c.helloChan, 5, "hello timeout", HelloRequest{ + return request(c, c.helloChan, 5, "hello", HelloRequest{ Type: HELLO, UAID: uaid, ChannelIDs: channelIDs, @@ -59,7 +66,7 @@ func (c *AutoPushClient) Hello(uaid string, channelIDs []string) (HelloResponse, } func (c *AutoPushClient) Register(channelID string, vapidKey string) (RegisterResponse, error) { - return request(c, c.registerChan, 5, "register timeout", RegisterRequest{ + return request(c, c.registerChan, 5, "register", RegisterRequest{ Type: REGISTER, ChannelID: channelID, Key: vapidKey, @@ -67,7 +74,7 @@ func (c *AutoPushClient) Register(channelID string, vapidKey string) (RegisterRe } func (c *AutoPushClient) Unregister(channelID string) (UnregisterResponse, error) { - return request(c, c.unregisterChan, 5, "unregister timeout", UnregisterRequest{ + return request(c, c.unregisterChan, 5, "unregister", UnregisterRequest{ Type: UNREGISTER, ChannelID: channelID, }) @@ -102,7 +109,8 @@ func (c *AutoPushClient) Decrypt( return &result, nil } -func NewAutoPushClient() (ap *AutoPushClient, ch chan Notification) { +func NewAutoPushClient(ctx context.Context) (ap *AutoPushClient, ch chan Notification) { + ctx, cancel := context.WithCancel(ctx) ap = &AutoPushClient{ ece: rfc8291.NewRFC8291(sha256.New), helloChan: make(chan HelloResponse), @@ -110,8 +118,29 @@ func NewAutoPushClient() (ap *AutoPushClient, ch chan Notification) { registerChan: make(chan RegisterResponse), unregisterChan: make(chan UnregisterResponse), WebSocketClient: websocket.NewWebSocketClient( - nil, + ctx, func(ws *websocket.WebSocketClient) { + // 3min: 10x more often than firefox + // if this is below 45sec, autopush will disconnect us + // + // https://github.com/mozilla-services/autopush-rs/blob/master/autoconnect/autoconnect-ws/autoconnect-ws-sm/src/identified/on_client_msg.rs#L295 + // https://searchfox.org/firefox-main/source/dom/push/PushServiceWebSocket.sys.mjs#463 + // https://searchfox.org/firefox-main/source/modules/libpref/init/all.js#3230 + pushPing := time.NewTicker(55 * time.Second) + + go func() { + for { + select { + case <-ctx.Done(): + return + case <-pushPing.C: + ap.Ping() + } + } + }() + }, + func(ws *websocket.WebSocketClient) { + cancel() close(ap.helloChan) close(ap.notificationChan) close(ap.registerChan) @@ -125,9 +154,17 @@ func NewAutoPushClient() (ap *AutoPushClient, ch chan Notification) { return } + if message.Type == NULL { + message.Type = PING + } + switch message.Type { case PING: - ws.SendJSON("{}") + if ap.awaitingPong { + ap.awaitingPong = false + } else { + ap.Ping() + } case HELLO: if data := unmarshaler[HelloResponse](payload, HELLO); data != nil { diff --git a/autopush/types.go b/autopush/types.go index 56ac4ec..b87da67 100644 --- a/autopush/types.go +++ b/autopush/types.go @@ -11,6 +11,7 @@ const ( type MessageType string const ( + NULL MessageType = "" PING MessageType = "ping" ACK MessageType = "ack" HELLO MessageType = "hello" diff --git a/examples/nicopush/main.go b/examples/nicopush/main.go index dd4b868..213e427 100644 --- a/examples/nicopush/main.go +++ b/examples/nicopush/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/ecdh" "encoding/base64" "encoding/json" @@ -101,6 +102,7 @@ func main() { } nicoPushClient, notificationChan, err := nicopush.NewNicoPushClient( + context.Background(), config.UAID, config.ChannelIDs, config.AuthSecret, diff --git a/go.mod b/go.mod index a0ae3b6..69a4c63 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.min.rip/min/webpush-client-go go 1.23.6 require ( - git.min.rip/min/websocket-client-go v1.1.1 + git.min.rip/min/websocket-client-go v1.2.0 github.com/google/uuid v1.6.0 golang.org/x/crypto v0.35.0 golang.org/x/net v0.35.0 diff --git a/go.sum b/go.sum index 8aa622e..dbb593f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.min.rip/min/websocket-client-go v1.1.1 h1:oBIGt2HGPRpeqNY+isDagIKQUQ1W3KhVWJrp0vsMdwA= -git.min.rip/min/websocket-client-go v1.1.1/go.mod h1:qE2autSEJU74jUb+tET0J+Ozg2w7BVtg31Toq83kz4Q= +git.min.rip/min/websocket-client-go v1.2.0 h1:hIwowEju/7lHHC3V6544jatB8Z9faKWbKD6gqZJwRq4= +git.min.rip/min/websocket-client-go v1.2.0/go.mod h1:qE2autSEJU74jUb+tET0J+Ozg2w7BVtg31Toq83kz4Q= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= diff --git a/sites/nicopush/client.go b/sites/nicopush/client.go index 78b641c..9ea98b3 100644 --- a/sites/nicopush/client.go +++ b/sites/nicopush/client.go @@ -2,6 +2,7 @@ package nicopush import ( "bytes" + "context" "crypto/ecdh" "crypto/sha256" "encoding/base64" @@ -32,6 +33,7 @@ type NicoPushClient struct { } func NewNicoPushClient( + ctx context.Context, uaid string, channelIDs []string, authSecret []byte, @@ -43,7 +45,7 @@ func NewNicoPushClient( return nil, nil, err } - ap, ch := autopush.NewAutoPushClient() + ap, ch := autopush.NewAutoPushClient(ctx) client := &NicoPushClient{ autoPushClient: ap,