webpush icon indicating copy to clipboard operation
webpush copied to clipboard

Rework WebPushService

Open andreblanke opened this issue 1 year ago • 2 comments

Hi there again. I am proposing a few more relatively closely related changes to the API of the WebPushService class. Namely:

  • a7a8f08 which adds a Notification class to hold all data for a web push notification along with a WebPushService.send(Notification) method. This reduces the amount of method parameters, simplifies the interface of the WebPushService class, and allows passing around a web push notification as one coherent entity.
    The multi-parameter send methods are kept for now, but deprecated with a suggested replacement. The example in the README has been updated accordingly.

  • 7982c15 which turns WebPushService into an abstract class which JdkHttpClientWebPushService inherits from as an implementation. Like this, code can be written against the interface of WebPushService and the underlying implementation be swapped out, e.g., when a custom HTTP client should be used. This is also a step into the direction of #47 since the JDK HttpClient-related code is isolated.

  • 5d4f7e4 which changes the exception handling again because it turned out the WebPushStatusException was still not enough for my use case (sorry for that) since I require access to the HTTP response headers as well (e.g., Retry-After when an HTTP 429 response is returned), which WebPush.getSubscriptionState cannot provide.
    The method was changed to throw the more generic WebPushException again and WebPushStatusException was removed. Instead, implementations of WebPushService should be able to throw their own exceptions inheriting from WebPushException which provide more context such as the concrete HTTP response object that caused the exception to be thrown. This new exception class would be JdkHttpClientWebPushService.WebPushHttpException.
    Instead of WebPushService implementation-specific exception types the WebPushStatusException could be extended to include a response: Any? = null field, but that felt really unclean.

I realize that quite a bit of library code is touched by my changes, but it'd be great if you consider including them.

andreblanke avatar Apr 28 '24 12:04 andreblanke

Hi @andreblanke, thank you very much for the work you invested in this. I don't have enough time to fully review this now, but some points that come to my mind instantly:

  • This is really backwards incompatible change, so should probably wait for a new major version
  • Can we do anything less breaking right now to saturate your use case?
    • For what do you need those headers? Are they useful for other users of this lib?
    • If so, should be integrate them in some kind of "response" from this library?

morki avatar Apr 29 '24 06:04 morki

Thanks for your quick reply! It's understandable you can't immediately review all the changes.

This is really backwards incompatible change, so should probably wait for a new major version

I agree that a new major version would be appropriate. Although the only breaking change (leaving aside the deprecations) would be the instantiation of the WebPushService.

Before:

val webPushService: WebPushService = WebPushService(subject, vapidKeys)

After:

val webPushService: WebPushService = JdkHttpClientWebPushService(subject, vapidKeys)

It might also make sense to add a static factory method such as WebPushService.create instead of manually invoking a constructor.

Can we do anything less breaking right now to saturate your use case?

The simplest option would be to add the HTTP headers to the WebPushStatusException. My issue with that is that the exception might include even more fields in the future which would usually be contained in an HttpResponse object or similar.
Right now there's already the status code, the response body (only accessible when parsing the exception message), and then the headers which I'd like to access. Since the exception is thrown by getSubscriptionState, all that information must be passed to the method only for it to be possibly included in the exception.

Alternatively, the approach taken by 5d4f7e4 could be used standalone without separating the interface and implementation of WebPushService.
getSubscriptionState could throw a general WebPushException which is subsequently caught by WebPushService where a WebPushHttpException is thrown containing the HttpResponse. A remaining question would be how to deal with WebPushStatusException that now exists.

For what do you need those headers? Are they useful for other users of this lib?

I would like to back off from sending push notifications if the provider responds with an HTTP 429 Too Many Requests status which is usually accompanied by a Retry-After header containing either an amount of seconds to wait or a date-time.

If so, should be integrate them in some kind of "response" from this library?

I think it'd make sense to have this as functionality within the library itself, yes.

andreblanke avatar Apr 30 '24 20:04 andreblanke