packages feed

push-notify-apn 0.1.0.8 → 0.1.1.0

raw patch · 4 files changed

+108/−65 lines, 4 filesdep +hspecdep +semigroupsPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, semigroups

API changes (from Hackage documentation)

+ Network.PushNotify.APN: ApnMessageResultBadDeviceToken :: ApnMessageResult
+ Network.PushNotify.APN: bodyMessage :: Text -> JsonApsMessage
+ Network.PushNotify.APN: setMessageBody :: Text -> JsonApsMessage -> JsonApsMessage

Files

changelog.md view
@@ -1,3 +1,13 @@+0.1.1.0+=======++- Let the client send alerts with no title++0.1.0.9+=======++- Add "semigroups" as a dependency for older ghc versions+ 0.1.0.8 ======= 
push-notify-apn.cabal view
@@ -1,5 +1,5 @@ name:                push-notify-apn-version:             0.1.0.8+version:             0.1.1.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@@ -32,6 +32,7 @@                      , http2                      , http2-client                      , random+                     , semigroups                      , text                      , time                      , tls@@ -48,6 +49,7 @@                      , push-notify-apn                      , bytestring                      , optparse-applicative+                     , semigroups                      , text   default-language:    Haskell2010 @@ -57,6 +59,8 @@   main-is:             Spec.hs   build-depends:       base                      , push-notify-apn+                     , aeson+                     , hspec   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010 
src/Network/PushNotify/APN.hs view
@@ -7,8 +7,9 @@ -- Portability: portable -- -- Send push notifications using Apple's HTTP2 APN API+{-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TupleSections     #-}  module Network.PushNotify.APN     ( newSession@@ -20,8 +21,10 @@     , sendSilentMessage     , sendRawMessage     , alertMessage+    , bodyMessage     , emptyMessage     , setAlertMessage+    , setMessageBody     , setBadge     , setCategory     , setSound@@ -39,60 +42,60 @@     , ApnToken     ) where -import Control.Concurrent-import Control.Concurrent.QSem-import Control.Exception-import Control.Monad-import Data.Aeson-import Data.Aeson.Types-import Data.ByteString (ByteString)-import Data.Char (toLower)-import Data.Default (def)-import Data.Either-import Data.Int-import Data.IORef-import Data.Map.Strict (Map)-import Data.Maybe-import Data.Text (Text)-import Data.Time.Clock.POSIX-import Data.X509-import Data.X509.CertificateStore-import GHC.Generics-import Network.HTTP2.Client-import Network.HTTP2.Client.FrameConnection-import Network.HTTP2.Client.Helpers-import Network.TLS hiding (sendData)-import Network.TLS.Extra.Cipher-import System.IO.Error-import System.Mem.Weak-import System.Random+import           Control.Concurrent+import           Control.Concurrent.QSem+import           Control.Exception+import           Control.Monad+import           Data.Aeson+import           Data.Aeson.Types+import           Data.ByteString                      (ByteString)+import           Data.Char                            (toLower)+import           Data.Default                         (def)+import           Data.Either+import           Data.Int+import           Data.IORef+import           Data.Map.Strict                      (Map)+import           Data.Maybe+import           Data.Text                            (Text)+import           Data.Time.Clock.POSIX+import           Data.X509+import           Data.X509.CertificateStore+import           GHC.Generics+import           Network.HTTP2.Client+import           Network.HTTP2.Client.FrameConnection+import           Network.HTTP2.Client.Helpers+import           Network.TLS                          hiding (sendData)+import           Network.TLS.Extra.Cipher+import           System.IO.Error+import           System.Mem.Weak+import           System.Random -import qualified Data.ByteString as S-import qualified Data.ByteString.Base16 as B16-import qualified Data.ByteString.Lazy as L-import qualified Data.List as DL-import qualified Data.Map.Strict as M-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE+import qualified Data.ByteString                      as S+import qualified Data.ByteString.Base16               as B16+import qualified Data.ByteString.Lazy                 as L+import qualified Data.List                            as DL+import qualified Data.Map.Strict                      as M+import qualified Data.Text                            as T+import qualified Data.Text.Encoding                   as TE -import qualified Network.HTTP2 as HTTP2-import qualified Network.HPACK as HTTP2+import qualified Network.HPACK                        as HTTP2+import qualified Network.HTTP2                        as HTTP2  -- | A session that manages connections to Apple's push notification service data ApnSession = ApnSession-    { apnSessionPool                 :: !(IORef [ApnConnection])-    , apnSessionConnectionInfo       :: !ApnConnectionInfo-    , apnSessionConnectionManager    :: !ThreadId-    , apnSessionOpen                 :: !(IORef Bool)}+    { apnSessionPool              :: !(IORef [ApnConnection])+    , apnSessionConnectionInfo    :: !ApnConnectionInfo+    , apnSessionConnectionManager :: !ThreadId+    , apnSessionOpen              :: !(IORef Bool)}  -- | Information about an APN connection data ApnConnectionInfo = ApnConnectionInfo-    { aciCertPath                    :: !FilePath-    , aciCertKey                     :: !FilePath-    , aciCaPath                      :: !FilePath-    , aciHostname                    :: !Text-    , aciMaxConcurrentStreams        :: !Int-    , aciTopic                       :: !ByteString }+    { aciCertPath             :: !FilePath+    , aciCertKey              :: !FilePath+    , aciCaPath               :: !FilePath+    , aciHostname             :: !Text+    , aciMaxConcurrentStreams :: !Int+    , aciTopic                :: !ByteString }  -- | A connection to an APN API server data ApnConnection = ApnConnection@@ -128,6 +131,7 @@ -- | The result of a send request data ApnMessageResult = ApnMessageResultOk                       | ApnMessageResultFatalError+                      | ApnMessageResultBadDeviceToken                       | ApnMessageResultTemporaryError                       | ApnMessageResultTokenNoLongerValid     deriving (Enum, Eq, Show)@@ -137,29 +141,31 @@  -- | The specification of a push notification's message body data JsonApsAlert = JsonApsAlert-    { jaaTitle                       :: !Text+    { jaaTitle :: !(Maybe Text)     -- ^ A short string describing the purpose of the notification.-    , jaaBody                        :: !Text+    , jaaBody  :: !Text     -- ^ The text of the alert message.     } deriving (Generic, Show)  instance ToJSON JsonApsAlert where     toJSON     = genericToJSON     defaultOptions-        { fieldLabelModifier = drop 3 . map toLower }+        { fieldLabelModifier = drop 3 . map toLower+        , omitNothingFields  = True+        }  -- | Push notification message's content data JsonApsMessage     -- | Push notification message's content     = JsonApsMessage-    { jamAlert                       :: !(Maybe JsonApsAlert)+    { jamAlert    :: !(Maybe JsonApsAlert)     -- ^ A text to display in the notification-    , jamBadge                       :: !(Maybe Int)+    , jamBadge    :: !(Maybe Int)     -- ^ A number to display next to the app's icon. If set to (Just 0), the number is removed.-    , jamSound                       :: !(Maybe Text)+    , jamSound    :: !(Maybe Text)     -- ^ A sound to play, that's located in the Library/Sounds directory of the app     -- This should be the name of a sound file in the application's main bundle, or     -- in the Library/Sounds directory of the app.-    , jamCategory                    :: !(Maybe Text)+    , jamCategory :: !(Maybe Text)     -- ^ The category of the notification. Must be registered by the app beforehand.     } deriving (Generic, Show) @@ -231,6 +237,14 @@     -- ^ The modified message alertMessage title text = setAlertMessage title text emptyMessage +-- | Create a new APN message with a body and no title+bodyMessage+    :: Text+    -- ^ The body of the message+    -> JsonApsMessage+    -- ^ The modified message+bodyMessage text = setMessageBody text emptyMessage+ -- | Set the alert part of an APN message setAlertMessage     :: Text@@ -243,8 +257,22 @@     -- ^ The modified message setAlertMessage title text a = a { jamAlert = Just jam }   where-    jam = JsonApsAlert title text+    jam = JsonApsAlert (Just title) text +-- | Set the body of an APN message without affecting the title+setMessageBody+    :: Text+    -- ^ The body of the message+    -> JsonApsMessage+    -- ^ The message to alter+    -> JsonApsMessage+    -- ^ The modified message+setMessageBody text a = a { jamAlert = Just newJaa }+  where+    newJaa = case jamAlert a of+                Nothing  -> JsonApsAlert Nothing text+                Just jaa -> jaa { jaaBody = text }+ -- | Remove the alert part of an APN message clearAlertMessage     :: JsonApsMessage@@ -261,9 +289,9 @@ data JsonAps     -- | A push notification message     = JsonAps-    { jaAps                          :: !JsonApsMessage+    { jaAps                :: !JsonApsMessage     -- ^ The main content of the message-    , jaAppSpecificContent           :: !(Maybe Text)+    , jaAppSpecificContent :: !(Maybe Text)     -- ^ Extra information to be used by the receiving app     } deriving (Generic, Show) @@ -334,11 +362,11 @@ -- to call this function. closeSession :: ApnSession -> IO () closeSession s = do-    isOpen <- atomicModifyIORef' (apnSessionOpen s) (\a -> (False, a))+    isOpen <- atomicModifyIORef' (apnSessionOpen s) (False,)     unless isOpen $ error "Session is already closed"     killThread (apnSessionConnectionManager s)     let ioref = apnSessionPool s-    openConnections <- atomicModifyIORef' ioref (\conns -> ([], conns))+    openConnections <- atomicModifyIORef' ioref ([],)     mapM_ closeApnConnection openConnections  -- | Check whether a session is open or has been closed@@ -529,21 +557,23 @@                 let pph hStreamId hStream hHeaders hIfc hOfc =                         print hHeaders                 response <- waitStream stream isfc pph-                let (errOrHeaders, _, _) = response+                let (errOrHeaders, frameResponses, _) = response                 case errOrHeaders of                     Left err -> return ApnMessageResultTemporaryError                     Right hdrs1 -> do                         let Just status = DL.lookup ":status" hdrs1                         return $ case status of                             "200" -> ApnMessageResultOk-                            "400" -> ApnMessageResultFatalError+                            "400" -> if Right "{\"reason\":\"BadDeviceToken\"}" `DL.elem` frameResponses+                                        then ApnMessageResultBadDeviceToken+                                        else ApnMessageResultFatalError                             "403" -> ApnMessageResultFatalError                             "405" -> ApnMessageResultFatalError                             "410" -> ApnMessageResultTokenNoLongerValid                             "413" -> ApnMessageResultFatalError                             "429" -> ApnMessageResultTemporaryError                             "500" -> ApnMessageResultTemporaryError-                            "503" -> ApnMessageResultTemporaryError           +                            "503" -> ApnMessageResultTemporaryError         in StreamDefinition init handler     case res of         Left _     -> return ApnMessageResultTemporaryError -- Too much concurrency
test/Spec.hs view
@@ -1,2 +1,1 @@-main :: IO ()-main = putStrLn "Test suite not yet implemented"+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}