Skip to content

Commit ea40a66

Browse files
committed
Prepare webpush requests
1 parent 941a159 commit ea40a66

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

simplexmq.cabal

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ library
259259
Simplex.Messaging.Notifications.Server.Main
260260
Simplex.Messaging.Notifications.Server.Prometheus
261261
Simplex.Messaging.Notifications.Server.Push.APNS
262+
Simplex.Messaging.Notifications.Server.Push.WebPush
262263
Simplex.Messaging.Notifications.Server.Push
263264
Simplex.Messaging.Notifications.Server.Push.APNS.Internal
264265
Simplex.Messaging.Notifications.Server.Stats
@@ -303,6 +304,8 @@ library
303304
, directory ==1.3.*
304305
, filepath ==1.4.*
305306
, hourglass ==0.2.*
307+
, http-client ==0.7.*
308+
, http-client-tls ==0.3.6.*
306309
, http-types ==0.12.*
307310
, http2 >=4.2.2 && <4.3
308311
, iproute ==1.7.*

src/Simplex/Messaging/Notifications/Server.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ ntfPush s@NtfPushServer {pushQ} = forever $ do
677677
err e
678678
PPPermanentError -> err e
679679
PPInvalidPusher -> err e
680+
_ -> err e
680681
where
681682
retryDeliver :: IO (Either PushProviderError ())
682683
retryDeliver = do

src/Simplex/Messaging/Notifications/Server/Env.hs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ import Simplex.Messaging.Transport.Server (AddHTTP, ServerCredentials, Transport
4646
import System.Exit (exitFailure)
4747
import System.Mem.Weak (Weak)
4848
import UnliftIO.STM
49+
import Simplex.Messaging.Notifications.Server.Push.WebPush (wpPushProviderClient)
50+
import Network.HTTP.Client (newManager)
51+
import Network.HTTP.Client.TLS (tlsManagerSettings)
4952

5053
data NtfServerConfig = NtfServerConfig
5154
{ transports :: [(ServiceName, ASrvTransport, AddHTTP)],
@@ -160,13 +163,27 @@ newNtfPushServer qSize apnsConfig = do
160163
pure NtfPushServer {pushQ, pushClients, apnsConfig}
161164

162165
newPushClient :: NtfPushServer -> PushProvider -> IO PushProviderClient
163-
newPushClient NtfPushServer {apnsConfig, pushClients} pp = do
166+
newPushClient s pp = do
167+
case pp of
168+
PPWebPush -> newWPPushClient s
169+
_ -> newAPNSPushClient s pp
170+
171+
newAPNSPushClient :: NtfPushServer -> PushProvider -> IO PushProviderClient
172+
newAPNSPushClient NtfPushServer {apnsConfig, pushClients} pp = do
164173
c <- case apnsProviderHost pp of
165174
Nothing -> pure $ \_ _ -> pure ()
166175
Just host -> apnsPushProviderClient <$> createAPNSPushClient host apnsConfig
167176
atomically $ TM.insert pp c pushClients
168177
pure c
169178

179+
newWPPushClient :: NtfPushServer -> IO PushProviderClient
180+
newWPPushClient NtfPushServer {pushClients} = do
181+
logDebug "New WP Client requested"
182+
manager <- newManager tlsManagerSettings
183+
let c = wpPushProviderClient manager
184+
atomically $ TM.insert PPWebPush c pushClients
185+
pure c
186+
170187
getPushClient :: NtfPushServer -> PushProvider -> IO PushProviderClient
171188
getPushClient s@NtfPushServer {pushClients} pp =
172189
TM.lookupIO pp pushClients >>= maybe (newPushClient s pp) pure

src/Simplex/Messaging/Notifications/Server/Push.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Network.HTTP.Types (Status)
3636
import Control.Exception (Exception)
3737
import Simplex.Messaging.Notifications.Server.Store.Types (NtfTknRec)
3838
import Control.Monad.Except (ExceptT)
39+
import GHC.Exception (SomeException)
3940

4041
data JWTHeader = JWTHeader
4142
{ alg :: Text, -- key algorithm, ES256 for APNS
@@ -94,6 +95,10 @@ data PushProviderError
9495
| PPRetryLater
9596
| PPPermanentError
9697
| PPInvalidPusher
98+
| PPWPInvalidUrl
99+
| PPWPRemovedEndpoint
100+
| PPWPRequestTooLong
101+
| PPWPOtherError SomeException
97102
deriving (Show, Exception)
98103

99104
type PushProviderClient = NtfTknRec -> PushNotification -> ExceptT PushProviderError IO ()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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

Comments
 (0)