Initial public release of birdwatch-relay

This commit is contained in:
LOrignal_GaZzz 2026-05-03 22:47:38 -05:00
commit c176f2ad24
17 changed files with 2025 additions and 0 deletions

View file

@ -0,0 +1,57 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// Dispatcher fan-outs an event payload to every registered device, encrypting
// the payload to each device's X25519 public key (NaCl sealed box) and sending
// the ciphertext via Firebase Cloud Messaging.
package notify
import (
"context"
"log/slog"
"github.com/cyrilinait/birdwatch-relay/internal/crypto"
"github.com/cyrilinait/birdwatch-relay/internal/fcm"
"github.com/cyrilinait/birdwatch-relay/internal/storage"
)
type Dispatcher struct {
store *storage.Store
sender *fcm.Sender
}
func NewDispatcher(store *storage.Store, sender *fcm.Sender) *Dispatcher {
return &Dispatcher{store: store, sender: sender}
}
// Send fans the payload out to every registered device. The payload is sealed
// to each device's public key independently — Google FCM only ever sees
// ciphertext + the device token. Errors per device are logged but do not stop
// the rest of the fanout.
func (d *Dispatcher) Send(ctx context.Context, payload []byte) {
devices, err := d.store.ListAll()
if err != nil {
slog.Error("dispatcher: list devices failed", "err", err)
return
}
if len(devices) == 0 {
slog.Debug("dispatcher: no registered devices, dropping event")
return
}
var sent, failed int
for _, dev := range devices {
ct, err := crypto.Seal(payload, dev.PublicKey)
if err != nil {
slog.Error("dispatcher: seal failed", "err", err, "device_id", dev.ID)
failed++
continue
}
if err := d.sender.SendCiphertext(ctx, dev.FCMToken, ct); err != nil {
slog.Warn("dispatcher: fcm send failed", "err", err, "device_id", dev.ID)
failed++
continue
}
sent++
}
slog.Info("dispatcher fanout", "sent", sent, "failed", failed, "total", len(devices))
}