{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Network.Pusher.WebSockets.Event -- Copyright : (c) 2016 Michael Walker -- License : MIT -- Maintainer : Michael Walker -- Stability : experimental -- Portability : OverloadedStrings -- -- Functions for creating event handlers and triggering events. module Network.Pusher.WebSockets.Event ( eventType , eventChannel -- * Event Handlers , Binding , bind , bindAll , unbind -- * Client Events , triggerEvent , localEvent ) where -- 'base' imports import Data.Maybe (fromMaybe) -- library imports import Control.Concurrent.STM (atomically, readTVar) import Control.Lens ((^?), (.~), (&), ix) import Control.Monad.IO.Class (liftIO) import Data.Aeson (Value(..), decodeStrict') import Data.Aeson.Lens (_String) import qualified Data.HashMap.Strict as H import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) -- local imports import Network.Pusher.WebSockets.Internal ------------------------------------------------------------------------------- -- | Get the value of the \"event\" field. -- -- If not present (which should never happen!), returns the empty -- string. eventType :: Value -> Text eventType event = fromMaybe "" (event ^? ix "event" . _String) -- | Get the value of the \"channel\" field. -- -- This will be @Nothing@ if the event was broadcast to all clients, -- with no channel restriction. eventChannel :: Value -> Maybe Channel eventChannel event = fmap Channel (event ^? ix "channel" . _String) ------------------------------------------------------------------------------- -- | Bind an event handler to an event type, optionally restricted to a -- channel. -- -- Attempts to decode the \"data\" field of the event as stringified -- JSON; if that fails, it is left as a string. -- -- If multiple handlers match a received event, all will be -- executed. The order is unspecified, and may not be consistent. bind :: Text -- ^ Event name. -> Maybe Channel -- ^ Channel name: If @Nothing@, all events of that name are -- handled. -> (Value -> PusherClient ()) -- ^ Event handler. -> PusherClient Binding bind = bindGeneric . Just -- | Variant of 'bind' which binds to all events in the given channel; -- or all events if no channel. bindAll :: Maybe Channel -> (Value -> PusherClient ()) -> PusherClient Binding bindAll = bindGeneric Nothing -- | Internal: register a new event handler. bindGeneric :: Maybe Text -> Maybe Channel -> (Value -> PusherClient ()) -> PusherClient Binding bindGeneric event channel handler = do pusher <- ask liftIO . atomically $ do b@(Binding i) <- readTVar (nextBinding pusher) let b' = Binding (i+1) strictModifyTVar (nextBinding pusher) (const b') let h = Handler event channel wrappedHandler strictModifyTVar (eventHandlers pusher) (H.insert b h) pure b where -- Before invoking the handler, have a stab at decoding the data -- field. wrappedHandler ev@(Object o) = handler $ case H.lookup "data" o >>= attemptDecode of Just decoded -> ev & ix "data" .~ decoded Nothing -> ev wrappedHandler ev = handler ev -- Attempt to interpret as stringified JSON. attemptDecode (String s) = decodeStrict' (encodeUtf8 s) attemptDecode _ = Nothing -- | Remove a binding unbind :: Binding -> PusherClient () unbind binding = do pusher <- ask strictModifyTVarIO (eventHandlers pusher) (H.delete binding) ------------------------------------------------------------------------------- -- | Send an event with some JSON data. This does not trigger local -- event handlers. triggerEvent :: Text -> Maybe Channel -> Value -> PusherClient () triggerEvent = sendMessage SendMessage -- | Trigger local event handlers, but do not send the event over the -- network. localEvent :: Text -> Maybe Channel -> Value -> PusherClient () localEvent = sendMessage SendLocalMessage -- | Helper function for 'triggerEvent' and 'localEvent' sendMessage :: (Value -> PusherCommand) -> Text -> Maybe Channel -> Value -> PusherClient () sendMessage cmd event channel data_ = do pusher <- ask liftIO (sendCommand pusher (cmd json)) where json = Object . H.fromList $ concat [ [("event", String event)] , [("channel", String chan) | Just (Channel chan) <- [channel]] , [("data", data_)] ]