New creates a new Dispatcher to dispatch web push notifications. This is *not* integrated into the enqueue system unfortunately. That's because the notifications system has a enqueue system, and push notifications at time of implementation are being used for updates inside of a workspace, which we
(ctx context.Context, log *slog.Logger, db database.Store, vapidSub string, opts ...Option)
| 106 | // |
| 107 | // See: https://github.com/coder/internal/issues/528 |
| 108 | func New(ctx context.Context, log *slog.Logger, db database.Store, vapidSub string, opts ...Option) (Dispatcher, error) { |
| 109 | cfg := options{ |
| 110 | clock: quartz.NewReal(), |
| 111 | subscriptionCacheTTL: defaultSubscriptionCacheTTL, |
| 112 | } |
| 113 | for _, opt := range opts { |
| 114 | opt(&cfg) |
| 115 | } |
| 116 | if cfg.clock == nil { |
| 117 | cfg.clock = quartz.NewReal() |
| 118 | } |
| 119 | if cfg.subscriptionCacheTTL <= 0 { |
| 120 | cfg.subscriptionCacheTTL = defaultSubscriptionCacheTTL |
| 121 | } |
| 122 | if cfg.httpClient == nil { |
| 123 | cfg.httpClient = newSSRFSafeHTTPClient() |
| 124 | } |
| 125 | |
| 126 | keys, err := db.GetWebpushVAPIDKeys(ctx) |
| 127 | if err != nil { |
| 128 | if !errors.Is(err, sql.ErrNoRows) { |
| 129 | return nil, xerrors.Errorf("get notification vapid keys: %w", err) |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | if keys.VapidPublicKey == "" || keys.VapidPrivateKey == "" { |
| 134 | // Generate new VAPID keys. This also deletes all existing push |
| 135 | // subscriptions as part of the transaction, as they are no longer |
| 136 | // valid. |
| 137 | newPrivateKey, newPublicKey, err := RegenerateVAPIDKeys(ctx, db) |
| 138 | if err != nil { |
| 139 | return nil, xerrors.Errorf("regenerate vapid keys: %w", err) |
| 140 | } |
| 141 | |
| 142 | keys.VapidPublicKey = newPublicKey |
| 143 | keys.VapidPrivateKey = newPrivateKey |
| 144 | } |
| 145 | |
| 146 | return &Webpusher{ |
| 147 | vapidSub: vapidSub, |
| 148 | store: db, |
| 149 | log: log, |
| 150 | VAPIDPublicKey: keys.VapidPublicKey, |
| 151 | VAPIDPrivateKey: keys.VapidPrivateKey, |
| 152 | clock: cfg.clock, |
| 153 | subscriptionCacheTTL: cfg.subscriptionCacheTTL, |
| 154 | subscriptionCache: make(map[uuid.UUID]cachedSubscriptions), |
| 155 | subscriptionGenerations: make(map[uuid.UUID]uint64), |
| 156 | httpClient: cfg.httpClient, |
| 157 | }, nil |
| 158 | } |
| 159 | |
| 160 | type cachedSubscriptions struct { |
| 161 | subscriptions []database.WebpushSubscription |