packages feed

push-notify-apn 0.1.2.0 → 0.2.0.0

raw patch · 3 files changed

+55/−84 lines, 3 filesdep −lifted-basePVP ok

version bump matches the API change (PVP)

Dependencies removed: lifted-base

API changes (from Hackage documentation)

- Network.PushNotify.APN: ApnMessageResultClientError :: ClientError -> ApnMessageResult
+ Network.PushNotify.APN: instance Network.PushNotify.APN.SpecifyError Network.PushNotify.APN.ApnMessageResult

Files

changelog.md view
@@ -1,10 +1,3 @@--0.1.2.0-=======--- Threading bugfix-- Migrate to http2-client-0.9.0.0 and lts-14- 0.1.1.1 ======= 
push-notify-apn.cabal view
@@ -1,5 +1,5 @@ name:                push-notify-apn-version:             0.1.2.0+version:             0.2.0.0 synopsis:            Send push notifications to mobile iOS devices description:     push-notify-apn is a library and command line utility that can be used to send@@ -31,7 +31,6 @@                      , data-default                      , http2                      , http2-client-                     , lifted-base                      , random                      , semigroups                      , text
src/Network/PushNotify/APN.hs view
@@ -10,7 +10,6 @@ {-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections     #-}  module Network.PushNotify.APN@@ -49,7 +48,7 @@  import           Control.Concurrent import           Control.Concurrent.QSem-import           Control.Exception.Lifted (Exception, bracket_, throw, throwIO)+import           Control.Exception import           Control.Monad import           Data.Aeson import           Data.Aeson.Types@@ -63,7 +62,6 @@ import           Data.Maybe import           Data.Semigroup                       ((<>)) import           Data.Text                            (Text)-import           Data.Time.Clock import           Data.Time.Clock.POSIX import           Data.Typeable                        (Typeable) import           Data.X509@@ -153,9 +151,11 @@                       | ApnMessageResultFatalError ApnFatalError                       | ApnMessageResultTemporaryError ApnTemporaryError                       | ApnMessageResultIOError IOError-                      | ApnMessageResultClientError ClientError     deriving (Eq, Show) +instance SpecifyError ApnMessageResult where+    isAnError = ApnMessageResultIOError+ -- | The specification of a push notification's message body data JsonApsAlert = JsonApsAlert     { jaaTitle :: !(Maybe Text)@@ -416,41 +416,31 @@ isOpen :: ApnSession -> IO Bool isOpen = readIORef . apnSessionOpen -withConnection :: ApnSession -> (ApnConnection -> ClientIO a) -> ClientIO a+withConnection :: ApnSession -> (ApnConnection -> IO a) -> IO a withConnection s action = do-    lift $ ensureOpen s+    ensureOpen s     let pool = apnSessionPool s-    mConn <- getExistingConnection pool-    case mConn of-        Nothing -> do-            conn <- newConnection s-            res <- action conn-            lift $ atomicModifyIORef' pool (\a -> (conn:a, ()))+    connections <- readIORef pool+    let len = length connections+    if len == 0+    then do+        conn <- newConnection s+        res <- action conn+        atomicModifyIORef' pool (\a -> (conn:a, ()))+        return res+    else do+        num <- randomRIO (0, len - 1)+        currtime <- round <$> getPOSIXTime :: IO Int64+        let conn = connections !! num+            conn1 = conn { apnConnectionLastUsed=currtime }+        atomicModifyIORef' pool (\a -> (removeNth num a, ()))+        isOpen <- readIORef (apnConnectionOpen conn)+        if isOpen+        then do+            res <- action conn1+            atomicModifyIORef' pool (\a -> (conn1:a, ()))             return res-        Just conn -> do-            currtime <- round <$> lift getPOSIXTime-            conn <- pure (conn { apnConnectionLastUsed=currtime })-            isOpen <- lift $ readIORef (apnConnectionOpen conn)-            if isOpen-            then do-                res <- action conn-                lift $ atomicModifyIORef' pool (\a -> (conn:a, ()))-                return res-            else withConnection s action-    where-        getExistingConnection :: (IORef [ApnConnection]) -> ClientIO (Maybe ApnConnection)-        getExistingConnection pool =-            do connections <- lift $ readIORef pool-               let len = length connections-               if len == 0-               then return Nothing-               else do-                   num <- lift $ randomRIO (0, len - 1)-                   lift $ atomicModifyIORef' pool $ \conns ->-                       case removeNthMay num conns of-                         Nothing -> (conns, Nothing)-                         Just (myConn, conns) -> (conns, Just myConn)-+        else withConnection s action  checkCertificates :: ApnConnectionInfo -> IO Bool checkCertificates aci = do@@ -458,13 +448,14 @@     credential <- credentialLoadX509 (aciCertPath aci) (aciCertKey aci)     return $ isJust castore && isRight credential -removeNthMay :: Int -> [a] -> Maybe (a, [a])-removeNthMay _ [] = Nothing-removeNthMay 0 (x:xs) = Just (x, xs)-removeNthMay n (x:xs) = fmap (second (x:)) (removeNthMay (n-1) xs)-    where-      second f (x, y) = (x, f y)+replaceNth n newVal (x:xs)+    | n == 0 = newVal:xs+    | otherwise = x:replaceNth (n-1) newVal xs +removeNth n (x:xs)+    | n == 0 = xs+    | otherwise = x:removeNth (n-1) xs+ manage :: Int64 -> IORef [ApnConnection] -> IO () manage timeout ioref = forever $ do     currtime <- round <$> getPOSIXTime :: IO Int64@@ -474,11 +465,11 @@     mapM_ closeApnConnection expiredOnes     threadDelay 60000000 -newConnection :: ApnSession -> ClientIO ApnConnection+newConnection :: ApnSession -> IO ApnConnection newConnection apnSession = do     let aci = apnSessionConnectionInfo apnSession-    Just castore <- lift $ readCertificateStore $ aciCaPath aci-    Right credential <- lift $ credentialLoadX509 (aciCertPath aci) (aciCertKey aci)+    Just castore <- readCertificateStore $ aciCaPath aci+    Right credential <- credentialLoadX509 (aciCertPath aci) (aciCertKey aci)     let credentials = Credentials [credential]         shared      = def { sharedCredentials = credentials                           , sharedCAStore=castore }@@ -506,29 +497,26 @@          hostname = aciHostname aci     httpFrameConnection <- newHttp2FrameConnection (T.unpack hostname) 443 (Just clip)-    isOpen <- lift $ newIORef True+    isOpen <- newIORef True     let handleGoAway rsgaf = do-            lift $ writeIORef isOpen False+            writeIORef isOpen False             return ()     client <- newHttp2Client httpFrameConnection 4096 4096 conf handleGoAway ignoreFallbackHandler     linkAsyncs client-    flowWorker <- lift $ forkIO $ forever $ do-        updated <- runClientIO $ _updateWindow $ _incomingFlowControl client+    flowWorker <- forkIO $ forever $ do+        updated <- _updateWindow $ _incomingFlowControl client         threadDelay 1000000 -    workersem <- lift $ newQSem maxConcurrentStreams-    currtime :: Int64 <- round <$> lift getPOSIXTime+    workersem <- newQSem maxConcurrentStreams+    currtime <- round <$> getPOSIXTime :: IO Int64     return $ ApnConnection client aci workersem currtime flowWorker isOpen   closeApnConnection :: ApnConnection -> IO ()-closeApnConnection connection =-    -- Ignoring ClientErrors in this place. We want to close our session, so we do not need to-    -- fail on this kind of errors.-    void $ runClientIO $ do-    lift $ writeIORef (apnConnectionOpen connection) False+closeApnConnection connection = do+    writeIORef (apnConnectionOpen connection) False     let flowWorker = apnConnectionFlowControlWorker connection-    lift $ killThread flowWorker+    killThread flowWorker     _gtfo (apnConnectionConnection connection) HTTP2.NoError ""     _close (apnConnectionConnection connection) @@ -543,7 +531,7 @@     -- ^ The message to send     -> IO ApnMessageResult     -- ^ The response from the APN server-sendRawMessage s token payload = catchErrors $+sendRawMessage s token payload = catchIOErrors $     withConnection s $ \c ->         sendApnRaw c token payload @@ -557,7 +545,7 @@     -- ^ The message to send     -> IO ApnMessageResult     -- ^ The response from the APN server-sendMessage s token payload = catchErrors $+sendMessage s token payload = catchIOErrors $     withConnection s $ \c ->         sendApnRaw c token message   where message = L.toStrict $ encode payload@@ -570,7 +558,7 @@     -- ^ Device to send the message to     -> IO ApnMessageResult     -- ^ The response from the APN server-sendSilentMessage s token = catchErrors $+sendSilentMessage s token = catchIOErrors $     withConnection s $ \c ->         sendApnRaw c token message   where message = "{\"aps\":{\"content-available\":1}}"@@ -588,10 +576,10 @@     -- ^ Device to send the message to     -> ByteString     -- ^ The message to send-    -> ClientIO ApnMessageResult+    -> IO ApnMessageResult sendApnRaw connection token message = bracket_-  (lift $ waitQSem (apnConnectionWorkerPool connection))-  (lift $ signalQSem (apnConnectionWorkerPool connection)) $ do+  (waitQSem (apnConnectionWorkerPool connection))+  (signalQSem (apnConnectionWorkerPool connection)) $ do     let requestHeaders = [ ( ":method", "POST" )                   , ( ":scheme", "https" )                   , ( ":authority", TE.encodeUtf8 hostname )@@ -609,7 +597,7 @@                 -- sendData client stream (HTTP2.setEndStream) message                 upload message (HTTP2.setEndHeader . HTTP2.setEndStream) client (_outgoingFlowControl client) stream osfc                 let pph hStreamId hStream hHeaders hIfc hOfc =-                        lift $ print hHeaders+                        print hHeaders                 response <- waitStream stream isfc pph                 let (errOrHeaders, frameResponses, _) = response                 case errOrHeaders of@@ -644,17 +632,8 @@         getHeaderEx :: HTTP2.HeaderName -> [HTTP2.Header] -> HTTP2.HeaderValue         getHeaderEx name headers = fromMaybe (throw $ ApnExceptionMissingHeader name) (DL.lookup name headers) --catchErrors :: ClientIO ApnMessageResult -> IO ApnMessageResult-catchErrors = catchIOErrors . catchClientErrors-    where-        catchIOErrors :: IO ApnMessageResult -> IO ApnMessageResult-        catchIOErrors = flip catchIOError (return . ApnMessageResultIOError)--        catchClientErrors :: ClientIO ApnMessageResult -> IO ApnMessageResult-        catchClientErrors act =-            either ApnMessageResultClientError id <$> runClientIO act-+catchIOErrors :: SpecifyError a => IO a -> IO a+catchIOErrors = flip catchIOError (return . isAnError)  -- The type of permanent error indicated by APNS -- See https://apple.co/2RDCdWC table 8-6 for the meaning of each value.