{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
-- |
-- Module : Network.Pusher
-- Description : Haskell interface to the Pusher Channels HTTP API
-- Copyright : (c) Will Sewell, 2016
-- Licence : MIT
-- Maintainer : me@willsewell.com
-- Stability : stable
--
-- Exposes the functions necessary for interacting with the Pusher Channels HTTP
-- API, as well as functions for generating auth signatures for private and
-- presence channels.
--
-- First create a 'Settings'. The easiest way of doing this is by using
-- 'defaultSettings'. From that you can use 'newPusher' to create a 'Pusher'
-- instance and then call the functions defined in this module to make the HTTP
-- requests.
--
-- If any of the requests fail, the return values of the functions will result in
-- a 'Left' 'PusherError' when run.
--
-- An example of how you would use these functions:
--
-- @
-- let
-- settings =
-- 'defaultSettings'
-- { 'pusherAddress' = 'Cluster' "mt1",
-- 'pusherAppID' = 123,
-- 'pusherToken' = 'Token' "wrd12344rcd234" "124df34d545v"
-- }
-- pusher <- 'newPusher' settings
--
-- result <-
-- 'trigger' pusher ["my-channel"] "my-event" "my-data" Nothing
--
-- case result of
-- Left e -> putStrLn $ displayException e
-- Right resp -> print resp
--
-- @
--
-- There are simple working examples in the example/ directory.
--
-- See https://pusher.com/docs/channels/server_api/http-api for more detail on the
-- HTTP requests.
module Network.Pusher
( -- * Data types
-- ** Settings
Settings (..),
defaultSettings,
Token (..),
Address (..),
-- ** Main Pusher type
Pusher,
newPusher,
newPusherWithConnManager,
-- * HTTP Requests
-- ** Trigger events
trigger,
-- ** Channel queries
channels,
channel,
users,
-- * Authentication
authenticatePresence,
authenticatePrivate,
-- * Errors
PusherError (..),
-- * Webhooks
parseWebhookPayload,
WebhookEv (..),
WebhookPayload (..),
Webhooks (..),
parseAppKeyHdr,
parseAuthSignatureHdr,
parseWebhooksBody,
verifyWebhooksBody,
parseWebhookPayloadWith,
)
where
import Control.Monad.IO.Class
( MonadIO,
liftIO,
)
import qualified Data.Aeson as A
import qualified Data.ByteString as B
import qualified Data.Text as T
import Network.Pusher.Data
( Address (..),
Pusher (..),
Settings (..),
Token (..),
defaultSettings,
newPusher,
newPusherWithConnManager,
)
import Network.Pusher.Error (PusherError (..))
import qualified Network.Pusher.Internal as Pusher
import qualified Network.Pusher.Internal.Auth as Auth
import qualified Network.Pusher.Internal.HTTP as HTTP
import Network.Pusher.Internal.Util (getSystemTimeSeconds)
import Network.Pusher.Protocol
( ChannelInfoQuery,
ChannelsInfo,
ChannelsInfoQuery,
FullChannelInfo,
Users,
)
import Network.Pusher.Webhook
( WebhookEv (..),
WebhookPayload (..),
Webhooks (..),
parseAppKeyHdr,
parseAuthSignatureHdr,
parseWebhookPayloadWith,
parseWebhooksBody,
verifyWebhooksBody,
)
-- | Trigger an event to one or more channels.
trigger ::
MonadIO m =>
Pusher ->
-- | The list of channels to trigger to.
[T.Text] ->
-- | Event name.
T.Text ->
-- | Event data. Often encoded JSON.
T.Text ->
-- | An optional socket ID of a connection you wish to exclude.
Maybe T.Text ->
m (Either PusherError ())
trigger pusher chans event dat socketId = do
(requestParams, requestBody) <-
Pusher.mkTriggerRequest pusher chans event dat socketId
<$> getSystemTimeSeconds
liftIO $ HTTP.post (pConnectionManager pusher) requestParams requestBody
-- | Query a list of channels for information.
channels ::
MonadIO m =>
Pusher ->
-- | A channel prefix you wish to filter on.
T.Text ->
-- | Data you wish to query for, currently just the user count.
ChannelsInfoQuery ->
-- | The returned data.
m (Either PusherError ChannelsInfo)
channels pusher prefixFilter attributes = do
requestParams <-
Pusher.mkChannelsRequest pusher prefixFilter attributes
<$> getSystemTimeSeconds
liftIO $ HTTP.get (pConnectionManager pusher) requestParams
-- | Query for information on a single channel.
channel ::
MonadIO m =>
Pusher ->
B.ByteString ->
-- | Can query user count and also subscription count (if enabled).
ChannelInfoQuery ->
m (Either PusherError FullChannelInfo)
channel pusher chan attributes = do
requestParams <-
Pusher.mkChannelRequest pusher chan attributes <$> getSystemTimeSeconds
liftIO $ HTTP.get (pConnectionManager pusher) requestParams
-- | Get a list of users in a presence channel.
users :: MonadIO m => Pusher -> B.ByteString -> m (Either PusherError Users)
users pusher chan = do
requestParams <- Pusher.mkUsersRequest pusher chan <$> getSystemTimeSeconds
liftIO $ HTTP.get (pConnectionManager pusher) requestParams
-- | Generate an auth signature of the form "app_key:auth_sig" for a user of a
-- private channel.
authenticatePrivate :: Pusher -> T.Text -> T.Text -> B.ByteString
authenticatePrivate pusher = Auth.authenticatePrivate (pToken pusher)
-- | Generate an auth signature of the form "app_key:auth_sig" for a user of a
-- presence channel.
authenticatePresence ::
A.ToJSON a => Pusher -> T.Text -> T.Text -> a -> B.ByteString
authenticatePresence pusher = Auth.authenticatePresence (pToken pusher)
-- | Parse webhooks from a list of HTTP headers and a HTTP body given their
-- app key matches the one in our Pusher Channels credentials and the webhook
-- is correctly encrypted by the corresponding app secret.
parseWebhookPayload ::
Pusher ->
[(B.ByteString, B.ByteString)] ->
B.ByteString ->
Maybe WebhookPayload
parseWebhookPayload pusher =
let token = pToken pusher
ourAppKey = tokenKey token
ourAppSecret = tokenSecret token
lookupKeysSecret whAppKey =
if whAppKey == ourAppKey then Just ourAppSecret else Nothing
in parseWebhookPayloadWith lookupKeysSecret