refactor: agentic comment cleanup
This commit is contained in:
@@ -20,7 +20,7 @@ const (
|
||||
ExpoErrorDeviceNotRegistered = "DeviceNotRegistered"
|
||||
)
|
||||
|
||||
// Message is one push to one device. Sound defaults to "default" when empty.
|
||||
// Sound defaults to "default" when empty (set in Send).
|
||||
type Message struct {
|
||||
To string `json:"to"`
|
||||
Title string `json:"title,omitempty"`
|
||||
@@ -29,9 +29,8 @@ type Message struct {
|
||||
Sound string `json:"sound,omitempty"`
|
||||
}
|
||||
|
||||
// Ticket is the synchronous response Expo returns per message. Status is
|
||||
// either "ok" or "error". On error, Details["error"] carries the code (e.g.
|
||||
// "DeviceNotRegistered", "MessageTooBig", "InvalidCredentials").
|
||||
// Status is "ok" or "error". On error, Details["error"] carries the code
|
||||
// (e.g. "DeviceNotRegistered", "MessageTooBig", "InvalidCredentials").
|
||||
type Ticket struct {
|
||||
Status string `json:"status"`
|
||||
ID string `json:"id,omitempty"`
|
||||
@@ -39,8 +38,8 @@ type Ticket struct {
|
||||
Details map[string]any `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// ExpoClient is a minimal HTTP client for the Expo Push API. It does NOT poll
|
||||
// receipts and does NOT retry.
|
||||
// ExpoClient does NOT poll receipts and does NOT retry — fire-and-forget,
|
||||
// with DeviceNotRegistered handled out-of-band by the notifier.
|
||||
type ExpoClient struct {
|
||||
http *http.Client
|
||||
accessToken string
|
||||
@@ -54,14 +53,13 @@ func NewExpoClient(accessToken string) *ExpoClient {
|
||||
}
|
||||
|
||||
type expoSendResponse struct {
|
||||
Data []Ticket `json:"data"`
|
||||
Errors []map[string]any `json:"errors,omitempty"`
|
||||
Data []Ticket `json:"data"`
|
||||
Errors []map[string]any `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
// Send delivers messages in batches of up to expoMaxBatchSize. Returned tickets
|
||||
// preserve the input order across batches: tickets[i] corresponds to msgs[i].
|
||||
// A request-level error (network, 5xx, malformed body) aborts the remaining
|
||||
// batches and is returned to the caller along with whatever tickets succeeded.
|
||||
// Send batches msgs (cap expoMaxBatchSize) and preserves input order:
|
||||
// tickets[i] corresponds to msgs[i]. A request-level failure aborts the
|
||||
// remaining batches; tickets already collected are returned with the error.
|
||||
func (c *ExpoClient) Send(ctx context.Context, msgs []Message) ([]Ticket, error) {
|
||||
if len(msgs) == 0 {
|
||||
return nil, nil
|
||||
|
||||
@@ -11,14 +11,12 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// PusherClient is the subset of the pusher gRPC service the notifier needs.
|
||||
// Defined here (rather than depending on the generated client interface) so
|
||||
// tests can supply a fake without standing up a gRPC server.
|
||||
// PusherClient is a narrow subset of the pusher gRPC service so tests can
|
||||
// supply a fake without standing up a real server.
|
||||
type PusherClient interface {
|
||||
IsOnline(ctx context.Context, in *pbpusher.IsOnlineRequest, opts ...grpc.CallOption) (*pbpusher.IsOnlineResponse, error)
|
||||
}
|
||||
|
||||
// NotifyInput is everything the notifier needs to know about a single newly-created particle.
|
||||
type NotifyInput struct {
|
||||
NetworkID string
|
||||
SenderHumanID string
|
||||
@@ -39,12 +37,11 @@ type NotifyInput struct {
|
||||
Body string
|
||||
}
|
||||
|
||||
// Notifier orchestrates the per-particle fanout:
|
||||
// 1. Resolve recipients (stream visibility ∩ network members, minus sender).
|
||||
// 2. Filter out anyone with an active WebSocket connection.
|
||||
// 3. Look up each remaining human's push tokens.
|
||||
// 4. POST a single batched request to Expo.
|
||||
// 5. Delete any token Expo reports as DeviceNotRegistered.
|
||||
// Notifier fans out one particle to Expo:
|
||||
// 1. Resolve recipients (visibility ∩ network members, minus sender).
|
||||
// 2. Drop anyone currently connected via WebSocket.
|
||||
// 3. Send a batched Expo request for the remainder's tokens.
|
||||
// 4. Prune tokens Expo reports as DeviceNotRegistered.
|
||||
type Notifier struct {
|
||||
networkR network.Reader
|
||||
tokens Service
|
||||
@@ -127,9 +124,8 @@ func (n *Notifier) queryOnline(ctx context.Context, humanIDs []string) (map[stri
|
||||
return resp.Online, nil
|
||||
}
|
||||
|
||||
// cleanupDeadTokens DELETEs any token Expo reports as DeviceNotRegistered.
|
||||
// This is the one feedback signal we honor — other ticket errors (e.g.
|
||||
// MessageTooBig, RateLimit) are logged but never retried.
|
||||
// DeviceNotRegistered is the one feedback signal we honor; other ticket
|
||||
// errors (MessageTooBig, RateLimit, …) are logged and dropped.
|
||||
func (n *Notifier) cleanupDeadTokens(ctx context.Context, msgs []Message, tickets []Ticket) {
|
||||
for i, t := range tickets {
|
||||
if i >= len(msgs) {
|
||||
@@ -160,12 +156,12 @@ func buildMessages(tokens []*PushToken, in NotifyInput) []Message {
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"kind": "particle_created",
|
||||
"network_id": in.NetworkID,
|
||||
"stream_id": in.StreamID,
|
||||
"particle_id": in.ParticleID,
|
||||
"sender_human_id": in.SenderHumanID,
|
||||
"particle_kind": in.ParticleKind,
|
||||
"kind": "particle_created",
|
||||
"network_id": in.NetworkID,
|
||||
"stream_id": in.StreamID,
|
||||
"particle_id": in.ParticleID,
|
||||
"sender_human_id": in.SenderHumanID,
|
||||
"particle_kind": in.ParticleKind,
|
||||
}
|
||||
|
||||
msgs := make([]Message, 0, len(tokens))
|
||||
|
||||
@@ -6,22 +6,18 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Service is the full surface for per-device push token storage. HTTP handlers
|
||||
// use Register/Unregister; the worker's notifier uses ListForHumans and
|
||||
// DeleteByToken. Both consumers share the same underlying repository.
|
||||
// Service stores per-device Expo push tokens and exposes the operations
|
||||
// needed by both the HTTP handlers and the worker-side notifier.
|
||||
type Service interface {
|
||||
// Register upserts a token for the given human. Returns ErrInvalidToken /
|
||||
// ErrInvalidPlatform on bad input.
|
||||
// Register returns ErrInvalidToken / ErrInvalidPlatform on bad input.
|
||||
Register(ctx context.Context, humanID string, in RegisterInput) error
|
||||
// Unregister removes a token, scoped to the calling human so a user can't
|
||||
// delete another user's token. Returns ErrNotFound if the token doesn't
|
||||
// belong to humanID (or doesn't exist).
|
||||
// Unregister is scoped to humanID so a user can't delete another user's
|
||||
// token. Returns ErrNotFound if the token isn't owned by humanID.
|
||||
Unregister(ctx context.Context, humanID, token string) error
|
||||
// ListForHumans returns every push token belonging to any of the given
|
||||
// human IDs. Returns an empty slice when nothing matches.
|
||||
// ListForHumans returns an empty slice when nothing matches.
|
||||
ListForHumans(ctx context.Context, humanIDs []string) ([]*PushToken, error)
|
||||
// DeleteByToken removes a token regardless of owning human. Used by the
|
||||
// notifier to clean up after Expo returns DeviceNotRegistered.
|
||||
// DeleteByToken removes a token regardless of owner — used to prune after
|
||||
// Expo reports DeviceNotRegistered.
|
||||
DeleteByToken(ctx context.Context, token string) error
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ func (p Platform) Valid() bool {
|
||||
}
|
||||
|
||||
type PushToken struct {
|
||||
Token string
|
||||
HumanID string
|
||||
Platform Platform
|
||||
AppVersion string
|
||||
CreatedAt time.Time
|
||||
LastSeenAt time.Time
|
||||
Token string
|
||||
HumanID string
|
||||
Platform Platform
|
||||
AppVersion string
|
||||
CreatedAt time.Time
|
||||
LastSeenAt time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user