{-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE JavaScriptFFI #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE InterruptibleFFI #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE UnliftedFFITypes #-} {-# LANGUAGE GHCForeignImportPrim #-} {-# LANGUAGE UnboxedTuples #-} module JavaScript.Web.WebSocket ( WebSocket , WebSocketRequest(..) , ReadyState(..) , BinaryType(..) , connect , close , send , sendArrayBuffer , sendBlob , getBufferedAmount , getExtensions , getProtocol , getReadyState , getBinaryType , setBinaryType , getUrl ) where import GHCJS.Concurrent import GHCJS.Prim import GHCJS.Foreign.Callback.Internal (Callback(..)) import qualified GHCJS.Foreign.Callback as CB import GHC.Exts import Control.Exception import Control.Monad import Data.Data import Data.Maybe import Data.Typeable import System.IO import Data.JSString (JSString) import qualified Data.JSString as JSS import JavaScript.Array (JSArray) import qualified JavaScript.Array as JSA import JavaScript.TypedArray.ArrayBuffer (ArrayBuffer) import JavaScript.Web.Blob (Blob) import JavaScript.Web.MessageEvent import JavaScript.Web.MessageEvent.Internal import JavaScript.Web.CloseEvent import JavaScript.Web.CloseEvent.Internal import JavaScript.Web.ErrorEvent import JavaScript.Web.ErrorEvent.Internal import Unsafe.Coerce data WebSocketRequest = WebSocketRequest { url :: JSString , protocols :: [JSString] , onClose :: Maybe (CloseEvent -> IO ()) -- ^ called when the connection closes (at most once) , onMessage :: Maybe (MessageEvent -> IO ()) -- ^ called for each message } newtype WebSocket = WebSocket JSVal -- instance IsJSVal WebSocket data ReadyState = Connecting | Open | Closing | Closed deriving (Data, Typeable, Enum, Eq, Ord, Show) data BinaryType = Blob | ArrayBuffer deriving (Data, Typeable, Enum, Eq, Ord, Show) {- | create a WebSocket -} connect :: WebSocketRequest -> IO WebSocket connect req = do mcb <- maybeCallback MessageEvent (onMessage req) ccb <- maybeCallback CloseEvent (onClose req) withoutPreemption $ do ws <- case protocols req of [] -> js_createDefault (url req) [x] -> js_createStr (url req) x (js_open ws mcb ccb >>= handleOpenErr >> return ws) `onException` js_close 1000 "Haskell Exception" ws maybeCallback :: (JSVal -> a) -> Maybe (a -> IO ()) -> IO JSVal maybeCallback _ Nothing = return jsNull maybeCallback f (Just g) = do Callback cb <- CB.syncCallback1 CB.ContinueAsync (g . f) return cb handleOpenErr :: JSVal -> IO () handleOpenErr r | isNull r = return () | otherwise = throwIO (userError "WebSocket failed to connect") -- fixme {- | close a websocket and release the callbacks -} close :: Maybe Int -> Maybe JSString -> WebSocket -> IO () close value reason ws = js_close (fromMaybe 1000 value) (fromMaybe JSS.empty reason) ws {-# INLINE close #-} send :: JSString -> WebSocket -> IO () send xs ws = js_send xs ws {-# INLINE send #-} sendBlob :: Blob -> WebSocket -> IO () sendBlob = js_sendBlob {-# INLINE sendBlob #-} sendArrayBuffer :: ArrayBuffer -> WebSocket -> IO () sendArrayBuffer = js_sendArrayBuffer {-# INLINE sendArrayBuffer #-} getBufferedAmount :: WebSocket -> IO Int getBufferedAmount ws = js_getBufferedAmount ws {-# INLINE getBufferedAmount #-} getExtensions :: WebSocket -> IO JSString getExtensions ws = js_getExtensions ws {-# INLINE getExtensions #-} getProtocol :: WebSocket -> IO JSString getProtocol ws = js_getProtocol ws {-# INLINE getProtocol #-} getReadyState :: WebSocket -> IO ReadyState getReadyState ws = fmap toEnum (js_getReadyState ws) {-# INLINE getReadyState #-} getBinaryType :: WebSocket -> IO BinaryType getBinaryType ws = fmap toEnum (js_getBinaryType ws) {-# INLINE getBinaryType #-} getUrl :: WebSocket -> JSString getUrl ws = js_getUrl ws {-# INLINE getUrl #-} getLastError :: WebSocket -> IO (Maybe ErrorEvent) getLastError ws = do le <- js_getLastError ws return $ if isNull le then Nothing else Just (ErrorEvent le) {-# INLINE getLastError #-} setBinaryType :: BinaryType -> WebSocket -> IO () setBinaryType Blob = js_setBinaryType (JSS.pack "blob") setBinaryType ArrayBuffer = js_setBinaryType (JSS.pack "arraybuffer") -- ----------------------------------------------------------------------------- foreign import javascript safe "new WebSocket($1)" js_createDefault :: JSString -> IO WebSocket foreign import javascript safe "new WebSocket($1, $2)" js_createStr :: JSString -> JSString -> IO WebSocket foreign import javascript safe "new WebSocket($1, $2)" js_createArr :: JSString -> JSArray -> IO WebSocket foreign import javascript interruptible "h$openWebSocket($1, $2, $3, $c);" js_open :: WebSocket -> JSVal -> JSVal -> IO JSVal foreign import javascript safe "h$closeWebSocket($1, $2, $3);" js_close :: Int -> JSString -> WebSocket -> IO () foreign import javascript unsafe "$2.send($1);" js_send :: JSString -> WebSocket -> IO () foreign import javascript unsafe "$2.send($1);" js_sendBlob :: Blob -> WebSocket -> IO () foreign import javascript unsafe "$2.send($1);" js_sendArrayBuffer :: ArrayBuffer -> WebSocket -> IO () foreign import javascript unsafe "$1.bufferedAmount" js_getBufferedAmount :: WebSocket -> IO Int foreign import javascript unsafe "$1.readyState" js_getReadyState :: WebSocket -> IO Int foreign import javascript unsafe "$1.protocol" js_getProtocol :: WebSocket -> IO JSString foreign import javascript unsafe "$1.extensions" js_getExtensions :: WebSocket -> IO JSString foreign import javascript unsafe "$1.url" js_getUrl :: WebSocket -> JSString foreign import javascript unsafe "$1.binaryType === 'blob' ? 0 : 1" js_getBinaryType :: WebSocket -> IO Int foreign import javascript unsafe "$1.lastError" js_getLastError :: WebSocket -> IO JSVal foreign import javascript unsafe "$2.binaryType = $1" js_setBinaryType :: JSString -> WebSocket -> IO ()