packages feed

socketio 0.1.2 → 0.1.3

raw patch · 12 files changed

+239/−209 lines, 12 filesdep +attoparsecdep +blaze-builderdep +conduitdep −parsec

Dependencies added: attoparsec, blaze-builder, conduit, conduit-extra

Dependencies removed: parsec

Files

Web/SocketIO/Connection.hs view
@@ -84,9 +84,9 @@ retrieveSession (Disconnect sessionID) = do     result <- lookupSession sessionID     return (Disconnect sessionID, split result)-retrieveSession (Emit sessionID e) = do+retrieveSession (Request sessionID e) = do     result <- lookupSession sessionID-    return (Emit sessionID e, split result)+    return (Request sessionID e, split result)  -------------------------------------------------------------------------------- -- | Big time@@ -146,19 +146,34 @@     logWithSessionID Warn sessionID "[Request] Disconnect: Session not found"      return MsgNoop -handleConnection (Emit sessionID _, Just (session, Connecting)) = do+handleConnection (Request sessionID _, Just (session, Connecting)) = do     extendTimeout session-    logWithSessionID Warn sessionID "[Request] Emit: Session still connecting, not ACKed" +    logWithSessionID Warn sessionID "[Request] Request: Session still connecting, not ACKed"      return $ MsgError NoEndpoint NoData -handleConnection (Emit sessionID event@(Event eventName (Payload payloads)), Just (session, Connected)) = do-    logWithSessionID Debug sessionID $ "[Request] Emit: " <> serialize eventName <> " " <> serialize payloads++handleConnection (Request sessionID (MsgEvent _ _ event@(Event eventName (Payload payloads))), Just (session, Connected)) = do+    logWithSessionID Debug sessionID $ "[Request] Request: MsgEvent " <> serialize eventName <> " " <> serialize payloads     runSession (SessionEmit event) session -handleConnection (Emit sessionID NoEvent, Just (_, Connected)) = do-    logWithSessionID Warn sessionID "[Request] Emit: event malformed"+handleConnection (Request sessionID (MsgEvent _ _ NoEvent), Just (_, Connected)) = do+    logWithSessionID Warn sessionID "[Request] Request: MsgEvent malformed"     return $ MsgError NoEndpoint NoData -handleConnection (Emit sessionID _, Nothing) = do-    logWithSessionID Warn sessionID "[Request] Emit: Session not found" +handleConnection (Request sessionID message, Just (_, Connected)) = do+    logWithSessionID Debug sessionID $ "[Request] Request: " <> serialize message+    return MsgNoop+    --runSession (SessionEmit event) session+++--handleConnection (Request sessionID event@(Event eventName (Payload payloads)), Just (session, Connected)) = do+--    logWithSessionID Debug sessionID $ "[Request] Request: " <> serialize eventName <> " " <> serialize payloads+--    runSession (SessionEmit event) session++--handleConnection (Request sessionID NoEvent, Just (_, Connected)) = do+--    logWithSessionID Warn sessionID "[Request] Request: malformed"+--    return $ MsgError NoEndpoint NoData++handleConnection (Request sessionID _, Nothing) = do+    logWithSessionID Warn sessionID "[Request] Request: Session not found"      return $ MsgError NoEndpoint NoData
Web/SocketIO/Event.hs view
@@ -10,12 +10,13 @@ -------------------------------------------------------------------------------- import              Control.Applicative ((<$>)) import              Control.Monad.Reader+import qualified    Data.Aeson                          as Aeson import qualified    Data.ByteString.Lazy                as BL  {-# DEPRECATED reply "use msg instead" #-} -------------------------------------------------------------------------------- -- | This function is deprecated; use 'msg' instead-reply :: CallbackM [Text]+reply :: CallbackM [Aeson.Value] reply = do     Payload p <- callbackEnvPayload <$> ask     return p@@ -29,7 +30,7 @@ --     liftIO $ print payload --     emit "echo" payload  -- @-msg :: CallbackM [Text]+msg :: CallbackM [Aeson.Value] msg = do     Payload p <- callbackEnvPayload <$> ask     return p
Web/SocketIO/Log.hs view
@@ -2,7 +2,7 @@ -- | Exports some logging utilities. {-# LANGUAGE OverloadedStrings #-} -module Web.SocketIO.Log ((<>), logRaw, logWithSession, logWithSessionID) where+module Web.SocketIO.Log ((<>), logRaw, logWithSession, logWithSessionID, showStatusBar) where  -------------------------------------------------------------------------------- import Web.SocketIO.Types@@ -36,3 +36,8 @@ logWithSession logType message = do     Session sessionID _ _ _ _ <- getSession     logRaw logType $ fromByteString (sessionID <> "    " <> message)++--------------------------------------------------------------------------------+-- | Show status bar+showStatusBar :: IO ()+showStatusBar = return ()
Web/SocketIO/Protocol.hs view
@@ -2,7 +2,11 @@ -- | Socket.IO Protocol 1.0 {-# LANGUAGE OverloadedStrings #-} -module Web.SocketIO.Protocol (parseFramedMessage, parsePath) where+module Web.SocketIO.Protocol +    (   demultiplexMessage+    ,   parseFramedMessage+    ,   parsePath+    ) where  -------------------------------------------------------------------------------- import              Web.SocketIO.Types@@ -10,146 +14,135 @@ -------------------------------------------------------------------------------- import              Control.Applicative                     ((<$>), (<*>)) import              Data.Aeson-import qualified    Data.ByteString.Lazy                    as BL-import              Text.Parsec-import              Text.Parsec.ByteString.Lazy------------------------------------------------------------------------------------- | Parse raw ByteString to Messages-parseFramedMessage :: BL.ByteString -> Framed Message-parseFramedMessage input = if isSingleton-    then Framed $ [parseMessage' input]-    else Framed $ map parseMessage' splitted-    where   splitted = split input-            parseMessage' x = case parse parseMessage "" x of-                Left _  -> MsgNoop-                Right a -> a-            isSingleton = not (BL.null input) && BL.head input /= 239------------------------------------------------------------------------------------ | Split raw ByteString with U+FFFD as delimiter-split :: BL.ByteString -> [BL.ByteString]-split str = map (BL.drop 2) . skipOddIndexed True . filter isDelimiter . BL.split 239 $ str-    where   isDelimiter x = not (BL.null x)-                             && (BL.head x == 191)-                             && (BL.head (BL.tail x) == 189)-            skipOddIndexed _ [] = []-            skipOddIndexed True (_:xs) = skipOddIndexed False xs-            skipOddIndexed False (x:xs) = x : skipOddIndexed True xs------------------------------------------------------------------------------------- | Message, not framed-parseMessage :: Parser Message-parseMessage = do-    n <- digit-    case n of-        '0' ->  (parseID >> parseEndpoint >>= return . MsgDisconnect)-            <|> (                  return $ MsgDisconnect NoEndpoint)-        '1' ->  (parseID >> parseEndpoint >>= return . MsgConnect)-            <|> (                  return $ MsgConnect NoEndpoint)-        '2' ->  return MsgHeartbeat-        '3' ->  parseRegularMessage Msg-        '4' ->  parseRegularMessage MsgJSON-        '5' ->  MsgEvent    <$> parseID -                            <*> parseEndpoint -                            <*> parseEvent-        '6' ->  try (do -                string ":::"-                n' <- read <$> number-                char '+'-                d <- fromString <$> text-                return $ MsgACK (ID n') (Data d)-            ) <|> (do-                string ":::"-                n' <- read <$> number-                return $ MsgACK (ID n') NoData-            )-        '7' -> colon >> MsgError <$> parseEndpoint <*> parseData-        '8' ->  return $ MsgNoop-        _   ->  return $ MsgNoop-    where   parseRegularMessage ctr = ctr <$> parseID -                                          <*> parseEndpoint -                                          <*> parseData-----------------------------------------------------------------------------------endpoint :: Parser String-endpoint = many1 $ satisfy (/= ':')-----------------------------------------------------------------------------------number :: Parser String-number = many1 digit+import qualified    Data.ByteString                         as B+import              Data.Conduit+import              Data.Conduit.Attoparsec                 (conduitParserEither)+import              Data.Attoparsec.ByteString.Lazy+import              Data.Attoparsec.ByteString.Char8        (digit, decimal)+import              Prelude                                 hiding (take, takeWhile)  ---------------------------------------------------------------------------------colon :: Parser Char-colon = char ':'+-- | Demultiplexing messages+demultiplexMessage :: Conduit ByteString IO Message+demultiplexMessage = do+    conduitParserEither framedMessageParser =$= awaitForever go+    where   go (Left s) = error $ show s+            go (Right (_, p)) = mapM yield p ----------------------------------------------------------------------------------parseID :: Parser ID-parseID  =  try (colon >> number >>= plus >>= return . IDPlus . read)-        <|> try (colon >> number          >>= return . ID . read)-        <|>     (colon >>                     return   NoID)-        where   plus n = char '+' >> return n +----------------------------------------------------------------------------------+---- | Using U+FFFD as delimiter+frameParser :: Parser a -> Parser a+frameParser parser = do+    string "�"+    len <- decimal+    string "�"+    x <- take len+    case parseOnly parser x of+        Left e  -> error e+        Right r -> return r  ---------------------------------------------------------------------------------parseEndpoint :: Parser Endpoint-parseEndpoint    =  try (colon >> fromString <$> endpoint >>= return . Endpoint)-                <|>     (colon >>                             return   NoEndpoint)+-- | Message, framed with List+framedMessageParser :: Parser [Message]+framedMessageParser = choice [many1 (frameParser messageParser), many' messageParser]  ---------------------------------------------------------------------------------parseData :: Parser Data-parseData    =  try (colon >> text >>= return . Data . fromString)-            <|>     (colon >>      return   NoData)+-- | Wrapped for testing+parseFramedMessage :: ByteString -> Framed Message+parseFramedMessage input = case parseOnly framedMessageParser input of+    Left e -> error e+    Right r -> Framed r  ---------------------------------------------------------------------------------parseEvent :: Parser Event-parseEvent = try (do-                colon-                t <- text-                case decode (fromString t) of-                    Just e -> return e-                    Nothing -> return NoEvent-            )-            <|>     (colon >>          return   NoEvent)-+-- | Message, not framed+messageParser :: Parser Message+messageParser = do+    n <- digit+    case n of+        '0' -> choice+            [   idParser >> endpointParser >>= return . MsgDisconnect+            ,                                  return $ MsgDisconnect NoEndpoint+            ]+        '1' -> choice+            [   idParser >> endpointParser >>= return . MsgConnect+            ,                                  return $ MsgConnect NoEndpoint +            ]+        '2' -> return MsgHeartbeat+        '3' -> Msg          <$> idParser +                            <*> endpointParser +                            <*> dataParser+        '4' -> MsgJSON      <$> idParser +                            <*> endpointParser +                            <*> dataParser+        '5' -> MsgEvent     <$> idParser +                            <*> endpointParser +                            <*> eventParser+        '6' -> choice+            [   do  string ":::"+                    d <- decimal+                    string "+"+                    x <- takeWhile (const True)+                    +                    return $ MsgACK (ID d) (if B.null x then NoData else Data x)+                    +            ,   do  string ":::"+                    d <- decimal+                    return $ MsgACK (ID d) NoData+            ]+        '7' -> string ":" >> MsgError <$> endpointParser <*> dataParser+        '8' -> return MsgNoop+        _   -> return MsgNoop ------------------------------------------------------------------------------------ | Slashes as delimiters-textWithoutSlash :: Parser String-textWithoutSlash = many1 $ satisfy (/= '/')+idParser :: Parser ID+idParser = choice+    [   string ":" >> decimal >>= plus >>= return . IDPlus+    ,   string ":" >> decimal          >>= return . ID+    ,   string ":" >>                      return   NoID+    ]+    where   plus n = string "+" >> return n -slash :: Parser Char-slash = char '/'+endpointParser :: Parser Endpoint+endpointParser = do+    string ":"+    option NoEndpoint (takeWhile1 (/= 58) >>= return . Endpoint) ------------------------------------------------------------------------------------ | Non-empty text-text :: Parser String-text = many1 anyChar+dataParser :: Parser Data+dataParser = do+    string ":"+    option NoData (takeWhile1 (/= 58) >>= return . Data) ---------------------------------------------------------------------------------parseTransport :: Parser Transport-parseTransport = try (string "websocket" >> return WebSocket) -        <|> (string "xhr-polling" >> return XHRPolling)-        <|> (string "unknown" >> return NoTransport)-        <|> return NoTransport+eventParser :: Parser Event+eventParser = do+    string ":"+    t <- takeWhile (const True)+    case decode (serialize t) of+        Just e  -> return e+        Nothing -> return NoEvent ---------------------------------------------------------------------------------               --- | With wrapper+------------------------------------------------------------------------------               +-- | Parse given HTTP request parsePath :: ByteString -> Path-parsePath path = case parse parsePath' "" (fromByteString path) of+parsePath p = case parseOnly pathParser p of     Left _  -> WithoutSession "" ""     Right x -> x  ------------------------------------------------------------------------------------ | Raw Parsec combinator, without wrapper-parsePath' :: Parser Path-parsePath' = do-    slash-    namespace <- fromString <$> textWithoutSlash-    slash-    protocol <- fromString <$> textWithoutSlash-    slash-    try (do-        transport <- parseTransport-        slash-        sessionID <- fromString <$> textWithoutSlash+pathParser :: Parser Path+pathParser = do+    string "/"+    namespace <- takeTill (== 47) -- 0x47: slash+    take 1  -- slip the second slash+    protocol <- takeTill (== 47)+    take 1  -- slip the third slash+    option (WithoutSession namespace protocol) $ do+        transport <- transportParser+        string "/"+        sessionID <- takeTill (== 47)         return $ WithSession namespace protocol transport sessionID-        ) <|> (return $ WithoutSession namespace protocol)++transportParser :: Parser Transport+transportParser = choice+    [   string "websocket"      >> return WebSocket+    ,   string "xhr-polling"    >> return XHRPolling+    ,   string "unknown"        >> return NoTransport+    ,   skipWhile (/= 47)       >> return NoTransport+    ]
Web/SocketIO/Request.hs view
@@ -1,54 +1,74 @@ ----------------------------------------------------------------------------------- | Converts HTTP requests to Socket.IO requests+-- | Converts HTTP requests to Socket.IO requests and run them {-# LANGUAGE OverloadedStrings #-}-module Web.SocketIO.Request (parseHTTPRequest) where +module Web.SocketIO.Request (sourceHTTPRequest, runRequest) where+ -------------------------------------------------------------------------------- import              Web.SocketIO.Types import              Web.SocketIO.Protocol  ---------------------------------------------------------------------------------import              Control.Applicative                     ((<$>))   -import qualified    Network.Wai                             as Wai-import              Network.HTTP.Types                      (Method)+import              Blaze.ByteString.Builder        (Builder)+import qualified    Blaze.ByteString.Builder        as Builder+import qualified    Data.ByteString                 as B+import              Data.Conduit+import qualified    Data.Conduit.List               as CL+import qualified    Network.Wai                     as Wai  ----------------------------------------------------------------------------------- | Information of a HTTP reqeust we need-type RequestInfo = (Method, Path, Framed Message)+-- | Run!+runRequest :: (Request -> IO Message) -> Conduit Request IO (Flush Builder)+runRequest runner = CL.mapM runner =$= serializeMessage =$= toFlushBuilder  ----------------------------------------------------------------------------------- | Extracts from HTTP reqeusts-retrieveRequestInfo :: Wai.Request -> IO RequestInfo-retrieveRequestInfo request = do--    framedMessages <- parseHTTPBody request-+-- | Extracts HTTP requests+sourceHTTPRequest :: Wai.Request -> Source IO Request+sourceHTTPRequest request = do     let path = parsePath (Wai.rawPathInfo request)+    let method = Wai.requestMethod request -    return -        (   Wai.requestMethod request-        ,   path -        ,   framedMessages-        )+    case (method, path) of+        ("GET", (WithoutSession _ _)) -> yield Handshake+        ("GET", (WithSession _ _ _ sessionID)) -> yield (Connect sessionID)+        ("POST", (WithSession _ _ _ sessionID)) -> Wai.requestBody request $= demultiplexMessage =$= awaitForever (yield . Request sessionID)+        (_, (WithSession _ _ _ sessionID)) -> yield (Disconnect sessionID)+        _ -> error "error handling http request"  ----------------------------------------------------------------------------------- | Converts to SocketIO request-processRequestInfo :: RequestInfo -> [Request]-processRequestInfo ("GET" , (WithoutSession _ _)         , _              ) = [Handshake]-processRequestInfo ("GET" , (WithSession _ _ _ sessionID), _              ) = [Connect sessionID]-processRequestInfo ("POST", (WithSession _ _ _ sessionID), Framed messages) = requests-    where   requests = foldr extractEvent [] messages-            extractEvent (MsgEvent _ _ event) acc = Emit sessionID event : acc-            extractEvent _                    acc = acc-processRequestInfo (_     , (WithSession _ _ _ sessionID), _              ) = [Disconnect sessionID]-processRequestInfo _    = error "error parsing http request"- ------------------------------------------------------------------------------------ | The request part-parseHTTPRequest :: Wai.Request -> IO [Request]-parseHTTPRequest request = fmap processRequestInfo (retrieveRequestInfo request)+-- | Serialize Messages, frame when necessary.+serializeMessage :: Conduit Message IO ByteString+serializeMessage = toByteString 0+    where   toByteString :: Int -> Conduit Message IO ByteString+            toByteString i = do+                m <- await+                n <- await+                case (m, n) of+                    -- []+                    (Nothing, Nothing) -> yield (serialize (Framed [] :: Framed Message))+                    -- [m'], singleton+                    (Just m', Nothing) -> if i == 0+                        then yield (serialize m') -- true singleton+                        else yield (frame m') -- just a recursion base case +                    -- WTF+                    (Nothing, Just _ ) -> return ()+                    -- [m', n'], frame m', leftover n'+                    (Just m', Just n') -> do+                        yield (frame m')+                        leftover n'+                        toByteString (i + 1)+                        +            frame b = "�" <> serialize size <> "�" <> b'+                where   b' = serialize b+                        size = B.length b'  ----------------------------------------------------------------------------------- | The message part-parseHTTPBody :: Wai.Request -> IO (Framed Message)-parseHTTPBody req = parseFramedMessage <$> Wai.lazyRequestBody req+-- | Convert Framed Message to Flush Builder so that `Wai.responseSource` can consume it+toFlushBuilder :: Conduit ByteString IO (Flush Builder)+toFlushBuilder = do +    b <- await+    case b of+        Just b' -> do+            yield $ Chunk (Builder.fromByteString b')+            toFlushBuilder+        Nothing -> yield $ Flush
Web/SocketIO/Server.hs view
@@ -16,7 +16,8 @@  -------------------------------------------------------------------------------- import              Control.Monad.Trans             (liftIO)-import              Network.HTTP.Types              (Status, status200, status403)+import              Data.Conduit+import              Network.HTTP.Types              (status200) import              Network.HTTP.Types.Header       (ResponseHeaders) import qualified    Network.Wai                     as Wai import qualified    Network.Wai.Handler.Warp        as Warp@@ -45,6 +46,7 @@     -- run it with Warp     Warp.run port (httpApp vorspann (runConnection env)) + -------------------------------------------------------------------------------- -- | Wrapped as a HTTP app httpApp :: ResponseHeaders -> (Request -> IO Message) -> Wai.Application@@ -53,9 +55,10 @@     let origin = lookupOrigin httpRequest     let headerFields' = insertOrigin headerFields origin -    reqs <- parseHTTPRequest httpRequest-    mapM runConnection' reqs >>= waiResponse headerFields' +    let sourceBody = sourceHTTPRequest httpRequest $= runRequest runConnection' +    return $ Wai.responseSource status200 headerFields' sourceBody+     where   lookupOrigin req = case lookup "Origin" (Wai.requestHeaders req) of                 Just origin -> origin                 Nothing     -> "*"@@ -100,18 +103,3 @@     ,   heartbeatInterval = 25     ,   pollingDuration = 20 }------------------------------------------------------------------------------------- | Make Wai response-waiResponse :: Monad m => ResponseHeaders -> [Message] -> m Wai.Response-waiResponse vorspann messages = do-    return . Wai.responseLBS status vorspann . serialize . Framed $ messages-    where   status = if status403 `elem` (map toHTTPStatus messages)-                        then status403-                        else status200------------------------------------------------------------------------------------- | Maps SocketIO respond message to HTTP status code-toHTTPStatus :: Message -> Status-toHTTPStatus (MsgError _ _) = status403-toHTTPStatus _ = status200
Web/SocketIO/Session.hs view
@@ -30,10 +30,10 @@                 heartbeatTimeout'                 (closeTimeout configuration)                 (transports configuration)-      handleSession SessionConnect = do     logWithSession Info $ "Connected"+    triggerEvent (Event "connection" (Payload []))     return $ MsgConnect NoEndpoint  handleSession SessionPolling = do
Web/SocketIO/Types/Base.hs view
@@ -19,6 +19,7 @@ import              Control.Monad.Reader        import              Control.Monad.Writer import              Control.Monad.Base+import qualified    Data.Aeson                              as Aeson import qualified    Data.HashMap.Strict                     as H import              Data.IORef.Lifted import              Network.HTTP.Types.Header               (ResponseHeaders)@@ -153,7 +154,7 @@     -- `emit` \"launch\" [\"missile\", \"nuke\"]      -- @     emit    :: EventName            -- ^ name of event to trigger-            -> [Text]               -- ^ payload to carry with+            -> [Aeson.Value]        -- ^ payload to carry with             -> m ()      -- | Sends a message to everybody except for the socket that starts it.@@ -162,7 +163,7 @@     -- `broadcast` \"hide\" [\"nukes coming!\"]      -- @     broadcast   :: EventName        -- ^ name of event to trigger-                -> [Text]           -- ^ payload to carry with+                -> [Aeson.Value]    -- ^ payload to carry with                 -> m ()      -- | 
Web/SocketIO/Types/Event.hs view
@@ -18,10 +18,7 @@  ---------------------------------------------------------------------------------- import              Control.Applicative-import              Data.Aeson                              -import              Data.Aeson.Encode               (encodeToTextBuilder)-import              Data.List                       (intersperse)                              -import              Data.Text.Internal.Builder      (toLazyText)+import              Data.Aeson                      as Aeson import qualified    Data.Text.Lazy                  as TL import              Data.Vector                     (toList) --------------------------------------------------------------------------------@@ -30,10 +27,10 @@  -------------------------------------------------------------------------------- -- | Payload carried by an Event-data Payload = Payload [Text] deriving (Eq, Show)+data Payload = Payload [Aeson.Value] deriving (Eq, Show)  instance Serializable Payload where-    serialize (Payload payload) = serialize $ '[' `TL.cons` (TL.concat $ intersperse "," payload) `TL.snoc` ']'+    serialize (Payload payload) = serialize $ Aeson.encode payload  -------------------------------------------------------------------------------- -- | Event@@ -52,7 +49,7 @@                           (toArgumentList <$> v .:? "args")         where   toArgumentList :: Maybe Value -> Payload                 toArgumentList Nothing          = Payload []-                toArgumentList (Just (Array a)) = Payload $ filter (/= "null") . map (toLazyText . encodeToTextBuilder) . toList $ a+                toArgumentList (Just (Array a)) = Payload $ filter (/= Aeson.Null) . toList $ a                 toArgumentList _                = Payload []     parseJSON _ = return NoEvent
Web/SocketIO/Types/Request.hs view
@@ -40,11 +40,11 @@                                    <> "/"  ----------------------------------------------------------------------------------- | Incoming HTTP request+-- | Incoming request data Request    = Handshake                 | Disconnect SessionID                 | Connect SessionID -                | Emit SessionID Event+                | Request SessionID Message                 deriving (Show)  --------------------------------------------------------------------------------@@ -56,7 +56,7 @@     serialize (Framed [message]) = serialize message     serialize (Framed messages) = mconcat $ map frame messages         where   frame message = let serialized = serialize message  -                                in "�" <> serialize size <> "�" <> serialized+                                in "�" <> serialize size <> "�" <> serialized                                 where   size = B.length (serialize message)  --------------------------------------------------------------------------------
Web/SocketIO/Types/String.hs view
@@ -19,6 +19,7 @@   --------------------------------------------------------------------------------+import qualified    Data.Aeson                              as Aeson import qualified    Data.String                             as S import qualified    Data.Text                               as T import qualified    Data.Text.Encoding                      as TE@@ -155,6 +156,9 @@                  , Show a) => a -> s     serialize = S.fromString . show +instance Serializable Aeson.Value where+    serialize = fromLazyByteString . Aeson.encode+ instance Serializable T.Text where     serialize = fromText @@ -163,7 +167,7 @@  instance Serializable ByteString where     serialize = fromByteString-    + instance Serializable BL.ByteString where     serialize = fromLazyByteString 
socketio.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:               socketio-version:            0.1.2+version:            0.1.3 synopsis:           Socket.IO server description:      Socket.IO for Haskell folks.@@ -69,8 +69,11 @@     build-depends:    base                  >=4.6   && <5.0                     , text                  >=1.1   && <2.0                     , bytestring            >=0.10  && <1.0+                    , blaze-builder         >=0.3   && <1.0                     , aeson                 >=0.7   && <1.0-                    , parsec                >=3.1   && <4.0+                    , conduit               >=1.1   && <2.0+                    , conduit-extra         >=1.1   && <2.0+                    , attoparsec            >=0.11  && <1.0                     , ansi-terminal         >=0.6   && <1.0                     , unordered-containers  >=0.2   && <1.0                     , random                >=1.0   && <2.0@@ -99,8 +102,11 @@     build-depends:    base                  >=4.6   && <5.0                     , text                  >=1.1   && <2.0                     , bytestring            >=0.10  && <1.0+                    , blaze-builder         >=0.3   && <1.0                     , aeson                 >=0.7   && <1.0-                    , parsec                >=3.1   && <4.0+                    , conduit               >=1.1   && <2.0+                    , conduit-extra         >=1.1   && <2.0+                    , attoparsec            >=0.11  && <1.0                     , ansi-terminal         >=0.6   && <1.0                     , unordered-containers  >=0.2   && <1.0                     , random                >=1.0   && <2.0