push-notify-general (empty) → 0.1.0.0
raw patch · 8 files changed
+588/−0 lines, 8 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, containers, data-default, hashable, http-conduit, http-types, push-notify, push-notify-ccs, text, unordered-containers, xml-conduit, yesod
Files
- LICENSE +23/−0
- Network/PushNotify/General.hs +116/−0
- Network/PushNotify/General/Send.hs +132/−0
- Network/PushNotify/General/Types.hs +200/−0
- Network/PushNotify/General/YesodPushApp.hs +53/−0
- Network/PushNotify/General/YesodPushAppRoutes.hs +14/−0
- Setup.hs +2/−0
- push-notify-general.cabal +48/−0
+ LICENSE view
@@ -0,0 +1,23 @@+Copyright (c) 2013 Marcos Pividori++Permission is hereby granted, free of charge, to any+person obtaining a copy of this software and associated+documentation files (the "Software"), to deal in the+Software without restriction, including without limitation+the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the+Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice+shall be included in all copies or substantial portions of+the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Network/PushNotify/General.hs view
@@ -0,0 +1,116 @@+module Network.PushNotify.General+ (+ -- * The purpose of this library+ -- $purpose+ + -- * How to use this library+ -- $explanation++ -- * PushManager Subsite+ -- $subsite+ + -- * Push Service+ startPushService+ , closePushService+ , sendPush+ , withPushManager+ -- * Push Manager+ , PushManager+ -- * Push Settings+ , Device(..)+ , PushServiceConfig(..)+ , RegisterResult(..)+ , PushConfig(..)+ , GCMConfig(..)+ -- * Push Message+ , PushNotification(..)+ , generalNotif+ -- * Push Result+ , PushResult(..)+ ) where++import Network.PushNotify.General.Send+import Network.PushNotify.General.Types+import Network.PushNotify.General.YesodPushApp++-- $purpose+-- This library defines a general API for communicating with iOS,+-- Android and WPhone powered devices, sending/receiving Push Notifications.+--+-- The main idea is to hide as much as possible the differences between the different services.+--+-- Push Notification services in general, are very similar. They all are asynchronous, best-effort services+-- that offer third-party developers a channel to send data to apps from a cloud service in a power-efficient manner.+-- The main differences between them, refer to details as: the maxim payload length, the quality of service,+-- queueing the messages or not, and the time limit for this, the way the messages are handled on devices, etc.+--+-- The registration of devices is very similar too. The device/app gets a unique ID provided by the different services,+-- and sends this identifier to your 3rd party server.+-- So then, when you want to send messages to these devices, you have to send them to the Push Servers, and+-- they will deliver the messages to the proper devices.+--+-- So, 'Device' will be our general identifier.+--++-- $explanation+-- All the communication with devices will be abstracted behind the 'PushManager' data type.+--+-- You will reference to it when intending to send notifications. Also, if you properly set the callback functions,+-- you will be able to receive messages from devices.+--+-- So, the mains steps:+--+-- * First you establish the main configuration for the push service: 'PushServiceConfig' . This means, set the configurations for the+-- different Push services you want to use, and also set the callbacks functions. These functions could be used to actualize a DB,+-- do some processing in background, etc.+--+-- * Then you start the service with the 'startPushService' function and you get the 'PushManager'.+-- So, you can add this subsite to your Yesod app or just ignore it if you don't want to receive messages from devices.+--+-- * When you want to send notifications:+--+-- (1) You have to specify the 'PushNotification' , setting the parameters. Here you have to do it+-- for each kind of notification, because they contain different structures.+--+-- 2. Then, you can send these with the 'sendPush' function, passing the 'PushManager' as an argument. Also, you can get useful information+-- from the 'PushResult' value after communicating with servers.+--+-- * At the end, you stop the service with: 'closePushService'.+--+--+-- When your Apps on devices need to send an upstream message they have 2 options:+--+-- (1) Messages through CCS (only Android devices).+-- For this you have to set the proper CCS settings when starting the Push Service.+--+-- 2. Messages as HTTP requests to 'PushManager' Yesod subsite (all devices).+-- (using the route of the Yesod subsite: something like \".../messages\" )+--+-- You can see many test examples on the GitHub repository: <https://github.com/MarcosPividori/GSoC-Communicating-with-mobile-devices>++-- $subsite+-- Everytime a device sends a message to our system as HTTP POST request it should provide the pair+-- {\"regId\" : identifier , \"system\" : system } specifing the ID and the system running on the device, so we can build +-- the 'Device' identifier.+--+-- Now we support APNS, MPNS and GCM. So the possible combinations are:+--+-- { \"regId\" : 'Network.PushNotify.Gcm.RegId' , \"system\" : \"ANDROID\" }+--+-- { \"regId\" : 'Network.PushNotify.Mpns.DeviceURI' , \"system\" : \"WPHONE\" }+--+-- { \"regId\" : 'Network.PushNotify.Apns.DeviceToken' , \"system\" : \"IOS\" } (token stored in hexadecimal representation)+-- +-- This 'PushManager' Yesod subsite consists of 2 routes for receiving JSON data:+--+-- * /register -> this route will receive the registration from devices. So devices have to send the pairs+-- {\"regId\" : identifier , \"system\" : system } and any extra information. When a new intent for a registration arrives,+-- the 'newDeviceCallback' function will be called with the identifier and JSON data as arguments so you can ask for more information+-- as username and password, etc. Depending on the callback function result, the response to the POST request+-- will be set, so device can know if it has successfully registered on the server.+--+-- * /messages -> this route will receive POST messages from devices. Again, devices have to send the pairs+-- {\"regId\" : identifier , \"system\" : system } and any other extra information.+-- Everytime a new messages arrives to the Yesod subsite, the 'newMessageCallback' function will be called with the identifier+-- and JSON data as arguments.+-- This abstraction lets us use the same callback function to handle the messages that arrive from CCS too (XMPP connection for GCM).
+ Network/PushNotify/General/Send.hs view
@@ -0,0 +1,132 @@+-- | This Module define the main functions to send Push Notifications.+{-# LANGUAGE ScopedTypeVariables #-}+module Network.PushNotify.General.Send(+ startPushService+ , closePushService+ , sendPush+ , withPushManager+ ) where++import Network.PushNotify.General.Types+import Network.PushNotify.General.YesodPushApp++import Yesod+import Data.Text (Text)+import Data.Maybe+import Data.Monoid+import Data.Default+import Control.Concurrent+import Control.Monad+import Control.Exception as CE+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import Network.HTTP.Conduit+import Network.PushNotify.Gcm+import Network.PushNotify.Apns+import Network.PushNotify.Mpns+import Network.PushNotify.Ccs++-- | 'startPushService' starts the PushService creating a PushManager.+startPushService :: PushServiceConfig -> IO PushManager+startPushService pConfig = do+ let cnfg = pushConfig pConfig+ gcmflag = case gcmConfig cnfg of+ Just (Http _) -> True+ _ -> False+ httpMan <- if gcmflag || isJust (mpnsConfig cnfg)+ then do+ m <- newManager def+ return (Just m)+ else return Nothing+ apnsMan <- case apnsConfig cnfg of+ Just cnf -> do+ m <- startAPNS cnf+ return (Just m)+ Nothing -> return Nothing+ ccsMan <- case gcmConfig cnfg of+ Just (Ccs cnf) -> do+ m <- startCCS cnf (\d -> (newMessageCallback pConfig) (GCM d))+ return (Just m)+ _ -> return Nothing+ return $ PushManager httpMan apnsMan ccsMan pConfig++-- | 'closePushService' stops the Push service.+closePushService :: PushManager -> IO ()+closePushService man = do+ case httpManager man of+ Just m -> closeManager m+ _ -> return ()+ case apnsManager man of+ Just m -> closeAPNS m+ _ -> return ()+ case ccsManager man of+ Just m -> closeCCS m+ _ -> return ()++-- | 'withPushManager' creates a new manager, uses it in the provided function, and then releases it.+withPushManager :: PushServiceConfig -> (PushManager -> IO a) -> IO a+withPushManager confg fun = CE.bracket (startPushService confg) closePushService fun++forgetConst :: Device -> Text+forgetConst (GCM x) = x+forgetConst (APNS x) = x+forgetConst (MPNS x) = x++isGCM (GCM _) = True+isGCM _ = False++isAPNS (APNS _) = True+isAPNS _ = False++isMPNS (MPNS _) = True+isMPNS _ = False++-- | 'sendPush' sends messages to the appropiate Push Servers.+sendPush :: PushManager -> PushNotification -> HS.HashSet Device -> IO PushResult+sendPush man notif devices = do+ let gcmDevices = HS.map forgetConst $ HS.filter isGCM devices+ apnsDevices = HS.map forgetConst $ HS.filter isAPNS devices+ mpnsDevices = HS.map forgetConst $ HS.filter isMPNS devices+ pConfig = serviceConfig man+ config = pushConfig pConfig++ r1 <- case (HS.null gcmDevices , gcmConfig config , gcmNotif notif , httpManager man , ccsManager man) of+ (False,Just (Ccs cnf),Just msg,_,Just m) -> do+ let msg' = msg{registration_ids = gcmDevices}+ CE.catch (sendCCS' m msg')+ (\e -> let _ = e :: CE.SomeException in+ case httpManager man of+ Just hman -> sendGCM' hman def{apiKey = aPiKey cnf} msg'+ _ -> return $ exceptionResult (HM.fromList $ map (\d -> (GCM d,Right e)) $+ HS.toList $ registration_ids msg') )++ (False,Just (Http cnf),Just msg,Just m,_ ) -> sendGCM' m cnf msg{registration_ids = gcmDevices}++ _ -> return def++ r2 <- case (HS.null apnsDevices , apnsNotif notif , apnsManager man) of+ (False,Just msg,Just m) -> sendAPNS' m msg{deviceTokens = apnsDevices}+ _ -> return def++ r3 <- case (HS.null mpnsDevices , mpnsConfig config , mpnsNotif notif , httpManager man) of+ (False,Just cnf,Just msg,Just m) -> sendMPNS' m cnf msg{deviceURIs = mpnsDevices}+ _ -> return def++ let res = r1 <> r2 <> r3++ when (not $ HS.null $ unRegistered res) $ mapM_ (unRegisteredCallback pConfig) (HS.toList $ unRegistered res)++ when (not $ HM.null $ newIds res) $ mapM_ (newIdCallback pConfig) (HM.toList $ newIds res)++ return res++ where+ sendCCS' a b = sendCCS a b >>= return . toPushResult+ sendMPNS' a b c = sendMPNS a b c >>= return . toPushResult+ sendGCM' a b c = CE.catch (sendGCM a b c >>= return . toPushResult)+ (\(e :: CE.SomeException) -> return $ exceptionResult ( HM.fromList $ map (\d -> (GCM d,Right e)) $ + HS.toList $ registration_ids c))+ sendAPNS' a b = CE.catch (sendAPNS a b >>= return . toPushResult)+ (\(e :: CE.SomeException) -> return $ exceptionResult ( HM.fromList $ map (\d -> (APNS d,Right e)) $+ HS.toList $ deviceTokens b))+ exceptionResult l = PushResult HS.empty l HS.empty HS.empty HM.empty
+ Network/PushNotify/General/Types.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE OverloadedStrings , DeriveGeneric #-}+-- | This Module defines the main data types for the Push Service.+module Network.PushNotify.General.Types+ ( -- * Push Settings + Device(..)+ , PushServiceConfig(..)+ , RegisterResult(..)+ , GCMConfig(..)+ , PushConfig(..)+ -- * Push Manager+ , PushManager(..)+ -- * Push Message+ , PushNotification(..)+ , generalNotif+ -- * Push Result+ , PushResult(..)+ , IsPushResult(..)+ ) where++import Network.PushNotify.Gcm+import Network.PushNotify.Apns+import Network.PushNotify.Mpns+import Network.PushNotify.Ccs+import Network.HTTP.Conduit+import Network.HTTP.Types.Status+import Control.Exception++import qualified Data.Map as M+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as BS+import qualified Data.Text.Encoding as E+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import Data.Aeson+import Data.Default+import Data.Hashable+import Data.List+import Data.Monoid+import Data.Text (Text,pack)+import Text.XML++-- | Unique identifier of an app/device.+data Device = GCM RegId -- ^ An Android app.+ | APNS DeviceToken -- ^ An iOS app.+ | MPNS DeviceURI -- ^ A WPhone app.+ deriving (Show, Read, Eq)++instance Hashable Device where+ hashWithSalt s (GCM n) = s `hashWithSalt`+ (0::Int) `hashWithSalt` n+ hashWithSalt s (MPNS n) = s `hashWithSalt`+ (1::Int) `hashWithSalt` n+ hashWithSalt s (APNS n) = s `hashWithSalt`+ (2::Int) `hashWithSalt` n++-- | General notification to be sent.+data PushNotification = PushNotification {+ apnsNotif :: Maybe APNSmessage+ , gcmNotif :: Maybe GCMmessage+ , mpnsNotif :: Maybe MPNSmessage+ } deriving Show++instance Default PushNotification where+ def = PushNotification Nothing Nothing Nothing++-- | 'generalNotif' builds a general notification from JSON data.+--+-- If data length exceeds 256 bytes (max payload limit for APNS) it will fails.+--+-- For MPNS, data will be XML-labeled as \"jsonData\".+generalNotif :: Object -> IO PushNotification+generalNotif dat = do+ let msg = PushNotification {+ apnsNotif = Just def{ rest = Just dat}+ , gcmNotif = Just def{ data_object = Just dat}+ , mpnsNotif = Just def{ restXML = Document (Prologue [] Nothing [])+ (Element (Name "jsonData" Nothing Nothing)+ M.empty+ [NodeContent $ E.decodeUtf8 $+ BS.concat . BL.toChunks $ encode dat])+ []+ }+ }+ if ((BL.length . encode . apnsNotif) msg > 256)+ then fail "Too long payload"+ else return msg++-- | Settings for GCM service.+data GCMConfig = Http GCMHttpConfig | Ccs GCMCcsConfig++-- | Main settings for the different Push Services. @Nothing@ means the service won't be used.+data PushConfig = PushConfig{+ gcmConfig :: Maybe GCMConfig+ , apnsConfig :: Maybe APNSConfig+ , mpnsConfig :: Maybe MPNSConfig+ }++instance Default PushConfig where+ def = PushConfig Nothing Nothing Nothing++-- | 'RegisterResult' represents the result of a device attempting to register+data RegisterResult = SuccessfulReg | ErrorReg Text++-- | Main settings for the Push Service.+data PushServiceConfig = PushServiceConfig {+ pushConfig :: PushConfig -- ^ Main configuration.+ , newMessageCallback :: Device -> Value -> IO () -- ^ The callback function to be called when receiving messages from devices+ -- (This means through the CCS connection or as POST requests+ -- to the Yesod subsite).+ , newDeviceCallback :: Device -> Value -> IO RegisterResult -- ^ The callback function to be called when+ -- a new device try to register on server.+ , unRegisteredCallback :: Device -> IO () -- ^ The callback function to be called when a device unregisters.+ , newIdCallback :: (Device,Device) -> IO () -- ^ The callback function to be called when a device's identifier changes.+ }++instance Default PushServiceConfig where+ def = PushServiceConfig {+ pushConfig = def+ , newMessageCallback = \_ _ -> return ()+ , newDeviceCallback = \_ _ -> return SuccessfulReg+ , unRegisteredCallback = \_ -> return ()+ , newIdCallback = \_ -> return ()+ }++-- | Main manager for the Push Service.+--+-- This 'PushManager' will be used to send notifications and also can be added as a subsite to a Yesod app+-- in order to receive registrations and messages from devices as HTTP POST requests.+data PushManager = PushManager {+ httpManager :: Maybe Manager -- ^ Conduit manager for sending push notifications though POST requests.+ , apnsManager :: Maybe APNSManager -- ^ Apns manager for sending push notifications though APNS servers.+ , ccsManager :: Maybe CCSManager -- ^ Ccs manager for communicating with GCM through Cloud Connection Server.+ , serviceConfig :: PushServiceConfig -- ^ Main configuration.+ }++-- | PushResult represents a general result after communicating with a Push Server.+data PushResult = PushResult {+ successful :: HS.HashSet Device -- ^ Notifications that were successfully sent.+ , failed :: HM.HashMap Device (Either Text SomeException) -- ^ Notifications that were not successfully sent.+ , toResend :: HS.HashSet Device -- ^ Failed notifications that you need to resend,+ -- because servers were not available or there was a problem with the connection.+ , unRegistered :: HS.HashSet Device -- ^ Set of unregistered devices.+ , newIds :: HM.HashMap Device Device -- ^ Map of devices which have changed their identifiers. (old -> new)+ } deriving Show++instance Default PushResult where+ def = PushResult HS.empty HM.empty HS.empty HS.empty HM.empty++instance Monoid PushResult where+ mempty = def+ mappend (PushResult a1 b1 c1 d1 e1)+ (PushResult a2 b2 c2 d2 e2) = PushResult (HS.union a1 a2) (HM.union b1 b2)+ (HS.union c1 c2) (HS.union d1 d2) (HM.union e1 e2)++-- | This class represent the translation from a specific service's result into a general Push result.+class IsPushResult a where+ toPushResult :: a -> PushResult+++instance IsPushResult GCMresult where+ toPushResult r = def {+ successful = HS.map GCM $ HS.fromList $ HM.keys $ messagesIds r+ , failed = HM.fromList $ map (\(x,y) -> (GCM x,Left y)) (HM.toList $ errorRest r)+ <> map (\x -> (GCM x,Left "UnregisteredError")) (HS.toList $ errorUnRegistered r)+ <> map (\x -> (GCM x,Left "InternalError")) (HS.toList $ errorToReSend r)+ , toResend = HS.map GCM $ errorToReSend r+ , unRegistered = HS.map GCM $ errorUnRegistered r <> (HS.fromList . HM.keys . errorRest) r+ -- I decide to unregister all regId with error different to Unregistered or Unavailable. (errorRest)+ -- Because these are non-recoverable error.+ , newIds = HM.fromList $ map (\(x,y) -> (GCM x,GCM y)) $ HM.toList $ newRegids r+ }+++instance IsPushResult APNSresult where+ toPushResult r = def {+ successful = HS.map APNS $ successfulTokens r+ , failed = HM.fromList $ map (\x -> (APNS x , Left "CommunicatingError")) $ HS.toList $ toReSendTokens r+ , toResend = HS.map APNS $ toReSendTokens r+ }++instance IsPushResult APNSFeedBackresult where+ toPushResult r = def {+ unRegistered = HS.fromList $ map APNS $ HM.keys $ unRegisteredTokens r+ }+++instance IsPushResult MPNSresult where+ toPushResult r = let (successList,failureList) = partition ((== Just Received) . notificationStatus . snd ) $ + HM.toList $ successfullResults r+ in def {+ successful = HS.fromList $ map (MPNS . fst) successList+ , failed = (HM.fromList $ map (\(x,y) -> (MPNS x , Right y)) (HM.toList $ errorException r))+ <> (HM.fromList $ map (\(x,y) -> (MPNS x , Left $ pack $ show $ notificationStatus y)) failureList)+ , toResend = HS.map MPNS . HS.fromList . HM.keys . HM.filter error500 $ errorException r+ , unRegistered = HS.map MPNS . HS.fromList . HM.keys . HM.filter ((== Just Expired) . subscriptionStatus) $ successfullResults r+ } where+ error500 :: SomeException -> Bool+ error500 e = case (fromException e) :: Maybe HttpException of+ Just (StatusCodeException status _ _) -> (statusCode status) >= 500+ _ -> False
+ Network/PushNotify/General/YesodPushApp.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE OverloadedStrings, TypeFamilies, TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts, QuasiQuotes #-}+-- | This Module defines the Yesod subsite to be used for the registration and reception of messages from devices.+module Network.PushNotify.General.YesodPushApp(+ PushManager(..)+ ) where++import Network.PushNotify.General.Types+import Network.PushNotify.General.YesodPushAppRoutes+import Yesod+import Control.Concurrent+import Data.Text+import Data.Aeson+import qualified Data.HashMap.Strict as HM++instance (RenderMessage master FormMessage, Yesod master) => YesodSubDispatch PushManager (HandlerT master IO) where+ yesodSubDispatch = $(mkYesodSubDispatch resourcesPushManager)++-- 'postRegister' allows a mobile device to register. (JSON POST messages to '/register')+postSubRegisterR :: (RenderMessage master FormMessage, Yesod master) => HandlerT PushManager (HandlerT master IO) ()+postSubRegisterR = do+ value <- parseJsonBody_+ case value of+ Object v -> do+ iden <- lookForIdentifier v+ pushManager <- getYesod+ res <- liftIO $ (newDeviceCallback $ serviceConfig pushManager) iden value+ case res of+ SuccessfulReg -> sendResponse $ RepJson emptyContent -- successful registration.+ ErrorReg t -> permissionDenied t -- error in registration.+ _ -> invalidArgs []++lookForIdentifier :: Object -> HandlerT PushManager (HandlerT master IO) Device+lookForIdentifier v = do+ regId <- case (HM.lookup "regId" v) of+ Just (String s) -> return s+ _ -> invalidArgs []+ case (HM.lookup "system" v) of+ Just (String "ANDROID") -> return $ GCM regId -- A Android device.+ Just (String "WPHONE") -> return $ MPNS regId -- A WPhone device.+ Just (String "IOS") -> return $ APNS regId -- A iOS device.+ _ -> invalidArgs []++-- 'postMessages' allows a mobile device to send a message. (JSON POST messages to '/messages')+postSubMessagesR :: (RenderMessage master FormMessage, Yesod master) => HandlerT PushManager (HandlerT master IO) ()+postSubMessagesR = do+ value <- parseJsonBody_+ case value of+ Object v -> do+ iden <- lookForIdentifier v+ pushManager <- getYesod+ liftIO $ forkIO $ (newMessageCallback $ serviceConfig pushManager) iden value+ sendResponse $ RepJson emptyContent+ _ -> invalidArgs []
+ Network/PushNotify/General/YesodPushAppRoutes.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE OverloadedStrings, TypeFamilies, TemplateHaskell, QuasiQuotes #-}++module Network.PushNotify.General.YesodPushAppRoutes where++import Yesod+import Network.PushNotify.General.Types+import Control.Concurrent+import Data.Text++-- Yesod subsite to be used for the registration and reception of messages from devices.+mkYesodSubData "PushManager" [parseRoutes|+/register SubRegisterR POST+/messages SubMessagesR POST+|]
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ push-notify-general.cabal view
@@ -0,0 +1,48 @@+name: push-notify-general+version: 0.1.0.0+synopsis: A general library for sending/receiving push notif. through dif. services.++description: This library offers an API for sending/receiving notifications, and handling the registration of devices on the server.+ .+ It provides a general abstraction which can be used to communicate through different services as APNS, GCM, MPNS.+ .+ For more information and test examples: <http://gsoc2013cwithmobiledevices.blogspot.com.ar/>+ .+ GitHub repository: <https://github.com/MarcosPividori/GSoC-Communicating-with-mobile-devices>++homepage: http://gsoc2013cwithmobiledevices.blogspot.com.ar/+license: MIT+license-file: LICENSE+author: Marcos Pividori, mentored by Michael Snoyman.+maintainer: Marcos Pividori <marcos.pividori@gmail.com>++category: Network, Cloud, Mobile+build-type: Simple+cabal-version: >=1.8++library+ exposed-modules: Network.PushNotify.General++ other-modules: Network.PushNotify.General.Send+ , Network.PushNotify.General.Types+ , Network.PushNotify.General.YesodPushApp+ , Network.PushNotify.General.YesodPushAppRoutes+ + build-depends: base >=4.5 && <5+ , aeson >=0.6+ , bytestring >=0.9+ , containers >=0.4+ , data-default >=0.5+ , hashable >=1.2+ , http-conduit >=1.9+ , http-types >=0.8+ , push-notify >=0.1+ , push-notify-ccs >=0.1+ , text >=0.11+ , unordered-containers >=0.2+ , xml-conduit >=1.1+ , yesod >=1.2++source-repository head+ type: git+ location: https://github.com/MarcosPividori/GSoC-Communicating-with-mobile-devices.git