|
| 1 | +{-# LANGUAGE DeriveAnyClass #-} |
| 2 | +{-# LANGUAGE DuplicateRecordFields #-} |
| 3 | +{-# LANGUAGE LambdaCase #-} |
| 4 | +{-# LANGUAGE NamedFieldPuns #-} |
| 5 | +{-# LANGUAGE OverloadedStrings #-} |
| 6 | +{-# LANGUAGE TemplateHaskell #-} |
| 7 | +{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} |
| 8 | + |
| 9 | +{-# HLINT ignore "Use newtype instead of data" #-} |
| 10 | +{-# LANGUAGE OverloadedRecordDot #-} |
| 11 | +{-# LANGUAGE TypeApplications #-} |
| 12 | + |
| 13 | +module Simplex.Messaging.Notifications.Server.Push.WebPush where |
| 14 | + |
| 15 | +import Network.HTTP.Client |
| 16 | +import Simplex.Messaging.Notifications.Protocol (DeviceToken (WPDeviceToken), WPEndpoint (..)) |
| 17 | +import Simplex.Messaging.Notifications.Server.Store.Types |
| 18 | +import Simplex.Messaging.Notifications.Server.Push |
| 19 | +import Control.Monad.Except |
| 20 | +import Control.Logger.Simple (logDebug) |
| 21 | +import Simplex.Messaging.Util (tshow) |
| 22 | +import qualified Data.ByteString.Char8 as B |
| 23 | +import Data.ByteString.Char8 (ByteString) |
| 24 | +import Control.Monad.IO.Class (liftIO) |
| 25 | +import Control.Exception ( fromException, SomeException, try ) |
| 26 | +import qualified Network.HTTP.Types as N |
| 27 | + |
| 28 | +wpPushProviderClient :: Manager -> PushProviderClient |
| 29 | +wpPushProviderClient mg tkn _ = do |
| 30 | + e <- B.unpack <$> endpoint tkn |
| 31 | + r <- liftPPWPError $ parseUrlThrow e |
| 32 | + logDebug $ "Request to " <> tshow r.host |
| 33 | + let requestHeaders = [ |
| 34 | + ("TTL", "2592000") -- 30 days |
| 35 | + , ("Urgency", "High") |
| 36 | + , ("Content-Encoding", "aes128gcm") |
| 37 | + -- TODO: topic for pings and interval |
| 38 | + ] |
| 39 | + let req = r { |
| 40 | + method = "POST" |
| 41 | + , requestHeaders |
| 42 | + , requestBody = "ping" |
| 43 | + , redirectCount = 0 |
| 44 | + } |
| 45 | + _ <- liftPPWPError $ httpNoBody req mg |
| 46 | + pure () |
| 47 | + where |
| 48 | + endpoint :: NtfTknRec -> ExceptT PushProviderError IO ByteString |
| 49 | + endpoint NtfTknRec {token} = do |
| 50 | + case token of |
| 51 | + WPDeviceToken WPEndpoint{ endpoint = e } -> pure e |
| 52 | + _ -> fail "Wrong device token" |
| 53 | + |
| 54 | +liftPPWPError :: IO a -> ExceptT PushProviderError IO a |
| 55 | +liftPPWPError = liftPPWPError' toPPWPError |
| 56 | + |
| 57 | +liftPPWPError' :: (SomeException -> PushProviderError) -> IO a -> ExceptT PushProviderError IO a |
| 58 | +liftPPWPError' err a = do |
| 59 | + res <- liftIO $ try @SomeException a |
| 60 | + either (throwError . err) return res |
| 61 | + |
| 62 | +toPPWPError :: SomeException -> PushProviderError |
| 63 | +toPPWPError e = case fromException e of |
| 64 | + Just (InvalidUrlException _ _) -> PPWPInvalidUrl |
| 65 | + Just (HttpExceptionRequest _ (StatusCodeException resp _)) -> fromStatusCode (responseStatus resp) ("" :: String) |
| 66 | + _ -> PPWPOtherError e |
| 67 | + where |
| 68 | + fromStatusCode status reason |
| 69 | + | status == N.status200 = PPWPRemovedEndpoint |
| 70 | + | status == N.status410 = PPWPRemovedEndpoint |
| 71 | + | status == N.status413 = PPWPRequestTooLong |
| 72 | + | status == N.status429 = PPRetryLater |
| 73 | + | status >= N.status500 = PPRetryLater |
| 74 | + | otherwise = PPResponseError (Just status) (tshow reason) |
0 commit comments