Add an example of Websockets with ArrayBuffer.
parent
4aa473f1f0
commit
ef623b8f1c
|
@ -0,0 +1,189 @@
|
|||
module Main where
|
||||
|
||||
import Prelude
|
||||
|
||||
import Control.Monad.Except (runExcept)
|
||||
import Data.Array as Array
|
||||
import Data.ArrayBuffer.DataView as DataView
|
||||
import Data.ArrayBuffer.Types (ArrayBuffer, ArrayView, DataView, Int32)
|
||||
import Data.Bifunctor (lmap)
|
||||
import Data.Either (Either(Left, Right))
|
||||
import Data.Maybe (Maybe(Just))
|
||||
import Data.String as String
|
||||
import Data.Traversable (traverse_)
|
||||
import Effect (Effect)
|
||||
import Effect.Class.Console as Console
|
||||
import Foreign (Foreign)
|
||||
import Foreign as F
|
||||
import Halogen.Subscription as HS
|
||||
import Web.Event.Event (Event)
|
||||
import Web.Event.Event as Event
|
||||
import Web.Event.EventTarget (EventTarget)
|
||||
import Web.Event.EventTarget as EventTarget
|
||||
import Web.Socket.BinaryType (BinaryType(ArrayBuffer))
|
||||
import Web.Socket.Event.CloseEvent (CloseEvent)
|
||||
import Web.Socket.Event.CloseEvent as CloseEvent
|
||||
import Web.Socket.Event.EventTypes as EventTypes
|
||||
import Web.Socket.Event.MessageEvent as MessageEvent
|
||||
import Web.Socket.WebSocket (WebSocket)
|
||||
import Web.Socket.WebSocket as WS
|
||||
|
||||
import Data.ArrayBuffer.Builder (execPut, putUint32be)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Types
|
||||
|
||||
data WebSocketEvent
|
||||
= WSMessage Foreign
|
||||
| WSClose CloseEvent
|
||||
| WSOpen Event
|
||||
| WSError Event
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Program entrypoint
|
||||
|
||||
main :: Effect Unit
|
||||
main = do
|
||||
Console.log "1. Let's start the application!"
|
||||
websocketProgram "ws://127.0.0.1:8080"
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Here is where you edit in what you want to happen in this program
|
||||
-- in response to each type of `WebSocketEvent`.
|
||||
|
||||
websocketProgram :: String -> Effect Unit
|
||||
websocketProgram url = do
|
||||
|
||||
Console.log "2. Let's start the application!"
|
||||
webSocket <- WS.create url []
|
||||
|
||||
void $ HS.subscribe (webSocketEmitter webSocket) case _ of
|
||||
|
||||
WSOpen _event -> do
|
||||
Console.log "Open event"
|
||||
WS.setBinaryType webSocket ArrayBuffer
|
||||
-- Console.log "WS set to binary, let's send something!"
|
||||
-- sendArrayBuffer webSocket
|
||||
|
||||
WSMessage foreign' -> do
|
||||
Console.log "Message event"
|
||||
case foreignToDataView foreign' of
|
||||
Left errs ->
|
||||
Console.error errs
|
||||
Right dataView -> do
|
||||
Console.log "Do something with this DataView!"
|
||||
let
|
||||
byteOffset = 0
|
||||
number = DataView.getInt32be dataView byteOffset
|
||||
Console.log "Received number is: " <> show number
|
||||
-- DataView.getInt32be dataView byteOffset >>= traverse_ \int32 ->
|
||||
-- Console.log $ mkMessage byteOffset int32
|
||||
|
||||
WSClose _closeEvent -> do
|
||||
Console.log "Close event"
|
||||
|
||||
WSError _event -> do
|
||||
Console.log "Error event"
|
||||
|
||||
mkMessage :: Int -> Int -> String
|
||||
mkMessage byteOffset i32 =
|
||||
"The Int32 at ByteOffset " <> show byteOffset <> " was " <> show i32
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- A unified emitter (of the 4 websocket event types) that we subscribe to once
|
||||
-- as you see in `webSocketProgram` above.
|
||||
webSocketEmitter
|
||||
:: WebSocket
|
||||
-> HS.Emitter WebSocketEvent
|
||||
webSocketEmitter webSocket =
|
||||
HS.makeEmitter \pushWsEvent -> do
|
||||
messageId <- HS.subscribe messageEmitter (pushWsEvent <<< WSMessage)
|
||||
closeId <- HS.subscribe closeEmitter (pushWsEvent <<< WSClose)
|
||||
openId <- HS.subscribe openEmitter (pushWsEvent <<< WSOpen)
|
||||
errorId <- HS.subscribe errorEmitter (pushWsEvent <<< WSError)
|
||||
|
||||
pure do
|
||||
HS.unsubscribe messageId
|
||||
HS.unsubscribe closeId
|
||||
HS.unsubscribe openId
|
||||
HS.unsubscribe errorId
|
||||
|
||||
where
|
||||
target :: EventTarget
|
||||
target = WS.toEventTarget webSocket
|
||||
|
||||
fromMessageEvent :: Event -> Maybe Foreign
|
||||
fromMessageEvent event = do
|
||||
messageEvent <- MessageEvent.fromEvent event
|
||||
pure $ MessageEvent.data_ messageEvent
|
||||
|
||||
messageEmitter = mkEventListener EventTypes.onMessage target fromMessageEvent
|
||||
closeEmitter = mkEventListener EventTypes.onClose target CloseEvent.fromEvent
|
||||
openEmitter = mkEventListener EventTypes.onOpen target Just
|
||||
errorEmitter = mkEventListener EventTypes.onError target Just
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- If the websocket is sending you back array buffers, this might be used to
|
||||
-- assist you in decoding those array buffers from the MessageEvent's data.
|
||||
|
||||
foreignToArrayBuffer :: Foreign -> Either String ArrayBuffer
|
||||
foreignToArrayBuffer
|
||||
= lmap renderForeignErrors
|
||||
<<< runExcept
|
||||
<<< F.unsafeReadTagged "ArrayBuffer"
|
||||
where
|
||||
renderForeignErrors :: F.MultipleErrors -> String
|
||||
renderForeignErrors =
|
||||
String.joinWith "; " <<< Array.fromFoldable <<< map F.renderForeignError
|
||||
|
||||
arrayBufferToDataView :: ArrayBuffer -> DataView
|
||||
arrayBufferToDataView = DataView.whole
|
||||
|
||||
foreignToDataView :: Foreign -> Either String DataView
|
||||
foreignToDataView = map arrayBufferToDataView <<< foreignToArrayBuffer
|
||||
|
||||
{-
|
||||
|
||||
From monoidmusician's link to the `Data.ArrayBuffer.DataView` module:
|
||||
|
||||
-- Fetch big-endian int32 value at a certain index in a DataView.
|
||||
DataView.getInt32be :: DataView -> ByteOffset -> Effect (Maybe Int)
|
||||
|
||||
-- Store big-endian int32 value at a certain index in a DataView.
|
||||
DataView.setInt32be :: DataView -> ByteOffset -> Int -> Effect Boolean
|
||||
|
||||
Using `DataView.getInt32be` on the `DataView` obtained above should be like
|
||||
this JS example in the MDN docs:
|
||||
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType#examples
|
||||
|
||||
-}
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- The module `Web.Socket.WebSocket` in the `purescript-web-socket` package does
|
||||
-- have these two functions below if you want to send just an `ArrayBuffer` or
|
||||
-- `ArrayView` alone.
|
||||
|
||||
sendArrayBuffer :: WebSocket -> ArrayBuffer -> Effect Unit
|
||||
sendArrayBuffer = WS.sendArrayBuffer
|
||||
|
||||
sendArrayBufferView :: WebSocket -> ArrayView Int32 -> Effect Unit
|
||||
sendArrayBufferView = WS.sendArrayBufferView
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Taken directly from `Halogen.Query.Event.eventListener`. I find it's nicer
|
||||
-- to use and re-use this (...than working directly with `addEventListener`,
|
||||
-- `removeEventListener`, etc. each time).
|
||||
mkEventListener
|
||||
:: forall a
|
||||
. Event.EventType
|
||||
-> EventTarget.EventTarget
|
||||
-> (Event.Event -> Maybe a)
|
||||
-> HS.Emitter a
|
||||
mkEventListener eventType target f =
|
||||
HS.makeEmitter \push -> do
|
||||
listener <- EventTarget.eventListener \ev -> traverse_ push (f ev)
|
||||
EventTarget.addEventListener eventType listener false target
|
||||
pure do
|
||||
EventTarget.removeEventListener eventType listener false target
|
Loading…
Reference in New Issue